Merge branch 'next'
This commit is contained in:
commit
96128c9cfb
19
CMDMODE
19
CMDMODE
|
@ -2,7 +2,8 @@
|
|||
- Command mode
|
||||
---------------------
|
||||
|
||||
This is the grammar for the command mode (your configuration file uses these commands, too).
|
||||
This is the grammar for the 'command mode' (your configuration file
|
||||
uses these commands, too).
|
||||
|
||||
left := <h> | <cursor-left>
|
||||
right := <l> | <cursor-right>
|
||||
|
@ -17,15 +18,17 @@ cmd := [ <times> ] [ <move> | <snap> ] <where>
|
|||
with := <w> { [ <times> ] <where> }+ <space> <cmd>
|
||||
jump := [ "<window class>[/<window title>]" | <workspace> [ <column> <row> ] ]
|
||||
focus := focus [ <times> | floating | tiling | ft ]
|
||||
(travels the focus stack backwards the given amount of times (by default 1), so
|
||||
it selects the window which had the focus before you focused the current one when
|
||||
specifying "focus 1".
|
||||
The special values 'floating' (select the next floating window), 'tiling'
|
||||
(select the next tiling window), 'ft' (if the current window is floating,
|
||||
select the next tiling window and vice-versa) are also valid)
|
||||
(travels the focus stack backwards, <times> number of times (by default 1).
|
||||
So by specifying "focus 1" it selects the window which last had the focus
|
||||
before you focused the current one window.
|
||||
The following 3 special values are also valid:
|
||||
'floating' (select the next floating window).
|
||||
'tiling' (select the next tiling window).
|
||||
'ft' (toggle tiling/floating: if the current window is floating,
|
||||
select the next tiling window and vice-versa)
|
||||
special := [ exec <path> | kill | exit | restart ]
|
||||
|
||||
input := [ <cmd> | <with> | <jump> | <focus> | <special> ]
|
||||
input := [ <cmd> | <with> | <jump> | <focus> | <special> ]
|
||||
|
||||
you can cancel command mode by pressing escape anytime.
|
||||
|
||||
|
|
9
DEPENDS
9
DEPENDS
|
@ -1,12 +1,14 @@
|
|||
You need the following libraries. The version given is to be understand as the minimum
|
||||
version. However, if any of these libraries changes the API, i3 may not compile anymore.
|
||||
In that case, please try using the versions mentioned below until a fix is provided.
|
||||
You need the following libraries. The version given is to be understood as the
|
||||
minimum version required. However, if any of these libraries changes the API,
|
||||
i3 may not compile anymore. In that case, please try using the versions
|
||||
mentioned below until a fix is provided.
|
||||
|
||||
* xcb-proto-1.3 (2008-12-10)
|
||||
* libxcb-1.1.93 (2008-12-11)
|
||||
* xcb-util-0.3.3 (2009-01-31)
|
||||
* libev
|
||||
* flex and bison
|
||||
* yajl (the IPC interface uses JSON to serialize data)
|
||||
* asciidoc >= 8.3.0 for docs/hacking-howto
|
||||
* asciidoc, xmlto, docbook-xml for man/i3.man
|
||||
* Xlib, the one that comes with your X-Server
|
||||
|
@ -24,6 +26,7 @@ http://xcb.freedesktop.org/dist/xcb-util-0.3.5.tar.bz2
|
|||
http://libev.schmorp.de/
|
||||
http://flex.sourceforge.net/
|
||||
http://www.gnu.org/software/bison/
|
||||
http://lloyd.github.com/yajl/
|
||||
|
||||
http://i3.zekjur.net/i3lock/
|
||||
http://tools.suckless.org/dmenu
|
||||
|
|
55
Makefile
55
Makefile
|
@ -6,12 +6,22 @@ include $(TOPDIR)/common.mk
|
|||
AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c
|
||||
FILES:=$(filter-out $(AUTOGENERATED),$(wildcard src/*.c))
|
||||
FILES:=$(FILES:.c=.o)
|
||||
HEADERS=$(wildcard include/*.h)
|
||||
HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h))
|
||||
|
||||
# Recursively generate loglevels.h by explicitly calling make
|
||||
# We need this step because we need to ensure that loglevels.h will be
|
||||
# updated if necessary, but we also want to save rebuilds of the object
|
||||
# files, so we cannot let the object files depend on loglevels.h.
|
||||
ifeq ($(MAKECMDGOALS),loglevels.h)
|
||||
UNUSED:=$(warning Generating loglevels.h)
|
||||
else
|
||||
UNUSED:=$(shell $(MAKE) loglevels.h)
|
||||
endif
|
||||
|
||||
# Depend on the specific file (.c for each .o) and on all headers
|
||||
src/%.o: src/%.c ${HEADERS}
|
||||
echo "CC $<"
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
$(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/$(shell basename $< .c)/ { print NR }' loglevels.tmp))" -c -o $@ $<
|
||||
|
||||
all: src/cfgparse.y.o src/cfgparse.yy.o ${FILES}
|
||||
echo "LINK i3"
|
||||
|
@ -22,25 +32,40 @@ all: src/cfgparse.y.o src/cfgparse.yy.o ${FILES}
|
|||
echo "SUBDIR i3-input"
|
||||
$(MAKE) TOPDIR=$(TOPDIR) -C i3-input
|
||||
|
||||
src/cfgparse.yy.o: src/cfgparse.l
|
||||
loglevels.h:
|
||||
echo "LOGLEVELS"
|
||||
for file in $$(ls src/*.c src/*.y src/*.l | grep -v 'cfgparse.\(tab\|yy\).c'); \
|
||||
do \
|
||||
echo $$(basename $$file .c); \
|
||||
done > loglevels.tmp
|
||||
(echo "char *loglevels[] = {"; for file in $$(cat loglevels.tmp); \
|
||||
do \
|
||||
echo "\"$$file\", "; \
|
||||
done; \
|
||||
echo "};") > include/loglevels.h;
|
||||
|
||||
src/cfgparse.yy.o: src/cfgparse.l src/cfgparse.y.o ${HEADERS}
|
||||
echo "LEX $<"
|
||||
flex -i -o$(@:.o=.c) $<
|
||||
$(CC) $(CFLAGS) -c -o $@ $(@:.o=.c)
|
||||
$(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c)
|
||||
|
||||
src/cfgparse.y.o: src/cfgparse.y
|
||||
src/cfgparse.y.o: src/cfgparse.y ${HEADERS}
|
||||
echo "YACC $<"
|
||||
bison --debug --verbose -b $(basename $< .y) -d $<
|
||||
$(CC) $(CFLAGS) -c -o $@ $(<:.y=.tab.c)
|
||||
$(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c)
|
||||
|
||||
install: all
|
||||
echo "INSTALL"
|
||||
$(INSTALL) -d -m 0755 $(DESTDIR)/usr/bin
|
||||
$(INSTALL) -d -m 0755 $(DESTDIR)/etc/i3
|
||||
$(INSTALL) -d -m 0755 $(DESTDIR)/usr/share/xsessions
|
||||
$(INSTALL) -m 0755 i3 $(DESTDIR)/usr/bin/
|
||||
test -e $(DESTDIR)/etc/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)/etc/i3/config
|
||||
$(INSTALL) -m 0644 i3.welcome $(DESTDIR)/etc/i3/welcome
|
||||
$(INSTALL) -m 0644 i3.desktop $(DESTDIR)/usr/share/xsessions/
|
||||
$(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
|
||||
$(INSTALL) -d -m 0755 $(DESTDIR)$(SYSCONFDIR)/i3
|
||||
$(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/include/i3
|
||||
$(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/xsessions
|
||||
$(INSTALL) -m 0755 i3 $(DESTDIR)$(PREFIX)/bin/
|
||||
$(INSTALL) -m 0755 i3-wsbar $(DESTDIR)$(PREFIX)/bin/
|
||||
test -e $(DESTDIR)$(SYSCONFDIR)/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)$(SYSCONFDIR)/i3/config
|
||||
$(INSTALL) -m 0644 i3.welcome $(DESTDIR)$(SYSCONFDIR)/i3/welcome
|
||||
$(INSTALL) -m 0644 i3.desktop $(DESTDIR)$(PREFIX)/share/xsessions/
|
||||
$(INSTALL) -m 0644 include/i3/ipc.h $(DESTDIR)$(PREFIX)/include/i3/
|
||||
$(MAKE) TOPDIR=$(TOPDIR) -C i3-msg install
|
||||
$(MAKE) TOPDIR=$(TOPDIR) -C i3-input install
|
||||
|
||||
|
@ -48,7 +73,7 @@ dist: distclean
|
|||
[ ! -d i3-${VERSION} ] || rm -rf i3-${VERSION}
|
||||
[ ! -e i3-${VERSION}.tar.bz2 ] || rm i3-${VERSION}.tar.bz2
|
||||
mkdir i3-${VERSION}
|
||||
cp DEPENDS GOALS LICENSE PACKAGE-MAINTAINER TODO RELEASE-NOTES-${VERSION} i3.config i3.desktop i3.welcome pseudo-doc.doxygen Makefile i3-${VERSION}
|
||||
cp DEPENDS GOALS LICENSE PACKAGE-MAINTAINER TODO RELEASE-NOTES-${VERSION} i3.config i3.desktop i3.welcome i3-wsbar pseudo-doc.doxygen Makefile i3-${VERSION}
|
||||
cp -r src i3-msg include man i3-${VERSION}
|
||||
# Only copy toplevel documentation (important stuff)
|
||||
mkdir i3-${VERSION}/docs
|
||||
|
@ -64,7 +89,7 @@ dist: distclean
|
|||
rm -rf i3-${VERSION}
|
||||
|
||||
clean:
|
||||
rm -f src/*.o src/cfgparse.tab.{c,h} src/cfgparse.yy.c
|
||||
rm -f src/*.o src/cfgparse.tab.{c,h} src/cfgparse.output src/cfgparse.yy.c loglevels.tmp include/loglevels.h
|
||||
$(MAKE) -C docs clean
|
||||
$(MAKE) -C man clean
|
||||
$(MAKE) TOPDIR=$(TOPDIR) -C i3-msg clean
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
Dear package maintainer,
|
||||
|
||||
thanks for packaging i3. By doing so, you are improving your distribution and i3 in general.
|
||||
thanks for packaging i3. By doing so, you are improving your distribution
|
||||
and i3 in general.
|
||||
|
||||
Please read the file DEPENDS now, so you know which libraries are necessary and where to
|
||||
get them from if your distribution does not already have packages for them.
|
||||
Please read the file DEPENDS now, so you know which libraries are necessary
|
||||
and where to get them from if your distribution does not already have
|
||||
packages for them.
|
||||
|
||||
Please make sure the manpage for i3 will be properly created and installed
|
||||
in your package.
|
||||
|
||||
Please make sure the manpage for i3 will be properly created and installed in your package.
|
||||
On debian, this looks like this:
|
||||
|
||||
# Compilation
|
||||
|
@ -17,10 +21,11 @@ On debian, this looks like this:
|
|||
mkdir -p $(CURDIR)/debian/i3-wm/usr/share/man/man1
|
||||
cp man/i3.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
|
||||
|
||||
If you got any questions, ideas, hints, problems or whatever, please do not
|
||||
hesitate to contact me. I will help you out. Just drop me an E-Mail (find the address at
|
||||
http://michael.stapelberg.de/Kontakt, scroll down to bottom), contact me using the same
|
||||
address in jabber or ask on our IRC channel (#i3 on irc.twice-irc.de).
|
||||
If you have any questions, ideas, hints, problems or whatever, please do not
|
||||
hesitate to contact me. I will help you out. Just drop me an E-Mail (find the
|
||||
address at http://michael.stapelberg.de/Kontakt, scroll down to bottom),
|
||||
contact me using the same address in jabber or ask on our IRC channel:
|
||||
(#i3 on irc.twice-irc.de).
|
||||
|
||||
Thanks again for your efforts,
|
||||
Michael
|
||||
|
|
15
common.mk
15
common.mk
|
@ -1,13 +1,21 @@
|
|||
UNAME=$(shell uname)
|
||||
DEBUG=1
|
||||
INSTALL=install
|
||||
GIT_VERSION:=$(shell git describe --tags --always)
|
||||
PREFIX=/usr
|
||||
ifeq ($(PREFIX),/usr)
|
||||
SYSCONFDIR=/etc
|
||||
else
|
||||
SYSCONFDIR=$(PREFIX)/etc
|
||||
endif
|
||||
GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1))"
|
||||
VERSION:=$(shell git describe --tags --abbrev=0)
|
||||
|
||||
CFLAGS += -std=c99
|
||||
CFLAGS += -pipe
|
||||
CFLAGS += -Wall
|
||||
CFLAGS += -Wunused
|
||||
# unused-function, unused-label, unused-variable are turned on by -Wall
|
||||
# We don’t want unused-parameter because of the use of many callbacks
|
||||
CFLAGS += -Wunused-value
|
||||
CFLAGS += -Iinclude
|
||||
CFLAGS += -I/usr/local/include
|
||||
CFLAGS += -DI3_VERSION=\"${GIT_VERSION}\"
|
||||
|
@ -36,7 +44,9 @@ LDFLAGS += -lxcb-atom
|
|||
LDFLAGS += -lxcb-aux
|
||||
LDFLAGS += -lxcb-icccm
|
||||
LDFLAGS += -lxcb-xinerama
|
||||
LDFLAGS += -lxcb-randr
|
||||
LDFLAGS += -lxcb
|
||||
LDFLAGS += -lyajl
|
||||
LDFLAGS += -lX11
|
||||
LDFLAGS += -lev
|
||||
LDFLAGS += -L/usr/local/lib -L/usr/pkg/lib
|
||||
|
@ -48,7 +58,6 @@ LDFLAGS += -Wl,-rpath,/usr/local/lib -Wl,-rpath,/usr/pkg/lib
|
|||
endif
|
||||
|
||||
ifeq ($(UNAME),OpenBSD)
|
||||
CFLAGS += -ftrampolines
|
||||
CFLAGS += -I${X11BASE}/include
|
||||
LDFLAGS += -liconv
|
||||
LDFLAGS += -L${X11BASE}/lib
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
i3-wm (3.e-0) unstable; urgency=low
|
||||
|
||||
* NOT YET RELEASED
|
||||
|
||||
-- Michael Stapelberg <michael@stapelberg.de> Mon, 21 Dec 2009 23:08:01 +0100
|
||||
|
||||
i3-wm (3.d-bf1-1) unstable; urgency=low
|
||||
|
||||
* Bugfix: Don’t draw window title when titlebar is disabled
|
||||
|
|
|
@ -3,13 +3,12 @@ Section: utils
|
|||
Priority: extra
|
||||
Maintainer: Michael Stapelberg <michael@stapelberg.de>
|
||||
DM-Upload-Allowed: yes
|
||||
Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison
|
||||
Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev, perl
|
||||
Standards-Version: 3.8.3
|
||||
Homepage: http://i3.zekjur.net/
|
||||
|
||||
Package: i3
|
||||
Architecture: any
|
||||
Priority: extra
|
||||
Section: x11
|
||||
Depends: i3-wm, ${misc:Depends}
|
||||
Recommends: i3lock, dwm-tools, i3status
|
||||
|
@ -22,25 +21,23 @@ Description: metapackage (i3 window manager, screen locker, menu, statusbar)
|
|||
|
||||
Package: i3-wm
|
||||
Architecture: any
|
||||
Priority: extra
|
||||
Section: x11
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}, x11-utils
|
||||
Provides: x-window-manager
|
||||
Suggests: rxvt-unicode | x-terminal-emulator
|
||||
Recommends: xfonts-base
|
||||
Description: an improved dynamic tiling window manager
|
||||
Key features of i3 are correct implementation of Xinerama (workspaces are
|
||||
Key features of i3 are good support of multi-monitor setups (workspaces are
|
||||
assigned to virtual screens, i3 does the right thing when attaching new
|
||||
monitors), XrandR support (not done yet), horizontal and vertical columns
|
||||
(think of a table) in tiling. Also, special focus is on writing clean,
|
||||
readable and well documented code. i3 uses xcb for asynchronous
|
||||
communication with X11, and has several measures to be very fast.
|
||||
monitors), XRandR support, horizontal and vertical columns (think of a table)
|
||||
in tiling. Also, special focus is on writing clean, readable and well
|
||||
documented code. i3 uses XCB for asynchronous communication with X11, and has
|
||||
several measures to be very fast.
|
||||
.
|
||||
Please be aware i3 is primarily targeted at advanced users and developers.
|
||||
|
||||
Package: i3-wm-dbg
|
||||
Architecture: any
|
||||
Priority: extra
|
||||
Section: debug
|
||||
Depends: i3-wm (=${binary:Version}), ${misc:Depends}
|
||||
Description: Debugging symbols for the i3 window manager
|
||||
|
|
|
@ -8,3 +8,5 @@ docs/two_columns.png
|
|||
docs/two_terminals.png
|
||||
docs/modes.png
|
||||
docs/stacklimit.png
|
||||
docs/ipc.html
|
||||
docs/multi-monitor.html
|
||||
|
|
|
@ -45,6 +45,7 @@ install: build
|
|||
cp man/i3.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
|
||||
cp man/i3-msg.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
|
||||
cp man/i3-input.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
|
||||
cp man/i3-wsbar.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1
|
||||
|
||||
|
||||
# Build architecture-independent files here.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
all: hacking-howto.html debugging.html userguide.html
|
||||
all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html
|
||||
|
||||
hacking-howto.html: hacking-howto
|
||||
asciidoc -a toc -n $<
|
||||
|
@ -10,6 +10,12 @@ debugging.html: debugging
|
|||
userguide.html: userguide
|
||||
asciidoc -a toc -n $<
|
||||
|
||||
ipc.html: ipc
|
||||
asciidoc -a toc -n $<
|
||||
|
||||
multi-monitor.html: multi-monitor
|
||||
asciidoc -a toc -n $<
|
||||
|
||||
clean:
|
||||
rm -f */*.{aux,log,toc,bm,pdf,dvi}
|
||||
rm -f *.log *.html
|
||||
|
|
|
@ -1,54 +1,58 @@
|
|||
Debugging i3: How To
|
||||
==================
|
||||
====================
|
||||
Michael Stapelberg <michael+i3@stapelberg.de>
|
||||
April 2009
|
||||
March 2010
|
||||
|
||||
This document describes how to debug i3 suitably for sending us useful bug reports, even
|
||||
if you have no clue of C programming.
|
||||
This document describes how to debug i3 suitably for sending us useful bug
|
||||
reports, even if you have no clue of C programming.
|
||||
|
||||
First of all: Thank you for being interested in debugging i3. It really means something
|
||||
to us to get your bug fixed. If you have any questions about the debugging and/or need
|
||||
further help, do not hesitate to contact us!
|
||||
First of all: Thank you for being interested in debugging i3. It really means
|
||||
something to us to get your bug fixed. If you have any questions about the
|
||||
debugging and/or need further help, do not hesitate to contact us!
|
||||
|
||||
== Enabling logging
|
||||
|
||||
i3 spits out much information onto stdout. To have a clearly defined place where logfiles
|
||||
will be saved, you should redirect stdout and stderr in xsession. While you’re at it,
|
||||
putting each run of i3 in a separate logfile with date/time in it is a good idea to not
|
||||
get confused about the different logfiles later on.
|
||||
i3 spits out much information onto stdout, if told so. To have a clearly
|
||||
defined place where log files will be saved, you should redirect stdout and
|
||||
stderr in xsession. While you’re at it, putting each run of i3 in a separate
|
||||
log file with date/time in it is a good idea to not get confused about the
|
||||
different log files later on.
|
||||
|
||||
--------------------------------------------------------------------
|
||||
exec /usr/bin/i3 >/home/michael/i3/i3log-$(date +'%F-%k-%M-%S') 2>&1
|
||||
exec /usr/bin/i3 -V -d all >/home/michael/i3/i3log-$(date +'%F-%k-%M-%S') 2>&1
|
||||
--------------------------------------------------------------------
|
||||
|
||||
== Enabling coredumps
|
||||
== Enabling core dumps
|
||||
|
||||
When i3 crashes, often you have the chance of getting a coredump (an image of the memory
|
||||
of the i3 process which can be loaded into a debugger). To get a core-dump, you have to
|
||||
make sure that the user limit for core dump files is set high enough. Many systems ship
|
||||
with a default value which even forbids core dumps completely. To disable the limit
|
||||
completely and thus enable coredumps, use the following command (in your .xsession, before
|
||||
starting i3):
|
||||
When i3 crashes, often you have the chance of getting a 'core dump' (an image
|
||||
of the memory of the i3 process which can be loaded into a debugger). To get a
|
||||
core dump, you have to make sure that the user limit for core dump files is set
|
||||
high enough. Many systems ship with a default value which even forbids core
|
||||
dumps completely. To disable the limit completely and thus enable core dumps,
|
||||
use the following command (in your .xsession, before starting i3):
|
||||
|
||||
-------------------
|
||||
ulimit -c unlimited
|
||||
-------------------
|
||||
|
||||
Furthermore, to easily recognize core dumps and allow multiple of them, you should set
|
||||
a custom core dump filename pattern, using a command like the following:
|
||||
Furthermore, to easily recognize core dumps and allow multiple of them, you
|
||||
should set a custom core dump filename pattern, using a command like the
|
||||
following:
|
||||
|
||||
---------------------------------------------
|
||||
sudo sysctl -w kernel.core_pattern=core.%e.%p
|
||||
---------------------------------------------
|
||||
|
||||
This will generate files which have the executable’s file name (%e) and the process id
|
||||
(%p) in it. You can save this setting across reboots using +/etc/sysctl.conf+.
|
||||
This will generate files which have the executable’s file name (%e) and the
|
||||
process id (%p) in it. You can save this setting across reboots using
|
||||
+/etc/sysctl.conf+.
|
||||
|
||||
== Compiling with debug symbols
|
||||
|
||||
To actually get useful coredumps, you should make sure that your version of i3 is compiled
|
||||
with debug symbols, that is, that they are not stripped during the build process. You
|
||||
can check whether your executable contains symbols by issuing the following command:
|
||||
To actually get useful core dumps, you should make sure that your version of i3
|
||||
is compiled with debug symbols, that is, that they are not stripped during the
|
||||
build process. You can check whether your executable contains symbols by
|
||||
issuing the following command:
|
||||
|
||||
----------------
|
||||
file $(which i3)
|
||||
|
@ -60,23 +64,23 @@ You should get an output like this:
|
|||
linked (uses shared libs), for GNU/Linux 2.6.18, not stripped
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
Notice the +not stripped+, which is the important part. If you have a version which is
|
||||
stripped, please have a look if your distribution provides debug symbols (package +i3-wm-dbg+
|
||||
on Debian for example) or if you can turn off stripping. If nothing helps, please build
|
||||
i3 from source.
|
||||
Notice the +not stripped+, which is the important part. If you have a version
|
||||
which is stripped, please have a look if your distribution provides debug
|
||||
symbols (package +i3-wm-dbg+ on Debian for example) or if you can turn off
|
||||
stripping. If nothing helps, please build i3 from source.
|
||||
|
||||
== Generating a backtrace
|
||||
|
||||
Once you have made sure that your i3 is compiled with debug symbols and that coredumps
|
||||
are enabled, you can start getting some sense out of the coredumps.
|
||||
Once you have made sure that your i3 is compiled with debug symbols and that
|
||||
core dumps are enabled, you can start making sense out of the core dumps.
|
||||
|
||||
Because the coredump depends on the original executable (and its debug symbols), please
|
||||
do this as soon as you encounter the problem. If you re-compile i3, your coredump might
|
||||
be useless afterwards.
|
||||
Because the core dump depends on the original executable (and its debug
|
||||
symbols), please do this as soon as you encounter the problem. If you
|
||||
re-compile i3, your core dump might be useless afterwards.
|
||||
|
||||
Please install +gdb+, a debugger for C. No worries, you don’t need to learn it now.
|
||||
Start gdb using the following command (replacing the actual name of the coredump of
|
||||
course):
|
||||
Please install +gdb+, a debugger for C. No worries, you don’t need to learn it
|
||||
now. Start gdb using the following command (replacing the actual name of the
|
||||
core dump of course):
|
||||
|
||||
----------------------------
|
||||
gdb $(which i3) core.i3.3849
|
||||
|
@ -88,11 +92,13 @@ Then, generate a backtrace using:
|
|||
backtrace full
|
||||
--------------
|
||||
|
||||
== Sending bugreports/debugging on IRC
|
||||
== Sending bug reports/debugging on IRC
|
||||
|
||||
When sending bugreports, please paste the relevant part of the log (if in doubt, please send us rather
|
||||
too much information than too less) and the whole backtrace (if there was a coredump).
|
||||
When sending bug reports, please paste the relevant part of the log (if in
|
||||
doubt, please send us rather too much information than too less) and the whole
|
||||
backtrace (if there was a core dump).
|
||||
|
||||
When debugging with us in IRC, be prepared to use a so called nopaste service such as http://nopaste.info
|
||||
because pasting large amounts of text in IRC sometimes leads to incomplete lines (servers have line
|
||||
When debugging with us in IRC, be prepared to use a so called nopaste service
|
||||
such as http://nopaste.info or http://pastebin.com because pasting large
|
||||
amounts of text in IRC sometimes leads to incomplete lines (servers have line
|
||||
length limitations) or flood kicks.
|
||||
|
|
|
@ -1,72 +1,81 @@
|
|||
Hacking i3: How To
|
||||
==================
|
||||
Michael Stapelberg <michael+i3@stapelberg.de>
|
||||
May 2009
|
||||
December 2009
|
||||
|
||||
This document is intended to be the first thing you read before looking and/or touching
|
||||
i3’s source code. It should contain all important information to help you understand
|
||||
why things are like they are. If it does not mention something you find necessary, please
|
||||
do not hesitate to contact me.
|
||||
This document is intended to be the first thing you read before looking and/or
|
||||
touching i3’s source code. It should contain all important information to help
|
||||
you understand why things are like they are. If it does not mention something
|
||||
you find necessary, please do not hesitate to contact me.
|
||||
|
||||
== Window Managers
|
||||
|
||||
A window manager is not necessarily needed to run X, but it is usually used in combination
|
||||
to facilitate some things. The window manager's job is to take care of the placement of
|
||||
windows, to provide the user some mechanisms to change the position/size of windows and
|
||||
to communicate with clients to a certain extent (for example handle fullscreen requests
|
||||
of clients such as MPlayer).
|
||||
A window manager is not necessarily needed to run X, but it is usually used in
|
||||
combination with X to facilitate some things. The window manager's job is to
|
||||
take care of the placement of windows, to provide the user with some mechanisms
|
||||
to change the position/size of windows and to communicate with clients to a
|
||||
certain extent (for example handle fullscreen requests of clients such as
|
||||
MPlayer).
|
||||
|
||||
There are no different contexts in which X11 clients run, so a window manager is just another
|
||||
client, like all other X11 applications. However, it handles some events which normal clients
|
||||
usually don’t handle.
|
||||
There are no different contexts in which X11 clients run, so a window manager
|
||||
is just another client, like all other X11 applications. However, it handles
|
||||
some events which normal clients usually don’t handle.
|
||||
|
||||
In the case of i3, the tasks (and order of them) are the following:
|
||||
|
||||
. Grab the key bindings (events will be sent upon keypress/keyrelease)
|
||||
. Iterate through all existing windows (if the window manager is not started as the first
|
||||
client of X) and manage them (= reparent them, create window decorations)
|
||||
. Iterate through all existing windows (if the window manager is not started as
|
||||
the first client of X) and manage them (reparent them, create window
|
||||
decorations, etc.)
|
||||
. When new windows are created, manage them
|
||||
. Handle the client’s `_WM_STATE` property, but only the `_WM_STATE_FULLSCREEN`
|
||||
. Handle the client’s `WM_NAME` property
|
||||
. Handle the client’s size hints to display them proportionally
|
||||
. Handle the client’s urgency hint
|
||||
. Handle enter notifications (focus follows mouse)
|
||||
. Handle button (as in mouse buttons) presses for focus/raise on click
|
||||
. Handle expose events to re-draw own windows such as decorations
|
||||
. React to the user’s commands: Change focus, Move windows, Switch workspaces,
|
||||
Change the layout mode of a container (default/stacking), Start a new application,
|
||||
Restart the window manager
|
||||
Change the layout mode of a container (default/stacking/tabbed), start a new
|
||||
application, restart the window manager
|
||||
|
||||
In the following chapters, each of these tasks and their implementation details will be discussed.
|
||||
In the following chapters, each of these tasks and their implementation details
|
||||
will be discussed.
|
||||
|
||||
=== Tiling window managers
|
||||
|
||||
Traditionally, there are two approaches to managing windows: The most common one nowadays is
|
||||
floating, which means the user can freely move/resize the windows. The other approach is called
|
||||
tiling, which means that your window manager distributing windows to use as much space as
|
||||
possible while not overlapping.
|
||||
Traditionally, there are two approaches to managing windows: The most common
|
||||
one nowadays is floating, which means the user can freely move/resize the
|
||||
windows. The other approach is called tiling, which means that your window
|
||||
manager distributes windows to use as much space as possible while not
|
||||
overlapping each other.
|
||||
|
||||
The idea behind tiling is that you should not need to waste your time moving/resizing windows
|
||||
while you usually want to get some work done. After all, most users sooner or later tend to
|
||||
lay out their windows in a way which corresponds to tiling or stacking mode in i3. Therefore,
|
||||
why not let i3 do this for you? Certainly, it’s faster than you could ever do it.
|
||||
The idea behind tiling is that you should not need to waste your time
|
||||
moving/resizing windows while you usually want to get some work done. After
|
||||
all, most users sooner or later tend to lay out their windows in a way which
|
||||
corresponds to tiling or stacking mode in i3. Therefore, why not let i3 do this
|
||||
for you? Certainly, it’s faster than you could ever do it.
|
||||
|
||||
The problem with most tiling window managers is that they are too unflexible. In my opinion, a
|
||||
window manager is just another tool, and similar to vim which can edit all kinds of text files
|
||||
(like source code, HTML, …) and is not limited to a specific file type, a window manager should
|
||||
not limit itself to a certain layout (like dwm, awesome, …) but provide mechanisms for you to
|
||||
easily create the layout you need at the moment.
|
||||
The problem with most tiling window managers is that they are too unflexible.
|
||||
In my opinion, a window manager is just another tool, and similar to vim which
|
||||
can edit all kinds of text files (like source code, HTML, …) and is not limited
|
||||
to a specific file type, a window manager should not limit itself to a certain
|
||||
layout (like dwm, awesome, …) but provide mechanisms for you to easily create
|
||||
the layout you need at the moment.
|
||||
|
||||
=== The layout table
|
||||
|
||||
To accomplish flexible layouts, we decided to simply use a table. The table grows and shrinks
|
||||
as you need it. Each cell holds a container which then holds windows (see picture below). You
|
||||
can use different layouts for each container (default layout and stacking layout).
|
||||
To accomplish flexible layouts, we decided to simply use a table. The table
|
||||
grows and shrinks as you need it. Each cell holds a container which then holds
|
||||
windows (see picture below). You can use different layouts for each container
|
||||
(default layout and stacking layout).
|
||||
|
||||
So, when you open a terminal and immediately open another one, they reside in the same container,
|
||||
in default layout. The layout table has exactly one column, one row and therefore one cell.
|
||||
When you move one of the terminals to the right, the table needs to grow. It will be expanded
|
||||
to two columns and one row. This enables you to have different layouts for each container.
|
||||
The table then looks like this:
|
||||
So, when you open a terminal and immediately open another one, they reside in
|
||||
the same container, in default layout. The layout table has exactly one column,
|
||||
one row and therefore one cell. When you move one of the terminals to the
|
||||
right, the table needs to grow. It will be expanded to two columns and one row.
|
||||
This enables you to have different layouts for each container. The table then
|
||||
looks like this:
|
||||
|
||||
[width="15%",cols="^,^"]
|
||||
|========
|
||||
|
@ -81,9 +90,10 @@ When moving terminal 2 to the bottom, the table will be expanded again.
|
|||
| | T2
|
||||
|========
|
||||
|
||||
You can really think of the layout table like a traditional HTML table, if you’ve ever
|
||||
designed one. Especially col- and rowspan work equally. Below you see an example of
|
||||
colspan=2 for the first container (which has T1 as window).
|
||||
You can really think of the layout table like a traditional HTML table, if
|
||||
you’ve ever designed one. Especially col- and rowspan work similarly. Below,
|
||||
you see an example of colspan=2 for the first container (which has T1 as
|
||||
window).
|
||||
|
||||
[width="15%",cols="^asciidoc"]
|
||||
|========
|
||||
|
@ -100,19 +110,30 @@ Furthermore, you can freely resize table cells.
|
|||
== Files
|
||||
|
||||
include/data.h::
|
||||
Contains data definitions used by nearly all files. You really need to read this first.
|
||||
Contains data definitions used by nearly all files. You really need to read
|
||||
this first.
|
||||
|
||||
include/*.h::
|
||||
Contains forward definitions for all public functions, aswell as doxygen-compatible
|
||||
comments (so if you want to get a bit more of the big picture, either browse all
|
||||
header files or use doxygen if you prefer that).
|
||||
Contains forward definitions for all public functions, as well as
|
||||
doxygen-compatible comments (so if you want to get a bit more of the big
|
||||
picture, either browse all header files or use doxygen if you prefer that).
|
||||
|
||||
src/cfgparse.l::
|
||||
Contains the lexer for i3’s configuration file, written for +flex(1)+.
|
||||
|
||||
src/cfgparse.y::
|
||||
Contains the parser for i3’s configuration file, written for +bison(1)+.
|
||||
|
||||
src/click.c::
|
||||
Contains all functions which handle mouse button clicks (right mouse button
|
||||
clicks initiate resizing and thus are relatively complex).
|
||||
|
||||
src/client.c::
|
||||
Contains all functions which are specific to a certain client (make it
|
||||
fullscreen, see if its class/name matches a pattern, kill it, …).
|
||||
|
||||
src/commands.c::
|
||||
Parsing commands and actually execute them (focussing, moving, …).
|
||||
Parsing commands and actually executing them (focusing, moving, …).
|
||||
|
||||
src/config.c::
|
||||
Parses the configuration file.
|
||||
|
@ -124,7 +145,7 @@ src/floating.c::
|
|||
Contains functions for floating mode (mostly resizing/dragging).
|
||||
|
||||
src/handlers.c::
|
||||
Contains all handlers for all kind of X events (new window title, new hints,
|
||||
Contains all handlers for all kinds of X events (new window title, new hints,
|
||||
unmapping, key presses, button presses, …).
|
||||
|
||||
src/ipc.c::
|
||||
|
@ -143,9 +164,6 @@ reparents the window and inserts it into our data structures.
|
|||
src/resize.c::
|
||||
Contains the functions to resize columns/rows in the table.
|
||||
|
||||
src/resize.c::
|
||||
Contains the functions to resize columns/rows in the table.
|
||||
|
||||
src/table.c::
|
||||
Manages the most important internal data structure, the design table.
|
||||
|
||||
|
@ -159,12 +177,13 @@ src/xcb.c::
|
|||
Contains wrappers to use xcb more easily.
|
||||
|
||||
src/xinerama.c::
|
||||
(Re-)initializes the available screens and converts them to virtual screens (see below).
|
||||
(Re-)initializes the available screens and converts them to virtual screens
|
||||
(see below).
|
||||
|
||||
== Data structures
|
||||
|
||||
See include/data.h for documented data structures. The most important ones are explained
|
||||
right here.
|
||||
See include/data.h for documented data structures. The most important ones are
|
||||
explained right here.
|
||||
|
||||
image:bigpicture.png[The Big Picture]
|
||||
|
||||
|
@ -178,37 +197,41 @@ So, the hierarchy is:
|
|||
|
||||
=== Virtual screens
|
||||
|
||||
A virtual screen (type `i3Screen`) is generated from the connected screens obtained
|
||||
through Xinerama. The difference to the raw Xinerama monitors as seen when using +xrandr(1)+
|
||||
is that it falls back to the lowest common resolution of the logical screens.
|
||||
A virtual screen (type `i3Screen`) is generated from the connected screens
|
||||
obtained through Xinerama. The difference to the raw Xinerama monitors as seen
|
||||
when using +xrandr(1)+ is that it falls back to the lowest common resolution of
|
||||
the logical screens.
|
||||
|
||||
For example, if your notebook has 1280x800 and you connect a video projector with
|
||||
1024x768, set up in clone mode (+xrandr \--output VGA \--mode 1024x768 \--same-as LVDS+),
|
||||
i3 will have one virtual screen.
|
||||
For example, if your notebook has 1280x800 and you connect a video projector
|
||||
with 1024x768, set up in clone mode (+xrandr \--output VGA \--mode 1024x768
|
||||
\--same-as LVDS+), i3 will have one virtual screen.
|
||||
|
||||
However, if you configure it using +xrandr \--output VGA \--mode 1024x768 \--right-of LVDS+,
|
||||
i3 will generate two virtual screens. For each virtual screen, a new workspace will be
|
||||
assigned. New workspaces are created on the screen you are currently on.
|
||||
However, if you configure it using +xrandr \--output VGA \--mode 1024x768
|
||||
\--right-of LVDS+, i3 will generate two virtual screens. For each virtual
|
||||
screen, a new workspace will be assigned. New workspaces are created on the
|
||||
screen you are currently on.
|
||||
|
||||
=== Workspace
|
||||
|
||||
A workspace is identified by its number. Basically, you could think of workspaces
|
||||
as different desks in your bureau, if you like the desktop methaphor. They just contain
|
||||
different sets of windows and are completely separate of each other. Other window
|
||||
managers also call this ``Virtual desktops''.
|
||||
A workspace is identified by its number. Basically, you could think of
|
||||
workspaces as different desks in your office, if you like the desktop
|
||||
methaphor. They just contain different sets of windows and are completely
|
||||
separate of each other. Other window managers also call this ``Virtual
|
||||
desktops''.
|
||||
|
||||
=== The layout table
|
||||
|
||||
Each workspace has a table, which is just a two-dimensional dynamic array containing
|
||||
Containers (see below). This table grows and shrinks as you need it (by moving windows
|
||||
to the right you can create a new column in the table, by moving them to the bottom
|
||||
you create a new row).
|
||||
Each workspace has a table, which is just a two-dimensional dynamic array
|
||||
containing Containers (see below). This table grows and shrinks as you need it
|
||||
(by moving windows to the right you can create a new column in the table, by
|
||||
moving them to the bottom you create a new row).
|
||||
|
||||
=== Container
|
||||
|
||||
A container is the content of a table’s cell. It holds an arbitrary amount of windows
|
||||
and has a specific layout (default layout or stack layout). Containers can consume
|
||||
multiple table cells by modifying their colspan/rowspan attribute.
|
||||
A container is the content of a table’s cell. It holds an arbitrary amount of
|
||||
windows and has a specific layout (default layout, stack layout or tabbed
|
||||
layout). Containers can consume multiple table cells by modifying their
|
||||
colspan/rowspan attribute.
|
||||
|
||||
=== Client
|
||||
|
||||
|
@ -216,20 +239,22 @@ A client is x11-speak for a window.
|
|||
|
||||
== List/queue macros
|
||||
|
||||
i3 makes heavy use of the list macros defined in BSD operating systems. To ensure
|
||||
that the operating system on which i3 is compiled has all the awaited features,
|
||||
i3 comes with `include/queue.h`. On BSD systems, you can use man `queue(3)`. On Linux,
|
||||
you have to use google.
|
||||
i3 makes heavy use of the list macros defined in BSD operating systems. To
|
||||
ensure that the operating system on which i3 is compiled has all the expected
|
||||
features, i3 comes with `include/queue.h`. On BSD systems, you can use man
|
||||
`queue(3)`. On Linux, you have to use google (or read the source).
|
||||
|
||||
The lists used are `SLIST` (single linked lists) and `CIRCLEQ` (circular queues).
|
||||
Usually, only forward traversal is necessary, so an `SLIST` works fine. However,
|
||||
for the windows inside a container, a `CIRCLEQ` is necessary to go from the currently
|
||||
The lists used are `SLIST` (single linked lists), `CIRCLEQ` (circular
|
||||
queues) and TAILQ (tail queues). Usually, only forward traversal is necessary,
|
||||
so an `SLIST` works fine. If inserting elements at arbitrary positions or at
|
||||
the end of a list is necessary, a `TAILQ` is used instead. However, for the
|
||||
windows inside a container, a `CIRCLEQ` is necessary to go from the currently
|
||||
selected window to the window above/below.
|
||||
|
||||
== Naming conventions
|
||||
|
||||
There is a row of standard variables used in many events. The following names should be
|
||||
chosen for those:
|
||||
There is a row of standard variables used in many events. The following names
|
||||
should be chosen for those:
|
||||
|
||||
* ``conn'' is the xcb_connection_t
|
||||
* ``event'' is the event of the particular type
|
||||
|
@ -249,116 +274,138 @@ chosen for those:
|
|||
|
||||
=== Grabbing the bindings
|
||||
|
||||
Grabbing the bindings is quite straight-forward. You pass X your combination of modifiers and
|
||||
the keycode you want to grab and whether you want to grab them actively or passively. Most
|
||||
bindings (everything except for bindings using Mode_switch) are grabbed passively, that is,
|
||||
just the window manager gets the event and cannot replay it.
|
||||
Grabbing the bindings is quite straight-forward. You pass X your combination of
|
||||
modifiers and the keycode you want to grab and whether you want to grab them
|
||||
actively or passively. Most bindings (everything except for bindings using
|
||||
Mode_switch) are grabbed passively, that is, just the window manager gets the
|
||||
event and cannot replay it.
|
||||
|
||||
We need to grab bindings that use Mode_switch actively because of a bug in X. When the window
|
||||
manager receives the keypress/keyrelease event for an actively grabbed keycode, it has to decide
|
||||
what to do with this event: It can either replay it so that other applications get it or it
|
||||
can prevent other applications from receiving it.
|
||||
We need to grab bindings that use Mode_switch actively because of a bug in X.
|
||||
When the window manager receives the keypress/keyrelease event for an actively
|
||||
grabbed keycode, it has to decide what to do with this event: It can either
|
||||
replay it so that other applications get it or it can prevent other
|
||||
applications from receiving it.
|
||||
|
||||
So, why do we need to grab keycodes actively? Because X does not set the state-property of
|
||||
keypress/keyrelease events properly. The Mode_switch bit is not set and we need to get it
|
||||
using XkbGetState. This means we cannot pass X our combination of modifiers containing Mode_switch
|
||||
when grabbing the key and therefore need to grab the keycode itself without any modiffiers.
|
||||
This means, if you bind Mode_switch + keycode 38 ("a"), i3 will grab keycode 38 ("a") and
|
||||
check on each press of "a" if the Mode_switch bit is set using XKB. If yes, it will handle
|
||||
the event, if not, it will replay the event.
|
||||
So, why do we need to grab keycodes actively? Because X does not set the
|
||||
state-property of keypress/keyrelease events properly. The Mode_switch bit is
|
||||
not set and we need to get it using XkbGetState. This means we cannot pass X
|
||||
our combination of modifiers containing Mode_switch when grabbing the key and
|
||||
therefore need to grab the keycode itself without any modifiers. This means,
|
||||
if you bind Mode_switch + keycode 38 ("a"), i3 will grab keycode 38 ("a") and
|
||||
check on each press of "a" if the Mode_switch bit is set using XKB. If yes, it
|
||||
will handle the event, if not, it will replay the event.
|
||||
|
||||
=== Handling a keypress
|
||||
|
||||
As mentioned in "Grabbing the bindings", upon a keypress event, i3 first gets the correct state.
|
||||
As mentioned in "Grabbing the bindings", upon a keypress event, i3 first gets
|
||||
the correct state.
|
||||
|
||||
Then, it looks through all bindings and gets the one which matches the received event.
|
||||
Then, it looks through all bindings and gets the one which matches the received
|
||||
event.
|
||||
|
||||
The bound command is parsed directly in command mode.
|
||||
|
||||
== Manage windows (src/mainx.c, manage_window() and reparent_window())
|
||||
|
||||
`manage_window()` does some checks to decide whether the window should be managed at all:
|
||||
`manage_window()` does some checks to decide whether the window should be
|
||||
managed at all:
|
||||
|
||||
* Windows have to be mapped, that is, visible on screen
|
||||
* The override_redirect must not be set. Windows with override_redirect shall not be
|
||||
managed by a window manager
|
||||
* The override_redirect must not be set. Windows with override_redirect shall
|
||||
not be managed by a window manager
|
||||
|
||||
Afterwards, i3 gets the intial geometry and reparents the window if it wasn’t already
|
||||
managed.
|
||||
Afterwards, i3 gets the intial geometry and reparents the window (see
|
||||
`reparent_window()`) if it wasn’t already managed.
|
||||
|
||||
Reparenting means that for each window which is reparented, a new window, slightly larger
|
||||
than the original one, is created. The original window is then reparented to the bigger one
|
||||
(called "frame").
|
||||
Reparenting means that for each window which is reparented, a new window,
|
||||
slightly larger than the original one, is created. The original window is then
|
||||
reparented to the bigger one (called "frame").
|
||||
|
||||
After reparenting, the window type (`_NET_WM_WINDOW_TYPE`) is checked to see whether this
|
||||
window is a dock (`_NET_WM_WINDOW_TYPE_DOCK`), like dzen2 for example. Docks are handled
|
||||
differently, they don’t have decorations and are not assigned to a specific container.
|
||||
Instead, they are positioned at the bottom of the screen. To get the height which needsd
|
||||
to be reserved for the window, the `_NET_WM_STRUT_PARTIAL` property is used.
|
||||
After reparenting, the window type (`_NET_WM_WINDOW_TYPE`) is checked to see
|
||||
whether this window is a dock (`_NET_WM_WINDOW_TYPE_DOCK`), like dzen2 for
|
||||
example. Docks are handled differently, they don’t have decorations and are not
|
||||
assigned to a specific container. Instead, they are positioned at the bottom
|
||||
of the screen. To get the height which needsd to be reserved for the window,
|
||||
the `_NET_WM_STRUT_PARTIAL` property is used.
|
||||
|
||||
Furthermore, the list of assignments (to other workspaces, which may be on
|
||||
other screens) is checked. If the window matches one of the user’s criteria,
|
||||
it may either be put in floating mode or moved to a different workspace. If the
|
||||
target workspace is not visible, the window will not be mapped.
|
||||
|
||||
== What happens when an application is started?
|
||||
|
||||
i3 does not care for applications. All it notices is when new windows are mapped (see
|
||||
`src/handlers.c`, `handle_map_request()`). The window is then reparented (see section
|
||||
"Manage windows").
|
||||
i3 does not care for applications. All it notices is when new windows are
|
||||
mapped (see `src/handlers.c`, `handle_map_request()`). The window is then
|
||||
reparented (see section "Manage windows").
|
||||
|
||||
After reparenting the window, `render_layout()` is called which renders the internal
|
||||
layout table. The window was placed in the currently focused container and
|
||||
therefore the new window and the old windows (if any) need to be moved/resized
|
||||
so that the currently active layout (default mode/stacking mode) is rendered
|
||||
correctly. To move/resize windows, a window is ``configured'' in X11-speak.
|
||||
After reparenting the window, `render_layout()` is called which renders the
|
||||
internal layout table. The new window has been placed in the currently focused
|
||||
container and therefore the new window and the old windows (if any) need to be
|
||||
moved/resized so that the currently active layout (default/stacking/tabbed mode)
|
||||
is rendered correctly. To move/resize windows, a window is ``configured'' in
|
||||
X11-speak.
|
||||
|
||||
Some applications, such as MPlayer obivously assume the window manager is stupid
|
||||
and try to configure their windows by themselves. This generates an event called
|
||||
configurerequest. i3 handles these events and tells the window the size it had
|
||||
before the configurerequest (with the exception of not yet mapped windows, which
|
||||
get configured like they want to, and floating windows, which can reconfigure
|
||||
themselves).
|
||||
Some applications, such as MPlayer obviously assume the window manager is
|
||||
stupid and try to configure their windows by themselves. This generates an
|
||||
event called configurerequest. i3 handles these events and tells the window the
|
||||
size it had before the configurerequest (with the exception of not yet mapped
|
||||
windows, which get configured like they want to, and floating windows, which
|
||||
can reconfigure themselves).
|
||||
|
||||
== _NET_WM_STATE
|
||||
|
||||
Only the _NET_WM_STATE_FULLSCREEN atom is handled. It calls ``toggle_fullscreen()'' for the
|
||||
specific client which just configures the client to use the whole screen on which it
|
||||
currently is. Also, it is set as fullscreen_client for the i3Screen.
|
||||
Only the _NET_WM_STATE_FULLSCREEN atom is handled. It calls
|
||||
``toggle_fullscreen()'' for the specific client which just configures the
|
||||
client to use the whole screen on which it currently is. Also, it is set as
|
||||
fullscreen_client for the i3Screen.
|
||||
|
||||
== WM_NAME
|
||||
|
||||
When the WM_NAME property of a window changes, its decoration (containing the title)
|
||||
is re-rendered.
|
||||
When the WM_NAME property of a window changes, its decoration (containing the
|
||||
title) is re-rendered. Note that WM_NAME is in COMPOUND_TEXT encoding which is
|
||||
totally uncommon and cumbersome. Therefore, the _NET_WM_NAME atom will be used
|
||||
if present.
|
||||
|
||||
== _NET_WM_NAME
|
||||
|
||||
Like WM_NAME, this atom contains the title of a window. However, _NET_WM_NAME
|
||||
is encoded in UTF-8. i3 will recode it to UCS-2 in order to be able to pass it
|
||||
to X. Using an appropriate font (ISO-10646), you can see most special
|
||||
characters (every special character contained in your font).
|
||||
|
||||
== Size hints
|
||||
|
||||
Size hints specify the minimum/maximum size for a given window aswell as its aspect ratio.
|
||||
At the moment, as i3 does not have a floating mode yet, only the aspect ratio is parsed.
|
||||
This is important for clients like mplayer, who only set the aspect ratio and resize their
|
||||
window to be as small as possible (but only with some video outputs, for example in Xv,
|
||||
while when using x11, mplayer does the necessary centering for itself).
|
||||
Size hints specify the minimum/maximum size for a given window as well as its
|
||||
aspect ratio. This is important for clients like mplayer, who only set the
|
||||
aspect ratio and resize their window to be as small as possible (but only with
|
||||
some video outputs, for example in Xv, while when using x11, mplayer does the
|
||||
necessary centering for itself).
|
||||
|
||||
So, when an aspect ratio was specified, i3 adjusts the height of the window until the
|
||||
size maintains the correct aspect ratio. For the code to do this, see src/layout.c,
|
||||
function resize_client().
|
||||
So, when an aspect ratio was specified, i3 adjusts the height of the window
|
||||
until the size maintains the correct aspect ratio. For the code to do this, see
|
||||
src/layout.c, function resize_client().
|
||||
|
||||
== Rendering (src/layout.c, render_layout() and render_container())
|
||||
|
||||
There are two entry points to rendering: render_layout() and render_container(). The
|
||||
former one renders all virtual screens, the currently active workspace of each virtual
|
||||
screen and all containers (inside the table cells) of these workspaces using
|
||||
render_container(). Therefore, if you need to render only a single container, for
|
||||
example because a window was removed, added or changed its title, you should directly
|
||||
call render_container().
|
||||
There are several entry points to rendering: `render_layout()`,
|
||||
`render_workspace()` and `render_container()`. The former one calls
|
||||
`render_workspace()` for every screen, which in turn will call
|
||||
`render_container()` for every container inside its layout table. Therefore, if
|
||||
you need to render only a single container, for example because a window was
|
||||
removed, added or changed its title, you should directly call
|
||||
render_container().
|
||||
|
||||
Rendering consists of two steps: In the first one, in render_layout(), each container
|
||||
gets its position (screen offset + offset in the table) and size (container's width
|
||||
times colspan/rowspan). Then, render_container() is called:
|
||||
|
||||
render_container() then takes different approaches, depending on the mode the container
|
||||
is in.
|
||||
Rendering consists of two steps: In the first one, in `render_workspace()`, each
|
||||
container gets its position (screen offset + offset in the table) and size
|
||||
(container's width times colspan/rowspan). Then, `render_container()` is called,
|
||||
which takes different approaches, depending on the mode the container is in:
|
||||
|
||||
=== Common parts
|
||||
|
||||
On the frame (the window which was created around the client’s window for the decorations),
|
||||
a black rectangle is drawn as a background for windows like MPlayer, which don’t completely
|
||||
fit into the frame.
|
||||
On the frame (the window which was created around the client’s window for the
|
||||
decorations), a black rectangle is drawn as a background for windows like
|
||||
MPlayer, which do not completely fit into the frame.
|
||||
|
||||
=== Default mode
|
||||
|
||||
|
@ -366,100 +413,112 @@ Each clients gets the container’s width and an equal amount of height.
|
|||
|
||||
=== Stack mode
|
||||
|
||||
In stack mode, a window containing the decorations of all windows inside the container
|
||||
is placed at the top. The currently focused window is then given the whole remaining
|
||||
space.
|
||||
In stack mode, a window containing the decorations of all windows inside the
|
||||
container is placed at the top. The currently focused window is then given the
|
||||
whole remaining space.
|
||||
|
||||
=== Tabbed mode
|
||||
|
||||
Tabbed mode is like stack mode, except that the window decorations are drawn
|
||||
in one single line at the top of the container.
|
||||
|
||||
=== Window decorations
|
||||
|
||||
The window decorations consist of a rectangle in the appropriate color (depends on whether
|
||||
this window is the currently focused one or the last focused one in a not focused container
|
||||
or not focused at all) forming the background. Afterwards, two lighter lines are drawn
|
||||
and the last step is drawing the window’s title (see WM_NAME) onto it.
|
||||
The window decorations consist of a rectangle in the appropriate color (depends
|
||||
on whether this window is the currently focused one, the last focused one in a
|
||||
not focused container or not focused at all) forming the background.
|
||||
Afterwards, two lighter lines are drawn and the last step is drawing the
|
||||
window’s title (see WM_NAME) onto it.
|
||||
|
||||
=== Fullscreen windows
|
||||
|
||||
For fullscreen windows, the `rect` (x, y, width, height) is not changed to allow the client
|
||||
to easily go back to its previous position. Instead, fullscreen windows are skipped
|
||||
when rendering.
|
||||
For fullscreen windows, the `rect` (x, y, width, height) is not changed to
|
||||
allow the client to easily go back to its previous position. Instead,
|
||||
fullscreen windows are skipped when rendering.
|
||||
|
||||
=== Resizing containers
|
||||
|
||||
By clicking and dragging the border of a container, you can resize the whole column
|
||||
(respectively row) which this container is in. This is necessary to keep the table
|
||||
layout working and consistent.
|
||||
By clicking and dragging the border of a container, you can resize the whole
|
||||
column (respectively row) which this container is in. This is necessary to keep
|
||||
the table layout working and consistent.
|
||||
|
||||
Currently, only vertical resizing is implemented.
|
||||
The resizing works similarly to the resizing of floating windows or movement of
|
||||
floating windows:
|
||||
|
||||
The resizing works similarly to the resizing of floating windows or movement of floating
|
||||
windows:
|
||||
|
||||
* A new, invisible window with the size of the root window is created (+grabwin+)
|
||||
* Another window, 2px width and as high as your screen (or vice versa for horizontal
|
||||
resizing) is created. Its background color is the border color and it is only
|
||||
there to signalize the user how big the container will be (it creates the impression
|
||||
of dragging the border out of the container).
|
||||
* The +drag_pointer+ function of +src/floating.c+ is called to grab the pointer and
|
||||
enter an own event loop which will pass all events (expose events) but motion notify
|
||||
events. This function then calls the specified callback (+resize_callback+) which
|
||||
does some boundary checking and moves the helper window. As soon as the mouse
|
||||
button is released, this loop will be terminated.
|
||||
* The new width_factor for each involved column (respectively row) will be calculated.
|
||||
* A new, invisible window with the size of the root window is created
|
||||
(+grabwin+)
|
||||
* Another window, 2px width and as high as your screen (or vice versa for
|
||||
horizontal resizing) is created. Its background color is the border color and
|
||||
it is only there to inform the user how big the container will be (it
|
||||
creates the impression of dragging the border out of the container).
|
||||
* The +drag_pointer+ function of +src/floating.c+ is called to grab the pointer
|
||||
and enter its own event loop which will pass all events (expose events) but
|
||||
motion notify events. This function then calls the specified callback
|
||||
(+resize_callback+) which does some boundary checking and moves the helper
|
||||
window. As soon as the mouse button is released, this loop will be
|
||||
terminated.
|
||||
* The new width_factor for each involved column (respectively row) will be
|
||||
calculated.
|
||||
|
||||
== User commands / commandmode (src/commands.c)
|
||||
|
||||
Like in vim, you can control i3 using commands. They are intended to be a powerful
|
||||
alternative to lots of shortcuts, because they can be combined. There are a few special
|
||||
commands, which are the following:
|
||||
Like in vim, you can control i3 using commands. They are intended to be a
|
||||
powerful alternative to lots of shortcuts, because they can be combined. There
|
||||
are a few special commands, which are the following:
|
||||
|
||||
exec <command>::
|
||||
Starts the given command by passing it to `/bin/sh`.
|
||||
|
||||
restart::
|
||||
Restarts i3 by executing `argv[0]` (the path with which you started i3) without forking.
|
||||
Restarts i3 by executing `argv[0]` (the path with which you started i3) without
|
||||
forking.
|
||||
|
||||
w::
|
||||
"With". This is used to select a bunch of windows. Currently, only selecting the whole
|
||||
container in which the window is in, is supported by specifying "w".
|
||||
"With". This is used to select a bunch of windows. Currently, only selecting
|
||||
the whole container in which the window is in, is supported by specifying "w".
|
||||
|
||||
f, s, d::
|
||||
Toggle fullscreen, stacking, default mode for the current window/container.
|
||||
|
||||
The other commands are to be combined with a direction. The directions are h, j, k and l,
|
||||
like in vim (h = left, j = down, k = up, l = right). When you just specify the direction
|
||||
keys, i3 will move the focus in that direction. You can provide "m" or "s" before the
|
||||
direction to move a window respectively or snap.
|
||||
The other commands are to be combined with a direction. The directions are h,
|
||||
j, k and l, like in vim (h = left, j = down, k = up, l = right). When you just
|
||||
specify the direction keys, i3 will move the focus in that direction. You can
|
||||
provide "m" or "s" before the direction to move a window respectively or snap.
|
||||
|
||||
== Gotchas
|
||||
|
||||
* Forgetting to call `xcb_flush(conn);` after sending a request. This usually leads to
|
||||
code which looks like it works fine but which does not work under certain conditions.
|
||||
* Forgetting to call `xcb_flush(conn);` after sending a request. This usually
|
||||
leads to code which looks like it works fine but which does not work under
|
||||
certain conditions.
|
||||
|
||||
== Using git / sending patches
|
||||
|
||||
For a short introduction into using git, see http://www.spheredev.org/wiki/Git_for_the_lazy
|
||||
or, for more documentation, see http://git-scm.com/documentation
|
||||
For a short introduction into using git, see
|
||||
http://www.spheredev.org/wiki/Git_for_the_lazy or, for more documentation, see
|
||||
http://git-scm.com/documentation
|
||||
|
||||
When you want to send a patch because you fixed a bug or implemented a cool feature (please
|
||||
talk to us before working on features to see whether they are maybe already implemented, not
|
||||
possible because of some reason or don’t fit into the concept), please use git to create
|
||||
a patchfile.
|
||||
When you want to send a patch because you fixed a bug or implemented a cool
|
||||
feature (please talk to us before working on features to see whether they are
|
||||
maybe already implemented, not possible for some some reason, or don’t fit
|
||||
into the concept), please use git to create a patchfile.
|
||||
|
||||
First of all, update your working copy to the latest version of the master branch:
|
||||
First of all, update your working copy to the latest version of the master
|
||||
branch:
|
||||
|
||||
--------
|
||||
git pull
|
||||
--------
|
||||
|
||||
Afterwards, make the necessary changes for your bugfix/feature. Then, review the changes
|
||||
using +git diff+ (you might want to enable colors in the diff using +git config diff.color auto+).
|
||||
When you are definitely done, use +git commit -a+ to commit all changes you’ve made.
|
||||
Afterwards, make the necessary changes for your bugfix/feature. Then, review
|
||||
the changes using +git diff+ (you might want to enable colors in the diff using
|
||||
+git config diff.color auto+). When you are definitely done, use +git commit
|
||||
-a+ to commit all changes you’ve made.
|
||||
|
||||
Then, use the following command to generate a patchfile which we can directly apply to
|
||||
the branch, preserving your commit message and name:
|
||||
Then, use the following command to generate a patchfile which we can directly
|
||||
apply to the branch, preserving your commit message and name:
|
||||
|
||||
-----------------------
|
||||
git format-patch origin
|
||||
-----------------------
|
||||
|
||||
Just send us the generated file via mail.
|
||||
Just send us the generated file via email.
|
||||
|
|
|
@ -0,0 +1,301 @@
|
|||
IPC interface (interprocess communication)
|
||||
==========================================
|
||||
Michael Stapelberg <michael+i3@stapelberg.de>
|
||||
March 2010
|
||||
|
||||
This document describes how to interface with i3 from a separate process. This
|
||||
is useful for example to remote-control i3 (to write test cases for example) or
|
||||
to get various information like the current workspaces to implement an external
|
||||
workspace bar.
|
||||
|
||||
The method of choice for IPC in our case is a unix socket because it has very
|
||||
little overhead on both sides and is usually available without headaches in
|
||||
most languages. In the default configuration file, no ipc-socket path is
|
||||
specified and thus no socket is created. The standard path (which +i3-msg+ and
|
||||
+i3-input+ use) is +~/.i3/ipc.sock+.
|
||||
|
||||
== Establishing a connection
|
||||
|
||||
To establish a connection, simply open the IPC socket. The following code
|
||||
snippet illustrates this in Perl:
|
||||
|
||||
-------------------------------------------------------------
|
||||
use IO::Socket::UNIX;
|
||||
my $sock = IO::Socket::UNIX->new(Peer => '~/.i3/ipc.sock');
|
||||
-------------------------------------------------------------
|
||||
|
||||
== Sending messages to i3
|
||||
|
||||
To send a message to i3, you have to format in the binary message format which
|
||||
i3 expects. This format specifies a magic string in the beginning to ensure
|
||||
the integrity of messages (to prevent follow-up errors). Following the magic
|
||||
string comes the length of the payload of the message as 32-bit integer, and
|
||||
the type of the message as 32-bit integer (the integers are not converted, so
|
||||
they are in native byte order).
|
||||
|
||||
The magic string currently is "i3-ipc" and will only be changed when a change
|
||||
in the IPC API is done which breaks compatibility (we hope that we don’t need
|
||||
to do that).
|
||||
|
||||
Currently implemented message types are the following:
|
||||
|
||||
COMMAND (0)::
|
||||
The payload of the message is a command for i3 (like the commands you
|
||||
can bind to keys in the configuration file) and will be executed
|
||||
directly after receiving it. There is no reply to this message.
|
||||
GET_WORKSPACES (1)::
|
||||
Gets the current workspaces. The reply will be a JSON-encoded list of
|
||||
workspaces (see the reply section).
|
||||
SUBSCRIBE (2)::
|
||||
Subscribes your connection to certain events. See <<events>> for a
|
||||
description of this message and the concept of events.
|
||||
GET_OUTPUTS (3)::
|
||||
Gets the current outputs. The reply will be a JSON-encoded list of outputs
|
||||
(see the reply section).
|
||||
|
||||
So, a typical message could look like this:
|
||||
--------------------------------------------------
|
||||
"i3-ipc" <message length> <message type> <payload>
|
||||
--------------------------------------------------
|
||||
|
||||
Or, as a hexdump:
|
||||
------------------------------------------------------------------------------
|
||||
00000000 69 33 2d 69 70 63 04 00 00 00 00 00 00 00 65 78 |i3-ipc........ex|
|
||||
00000010 69 74 0a |it.|
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To generate and send such a message, you could use the following code in Perl:
|
||||
------------------------------------------------------------
|
||||
sub format_ipc_command {
|
||||
my ($msg) = @_;
|
||||
my $len;
|
||||
# Get the real byte count (vs. amount of characters)
|
||||
{ use bytes; $len = length($msg); }
|
||||
return "i3-ipc" . pack("LL", $len, 0) . $msg;
|
||||
}
|
||||
|
||||
$sock->write(format_ipc_command("exit"));
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
== Receiving replies from i3
|
||||
|
||||
Replies from i3 usually consist of a simple string (the length of the string
|
||||
is the message_length, so you can consider them length-prefixed) which in turn
|
||||
contain the JSON serialization of a data structure. For example, the
|
||||
GET_WORKSPACES message returns an array of workspaces (each workspace is a map
|
||||
with certain attributes).
|
||||
|
||||
=== Reply format
|
||||
|
||||
The reply format is identical to the normal message format. There also is
|
||||
the magic string, then the message length, then the message type and the
|
||||
payload.
|
||||
|
||||
The following reply types are implemented:
|
||||
|
||||
COMMAND (0)::
|
||||
Confirmation/Error code for the COMMAND message.
|
||||
GET_WORKSPACES (1)::
|
||||
Reply to the GET_WORKSPACES message.
|
||||
SUBSCRIBE (2)::
|
||||
Confirmation/Error code for the SUBSCRIBE message.
|
||||
GET_OUTPUTS (3)::
|
||||
Reply to the GET_OUTPUTS message.
|
||||
|
||||
=== COMMAND reply
|
||||
|
||||
The reply consists of a single serialized map. At the moment, the only
|
||||
property is +success (bool)+, but this will be expanded in future versions.
|
||||
|
||||
*Example:*
|
||||
-------------------
|
||||
{ "success": true }
|
||||
-------------------
|
||||
|
||||
=== GET_WORKSPACES reply
|
||||
|
||||
The reply consists of a serialized list of workspaces. Each workspace has the
|
||||
following properties:
|
||||
|
||||
num (integer)::
|
||||
The logical number of the workspace. Corresponds to the command
|
||||
to switch to this workspace.
|
||||
name (string)::
|
||||
The name of this workspace (by default num+1), as changed by the
|
||||
user. Encoded in UTF-8.
|
||||
visible (boolean)::
|
||||
Whether this workspace is currently visible on an output (multiple
|
||||
workspaces can be visible at the same time).
|
||||
focused (boolean)::
|
||||
Whether this workspace currently has the focus (only one workspace
|
||||
can have the focus at the same time).
|
||||
urgent (boolean)::
|
||||
Whether a window on this workspace has the "urgent" flag set.
|
||||
rect (map)::
|
||||
The rectangle of this workspace (equals the rect of the output it
|
||||
is on), consists of x, y, width, height.
|
||||
output (string)::
|
||||
The video output this workspace is on (LVDS1, VGA1, …).
|
||||
|
||||
*Example:*
|
||||
-------------------
|
||||
[
|
||||
{
|
||||
"num": 0,
|
||||
"name": "1",
|
||||
"visible": true,
|
||||
"focused": true,
|
||||
"urgent": false,
|
||||
"rect": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 1280,
|
||||
"height": 800
|
||||
},
|
||||
"output": "LVDS1"
|
||||
},
|
||||
{
|
||||
"num": 1,
|
||||
"name": "2",
|
||||
"visible": false,
|
||||
"focused": false,
|
||||
"urgent": false,
|
||||
"rect": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 1280,
|
||||
"height": 800
|
||||
},
|
||||
"output": "LVDS1"
|
||||
}
|
||||
]
|
||||
-------------------
|
||||
|
||||
=== SUBSCRIBE reply
|
||||
|
||||
The reply consists of a single serialized map. The only property is
|
||||
+success (bool)+, indicating whether the subscription was successful (the
|
||||
default) or whether a JSON parse error occurred.
|
||||
|
||||
*Example:*
|
||||
-------------------
|
||||
{ "success": true }
|
||||
-------------------
|
||||
|
||||
=== GET_OUTPUTS reply
|
||||
|
||||
The reply consists of a serialized list of outputs. Each output has the
|
||||
following properties:
|
||||
|
||||
name (string)::
|
||||
The name of this output (as seen in +xrandr(1)+). Encoded in UTF-8.
|
||||
active (boolean)::
|
||||
Whether this output is currently active (has a valid mode).
|
||||
current_workspace (integer)::
|
||||
The current workspace which is visible on this output. +null+ if the
|
||||
output is not active.
|
||||
rect (map)::
|
||||
The rectangle of this output (equals the rect of the output it
|
||||
is on), consists of x, y, width, height.
|
||||
|
||||
*Example:*
|
||||
-------------------
|
||||
[
|
||||
{
|
||||
"name": "LVDS1",
|
||||
"active": true,
|
||||
"current_workspace": 4,
|
||||
"rect": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 1280,
|
||||
"height": 800
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "VGA1",
|
||||
"active": true,
|
||||
"current_workspace": 1,
|
||||
"rect": {
|
||||
"x": 1280,
|
||||
"y": 0,
|
||||
"width": 1280,
|
||||
"height": 1024
|
||||
},
|
||||
}
|
||||
]
|
||||
-------------------
|
||||
|
||||
== Events
|
||||
|
||||
[[events]]
|
||||
|
||||
To get informed when certain things happen in i3, clients can subscribe to
|
||||
events. Events consist of a name (like "workspace") and an event reply type
|
||||
(like I3_IPC_EVENT_WORKSPACE). The events sent by i3 are in the same format
|
||||
as replies to specific commands.
|
||||
|
||||
Caveat: As soon as you subscribe to an event, it is not guaranteed any longer
|
||||
that the requests to i3 are processed in order. This means, the following
|
||||
situation can happen: You send a GET_WORKSPACES request but you receive a
|
||||
"workspace" event before receiving the reply to GET_WORKSPACES. If your
|
||||
program does not want to cope which such kinds of race conditions (an
|
||||
event based library may not have a problem here), I suggest you create a
|
||||
separate connection to receive events.
|
||||
|
||||
=== Subscribing to events
|
||||
|
||||
By sending a message of type SUBSCRIBE with a JSON-encoded array as payload
|
||||
you can register to an event.
|
||||
|
||||
*Example:*
|
||||
---------------------------------
|
||||
type: SUBSCRIBE
|
||||
payload: [ "workspace", "focus" ]
|
||||
---------------------------------
|
||||
|
||||
=== Available events
|
||||
|
||||
workspace::
|
||||
Sent when the user switches to a different workspace, when a new
|
||||
workspace is initialized or when a workspace is removed (because the
|
||||
last client vanished).
|
||||
output::
|
||||
Sent when RandR issues a change notification (of either screens,
|
||||
outputs, CRTCs or output properties).
|
||||
|
||||
=== workspace event
|
||||
|
||||
This event consists of a single serialized map containing a property
|
||||
+change (string)+ which indicates the type of the change ("focus", "init",
|
||||
"empty", "urgent").
|
||||
|
||||
*Example:*
|
||||
---------------------
|
||||
{ "change": "focus" }
|
||||
---------------------
|
||||
|
||||
=== output event
|
||||
|
||||
This event consists of a single serialized map containing a property
|
||||
+change (string)+ which indicates the type of the change (currently only
|
||||
"unspecified").
|
||||
|
||||
*Example:*
|
||||
---------------------------
|
||||
{ "change": "unspecified" }
|
||||
---------------------------
|
||||
|
||||
== See also
|
||||
|
||||
For some languages, libraries are available (so you don’t have to implement
|
||||
all this on your own). This list names some (if you wrote one, please let me
|
||||
know):
|
||||
|
||||
C::
|
||||
i3 includes a headerfile +i3/ipc.h+ which provides you all constants.
|
||||
However, there is no library yet.
|
||||
Ruby::
|
||||
http://github.com/badboy/i3-ipc
|
||||
Perl::
|
||||
http://search.cpan.org/search?query=AnyEvent::I3
|
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 131 KiB |
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 129 KiB |
|
@ -0,0 +1,60 @@
|
|||
The multi-monitor situation
|
||||
===========================
|
||||
Michael Stapelberg <michael+i3@stapelberg.de>
|
||||
March 2010
|
||||
|
||||
…or: oh no, I have an nVidia graphics card!
|
||||
|
||||
== The quick fix
|
||||
|
||||
If you are using the nVidia binary graphics driver (also known as 'blob')
|
||||
you need to use the +--force-xinerama+ flag (in your .xsession) when starting
|
||||
i3, like so:
|
||||
|
||||
.Example:
|
||||
----------------------------------------------
|
||||
exec i3 --force-xinerama -V >>~/.i3/i3log 2>&1
|
||||
----------------------------------------------
|
||||
|
||||
== The explanation
|
||||
|
||||
Starting with version 3.ε, i3 uses the RandR (Rotate and Resize) API instead
|
||||
of Xinerama. The reason for this, is that RandR provides more information
|
||||
about your outputs and connected screens than Xinerama does. To be specific,
|
||||
the code which handled on-the-fly screen reconfiguration (meaning without
|
||||
restarting the X server) was a very messy heuristic and most of the time did
|
||||
not work correctly -- that is just not possible with the little information
|
||||
Xinerama offers (just a list of screen resolutions, no identifiers for the
|
||||
screens or any additional information). Xinerama simply was not designed
|
||||
for dynamic configuration.
|
||||
|
||||
So RandR came along, as a more powerful alternative (RandR 1.2 to be specific).
|
||||
It offers all of Xinerama’s possibilities and lots more. Using the RandR API
|
||||
made our code much more robust and clean. Also, you can now reliably assign
|
||||
workspaces to output names instead of some rather unreliable screen identifier
|
||||
(position inside the list of screens, which could change, and so on…).
|
||||
|
||||
As RandR has been around for about three years as of this writing, it seemed
|
||||
like a very good idea to us, and it still is a very good one. What we did not
|
||||
expect, however, was the nVidia binary driver. It still does not support RandR
|
||||
(as of March 2010), even though nVidia has announced that it will support RandR
|
||||
eventually. What does this mean for you, if you are stuck with the binary
|
||||
driver for some reason (say the free drivers don’t work with your card)? First
|
||||
of all, you are stuck with TwinView and cannot use +xrandr+. While this ruins
|
||||
the user experience, the more grave problem is that the nVidia driver not only
|
||||
does not support dynamic configuration using RandR, it also does not expose
|
||||
correct multi-monitor information via the RandR API. So, in some setups, i3
|
||||
will not find any screens; in others, it will find one large screen which
|
||||
actually contains both of your physical screens (but it will not know that
|
||||
these are two screens).
|
||||
|
||||
For this very reason, we decided to implement the following workaround: As
|
||||
long as the nVidia driver does not support RandR, an option called
|
||||
+--force-xinerama+ is available in i3. This option gets the list of screens
|
||||
*once* when starting, and never updates it. As the nVidia driver cannot do
|
||||
dynamic configuration anyways, this is not a big deal.
|
||||
|
||||
== See also
|
||||
|
||||
For more information on how to use multi-monitor setups, see the i3 User’s
|
||||
Guide.
|
625
docs/userguide
625
docs/userguide
|
@ -1,64 +1,83 @@
|
|||
i3 User’s Guide
|
||||
===============
|
||||
Michael Stapelberg <michael+i3@stapelberg.de>
|
||||
August 2009
|
||||
March 2010
|
||||
|
||||
This document contains all information you need to configuring and using the i3
|
||||
This document contains all the information you need to configure and use the i3
|
||||
window manager. If it does not, please contact me on IRC, Jabber or E-Mail and
|
||||
I’ll help you out.
|
||||
|
||||
For a complete listing of the default keybindings, please see the manpage.
|
||||
== Default keybindings
|
||||
|
||||
For the "too long; didn’t read" people, here is an overview of the default
|
||||
keybindings (click to see the full size image):
|
||||
|
||||
*Keys to use with Mod1 (alt):*
|
||||
|
||||
image:keyboard-layer1.png["Keys to use with Mod1 (alt)",width=600,link="keyboard-layer1.png"]
|
||||
|
||||
*Keys to use with Shift+Mod1:*
|
||||
|
||||
image:keyboard-layer2.png["Keys to use with Shift+Mod1",width=600,link="keyboard-layer2.png"]
|
||||
|
||||
As i3 uses keycodes in the default configuration, it does not matter which
|
||||
keyboard layout you actually use. The key positions are what matters (of course
|
||||
you can also use keysymbols, see <<keybindings>>).
|
||||
|
||||
The red keys are the modifiers you need to press (by default), the blue keys
|
||||
are your homerow.
|
||||
|
||||
== Using i3
|
||||
|
||||
=== Creating terminals and moving around
|
||||
=== Opening terminals and moving around
|
||||
|
||||
A very basic operation is to create a new terminal. By default, the keybinding
|
||||
for that is Mod1+Enter, that is Alt+Enter in the default configuration. By
|
||||
pressing Mod1+Enter, a new terminal will be created and it will fill the whole
|
||||
space which is available on your screen.
|
||||
One very basic operation is opening a new terminal. By default, the keybinding
|
||||
for this is Mod1+Enter, that is Alt+Enter in the default configuration. By
|
||||
pressing Mod1+Enter, a new terminal will be opened. It will fill the whole
|
||||
space available on your screen.
|
||||
|
||||
image:single_terminal.png[Single terminal]
|
||||
|
||||
It is important to keep in mind that i3 uses a table to manage your windows. At
|
||||
the moment, you have exactly one column and one row which leaves you with one
|
||||
cell. In this cell, there is a container in which your newly opened terminal is.
|
||||
cell. In this cell there is a container, which is where your new terminal is
|
||||
opened.
|
||||
|
||||
If you now open another terminal, you still have only one cell. However, the
|
||||
container has both of your terminals. So, a container is just a group of clients
|
||||
with a specific layout. You can resize containers as they directly resemble
|
||||
columns/rows of the layout table.
|
||||
container in that cell holds both of your terminals. So, a container is just a
|
||||
group of clients with a specific layout. Containers can be resized by adjusting
|
||||
the size of the cell that holds them.
|
||||
|
||||
image:two_terminals.png[Two terminals]
|
||||
|
||||
To move the focus between the two terminals, you use the direction keys which
|
||||
you may know from the editor +vi+. However, in i3, your homerow is used for
|
||||
these keys (in +vi+, the keys are shifted to the left by one for compatibility
|
||||
with most keyboard layouts). Therefore, +Mod1+J+ is left, +Mod1+K+ is down, +Mod1+L+
|
||||
is up and `Mod1+;` is right. So, to switch between the terminals, use +Mod1+K+ or
|
||||
+Mod1+L+.
|
||||
with most keyboard layouts). Therefore, +Mod1+J+ is left, +Mod1+K+ is down,
|
||||
+Mod1+L+ is up and `Mod1+;` is right. So, to switch between the terminals,
|
||||
use +Mod1+K+ or +Mod1+L+.
|
||||
|
||||
To create a new row/column, you can simply move a terminal (or any other window)
|
||||
to the direction you want to expand your table. So, let’s expand the table to
|
||||
the right by pressing `Mod1+Shift+;`.
|
||||
To create a new row/column (and a new cell), you can simply move a terminal (or
|
||||
any other window) in the direction you want to expand your table. So, let’s
|
||||
expand the table to the right by pressing `Mod1+Shift+;`.
|
||||
|
||||
image:two_columns.png[Two columns]
|
||||
|
||||
=== Changing mode of containers
|
||||
=== Changing container modes
|
||||
|
||||
A container can be in different modes:
|
||||
A container can have the following modes:
|
||||
|
||||
default::
|
||||
Windows are sized so that every window gets an equal amount of space of the
|
||||
Windows are sized so that every window gets an equal amount of space in the
|
||||
container.
|
||||
stacking::
|
||||
Only the focused client of the container is displayed and you get a list of
|
||||
Only the focused window in the container is displayed. You get a list of
|
||||
windows at the top of the container.
|
||||
tabbed::
|
||||
The same principle as +stacking+, but the list of windows at the top is only
|
||||
a single line which will be vertically split.
|
||||
a single line which is vertically split.
|
||||
|
||||
To switch the mode, press +Mod1+e+ for default, +Mod1+h+ for stacking and
|
||||
To switch modes, press +Mod1+e+ for default, +Mod1+h+ for stacking and
|
||||
+Mod1+w+ for tabbed.
|
||||
|
||||
image:modes.png[Container modes]
|
||||
|
@ -68,25 +87,29 @@ image:modes.png[Container modes]
|
|||
To display a window fullscreen or to go out of fullscreen mode again, press
|
||||
+Mod1+f+.
|
||||
|
||||
There is also a global fullscreen mode in i3 in which the client will use all
|
||||
available outputs. To use it, or to get out of it again, press +Mod1+Shift+f+.
|
||||
|
||||
=== Opening other applications
|
||||
|
||||
Aside from opening applicatios from a terminal, you can also use the handy
|
||||
Aside from opening applications from a terminal, you can also use the handy
|
||||
+dmenu+ which is opened by pressing +Mod1+v+ by default. Just type the name
|
||||
(or a part of it) of the application which you want to open. It has to be in
|
||||
your +$PATH+ for that to work.
|
||||
(or a part of it) of the application which you want to open. The application
|
||||
typed has to be in your +$PATH+ for this to work.
|
||||
|
||||
Furthermore, if you have applications you open very frequently, you can also
|
||||
create a keybinding for it. See the section "Configuring i3" for details.
|
||||
Additionally, if you have applications you open very frequently, you can
|
||||
create a keybinding for starting the application directly. See the section
|
||||
"Configuring i3" for details.
|
||||
|
||||
=== Closing windows
|
||||
|
||||
If an application does not provide a mechanism to close (most applications
|
||||
If an application does not provide a mechanism for closing (most applications
|
||||
provide a menu, the escape key or a shortcut like +Control+W+ to close), you
|
||||
can press +Mod1+Shift+q+ to kill a window. For applications which support
|
||||
the WM_DELETE protocol, this will correctly close the application (saving
|
||||
any modifications or doing other cleanup). If the application doesn’t support
|
||||
it, your X server will kill the window and the behaviour depends on the
|
||||
application.
|
||||
the WM_DELETE protocol your X server will kill the window and the behaviour
|
||||
depends on the application.
|
||||
|
||||
=== Using workspaces
|
||||
|
||||
|
@ -96,13 +119,13 @@ another workspace, press +Mod1+num+ where +num+ is the number of the workspace
|
|||
you want to use. If the workspace does not exist yet, it will be created.
|
||||
|
||||
A common paradigm is to put the web browser on one workspace, communication
|
||||
applications (+mutt+, +irssi+, ...) on another one and the ones with which you
|
||||
work on the third one. Of course, there is no need to follow this approach.
|
||||
applications (+mutt+, +irssi+, ...) on another one, and the ones with which you
|
||||
work, on the third one. Of course, there is no need to follow this approach.
|
||||
|
||||
If you have multiple screens, a workspace will be created on each screen. If
|
||||
you open a new workspace, it will be bound to the screen you created it on.
|
||||
When you switch to a workspace on another screen, i3 will set focus to this
|
||||
screen.
|
||||
If you have multiple screens, a workspace will be created on each screen at
|
||||
startup. If you open a new workspace, it will be bound to the screen you
|
||||
created it on. When you switch to a workspace on another screen, i3 will set
|
||||
focus to that screen.
|
||||
|
||||
=== Moving windows to workspaces
|
||||
|
||||
|
@ -113,20 +136,22 @@ it does not yet exist.
|
|||
|
||||
=== Resizing columns/rows
|
||||
|
||||
To resize columns or rows just grab the border between the two columns/rows
|
||||
To resize columns or rows, just grab the border between the two columns/rows
|
||||
and move it to the wanted size. Please keep in mind that each cell of the table
|
||||
holds a +container+ and thus you cannot horizontally resize single windows.
|
||||
holds a +container+ and thus you cannot horizontally resize single windows. If
|
||||
you need applications with different horizontal sizes, place them in seperate
|
||||
cells one above the other.
|
||||
|
||||
See <<resizingconfig>> for how to configure i3 to be able to resize
|
||||
columns/rows with your keyboard.
|
||||
|
||||
=== Restarting i3 inplace
|
||||
|
||||
To restart i3 inplace (and thus get it into a clean state if it has a bug, to
|
||||
reload your configuration or even to upgrade to a newer version of i3) you
|
||||
can use +Mod1+Shift+r+. Be aware, though, that this kills your current layout
|
||||
and all the windows you have opened will be put in a default container in only
|
||||
one cell. Saving the layout will be implemented in a later version.
|
||||
To restart i3 inplace (and thus get into a clean state if there is a bug, or
|
||||
to upgrade to a newer version of i3) you can use +Mod1+Shift+r+. Be aware,
|
||||
though, that this kills your current layout and all the windows you have opened
|
||||
will be put in a default container in only one cell. Saving layouts will be
|
||||
implemented in a later version.
|
||||
|
||||
=== Exiting i3
|
||||
|
||||
|
@ -135,7 +160,7 @@ To cleanly exit i3 without killing your X server, you can use +Mod1+Shift+e+.
|
|||
=== Snapping
|
||||
|
||||
Snapping is a mechanism to increase/decrease the colspan/rowspan of a container.
|
||||
Colspan/rowspan is the amount of columns/rows a specific cell of the table
|
||||
Colspan/rowspan is the number of columns/rows a specific cell of the table
|
||||
consumes. This is easier explained by giving an example, so take the following
|
||||
layout:
|
||||
|
||||
|
@ -146,47 +171,67 @@ by pressing +Mod1+Control+k+ (or snap container 2 rightwards).
|
|||
|
||||
=== Floating
|
||||
|
||||
Floating is the opposite of tiling mode. The position and size of a window
|
||||
are then not managed by i3, but by you. Using this mode violates the tiling
|
||||
Floating mode is the opposite of tiling mode. The position and size of a window
|
||||
are not managed by i3, but by you. Using this mode violates the tiling
|
||||
paradigm but can be useful for some corner cases like "Save as" dialog
|
||||
windows or toolbar windows (GIMP or similar).
|
||||
windows, or toolbar windows (GIMP or similar).
|
||||
|
||||
You can enable floating for a window by pressing +Mod1+Shift+Space+. By
|
||||
dragging the window’s titlebar with your mouse, you can move the window
|
||||
around. By grabbing the borders and moving them you can resize the window.
|
||||
You can enable floating mode for a window by pressing +Mod1+Shift+Space+. By
|
||||
dragging the window’s titlebar with your mouse you can move the window
|
||||
around. By grabbing the borders and moving them you can resize the window. You
|
||||
can also do that by using the <<floating_modifier>>.
|
||||
|
||||
Bindings for doing this with your keyboard will follow.
|
||||
For resizing floating windows with your keyboard, see <<resizingconfig>>.
|
||||
|
||||
Floating clients are always on top of tiling clients.
|
||||
Floating windows are always on top of tiling windows.
|
||||
|
||||
== Configuring i3
|
||||
|
||||
This is where the real fun begins ;-). Most things are very dependant on your
|
||||
ideal working environment, so we can’t make reasonable defaults for them.
|
||||
ideal working environment so we can’t make reasonable defaults for them.
|
||||
|
||||
While not using a programming language for the configuration, i3 stays
|
||||
quite flexible regarding to the things you usually want your window manager
|
||||
quite flexible in regards to the things you usually want your window manager
|
||||
to do.
|
||||
|
||||
For example, you can configure bindings to jump to specific windows,
|
||||
you can set specific applications to start on a specific workspace, you can
|
||||
automatically start applications, you can change the colors of i3 or bind
|
||||
your keys to do useful stuff.
|
||||
you can set specific applications to start on specific workspaces, you can
|
||||
automatically start applications, you can change the colors of i3, and you
|
||||
can bind your keys to do useful things.
|
||||
|
||||
To change the configuration of i3, copy +/etc/i3/config+ to +~/.i3/config+
|
||||
and edit it with a text editor.
|
||||
To change the configuration of i3, copy +/etc/i3/config+ to +\~/.i3/config+
|
||||
(or +~/.config/i3/config+ if you like the XDG directory scheme) and edit it
|
||||
with a text editor.
|
||||
|
||||
=== General configuration
|
||||
=== Comments
|
||||
|
||||
terminal::
|
||||
Specifies the terminal emulator program you prefer. It will be started
|
||||
by default when you press Mod1+Enter, but you can overwrite this. Refer
|
||||
to it as +$terminal+ to keep things modular.
|
||||
font::
|
||||
Specifies the default font you want i3 to use. Use an X core font
|
||||
descriptor here, like
|
||||
+-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1+. You can
|
||||
use +xfontsel(1)+ to pick one.
|
||||
It is possible and recommended to use comments in your configuration file to
|
||||
properly document your setup for later reference. Comments are started with
|
||||
a # and can only be used at the beginning of a line:
|
||||
|
||||
*Examples*:
|
||||
-------------------
|
||||
# This is a comment
|
||||
-------------------
|
||||
|
||||
=== Fonts
|
||||
|
||||
i3 uses X core fonts (not Xft) for rendering window titles and the internal
|
||||
workspace bar. You can use +xfontsel(1)+ to generate such a font description.
|
||||
To see special characters (Unicode), you need to use a font which supports
|
||||
the ISO-10646 encoding.
|
||||
|
||||
*Syntax*:
|
||||
------------------------------
|
||||
font <X core font description>
|
||||
------------------------------
|
||||
|
||||
*Examples*:
|
||||
--------------------------------------------------------------
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
--------------------------------------------------------------
|
||||
|
||||
[[keybindings]]
|
||||
|
||||
=== Keyboard bindings
|
||||
|
||||
|
@ -194,19 +239,19 @@ A keyboard binding makes i3 execute a command (see below) upon pressing a
|
|||
specific key. i3 allows you to bind either on keycodes or on keysyms (you can
|
||||
also mix your bindings, though i3 will not protect you from overlapping ones).
|
||||
|
||||
* A keysym (key symbol) is a description for a specific symbol, like "a" or "b",
|
||||
but also more strange ones like "underscore" instead of "_". These are the ones
|
||||
you also use in Xmodmap to remap your keys. To get the current mapping of your
|
||||
keys, use +xmodmap -pke+.
|
||||
* A keysym (key symbol) is a description for a specific symbol, like "a"
|
||||
or "b", but also more strange ones like "underscore" instead of "_". These
|
||||
are the ones you use in Xmodmap to remap your keys. To get the current
|
||||
mapping of your keys, use +xmodmap -pke+.
|
||||
|
||||
* Keycodes however do not need to have a symbol assigned (handy for some hotkeys
|
||||
* Keycodes do not need to have a symbol assigned (handy for some hotkeys
|
||||
on some notebooks) and they will not change their meaning as you switch to a
|
||||
different keyboard layout.
|
||||
different keyboard layout (when using +xmodmap+).
|
||||
|
||||
My recommendation is: If you often switch keyboard layouts because you try to
|
||||
learn a different one, but you want to keep your bindings at the same place,
|
||||
use keycodes. If you don’t switch layouts and like a clean and simple config
|
||||
file, use keysyms.
|
||||
My recommendation is: If you often switch keyboard layouts but you want to keep
|
||||
your bindings in the same physical location on the keyboard, use keycodes.
|
||||
If you don’t switch layouts, and want a clean and simple config file, use
|
||||
keysyms.
|
||||
|
||||
*Syntax*:
|
||||
----------------------------------
|
||||
|
@ -217,10 +262,10 @@ bind [Modifiers+]keycode command
|
|||
*Examples*:
|
||||
--------------------------------
|
||||
# Fullscreen
|
||||
bind Mod1+f f
|
||||
bindsym Mod1+f f
|
||||
|
||||
# Restart
|
||||
bind Mod1+Shift+r restart
|
||||
bindsym Mod1+Shift+r restart
|
||||
|
||||
# Notebook-specific hotkeys
|
||||
bind 214 exec /home/michael/toggle_beamer.sh
|
||||
|
@ -238,14 +283,20 @@ umlauts or special characters 'and' having some comfortably reachable key
|
|||
bindings. For example, when typing, capslock+1 or capslock+2 for switching
|
||||
workspaces is totally convenient. Try it :-).
|
||||
|
||||
[[floating_modifier]]
|
||||
|
||||
=== The floating modifier
|
||||
|
||||
To move floating windows with your mouse, you can either grab their titlebar
|
||||
or configure the so called floating modifier which you can then press and
|
||||
click anywhere in the window itself. The most common setup is to configure
|
||||
it as the same one you use for managing windows (Mod1 for example). Afterwards,
|
||||
you can press Mod1, click into a window using your left mouse button and drag
|
||||
it to the position you want it at.
|
||||
click anywhere in the window itself to move it. The most common setup is to
|
||||
use the same key you use for managing windows (Mod1 for example). Then
|
||||
you can press Mod1, click into a window using your left mouse button, and drag
|
||||
it to the position you want.
|
||||
|
||||
When holding the floating modifier, you can resize a floating window by
|
||||
pressing the right mouse button on it and moving around while holding it. If
|
||||
you hold the shift button as well, the resize will be proportional.
|
||||
|
||||
*Syntax*:
|
||||
--------------------------------
|
||||
|
@ -259,8 +310,7 @@ floating_modifier Mod1
|
|||
|
||||
=== Layout mode for new containers
|
||||
|
||||
This option is only available when using the new lexer/parser (pass +-l+ to i3
|
||||
when starting). It determines in which mode new containers will start. See also
|
||||
This option determines in which mode new containers will start. See also
|
||||
<<stack-limit>>.
|
||||
|
||||
*Syntax*:
|
||||
|
@ -276,8 +326,7 @@ new_container tabbed
|
|||
|
||||
=== Border style for new windows
|
||||
|
||||
This option is only available when using the new lexer/parser (pass +-l+ to i3
|
||||
when starting). It determines which border new windows will have.
|
||||
This option determines which border style new windows will have.
|
||||
|
||||
*Syntax*:
|
||||
---------------------------------------------
|
||||
|
@ -291,10 +340,10 @@ new_window bp
|
|||
|
||||
=== Variables
|
||||
|
||||
As you learned in the previous section about keyboard bindings, you will have
|
||||
As you learned in the section about keyboard bindings, you will have
|
||||
to configure lots of bindings containing modifier keys. If you want to save
|
||||
yourself some typing and have the possibility to change the modifier you want
|
||||
to use later, variables can be handy.
|
||||
yourself some typing and be able to change the modifier you use later,
|
||||
variables can be handy.
|
||||
|
||||
*Syntax*:
|
||||
--------------
|
||||
|
@ -307,20 +356,23 @@ set $m Mod1
|
|||
bindsym $m+Shift+r restart
|
||||
------------------------
|
||||
|
||||
Variables are directly replaced in the file when parsing, there is no fancy
|
||||
Variables are directly replaced in the file when parsing. There is no fancy
|
||||
handling and there are absolutely no plans to change this. If you need a more
|
||||
dynamic configuration, you should create a little script, like when configuring
|
||||
wmii.
|
||||
dynamic configuration you should create a little script which generates a
|
||||
configuration file and run it before starting i3 (for example in your
|
||||
+.xsession+ file).
|
||||
|
||||
=== Automatically putting clients on specific workspaces
|
||||
|
||||
It is recommended that you match on window classes whereever possible because
|
||||
some applications first create their window and then care about setting the
|
||||
correct title. Firefox with Vimperator comes to mind, as the window starts up
|
||||
being named Firefox and only when Vimperator is loaded, the title changes. As
|
||||
i3 will get the title as soon as the application maps the window (mapping means
|
||||
actually displaying it on the screen), you’d need to have to match on Firefox
|
||||
in this case.
|
||||
[[assign_workspace]]
|
||||
|
||||
It is recommended that you match on window classes wherever possible because
|
||||
some applications first create their window, and then worry about setting the
|
||||
correct title. Firefox with Vimperator comes to mind. The window starts up
|
||||
being named Firefox, and only when Vimperator is loaded does the title change.
|
||||
As i3 will get the title as soon as the application maps the window (mapping
|
||||
means actually displaying it on the screen), you’d need to have to match on
|
||||
'Firefox' in this case.
|
||||
|
||||
You can prefix or suffix workspaces with a `~` to specify that matching clients
|
||||
should be put into floating mode. If you specify only a `~`, the client will
|
||||
|
@ -341,11 +393,14 @@ assign "gecko" → ~4
|
|||
assign "xv/MPlayer" → ~
|
||||
----------------------
|
||||
|
||||
=== Automatically starting applications on startup
|
||||
Note that the arrow is not required, it just looks good :-). If you decide to
|
||||
use it, it has to be a UTF-8 encoded arrow, not "->" or something like that.
|
||||
|
||||
=== Automatically starting applications on i3 startup
|
||||
|
||||
By using the +exec+ keyword outside a keybinding, you can configure which
|
||||
commands will be performed by i3 on the first start (not when reloading inplace
|
||||
however). The commands will be run in order.
|
||||
commands will be performed by i3 on initial startup (not when restarting i3
|
||||
in-place however). These commands will be run in order.
|
||||
|
||||
*Syntax*:
|
||||
------------
|
||||
|
@ -357,35 +412,29 @@ exec command
|
|||
exec sudo i3status | dzen2 -dock
|
||||
--------------------------------
|
||||
|
||||
[[workspace_screen]]
|
||||
|
||||
=== Automatically putting workspaces on specific screens
|
||||
|
||||
If you use the assigning of clients to workspaces and start some clients
|
||||
automatically, it might be handy to put the workspaces on specific screens.
|
||||
Also, the assignment of workspaces to screens will determine the workspace
|
||||
which i3 uses for a new screen when adding screens or when starting (e.g., by
|
||||
default it will use 1 for the first screen, 2 for the second screen and so on).
|
||||
If you assign clients to workspaces, it might be handy to put the
|
||||
workspaces on specific screens. Also, the assignment of workspaces to screens
|
||||
will determine which workspace i3 uses for a new screen when adding screens
|
||||
or when starting (e.g., by default it will use 1 for the first screen, 2 for
|
||||
the second screen and so on).
|
||||
|
||||
*Syntax*:
|
||||
----------------------------------
|
||||
workspace <number> screen <screen>
|
||||
workspace <number> output <output>
|
||||
----------------------------------
|
||||
|
||||
Screen can be either a number (starting at 0 for the first screen) or a
|
||||
position. When using numbers, it is not guaranteed that your screens always
|
||||
get the same number. Though, unless you upgrade your X server or drivers, the
|
||||
order usually stays the same. When using positions, you have to specify the
|
||||
exact pixel where the screen *starts*, not a pixel which is contained by the
|
||||
screen. Thus, if your first screen has the dimensions 1280x800, you can match
|
||||
the second screen right of it by specifying 1280. You cannot use 1281.
|
||||
The 'output' is the name of the RandR output you attach your screen to. On a
|
||||
laptop, you might have VGA1 and LVDS1 as output names. You can see the
|
||||
available outputs by running +xrandr --current+.
|
||||
|
||||
*Examples*:
|
||||
---------------------------
|
||||
workspace 1 screen 0
|
||||
workspace 5 screen 1
|
||||
|
||||
workspace 1 screen 1280
|
||||
workspace 2 screen x800
|
||||
workspace 3 screen 1280x800
|
||||
workspace 1 output LVDS1
|
||||
workspace 5 output VGA1
|
||||
---------------------------
|
||||
|
||||
=== Named workspaces
|
||||
|
@ -396,10 +445,10 @@ them names (of course UTF-8 is supported):
|
|||
*Syntax*:
|
||||
---------------------------------------
|
||||
workspace <number> <name>
|
||||
workspace <number> screen <screen> name
|
||||
workspace <number> output <output> name
|
||||
---------------------------------------
|
||||
|
||||
For more details about the screen-part of this command, see above.
|
||||
For more details about the 'output' part of this command, see above.
|
||||
|
||||
*Examples*:
|
||||
--------------------------
|
||||
|
@ -436,7 +485,7 @@ bar.unfocused::
|
|||
bar.urgent::
|
||||
A workspace which has at least one client with an activated urgency hint.
|
||||
|
||||
Colors are in HTML hex format, see below.
|
||||
Colors are in HTML hex format (#rrggbb), see the following example:
|
||||
|
||||
*Examples*:
|
||||
--------------------------------------
|
||||
|
@ -444,19 +493,64 @@ Colors are in HTML hex format, see below.
|
|||
client.focused #2F343A #900000 #FFFFFF
|
||||
--------------------------------------
|
||||
|
||||
Note that for the window decorations, the color around the child window is the
|
||||
background color, and the border color is only the two thin lines at the top of
|
||||
the window.
|
||||
|
||||
=== Interprocess communication
|
||||
|
||||
i3 uses unix sockets to provide an IPC interface. At the moment, this interface
|
||||
is only useful for sending commands. To enable it, you have to configure a path
|
||||
where the unix socket will be stored. The default path is +/tmp/i3-ipc.sock+.
|
||||
i3 uses unix sockets to provide an IPC interface. This allows third-party
|
||||
programs to get information from i3, such as the current workspaces
|
||||
(to display a workspace bar), and to control i3.
|
||||
|
||||
To enable it, you have to configure a path where the unix socket will be
|
||||
stored. The default path is +~/.i3/ipc.sock+.
|
||||
|
||||
*Examples*:
|
||||
----------------------------
|
||||
ipc-socket /tmp/i3-ipc.sock
|
||||
ipc-socket ~/.i3/ipc.sock
|
||||
----------------------------
|
||||
|
||||
You can then use the i3-msg command to perform any command listed in the next
|
||||
section.
|
||||
You can then use the +i3-msg+ application to perform any command listed in
|
||||
the next section.
|
||||
|
||||
=== Disable focus follows mouse
|
||||
|
||||
If you have a setup where your mouse usually is in your way (like a touchpad
|
||||
on your laptop which you do not want to disable completely), you might want
|
||||
to disable 'focus follows mouse' and control focus only by using your keyboard.
|
||||
The mouse will still be useful inside the currently active window (for example
|
||||
to click on links in your browser window).
|
||||
|
||||
*Syntax*:
|
||||
----------------------------
|
||||
focus_follows_mouse <yes|no>
|
||||
----------------------------
|
||||
|
||||
*Examples*:
|
||||
----------------------
|
||||
focus_follows_mouse no
|
||||
----------------------
|
||||
|
||||
=== Internal workspace bar
|
||||
|
||||
The internal workspace bar (the thing at the bottom of your screen) is very
|
||||
simple -- it does not provide a way to display custom text and it does not
|
||||
offer advanced customization features. This is intended because we do not
|
||||
want to duplicate functionality of tools like +dzen2+, +xmobar+ and so on
|
||||
(they render bars, we manage windows). Instead, there is an option which will
|
||||
turn off the internal bar completely, so that you can use a separate program to
|
||||
display it (see +i3-wsbar+, a sample implementation of such a program):
|
||||
|
||||
*Syntax*:
|
||||
----------------------
|
||||
workspace_bar <yes|no>
|
||||
----------------------
|
||||
|
||||
*Examples*:
|
||||
----------------
|
||||
workspace_bar no
|
||||
----------------
|
||||
|
||||
== List of commands
|
||||
|
||||
|
@ -464,7 +558,8 @@ section.
|
|||
|
||||
To change the layout of the current container to stacking, use +s+, for default
|
||||
use +d+ and for tabbed, use +T+. To make the current client (!) fullscreen,
|
||||
use +f+, to make it floating (or tiling again) use +t+:
|
||||
use +f+, to make it span all outputs, use +fg+, to make it floating (or
|
||||
tiling again) use +t+:
|
||||
|
||||
*Examples*:
|
||||
--------------
|
||||
|
@ -475,17 +570,20 @@ bindsym Mod1+w T
|
|||
# Toggle fullscreen
|
||||
bindsym Mod1+f f
|
||||
|
||||
# Toggle global fullscreen
|
||||
bindsym Mod1+Shift+f fg
|
||||
|
||||
# Toggle floating/tiling
|
||||
bindsym Mod1+t t
|
||||
--------------
|
||||
|
||||
=== Focussing/Moving/Snapping clients/containers/screens
|
||||
=== Focusing/Moving/Snapping clients/containers/screens
|
||||
|
||||
To change the focus, use one of the +h+, +j+, +k+ and +l+ commands, meaning
|
||||
respectively left, down, up, right. To focus a container, prefix it with +wc+,
|
||||
to focus a screen, prefix it with +ws+.
|
||||
left, down, up, right (respectively). To focus a container, prefix it with
|
||||
+wc+. To focus a screen, prefix it with +ws+.
|
||||
|
||||
The same principle applies for moving and snapping, just prefix the command
|
||||
The same principle applies for moving and snapping: just prefix the command
|
||||
with +m+ when moving and with +s+ when snapping:
|
||||
|
||||
*Examples*:
|
||||
|
@ -519,9 +617,9 @@ To change to a specific workspace, the command is just the number of the
|
|||
workspace, e.g. +1+ or +3+. To move the current client to a specific workspace,
|
||||
prefix the number with an +m+.
|
||||
|
||||
Furthermore, you can switch to the next and previous workspace with the
|
||||
commands +nw+ and +pw+, which is handy for example if you have workspace
|
||||
1, 3, 4 and 9 and you want to cycle through them with a single key combination.
|
||||
You can also switch to the next and previous workspace with the commands +nw+
|
||||
and +pw+, which is handy, for example, if you have workspace 1, 3, 4 and 9 and
|
||||
you want to cycle through them with a single key combination.
|
||||
|
||||
*Examples*:
|
||||
-------------------------
|
||||
|
@ -542,8 +640,7 @@ bindsym Mod1+p pw
|
|||
=== Resizing columns/rows
|
||||
|
||||
If you want to resize columns/rows using your keyboard, you can use the
|
||||
+resize+ command, I recommend using it a +mode+ (you need to use the new
|
||||
lexer/parser for that, so pass +-l+ to i3 when starting):
|
||||
+resize+ command, I recommend using it inside a so called +mode+:
|
||||
|
||||
.Example: Configuration file, defining a mode for resizing
|
||||
----------------------------------------------------------------------
|
||||
|
@ -568,15 +665,18 @@ mode "resize" {
|
|||
|
||||
bind 36 mode default
|
||||
}
|
||||
|
||||
# Enter resize mode
|
||||
bindsym Mod1+r mode resize
|
||||
----------------------------------------------------------------------
|
||||
|
||||
=== Jumping to specific windows
|
||||
|
||||
Especially when in a multi-monitor environment, you want to quickly jump to a specific
|
||||
window, for example while currently working on workspace 3 you may want to jump to
|
||||
your mailclient to mail your boss that you’ve achieved some important goal. Instead
|
||||
of figuring out how to navigate to your mailclient, it would be more convenient to
|
||||
have a shortcut.
|
||||
Often when in a multi-monitor environment, you want to quickly jump to a
|
||||
specific window. For example, while working on workspace 3 you may want to
|
||||
jump to your mail client to email your boss that you’ve achieved some
|
||||
important goal. Instead of figuring out how to navigate to your mailclient,
|
||||
it would be more convenient to have a shortcut.
|
||||
|
||||
*Syntax*:
|
||||
----------------------------------------------------
|
||||
|
@ -584,8 +684,9 @@ jump ["]window class[/window title]["]
|
|||
jump workspace [ column row ]
|
||||
----------------------------------------------------
|
||||
|
||||
You can either use the same matching algorithm as in the +assign+ command (see above)
|
||||
or you can specify the position of the client if you always use the same layout.
|
||||
You can either use the same matching algorithm as in the +assign+ command
|
||||
(see above) or you can specify the position of the client if you always use
|
||||
the same layout.
|
||||
|
||||
*Examples*:
|
||||
--------------------------------------
|
||||
|
@ -595,19 +696,19 @@ bindsym Mod1+a jump "urxvt/VIM"
|
|||
|
||||
=== VIM-like marks (mark/goto)
|
||||
|
||||
[[vim_like_marks]]
|
||||
|
||||
This feature is like the jump feature: It allows you to directly jump to a
|
||||
specific window (this means switching to the appropriate workspace and setting
|
||||
focus to the windows). However, you can directly mark a specific window with
|
||||
an arbitrary label and use it afterwards, that is, you do not need to ensure
|
||||
that your windows have unique classes or titles and you do not need to change
|
||||
your configuration file.
|
||||
an arbitrary label and use it afterwards. You do not need to ensure that your
|
||||
windows have unique classes or titles, and you do not need to change your
|
||||
configuration file.
|
||||
|
||||
As the command needs to include the label with which you want to mark the
|
||||
window, you cannot simply bind it to a key (or, you could bind it to a key and
|
||||
only use the set of labels for which you created bindings). +i3-input+ is a
|
||||
tool created for this purpose: It lets you input a command and sends the
|
||||
command to i3. It can also prefix this command and display a custom prompt for
|
||||
the input dialog.
|
||||
window, you cannot simply bind it to a key. +i3-input+ is a tool created
|
||||
for this purpose: It lets you input a command and sends the command to i3. It
|
||||
can also prefix this command and display a custom prompt for the input dialog.
|
||||
|
||||
*Syntax*:
|
||||
-----------------
|
||||
|
@ -624,18 +725,21 @@ bindsym Mod1+m exec i3-input -p 'mark ' -l 1 -P 'Mark: '
|
|||
bindsym Mod1+g exec i3-input -p 'goto ' -l 1 -P 'Goto: '
|
||||
---------------------------------------
|
||||
|
||||
Alternatively, if you do not want to mess with +i3-input+, you could create
|
||||
seperate bindings for a specific set of labels and then only use those labels.
|
||||
|
||||
=== Traveling the focus stack
|
||||
|
||||
This mechanism can be thought of as the opposite of the +jump+ command. It travels
|
||||
the focus stack and jumps to the window you focused before.
|
||||
This mechanism can be thought of as the opposite of the +jump+ command.
|
||||
It travels the focus stack and jumps to the window which had focus previously.
|
||||
|
||||
*Syntax*:
|
||||
--------------
|
||||
focus [number] | floating | tilling | ft
|
||||
focus [number] | floating | tiling | ft
|
||||
--------------
|
||||
|
||||
Where +number+ by default is 1 meaning that the next client in the focus stack will
|
||||
be selected.
|
||||
Where +number+ by default is 1 meaning that the next client in the focus stack
|
||||
will be selected.
|
||||
|
||||
The special values have the following meaning:
|
||||
|
||||
|
@ -644,14 +748,14 @@ floating::
|
|||
tiling::
|
||||
The next tiling window is selected.
|
||||
ft::
|
||||
If the current window is floating, the next tiling window will be selected
|
||||
and vice-versa.
|
||||
If the current window is floating, the next tiling window will be
|
||||
selected; and vice-versa.
|
||||
|
||||
=== Changing border style
|
||||
|
||||
To change the border of the current client, you can use +bn+ to use the normal
|
||||
border (including window title), +bp+ to use a 1-pixel border (no window title)
|
||||
and +bb+ to make the client borderless. There also is +bt+ which will toggle
|
||||
and +bb+ to make the client borderless. There is also +bt+ which will toggle
|
||||
the different border styles.
|
||||
|
||||
*Examples*:
|
||||
|
@ -665,12 +769,12 @@ bindsym Mod1+u bb
|
|||
|
||||
=== Changing the stack-limit of a container
|
||||
|
||||
If you have a single container with a lot of windows inside (say, more than
|
||||
If you have a single container with a lot of windows inside it (say, more than
|
||||
10), the default layout of a stacking container can get a little unhandy.
|
||||
Depending on your screen’s size, you might end up only using half of the
|
||||
titlebars of each window in the container.
|
||||
Depending on your screen’s size, you might end up seeing only half of the
|
||||
titlebars for each window in the container.
|
||||
|
||||
Using the +stack-limit+ command, you can limit the amount of rows or columns
|
||||
Using the +stack-limit+ command, you can limit the number of rows or columns
|
||||
in a stacking container. i3 will create columns or rows (depending on what
|
||||
you limited) automatically as needed.
|
||||
|
||||
|
@ -696,9 +800,9 @@ You can make i3 reload its configuration file with +reload+. You can also
|
|||
restart i3 inplace with the +restart+ command to get it out of some weird state
|
||||
(if that should ever happen) or to perform an upgrade without having to restart
|
||||
your X session. However, your layout is not preserved at the moment, meaning
|
||||
that all open windows will be in a single container in default layout. To exit
|
||||
i3 properly, you can use the +exit+ command, however you don’t need to (e.g.,
|
||||
simply killing your X session is fine aswell).
|
||||
that all open windows will end up in a single container in default layout
|
||||
after the restart. To exit i3 properly, you can use the +exit+ command,
|
||||
however you don’t need to (simply killing your X session is fine as well).
|
||||
|
||||
*Examples*:
|
||||
----------------------------
|
||||
|
@ -706,3 +810,174 @@ bindsym Mod1+Shift+r restart
|
|||
bindsym Mod1+Shift+w reload
|
||||
bindsym Mod1+Shift+e exit
|
||||
----------------------------
|
||||
|
||||
[[multi_monitor]]
|
||||
|
||||
== Multiple monitors
|
||||
|
||||
As you can see in the goal list on the website, i3 was specifically developed
|
||||
with support for multiple monitors in mind. This section will explain how to
|
||||
handle multiple monitors.
|
||||
|
||||
When you have only one monitor, things are simple. You usually start with
|
||||
workspace 1 on your monitor and open new ones as you need them.
|
||||
|
||||
When you have more than one monitor, each monitor will get an initial
|
||||
workspace. The first monitor gets 1, the second gets 2 and a possible third
|
||||
would get 3. When you switch to a workspace on a different monitor, i3 will
|
||||
switch to that monitor and then switch to the workspace. This way, you don’t
|
||||
need shortcuts to switch to a specific monitor, and you don’t need to remember
|
||||
where you put which workspace. New workspaces will be opened on the currently
|
||||
active monitor. It is not possible to have a monitor without a workspace.
|
||||
|
||||
The idea of making workspaces global is based on the observation that most
|
||||
users have a very limited set of workspaces on their additional monitors.
|
||||
They are often used for a specific task (browser, shell) or for monitoring
|
||||
several things (mail, IRC, syslog, …). Thus, using one workspace on one monitor
|
||||
and "the rest" on the other monitors often makes sense. However, as you can
|
||||
create an unlimited number of workspaces in i3 and tie them to specific
|
||||
screens, you can have the "traditional" approach of having X workspaces per
|
||||
screen by changing your configuration (using modes, for example).
|
||||
|
||||
=== Configuring your monitors
|
||||
|
||||
To help you get going if you have never used multiple monitors before, here is
|
||||
a short overview of the xrandr options which will probably be of interest to
|
||||
you. It is always useful to get an overview of the current screen configuration.
|
||||
Just run "xrandr" and you will get an output like the following:
|
||||
-------------------------------------------------------------------------------
|
||||
$ xrandr
|
||||
Screen 0: minimum 320 x 200, current 1280 x 800, maximum 8192 x 8192
|
||||
VGA1 disconnected (normal left inverted right x axis y axis)
|
||||
LVDS1 connected 1280x800+0+0 (normal left inverted right x axis y axis) 261mm x 163mm
|
||||
1280x800 60.0*+ 50.0
|
||||
1024x768 85.0 75.0 70.1 60.0
|
||||
832x624 74.6
|
||||
800x600 85.1 72.2 75.0 60.3 56.2
|
||||
640x480 85.0 72.8 75.0 59.9
|
||||
720x400 85.0
|
||||
640x400 85.1
|
||||
640x350 85.1
|
||||
--------------------------------------------------------------------------------------
|
||||
|
||||
Several things are important here: You can see that +LVDS1+ is connected (of
|
||||
course, it is the internal flat panel) but +VGA1+ is not. If you have a monitor
|
||||
connected to one of the ports but xrandr still says "disconnected", you should
|
||||
check your cable, monitor or graphics driver.
|
||||
|
||||
The maximum resolution you can see at the end of the first line is the maximum
|
||||
combined resolution of your monitors. By default, it is usually too low and has
|
||||
to be increased by editing +/etc/X11/xorg.conf+.
|
||||
|
||||
So, say you connected VGA1 and want to use it as an additional screen:
|
||||
-------------------------------------------
|
||||
xrandr --output VGA1 --auto --left-of LVDS1
|
||||
-------------------------------------------
|
||||
This command makes xrandr try to find the native resolution of the device
|
||||
connected to +VGA1+ and configures it to the left of your internal flat panel.
|
||||
When running "xrandr" again, the output looks like this:
|
||||
-------------------------------------------------------------------------------
|
||||
$ xrandr
|
||||
Screen 0: minimum 320 x 200, current 2560 x 1024, maximum 8192 x 8192
|
||||
VGA1 connected 1280x1024+0+0 (normal left inverted right x axis y axis) 338mm x 270mm
|
||||
1280x1024 60.0*+ 75.0
|
||||
1280x960 60.0
|
||||
1152x864 75.0
|
||||
1024x768 75.1 70.1 60.0
|
||||
832x624 74.6
|
||||
800x600 72.2 75.0 60.3 56.2
|
||||
640x480 72.8 75.0 66.7 60.0
|
||||
720x400 70.1
|
||||
LVDS1 connected 1280x800+1280+0 (normal left inverted right x axis y axis) 261mm x 163mm
|
||||
1280x800 60.0*+ 50.0
|
||||
1024x768 85.0 75.0 70.1 60.0
|
||||
832x624 74.6
|
||||
800x600 85.1 72.2 75.0 60.3 56.2
|
||||
640x480 85.0 72.8 75.0 59.9
|
||||
720x400 85.0
|
||||
640x400 85.1
|
||||
640x350 85.1
|
||||
-------------------------------------------------------------------------------
|
||||
Please note that i3 uses exactly the same API as xrandr does, so it will see
|
||||
only what you can see in xrandr.
|
||||
|
||||
See also <<presentations>> for more examples of multi-monitor setups.
|
||||
|
||||
=== Interesting configuration for multi-monitor environments
|
||||
|
||||
There are several things to configure in i3 which may be interesting if you
|
||||
have more than one monitor:
|
||||
|
||||
1. You can specify which workspace should be put on which screen. This
|
||||
allows you to have a different set of workspaces when starting than just
|
||||
1 for the first monitor, 2 for the second and so on. See
|
||||
<<workspace_screen>>.
|
||||
2. If you want some applications to generally open on the bigger screen
|
||||
(MPlayer, Firefox, …), you can assign them to a specific workspace, see
|
||||
<<assign_workspace>>.
|
||||
3. If you have many workspaces on many monitors, it might get hard to keep
|
||||
track of which window you put where. Thus, you can use vim-like marks to
|
||||
quickly switch between windows. See <<vim_like_marks>>.
|
||||
|
||||
== i3 and the rest of your software world
|
||||
|
||||
=== Displaying a status line
|
||||
|
||||
A very common thing amongst users of exotic window managers is a status line at
|
||||
some corner of the screen. It is an often superior replacement to the widget
|
||||
approach you have in the task bar of a traditional desktop environment.
|
||||
|
||||
If you don’t already have your favorite way of generating such a status line
|
||||
(self-written scripts, conky, …), then i3status is the recommended tool for
|
||||
this task. It was written in C with the goal of using as few syscalls as
|
||||
possible to reduce the time your CPU is woken up from sleep states.
|
||||
|
||||
Regardless of which application you use to generate the status line, you
|
||||
want to make sure that the application does one of the following things:
|
||||
|
||||
1. Register as a dock window using EWMH hints. This will make i3 position the
|
||||
window above the workspace bar but below every other client. This is the
|
||||
recommended way, but in case of dzen2, for example, you need to check out
|
||||
the source of dzen2 from subversion, as the -dock option is not present
|
||||
in the released versions.
|
||||
2. Overlay the internal workspace bar. This method will not waste any space
|
||||
on the workspace bar, however, it is rather hackish. Just configure
|
||||
the output window to be over the workspace bar (say -x 200 and -y 780 if
|
||||
your screen is 800 px height).
|
||||
|
||||
The planned solution for this problem is to make the workspace bar optional
|
||||
and switch to a third party application completely (dzen2 for example)
|
||||
which will then contain the workspace bar.
|
||||
|
||||
=== Giving presentations (multi-monitor)
|
||||
|
||||
When giving a presentation, you typically want the audience to see what you see
|
||||
on your screen and then go through a series of slides (if the presentation is
|
||||
simple). For more complex presentations, you might want to have some notes
|
||||
which only you can see on your screen, while the audience can only see the
|
||||
slides.
|
||||
|
||||
[[presentations]]
|
||||
==== Case 1: everybody gets the same output
|
||||
This is the simple case. You connect your computer to the video projector,
|
||||
turn on both (computer and video projector) and configure your X server to
|
||||
clone the internal flat panel of your computer to the video output:
|
||||
-----------------------------------------------------
|
||||
xrandr --output VGA1 --mode 1024x768 --same-as LVDS1
|
||||
-----------------------------------------------------
|
||||
i3 will then use the lowest common subset of screen resolutions, the rest of
|
||||
your screen will be left untouched (it will show the X background). So, in
|
||||
our example, this would be 1024x768 (my notebook has 1280x800).
|
||||
|
||||
==== Case 2: you can see more than your audience
|
||||
This case is a bit harder. First of all, you should configure the VGA output
|
||||
somewhere near your internal flat panel, say right of it:
|
||||
-----------------------------------------------------
|
||||
xrandr --output VGA1 --mode 1024x768 --right-of LVDS1
|
||||
-----------------------------------------------------
|
||||
Now, i3 will put a new workspace (depending on your settings) on the new screen
|
||||
and you are in multi-monitor mode (see <<multi_monitor>>).
|
||||
|
||||
Because i3 is not a compositing window manager, there is no ability to
|
||||
display a window on two screens at the same time. Instead, your presentation
|
||||
software needs to do this job (that is, open a window on each screen).
|
||||
|
|
|
@ -18,8 +18,8 @@ all: ${FILES}
|
|||
|
||||
install: all
|
||||
echo "INSTALL"
|
||||
$(INSTALL) -d -m 0755 $(DESTDIR)/usr/bin
|
||||
$(INSTALL) -m 0755 i3-input $(DESTDIR)/usr/bin/
|
||||
$(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
|
||||
$(INSTALL) -m 0755 i3-input $(DESTDIR)$(PREFIX)/bin/
|
||||
|
||||
clean:
|
||||
rm -f *.o
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
char *convert_ucs_to_utf8(char *input);
|
||||
char *convert_utf8_to_ucs2(char *input, int *real_strlen);
|
||||
uint32_t get_colorpixel(xcb_connection_t *conn, char *hex);
|
||||
uint32_t get_mode_switch_mask(xcb_connection_t *conn);
|
||||
uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode);
|
||||
int connect_ipc(char *socket_path);
|
||||
void ipc_send_message(int sockfd, uint32_t message_size,
|
||||
uint32_t message_type, uint8_t *payload);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
|
@ -22,6 +22,7 @@
|
|||
#include <err.h>
|
||||
#include <stdint.h>
|
||||
#include <getopt.h>
|
||||
#include <glob.h>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_aux.h>
|
||||
|
@ -37,6 +38,7 @@
|
|||
static int sockfd;
|
||||
static xcb_key_symbols_t *symbols;
|
||||
static int modeswitchmask;
|
||||
static int numlockmask;
|
||||
static bool modeswitch_active = false;
|
||||
static xcb_window_t win;
|
||||
static xcb_pixmap_t pixmap;
|
||||
|
@ -50,6 +52,21 @@ static char *prompt;
|
|||
static int prompt_len;
|
||||
static int limit;
|
||||
|
||||
/*
|
||||
* This function resolves ~ in pathnames (and more, see glob(3)).
|
||||
*
|
||||
*/
|
||||
static char *glob_path(const char *path) {
|
||||
static glob_t globbuf;
|
||||
if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0)
|
||||
errx(EXIT_FAILURE, "glob() failed");
|
||||
char *result = strdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path);
|
||||
if (result == NULL)
|
||||
err(EXIT_FAILURE, "malloc() failed");
|
||||
globfree(&globbuf);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Concats the glyphs (either UCS-2 or UTF-8) to a single string, suitable for
|
||||
* rendering it (UCS-2) or sending it to i3 (UTF-8).
|
||||
|
@ -119,6 +136,9 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t
|
|||
static int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_release_event_t *event) {
|
||||
printf("releasing %d, state raw = %d\n", event->detail, event->state);
|
||||
|
||||
/* fix state */
|
||||
event->state &= ~numlockmask;
|
||||
|
||||
xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state);
|
||||
if (sym == XK_Mode_switch) {
|
||||
printf("Mode switch disabled\n");
|
||||
|
@ -163,6 +183,11 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
|
|||
if (modeswitch_active)
|
||||
event->state |= modeswitchmask;
|
||||
|
||||
/* Apparantly, after activating numlock once, the numlock modifier
|
||||
* stays turned on (use xev(1) to verify). So, to resolve useful
|
||||
* keysyms, we remove the numlock flag from the event state */
|
||||
event->state &= ~numlockmask;
|
||||
|
||||
xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state);
|
||||
if (sym == XK_Mode_switch) {
|
||||
printf("Mode switch enabled\n");
|
||||
|
@ -232,7 +257,7 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
|
|||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
char *socket_path = "/tmp/i3-ipc.sock";
|
||||
char *socket_path = glob_path("~/.i3/ipc.sock");
|
||||
char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
|
||||
int o, option_index = 0;
|
||||
|
||||
|
@ -251,7 +276,7 @@ int main(int argc, char *argv[]) {
|
|||
while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
|
||||
switch (o) {
|
||||
case 's':
|
||||
socket_path = strdup(optarg);
|
||||
socket_path = glob_path(optarg);
|
||||
break;
|
||||
case 'v':
|
||||
printf("i3-input " I3_VERSION);
|
||||
|
@ -290,7 +315,8 @@ int main(int argc, char *argv[]) {
|
|||
xcb_event_set_key_release_handler(&evenths, handle_key_release, NULL);
|
||||
xcb_event_set_expose_handler(&evenths, handle_expose, NULL);
|
||||
|
||||
modeswitchmask = get_mode_switch_mask(conn);
|
||||
modeswitchmask = get_mod_mask(conn, XK_Mode_switch);
|
||||
numlockmask = get_mod_mask(conn, XK_Num_Lock);
|
||||
symbols = xcb_key_symbols_alloc(conn);
|
||||
|
||||
uint32_t font_id = get_font_id(conn, pattern, &font_height);
|
||||
|
@ -306,11 +332,33 @@ int main(int argc, char *argv[]) {
|
|||
xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font_height + 8);
|
||||
xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
|
||||
|
||||
/* Set input focus (we have override_redirect=1, so the wm will not do
|
||||
* this for us) */
|
||||
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME);
|
||||
|
||||
/* Create graphics context */
|
||||
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id);
|
||||
|
||||
/* Grab the keyboard to get all input */
|
||||
xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
|
||||
xcb_flush(conn);
|
||||
|
||||
/* Try (repeatedly, if necessary) to grab the keyboard. We might not
|
||||
* get the keyboard at the first attempt because of the keybinding
|
||||
* still being active when started via a wm’s keybinding. */
|
||||
xcb_grab_keyboard_cookie_t cookie;
|
||||
xcb_grab_keyboard_reply_t *reply = NULL;
|
||||
|
||||
int count = 0;
|
||||
while ((reply == NULL || reply->status != XCB_GRAB_STATUS_SUCCESS) && (count++ < 500)) {
|
||||
cookie = xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
|
||||
reply = xcb_grab_keyboard_reply(conn, cookie, NULL);
|
||||
usleep(1000);
|
||||
}
|
||||
|
||||
if (reply->status != XCB_GRAB_STATUS_SUCCESS) {
|
||||
fprintf(stderr, "Could not grab keyboard, status = %d\n", reply->status);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
xcb_flush(conn);
|
||||
|
||||
|
|
|
@ -53,12 +53,12 @@ uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) {
|
|||
* keycode).
|
||||
*
|
||||
*/
|
||||
uint32_t get_mode_switch_mask(xcb_connection_t *conn) {
|
||||
uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode) {
|
||||
xcb_key_symbols_t *symbols = xcb_key_symbols_alloc(conn);
|
||||
|
||||
xcb_get_modifier_mapping_reply_t *modmap_r;
|
||||
xcb_keycode_t *modmap, kc;
|
||||
xcb_keycode_t *modeswitchcodes = xcb_key_symbols_get_keycode(symbols, XK_Mode_switch);
|
||||
xcb_keycode_t *modeswitchcodes = xcb_key_symbols_get_keycode(symbols, keycode);
|
||||
if (modeswitchcodes == NULL)
|
||||
return 0;
|
||||
|
||||
|
@ -66,7 +66,7 @@ uint32_t get_mode_switch_mask(xcb_connection_t *conn) {
|
|||
modmap = xcb_get_modifier_mapping_keycodes(modmap_r);
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
for(int j = 0; j < modmap_r->keycodes_per_modifier; j++) {
|
||||
for (int j = 0; j < modmap_r->keycodes_per_modifier; j++) {
|
||||
kc = modmap[i * modmap_r->keycodes_per_modifier + j];
|
||||
for (xcb_keycode_t *ktest = modeswitchcodes; *ktest; ktest++) {
|
||||
if (*ktest != kc)
|
||||
|
|
|
@ -3,6 +3,8 @@ TOPDIR=..
|
|||
|
||||
include $(TOPDIR)/common.mk
|
||||
|
||||
CFLAGS += -I$(TOPDIR)/include
|
||||
|
||||
# Depend on the object files of all source-files in src/*.c and on all header files
|
||||
FILES=$(patsubst %.c,%.o,$(wildcard *.c))
|
||||
HEADERS=$(wildcard *.h)
|
||||
|
@ -18,8 +20,8 @@ all: ${FILES}
|
|||
|
||||
install: all
|
||||
echo "INSTALL"
|
||||
$(INSTALL) -d -m 0755 $(DESTDIR)/usr/bin
|
||||
$(INSTALL) -m 0755 i3-msg $(DESTDIR)/usr/bin/
|
||||
$(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
|
||||
$(INSTALL) -m 0755 i3-msg $(DESTDIR)$(PREFIX)/bin/
|
||||
|
||||
clean:
|
||||
rm -f *.o
|
||||
|
|
113
i3-msg/main.c
113
i3-msg/main.c
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
#include <ev.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
@ -26,6 +27,24 @@
|
|||
#include <err.h>
|
||||
#include <stdint.h>
|
||||
#include <getopt.h>
|
||||
#include <glob.h>
|
||||
|
||||
#include <i3/ipc.h>
|
||||
|
||||
/*
|
||||
* This function resolves ~ in pathnames (and more, see glob(3)).
|
||||
*
|
||||
*/
|
||||
static char *glob_path(const char *path) {
|
||||
static glob_t globbuf;
|
||||
if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0)
|
||||
errx(EXIT_FAILURE, "glob() failed");
|
||||
char *result = strdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path);
|
||||
if (result == NULL)
|
||||
err(EXIT_FAILURE, "malloc() failed");
|
||||
globfree(&globbuf);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Formats a message (payload) of the given size and type and sends it to i3 via
|
||||
|
@ -34,12 +53,12 @@
|
|||
*/
|
||||
static void ipc_send_message(int sockfd, uint32_t message_size,
|
||||
uint32_t message_type, uint8_t *payload) {
|
||||
int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) + sizeof(uint32_t) + message_size;
|
||||
int buffer_size = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t) + message_size;
|
||||
char msg[buffer_size];
|
||||
char *walk = msg;
|
||||
|
||||
strcpy(walk, "i3-ipc");
|
||||
walk += strlen("i3-ipc");
|
||||
strcpy(walk, I3_IPC_MAGIC);
|
||||
walk += strlen(I3_IPC_MAGIC);
|
||||
memcpy(walk, &message_size, sizeof(uint32_t));
|
||||
walk += sizeof(uint32_t);
|
||||
memcpy(walk, &message_type, sizeof(uint32_t));
|
||||
|
@ -58,26 +77,84 @@ static void ipc_send_message(int sockfd, uint32_t message_size,
|
|||
}
|
||||
}
|
||||
|
||||
static void ipc_recv_message(int sockfd, uint32_t message_type,
|
||||
uint32_t *reply_length, uint8_t **reply) {
|
||||
/* Read the message header first */
|
||||
uint32_t to_read = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t);
|
||||
char msg[to_read];
|
||||
char *walk = msg;
|
||||
|
||||
uint32_t read_bytes = 0;
|
||||
while (read_bytes < to_read) {
|
||||
int n = read(sockfd, msg + read_bytes, to_read);
|
||||
if (n == -1)
|
||||
err(EXIT_FAILURE, "read() failed");
|
||||
if (n == 0)
|
||||
errx(EXIT_FAILURE, "received EOF instead of reply");
|
||||
|
||||
read_bytes += n;
|
||||
to_read -= n;
|
||||
}
|
||||
|
||||
if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0)
|
||||
errx(EXIT_FAILURE, "invalid magic in reply");
|
||||
|
||||
walk += strlen(I3_IPC_MAGIC);
|
||||
*reply_length = *((uint32_t*)walk);
|
||||
walk += sizeof(uint32_t);
|
||||
if (*((uint32_t*)walk) != message_type)
|
||||
errx(EXIT_FAILURE, "unexpected reply type (got %d, expected %d)", *((uint32_t*)walk), message_type);
|
||||
walk += sizeof(uint32_t);
|
||||
|
||||
*reply = malloc(*reply_length);
|
||||
if ((*reply) == NULL)
|
||||
err(EXIT_FAILURE, "malloc() failed");
|
||||
|
||||
to_read = *reply_length;
|
||||
read_bytes = 0;
|
||||
while (read_bytes < to_read) {
|
||||
int n = read(sockfd, *reply + read_bytes, to_read);
|
||||
if (n == -1)
|
||||
err(EXIT_FAILURE, "read() failed");
|
||||
|
||||
read_bytes += n;
|
||||
to_read -= n;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
char *socket_path = "/tmp/i3-ipc.sock";
|
||||
char *socket_path = glob_path("~/.i3/ipc.sock");
|
||||
int o, option_index = 0;
|
||||
int message_type = I3_IPC_MESSAGE_TYPE_COMMAND;
|
||||
char *payload = "";
|
||||
bool quiet = false;
|
||||
|
||||
static struct option long_options[] = {
|
||||
{"socket", required_argument, 0, 's'},
|
||||
{"type", required_argument, 0, 't'},
|
||||
{"version", no_argument, 0, 'v'},
|
||||
{"quiet", no_argument, 0, 'q'},
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
char *options_string = "s:t:vh";
|
||||
char *options_string = "s:t:vhq";
|
||||
|
||||
while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
|
||||
if (o == 's') {
|
||||
socket_path = strdup(optarg);
|
||||
break;
|
||||
socket_path = glob_path(optarg);
|
||||
} else if (o == 't') {
|
||||
printf("currently only commands are implemented\n");
|
||||
if (strcasecmp(optarg, "command") == 0)
|
||||
message_type = I3_IPC_MESSAGE_TYPE_COMMAND;
|
||||
else if (strcasecmp(optarg, "get_workspaces") == 0)
|
||||
message_type = I3_IPC_MESSAGE_TYPE_GET_WORKSPACES;
|
||||
else {
|
||||
printf("Unknown message type\n");
|
||||
printf("Known types: command, get_workspaces\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (o == 'q') {
|
||||
quiet = true;
|
||||
} else if (o == 'v') {
|
||||
printf("i3-msg " I3_VERSION);
|
||||
return 0;
|
||||
|
@ -88,11 +165,8 @@ int main(int argc, char *argv[]) {
|
|||
}
|
||||
}
|
||||
|
||||
if (optind >= argc) {
|
||||
fprintf(stderr, "Error: missing message\n");
|
||||
fprintf(stderr, "i3-msg [-s <socket>] [-t <type>] <message>\n");
|
||||
return 1;
|
||||
}
|
||||
if (optind < argc)
|
||||
payload = argv[optind];
|
||||
|
||||
int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
|
||||
if (sockfd == -1)
|
||||
|
@ -105,7 +179,16 @@ int main(int argc, char *argv[]) {
|
|||
if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0)
|
||||
err(EXIT_FAILURE, "Could not connect to i3");
|
||||
|
||||
ipc_send_message(sockfd, strlen(argv[optind]), 0, (uint8_t*)argv[optind]);
|
||||
ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t*)payload);
|
||||
|
||||
if (quiet)
|
||||
return 0;
|
||||
|
||||
uint32_t reply_length;
|
||||
uint8_t *reply;
|
||||
ipc_recv_message(sockfd, message_type, &reply_length, &reply);
|
||||
printf("%.*s", reply_length, reply);
|
||||
free(reply);
|
||||
|
||||
close(sockfd);
|
||||
|
||||
|
|
|
@ -0,0 +1,244 @@
|
|||
#!/usr/bin/env perl
|
||||
# vim:ts=4:sw=4:expandtab:ft=perl
|
||||
# © 2010 Michael Stapelberg, see LICENSE for license information
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Getopt::Long;
|
||||
use Pod::Usage;
|
||||
use IPC::Run qw(start pump);
|
||||
use AnyEvent::I3;
|
||||
use AnyEvent;
|
||||
use v5.10;
|
||||
|
||||
my $stdin;
|
||||
my $i3 = i3;
|
||||
my ($workspaces, $outputs) = ([], {});
|
||||
my $last_line = "";
|
||||
|
||||
my $command = "";
|
||||
my $input_on = "";
|
||||
my $output_on = "";
|
||||
my $show_all = 0;
|
||||
|
||||
my $result = GetOptions(
|
||||
'command=s' => \$command,
|
||||
'input-on=s' => \$input_on,
|
||||
'output-on=s' => \$output_on,
|
||||
'show-all' => \$show_all,
|
||||
'help' => sub { pod2usage(1); exit 0 },
|
||||
);
|
||||
|
||||
if ($command eq '') {
|
||||
say "i3-wsbar is only useful in combination with dzen2.";
|
||||
say "Please specify -c (command)";
|
||||
exit 1;
|
||||
}
|
||||
|
||||
my @input_on = split(/,/, $input_on);
|
||||
my @output_on = split(/,/, $output_on);
|
||||
|
||||
# Disable buffering
|
||||
$| = 1;
|
||||
|
||||
# Wait a short amount of time and try to connect to i3 again
|
||||
sub reconnect {
|
||||
my $timer;
|
||||
my $c = sub {
|
||||
$timer = AnyEvent->timer(
|
||||
after => 0.01,
|
||||
cb => sub { $i3->connect->cb(\&connected) }
|
||||
);
|
||||
};
|
||||
$c->();
|
||||
}
|
||||
|
||||
# Connection attempt succeeded or failed
|
||||
sub connected {
|
||||
my ($cv) = @_;
|
||||
|
||||
if (!$cv->recv) {
|
||||
reconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
$i3->subscribe({
|
||||
workspace => \&ws_change,
|
||||
output => \&output_change,
|
||||
_error => sub { reconnect() }
|
||||
});
|
||||
ws_change();
|
||||
output_change();
|
||||
}
|
||||
|
||||
# Called when a ws changes
|
||||
sub ws_change {
|
||||
# Request the current workspaces and update the output afterwards
|
||||
$i3->get_workspaces->cb(
|
||||
sub {
|
||||
my ($cv) = @_;
|
||||
$workspaces = $cv->recv;
|
||||
update_output();
|
||||
});
|
||||
}
|
||||
|
||||
# Called when the reply to the GET_OUTPUTS message arrives
|
||||
# Compares old outputs with new outputs and starts/kills
|
||||
# $command for each output (if specified)
|
||||
sub got_outputs {
|
||||
my $reply = shift->recv;
|
||||
my %old = %{$outputs};
|
||||
my %new = map { ($_->{name}, $_) } grep { $_->{active} } @{$reply};
|
||||
|
||||
# If no command was given, we do not need to compare outputs
|
||||
if ($command eq '') {
|
||||
update_output();
|
||||
return;
|
||||
}
|
||||
|
||||
# Handle new outputs
|
||||
for my $name (keys %new) {
|
||||
next if @output_on and !($name ~~ @output_on);
|
||||
|
||||
if (defined($old{$name})) {
|
||||
# Check if the mode changed (by reversing the hashes so
|
||||
# that we can check for equality using the smartmatch op)
|
||||
my %oldrect = reverse %{$old{$name}->{rect}};
|
||||
my %newrect = reverse %{$new{$name}->{rect}};
|
||||
next if (%oldrect ~~ %newrect);
|
||||
|
||||
# On mode changes, we re-start the command
|
||||
$outputs->{$name}->{cmd}->finish;
|
||||
delete $outputs->{$name};
|
||||
}
|
||||
|
||||
my $x = $new{$name}->{rect}->{x};
|
||||
my $launch = $command;
|
||||
$launch =~ s/([^%])%x/$1$x/g;
|
||||
$launch =~ s/%%x/%x/g;
|
||||
|
||||
$new{$name}->{cmd_input} = '';
|
||||
my @cmd = ('/bin/sh', '-c', $launch);
|
||||
$new{$name}->{cmd} = start \@cmd, \$new{$name}->{cmd_input};
|
||||
$outputs->{$name} = $new{$name};
|
||||
}
|
||||
|
||||
# Handle old outputs
|
||||
for my $name (keys %old) {
|
||||
next if defined($new{$name});
|
||||
|
||||
$outputs->{$name}->{cmd}->finish;
|
||||
delete $outputs->{$name};
|
||||
}
|
||||
|
||||
update_output();
|
||||
}
|
||||
|
||||
sub output_change {
|
||||
$i3->get_outputs->cb(\&got_outputs)
|
||||
}
|
||||
|
||||
sub update_output {
|
||||
my $dzen_bg = "#111111";
|
||||
my $out;
|
||||
|
||||
for my $name (keys %{$outputs}) {
|
||||
my $width = $outputs->{$name}->{rect}->{width};
|
||||
|
||||
$out = qq|^pa(;2)|;
|
||||
for my $ws (@{$workspaces}) {
|
||||
next if $ws->{output} ne $name and !$show_all;
|
||||
|
||||
my ($bg, $fg) = qw(333333 888888);
|
||||
($bg, $fg) = qw(4c7899 ffffff) if $ws->{visible};
|
||||
($bg, $fg) = qw(900000 ffffff) if $ws->{urgent};
|
||||
|
||||
my $cmd = q|i3-msg "| . $ws->{num} . q|"|;
|
||||
my $name = $ws->{name};
|
||||
|
||||
# Begin the clickable area
|
||||
$out .= qq|^ca(1,$cmd)|;
|
||||
|
||||
# Draw the rest of the bar in the background color, but
|
||||
# don’t move the "cursor"
|
||||
$out .= qq|^p(_LOCK_X)^fg(#$bg)^r(${width}x17)^p(_UNLOCK_X)|;
|
||||
# Draw the name of the workspace without overwriting the
|
||||
# background color
|
||||
$out .= qq|^p(+3)^fg(#$fg)^ib(1)$name^ib(0)^p(+5)|;
|
||||
# Draw the rest of the bar in the normal background color
|
||||
# without moving the "cursor"
|
||||
$out .= qq|^p(_LOCK_X)^fg($dzen_bg)^r(${width}x17)^p(_UNLOCK_X)|;
|
||||
|
||||
# End the clickable area
|
||||
$out .= qq|^ca()|;
|
||||
|
||||
# Move to the next rect, reset Y coordinate
|
||||
$out .= qq|^p(2)^pa(;2)|;
|
||||
}
|
||||
|
||||
$out .= qq|^p(_LOCK_X)^fg($dzen_bg)^r(${width}x17)^p(_UNLOCK_X)^fg(white)|;
|
||||
$out .= qq|^p(+5)|;
|
||||
$out .= $last_line if (!@input_on or $name ~~ @input_on);
|
||||
$out .= "\n";
|
||||
|
||||
$outputs->{$name}->{cmd_input} = $out;
|
||||
pump $outputs->{$name}->{cmd} while length $outputs->{$name}->{cmd_input};
|
||||
}
|
||||
}
|
||||
|
||||
$i3->connect->cb(\&connected);
|
||||
|
||||
$stdin = AnyEvent->io(
|
||||
fh => \*STDIN,
|
||||
poll => 'r',
|
||||
cb => sub {
|
||||
chomp (my $line = <STDIN>);
|
||||
$last_line = $line;
|
||||
update_output();
|
||||
});
|
||||
|
||||
# let AnyEvent do the rest ("endless loop")
|
||||
AnyEvent->condvar->recv
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
i3-wsbar - sample implementation of a standalone workspace bar
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
i3-wsbar -c <dzen2-commandline> [options]
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over 4
|
||||
|
||||
=item B<--command> <command>
|
||||
|
||||
This command (at the moment only dzen2 is supported) will be started for each
|
||||
output. C<%x> will be replaced with the X coordinate of the output.
|
||||
|
||||
Example:
|
||||
--command "dzen2 -dock -x %x"
|
||||
|
||||
=item B<--input-on> <list-of-RandR-outputs>
|
||||
|
||||
Specifies on which outputs the contents of stdin should be appended to the
|
||||
workspace bar.
|
||||
|
||||
Example:
|
||||
--input-on "LVDS1"
|
||||
|
||||
=item B<--output-on> <list-of-RandR-outputs>
|
||||
|
||||
Specifies for which outputs i3-wsbar should start C<command>.
|
||||
|
||||
=item B<--show-all>
|
||||
|
||||
If enabled, all workspaces are shown (not only those of the current output).
|
||||
Handy to use with C<--output-on>.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
11
i3.config
11
i3.config
|
@ -1,11 +1,6 @@
|
|||
# This configuration uses Mod1 and Mod3. Make sure they are mapped properly using xev(1)
|
||||
# and xmodmap(1). Usually, Mod1 is Alt (Alt_L) and Mod3 is Windows (Super_L)
|
||||
|
||||
# Tell i3 about your preferred terminal. You can refer to this as $terminal
|
||||
# later. It is recommended to set this option to allow i3 to open a terminal
|
||||
# containing the introduction on first start.
|
||||
terminal /usr/bin/urxvt
|
||||
|
||||
# ISO 10646 = Unicode
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
|
@ -112,7 +107,7 @@ bind Mod1+36 exec /usr/bin/urxvt
|
|||
bind Mod1+Shift+24 kill
|
||||
|
||||
# Mod1+v starts dmenu and launches the selected application
|
||||
# for now, we don’t have an own launcher
|
||||
# for now, we don’t have a launcher of our own.
|
||||
bind Mod1+55 exec /usr/bin/dmenu_run
|
||||
|
||||
# Mod1+Shift+e exits i3
|
||||
|
@ -121,6 +116,10 @@ bind Mod1+Shift+26 exit
|
|||
# Mod1+Shift+r restarts i3 inplace
|
||||
bind Mod1+Shift+27 restart
|
||||
|
||||
# The IPC interface allows programs like an external workspace bar
|
||||
# (i3-wsbar) or i3-msg (can be used to "remote-control" i3) to work.
|
||||
ipc-socket ~/.i3/ipc.sock
|
||||
|
||||
#############################################################
|
||||
# DELETE THE FOLLOWING LINES TO DISABLE THE WELCOME MESSAGE #
|
||||
#############################################################
|
||||
|
|
13
i3.welcome
13
i3.welcome
|
@ -1,6 +1,6 @@
|
|||
1.) Welcome to i3!
|
||||
|
||||
This message provides you with an overview of the default keybindings to use i3.
|
||||
This message provides an overview of the default keybindings to use i3.
|
||||
Please also make sure to have a look at the man page and the user's guide:
|
||||
http://i3.zekjur.net/docs/userguide.html
|
||||
|
||||
|
@ -8,7 +8,8 @@ http://i3.zekjur.net/docs/userguide.html
|
|||
2.) Configuration Files
|
||||
|
||||
/etc/i3/config is the default configuration. It is recommended to copy it and
|
||||
afterwards edit it to suit your needs (you can especially disable this message):
|
||||
afterwards edit it to suit your needs (in particular, you may want to disable
|
||||
this message):
|
||||
|
||||
cp /etc/i3/config ~/.i3/config
|
||||
|
||||
|
@ -17,7 +18,7 @@ afterwards edit it to suit your needs (you can especially disable this message):
|
|||
|
||||
The following explanation is related to the QWERTY layout, but as the default
|
||||
configuration uses keycodes instead of keysymbols for binding, you still have
|
||||
to press the same keys, regardless of your keyboard layout.
|
||||
to press the same physical keys, regardless of your keyboard layout.
|
||||
|
||||
The Mod1 key is usually bound to the "Alt" key on your keyboard.
|
||||
|
||||
|
@ -28,15 +29,15 @@ The directional keys are j(left), k(down), l(up) and ;(right). You can also use
|
|||
the arrow keys on your keyboard, if you prefer them.
|
||||
|
||||
Mod1+<directional key> moves the focus to the window in the given direction
|
||||
Mod1+Shift+<directional key> moves the window to the given direction,
|
||||
Mod1+Shift+<directional key> moves the window to the given direction
|
||||
Mod1+<number> opens the corresponding workspace
|
||||
Mod1+Shift+<number> moves a window to the wished workspace
|
||||
Mod1+Shift+<number> moves a window to the selected workspace
|
||||
Mod1+h sets the mode of a container to stacking
|
||||
Mod1+e sets the mode back to default
|
||||
Mod1+f toggles fullscreen mode for the current window
|
||||
Mod1+Shift+Space toggles floating mode for the current window
|
||||
Mod1+Shift+q closes a window
|
||||
Mod1+Shift+r restarts i3 in-place (you will lose your layout, though)
|
||||
Mod1+Shift+r restarts i3 in-place (at this time, you will lose your layout)
|
||||
Mod1+Shift+e exits i3
|
||||
|
||||
If you have any questions, please don't hesitate to ask!
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* (c) 2009 Michael Stapelberg and contributors
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
|
@ -51,7 +51,13 @@ bool client_matches_class_name(Client *client, char *to_class, char *to_title,
|
|||
* and when moving a fullscreen client to another screen.
|
||||
*
|
||||
*/
|
||||
void client_enter_fullscreen(xcb_connection_t *conn, Client *client);
|
||||
void client_enter_fullscreen(xcb_connection_t *conn, Client *client, bool global);
|
||||
|
||||
/**
|
||||
* Leaves fullscreen mode for the given client. This is called by toggle_fullscreen.
|
||||
*
|
||||
*/
|
||||
void client_leave_fullscreen(xcb_connection_t *conn, Client *client);
|
||||
|
||||
/**
|
||||
* Leaves fullscreen mode for the current client. This is called by toggle_fullscreen.
|
||||
|
@ -68,6 +74,12 @@ void client_leave_fullscreen(xcb_connection_t *conn, Client *client);
|
|||
*/
|
||||
void client_toggle_fullscreen(xcb_connection_t *conn, Client *client);
|
||||
|
||||
/**
|
||||
* Like client_toggle_fullscreen(), but putting it in global fullscreen-mode.
|
||||
*
|
||||
*/
|
||||
void client_toggle_fullscreen_global(xcb_connection_t *conn, Client *client);
|
||||
|
||||
/**
|
||||
* Sets the position of the given client in the X stack to the highest (tiling
|
||||
* layer is always on the same position, so this doesn’t matter) below the
|
||||
|
@ -118,12 +130,26 @@ void client_map(xcb_connection_t *conn, Client *client);
|
|||
*/
|
||||
void client_mark(xcb_connection_t *conn, Client *client, const char *mark);
|
||||
|
||||
/**
|
||||
* Returns the minimum height of a specific window. The height is calculated
|
||||
* by using 2 pixels (for the client window itself), possibly padding this to
|
||||
* comply with the client’s base_height and then adding the decoration height.
|
||||
*
|
||||
*/
|
||||
uint32_t client_min_height(Client *client);
|
||||
|
||||
/**
|
||||
* See client_min_height.
|
||||
*
|
||||
*/
|
||||
uint32_t client_min_width(Client *client);
|
||||
|
||||
/**
|
||||
* Pretty-prints the client’s information into the logfile.
|
||||
*
|
||||
*/
|
||||
#define CLIENT_LOG(client) do { \
|
||||
LOG("Window: frame 0x%08x, child 0x%08x\n", client->frame, client->child); \
|
||||
DLOG("Window: frame 0x%08x, child 0x%08x\n", client->frame, client->child); \
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
|
|
|
@ -3,12 +3,14 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
* include/config.h: Contains all structs/variables for
|
||||
* the configurable part of i3
|
||||
* include/config.h: Contains all structs/variables for the configurable
|
||||
* part of i3 as well as functions handling the configuration file (calling
|
||||
* the parser (src/cfgparse.y) with the correct path, switching key bindings
|
||||
* mode).
|
||||
*
|
||||
*/
|
||||
|
||||
|
@ -21,9 +23,23 @@
|
|||
|
||||
typedef struct Config Config;
|
||||
extern Config config;
|
||||
extern bool config_use_lexer;
|
||||
extern SLIST_HEAD(modes_head, Mode) modes;
|
||||
|
||||
/**
|
||||
* Used during the config file lexing/parsing to keep the state of the lexer
|
||||
* in order to provide useful error messages in yyerror().
|
||||
*
|
||||
*/
|
||||
struct context {
|
||||
int line_number;
|
||||
char *line_copy;
|
||||
const char *filename;
|
||||
|
||||
/* These are the same as in YYLTYPE */
|
||||
int first_column;
|
||||
int last_column;
|
||||
};
|
||||
|
||||
/**
|
||||
* Part of the struct Config. It makes sense to group colors for background,
|
||||
* border and text as every element in i3 has them (window decorations, bar).
|
||||
|
@ -76,6 +92,18 @@ struct Config {
|
|||
int container_stack_limit;
|
||||
int container_stack_limit_value;
|
||||
|
||||
/** By default, focus follows mouse. If the user explicitly wants to
|
||||
* turn this off (and instead rely only on the keyboard for changing
|
||||
* focus), we allow him to do this with this relatively special option.
|
||||
* It is not planned to add any different focus models. */
|
||||
bool disable_focus_follows_mouse;
|
||||
|
||||
/** By default, a workspace bar is drawn at the bottom of the screen.
|
||||
* If you want to have a more fancy bar, it is recommended to replace
|
||||
* the whole bar by dzen2, for example using the i3-wsbar script which
|
||||
* comes with i3. Thus, you can turn it off entirely. */
|
||||
bool disable_workspace_bar;
|
||||
|
||||
const char *default_border;
|
||||
|
||||
/** The modifier which needs to be pressed in combination with your mouse
|
||||
|
@ -96,6 +124,18 @@ struct Config {
|
|||
} bar;
|
||||
};
|
||||
|
||||
/**
|
||||
* This function resolves ~ in pathnames.
|
||||
*
|
||||
*/
|
||||
char *glob_path(const char *path);
|
||||
|
||||
/**
|
||||
* Checks if the given path exists by calling stat().
|
||||
*
|
||||
*/
|
||||
bool path_exists(const char *path);
|
||||
|
||||
/**
|
||||
* Reads the configuration from ~/.i3/config or /etc/i3/config if not found.
|
||||
*
|
||||
|
@ -105,6 +145,12 @@ struct Config {
|
|||
*/
|
||||
void load_configuration(xcb_connection_t *conn, const char *override_configfile, bool reload);
|
||||
|
||||
/**
|
||||
* Translates keysymbols to keycodes for all bindings which use keysyms.
|
||||
*
|
||||
*/
|
||||
void translate_keysyms();
|
||||
|
||||
/**
|
||||
* Ungrabs all keys, to be called before re-grabbing the keys because of a
|
||||
* mapping_notify event or a configuration file reload
|
||||
|
@ -116,7 +162,7 @@ void ungrab_all_keys(xcb_connection_t *conn);
|
|||
* Grab the bound keys (tell X to send us keypress events for those keycodes)
|
||||
*
|
||||
*/
|
||||
void grab_all_keys(xcb_connection_t *conn);
|
||||
void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch);
|
||||
|
||||
/**
|
||||
* Switches the key bindings to the given mode, if the mode exists
|
||||
|
@ -124,4 +170,14 @@ void grab_all_keys(xcb_connection_t *conn);
|
|||
*/
|
||||
void switch_mode(xcb_connection_t *conn, const char *new_mode);
|
||||
|
||||
/**
|
||||
* Returns a pointer to the Binding with the specified modifiers and keycode
|
||||
* or NULL if no such binding exists.
|
||||
*
|
||||
*/
|
||||
Binding *get_binding(uint16_t modifiers, xcb_keycode_t keycode);
|
||||
|
||||
/* prototype for src/cfgparse.y */
|
||||
void parse_file(const char *f);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* vim:ts=8:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
*/
|
||||
#include "data.h"
|
||||
|
||||
#ifndef _CONTAINER_H
|
||||
#define _CONTAINER_H
|
||||
|
||||
/**
|
||||
* Returns the mode of the given container (or MODE_DEFAULT if a NULL pointer
|
||||
* was passed in order to save a few explicit checks in other places). If
|
||||
* for_frame was set to true, the special case of having exactly one client
|
||||
* in a container is handled so that MODE_DEFAULT is returned. For some parts
|
||||
* of the rendering, this is interesting, other parts need the real mode.
|
||||
*
|
||||
*/
|
||||
int container_mode(Container *con, bool for_frame);
|
||||
|
||||
#endif
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
|
@ -11,6 +11,7 @@
|
|||
*
|
||||
*/
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/randr.h>
|
||||
#include <xcb/xcb_atom.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
|
@ -25,11 +26,12 @@
|
|||
*
|
||||
* Let’s start from the biggest to the smallest:
|
||||
*
|
||||
* - An i3Screen is a virtual screen (Xinerama). This can be a single one,
|
||||
* though two monitors might be connected, if you’re running clone
|
||||
* mode. There can also be multiple of them.
|
||||
* - An Output is a physical output on your graphics driver. Outputs which
|
||||
* are currently in use have (output->active == true). Each output has a
|
||||
* position and a mode. An output usually corresponds to one connected
|
||||
* screen (except if you are running multiple screens in clone mode).
|
||||
*
|
||||
* - Each i3Screen contains Workspaces. The concept is known from various
|
||||
* - Each Output contains Workspaces. The concept is known from various
|
||||
* other window managers. Basically, a workspace is a specific set of
|
||||
* windows, usually grouped thematically (irc, www, work, …). You can switch
|
||||
* between these.
|
||||
|
@ -54,7 +56,7 @@ typedef struct Client Client;
|
|||
typedef struct Binding Binding;
|
||||
typedef struct Workspace Workspace;
|
||||
typedef struct Rect Rect;
|
||||
typedef struct Screen i3Screen;
|
||||
typedef struct xoutput Output;
|
||||
|
||||
/******************************************************************************
|
||||
* Helper types
|
||||
|
@ -75,6 +77,14 @@ enum {
|
|||
|
||||
/**
|
||||
* Stores a rectangle, for example the size of a window, the child window etc.
|
||||
* It needs to be packed so that the compiler will not add any padding bytes.
|
||||
* (it is used in src/ewmh.c for example)
|
||||
*
|
||||
* Note that x and y can contain signed values in some cases (for example when
|
||||
* used for the coordinates of a window, which can be set outside of the
|
||||
* visible area, but not when specifying the position of a workspace for the
|
||||
* _NET_WM_WORKAREA hint). Not declaring x/y as int32_t saves us a lot of
|
||||
* typecasts.
|
||||
*
|
||||
* Note that x and y can contain signed values in some cases (for example when
|
||||
* used for the coordinates of a window, which can be set outside of the
|
||||
|
@ -84,9 +94,11 @@ enum {
|
|||
*
|
||||
*/
|
||||
struct Rect {
|
||||
uint32_t x, y;
|
||||
uint32_t width, height;
|
||||
};
|
||||
uint32_t x;
|
||||
uint32_t y;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
} __attribute__((packed));
|
||||
|
||||
/**
|
||||
* Defines a position in the table
|
||||
|
@ -171,6 +183,9 @@ struct Workspace {
|
|||
/** Number of this workspace, starting from 0 */
|
||||
int num;
|
||||
|
||||
/** Name of the workspace (in UTF-8) */
|
||||
char *utf8_name;
|
||||
|
||||
/** Name of the workspace (in UCS-2) */
|
||||
char *name;
|
||||
|
||||
|
@ -200,12 +215,8 @@ struct Workspace {
|
|||
/** Are the floating clients on this workspace currently hidden? */
|
||||
bool floating_hidden;
|
||||
|
||||
/** A <screen> specifier on which this workspace would like to be (if
|
||||
* the screen is available). screen := <number> | <position> */
|
||||
char *preferred_screen;
|
||||
|
||||
/** Temporary flag needed for re-querying xinerama screens */
|
||||
bool reassigned;
|
||||
/** The name of the RandR output this screen should be on */
|
||||
char *preferred_output;
|
||||
|
||||
/** True if any client on this workspace has its urgent flag set */
|
||||
bool urgent;
|
||||
|
@ -224,8 +235,8 @@ struct Workspace {
|
|||
* appended) */
|
||||
TAILQ_HEAD(floating_clients_head, Client) floating_clients;
|
||||
|
||||
/** Backpointer to the screen this workspace is on */
|
||||
i3Screen *screen;
|
||||
/** Backpointer to the output this workspace is on */
|
||||
Output *output;
|
||||
|
||||
/** This is a two-dimensional dynamic array of
|
||||
* Container-pointers. I’ve always wanted to be a three-star
|
||||
|
@ -492,14 +503,26 @@ struct Container {
|
|||
};
|
||||
|
||||
/**
|
||||
* This is a virtual screen (Xinerama). This can be a single one, though two
|
||||
* monitors might be connected, if you’re running clone mode. There can also
|
||||
* be multiple of them.
|
||||
* An Output is a physical output on your graphics driver. Outputs which
|
||||
* are currently in use have (output->active == true). Each output has a
|
||||
* position and a mode. An output usually corresponds to one connected
|
||||
* screen (except if you are running multiple screens in clone mode).
|
||||
*
|
||||
*/
|
||||
struct Screen {
|
||||
/** Virtual screen number */
|
||||
int num;
|
||||
struct xoutput {
|
||||
/** Output id, so that we can requery the output directly later */
|
||||
xcb_randr_output_t id;
|
||||
/** Name of the output */
|
||||
char *name;
|
||||
|
||||
/** Whether the output is currently active (has a CRTC attached with a
|
||||
* valid mode) */
|
||||
bool active;
|
||||
|
||||
/** Internal flags, necessary for querying RandR screens (happens in
|
||||
* two stages) */
|
||||
bool changed;
|
||||
bool to_be_disabled;
|
||||
|
||||
/** Current workspace selected on this virtual screen */
|
||||
Workspace *current_workspace;
|
||||
|
@ -515,7 +538,7 @@ struct Screen {
|
|||
* _NET_WM_WINDOW_TYPE_DOCK */
|
||||
SLIST_HEAD(dock_clients_head, Client) dock_clients;
|
||||
|
||||
TAILQ_ENTRY(Screen) screens;
|
||||
TAILQ_ENTRY(xoutput) outputs;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* vim:ts=8:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
*/
|
||||
#ifndef _EWMH_C
|
||||
#define _EWMH_C
|
||||
|
||||
/**
|
||||
* Updates _NET_CURRENT_DESKTOP with the current desktop number.
|
||||
*
|
||||
* EWMH: The index of the current desktop. This is always an integer between 0
|
||||
* and _NET_NUMBER_OF_DESKTOPS - 1.
|
||||
*
|
||||
*/
|
||||
void ewmh_update_current_desktop();
|
||||
|
||||
/**
|
||||
* Updates _NET_ACTIVE_WINDOW with the currently focused window.
|
||||
*
|
||||
* EWMH: The window ID of the currently active window or None if no window has
|
||||
* the focus.
|
||||
*
|
||||
*/
|
||||
void ewmh_update_active_window(xcb_window_t window);
|
||||
|
||||
/**
|
||||
* Updates the workarea for each desktop.
|
||||
*
|
||||
* EWMH: Contains a geometry for each desktop. These geometries specify an area
|
||||
* that is completely contained within the viewport. Work area SHOULD be used by
|
||||
* desktop applications to place desktop icons appropriately.
|
||||
*
|
||||
*/
|
||||
void ewmh_update_workarea();
|
||||
|
||||
#endif
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
|
@ -12,10 +12,19 @@
|
|||
#define _FLOATING_H
|
||||
|
||||
/** Callback for dragging */
|
||||
typedef void(*callback_t)(Rect*, uint32_t, uint32_t);
|
||||
typedef void(*callback_t)(xcb_connection_t*, Client*, Rect*, uint32_t, uint32_t, void*);
|
||||
|
||||
/** Macro to create a callback function for dragging */
|
||||
#define DRAGGING_CB(name) \
|
||||
static void name(xcb_connection_t *conn, Client *client, \
|
||||
Rect *old_rect, uint32_t new_x, uint32_t new_y, \
|
||||
void *extra)
|
||||
|
||||
/** On which border was the dragging initiated? */
|
||||
typedef enum { BORDER_LEFT, BORDER_RIGHT, BORDER_TOP, BORDER_BOTTOM} border_t;
|
||||
typedef enum { BORDER_LEFT = (1 << 0),
|
||||
BORDER_RIGHT = (1 << 1),
|
||||
BORDER_TOP = (1 << 2),
|
||||
BORDER_BOTTOM = (1 << 3)} border_t;
|
||||
|
||||
/**
|
||||
* Enters floating mode for the given client. Correctly takes care of the
|
||||
|
@ -56,13 +65,13 @@ void floating_drag_window(xcb_connection_t *conn, Client *client,
|
|||
xcb_button_press_event_t *event);
|
||||
|
||||
/**
|
||||
* Called when the user right-clicked on the titlebar of a floating window to
|
||||
* resize it.
|
||||
* Called when the user clicked on a floating window while holding the
|
||||
* floating_modifier and the right mouse button.
|
||||
* Calls the drag_pointer function with the resize_window callback
|
||||
*
|
||||
*/
|
||||
void floating_resize_window(xcb_connection_t *conn, Client *client,
|
||||
xcb_button_press_event_t *event);
|
||||
bool proportional, xcb_button_press_event_t *event);
|
||||
|
||||
/**
|
||||
* Changes focus in the given direction for floating clients.
|
||||
|
@ -97,6 +106,7 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace);
|
|||
*
|
||||
*/
|
||||
void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event,
|
||||
xcb_window_t confine_to, border_t border, callback_t callback);
|
||||
xcb_window_t confine_to, border_t border, callback_t callback,
|
||||
void *extra);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* (c) 2009 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
|
@ -11,13 +11,7 @@
|
|||
#ifndef _HANDLERS_H
|
||||
#define _HANDLERS_H
|
||||
|
||||
/**
|
||||
* Due to bindings like Mode_switch + <a>, we need to bind some keys in
|
||||
* XCB_GRAB_MODE_SYNC. Therefore, we just replay all key presses.
|
||||
*
|
||||
*/
|
||||
int handle_key_release(void *ignored, xcb_connection_t *conn,
|
||||
xcb_key_release_event_t *event);
|
||||
#include <xcb/randr.h>
|
||||
|
||||
/**
|
||||
* There was a key press. We compare this key code with our bindings table and
|
||||
|
@ -74,6 +68,14 @@ int handle_map_request(void *prophs, xcb_connection_t *conn,
|
|||
*/
|
||||
int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_notify_event_t *event);
|
||||
|
||||
/**
|
||||
* Gets triggered upon a RandR screen change event, that is when the user
|
||||
* changes the screen configuration in any way (mode, position, …)
|
||||
*
|
||||
*/
|
||||
int handle_screen_change(void *prophs, xcb_connection_t *conn,
|
||||
xcb_generic_event_t *e);
|
||||
|
||||
/**
|
||||
* Configure requests are received when the application wants to resize
|
||||
* windows on their own.
|
||||
|
@ -92,6 +94,18 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn,
|
|||
*/
|
||||
int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_notify_event_t *event);
|
||||
|
||||
/**
|
||||
* A destroy notify event is sent when the window is not unmapped, but
|
||||
* immediately destroyed (for example when starting a window and immediately
|
||||
* killing the program which started it).
|
||||
*
|
||||
* We just pass on the event to the unmap notify handler (by copying the
|
||||
* important fields in the event data structure).
|
||||
*
|
||||
*/
|
||||
int handle_destroy_notify_event(void *data, xcb_connection_t *conn,
|
||||
xcb_destroy_notify_event_t *event);
|
||||
|
||||
/**
|
||||
* Called when a window changes its title
|
||||
*
|
||||
|
|
|
@ -21,19 +21,20 @@
|
|||
#ifndef _I3_H
|
||||
#define _I3_H
|
||||
|
||||
#define NUM_ATOMS 18
|
||||
#define NUM_ATOMS 21
|
||||
|
||||
extern xcb_connection_t *global_conn;
|
||||
extern xcb_key_symbols_t *keysyms;
|
||||
extern char **start_argv;
|
||||
extern Display *xkbdpy;
|
||||
extern int xkb_current_group;
|
||||
extern TAILQ_HEAD(bindings_head, Binding) *bindings;
|
||||
extern TAILQ_HEAD(autostarts_head, Autostart) autostarts;
|
||||
extern TAILQ_HEAD(assignments_head, Assignment) assignments;
|
||||
extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins;
|
||||
extern xcb_event_handlers_t evenths;
|
||||
extern int num_screens;
|
||||
extern uint8_t root_depth;
|
||||
extern bool xkb_supported;
|
||||
extern xcb_atom_t atoms[NUM_ATOMS];
|
||||
extern xcb_window_t root;
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
|
@ -15,10 +15,53 @@
|
|||
#ifndef _I3_IPC_H
|
||||
#define _I3_IPC_H
|
||||
|
||||
/*
|
||||
* Messages from clients to i3
|
||||
*
|
||||
*/
|
||||
|
||||
/** Never change this, only on major IPC breakage (don’t do that) */
|
||||
#define I3_IPC_MAGIC "i3-ipc"
|
||||
|
||||
/** The payload of the message will be interpreted as a command */
|
||||
#define I3_IPC_MESSAGE_TYPE_COMMAND 0
|
||||
#define I3_IPC_MESSAGE_TYPE_COMMAND 0
|
||||
|
||||
/** Requests the current workspaces from i3 */
|
||||
#define I3_IPC_MESSAGE_TYPE_GET_WORKSPACES 1
|
||||
|
||||
/** Subscribe to the specified events */
|
||||
#define I3_IPC_MESSAGE_TYPE_SUBSCRIBE 2
|
||||
|
||||
/** Requests the current outputs from i3 */
|
||||
#define I3_IPC_MESSAGE_TYPE_GET_OUTPUTS 3
|
||||
|
||||
/*
|
||||
* Messages from i3 to clients
|
||||
*
|
||||
*/
|
||||
|
||||
/** Command reply type */
|
||||
#define I3_IPC_REPLY_TYPE_COMMAND 0
|
||||
|
||||
/** Workspaces reply type */
|
||||
#define I3_IPC_REPLY_TYPE_WORKSPACES 1
|
||||
|
||||
/** Subscription reply type */
|
||||
#define I3_IPC_REPLY_TYPE_SUBSCRIBE 2
|
||||
|
||||
/** Outputs reply type */
|
||||
#define I3_IPC_REPLY_TYPE_OUTPUTS 3
|
||||
|
||||
/*
|
||||
* Events from i3 to clients. Events have the first bit set high.
|
||||
*
|
||||
*/
|
||||
#define I3_IPC_EVENT_MASK (1 << 31)
|
||||
|
||||
/* The workspace event will be triggered upon changes in the workspace list */
|
||||
#define I3_IPC_EVENT_WORKSPACE (I3_IPC_EVENT_MASK | 0)
|
||||
|
||||
/* The output event will be triggered upon changes in the output list */
|
||||
#define I3_IPC_EVENT_OUTPUT (I3_IPC_EVENT_MASK | 1)
|
||||
|
||||
#endif
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
|
@ -16,6 +16,34 @@
|
|||
|
||||
#include "i3/ipc.h"
|
||||
|
||||
typedef struct ipc_client {
|
||||
int fd;
|
||||
|
||||
/* The events which this client wants to receive */
|
||||
int num_events;
|
||||
char **events;
|
||||
|
||||
TAILQ_ENTRY(ipc_client) clients;
|
||||
} ipc_client;
|
||||
|
||||
/*
|
||||
* Callback type for the different message types.
|
||||
*
|
||||
* message is the raw packet, as received from the UNIX domain socket. size
|
||||
* is the remaining size of bytes for this packet.
|
||||
*
|
||||
* message_size is the size of the message as the sender specified it.
|
||||
* message_type is the type of the message as the sender specified it.
|
||||
*
|
||||
*/
|
||||
typedef void(*handler_t)(int, uint8_t*, int, uint32_t, uint32_t);
|
||||
|
||||
/* Macro to declare a callback */
|
||||
#define IPC_HANDLER(name) \
|
||||
static void handle_ ## name (int fd, uint8_t *message, \
|
||||
int size, uint32_t message_size, \
|
||||
uint32_t message_type)
|
||||
|
||||
/**
|
||||
* Handler for activity on the listening socket, meaning that a new client
|
||||
* has just connected and we should accept() him. Sets up the event handler
|
||||
|
@ -32,4 +60,18 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents);
|
|||
*/
|
||||
int ipc_create_socket(const char *filename);
|
||||
|
||||
/**
|
||||
* Sends the specified event to all IPC clients which are currently connected
|
||||
* and subscribed to this kind of event.
|
||||
*
|
||||
*/
|
||||
void ipc_send_event(const char *event, uint32_t message_type, const char *payload);
|
||||
|
||||
/**
|
||||
* Calls shutdown() on each socket and closes it. This function to be called
|
||||
* when exiting or restarting only!
|
||||
*
|
||||
*/
|
||||
void ipc_shutdown();
|
||||
|
||||
#endif
|
||||
|
|
|
@ -79,7 +79,7 @@ void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace,
|
|||
* Renders the given workspace on the given screen
|
||||
*
|
||||
*/
|
||||
void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws);
|
||||
void render_workspace(xcb_connection_t *conn, Output *output, Workspace *r_ws);
|
||||
|
||||
/**
|
||||
* Renders the whole layout, that is: Go through each screen, each workspace,
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* vim:ts=8:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
*/
|
||||
#ifndef _LOG_H
|
||||
#define _LOG_H
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/** ##__VA_ARGS__ means: leave out __VA_ARGS__ completely if it is empty, that
|
||||
is, delete the preceding comma */
|
||||
#define LOG(fmt, ...) verboselog(fmt, ##__VA_ARGS__)
|
||||
#define ELOG(fmt, ...) errorlog("ERROR: " fmt, ##__VA_ARGS__)
|
||||
#define DLOG(fmt, ...) debuglog(LOGLEVEL, "%s:%s:%d - " fmt, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__)
|
||||
|
||||
extern char *loglevels[];
|
||||
|
||||
/**
|
||||
* Enables the given loglevel.
|
||||
*
|
||||
*/
|
||||
void add_loglevel(const char *level);
|
||||
|
||||
/**
|
||||
* Set verbosity of i3. If verbose is set to true, informative messages will
|
||||
* be printed to stdout. If verbose is set to false, only errors will be
|
||||
* printed.
|
||||
*
|
||||
*/
|
||||
void set_verbosity(bool _verbose);
|
||||
|
||||
/**
|
||||
* Logs the given message to stdout while prefixing the current time to it,
|
||||
* but only if the corresponding debug loglevel was activated.
|
||||
*
|
||||
*/
|
||||
void debuglog(int lev, char *fmt, ...);
|
||||
|
||||
/**
|
||||
* Logs the given message to stdout while prefixing the current time to it.
|
||||
*
|
||||
*/
|
||||
void errorlog(char *fmt, ...);
|
||||
|
||||
/**
|
||||
* Logs the given message to stdout while prefixing the current time to it,
|
||||
* but only if verbose mode is activated.
|
||||
*
|
||||
*/
|
||||
void verboselog(char *fmt, ...);
|
||||
|
||||
/**
|
||||
* Logs the given message to stdout while prefixing the current time to it.
|
||||
* This is to be called by LOG() which includes filename/linenumber
|
||||
*
|
||||
*/
|
||||
void slog(char *fmt, va_list args);
|
||||
|
||||
#endif
|
|
@ -23,6 +23,16 @@
|
|||
void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t
|
||||
*prophs, xcb_window_t root);
|
||||
|
||||
/**
|
||||
* Restores the geometry of each window by reparenting it to the root window
|
||||
* at the position of its frame.
|
||||
*
|
||||
* This is to be called *only* before exiting/restarting i3 because of evil
|
||||
* side-effects which are to be expected when continuing to run i3.
|
||||
*
|
||||
*/
|
||||
void restore_geometry(xcb_connection_t *conn);
|
||||
|
||||
/**
|
||||
* Do some sanity checks and then reparent the window.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* vim:ts=8:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
*/
|
||||
#include "data.h"
|
||||
#include <xcb/randr.h>
|
||||
|
||||
#ifndef _RANDR_H
|
||||
#define _RANDR_H
|
||||
|
||||
TAILQ_HEAD(outputs_head, xoutput);
|
||||
extern struct outputs_head outputs;
|
||||
|
||||
/**
|
||||
* We have just established a connection to the X server and need the initial
|
||||
* XRandR information to setup workspaces for each screen.
|
||||
*
|
||||
*/
|
||||
void initialize_randr(xcb_connection_t *conn, int *event_base);
|
||||
|
||||
/**
|
||||
* Disables RandR support by creating exactly one output with the size of the
|
||||
* X11 screen.
|
||||
*
|
||||
*/
|
||||
void disable_randr(xcb_connection_t *conn);
|
||||
|
||||
/**
|
||||
* Initializes the specified output, assigning the specified workspace to it.
|
||||
*
|
||||
*/
|
||||
void initialize_output(xcb_connection_t *conn, Output *output, Workspace *workspace);
|
||||
|
||||
/**
|
||||
* (Re-)queries the outputs via RandR and stores them in the list of outputs.
|
||||
*
|
||||
*/
|
||||
void randr_query_outputs(xcb_connection_t *conn);
|
||||
|
||||
/**
|
||||
* Returns the first output which is active.
|
||||
*
|
||||
*/
|
||||
Output *get_first_output();
|
||||
|
||||
/**
|
||||
* Returns the output with the given name if it is active (!) or NULL.
|
||||
*
|
||||
*/
|
||||
Output *get_output_by_name(const char *name);
|
||||
|
||||
/**
|
||||
* Returns the active (!) output which contains the coordinates x, y or NULL
|
||||
* if there is no output which contains these coordinates.
|
||||
*
|
||||
*/
|
||||
Output *get_output_containing(int x, int y);
|
||||
|
||||
/**
|
||||
* Gets the output which is the last one in the given direction, for example
|
||||
* the output on the most bottom when direction == D_DOWN, the output most
|
||||
* right when direction == D_RIGHT and so on.
|
||||
*
|
||||
* This function always returns a output.
|
||||
*
|
||||
*/
|
||||
Output *get_output_most(direction_t direction, Output *current);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* vim:ts=8:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Jan-Erik Rediger
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
*/
|
||||
#ifndef _SIGHANDLER_H
|
||||
#define _SIGHANDLER_H
|
||||
|
||||
/**
|
||||
* Setup signal handlers to safely handle SIGSEGV and SIGFPE
|
||||
*
|
||||
*/
|
||||
void setup_signal_handler();
|
||||
|
||||
#endif
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* (c) 2009 Michael Stapelberg and contributors
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
|
@ -34,10 +34,6 @@
|
|||
} \
|
||||
while (0)
|
||||
|
||||
/** ##__VA_ARGS__ means: leave out __VA_ARGS__ completely if it is empty, that
|
||||
is, delete the preceding comma */
|
||||
#define LOG(fmt, ...) slog("%s:%s:%d - " fmt, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__)
|
||||
|
||||
TAILQ_HEAD(keyvalue_table_head, keyvalue_element);
|
||||
extern struct keyvalue_table_head by_parent;
|
||||
extern struct keyvalue_table_head by_child;
|
||||
|
@ -46,11 +42,11 @@ int min(int a, int b);
|
|||
int max(int a, int b);
|
||||
|
||||
/**
|
||||
* Logs the given message to stdout while prefixing the current time to it.
|
||||
* This is to be called by LOG() which includes filename/linenumber
|
||||
* Updates *destination with new_value and returns true if it was changed or false
|
||||
* if it was the same
|
||||
*
|
||||
*/
|
||||
void slog(char *fmt, ...);
|
||||
bool update_if_necessary(uint32_t *destination, const uint32_t new_value);
|
||||
|
||||
/**
|
||||
* Safe-wrapper around malloc which exits if malloc returns NULL (meaning that
|
||||
|
@ -161,6 +157,13 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode);
|
|||
Client *get_matching_client(xcb_connection_t *conn,
|
||||
const char *window_classtitle, Client *specific);
|
||||
|
||||
/*
|
||||
* Restart i3 in-place
|
||||
* appends -a to argument list to disable autostart
|
||||
*
|
||||
*/
|
||||
void i3_restart();
|
||||
|
||||
#if defined(__OpenBSD__)
|
||||
/* OpenBSD does not provide memmem(), so we provide FreeBSD’s implementation */
|
||||
void *memmem(const void *l, size_t l_len, const void *s, size_t s_len);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
|
@ -11,7 +11,7 @@
|
|||
#include <xcb/xcb.h>
|
||||
|
||||
#include "data.h"
|
||||
#include "xinerama.h"
|
||||
#include "randr.h"
|
||||
|
||||
#ifndef _WORKSPACE_H
|
||||
#define _WORKSPACE_H
|
||||
|
@ -44,6 +44,17 @@ bool workspace_is_visible(Workspace *ws);
|
|||
/** Switches to the given workspace */
|
||||
void workspace_show(xcb_connection_t *conn, int workspace);
|
||||
|
||||
/**
|
||||
* Assigns the given workspace to the given screen by correctly updating its
|
||||
* state and reconfiguring all the clients on this workspace.
|
||||
*
|
||||
* This is called when initializing a screen and when re-assigning it to a
|
||||
* different screen which just got available (if you configured it to be on
|
||||
* screen 1 and you just plugged in screen 1).
|
||||
*
|
||||
*/
|
||||
void workspace_assign_to(Workspace *ws, Output *screen, bool hide_it);
|
||||
|
||||
/**
|
||||
* Initializes the given workspace if it is not already initialized. The given
|
||||
* screen is to be understood as a fallback, if the workspace itself either
|
||||
|
@ -51,14 +62,14 @@ void workspace_show(xcb_connection_t *conn, int workspace);
|
|||
* the screen is not attached at the moment.
|
||||
*
|
||||
*/
|
||||
void workspace_initialize(Workspace *ws, i3Screen *screen);
|
||||
void workspace_initialize(Workspace *ws, Output *screen, bool recheck);
|
||||
|
||||
/**
|
||||
* Gets the first unused workspace for the given screen, taking into account
|
||||
* the preferred_screen setting of every workspace (workspace assignments).
|
||||
*
|
||||
*/
|
||||
Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen *screen);
|
||||
Workspace *get_first_workspace_for_output(Output *screen);
|
||||
|
||||
/**
|
||||
* Unmaps all clients (and stack windows) of the given workspace.
|
||||
|
|
|
@ -61,7 +61,10 @@ enum { _NET_SUPPORTED = 0,
|
|||
WM_DELETE_WINDOW,
|
||||
UTF8_STRING,
|
||||
WM_STATE,
|
||||
WM_CLIENT_LEADER
|
||||
WM_CLIENT_LEADER,
|
||||
_NET_CURRENT_DESKTOP,
|
||||
_NET_ACTIVE_WINDOW,
|
||||
_NET_WORKAREA
|
||||
};
|
||||
|
||||
extern unsigned int xcb_numlock_mask;
|
||||
|
@ -161,4 +164,10 @@ void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap)
|
|||
int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *text,
|
||||
int length);
|
||||
|
||||
/**
|
||||
* Configures the given window to have the size/position specified by given rect
|
||||
*
|
||||
*/
|
||||
void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* (c) 2009 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
|
@ -13,16 +13,6 @@
|
|||
#ifndef _XINERAMA_H
|
||||
#define _XINERAMA_H
|
||||
|
||||
TAILQ_HEAD(screens_head, Screen);
|
||||
extern struct screens_head *virtual_screens;
|
||||
|
||||
/**
|
||||
* Returns true if both screen objects describe the same screen (checks their
|
||||
* size and position).
|
||||
*
|
||||
*/
|
||||
bool screens_are_equal(i3Screen *screen1, i3Screen *screen2);
|
||||
|
||||
/**
|
||||
* We have just established a connection to the X server and need the initial
|
||||
* Xinerama information to setup workspaces for each screen.
|
||||
|
@ -30,33 +20,4 @@ bool screens_are_equal(i3Screen *screen1, i3Screen *screen2);
|
|||
*/
|
||||
void initialize_xinerama(xcb_connection_t *conn);
|
||||
|
||||
/**
|
||||
* This is called when the rootwindow receives a configure_notify event and
|
||||
* therefore the number/position of the Xinerama screens could have changed.
|
||||
*
|
||||
*/
|
||||
void xinerama_requery_screens(xcb_connection_t *conn);
|
||||
|
||||
/**
|
||||
* Looks in virtual_screens for the i3Screen whose start coordinates are x, y
|
||||
*
|
||||
*/
|
||||
i3Screen *get_screen_at(int x, int y, struct screens_head *screenlist);
|
||||
|
||||
/**
|
||||
* Looks in virtual_screens for the i3Screen which contains coordinates x, y
|
||||
*
|
||||
*/
|
||||
i3Screen *get_screen_containing(int x, int y);
|
||||
|
||||
/**
|
||||
* Gets the screen which is the last one in the given direction, for example
|
||||
* the screen on the most bottom when direction == D_DOWN, the screen most
|
||||
* right when direction == D_RIGHT and so on.
|
||||
*
|
||||
* This function always returns a screen.
|
||||
*
|
||||
*/
|
||||
i3Screen *get_screen_most(direction_t direction, i3Screen *current);
|
||||
|
||||
#endif
|
||||
|
|
22
man/Makefile
22
man/Makefile
|
@ -1,6 +1,18 @@
|
|||
all:
|
||||
a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3.man
|
||||
a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3-msg.man
|
||||
a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3-input.man
|
||||
A2M:=a2x -f manpage --asciidoc-opts="-f asciidoc.conf"
|
||||
|
||||
all: i3.1 i3-msg.1 i3-input.1 i3-wsbar.1
|
||||
|
||||
%.1: %.man asciidoc.conf
|
||||
${A2M} $<
|
||||
|
||||
i3-wsbar.1: ../i3-wsbar
|
||||
pod2man $^ > $@
|
||||
|
||||
clean:
|
||||
rm -f i3.{1,html,xml} i3-msg.{1,html,xml} i3-input.{1,html,xml}
|
||||
for file in "i3 i3-msg i3-input"; \
|
||||
do \
|
||||
rm -f $${file}.1 $${file}.html $${file}.xml; \
|
||||
done
|
||||
|
||||
distclean: clean
|
||||
rm -f *.1
|
||||
|
|
|
@ -7,7 +7,7 @@ template::[header-declarations]
|
|||
<refentrytitle>{mantitle}</refentrytitle>
|
||||
<manvolnum>{manvolnum}</manvolnum>
|
||||
<refmiscinfo class="source">i3</refmiscinfo>
|
||||
<refmiscinfo class="version">delta</refmiscinfo>
|
||||
<refmiscinfo class="version">epsilon</refmiscinfo>
|
||||
<refmiscinfo class="manual">i3 Manual</refmiscinfo>
|
||||
</refmeta>
|
||||
<refnamediv>
|
||||
|
|
|
@ -5,7 +5,7 @@ v3.delta, November 2009
|
|||
|
||||
== NAME
|
||||
|
||||
i3-input - interactively take a command for i3
|
||||
i3-input - interactively take a command for i3 window manager
|
||||
|
||||
== SYNOPSIS
|
||||
|
||||
|
@ -13,8 +13,9 @@ i3-input [-s <socket>] [-p <prefix>] [-l <limit>] [-P <prompt>] [-v]
|
|||
|
||||
== DESCRIPTION
|
||||
|
||||
i3-input is a tool to take commands (or parts of a command) and then send it
|
||||
to i3. This is useful for example for the mark/goto command.
|
||||
i3-input is a tool to take commands (or parts of a command) composed by
|
||||
the user, and send it/them to i3. This is useful, for example, for the
|
||||
mark/goto command.
|
||||
|
||||
== EXAMPLE
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ v3.delta, November 2009
|
|||
|
||||
== NAME
|
||||
|
||||
i3-msg - send messages to i3
|
||||
i3-msg - send messages to i3 window manager
|
||||
|
||||
== SYNOPSIS
|
||||
|
||||
|
|
117
man/i3.man
117
man/i3.man
|
@ -1,7 +1,7 @@
|
|||
i3(1)
|
||||
=====
|
||||
Michael Stapelberg <michael+i3@stapelberg.de>
|
||||
v3.delta, November 2009
|
||||
v3.epsilon, March 2010
|
||||
|
||||
== NAME
|
||||
|
||||
|
@ -9,65 +9,88 @@ i3 - an improved dynamic, tiling window manager
|
|||
|
||||
== SYNOPSIS
|
||||
|
||||
i3 [-c configfile] [-a]
|
||||
i3 [-a] [-c configfile] [-C] [-d <loglevel>] [-v] [-V]
|
||||
|
||||
== OPTIONS
|
||||
|
||||
-c::
|
||||
Specifies an alternate configuration file path
|
||||
|
||||
-a::
|
||||
Disables autostart.
|
||||
|
||||
-c::
|
||||
Specifies an alternate configuration file path.
|
||||
|
||||
-C::
|
||||
Check the configuration file for validity and exit.
|
||||
|
||||
-d::
|
||||
Specifies the debug loglevel. To see the most output, use -d all.
|
||||
|
||||
-v::
|
||||
Display version number (and date of the last commit).
|
||||
|
||||
-V::
|
||||
Be verbose.
|
||||
|
||||
== DESCRIPTION
|
||||
|
||||
=== INTRODUCTION
|
||||
|
||||
i3 was created because wmii, our favorite window manager at the time, didn’t
|
||||
provide some features we wanted (Xinerama done right, for example), had some
|
||||
bugs, didn’t progress since quite some time and wasn’t easy to hack at all
|
||||
provide some features we wanted (multi-monitor done right, for example), had
|
||||
some bugs, didn’t progress since quite some time and wasn’t easy to hack at all
|
||||
(source code comments/documentation completely lacking). Still, we think the
|
||||
wmii developers and contributors did a great job. Thank you for inspiring us to
|
||||
create i3.
|
||||
|
||||
Please be aware that i3 is primarily targeted at advanced users and developers.
|
||||
|
||||
=== IMPORTANT NOTE TO nVidia BINARY DRIVER USERS
|
||||
|
||||
If you are using the nVidia binary graphics driver (also known as 'blob')
|
||||
you need to use the +--force-xinerama+ flag (in your .xsession) when starting
|
||||
i3, like so:
|
||||
|
||||
----------------------------------------------
|
||||
exec i3 --force-xinerama -V >>~/.i3/i3log 2>&1
|
||||
----------------------------------------------
|
||||
|
||||
See also docs/multi-monitor for the full explanation.
|
||||
|
||||
=== TERMINOLOGY
|
||||
|
||||
Client::
|
||||
A client is X11-speak for a window.
|
||||
|
||||
Table::
|
||||
Your workspace is managed using a table. You can move windows around and create new columns
|
||||
(move a client to the right) or rows (move it to the bottom) implicitly.
|
||||
Your workspace is managed using a table. You can move windows around and create
|
||||
new columns (move a client to the right) or rows (move it to the bottom)
|
||||
implicitly.
|
||||
+
|
||||
By "snapping" a client in a specific direction, you increase its colspan/rowspan.
|
||||
|
||||
Container::
|
||||
A container contains a variable number of clients. Each cell of the table is a container.
|
||||
A container contains a variable number of clients. Each cell of the table is a
|
||||
container.
|
||||
+
|
||||
Containers can be used in various modes. The default mode is called "default" and just
|
||||
resizes each client equally so that it fits.
|
||||
Containers can be used in various modes. The default mode is called "default"
|
||||
and just resizes each client equally so that it fits.
|
||||
|
||||
Workspace::
|
||||
A workspace is a set of clients (technically speaking, it’s just a table). Other window
|
||||
managers call this "Virtual Desktops".
|
||||
A workspace is a set of clients (technically speaking, it’s just a table).
|
||||
Other window managers call this "Virtual Desktops".
|
||||
+
|
||||
In i3, each workspace is assigned to a specific virtual screen. By default, screen 1
|
||||
has workspace 1, screen 2 has workspace 2 and so on… However, when you create a new
|
||||
workspace (by simply switching to it), it’ll be assigned the screen you are currently
|
||||
on.
|
||||
In i3, each workspace is assigned to a specific virtual screen. By default,
|
||||
screen 1 has workspace 1, screen 2 has workspace 2 and so on… However, when you
|
||||
create a new workspace (by simply switching to it), it’ll be assigned the
|
||||
screen you are currently on.
|
||||
|
||||
Virtual Screen::
|
||||
Using Xinerama, you can have an X11 screen spanning multiple real monitors. Furthermore,
|
||||
you can set them up in cloning mode or with positions (monitor 1 is left of monitor 2).
|
||||
Output::
|
||||
Using XRandR, you can have an X11 screen spanning multiple real monitors.
|
||||
Furthermore, you can set them up in cloning mode or with positions (monitor 1
|
||||
is left of monitor 2).
|
||||
+
|
||||
A virtual screen is the result of your Xinerama setup. For example, if you have attached
|
||||
two real monitors (let’s say your laptop screen and a video projector) and enabled cloning, i3
|
||||
will use one virtual screen with the size of the smallest screen you have attached (so
|
||||
that you can see all your windows on each screen all the time).
|
||||
If you have two monitors attached, one configured to be left of the other, i3 will use
|
||||
two virtual screens.
|
||||
i3 uses the RandR API to query which outputs are available and which screens
|
||||
are connected to these outputs.
|
||||
|
||||
== KEYBINDINGS
|
||||
|
||||
|
@ -114,31 +137,34 @@ Mod1+t::
|
|||
Select the first tiling window if the current window is floating and vice-versa.
|
||||
|
||||
Mod1+Shift+q::
|
||||
Kills the current window. This is equivalent to "clicking on the close button", meaning a polite
|
||||
request to the application to close this window. For example, Firefox will save its session
|
||||
upon such a request. If the application does not support that, the window will be killed and
|
||||
it depends on the application what happens.
|
||||
Kills the current window. This is equivalent to "clicking on the close button",
|
||||
meaning a polite request to the application to close this window. For example,
|
||||
Firefox will save its session upon such a request. If the application does not
|
||||
support that, the window will be killed and it depends on the application what
|
||||
happens.
|
||||
|
||||
Mod1+Shift+r::
|
||||
Restarts i3 in place (without losing any windows, but the layout).
|
||||
Restarts i3 in place (without losing any windows, but at this time, the layout
|
||||
and placement of windows is not retained).
|
||||
|
||||
Mod1+Shift+e::
|
||||
Exits i3.
|
||||
|
||||
== FILES
|
||||
|
||||
=== ~/.i3/config
|
||||
=== \~/.i3/config (or ~/.config/i3/config)
|
||||
|
||||
When starting, i3 looks for ~/.i3/config and loads the configuration. If ~/.i3/config is not found,
|
||||
i3 tries /etc/i3/config. You can specify a custom path using the -c option.
|
||||
When starting, i3 looks for configuration files in the following order:
|
||||
|
||||
At the moment, you can specify only the path to your favorite terminal emulator, the font and keybindings.
|
||||
1. ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set)
|
||||
2. /etc/xdg/i3/config (or $XDG_CONFIG_DIRS/i3/config if set)
|
||||
3. ~/.i3/config
|
||||
4. /etc/i3/config
|
||||
|
||||
At the moment, you have to bind to keycodes (find them out via xev(1)).
|
||||
You can specify a custom path using the -c option.
|
||||
|
||||
.Sample configuration
|
||||
-------------------------------------------------------------
|
||||
terminal /usr/bin/urxvt
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
# Start terminal (Mod1+Enter)
|
||||
|
@ -265,22 +291,25 @@ ulimit -c unlimited
|
|||
|
||||
# Start i3 and log to ~/.i3/logfile
|
||||
echo "Starting at $(date)" >> ~/.i3/logfile
|
||||
exec /usr/bin/i3 >> ~/.i3/logfile
|
||||
exec /usr/bin/i3 -V -d all >> ~/.i3/logfile
|
||||
-------------------------------------------------------------
|
||||
|
||||
== TODO
|
||||
|
||||
There is still lot of work to do. Please check our bugtracker for up-to-date information
|
||||
about tasks which are still not finished.
|
||||
There is still lot of work to do. Please check our bugtracker for up-to-date
|
||||
information about tasks which are still not finished.
|
||||
|
||||
== SEE ALSO
|
||||
|
||||
You should have a copy of the userguide (featuring nice screenshots/graphics which is why this
|
||||
is not integrated into this manpage), the debugging guide and the "how to hack" guide. If you
|
||||
are building from source, run +make -C docs+.
|
||||
You should have a copy of the userguide (featuring nice screenshots/graphics
|
||||
which is why this is not integrated into this manpage), the debugging guide,
|
||||
and the "how to hack" guide. If you are building from source, run:
|
||||
+make -C docs+
|
||||
|
||||
You can also access these documents online at http://i3.zekjur.net/
|
||||
|
||||
i3-input(1), i3-msg(1), i3-wsbar(1)
|
||||
|
||||
== AUTHOR
|
||||
|
||||
Michael Stapelberg and contributors
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
%option nounput
|
||||
%option noinput
|
||||
%option noyy_top_state
|
||||
%option stack
|
||||
|
||||
%{
|
||||
/*
|
||||
|
@ -13,20 +15,59 @@
|
|||
|
||||
#include "data.h"
|
||||
#include "config.h"
|
||||
#include "log.h"
|
||||
#include "util.h"
|
||||
|
||||
int yycolumn = 1;
|
||||
|
||||
#define YY_DECL int yylex (struct context *context)
|
||||
|
||||
#define YY_USER_ACTION { \
|
||||
context->first_column = yycolumn; \
|
||||
context->last_column = yycolumn+yyleng-1; \
|
||||
yycolumn += yyleng; \
|
||||
}
|
||||
|
||||
%}
|
||||
|
||||
%Start BIND_COND
|
||||
%Start BINDSYM_COND
|
||||
%Start BIND_AWS_COND
|
||||
%Start BINDSYM_AWS_COND
|
||||
%Start BIND_A2WS_COND
|
||||
%Start ASSIGN_COND
|
||||
%Start COLOR_COND
|
||||
%Start SCREEN_COND
|
||||
%Start SCREEN_AWS_COND
|
||||
EOL (\r?\n)
|
||||
|
||||
%s BIND_COND
|
||||
%s BINDSYM_COND
|
||||
%s BIND_AWS_COND
|
||||
%s BINDSYM_AWS_COND
|
||||
%s BIND_A2WS_COND
|
||||
%s ASSIGN_COND
|
||||
%s COLOR_COND
|
||||
%s OUTPUT_COND
|
||||
%s OUTPUT_AWS_COND
|
||||
%x BUFFER_LINE
|
||||
|
||||
%%
|
||||
|
||||
{
|
||||
/* This is called when a new line is lexed. We only want the
|
||||
* first line to match to go into state BUFFER_LINE */
|
||||
if (context->line_number == 0) {
|
||||
context->line_number = 1;
|
||||
BEGIN(INITIAL);
|
||||
yy_push_state(BUFFER_LINE);
|
||||
}
|
||||
}
|
||||
|
||||
<BUFFER_LINE>^[^\r\n]*/{EOL}? {
|
||||
/* save whole line */
|
||||
context->line_copy = strdup(yytext);
|
||||
|
||||
yyless(0);
|
||||
yy_pop_state();
|
||||
yy_set_bol(true);
|
||||
yycolumn = 1;
|
||||
}
|
||||
|
||||
|
||||
<BIND_A2WS_COND>[^\n]+ { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR; }
|
||||
<OUTPUT_AWS_COND>[a-zA-Z0-9_-]+ { yylval.string = strdup(yytext); return OUTPUT; }
|
||||
^[ \t]*#[^\n]* { return TOKCOMMENT; }
|
||||
<COLOR_COND>[0-9a-fA-F]+ { yylval.string = strdup(yytext); return HEX; }
|
||||
[0-9]+ { yylval.number = atoi(yytext); return NUMBER; }
|
||||
|
@ -35,7 +76,14 @@ bind { BEGIN(BIND_COND); return TOKBIND; }
|
|||
bindsym { BEGIN(BINDSYM_COND); return TOKBINDSYM; }
|
||||
floating_modifier { BEGIN(INITIAL); return TOKFLOATING_MODIFIER; }
|
||||
workspace { BEGIN(INITIAL); return TOKWORKSPACE; }
|
||||
screen { BEGIN(SCREEN_COND); return TOKSCREEN; }
|
||||
output { BEGIN(OUTPUT_COND); return TOKOUTPUT; }
|
||||
screen {
|
||||
/* for compatibility until v3.φ */
|
||||
ELOG("Assignments to screens are DEPRECATED and will not work. " \
|
||||
"Please replace them with assignments to outputs.\n");
|
||||
BEGIN(OUTPUT_COND);
|
||||
return TOKOUTPUT;
|
||||
}
|
||||
terminal { BEGIN(BIND_AWS_COND); return TOKTERMINAL; }
|
||||
font { BEGIN(BIND_AWS_COND); return TOKFONT; }
|
||||
assign { BEGIN(ASSIGN_COND); return TOKASSIGN; }
|
||||
|
@ -44,6 +92,8 @@ ipc-socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; }
|
|||
ipc_socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; }
|
||||
new_container { return TOKNEWCONTAINER; }
|
||||
new_window { return TOKNEWWINDOW; }
|
||||
focus_follows_mouse { return TOKFOCUSFOLLOWSMOUSE; }
|
||||
workspace_bar { return TOKWORKSPACEBAR; }
|
||||
default { yylval.number = MODE_DEFAULT; return TOKCONTAINERMODE; }
|
||||
stacking { yylval.number = MODE_STACK; return TOKCONTAINERMODE; }
|
||||
tabbed { yylval.number = MODE_TABBED; return TOKCONTAINERMODE; }
|
||||
|
@ -65,16 +115,21 @@ Mod4 { yylval.number = BIND_MOD4; return MODIFIER; }
|
|||
Mod5 { yylval.number = BIND_MOD5; return MODIFIER; }
|
||||
Mode_switch { yylval.number = BIND_MODE_SWITCH; return MODIFIER; }
|
||||
control { return TOKCONTROL; }
|
||||
ctrl { return TOKCONTROL; }
|
||||
shift { return TOKSHIFT; }
|
||||
→ { return TOKARROW; }
|
||||
\n /* ignore end of line */;
|
||||
<SCREEN_AWS_COND>x { return (int)yytext[0]; }
|
||||
{EOL} {
|
||||
FREE(context->line_copy);
|
||||
context->line_number++;
|
||||
BEGIN(INITIAL);
|
||||
yy_push_state(BUFFER_LINE);
|
||||
}
|
||||
<BIND_COND>[ \t]+ { BEGIN(BIND_AWS_COND); return WHITESPACE; }
|
||||
<BINDSYM_COND>[ \t]+ { BEGIN(BINDSYM_AWS_COND); return WHITESPACE; }
|
||||
<BIND_AWS_COND>[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
|
||||
<BINDSYM_AWS_COND>[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
|
||||
<SCREEN_COND>[ \t]+ { BEGIN(SCREEN_AWS_COND); return WHITESPACE; }
|
||||
<SCREEN_AWS_COND>[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
|
||||
<OUTPUT_COND>[ \t]+ { BEGIN(OUTPUT_AWS_COND); return WHITESPACE; }
|
||||
<OUTPUT_AWS_COND>[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
|
||||
[ \t]+ { return WHITESPACE; }
|
||||
\"[^\"]+\" {
|
||||
/* if ASSIGN_COND then */
|
||||
|
@ -89,4 +144,11 @@ shift { return TOKSHIFT; }
|
|||
<BINDSYM_AWS_COND>[a-zA-Z0-9_]+ { yylval.string = strdup(yytext); return WORD; }
|
||||
[a-zA-Z]+ { yylval.string = strdup(yytext); return WORD; }
|
||||
. { return (int)yytext[0]; }
|
||||
|
||||
<<EOF>> {
|
||||
while (yy_start_stack_ptr > 0)
|
||||
yy_pop_state();
|
||||
yyterminate();
|
||||
}
|
||||
|
||||
%%
|
||||
|
|
186
src/cfgparse.y
186
src/cfgparse.y
|
@ -21,20 +21,35 @@
|
|||
#include "table.h"
|
||||
#include "workspace.h"
|
||||
#include "xcb.h"
|
||||
|
||||
#include "log.h"
|
||||
|
||||
typedef struct yy_buffer_state *YY_BUFFER_STATE;
|
||||
extern int yylex(void);
|
||||
extern int yylex(struct context *context);
|
||||
extern int yyparse(void);
|
||||
extern FILE *yyin;
|
||||
YY_BUFFER_STATE yy_scan_string(const char *);
|
||||
|
||||
static struct bindings_head *current_bindings;
|
||||
static struct context *context;
|
||||
|
||||
int yydebug = 1;
|
||||
/* We don’t need yydebug for now, as we got decent error messages using
|
||||
* yyerror(). Should you ever want to extend the parser, it might be handy
|
||||
* to just comment it in again, so it stays here. */
|
||||
//int yydebug = 1;
|
||||
|
||||
void yyerror(const char *str) {
|
||||
fprintf(stderr,"error: %s\n",str);
|
||||
void yyerror(const char *error_message) {
|
||||
ELOG("\n");
|
||||
ELOG("CONFIG: %s\n", error_message);
|
||||
ELOG("CONFIG: in file \"%s\", line %d:\n",
|
||||
context->filename, context->line_number);
|
||||
ELOG("CONFIG: %s\n", context->line_copy);
|
||||
ELOG("CONFIG: ");
|
||||
for (int c = 1; c <= context->last_column; c++)
|
||||
if (c >= context->first_column)
|
||||
printf("^");
|
||||
else printf(" ");
|
||||
printf("\n");
|
||||
ELOG("\n");
|
||||
}
|
||||
|
||||
int yywrap() {
|
||||
|
@ -55,7 +70,7 @@ void parse_file(const char *f) {
|
|||
if (fstat(fd, &stbuf) == -1)
|
||||
die("Could not fstat file: %s\n", strerror(errno));
|
||||
|
||||
buf = smalloc(stbuf.st_size * sizeof(char));
|
||||
buf = scalloc((stbuf.st_size + 1) * sizeof(char));
|
||||
while (read_bytes < stbuf.st_size) {
|
||||
if ((ret = read(fd, buf + read_bytes, (stbuf.st_size - read_bytes))) < 0)
|
||||
die("Could not read(): %s\n", strerror(errno));
|
||||
|
@ -95,7 +110,7 @@ void parse_file(const char *f) {
|
|||
new->key = sstrdup(v_key);
|
||||
new->value = sstrdup(v_value);
|
||||
SLIST_INSERT_HEAD(&variables, new, variables);
|
||||
LOG("Got new variable %s = %s\n", v_key, v_value);
|
||||
DLOG("Got new variable %s = %s\n", v_key, v_value);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -149,18 +164,33 @@ void parse_file(const char *f) {
|
|||
|
||||
yy_scan_string(new);
|
||||
|
||||
context = scalloc(sizeof(struct context));
|
||||
context->filename = f;
|
||||
|
||||
if (yyparse() != 0) {
|
||||
fprintf(stderr, "Could not parse configfile\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
FREE(context->line_copy);
|
||||
free(context);
|
||||
free(new);
|
||||
free(buf);
|
||||
|
||||
while (!SLIST_EMPTY(&variables)) {
|
||||
current = SLIST_FIRST(&variables);
|
||||
FREE(current->key);
|
||||
FREE(current->value);
|
||||
SLIST_REMOVE_HEAD(&variables, variables);
|
||||
FREE(current);
|
||||
}
|
||||
}
|
||||
|
||||
%}
|
||||
|
||||
%expect 1
|
||||
%error-verbose
|
||||
%lex-param { struct context *context }
|
||||
|
||||
%union {
|
||||
int number;
|
||||
|
@ -170,40 +200,44 @@ void parse_file(const char *f) {
|
|||
struct Binding *binding;
|
||||
}
|
||||
|
||||
%token <number>NUMBER
|
||||
%token <string>WORD
|
||||
%token <string>STR
|
||||
%token <string>STR_NG
|
||||
%token <string>HEX
|
||||
%token <number>NUMBER "<number>"
|
||||
%token <string>WORD "<word>"
|
||||
%token <string>STR "<string>"
|
||||
%token <string>STR_NG "<string (non-greedy)>"
|
||||
%token <string>HEX "<hex>"
|
||||
%token <string>OUTPUT "<RandR output>"
|
||||
%token TOKBIND
|
||||
%token TOKTERMINAL
|
||||
%token TOKCOMMENT
|
||||
%token TOKFONT
|
||||
%token TOKBINDSYM
|
||||
%token MODIFIER
|
||||
%token TOKCONTROL
|
||||
%token TOKSHIFT
|
||||
%token WHITESPACE
|
||||
%token TOKFLOATING_MODIFIER
|
||||
%token QUOTEDSTRING
|
||||
%token TOKWORKSPACE
|
||||
%token TOKSCREEN
|
||||
%token TOKASSIGN
|
||||
%token TOKCOMMENT "<comment>"
|
||||
%token TOKFONT "font"
|
||||
%token TOKBINDSYM "bindsym"
|
||||
%token MODIFIER "<modifier>"
|
||||
%token TOKCONTROL "control"
|
||||
%token TOKSHIFT "shift"
|
||||
%token WHITESPACE "<whitespace>"
|
||||
%token TOKFLOATING_MODIFIER "floating_modifier"
|
||||
%token QUOTEDSTRING "<quoted string>"
|
||||
%token TOKWORKSPACE "workspace"
|
||||
%token TOKOUTPUT "output"
|
||||
%token TOKASSIGN "assign"
|
||||
%token TOKSET
|
||||
%token TOKIPCSOCKET
|
||||
%token TOKEXEC
|
||||
%token TOKIPCSOCKET "ipc_socket"
|
||||
%token TOKEXEC "exec"
|
||||
%token TOKCOLOR
|
||||
%token TOKARROW
|
||||
%token TOKMODE
|
||||
%token TOKNEWCONTAINER
|
||||
%token TOKNEWWINDOW
|
||||
%token TOKCONTAINERMODE
|
||||
%token TOKSTACKLIMIT
|
||||
%token TOKARROW "→"
|
||||
%token TOKMODE "mode"
|
||||
%token TOKNEWCONTAINER "new_container"
|
||||
%token TOKNEWWINDOW "new_window"
|
||||
%token TOKFOCUSFOLLOWSMOUSE "focus_follows_mouse"
|
||||
%token TOKWORKSPACEBAR "workspace_bar"
|
||||
%token TOKCONTAINERMODE "default/stacking/tabbed"
|
||||
%token TOKSTACKLIMIT "stack-limit"
|
||||
|
||||
%%
|
||||
|
||||
lines: /* empty */
|
||||
| lines WHITESPACE line
|
||||
| lines error
|
||||
| lines line
|
||||
;
|
||||
|
||||
|
@ -213,6 +247,8 @@ line:
|
|||
| floating_modifier
|
||||
| new_container
|
||||
| new_window
|
||||
| focus_follows_mouse
|
||||
| workspace_bar
|
||||
| workspace
|
||||
| assign
|
||||
| ipcsocket
|
||||
|
@ -251,7 +287,7 @@ bind:
|
|||
|
||||
new->keycode = $<number>2;
|
||||
new->mods = $<number>1;
|
||||
new->command = sstrdup($<string>4);
|
||||
new->command = $<string>4;
|
||||
|
||||
$<binding>$ = new;
|
||||
}
|
||||
|
@ -263,9 +299,9 @@ bindsym:
|
|||
printf("\tFound symbolic mod%d with key %s and command %s\n", $<number>1, $<string>2, $<string>4);
|
||||
Binding *new = scalloc(sizeof(Binding));
|
||||
|
||||
new->symbol = sstrdup($<string>2);
|
||||
new->symbol = $<string>2;
|
||||
new->mods = $<number>1;
|
||||
new->command = sstrdup($<string>4);
|
||||
new->command = $<string>4;
|
||||
|
||||
$<binding>$ = new;
|
||||
}
|
||||
|
@ -295,7 +331,7 @@ mode:
|
|||
}
|
||||
|
||||
struct Mode *mode = scalloc(sizeof(struct Mode));
|
||||
mode->name = strdup($<string>3);
|
||||
mode->name = $<string>3;
|
||||
mode->bindings = current_bindings;
|
||||
current_bindings = NULL;
|
||||
SLIST_INSERT_HEAD(&modes, mode, modes);
|
||||
|
@ -325,7 +361,7 @@ modeline:
|
|||
floating_modifier:
|
||||
TOKFLOATING_MODIFIER WHITESPACE binding_modifiers
|
||||
{
|
||||
LOG("floating modifier = %d\n", $<number>3);
|
||||
DLOG("floating modifier = %d\n", $<number>3);
|
||||
config.floating_modifier = $<number>3;
|
||||
}
|
||||
;
|
||||
|
@ -333,7 +369,7 @@ floating_modifier:
|
|||
new_container:
|
||||
TOKNEWCONTAINER WHITESPACE TOKCONTAINERMODE
|
||||
{
|
||||
LOG("new containers will be in mode %d\n", $<number>3);
|
||||
DLOG("new containers will be in mode %d\n", $<number>3);
|
||||
config.container_mode = $<number>3;
|
||||
|
||||
/* We also need to change the layout of the already existing
|
||||
|
@ -355,7 +391,7 @@ new_container:
|
|||
}
|
||||
| TOKNEWCONTAINER WHITESPACE TOKSTACKLIMIT WHITESPACE TOKSTACKLIMIT WHITESPACE NUMBER
|
||||
{
|
||||
LOG("stack-limit %d with val %d\n", $<number>5, $<number>7);
|
||||
DLOG("stack-limit %d with val %d\n", $<number>5, $<number>7);
|
||||
config.container_stack_limit = $<number>5;
|
||||
config.container_stack_limit_value = $<number>7;
|
||||
|
||||
|
@ -374,32 +410,69 @@ new_container:
|
|||
new_window:
|
||||
TOKNEWWINDOW WHITESPACE WORD
|
||||
{
|
||||
LOG("new windows should start in mode %s\n", $<string>3);
|
||||
config.default_border = strdup($<string>3);
|
||||
DLOG("new windows should start in mode %s\n", $<string>3);
|
||||
config.default_border = sstrdup($<string>3);
|
||||
}
|
||||
;
|
||||
|
||||
bool:
|
||||
NUMBER
|
||||
{
|
||||
$<number>$ = ($<number>1 == 1);
|
||||
}
|
||||
| WORD
|
||||
{
|
||||
DLOG("checking word \"%s\"\n", $<string>1);
|
||||
$<number>$ = (strcasecmp($<string>1, "yes") == 0 ||
|
||||
strcasecmp($<string>1, "true") == 0 ||
|
||||
strcasecmp($<string>1, "on") == 0 ||
|
||||
strcasecmp($<string>1, "enable") == 0 ||
|
||||
strcasecmp($<string>1, "active") == 0);
|
||||
}
|
||||
;
|
||||
|
||||
focus_follows_mouse:
|
||||
TOKFOCUSFOLLOWSMOUSE WHITESPACE bool
|
||||
{
|
||||
DLOG("focus follows mouse = %d\n", $<number>3);
|
||||
config.disable_focus_follows_mouse = !($<number>3);
|
||||
}
|
||||
;
|
||||
|
||||
workspace_bar:
|
||||
TOKWORKSPACEBAR WHITESPACE bool
|
||||
{
|
||||
DLOG("workspace bar = %d\n", $<number>3);
|
||||
config.disable_workspace_bar = !($<number>3);
|
||||
}
|
||||
;
|
||||
|
||||
workspace:
|
||||
TOKWORKSPACE WHITESPACE NUMBER WHITESPACE TOKSCREEN WHITESPACE screen optional_workspace_name
|
||||
TOKWORKSPACE WHITESPACE NUMBER WHITESPACE TOKOUTPUT WHITESPACE OUTPUT optional_workspace_name
|
||||
{
|
||||
int ws_num = $<number>3;
|
||||
if (ws_num < 1) {
|
||||
LOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
|
||||
DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
|
||||
} else {
|
||||
Workspace *ws = workspace_get(ws_num - 1);
|
||||
ws->preferred_screen = sstrdup($<string>7);
|
||||
if ($<string>8 != NULL)
|
||||
ws->preferred_output = $<string>7;
|
||||
if ($<string>8 != NULL) {
|
||||
workspace_set_name(ws, $<string>8);
|
||||
free($<string>8);
|
||||
}
|
||||
}
|
||||
}
|
||||
| TOKWORKSPACE WHITESPACE NUMBER WHITESPACE workspace_name
|
||||
{
|
||||
int ws_num = $<number>3;
|
||||
if (ws_num < 1) {
|
||||
LOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
|
||||
DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
|
||||
} else {
|
||||
if ($<string>5 != NULL)
|
||||
DLOG("workspace name to: %s\n", $<string>5);
|
||||
if ($<string>5 != NULL) {
|
||||
workspace_set_name(workspace_get(ws_num - 1), $<string>5);
|
||||
free($<string>5);
|
||||
}
|
||||
}
|
||||
}
|
||||
;
|
||||
|
@ -415,13 +488,6 @@ workspace_name:
|
|||
| WORD { $<string>$ = $<string>1; }
|
||||
;
|
||||
|
||||
screen:
|
||||
NUMBER { asprintf(&$<string>$, "%d", $<number>1); }
|
||||
| NUMBER 'x' { asprintf(&$<string>$, "%d", $<number>1); }
|
||||
| NUMBER 'x' NUMBER { asprintf(&$<string>$, "%dx%d", $<number>1, $<number>3); }
|
||||
| 'x' NUMBER { asprintf(&$<string>$, "x%d", $<number>2); }
|
||||
;
|
||||
|
||||
assign:
|
||||
TOKASSIGN WHITESPACE window_class WHITESPACE optional_arrow assign_target
|
||||
{
|
||||
|
@ -430,7 +496,7 @@ assign:
|
|||
struct Assignment *new = $<assignment>6;
|
||||
printf(" to %d\n", new->workspace);
|
||||
printf(" floating = %d\n", new->floating);
|
||||
new->windowclass_title = strdup($<string>3);
|
||||
new->windowclass_title = $<string>3;
|
||||
TAILQ_INSERT_TAIL(&assignments, new, assignments);
|
||||
}
|
||||
;
|
||||
|
@ -471,7 +537,7 @@ optional_arrow:
|
|||
ipcsocket:
|
||||
TOKIPCSOCKET WHITESPACE STR
|
||||
{
|
||||
config.ipc_socket_path = sstrdup($<string>3);
|
||||
config.ipc_socket_path = $<string>3;
|
||||
}
|
||||
;
|
||||
|
||||
|
@ -479,7 +545,7 @@ exec:
|
|||
TOKEXEC WHITESPACE STR
|
||||
{
|
||||
struct Autostart *new = smalloc(sizeof(struct Autostart));
|
||||
new->command = sstrdup($<string>3);
|
||||
new->command = $<string>3;
|
||||
TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
|
||||
}
|
||||
;
|
||||
|
@ -487,15 +553,15 @@ exec:
|
|||
terminal:
|
||||
TOKTERMINAL WHITESPACE STR
|
||||
{
|
||||
config.terminal = sstrdup($<string>3);
|
||||
printf("terminal %s\n", config.terminal);
|
||||
ELOG("The terminal option is DEPRECATED and has no effect. "
|
||||
"Please remove it from your configuration file.\n");
|
||||
}
|
||||
;
|
||||
|
||||
font:
|
||||
TOKFONT WHITESPACE STR
|
||||
{
|
||||
config.font = sstrdup($<string>3);
|
||||
config.font = $<string>3;
|
||||
printf("font %s\n", config.font);
|
||||
}
|
||||
;
|
||||
|
|
71
src/click.c
71
src/click.c
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
|
@ -36,6 +36,8 @@
|
|||
#include "commands.h"
|
||||
#include "floating.h"
|
||||
#include "resize.h"
|
||||
#include "log.h"
|
||||
#include "randr.h"
|
||||
|
||||
static struct Stack_Window *get_stack_window(xcb_window_t window_id) {
|
||||
struct Stack_Window *current;
|
||||
|
@ -97,18 +99,18 @@ static bool button_press_stackwin(xcb_connection_t *conn, xcb_button_press_event
|
|||
int wrap = ceil((float)num_clients / container->stack_limit_value);
|
||||
int clicked_column = (event->event_x / (stack_win->rect.width / container->stack_limit_value));
|
||||
int clicked_row = (event->event_y / decoration_height);
|
||||
LOG("clicked on column %d, row %d\n", clicked_column, clicked_row);
|
||||
DLOG("clicked on column %d, row %d\n", clicked_column, clicked_row);
|
||||
destination = (wrap * clicked_column) + clicked_row;
|
||||
} else {
|
||||
int width = (stack_win->rect.width / ceil((float)num_clients / container->stack_limit_value));
|
||||
int clicked_column = (event->event_x / width);
|
||||
int clicked_row = (event->event_y / decoration_height);
|
||||
LOG("clicked on column %d, row %d\n", clicked_column, clicked_row);
|
||||
DLOG("clicked on column %d, row %d\n", clicked_column, clicked_row);
|
||||
destination = (container->stack_limit_value * clicked_column) + clicked_row;
|
||||
}
|
||||
}
|
||||
|
||||
LOG("Click on stack_win for client %d\n", destination);
|
||||
DLOG("Click on stack_win for client %d\n", destination);
|
||||
CIRCLEQ_FOREACH(client, &(stack_win->container->clients), clients)
|
||||
if (c++ == destination) {
|
||||
set_focus(conn, client, true);
|
||||
|
@ -124,26 +126,26 @@ static bool button_press_stackwin(xcb_connection_t *conn, xcb_button_press_event
|
|||
*
|
||||
*/
|
||||
static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *event) {
|
||||
i3Screen *screen;
|
||||
TAILQ_FOREACH(screen, virtual_screens, screens) {
|
||||
if (screen->bar != event->event)
|
||||
Output *output;
|
||||
TAILQ_FOREACH(output, &outputs, outputs) {
|
||||
if (output->bar != event->event)
|
||||
continue;
|
||||
|
||||
LOG("Click on a bar\n");
|
||||
DLOG("Click on a bar\n");
|
||||
|
||||
/* Check if the button was one of button4 or button5 (scroll up / scroll down) */
|
||||
if (event->detail == XCB_BUTTON_INDEX_4 || event->detail == XCB_BUTTON_INDEX_5) {
|
||||
Workspace *ws = c_ws;
|
||||
if (event->detail == XCB_BUTTON_INDEX_5) {
|
||||
while ((ws = TAILQ_NEXT(ws, workspaces)) != TAILQ_END(workspaces_head)) {
|
||||
if (ws->screen == screen) {
|
||||
if (ws->output == output) {
|
||||
workspace_show(conn, ws->num + 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
while ((ws = TAILQ_PREV(ws, workspaces_head, workspaces)) != TAILQ_END(workspaces)) {
|
||||
if (ws->screen == screen) {
|
||||
if (ws->output == output) {
|
||||
workspace_show(conn, ws->num + 1);
|
||||
return true;
|
||||
}
|
||||
|
@ -152,13 +154,13 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e
|
|||
return true;
|
||||
}
|
||||
int drawn = 0;
|
||||
/* Because workspaces can be on different screens, we need to loop
|
||||
through all of them and decide to count it based on its ->screen */
|
||||
/* Because workspaces can be on different outputs, we need to loop
|
||||
through all of them and decide to count it based on its ->output */
|
||||
Workspace *ws;
|
||||
TAILQ_FOREACH(ws, workspaces, workspaces) {
|
||||
if (ws->screen != screen)
|
||||
if (ws->output != output)
|
||||
continue;
|
||||
LOG("Checking if click was on workspace %d with drawn = %d, tw = %d\n",
|
||||
DLOG("Checking if click was on workspace %d with drawn = %d, tw = %d\n",
|
||||
ws->num, drawn, ws->text_width);
|
||||
if (event->event_x > (drawn + 1) &&
|
||||
event->event_x <= (drawn + 1 + ws->text_width + 5 + 5)) {
|
||||
|
@ -201,7 +203,7 @@ static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client,
|
|||
Workspace *ws = con->workspace;
|
||||
int first = 0, second = 0;
|
||||
|
||||
LOG("click was %d px to the right, %d px to the left, %d px to top, %d px to bottom\n",
|
||||
DLOG("click was %d px to the right, %d px to the left, %d px to top, %d px to bottom\n",
|
||||
to_right, to_left, to_top, to_bottom);
|
||||
|
||||
if (to_right < to_left &&
|
||||
|
@ -209,7 +211,7 @@ static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client,
|
|||
to_right < to_bottom) {
|
||||
/* …right border */
|
||||
first = con->col + (con->colspan - 1);
|
||||
LOG("column %d\n", first);
|
||||
DLOG("column %d\n", first);
|
||||
|
||||
if (!cell_exists(ws, first, con->row) ||
|
||||
(first == (ws->cols-1)))
|
||||
|
@ -251,7 +253,7 @@ static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client,
|
|||
}
|
||||
|
||||
int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) {
|
||||
LOG("Button %d pressed\n", event->state);
|
||||
DLOG("Button %d pressed\n", event->state);
|
||||
/* This was either a focus for a client’s parent (= titlebar)… */
|
||||
Client *client = table_get(&by_child, event->event);
|
||||
bool border_click = false;
|
||||
|
@ -263,23 +265,28 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
|
|||
* to move around the client if it was floating. if not, we just process
|
||||
* as usual. */
|
||||
if (config.floating_modifier != 0 &&
|
||||
(event->state & config.floating_modifier) != 0) {
|
||||
(event->state & config.floating_modifier) == config.floating_modifier) {
|
||||
if (client == NULL) {
|
||||
LOG("Not handling, floating_modifier was pressed and no client found\n");
|
||||
DLOG("Not handling, floating_modifier was pressed and no client found\n");
|
||||
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
|
||||
xcb_flush(conn);
|
||||
return 1;
|
||||
}
|
||||
if (client->fullscreen) {
|
||||
LOG("Not handling, client is in fullscreen mode\n");
|
||||
DLOG("Not handling, client is in fullscreen mode\n");
|
||||
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
|
||||
xcb_flush(conn);
|
||||
return 1;
|
||||
}
|
||||
if (client_is_floating(client)) {
|
||||
LOG("button %d pressed\n", event->detail);
|
||||
DLOG("button %d pressed\n", event->detail);
|
||||
if (event->detail == 1) {
|
||||
LOG("left mouse button, dragging\n");
|
||||
DLOG("left mouse button, dragging\n");
|
||||
floating_drag_window(conn, client, event);
|
||||
} else if (event->detail == 3) {
|
||||
LOG("right mouse button\n");
|
||||
floating_resize_window(conn, client, event);
|
||||
bool proportional = (event->state & BIND_SHIFT);
|
||||
DLOG("right mouse button\n");
|
||||
floating_resize_window(conn, client, proportional, event);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
@ -301,7 +308,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
|
|||
if (button_press_bar(conn, event))
|
||||
return 1;
|
||||
|
||||
LOG("Could not handle this button press\n");
|
||||
DLOG("Could not handle this button press\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -309,19 +316,19 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
|
|||
set_focus(conn, client, true);
|
||||
|
||||
/* Let’s see if this was on the borders (= resize). If not, we’re done */
|
||||
LOG("press button on x=%d, y=%d\n", event->event_x, event->event_y);
|
||||
DLOG("press button on x=%d, y=%d\n", event->event_x, event->event_y);
|
||||
resize_orientation_t orientation = O_VERTICAL;
|
||||
Container *con = client->container;
|
||||
int first, second;
|
||||
|
||||
if (client->dock) {
|
||||
LOG("dock. done.\n");
|
||||
DLOG("dock. done.\n");
|
||||
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
|
||||
xcb_flush(conn);
|
||||
return 1;
|
||||
}
|
||||
|
||||
LOG("event->event_x = %d, client->rect.width = %d\n", event->event_x, client->rect.width);
|
||||
DLOG("event->event_x = %d, client->rect.width = %d\n", event->event_x, client->rect.width);
|
||||
|
||||
/* Some clients (xfontsel for example) seem to pass clicks on their
|
||||
* window to the parent window, thus we receive an event here which in
|
||||
|
@ -331,12 +338,12 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
|
|||
event->event_x <= (client->child_rect.x + client->child_rect.width) &&
|
||||
event->event_y >= client->child_rect.y &&
|
||||
event->event_y <= (client->child_rect.y + client->child_rect.height)) {
|
||||
LOG("Fixing border_click = false because of click in child\n");
|
||||
DLOG("Fixing border_click = false because of click in child\n");
|
||||
border_click = false;
|
||||
}
|
||||
|
||||
if (!border_click) {
|
||||
LOG("client. done.\n");
|
||||
DLOG("client. done.\n");
|
||||
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
|
||||
/* Floating clients should be raised on click */
|
||||
if (client_is_floating(client))
|
||||
|
@ -348,7 +355,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
|
|||
/* Don’t handle events inside the titlebar, only borders are interesting */
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
if (event->event_y >= 2 && event->event_y <= (font->height + 2 + 2)) {
|
||||
LOG("click on titlebar\n");
|
||||
DLOG("click on titlebar\n");
|
||||
|
||||
/* Floating clients can be dragged by grabbing their titlebar */
|
||||
if (client_is_floating(client)) {
|
||||
|
@ -392,7 +399,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
|
|||
} else if (event->event_x > 2) {
|
||||
/* …right border */
|
||||
first = con->col + (con->colspan - 1);
|
||||
LOG("column %d\n", first);
|
||||
DLOG("column %d\n", first);
|
||||
|
||||
if (!cell_exists(ws, first, con->row) ||
|
||||
(first == (ws->cols-1)))
|
||||
|
|
176
src/client.c
176
src/client.c
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
|
@ -13,6 +13,7 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_icccm.h>
|
||||
|
@ -26,6 +27,8 @@
|
|||
#include "client.h"
|
||||
#include "table.h"
|
||||
#include "workspace.h"
|
||||
#include "config.h"
|
||||
#include "log.h"
|
||||
|
||||
/*
|
||||
* Removes the given client from the container, either because it will be inserted into another
|
||||
|
@ -43,7 +46,7 @@ void client_remove_from_container(xcb_connection_t *conn, Client *client, Contai
|
|||
if (CIRCLEQ_EMPTY(&(container->clients)) &&
|
||||
(container->mode == MODE_STACK ||
|
||||
container->mode == MODE_TABBED)) {
|
||||
LOG("Unmapping stack window\n");
|
||||
DLOG("Unmapping stack window\n");
|
||||
struct Stack_Window *stack_win = &(container->stack_win);
|
||||
stack_win->rect.height = 0;
|
||||
xcb_unmap_window(conn, stack_win->window);
|
||||
|
@ -150,44 +153,82 @@ bool client_matches_class_name(Client *client, char *to_class, char *to_title,
|
|||
* and when moving a fullscreen client to another screen.
|
||||
*
|
||||
*/
|
||||
void client_enter_fullscreen(xcb_connection_t *conn, Client *client) {
|
||||
Workspace *workspace = client->workspace;
|
||||
void client_enter_fullscreen(xcb_connection_t *conn, Client *client, bool global) {
|
||||
Workspace *workspace;
|
||||
Output *output;
|
||||
Rect r;
|
||||
|
||||
if (workspace->fullscreen_client != NULL) {
|
||||
LOG("Not entering fullscreen mode, there already is a fullscreen client.\n");
|
||||
return;
|
||||
if (global) {
|
||||
TAILQ_FOREACH(output, &outputs, outputs) {
|
||||
if (!output->active)
|
||||
continue;
|
||||
|
||||
if (output->current_workspace->fullscreen_client == NULL)
|
||||
continue;
|
||||
|
||||
LOG("Not entering global fullscreen mode, there already "
|
||||
"is a fullscreen client on output %s.\n", output->name);
|
||||
return;
|
||||
}
|
||||
|
||||
r = (Rect) { UINT_MAX, UINT_MAX, 0,0 };
|
||||
Output *output;
|
||||
|
||||
/* Set fullscreen_client for each active workspace.
|
||||
* Expand the rectangle to contain all outputs. */
|
||||
TAILQ_FOREACH(output, &outputs, outputs) {
|
||||
if (!output->active)
|
||||
continue;
|
||||
|
||||
output->current_workspace->fullscreen_client = client;
|
||||
|
||||
/* Temporarily abuse width/heigth as coordinates of the lower right corner */
|
||||
if (r.x > output->rect.x)
|
||||
r.x = output->rect.x;
|
||||
if (r.y > output->rect.y)
|
||||
r.y = output->rect.y;
|
||||
if (r.x + r.width < output->rect.x + output->rect.width)
|
||||
r.width = output->rect.x + output->rect.width;
|
||||
if (r.y + r.height < output->rect.y + output->rect.height)
|
||||
r.height = output->rect.y + output->rect.height;
|
||||
}
|
||||
|
||||
/* Putting them back to their original meaning */
|
||||
r.height -= r.x;
|
||||
r.width -= r.y;
|
||||
|
||||
LOG("Entering global fullscreen mode...\n");
|
||||
} else {
|
||||
workspace = client->workspace;
|
||||
if (workspace->fullscreen_client != NULL && workspace->fullscreen_client != client) {
|
||||
LOG("Not entering fullscreen mode, there already is a fullscreen client.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
workspace->fullscreen_client = client;
|
||||
r = workspace->rect;
|
||||
|
||||
LOG("Entering fullscreen mode...\n");
|
||||
}
|
||||
|
||||
client->fullscreen = true;
|
||||
workspace->fullscreen_client = client;
|
||||
LOG("Entering fullscreen mode...\n");
|
||||
|
||||
/* We just entered fullscreen mode, let’s configure the window */
|
||||
uint32_t mask = XCB_CONFIG_WINDOW_X |
|
||||
XCB_CONFIG_WINDOW_Y |
|
||||
XCB_CONFIG_WINDOW_WIDTH |
|
||||
XCB_CONFIG_WINDOW_HEIGHT;
|
||||
uint32_t values[4] = {workspace->rect.x,
|
||||
workspace->rect.y,
|
||||
workspace->rect.width,
|
||||
workspace->rect.height};
|
||||
DLOG("child itself will be at %dx%d with size %dx%d\n",
|
||||
r.x, r.y, r.width, r.height);
|
||||
|
||||
LOG("child itself will be at %dx%d with size %dx%d\n",
|
||||
values[0], values[1], values[2], values[3]);
|
||||
|
||||
xcb_configure_window(conn, client->frame, mask, values);
|
||||
xcb_set_window_rect(conn, client->frame, r);
|
||||
|
||||
/* Child’s coordinates are relative to the parent (=frame) */
|
||||
values[0] = 0;
|
||||
values[1] = 0;
|
||||
xcb_configure_window(conn, client->child, mask, values);
|
||||
r.x = 0;
|
||||
r.y = 0;
|
||||
xcb_set_window_rect(conn, client->child, r);
|
||||
|
||||
/* Raise the window */
|
||||
values[0] = XCB_STACK_MODE_ABOVE;
|
||||
uint32_t values[] = { XCB_STACK_MODE_ABOVE };
|
||||
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
|
||||
Rect child_rect = workspace->rect;
|
||||
child_rect.x = child_rect.y = 0;
|
||||
fake_configure_notify(conn, child_rect, client->child);
|
||||
fake_configure_notify(conn, r, client->child);
|
||||
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
@ -234,34 +275,26 @@ void client_toggle_fullscreen(xcb_connection_t *conn, Client *client) {
|
|||
/* dock clients cannot enter fullscreen mode */
|
||||
assert(!client->dock);
|
||||
|
||||
Workspace *workspace = client->workspace;
|
||||
if (!client->fullscreen) {
|
||||
client_enter_fullscreen(conn, client, false);
|
||||
} else {
|
||||
client_leave_fullscreen(conn, client);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Like client_toggle_fullscreen(), but putting it in global fullscreen-mode.
|
||||
*
|
||||
*/
|
||||
void client_toggle_fullscreen_global(xcb_connection_t *conn, Client *client) {
|
||||
/* dock clients cannot enter fullscreen mode */
|
||||
assert(!client->dock);
|
||||
|
||||
if (!client->fullscreen) {
|
||||
client_enter_fullscreen(conn, client);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG("leaving fullscreen mode\n");
|
||||
client->fullscreen = false;
|
||||
workspace->fullscreen_client = NULL;
|
||||
if (client_is_floating(client)) {
|
||||
/* For floating clients it’s enough if we just reconfigure that window (in fact,
|
||||
* re-rendering the layout will not update the client.) */
|
||||
reposition_client(conn, client);
|
||||
resize_client(conn, client);
|
||||
/* redecorate_window flushes */
|
||||
redecorate_window(conn, client);
|
||||
client_enter_fullscreen(conn, client, true);
|
||||
} else {
|
||||
client_set_below_floating(conn, client);
|
||||
|
||||
/* Because the coordinates of the window haven’t changed, it would not be
|
||||
re-configured if we don’t set the following flag */
|
||||
client->force_reconfigure = true;
|
||||
/* We left fullscreen mode, redraw the whole layout to ensure enternotify events are disabled */
|
||||
render_layout(conn);
|
||||
client_leave_fullscreen(conn, client);
|
||||
}
|
||||
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -277,14 +310,14 @@ void client_set_below_floating(xcb_connection_t *conn, Client *client) {
|
|||
if (first_floating == TAILQ_END(&(ws->floating_clients)))
|
||||
return;
|
||||
|
||||
LOG("Setting below floating\n");
|
||||
DLOG("Setting below floating\n");
|
||||
uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW };
|
||||
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
|
||||
if (client->workspace->fullscreen_client == NULL)
|
||||
return;
|
||||
|
||||
LOG("(and below fullscreen)\n");
|
||||
DLOG("(and below fullscreen)\n");
|
||||
/* Ensure that the window is still below the fullscreen window */
|
||||
values[0] = client->workspace->fullscreen_client->frame;
|
||||
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
|
@ -407,3 +440,38 @@ void client_mark(xcb_connection_t *conn, Client *client, const char *mark) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the minimum height of a specific window. The height is calculated
|
||||
* by using 2 pixels (for the client window itself), possibly padding this to
|
||||
* comply with the client’s base_height and then adding the decoration height.
|
||||
*
|
||||
*/
|
||||
uint32_t client_min_height(Client *client) {
|
||||
uint32_t height = max(2, client->base_height);
|
||||
i3Font *font = load_font(global_conn, config.font);
|
||||
|
||||
if (client->titlebar_position == TITLEBAR_OFF && client->borderless)
|
||||
return height;
|
||||
|
||||
if (client->titlebar_position == TITLEBAR_OFF && !client->borderless)
|
||||
return height + 2;
|
||||
|
||||
return height + font->height + 2 + 2;
|
||||
}
|
||||
|
||||
/*
|
||||
* See client_min_height.
|
||||
*
|
||||
*/
|
||||
uint32_t client_min_width(Client *client) {
|
||||
uint32_t width = max(2, client->base_width);
|
||||
|
||||
if (client->titlebar_position == TITLEBAR_OFF && client->borderless)
|
||||
return width;
|
||||
|
||||
if (client->titlebar_position == TITLEBAR_OFF && !client->borderless)
|
||||
return width + 2;
|
||||
|
||||
return width + 2 + 2;
|
||||
}
|
||||
|
|
357
src/commands.c
357
src/commands.c
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
|
@ -22,7 +22,7 @@
|
|||
#include "table.h"
|
||||
#include "layout.h"
|
||||
#include "i3.h"
|
||||
#include "xinerama.h"
|
||||
#include "randr.h"
|
||||
#include "client.h"
|
||||
#include "floating.h"
|
||||
#include "xcb.h"
|
||||
|
@ -30,6 +30,10 @@
|
|||
#include "workspace.h"
|
||||
#include "commands.h"
|
||||
#include "resize.h"
|
||||
#include "log.h"
|
||||
#include "sighandler.h"
|
||||
#include "manage.h"
|
||||
#include "ipc.h"
|
||||
|
||||
bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) {
|
||||
/* If this container is empty, we’re done */
|
||||
|
@ -45,7 +49,7 @@ bool focus_window_in_container(xcb_connection_t *conn, Container *container, dir
|
|||
else if (direction == D_DOWN) {
|
||||
if ((candidate = CIRCLEQ_NEXT_OR_NULL(&(container->clients), container->currently_focused, clients)) == NULL)
|
||||
candidate = CIRCLEQ_FIRST(&(container->clients));
|
||||
} else LOG("Direction not implemented!\n");
|
||||
} else ELOG("Direction not implemented!\n");
|
||||
|
||||
/* If we could not switch, the container contains exactly one client. We return false */
|
||||
if (candidate == container->currently_focused)
|
||||
|
@ -69,16 +73,16 @@ static void jump_to_mark(xcb_connection_t *conn, const char *mark) {
|
|||
if (current->mark == NULL || strcmp(current->mark, mark) != 0)
|
||||
continue;
|
||||
|
||||
workspace_show(conn, current->workspace->num + 1);
|
||||
set_focus(conn, current, true);
|
||||
workspace_show(conn, current->workspace->num + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG("No window with this mark found\n");
|
||||
ELOG("No window with this mark found\n");
|
||||
}
|
||||
|
||||
static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t thing) {
|
||||
LOG("focusing direction %d\n", direction);
|
||||
DLOG("focusing direction %d\n", direction);
|
||||
|
||||
int new_row = current_row,
|
||||
new_col = current_col;
|
||||
|
@ -86,19 +90,20 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
|
|||
Workspace *t_ws = c_ws;
|
||||
|
||||
/* Makes sure new_col and new_row are within bounds of the new workspace */
|
||||
void check_colrow_boundaries() {
|
||||
if (new_col >= t_ws->cols)
|
||||
new_col = (t_ws->cols - 1);
|
||||
if (new_row >= t_ws->rows)
|
||||
new_row = (t_ws->rows - 1);
|
||||
}
|
||||
#define CHECK_COLROW_BOUNDARIES \
|
||||
do { \
|
||||
if (new_col >= t_ws->cols) \
|
||||
new_col = (t_ws->cols - 1); \
|
||||
if (new_row >= t_ws->rows) \
|
||||
new_row = (t_ws->rows - 1); \
|
||||
} while (0)
|
||||
|
||||
/* There always is a container. If not, current_col or current_row is wrong */
|
||||
assert(container != NULL);
|
||||
|
||||
if (container->workspace->fullscreen_client != NULL) {
|
||||
LOG("You're in fullscreen mode. Won't switch focus\n");
|
||||
return;
|
||||
LOG("You're in fullscreen mode. Forcing focus to operate on whole screens\n");
|
||||
thing = THING_SCREEN;
|
||||
}
|
||||
|
||||
/* For focusing screens, situation is different: we get the rect
|
||||
|
@ -106,7 +111,7 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
|
|||
* right/left/bottom/top and just switch to the workspace on
|
||||
* the target screen. */
|
||||
if (thing == THING_SCREEN) {
|
||||
i3Screen *cs = c_ws->screen;
|
||||
Output *cs = c_ws->output;
|
||||
assert(cs != NULL);
|
||||
Rect bounds = cs->rect;
|
||||
|
||||
|
@ -118,20 +123,20 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
|
|||
bounds.y -= bounds.height;
|
||||
else bounds.y += bounds.height;
|
||||
|
||||
i3Screen *target = get_screen_containing(bounds.x, bounds.y);
|
||||
Output *target = get_output_containing(bounds.x, bounds.y);
|
||||
if (target == NULL) {
|
||||
LOG("Target screen NULL\n");
|
||||
DLOG("Target output NULL\n");
|
||||
/* Wrap around if the target screen is out of bounds */
|
||||
if (direction == D_RIGHT)
|
||||
target = get_screen_most(D_LEFT, cs);
|
||||
target = get_output_most(D_LEFT, cs);
|
||||
else if (direction == D_LEFT)
|
||||
target = get_screen_most(D_RIGHT, cs);
|
||||
target = get_output_most(D_RIGHT, cs);
|
||||
else if (direction == D_UP)
|
||||
target = get_screen_most(D_DOWN, cs);
|
||||
else target = get_screen_most(D_UP, cs);
|
||||
target = get_output_most(D_DOWN, cs);
|
||||
else target = get_output_most(D_UP, cs);
|
||||
}
|
||||
|
||||
LOG("Switching to ws %d\n", target->current_workspace + 1);
|
||||
DLOG("Switching to ws %d\n", target->current_workspace + 1);
|
||||
workspace_show(conn, target->current_workspace->num + 1);
|
||||
return;
|
||||
}
|
||||
|
@ -159,31 +164,48 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
|
|||
}
|
||||
} else {
|
||||
/* Let’s see if there is a screen down/up there to which we can switch */
|
||||
LOG("container is at %d with height %d\n", container->y, container->height);
|
||||
i3Screen *screen;
|
||||
DLOG("container is at %d with height %d\n", container->y, container->height);
|
||||
Output *output;
|
||||
int destination_y = (direction == D_UP ? (container->y - 1) : (container->y + container->height + 1));
|
||||
if ((screen = get_screen_containing(container->x, destination_y)) == NULL) {
|
||||
LOG("Wrapping screen around vertically\n");
|
||||
if ((output = get_output_containing(container->x, destination_y)) == NULL) {
|
||||
DLOG("Wrapping screen around vertically\n");
|
||||
/* No screen found? Then wrap */
|
||||
screen = get_screen_most((direction == D_UP ? D_DOWN : D_UP), container->workspace->screen);
|
||||
output = get_output_most((direction == D_UP ? D_DOWN : D_UP), container->workspace->output);
|
||||
}
|
||||
t_ws = screen->current_workspace;
|
||||
t_ws = output->current_workspace;
|
||||
new_row = (direction == D_UP ? (t_ws->rows - 1) : 0);
|
||||
}
|
||||
|
||||
check_colrow_boundaries();
|
||||
CHECK_COLROW_BOUNDARIES;
|
||||
|
||||
LOG("new_col = %d, new_row = %d\n", new_col, new_row);
|
||||
DLOG("new_col = %d, new_row = %d\n", new_col, new_row);
|
||||
if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
|
||||
LOG("Cell empty, checking for colspanned client above...\n");
|
||||
DLOG("Cell empty, checking for colspanned client above...\n");
|
||||
for (int cols = 0; cols < new_col; cols += t_ws->table[cols][new_row]->colspan) {
|
||||
if (new_col > (cols + (t_ws->table[cols][new_row]->colspan - 1)))
|
||||
continue;
|
||||
|
||||
new_col = cols;
|
||||
DLOG("Fixed it to new col %d\n", new_col);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
|
||||
DLOG("Cell still empty, checking for full cols above spanned width...\n");
|
||||
DLOG("new_col = %d\n", new_col);
|
||||
DLOG("colspan = %d\n", container->colspan);
|
||||
for (int cols = new_col;
|
||||
cols < container->col + container->colspan;
|
||||
cols += t_ws->table[cols][new_row]->colspan) {
|
||||
DLOG("candidate: new_row = %d, cols = %d\n", new_row, cols);
|
||||
if (t_ws->table[cols][new_row]->currently_focused == NULL)
|
||||
continue;
|
||||
|
||||
new_col = cols;
|
||||
DLOG("Fixed it to new col %d\n", new_col);
|
||||
break;
|
||||
}
|
||||
LOG("Fixed it to new col %d\n", new_col);
|
||||
}
|
||||
} else if (direction == D_LEFT || direction == D_RIGHT) {
|
||||
if (direction == D_RIGHT && cell_exists(t_ws, current_col+1, current_row))
|
||||
|
@ -202,37 +224,55 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
|
|||
}
|
||||
} else {
|
||||
/* Let’s see if there is a screen left/right here to which we can switch */
|
||||
LOG("container is at %d with width %d\n", container->x, container->width);
|
||||
i3Screen *screen;
|
||||
DLOG("container is at %d with width %d\n", container->x, container->width);
|
||||
Output *output;
|
||||
int destination_x = (direction == D_LEFT ? (container->x - 1) : (container->x + container->width + 1));
|
||||
if ((screen = get_screen_containing(destination_x, container->y)) == NULL) {
|
||||
LOG("Wrapping screen around horizontally\n");
|
||||
screen = get_screen_most((direction == D_LEFT ? D_RIGHT : D_LEFT), container->workspace->screen);
|
||||
if ((output = get_output_containing(destination_x, container->y)) == NULL) {
|
||||
DLOG("Wrapping screen around horizontally\n");
|
||||
output = get_output_most((direction == D_LEFT ? D_RIGHT : D_LEFT), container->workspace->output);
|
||||
}
|
||||
t_ws = screen->current_workspace;
|
||||
t_ws = output->current_workspace;
|
||||
new_col = (direction == D_LEFT ? (t_ws->cols - 1) : 0);
|
||||
}
|
||||
|
||||
check_colrow_boundaries();
|
||||
CHECK_COLROW_BOUNDARIES;
|
||||
|
||||
LOG("new_col = %d, new_row = %d\n", new_col, new_row);
|
||||
DLOG("new_col = %d, new_row = %d\n", new_col, new_row);
|
||||
if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
|
||||
LOG("Cell empty, checking for rowspanned client above...\n");
|
||||
DLOG("Cell empty, checking for rowspanned client above...\n");
|
||||
for (int rows = 0; rows < new_row; rows += t_ws->table[new_col][rows]->rowspan) {
|
||||
if (new_row > (rows + (t_ws->table[new_col][rows]->rowspan - 1)))
|
||||
continue;
|
||||
|
||||
new_row = rows;
|
||||
DLOG("Fixed it to new row %d\n", new_row);
|
||||
break;
|
||||
}
|
||||
LOG("Fixed it to new row %d\n", new_row);
|
||||
}
|
||||
|
||||
if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
|
||||
DLOG("Cell still empty, checking for full cols near full spanned height...\n");
|
||||
DLOG("new_row = %d\n", new_row);
|
||||
DLOG("rowspan = %d\n", container->rowspan);
|
||||
for (int rows = new_row;
|
||||
rows < container->row + container->rowspan;
|
||||
rows += t_ws->table[new_col][rows]->rowspan) {
|
||||
DLOG("candidate: new_col = %d, rows = %d\n", new_col, rows);
|
||||
if (t_ws->table[new_col][rows]->currently_focused == NULL)
|
||||
continue;
|
||||
|
||||
new_row = rows;
|
||||
DLOG("Fixed it to new col %d\n", new_row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
LOG("direction unhandled\n");
|
||||
ELOG("direction unhandled\n");
|
||||
return;
|
||||
}
|
||||
|
||||
check_colrow_boundaries();
|
||||
CHECK_COLROW_BOUNDARIES;
|
||||
|
||||
if (t_ws->table[new_col][new_row]->currently_focused != NULL)
|
||||
set_focus(conn, t_ws->table[new_col][new_row]->currently_focused, true);
|
||||
|
@ -254,7 +294,7 @@ static bool move_current_window_in_container(xcb_connection_t *conn, Client *cli
|
|||
if (other == CIRCLEQ_END(&(client->container->clients)))
|
||||
return false;
|
||||
|
||||
LOG("i can do that\n");
|
||||
DLOG("i can do that\n");
|
||||
/* We can move the client inside its current container */
|
||||
CIRCLEQ_REMOVE(&(client->container->clients), client, clients);
|
||||
if (direction == D_UP)
|
||||
|
@ -357,7 +397,7 @@ static void move_current_window(xcb_connection_t *conn, direction_t direction) {
|
|||
/* Fix colspan/rowspan if it’d overlap */
|
||||
fix_colrowspan(conn, workspace);
|
||||
|
||||
render_workspace(conn, workspace->screen, workspace);
|
||||
render_workspace(conn, workspace->output, workspace);
|
||||
xcb_flush(conn);
|
||||
|
||||
set_focus(conn, current_client, true);
|
||||
|
@ -411,7 +451,7 @@ static void move_current_container(xcb_connection_t *conn, direction_t direction
|
|||
return;
|
||||
}
|
||||
|
||||
LOG("old = %d,%d and new = %d,%d\n", container->col, container->row, new->col, new->row);
|
||||
DLOG("old = %d,%d and new = %d,%d\n", container->col, container->row, new->col, new->row);
|
||||
|
||||
/* Swap the containers */
|
||||
int col = new->col;
|
||||
|
@ -453,7 +493,7 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction
|
|||
/* Snap to the left is actually a move to the left and then a snap right */
|
||||
if (!cell_exists(container->workspace, container->col - 1, container->row) ||
|
||||
CUR_TABLE[container->col-1][container->row]->currently_focused != NULL) {
|
||||
LOG("cannot snap to left - the cell is already used\n");
|
||||
ELOG("cannot snap to left - the cell is already used\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -466,18 +506,18 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction
|
|||
for (int i = 0; i < container->rowspan; i++)
|
||||
if (!cell_exists(container->workspace, new_col, container->row + i) ||
|
||||
CUR_TABLE[new_col][container->row + i]->currently_focused != NULL) {
|
||||
LOG("cannot snap to right - the cell is already used\n");
|
||||
ELOG("cannot snap to right - the cell is already used\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check if there are other cells with rowspan, which are in our way.
|
||||
* If so, reduce their rowspan. */
|
||||
for (int i = container->row-1; i >= 0; i--) {
|
||||
LOG("we got cell %d, %d with rowspan %d\n",
|
||||
DLOG("we got cell %d, %d with rowspan %d\n",
|
||||
new_col, i, CUR_TABLE[new_col][i]->rowspan);
|
||||
while ((CUR_TABLE[new_col][i]->rowspan-1) >= (container->row - i))
|
||||
CUR_TABLE[new_col][i]->rowspan--;
|
||||
LOG("new rowspan = %d\n", CUR_TABLE[new_col][i]->rowspan);
|
||||
DLOG("new rowspan = %d\n", CUR_TABLE[new_col][i]->rowspan);
|
||||
}
|
||||
|
||||
container->colspan++;
|
||||
|
@ -486,7 +526,7 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction
|
|||
case D_UP:
|
||||
if (!cell_exists(container->workspace, container->col, container->row - 1) ||
|
||||
CUR_TABLE[container->col][container->row-1]->currently_focused != NULL) {
|
||||
LOG("cannot snap to top - the cell is already used\n");
|
||||
ELOG("cannot snap to top - the cell is already used\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -494,21 +534,21 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction
|
|||
snap_current_container(conn, D_DOWN);
|
||||
return;
|
||||
case D_DOWN: {
|
||||
LOG("snapping down\n");
|
||||
DLOG("snapping down\n");
|
||||
int new_row = container->row + container->rowspan;
|
||||
for (int i = 0; i < container->colspan; i++)
|
||||
if (!cell_exists(container->workspace, container->col + i, new_row) ||
|
||||
CUR_TABLE[container->col + i][new_row]->currently_focused != NULL) {
|
||||
LOG("cannot snap down - the cell is already used\n");
|
||||
ELOG("cannot snap down - the cell is already used\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = container->col-1; i >= 0; i--) {
|
||||
LOG("we got cell %d, %d with colspan %d\n",
|
||||
DLOG("we got cell %d, %d with colspan %d\n",
|
||||
i, new_row, CUR_TABLE[i][new_row]->colspan);
|
||||
while ((CUR_TABLE[i][new_row]->colspan-1) >= (container->col - i))
|
||||
CUR_TABLE[i][new_row]->colspan--;
|
||||
LOG("new colspan = %d\n", CUR_TABLE[i][new_row]->colspan);
|
||||
DLOG("new colspan = %d\n", CUR_TABLE[i][new_row]->colspan);
|
||||
|
||||
}
|
||||
|
||||
|
@ -530,12 +570,12 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl
|
|||
|
||||
LOG("moving floating\n");
|
||||
|
||||
workspace_initialize(t_ws, c_ws->screen);
|
||||
workspace_initialize(t_ws, c_ws->output, false);
|
||||
|
||||
/* Check if there is already a fullscreen client on the destination workspace and
|
||||
* stop moving if so. */
|
||||
if (client->fullscreen && (t_ws->fullscreen_client != NULL)) {
|
||||
LOG("Not moving: Fullscreen client already existing on destination workspace.\n");
|
||||
ELOG("Not moving: Fullscreen client already existing on destination workspace.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -543,21 +583,26 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl
|
|||
|
||||
/* If we’re moving it to an invisible screen, we need to unmap it */
|
||||
if (!workspace_is_visible(t_ws)) {
|
||||
LOG("This workspace is not visible, unmapping\n");
|
||||
DLOG("This workspace is not visible, unmapping\n");
|
||||
client_unmap(conn, client);
|
||||
} else {
|
||||
/* If this is not the case, we move the window to a workspace
|
||||
* which is on another screen, so we also need to adjust its
|
||||
* coordinates. */
|
||||
LOG("before x = %d, y = %d\n", client->rect.x, client->rect.y);
|
||||
DLOG("before x = %d, y = %d\n", client->rect.x, client->rect.y);
|
||||
uint32_t relative_x = client->rect.x - old_ws->rect.x,
|
||||
relative_y = client->rect.y - old_ws->rect.y;
|
||||
LOG("rel_x = %d, rel_y = %d\n", relative_x, relative_y);
|
||||
client->rect.x = t_ws->rect.x + relative_x;
|
||||
client->rect.y = t_ws->rect.y + relative_y;
|
||||
LOG("after x = %d, y = %d\n", client->rect.x, client->rect.y);
|
||||
reposition_client(conn, client);
|
||||
xcb_flush(conn);
|
||||
DLOG("rel_x = %d, rel_y = %d\n", relative_x, relative_y);
|
||||
if (client->fullscreen) {
|
||||
client_enter_fullscreen(conn, client, false);
|
||||
memcpy(&(client->rect), &(t_ws->rect), sizeof(Rect));
|
||||
} else {
|
||||
client->rect.x = t_ws->rect.x + relative_x;
|
||||
client->rect.y = t_ws->rect.y + relative_y;
|
||||
DLOG("after x = %d, y = %d\n", client->rect.x, client->rect.y);
|
||||
reposition_client(conn, client);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
}
|
||||
|
||||
/* Configure the window above all tiling windows (or below a fullscreen
|
||||
|
@ -576,12 +621,14 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl
|
|||
}
|
||||
}
|
||||
|
||||
LOG("done\n");
|
||||
DLOG("done\n");
|
||||
|
||||
render_layout(conn);
|
||||
|
||||
if (workspace_is_visible(t_ws))
|
||||
if (workspace_is_visible(t_ws)) {
|
||||
client_warp_pointer_into(conn, client);
|
||||
set_focus(conn, client, true);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -600,18 +647,18 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
|
|||
|
||||
Client *current_client = container->currently_focused;
|
||||
if (current_client == NULL) {
|
||||
LOG("No currently focused client in current container.\n");
|
||||
ELOG("No currently focused client in current container.\n");
|
||||
return;
|
||||
}
|
||||
Client *to_focus = CIRCLEQ_NEXT_OR_NULL(&(container->clients), current_client, clients);
|
||||
if (to_focus == NULL)
|
||||
to_focus = CIRCLEQ_PREV_OR_NULL(&(container->clients), current_client, clients);
|
||||
|
||||
workspace_initialize(t_ws, container->workspace->screen);
|
||||
workspace_initialize(t_ws, container->workspace->output, false);
|
||||
/* Check if there is already a fullscreen client on the destination workspace and
|
||||
* stop moving if so. */
|
||||
if (current_client->fullscreen && (t_ws->fullscreen_client != NULL)) {
|
||||
LOG("Not moving: Fullscreen client already existing on destination workspace.\n");
|
||||
ELOG("Not moving: Fullscreen client already existing on destination workspace.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -627,7 +674,7 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
|
|||
CIRCLEQ_INSERT_TAIL(&(to_container->clients), current_client, clients);
|
||||
|
||||
SLIST_INSERT_HEAD(&(to_container->workspace->focus_stack), current_client, focus_clients);
|
||||
LOG("Moved.\n");
|
||||
DLOG("Moved.\n");
|
||||
|
||||
current_client->container = to_container;
|
||||
current_client->workspace = to_container->workspace;
|
||||
|
@ -636,12 +683,12 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
|
|||
|
||||
/* If we’re moving it to an invisible screen, we need to unmap it */
|
||||
if (!workspace_is_visible(to_container->workspace)) {
|
||||
LOG("This workspace is not visible, unmapping\n");
|
||||
DLOG("This workspace is not visible, unmapping\n");
|
||||
client_unmap(conn, current_client);
|
||||
} else {
|
||||
if (current_client->fullscreen) {
|
||||
LOG("Calling client_enter_fullscreen again\n");
|
||||
client_enter_fullscreen(conn, current_client);
|
||||
DLOG("Calling client_enter_fullscreen again\n");
|
||||
client_enter_fullscreen(conn, current_client, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -650,8 +697,10 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
|
|||
|
||||
render_layout(conn);
|
||||
|
||||
if (workspace_is_visible(to_container->workspace))
|
||||
if (workspace_is_visible(to_container->workspace)) {
|
||||
client_warp_pointer_into(conn, current_client);
|
||||
set_focus(conn, current_client, true);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -672,11 +721,12 @@ static void jump_to_window(xcb_connection_t *conn, const char *arguments) {
|
|||
|
||||
if ((client = get_matching_client(conn, classtitle, NULL)) == NULL) {
|
||||
free(classtitle);
|
||||
LOG("No matching client found.\n");
|
||||
ELOG("No matching client found.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
free(classtitle);
|
||||
workspace_show(conn, client->workspace->num + 1);
|
||||
set_focus(conn, client, true);
|
||||
}
|
||||
|
||||
|
@ -694,7 +744,7 @@ static void jump_to_container(xcb_connection_t *conn, const char *arguments) {
|
|||
|
||||
/* No match? Either no arguments were specified, or no numbers */
|
||||
if (result < 1) {
|
||||
LOG("At least one valid argument required\n");
|
||||
ELOG("At least one valid argument required\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -704,7 +754,7 @@ static void jump_to_container(xcb_connection_t *conn, const char *arguments) {
|
|||
if (result < 3)
|
||||
return;
|
||||
|
||||
LOG("Boundary-checking col %d, row %d... (max cols %d, max rows %d)\n", col, row, c_ws->cols, c_ws->rows);
|
||||
DLOG("Boundary-checking col %d, row %d... (max cols %d, max rows %d)\n", col, row, c_ws->cols, c_ws->rows);
|
||||
|
||||
/* Move to row/col */
|
||||
if (row >= c_ws->rows)
|
||||
|
@ -712,7 +762,7 @@ static void jump_to_container(xcb_connection_t *conn, const char *arguments) {
|
|||
if (col >= c_ws->cols)
|
||||
col = c_ws->cols - 1;
|
||||
|
||||
LOG("Jumping to col %d, row %d\n", col, row);
|
||||
DLOG("Jumping to col %d, row %d\n", col, row);
|
||||
if (c_ws->table[col][row]->currently_focused != NULL)
|
||||
set_focus(conn, c_ws->table[col][row]->currently_focused, true);
|
||||
}
|
||||
|
@ -741,7 +791,7 @@ static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) {
|
|||
} else if (strcasecmp(arguments, "ft") == 0) {
|
||||
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
|
||||
if (last_focused == SLIST_END(&(c_ws->focus_stack))) {
|
||||
LOG("Cannot select the next floating/tiling client because there is no client at all\n");
|
||||
ELOG("Cannot select the next floating/tiling client because there is no client at all\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -749,17 +799,17 @@ static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) {
|
|||
} else {
|
||||
/* …or a number was specified */
|
||||
if (sscanf(arguments, "%u", ×) != 1) {
|
||||
LOG("No or invalid argument given (\"%s\"), using default of 1 times\n", arguments);
|
||||
ELOG("No or invalid argument given (\"%s\"), using default of 1 times\n", arguments);
|
||||
times = 1;
|
||||
}
|
||||
|
||||
SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients) {
|
||||
if (++count < times) {
|
||||
LOG("Skipping\n");
|
||||
DLOG("Skipping\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG("Focussing\n");
|
||||
DLOG("Focussing\n");
|
||||
set_focus(conn, current, true);
|
||||
break;
|
||||
}
|
||||
|
@ -774,29 +824,6 @@ static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Goes through the list of arguments (for exec()) and checks if the given argument
|
||||
* is present. If not, it copies the arguments (because we cannot realloc it) and
|
||||
* appends the given argument.
|
||||
*
|
||||
*/
|
||||
static char **append_argument(char **original, char *argument) {
|
||||
int num_args;
|
||||
for (num_args = 0; original[num_args] != NULL; num_args++) {
|
||||
LOG("original argument: \"%s\"\n", original[num_args]);
|
||||
/* If the argument is already present we return the original pointer */
|
||||
if (strcmp(original[num_args], argument) == 0)
|
||||
return original;
|
||||
}
|
||||
/* Copy the original array */
|
||||
char **result = smalloc((num_args+2) * sizeof(char*));
|
||||
memcpy(result, original, num_args * sizeof(char*));
|
||||
result[num_args] = argument;
|
||||
result[num_args+1] = NULL;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Switch to next or previous existing workspace
|
||||
*
|
||||
|
@ -805,16 +832,32 @@ static void next_previous_workspace(xcb_connection_t *conn, int direction) {
|
|||
Workspace *ws = c_ws;
|
||||
|
||||
if (direction == 'n') {
|
||||
while ((ws = TAILQ_NEXT(ws, workspaces)) != TAILQ_END(workspaces_head)) {
|
||||
if (ws->screen == NULL)
|
||||
while (1) {
|
||||
ws = TAILQ_NEXT(ws, workspaces);
|
||||
|
||||
if (ws == TAILQ_END(workspaces))
|
||||
ws = TAILQ_FIRST(workspaces);
|
||||
|
||||
if (ws == c_ws)
|
||||
return;
|
||||
|
||||
if (ws->output == NULL)
|
||||
continue;
|
||||
|
||||
workspace_show(conn, ws->num + 1);
|
||||
return;
|
||||
}
|
||||
} else if (direction == 'p') {
|
||||
while ((ws = TAILQ_PREV(ws, workspaces_head, workspaces)) != TAILQ_END(workspaces)) {
|
||||
if (ws->screen == NULL)
|
||||
while (1) {
|
||||
ws = TAILQ_PREV(ws, workspaces_head, workspaces);
|
||||
|
||||
if (ws == TAILQ_END(workspaces))
|
||||
ws = TAILQ_LAST(workspaces, workspaces_head);
|
||||
|
||||
if (ws == c_ws)
|
||||
return;
|
||||
|
||||
if (ws->output == NULL)
|
||||
continue;
|
||||
|
||||
workspace_show(conn, ws->num + 1);
|
||||
|
@ -827,7 +870,34 @@ static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, c
|
|||
int first, second;
|
||||
resize_orientation_t orientation = O_VERTICAL;
|
||||
Container *con = last_focused->container;
|
||||
Workspace *ws = con->workspace;
|
||||
Workspace *ws = last_focused->workspace;
|
||||
|
||||
if (client_is_floating(last_focused)) {
|
||||
DLOG("Resizing a floating client\n");
|
||||
if (STARTS_WITH(command, "left")) {
|
||||
command += strlen("left");
|
||||
last_focused->rect.width += atoi(command);
|
||||
last_focused->rect.x -= atoi(command);
|
||||
} else if (STARTS_WITH(command, "right")) {
|
||||
command += strlen("right");
|
||||
last_focused->rect.width += atoi(command);
|
||||
} else if (STARTS_WITH(command, "top")) {
|
||||
command += strlen("top");
|
||||
last_focused->rect.height += atoi(command);
|
||||
last_focused->rect.y -= atoi(command);
|
||||
} else if (STARTS_WITH(command, "bottom")) {
|
||||
command += strlen("bottom");
|
||||
last_focused->rect.height += atoi(command);
|
||||
} else {
|
||||
ELOG("Syntax: resize <left|right|top|bottom> [+|-]<pixels>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* resize_client flushes */
|
||||
resize_client(conn, last_focused);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (STARTS_WITH(command, "left")) {
|
||||
if (con->col == 0)
|
||||
|
@ -837,7 +907,7 @@ static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, c
|
|||
command += strlen("left");
|
||||
} else if (STARTS_WITH(command, "right")) {
|
||||
first = con->col + (con->colspan - 1);
|
||||
LOG("column %d\n", first);
|
||||
DLOG("column %d\n", first);
|
||||
|
||||
if (!cell_exists(ws, first, con->row) ||
|
||||
(first == (ws->cols-1)))
|
||||
|
@ -862,7 +932,7 @@ static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, c
|
|||
orientation = O_HORIZONTAL;
|
||||
command += strlen("bottom");
|
||||
} else {
|
||||
LOG("Syntax: resize <left|right|top|bottom> [+|-]<pixels>\n");
|
||||
ELOG("Syntax: resize <left|right|top|bottom> [+|-]<pixels>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -898,14 +968,14 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
|||
|
||||
if (STARTS_WITH(command, "mark")) {
|
||||
if (last_focused == NULL) {
|
||||
LOG("There is no window to mark\n");
|
||||
ELOG("There is no window to mark\n");
|
||||
return;
|
||||
}
|
||||
const char *rest = command + strlen("mark");
|
||||
while (*rest == ' ')
|
||||
rest++;
|
||||
if (*rest == '\0') {
|
||||
LOG("interactive mark starting\n");
|
||||
DLOG("interactive mark starting\n");
|
||||
start_application("i3-input -p 'mark ' -l 1 -P 'Mark: '");
|
||||
} else {
|
||||
LOG("mark with \"%s\"\n", rest);
|
||||
|
@ -919,7 +989,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
|||
while (*rest == ' ')
|
||||
rest++;
|
||||
if (*rest == '\0') {
|
||||
LOG("interactive go to mark starting\n");
|
||||
DLOG("interactive go to mark starting\n");
|
||||
start_application("i3-input -p 'goto ' -l 1 -P 'Goto: '");
|
||||
} else {
|
||||
LOG("go to \"%s\"\n", rest);
|
||||
|
@ -930,7 +1000,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
|||
|
||||
if (STARTS_WITH(command, "stack-limit ")) {
|
||||
if (last_focused == NULL || client_is_floating(last_focused)) {
|
||||
LOG("No container focused\n");
|
||||
ELOG("No container focused\n");
|
||||
return;
|
||||
}
|
||||
const char *rest = command + strlen("stack-limit ");
|
||||
|
@ -941,7 +1011,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
|||
last_focused->container->stack_limit = STACK_LIMIT_COLS;
|
||||
rest += strlen("cols ");
|
||||
} else {
|
||||
LOG("Syntax: stack-limit <cols|rows> <limit>\n");
|
||||
ELOG("Syntax: stack-limit <cols|rows> <limit>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -969,28 +1039,28 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
|||
/* Is it an <exit>? */
|
||||
if (STARTS_WITH(command, "exit")) {
|
||||
LOG("User issued exit-command, exiting without error.\n");
|
||||
restore_geometry(global_conn);
|
||||
ipc_shutdown();
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/* Is it a <reload>? */
|
||||
if (STARTS_WITH(command, "reload")) {
|
||||
load_configuration(conn, NULL, true);
|
||||
render_layout(conn);
|
||||
/* Send an IPC event just in case the ws names have changed */
|
||||
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"reload\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Is it <restart>? Then restart in place. */
|
||||
if (STARTS_WITH(command, "restart")) {
|
||||
LOG("restarting \"%s\"...\n", start_argv[0]);
|
||||
/* make sure -a is in the argument list or append it */
|
||||
start_argv = append_argument(start_argv, "-a");
|
||||
|
||||
execvp(start_argv[0], start_argv);
|
||||
/* not reached */
|
||||
i3_restart();
|
||||
}
|
||||
|
||||
if (STARTS_WITH(command, "kill")) {
|
||||
if (last_focused == NULL) {
|
||||
LOG("There is no window to kill\n");
|
||||
ELOG("There is no window to kill\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1015,25 +1085,28 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
|||
return;
|
||||
}
|
||||
|
||||
/* Is it 'f' for fullscreen? */
|
||||
/* Is it 'f' for fullscreen, or 'fg' for fullscreen_global? */
|
||||
if (command[0] == 'f') {
|
||||
if (last_focused == NULL)
|
||||
return;
|
||||
client_toggle_fullscreen(conn, last_focused);
|
||||
if (command[1] == 'g')
|
||||
client_toggle_fullscreen_global(conn, last_focused);
|
||||
else
|
||||
client_toggle_fullscreen(conn, last_focused);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Is it just 's' for stacking or 'd' for default? */
|
||||
if ((command[0] == 's' || command[0] == 'd' || command[0] == 'T') && (command[1] == '\0')) {
|
||||
if (last_focused != NULL && client_is_floating(last_focused)) {
|
||||
LOG("not switching, this is a floating client\n");
|
||||
ELOG("not switching, this is a floating client\n");
|
||||
return;
|
||||
}
|
||||
LOG("Switching mode for current container\n");
|
||||
int new_mode = MODE_DEFAULT;
|
||||
if (command[0] == 's')
|
||||
if (command[0] == 's' && CUR_CELL->mode != MODE_STACK)
|
||||
new_mode = MODE_STACK;
|
||||
if (command[0] == 'T')
|
||||
if (command[0] == 'T' && CUR_CELL->mode != MODE_TABBED)
|
||||
new_mode = MODE_TABBED;
|
||||
switch_layout_mode(conn, CUR_CELL, new_mode);
|
||||
return;
|
||||
|
@ -1043,7 +1116,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
|||
/* or even 'bt' (toggle border: 'bp' -> 'bb' -> 'bn' ) */
|
||||
if (command[0] == 'b') {
|
||||
if (last_focused == NULL) {
|
||||
LOG("No window focused, cannot change border type\n");
|
||||
ELOG("No window focused, cannot change border type\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1084,7 +1157,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
|||
with = WITH_SCREEN;
|
||||
command++;
|
||||
} else {
|
||||
LOG("not yet implemented.\n");
|
||||
ELOG("not yet implemented.\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1097,7 +1170,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
|||
return;
|
||||
}
|
||||
if (last_focused == NULL) {
|
||||
LOG("Cannot toggle tiling/floating: workspace empty\n");
|
||||
ELOG("Cannot toggle tiling/floating: workspace empty\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1113,7 +1186,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
|||
/* Fix colspan/rowspan if it’d overlap */
|
||||
fix_colrowspan(conn, ws);
|
||||
|
||||
render_workspace(conn, ws->screen, ws);
|
||||
render_workspace(conn, ws->output, ws);
|
||||
|
||||
/* Re-focus the client because cleanup_table sets the focus to the last
|
||||
* focused client inside a container only. */
|
||||
|
@ -1134,7 +1207,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
|||
direction_t direction;
|
||||
int times = strtol(command, &rest, 10);
|
||||
if (rest == NULL) {
|
||||
LOG("Invalid command (\"%s\")\n", command);
|
||||
ELOG("Invalid command (\"%s\")\n", command);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1152,7 +1225,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
|||
int workspace = strtol(rest, &rest, 10);
|
||||
|
||||
if (rest == NULL) {
|
||||
LOG("Invalid command (\"%s\")\n", command);
|
||||
ELOG("Invalid command (\"%s\")\n", command);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1164,13 +1237,13 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
|||
}
|
||||
|
||||
if (last_focused == NULL) {
|
||||
LOG("Not performing (no window found)\n");
|
||||
ELOG("Not performing (no window found)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (client_is_floating(last_focused) &&
|
||||
(action != ACTION_FOCUS && action != ACTION_MOVE)) {
|
||||
LOG("Not performing (floating)\n");
|
||||
ELOG("Not performing (floating)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1185,7 +1258,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
|||
else if (*rest == 'l')
|
||||
direction = D_RIGHT;
|
||||
else {
|
||||
LOG("unknown direction: %c\n", *rest);
|
||||
ELOG("unknown direction: %c\n", *rest);
|
||||
return;
|
||||
}
|
||||
rest++;
|
||||
|
@ -1208,7 +1281,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
|||
/* TODO: this should swap the screen’s contents
|
||||
* (e.g. all workspaces) with the next/previous/…
|
||||
* screen */
|
||||
LOG("Not yet implemented\n");
|
||||
ELOG("Not yet implemented\n");
|
||||
continue;
|
||||
}
|
||||
if (client_is_floating(last_focused)) {
|
||||
|
@ -1223,7 +1296,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
|||
|
||||
if (action == ACTION_SNAP) {
|
||||
if (with == WITH_SCREEN) {
|
||||
LOG("You cannot snap a screen (it makes no sense).\n");
|
||||
ELOG("You cannot snap a screen (it makes no sense).\n");
|
||||
continue;
|
||||
}
|
||||
snap_current_container(conn, direction);
|
||||
|
|
626
src/config.c
626
src/config.c
|
@ -3,16 +3,23 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
* src/config.c: Contains all functions handling the configuration file (calling
|
||||
* the parser (src/cfgparse.y) with the correct path, switching key bindings
|
||||
* mode).
|
||||
*
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <stdlib.h>
|
||||
#include <glob.h>
|
||||
#include <wordexp.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* We need Xlib for XStringToKeysym */
|
||||
#include <X11/Xlib.h>
|
||||
|
@ -25,48 +32,44 @@
|
|||
#include "xcb.h"
|
||||
#include "table.h"
|
||||
#include "workspace.h"
|
||||
|
||||
/* prototype for src/cfgparse.y, will be cleaned up as soon as we completely
|
||||
* switched to the new scanner/parser. */
|
||||
void parse_file(const char *f);
|
||||
#include "log.h"
|
||||
|
||||
Config config;
|
||||
struct modes_head modes;
|
||||
|
||||
bool config_use_lexer = false;
|
||||
|
||||
/*
|
||||
* This function resolves ~ in pathnames.
|
||||
*
|
||||
*/
|
||||
static char *glob_path(const char *path) {
|
||||
char *glob_path(const char *path) {
|
||||
static glob_t globbuf;
|
||||
if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0)
|
||||
die("glob() failed");
|
||||
char *result = sstrdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path);
|
||||
globfree(&globbuf);
|
||||
|
||||
/* If the file does not exist yet, we still may need to resolve tilde,
|
||||
* so call wordexp */
|
||||
if (strcmp(result, path) == 0) {
|
||||
wordexp_t we;
|
||||
wordexp(path, &we, WRDE_NOCMD);
|
||||
if (we.we_wordc > 0) {
|
||||
free(result);
|
||||
result = sstrdup(we.we_wordv[0]);
|
||||
}
|
||||
wordfree(&we);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function does a very simple replacement of each instance of key with value.
|
||||
* Checks if the given path exists by calling stat().
|
||||
*
|
||||
*/
|
||||
static void replace_variable(char *buffer, const char *key, const char *value) {
|
||||
char *pos;
|
||||
/* To prevent endless recursions when the user makes an error configuring,
|
||||
* we stop after 100 replacements. That should be vastly more than enough. */
|
||||
int c = 0;
|
||||
while ((pos = strcasestr(buffer, key)) != NULL && c++ < 100) {
|
||||
char *rest = pos + strlen(key);
|
||||
*pos = '\0';
|
||||
char *replaced;
|
||||
asprintf(&replaced, "%s%s%s", buffer, value, rest);
|
||||
/* Hm, this is a bit ugly, but sizeof(buffer) = 4, as it’s just a pointer.
|
||||
* So we need to hard-code the dimensions here. */
|
||||
strncpy(buffer, replaced, 1026);
|
||||
free(replaced);
|
||||
}
|
||||
bool path_exists(const char *path) {
|
||||
struct stat buf;
|
||||
return (stat(path, &buf) == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -75,57 +78,88 @@ static void replace_variable(char *buffer, const char *key, const char *value) {
|
|||
*
|
||||
*/
|
||||
void ungrab_all_keys(xcb_connection_t *conn) {
|
||||
LOG("Ungrabbing all keys\n");
|
||||
DLOG("Ungrabbing all keys\n");
|
||||
xcb_ungrab_key(conn, XCB_GRAB_ANY, root, XCB_BUTTON_MASK_ANY);
|
||||
}
|
||||
|
||||
static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint32_t keycode) {
|
||||
LOG("Grabbing %d\n", keycode);
|
||||
if ((bind->mods & BIND_MODE_SWITCH) != 0)
|
||||
xcb_grab_key(conn, 0, root, 0, keycode,
|
||||
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC);
|
||||
else {
|
||||
/* Grab the key in all combinations */
|
||||
#define GRAB_KEY(modifier) xcb_grab_key(conn, 0, root, modifier, keycode, \
|
||||
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC)
|
||||
GRAB_KEY(bind->mods);
|
||||
GRAB_KEY(bind->mods | xcb_numlock_mask);
|
||||
GRAB_KEY(bind->mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK);
|
||||
DLOG("Grabbing %d\n", keycode);
|
||||
/* Grab the key in all combinations */
|
||||
#define GRAB_KEY(modifier) \
|
||||
do { \
|
||||
xcb_grab_key(conn, 0, root, modifier, keycode, \
|
||||
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); \
|
||||
} while (0)
|
||||
int mods = bind->mods;
|
||||
if ((bind->mods & BIND_MODE_SWITCH) != 0) {
|
||||
mods &= ~BIND_MODE_SWITCH;
|
||||
if (mods == 0)
|
||||
mods = XCB_MOD_MASK_ANY;
|
||||
}
|
||||
GRAB_KEY(mods);
|
||||
GRAB_KEY(mods | xcb_numlock_mask);
|
||||
GRAB_KEY(mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK);
|
||||
}
|
||||
|
||||
/*
|
||||
* Grab the bound keys (tell X to send us keypress events for those keycodes)
|
||||
* Returns a pointer to the Binding with the specified modifiers and keycode
|
||||
* or NULL if no such binding exists.
|
||||
*
|
||||
*/
|
||||
void grab_all_keys(xcb_connection_t *conn) {
|
||||
Binding *get_binding(uint16_t modifiers, xcb_keycode_t keycode) {
|
||||
Binding *bind;
|
||||
|
||||
TAILQ_FOREACH(bind, bindings, bindings) {
|
||||
/* First compare the modifiers */
|
||||
if (bind->mods != modifiers)
|
||||
continue;
|
||||
|
||||
/* If a symbol was specified by the user, we need to look in
|
||||
* the array of translated keycodes for the event’s keycode */
|
||||
if (bind->symbol != NULL) {
|
||||
if (memmem(bind->translated_to,
|
||||
bind->number_keycodes * sizeof(xcb_keycode_t),
|
||||
&keycode, sizeof(xcb_keycode_t)) != NULL)
|
||||
break;
|
||||
} else {
|
||||
/* This case is easier: The user specified a keycode */
|
||||
if (bind->keycode == keycode)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (bind == TAILQ_END(bindings) ? NULL : bind);
|
||||
}
|
||||
|
||||
/*
|
||||
* Translates keysymbols to keycodes for all bindings which use keysyms.
|
||||
*
|
||||
*/
|
||||
void translate_keysyms() {
|
||||
Binding *bind;
|
||||
TAILQ_FOREACH(bind, bindings, bindings) {
|
||||
/* The easy case: the user specified a keycode directly. */
|
||||
if (bind->keycode > 0) {
|
||||
grab_keycode_for_binding(conn, bind, bind->keycode);
|
||||
if (bind->keycode > 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
/* We need to translate the symbol to a keycode */
|
||||
xcb_keysym_t keysym = XStringToKeysym(bind->symbol);
|
||||
if (keysym == NoSymbol) {
|
||||
LOG("Could not translate string to key symbol: \"%s\"\n", bind->symbol);
|
||||
ELOG("Could not translate string to key symbol: \"%s\"\n", bind->symbol);
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef OLD_XCB_KEYSYMS_API
|
||||
bind->number_keycodes = 1;
|
||||
xcb_keycode_t code = xcb_key_symbols_get_keycode(keysyms, keysym);
|
||||
LOG("Translated symbol \"%s\" to 1 keycode (%d)\n", bind->symbol, code);
|
||||
grab_keycode_for_binding(conn, bind, code);
|
||||
DLOG("Translated symbol \"%s\" to 1 keycode (%d)\n", bind->symbol, code);
|
||||
grab_keycode_for_binding(global_conn, bind, code);
|
||||
bind->translated_to = smalloc(sizeof(xcb_keycode_t));
|
||||
memcpy(bind->translated_to, &code, sizeof(xcb_keycode_t));
|
||||
#else
|
||||
uint32_t last_keycode = 0;
|
||||
xcb_keycode_t *keycodes = xcb_key_symbols_get_keycode(keysyms, keysym);
|
||||
if (keycodes == NULL) {
|
||||
LOG("Could not translate symbol \"%s\"\n", bind->symbol);
|
||||
DLOG("Could not translate symbol \"%s\"\n", bind->symbol);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -136,11 +170,10 @@ void grab_all_keys(xcb_connection_t *conn) {
|
|||
* and skip them */
|
||||
if (last_keycode == *walk)
|
||||
continue;
|
||||
grab_keycode_for_binding(conn, bind, *walk);
|
||||
last_keycode = *walk;
|
||||
bind->number_keycodes++;
|
||||
}
|
||||
LOG("Translated symbol \"%s\" to %d keycode\n", bind->symbol, bind->number_keycodes);
|
||||
DLOG("Translated symbol \"%s\" to %d keycode\n", bind->symbol, bind->number_keycodes);
|
||||
bind->translated_to = smalloc(bind->number_keycodes * sizeof(xcb_keycode_t));
|
||||
memcpy(bind->translated_to, keycodes, bind->number_keycodes * sizeof(xcb_keycode_t));
|
||||
free(keycodes);
|
||||
|
@ -148,6 +181,29 @@ void grab_all_keys(xcb_connection_t *conn) {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Grab the bound keys (tell X to send us keypress events for those keycodes)
|
||||
*
|
||||
*/
|
||||
void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch) {
|
||||
Binding *bind;
|
||||
TAILQ_FOREACH(bind, bindings, bindings) {
|
||||
if ((bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) == 0) ||
|
||||
(!bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) != 0))
|
||||
continue;
|
||||
|
||||
/* The easy case: the user specified a keycode directly. */
|
||||
if (bind->keycode > 0) {
|
||||
grab_keycode_for_binding(conn, bind, bind->keycode);
|
||||
continue;
|
||||
}
|
||||
|
||||
xcb_keycode_t *walk = bind->translated_to;
|
||||
for (int i = 0; i < bind->number_keycodes; i++)
|
||||
grab_keycode_for_binding(conn, bind, *walk);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Switches the key bindings to the given mode, if the mode exists
|
||||
*
|
||||
|
@ -163,18 +219,89 @@ void switch_mode(xcb_connection_t *conn, const char *new_mode) {
|
|||
|
||||
ungrab_all_keys(conn);
|
||||
bindings = mode->bindings;
|
||||
grab_all_keys(conn);
|
||||
translate_keysyms();
|
||||
grab_all_keys(conn, false);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG("ERROR: Mode not found\n");
|
||||
ELOG("ERROR: Mode not found\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Reads the configuration from ~/.i3/config or /etc/i3/config if not found.
|
||||
* Get the path of the first configuration file found. Checks the XDG folders
|
||||
* first ($XDG_CONFIG_HOME, $XDG_CONFIG_DIRS), then the traditional paths.
|
||||
*
|
||||
* If you specify override_configpath, only this path is used to look for a
|
||||
* configuration file.
|
||||
*/
|
||||
static char *get_config_path() {
|
||||
/* 1: check for $XDG_CONFIG_HOME/i3/config */
|
||||
char *xdg_config_home, *xdg_config_dirs, *config_path;
|
||||
|
||||
if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL)
|
||||
xdg_config_home = "~/.config";
|
||||
|
||||
xdg_config_home = glob_path(xdg_config_home);
|
||||
if (asprintf(&config_path, "%s/i3/config", xdg_config_home) == -1)
|
||||
die("asprintf() failed");
|
||||
free(xdg_config_home);
|
||||
|
||||
if (path_exists(config_path))
|
||||
return config_path;
|
||||
free(config_path);
|
||||
|
||||
/* 2: check for $XDG_CONFIG_DIRS/i3/config */
|
||||
if ((xdg_config_dirs = getenv("XDG_CONFIG_DIRS")) == NULL)
|
||||
xdg_config_dirs = "/etc/xdg";
|
||||
|
||||
char *buf = strdup(xdg_config_dirs);
|
||||
char *tok = strtok(buf, ":");
|
||||
while (tok != NULL) {
|
||||
tok = glob_path(tok);
|
||||
if (asprintf(&config_path, "%s/i3/config", tok) == -1)
|
||||
die("asprintf() failed");
|
||||
free(tok);
|
||||
if (path_exists(config_path)) {
|
||||
free(buf);
|
||||
return config_path;
|
||||
}
|
||||
free(config_path);
|
||||
tok = strtok(NULL, ":");
|
||||
}
|
||||
free(buf);
|
||||
|
||||
/* 3: check traditional paths */
|
||||
config_path = glob_path("~/.i3/config");
|
||||
if (path_exists(config_path))
|
||||
return config_path;
|
||||
|
||||
config_path = strdup("/etc/i3/config");
|
||||
if (!path_exists(config_path))
|
||||
die("Neither $XDG_CONFIG_HOME/i3/config, nor "
|
||||
"$XDG_CONFIG_DIRS/i3/config, nor ~/.i3/config nor "
|
||||
"/etc/i3/config exist.");
|
||||
|
||||
return config_path;
|
||||
}
|
||||
|
||||
/*
|
||||
* Finds the configuration file to use (either the one specified by
|
||||
* override_configpath), the user’s one or the system default) and calls
|
||||
* parse_file().
|
||||
*
|
||||
*/
|
||||
static void parse_configuration(const char *override_configpath) {
|
||||
if (override_configpath != NULL) {
|
||||
parse_file(override_configpath);
|
||||
return;
|
||||
}
|
||||
|
||||
char *path = get_config_path();
|
||||
DLOG("Parsing configfile %s\n", path);
|
||||
parse_file(path);
|
||||
free(path);
|
||||
}
|
||||
|
||||
/*
|
||||
* (Re-)loads the configuration file (sets useful defaults before).
|
||||
*
|
||||
*/
|
||||
void load_configuration(xcb_connection_t *conn, const char *override_configpath, bool reload) {
|
||||
|
@ -208,6 +335,11 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
|
|||
TAILQ_REMOVE(&assignments, assign, assignments);
|
||||
FREE(assign);
|
||||
}
|
||||
|
||||
/* Clear workspace names */
|
||||
Workspace *ws;
|
||||
TAILQ_FOREACH(ws, workspaces, workspaces)
|
||||
workspace_set_name(ws, NULL);
|
||||
}
|
||||
|
||||
SLIST_INIT(&modes);
|
||||
|
@ -220,388 +352,36 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
|
|||
|
||||
bindings = default_mode->bindings;
|
||||
|
||||
SLIST_HEAD(variables_head, Variable) variables;
|
||||
|
||||
#define OPTION_STRING(name) \
|
||||
if (strcasecmp(key, #name) == 0) { \
|
||||
config.name = sstrdup(value); \
|
||||
continue; \
|
||||
}
|
||||
|
||||
#define REQUIRED_OPTION(name) \
|
||||
if (config.name == NULL) \
|
||||
die("You did not specify required configuration option " #name "\n");
|
||||
|
||||
#define OPTION_COLORTRIPLE(opt, name) \
|
||||
if (strcasecmp(key, opt) == 0) { \
|
||||
char border[8], background[8], text[8]; \
|
||||
memset(border, 0, sizeof(border)); \
|
||||
memset(background, 0, sizeof(background)); \
|
||||
memset(text, 0, sizeof(text)); \
|
||||
border[0] = background[0] = text[0] = '#'; \
|
||||
if (sscanf(value, "#%06[0-9a-fA-F] #%06[0-9a-fA-F] #%06[0-9a-fA-F]", \
|
||||
border + 1, background + 1, text + 1) != 3 || \
|
||||
strlen(border) != 7 || \
|
||||
strlen(background) != 7 || \
|
||||
strlen(text) != 7) \
|
||||
die("invalid color code line: %s\n", value); \
|
||||
config.name.border = get_colorpixel(conn, border); \
|
||||
config.name.background = get_colorpixel(conn, background); \
|
||||
config.name.text = get_colorpixel(conn, text); \
|
||||
continue; \
|
||||
}
|
||||
|
||||
/* Clear the old config or initialize the data structure */
|
||||
memset(&config, 0, sizeof(config));
|
||||
|
||||
SLIST_INIT(&variables);
|
||||
|
||||
/* Initialize default colors */
|
||||
config.client.focused.border = get_colorpixel(conn, "#4c7899");
|
||||
config.client.focused.background = get_colorpixel(conn, "#285577");
|
||||
config.client.focused.text = get_colorpixel(conn, "#ffffff");
|
||||
#define INIT_COLOR(x, cborder, cbackground, ctext) \
|
||||
do { \
|
||||
x.border = get_colorpixel(conn, cborder); \
|
||||
x.background = get_colorpixel(conn, cbackground); \
|
||||
x.text = get_colorpixel(conn, ctext); \
|
||||
} while (0)
|
||||
|
||||
config.client.focused_inactive.border = get_colorpixel(conn, "#333333");
|
||||
config.client.focused_inactive.background = get_colorpixel(conn, "#5f676a");
|
||||
config.client.focused_inactive.text = get_colorpixel(conn, "#ffffff");
|
||||
INIT_COLOR(config.client.focused, "#4c7899", "#285577", "#ffffff");
|
||||
INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff");
|
||||
INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888");
|
||||
INIT_COLOR(config.client.urgent, "#2f343a", "#900000", "#ffffff");
|
||||
INIT_COLOR(config.bar.focused, "#4c7899", "#285577", "#ffffff");
|
||||
INIT_COLOR(config.bar.unfocused, "#333333", "#222222", "#888888");
|
||||
INIT_COLOR(config.bar.urgent, "#2f343a", "#900000", "#ffffff");
|
||||
|
||||
config.client.unfocused.border = get_colorpixel(conn, "#333333");
|
||||
config.client.unfocused.background = get_colorpixel(conn, "#222222");
|
||||
config.client.unfocused.text = get_colorpixel(conn, "#888888");
|
||||
parse_configuration(override_configpath);
|
||||
|
||||
config.client.urgent.border = get_colorpixel(conn, "#2f343a");
|
||||
config.client.urgent.background = get_colorpixel(conn, "#900000");
|
||||
config.client.urgent.text = get_colorpixel(conn, "#ffffff");
|
||||
|
||||
config.bar.focused.border = get_colorpixel(conn, "#4c7899");
|
||||
config.bar.focused.background = get_colorpixel(conn, "#285577");
|
||||
config.bar.focused.text = get_colorpixel(conn, "#ffffff");
|
||||
|
||||
config.bar.unfocused.border = get_colorpixel(conn, "#333333");
|
||||
config.bar.unfocused.background = get_colorpixel(conn, "#222222");
|
||||
config.bar.unfocused.text = get_colorpixel(conn, "#888888");
|
||||
|
||||
config.bar.urgent.border = get_colorpixel(conn, "#2f343a");
|
||||
config.bar.urgent.background = get_colorpixel(conn, "#900000");
|
||||
config.bar.urgent.text = get_colorpixel(conn, "#ffffff");
|
||||
|
||||
if (config_use_lexer) {
|
||||
/* Yes, this will be cleaned up soon. */
|
||||
if (override_configpath != NULL) {
|
||||
parse_file(override_configpath);
|
||||
} else {
|
||||
FILE *handle;
|
||||
char *globbed = glob_path("~/.i3/config");
|
||||
if ((handle = fopen(globbed, "r")) == NULL) {
|
||||
if ((handle = fopen("/etc/i3/config", "r")) == NULL) {
|
||||
die("Neither \"%s\" nor /etc/i3/config could be opened\n", globbed);
|
||||
} else {
|
||||
parse_file("/etc/i3/config");
|
||||
}
|
||||
} else {
|
||||
parse_file(globbed);
|
||||
}
|
||||
}
|
||||
if (reload)
|
||||
grab_all_keys(conn);
|
||||
} else {
|
||||
|
||||
FILE *handle;
|
||||
if (override_configpath != NULL) {
|
||||
if ((handle = fopen(override_configpath, "r")) == NULL)
|
||||
die("Could not open configfile \"%s\".\n", override_configpath);
|
||||
} else {
|
||||
/* We first check for ~/.i3/config, then for /etc/i3/config */
|
||||
char *globbed = glob_path("~/.i3/config");
|
||||
if ((handle = fopen(globbed, "r")) == NULL)
|
||||
if ((handle = fopen("/etc/i3/config", "r")) == NULL)
|
||||
die("Neither \"%s\" nor /etc/i3/config could be opened\n", globbed);
|
||||
free(globbed);
|
||||
}
|
||||
char key[512], value[512], buffer[1026];
|
||||
|
||||
while (!feof(handle)) {
|
||||
if (fgets(buffer, 1024, handle) == NULL) {
|
||||
/* fgets returns NULL on EOF and on error, so see which one it is. */
|
||||
if (feof(handle))
|
||||
break;
|
||||
die("Could not read configuration file\n");
|
||||
}
|
||||
|
||||
if (config.terminal != NULL)
|
||||
replace_variable(buffer, "$terminal", config.terminal);
|
||||
|
||||
/* Replace all custom variables */
|
||||
struct Variable *current;
|
||||
SLIST_FOREACH(current, &variables, variables)
|
||||
replace_variable(buffer, current->key, current->value);
|
||||
|
||||
/* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */
|
||||
if (sscanf(buffer, "%s %[^\n]", key, value) < 1 ||
|
||||
key[0] == '#' || strlen(key) < 3)
|
||||
continue;
|
||||
|
||||
OPTION_STRING(terminal);
|
||||
OPTION_STRING(font);
|
||||
|
||||
/* Colors */
|
||||
OPTION_COLORTRIPLE("client.focused", client.focused);
|
||||
OPTION_COLORTRIPLE("client.focused_inactive", client.focused_inactive);
|
||||
OPTION_COLORTRIPLE("client.unfocused", client.unfocused);
|
||||
OPTION_COLORTRIPLE("client.urgent", client.urgent);
|
||||
OPTION_COLORTRIPLE("bar.focused", bar.focused);
|
||||
OPTION_COLORTRIPLE("bar.unfocused", bar.unfocused);
|
||||
OPTION_COLORTRIPLE("bar.urgent", bar.urgent);
|
||||
|
||||
/* exec-lines (autostart) */
|
||||
if (strcasecmp(key, "exec") == 0) {
|
||||
struct Autostart *new = smalloc(sizeof(struct Autostart));
|
||||
new->command = sstrdup(value);
|
||||
TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* key bindings */
|
||||
if (strcasecmp(key, "bind") == 0 || strcasecmp(key, "bindsym") == 0) {
|
||||
#define CHECK_MODIFIER(name) \
|
||||
if (strncasecmp(walk, #name, strlen(#name)) == 0) { \
|
||||
modifiers |= BIND_##name; \
|
||||
walk += strlen(#name) + 1; \
|
||||
continue; \
|
||||
}
|
||||
char *walk = value, *rest;
|
||||
uint32_t modifiers = 0;
|
||||
|
||||
while (*walk != '\0') {
|
||||
/* Need to check for Mod1-5, Ctrl, Shift, Mode_switch */
|
||||
CHECK_MODIFIER(SHIFT);
|
||||
CHECK_MODIFIER(CONTROL);
|
||||
CHECK_MODIFIER(MODE_SWITCH);
|
||||
CHECK_MODIFIER(MOD1);
|
||||
CHECK_MODIFIER(MOD2);
|
||||
CHECK_MODIFIER(MOD3);
|
||||
CHECK_MODIFIER(MOD4);
|
||||
CHECK_MODIFIER(MOD5);
|
||||
|
||||
/* No modifier found? Then we’re done with this step */
|
||||
break;
|
||||
}
|
||||
|
||||
Binding *new = scalloc(sizeof(Binding));
|
||||
|
||||
/* Now check for the keycode or copy the symbol */
|
||||
if (strcasecmp(key, "bind") == 0) {
|
||||
int keycode = strtol(walk, &rest, 10);
|
||||
if (!rest || *rest != ' ')
|
||||
die("Invalid binding (keycode)\n");
|
||||
new->keycode = keycode;
|
||||
} else {
|
||||
rest = walk;
|
||||
char *sym = rest;
|
||||
while (*rest != '\0' && *rest != ' ')
|
||||
rest++;
|
||||
if (*rest != ' ')
|
||||
die("Invalid binding (keysym)\n");
|
||||
#if defined(__OpenBSD__)
|
||||
size_t len = strlen(sym);
|
||||
if (len > (rest - sym))
|
||||
len = (rest - sym);
|
||||
new->symbol = smalloc(len + 1);
|
||||
memcpy(new->symbol, sym, len+1);
|
||||
new->symbol[len]='\0';
|
||||
#else
|
||||
new->symbol = strndup(sym, (rest - sym));
|
||||
#endif
|
||||
}
|
||||
rest++;
|
||||
LOG("keycode = %d, symbol = %s, modifiers = %d, command = *%s*\n", new->keycode, new->symbol, modifiers, rest);
|
||||
new->mods = modifiers;
|
||||
new->command = sstrdup(rest);
|
||||
TAILQ_INSERT_TAIL(bindings, new, bindings);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcasecmp(key, "floating_modifier") == 0) {
|
||||
char *walk = value;
|
||||
uint32_t modifiers = 0;
|
||||
|
||||
while (*walk != '\0') {
|
||||
/* Need to check for Mod1-5, Ctrl, Shift, Mode_switch */
|
||||
CHECK_MODIFIER(SHIFT);
|
||||
CHECK_MODIFIER(CONTROL);
|
||||
CHECK_MODIFIER(MODE_SWITCH);
|
||||
CHECK_MODIFIER(MOD1);
|
||||
CHECK_MODIFIER(MOD2);
|
||||
CHECK_MODIFIER(MOD3);
|
||||
CHECK_MODIFIER(MOD4);
|
||||
CHECK_MODIFIER(MOD5);
|
||||
|
||||
/* No modifier found? Then we’re done with this step */
|
||||
break;
|
||||
}
|
||||
|
||||
LOG("Floating modifiers = %d\n", modifiers);
|
||||
config.floating_modifier = modifiers;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* workspace "workspace number" [screen <screen>] ["name of the workspace"]
|
||||
* with screen := <number> | <position>, e.g. screen 1280 or screen 1 */
|
||||
if (strcasecmp(key, "name") == 0 || strcasecmp(key, "workspace") == 0) {
|
||||
LOG("workspace: %s\n",value);
|
||||
char *ws_str = sstrdup(value);
|
||||
char *end = strchr(ws_str, ' ');
|
||||
if (end == NULL)
|
||||
die("Malformed name, couln't find terminating space\n");
|
||||
*end = '\0';
|
||||
|
||||
/* Strip trailing whitespace */
|
||||
while (strlen(value) > 0 && value[strlen(value)-1] == ' ')
|
||||
value[strlen(value)-1] = '\0';
|
||||
|
||||
int ws_num = atoi(ws_str);
|
||||
|
||||
if (ws_num < 1 || ws_num > 10)
|
||||
die("Malformed name, invalid workspace number\n");
|
||||
|
||||
/* find the name */
|
||||
char *name = value;
|
||||
name += strlen(ws_str) + 1;
|
||||
|
||||
if (strncasecmp(name, "screen ", strlen("screen ")) == 0) {
|
||||
char *screen = strdup(name + strlen("screen "));
|
||||
if ((end = strchr(screen, ' ')) != NULL)
|
||||
*end = '\0';
|
||||
LOG("Setting preferred screen for workspace %d to \"%s\"\n", ws_num, screen);
|
||||
workspace_get(ws_num-1)->preferred_screen = screen;
|
||||
|
||||
name += strlen("screen ") + strlen(screen);
|
||||
}
|
||||
|
||||
/* Strip leading whitespace */
|
||||
while (*name != '\0' && *name == ' ')
|
||||
name++;
|
||||
|
||||
LOG("rest to parse = %s\n", name);
|
||||
|
||||
if (name == '\0') {
|
||||
free(ws_str);
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG("setting name to \"%s\"\n", name);
|
||||
|
||||
if (*name != '\0')
|
||||
workspace_set_name(workspace_get(ws_num - 1), name);
|
||||
free(ws_str);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* assign window class[/window title] → workspace */
|
||||
if (strcasecmp(key, "assign") == 0) {
|
||||
LOG("assign: \"%s\"\n", value);
|
||||
char *class_title;
|
||||
char *target;
|
||||
char *end;
|
||||
|
||||
/* If the window class/title is quoted we skip quotes */
|
||||
if (value[0] == '"') {
|
||||
class_title = sstrdup(value+1);
|
||||
end = strchr(class_title, '"');
|
||||
} else {
|
||||
class_title = sstrdup(value);
|
||||
/* If it is not quoted, we terminate it at the first space */
|
||||
end = strchr(class_title, ' ');
|
||||
}
|
||||
if (end == NULL)
|
||||
die("Malformed assignment, couldn't find terminating quote\n");
|
||||
*end = '\0';
|
||||
|
||||
/* Strip trailing whitespace */
|
||||
while (strlen(value) > 0 && value[strlen(value)-1] == ' ')
|
||||
value[strlen(value)-1] = '\0';
|
||||
|
||||
/* Strip trailing whitespace */
|
||||
while (strlen(value) > 0 && value[strlen(value)-1] == ' ')
|
||||
value[strlen(value)-1] = '\0';
|
||||
|
||||
/* The target is the last argument separated by a space */
|
||||
if ((target = strrchr(value, ' ')) == NULL)
|
||||
die("Malformed assignment, couldn't find target (\"%s\")\n", value);
|
||||
target++;
|
||||
|
||||
if (strchr(target, '~') == NULL && (atoi(target) < 1 || atoi(target) > 10))
|
||||
die("Malformed assignment, invalid workspace number\n");
|
||||
|
||||
LOG("assignment parsed: \"%s\" to \"%s\"\n", class_title, target);
|
||||
|
||||
struct Assignment *new = scalloc(sizeof(struct Assignment));
|
||||
new->windowclass_title = class_title;
|
||||
if (strchr(target, '~') != NULL)
|
||||
new->floating = ASSIGN_FLOATING_ONLY;
|
||||
|
||||
while (*target == '~')
|
||||
target++;
|
||||
|
||||
if (atoi(target) >= 1) {
|
||||
if (new->floating == ASSIGN_FLOATING_ONLY)
|
||||
new->floating = ASSIGN_FLOATING;
|
||||
new->workspace = atoi(target);
|
||||
}
|
||||
TAILQ_INSERT_TAIL(&assignments, new, assignments);
|
||||
|
||||
LOG("Assignment loaded: \"%s\":\n", class_title);
|
||||
if (new->floating != ASSIGN_FLOATING_ONLY)
|
||||
LOG(" to workspace %d\n", new->workspace);
|
||||
|
||||
if (new->floating != ASSIGN_FLOATING_NO)
|
||||
LOG(" will be floating\n");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* set a custom variable */
|
||||
if (strcasecmp(key, "set") == 0) {
|
||||
if (value[0] != '$')
|
||||
die("Malformed variable assignment, name has to start with $\n");
|
||||
|
||||
/* get key/value for this variable */
|
||||
char *v_key = value, *v_value;
|
||||
if ((v_value = strstr(value, " ")) == NULL)
|
||||
die("Malformed variable assignment, need a value\n");
|
||||
|
||||
*(v_value++) = '\0';
|
||||
|
||||
struct Variable *new = scalloc(sizeof(struct Variable));
|
||||
new->key = sstrdup(v_key);
|
||||
new->value = sstrdup(v_value);
|
||||
SLIST_INSERT_HEAD(&variables, new, variables);
|
||||
LOG("Got new variable %s = %s\n", v_key, v_value);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcasecmp(key, "ipc-socket") == 0) {
|
||||
config.ipc_socket_path = sstrdup(value);
|
||||
continue;
|
||||
}
|
||||
|
||||
die("Unknown configfile option: %s\n", key);
|
||||
}
|
||||
/* now grab all keys again */
|
||||
if (reload)
|
||||
grab_all_keys(conn);
|
||||
fclose(handle);
|
||||
|
||||
while (!SLIST_EMPTY(&variables)) {
|
||||
struct Variable *v = SLIST_FIRST(&variables);
|
||||
SLIST_REMOVE_HEAD(&variables, variables);
|
||||
free(v->key);
|
||||
free(v->value);
|
||||
free(v);
|
||||
}
|
||||
if (reload) {
|
||||
translate_keysyms();
|
||||
grab_all_keys(conn, false);
|
||||
}
|
||||
|
||||
REQUIRED_OPTION(terminal);
|
||||
REQUIRED_OPTION(font);
|
||||
|
||||
/* Set an empty name for every workspace which got no name */
|
||||
|
@ -618,6 +398,4 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
|
|||
|
||||
workspace_set_name(ws, NULL);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* vim:ts=8:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "data.h"
|
||||
#include "log.h"
|
||||
|
||||
/*
|
||||
* Returns the mode of the given container (or MODE_DEFAULT if a NULL pointer
|
||||
* was passed in order to save a few explicit checks in other places). If
|
||||
* for_frame was set to true, the special case of having exactly one client
|
||||
* in a container is handled so that MODE_DEFAULT is returned. For some parts
|
||||
* of the rendering, this is interesting, other parts need the real mode.
|
||||
*
|
||||
*/
|
||||
int container_mode(Container *con, bool for_frame) {
|
||||
int num_clients = 0;
|
||||
Client *client;
|
||||
|
||||
if (con == NULL || con->mode == MODE_DEFAULT)
|
||||
return MODE_DEFAULT;
|
||||
|
||||
if (!for_frame)
|
||||
return con->mode;
|
||||
|
||||
CIRCLEQ_FOREACH(client, &(con->clients), clients)
|
||||
num_clients++;
|
||||
|
||||
/* If the container contains only one client, mode is irrelevant */
|
||||
if (num_clients == 1) {
|
||||
DLOG("mode to default\n");
|
||||
return MODE_DEFAULT;
|
||||
}
|
||||
|
||||
return con->mode;
|
||||
}
|
12
src/debug.c
12
src/debug.c
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
|
@ -14,6 +14,8 @@
|
|||
#include <stdio.h>
|
||||
#include <xcb/xcb.h>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
static const char *labelError[] = {
|
||||
"Success",
|
||||
"BadRequest",
|
||||
|
@ -219,19 +221,21 @@ int format_event(xcb_generic_event_t *e) {
|
|||
|
||||
switch(e->response_type) {
|
||||
case 0:
|
||||
printf("Error %s on seqnum %d (%s).\n",
|
||||
DLOG("Error %s on seqnum %d (%s).\n",
|
||||
labelError[*((uint8_t *) e + 1)],
|
||||
seqnum,
|
||||
labelRequest[*((uint8_t *) e + 10)]);
|
||||
break;
|
||||
default:
|
||||
printf("Event %s following seqnum %d%s.\n",
|
||||
if (e->response_type > sizeof(labelEvent) / sizeof(char*))
|
||||
break;
|
||||
DLOG("Event %s following seqnum %d%s.\n",
|
||||
labelEvent[e->response_type],
|
||||
seqnum,
|
||||
labelSendEvent[sendEvent]);
|
||||
break;
|
||||
case XCB_KEYMAP_NOTIFY:
|
||||
printf("Event %s%s.\n",
|
||||
DLOG("Event %s%s.\n",
|
||||
labelEvent[e->response_type],
|
||||
labelSendEvent[sendEvent]);
|
||||
break;
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* vim:ts=8:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
* ewmh.c: Functions to get/set certain EWMH properties easily.
|
||||
*
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "data.h"
|
||||
#include "table.h"
|
||||
#include "i3.h"
|
||||
#include "xcb.h"
|
||||
#include "util.h"
|
||||
#include "log.h"
|
||||
|
||||
/*
|
||||
* Updates _NET_CURRENT_DESKTOP with the current desktop number.
|
||||
*
|
||||
* EWMH: The index of the current desktop. This is always an integer between 0
|
||||
* and _NET_NUMBER_OF_DESKTOPS - 1.
|
||||
*
|
||||
*/
|
||||
void ewmh_update_current_desktop() {
|
||||
uint32_t current_desktop = c_ws->num;
|
||||
xcb_change_property(global_conn, XCB_PROP_MODE_REPLACE, root,
|
||||
atoms[_NET_CURRENT_DESKTOP], CARDINAL, 32, 1,
|
||||
¤t_desktop);
|
||||
}
|
||||
|
||||
/*
|
||||
* Updates _NET_ACTIVE_WINDOW with the currently focused window.
|
||||
*
|
||||
* EWMH: The window ID of the currently active window or None if no window has
|
||||
* the focus.
|
||||
*
|
||||
*/
|
||||
void ewmh_update_active_window(xcb_window_t window) {
|
||||
xcb_change_property(global_conn, XCB_PROP_MODE_REPLACE, root,
|
||||
atoms[_NET_ACTIVE_WINDOW], WINDOW, 32, 1, &window);
|
||||
}
|
||||
|
||||
/*
|
||||
* Updates the workarea for each desktop.
|
||||
*
|
||||
* EWMH: Contains a geometry for each desktop. These geometries specify an area
|
||||
* that is completely contained within the viewport. Work area SHOULD be used by
|
||||
* desktop applications to place desktop icons appropriately.
|
||||
*
|
||||
*/
|
||||
void ewmh_update_workarea() {
|
||||
Workspace *ws;
|
||||
int num_workspaces = 0, count = 0;
|
||||
Rect last_rect = {0, 0, 0, 0};
|
||||
|
||||
/* Get the number of workspaces */
|
||||
TAILQ_FOREACH(ws, workspaces, workspaces) {
|
||||
/* Check if we need to initialize last_rect. The case that the
|
||||
* first workspace is all-zero may happen when the user
|
||||
* assigned workspace 2 for his first screen, for example. Thus
|
||||
* we need an initialized last_rect in the very first run of
|
||||
* the following loop. */
|
||||
if (last_rect.width == 0 && last_rect.height == 0 &&
|
||||
ws->rect.width != 0 && ws->rect.height != 0) {
|
||||
memcpy(&last_rect, &(ws->rect), sizeof(Rect));
|
||||
}
|
||||
num_workspaces++;
|
||||
}
|
||||
|
||||
DLOG("Got %d workspaces\n", num_workspaces);
|
||||
uint8_t *workarea = smalloc(sizeof(Rect) * num_workspaces);
|
||||
TAILQ_FOREACH(ws, workspaces, workspaces) {
|
||||
DLOG("storing %d: %dx%d with %d x %d\n", count, ws->rect.x,
|
||||
ws->rect.y, ws->rect.width, ws->rect.height);
|
||||
/* If a workspace is not yet initialized and thus its
|
||||
* dimensions are zero, we will instead put the dimensions
|
||||
* of the last workspace in the list. For example firefox
|
||||
* intersects all workspaces and does not cope so well with
|
||||
* an all-zero workspace. */
|
||||
if (ws->rect.width == 0 || ws->rect.height == 0) {
|
||||
DLOG("re-using last_rect (%dx%d, %d, %d)\n",
|
||||
last_rect.x, last_rect.y, last_rect.width,
|
||||
last_rect.height);
|
||||
memcpy(workarea + (sizeof(Rect) * count++), &last_rect, sizeof(Rect));
|
||||
continue;
|
||||
}
|
||||
memcpy(workarea + (sizeof(Rect) * count++), &(ws->rect), sizeof(Rect));
|
||||
memcpy(&last_rect, &(ws->rect), sizeof(Rect));
|
||||
}
|
||||
xcb_change_property(global_conn, XCB_PROP_MODE_REPLACE, root,
|
||||
atoms[_NET_WORKAREA], CARDINAL, 32,
|
||||
num_workspaces * (sizeof(Rect) / sizeof(uint32_t)),
|
||||
workarea);
|
||||
free(workarea);
|
||||
xcb_flush(global_conn);
|
||||
}
|
282
src/floating.c
282
src/floating.c
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
|
@ -27,6 +27,7 @@
|
|||
#include "client.h"
|
||||
#include "floating.h"
|
||||
#include "workspace.h"
|
||||
#include "log.h"
|
||||
|
||||
/*
|
||||
* Toggles floating mode for the given client.
|
||||
|
@ -42,12 +43,12 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic
|
|||
i3Font *font = load_font(conn, config.font);
|
||||
|
||||
if (client->dock) {
|
||||
LOG("Not putting dock client into floating mode\n");
|
||||
DLOG("Not putting dock client into floating mode\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (con == NULL) {
|
||||
LOG("This client is already in floating (container == NULL), re-inserting\n");
|
||||
DLOG("This client is already in floating (container == NULL), re-inserting\n");
|
||||
Client *next_tiling;
|
||||
Workspace *ws = client->workspace;
|
||||
SLIST_FOREACH(next_tiling, &(ws->focus_stack), focus_clients)
|
||||
|
@ -62,7 +63,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic
|
|||
/* Remove the client from the list of floating clients */
|
||||
TAILQ_REMOVE(&(ws->floating_clients), client, floating_clients);
|
||||
|
||||
LOG("destination container = %p\n", con);
|
||||
DLOG("destination container = %p\n", con);
|
||||
Client *old_focused = con->currently_focused;
|
||||
/* Preserve position/size */
|
||||
memcpy(&(client->floating_rect), &(client->rect), sizeof(Rect));
|
||||
|
@ -74,7 +75,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic
|
|||
CIRCLEQ_INSERT_AFTER(&(con->clients), old_focused, client, clients);
|
||||
else CIRCLEQ_INSERT_TAIL(&(con->clients), client, clients);
|
||||
|
||||
LOG("Re-inserted the client into the matrix.\n");
|
||||
DLOG("Re-inserted the window.\n");
|
||||
con->currently_focused = client;
|
||||
|
||||
client_set_below_floating(conn, client);
|
||||
|
@ -85,7 +86,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic
|
|||
return;
|
||||
}
|
||||
|
||||
LOG("Entering floating for client %08x\n", client->child);
|
||||
DLOG("Entering floating for client %08x\n", client->child);
|
||||
|
||||
/* Remove the client of its container */
|
||||
client_remove_from_container(conn, client, con, false);
|
||||
|
@ -95,7 +96,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic
|
|||
TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients);
|
||||
|
||||
if (con->currently_focused == client) {
|
||||
LOG("Need to re-adjust currently_focused\n");
|
||||
DLOG("Need to re-adjust currently_focused\n");
|
||||
/* Get the next client in the focus stack for this particular container */
|
||||
con->currently_focused = get_last_focused_client(conn, con, NULL);
|
||||
}
|
||||
|
@ -118,11 +119,11 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic
|
|||
client->rect.width = client->child_rect.width + 2 + 2;
|
||||
client->rect.height = client->child_rect.height + (font->height + 2 + 2) + 2;
|
||||
|
||||
LOG("copying size from tiling (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
|
||||
DLOG("copying size from tiling (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
|
||||
client->floating_rect.width, client->floating_rect.height);
|
||||
} else {
|
||||
/* If the client was already in floating before we restore the old position / size */
|
||||
LOG("using: (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
|
||||
DLOG("using: (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y,
|
||||
client->floating_rect.width, client->floating_rect.height);
|
||||
memcpy(&(client->rect), &(client->floating_rect), sizeof(Rect));
|
||||
}
|
||||
|
@ -161,6 +162,69 @@ void floating_assign_to_workspace(Client *client, Workspace *new_workspace) {
|
|||
client->workspace->fullscreen_client = client;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is an ugly data structure which we need because there is no standard
|
||||
* way of having nested functions (only available as a gcc extension at the
|
||||
* moment, clang doesn’t support it) or blocks (only available as a clang
|
||||
* extension and only on Mac OS X systems at the moment).
|
||||
*
|
||||
*/
|
||||
struct resize_callback_params {
|
||||
border_t border;
|
||||
xcb_button_press_event_t *event;
|
||||
};
|
||||
|
||||
DRAGGING_CB(resize_callback) {
|
||||
struct resize_callback_params *params = extra;
|
||||
xcb_button_press_event_t *event = params->event;
|
||||
switch (params->border) {
|
||||
case BORDER_RIGHT: {
|
||||
int new_width = old_rect->width + (new_x - event->root_x);
|
||||
if ((new_width < 0) ||
|
||||
(new_width < client_min_width(client) && client->rect.width >= new_width))
|
||||
return;
|
||||
client->rect.width = new_width;
|
||||
break;
|
||||
}
|
||||
|
||||
case BORDER_BOTTOM: {
|
||||
int new_height = old_rect->height + (new_y - event->root_y);
|
||||
if ((new_height < 0) ||
|
||||
(new_height < client_min_height(client) && client->rect.height >= new_height))
|
||||
return;
|
||||
client->rect.height = old_rect->height + (new_y - event->root_y);
|
||||
break;
|
||||
}
|
||||
|
||||
case BORDER_TOP: {
|
||||
int new_height = old_rect->height + (event->root_y - new_y);
|
||||
if ((new_height < 0) ||
|
||||
(new_height < client_min_height(client) && client->rect.height >= new_height))
|
||||
return;
|
||||
|
||||
client->rect.y = old_rect->y + (new_y - event->root_y);
|
||||
client->rect.height = new_height;
|
||||
break;
|
||||
}
|
||||
|
||||
case BORDER_LEFT: {
|
||||
int new_width = old_rect->width + (event->root_x - new_x);
|
||||
if ((new_width < 0) ||
|
||||
(new_width < client_min_width(client) && client->rect.width >= new_width))
|
||||
return;
|
||||
client->rect.x = old_rect->x + (new_x - event->root_x);
|
||||
client->rect.width = new_width;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Push the new position/size to X11 */
|
||||
reposition_client(conn, client);
|
||||
resize_client(conn, client);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Called whenever the user clicks on a border (not the titlebar!) of a floating window.
|
||||
* Determines on which border the user clicked and launches the drag_pointer function
|
||||
|
@ -168,59 +232,10 @@ void floating_assign_to_workspace(Client *client, Workspace *new_workspace) {
|
|||
*
|
||||
*/
|
||||
int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
|
||||
|
||||
LOG("floating border click\n");
|
||||
DLOG("floating border click\n");
|
||||
|
||||
border_t border;
|
||||
|
||||
void resize_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
|
||||
switch (border) {
|
||||
case BORDER_RIGHT: {
|
||||
int new_width = old_rect->width + (new_x - event->root_x);
|
||||
if ((new_width < 0) ||
|
||||
(new_width < 50 && client->rect.width >= new_width))
|
||||
return;
|
||||
client->rect.width = new_width;
|
||||
break;
|
||||
}
|
||||
|
||||
case BORDER_BOTTOM: {
|
||||
int new_height = old_rect->height + (new_y - event->root_y);
|
||||
if ((new_height < 0) ||
|
||||
(new_height < 20 && client->rect.height >= new_height))
|
||||
return;
|
||||
client->rect.height = old_rect->height + (new_y - event->root_y);
|
||||
break;
|
||||
}
|
||||
|
||||
case BORDER_TOP: {
|
||||
int new_height = old_rect->height + (event->root_y - new_y);
|
||||
if ((new_height < 0) ||
|
||||
(new_height < 20 && client->rect.height >= new_height))
|
||||
return;
|
||||
|
||||
client->rect.y = old_rect->y + (new_y - event->root_y);
|
||||
client->rect.height = new_height;
|
||||
break;
|
||||
}
|
||||
|
||||
case BORDER_LEFT: {
|
||||
int new_width = old_rect->width + (event->root_x - new_x);
|
||||
if ((new_width < 0) ||
|
||||
(new_width < 50 && client->rect.width >= new_width))
|
||||
return;
|
||||
client->rect.x = old_rect->x + (new_x - event->root_x);
|
||||
client->rect.width = new_width;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Push the new position/size to X11 */
|
||||
reposition_client(conn, client);
|
||||
resize_client(conn, client);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
if (event->event_y < 2)
|
||||
border = BORDER_TOP;
|
||||
else if (event->event_y >= (client->rect.height - 2))
|
||||
|
@ -230,17 +245,31 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre
|
|||
else if (event->event_x >= (client->rect.width - 2))
|
||||
border = BORDER_RIGHT;
|
||||
else {
|
||||
LOG("Not on any border, not doing anything.\n");
|
||||
DLOG("Not on any border, not doing anything.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
LOG("border = %d\n", border);
|
||||
DLOG("border = %d\n", border);
|
||||
|
||||
drag_pointer(conn, client, event, XCB_NONE, border, resize_callback);
|
||||
struct resize_callback_params params = { border, event };
|
||||
|
||||
drag_pointer(conn, client, event, XCB_NONE, border, resize_callback, ¶ms);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
DRAGGING_CB(drag_window_callback) {
|
||||
struct xcb_button_press_event_t *event = extra;
|
||||
|
||||
/* Reposition the client correctly while moving */
|
||||
client->rect.x = old_rect->x + (new_x - event->root_x);
|
||||
client->rect.y = old_rect->y + (new_y - event->root_y);
|
||||
reposition_client(conn, client);
|
||||
/* Because reposition_client does not send a faked configure event (only resize does),
|
||||
* we need to initiate that on our own */
|
||||
fake_absolute_configure_notify(conn, client);
|
||||
/* fake_absolute_configure_notify flushes */
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when the user clicked on the titlebar of a floating window.
|
||||
|
@ -248,47 +277,95 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre
|
|||
*
|
||||
*/
|
||||
void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
|
||||
LOG("floating_drag_window\n");
|
||||
DLOG("floating_drag_window\n");
|
||||
|
||||
void drag_window_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
|
||||
/* Reposition the client correctly while moving */
|
||||
client->rect.x = old_rect->x + (new_x - event->root_x);
|
||||
client->rect.y = old_rect->y + (new_y - event->root_y);
|
||||
reposition_client(conn, client);
|
||||
/* Because reposition_client does not send a faked configure event (only resize does),
|
||||
* we need to initiate that on our own */
|
||||
fake_absolute_configure_notify(conn, client);
|
||||
/* fake_absolute_configure_notify flushes */
|
||||
}
|
||||
|
||||
drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback);
|
||||
drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback, event);
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when the user right-clicked on the titlebar of a floating window to
|
||||
* resize it.
|
||||
* This is an ugly data structure which we need because there is no standard
|
||||
* way of having nested functions (only available as a gcc extension at the
|
||||
* moment, clang doesn’t support it) or blocks (only available as a clang
|
||||
* extension and only on Mac OS X systems at the moment).
|
||||
*
|
||||
*/
|
||||
struct resize_window_callback_params {
|
||||
border_t corner;
|
||||
bool proportional;
|
||||
xcb_button_press_event_t *event;
|
||||
};
|
||||
|
||||
DRAGGING_CB(resize_window_callback) {
|
||||
struct resize_window_callback_params *params = extra;
|
||||
xcb_button_press_event_t *event = params->event;
|
||||
border_t corner = params->corner;
|
||||
|
||||
int32_t dest_x = client->rect.x;
|
||||
int32_t dest_y = client->rect.y;
|
||||
uint32_t dest_width;
|
||||
uint32_t dest_height;
|
||||
|
||||
double ratio = (double) old_rect->width / old_rect->height;
|
||||
|
||||
/* First guess: We resize by exactly the amount the mouse moved,
|
||||
* taking into account in which corner the client was grabbed */
|
||||
if (corner & BORDER_LEFT)
|
||||
dest_width = old_rect->width - (new_x - event->root_x);
|
||||
else dest_width = old_rect->width + (new_x - event->root_x);
|
||||
|
||||
if (corner & BORDER_TOP)
|
||||
dest_height = old_rect->height - (new_y - event->root_y);
|
||||
else dest_height = old_rect->height + (new_y - event->root_y);
|
||||
|
||||
/* Obey minimum window size */
|
||||
dest_width = max(dest_width, client_min_width(client));
|
||||
dest_height = max(dest_height, client_min_height(client));
|
||||
|
||||
/* User wants to keep proportions, so we may have to adjust our values */
|
||||
if (params->proportional) {
|
||||
dest_width = max(dest_width, (int) (dest_height * ratio));
|
||||
dest_height = max(dest_height, (int) (dest_width / ratio));
|
||||
}
|
||||
|
||||
/* If not the lower right corner is grabbed, we must also reposition
|
||||
* the client by exactly the amount we resized it */
|
||||
if (corner & BORDER_LEFT)
|
||||
dest_x = old_rect->x + (old_rect->width - dest_width);
|
||||
|
||||
if (corner & BORDER_TOP)
|
||||
dest_y = old_rect->y + (old_rect->height - dest_height);
|
||||
|
||||
client->rect = (Rect) { dest_x, dest_y, dest_width, dest_height };
|
||||
|
||||
/* resize_client flushes */
|
||||
resize_client(conn, client);
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when the user clicked on a floating window while holding the
|
||||
* floating_modifier and the right mouse button.
|
||||
* Calls the drag_pointer function with the resize_window callback
|
||||
*
|
||||
*/
|
||||
void floating_resize_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
|
||||
LOG("floating_resize_window\n");
|
||||
void floating_resize_window(xcb_connection_t *conn, Client *client,
|
||||
bool proportional, xcb_button_press_event_t *event) {
|
||||
DLOG("floating_resize_window\n");
|
||||
|
||||
void resize_window_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
|
||||
int32_t new_width = old_rect->width + (new_x - event->root_x);
|
||||
int32_t new_height = old_rect->height + (new_y - event->root_y);
|
||||
/* corner saves the nearest corner to the original click. It contains
|
||||
* a bitmask of the nearest borders (BORDER_LEFT, BORDER_RIGHT, …) */
|
||||
border_t corner = 0;
|
||||
|
||||
/* Obey minimum window size and reposition the client */
|
||||
if (new_width >= 50)
|
||||
client->rect.width = new_width;
|
||||
if (event->event_x <= (client->rect.width / 2))
|
||||
corner |= BORDER_LEFT;
|
||||
else corner |= BORDER_RIGHT;
|
||||
|
||||
if (new_height >= 20)
|
||||
client->rect.height = new_height;
|
||||
if (event->event_y <= (client->rect.height / 2))
|
||||
corner |= BORDER_TOP;
|
||||
else corner |= BORDER_RIGHT;
|
||||
|
||||
/* resize_client flushes */
|
||||
resize_client(conn, client);
|
||||
}
|
||||
struct resize_window_callback_params params = { corner, proportional, event };
|
||||
|
||||
drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback);
|
||||
drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback, ¶ms);
|
||||
}
|
||||
|
||||
|
||||
|
@ -301,7 +378,7 @@ void floating_resize_window(xcb_connection_t *conn, Client *client, xcb_button_p
|
|||
*
|
||||
*/
|
||||
void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event,
|
||||
xcb_window_t confine_to, border_t border, callback_t callback) {
|
||||
xcb_window_t confine_to, border_t border, callback_t callback, void *extra) {
|
||||
xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
|
||||
uint32_t new_x, new_y;
|
||||
Rect old_rect;
|
||||
|
@ -351,12 +428,12 @@ void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event
|
|||
break;
|
||||
|
||||
case XCB_UNMAP_NOTIFY:
|
||||
LOG("Unmap-notify, aborting\n");
|
||||
DLOG("Unmap-notify, aborting\n");
|
||||
xcb_event_handle(&evenths, inside_event);
|
||||
goto done;
|
||||
|
||||
default:
|
||||
LOG("Passing to original handler\n");
|
||||
DLOG("Passing to original handler\n");
|
||||
/* Use original handler */
|
||||
xcb_event_handle(&evenths, inside_event);
|
||||
break;
|
||||
|
@ -371,7 +448,7 @@ void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event
|
|||
new_x = ((xcb_motion_notify_event_t*)last_motion_notify)->root_x;
|
||||
new_y = ((xcb_motion_notify_event_t*)last_motion_notify)->root_y;
|
||||
|
||||
callback(&old_rect, new_x, new_y);
|
||||
callback(conn, client, &old_rect, new_x, new_y, extra);
|
||||
FREE(last_motion_notify);
|
||||
}
|
||||
done:
|
||||
|
@ -387,7 +464,7 @@ done:
|
|||
*
|
||||
*/
|
||||
void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, direction_t direction) {
|
||||
LOG("floating focus\n");
|
||||
DLOG("floating focus\n");
|
||||
|
||||
if (direction == D_LEFT || direction == D_RIGHT) {
|
||||
/* Go to the next/previous floating client */
|
||||
|
@ -409,10 +486,15 @@ void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused,
|
|||
*
|
||||
*/
|
||||
void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_t direction) {
|
||||
LOG("floating move\n");
|
||||
DLOG("floating move\n");
|
||||
|
||||
if (currently_focused->fullscreen) {
|
||||
DLOG("Cannot move fullscreen windows\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Rect destination = currently_focused->rect;
|
||||
Rect *screen = &(currently_focused->workspace->screen->rect);
|
||||
Rect *screen = &(currently_focused->workspace->output->rect);
|
||||
|
||||
switch (direction) {
|
||||
case D_LEFT:
|
||||
|
@ -437,7 +519,7 @@ void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_
|
|||
(int32_t)(destination.x + 5) >= (int32_t)(screen->x + screen->width) ||
|
||||
(int32_t)(destination.y + destination.height - 5) <= (int32_t)screen->y ||
|
||||
(int32_t)(destination.y + 5) >= (int32_t)(screen->y + screen->height)) {
|
||||
LOG("boundary check failed, not moving\n");
|
||||
DLOG("boundary check failed, not moving\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -459,7 +541,7 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace) {
|
|||
Client *client;
|
||||
|
||||
workspace->floating_hidden = !workspace->floating_hidden;
|
||||
LOG("floating_hidden is now: %d\n", workspace->floating_hidden);
|
||||
DLOG("floating_hidden is now: %d\n", workspace->floating_hidden);
|
||||
TAILQ_FOREACH(client, &(workspace->floating_clients), floating_clients) {
|
||||
if (workspace->floating_hidden)
|
||||
client_unmap(conn, client);
|
||||
|
|
318
src/handlers.c
318
src/handlers.c
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
|
@ -17,6 +17,7 @@
|
|||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_atom.h>
|
||||
#include <xcb/xcb_icccm.h>
|
||||
#include <xcb/randr.h>
|
||||
|
||||
#include <X11/XKBlib.h>
|
||||
|
||||
|
@ -28,7 +29,7 @@
|
|||
#include "data.h"
|
||||
#include "xcb.h"
|
||||
#include "util.h"
|
||||
#include "xinerama.h"
|
||||
#include "randr.h"
|
||||
#include "config.h"
|
||||
#include "queue.h"
|
||||
#include "resize.h"
|
||||
|
@ -36,6 +37,9 @@
|
|||
#include "manage.h"
|
||||
#include "floating.h"
|
||||
#include "workspace.h"
|
||||
#include "log.h"
|
||||
#include "container.h"
|
||||
#include "ipc.h"
|
||||
|
||||
/* After mapping/unmapping windows, a notify event is generated. However, we don’t want it,
|
||||
since it’d trigger an infinite loop of switching between the different windows when
|
||||
|
@ -78,77 +82,45 @@ static bool event_is_ignored(const int sequence) {
|
|||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Due to bindings like Mode_switch + <a>, we need to bind some keys in XCB_GRAB_MODE_SYNC.
|
||||
* Therefore, we just replay all key presses.
|
||||
*
|
||||
*/
|
||||
int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_release_event_t *event) {
|
||||
xcb_allow_events(conn, XCB_ALLOW_REPLAY_KEYBOARD, event->time);
|
||||
xcb_flush(conn);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* There was a key press. We compare this key code with our bindings table and pass
|
||||
* the bound action to parse_command().
|
||||
*
|
||||
*/
|
||||
int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) {
|
||||
LOG("Keypress %d, state raw = %d\n", event->detail, event->state);
|
||||
DLOG("Keypress %d, state raw = %d\n", event->detail, event->state);
|
||||
|
||||
/* Remove the numlock bit, all other bits are modifiers we can bind to */
|
||||
uint16_t state_filtered = event->state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK);
|
||||
LOG("(removed numlock, state = %d)\n", state_filtered);
|
||||
DLOG("(removed numlock, state = %d)\n", state_filtered);
|
||||
/* Only use the lower 8 bits of the state (modifier masks) so that mouse
|
||||
* button masks are filtered out */
|
||||
state_filtered &= 0xFF;
|
||||
LOG("(removed upper 8 bits, state = %d)\n", state_filtered);
|
||||
DLOG("(removed upper 8 bits, state = %d)\n", state_filtered);
|
||||
|
||||
/* We need to get the keysym group (There are group 1 to group 4, each holding
|
||||
two keysyms (without shift and with shift) using Xkb because X fails to
|
||||
provide them reliably (it works in Xephyr, it does not in real X) */
|
||||
XkbStateRec state;
|
||||
if (XkbGetState(xkbdpy, XkbUseCoreKbd, &state) == Success && (state.group+1) == 2)
|
||||
if (xkb_current_group == XkbGroup2Index)
|
||||
state_filtered |= BIND_MODE_SWITCH;
|
||||
|
||||
LOG("(checked mode_switch, state %d)\n", state_filtered);
|
||||
DLOG("(checked mode_switch, state %d)\n", state_filtered);
|
||||
|
||||
/* Find the binding */
|
||||
Binding *bind;
|
||||
TAILQ_FOREACH(bind, bindings, bindings) {
|
||||
/* First compare the modifiers */
|
||||
if (bind->mods != state_filtered)
|
||||
continue;
|
||||
Binding *bind = get_binding(state_filtered, event->detail);
|
||||
|
||||
/* If a symbol was specified by the user, we need to look in
|
||||
* the array of translated keycodes for the event’s keycode */
|
||||
if (bind->symbol != NULL) {
|
||||
if (memmem(bind->translated_to,
|
||||
bind->number_keycodes * sizeof(xcb_keycode_t),
|
||||
&(event->detail), sizeof(xcb_keycode_t)) != NULL)
|
||||
break;
|
||||
} else {
|
||||
/* This case is easier: The user specified a keycode */
|
||||
if (bind->keycode == event->detail)
|
||||
break;
|
||||
/* No match? Then the user has Mode_switch enabled but does not have a
|
||||
* specific keybinding. Fall back to the default keybindings (without
|
||||
* Mode_switch). Makes it much more convenient for users of a hybrid
|
||||
* layout (like us, ru). */
|
||||
if (bind == NULL) {
|
||||
state_filtered &= ~(BIND_MODE_SWITCH);
|
||||
DLOG("no match, new state_filtered = %d\n", state_filtered);
|
||||
if ((bind = get_binding(state_filtered, event->detail)) == NULL) {
|
||||
ELOG("Could not lookup key binding (modifiers %d, keycode %d)\n",
|
||||
state_filtered, event->detail);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* No match? Then it was an actively grabbed key, that is with Mode_switch, and
|
||||
the user did not press Mode_switch, so just pass it… */
|
||||
if (bind == TAILQ_END(bindings)) {
|
||||
xcb_allow_events(conn, ReplayKeyboard, event->time);
|
||||
xcb_flush(conn);
|
||||
return 1;
|
||||
}
|
||||
|
||||
parse_command(conn, bind->command);
|
||||
if (state_filtered & BIND_MODE_SWITCH) {
|
||||
LOG("Mode_switch -> allow_events(SyncKeyboard)\n");
|
||||
xcb_allow_events(conn, SyncKeyboard, event->time);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -159,21 +131,39 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_
|
|||
*
|
||||
*/
|
||||
static void check_crossing_screen_boundary(uint32_t x, uint32_t y) {
|
||||
i3Screen *screen;
|
||||
Output *output;
|
||||
|
||||
if ((screen = get_screen_containing(x, y)) == NULL) {
|
||||
LOG("ERROR: No such screen\n");
|
||||
if ((output = get_output_containing(x, y)) == NULL) {
|
||||
ELOG("ERROR: No such screen\n");
|
||||
return;
|
||||
}
|
||||
if (screen == c_ws->screen)
|
||||
if (output == c_ws->output)
|
||||
return;
|
||||
|
||||
c_ws->current_row = current_row;
|
||||
c_ws->current_col = current_col;
|
||||
c_ws = screen->current_workspace;
|
||||
c_ws = output->current_workspace;
|
||||
current_row = c_ws->current_row;
|
||||
current_col = c_ws->current_col;
|
||||
LOG("We're now on virtual screen number %d\n", screen->num);
|
||||
DLOG("We're now on output %p\n", output);
|
||||
|
||||
/* While usually this function is only called when the user switches
|
||||
* to a different output using his mouse (and thus the output is
|
||||
* empty), it may be that the following race condition occurs:
|
||||
* 1) the user actives a new output (say VGA1).
|
||||
* 2) the cursor is sent to the first pixel of the new VGA1, thus
|
||||
* generating an enter_notify for the screen (the enter_notify
|
||||
* is not yet received by i3).
|
||||
* 3) i3 requeries screen configuration and maps a workspace onto the
|
||||
* new output.
|
||||
* 4) the enter_notify event arrives and c_ws is set to the new
|
||||
* workspace but the existing windows on the new workspace are not
|
||||
* focused.
|
||||
*
|
||||
* Therefore, we re-set the focus here to be sure it’s correct. */
|
||||
Client *first_client = SLIST_FIRST(&(c_ws->focus_stack));
|
||||
if (first_client != NULL)
|
||||
set_focus(global_conn, first_client, true);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -181,9 +171,9 @@ static void check_crossing_screen_boundary(uint32_t x, uint32_t y) {
|
|||
*
|
||||
*/
|
||||
int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_event_t *event) {
|
||||
LOG("enter_notify for %08x, mode = %d, detail %d, serial %d\n", event->event, event->mode, event->detail, event->sequence);
|
||||
DLOG("enter_notify for %08x, mode = %d, detail %d, serial %d\n", event->event, event->mode, event->detail, event->sequence);
|
||||
if (event->mode != XCB_NOTIFY_MODE_NORMAL) {
|
||||
LOG("This was not a normal notify, ignoring\n");
|
||||
DLOG("This was not a normal notify, ignoring\n");
|
||||
return 1;
|
||||
}
|
||||
/* Some events are not interesting, because they were not generated actively by the
|
||||
|
@ -210,7 +200,7 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_
|
|||
|
||||
/* If not, then the user moved his cursor to the root window. In that case, we adjust c_ws */
|
||||
if (client == NULL) {
|
||||
LOG("Getting screen at %d x %d\n", event->root_x, event->root_y);
|
||||
DLOG("Getting screen at %d x %d\n", event->root_x, event->root_y);
|
||||
check_crossing_screen_boundary(event->root_x, event->root_y);
|
||||
return 1;
|
||||
}
|
||||
|
@ -220,19 +210,20 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_
|
|||
if (client->container != NULL &&
|
||||
client->container->mode == MODE_STACK &&
|
||||
client->container->currently_focused != client) {
|
||||
LOG("Plausibility check says: no\n");
|
||||
DLOG("Plausibility check says: no\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (client->workspace != c_ws && client->workspace->screen == c_ws->screen) {
|
||||
if (client->workspace != c_ws && client->workspace->output == c_ws->output) {
|
||||
/* This can happen when a client gets assigned to a different workspace than
|
||||
* the current one (see src/mainx.c:reparent_window). Shortly after it was created,
|
||||
* an enter_notify will follow. */
|
||||
LOG("enter_notify for a client on a different workspace but the same screen, ignoring\n");
|
||||
DLOG("enter_notify for a client on a different workspace but the same screen, ignoring\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
set_focus(conn, client, false);
|
||||
if (!config.disable_focus_follows_mouse)
|
||||
set_focus(conn, client, false);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
@ -264,13 +255,14 @@ int handle_mapping_notify(void *ignored, xcb_connection_t *conn, xcb_mapping_not
|
|||
event->request != XCB_MAPPING_MODIFIER)
|
||||
return 0;
|
||||
|
||||
LOG("Received mapping_notify for keyboard or modifier mapping, re-grabbing keys\n");
|
||||
DLOG("Received mapping_notify for keyboard or modifier mapping, re-grabbing keys\n");
|
||||
xcb_refresh_keyboard_mapping(keysyms, event);
|
||||
|
||||
xcb_get_numlock_mask(conn);
|
||||
|
||||
ungrab_all_keys(conn);
|
||||
grab_all_keys(conn);
|
||||
translate_keysyms();
|
||||
grab_all_keys(conn, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -284,7 +276,7 @@ int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_eve
|
|||
|
||||
cookie = xcb_get_window_attributes_unchecked(conn, event->window);
|
||||
|
||||
LOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence);
|
||||
DLOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence);
|
||||
add_ignore_event(event->sequence);
|
||||
|
||||
manage_window(prophs, conn, event->window, cookie, false);
|
||||
|
@ -298,7 +290,7 @@ int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_eve
|
|||
*
|
||||
*/
|
||||
int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure_request_event_t *event) {
|
||||
LOG("window 0x%08x wants to be at %dx%d with %dx%d\n",
|
||||
DLOG("window 0x%08x wants to be at %dx%d with %dx%d\n",
|
||||
event->window, event->x, event->y, event->width, event->height);
|
||||
|
||||
Client *client = table_get(&by_child, event->window);
|
||||
|
@ -328,7 +320,7 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
|
|||
}
|
||||
|
||||
if (client->fullscreen) {
|
||||
LOG("Client is in fullscreen mode\n");
|
||||
DLOG("Client is in fullscreen mode\n");
|
||||
|
||||
Rect child_rect = client->workspace->rect;
|
||||
child_rect.x = child_rect.y = 0;
|
||||
|
@ -389,7 +381,7 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
|
|||
}
|
||||
}
|
||||
|
||||
LOG("Accepted new position/size for floating client: (%d, %d) size %d x %d\n",
|
||||
DLOG("Accepted new position/size for floating client: (%d, %d) size %d x %d\n",
|
||||
client->rect.x, client->rect.y, client->rect.width, client->rect.height);
|
||||
|
||||
/* Push the new position/size to X11 */
|
||||
|
@ -402,22 +394,22 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
|
|||
|
||||
/* Dock clients can be reconfigured in their height */
|
||||
if (client->dock) {
|
||||
LOG("Reconfiguring height of this dock client\n");
|
||||
DLOG("Reconfiguring height of this dock client\n");
|
||||
|
||||
if (!(event->value_mask & XCB_CONFIG_WINDOW_HEIGHT)) {
|
||||
LOG("Ignoring configure request, no height given\n");
|
||||
DLOG("Ignoring configure request, no height given\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
client->desired_height = event->height;
|
||||
render_workspace(conn, c_ws->screen, c_ws);
|
||||
render_workspace(conn, c_ws->output, c_ws);
|
||||
xcb_flush(conn);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (client->fullscreen) {
|
||||
LOG("Client is in fullscreen mode\n");
|
||||
DLOG("Client is in fullscreen mode\n");
|
||||
|
||||
Rect child_rect = client->container->workspace->rect;
|
||||
child_rect.x = child_rect.y = 0;
|
||||
|
@ -432,26 +424,30 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure
|
|||
}
|
||||
|
||||
/*
|
||||
* Configuration notifies are only handled because we need to set up ignore for the following
|
||||
* enter notify events
|
||||
* Configuration notifies are only handled because we need to set up ignore for
|
||||
* the following enter notify events.
|
||||
*
|
||||
*/
|
||||
int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_notify_event_t *event) {
|
||||
xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
|
||||
|
||||
/* We ignore this sequence twice because events for child and frame should be ignored */
|
||||
add_ignore_event(event->sequence);
|
||||
add_ignore_event(event->sequence);
|
||||
|
||||
if (event->event == root) {
|
||||
LOG("event->x = %d, ->y = %d, ->width = %d, ->height = %d\n", event->x, event->y, event->width, event->height);
|
||||
LOG("reconfigure of the root window, need to xinerama\n");
|
||||
/* FIXME: Somehow, this is occuring too often. Therefore, we check for 0/0,
|
||||
but is there a better way? */
|
||||
if (event->x == 0 && event->y == 0)
|
||||
xinerama_requery_screens(conn);
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets triggered upon a RandR screen change event, that is when the user
|
||||
* changes the screen configuration in any way (mode, position, …)
|
||||
*
|
||||
*/
|
||||
int handle_screen_change(void *prophs, xcb_connection_t *conn,
|
||||
xcb_generic_event_t *e) {
|
||||
DLOG("RandR screen change\n");
|
||||
|
||||
randr_query_outputs(conn);
|
||||
|
||||
ipc_send_event("output", I3_IPC_EVENT_OUTPUT, "{\"change\":\"unspecified\"}");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
@ -474,10 +470,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
|
|||
return 1;
|
||||
}
|
||||
|
||||
LOG("event->window = %08x, event->event = %08x\n", event->window, event->event);
|
||||
LOG("UnmapNotify for 0x%08x (received from 0x%08x)\n", event->window, event->event);
|
||||
DLOG("event->window = %08x, event->event = %08x\n", event->window, event->event);
|
||||
DLOG("UnmapNotify for 0x%08x (received from 0x%08x)\n", event->window, event->event);
|
||||
if (client == NULL) {
|
||||
LOG("not a managed window. Ignoring.\n");
|
||||
DLOG("not a managed window. Ignoring.\n");
|
||||
|
||||
/* This was most likely the destroyed frame of a client which is
|
||||
* currently being unmapped, so we add this sequence (again!) to
|
||||
|
@ -490,9 +486,14 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
|
|||
|
||||
client = table_remove(&by_child, event->window);
|
||||
|
||||
/* If this was the fullscreen client, we need to unset it */
|
||||
if (client->fullscreen)
|
||||
client->workspace->fullscreen_client = NULL;
|
||||
/* If this was the fullscreen client, we need to unset it from all
|
||||
* workspaces it was on (global fullscreen) */
|
||||
if (client->fullscreen) {
|
||||
Workspace *ws;
|
||||
TAILQ_FOREACH(ws, workspaces, workspaces)
|
||||
if (ws->fullscreen_client == client)
|
||||
ws->fullscreen_client = NULL;
|
||||
}
|
||||
|
||||
/* Clients without a container are either floating or dock windows */
|
||||
if (client->container != NULL) {
|
||||
|
@ -508,17 +509,17 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
|
|||
if ((con->currently_focused != NULL) && ((con == CUR_CELL) || client->fullscreen))
|
||||
set_focus(conn, con->currently_focused, true);
|
||||
} else if (client_is_floating(client)) {
|
||||
LOG("Removing from floating clients\n");
|
||||
DLOG("Removing from floating clients\n");
|
||||
TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients);
|
||||
SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
|
||||
}
|
||||
|
||||
if (client->dock) {
|
||||
LOG("Removing from dock clients\n");
|
||||
SLIST_REMOVE(&(client->workspace->screen->dock_clients), client, Client, dock_clients);
|
||||
DLOG("Removing from dock clients\n");
|
||||
SLIST_REMOVE(&(client->workspace->output->dock_clients), client, Client, dock_clients);
|
||||
}
|
||||
|
||||
LOG("child of 0x%08x.\n", client->frame);
|
||||
DLOG("child of 0x%08x.\n", client->frame);
|
||||
xcb_reparent_window(conn, client->child, root, 0, 0);
|
||||
|
||||
client_unmap(conn, client);
|
||||
|
@ -542,8 +543,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
|
|||
if (workspace_is_visible(client->workspace))
|
||||
workspace_empty = false;
|
||||
|
||||
if (workspace_empty)
|
||||
client->workspace->screen = NULL;
|
||||
if (workspace_empty) {
|
||||
client->workspace->output = NULL;
|
||||
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}");
|
||||
}
|
||||
|
||||
/* Remove the urgency flag if set */
|
||||
client->urgent = false;
|
||||
|
@ -564,7 +567,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
|
|||
if (to_focus != NULL)
|
||||
set_focus(conn, to_focus, true);
|
||||
else {
|
||||
LOG("Restoring focus to root screen\n");
|
||||
DLOG("Restoring focus to root screen\n");
|
||||
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
@ -573,6 +576,26 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
|
|||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* A destroy notify event is sent when the window is not unmapped, but
|
||||
* immediately destroyed (for example when starting a window and immediately
|
||||
* killing the program which started it).
|
||||
*
|
||||
* We just pass on the event to the unmap notify handler (by copying the
|
||||
* important fields in the event data structure).
|
||||
*
|
||||
*/
|
||||
int handle_destroy_notify_event(void *data, xcb_connection_t *conn, xcb_destroy_notify_event_t *event) {
|
||||
DLOG("destroy notify for 0x%08x, 0x%08x\n", event->event, event->window);
|
||||
|
||||
xcb_unmap_notify_event_t unmap;
|
||||
unmap.sequence = event->sequence;
|
||||
unmap.event = event->event;
|
||||
unmap.window = event->window;
|
||||
|
||||
return handle_unmap_notify_event(NULL, conn, &unmap);
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when a window changes its title
|
||||
*
|
||||
|
@ -580,7 +603,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
|
|||
int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
|
||||
xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
|
||||
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
|
||||
LOG("_NET_WM_NAME not specified, not changing\n");
|
||||
DLOG("_NET_WM_NAME not specified, not changing\n");
|
||||
return 1;
|
||||
}
|
||||
Client *client = table_get(&by_child, window);
|
||||
|
@ -618,9 +641,11 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
|
|||
if (client->dock)
|
||||
return 1;
|
||||
|
||||
if (client->container != NULL &&
|
||||
(client->container->mode == MODE_STACK ||
|
||||
client->container->mode == MODE_TABBED))
|
||||
if (!workspace_is_visible(client->workspace))
|
||||
return 1;
|
||||
|
||||
int mode = container_mode(client->container, true);
|
||||
if (mode == MODE_STACK || mode == MODE_TABBED)
|
||||
render_container(conn, client->container);
|
||||
else decorate_window(conn, client, client->frame, client->titlegc, 0, 0);
|
||||
xcb_flush(conn);
|
||||
|
@ -642,7 +667,7 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
|
|||
int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t state,
|
||||
xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
|
||||
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
|
||||
LOG("prop == NULL\n");
|
||||
DLOG("prop == NULL\n");
|
||||
return 1;
|
||||
}
|
||||
Client *client = table_get(&by_child, window);
|
||||
|
@ -657,7 +682,7 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t
|
|||
char *new_name;
|
||||
if (asprintf(&new_name, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)) == -1) {
|
||||
perror("Could not get old name");
|
||||
LOG("Could not get old name\n");
|
||||
DLOG("Could not get old name\n");
|
||||
return 1;
|
||||
}
|
||||
/* Convert it to UCS-2 here for not having to convert it later every time we want to pass it to X */
|
||||
|
@ -685,6 +710,9 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t
|
|||
if (client->dock)
|
||||
return 1;
|
||||
|
||||
if (!workspace_is_visible(client->workspace))
|
||||
return 1;
|
||||
|
||||
if (client->container != NULL &&
|
||||
(client->container->mode == MODE_STACK ||
|
||||
client->container->mode == MODE_TABBED))
|
||||
|
@ -702,7 +730,7 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t
|
|||
int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state,
|
||||
xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
|
||||
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
|
||||
LOG("prop == NULL\n");
|
||||
DLOG("prop == NULL\n");
|
||||
return 1;
|
||||
}
|
||||
Client *client = table_get(&by_child, window);
|
||||
|
@ -736,7 +764,7 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
|
|||
skip all events but the last one */
|
||||
if (event->count != 0)
|
||||
return 1;
|
||||
LOG("window = %08x\n", event->window);
|
||||
DLOG("window = %08x\n", event->window);
|
||||
|
||||
Client *client = table_get(&by_parent, event->window);
|
||||
if (client == NULL) {
|
||||
|
@ -750,9 +778,9 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
|
|||
}
|
||||
|
||||
/* …or one of the bars? */
|
||||
i3Screen *screen;
|
||||
TAILQ_FOREACH(screen, virtual_screens, screens)
|
||||
if (screen->bar == event->window)
|
||||
Output *output;
|
||||
TAILQ_FOREACH(output, &outputs, outputs)
|
||||
if (output->bar == event->window)
|
||||
render_layout(conn);
|
||||
return 1;
|
||||
}
|
||||
|
@ -760,9 +788,7 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
|
|||
if (client->dock)
|
||||
return 1;
|
||||
|
||||
if (client->container == NULL ||
|
||||
(client->container->mode != MODE_STACK &&
|
||||
client->container->mode != MODE_TABBED))
|
||||
if (container_mode(client->container, true) == MODE_DEFAULT)
|
||||
decorate_window(conn, client, client->frame, client->titlegc, 0, 0);
|
||||
else {
|
||||
uint32_t background_color;
|
||||
|
@ -787,7 +813,7 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
|
|||
|
||||
/* Draw a black background */
|
||||
xcb_change_gc_single(conn, client->titlegc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
|
||||
if (client->titlebar_position == TITLEBAR_OFF) {
|
||||
if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) {
|
||||
xcb_rectangle_t crect = {1, 0, client->rect.width - (1 + 1), client->rect.height - 1};
|
||||
xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
|
||||
} else {
|
||||
|
@ -821,7 +847,7 @@ int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message
|
|||
event->data.data32[0] == _NET_WM_STATE_TOGGLE)))
|
||||
client_toggle_fullscreen(conn, client);
|
||||
} else {
|
||||
LOG("unhandled clientmessage\n");
|
||||
ELOG("unhandled clientmessage\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -832,7 +858,7 @@ int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_wi
|
|||
xcb_atom_t atom, xcb_get_property_reply_t *property) {
|
||||
/* TODO: Implement this one. To do this, implement a little test program which sleep(1)s
|
||||
before changing this property. */
|
||||
LOG("_NET_WM_WINDOW_TYPE changed, this is not yet implemented.\n");
|
||||
ELOG("_NET_WM_WINDOW_TYPE changed, this is not yet implemented.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -847,7 +873,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
|
|||
xcb_atom_t name, xcb_get_property_reply_t *reply) {
|
||||
Client *client = table_get(&by_child, window);
|
||||
if (client == NULL) {
|
||||
LOG("Received WM_SIZE_HINTS for unknown client\n");
|
||||
DLOG("Received WM_SIZE_HINTS for unknown client\n");
|
||||
return 1;
|
||||
}
|
||||
xcb_size_hints_t size_hints;
|
||||
|
@ -862,27 +888,24 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
|
|||
|
||||
if ((size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE)) {
|
||||
// TODO: Minimum size is not yet implemented
|
||||
//LOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height);
|
||||
DLOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height);
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
if ((size_hints.flags & XCB_SIZE_HINT_P_RESIZE_INC)) {
|
||||
bool changed = false;
|
||||
|
||||
if (size_hints.width_inc > 0)
|
||||
if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF)
|
||||
if (client->width_increment != size_hints.width_inc) {
|
||||
client->width_increment = size_hints.width_inc;
|
||||
changed = true;
|
||||
}
|
||||
if (size_hints.height_inc > 0)
|
||||
if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF)
|
||||
if (client->height_increment != size_hints.height_inc) {
|
||||
client->height_increment = size_hints.height_inc;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
resize_client(conn, client);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
if (changed)
|
||||
DLOG("resize increments changed\n");
|
||||
}
|
||||
|
||||
int base_width = 0, base_height = 0;
|
||||
|
@ -890,10 +913,11 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
|
|||
/* base_width/height are the desired size of the window.
|
||||
We check if either the program-specified size or the program-specified
|
||||
min-size is available */
|
||||
if (size_hints.flags & XCB_SIZE_HINT_P_SIZE) {
|
||||
if (size_hints.flags & XCB_SIZE_HINT_BASE_SIZE) {
|
||||
base_width = size_hints.base_width;
|
||||
base_height = size_hints.base_height;
|
||||
} else if (size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE) {
|
||||
/* TODO: is this right? icccm says not */
|
||||
base_width = size_hints.min_width;
|
||||
base_height = size_hints.min_height;
|
||||
}
|
||||
|
@ -902,11 +926,18 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
|
|||
base_height != client->base_height) {
|
||||
client->base_width = base_width;
|
||||
client->base_height = base_height;
|
||||
LOG("client's base_height changed to %d\n", base_height);
|
||||
DLOG("client's base_height changed to %d\n", base_height);
|
||||
DLOG("client's base_width changed to %d\n", base_width);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
if (client->fullscreen)
|
||||
LOG("Not resizing client, it is in fullscreen mode\n");
|
||||
else
|
||||
DLOG("Not resizing client, it is in fullscreen mode\n");
|
||||
else {
|
||||
resize_client(conn, client);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
}
|
||||
|
||||
/* If no aspect ratio was set or if it was invalid, we ignore the hints */
|
||||
|
@ -922,8 +953,8 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
|
|||
double min_aspect = (double)size_hints.min_aspect_num / size_hints.min_aspect_den;
|
||||
double max_aspect = (double)size_hints.max_aspect_num / size_hints.min_aspect_den;
|
||||
|
||||
LOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect);
|
||||
LOG("width = %f, height = %f\n", width, height);
|
||||
DLOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect);
|
||||
DLOG("width = %f, height = %f\n", width, height);
|
||||
|
||||
/* Sanity checks, this is user-input, in a way */
|
||||
if (max_aspect <= 0 || min_aspect <= 0 || height == 0 || (width / height) <= 0)
|
||||
|
@ -940,7 +971,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w
|
|||
|
||||
client->force_reconfigure = true;
|
||||
|
||||
if (client->container != NULL) {
|
||||
if (client->container != NULL && workspace_is_visible(client->workspace)) {
|
||||
render_container(conn, client->container);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
@ -956,7 +987,7 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t
|
|||
xcb_atom_t name, xcb_get_property_reply_t *reply) {
|
||||
Client *client = table_get(&by_child, window);
|
||||
if (client == NULL) {
|
||||
LOG("Received WM_HINTS for unknown client\n");
|
||||
DLOG("Received WM_HINTS for unknown client\n");
|
||||
return 1;
|
||||
}
|
||||
xcb_wm_hints_t hints;
|
||||
|
@ -971,7 +1002,7 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t
|
|||
|
||||
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
|
||||
if (!client->urgent && client == last_focused) {
|
||||
LOG("Ignoring urgency flag for current client\n");
|
||||
DLOG("Ignoring urgency flag for current client\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -981,14 +1012,15 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t
|
|||
LOG("Urgency flag changed to %d\n", client->urgent);
|
||||
|
||||
workspace_update_urgent_flag(client->workspace);
|
||||
redecorate_window(conn, client);
|
||||
|
||||
/* If the workspace this client is on is not visible, we need to redraw
|
||||
* the workspace bar */
|
||||
if (!workspace_is_visible(client->workspace)) {
|
||||
i3Screen *screen = client->workspace->screen;
|
||||
render_workspace(conn, screen, screen->current_workspace);
|
||||
Output *output = client->workspace->output;
|
||||
render_workspace(conn, output, output->current_workspace);
|
||||
xcb_flush(conn);
|
||||
} else {
|
||||
redecorate_window(conn, client);
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
@ -1005,7 +1037,7 @@ int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_
|
|||
xcb_atom_t name, xcb_get_property_reply_t *reply) {
|
||||
Client *client = table_get(&by_child, window);
|
||||
if (client == NULL) {
|
||||
LOG("No such client\n");
|
||||
DLOG("No such client\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -1021,7 +1053,7 @@ int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_
|
|||
}
|
||||
|
||||
if (client->floating == FLOATING_AUTO_OFF) {
|
||||
LOG("This is a popup window, putting into floating\n");
|
||||
DLOG("This is a popup window, putting into floating\n");
|
||||
toggle_floating_mode(conn, client, true);
|
||||
}
|
||||
|
||||
|
@ -1047,10 +1079,10 @@ int handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state
|
|||
return 1;
|
||||
|
||||
xcb_window_t *leader = xcb_get_property_value(prop);
|
||||
if (leader == NULL || *leader == 0)
|
||||
if (leader == NULL)
|
||||
return 1;
|
||||
|
||||
LOG("Client leader changed to %08x\n", *leader);
|
||||
DLOG("Client leader changed to %08x\n", *leader);
|
||||
|
||||
client->leader = *leader;
|
||||
|
||||
|
|
399
src/ipc.c
399
src/ipc.c
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
|
@ -12,6 +12,7 @@
|
|||
*/
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
@ -21,19 +22,24 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <libgen.h>
|
||||
#include <ev.h>
|
||||
#include <yajl/yajl_gen.h>
|
||||
#include <yajl/yajl_parse.h>
|
||||
|
||||
#include "queue.h"
|
||||
#include "i3/ipc.h"
|
||||
#include "ipc.h"
|
||||
#include "i3.h"
|
||||
#include "util.h"
|
||||
#include "commands.h"
|
||||
#include "log.h"
|
||||
#include "table.h"
|
||||
#include "randr.h"
|
||||
#include "config.h"
|
||||
|
||||
typedef struct ipc_client {
|
||||
int fd;
|
||||
|
||||
TAILQ_ENTRY(ipc_client) clients;
|
||||
} ipc_client;
|
||||
/* Shorter names for all those yajl_gen_* functions */
|
||||
#define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__)
|
||||
#define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str))
|
||||
|
||||
TAILQ_HEAD(ipc_client_head, ipc_client) all_clients = TAILQ_HEAD_INITIALIZER(all_clients);
|
||||
|
||||
|
@ -50,49 +56,318 @@ static void set_nonblock(int sockfd) {
|
|||
err(-1, "Could not set O_NONBLOCK");
|
||||
}
|
||||
|
||||
#if 0
|
||||
void broadcast(EV_P_ struct ev_timer *t, int revents) {
|
||||
ipc_client *current;
|
||||
TAILQ_FOREACH(current, &all_clients, clients) {
|
||||
write(current->fd, "hi there!\n", strlen("hi there!\n"));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Decides what to do with the received message.
|
||||
*
|
||||
* message is the raw packet, as received from the UNIX domain socket. size
|
||||
* is the remaining size of bytes for this packet.
|
||||
*
|
||||
* message_size is the size of the message as the sender specified it.
|
||||
* message_type is the type of the message as the sender specified it.
|
||||
* Emulates mkdir -p (creates any missing folders)
|
||||
*
|
||||
*/
|
||||
static void ipc_handle_message(uint8_t *message, int size,
|
||||
uint32_t message_size, uint32_t message_type) {
|
||||
LOG("handling message of size %d\n", size);
|
||||
LOG("sender specified size %d\n", message_size);
|
||||
LOG("sender specified type %d\n", message_type);
|
||||
LOG("payload as a string = %s\n", message);
|
||||
static bool mkdirp(const char *path) {
|
||||
if (mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == 0)
|
||||
return true;
|
||||
if (errno != ENOENT) {
|
||||
ELOG("mkdir(%s) failed: %s\n", path, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
char *copy = strdup(path);
|
||||
/* strip trailing slashes, if any */
|
||||
while (copy[strlen(copy)-1] == '/')
|
||||
copy[strlen(copy)-1] = '\0';
|
||||
|
||||
switch (message_type) {
|
||||
case I3_IPC_MESSAGE_TYPE_COMMAND: {
|
||||
/* To get a properly terminated buffer, we copy
|
||||
* message_size bytes out of the buffer */
|
||||
char *command = scalloc(message_size);
|
||||
strncpy(command, (const char*)message, message_size);
|
||||
parse_command(global_conn, (const char*)command);
|
||||
free(command);
|
||||
char *sep = strrchr(copy, '/');
|
||||
if (sep == NULL)
|
||||
return false;
|
||||
*sep = '\0';
|
||||
bool result = false;
|
||||
if (mkdirp(copy))
|
||||
result = mkdirp(path);
|
||||
free(copy);
|
||||
|
||||
break;
|
||||
return result;
|
||||
}
|
||||
|
||||
static void ipc_send_message(int fd, const unsigned char *payload,
|
||||
int message_type, int message_size) {
|
||||
int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) +
|
||||
sizeof(uint32_t) + message_size;
|
||||
char msg[buffer_size];
|
||||
char *walk = msg;
|
||||
|
||||
strcpy(walk, "i3-ipc");
|
||||
walk += strlen("i3-ipc");
|
||||
memcpy(walk, &message_size, sizeof(uint32_t));
|
||||
walk += sizeof(uint32_t);
|
||||
memcpy(walk, &message_type, sizeof(uint32_t));
|
||||
walk += sizeof(uint32_t);
|
||||
memcpy(walk, payload, message_size);
|
||||
|
||||
int sent_bytes = 0;
|
||||
int bytes_to_go = buffer_size;
|
||||
while (sent_bytes < bytes_to_go) {
|
||||
int n = write(fd, msg + sent_bytes, bytes_to_go);
|
||||
if (n == -1) {
|
||||
DLOG("write() failed: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
default:
|
||||
LOG("unhandled ipc message\n");
|
||||
break;
|
||||
|
||||
sent_bytes += n;
|
||||
bytes_to_go -= n;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sends the specified event to all IPC clients which are currently connected
|
||||
* and subscribed to this kind of event.
|
||||
*
|
||||
*/
|
||||
void ipc_send_event(const char *event, uint32_t message_type, const char *payload) {
|
||||
ipc_client *current;
|
||||
TAILQ_FOREACH(current, &all_clients, clients) {
|
||||
/* see if this client is interested in this event */
|
||||
bool interested = false;
|
||||
for (int i = 0; i < current->num_events; i++) {
|
||||
if (strcasecmp(current->events[i], event) != 0)
|
||||
continue;
|
||||
interested = true;
|
||||
break;
|
||||
}
|
||||
if (!interested)
|
||||
continue;
|
||||
|
||||
ipc_send_message(current->fd, (const unsigned char*)payload,
|
||||
message_type, strlen(payload));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Calls shutdown() on each socket and closes it. This function to be called
|
||||
* when exiting or restarting only!
|
||||
*
|
||||
*/
|
||||
void ipc_shutdown() {
|
||||
ipc_client *current;
|
||||
TAILQ_FOREACH(current, &all_clients, clients) {
|
||||
shutdown(current->fd, SHUT_RDWR);
|
||||
close(current->fd);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Executes the command and returns whether it could be successfully parsed
|
||||
* or not (at the moment, always returns true).
|
||||
*
|
||||
*/
|
||||
IPC_HANDLER(command) {
|
||||
/* To get a properly terminated buffer, we copy
|
||||
* message_size bytes out of the buffer */
|
||||
char *command = scalloc(message_size);
|
||||
strncpy(command, (const char*)message, message_size);
|
||||
parse_command(global_conn, (const char*)command);
|
||||
free(command);
|
||||
|
||||
/* For now, every command gets a positive acknowledge
|
||||
* (will change with the new command parser) */
|
||||
const char *reply = "{\"success\":true}";
|
||||
ipc_send_message(fd, (const unsigned char*)reply,
|
||||
I3_IPC_REPLY_TYPE_COMMAND, strlen(reply));
|
||||
}
|
||||
|
||||
/*
|
||||
* Formats the reply message for a GET_WORKSPACES request and sends it to the
|
||||
* client
|
||||
*
|
||||
*/
|
||||
IPC_HANDLER(get_workspaces) {
|
||||
Workspace *ws;
|
||||
|
||||
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
|
||||
if (last_focused == SLIST_END(&(c_ws->focus_stack)))
|
||||
last_focused = NULL;
|
||||
|
||||
yajl_gen gen = yajl_gen_alloc(NULL, NULL);
|
||||
y(array_open);
|
||||
|
||||
TAILQ_FOREACH(ws, workspaces, workspaces) {
|
||||
if (ws->output == NULL)
|
||||
continue;
|
||||
|
||||
y(map_open);
|
||||
ystr("num");
|
||||
y(integer, ws->num + 1);
|
||||
|
||||
ystr("name");
|
||||
ystr(ws->utf8_name);
|
||||
|
||||
ystr("visible");
|
||||
y(bool, ws->output->current_workspace == ws);
|
||||
|
||||
ystr("focused");
|
||||
y(bool, c_ws == ws);
|
||||
|
||||
ystr("rect");
|
||||
y(map_open);
|
||||
ystr("x");
|
||||
y(integer, ws->rect.x);
|
||||
ystr("y");
|
||||
y(integer, ws->rect.y);
|
||||
ystr("width");
|
||||
y(integer, ws->rect.width);
|
||||
ystr("height");
|
||||
y(integer, ws->rect.height);
|
||||
y(map_close);
|
||||
|
||||
ystr("output");
|
||||
ystr(ws->output->name);
|
||||
|
||||
ystr("urgent");
|
||||
y(bool, ws->urgent);
|
||||
|
||||
y(map_close);
|
||||
}
|
||||
|
||||
y(array_close);
|
||||
|
||||
const unsigned char *payload;
|
||||
unsigned int length;
|
||||
y(get_buf, &payload, &length);
|
||||
|
||||
ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_WORKSPACES, length);
|
||||
y(free);
|
||||
}
|
||||
|
||||
/*
|
||||
* Formats the reply message for a GET_OUTPUTS request and sends it to the
|
||||
* client
|
||||
*
|
||||
*/
|
||||
IPC_HANDLER(get_outputs) {
|
||||
Output *output;
|
||||
|
||||
yajl_gen gen = yajl_gen_alloc(NULL, NULL);
|
||||
y(array_open);
|
||||
|
||||
TAILQ_FOREACH(output, &outputs, outputs) {
|
||||
y(map_open);
|
||||
|
||||
ystr("name");
|
||||
ystr(output->name);
|
||||
|
||||
ystr("active");
|
||||
y(bool, output->active);
|
||||
|
||||
ystr("rect");
|
||||
y(map_open);
|
||||
ystr("x");
|
||||
y(integer, output->rect.x);
|
||||
ystr("y");
|
||||
y(integer, output->rect.y);
|
||||
ystr("width");
|
||||
y(integer, output->rect.width);
|
||||
ystr("height");
|
||||
y(integer, output->rect.height);
|
||||
y(map_close);
|
||||
|
||||
ystr("current_workspace");
|
||||
if (output->current_workspace == NULL)
|
||||
y(null);
|
||||
else y(integer, output->current_workspace->num + 1);
|
||||
|
||||
y(map_close);
|
||||
}
|
||||
|
||||
y(array_close);
|
||||
|
||||
const unsigned char *payload;
|
||||
unsigned int length;
|
||||
y(get_buf, &payload, &length);
|
||||
|
||||
ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_OUTPUTS, length);
|
||||
y(free);
|
||||
}
|
||||
|
||||
/*
|
||||
* Callback for the YAJL parser (will be called when a string is parsed).
|
||||
*
|
||||
*/
|
||||
static int add_subscription(void *extra, const unsigned char *s,
|
||||
unsigned int len) {
|
||||
ipc_client *client = extra;
|
||||
|
||||
DLOG("should add subscription to extra %p, sub %.*s\n", client, len, s);
|
||||
int event = client->num_events;
|
||||
|
||||
client->num_events++;
|
||||
client->events = realloc(client->events, client->num_events * sizeof(char*));
|
||||
/* We copy the string because it is not null-terminated and strndup()
|
||||
* is missing on some BSD systems */
|
||||
client->events[event] = scalloc(len+1);
|
||||
memcpy(client->events[event], s, len);
|
||||
|
||||
DLOG("client is now subscribed to:\n");
|
||||
for (int i = 0; i < client->num_events; i++)
|
||||
DLOG("event %s\n", client->events[i]);
|
||||
DLOG("(done)\n");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Subscribes this connection to the event types which were given as a JSON
|
||||
* serialized array in the payload field of the message.
|
||||
*
|
||||
*/
|
||||
IPC_HANDLER(subscribe) {
|
||||
yajl_handle p;
|
||||
yajl_callbacks callbacks;
|
||||
yajl_status stat;
|
||||
ipc_client *current, *client = NULL;
|
||||
|
||||
/* Search the ipc_client structure for this connection */
|
||||
TAILQ_FOREACH(current, &all_clients, clients) {
|
||||
if (current->fd != fd)
|
||||
continue;
|
||||
|
||||
client = current;
|
||||
break;
|
||||
}
|
||||
|
||||
if (client == NULL) {
|
||||
ELOG("Could not find ipc_client data structure for fd %d\n", fd);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Setup the JSON parser */
|
||||
memset(&callbacks, 0, sizeof(yajl_callbacks));
|
||||
callbacks.yajl_string = add_subscription;
|
||||
|
||||
p = yajl_alloc(&callbacks, NULL, NULL, (void*)client);
|
||||
stat = yajl_parse(p, (const unsigned char*)message, message_size);
|
||||
if (stat != yajl_status_ok) {
|
||||
unsigned char *err;
|
||||
err = yajl_get_error(p, true, (const unsigned char*)message,
|
||||
message_size);
|
||||
ELOG("YAJL parse error: %s\n", err);
|
||||
yajl_free_error(p, err);
|
||||
|
||||
const char *reply = "{\"success\":false}";
|
||||
ipc_send_message(fd, (const unsigned char*)reply,
|
||||
I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply));
|
||||
yajl_free(p);
|
||||
return;
|
||||
}
|
||||
yajl_free(p);
|
||||
const char *reply = "{\"success\":true}";
|
||||
ipc_send_message(fd, (const unsigned char*)reply,
|
||||
I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply));
|
||||
}
|
||||
|
||||
/* The index of each callback function corresponds to the numeric
|
||||
* value of the message type (see include/i3/ipc.h) */
|
||||
handler_t handlers[4] = {
|
||||
handle_command,
|
||||
handle_get_workspaces,
|
||||
handle_subscribe,
|
||||
handle_get_outputs
|
||||
};
|
||||
|
||||
/*
|
||||
* Handler for activity on a client connection, receives a message from a
|
||||
* client.
|
||||
|
@ -122,11 +397,13 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
|
|||
close(w->fd);
|
||||
|
||||
/* Delete the client from the list of clients */
|
||||
struct ipc_client *current;
|
||||
ipc_client *current;
|
||||
TAILQ_FOREACH(current, &all_clients, clients) {
|
||||
if (current->fd != w->fd)
|
||||
continue;
|
||||
|
||||
for (int i = 0; i < current->num_events; i++)
|
||||
free(current->events[i]);
|
||||
/* We can call TAILQ_REMOVE because we break out of the
|
||||
* TAILQ_FOREACH afterwards */
|
||||
TAILQ_REMOVE(&all_clients, current, clients);
|
||||
|
@ -135,7 +412,7 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
|
|||
|
||||
ev_io_stop(EV_A_ w);
|
||||
|
||||
LOG("IPC: client disconnected\n");
|
||||
DLOG("IPC: client disconnected\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -144,18 +421,18 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
|
|||
|
||||
/* Check if the message starts with the i3 IPC magic code */
|
||||
if (n < strlen(I3_IPC_MAGIC)) {
|
||||
LOG("IPC: message too short, ignoring\n");
|
||||
DLOG("IPC: message too short, ignoring\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (strncmp(buf, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) {
|
||||
LOG("IPC: message does not start with the IPC magic\n");
|
||||
DLOG("IPC: message does not start with the IPC magic\n");
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t *message = (uint8_t*)buf;
|
||||
while (n > 0) {
|
||||
LOG("IPC: n = %d\n", n);
|
||||
DLOG("IPC: n = %d\n", n);
|
||||
message += strlen(I3_IPC_MAGIC);
|
||||
n -= strlen(I3_IPC_MAGIC);
|
||||
|
||||
|
@ -165,7 +442,7 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
|
|||
n -= sizeof(uint32_t);
|
||||
|
||||
if (message_size > n) {
|
||||
LOG("IPC: Either the message size was wrong or the message was not read completely, dropping\n");
|
||||
DLOG("IPC: Either the message size was wrong or the message was not read completely, dropping\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -174,7 +451,12 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
|
|||
message += sizeof(uint32_t);
|
||||
n -= sizeof(uint32_t);
|
||||
|
||||
ipc_handle_message(message, n, message_size, message_type);
|
||||
if (message_type >= (sizeof(handlers) / sizeof(handler_t)))
|
||||
DLOG("Unhandled message type: %d\n", message_type);
|
||||
else {
|
||||
handler_t h = handlers[message_type];
|
||||
h(w->fd, message, n, message_size, message_type);
|
||||
}
|
||||
n -= message_size;
|
||||
message += message_size;
|
||||
}
|
||||
|
@ -200,13 +482,13 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) {
|
|||
|
||||
set_nonblock(client);
|
||||
|
||||
struct ev_io *package = calloc(sizeof(struct ev_io), 1);
|
||||
struct ev_io *package = scalloc(sizeof(struct ev_io));
|
||||
ev_io_init(package, ipc_receive_message, client, EV_READ);
|
||||
ev_io_start(EV_A_ package);
|
||||
|
||||
LOG("IPC: new client connected\n");
|
||||
DLOG("IPC: new client connected\n");
|
||||
|
||||
struct ipc_client *new = calloc(sizeof(struct ipc_client), 1);
|
||||
ipc_client *new = scalloc(sizeof(ipc_client));
|
||||
new->fd = client;
|
||||
|
||||
TAILQ_INSERT_TAIL(&all_clients, new, clients);
|
||||
|
@ -220,11 +502,20 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) {
|
|||
int ipc_create_socket(const char *filename) {
|
||||
int sockfd;
|
||||
|
||||
char *globbed = glob_path(filename);
|
||||
DLOG("Creating IPC-socket at %s\n", globbed);
|
||||
char *copy = sstrdup(globbed);
|
||||
const char *dir = dirname(copy);
|
||||
if (!path_exists(dir))
|
||||
mkdirp(dir);
|
||||
free(copy);
|
||||
|
||||
/* Unlink the unix domain socket before */
|
||||
unlink(filename);
|
||||
unlink(globbed);
|
||||
|
||||
if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) {
|
||||
perror("socket()");
|
||||
free(globbed);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -233,12 +524,14 @@ int ipc_create_socket(const char *filename) {
|
|||
struct sockaddr_un addr;
|
||||
memset(&addr, 0, sizeof(struct sockaddr_un));
|
||||
addr.sun_family = AF_LOCAL;
|
||||
strcpy(addr.sun_path, filename);
|
||||
strcpy(addr.sun_path, globbed);
|
||||
if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) {
|
||||
perror("bind()");
|
||||
free(globbed);
|
||||
return -1;
|
||||
}
|
||||
|
||||
free(globbed);
|
||||
set_nonblock(sockfd);
|
||||
|
||||
if (listen(sockfd, 5) < 0) {
|
||||
|
|
230
src/layout.c
230
src/layout.c
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
|
@ -22,23 +22,14 @@
|
|||
#include "xcb.h"
|
||||
#include "table.h"
|
||||
#include "util.h"
|
||||
#include "xinerama.h"
|
||||
#include "randr.h"
|
||||
#include "layout.h"
|
||||
#include "client.h"
|
||||
#include "floating.h"
|
||||
#include "handlers.h"
|
||||
#include "workspace.h"
|
||||
|
||||
/*
|
||||
* Updates *destination with new_value and returns true if it was changed or false
|
||||
* if it was the same
|
||||
*
|
||||
*/
|
||||
static bool update_if_necessary(uint32_t *destination, const uint32_t new_value) {
|
||||
uint32_t old_value = *destination;
|
||||
|
||||
return ((*destination = new_value) != old_value);
|
||||
}
|
||||
#include "log.h"
|
||||
#include "container.h"
|
||||
|
||||
/*
|
||||
* Gets the unoccupied space (= space which is available for windows which were resized by the user)
|
||||
|
@ -50,16 +41,16 @@ int get_unoccupied_x(Workspace *workspace) {
|
|||
double unoccupied = workspace->rect.width;
|
||||
double default_factor = ((float)workspace->rect.width / workspace->cols) / workspace->rect.width;
|
||||
|
||||
LOG("get_unoccupied_x(), starting with %f, default_factor = %f\n", unoccupied, default_factor);
|
||||
DLOG("get_unoccupied_x(), starting with %f, default_factor = %f\n", unoccupied, default_factor);
|
||||
|
||||
for (int cols = 0; cols < workspace->cols; cols++) {
|
||||
LOG("width_factor[%d] = %f, unoccupied = %f\n", cols, workspace->width_factor[cols], unoccupied);
|
||||
DLOG("width_factor[%d] = %f, unoccupied = %f\n", cols, workspace->width_factor[cols], unoccupied);
|
||||
|
||||
if (workspace->width_factor[cols] == 0)
|
||||
unoccupied -= workspace->rect.width * default_factor;
|
||||
}
|
||||
|
||||
LOG("unoccupied space: %f\n", unoccupied);
|
||||
DLOG("unoccupied space: %f\n", unoccupied);
|
||||
return unoccupied;
|
||||
}
|
||||
|
||||
|
@ -69,15 +60,15 @@ int get_unoccupied_y(Workspace *workspace) {
|
|||
double unoccupied = height;
|
||||
double default_factor = ((float)height / workspace->rows) / height;
|
||||
|
||||
LOG("get_unoccupied_y(), starting with %f, default_factor = %f\n", unoccupied, default_factor);
|
||||
DLOG("get_unoccupied_y(), starting with %f, default_factor = %f\n", unoccupied, default_factor);
|
||||
|
||||
for (int rows = 0; rows < workspace->rows; rows++) {
|
||||
LOG("height_factor[%d] = %f, unoccupied = %f\n", rows, workspace->height_factor[rows], unoccupied);
|
||||
DLOG("height_factor[%d] = %f, unoccupied = %f\n", rows, workspace->height_factor[rows], unoccupied);
|
||||
if (workspace->height_factor[rows] == 0)
|
||||
unoccupied -= height * default_factor;
|
||||
}
|
||||
|
||||
LOG("unoccupied space: %f\n", unoccupied);
|
||||
DLOG("unoccupied space: %f\n", unoccupied);
|
||||
return unoccupied;
|
||||
}
|
||||
|
||||
|
@ -140,16 +131,15 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
|
|||
- Draw two lines in a lighter color
|
||||
- Draw the window’s title
|
||||
*/
|
||||
int mode = container_mode(client->container, true);
|
||||
|
||||
/* Draw a rectangle in background color around the window */
|
||||
if (client->borderless && (client->container == NULL ||
|
||||
(client->container->mode != MODE_STACK &&
|
||||
client->container->mode != MODE_TABBED)))
|
||||
if (client->borderless && mode == MODE_DEFAULT)
|
||||
xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
|
||||
else xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, color->background);
|
||||
|
||||
/* In stacking mode, we only render the rect for this specific decoration */
|
||||
if (client->container != NULL && (client->container->mode == MODE_STACK || client->container->mode == MODE_TABBED)) {
|
||||
if (mode == MODE_STACK || mode == MODE_TABBED) {
|
||||
/* We need to use the container’s width because it is the more recent value - when
|
||||
in stacking mode, clients get reconfigured only on demand (the not active client
|
||||
is not reconfigured), so the client’s rect.width would be wrong */
|
||||
|
@ -164,7 +154,10 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
|
|||
/* Draw the inner background to have a black frame around clients (such as mplayer)
|
||||
which cannot be resized exactly in our frames and therefore are centered */
|
||||
xcb_change_gc_single(conn, client->titlegc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
|
||||
if (client->titlebar_position == TITLEBAR_OFF) {
|
||||
if (client->titlebar_position == TITLEBAR_OFF && client->borderless) {
|
||||
xcb_rectangle_t crect = {0, 0, client->rect.width, client->rect.height};
|
||||
xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
|
||||
} else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) {
|
||||
xcb_rectangle_t crect = {1, 1, client->rect.width - (1 + 1), client->rect.height - (1 + 1)};
|
||||
xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
|
||||
} else {
|
||||
|
@ -174,22 +167,21 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
|
|||
}
|
||||
}
|
||||
|
||||
mode = container_mode(client->container, false);
|
||||
|
||||
if (client->titlebar_position != TITLEBAR_OFF) {
|
||||
/* Draw the lines */
|
||||
xcb_draw_line(conn, drawable, gc, color->border, offset_x, offset_y, offset_x + client->rect.width, offset_y);
|
||||
if ((client->container == NULL ||
|
||||
(client->container->mode != MODE_STACK &&
|
||||
client->container->mode != MODE_TABBED) ||
|
||||
CIRCLEQ_NEXT_OR_NULL(&(client->container->clients), client, clients) == NULL))
|
||||
xcb_draw_line(conn, drawable, gc, color->border,
|
||||
offset_x + 2, /* x */
|
||||
offset_y + font->height + 3, /* y */
|
||||
offset_x + client->rect.width - 3, /* to_x */
|
||||
offset_y + font->height + 3 /* to_y */);
|
||||
xcb_draw_line(conn, drawable, gc, color->border,
|
||||
offset_x + 2, /* x */
|
||||
offset_y + font->height + 3, /* y */
|
||||
offset_x + client->rect.width - 3, /* to_x */
|
||||
offset_y + font->height + 3 /* to_y */);
|
||||
}
|
||||
|
||||
/* If the client has a title, we draw it */
|
||||
if (client->name != NULL && client->titlebar_position != TITLEBAR_OFF) {
|
||||
if (client->name != NULL &&
|
||||
(mode != MODE_DEFAULT || client->titlebar_position != TITLEBAR_OFF)) {
|
||||
/* Draw the font */
|
||||
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
|
||||
uint32_t values[] = { color->text, color->background, font->id };
|
||||
|
@ -212,9 +204,9 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
|
|||
*
|
||||
*/
|
||||
void reposition_client(xcb_connection_t *conn, Client *client) {
|
||||
i3Screen *screen;
|
||||
Output *output;
|
||||
|
||||
LOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y);
|
||||
DLOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y);
|
||||
/* Note: We can use a pointer to client->x like an array of uint32_ts
|
||||
because it is followed by client->y by definition */
|
||||
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x));
|
||||
|
@ -223,19 +215,25 @@ void reposition_client(xcb_connection_t *conn, Client *client) {
|
|||
return;
|
||||
|
||||
/* If the client is floating, we need to check if we moved it to a different workspace */
|
||||
screen = get_screen_containing(client->rect.x + (client->rect.width / 2),
|
||||
output = get_output_containing(client->rect.x + (client->rect.width / 2),
|
||||
client->rect.y + (client->rect.height / 2));
|
||||
if (client->workspace->screen == screen)
|
||||
if (client->workspace->output == output)
|
||||
return;
|
||||
|
||||
if (screen == NULL) {
|
||||
LOG("Boundary checking disabled, no screen found for (%d, %d)\n", client->rect.x, client->rect.y);
|
||||
if (output == NULL) {
|
||||
DLOG("Boundary checking disabled, no output found for (%d, %d)\n", client->rect.x, client->rect.y);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG("Client is on workspace %p with screen %p\n", client->workspace, client->workspace->screen);
|
||||
LOG("but screen at %d, %d is %p\n", client->rect.x, client->rect.y, screen);
|
||||
floating_assign_to_workspace(client, screen->current_workspace);
|
||||
if (output->current_workspace == NULL) {
|
||||
DLOG("Boundary checking deferred, no current workspace on output\n");
|
||||
client->force_reconfigure = true;
|
||||
return;
|
||||
}
|
||||
|
||||
DLOG("Client is on workspace %p with output %p\n", client->workspace, client->workspace->output);
|
||||
DLOG("but output at %d, %d is %p\n", client->rect.x, client->rect.y, output);
|
||||
floating_assign_to_workspace(client, output->current_workspace);
|
||||
|
||||
set_focus(conn, client, true);
|
||||
}
|
||||
|
@ -250,24 +248,15 @@ void reposition_client(xcb_connection_t *conn, Client *client) {
|
|||
void resize_client(xcb_connection_t *conn, Client *client) {
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
|
||||
LOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y);
|
||||
LOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height);
|
||||
xcb_configure_window(conn, client->frame,
|
||||
XCB_CONFIG_WINDOW_X |
|
||||
XCB_CONFIG_WINDOW_Y |
|
||||
XCB_CONFIG_WINDOW_WIDTH |
|
||||
XCB_CONFIG_WINDOW_HEIGHT,
|
||||
&(client->rect.x));
|
||||
DLOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y);
|
||||
DLOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height);
|
||||
xcb_set_window_rect(conn, client->frame, client->rect);
|
||||
|
||||
/* Adjust the position of the child inside its frame.
|
||||
* The coordinates of the child are relative to its frame, we
|
||||
* add a border of 2 pixel to each value */
|
||||
uint32_t mask = XCB_CONFIG_WINDOW_X |
|
||||
XCB_CONFIG_WINDOW_Y |
|
||||
XCB_CONFIG_WINDOW_WIDTH |
|
||||
XCB_CONFIG_WINDOW_HEIGHT;
|
||||
Rect *rect = &(client->child_rect);
|
||||
switch ((client->container != NULL ? client->container->mode : MODE_DEFAULT)) {
|
||||
switch (container_mode(client->container, true)) {
|
||||
case MODE_STACK:
|
||||
case MODE_TABBED:
|
||||
rect->x = 2;
|
||||
|
@ -301,7 +290,7 @@ void resize_client(xcb_connection_t *conn, Client *client) {
|
|||
/* Obey the ratio, if any */
|
||||
if (client->proportional_height != 0 &&
|
||||
client->proportional_width != 0) {
|
||||
LOG("proportional height = %d, width = %d\n", client->proportional_height, client->proportional_width);
|
||||
DLOG("proportional height = %d, width = %d\n", client->proportional_height, client->proportional_width);
|
||||
double new_height = rect->height + 1;
|
||||
int new_width = rect->width;
|
||||
|
||||
|
@ -317,26 +306,26 @@ void resize_client(xcb_connection_t *conn, Client *client) {
|
|||
|
||||
rect->height = new_height;
|
||||
rect->width = new_width;
|
||||
LOG("new_height = %f, new_width = %d\n", new_height, new_width);
|
||||
DLOG("new_height = %f, new_width = %d\n", new_height, new_width);
|
||||
}
|
||||
|
||||
if (client->height_increment > 1) {
|
||||
int old_height = rect->height;
|
||||
rect->height -= (rect->height - client->base_height) % client->height_increment;
|
||||
LOG("Lost %d pixel due to client's height_increment (%d px, base_height = %d)\n",
|
||||
DLOG("Lost %d pixel due to client's height_increment (%d px, base_height = %d)\n",
|
||||
old_height - rect->height, client->height_increment, client->base_height);
|
||||
}
|
||||
|
||||
if (client->width_increment > 1) {
|
||||
int old_width = rect->width;
|
||||
rect->width -= (rect->width - client->base_width) % client->width_increment;
|
||||
LOG("Lost %d pixel due to client's width_increment (%d px, base_width = %d)\n",
|
||||
DLOG("Lost %d pixel due to client's width_increment (%d px, base_width = %d)\n",
|
||||
old_width - rect->width, client->width_increment, client->base_width);
|
||||
}
|
||||
|
||||
LOG("child will be at %dx%d with size %dx%d\n", rect->x, rect->y, rect->width, rect->height);
|
||||
DLOG("child will be at %dx%d with size %dx%d\n", rect->x, rect->y, rect->width, rect->height);
|
||||
|
||||
xcb_configure_window(conn, client->child, mask, &(rect->x));
|
||||
xcb_set_window_rect(conn, client->child, *rect);
|
||||
|
||||
/* After configuring a child window we need to fake a configure_notify_event (see ICCCM 4.2.3).
|
||||
* This is necessary to inform the client of its position relative to the root window,
|
||||
|
@ -364,6 +353,10 @@ void render_container(xcb_connection_t *conn, Container *container) {
|
|||
num_clients++;
|
||||
|
||||
if (container->mode == MODE_DEFAULT) {
|
||||
int height = (container->height / max(1, num_clients));
|
||||
int rest_pixels = (container->height % max(1, num_clients));
|
||||
DLOG("height per client = %d, rest = %d\n", height, rest_pixels);
|
||||
|
||||
CIRCLEQ_FOREACH(client, &(container->clients), clients) {
|
||||
/* If the client is in fullscreen mode, it does not get reconfigured */
|
||||
if (container->workspace->fullscreen_client == client) {
|
||||
|
@ -371,6 +364,13 @@ void render_container(xcb_connection_t *conn, Container *container) {
|
|||
continue;
|
||||
}
|
||||
|
||||
/* If we have some pixels left to distribute, add one
|
||||
* pixel to each client as long as possible. */
|
||||
int this_height = height;
|
||||
if (rest_pixels > 0) {
|
||||
height++;
|
||||
rest_pixels--;
|
||||
}
|
||||
/* Check if we changed client->x or client->y by updating it.
|
||||
* Note the bitwise OR instead of logical OR to force evaluation of both statements */
|
||||
if (client->force_reconfigure |
|
||||
|
@ -378,7 +378,7 @@ void render_container(xcb_connection_t *conn, Container *container) {
|
|||
update_if_necessary(&(client->rect.y), container->y +
|
||||
(container->height / num_clients) * current_client) |
|
||||
update_if_necessary(&(client->rect.width), container->width) |
|
||||
update_if_necessary(&(client->rect.height), container->height / num_clients))
|
||||
update_if_necessary(&(client->rect.height), this_height))
|
||||
resize_client(conn, client);
|
||||
|
||||
/* TODO: vertical default layout */
|
||||
|
@ -398,16 +398,16 @@ void render_container(xcb_connection_t *conn, Container *container) {
|
|||
|
||||
/* Check if we need to remap our stack title window, it gets unmapped when the container
|
||||
is empty in src/handlers.c:unmap_notify() */
|
||||
if (stack_win->rect.height == 0 && num_clients > 0) {
|
||||
LOG("remapping stack win\n");
|
||||
if (stack_win->rect.height == 0 && num_clients > 1) {
|
||||
DLOG("remapping stack win\n");
|
||||
xcb_map_window(conn, stack_win->window);
|
||||
} else LOG("not remapping stackwin, height = %d, num_clients = %d\n",
|
||||
} else DLOG("not remapping stackwin, height = %d, num_clients = %d\n",
|
||||
stack_win->rect.height, num_clients);
|
||||
|
||||
if (container->mode == MODE_TABBED) {
|
||||
/* By setting num_clients to 1 we force that the stack window will be only one line
|
||||
* high. The rest of the code is useful in both cases. */
|
||||
LOG("tabbed mode, setting num_clients = 1\n");
|
||||
DLOG("tabbed mode, setting num_clients = 1\n");
|
||||
if (stack_lines > 1)
|
||||
stack_lines = 1;
|
||||
}
|
||||
|
@ -418,11 +418,21 @@ void render_container(xcb_connection_t *conn, Container *container) {
|
|||
stack_lines = min(num_clients, container->stack_limit_value);
|
||||
}
|
||||
|
||||
int height = decoration_height * stack_lines;
|
||||
if (num_clients == 1) {
|
||||
height = 0;
|
||||
stack_win->rect.height = 0;
|
||||
xcb_unmap_window(conn, stack_win->window);
|
||||
|
||||
DLOG("Just one client, setting height to %d\n", height);
|
||||
}
|
||||
|
||||
/* Check if we need to reconfigure our stack title window */
|
||||
if (update_if_necessary(&(stack_win->rect.x), container->x) |
|
||||
update_if_necessary(&(stack_win->rect.y), container->y) |
|
||||
update_if_necessary(&(stack_win->rect.width), container->width) |
|
||||
update_if_necessary(&(stack_win->rect.height), decoration_height * stack_lines)) {
|
||||
if (height > 0 && (
|
||||
update_if_necessary(&(stack_win->rect.x), container->x) |
|
||||
update_if_necessary(&(stack_win->rect.y), container->y) |
|
||||
update_if_necessary(&(stack_win->rect.width), container->width) |
|
||||
update_if_necessary(&(stack_win->rect.height), height))) {
|
||||
|
||||
/* Configuration can happen in two slightly different ways:
|
||||
|
||||
|
@ -456,7 +466,8 @@ void render_container(xcb_connection_t *conn, Container *container) {
|
|||
}
|
||||
|
||||
/* Prepare the pixmap for usage */
|
||||
cached_pixmap_prepare(conn, &(stack_win->pixmap));
|
||||
if (num_clients > 1)
|
||||
cached_pixmap_prepare(conn, &(stack_win->pixmap));
|
||||
|
||||
int current_row = 0, current_col = 0;
|
||||
int wrap = 0;
|
||||
|
@ -486,9 +497,9 @@ void render_container(xcb_connection_t *conn, Container *container) {
|
|||
* Note the bitwise OR instead of logical OR to force evaluation of all statements */
|
||||
if (client->force_reconfigure |
|
||||
update_if_necessary(&(client->rect.x), container->x) |
|
||||
update_if_necessary(&(client->rect.y), container->y + (decoration_height * stack_lines)) |
|
||||
update_if_necessary(&(client->rect.y), container->y + height) |
|
||||
update_if_necessary(&(client->rect.width), container->width) |
|
||||
update_if_necessary(&(client->rect.height), container->height - (decoration_height * stack_lines)))
|
||||
update_if_necessary(&(client->rect.height), container->height - height))
|
||||
resize_client(conn, client);
|
||||
|
||||
client->force_reconfigure = false;
|
||||
|
@ -520,13 +531,15 @@ void render_container(xcb_connection_t *conn, Container *container) {
|
|||
current_client++;
|
||||
} else if (container->mode == MODE_TABBED) {
|
||||
if (container->stack_limit == STACK_LIMIT_ROWS) {
|
||||
LOG("You limited this container in its rows. "
|
||||
LOG("You limited a tabbed container in its rows. "
|
||||
"This makes no sense in tabbing mode.\n");
|
||||
}
|
||||
offset_x = current_client++ * size_each;
|
||||
}
|
||||
decorate_window(conn, client, stack_win->pixmap.id, stack_win->pixmap.gc,
|
||||
offset_x, offset_y);
|
||||
if (stack_win->pixmap.id == XCB_NONE)
|
||||
continue;
|
||||
decorate_window(conn, client, stack_win->pixmap.id,
|
||||
stack_win->pixmap.gc, offset_x, offset_y);
|
||||
}
|
||||
|
||||
/* Check if we need to fill one column because of an uneven
|
||||
|
@ -553,6 +566,8 @@ void render_container(xcb_connection_t *conn, Container *container) {
|
|||
}
|
||||
}
|
||||
|
||||
if (stack_win->pixmap.id == XCB_NONE)
|
||||
return;
|
||||
xcb_copy_area(conn, stack_win->pixmap.id, stack_win->window, stack_win->pixmap.gc,
|
||||
0, 0, 0, 0, stack_win->rect.width, stack_win->rect.height);
|
||||
}
|
||||
|
@ -560,8 +575,8 @@ void render_container(xcb_connection_t *conn, Container *container) {
|
|||
|
||||
static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int *height) {
|
||||
Client *client;
|
||||
SLIST_FOREACH(client, &(r_ws->screen->dock_clients), dock_clients) {
|
||||
LOG("client is at %d, should be at %d\n", client->rect.y, *height);
|
||||
SLIST_FOREACH(client, &(r_ws->output->dock_clients), dock_clients) {
|
||||
DLOG("client is at %d, should be at %d\n", client->rect.y, *height);
|
||||
if (client->force_reconfigure |
|
||||
update_if_necessary(&(client->rect.x), r_ws->rect.x) |
|
||||
update_if_necessary(&(client->rect.y), *height))
|
||||
|
@ -573,55 +588,55 @@ static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int
|
|||
resize_client(conn, client);
|
||||
|
||||
client->force_reconfigure = false;
|
||||
LOG("desired_height = %d\n", client->desired_height);
|
||||
DLOG("desired_height = %d\n", client->desired_height);
|
||||
*height += client->desired_height;
|
||||
}
|
||||
}
|
||||
|
||||
static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int width, int height) {
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
i3Screen *screen = r_ws->screen;
|
||||
Output *output = r_ws->output;
|
||||
enum { SET_NORMAL = 0, SET_FOCUSED = 1 };
|
||||
|
||||
/* Fill the whole bar in black */
|
||||
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
|
||||
xcb_change_gc_single(conn, output->bargc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
|
||||
xcb_rectangle_t rect = {0, 0, width, height};
|
||||
xcb_poly_fill_rectangle(conn, screen->bar, screen->bargc, 1, &rect);
|
||||
xcb_poly_fill_rectangle(conn, output->bar, output->bargc, 1, &rect);
|
||||
|
||||
/* Set font */
|
||||
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FONT, font->id);
|
||||
xcb_change_gc_single(conn, output->bargc, XCB_GC_FONT, font->id);
|
||||
|
||||
int drawn = 0;
|
||||
Workspace *ws;
|
||||
TAILQ_FOREACH(ws, workspaces, workspaces) {
|
||||
if (ws->screen != screen)
|
||||
if (ws->output != output)
|
||||
continue;
|
||||
|
||||
struct Colortriple *color;
|
||||
|
||||
if (screen->current_workspace == ws)
|
||||
if (output->current_workspace == ws)
|
||||
color = &(config.bar.focused);
|
||||
else if (ws->urgent)
|
||||
color = &(config.bar.urgent);
|
||||
else color = &(config.bar.unfocused);
|
||||
|
||||
/* Draw the outer rect */
|
||||
xcb_draw_rect(conn, screen->bar, screen->bargc, color->border,
|
||||
xcb_draw_rect(conn, output->bar, output->bargc, color->border,
|
||||
drawn, /* x */
|
||||
1, /* y */
|
||||
ws->text_width + 5 + 5, /* width = text width + 5 px left + 5px right */
|
||||
height - 2 /* height = max. height - 1 px upper and 1 px bottom border */);
|
||||
|
||||
/* Draw the background of this rect */
|
||||
xcb_draw_rect(conn, screen->bar, screen->bargc, color->background,
|
||||
xcb_draw_rect(conn, output->bar, output->bargc, color->background,
|
||||
drawn + 1,
|
||||
2,
|
||||
ws->text_width + 4 + 4,
|
||||
height - 4);
|
||||
|
||||
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, color->text);
|
||||
xcb_change_gc_single(conn, screen->bargc, XCB_GC_BACKGROUND, color->background);
|
||||
xcb_image_text_16(conn, ws->name_len, screen->bar, screen->bargc, drawn + 5 /* X */,
|
||||
xcb_change_gc_single(conn, output->bargc, XCB_GC_FOREGROUND, color->text);
|
||||
xcb_change_gc_single(conn, output->bargc, XCB_GC_BACKGROUND, color->background);
|
||||
xcb_image_text_16(conn, ws->name_len, output->bar, output->bargc, drawn + 5 /* X */,
|
||||
font->height + 1 /* Y = baseline of font */,
|
||||
(xcb_char2b_t*)ws->name);
|
||||
drawn += ws->text_width + 12;
|
||||
|
@ -662,18 +677,19 @@ void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace, bo
|
|||
* Renders the given workspace on the given screen
|
||||
*
|
||||
*/
|
||||
void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws) {
|
||||
void render_workspace(xcb_connection_t *conn, Output *output, Workspace *r_ws) {
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
int width = r_ws->rect.width;
|
||||
int height = r_ws->rect.height;
|
||||
|
||||
/* Reserve space for dock clients */
|
||||
Client *client;
|
||||
SLIST_FOREACH(client, &(screen->dock_clients), dock_clients)
|
||||
SLIST_FOREACH(client, &(output->dock_clients), dock_clients)
|
||||
height -= client->desired_height;
|
||||
|
||||
/* Space for the internal bar */
|
||||
height -= (font->height + 6);
|
||||
if (!config.disable_workspace_bar)
|
||||
height -= (font->height + 6);
|
||||
|
||||
int xoffset[r_ws->rows];
|
||||
int yoffset[r_ws->cols];
|
||||
|
@ -707,7 +723,7 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws)
|
|||
single_width = container->width;
|
||||
}
|
||||
|
||||
LOG("height is %d\n", height);
|
||||
DLOG("height is %d\n", height);
|
||||
|
||||
container->height = 0;
|
||||
|
||||
|
@ -727,10 +743,21 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws)
|
|||
yoffset[cols] += single_height;
|
||||
}
|
||||
|
||||
/* Reposition all floating clients with force_reconfigure == true */
|
||||
TAILQ_FOREACH(client, &(r_ws->floating_clients), floating_clients) {
|
||||
if (!client->force_reconfigure)
|
||||
continue;
|
||||
|
||||
client->force_reconfigure = false;
|
||||
reposition_client(conn, client);
|
||||
resize_client(conn, client);
|
||||
}
|
||||
|
||||
ignore_enter_notify_forall(conn, r_ws, false);
|
||||
|
||||
render_bars(conn, r_ws, width, &height);
|
||||
render_internal_bar(conn, r_ws, width, font->height + 6);
|
||||
if (!config.disable_workspace_bar)
|
||||
render_internal_bar(conn, r_ws, width, font->height + 6);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -742,14 +769,11 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws)
|
|||
*
|
||||
*/
|
||||
void render_layout(xcb_connection_t *conn) {
|
||||
i3Screen *screen;
|
||||
Output *output;
|
||||
|
||||
if (virtual_screens == NULL)
|
||||
return;
|
||||
|
||||
TAILQ_FOREACH(screen, virtual_screens, screens)
|
||||
if (screen->current_workspace != NULL)
|
||||
render_workspace(conn, screen, screen->current_workspace);
|
||||
TAILQ_FOREACH(output, &outputs, outputs)
|
||||
if (output->current_workspace != NULL)
|
||||
render_workspace(conn, output, output->current_workspace);
|
||||
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* vim:ts=8:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
* src/log.c: handles the setting of loglevels, contains the logging functions.
|
||||
*
|
||||
*/
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "util.h"
|
||||
#include "log.h"
|
||||
|
||||
/* loglevels.h is autogenerated at make time */
|
||||
#include "loglevels.h"
|
||||
|
||||
static uint32_t loglevel = 0;
|
||||
static bool verbose = false;
|
||||
|
||||
/**
|
||||
* Set verbosity of i3. If verbose is set to true, informative messages will
|
||||
* be printed to stdout. If verbose is set to false, only errors will be
|
||||
* printed.
|
||||
*
|
||||
*/
|
||||
void set_verbosity(bool _verbose) {
|
||||
verbose = _verbose;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the given loglevel.
|
||||
*
|
||||
*/
|
||||
void add_loglevel(const char *level) {
|
||||
/* Handle the special loglevel "all" */
|
||||
if (strcasecmp(level, "all") == 0) {
|
||||
loglevel = UINT32_MAX;
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < sizeof(loglevels) / sizeof(char*); i++) {
|
||||
if (strcasecmp(loglevels[i], level) != 0)
|
||||
continue;
|
||||
|
||||
/* The position in the array (plus one) is the amount of times
|
||||
* which we need to shift 1 to the left to get our bitmask for
|
||||
* the specific loglevel. */
|
||||
loglevel |= (1 << (i+1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Logs the given message to stdout while prefixing the current time to it.
|
||||
* This is to be called by *LOG() which includes filename/linenumber/function.
|
||||
*
|
||||
*/
|
||||
void vlog(char *fmt, va_list args) {
|
||||
char timebuf[64];
|
||||
|
||||
/* Get current time */
|
||||
time_t t = time(NULL);
|
||||
/* Convert time to local time (determined by the locale) */
|
||||
struct tm *tmp = localtime(&t);
|
||||
/* Generate time prefix */
|
||||
strftime(timebuf, sizeof(timebuf), "%x %X - ", tmp);
|
||||
printf("%s", timebuf);
|
||||
vprintf(fmt, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the given message to stdout while prefixing the current time to it,
|
||||
* but only if verbose mode is activated.
|
||||
*
|
||||
*/
|
||||
void verboselog(char *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
if (!verbose)
|
||||
return;
|
||||
|
||||
va_start(args, fmt);
|
||||
vlog(fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the given message to stdout while prefixing the current time to it.
|
||||
*
|
||||
*/
|
||||
void errorlog(char *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
vlog(fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
/*
|
||||
* Logs the given message to stdout while prefixing the current time to it,
|
||||
* but only if the corresponding debug loglevel was activated.
|
||||
* This is to be called by DLOG() which includes filename/linenumber
|
||||
*
|
||||
*/
|
||||
void debuglog(int lev, char *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
if ((loglevel & lev) == 0)
|
||||
return;
|
||||
|
||||
va_start(args, fmt);
|
||||
vlog(fmt, args);
|
||||
va_end(args);
|
||||
}
|
241
src/mainx.c
241
src/mainx.c
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
|
@ -30,7 +30,6 @@
|
|||
#include <xcb/xcb_property.h>
|
||||
#include <xcb/xcb_keysyms.h>
|
||||
#include <xcb/xcb_icccm.h>
|
||||
#include <xcb/xinerama.h>
|
||||
|
||||
#include <ev.h>
|
||||
|
||||
|
@ -45,9 +44,16 @@
|
|||
#include "table.h"
|
||||
#include "util.h"
|
||||
#include "xcb.h"
|
||||
#include "randr.h"
|
||||
#include "xinerama.h"
|
||||
#include "manage.h"
|
||||
#include "ipc.h"
|
||||
#include "log.h"
|
||||
#include "sighandler.h"
|
||||
|
||||
static int xkb_event_base;
|
||||
|
||||
int xkb_current_group;
|
||||
|
||||
xcb_connection_t *global_conn;
|
||||
|
||||
|
@ -82,6 +88,9 @@ int num_screens = 0;
|
|||
/* The depth of the root screen (used e.g. for creating new pixmaps later) */
|
||||
uint8_t root_depth;
|
||||
|
||||
/* We hope that XKB is supported and set this to false */
|
||||
bool xkb_supported = true;
|
||||
|
||||
/*
|
||||
* This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb.
|
||||
* See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop
|
||||
|
@ -119,25 +128,65 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
|
|||
*
|
||||
*/
|
||||
static void xkb_got_event(EV_P_ struct ev_io *w, int revents) {
|
||||
LOG("got xkb event, yay\n");
|
||||
XEvent ev;
|
||||
DLOG("Handling XKB event\n");
|
||||
XkbEvent ev;
|
||||
|
||||
/* When using xmodmap, every change (!) gets an own event.
|
||||
* Therefore, we just read all events and only handle the
|
||||
* mapping_notify once (we do not receive any other XKB
|
||||
* events anyway). */
|
||||
while (XPending(xkbdpy))
|
||||
XNextEvent(xkbdpy, &ev);
|
||||
* mapping_notify once. */
|
||||
bool mapping_changed = false;
|
||||
while (XPending(xkbdpy)) {
|
||||
XNextEvent(xkbdpy, (XEvent*)&ev);
|
||||
/* While we should never receive a non-XKB event,
|
||||
* better do sanity checking */
|
||||
if (ev.type != xkb_event_base)
|
||||
continue;
|
||||
|
||||
if (ev.any.xkb_type == XkbMapNotify) {
|
||||
mapping_changed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ev.any.xkb_type != XkbStateNotify) {
|
||||
ELOG("Unknown XKB event received (type %d)\n", ev.any.xkb_type);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* See The XKB Extension: Library Specification, section 14.1 */
|
||||
/* We check if the current group (each group contains
|
||||
* two levels) has been changed. Mode_switch activates
|
||||
* group XkbGroup2Index */
|
||||
if (xkb_current_group == ev.state.group)
|
||||
continue;
|
||||
|
||||
xkb_current_group = ev.state.group;
|
||||
|
||||
if (ev.state.group == XkbGroup2Index) {
|
||||
DLOG("Mode_switch enabled\n");
|
||||
grab_all_keys(global_conn, true);
|
||||
}
|
||||
|
||||
if (ev.state.group == XkbGroup1Index) {
|
||||
DLOG("Mode_switch disabled\n");
|
||||
ungrab_all_keys(global_conn);
|
||||
grab_all_keys(global_conn, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!mapping_changed)
|
||||
return;
|
||||
|
||||
DLOG("Keyboard mapping changed, updating keybindings\n");
|
||||
xcb_key_symbols_free(keysyms);
|
||||
keysyms = xcb_key_symbols_alloc(global_conn);
|
||||
|
||||
xcb_get_numlock_mask(global_conn);
|
||||
|
||||
ungrab_all_keys(global_conn);
|
||||
LOG("Re-grabbing...\n");
|
||||
grab_all_keys(global_conn);
|
||||
LOG("Done\n");
|
||||
|
||||
DLOG("Re-grabbing...\n");
|
||||
translate_keysyms();
|
||||
grab_all_keys(global_conn, (xkb_current_group == XkbGroup2Index));
|
||||
DLOG("Done\n");
|
||||
}
|
||||
|
||||
|
||||
|
@ -145,6 +194,8 @@ int main(int argc, char *argv[], char *env[]) {
|
|||
int i, screens, opt;
|
||||
char *override_configpath = NULL;
|
||||
bool autostart = true;
|
||||
bool only_check_config = false;
|
||||
bool force_xinerama = false;
|
||||
xcb_connection_t *conn;
|
||||
xcb_property_handlers_t prophs;
|
||||
xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS];
|
||||
|
@ -153,6 +204,7 @@ int main(int argc, char *argv[], char *env[]) {
|
|||
{"config", required_argument, 0, 'c'},
|
||||
{"version", no_argument, 0, 'v'},
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{"force-xinerama", no_argument, 0, 0},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
int option_index = 0;
|
||||
|
@ -165,7 +217,7 @@ int main(int argc, char *argv[], char *env[]) {
|
|||
|
||||
start_argv = argv;
|
||||
|
||||
while ((opt = getopt_long(argc, argv, "c:vahl", long_options, &option_index)) != -1) {
|
||||
while ((opt = getopt_long(argc, argv, "c:Cvahld:V", long_options, &option_index)) != -1) {
|
||||
switch (opt) {
|
||||
case 'a':
|
||||
LOG("Autostart disabled using -a\n");
|
||||
|
@ -174,18 +226,46 @@ int main(int argc, char *argv[], char *env[]) {
|
|||
case 'c':
|
||||
override_configpath = sstrdup(optarg);
|
||||
break;
|
||||
case 'v':
|
||||
printf("i3 version " I3_VERSION " © 2009 Michael Stapelberg and contributors\n");
|
||||
exit(EXIT_SUCCESS);
|
||||
case 'l':
|
||||
config_use_lexer = true;
|
||||
case 'C':
|
||||
LOG("Checking configuration file only (-C)\n");
|
||||
only_check_config = true;
|
||||
break;
|
||||
case 'v':
|
||||
printf("i3 version " I3_VERSION " © 2009-2010 Michael Stapelberg and contributors\n");
|
||||
exit(EXIT_SUCCESS);
|
||||
case 'V':
|
||||
set_verbosity(true);
|
||||
break;
|
||||
case 'd':
|
||||
LOG("Enabling debug loglevel %s\n", optarg);
|
||||
add_loglevel(optarg);
|
||||
break;
|
||||
case 'l':
|
||||
/* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */
|
||||
break;
|
||||
case 0:
|
||||
if (strcmp(long_options[option_index].name, "force-xinerama") == 0) {
|
||||
force_xinerama = true;
|
||||
ELOG("Using Xinerama instead of RandR. This option should be "
|
||||
"avoided at all cost because it does not refresh the list "
|
||||
"of screens, so you cannot configure displays at runtime. "
|
||||
"Please check if your driver really does not support RandR "
|
||||
"and disable this option as soon as you can.\n");
|
||||
break;
|
||||
}
|
||||
/* fall-through */
|
||||
default:
|
||||
fprintf(stderr, "Usage: %s [-c configfile] [-a] [-v]\n", argv[0]);
|
||||
fprintf(stderr, "Usage: %s [-c configfile] [-d loglevel] [-a] [-v] [-V] [-C]\n", argv[0]);
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "-a: disable autostart\n");
|
||||
fprintf(stderr, "-v: display version and exit\n");
|
||||
fprintf(stderr, "-V: enable verbose mode\n");
|
||||
fprintf(stderr, "-d <loglevel>: enable debug loglevel <loglevel>\n");
|
||||
fprintf(stderr, "-c <configfile>: use the provided configfile instead\n");
|
||||
fprintf(stderr, "-C: check configuration file and exit\n");
|
||||
fprintf(stderr, "--force-xinerama: Use Xinerama instead of RandR. This "
|
||||
"option should only be used if you are stuck with the "
|
||||
"nvidia closed source driver which does not support RandR.\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
@ -204,6 +284,10 @@ int main(int argc, char *argv[], char *env[]) {
|
|||
die("Cannot open display\n");
|
||||
|
||||
load_configuration(conn, override_configpath, false);
|
||||
if (only_check_config) {
|
||||
LOG("Done checking configuration file. Exiting.\n");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/* Create the initial container on the first workspace. This used to
|
||||
* be part of init_table, but since it possibly requires an X
|
||||
|
@ -234,6 +318,9 @@ int main(int argc, char *argv[], char *env[]) {
|
|||
REQUEST_ATOM(UTF8_STRING);
|
||||
REQUEST_ATOM(WM_STATE);
|
||||
REQUEST_ATOM(WM_CLIENT_LEADER);
|
||||
REQUEST_ATOM(_NET_CURRENT_DESKTOP);
|
||||
REQUEST_ATOM(_NET_ACTIVE_WINDOW);
|
||||
REQUEST_ATOM(_NET_WORKAREA);
|
||||
|
||||
/* TODO: this has to be more beautiful somewhen */
|
||||
int major, minor, error;
|
||||
|
@ -241,28 +328,32 @@ int main(int argc, char *argv[], char *env[]) {
|
|||
major = XkbMajorVersion;
|
||||
minor = XkbMinorVersion;
|
||||
|
||||
int evBase, errBase;
|
||||
int errBase;
|
||||
|
||||
if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) {
|
||||
fprintf(stderr, "XkbOpenDisplay() failed\n");
|
||||
return 1;
|
||||
if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &xkb_event_base, &errBase, &major, &minor, &error)) == NULL) {
|
||||
ELOG("ERROR: XkbOpenDisplay() failed, disabling XKB support\n");
|
||||
xkb_supported = false;
|
||||
}
|
||||
|
||||
if (fcntl(ConnectionNumber(xkbdpy), F_SETFD, FD_CLOEXEC) == -1) {
|
||||
fprintf(stderr, "Could not set FD_CLOEXEC on xkbdpy\n");
|
||||
return 1;
|
||||
}
|
||||
if (xkb_supported) {
|
||||
if (fcntl(ConnectionNumber(xkbdpy), F_SETFD, FD_CLOEXEC) == -1) {
|
||||
fprintf(stderr, "Could not set FD_CLOEXEC on xkbdpy\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int i1;
|
||||
if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) {
|
||||
fprintf(stderr, "XKB not supported by X-server\n");
|
||||
return 1;
|
||||
}
|
||||
/* end of ugliness */
|
||||
int i1;
|
||||
if (!XkbQueryExtension(xkbdpy,&i1,&xkb_event_base,&errBase,&major,&minor)) {
|
||||
fprintf(stderr, "XKB not supported by X-server\n");
|
||||
return 1;
|
||||
}
|
||||
/* end of ugliness */
|
||||
|
||||
if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd, XkbMapNotifyMask, XkbMapNotifyMask)) {
|
||||
fprintf(stderr, "Could not set XKB event mask\n");
|
||||
return 1;
|
||||
if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd,
|
||||
XkbMapNotifyMask | XkbStateNotifyMask,
|
||||
XkbMapNotifyMask | XkbStateNotifyMask)) {
|
||||
fprintf(stderr, "Could not set XKB event mask\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialize event loop using libev */
|
||||
|
@ -278,11 +369,13 @@ int main(int argc, char *argv[], char *env[]) {
|
|||
ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
|
||||
ev_io_start(loop, xcb_watcher);
|
||||
|
||||
ev_io_init(xkb, xkb_got_event, ConnectionNumber(xkbdpy), EV_READ);
|
||||
ev_io_start(loop, xkb);
|
||||
if (xkb_supported) {
|
||||
ev_io_init(xkb, xkb_got_event, ConnectionNumber(xkbdpy), EV_READ);
|
||||
ev_io_start(loop, xkb);
|
||||
|
||||
/* Flush the buffer so that libev can properly get new events */
|
||||
XFlush(xkbdpy);
|
||||
/* Flush the buffer so that libev can properly get new events */
|
||||
XFlush(xkbdpy);
|
||||
}
|
||||
|
||||
ev_check_init(xcb_check, xcb_check_cb);
|
||||
ev_check_start(loop, xcb_check);
|
||||
|
@ -305,9 +398,8 @@ int main(int argc, char *argv[], char *env[]) {
|
|||
/* Expose = an Application should redraw itself, in this case it’s our titlebars. */
|
||||
xcb_event_set_expose_handler(&evenths, handle_expose_event, NULL);
|
||||
|
||||
/* Key presses/releases are pretty obvious, I think */
|
||||
/* Key presses are pretty obvious, I think */
|
||||
xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL);
|
||||
xcb_event_set_key_release_handler(&evenths, handle_key_release, NULL);
|
||||
|
||||
/* Enter window = user moved his mouse over the window */
|
||||
xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, NULL);
|
||||
|
@ -322,6 +414,9 @@ int main(int argc, char *argv[], char *env[]) {
|
|||
it any longer. Usually, the client destroys the window shortly afterwards. */
|
||||
xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, NULL);
|
||||
|
||||
/* Destroy notify is handled the same as unmap notify */
|
||||
xcb_event_set_destroy_notify_handler(&evenths, handle_destroy_notify_event, NULL);
|
||||
|
||||
/* Configure notify = window’s configuration (geometry, stacking, …). We only need
|
||||
it to set up ignore the following enter_notify events */
|
||||
xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, NULL);
|
||||
|
@ -359,13 +454,15 @@ int main(int argc, char *argv[], char *env[]) {
|
|||
XCB_EVENT_MASK_POINTER_MOTION |
|
||||
XCB_EVENT_MASK_PROPERTY_CHANGE |
|
||||
XCB_EVENT_MASK_ENTER_WINDOW };
|
||||
xcb_change_window_attributes(conn, root, mask, values);
|
||||
xcb_void_cookie_t cookie;
|
||||
cookie = xcb_change_window_attributes_checked(conn, root, mask, values);
|
||||
check_error(conn, cookie, "Another window manager seems to be running");
|
||||
|
||||
/* Setup NetWM atoms */
|
||||
#define GET_ATOM(name) { \
|
||||
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, atom_cookies[name], NULL); \
|
||||
if (!reply) { \
|
||||
LOG("Could not get atom " #name "\n"); \
|
||||
ELOG("Could not get atom " #name "\n"); \
|
||||
exit(-1); \
|
||||
} \
|
||||
atoms[name] = reply->atom; \
|
||||
|
@ -390,6 +487,9 @@ int main(int argc, char *argv[], char *env[]) {
|
|||
GET_ATOM(UTF8_STRING);
|
||||
GET_ATOM(WM_STATE);
|
||||
GET_ATOM(WM_CLIENT_LEADER);
|
||||
GET_ATOM(_NET_CURRENT_DESKTOP);
|
||||
GET_ATOM(_NET_ACTIVE_WINDOW);
|
||||
GET_ATOM(_NET_WORKAREA);
|
||||
|
||||
xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, handle_window_type, NULL);
|
||||
/* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */
|
||||
|
@ -423,38 +523,39 @@ int main(int argc, char *argv[], char *env[]) {
|
|||
|
||||
xcb_get_numlock_mask(conn);
|
||||
|
||||
grab_all_keys(conn);
|
||||
translate_keysyms();
|
||||
grab_all_keys(conn, false);
|
||||
|
||||
/* Autostarting exec-lines */
|
||||
struct Autostart *exec;
|
||||
if (autostart) {
|
||||
TAILQ_FOREACH(exec, &autostarts, autostarts) {
|
||||
LOG("auto-starting %s\n", exec->command);
|
||||
start_application(exec->command);
|
||||
}
|
||||
int randr_base;
|
||||
if (force_xinerama) {
|
||||
initialize_xinerama(conn);
|
||||
} else {
|
||||
DLOG("Checking for XRandR...\n");
|
||||
initialize_randr(conn, &randr_base);
|
||||
|
||||
xcb_event_set_handler(&evenths,
|
||||
randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY,
|
||||
handle_screen_change,
|
||||
NULL);
|
||||
}
|
||||
|
||||
/* check for Xinerama */
|
||||
LOG("Checking for Xinerama...\n");
|
||||
initialize_xinerama(conn);
|
||||
|
||||
xcb_flush(conn);
|
||||
|
||||
/* Get pointer position to see on which screen we’re starting */
|
||||
xcb_query_pointer_reply_t *reply;
|
||||
if ((reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL)) == NULL) {
|
||||
LOG("Could not get pointer position\n");
|
||||
ELOG("Could not get pointer position\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y);
|
||||
Output *screen = get_output_containing(reply->root_x, reply->root_y);
|
||||
if (screen == NULL) {
|
||||
LOG("ERROR: No screen at %d x %d, starting on the first screen\n",
|
||||
ELOG("ERROR: No screen at %d x %d, starting on the first screen\n",
|
||||
reply->root_x, reply->root_y);
|
||||
screen = TAILQ_FIRST(virtual_screens);
|
||||
screen = get_first_output();
|
||||
}
|
||||
|
||||
LOG("Starting on %d\n", screen->current_workspace);
|
||||
DLOG("Starting on %p\n", screen->current_workspace);
|
||||
c_ws = screen->current_workspace;
|
||||
|
||||
manage_existing_windows(conn, &prophs, root);
|
||||
|
@ -463,7 +564,7 @@ int main(int argc, char *argv[], char *env[]) {
|
|||
if (config.ipc_socket_path != NULL) {
|
||||
int ipc_socket = ipc_create_socket(config.ipc_socket_path);
|
||||
if (ipc_socket == -1) {
|
||||
LOG("Could not create the IPC socket, IPC disabled\n");
|
||||
ELOG("Could not create the IPC socket, IPC disabled\n");
|
||||
} else {
|
||||
struct ev_io *ipc_io = scalloc(sizeof(struct ev_io));
|
||||
ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ);
|
||||
|
@ -474,8 +575,24 @@ int main(int argc, char *argv[], char *env[]) {
|
|||
/* Handle the events which arrived until now */
|
||||
xcb_check_cb(NULL, NULL, 0);
|
||||
|
||||
setup_signal_handler();
|
||||
|
||||
/* Ignore SIGPIPE to survive errors when an IPC client disconnects
|
||||
* while we are sending him a message */
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
/* Ungrab the server to receive events and enter libev’s eventloop */
|
||||
xcb_ungrab_server(conn);
|
||||
|
||||
/* Autostarting exec-lines */
|
||||
struct Autostart *exec;
|
||||
if (autostart) {
|
||||
TAILQ_FOREACH(exec, &autostarts, autostarts) {
|
||||
LOG("auto-starting %s\n", exec->command);
|
||||
start_application(exec->command);
|
||||
}
|
||||
}
|
||||
|
||||
ev_loop(loop, 0);
|
||||
|
||||
/* not reached */
|
||||
|
|
103
src/manage.c
103
src/manage.c
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
|
@ -30,6 +30,8 @@
|
|||
#include "floating.h"
|
||||
#include "client.h"
|
||||
#include "workspace.h"
|
||||
#include "log.h"
|
||||
#include "ewmh.h"
|
||||
|
||||
/*
|
||||
* Go through all existing windows (if the window manager is restarted) and manage them
|
||||
|
@ -61,6 +63,28 @@ void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *pr
|
|||
free(cookies);
|
||||
}
|
||||
|
||||
/*
|
||||
* Restores the geometry of each window by reparenting it to the root window
|
||||
* at the position of its frame.
|
||||
*
|
||||
* This is to be called *only* before exiting/restarting i3 because of evil
|
||||
* side-effects which are to be expected when continuing to run i3.
|
||||
*
|
||||
*/
|
||||
void restore_geometry(xcb_connection_t *conn) {
|
||||
Workspace *ws;
|
||||
Client *client;
|
||||
DLOG("Restoring geometry\n");
|
||||
|
||||
TAILQ_FOREACH(ws, workspaces, workspaces)
|
||||
SLIST_FOREACH(client, &(ws->focus_stack), focus_clients)
|
||||
xcb_reparent_window(conn, client->child, root,
|
||||
client->rect.x, client->rect.y);
|
||||
|
||||
/* Make sure our changes reach the X server, we restart/exit now */
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Do some sanity checks and then reparent the window.
|
||||
*
|
||||
|
@ -78,7 +102,7 @@ void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn,
|
|||
/* Check if the window is mapped (it could be not mapped when intializing and
|
||||
calling manage_window() for every window) */
|
||||
if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) {
|
||||
LOG("Could not get attributes\n");
|
||||
ELOG("Could not get attributes\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -156,9 +180,9 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
|
|||
/* Events for already managed windows should already be filtered in manage_window() */
|
||||
assert(new == NULL);
|
||||
|
||||
LOG("Reparenting window 0x%08x\n", child);
|
||||
LOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
|
||||
new = calloc(sizeof(Client), 1);
|
||||
LOG("Managing window 0x%08x\n", child);
|
||||
DLOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
|
||||
new = scalloc(sizeof(Client));
|
||||
new->force_reconfigure = true;
|
||||
|
||||
/* Update the data structures */
|
||||
|
@ -220,7 +244,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
|
|||
new->awaiting_useless_unmap = true;
|
||||
xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
|
||||
if (xcb_request_check(conn, cookie) != NULL) {
|
||||
LOG("Could not reparent the window, aborting\n");
|
||||
DLOG("Could not reparent the window, aborting\n");
|
||||
xcb_destroy_window(conn, new->frame);
|
||||
free(new);
|
||||
return;
|
||||
|
@ -247,13 +271,19 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
|
|||
if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
|
||||
for (int i = 0; i < xcb_get_property_value_length(preply); i++)
|
||||
if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
|
||||
LOG("Window is a dock.\n");
|
||||
DLOG("Window is a dock.\n");
|
||||
Output *t_out = get_output_containing(x, y);
|
||||
if (t_out != c_ws->output) {
|
||||
DLOG("Dock client requested to be on output %s by geometry (%d, %d)\n",
|
||||
t_out->name, x, y);
|
||||
new->workspace = t_out->current_workspace;
|
||||
}
|
||||
new->dock = true;
|
||||
new->borderless = true;
|
||||
new->titlebar_position = TITLEBAR_OFF;
|
||||
new->force_reconfigure = true;
|
||||
new->container = NULL;
|
||||
SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
|
||||
SLIST_INSERT_HEAD(&(t_out->dock_clients), new, dock_clients);
|
||||
/* If it’s a dock we can’t make it float, so we break */
|
||||
new->floating = FLOATING_AUTO_OFF;
|
||||
break;
|
||||
|
@ -263,19 +293,19 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
|
|||
atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
|
||||
/* Set the dialog window to automatically floating, will be used below */
|
||||
new->floating = FLOATING_AUTO_ON;
|
||||
LOG("dialog/utility/toolbar/splash window, automatically floating\n");
|
||||
DLOG("dialog/utility/toolbar/splash window, automatically floating\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* All clients which have a leader should be floating */
|
||||
if (!new->dock && !client_is_floating(new) && new->leader != 0) {
|
||||
LOG("Client has WM_CLIENT_LEADER hint set, setting floating\n");
|
||||
DLOG("Client has WM_CLIENT_LEADER hint set, setting floating\n");
|
||||
new->floating = FLOATING_AUTO_ON;
|
||||
}
|
||||
|
||||
if (new->workspace->auto_float) {
|
||||
new->floating = FLOATING_AUTO_ON;
|
||||
LOG("workspace is in autofloat mode, setting floating\n");
|
||||
DLOG("workspace is in autofloat mode, setting floating\n");
|
||||
}
|
||||
|
||||
if (new->dock) {
|
||||
|
@ -289,12 +319,12 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
|
|||
TODO: bars at the top */
|
||||
new->desired_height = strut[3];
|
||||
if (new->desired_height == 0) {
|
||||
LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
|
||||
DLOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
|
||||
new->desired_height = original_height;
|
||||
}
|
||||
LOG("the client wants to be %d pixels high\n", new->desired_height);
|
||||
DLOG("the client wants to be %d pixels high\n", new->desired_height);
|
||||
} else {
|
||||
LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
|
||||
DLOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
|
||||
new->desired_height = original_height;
|
||||
}
|
||||
} else {
|
||||
|
@ -316,6 +346,29 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
|
|||
preply = xcb_get_property_reply(conn, leader_cookie, NULL);
|
||||
handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply);
|
||||
|
||||
/* if WM_CLIENT_LEADER is set, we put the new window on the
|
||||
* same window as its leader. This might be overwritten by
|
||||
* assignments afterwards. */
|
||||
if (new->leader != XCB_NONE) {
|
||||
DLOG("client->leader is set (to 0x%08x)\n", new->leader);
|
||||
Client *parent = table_get(&by_child, new->leader);
|
||||
if (parent != NULL && parent->container != NULL) {
|
||||
Workspace *t_ws = parent->workspace;
|
||||
new->container = t_ws->table[parent->container->col][parent->container->row];
|
||||
new->workspace = t_ws;
|
||||
old_focused = new->container->currently_focused;
|
||||
map_frame = workspace_is_visible(t_ws);
|
||||
new->urgent = true;
|
||||
/* This is a little tricky: we cannot use
|
||||
* workspace_update_urgent_flag() because the
|
||||
* new window was not yet inserted into the
|
||||
* focus stack on t_ws. */
|
||||
t_ws->urgent = true;
|
||||
} else {
|
||||
DLOG("parent is not usable\n");
|
||||
}
|
||||
}
|
||||
|
||||
struct Assignment *assign;
|
||||
TAILQ_FOREACH(assign, &assignments, assignments) {
|
||||
if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
|
||||
|
@ -332,14 +385,14 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
|
|||
LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
|
||||
assign->windowclass_title, assign->workspace);
|
||||
|
||||
if (c_ws->screen->current_workspace->num == (assign->workspace-1)) {
|
||||
LOG("We are already there, no need to do anything\n");
|
||||
if (c_ws->output->current_workspace->num == (assign->workspace-1)) {
|
||||
DLOG("We are already there, no need to do anything\n");
|
||||
break;
|
||||
}
|
||||
|
||||
LOG("Changing container/workspace and unmapping the client\n");
|
||||
DLOG("Changing container/workspace and unmapping the client\n");
|
||||
Workspace *t_ws = workspace_get(assign->workspace-1);
|
||||
workspace_initialize(t_ws, c_ws->screen);
|
||||
workspace_initialize(t_ws, c_ws->output, false);
|
||||
|
||||
new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
|
||||
new->workspace = t_ws;
|
||||
|
@ -351,7 +404,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
|
|||
}
|
||||
|
||||
if (new->workspace->fullscreen_client != NULL) {
|
||||
LOG("Setting below fullscreen window\n");
|
||||
DLOG("Setting below fullscreen window\n");
|
||||
|
||||
/* If we are in fullscreen, we should place the window below
|
||||
* the fullscreen window to not be annoying */
|
||||
|
@ -394,10 +447,10 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
|
|||
* to (0, 0), so we push them to a reasonable position
|
||||
* (centered over their leader) */
|
||||
if (new->leader != 0 && x == 0 && y == 0) {
|
||||
LOG("Floating client wants to (0x0), moving it over its leader instead\n");
|
||||
DLOG("Floating client wants to (0x0), moving it over its leader instead\n");
|
||||
Client *leader = table_get(&by_child, new->leader);
|
||||
if (leader == NULL) {
|
||||
LOG("leader is NULL, centering it over current workspace\n");
|
||||
DLOG("leader is NULL, centering it over current workspace\n");
|
||||
|
||||
x = c_ws->rect.x + (c_ws->rect.width / 2) - (new->rect.width / 2);
|
||||
y = c_ws->rect.y + (c_ws->rect.height / 2) - (new->rect.height / 2);
|
||||
|
@ -408,10 +461,10 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
|
|||
}
|
||||
new->floating_rect.x = new->rect.x = x;
|
||||
new->floating_rect.y = new->rect.y = y;
|
||||
LOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
|
||||
DLOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
|
||||
new->floating_rect.x, new->floating_rect.y,
|
||||
new->floating_rect.width, new->floating_rect.height);
|
||||
LOG("outer rect (%d, %d) size (%d, %d)\n",
|
||||
DLOG("outer rect (%d, %d) size (%d, %d)\n",
|
||||
new->rect.x, new->rect.y, new->rect.width, new->rect.height);
|
||||
|
||||
/* Make sure it is on top of the other windows */
|
||||
|
@ -455,8 +508,10 @@ map:
|
|||
if (map_frame)
|
||||
render_container(conn, new->container);
|
||||
}
|
||||
if (new->container == CUR_CELL || client_is_floating(new))
|
||||
if (new->container == CUR_CELL || client_is_floating(new)) {
|
||||
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
|
||||
ewmh_update_active_window(new->child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,532 @@
|
|||
/*
|
||||
* vim:ts=8:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
* For more information on RandR, please see the X.org RandR specification at
|
||||
* http://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt
|
||||
* (take your time to read it completely, it answers all questions).
|
||||
*
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <assert.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/randr.h>
|
||||
|
||||
#include "queue.h"
|
||||
#include "i3.h"
|
||||
#include "data.h"
|
||||
#include "table.h"
|
||||
#include "util.h"
|
||||
#include "layout.h"
|
||||
#include "xcb.h"
|
||||
#include "config.h"
|
||||
#include "workspace.h"
|
||||
#include "log.h"
|
||||
#include "ewmh.h"
|
||||
#include "ipc.h"
|
||||
#include "client.h"
|
||||
|
||||
/* While a clean namespace is usually a pretty good thing, we really need
|
||||
* to use shorter names than the whole xcb_randr_* default names. */
|
||||
typedef xcb_randr_get_crtc_info_reply_t crtc_info;
|
||||
typedef xcb_randr_mode_info_t mode_info;
|
||||
typedef xcb_randr_get_screen_resources_current_reply_t resources_reply;
|
||||
|
||||
/* Stores all outputs available in your current session. */
|
||||
struct outputs_head outputs = TAILQ_HEAD_INITIALIZER(outputs);
|
||||
|
||||
static bool randr_disabled = false;
|
||||
|
||||
/*
|
||||
* Get a specific output by its internal X11 id. Used by randr_query_outputs
|
||||
* to check if the output is new (only in the first scan) or if we are
|
||||
* re-scanning.
|
||||
*
|
||||
*/
|
||||
static Output *get_output_by_id(xcb_randr_output_t id) {
|
||||
Output *output;
|
||||
TAILQ_FOREACH(output, &outputs, outputs)
|
||||
if (output->id == id)
|
||||
return output;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the output with the given name if it is active (!) or NULL.
|
||||
*
|
||||
*/
|
||||
Output *get_output_by_name(const char *name) {
|
||||
Output *output;
|
||||
TAILQ_FOREACH(output, &outputs, outputs)
|
||||
if (output->active &&
|
||||
strcasecmp(output->name, name) == 0)
|
||||
return output;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the first output which is active.
|
||||
*
|
||||
*/
|
||||
Output *get_first_output() {
|
||||
Output *output;
|
||||
|
||||
TAILQ_FOREACH(output, &outputs, outputs)
|
||||
if (output->active)
|
||||
return output;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the active (!) output which contains the coordinates x, y or NULL
|
||||
* if there is no output which contains these coordinates.
|
||||
*
|
||||
*/
|
||||
Output *get_output_containing(int x, int y) {
|
||||
Output *output;
|
||||
TAILQ_FOREACH(output, &outputs, outputs) {
|
||||
if (!output->active)
|
||||
continue;
|
||||
DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n",
|
||||
x, y, output->rect.x, output->rect.y, output->rect.width, output->rect.height);
|
||||
if (x >= output->rect.x && x < (output->rect.x + output->rect.width) &&
|
||||
y >= output->rect.y && y < (output->rect.y + output->rect.height))
|
||||
return output;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets the output which is the last one in the given direction, for example
|
||||
* the output on the most bottom when direction == D_DOWN, the output most
|
||||
* right when direction == D_RIGHT and so on.
|
||||
*
|
||||
* This function always returns a output.
|
||||
*
|
||||
*/
|
||||
Output *get_output_most(direction_t direction, Output *current) {
|
||||
Output *output, *candidate = NULL;
|
||||
int position = 0;
|
||||
TAILQ_FOREACH(output, &outputs, outputs) {
|
||||
if (!output->active)
|
||||
continue;
|
||||
|
||||
/* Repeated calls of WIN determine the winner of the comparison */
|
||||
#define WIN(variable, condition) \
|
||||
if (variable condition) { \
|
||||
candidate = output; \
|
||||
position = variable; \
|
||||
} \
|
||||
break;
|
||||
|
||||
if (((direction == D_UP) || (direction == D_DOWN)) &&
|
||||
(current->rect.x != output->rect.x))
|
||||
continue;
|
||||
|
||||
if (((direction == D_LEFT) || (direction == D_RIGHT)) &&
|
||||
(current->rect.y != output->rect.y))
|
||||
continue;
|
||||
|
||||
switch (direction) {
|
||||
case D_UP:
|
||||
WIN(output->rect.y, <= position);
|
||||
case D_DOWN:
|
||||
WIN(output->rect.y, >= position);
|
||||
case D_LEFT:
|
||||
WIN(output->rect.x, <= position);
|
||||
case D_RIGHT:
|
||||
WIN(output->rect.x, >= position);
|
||||
}
|
||||
}
|
||||
|
||||
assert(candidate != NULL);
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initializes the specified output, assigning the specified workspace to it.
|
||||
*
|
||||
*/
|
||||
void initialize_output(xcb_connection_t *conn, Output *output, Workspace *workspace) {
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
|
||||
workspace->output = output;
|
||||
output->current_workspace = workspace;
|
||||
|
||||
/* Copy rect for the workspace */
|
||||
memcpy(&(workspace->rect), &(output->rect), sizeof(Rect));
|
||||
|
||||
/* Map clients on the workspace, if any */
|
||||
workspace_map_clients(conn, workspace);
|
||||
|
||||
/* Create a bar window on each output */
|
||||
if (!config.disable_workspace_bar) {
|
||||
Rect bar_rect = {output->rect.x,
|
||||
output->rect.y + output->rect.height - (font->height + 6),
|
||||
output->rect.x + output->rect.width,
|
||||
font->height + 6};
|
||||
uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
|
||||
uint32_t values[] = {1, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS};
|
||||
output->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, true, mask, values);
|
||||
output->bargc = xcb_generate_id(conn);
|
||||
xcb_create_gc(conn, output->bargc, output->bar, 0, 0);
|
||||
}
|
||||
|
||||
SLIST_INIT(&(output->dock_clients));
|
||||
|
||||
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}");
|
||||
DLOG("initialized output at (%d, %d) with %d x %d\n",
|
||||
output->rect.x, output->rect.y, output->rect.width, output->rect.height);
|
||||
|
||||
DLOG("assigning configured workspaces to this output...\n");
|
||||
Workspace *ws;
|
||||
TAILQ_FOREACH(ws, workspaces, workspaces) {
|
||||
if (ws == workspace)
|
||||
continue;
|
||||
if (ws->preferred_output == NULL ||
|
||||
get_output_by_name(ws->preferred_output) != output)
|
||||
continue;
|
||||
|
||||
DLOG("assigning ws %d\n", ws->num + 1);
|
||||
workspace_assign_to(ws, output, true);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Disables RandR support by creating exactly one output with the size of the
|
||||
* X11 screen.
|
||||
*
|
||||
*/
|
||||
void disable_randr(xcb_connection_t *conn) {
|
||||
xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
|
||||
|
||||
DLOG("RandR extension unusable, disabling.\n");
|
||||
|
||||
Output *s = scalloc(sizeof(Output));
|
||||
|
||||
s->active = true;
|
||||
s->rect.x = 0;
|
||||
s->rect.y = 0;
|
||||
s->rect.width = root_screen->width_in_pixels;
|
||||
s->rect.height = root_screen->height_in_pixels;
|
||||
s->name = "xroot-0";
|
||||
|
||||
TAILQ_INSERT_TAIL(&outputs, s, outputs);
|
||||
|
||||
randr_disabled = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function needs to be called when changing the mode of an output when
|
||||
* it already has some workspaces (or a bar window) assigned.
|
||||
*
|
||||
* It reconfigures the bar window for the new mode, copies the new rect into
|
||||
* each workspace on this output and forces all windows on the affected
|
||||
* workspaces to be reconfigured.
|
||||
*
|
||||
* It is necessary to call render_layout() afterwards.
|
||||
*
|
||||
*/
|
||||
static void output_change_mode(xcb_connection_t *conn, Output *output) {
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
Workspace *ws;
|
||||
Client *client;
|
||||
|
||||
DLOG("Output mode changed, reconfiguring bar, updating workspaces\n");
|
||||
Rect bar_rect = {output->rect.x,
|
||||
output->rect.y + output->rect.height - (font->height + 6),
|
||||
output->rect.x + output->rect.width,
|
||||
font->height + 6};
|
||||
|
||||
xcb_set_window_rect(conn, output->bar, bar_rect);
|
||||
|
||||
/* go through all workspaces and set force_reconfigure */
|
||||
TAILQ_FOREACH(ws, workspaces, workspaces) {
|
||||
if (ws->output != output)
|
||||
continue;
|
||||
|
||||
SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) {
|
||||
client->force_reconfigure = true;
|
||||
if (!client_is_floating(client))
|
||||
continue;
|
||||
/* For floating clients we need to translate the
|
||||
* coordinates (old workspace to new workspace) */
|
||||
DLOG("old: (%x, %x)\n", client->rect.x, client->rect.y);
|
||||
client->rect.x -= ws->rect.x;
|
||||
client->rect.y -= ws->rect.y;
|
||||
client->rect.x += ws->output->rect.x;
|
||||
client->rect.y += ws->output->rect.y;
|
||||
DLOG("new: (%x, %x)\n", client->rect.x, client->rect.y);
|
||||
}
|
||||
|
||||
/* Update dimensions from output */
|
||||
memcpy(&(ws->rect), &(ws->output->rect), sizeof(Rect));
|
||||
|
||||
/* Update the dimensions of a fullscreen client, if any */
|
||||
if (ws->fullscreen_client != NULL) {
|
||||
DLOG("Updating fullscreen client size\n");
|
||||
client = ws->fullscreen_client;
|
||||
Rect r = ws->rect;
|
||||
xcb_set_window_rect(conn, client->frame, r);
|
||||
|
||||
r.x = 0;
|
||||
r.y = 0;
|
||||
xcb_set_window_rect(conn, client->child, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets called by randr_query_outputs() for each output. The function adds new
|
||||
* outputs to the list of outputs, checks if the mode of existing outputs has
|
||||
* been changed or if an existing output has been disabled. It will then change
|
||||
* either the "changed" or the "to_be_deleted" flag of the output, if
|
||||
* appropriate.
|
||||
*
|
||||
*/
|
||||
static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id,
|
||||
xcb_randr_get_output_info_reply_t *output,
|
||||
xcb_timestamp_t cts, resources_reply *res) {
|
||||
/* each CRT controller has a position in which we are interested in */
|
||||
crtc_info *crtc;
|
||||
|
||||
Output *new = get_output_by_id(id);
|
||||
bool existing = (new != NULL);
|
||||
if (!existing)
|
||||
new = scalloc(sizeof(Output));
|
||||
new->id = id;
|
||||
FREE(new->name);
|
||||
asprintf(&new->name, "%.*s",
|
||||
xcb_randr_get_output_info_name_length(output),
|
||||
xcb_randr_get_output_info_name(output));
|
||||
|
||||
DLOG("found output with name %s\n", new->name);
|
||||
|
||||
/* Even if no CRTC is used at the moment, we store the output so that
|
||||
* we do not need to change the list ever again (we only update the
|
||||
* position/size) */
|
||||
if (output->crtc == XCB_NONE) {
|
||||
if (!existing)
|
||||
TAILQ_INSERT_TAIL(&outputs, new, outputs);
|
||||
else if (new->active)
|
||||
new->to_be_disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
xcb_randr_get_crtc_info_cookie_t icookie;
|
||||
icookie = xcb_randr_get_crtc_info(conn, output->crtc, cts);
|
||||
if ((crtc = xcb_randr_get_crtc_info_reply(conn, icookie, NULL)) == NULL) {
|
||||
DLOG("Skipping output %s: could not get CRTC (%p)\n",
|
||||
new->name, crtc);
|
||||
free(new);
|
||||
return;
|
||||
}
|
||||
|
||||
bool updated = update_if_necessary(&(new->rect.x), crtc->x) |
|
||||
update_if_necessary(&(new->rect.y), crtc->y) |
|
||||
update_if_necessary(&(new->rect.width), crtc->width) |
|
||||
update_if_necessary(&(new->rect.height), crtc->height);
|
||||
free(crtc);
|
||||
new->active = (new->rect.width != 0 && new->rect.height != 0);
|
||||
if (!new->active) {
|
||||
DLOG("width/height 0/0, disabling output\n");
|
||||
return;
|
||||
}
|
||||
|
||||
DLOG("mode: %dx%d+%d+%d\n", new->rect.width, new->rect.height,
|
||||
new->rect.x, new->rect.y);
|
||||
|
||||
/* If we don’t need to change an existing output or if the output
|
||||
* does not exist in the first place, the case is simple: we either
|
||||
* need to insert the new output or we are done. */
|
||||
if (!updated || !existing) {
|
||||
if (!existing)
|
||||
TAILQ_INSERT_TAIL(&outputs, new, outputs);
|
||||
return;
|
||||
}
|
||||
|
||||
new->changed = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* (Re-)queries the outputs via RandR and stores them in the list of outputs.
|
||||
*
|
||||
*/
|
||||
void randr_query_outputs(xcb_connection_t *conn) {
|
||||
Workspace *ws;
|
||||
Output *output, *other, *first;
|
||||
xcb_randr_get_screen_resources_current_cookie_t rcookie;
|
||||
resources_reply *res;
|
||||
/* timestamp of the configuration so that we get consistent replies to all
|
||||
* requests (if the configuration changes between our different calls) */
|
||||
xcb_timestamp_t cts;
|
||||
|
||||
/* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */
|
||||
xcb_randr_output_t *randr_outputs;
|
||||
|
||||
if (randr_disabled)
|
||||
return;
|
||||
|
||||
/* Get screen resources (crtcs, outputs, modes) */
|
||||
rcookie = xcb_randr_get_screen_resources_current(conn, root);
|
||||
if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) {
|
||||
disable_randr(conn);
|
||||
return;
|
||||
}
|
||||
cts = res->config_timestamp;
|
||||
|
||||
int len = xcb_randr_get_screen_resources_current_outputs_length(res);
|
||||
randr_outputs = xcb_randr_get_screen_resources_current_outputs(res);
|
||||
|
||||
/* Request information for each output */
|
||||
xcb_randr_get_output_info_cookie_t ocookie[len];
|
||||
for (int i = 0; i < len; i++)
|
||||
ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts);
|
||||
|
||||
/* Loop through all outputs available for this X11 screen */
|
||||
for (int i = 0; i < len; i++) {
|
||||
xcb_randr_get_output_info_reply_t *output;
|
||||
|
||||
if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL)
|
||||
continue;
|
||||
|
||||
handle_output(conn, randr_outputs[i], output, cts, res);
|
||||
free(output);
|
||||
}
|
||||
|
||||
free(res);
|
||||
/* Check for clones, disable the clones and reduce the mode to the
|
||||
* lowest common mode */
|
||||
TAILQ_FOREACH(output, &outputs, outputs) {
|
||||
if (!output->active || output->to_be_disabled)
|
||||
continue;
|
||||
DLOG("output %p, position (%d, %d), checking for clones\n",
|
||||
output, output->rect.x, output->rect.y);
|
||||
|
||||
for (other = output;
|
||||
other != TAILQ_END(&outputs);
|
||||
other = TAILQ_NEXT(other, outputs)) {
|
||||
if (other == output || !other->active || other->to_be_disabled)
|
||||
continue;
|
||||
|
||||
if (other->rect.x != output->rect.x ||
|
||||
other->rect.y != output->rect.y)
|
||||
continue;
|
||||
|
||||
DLOG("output %p has the same position, his mode = %d x %d\n",
|
||||
other, other->rect.width, other->rect.height);
|
||||
uint32_t width = min(other->rect.width, output->rect.width);
|
||||
uint32_t height = min(other->rect.height, output->rect.height);
|
||||
|
||||
if (update_if_necessary(&(output->rect.width), width) |
|
||||
update_if_necessary(&(output->rect.height), height))
|
||||
output->changed = true;
|
||||
|
||||
update_if_necessary(&(other->rect.width), width);
|
||||
update_if_necessary(&(other->rect.height), height);
|
||||
|
||||
DLOG("disabling output %p (%s)\n", other, other->name);
|
||||
other->to_be_disabled = true;
|
||||
|
||||
DLOG("new output mode %d x %d, other mode %d x %d\n",
|
||||
output->rect.width, output->rect.height,
|
||||
other->rect.width, other->rect.height);
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle outputs which have a new mode or are disabled now (either
|
||||
* because the user disabled them or because they are clones) */
|
||||
TAILQ_FOREACH(output, &outputs, outputs) {
|
||||
if (output->to_be_disabled) {
|
||||
output->active = false;
|
||||
DLOG("Output %s disabled, re-assigning workspaces/docks\n", output->name);
|
||||
|
||||
if ((first = get_first_output()) == NULL)
|
||||
die("No usable outputs available\n");
|
||||
|
||||
bool needs_init = (first->current_workspace == NULL);
|
||||
|
||||
TAILQ_FOREACH(ws, workspaces, workspaces) {
|
||||
if (ws->output != output)
|
||||
continue;
|
||||
|
||||
workspace_assign_to(ws, first, true);
|
||||
if (!needs_init)
|
||||
continue;
|
||||
initialize_output(conn, first, ws);
|
||||
needs_init = false;
|
||||
}
|
||||
|
||||
Client *dock;
|
||||
while (!SLIST_EMPTY(&(output->dock_clients))) {
|
||||
dock = SLIST_FIRST(&(output->dock_clients));
|
||||
SLIST_REMOVE_HEAD(&(output->dock_clients), dock_clients);
|
||||
SLIST_INSERT_HEAD(&(first->dock_clients), dock, dock_clients);
|
||||
}
|
||||
output->current_workspace = NULL;
|
||||
output->to_be_disabled = false;
|
||||
} else if (output->changed) {
|
||||
output_change_mode(conn, output);
|
||||
output->changed = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (TAILQ_EMPTY(&outputs)) {
|
||||
ELOG("No outputs found via RandR, disabling\n");
|
||||
disable_randr(conn);
|
||||
}
|
||||
|
||||
ewmh_update_workarea();
|
||||
|
||||
/* Just go through each active output and associate one workspace */
|
||||
TAILQ_FOREACH(output, &outputs, outputs) {
|
||||
if (!output->active || output->current_workspace != NULL)
|
||||
continue;
|
||||
ws = get_first_workspace_for_output(output);
|
||||
initialize_output(conn, output, ws);
|
||||
}
|
||||
|
||||
/* render_layout flushes */
|
||||
render_layout(conn);
|
||||
}
|
||||
|
||||
/*
|
||||
* We have just established a connection to the X server and need the initial
|
||||
* XRandR information to setup workspaces for each screen.
|
||||
*
|
||||
*/
|
||||
void initialize_randr(xcb_connection_t *conn, int *event_base) {
|
||||
const xcb_query_extension_reply_t *extreply;
|
||||
|
||||
extreply = xcb_get_extension_data(conn, &xcb_randr_id);
|
||||
if (!extreply->present)
|
||||
disable_randr(conn);
|
||||
else randr_query_outputs(conn);
|
||||
|
||||
if (event_base != NULL)
|
||||
*event_base = extreply->first_event;
|
||||
|
||||
xcb_randr_select_input(conn, root,
|
||||
XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE |
|
||||
XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE |
|
||||
XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE |
|
||||
XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY);
|
||||
|
||||
xcb_flush(conn);
|
||||
}
|
175
src/resize.c
175
src/resize.c
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
|
@ -24,10 +24,49 @@
|
|||
#include "xcb.h"
|
||||
#include "debug.h"
|
||||
#include "layout.h"
|
||||
#include "xinerama.h"
|
||||
#include "randr.h"
|
||||
#include "config.h"
|
||||
#include "floating.h"
|
||||
#include "workspace.h"
|
||||
#include "log.h"
|
||||
|
||||
/*
|
||||
* This is an ugly data structure which we need because there is no standard
|
||||
* way of having nested functions (only available as a gcc extension at the
|
||||
* moment, clang doesn’t support it) or blocks (only available as a clang
|
||||
* extension and only on Mac OS X systems at the moment).
|
||||
*
|
||||
*/
|
||||
struct callback_params {
|
||||
resize_orientation_t orientation;
|
||||
Output *screen;
|
||||
xcb_window_t helpwin;
|
||||
uint32_t *new_position;
|
||||
};
|
||||
|
||||
DRAGGING_CB(resize_callback) {
|
||||
struct callback_params *params = extra;
|
||||
Output *screen = params->screen;
|
||||
DLOG("new x = %d, y = %d\n", new_x, new_y);
|
||||
if (params->orientation == O_VERTICAL) {
|
||||
/* Check if the new coordinates are within screen boundaries */
|
||||
if (new_x > (screen->rect.x + screen->rect.width - 25) ||
|
||||
new_x < (screen->rect.x + 25))
|
||||
return;
|
||||
|
||||
*(params->new_position) = new_x;
|
||||
xcb_configure_window(conn, params->helpwin, XCB_CONFIG_WINDOW_X, params->new_position);
|
||||
} else {
|
||||
if (new_y > (screen->rect.y + screen->rect.height - 25) ||
|
||||
new_y < (screen->rect.y + 25))
|
||||
return;
|
||||
|
||||
*(params->new_position) = new_y;
|
||||
xcb_configure_window(conn, params->helpwin, XCB_CONFIG_WINDOW_Y, params->new_position);
|
||||
}
|
||||
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Renders the resize window between the first/second container and resizes
|
||||
|
@ -36,10 +75,10 @@
|
|||
*/
|
||||
int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, int second,
|
||||
resize_orientation_t orientation, xcb_button_press_event_t *event) {
|
||||
int new_position;
|
||||
i3Screen *screen = get_screen_containing(event->root_x, event->root_y);
|
||||
uint32_t new_position;
|
||||
Output *screen = get_output_containing(event->root_x, event->root_y);
|
||||
if (screen == NULL) {
|
||||
LOG("BUG: No screen found at this position (%d, %d)\n", event->root_x, event->root_y);
|
||||
ELOG("BUG: No screen found at this position (%d, %d)\n", event->root_x, event->root_y);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -48,12 +87,12 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i
|
|||
* screens during runtime. Instead, we just use the most right and most
|
||||
* bottom Xinerama screen and use their position + width/height to get
|
||||
* the area of pixels currently in use */
|
||||
i3Screen *most_right = get_screen_most(D_RIGHT, screen),
|
||||
*most_bottom = get_screen_most(D_DOWN, screen);
|
||||
Output *most_right = get_output_most(D_RIGHT, screen),
|
||||
*most_bottom = get_output_most(D_DOWN, screen);
|
||||
|
||||
LOG("event->event_x = %d, event->root_x = %d\n", event->event_x, event->root_x);
|
||||
DLOG("event->event_x = %d, event->root_x = %d\n", event->event_x, event->root_x);
|
||||
|
||||
LOG("Screen dimensions: (%d, %d) %d x %d\n", screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
|
||||
DLOG("Screen dimensions: (%d, %d) %d x %d\n", screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
|
||||
|
||||
uint32_t mask = 0;
|
||||
uint32_t values[2];
|
||||
|
@ -92,36 +131,16 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i
|
|||
|
||||
xcb_window_t helpwin = create_window(conn, helprect, XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
||||
(orientation == O_VERTICAL ?
|
||||
XCB_CURSOR_SB_V_DOUBLE_ARROW :
|
||||
XCB_CURSOR_SB_H_DOUBLE_ARROW), true, mask, values);
|
||||
XCB_CURSOR_SB_H_DOUBLE_ARROW :
|
||||
XCB_CURSOR_SB_V_DOUBLE_ARROW), true, mask, values);
|
||||
|
||||
xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin);
|
||||
|
||||
xcb_flush(conn);
|
||||
|
||||
void resize_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) {
|
||||
LOG("new x = %d, y = %d\n", new_x, new_y);
|
||||
if (orientation == O_VERTICAL) {
|
||||
/* Check if the new coordinates are within screen boundaries */
|
||||
if (new_x > (screen->rect.x + screen->rect.width - 25) ||
|
||||
new_x < (screen->rect.x + 25))
|
||||
return;
|
||||
struct callback_params params = { orientation, screen, helpwin, &new_position };
|
||||
|
||||
values[0] = new_position = new_x;
|
||||
xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_X, values);
|
||||
} else {
|
||||
if (new_y > (screen->rect.y + screen->rect.height - 25) ||
|
||||
new_y < (screen->rect.y + 25))
|
||||
return;
|
||||
|
||||
values[0] = new_position = new_y;
|
||||
xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_Y, values);
|
||||
}
|
||||
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
drag_pointer(conn, NULL, event, grabwin, BORDER_TOP, resize_callback);
|
||||
drag_pointer(conn, NULL, event, grabwin, BORDER_TOP, resize_callback, ¶ms);
|
||||
|
||||
xcb_destroy_window(conn, helpwin);
|
||||
xcb_destroy_window(conn, grabwin);
|
||||
|
@ -163,8 +182,29 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco
|
|||
if (ws->width_factor[second] == 0)
|
||||
new_unoccupied_x += default_width;
|
||||
|
||||
LOG("\n\n\n");
|
||||
LOG("old = %d, new = %d\n", old_unoccupied_x, new_unoccupied_x);
|
||||
DLOG("\n\n\n");
|
||||
DLOG("old = %d, new = %d\n", old_unoccupied_x, new_unoccupied_x);
|
||||
|
||||
int cols_without_wf = 0;
|
||||
int old_width, old_second_width;
|
||||
for (int col = 0; col < ws->cols; col++)
|
||||
if (ws->width_factor[col] == 0)
|
||||
cols_without_wf++;
|
||||
|
||||
DLOG("old_unoccupied_x = %d\n", old_unoccupied_x);
|
||||
|
||||
DLOG("Updating first (before = %f)\n", ws->width_factor[first]);
|
||||
/* Convert 0 (for default width_factor) to actual numbers */
|
||||
if (ws->width_factor[first] == 0)
|
||||
old_width = (old_unoccupied_x / max(cols_without_wf, 1));
|
||||
else old_width = ws->width_factor[first] * old_unoccupied_x;
|
||||
|
||||
DLOG("second (before = %f)\n", ws->width_factor[second]);
|
||||
if (ws->width_factor[second] == 0)
|
||||
old_second_width = (old_unoccupied_x / max(cols_without_wf, 1));
|
||||
else old_second_width = ws->width_factor[second] * old_unoccupied_x;
|
||||
|
||||
DLOG("middle = %f\n", ws->width_factor[first]);
|
||||
|
||||
/* If the space used for customly resized columns has changed we need to adapt the
|
||||
* other customly resized columns, if any */
|
||||
|
@ -173,37 +213,33 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco
|
|||
if (ws->width_factor[col] == 0)
|
||||
continue;
|
||||
|
||||
LOG("Updating other column (%d) (current width_factor = %f)\n", col, ws->width_factor[col]);
|
||||
DLOG("Updating other column (%d) (current width_factor = %f)\n", col, ws->width_factor[col]);
|
||||
ws->width_factor[col] = (ws->width_factor[col] * old_unoccupied_x) / new_unoccupied_x;
|
||||
LOG("to %f\n", ws->width_factor[col]);
|
||||
DLOG("to %f\n", ws->width_factor[col]);
|
||||
}
|
||||
|
||||
LOG("old_unoccupied_x = %d\n", old_unoccupied_x);
|
||||
|
||||
LOG("Updating first (before = %f)\n", ws->width_factor[first]);
|
||||
DLOG("Updating first (before = %f)\n", ws->width_factor[first]);
|
||||
/* Convert 0 (for default width_factor) to actual numbers */
|
||||
if (ws->width_factor[first] == 0)
|
||||
ws->width_factor[first] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x;
|
||||
|
||||
LOG("middle = %f\n", ws->width_factor[first]);
|
||||
int old_width = ws->width_factor[first] * old_unoccupied_x;
|
||||
LOG("first->width = %d, pixels = %d\n", pixels);
|
||||
DLOG("first->width = %d, pixels = %d\n", old_width, pixels);
|
||||
ws->width_factor[first] *= (float)(old_width + pixels) / old_width;
|
||||
LOG("-> %f\n", ws->width_factor[first]);
|
||||
DLOG("-> %f\n", ws->width_factor[first]);
|
||||
|
||||
|
||||
LOG("Updating second (before = %f)\n", ws->width_factor[second]);
|
||||
DLOG("Updating second (before = %f)\n", ws->width_factor[second]);
|
||||
if (ws->width_factor[second] == 0)
|
||||
ws->width_factor[second] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x;
|
||||
LOG("middle = %f\n", ws->width_factor[second]);
|
||||
old_width = ws->width_factor[second] * old_unoccupied_x;
|
||||
LOG("second->width = %d, pixels = %d\n", pixels);
|
||||
ws->width_factor[second] *= (float)(old_width - pixels) / old_width;
|
||||
LOG("-> %f\n", ws->width_factor[second]);
|
||||
|
||||
LOG("new unoccupied_x = %d\n", get_unoccupied_x(ws));
|
||||
DLOG("middle = %f\n", ws->width_factor[second]);
|
||||
DLOG("second->width = %d, pixels = %d\n", old_second_width, pixels);
|
||||
ws->width_factor[second] *= (float)(old_second_width - pixels) / old_second_width;
|
||||
DLOG("-> %f\n", ws->width_factor[second]);
|
||||
|
||||
LOG("\n\n\n");
|
||||
DLOG("new unoccupied_x = %d\n", get_unoccupied_x(ws));
|
||||
|
||||
DLOG("\n\n\n");
|
||||
} else {
|
||||
int ws_height = workspace_height(ws);
|
||||
int default_height = ws_height / ws->rows;
|
||||
|
@ -228,24 +264,25 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco
|
|||
if (ws->height_factor[row] == 0)
|
||||
cols_without_hf++;
|
||||
|
||||
LOG("old_unoccupied_y = %d\n", old_unoccupied_y);
|
||||
DLOG("old_unoccupied_y = %d\n", old_unoccupied_y);
|
||||
|
||||
DLOG("Updating first (before = %f)\n", ws->height_factor[first]);
|
||||
|
||||
LOG("Updating first (before = %f)\n", ws->height_factor[first]);
|
||||
/* Convert 0 (for default width_factor) to actual numbers */
|
||||
if (ws->height_factor[first] == 0)
|
||||
old_height = (old_unoccupied_y / max(cols_without_hf, 1));
|
||||
else old_height = ws->height_factor[first] * old_unoccupied_y;
|
||||
|
||||
LOG("second (before = %f)\n", ws->height_factor[second]);
|
||||
DLOG("second (before = %f)\n", ws->height_factor[second]);
|
||||
if (ws->height_factor[second] == 0)
|
||||
old_second_height = (old_unoccupied_y / max(cols_without_hf, 1));
|
||||
else old_second_height = ws->height_factor[second] * old_unoccupied_y;
|
||||
|
||||
LOG("middle = %f\n", ws->height_factor[first]);
|
||||
DLOG("middle = %f\n", ws->height_factor[first]);
|
||||
|
||||
|
||||
LOG("\n\n\n");
|
||||
LOG("old = %d, new = %d\n", old_unoccupied_y, new_unoccupied_y);
|
||||
DLOG("\n\n\n");
|
||||
DLOG("old = %d, new = %d\n", old_unoccupied_y, new_unoccupied_y);
|
||||
|
||||
/* If the space used for customly resized columns has changed we need to adapt the
|
||||
* other customly resized columns, if any */
|
||||
|
@ -254,33 +291,33 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco
|
|||
if (ws->height_factor[row] == 0)
|
||||
continue;
|
||||
|
||||
LOG("Updating other column (%d) (current width_factor = %f)\n", row, ws->height_factor[row]);
|
||||
DLOG("Updating other column (%d) (current width_factor = %f)\n", row, ws->height_factor[row]);
|
||||
ws->height_factor[row] = (ws->height_factor[row] * old_unoccupied_y) / new_unoccupied_y;
|
||||
LOG("to %f\n", ws->height_factor[row]);
|
||||
DLOG("to %f\n", ws->height_factor[row]);
|
||||
}
|
||||
|
||||
|
||||
LOG("Updating first (before = %f)\n", ws->height_factor[first]);
|
||||
DLOG("Updating first (before = %f)\n", ws->height_factor[first]);
|
||||
/* Convert 0 (for default width_factor) to actual numbers */
|
||||
if (ws->height_factor[first] == 0)
|
||||
ws->height_factor[first] = ((float)ws_height / ws->rows) / new_unoccupied_y;
|
||||
|
||||
LOG("first->width = %d, pixels = %d\n", old_height, pixels);
|
||||
DLOG("first->width = %d, pixels = %d\n", old_height, pixels);
|
||||
ws->height_factor[first] *= (float)(old_height + pixels) / old_height;
|
||||
LOG("-> %f\n", ws->height_factor[first]);
|
||||
DLOG("-> %f\n", ws->height_factor[first]);
|
||||
|
||||
|
||||
LOG("Updating second (before = %f)\n", ws->height_factor[second]);
|
||||
DLOG("Updating second (before = %f)\n", ws->height_factor[second]);
|
||||
if (ws->height_factor[second] == 0)
|
||||
ws->height_factor[second] = ((float)ws_height / ws->rows) / new_unoccupied_y;
|
||||
LOG("middle = %f\n", ws->height_factor[second]);
|
||||
LOG("second->width = %d, pixels = %d\n", old_second_height, pixels);
|
||||
DLOG("middle = %f\n", ws->height_factor[second]);
|
||||
DLOG("second->width = %d, pixels = %d\n", old_second_height, pixels);
|
||||
ws->height_factor[second] *= (float)(old_second_height - pixels) / old_second_height;
|
||||
LOG("-> %f\n", ws->height_factor[second]);
|
||||
DLOG("-> %f\n", ws->height_factor[second]);
|
||||
|
||||
LOG("new unoccupied_y = %d\n", get_unoccupied_y(ws));
|
||||
DLOG("new unoccupied_y = %d\n", get_unoccupied_y(ws));
|
||||
|
||||
LOG("\n\n\n");
|
||||
DLOG("\n\n\n");
|
||||
}
|
||||
|
||||
render_layout(conn);
|
||||
|
|
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
* vim:ts=8:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Jan-Erik Rediger
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
* sighandler.c: contains all functions for signal handling
|
||||
*
|
||||
*/
|
||||
#include <ev.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <iconv.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_aux.h>
|
||||
#include <xcb/xcb_event.h>
|
||||
#include <xcb/xcb_keysyms.h>
|
||||
|
||||
#include <X11/keysym.h>
|
||||
|
||||
#include "i3.h"
|
||||
#include "util.h"
|
||||
#include "xcb.h"
|
||||
#include "log.h"
|
||||
#include "config.h"
|
||||
#include "randr.h"
|
||||
|
||||
static xcb_gcontext_t pixmap_gc;
|
||||
static xcb_pixmap_t pixmap;
|
||||
static int raised_signal;
|
||||
|
||||
static char *crash_text[] = {
|
||||
"i3 just crashed.",
|
||||
"To debug this problem, either attach gdb now",
|
||||
"or press 'e' to exit and get a core-dump.",
|
||||
"If you want to keep your session,",
|
||||
"press 'r' to restart i3 in-place."
|
||||
};
|
||||
static int crash_text_longest = 1;
|
||||
|
||||
/*
|
||||
* Draw the window containing the info text
|
||||
*
|
||||
*/
|
||||
static int sig_draw_window(xcb_connection_t *conn, xcb_window_t win, int width, int height, int font_height) {
|
||||
/* re-draw the background */
|
||||
xcb_rectangle_t border = { 0, 0, width, height},
|
||||
inner = { 2, 2, width - 4, height - 4};
|
||||
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000"));
|
||||
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
|
||||
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
|
||||
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
|
||||
|
||||
/* restore font color */
|
||||
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF"));
|
||||
|
||||
for (int i = 0; i < sizeof(crash_text) / sizeof(char*); i++) {
|
||||
int text_len = strlen(crash_text[i]);
|
||||
char *full_text = convert_utf8_to_ucs2(crash_text[i], &text_len);
|
||||
xcb_image_text_16(conn, text_len, pixmap, pixmap_gc, 8 /* X */,
|
||||
3 + (i + 1) * font_height /* Y = baseline of font */,
|
||||
(xcb_char2b_t*)full_text);
|
||||
free(full_text);
|
||||
}
|
||||
|
||||
/* Copy the contents of the pixmap to the real window */
|
||||
xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, width, height);
|
||||
xcb_flush(conn);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handles keypresses of 'e' or 'r' to exit or restart i3
|
||||
*
|
||||
*/
|
||||
static int sig_handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) {
|
||||
uint16_t state = event->state;
|
||||
|
||||
/* Apparantly, after activating numlock once, the numlock modifier
|
||||
* stays turned on (use xev(1) to verify). So, to resolve useful
|
||||
* keysyms, we remove the numlock flag from the event state */
|
||||
state &= ~xcb_numlock_mask;
|
||||
|
||||
xcb_keysym_t sym = xcb_key_press_lookup_keysym(keysyms, event, state);
|
||||
|
||||
if (sym == 'e') {
|
||||
DLOG("User issued exit-command, raising error again.\n");
|
||||
raise(raised_signal);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (sym == 'r')
|
||||
i3_restart();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Opens the window we use for input/output and maps it
|
||||
*
|
||||
*/
|
||||
static xcb_window_t open_input_window(xcb_connection_t *conn, Rect screen_rect, uint32_t width, uint32_t height) {
|
||||
xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
|
||||
xcb_window_t win = xcb_generate_id(conn);
|
||||
|
||||
uint32_t mask = 0;
|
||||
uint32_t values[2];
|
||||
|
||||
mask |= XCB_CW_BACK_PIXEL;
|
||||
values[0] = 0;
|
||||
|
||||
mask |= XCB_CW_OVERRIDE_REDIRECT;
|
||||
values[1] = 1;
|
||||
|
||||
/* center each popup on the specified screen */
|
||||
uint32_t x = screen_rect.x + ((screen_rect.width / 2) - (width / 2)),
|
||||
y = screen_rect.y + ((screen_rect.height / 2) - (height / 2));
|
||||
|
||||
xcb_create_window(conn,
|
||||
XCB_COPY_FROM_PARENT,
|
||||
win, /* the window id */
|
||||
root, /* parent == root */
|
||||
x, y, width, height, /* dimensions */
|
||||
0, /* border = 0, we draw our own */
|
||||
XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
||||
XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
|
||||
mask,
|
||||
values);
|
||||
|
||||
/* Map the window (= make it visible) */
|
||||
xcb_map_window(conn, win);
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle signals
|
||||
* It creates a window asking the user to restart in-place
|
||||
* or exit to generate a core dump
|
||||
*
|
||||
*/
|
||||
void handle_signal(int sig, siginfo_t *info, void *data) {
|
||||
DLOG("i3 crashed. SIG: %d\n", sig);
|
||||
|
||||
struct sigaction action;
|
||||
action.sa_handler = SIG_DFL;
|
||||
sigaction(sig, &action, NULL);
|
||||
raised_signal = sig;
|
||||
|
||||
xcb_connection_t *conn = global_conn;
|
||||
|
||||
/* setup event handler for key presses */
|
||||
xcb_event_handlers_t sig_evenths;
|
||||
memset(&sig_evenths, 0, sizeof(xcb_event_handlers_t));
|
||||
xcb_event_handlers_init(conn, &sig_evenths);
|
||||
xcb_event_set_key_press_handler(&sig_evenths, sig_handle_key_press, NULL);
|
||||
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
|
||||
/* width and height of the popup window, so that the text fits in */
|
||||
int crash_text_num = sizeof(crash_text) / sizeof(char*);
|
||||
int height = 13 + (crash_text_num * font->height);
|
||||
|
||||
/* calculate width for longest text */
|
||||
int text_len = strlen(crash_text[crash_text_longest]);
|
||||
char *longest_text = convert_utf8_to_ucs2(crash_text[crash_text_longest], &text_len);
|
||||
int font_width = predict_text_width(conn, config.font, longest_text, text_len);
|
||||
int width = font_width + 20;
|
||||
|
||||
/* Open a popup window on each virtual screen */
|
||||
Output *screen;
|
||||
xcb_window_t win;
|
||||
TAILQ_FOREACH(screen, &outputs, outputs) {
|
||||
if (!screen->active)
|
||||
continue;
|
||||
win = open_input_window(conn, screen->rect, width, height);
|
||||
|
||||
/* Create pixmap */
|
||||
pixmap = xcb_generate_id(conn);
|
||||
pixmap_gc = xcb_generate_id(conn);
|
||||
xcb_create_pixmap(conn, root_depth, pixmap, win, width, height);
|
||||
xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
|
||||
|
||||
/* Create graphics context */
|
||||
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font->id);
|
||||
|
||||
/* Grab the keyboard to get all input */
|
||||
xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
|
||||
|
||||
/* Grab the cursor inside the popup */
|
||||
xcb_grab_pointer(conn, false, win, XCB_NONE, XCB_GRAB_MODE_ASYNC,
|
||||
XCB_GRAB_MODE_ASYNC, win, XCB_NONE, XCB_CURRENT_TIME);
|
||||
|
||||
sig_draw_window(conn, win, width, height, font->height);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
xcb_event_wait_for_event_loop(&sig_evenths);
|
||||
}
|
||||
|
||||
/*
|
||||
* Setup signal handlers to safely handle SIGSEGV and SIGFPE
|
||||
*
|
||||
*/
|
||||
void setup_signal_handler() {
|
||||
struct sigaction action;
|
||||
|
||||
action.sa_sigaction = handle_signal;
|
||||
action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
|
||||
sigemptyset(&action.sa_mask);
|
||||
|
||||
if (sigaction(SIGSEGV, &action, NULL) == -1 ||
|
||||
sigaction(SIGFPE, &action, NULL) == -1)
|
||||
ELOG("Could not setup signal handler");
|
||||
}
|
63
src/table.c
63
src/table.c
|
@ -27,6 +27,7 @@
|
|||
#include "layout.h"
|
||||
#include "config.h"
|
||||
#include "workspace.h"
|
||||
#include "log.h"
|
||||
|
||||
int current_workspace = 0;
|
||||
int num_workspaces = 1;
|
||||
|
@ -52,7 +53,7 @@ void init_table() {
|
|||
|
||||
static void new_container(Workspace *workspace, Container **container, int col, int row, bool skip_layout_switch) {
|
||||
Container *new;
|
||||
new = *container = calloc(sizeof(Container), 1);
|
||||
new = *container = scalloc(sizeof(Container));
|
||||
CIRCLEQ_INIT(&(new->clients));
|
||||
new->colspan = 1;
|
||||
new->rowspan = 1;
|
||||
|
@ -96,9 +97,9 @@ void expand_table_rows_at_head(Workspace *workspace) {
|
|||
|
||||
workspace->height_factor = realloc(workspace->height_factor, sizeof(float) * workspace->rows);
|
||||
|
||||
LOG("rows = %d\n", workspace->rows);
|
||||
DLOG("rows = %d\n", workspace->rows);
|
||||
for (int rows = (workspace->rows - 1); rows >= 1; rows--) {
|
||||
LOG("Moving height_factor %d (%f) to %d\n", rows-1, workspace->height_factor[rows-1], rows);
|
||||
DLOG("Moving height_factor %d (%f) to %d\n", rows-1, workspace->height_factor[rows-1], rows);
|
||||
workspace->height_factor[rows] = workspace->height_factor[rows-1];
|
||||
}
|
||||
|
||||
|
@ -110,7 +111,7 @@ void expand_table_rows_at_head(Workspace *workspace) {
|
|||
/* Move the other rows */
|
||||
for (int cols = 0; cols < workspace->cols; cols++)
|
||||
for (int rows = workspace->rows - 1; rows > 0; rows--) {
|
||||
LOG("Moving row %d to %d\n", rows-1, rows);
|
||||
DLOG("Moving row %d to %d\n", rows-1, rows);
|
||||
workspace->table[cols][rows] = workspace->table[cols][rows-1];
|
||||
workspace->table[cols][rows]->row = rows;
|
||||
}
|
||||
|
@ -130,7 +131,7 @@ void expand_table_cols(Workspace *workspace) {
|
|||
workspace->width_factor[workspace->cols-1] = 0;
|
||||
|
||||
workspace->table = realloc(workspace->table, sizeof(Container**) * workspace->cols);
|
||||
workspace->table[workspace->cols-1] = calloc(sizeof(Container*) * workspace->rows, 1);
|
||||
workspace->table[workspace->cols-1] = scalloc(sizeof(Container*) * workspace->rows);
|
||||
|
||||
for (int c = 0; c < workspace->rows; c++)
|
||||
new_container(workspace, &(workspace->table[workspace->cols-1][c]), workspace->cols-1, c, true);
|
||||
|
@ -148,21 +149,21 @@ void expand_table_cols_at_head(Workspace *workspace) {
|
|||
|
||||
workspace->width_factor = realloc(workspace->width_factor, sizeof(float) * workspace->cols);
|
||||
|
||||
LOG("cols = %d\n", workspace->cols);
|
||||
DLOG("cols = %d\n", workspace->cols);
|
||||
for (int cols = (workspace->cols - 1); cols >= 1; cols--) {
|
||||
LOG("Moving width_factor %d (%f) to %d\n", cols-1, workspace->width_factor[cols-1], cols);
|
||||
DLOG("Moving width_factor %d (%f) to %d\n", cols-1, workspace->width_factor[cols-1], cols);
|
||||
workspace->width_factor[cols] = workspace->width_factor[cols-1];
|
||||
}
|
||||
|
||||
workspace->width_factor[0] = 0;
|
||||
|
||||
workspace->table = realloc(workspace->table, sizeof(Container**) * workspace->cols);
|
||||
workspace->table[workspace->cols-1] = calloc(sizeof(Container*) * workspace->rows, 1);
|
||||
workspace->table[workspace->cols-1] = scalloc(sizeof(Container*) * workspace->rows);
|
||||
|
||||
/* Move the other columns */
|
||||
for (int rows = 0; rows < workspace->rows; rows++)
|
||||
for (int cols = workspace->cols - 1; cols > 0; cols--) {
|
||||
LOG("Moving col %d to %d\n", cols-1, cols);
|
||||
DLOG("Moving col %d to %d\n", cols-1, cols);
|
||||
workspace->table[cols][rows] = workspace->table[cols-1][rows];
|
||||
workspace->table[cols][rows]->col = cols;
|
||||
}
|
||||
|
@ -201,7 +202,7 @@ static void shrink_table_cols(Workspace *workspace) {
|
|||
if (workspace->width_factor[cols] == 0)
|
||||
continue;
|
||||
|
||||
LOG("Added free space (%f) to %d (had %f)\n", free_space, cols,
|
||||
DLOG("Added free space (%f) to %d (had %f)\n", free_space, cols,
|
||||
workspace->width_factor[cols]);
|
||||
workspace->width_factor[cols] += free_space;
|
||||
break;
|
||||
|
@ -230,7 +231,7 @@ static void shrink_table_rows(Workspace *workspace) {
|
|||
if (workspace->height_factor[rows] == 0)
|
||||
continue;
|
||||
|
||||
LOG("Added free space (%f) to %d (had %f)\n", free_space, rows,
|
||||
DLOG("Added free space (%f) to %d (had %f)\n", free_space, rows,
|
||||
workspace->height_factor[rows]);
|
||||
workspace->height_factor[rows] += free_space;
|
||||
break;
|
||||
|
@ -256,7 +257,7 @@ static void free_container(xcb_connection_t *conn, Workspace *workspace, int col
|
|||
}
|
||||
|
||||
static void move_columns_from(xcb_connection_t *conn, Workspace *workspace, int cols) {
|
||||
LOG("firstly freeing \n");
|
||||
DLOG("firstly freeing \n");
|
||||
|
||||
/* Free the columns which are cleaned up */
|
||||
for (int rows = 0; rows < workspace->rows; rows++)
|
||||
|
@ -264,10 +265,10 @@ static void move_columns_from(xcb_connection_t *conn, Workspace *workspace, int
|
|||
|
||||
for (; cols < workspace->cols; cols++)
|
||||
for (int rows = 0; rows < workspace->rows; rows++) {
|
||||
LOG("at col = %d, row = %d\n", cols, rows);
|
||||
DLOG("at col = %d, row = %d\n", cols, rows);
|
||||
Container *new_container = workspace->table[cols][rows];
|
||||
|
||||
LOG("moving cols = %d to cols -1 = %d\n", cols, cols-1);
|
||||
DLOG("moving cols = %d to cols -1 = %d\n", cols, cols-1);
|
||||
workspace->table[cols-1][rows] = new_container;
|
||||
|
||||
new_container->row = rows;
|
||||
|
@ -283,7 +284,7 @@ static void move_rows_from(xcb_connection_t *conn, Workspace *workspace, int row
|
|||
for (int cols = 0; cols < workspace->cols; cols++) {
|
||||
Container *new_container = workspace->table[cols][rows];
|
||||
|
||||
LOG("moving rows = %d to rows -1 = %d\n", rows, rows - 1);
|
||||
DLOG("moving rows = %d to rows -1 = %d\n", rows, rows - 1);
|
||||
workspace->table[cols][rows-1] = new_container;
|
||||
|
||||
new_container->row = rows-1;
|
||||
|
@ -296,19 +297,19 @@ static void move_rows_from(xcb_connection_t *conn, Workspace *workspace, int row
|
|||
*
|
||||
*/
|
||||
void dump_table(xcb_connection_t *conn, Workspace *workspace) {
|
||||
LOG("dump_table()\n");
|
||||
DLOG("dump_table()\n");
|
||||
FOR_TABLE(workspace) {
|
||||
Container *con = workspace->table[cols][rows];
|
||||
LOG("----\n");
|
||||
LOG("at col=%d, row=%d\n", cols, rows);
|
||||
LOG("currently_focused = %p\n", con->currently_focused);
|
||||
DLOG("----\n");
|
||||
DLOG("at col=%d, row=%d\n", cols, rows);
|
||||
DLOG("currently_focused = %p\n", con->currently_focused);
|
||||
Client *loop;
|
||||
CIRCLEQ_FOREACH(loop, &(con->clients), clients) {
|
||||
LOG("got client %08x / %s\n", loop->child, loop->name);
|
||||
DLOG("got client %08x / %s\n", loop->child, loop->name);
|
||||
}
|
||||
LOG("----\n");
|
||||
DLOG("----\n");
|
||||
}
|
||||
LOG("done\n");
|
||||
DLOG("done\n");
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -316,7 +317,7 @@ void dump_table(xcb_connection_t *conn, Workspace *workspace) {
|
|||
*
|
||||
*/
|
||||
void cleanup_table(xcb_connection_t *conn, Workspace *workspace) {
|
||||
LOG("cleanup_table()\n");
|
||||
DLOG("cleanup_table()\n");
|
||||
|
||||
/* Check for empty columns if we got more than one column */
|
||||
for (int cols = 0; (workspace->cols > 1) && (cols < workspace->cols);) {
|
||||
|
@ -327,7 +328,7 @@ void cleanup_table(xcb_connection_t *conn, Workspace *workspace) {
|
|||
break;
|
||||
}
|
||||
if (completely_empty) {
|
||||
LOG("Removing completely empty column %d\n", cols);
|
||||
DLOG("Removing completely empty column %d\n", cols);
|
||||
if (cols < (workspace->cols - 1))
|
||||
move_columns_from(conn, workspace, cols+1);
|
||||
else {
|
||||
|
@ -344,14 +345,14 @@ void cleanup_table(xcb_connection_t *conn, Workspace *workspace) {
|
|||
/* Check for empty rows if we got more than one row */
|
||||
for (int rows = 0; (workspace->rows > 1) && (rows < workspace->rows);) {
|
||||
bool completely_empty = true;
|
||||
LOG("Checking row %d\n", rows);
|
||||
DLOG("Checking row %d\n", rows);
|
||||
for (int cols = 0; cols < workspace->cols; cols++)
|
||||
if (workspace->table[cols][rows]->currently_focused != NULL) {
|
||||
completely_empty = false;
|
||||
break;
|
||||
}
|
||||
if (completely_empty) {
|
||||
LOG("Removing completely empty row %d\n", rows);
|
||||
DLOG("Removing completely empty row %d\n", rows);
|
||||
if (rows < (workspace->rows - 1))
|
||||
move_rows_from(conn, workspace, rows+1);
|
||||
else {
|
||||
|
@ -381,25 +382,25 @@ void cleanup_table(xcb_connection_t *conn, Workspace *workspace) {
|
|||
*
|
||||
*/
|
||||
void fix_colrowspan(xcb_connection_t *conn, Workspace *workspace) {
|
||||
LOG("Fixing col/rowspan\n");
|
||||
DLOG("Fixing col/rowspan\n");
|
||||
|
||||
FOR_TABLE(workspace) {
|
||||
Container *con = workspace->table[cols][rows];
|
||||
if (con->colspan > 1) {
|
||||
LOG("gots one with colspan %d (at %d c, %d r)\n", con->colspan, cols, rows);
|
||||
DLOG("gots one with colspan %d (at %d c, %d r)\n", con->colspan, cols, rows);
|
||||
while (con->colspan > 1 &&
|
||||
(!cell_exists(workspace, cols + (con->colspan-1), rows) &&
|
||||
workspace->table[cols + (con->colspan - 1)][rows]->currently_focused != NULL))
|
||||
con->colspan--;
|
||||
LOG("fixed it to %d\n", con->colspan);
|
||||
DLOG("fixed it to %d\n", con->colspan);
|
||||
}
|
||||
if (con->rowspan > 1) {
|
||||
LOG("gots one with rowspan %d (at %d c, %d r)\n", con->rowspan, cols, rows);
|
||||
DLOG("gots one with rowspan %d (at %d c, %d r)\n", con->rowspan, cols, rows);
|
||||
while (con->rowspan > 1 &&
|
||||
(!cell_exists(workspace, cols, rows + (con->rowspan - 1)) &&
|
||||
workspace->table[cols][rows + (con->rowspan - 1)]->currently_focused != NULL))
|
||||
con->rowspan--;
|
||||
LOG("fixed it to %d\n", con->rowspan);
|
||||
DLOG("fixed it to %d\n", con->rowspan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
127
src/util.c
127
src/util.c
|
@ -31,6 +31,11 @@
|
|||
#include "util.h"
|
||||
#include "xcb.h"
|
||||
#include "client.h"
|
||||
#include "log.h"
|
||||
#include "ewmh.h"
|
||||
#include "manage.h"
|
||||
#include "workspace.h"
|
||||
#include "ipc.h"
|
||||
|
||||
static iconv_t conversion_descriptor = 0;
|
||||
struct keyvalue_table_head by_parent = TAILQ_HEAD_INITIALIZER(by_parent);
|
||||
|
@ -45,24 +50,14 @@ int max(int a, int b) {
|
|||
}
|
||||
|
||||
/*
|
||||
* Logs the given message to stdout while prefixing the current time to it.
|
||||
* This is to be called by LOG() which includes filename/linenumber
|
||||
* Updates *destination with new_value and returns true if it was changed or false
|
||||
* if it was the same
|
||||
*
|
||||
*/
|
||||
void slog(char *fmt, ...) {
|
||||
va_list args;
|
||||
char timebuf[64];
|
||||
bool update_if_necessary(uint32_t *destination, const uint32_t new_value) {
|
||||
uint32_t old_value = *destination;
|
||||
|
||||
va_start(args, fmt);
|
||||
/* Get current time */
|
||||
time_t t = time(NULL);
|
||||
/* Convert time to local time (determined by the locale) */
|
||||
struct tm *tmp = localtime(&t);
|
||||
/* Generate time prefix */
|
||||
strftime(timebuf, sizeof(timebuf), "%x %X - ", tmp);
|
||||
printf("%s", timebuf);
|
||||
vprintf(fmt, args);
|
||||
va_end(args);
|
||||
return ((*destination = new_value) != old_value);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -148,7 +143,7 @@ void start_application(const char *command) {
|
|||
shell = "/bin/sh";
|
||||
|
||||
/* This is the child */
|
||||
execl(shell, shell, "-c", command, NULL);
|
||||
execl(shell, shell, "-c", command, (void*)NULL);
|
||||
/* not reached */
|
||||
}
|
||||
exit(0);
|
||||
|
@ -164,7 +159,7 @@ void start_application(const char *command) {
|
|||
void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_message) {
|
||||
xcb_generic_error_t *error = xcb_request_check(conn, cookie);
|
||||
if (error != NULL) {
|
||||
fprintf(stderr, "ERROR: %s : %d\n", err_message , error->error_code);
|
||||
fprintf(stderr, "ERROR: %s (X error %d)\n", err_message , error->error_code);
|
||||
xcb_disconnect(conn);
|
||||
exit(-1);
|
||||
}
|
||||
|
@ -178,16 +173,16 @@ void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_mes
|
|||
*
|
||||
*/
|
||||
char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
|
||||
size_t input_size = strlen(input) + 1;
|
||||
/* UCS-2 consumes exactly two bytes for each glyph */
|
||||
int buffer_size = input_size * 2;
|
||||
size_t input_size = strlen(input) + 1;
|
||||
/* UCS-2 consumes exactly two bytes for each glyph */
|
||||
int buffer_size = input_size * 2;
|
||||
|
||||
char *buffer = smalloc(buffer_size);
|
||||
size_t output_size = buffer_size;
|
||||
/* We need to use an additional pointer, because iconv() modifies it */
|
||||
char *output = buffer;
|
||||
char *buffer = smalloc(buffer_size);
|
||||
size_t output_size = buffer_size;
|
||||
/* We need to use an additional pointer, because iconv() modifies it */
|
||||
char *output = buffer;
|
||||
|
||||
/* We convert the input into UCS-2 big endian */
|
||||
/* We convert the input into UCS-2 big endian */
|
||||
if (conversion_descriptor == 0) {
|
||||
conversion_descriptor = iconv_open("UCS-2BE", "UTF-8");
|
||||
if (conversion_descriptor == 0) {
|
||||
|
@ -196,22 +191,22 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
|
|||
}
|
||||
}
|
||||
|
||||
/* Get the conversion descriptor back to original state */
|
||||
iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
|
||||
/* Get the conversion descriptor back to original state */
|
||||
iconv(conversion_descriptor, NULL, NULL, NULL, NULL);
|
||||
|
||||
/* Convert our text */
|
||||
int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
|
||||
/* Convert our text */
|
||||
int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size);
|
||||
if (rc == (size_t)-1) {
|
||||
perror("Converting to UCS-2 failed");
|
||||
if (real_strlen != NULL)
|
||||
*real_strlen = 0;
|
||||
*real_strlen = 0;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (real_strlen != NULL)
|
||||
*real_strlen = ((buffer_size - output_size) / 2) - 1;
|
||||
*real_strlen = ((buffer_size - output_size) / 2) - 1;
|
||||
|
||||
return buffer;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -250,6 +245,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
|
|||
c_ws->current_row = current_row;
|
||||
c_ws->current_col = current_col;
|
||||
c_ws = client->workspace;
|
||||
ewmh_update_current_desktop();
|
||||
/* Load current_col/current_row if we switch to a client without a container */
|
||||
current_col = c_ws->current_col;
|
||||
current_row = c_ws->current_row;
|
||||
|
@ -265,6 +261,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
|
|||
CLIENT_LOG(client);
|
||||
/* Set focus to the entered window, and flush xcb buffer immediately */
|
||||
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, client->child, XCB_CURRENT_TIME);
|
||||
ewmh_update_active_window(client->child);
|
||||
//xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, 10, 10);
|
||||
|
||||
if (client->container != NULL) {
|
||||
|
@ -280,7 +277,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
|
|||
Client *last_focused = get_last_focused_client(conn, client->container, client);
|
||||
|
||||
if (last_focused != NULL) {
|
||||
LOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child);
|
||||
DLOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child);
|
||||
uint32_t values[] = { last_focused->frame, XCB_STACK_MODE_ABOVE };
|
||||
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
}
|
||||
|
@ -294,22 +291,27 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) {
|
|||
/* If the last client was a floating client, we need to go to the next
|
||||
* tiling client in stack and re-decorate it. */
|
||||
if (old_client != NULL && client_is_floating(old_client)) {
|
||||
LOG("Coming from floating client, searching next tiling...\n");
|
||||
DLOG("Coming from floating client, searching next tiling...\n");
|
||||
Client *current;
|
||||
SLIST_FOREACH(current, &(client->workspace->focus_stack), focus_clients) {
|
||||
if (client_is_floating(current))
|
||||
continue;
|
||||
|
||||
LOG("Found window: %p / child %p\n", current->frame, current->child);
|
||||
DLOG("Found window: %p / child %p\n", current->frame, current->child);
|
||||
redecorate_window(conn, current);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
|
||||
SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients);
|
||||
|
||||
/* Clear the urgency flag if set (necessary when i3 sets the flag, for
|
||||
* example when automatically putting windows on the workspace of their
|
||||
* leader) */
|
||||
client->urgent = false;
|
||||
workspace_update_urgent_flag(client->workspace);
|
||||
|
||||
/* If we’re in stacking mode, this renders the container to update changes in the title
|
||||
bars and to raise the focused client */
|
||||
if ((old_client != NULL) && (old_client != client) && !old_client->dock)
|
||||
|
@ -411,14 +413,14 @@ after_stackwin:
|
|||
if (client == container->currently_focused || client == last_focused)
|
||||
continue;
|
||||
|
||||
LOG("setting %08x below %08x / %08x\n", client->frame, container->currently_focused->frame);
|
||||
DLOG("setting %08x below %08x / %08x\n", client->frame, container->currently_focused->frame);
|
||||
uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW };
|
||||
xcb_configure_window(conn, client->frame,
|
||||
XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
}
|
||||
|
||||
if (last_focused != NULL) {
|
||||
LOG("Putting last_focused directly underneath the currently focused\n");
|
||||
DLOG("Putting last_focused directly underneath the currently focused\n");
|
||||
uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW };
|
||||
xcb_configure_window(conn, last_focused->frame,
|
||||
XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
|
@ -457,15 +459,15 @@ Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitl
|
|||
goto done;
|
||||
}
|
||||
|
||||
LOG("Getting clients for class \"%s\" / title \"%s\"\n", to_class, to_title);
|
||||
DLOG("Getting clients for class \"%s\" / title \"%s\"\n", to_class, to_title);
|
||||
Workspace *ws;
|
||||
TAILQ_FOREACH(ws, workspaces, workspaces) {
|
||||
if (ws->screen == NULL)
|
||||
if (ws->output == NULL)
|
||||
continue;
|
||||
|
||||
Client *client;
|
||||
SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) {
|
||||
LOG("Checking client with class=%s / %s, name=%s\n", client->window_class_instance,
|
||||
DLOG("Checking client with class=%s / %s, name=%s\n", client->window_class_instance,
|
||||
client->window_class_class, client->name);
|
||||
if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len))
|
||||
continue;
|
||||
|
@ -481,6 +483,47 @@ done:
|
|||
return matching;
|
||||
}
|
||||
|
||||
/*
|
||||
* Goes through the list of arguments (for exec()) and checks if the given argument
|
||||
* is present. If not, it copies the arguments (because we cannot realloc it) and
|
||||
* appends the given argument.
|
||||
*
|
||||
*/
|
||||
static char **append_argument(char **original, char *argument) {
|
||||
int num_args;
|
||||
for (num_args = 0; original[num_args] != NULL; num_args++) {
|
||||
DLOG("original argument: \"%s\"\n", original[num_args]);
|
||||
/* If the argument is already present we return the original pointer */
|
||||
if (strcmp(original[num_args], argument) == 0)
|
||||
return original;
|
||||
}
|
||||
/* Copy the original array */
|
||||
char **result = smalloc((num_args+2) * sizeof(char*));
|
||||
memcpy(result, original, num_args * sizeof(char*));
|
||||
result[num_args] = argument;
|
||||
result[num_args+1] = NULL;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Restart i3 in-place
|
||||
* appends -a to argument list to disable autostart
|
||||
*
|
||||
*/
|
||||
void i3_restart() {
|
||||
restore_geometry(global_conn);
|
||||
|
||||
ipc_shutdown();
|
||||
|
||||
LOG("restarting \"%s\"...\n", start_argv[0]);
|
||||
/* make sure -a is in the argument list or append it */
|
||||
start_argv = append_argument(start_argv, "-a");
|
||||
|
||||
execvp(start_argv[0], start_argv);
|
||||
/* not reached */
|
||||
}
|
||||
|
||||
#if defined(__OpenBSD__)
|
||||
|
||||
/*
|
||||
|
|
238
src/workspace.c
238
src/workspace.c
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
|
@ -22,10 +22,13 @@
|
|||
#include "config.h"
|
||||
#include "xcb.h"
|
||||
#include "table.h"
|
||||
#include "xinerama.h"
|
||||
#include "randr.h"
|
||||
#include "layout.h"
|
||||
#include "workspace.h"
|
||||
#include "client.h"
|
||||
#include "log.h"
|
||||
#include "ewmh.h"
|
||||
#include "ipc.h"
|
||||
|
||||
/*
|
||||
* Returns a pointer to the workspace with the given number (starting at 0),
|
||||
|
@ -42,10 +45,10 @@ Workspace *workspace_get(int number) {
|
|||
/* If we are still there, we could not find the requested workspace. */
|
||||
int last_ws = TAILQ_LAST(workspaces, workspaces_head)->num;
|
||||
|
||||
LOG("We need to initialize that one, last ws = %d\n", last_ws);
|
||||
DLOG("We need to initialize that one, last ws = %d\n", last_ws);
|
||||
|
||||
for (int c = last_ws; c < number; c++) {
|
||||
LOG("Creating new ws\n");
|
||||
DLOG("Creating new ws\n");
|
||||
|
||||
ws = scalloc(sizeof(Workspace));
|
||||
ws->num = c+1;
|
||||
|
@ -55,8 +58,12 @@ Workspace *workspace_get(int number) {
|
|||
workspace_set_name(ws, NULL);
|
||||
|
||||
TAILQ_INSERT_TAIL(workspaces, ws, workspaces);
|
||||
|
||||
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}");
|
||||
}
|
||||
LOG("done\n");
|
||||
DLOG("done\n");
|
||||
|
||||
ewmh_update_workarea();
|
||||
|
||||
return ws;
|
||||
}
|
||||
|
@ -80,13 +87,13 @@ void workspace_set_name(Workspace *ws, const char *name) {
|
|||
errx(1, "asprintf() failed");
|
||||
|
||||
FREE(ws->name);
|
||||
FREE(ws->utf8_name);
|
||||
|
||||
ws->name = convert_utf8_to_ucs2(label, &(ws->name_len));
|
||||
if (config.font != NULL)
|
||||
ws->text_width = predict_text_width(global_conn, config.font, ws->name, ws->name_len);
|
||||
else ws->text_width = 0;
|
||||
|
||||
free(label);
|
||||
ws->utf8_name = label;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -96,7 +103,7 @@ void workspace_set_name(Workspace *ws, const char *name) {
|
|||
*
|
||||
*/
|
||||
bool workspace_is_visible(Workspace *ws) {
|
||||
return (ws->screen->current_workspace == ws);
|
||||
return (ws->output != NULL && ws->output->current_workspace == ws);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -109,29 +116,29 @@ void workspace_show(xcb_connection_t *conn, int workspace) {
|
|||
/* t_ws (to workspace) is just a convenience pointer to the workspace we’re switching to */
|
||||
Workspace *t_ws = workspace_get(workspace-1);
|
||||
|
||||
LOG("show_workspace(%d)\n", workspace);
|
||||
DLOG("show_workspace(%d)\n", workspace);
|
||||
|
||||
/* Store current_row/current_col */
|
||||
c_ws->current_row = current_row;
|
||||
c_ws->current_col = current_col;
|
||||
|
||||
/* Check if the workspace has not been used yet */
|
||||
workspace_initialize(t_ws, c_ws->screen);
|
||||
workspace_initialize(t_ws, c_ws->output, false);
|
||||
|
||||
if (c_ws->screen != t_ws->screen) {
|
||||
/* We need to switch to the other screen first */
|
||||
LOG("moving over to other screen.\n");
|
||||
if (c_ws->output != t_ws->output) {
|
||||
/* We need to switch to the other output first */
|
||||
DLOG("moving over to other output.\n");
|
||||
|
||||
/* Store the old client */
|
||||
Client *old_client = CUR_CELL->currently_focused;
|
||||
|
||||
c_ws = t_ws->screen->current_workspace;
|
||||
c_ws = t_ws->output->current_workspace;
|
||||
current_col = c_ws->current_col;
|
||||
current_row = c_ws->current_row;
|
||||
if (CUR_CELL->currently_focused != NULL)
|
||||
need_warp = true;
|
||||
else {
|
||||
Rect *dims = &(c_ws->screen->rect);
|
||||
Rect *dims = &(c_ws->output->rect);
|
||||
xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0,
|
||||
dims->x + (dims->width / 2), dims->y + (dims->height / 2));
|
||||
}
|
||||
|
@ -140,10 +147,19 @@ void workspace_show(xcb_connection_t *conn, int workspace) {
|
|||
if ((old_client != NULL) && !old_client->dock)
|
||||
redecorate_window(conn, old_client);
|
||||
else xcb_flush(conn);
|
||||
|
||||
/* We need to check if a global fullscreen-client is blocking
|
||||
* the t_ws and if necessary switch that to local fullscreen */
|
||||
Client* client = c_ws->fullscreen_client;
|
||||
if (client != NULL && client->workspace != c_ws) {
|
||||
if (c_ws->fullscreen_client->workspace != c_ws)
|
||||
c_ws->fullscreen_client = NULL;
|
||||
client_enter_fullscreen(conn, client, false);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if we need to change something or if we’re already there */
|
||||
if (c_ws->screen->current_workspace->num == (workspace-1)) {
|
||||
if (c_ws->output->current_workspace->num == (workspace-1)) {
|
||||
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
|
||||
if (last_focused != SLIST_END(&(c_ws->focus_stack)))
|
||||
set_focus(conn, last_focused, true);
|
||||
|
@ -152,24 +168,28 @@ void workspace_show(xcb_connection_t *conn, int workspace) {
|
|||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Workspace *old_workspace = c_ws;
|
||||
c_ws = t_ws->screen->current_workspace = workspace_get(workspace-1);
|
||||
c_ws = t_ws->output->current_workspace = workspace_get(workspace-1);
|
||||
|
||||
/* Unmap all clients of the old workspace */
|
||||
workspace_unmap_clients(conn, old_workspace);
|
||||
|
||||
current_row = c_ws->current_row;
|
||||
current_col = c_ws->current_col;
|
||||
LOG("new current row = %d, current col = %d\n", current_row, current_col);
|
||||
DLOG("new current row = %d, current col = %d\n", current_row, current_col);
|
||||
|
||||
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
|
||||
|
||||
workspace_map_clients(conn, c_ws);
|
||||
|
||||
/* POTENTIAL TO IMPROVE HERE: due to the call to _map_clients first and
|
||||
* render_layout afterwards, there is a short flickering on the source
|
||||
* workspace (assign ws 3 to screen 0, ws 4 to screen 1, create single
|
||||
* workspace (assign ws 3 to output 0, ws 4 to output 1, create single
|
||||
* client on ws 4, move it to ws 3, switch to ws 3, you’ll see the
|
||||
* flickering). */
|
||||
|
||||
|
@ -184,63 +204,61 @@ void workspace_show(xcb_connection_t *conn, int workspace) {
|
|||
/* We can warp the pointer only after the window has been
|
||||
* reconfigured in render_layout, otherwise the pointer will
|
||||
* be warped to the old position, which will not work when we
|
||||
* moved it to another screen. */
|
||||
* moved it to another output. */
|
||||
if (last_focused != SLIST_END(&(c_ws->focus_stack)) && need_warp) {
|
||||
client_warp_pointer_into(conn, last_focused);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Parses the preferred_screen property of a workspace. You can either specify
|
||||
* the screen number (it is not given that the screen numbering always stays
|
||||
* the same) or the screen coordinates (exact coordinates, e.g. 1280 will match
|
||||
* the screen starting at x=1280, but 1281 will not). For coordinates, you can
|
||||
* either specify an x coordinate ("1280") or an y coordinate ("x800") or both
|
||||
* ("1280x800").
|
||||
* Assigns the given workspace to the given output by correctly updating its
|
||||
* state and reconfiguring all the clients on this workspace.
|
||||
*
|
||||
* This is called when initializing a output and when re-assigning it to a
|
||||
* different output which just got available (if you configured it to be on
|
||||
* output 1 and you just plugged in output 1).
|
||||
*
|
||||
*/
|
||||
static i3Screen *get_screen_from_preference(struct screens_head *slist, char *preference) {
|
||||
i3Screen *screen;
|
||||
char *rest;
|
||||
int preferred_screen = strtol(preference, &rest, 10);
|
||||
void workspace_assign_to(Workspace *ws, Output *output, bool hide_it) {
|
||||
Client *client;
|
||||
bool empty = true;
|
||||
bool visible = workspace_is_visible(ws);
|
||||
|
||||
LOG("Getting screen for preference \"%s\" (%d)\n", preference, preferred_screen);
|
||||
ws->output = output;
|
||||
|
||||
if ((rest == preference) || (preferred_screen >= num_screens)) {
|
||||
int x = INT_MAX, y = INT_MAX;
|
||||
if (strchr(preference, 'x') != NULL) {
|
||||
/* Check if only the y coordinate was specified */
|
||||
if (*preference == 'x')
|
||||
y = atoi(preference+1);
|
||||
else {
|
||||
x = atoi(preference);
|
||||
y = atoi(strchr(preference, 'x') + 1);
|
||||
}
|
||||
} else {
|
||||
x = atoi(preference);
|
||||
}
|
||||
/* Copy the dimensions from the virtual output */
|
||||
memcpy(&(ws->rect), &(ws->output->rect), sizeof(Rect));
|
||||
|
||||
LOG("Looking for screen at %d x %d\n", x, y);
|
||||
ewmh_update_workarea();
|
||||
|
||||
TAILQ_FOREACH(screen, slist, screens)
|
||||
if ((x == INT_MAX || screen->rect.x == x) &&
|
||||
(y == INT_MAX || screen->rect.y == y)) {
|
||||
LOG("found %p\n", screen);
|
||||
return screen;
|
||||
}
|
||||
|
||||
LOG("none found\n");
|
||||
return NULL;
|
||||
} else {
|
||||
int c = 0;
|
||||
TAILQ_FOREACH(screen, slist, screens)
|
||||
if (c++ == preferred_screen)
|
||||
return screen;
|
||||
/* Force reconfiguration for each client on that workspace */
|
||||
SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) {
|
||||
client->force_reconfigure = true;
|
||||
empty = false;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
if (empty)
|
||||
return;
|
||||
|
||||
/* Render the workspace to reconfigure the clients. However, they will be visible now, so… */
|
||||
render_workspace(global_conn, output, ws);
|
||||
|
||||
/* …unless we want to see them at the moment, we should hide that workspace */
|
||||
if (visible && !hide_it)
|
||||
return;
|
||||
|
||||
/* however, if this is the current workspace, we only need to adjust
|
||||
* the output’s current_workspace pointer (and must not unmap the
|
||||
* windows) */
|
||||
if (c_ws == ws) {
|
||||
DLOG("Need to adjust output->current_workspace...\n");
|
||||
output->current_workspace = c_ws;
|
||||
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
workspace_unmap_clients(global_conn, ws);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -250,35 +268,43 @@ static i3Screen *get_screen_from_preference(struct screens_head *slist, char *pr
|
|||
* the screen is not attached at the moment.
|
||||
*
|
||||
*/
|
||||
void workspace_initialize(Workspace *ws, i3Screen *screen) {
|
||||
if (ws->screen != NULL) {
|
||||
LOG("Workspace already initialized\n");
|
||||
void workspace_initialize(Workspace *ws, Output *output, bool recheck) {
|
||||
Output *old_output;
|
||||
|
||||
if (ws->output != NULL && !recheck) {
|
||||
DLOG("Workspace already initialized\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* If this workspace has no preferred screen or if the screen it wants
|
||||
* to be on is not available at the moment, we initialize it with
|
||||
* the screen which was given */
|
||||
if (ws->preferred_screen == NULL ||
|
||||
(ws->screen = get_screen_from_preference(virtual_screens, ws->preferred_screen)) == NULL)
|
||||
ws->screen = screen;
|
||||
old_output = ws->output;
|
||||
|
||||
/* Copy the dimensions from the virtual screen */
|
||||
memcpy(&(ws->rect), &(ws->screen->rect), sizeof(Rect));
|
||||
/* If this workspace has no preferred output or if the output it wants
|
||||
* to be on is not available at the moment, we initialize it with
|
||||
* the output which was given */
|
||||
if (ws->preferred_output == NULL ||
|
||||
(ws->output = get_output_by_name(ws->preferred_output)) == NULL)
|
||||
ws->output = output;
|
||||
|
||||
DLOG("old_output = %p, ws->output = %p\n", old_output, ws->output);
|
||||
/* If the assignment did not change, we do not need to update anything */
|
||||
if (old_output != NULL && ws->output == old_output)
|
||||
return;
|
||||
|
||||
workspace_assign_to(ws, ws->output, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets the first unused workspace for the given screen, taking into account
|
||||
* the preferred_screen setting of every workspace (workspace assignments).
|
||||
* the preferred_output setting of every workspace (workspace assignments).
|
||||
*
|
||||
*/
|
||||
Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen *screen) {
|
||||
Workspace *get_first_workspace_for_output(Output *output) {
|
||||
Workspace *result = NULL;
|
||||
|
||||
Workspace *ws;
|
||||
TAILQ_FOREACH(ws, workspaces, workspaces) {
|
||||
if (ws->preferred_screen == NULL ||
|
||||
!screens_are_equal(get_screen_from_preference(slist, ws->preferred_screen), screen))
|
||||
if (ws->preferred_output == NULL ||
|
||||
get_output_by_name(ws->preferred_output) != output)
|
||||
continue;
|
||||
|
||||
result = ws;
|
||||
|
@ -287,9 +313,8 @@ Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen *
|
|||
|
||||
if (result == NULL) {
|
||||
/* No assignment found, returning first unused workspace */
|
||||
Workspace *ws;
|
||||
TAILQ_FOREACH(ws, workspaces, workspaces) {
|
||||
if (ws->screen != NULL)
|
||||
if (ws->output != NULL)
|
||||
continue;
|
||||
|
||||
result = ws;
|
||||
|
@ -298,16 +323,15 @@ Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen *
|
|||
}
|
||||
|
||||
if (result == NULL) {
|
||||
LOG("No existing free workspace found to assign, creating a new one\n");
|
||||
DLOG("No existing free workspace found to assign, creating a new one\n");
|
||||
|
||||
Workspace *ws;
|
||||
int last_ws = 0;
|
||||
TAILQ_FOREACH(ws, workspaces, workspaces)
|
||||
last_ws = ws->num;
|
||||
result = workspace_get(last_ws + 1);
|
||||
}
|
||||
|
||||
workspace_initialize(result, screen);
|
||||
workspace_initialize(result, output, false);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -359,7 +383,7 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) {
|
|||
int unmapped_clients = 0;
|
||||
FOR_TABLE(u_ws)
|
||||
CIRCLEQ_FOREACH(client, &(u_ws->table[cols][rows]->clients), clients) {
|
||||
LOG("unmapping normal client %p / %p / %p\n", client, client->frame, client->child);
|
||||
DLOG("unmapping normal client %p / %p / %p\n", client, client->frame, client->child);
|
||||
client_unmap(conn, client);
|
||||
unmapped_clients++;
|
||||
}
|
||||
|
@ -369,7 +393,7 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) {
|
|||
if (!client_is_floating(client))
|
||||
continue;
|
||||
|
||||
LOG("unmapping floating client %p / %p / %p\n", client, client->frame, client->child);
|
||||
DLOG("unmapping floating client %p / %p / %p\n", client, client->frame, client->child);
|
||||
|
||||
client_unmap(conn, client);
|
||||
unmapped_clients++;
|
||||
|
@ -380,15 +404,15 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) {
|
|||
if (unmapped_clients == 0 && u_ws != c_ws) {
|
||||
/* Re-assign the workspace of all dock clients which use this workspace */
|
||||
Client *dock;
|
||||
LOG("workspace %p is empty\n", u_ws);
|
||||
SLIST_FOREACH(dock, &(u_ws->screen->dock_clients), dock_clients) {
|
||||
DLOG("workspace %p is empty\n", u_ws);
|
||||
SLIST_FOREACH(dock, &(u_ws->output->dock_clients), dock_clients) {
|
||||
if (dock->workspace != u_ws)
|
||||
continue;
|
||||
|
||||
LOG("Re-assigning dock client to c_ws (%p)\n", c_ws);
|
||||
DLOG("Re-assigning dock client to c_ws (%p)\n", c_ws);
|
||||
dock->workspace = c_ws;
|
||||
}
|
||||
u_ws->screen = NULL;
|
||||
u_ws->output = NULL;
|
||||
}
|
||||
|
||||
/* Unmap the stack windows on the given workspace, if any */
|
||||
|
@ -406,16 +430,50 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) {
|
|||
*/
|
||||
void workspace_update_urgent_flag(Workspace *ws) {
|
||||
Client *current;
|
||||
bool old_flag = ws->urgent;
|
||||
bool urgent = false;
|
||||
|
||||
SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) {
|
||||
if (!current->urgent)
|
||||
continue;
|
||||
|
||||
ws->urgent = true;
|
||||
return;
|
||||
urgent = true;
|
||||
break;
|
||||
}
|
||||
|
||||
ws->urgent = false;
|
||||
ws->urgent = urgent;
|
||||
|
||||
if (old_flag != urgent)
|
||||
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"urgent\"}");
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the width of the workspace.
|
||||
*
|
||||
*/
|
||||
int workspace_width(Workspace *ws) {
|
||||
return ws->rect.width;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the effective height of the workspace (without the internal bar and
|
||||
* without dock clients).
|
||||
*
|
||||
*/
|
||||
int workspace_height(Workspace *ws) {
|
||||
int height = ws->rect.height;
|
||||
i3Font *font = load_font(global_conn, config.font);
|
||||
|
||||
/* Reserve space for dock clients */
|
||||
Client *client;
|
||||
SLIST_FOREACH(client, &(ws->output->dock_clients), dock_clients)
|
||||
height -= client->desired_height;
|
||||
|
||||
/* Space for the internal bar */
|
||||
if (!config.disable_workspace_bar)
|
||||
height -= (font->height + 6);
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
42
src/xcb.c
42
src/xcb.c
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
|
@ -21,6 +21,7 @@
|
|||
#include "i3.h"
|
||||
#include "util.h"
|
||||
#include "xcb.h"
|
||||
#include "log.h"
|
||||
|
||||
TAILQ_HEAD(cached_fonts_head, Font) cached_fonts = TAILQ_HEAD_INITIALIZER(cached_fonts);
|
||||
unsigned int xcb_numlock_mask;
|
||||
|
@ -98,14 +99,6 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_cl
|
|||
/* If the window class is XCB_WINDOW_CLASS_INPUT_ONLY, depth has to be 0 */
|
||||
uint16_t depth = (window_class == XCB_WINDOW_CLASS_INPUT_ONLY ? 0 : XCB_COPY_FROM_PARENT);
|
||||
|
||||
/* Use the default cursor (left pointer) */
|
||||
if (cursor > -1) {
|
||||
i3Font *cursor_font = load_font(conn, "cursor");
|
||||
xcb_create_glyph_cursor(conn, cursor_id, cursor_font->id, cursor_font->id,
|
||||
XCB_CURSOR_LEFT_PTR, XCB_CURSOR_LEFT_PTR + 1,
|
||||
0, 0, 0, 65535, 65535, 65535);
|
||||
}
|
||||
|
||||
xcb_create_window(conn,
|
||||
depth,
|
||||
result, /* the window id */
|
||||
|
@ -117,8 +110,14 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_cl
|
|||
mask,
|
||||
values);
|
||||
|
||||
if (cursor > -1)
|
||||
xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id);
|
||||
/* Set the cursor */
|
||||
i3Font *cursor_font = load_font(conn, "cursor");
|
||||
xcb_create_glyph_cursor(conn, cursor_id, cursor_font->id, cursor_font->id,
|
||||
(cursor == -1 ? XCB_CURSOR_LEFT_PTR : cursor),
|
||||
(cursor == -1 ? XCB_CURSOR_LEFT_PTR : cursor) + 1,
|
||||
0, 0, 0, 65535, 65535, 65535);
|
||||
xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id);
|
||||
xcb_free_cursor(conn, cursor_id);
|
||||
|
||||
/* Map the window (= make it visible) */
|
||||
if (map)
|
||||
|
@ -270,7 +269,7 @@ void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window) {
|
|||
*
|
||||
*/
|
||||
void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap) {
|
||||
LOG("preparing pixmap\n");
|
||||
DLOG("preparing pixmap\n");
|
||||
|
||||
/* If the Rect did not change, the pixmap does not need to be recreated */
|
||||
if (memcmp(&(pixmap->rect), pixmap->referred_rect, sizeof(Rect)) == 0)
|
||||
|
@ -279,11 +278,11 @@ void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap)
|
|||
memcpy(&(pixmap->rect), pixmap->referred_rect, sizeof(Rect));
|
||||
|
||||
if (pixmap->id == 0 || pixmap->gc == 0) {
|
||||
LOG("Creating new pixmap...\n");
|
||||
DLOG("Creating new pixmap...\n");
|
||||
pixmap->id = xcb_generate_id(conn);
|
||||
pixmap->gc = xcb_generate_id(conn);
|
||||
} else {
|
||||
LOG("Re-creating this pixmap...\n");
|
||||
DLOG("Re-creating this pixmap...\n");
|
||||
xcb_free_gc(conn, pixmap->gc);
|
||||
xcb_free_pixmap(conn, pixmap->id);
|
||||
}
|
||||
|
@ -309,7 +308,7 @@ int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *t
|
|||
|
||||
cookie = xcb_query_text_extents(conn, font->id, length, (xcb_char2b_t*)text);
|
||||
if ((reply = xcb_query_text_extents_reply(conn, cookie, &error)) == NULL) {
|
||||
LOG("Could not get text extents (X error code %d)\n",
|
||||
ELOG("Could not get text extents (X error code %d)\n",
|
||||
error->error_code);
|
||||
/* We return the rather safe guess of 7 pixels, because a
|
||||
* rendering error is better than a crash. Plus, the user will
|
||||
|
@ -321,3 +320,16 @@ int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *t
|
|||
free(reply);
|
||||
return width;
|
||||
}
|
||||
|
||||
/*
|
||||
* Configures the given window to have the size/position specified by given rect
|
||||
*
|
||||
*/
|
||||
void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r) {
|
||||
xcb_configure_window(conn, window,
|
||||
XCB_CONFIG_WINDOW_X |
|
||||
XCB_CONFIG_WINDOW_Y |
|
||||
XCB_CONFIG_WINDOW_WIDTH |
|
||||
XCB_CONFIG_WINDOW_HEIGHT,
|
||||
&(r.x));
|
||||
}
|
||||
|
|
435
src/xinerama.c
435
src/xinerama.c
|
@ -3,234 +3,96 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
*
|
||||
* © 2009 Michael Stapelberg and contributors
|
||||
* © 2009-2010 Michael Stapelberg and contributors
|
||||
*
|
||||
* See file LICENSE for license information.
|
||||
*
|
||||
* This is LEGACY code (we support RandR, which can do much more than
|
||||
* Xinerama), but necessary for the poor users of the nVidia binary
|
||||
* driver which does not support RandR in 2010 *sigh*.
|
||||
*
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <assert.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xinerama.h>
|
||||
|
||||
#include "queue.h"
|
||||
#include "i3.h"
|
||||
#include "data.h"
|
||||
#include "table.h"
|
||||
#include "util.h"
|
||||
#include "xinerama.h"
|
||||
#include "layout.h"
|
||||
#include "xcb.h"
|
||||
#include "config.h"
|
||||
#include "workspace.h"
|
||||
#include "log.h"
|
||||
#include "randr.h"
|
||||
|
||||
/* This TAILQ of i3Screens stores the virtual screens, used for handling overlapping screens
|
||||
* (xrandr --same-as) */
|
||||
struct screens_head *virtual_screens;
|
||||
|
||||
static bool xinerama_enabled = true;
|
||||
static int num_screens;
|
||||
|
||||
/*
|
||||
* Returns true if both screen objects describe the same screen (checks their
|
||||
* size and position).
|
||||
* Looks in outputs for the Output whose start coordinates are x, y
|
||||
*
|
||||
*/
|
||||
bool screens_are_equal(i3Screen *screen1, i3Screen *screen2) {
|
||||
/* If one of both objects (or both) are NULL, we cannot compare them */
|
||||
if (screen1 == NULL || screen2 == NULL)
|
||||
return false;
|
||||
|
||||
/* If the pointers are equal, take the short-circuit */
|
||||
if (screen1 == screen2)
|
||||
return true;
|
||||
|
||||
/* Compare their size - other properties are not relevant to determine
|
||||
* if a screen is equal to another one */
|
||||
return (memcmp(&(screen1->rect), &(screen2->rect), sizeof(Rect)) == 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Looks in virtual_screens for the i3Screen whose start coordinates are x, y
|
||||
*
|
||||
*/
|
||||
i3Screen *get_screen_at(int x, int y, struct screens_head *screenlist) {
|
||||
i3Screen *screen;
|
||||
TAILQ_FOREACH(screen, screenlist, screens)
|
||||
if (screen->rect.x == x && screen->rect.y == y)
|
||||
return screen;
|
||||
static Output *get_screen_at(int x, int y) {
|
||||
Output *output;
|
||||
TAILQ_FOREACH(output, &outputs, outputs)
|
||||
if (output->rect.x == x && output->rect.y == y)
|
||||
return output;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Looks in virtual_screens for the i3Screen which contains coordinates x, y
|
||||
*
|
||||
*/
|
||||
i3Screen *get_screen_containing(int x, int y) {
|
||||
i3Screen *screen;
|
||||
TAILQ_FOREACH(screen, virtual_screens, screens) {
|
||||
LOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n",
|
||||
x, y, screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
|
||||
if (x >= screen->rect.x && x < (screen->rect.x + screen->rect.width) &&
|
||||
y >= screen->rect.y && y < (screen->rect.y + screen->rect.height))
|
||||
return screen;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets the screen which is the last one in the given direction, for example the screen
|
||||
* on the most bottom when direction == D_DOWN, the screen most right when direction == D_RIGHT
|
||||
* and so on.
|
||||
*
|
||||
* This function always returns a screen.
|
||||
*
|
||||
*/
|
||||
i3Screen *get_screen_most(direction_t direction, i3Screen *current) {
|
||||
i3Screen *screen, *candidate = NULL;
|
||||
int position = 0;
|
||||
TAILQ_FOREACH(screen, virtual_screens, screens) {
|
||||
/* Repeated calls of WIN determine the winner of the comparison */
|
||||
#define WIN(variable, condition) \
|
||||
if (variable condition) { \
|
||||
candidate = screen; \
|
||||
position = variable; \
|
||||
} \
|
||||
break;
|
||||
|
||||
if (((direction == D_UP) || (direction == D_DOWN)) &&
|
||||
(current->rect.x != screen->rect.x))
|
||||
continue;
|
||||
|
||||
if (((direction == D_LEFT) || (direction == D_RIGHT)) &&
|
||||
(current->rect.y != screen->rect.y))
|
||||
continue;
|
||||
|
||||
switch (direction) {
|
||||
case D_UP:
|
||||
WIN(screen->rect.y, <= position);
|
||||
case D_DOWN:
|
||||
WIN(screen->rect.y, >= position);
|
||||
case D_LEFT:
|
||||
WIN(screen->rect.x, <= position);
|
||||
case D_RIGHT:
|
||||
WIN(screen->rect.x, >= position);
|
||||
}
|
||||
}
|
||||
|
||||
assert(candidate != NULL);
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
static void initialize_screen(xcb_connection_t *conn, i3Screen *screen, Workspace *workspace) {
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
|
||||
workspace->screen = screen;
|
||||
screen->current_workspace = workspace;
|
||||
|
||||
/* Create a bar for each screen */
|
||||
Rect bar_rect = {screen->rect.x,
|
||||
screen->rect.y + screen->rect.height - (font->height + 6),
|
||||
screen->rect.x + screen->rect.width,
|
||||
font->height + 6};
|
||||
uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
|
||||
uint32_t values[] = {1, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS};
|
||||
screen->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, true, mask, values);
|
||||
screen->bargc = xcb_generate_id(conn);
|
||||
xcb_create_gc(conn, screen->bargc, screen->bar, 0, 0);
|
||||
|
||||
SLIST_INIT(&(screen->dock_clients));
|
||||
|
||||
LOG("that is virtual screen at %d x %d with %d x %d\n",
|
||||
screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
|
||||
}
|
||||
|
||||
/*
|
||||
* Fills virtual_screens with exactly one screen with width/height of the whole X server.
|
||||
*
|
||||
*/
|
||||
static void disable_xinerama(xcb_connection_t *conn) {
|
||||
xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
|
||||
|
||||
i3Screen *s = calloc(sizeof(i3Screen), 1);
|
||||
|
||||
s->rect.x = 0;
|
||||
s->rect.y = 0;
|
||||
s->rect.width = root_screen->width_in_pixels;
|
||||
s->rect.height = root_screen->height_in_pixels;
|
||||
|
||||
num_screens = 1;
|
||||
s->num = 0;
|
||||
|
||||
TAILQ_INSERT_TAIL(virtual_screens, s, screens);
|
||||
|
||||
xinerama_enabled = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets the Xinerama screens and converts them to virtual i3Screens (only one screen for two
|
||||
* Gets the Xinerama screens and converts them to virtual Outputs (only one screen for two
|
||||
* Xinerama screen which are configured in clone mode) in the given screenlist
|
||||
*
|
||||
*/
|
||||
static void query_screens(xcb_connection_t *conn, struct screens_head *screenlist) {
|
||||
static void query_screens(xcb_connection_t *conn) {
|
||||
xcb_xinerama_query_screens_reply_t *reply;
|
||||
xcb_xinerama_screen_info_t *screen_info;
|
||||
time_t before_trying = time(NULL);
|
||||
|
||||
/* Try repeatedly to find screens (there might be short timeframes in
|
||||
* which the X server does not return any screens, such as when rotating
|
||||
* screens), but not longer than 5 seconds (strictly speaking, only four
|
||||
* seconds of trying are guaranteed due to the 1-second-resolution) */
|
||||
while ((time(NULL) - before_trying) < 5) {
|
||||
reply = xcb_xinerama_query_screens_reply(conn, xcb_xinerama_query_screens_unchecked(conn), NULL);
|
||||
if (!reply) {
|
||||
LOG("Couldn't get Xinerama screens\n");
|
||||
return;
|
||||
}
|
||||
screen_info = xcb_xinerama_query_screens_screen_info(reply);
|
||||
int screens = xcb_xinerama_query_screens_screen_info_length(reply);
|
||||
num_screens = 0;
|
||||
reply = xcb_xinerama_query_screens_reply(conn, xcb_xinerama_query_screens_unchecked(conn), NULL);
|
||||
if (!reply) {
|
||||
ELOG("Couldn't get Xinerama screens\n");
|
||||
return;
|
||||
}
|
||||
screen_info = xcb_xinerama_query_screens_screen_info(reply);
|
||||
int screens = xcb_xinerama_query_screens_screen_info_length(reply);
|
||||
|
||||
for (int screen = 0; screen < screens; screen++) {
|
||||
i3Screen *s = get_screen_at(screen_info[screen].x_org, screen_info[screen].y_org, screenlist);
|
||||
if (s != NULL) {
|
||||
/* This screen already exists. We use the littlest screen so that the user
|
||||
can always see the complete workspace */
|
||||
s->rect.width = min(s->rect.width, screen_info[screen].width);
|
||||
s->rect.height = min(s->rect.height, screen_info[screen].height);
|
||||
} else {
|
||||
s = calloc(sizeof(i3Screen), 1);
|
||||
s->rect.x = screen_info[screen].x_org;
|
||||
s->rect.y = screen_info[screen].y_org;
|
||||
s->rect.width = screen_info[screen].width;
|
||||
s->rect.height = screen_info[screen].height;
|
||||
/* We always treat the screen at 0x0 as the primary screen */
|
||||
if (s->rect.x == 0 && s->rect.y == 0)
|
||||
TAILQ_INSERT_HEAD(screenlist, s, screens);
|
||||
else TAILQ_INSERT_TAIL(screenlist, s, screens);
|
||||
num_screens++;
|
||||
}
|
||||
|
||||
LOG("found Xinerama screen: %d x %d at %d x %d\n",
|
||||
screen_info[screen].width, screen_info[screen].height,
|
||||
screen_info[screen].x_org, screen_info[screen].y_org);
|
||||
for (int screen = 0; screen < screens; screen++) {
|
||||
Output *s = get_screen_at(screen_info[screen].x_org, screen_info[screen].y_org);
|
||||
if (s != NULL) {
|
||||
DLOG("Re-used old Xinerama screen %p\n", s);
|
||||
/* This screen already exists. We use the littlest screen so that the user
|
||||
can always see the complete workspace */
|
||||
s->rect.width = min(s->rect.width, screen_info[screen].width);
|
||||
s->rect.height = min(s->rect.height, screen_info[screen].height);
|
||||
} else {
|
||||
s = scalloc(sizeof(Output));
|
||||
asprintf(&(s->name), "xinerama-%d", num_screens);
|
||||
DLOG("Created new Xinerama screen %s (%p)\n", s->name, s);
|
||||
s->active = true;
|
||||
s->rect.x = screen_info[screen].x_org;
|
||||
s->rect.y = screen_info[screen].y_org;
|
||||
s->rect.width = screen_info[screen].width;
|
||||
s->rect.height = screen_info[screen].height;
|
||||
/* We always treat the screen at 0x0 as the primary screen */
|
||||
if (s->rect.x == 0 && s->rect.y == 0)
|
||||
TAILQ_INSERT_HEAD(&outputs, s, outputs);
|
||||
else TAILQ_INSERT_TAIL(&outputs, s, outputs);
|
||||
num_screens++;
|
||||
}
|
||||
|
||||
free(reply);
|
||||
DLOG("found Xinerama screen: %d x %d at %d x %d\n",
|
||||
screen_info[screen].width, screen_info[screen].height,
|
||||
screen_info[screen].x_org, screen_info[screen].y_org);
|
||||
}
|
||||
|
||||
if (num_screens == 0) {
|
||||
LOG("No screens found. This is weird. Trying again...\n");
|
||||
continue;
|
||||
}
|
||||
free(reply);
|
||||
|
||||
break;
|
||||
if (num_screens == 0) {
|
||||
ELOG("No screens found. Please fix your setup. i3 will exit now.\n");
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -240,198 +102,27 @@ static void query_screens(xcb_connection_t *conn, struct screens_head *screenlis
|
|||
*
|
||||
*/
|
||||
void initialize_xinerama(xcb_connection_t *conn) {
|
||||
virtual_screens = scalloc(sizeof(struct screens_head));
|
||||
TAILQ_INIT(virtual_screens);
|
||||
|
||||
if (!xcb_get_extension_data(conn, &xcb_xinerama_id)->present) {
|
||||
LOG("Xinerama extension not found, disabling.\n");
|
||||
disable_xinerama(conn);
|
||||
DLOG("Xinerama extension not found, disabling.\n");
|
||||
disable_randr(conn);
|
||||
} else {
|
||||
xcb_xinerama_is_active_reply_t *reply;
|
||||
reply = xcb_xinerama_is_active_reply(conn, xcb_xinerama_is_active(conn), NULL);
|
||||
|
||||
if (reply == NULL || !reply->state) {
|
||||
LOG("Xinerama is not active (in your X-Server), disabling.\n");
|
||||
disable_xinerama(conn);
|
||||
DLOG("Xinerama is not active (in your X-Server), disabling.\n");
|
||||
disable_randr(conn);
|
||||
} else
|
||||
query_screens(conn, virtual_screens);
|
||||
query_screens(conn);
|
||||
|
||||
FREE(reply);
|
||||
}
|
||||
|
||||
i3Screen *screen;
|
||||
num_screens = 0;
|
||||
/* Just go through each workspace and associate as many screens as we can. */
|
||||
TAILQ_FOREACH(screen, virtual_screens, screens) {
|
||||
screen->num = num_screens;
|
||||
num_screens++;
|
||||
Workspace *ws = get_first_workspace_for_screen(virtual_screens, screen);
|
||||
initialize_screen(conn, screen, ws);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This is called when the rootwindow receives a configure_notify event and therefore the
|
||||
* number/position of the Xinerama screens could have changed.
|
||||
*
|
||||
*/
|
||||
void xinerama_requery_screens(xcb_connection_t *conn) {
|
||||
i3Font *font = load_font(conn, config.font);
|
||||
|
||||
/* POSSIBLE PROBLEM: Is the order of the Xinerama screens always constant? That is, can
|
||||
it change when I move the --right-of video projector to --left-of? */
|
||||
|
||||
if (!xinerama_enabled) {
|
||||
LOG("Xinerama is disabled\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* We use a separate copy to diff with the previous set of screens */
|
||||
struct screens_head *new_screens = scalloc(sizeof(struct screens_head));
|
||||
TAILQ_INIT(new_screens);
|
||||
|
||||
query_screens(conn, new_screens);
|
||||
|
||||
i3Screen *first = TAILQ_FIRST(new_screens),
|
||||
*screen,
|
||||
*old_screen;
|
||||
int screen_count = 0;
|
||||
/* Mark each workspace which currently is assigned to a screen, so we
|
||||
* can garbage-collect afterwards */
|
||||
Output *output;
|
||||
Workspace *ws;
|
||||
TAILQ_FOREACH(ws, workspaces, workspaces)
|
||||
ws->reassigned = (ws->screen == NULL);
|
||||
|
||||
TAILQ_FOREACH(screen, new_screens, screens) {
|
||||
screen->num = screen_count;
|
||||
screen->current_workspace = NULL;
|
||||
|
||||
TAILQ_FOREACH(old_screen, virtual_screens, screens) {
|
||||
if (old_screen->num != screen_count)
|
||||
continue;
|
||||
|
||||
LOG("Found a matching screen\n");
|
||||
/* Use the same workspace */
|
||||
screen->current_workspace = old_screen->current_workspace;
|
||||
|
||||
/* Re-use the old bar window */
|
||||
screen->bar = old_screen->bar;
|
||||
screen->bargc = old_screen->bargc;
|
||||
LOG("old_screen->bar = %p\n", old_screen->bar);
|
||||
|
||||
Rect bar_rect = {screen->rect.x,
|
||||
screen->rect.y + screen->rect.height - (font->height + 6),
|
||||
screen->rect.x + screen->rect.width,
|
||||
font->height + 6};
|
||||
|
||||
LOG("configuring bar to be at %d x %d with %d x %d\n",
|
||||
bar_rect.x, bar_rect.y, bar_rect.height, bar_rect.width);
|
||||
xcb_configure_window(conn, screen->bar, XCB_CONFIG_WINDOW_X |
|
||||
XCB_CONFIG_WINDOW_Y |
|
||||
XCB_CONFIG_WINDOW_WIDTH |
|
||||
XCB_CONFIG_WINDOW_HEIGHT, &(bar_rect.x));
|
||||
|
||||
/* Copy the list head for the dock clients */
|
||||
screen->dock_clients = old_screen->dock_clients;
|
||||
SLIST_INIT(&(old_screen->dock_clients));
|
||||
|
||||
/* Update the dimensions */
|
||||
Workspace *ws;
|
||||
TAILQ_FOREACH(ws, workspaces, workspaces) {
|
||||
if (ws->screen != old_screen)
|
||||
continue;
|
||||
|
||||
LOG("re-assigning ws %d\n", ws->num);
|
||||
memcpy(&(ws->rect), &(screen->rect), sizeof(Rect));
|
||||
ws->screen = screen;
|
||||
ws->reassigned = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
if (screen->current_workspace == NULL) {
|
||||
/* Find the first unused workspace, preferring the ones
|
||||
* which are assigned to this screen and initialize
|
||||
* the screen with it. */
|
||||
LOG("getting first ws for screen %p\n", screen);
|
||||
Workspace *ws = get_first_workspace_for_screen(new_screens, screen);
|
||||
initialize_screen(conn, screen, ws);
|
||||
ws->reassigned = true;
|
||||
|
||||
/* As this workspace just got visible (we got a new screen
|
||||
* without workspace), we need to map its clients */
|
||||
workspace_map_clients(conn, ws);
|
||||
}
|
||||
screen_count++;
|
||||
/* Just go through each active output and associate one workspace */
|
||||
TAILQ_FOREACH(output, &outputs, outputs) {
|
||||
ws = get_first_workspace_for_output(output);
|
||||
initialize_output(conn, output, ws);
|
||||
}
|
||||
|
||||
/* check for dock_clients which are out of bounds */
|
||||
TAILQ_FOREACH(old_screen, virtual_screens, screens) {
|
||||
if (SLIST_EMPTY(&(old_screen->dock_clients)))
|
||||
continue;
|
||||
|
||||
LOG("dock_clients out of bounds at screen %p, reassigning\n", old_screen);
|
||||
if (SLIST_EMPTY(&(first->dock_clients))) {
|
||||
first->dock_clients = old_screen->dock_clients;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* We need to merge the lists */
|
||||
Client *dock_client;
|
||||
|
||||
while (!SLIST_EMPTY(&(old_screen->dock_clients))) {
|
||||
dock_client = SLIST_FIRST(&(old_screen->dock_clients));
|
||||
SLIST_INSERT_HEAD(&(first->dock_clients), dock_client, dock_clients);
|
||||
SLIST_REMOVE_HEAD(&(old_screen->dock_clients), dock_clients);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for workspaces which are out of bounds */
|
||||
TAILQ_FOREACH(ws, workspaces, workspaces) {
|
||||
if (ws->reassigned)
|
||||
continue;
|
||||
|
||||
Client *client;
|
||||
|
||||
LOG("Closing bar window (%p)\n", ws->screen->bar);
|
||||
xcb_destroy_window(conn, ws->screen->bar);
|
||||
|
||||
LOG("Workspace %d's screen out of bounds, assigning to first screen\n", ws->num + 1);
|
||||
ws->screen = first;
|
||||
memcpy(&(ws->rect), &(first->rect), sizeof(Rect));
|
||||
|
||||
/* Force reconfiguration for each client on that workspace */
|
||||
FOR_TABLE(ws)
|
||||
CIRCLEQ_FOREACH(client, &(ws->table[cols][rows]->clients), clients)
|
||||
client->force_reconfigure = true;
|
||||
|
||||
/* Render the workspace to reconfigure the clients. However, they will be visible now, so… */
|
||||
render_workspace(conn, first, ws);
|
||||
|
||||
/* …unless we want to see them at the moment, we should hide that workspace */
|
||||
if (workspace_is_visible(ws))
|
||||
continue;
|
||||
|
||||
workspace_unmap_clients(conn, ws);
|
||||
|
||||
if (c_ws == ws) {
|
||||
LOG("Need to adjust c_ws...\n");
|
||||
c_ws = first->current_workspace;
|
||||
}
|
||||
}
|
||||
xcb_flush(conn);
|
||||
|
||||
/* Free the old list */
|
||||
while (!TAILQ_EMPTY(virtual_screens)) {
|
||||
screen = TAILQ_FIRST(virtual_screens);
|
||||
TAILQ_REMOVE(virtual_screens, screen, screens);
|
||||
free(screen);
|
||||
}
|
||||
free(virtual_screens);
|
||||
|
||||
virtual_screens = new_screens;
|
||||
|
||||
LOG("Current workspace is now: %d\n", first->current_workspace);
|
||||
|
||||
render_layout(conn);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!perl
|
||||
# vim:ts=4:sw=4:expandtab
|
||||
|
||||
use Test::More tests => 4;
|
||||
use Test::More tests => 3;
|
||||
use Test::Deep;
|
||||
use X11::XCB qw(:all);
|
||||
use Data::Dumper;
|
||||
|
@ -9,6 +9,7 @@ use Time::HiRes qw(sleep);
|
|||
use FindBin;
|
||||
use lib "$FindBin::Bin/lib";
|
||||
use i3test;
|
||||
use AnyEvent::I3;
|
||||
|
||||
BEGIN {
|
||||
use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX');
|
||||
|
@ -17,19 +18,14 @@ BEGIN {
|
|||
|
||||
my $x = X11::XCB::Connection->new;
|
||||
|
||||
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
|
||||
|
||||
isa_ok($sock, 'IO::Socket::UNIX');
|
||||
|
||||
my $i3 = i3;
|
||||
|
||||
#####################################################################
|
||||
# Ensure IPC works by switching workspaces
|
||||
#####################################################################
|
||||
|
||||
# Switch to the first workspace to get a clean testing environment
|
||||
$sock->write(i3test::format_ipc_command("1"));
|
||||
|
||||
sleep(0.25);
|
||||
$i3->command('1')->recv;
|
||||
|
||||
# Create a window so we can get a focus different from NULL
|
||||
my $window = i3test::open_standard_window($x);
|
||||
|
@ -41,9 +37,7 @@ my $focus = $x->input_focus;
|
|||
diag("old focus = $focus");
|
||||
|
||||
# Switch to the nineth workspace
|
||||
$sock->write(i3test::format_ipc_command("9"));
|
||||
|
||||
sleep(0.25);
|
||||
$i3->command('9')->recv;
|
||||
|
||||
my $new_focus = $x->input_focus;
|
||||
isnt($focus, $new_focus, "Focus changed");
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
# the workspace to be empty).
|
||||
# TODO: skip it by default?
|
||||
|
||||
use Test::More tests => 8;
|
||||
use Test::More tests => 13;
|
||||
use Test::Deep;
|
||||
use X11::XCB qw(:all);
|
||||
use Data::Dumper;
|
||||
|
@ -12,29 +12,25 @@ use Time::HiRes qw(sleep);
|
|||
use FindBin;
|
||||
use lib "$FindBin::Bin/lib";
|
||||
use i3test;
|
||||
use AnyEvent::I3;
|
||||
|
||||
BEGIN {
|
||||
use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX');
|
||||
use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
|
||||
}
|
||||
|
||||
my $x = X11::XCB::Connection->new;
|
||||
|
||||
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
|
||||
isa_ok($sock, 'IO::Socket::UNIX');
|
||||
my $i3 = i3;
|
||||
|
||||
# Switch to the nineth workspace
|
||||
$sock->write(i3test::format_ipc_command("9"));
|
||||
|
||||
sleep(0.25);
|
||||
$i3->command('9')->recv;
|
||||
|
||||
#####################################################################
|
||||
# Create two windows and make sure focus switching works
|
||||
#####################################################################
|
||||
|
||||
# Change mode of the container to "default" for following tests
|
||||
$sock->write(i3test::format_ipc_command("d"));
|
||||
sleep(0.25);
|
||||
$i3->command('d')->recv;
|
||||
|
||||
my $top = i3test::open_standard_window($x);
|
||||
my $mid = i3test::open_standard_window($x);
|
||||
|
@ -52,8 +48,7 @@ diag("bottom id = " . $bottom->id);
|
|||
sub focus_after {
|
||||
my $msg = shift;
|
||||
|
||||
$sock->write(i3test::format_ipc_command($msg));
|
||||
sleep(0.5);
|
||||
$i3->command($msg)->recv;
|
||||
return $x->input_focus;
|
||||
}
|
||||
|
||||
|
@ -76,4 +71,48 @@ is($focus, $bottom->id, "Bottom window focused (wrapping to the top works)");
|
|||
$focus = focus_after("j");
|
||||
is($focus, $top->id, "Top window focused (wrapping to the bottom works)");
|
||||
|
||||
###############################################
|
||||
# Test focus with empty containers and colspan
|
||||
###############################################
|
||||
|
||||
# Switch to the 10. workspace
|
||||
$i3->command('10')->recv;
|
||||
|
||||
$top = i3test::open_standard_window($x);
|
||||
$bottom = i3test::open_standard_window($x);
|
||||
sleep 0.25;
|
||||
|
||||
$focus = focus_after("mj");
|
||||
$focus = focus_after("mh");
|
||||
$focus = focus_after("k");
|
||||
is($focus, $bottom->id, "Selecting top window without snapping doesn't work");
|
||||
|
||||
$focus = focus_after("sl");
|
||||
is($focus, $bottom->id, "Bottom window focused");
|
||||
|
||||
$focus = focus_after("k");
|
||||
is($focus, $top->id, "Top window focused");
|
||||
|
||||
# Same thing, but left/right instead of top/bottom
|
||||
|
||||
# Switch to the 11. workspace
|
||||
$i3->command('11')->recv;
|
||||
|
||||
my $left = i3test::open_standard_window($x);
|
||||
my $right = i3test::open_standard_window($x);
|
||||
sleep 0.25;
|
||||
|
||||
$focus = focus_after("ml");
|
||||
$focus = focus_after("h");
|
||||
$focus = focus_after("mk");
|
||||
$focus = focus_after("l");
|
||||
is($focus, $left->id, "Selecting right window without snapping doesn't work");
|
||||
|
||||
$focus = focus_after("sj");
|
||||
is($focus, $left->id, "left window focused");
|
||||
|
||||
$focus = focus_after("l");
|
||||
is($focus, $right->id, "right window focused");
|
||||
|
||||
|
||||
diag( "Testing i3, Perl $], $^X" );
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
# the workspace to be empty).
|
||||
# TODO: skip it by default?
|
||||
|
||||
use Test::More tests => 10;
|
||||
use Test::More tests => 8;
|
||||
use Test::Deep;
|
||||
use X11::XCB qw(:all);
|
||||
use Data::Dumper;
|
||||
|
@ -12,21 +12,18 @@ use Time::HiRes qw(sleep);
|
|||
use FindBin;
|
||||
use lib "$FindBin::Bin/lib";
|
||||
use i3test;
|
||||
use AnyEvent::I3;
|
||||
|
||||
BEGIN {
|
||||
use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX');
|
||||
use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
|
||||
}
|
||||
|
||||
my $x = X11::XCB::Connection->new;
|
||||
|
||||
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
|
||||
isa_ok($sock, 'IO::Socket::UNIX');
|
||||
my $i3 = i3;
|
||||
|
||||
# Switch to the nineth workspace
|
||||
$sock->write(i3test::format_ipc_command("9"));
|
||||
|
||||
sleep(0.25);
|
||||
$i3->command('9')->recv;
|
||||
|
||||
#####################################################################
|
||||
# Create two windows and make sure focus switching works
|
||||
|
@ -50,8 +47,7 @@ diag("bottom id = " . $bottom->id);
|
|||
sub focus_after {
|
||||
my $msg = shift;
|
||||
|
||||
$sock->write(i3test::format_ipc_command($msg));
|
||||
sleep(0.5);
|
||||
$i3->command($msg)->recv;
|
||||
return $x->input_focus;
|
||||
}
|
||||
|
||||
|
@ -82,9 +78,7 @@ is($focus, $top->id, "Top window focused");
|
|||
# Move window cross-workspace
|
||||
#####################################################################
|
||||
|
||||
$sock->write(i3test::format_ipc_command("m12"));
|
||||
$sock->write(i3test::format_ipc_command("t"));
|
||||
$sock->write(i3test::format_ipc_command("m13"));
|
||||
$sock->write(i3test::format_ipc_command("12"));
|
||||
$sock->write(i3test::format_ipc_command("13"));
|
||||
for my $cmd (qw(m12 t m13 12 13)) {
|
||||
$i3->command($cmd)->recv;
|
||||
}
|
||||
ok(1, "Still living");
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# Checks if the focus is correctly restored, when creating a floating client
|
||||
# over an unfocused tiling client and destroying the floating one again.
|
||||
|
||||
use Test::More tests => 6;
|
||||
use Test::More tests => 4;
|
||||
use Test::Deep;
|
||||
use X11::XCB qw(:all);
|
||||
use Data::Dumper;
|
||||
|
@ -11,28 +11,25 @@ use Time::HiRes qw(sleep);
|
|||
use FindBin;
|
||||
use lib "$FindBin::Bin/lib";
|
||||
use i3test;
|
||||
use AnyEvent::I3;
|
||||
|
||||
BEGIN {
|
||||
use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX');
|
||||
use_ok('X11::XCB::Window') or BAIL_OUT('Could not load X11::XCB::Window');
|
||||
}
|
||||
|
||||
my $x = X11::XCB::Connection->new;
|
||||
|
||||
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
|
||||
isa_ok($sock, 'IO::Socket::UNIX');
|
||||
my $i3 = i3;
|
||||
|
||||
# Switch to the nineth workspace
|
||||
$sock->write(i3test::format_ipc_command("9"));
|
||||
|
||||
sleep(0.25);
|
||||
$i3->command('9')->recv;
|
||||
|
||||
my $tiled_left = i3test::open_standard_window($x);
|
||||
my $tiled_right = i3test::open_standard_window($x);
|
||||
|
||||
sleep(0.25);
|
||||
|
||||
$sock->write(i3test::format_ipc_command("ml"));
|
||||
$i3->command('ml')->recv;
|
||||
|
||||
# Get input focus before creating the floating window
|
||||
my $focus = $x->input_focus;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
# the workspace to be empty).
|
||||
# TODO: skip it by default?
|
||||
|
||||
use Test::More tests => 24;
|
||||
use Test::More tests => 22;
|
||||
use Test::Deep;
|
||||
use X11::XCB qw(:all);
|
||||
use Data::Dumper;
|
||||
|
@ -12,21 +12,18 @@ use Time::HiRes qw(sleep);
|
|||
use FindBin;
|
||||
use lib "$FindBin::Bin/lib";
|
||||
use i3test;
|
||||
use AnyEvent::I3;
|
||||
|
||||
BEGIN {
|
||||
use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX');
|
||||
use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
|
||||
}
|
||||
|
||||
my $x = X11::XCB::Connection->new;
|
||||
|
||||
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
|
||||
isa_ok($sock, 'IO::Socket::UNIX');
|
||||
my $i3 = i3;
|
||||
|
||||
# Switch to the nineth workspace
|
||||
$sock->write(i3test::format_ipc_command("9"));
|
||||
|
||||
sleep(0.25);
|
||||
$i3->command('9')->recv;
|
||||
|
||||
#####################################################################
|
||||
# Create two windows and make sure focus switching works
|
||||
|
@ -50,8 +47,7 @@ diag("bottom id = " . $bottom->id);
|
|||
sub focus_after {
|
||||
my $msg = shift;
|
||||
|
||||
$sock->write(i3test::format_ipc_command($msg));
|
||||
sleep(0.25);
|
||||
$i3->command($msg)->recv;
|
||||
return $x->input_focus;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@ use i3test;
|
|||
use List::Util qw(first);
|
||||
|
||||
BEGIN {
|
||||
#use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX');
|
||||
use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
|
||||
}
|
||||
|
||||
|
@ -43,4 +42,17 @@ sleep 0.25;
|
|||
my $rect = $window->rect;
|
||||
is($rect->width, $primary->rect->width, 'dock client is as wide as the screen');
|
||||
|
||||
my $fwindow = $x->root->create_child(
|
||||
class => WINDOW_CLASS_INPUT_OUTPUT,
|
||||
rect => [ 0, 0, 30, 30],
|
||||
background_color => '#FF0000',
|
||||
type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'),
|
||||
);
|
||||
|
||||
$fwindow->transient_for($window);
|
||||
$fwindow->map;
|
||||
|
||||
sleep 0.25;
|
||||
|
||||
|
||||
diag( "Testing i3, Perl $], $^X" );
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
# the workspace to be empty).
|
||||
# TODO: skip it by default?
|
||||
|
||||
use Test::More tests => 9;
|
||||
use Test::More tests => 7;
|
||||
use Test::Deep;
|
||||
use X11::XCB qw(:all);
|
||||
use Data::Dumper;
|
||||
|
@ -13,21 +13,18 @@ use FindBin;
|
|||
use Digest::SHA1 qw(sha1_base64);
|
||||
use lib "$FindBin::Bin/lib";
|
||||
use i3test;
|
||||
use AnyEvent::I3;
|
||||
|
||||
BEGIN {
|
||||
use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX');
|
||||
use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
|
||||
}
|
||||
|
||||
my $x = X11::XCB::Connection->new;
|
||||
|
||||
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
|
||||
isa_ok($sock, 'IO::Socket::UNIX');
|
||||
my $i3 = i3;
|
||||
|
||||
# Switch to the nineth workspace
|
||||
$sock->write(i3test::format_ipc_command("9"));
|
||||
|
||||
sleep(0.25);
|
||||
$i3->command('9')->recv;
|
||||
|
||||
#####################################################################
|
||||
# Create two windows and make sure focus switching works
|
||||
|
@ -51,8 +48,7 @@ diag("bottom id = " . $bottom->id);
|
|||
sub focus_after {
|
||||
my $msg = shift;
|
||||
|
||||
$sock->write(i3test::format_ipc_command($msg));
|
||||
sleep(0.5);
|
||||
$i3->command($msg)->recv;
|
||||
return $x->input_focus;
|
||||
}
|
||||
|
||||
|
@ -74,7 +70,7 @@ my $random_mark = sha1_base64(rand());
|
|||
$focus = focus_after("goto $random_mark");
|
||||
is($focus, $mid->id, "focus unchanged");
|
||||
|
||||
$sock->write(i3test::format_ipc_command("mark $random_mark"));
|
||||
$i3->command("mark $random_mark")->recv;
|
||||
|
||||
$focus = focus_after("k");
|
||||
is($focus, $top->id, "Top window focused");
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
# the workspace to be empty).
|
||||
# TODO: skip it by default?
|
||||
|
||||
use Test::More tests => 17;
|
||||
use Test::More tests => 15;
|
||||
use Test::Deep;
|
||||
use X11::XCB qw(:all);
|
||||
use Data::Dumper;
|
||||
|
@ -13,21 +13,18 @@ use FindBin;
|
|||
use Digest::SHA1 qw(sha1_base64);
|
||||
use lib "$FindBin::Bin/lib";
|
||||
use i3test;
|
||||
use AnyEvent::I3;
|
||||
|
||||
BEGIN {
|
||||
use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX');
|
||||
use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
|
||||
}
|
||||
|
||||
my $x = X11::XCB::Connection->new;
|
||||
|
||||
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
|
||||
isa_ok($sock, 'IO::Socket::UNIX');
|
||||
my $i3 = i3;
|
||||
|
||||
# Switch to the nineth workspace
|
||||
$sock->write(i3test::format_ipc_command("9"));
|
||||
|
||||
sleep 0.25;
|
||||
$i3->command('9')->recv;
|
||||
|
||||
#####################################################################
|
||||
# Create a floating window and see if resizing works
|
||||
|
@ -78,13 +75,11 @@ sub test_resize {
|
|||
test_resize;
|
||||
|
||||
# Test borderless
|
||||
$sock->write(i3test::format_ipc_command("bb"));
|
||||
sleep 0.25;
|
||||
$i3->command('bb')->recv;
|
||||
|
||||
test_resize;
|
||||
|
||||
# Test with 1-px-border
|
||||
$sock->write(i3test::format_ipc_command("bp"));
|
||||
sleep 0.25;
|
||||
$i3->command('bp')->recv;
|
||||
|
||||
test_resize;
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
#!perl
|
||||
# vim:ts=4:sw=4:expandtab
|
||||
# Beware that this test uses workspace 9 to perform some tests (it expects
|
||||
# the workspace to be empty).
|
||||
# TODO: skip it by default?
|
||||
|
||||
use Test::More tests => 7;
|
||||
use Test::Deep;
|
||||
use X11::XCB qw(:all);
|
||||
use Data::Dumper;
|
||||
use Time::HiRes qw(sleep);
|
||||
use FindBin;
|
||||
use Digest::SHA1 qw(sha1_base64);
|
||||
use lib "$FindBin::Bin/lib";
|
||||
use i3test;
|
||||
use AnyEvent::I3;
|
||||
|
||||
BEGIN {
|
||||
use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
|
||||
}
|
||||
|
||||
my $x = X11::XCB::Connection->new;
|
||||
|
||||
my $i3 = i3;
|
||||
|
||||
# Switch to the nineth workspace
|
||||
$i3->command('9')->recv;
|
||||
|
||||
#####################################################################
|
||||
# Create two windows and put them in stacking mode
|
||||
#####################################################################
|
||||
|
||||
my $top = i3test::open_standard_window($x);
|
||||
sleep 0.25;
|
||||
my $bottom = i3test::open_standard_window($x);
|
||||
sleep 0.25;
|
||||
|
||||
$i3->command('s')->recv;
|
||||
|
||||
#####################################################################
|
||||
# Add the urgency hint, switch to a different workspace and back again
|
||||
#####################################################################
|
||||
$top->add_hint('urgency');
|
||||
sleep 1;
|
||||
|
||||
$i3->command('1')->recv;
|
||||
$i3->command('9')->recv;
|
||||
$i3->command('1')->recv;
|
||||
|
||||
my $std = i3test::open_standard_window($x);
|
||||
sleep 0.25;
|
||||
$std->add_hint('urgency');
|
||||
sleep 1;
|
|
@ -0,0 +1,66 @@
|
|||
#!perl
|
||||
# vim:ts=4:sw=4:expandtab
|
||||
# Beware that this test uses workspace 9 and 10 to perform some tests (it expects
|
||||
# the workspace to be empty).
|
||||
# TODO: skip it by default?
|
||||
|
||||
use Test::More tests => 3;
|
||||
use Test::Deep;
|
||||
use X11::XCB qw(:all);
|
||||
use Data::Dumper;
|
||||
use Time::HiRes qw(sleep);
|
||||
use FindBin;
|
||||
use Digest::SHA1 qw(sha1_base64);
|
||||
use lib "$FindBin::Bin/lib";
|
||||
use i3test;
|
||||
use AnyEvent::I3;
|
||||
|
||||
BEGIN {
|
||||
use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
|
||||
}
|
||||
|
||||
my $x = X11::XCB::Connection->new;
|
||||
my $i3 = i3;
|
||||
|
||||
# Switch to the nineth workspace
|
||||
$i3->command('9')->recv;
|
||||
|
||||
#####################################################################
|
||||
# Create a parent window
|
||||
#####################################################################
|
||||
|
||||
my $window = $x->root->create_child(
|
||||
class => WINDOW_CLASS_INPUT_OUTPUT,
|
||||
rect => [ 0, 0, 30, 30 ],
|
||||
background_color => '#C0C0C0',
|
||||
);
|
||||
|
||||
$window->name('Parent window');
|
||||
$window->map;
|
||||
|
||||
sleep 0.25;
|
||||
|
||||
#########################################################################
|
||||
# Switch workspace to 10 and open a child window. It should be positioned
|
||||
# on workspace 9.
|
||||
#########################################################################
|
||||
$i3->command('10')->recv;
|
||||
|
||||
my $child = $x->root->create_child(
|
||||
class => WINDOW_CLASS_INPUT_OUTPUT,
|
||||
rect => [ 0, 0, 30, 30 ],
|
||||
background_color => '#C0C0C0',
|
||||
);
|
||||
|
||||
$child->name('Child window');
|
||||
$child->client_leader($window);
|
||||
$child->map;
|
||||
|
||||
sleep 0.25;
|
||||
|
||||
isnt($x->input_focus, $child->id, "Child window focused");
|
||||
|
||||
# Switch back
|
||||
$i3->command('9')->recv;
|
||||
|
||||
is($x->input_focus, $child->id, "Child window focused");
|
|
@ -0,0 +1,25 @@
|
|||
#!perl
|
||||
# vim:ts=4:sw=4:expandtab
|
||||
|
||||
use Test::More tests => 3;
|
||||
use Test::Exception;
|
||||
use List::MoreUtils qw(all);
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/lib";
|
||||
use i3test;
|
||||
use AnyEvent::I3;
|
||||
|
||||
my $i3 = i3;
|
||||
|
||||
####################
|
||||
# Request workspaces
|
||||
####################
|
||||
|
||||
my $workspaces = $i3->get_workspaces->recv;
|
||||
|
||||
ok(@{$workspaces} > 0, "More than zero workspaces found");
|
||||
|
||||
my $name_exists = all { defined($_->{name}) } @{$workspaces};
|
||||
ok($name_exists, "All workspaces have a name");
|
||||
|
||||
diag( "Testing i3, Perl $], $^X" );
|
|
@ -29,15 +29,4 @@ sub open_standard_window {
|
|||
return $window;
|
||||
}
|
||||
|
||||
sub format_ipc_command {
|
||||
my $msg = shift;
|
||||
my $len;
|
||||
|
||||
{ use bytes; $len = length($msg); }
|
||||
|
||||
my $message = "i3-ipc" . pack("LL", $len, 0) . $msg;
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
1
|
||||
|
|
Loading…
Reference in New Issue