Merge branch 'next'

This commit is contained in:
Michael Stapelberg
2010-03-30 13:06:41 +02:00
88 changed files with 7702 additions and 2593 deletions

17
CMDMODE
View File

@ -2,7 +2,8 @@
- Command mode - 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> left := <h> | <cursor-left>
right := <l> | <cursor-right> right := <l> | <cursor-right>
@ -17,12 +18,14 @@ cmd := [ <times> ] [ <move> | <snap> ] <where>
with := <w> { [ <times> ] <where> }+ <space> <cmd> with := <w> { [ <times> ] <where> }+ <space> <cmd>
jump := [ "<window class>[/<window title>]" | <workspace> [ <column> <row> ] ] jump := [ "<window class>[/<window title>]" | <workspace> [ <column> <row> ] ]
focus := focus [ <times> | floating | tiling | ft ] focus := focus [ <times> | floating | tiling | ft ]
(travels the focus stack backwards the given amount of times (by default 1), so (travels the focus stack backwards, <times> number of times (by default 1).
it selects the window which had the focus before you focused the current one when So by specifying "focus 1" it selects the window which last had the focus
specifying "focus 1". before you focused the current one window.
The special values 'floating' (select the next floating window), 'tiling' The following 3 special values are also valid:
(select the next tiling window), 'ft' (if the current window is floating, 'floating' (select the next floating window).
select the next tiling window and vice-versa) are also valid) '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 ] special := [ exec <path> | kill | exit | restart ]
input := [ <cmd> | <with> | <jump> | <focus> | <special> ] input := [ <cmd> | <with> | <jump> | <focus> | <special> ]

View File

@ -1,12 +1,14 @@
You need the following libraries. The version given is to be understand as the minimum You need the following libraries. The version given is to be understood as the
version. However, if any of these libraries changes the API, i3 may not compile anymore. minimum version required. However, if any of these libraries changes the API,
In that case, please try using the versions mentioned below until a fix is provided. 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) * xcb-proto-1.3 (2008-12-10)
* libxcb-1.1.93 (2008-12-11) * libxcb-1.1.93 (2008-12-11)
* xcb-util-0.3.3 (2009-01-31) * xcb-util-0.3.3 (2009-01-31)
* libev * libev
* flex and bison * flex and bison
* yajl (the IPC interface uses JSON to serialize data)
* asciidoc >= 8.3.0 for docs/hacking-howto * asciidoc >= 8.3.0 for docs/hacking-howto
* asciidoc, xmlto, docbook-xml for man/i3.man * asciidoc, xmlto, docbook-xml for man/i3.man
* Xlib, the one that comes with your X-Server * 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://libev.schmorp.de/
http://flex.sourceforge.net/ http://flex.sourceforge.net/
http://www.gnu.org/software/bison/ http://www.gnu.org/software/bison/
http://lloyd.github.com/yajl/
http://i3.zekjur.net/i3lock/ http://i3.zekjur.net/i3lock/
http://tools.suckless.org/dmenu http://tools.suckless.org/dmenu

View File

@ -6,12 +6,22 @@ include $(TOPDIR)/common.mk
AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c
FILES:=$(filter-out $(AUTOGENERATED),$(wildcard src/*.c)) FILES:=$(filter-out $(AUTOGENERATED),$(wildcard src/*.c))
FILES:=$(FILES:.c=.o) 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 # Depend on the specific file (.c for each .o) and on all headers
src/%.o: src/%.c ${HEADERS} src/%.o: src/%.c ${HEADERS}
echo "CC $<" 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} all: src/cfgparse.y.o src/cfgparse.yy.o ${FILES}
echo "LINK i3" echo "LINK i3"
@ -22,25 +32,40 @@ all: src/cfgparse.y.o src/cfgparse.yy.o ${FILES}
echo "SUBDIR i3-input" echo "SUBDIR i3-input"
$(MAKE) TOPDIR=$(TOPDIR) -C 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 $<" echo "LEX $<"
flex -i -o$(@:.o=.c) $< 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 $<" echo "YACC $<"
bison --debug --verbose -b $(basename $< .y) -d $< 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 install: all
echo "INSTALL" echo "INSTALL"
$(INSTALL) -d -m 0755 $(DESTDIR)/usr/bin $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
$(INSTALL) -d -m 0755 $(DESTDIR)/etc/i3 $(INSTALL) -d -m 0755 $(DESTDIR)$(SYSCONFDIR)/i3
$(INSTALL) -d -m 0755 $(DESTDIR)/usr/share/xsessions $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/include/i3
$(INSTALL) -m 0755 i3 $(DESTDIR)/usr/bin/ $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/xsessions
test -e $(DESTDIR)/etc/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)/etc/i3/config $(INSTALL) -m 0755 i3 $(DESTDIR)$(PREFIX)/bin/
$(INSTALL) -m 0644 i3.welcome $(DESTDIR)/etc/i3/welcome $(INSTALL) -m 0755 i3-wsbar $(DESTDIR)$(PREFIX)/bin/
$(INSTALL) -m 0644 i3.desktop $(DESTDIR)/usr/share/xsessions/ 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-msg install
$(MAKE) TOPDIR=$(TOPDIR) -C i3-input install $(MAKE) TOPDIR=$(TOPDIR) -C i3-input install
@ -48,7 +73,7 @@ dist: distclean
[ ! -d i3-${VERSION} ] || rm -rf i3-${VERSION} [ ! -d i3-${VERSION} ] || rm -rf i3-${VERSION}
[ ! -e i3-${VERSION}.tar.bz2 ] || rm i3-${VERSION}.tar.bz2 [ ! -e i3-${VERSION}.tar.bz2 ] || rm i3-${VERSION}.tar.bz2
mkdir i3-${VERSION} 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} cp -r src i3-msg include man i3-${VERSION}
# Only copy toplevel documentation (important stuff) # Only copy toplevel documentation (important stuff)
mkdir i3-${VERSION}/docs mkdir i3-${VERSION}/docs
@ -64,7 +89,7 @@ dist: distclean
rm -rf i3-${VERSION} rm -rf i3-${VERSION}
clean: 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 docs clean
$(MAKE) -C man clean $(MAKE) -C man clean
$(MAKE) TOPDIR=$(TOPDIR) -C i3-msg clean $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg clean

View File

@ -1,11 +1,15 @@
Dear package maintainer, 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 Please read the file DEPENDS now, so you know which libraries are necessary
get them from if your distribution does not already have packages for them. 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: On debian, this looks like this:
# Compilation # Compilation
@ -17,10 +21,11 @@ On debian, this looks like this:
mkdir -p $(CURDIR)/debian/i3-wm/usr/share/man/man1 mkdir -p $(CURDIR)/debian/i3-wm/usr/share/man/man1
cp man/i3.1 $(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 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 hesitate to contact me. I will help you out. Just drop me an E-Mail (find the
http://michael.stapelberg.de/Kontakt, scroll down to bottom), contact me using the same address at http://michael.stapelberg.de/Kontakt, scroll down to bottom),
address in jabber or ask on our IRC channel (#i3 on irc.twice-irc.de). 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, Thanks again for your efforts,
Michael Michael

View File

@ -1,13 +1,21 @@
UNAME=$(shell uname) UNAME=$(shell uname)
DEBUG=1 DEBUG=1
INSTALL=install 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) VERSION:=$(shell git describe --tags --abbrev=0)
CFLAGS += -std=c99 CFLAGS += -std=c99
CFLAGS += -pipe CFLAGS += -pipe
CFLAGS += -Wall CFLAGS += -Wall
CFLAGS += -Wunused # unused-function, unused-label, unused-variable are turned on by -Wall
# We dont want unused-parameter because of the use of many callbacks
CFLAGS += -Wunused-value
CFLAGS += -Iinclude CFLAGS += -Iinclude
CFLAGS += -I/usr/local/include CFLAGS += -I/usr/local/include
CFLAGS += -DI3_VERSION=\"${GIT_VERSION}\" CFLAGS += -DI3_VERSION=\"${GIT_VERSION}\"
@ -36,7 +44,9 @@ LDFLAGS += -lxcb-atom
LDFLAGS += -lxcb-aux LDFLAGS += -lxcb-aux
LDFLAGS += -lxcb-icccm LDFLAGS += -lxcb-icccm
LDFLAGS += -lxcb-xinerama LDFLAGS += -lxcb-xinerama
LDFLAGS += -lxcb-randr
LDFLAGS += -lxcb LDFLAGS += -lxcb
LDFLAGS += -lyajl
LDFLAGS += -lX11 LDFLAGS += -lX11
LDFLAGS += -lev LDFLAGS += -lev
LDFLAGS += -L/usr/local/lib -L/usr/pkg/lib 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 endif
ifeq ($(UNAME),OpenBSD) ifeq ($(UNAME),OpenBSD)
CFLAGS += -ftrampolines
CFLAGS += -I${X11BASE}/include CFLAGS += -I${X11BASE}/include
LDFLAGS += -liconv LDFLAGS += -liconv
LDFLAGS += -L${X11BASE}/lib LDFLAGS += -L${X11BASE}/lib

6
debian/changelog vendored
View File

@ -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 i3-wm (3.d-bf1-1) unstable; urgency=low
* Bugfix: Dont draw window title when titlebar is disabled * Bugfix: Dont draw window title when titlebar is disabled

15
debian/control vendored
View File

@ -3,13 +3,12 @@ Section: utils
Priority: extra Priority: extra
Maintainer: Michael Stapelberg <michael@stapelberg.de> Maintainer: Michael Stapelberg <michael@stapelberg.de>
DM-Upload-Allowed: yes 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 Standards-Version: 3.8.3
Homepage: http://i3.zekjur.net/ Homepage: http://i3.zekjur.net/
Package: i3 Package: i3
Architecture: any Architecture: any
Priority: extra
Section: x11 Section: x11
Depends: i3-wm, ${misc:Depends} Depends: i3-wm, ${misc:Depends}
Recommends: i3lock, dwm-tools, i3status Recommends: i3lock, dwm-tools, i3status
@ -22,25 +21,23 @@ Description: metapackage (i3 window manager, screen locker, menu, statusbar)
Package: i3-wm Package: i3-wm
Architecture: any Architecture: any
Priority: extra
Section: x11 Section: x11
Depends: ${shlibs:Depends}, ${misc:Depends}, x11-utils Depends: ${shlibs:Depends}, ${misc:Depends}, x11-utils
Provides: x-window-manager Provides: x-window-manager
Suggests: rxvt-unicode | x-terminal-emulator Suggests: rxvt-unicode | x-terminal-emulator
Recommends: xfonts-base Recommends: xfonts-base
Description: an improved dynamic tiling window manager 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 assigned to virtual screens, i3 does the right thing when attaching new
monitors), XrandR support (not done yet), horizontal and vertical columns monitors), XRandR support, horizontal and vertical columns (think of a table)
(think of a table) in tiling. Also, special focus is on writing clean, in tiling. Also, special focus is on writing clean, readable and well
readable and well documented code. i3 uses xcb for asynchronous documented code. i3 uses XCB for asynchronous communication with X11, and has
communication with X11, and has several measures to be very fast. several measures to be very fast.
. .
Please be aware i3 is primarily targeted at advanced users and developers. Please be aware i3 is primarily targeted at advanced users and developers.
Package: i3-wm-dbg Package: i3-wm-dbg
Architecture: any Architecture: any
Priority: extra
Section: debug Section: debug
Depends: i3-wm (=${binary:Version}), ${misc:Depends} Depends: i3-wm (=${binary:Version}), ${misc:Depends}
Description: Debugging symbols for the i3 window manager Description: Debugging symbols for the i3 window manager

2
debian/i3-wm.docs vendored
View File

@ -8,3 +8,5 @@ docs/two_columns.png
docs/two_terminals.png docs/two_terminals.png
docs/modes.png docs/modes.png
docs/stacklimit.png docs/stacklimit.png
docs/ipc.html
docs/multi-monitor.html

1
debian/rules vendored
View File

@ -45,6 +45,7 @@ install: build
cp man/i3.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1 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-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-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. # Build architecture-independent files here.

View File

@ -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 hacking-howto.html: hacking-howto
asciidoc -a toc -n $< asciidoc -a toc -n $<
@ -10,6 +10,12 @@ debugging.html: debugging
userguide.html: userguide userguide.html: userguide
asciidoc -a toc -n $< asciidoc -a toc -n $<
ipc.html: ipc
asciidoc -a toc -n $<
multi-monitor.html: multi-monitor
asciidoc -a toc -n $<
clean: clean:
rm -f */*.{aux,log,toc,bm,pdf,dvi} rm -f */*.{aux,log,toc,bm,pdf,dvi}
rm -f *.log *.html rm -f *.log *.html

View File

@ -1,54 +1,58 @@
Debugging i3: How To Debugging i3: How To
================== ====================
Michael Stapelberg <michael+i3@stapelberg.de> 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 This document describes how to debug i3 suitably for sending us useful bug
if you have no clue of C programming. 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 First of all: Thank you for being interested in debugging i3. It really means
to us to get your bug fixed. If you have any questions about the debugging and/or need something to us to get your bug fixed. If you have any questions about the
further help, do not hesitate to contact us! debugging and/or need further help, do not hesitate to contact us!
== Enabling logging == Enabling logging
i3 spits out much information onto stdout. To have a clearly defined place where logfiles i3 spits out much information onto stdout, if told so. To have a clearly
will be saved, you should redirect stdout and stderr in xsession. While youre at it, defined place where log files will be saved, you should redirect stdout and
putting each run of i3 in a separate logfile with date/time in it is a good idea to not stderr in xsession. While youre at it, putting each run of i3 in a separate
get confused about the different logfiles later on. 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 When i3 crashes, often you have the chance of getting a 'core dump' (an image
of the i3 process which can be loaded into a debugger). To get a core-dump, you have to of the memory of the i3 process which can be loaded into a debugger). To get a
make sure that the user limit for core dump files is set high enough. Many systems ship core dump, you have to make sure that the user limit for core dump files is set
with a default value which even forbids core dumps completely. To disable the limit high enough. Many systems ship with a default value which even forbids core
completely and thus enable coredumps, use the following command (in your .xsession, before dumps completely. To disable the limit completely and thus enable core dumps,
starting i3): use the following command (in your .xsession, before starting i3):
------------------- -------------------
ulimit -c unlimited ulimit -c unlimited
------------------- -------------------
Furthermore, to easily recognize core dumps and allow multiple of them, you should set Furthermore, to easily recognize core dumps and allow multiple of them, you
a custom core dump filename pattern, using a command like the following: should set a custom core dump filename pattern, using a command like the
following:
--------------------------------------------- ---------------------------------------------
sudo sysctl -w kernel.core_pattern=core.%e.%p sudo sysctl -w kernel.core_pattern=core.%e.%p
--------------------------------------------- ---------------------------------------------
This will generate files which have the executables file name (%e) and the process id This will generate files which have the executables file name (%e) and the
(%p) in it. You can save this setting across reboots using +/etc/sysctl.conf+. process id (%p) in it. You can save this setting across reboots using
+/etc/sysctl.conf+.
== Compiling with debug symbols == Compiling with debug symbols
To actually get useful coredumps, you should make sure that your version of i3 is compiled To actually get useful core dumps, you should make sure that your version of i3
with debug symbols, that is, that they are not stripped during the build process. You is compiled with debug symbols, that is, that they are not stripped during the
can check whether your executable contains symbols by issuing the following command: build process. You can check whether your executable contains symbols by
issuing the following command:
---------------- ----------------
file $(which i3) 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 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 Notice the +not stripped+, which is the important part. If you have a version
stripped, please have a look if your distribution provides debug symbols (package +i3-wm-dbg+ which is stripped, please have a look if your distribution provides debug
on Debian for example) or if you can turn off stripping. If nothing helps, please build symbols (package +i3-wm-dbg+ on Debian for example) or if you can turn off
i3 from source. stripping. If nothing helps, please build i3 from source.
== Generating a backtrace == Generating a backtrace
Once you have made sure that your i3 is compiled with debug symbols and that coredumps Once you have made sure that your i3 is compiled with debug symbols and that
are enabled, you can start getting some sense out of the coredumps. 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 Because the core dump depends on the original executable (and its debug
do this as soon as you encounter the problem. If you re-compile i3, your coredump might symbols), please do this as soon as you encounter the problem. If you
be useless afterwards. re-compile i3, your core dump might be useless afterwards.
Please install +gdb+, a debugger for C. No worries, you dont need to learn it now. Please install +gdb+, a debugger for C. No worries, you dont need to learn it
Start gdb using the following command (replacing the actual name of the coredump of now. Start gdb using the following command (replacing the actual name of the
course): core dump of course):
---------------------------- ----------------------------
gdb $(which i3) core.i3.3849 gdb $(which i3) core.i3.3849
@ -88,11 +92,13 @@ Then, generate a backtrace using:
backtrace full 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 When sending bug reports, please paste the relevant part of the log (if in
too much information than too less) and the whole backtrace (if there was a coredump). 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 When debugging with us in IRC, be prepared to use a so called nopaste service
because pasting large amounts of text in IRC sometimes leads to incomplete lines (servers have line 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. length limitations) or flood kicks.

View File

@ -1,72 +1,81 @@
Hacking i3: How To Hacking i3: How To
================== ==================
Michael Stapelberg <michael+i3@stapelberg.de> 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 This document is intended to be the first thing you read before looking and/or
i3s source code. It should contain all important information to help you understand touching i3s source code. It should contain all important information to help
why things are like they are. If it does not mention something you find necessary, please you understand why things are like they are. If it does not mention something
do not hesitate to contact me. you find necessary, please do not hesitate to contact me.
== Window Managers == Window Managers
A window manager is not necessarily needed to run X, but it is usually used in combination A window manager is not necessarily needed to run X, but it is usually used in
to facilitate some things. The window manager's job is to take care of the placement of combination with X to facilitate some things. The window manager's job is to
windows, to provide the user some mechanisms to change the position/size of windows and take care of the placement of windows, to provide the user with some mechanisms
to communicate with clients to a certain extent (for example handle fullscreen requests to change the position/size of windows and to communicate with clients to a
of clients such as MPlayer). 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 There are no different contexts in which X11 clients run, so a window manager
client, like all other X11 applications. However, it handles some events which normal clients is just another client, like all other X11 applications. However, it handles
usually dont handle. some events which normal clients usually dont handle.
In the case of i3, the tasks (and order of them) are the following: 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) . 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 . Iterate through all existing windows (if the window manager is not started as
client of X) and manage them (= reparent them, create window decorations) the first client of X) and manage them (reparent them, create window
decorations, etc.)
. When new windows are created, manage them . When new windows are created, manage them
. Handle the clients `_WM_STATE` property, but only the `_WM_STATE_FULLSCREEN` . Handle the clients `_WM_STATE` property, but only the `_WM_STATE_FULLSCREEN`
. Handle the clients `WM_NAME` property . Handle the clients `WM_NAME` property
. Handle the clients size hints to display them proportionally . Handle the clients size hints to display them proportionally
. Handle the clients urgency hint
. Handle enter notifications (focus follows mouse) . Handle enter notifications (focus follows mouse)
. Handle button (as in mouse buttons) presses for focus/raise on click . Handle button (as in mouse buttons) presses for focus/raise on click
. Handle expose events to re-draw own windows such as decorations . Handle expose events to re-draw own windows such as decorations
. React to the users commands: Change focus, Move windows, Switch workspaces, . React to the users commands: Change focus, Move windows, Switch workspaces,
Change the layout mode of a container (default/stacking), Start a new application, Change the layout mode of a container (default/stacking/tabbed), start a new
Restart the window manager 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 === Tiling window managers
Traditionally, there are two approaches to managing windows: The most common one nowadays is Traditionally, there are two approaches to managing windows: The most common
floating, which means the user can freely move/resize the windows. The other approach is called one nowadays is floating, which means the user can freely move/resize the
tiling, which means that your window manager distributing windows to use as much space as windows. The other approach is called tiling, which means that your window
possible while not overlapping. 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 The idea behind tiling is that you should not need to waste your time
while you usually want to get some work done. After all, most users sooner or later tend to moving/resizing windows while you usually want to get some work done. After
lay out their windows in a way which corresponds to tiling or stacking mode in i3. Therefore, all, most users sooner or later tend to lay out their windows in a way which
why not let i3 do this for you? Certainly, its faster than you could ever do it. corresponds to tiling or stacking mode in i3. Therefore, why not let i3 do this
for you? Certainly, its faster than you could ever do it.
The problem with most tiling window managers is that they are too unflexible. In my opinion, a The problem with most tiling window managers is that they are too unflexible.
window manager is just another tool, and similar to vim which can edit all kinds of text files In my opinion, a window manager is just another tool, and similar to vim which
(like source code, HTML, …) and is not limited to a specific file type, a window manager should can edit all kinds of text files (like source code, HTML, …) and is not limited
not limit itself to a certain layout (like dwm, awesome, …) but provide mechanisms for you to to a specific file type, a window manager should not limit itself to a certain
easily create the layout you need at the moment. layout (like dwm, awesome, …) but provide mechanisms for you to easily create
the layout you need at the moment.
=== The layout table === The layout table
To accomplish flexible layouts, we decided to simply use a table. The table grows and shrinks To accomplish flexible layouts, we decided to simply use a table. The table
as you need it. Each cell holds a container which then holds windows (see picture below). You grows and shrinks as you need it. Each cell holds a container which then holds
can use different layouts for each container (default layout and stacking layout). 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, So, when you open a terminal and immediately open another one, they reside in
in default layout. The layout table has exactly one column, one row and therefore one cell. the same container, in default layout. The layout table has exactly one column,
When you move one of the terminals to the right, the table needs to grow. It will be expanded one row and therefore one cell. When you move one of the terminals to the
to two columns and one row. This enables you to have different layouts for each container. right, the table needs to grow. It will be expanded to two columns and one row.
The table then looks like this: This enables you to have different layouts for each container. The table then
looks like this:
[width="15%",cols="^,^"] [width="15%",cols="^,^"]
|======== |========
@ -81,9 +90,10 @@ When moving terminal 2 to the bottom, the table will be expanded again.
| | T2 | | T2
|======== |========
You can really think of the layout table like a traditional HTML table, if youve ever You can really think of the layout table like a traditional HTML table, if
designed one. Especially col- and rowspan work equally. Below you see an example of youve ever designed one. Especially col- and rowspan work similarly. Below,
colspan=2 for the first container (which has T1 as window). you see an example of colspan=2 for the first container (which has T1 as
window).
[width="15%",cols="^asciidoc"] [width="15%",cols="^asciidoc"]
|======== |========
@ -100,19 +110,30 @@ Furthermore, you can freely resize table cells.
== Files == Files
include/data.h:: 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:: include/*.h::
Contains forward definitions for all public functions, aswell as doxygen-compatible Contains forward definitions for all public functions, as well as
comments (so if you want to get a bit more of the big picture, either browse all doxygen-compatible comments (so if you want to get a bit more of the big
header files or use doxygen if you prefer that). picture, either browse all header files or use doxygen if you prefer that).
src/cfgparse.l::
Contains the lexer for i3s configuration file, written for +flex(1)+.
src/cfgparse.y::
Contains the parser for i3s 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:: src/client.c::
Contains all functions which are specific to a certain client (make it Contains all functions which are specific to a certain client (make it
fullscreen, see if its class/name matches a pattern, kill it, …). fullscreen, see if its class/name matches a pattern, kill it, …).
src/commands.c:: src/commands.c::
Parsing commands and actually execute them (focussing, moving, …). Parsing commands and actually executing them (focusing, moving, …).
src/config.c:: src/config.c::
Parses the configuration file. Parses the configuration file.
@ -124,7 +145,7 @@ src/floating.c::
Contains functions for floating mode (mostly resizing/dragging). Contains functions for floating mode (mostly resizing/dragging).
src/handlers.c:: 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, …). unmapping, key presses, button presses, …).
src/ipc.c:: src/ipc.c::
@ -143,9 +164,6 @@ reparents the window and inserts it into our data structures.
src/resize.c:: src/resize.c::
Contains the functions to resize columns/rows in the table. 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:: src/table.c::
Manages the most important internal data structure, the design table. Manages the most important internal data structure, the design table.
@ -159,12 +177,13 @@ src/xcb.c::
Contains wrappers to use xcb more easily. Contains wrappers to use xcb more easily.
src/xinerama.c:: 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 == Data structures
See include/data.h for documented data structures. The most important ones are explained See include/data.h for documented data structures. The most important ones are
right here. explained right here.
image:bigpicture.png[The Big Picture] image:bigpicture.png[The Big Picture]
@ -178,37 +197,41 @@ So, the hierarchy is:
=== Virtual screens === Virtual screens
A virtual screen (type `i3Screen`) is generated from the connected screens obtained A virtual screen (type `i3Screen`) is generated from the connected screens
through Xinerama. The difference to the raw Xinerama monitors as seen when using +xrandr(1)+ obtained through Xinerama. The difference to the raw Xinerama monitors as seen
is that it falls back to the lowest common resolution of the logical screens. 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 For example, if your notebook has 1280x800 and you connect a video projector
1024x768, set up in clone mode (+xrandr \--output VGA \--mode 1024x768 \--same-as LVDS+), with 1024x768, set up in clone mode (+xrandr \--output VGA \--mode 1024x768
i3 will have one virtual screen. \--same-as LVDS+), i3 will have one virtual screen.
However, if you configure it using +xrandr \--output VGA \--mode 1024x768 \--right-of LVDS+, However, if you configure it using +xrandr \--output VGA \--mode 1024x768
i3 will generate two virtual screens. For each virtual screen, a new workspace will be \--right-of LVDS+, i3 will generate two virtual screens. For each virtual
assigned. New workspaces are created on the screen you are currently on. screen, a new workspace will be assigned. New workspaces are created on the
screen you are currently on.
=== Workspace === Workspace
A workspace is identified by its number. Basically, you could think of workspaces A workspace is identified by its number. Basically, you could think of
as different desks in your bureau, if you like the desktop methaphor. They just contain workspaces as different desks in your office, if you like the desktop
different sets of windows and are completely separate of each other. Other window methaphor. They just contain different sets of windows and are completely
managers also call this ``Virtual desktops''. separate of each other. Other window managers also call this ``Virtual
desktops''.
=== The layout table === The layout table
Each workspace has a table, which is just a two-dimensional dynamic array containing Each workspace has a table, which is just a two-dimensional dynamic array
Containers (see below). This table grows and shrinks as you need it (by moving windows containing Containers (see below). This table grows and shrinks as you need it
to the right you can create a new column in the table, by moving them to the bottom (by moving windows to the right you can create a new column in the table, by
you create a new row). moving them to the bottom you create a new row).
=== Container === Container
A container is the content of a tables cell. It holds an arbitrary amount of windows A container is the content of a tables cell. It holds an arbitrary amount of
and has a specific layout (default layout or stack layout). Containers can consume windows and has a specific layout (default layout, stack layout or tabbed
multiple table cells by modifying their colspan/rowspan attribute. layout). Containers can consume multiple table cells by modifying their
colspan/rowspan attribute.
=== Client === Client
@ -216,20 +239,22 @@ A client is x11-speak for a window.
== List/queue macros == List/queue macros
i3 makes heavy use of the list macros defined in BSD operating systems. To ensure i3 makes heavy use of the list macros defined in BSD operating systems. To
that the operating system on which i3 is compiled has all the awaited features, ensure that the operating system on which i3 is compiled has all the expected
i3 comes with `include/queue.h`. On BSD systems, you can use man `queue(3)`. On Linux, features, i3 comes with `include/queue.h`. On BSD systems, you can use man
you have to use google. `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). The lists used are `SLIST` (single linked lists), `CIRCLEQ` (circular
Usually, only forward traversal is necessary, so an `SLIST` works fine. However, queues) and TAILQ (tail queues). Usually, only forward traversal is necessary,
for the windows inside a container, a `CIRCLEQ` is necessary to go from the currently 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. selected window to the window above/below.
== Naming conventions == Naming conventions
There is a row of standard variables used in many events. The following names should be There is a row of standard variables used in many events. The following names
chosen for those: should be chosen for those:
* ``conn'' is the xcb_connection_t * ``conn'' is the xcb_connection_t
* ``event'' is the event of the particular type * ``event'' is the event of the particular type
@ -249,116 +274,138 @@ chosen for those:
=== Grabbing the bindings === Grabbing the bindings
Grabbing the bindings is quite straight-forward. You pass X your combination of modifiers and Grabbing the bindings is quite straight-forward. You pass X your combination of
the keycode you want to grab and whether you want to grab them actively or passively. Most modifiers and the keycode you want to grab and whether you want to grab them
bindings (everything except for bindings using Mode_switch) are grabbed passively, that is, actively or passively. Most bindings (everything except for bindings using
just the window manager gets the event and cannot replay it. 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 We need to grab bindings that use Mode_switch actively because of a bug in X.
manager receives the keypress/keyrelease event for an actively grabbed keycode, it has to decide When the window manager receives the keypress/keyrelease event for an actively
what to do with this event: It can either replay it so that other applications get it or it grabbed keycode, it has to decide what to do with this event: It can either
can prevent other applications from receiving it. 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 So, why do we need to grab keycodes actively? Because X does not set the
keypress/keyrelease events properly. The Mode_switch bit is not set and we need to get it state-property of keypress/keyrelease events properly. The Mode_switch bit is
using XkbGetState. This means we cannot pass X our combination of modifiers containing Mode_switch not set and we need to get it using XkbGetState. This means we cannot pass X
when grabbing the key and therefore need to grab the keycode itself without any modiffiers. our combination of modifiers containing Mode_switch when grabbing the key and
This means, if you bind Mode_switch + keycode 38 ("a"), i3 will grab keycode 38 ("a") and therefore need to grab the keycode itself without any modifiers. This means,
check on each press of "a" if the Mode_switch bit is set using XKB. If yes, it will handle if you bind Mode_switch + keycode 38 ("a"), i3 will grab keycode 38 ("a") and
the event, if not, it will replay the event. 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 === 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. The bound command is parsed directly in command mode.
== Manage windows (src/mainx.c, manage_window() and reparent_window()) == 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 * Windows have to be mapped, that is, visible on screen
* The override_redirect must not be set. Windows with override_redirect shall not be * The override_redirect must not be set. Windows with override_redirect shall
managed by a window manager not be managed by a window manager
Afterwards, i3 gets the intial geometry and reparents the window if it wasnt already Afterwards, i3 gets the intial geometry and reparents the window (see
managed. `reparent_window()`) if it wasnt already managed.
Reparenting means that for each window which is reparented, a new window, slightly larger Reparenting means that for each window which is reparented, a new window,
than the original one, is created. The original window is then reparented to the bigger one slightly larger than the original one, is created. The original window is then
(called "frame"). reparented to the bigger one (called "frame").
After reparenting, the window type (`_NET_WM_WINDOW_TYPE`) is checked to see whether this After reparenting, the window type (`_NET_WM_WINDOW_TYPE`) is checked to see
window is a dock (`_NET_WM_WINDOW_TYPE_DOCK`), like dzen2 for example. Docks are handled whether this window is a dock (`_NET_WM_WINDOW_TYPE_DOCK`), like dzen2 for
differently, they dont have decorations and are not assigned to a specific container. example. Docks are handled differently, they dont have decorations and are not
Instead, they are positioned at the bottom of the screen. To get the height which needsd assigned to a specific container. Instead, they are positioned at the bottom
to be reserved for the window, the `_NET_WM_STRUT_PARTIAL` property is used. 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 users 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? == What happens when an application is started?
i3 does not care for applications. All it notices is when new windows are mapped (see i3 does not care for applications. All it notices is when new windows are
`src/handlers.c`, `handle_map_request()`). The window is then reparented (see section mapped (see `src/handlers.c`, `handle_map_request()`). The window is then
"Manage windows"). reparented (see section "Manage windows").
After reparenting the window, `render_layout()` is called which renders the internal After reparenting the window, `render_layout()` is called which renders the
layout table. The window was placed in the currently focused container and internal layout table. The new window has been placed in the currently focused
therefore the new window and the old windows (if any) need to be moved/resized container and therefore the new window and the old windows (if any) need to be
so that the currently active layout (default mode/stacking mode) is rendered moved/resized so that the currently active layout (default/stacking/tabbed mode)
correctly. To move/resize windows, a window is ``configured'' in X11-speak. 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 Some applications, such as MPlayer obviously assume the window manager is
and try to configure their windows by themselves. This generates an event called stupid and try to configure their windows by themselves. This generates an
configurerequest. i3 handles these events and tells the window the size it had event called configurerequest. i3 handles these events and tells the window the
before the configurerequest (with the exception of not yet mapped windows, which size it had before the configurerequest (with the exception of not yet mapped
get configured like they want to, and floating windows, which can reconfigure windows, which get configured like they want to, and floating windows, which
themselves). can reconfigure themselves).
== _NET_WM_STATE == _NET_WM_STATE
Only the _NET_WM_STATE_FULLSCREEN atom is handled. It calls ``toggle_fullscreen()'' for the Only the _NET_WM_STATE_FULLSCREEN atom is handled. It calls
specific client which just configures the client to use the whole screen on which it ``toggle_fullscreen()'' for the specific client which just configures the
currently is. Also, it is set as fullscreen_client for the i3Screen. client to use the whole screen on which it currently is. Also, it is set as
fullscreen_client for the i3Screen.
== WM_NAME == WM_NAME
When the WM_NAME property of a window changes, its decoration (containing the title) When the WM_NAME property of a window changes, its decoration (containing the
is re-rendered. 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
Size hints specify the minimum/maximum size for a given window aswell as its aspect ratio. Size hints specify the minimum/maximum size for a given window as well as its
At the moment, as i3 does not have a floating mode yet, only the aspect ratio is parsed. aspect ratio. This is important for clients like mplayer, who only set the
This is important for clients like mplayer, who only set the aspect ratio and resize their aspect ratio and resize their window to be as small as possible (but only with
window to be as small as possible (but only with some video outputs, for example in Xv, some video outputs, for example in Xv, while when using x11, mplayer does the
while when using x11, mplayer does the necessary centering for itself). necessary centering for itself).
So, when an aspect ratio was specified, i3 adjusts the height of the window until the So, when an aspect ratio was specified, i3 adjusts the height of the window
size maintains the correct aspect ratio. For the code to do this, see src/layout.c, until the size maintains the correct aspect ratio. For the code to do this, see
function resize_client(). src/layout.c, function resize_client().
== Rendering (src/layout.c, render_layout() and render_container()) == Rendering (src/layout.c, render_layout() and render_container())
There are two entry points to rendering: render_layout() and render_container(). The There are several entry points to rendering: `render_layout()`,
former one renders all virtual screens, the currently active workspace of each virtual `render_workspace()` and `render_container()`. The former one calls
screen and all containers (inside the table cells) of these workspaces using `render_workspace()` for every screen, which in turn will call
render_container(). Therefore, if you need to render only a single container, for `render_container()` for every container inside its layout table. Therefore, if
example because a window was removed, added or changed its title, you should directly you need to render only a single container, for example because a window was
call render_container(). 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 Rendering consists of two steps: In the first one, in `render_workspace()`, each
gets its position (screen offset + offset in the table) and size (container's width container gets its position (screen offset + offset in the table) and size
times colspan/rowspan). Then, render_container() is called: (container's width times colspan/rowspan). Then, `render_container()` is called,
which takes different approaches, depending on the mode the container is in:
render_container() then takes different approaches, depending on the mode the container
is in.
=== Common parts === Common parts
On the frame (the window which was created around the clients window for the decorations), On the frame (the window which was created around the clients window for the
a black rectangle is drawn as a background for windows like MPlayer, which dont completely decorations), a black rectangle is drawn as a background for windows like
fit into the frame. MPlayer, which do not completely fit into the frame.
=== Default mode === Default mode
@ -366,100 +413,112 @@ Each clients gets the containers width and an equal amount of height.
=== Stack mode === Stack mode
In stack mode, a window containing the decorations of all windows inside the container In stack mode, a window containing the decorations of all windows inside the
is placed at the top. The currently focused window is then given the whole remaining container is placed at the top. The currently focused window is then given the
space. 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 === Window decorations
The window decorations consist of a rectangle in the appropriate color (depends on whether The window decorations consist of a rectangle in the appropriate color (depends
this window is the currently focused one or the last focused one in a not focused container on whether this window is the currently focused one, the last focused one in a
or not focused at all) forming the background. Afterwards, two lighter lines are drawn not focused container or not focused at all) forming the background.
and the last step is drawing the windows title (see WM_NAME) onto it. Afterwards, two lighter lines are drawn and the last step is drawing the
windows title (see WM_NAME) onto it.
=== Fullscreen windows === Fullscreen windows
For fullscreen windows, the `rect` (x, y, width, height) is not changed to allow the client For fullscreen windows, the `rect` (x, y, width, height) is not changed to
to easily go back to its previous position. Instead, fullscreen windows are skipped allow the client to easily go back to its previous position. Instead,
when rendering. fullscreen windows are skipped when rendering.
=== Resizing containers === Resizing containers
By clicking and dragging the border of a container, you can resize the whole column By clicking and dragging the border of a container, you can resize the whole
(respectively row) which this container is in. This is necessary to keep the table column (respectively row) which this container is in. This is necessary to keep
layout working and consistent. 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 * A new, invisible window with the size of the root window is created
windows: (+grabwin+)
* Another window, 2px width and as high as your screen (or vice versa for
* A new, invisible window with the size of the root window is created (+grabwin+) horizontal resizing) is created. Its background color is the border color and
* Another window, 2px width and as high as your screen (or vice versa for horizontal it is only there to inform the user how big the container will be (it
resizing) is created. Its background color is the border color and it is only creates the impression of dragging the border out of the container).
there to signalize the user how big the container will be (it creates the impression * The +drag_pointer+ function of +src/floating.c+ is called to grab the pointer
of dragging the border out of the container). and enter its own event loop which will pass all events (expose events) but
* The +drag_pointer+ function of +src/floating.c+ is called to grab the pointer and motion notify events. This function then calls the specified callback
enter an own event loop which will pass all events (expose events) but motion notify (+resize_callback+) which does some boundary checking and moves the helper
events. This function then calls the specified callback (+resize_callback+) which window. As soon as the mouse button is released, this loop will be
does some boundary checking and moves the helper window. As soon as the mouse terminated.
button is released, this loop will be terminated. * The new width_factor for each involved column (respectively row) will be
* The new width_factor for each involved column (respectively row) will be calculated. calculated.
== User commands / commandmode (src/commands.c) == User commands / commandmode (src/commands.c)
Like in vim, you can control i3 using commands. They are intended to be a powerful Like in vim, you can control i3 using commands. They are intended to be a
alternative to lots of shortcuts, because they can be combined. There are a few special powerful alternative to lots of shortcuts, because they can be combined. There
commands, which are the following: are a few special commands, which are the following:
exec <command>:: exec <command>::
Starts the given command by passing it to `/bin/sh`. Starts the given command by passing it to `/bin/sh`.
restart:: 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:: w::
"With". This is used to select a bunch of windows. Currently, only selecting the whole "With". This is used to select a bunch of windows. Currently, only selecting
container in which the window is in, is supported by specifying "w". the whole container in which the window is in, is supported by specifying "w".
f, s, d:: f, s, d::
Toggle fullscreen, stacking, default mode for the current window/container. 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, The other commands are to be combined with a direction. The directions are h,
like in vim (h = left, j = down, k = up, l = right). When you just specify the direction j, k and l, like in vim (h = left, j = down, k = up, l = right). When you just
keys, i3 will move the focus in that direction. You can provide "m" or "s" before the specify the direction keys, i3 will move the focus in that direction. You can
direction to move a window respectively or snap. provide "m" or "s" before the direction to move a window respectively or snap.
== Gotchas == Gotchas
* Forgetting to call `xcb_flush(conn);` after sending a request. This usually leads to * Forgetting to call `xcb_flush(conn);` after sending a request. This usually
code which looks like it works fine but which does not work under certain conditions. leads to code which looks like it works fine but which does not work under
certain conditions.
== Using git / sending patches == Using git / sending patches
For a short introduction into using git, see http://www.spheredev.org/wiki/Git_for_the_lazy For a short introduction into using git, see
or, for more documentation, see http://git-scm.com/documentation 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 When you want to send a patch because you fixed a bug or implemented a cool
talk to us before working on features to see whether they are maybe already implemented, not feature (please talk to us before working on features to see whether they are
possible because of some reason or dont fit into the concept), please use git to create maybe already implemented, not possible for some some reason, or dont fit
a patchfile. 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 git pull
-------- --------
Afterwards, make the necessary changes for your bugfix/feature. Then, review the changes Afterwards, make the necessary changes for your bugfix/feature. Then, review
using +git diff+ (you might want to enable colors in the diff using +git config diff.color auto+). the changes using +git diff+ (you might want to enable colors in the diff using
When you are definitely done, use +git commit -a+ to commit all changes youve made. +git config diff.color auto+). When you are definitely done, use +git commit
-a+ to commit all changes youve made.
Then, use the following command to generate a patchfile which we can directly apply to Then, use the following command to generate a patchfile which we can directly
the branch, preserving your commit message and name: apply to the branch, preserving your commit message and name:
----------------------- -----------------------
git format-patch origin git format-patch origin
----------------------- -----------------------
Just send us the generated file via mail. Just send us the generated file via email.

301
docs/ipc Normal file
View File

@ -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 dont 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 dont 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

BIN
docs/keyboard-layer1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

915
docs/keyboard-layer1.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 131 KiB

BIN
docs/keyboard-layer2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

896
docs/keyboard-layer2.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 129 KiB

60
docs/multi-monitor Normal file
View File

@ -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 Xineramas 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 dont 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 Users
Guide.

View File

@ -1,64 +1,83 @@
i3 Users Guide i3 Users Guide
=============== ===============
Michael Stapelberg <michael+i3@stapelberg.de> 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 window manager. If it does not, please contact me on IRC, Jabber or E-Mail and
Ill help you out. Ill help you out.
For a complete listing of the default keybindings, please see the manpage. == Default keybindings
For the "too long; didnt 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 == 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 One very basic operation is opening a new terminal. By default, the keybinding
for that is Mod1+Enter, that is Alt+Enter in the default configuration. By for this 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 pressing Mod1+Enter, a new terminal will be opened. It will fill the whole
space which is available on your screen. space available on your screen.
image:single_terminal.png[Single terminal] image:single_terminal.png[Single terminal]
It is important to keep in mind that i3 uses a table to manage your windows. At 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 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 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 container in that cell holds both of your terminals. So, a container is just a
with a specific layout. You can resize containers as they directly resemble group of clients with a specific layout. Containers can be resized by adjusting
columns/rows of the layout table. the size of the cell that holds them.
image:two_terminals.png[Two terminals] image:two_terminals.png[Two terminals]
To move the focus between the two terminals, you use the direction keys which 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 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 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+ with most keyboard layouts). Therefore, +Mod1+J+ is left, +Mod1+K+ is down,
is up and `Mod1+;` is right. So, to switch between the terminals, use +Mod1+K+ or +Mod1+L+ is up and `Mod1+;` is right. So, to switch between the terminals,
+Mod1+L+. use +Mod1+K+ or +Mod1+L+.
To create a new row/column, you can simply move a terminal (or any other window) To create a new row/column (and a new cell), you can simply move a terminal (or
to the direction you want to expand your table. So, lets expand the table to any other window) in the direction you want to expand your table. So, lets
the right by pressing `Mod1+Shift+;`. expand the table to the right by pressing `Mod1+Shift+;`.
image:two_columns.png[Two columns] 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:: 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. container.
stacking:: 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. windows at the top of the container.
tabbed:: tabbed::
The same principle as +stacking+, but the list of windows at the top is only 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. +Mod1+w+ for tabbed.
image:modes.png[Container modes] 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 To display a window fullscreen or to go out of fullscreen mode again, press
+Mod1+f+. +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 === 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 +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 (or a part of it) of the application which you want to open. The application
your +$PATH+ for that to work. typed has to be in your +$PATH+ for this to work.
Furthermore, if you have applications you open very frequently, you can also Additionally, if you have applications you open very frequently, you can
create a keybinding for it. See the section "Configuring i3" for details. create a keybinding for starting the application directly. See the section
"Configuring i3" for details.
=== Closing windows === 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 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 can press +Mod1+Shift+q+ to kill a window. For applications which support
the WM_DELETE protocol, this will correctly close the application (saving the WM_DELETE protocol, this will correctly close the application (saving
any modifications or doing other cleanup). If the application doesnt support any modifications or doing other cleanup). If the application doesnt support
it, your X server will kill the window and the behaviour depends on the the WM_DELETE protocol your X server will kill the window and the behaviour
application. depends on the application.
=== Using workspaces === 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. 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 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 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. 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 If you have multiple screens, a workspace will be created on each screen at
you open a new workspace, it will be bound to the screen you created it on. startup. If you open a new workspace, it will be bound to the screen you
When you switch to a workspace on another screen, i3 will set focus to this created it on. When you switch to a workspace on another screen, i3 will set
screen. focus to that screen.
=== Moving windows to workspaces === Moving windows to workspaces
@ -113,20 +136,22 @@ it does not yet exist.
=== Resizing columns/rows === 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 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 See <<resizingconfig>> for how to configure i3 to be able to resize
columns/rows with your keyboard. columns/rows with your keyboard.
=== Restarting i3 inplace === Restarting i3 inplace
To restart i3 inplace (and thus get it into a clean state if it has a bug, to To restart i3 inplace (and thus get into a clean state if there is a bug, or
reload your configuration or even to upgrade to a newer version of i3) you to upgrade to a newer version of i3) you can use +Mod1+Shift+r+. Be aware,
can use +Mod1+Shift+r+. Be aware, though, that this kills your current layout though, that this kills your current layout and all the windows you have opened
and all the windows you have opened will be put in a default container in only will be put in a default container in only one cell. Saving layouts will be
one cell. Saving the layout will be implemented in a later version. implemented in a later version.
=== Exiting i3 === Exiting i3
@ -135,7 +160,7 @@ To cleanly exit i3 without killing your X server, you can use +Mod1+Shift+e+.
=== Snapping === Snapping
Snapping is a mechanism to increase/decrease the colspan/rowspan of a container. 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 consumes. This is easier explained by giving an example, so take the following
layout: layout:
@ -146,47 +171,67 @@ by pressing +Mod1+Control+k+ (or snap container 2 rightwards).
=== Floating === Floating
Floating is the opposite of tiling mode. The position and size of a window Floating mode 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 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 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 You can enable floating mode for a window by pressing +Mod1+Shift+Space+. By
dragging the windows titlebar with your mouse, you can move the window dragging the windows titlebar with your mouse you can move the window
around. By grabbing the borders and moving them you can resize 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 == Configuring i3
This is where the real fun begins ;-). Most things are very dependant on your This is where the real fun begins ;-). Most things are very dependant on your
ideal working environment, so we cant make reasonable defaults for them. ideal working environment so we cant make reasonable defaults for them.
While not using a programming language for the configuration, i3 stays 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. to do.
For example, you can configure bindings to jump to specific windows, For example, you can configure bindings to jump to specific windows,
you can set specific applications to start on a specific workspace, you can you can set specific applications to start on specific workspaces, you can
automatically start applications, you can change the colors of i3 or bind automatically start applications, you can change the colors of i3, and you
your keys to do useful stuff. can bind your keys to do useful things.
To change the configuration of i3, copy +/etc/i3/config+ to +~/.i3/config+ To change the configuration of i3, copy +/etc/i3/config+ to +\~/.i3/config+
and edit it with a text editor. (or +~/.config/i3/config+ if you like the XDG directory scheme) and edit it
with a text editor.
=== General configuration === Comments
terminal:: It is possible and recommended to use comments in your configuration file to
Specifies the terminal emulator program you prefer. It will be started properly document your setup for later reference. Comments are started with
by default when you press Mod1+Enter, but you can overwrite this. Refer a # and can only be used at the beginning of a line:
to it as +$terminal+ to keep things modular.
font:: *Examples*:
Specifies the default font you want i3 to use. Use an X core font -------------------
descriptor here, like # This is a comment
+-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1+. You can -------------------
use +xfontsel(1)+ to pick one.
=== 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 === 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 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). 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", * A keysym (key symbol) is a description for a specific symbol, like "a"
but also more strange ones like "underscore" instead of "_". These are the ones or "b", but also more strange ones like "underscore" instead of "_". These
you also use in Xmodmap to remap your keys. To get the current mapping of your are the ones you use in Xmodmap to remap your keys. To get the current
keys, use +xmodmap -pke+. 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 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 My recommendation is: If you often switch keyboard layouts but you want to keep
learn a different one, but you want to keep your bindings at the same place, your bindings in the same physical location on the keyboard, use keycodes.
use keycodes. If you dont switch layouts and like a clean and simple config If you dont switch layouts, and want a clean and simple config file, use
file, use keysyms. keysyms.
*Syntax*: *Syntax*:
---------------------------------- ----------------------------------
@ -217,10 +262,10 @@ bind [Modifiers+]keycode command
*Examples*: *Examples*:
-------------------------------- --------------------------------
# Fullscreen # Fullscreen
bind Mod1+f f bindsym Mod1+f f
# Restart # Restart
bind Mod1+Shift+r restart bindsym Mod1+Shift+r restart
# Notebook-specific hotkeys # Notebook-specific hotkeys
bind 214 exec /home/michael/toggle_beamer.sh 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 bindings. For example, when typing, capslock+1 or capslock+2 for switching
workspaces is totally convenient. Try it :-). workspaces is totally convenient. Try it :-).
[[floating_modifier]]
=== The floating modifier === The floating modifier
To move floating windows with your mouse, you can either grab their titlebar 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 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 click anywhere in the window itself to move it. The most common setup is to
it as the same one you use for managing windows (Mod1 for example). Afterwards, 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 you can press Mod1, click into a window using your left mouse button, and drag
it to the position you want it at. 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*: *Syntax*:
-------------------------------- --------------------------------
@ -259,8 +310,7 @@ floating_modifier Mod1
=== Layout mode for new containers === Layout mode for new containers
This option is only available when using the new lexer/parser (pass +-l+ to i3 This option determines in which mode new containers will start. See also
when starting). It determines in which mode new containers will start. See also
<<stack-limit>>. <<stack-limit>>.
*Syntax*: *Syntax*:
@ -276,8 +326,7 @@ new_container tabbed
=== Border style for new windows === Border style for new windows
This option is only available when using the new lexer/parser (pass +-l+ to i3 This option determines which border style new windows will have.
when starting). It determines which border new windows will have.
*Syntax*: *Syntax*:
--------------------------------------------- ---------------------------------------------
@ -291,10 +340,10 @@ new_window bp
=== Variables === 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 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 yourself some typing and be able to change the modifier you use later,
to use later, variables can be handy. variables can be handy.
*Syntax*: *Syntax*:
-------------- --------------
@ -307,20 +356,23 @@ set $m Mod1
bindsym $m+Shift+r restart 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 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 dynamic configuration you should create a little script which generates a
wmii. configuration file and run it before starting i3 (for example in your
+.xsession+ file).
=== Automatically putting clients on specific workspaces === Automatically putting clients on specific workspaces
It is recommended that you match on window classes whereever possible because [[assign_workspace]]
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 It is recommended that you match on window classes wherever possible because
being named Firefox and only when Vimperator is loaded, the title changes. As some applications first create their window, and then worry about setting the
i3 will get the title as soon as the application maps the window (mapping means correct title. Firefox with Vimperator comes to mind. The window starts up
actually displaying it on the screen), youd need to have to match on Firefox being named Firefox, and only when Vimperator is loaded does the title change.
in this case. As i3 will get the title as soon as the application maps the window (mapping
means actually displaying it on the screen), youd need to have to match on
'Firefox' in this case.
You can prefix or suffix workspaces with a `~` to specify that matching clients 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 should be put into floating mode. If you specify only a `~`, the client will
@ -341,11 +393,14 @@ assign "gecko" → ~4
assign "xv/MPlayer" → ~ 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 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 commands will be performed by i3 on initial startup (not when restarting i3
however). The commands will be run in order. in-place however). These commands will be run in order.
*Syntax*: *Syntax*:
------------ ------------
@ -357,35 +412,29 @@ exec command
exec sudo i3status | dzen2 -dock exec sudo i3status | dzen2 -dock
-------------------------------- --------------------------------
[[workspace_screen]]
=== Automatically putting workspaces on specific screens === Automatically putting workspaces on specific screens
If you use the assigning of clients to workspaces and start some clients If you assign clients to workspaces, it might be handy to put the
automatically, it might be handy to put the workspaces on specific screens. workspaces on specific screens. Also, the assignment of workspaces to screens
Also, the assignment of workspaces to screens will determine the workspace will determine which workspace i3 uses for a new screen when adding screens
which i3 uses for a new screen when adding screens or when starting (e.g., by or when starting (e.g., by default it will use 1 for the first screen, 2 for
default it will use 1 for the first screen, 2 for the second screen and so on). the second screen and so on).
*Syntax*: *Syntax*:
---------------------------------- ----------------------------------
workspace <number> screen <screen> workspace <number> output <output>
---------------------------------- ----------------------------------
Screen can be either a number (starting at 0 for the first screen) or a The 'output' is the name of the RandR output you attach your screen to. On a
position. When using numbers, it is not guaranteed that your screens always laptop, you might have VGA1 and LVDS1 as output names. You can see the
get the same number. Though, unless you upgrade your X server or drivers, the available outputs by running +xrandr --current+.
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.
*Examples*: *Examples*:
--------------------------- ---------------------------
workspace 1 screen 0 workspace 1 output LVDS1
workspace 5 screen 1 workspace 5 output VGA1
workspace 1 screen 1280
workspace 2 screen x800
workspace 3 screen 1280x800
--------------------------- ---------------------------
=== Named workspaces === Named workspaces
@ -396,10 +445,10 @@ them names (of course UTF-8 is supported):
*Syntax*: *Syntax*:
--------------------------------------- ---------------------------------------
workspace <number> <name> 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*: *Examples*:
-------------------------- --------------------------
@ -436,7 +485,7 @@ bar.unfocused::
bar.urgent:: bar.urgent::
A workspace which has at least one client with an activated urgency hint. 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*: *Examples*:
-------------------------------------- --------------------------------------
@ -444,19 +493,64 @@ Colors are in HTML hex format, see below.
client.focused #2F343A #900000 #FFFFFF 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 === Interprocess communication
i3 uses unix sockets to provide an IPC interface. At the moment, this interface i3 uses unix sockets to provide an IPC interface. This allows third-party
is only useful for sending commands. To enable it, you have to configure a path programs to get information from i3, such as the current workspaces
where the unix socket will be stored. The default path is +/tmp/i3-ipc.sock+. (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*: *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 You can then use the +i3-msg+ application to perform any command listed in
section. 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 == List of commands
@ -464,7 +558,8 @@ section.
To change the layout of the current container to stacking, use +s+, for default 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 +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*: *Examples*:
-------------- --------------
@ -475,17 +570,20 @@ bindsym Mod1+w T
# Toggle fullscreen # Toggle fullscreen
bindsym Mod1+f f bindsym Mod1+f f
# Toggle global fullscreen
bindsym Mod1+Shift+f fg
# Toggle floating/tiling # Toggle floating/tiling
bindsym Mod1+t t 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 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+, left, down, up, right (respectively). To focus a container, prefix it with
to focus a screen, prefix it with +ws+. +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: with +m+ when moving and with +s+ when snapping:
*Examples*: *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, workspace, e.g. +1+ or +3+. To move the current client to a specific workspace,
prefix the number with an +m+. prefix the number with an +m+.
Furthermore, you can switch to the next and previous workspace with the You can also switch to the next and previous workspace with the commands +nw+
commands +nw+ and +pw+, which is handy for example if you have workspace and +pw+, which is handy, for example, if you have workspace 1, 3, 4 and 9 and
1, 3, 4 and 9 and you want to cycle through them with a single key combination. you want to cycle through them with a single key combination.
*Examples*: *Examples*:
------------------------- -------------------------
@ -542,8 +640,7 @@ bindsym Mod1+p pw
=== Resizing columns/rows === Resizing columns/rows
If you want to resize columns/rows using your keyboard, you can use the 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 +resize+ command, I recommend using it inside a so called +mode+:
lexer/parser for that, so pass +-l+ to i3 when starting):
.Example: Configuration file, defining a mode for resizing .Example: Configuration file, defining a mode for resizing
---------------------------------------------------------------------- ----------------------------------------------------------------------
@ -568,15 +665,18 @@ mode "resize" {
bind 36 mode default bind 36 mode default
} }
# Enter resize mode
bindsym Mod1+r mode resize
---------------------------------------------------------------------- ----------------------------------------------------------------------
=== Jumping to specific windows === Jumping to specific windows
Especially when in a multi-monitor environment, you want to quickly jump to a specific Often when in a multi-monitor environment, you want to quickly jump to a
window, for example while currently working on workspace 3 you may want to jump to specific window. For example, while working on workspace 3 you may want to
your mailclient to mail your boss that youve achieved some important goal. Instead jump to your mail client to email your boss that youve achieved some
of figuring out how to navigate to your mailclient, it would be more convenient to important goal. Instead of figuring out how to navigate to your mailclient,
have a shortcut. it would be more convenient to have a shortcut.
*Syntax*: *Syntax*:
---------------------------------------------------- ----------------------------------------------------
@ -584,8 +684,9 @@ jump ["]window class[/window title]["]
jump workspace [ column row ] jump workspace [ column row ]
---------------------------------------------------- ----------------------------------------------------
You can either use the same matching algorithm as in the +assign+ command (see above) You can either use the same matching algorithm as in the +assign+ command
or you can specify the position of the client if you always use the same layout. (see above) or you can specify the position of the client if you always use
the same layout.
*Examples*: *Examples*:
-------------------------------------- --------------------------------------
@ -595,19 +696,19 @@ bindsym Mod1+a jump "urxvt/VIM"
=== VIM-like marks (mark/goto) === VIM-like marks (mark/goto)
[[vim_like_marks]]
This feature is like the jump feature: It allows you to directly jump to a 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 specific window (this means switching to the appropriate workspace and setting
focus to the windows). However, you can directly mark a specific window with 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 an arbitrary label and use it afterwards. You do not need to ensure that your
that your windows have unique classes or titles and you do not need to change windows have unique classes or titles, and you do not need to change your
your configuration file. configuration file.
As the command needs to include the label with which you want to mark the 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 window, you cannot simply bind it to a key. +i3-input+ is a tool created
only use the set of labels for which you created bindings). +i3-input+ is a for this purpose: It lets you input a command and sends the command to i3. It
tool created for this purpose: It lets you input a command and sends the can also prefix this command and display a custom prompt for the input dialog.
command to i3. It can also prefix this command and display a custom prompt for
the input dialog.
*Syntax*: *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: ' 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 === Traveling the focus stack
This mechanism can be thought of as the opposite of the +jump+ command. It travels This mechanism can be thought of as the opposite of the +jump+ command.
the focus stack and jumps to the window you focused before. It travels the focus stack and jumps to the window which had focus previously.
*Syntax*: *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 Where +number+ by default is 1 meaning that the next client in the focus stack
be selected. will be selected.
The special values have the following meaning: The special values have the following meaning:
@ -644,14 +748,14 @@ floating::
tiling:: tiling::
The next tiling window is selected. The next tiling window is selected.
ft:: ft::
If the current window is floating, the next tiling window will be selected If the current window is floating, the next tiling window will be
and vice-versa. selected; and vice-versa.
=== Changing border style === Changing border style
To change the border of the current client, you can use +bn+ to use the normal 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) 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. the different border styles.
*Examples*: *Examples*:
@ -665,12 +769,12 @@ bindsym Mod1+u bb
=== Changing the stack-limit of a container === 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. 10), the default layout of a stacking container can get a little unhandy.
Depending on your screens size, you might end up only using half of the Depending on your screens size, you might end up seeing only half of the
titlebars of each window in the container. 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 in a stacking container. i3 will create columns or rows (depending on what
you limited) automatically as needed. 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 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 (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 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 that all open windows will end up in a single container in default layout
i3 properly, you can use the +exit+ command, however you dont need to (e.g., after the restart. To exit i3 properly, you can use the +exit+ command,
simply killing your X session is fine aswell). however you dont need to (simply killing your X session is fine as well).
*Examples*: *Examples*:
---------------------------- ----------------------------
@ -706,3 +810,174 @@ bindsym Mod1+Shift+r restart
bindsym Mod1+Shift+w reload bindsym Mod1+Shift+w reload
bindsym Mod1+Shift+e exit 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 dont
need shortcuts to switch to a specific monitor, and you dont 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 dont 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).

View File

@ -18,8 +18,8 @@ all: ${FILES}
install: all install: all
echo "INSTALL" echo "INSTALL"
$(INSTALL) -d -m 0755 $(DESTDIR)/usr/bin $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
$(INSTALL) -m 0755 i3-input $(DESTDIR)/usr/bin/ $(INSTALL) -m 0755 i3-input $(DESTDIR)$(PREFIX)/bin/
clean: clean:
rm -f *.o rm -f *.o

View File

@ -8,7 +8,7 @@
char *convert_ucs_to_utf8(char *input); char *convert_ucs_to_utf8(char *input);
char *convert_utf8_to_ucs2(char *input, int *real_strlen); char *convert_utf8_to_ucs2(char *input, int *real_strlen);
uint32_t get_colorpixel(xcb_connection_t *conn, char *hex); 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); int connect_ipc(char *socket_path);
void ipc_send_message(int sockfd, uint32_t message_size, void ipc_send_message(int sockfd, uint32_t message_size,
uint32_t message_type, uint8_t *payload); uint32_t message_type, uint8_t *payload);

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
@ -22,6 +22,7 @@
#include <err.h> #include <err.h>
#include <stdint.h> #include <stdint.h>
#include <getopt.h> #include <getopt.h>
#include <glob.h>
#include <xcb/xcb.h> #include <xcb/xcb.h>
#include <xcb/xcb_aux.h> #include <xcb/xcb_aux.h>
@ -37,6 +38,7 @@
static int sockfd; static int sockfd;
static xcb_key_symbols_t *symbols; static xcb_key_symbols_t *symbols;
static int modeswitchmask; static int modeswitchmask;
static int numlockmask;
static bool modeswitch_active = false; static bool modeswitch_active = false;
static xcb_window_t win; static xcb_window_t win;
static xcb_pixmap_t pixmap; static xcb_pixmap_t pixmap;
@ -50,6 +52,21 @@ static char *prompt;
static int prompt_len; static int prompt_len;
static int limit; 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 * 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). * 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) { 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); 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); xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state);
if (sym == XK_Mode_switch) { if (sym == XK_Mode_switch) {
printf("Mode switch disabled\n"); 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) if (modeswitch_active)
event->state |= modeswitchmask; 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); xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state);
if (sym == XK_Mode_switch) { if (sym == XK_Mode_switch) {
printf("Mode switch enabled\n"); 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[]) { 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"; char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
int o, option_index = 0; 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) { while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
switch (o) { switch (o) {
case 's': case 's':
socket_path = strdup(optarg); socket_path = glob_path(optarg);
break; break;
case 'v': case 'v':
printf("i3-input " I3_VERSION); 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_key_release_handler(&evenths, handle_key_release, NULL);
xcb_event_set_expose_handler(&evenths, handle_expose, 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); symbols = xcb_key_symbols_alloc(conn);
uint32_t font_id = get_font_id(conn, pattern, &font_height); 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_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font_height + 8);
xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); 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 */ /* Create graphics context */
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id); xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id);
/* Grab the keyboard to get all input */ /* 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 wms 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); xcb_flush(conn);

View File

@ -53,12 +53,12 @@ uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) {
* keycode). * 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_key_symbols_t *symbols = xcb_key_symbols_alloc(conn);
xcb_get_modifier_mapping_reply_t *modmap_r; xcb_get_modifier_mapping_reply_t *modmap_r;
xcb_keycode_t *modmap, kc; 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) if (modeswitchcodes == NULL)
return 0; return 0;
@ -66,7 +66,7 @@ uint32_t get_mode_switch_mask(xcb_connection_t *conn) {
modmap = xcb_get_modifier_mapping_keycodes(modmap_r); modmap = xcb_get_modifier_mapping_keycodes(modmap_r);
for (int i = 0; i < 8; i++) 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]; kc = modmap[i * modmap_r->keycodes_per_modifier + j];
for (xcb_keycode_t *ktest = modeswitchcodes; *ktest; ktest++) { for (xcb_keycode_t *ktest = modeswitchcodes; *ktest; ktest++) {
if (*ktest != kc) if (*ktest != kc)

View File

@ -3,6 +3,8 @@ TOPDIR=..
include $(TOPDIR)/common.mk 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 # Depend on the object files of all source-files in src/*.c and on all header files
FILES=$(patsubst %.c,%.o,$(wildcard *.c)) FILES=$(patsubst %.c,%.o,$(wildcard *.c))
HEADERS=$(wildcard *.h) HEADERS=$(wildcard *.h)
@ -18,8 +20,8 @@ all: ${FILES}
install: all install: all
echo "INSTALL" echo "INSTALL"
$(INSTALL) -d -m 0755 $(DESTDIR)/usr/bin $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin
$(INSTALL) -m 0755 i3-msg $(DESTDIR)/usr/bin/ $(INSTALL) -m 0755 i3-msg $(DESTDIR)$(PREFIX)/bin/
clean: clean:
rm -f *.o rm -f *.o

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
@ -16,6 +16,7 @@
*/ */
#include <ev.h> #include <ev.h>
#include <stdio.h> #include <stdio.h>
#include <stdbool.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/un.h> #include <sys/un.h>
@ -26,6 +27,24 @@
#include <err.h> #include <err.h>
#include <stdint.h> #include <stdint.h>
#include <getopt.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 * 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, static void ipc_send_message(int sockfd, uint32_t message_size,
uint32_t message_type, uint8_t *payload) { 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 msg[buffer_size];
char *walk = msg; char *walk = msg;
strcpy(walk, "i3-ipc"); strcpy(walk, I3_IPC_MAGIC);
walk += strlen("i3-ipc"); walk += strlen(I3_IPC_MAGIC);
memcpy(walk, &message_size, sizeof(uint32_t)); memcpy(walk, &message_size, sizeof(uint32_t));
walk += sizeof(uint32_t); walk += sizeof(uint32_t);
memcpy(walk, &message_type, 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[]) { 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 o, option_index = 0;
int message_type = I3_IPC_MESSAGE_TYPE_COMMAND;
char *payload = "";
bool quiet = false;
static struct option long_options[] = { static struct option long_options[] = {
{"socket", required_argument, 0, 's'}, {"socket", required_argument, 0, 's'},
{"type", required_argument, 0, 't'}, {"type", required_argument, 0, 't'},
{"version", no_argument, 0, 'v'}, {"version", no_argument, 0, 'v'},
{"quiet", no_argument, 0, 'q'},
{"help", no_argument, 0, 'h'}, {"help", no_argument, 0, 'h'},
{0, 0, 0, 0} {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) { while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
if (o == 's') { if (o == 's') {
socket_path = strdup(optarg); socket_path = glob_path(optarg);
break;
} else if (o == 't') { } 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') { } else if (o == 'v') {
printf("i3-msg " I3_VERSION); printf("i3-msg " I3_VERSION);
return 0; return 0;
@ -88,11 +165,8 @@ int main(int argc, char *argv[]) {
} }
} }
if (optind >= argc) { if (optind < argc)
fprintf(stderr, "Error: missing message\n"); payload = argv[optind];
fprintf(stderr, "i3-msg [-s <socket>] [-t <type>] <message>\n");
return 1;
}
int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (sockfd == -1) 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) if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0)
err(EXIT_FAILURE, "Could not connect to i3"); 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); close(sockfd);

244
i3-wsbar Executable file
View File

@ -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
# dont 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

View File

@ -1,11 +1,6 @@
# This configuration uses Mod1 and Mod3. Make sure they are mapped properly using xev(1) # 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) # 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 # ISO 10646 = Unicode
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 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 bind Mod1+Shift+24 kill
# Mod1+v starts dmenu and launches the selected application # Mod1+v starts dmenu and launches the selected application
# for now, we dont have an own launcher # for now, we dont have a launcher of our own.
bind Mod1+55 exec /usr/bin/dmenu_run bind Mod1+55 exec /usr/bin/dmenu_run
# Mod1+Shift+e exits i3 # Mod1+Shift+e exits i3
@ -121,6 +116,10 @@ bind Mod1+Shift+26 exit
# Mod1+Shift+r restarts i3 inplace # Mod1+Shift+r restarts i3 inplace
bind Mod1+Shift+27 restart 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 # # DELETE THE FOLLOWING LINES TO DISABLE THE WELCOME MESSAGE #
############################################################# #############################################################

View File

@ -1,6 +1,6 @@
1.) Welcome to i3! 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: Please also make sure to have a look at the man page and the user's guide:
http://i3.zekjur.net/docs/userguide.html http://i3.zekjur.net/docs/userguide.html
@ -8,7 +8,8 @@ http://i3.zekjur.net/docs/userguide.html
2.) Configuration Files 2.) Configuration Files
/etc/i3/config is the default configuration. It is recommended to copy it and /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 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 The following explanation is related to the QWERTY layout, but as the default
configuration uses keycodes instead of keysymbols for binding, you still have 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. 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. the arrow keys on your keyboard, if you prefer them.
Mod1+<directional key> moves the focus to the window in the given direction 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+<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+h sets the mode of a container to stacking
Mod1+e sets the mode back to default Mod1+e sets the mode back to default
Mod1+f toggles fullscreen mode for the current window Mod1+f toggles fullscreen mode for the current window
Mod1+Shift+Space toggles floating mode for the current window Mod1+Shift+Space toggles floating mode for the current window
Mod1+Shift+q closes a 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 Mod1+Shift+e exits i3
If you have any questions, please don't hesitate to ask! If you have any questions, please don't hesitate to ask!

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* (c) 2009 Michael Stapelberg and contributors * © 2009 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * 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. * 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. * 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); 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 * Sets the position of the given client in the X stack to the highest (tiling
* layer is always on the same position, so this doesnt matter) below the * layer is always on the same position, so this doesnt 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); 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 clients 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 clients information into the logfile. * Pretty-prints the clients information into the logfile.
* *
*/ */
#define CLIENT_LOG(client) do { \ #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) } while (0)
#endif #endif

View File

@ -3,12 +3,14 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
* include/config.h: Contains all structs/variables for * include/config.h: Contains all structs/variables for the configurable
* the configurable part of i3 * 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; typedef struct Config Config;
extern Config config; extern Config config;
extern bool config_use_lexer;
extern SLIST_HEAD(modes_head, Mode) modes; 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, * 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). * 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;
int container_stack_limit_value; 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; const char *default_border;
/** The modifier which needs to be pressed in combination with your mouse /** The modifier which needs to be pressed in combination with your mouse
@ -96,6 +124,18 @@ struct Config {
} bar; } 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. * 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); 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 * Ungrabs all keys, to be called before re-grabbing the keys because of a
* mapping_notify event or a configuration file reload * 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) * 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 * 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); 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 #endif

26
include/container.h Normal file
View File

@ -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

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
@ -11,6 +11,7 @@
* *
*/ */
#include <xcb/xcb.h> #include <xcb/xcb.h>
#include <xcb/randr.h>
#include <xcb/xcb_atom.h> #include <xcb/xcb_atom.h>
#include <stdbool.h> #include <stdbool.h>
@ -25,11 +26,12 @@
* *
* Lets start from the biggest to the smallest: * Lets start from the biggest to the smallest:
* *
* - An i3Screen is a virtual screen (Xinerama). This can be a single one, * - An Output is a physical output on your graphics driver. Outputs which
* though two monitors might be connected, if youre running clone * are currently in use have (output->active == true). Each output has a
* mode. There can also be multiple of them. * 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 * other window managers. Basically, a workspace is a specific set of
* windows, usually grouped thematically (irc, www, work, …). You can switch * windows, usually grouped thematically (irc, www, work, …). You can switch
* between these. * between these.
@ -54,7 +56,7 @@ typedef struct Client Client;
typedef struct Binding Binding; typedef struct Binding Binding;
typedef struct Workspace Workspace; typedef struct Workspace Workspace;
typedef struct Rect Rect; typedef struct Rect Rect;
typedef struct Screen i3Screen; typedef struct xoutput Output;
/****************************************************************************** /******************************************************************************
* Helper types * Helper types
@ -75,6 +77,14 @@ enum {
/** /**
* Stores a rectangle, for example the size of a window, the child window etc. * 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 * 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 * used for the coordinates of a window, which can be set outside of the
@ -84,9 +94,11 @@ enum {
* *
*/ */
struct Rect { struct Rect {
uint32_t x, y; uint32_t x;
uint32_t width, height; uint32_t y;
}; uint32_t width;
uint32_t height;
} __attribute__((packed));
/** /**
* Defines a position in the table * Defines a position in the table
@ -171,6 +183,9 @@ struct Workspace {
/** Number of this workspace, starting from 0 */ /** Number of this workspace, starting from 0 */
int num; int num;
/** Name of the workspace (in UTF-8) */
char *utf8_name;
/** Name of the workspace (in UCS-2) */ /** Name of the workspace (in UCS-2) */
char *name; char *name;
@ -200,12 +215,8 @@ struct Workspace {
/** Are the floating clients on this workspace currently hidden? */ /** Are the floating clients on this workspace currently hidden? */
bool floating_hidden; bool floating_hidden;
/** A <screen> specifier on which this workspace would like to be (if /** The name of the RandR output this screen should be on */
* the screen is available). screen := <number> | <position> */ char *preferred_output;
char *preferred_screen;
/** Temporary flag needed for re-querying xinerama screens */
bool reassigned;
/** True if any client on this workspace has its urgent flag set */ /** True if any client on this workspace has its urgent flag set */
bool urgent; bool urgent;
@ -224,8 +235,8 @@ struct Workspace {
* appended) */ * appended) */
TAILQ_HEAD(floating_clients_head, Client) floating_clients; TAILQ_HEAD(floating_clients_head, Client) floating_clients;
/** Backpointer to the screen this workspace is on */ /** Backpointer to the output this workspace is on */
i3Screen *screen; Output *output;
/** This is a two-dimensional dynamic array of /** This is a two-dimensional dynamic array of
* Container-pointers. Ive always wanted to be a three-star * Container-pointers. Ive 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 * An Output is a physical output on your graphics driver. Outputs which
* monitors might be connected, if youre running clone mode. There can also * are currently in use have (output->active == true). Each output has a
* be multiple of them. * position and a mode. An output usually corresponds to one connected
* screen (except if you are running multiple screens in clone mode).
* *
*/ */
struct Screen { struct xoutput {
/** Virtual screen number */ /** Output id, so that we can requery the output directly later */
int num; 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 */ /** Current workspace selected on this virtual screen */
Workspace *current_workspace; Workspace *current_workspace;
@ -515,7 +538,7 @@ struct Screen {
* _NET_WM_WINDOW_TYPE_DOCK */ * _NET_WM_WINDOW_TYPE_DOCK */
SLIST_HEAD(dock_clients_head, Client) dock_clients; SLIST_HEAD(dock_clients_head, Client) dock_clients;
TAILQ_ENTRY(Screen) screens; TAILQ_ENTRY(xoutput) outputs;
}; };
#endif #endif

42
include/ewmh.h Normal file
View File

@ -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

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
@ -12,10 +12,19 @@
#define _FLOATING_H #define _FLOATING_H
/** Callback for dragging */ /** 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? */ /** 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 * 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); xcb_button_press_event_t *event);
/** /**
* Called when the user right-clicked on the titlebar of a floating window to * Called when the user clicked on a floating window while holding the
* resize it. * floating_modifier and the right mouse button.
* Calls the drag_pointer function with the resize_window callback * Calls the drag_pointer function with the resize_window callback
* *
*/ */
void floating_resize_window(xcb_connection_t *conn, Client *client, 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. * 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, 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 #endif

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * 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. * See file LICENSE for license information.
* *
@ -11,13 +11,7 @@
#ifndef _HANDLERS_H #ifndef _HANDLERS_H
#define _HANDLERS_H #define _HANDLERS_H
/** #include <xcb/randr.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);
/** /**
* There was a key press. We compare this key code with our bindings table and * 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); 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 * Configure requests are received when the application wants to resize
* windows on their own. * 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); 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 * Called when a window changes its title
* *

View File

@ -21,19 +21,20 @@
#ifndef _I3_H #ifndef _I3_H
#define _I3_H #define _I3_H
#define NUM_ATOMS 18 #define NUM_ATOMS 21
extern xcb_connection_t *global_conn; extern xcb_connection_t *global_conn;
extern xcb_key_symbols_t *keysyms; extern xcb_key_symbols_t *keysyms;
extern char **start_argv; extern char **start_argv;
extern Display *xkbdpy; extern Display *xkbdpy;
extern int xkb_current_group;
extern TAILQ_HEAD(bindings_head, Binding) *bindings; extern TAILQ_HEAD(bindings_head, Binding) *bindings;
extern TAILQ_HEAD(autostarts_head, Autostart) autostarts; extern TAILQ_HEAD(autostarts_head, Autostart) autostarts;
extern TAILQ_HEAD(assignments_head, Assignment) assignments; extern TAILQ_HEAD(assignments_head, Assignment) assignments;
extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins; extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins;
extern xcb_event_handlers_t evenths; extern xcb_event_handlers_t evenths;
extern int num_screens;
extern uint8_t root_depth; extern uint8_t root_depth;
extern bool xkb_supported;
extern xcb_atom_t atoms[NUM_ATOMS]; extern xcb_atom_t atoms[NUM_ATOMS];
extern xcb_window_t root; extern xcb_window_t root;

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
@ -15,10 +15,53 @@
#ifndef _I3_IPC_H #ifndef _I3_IPC_H
#define _I3_IPC_H #define _I3_IPC_H
/*
* Messages from clients to i3
*
*/
/** Never change this, only on major IPC breakage (dont do that) */ /** Never change this, only on major IPC breakage (dont do that) */
#define I3_IPC_MAGIC "i3-ipc" #define I3_IPC_MAGIC "i3-ipc"
/** The payload of the message will be interpreted as a command */ /** 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 #endif

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
@ -16,6 +16,34 @@
#include "i3/ipc.h" #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 * 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 * 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); 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 #endif

View File

@ -79,7 +79,7 @@ void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace,
* Renders the given workspace on the given screen * 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, * Renders the whole layout, that is: Go through each screen, each workspace,

66
include/log.h Normal file
View File

@ -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

View File

@ -23,6 +23,16 @@
void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t
*prophs, xcb_window_t root); *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. * Do some sanity checks and then reparent the window.
* *

75
include/randr.h Normal file
View File

@ -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

21
include/sighandler.h Normal file
View File

@ -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

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* (c) 2009 Michael Stapelberg and contributors * © 2009 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
@ -34,10 +34,6 @@
} \ } \
while (0) 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); TAILQ_HEAD(keyvalue_table_head, keyvalue_element);
extern struct keyvalue_table_head by_parent; extern struct keyvalue_table_head by_parent;
extern struct keyvalue_table_head by_child; extern struct keyvalue_table_head by_child;
@ -46,11 +42,11 @@ int min(int a, int b);
int max(int a, int b); int max(int a, int b);
/** /**
* Logs the given message to stdout while prefixing the current time to it. * Updates *destination with new_value and returns true if it was changed or false
* This is to be called by LOG() which includes filename/linenumber * 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 * 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, Client *get_matching_client(xcb_connection_t *conn,
const char *window_classtitle, Client *specific); const char *window_classtitle, Client *specific);
/*
* Restart i3 in-place
* appends -a to argument list to disable autostart
*
*/
void i3_restart();
#if defined(__OpenBSD__) #if defined(__OpenBSD__)
/* OpenBSD does not provide memmem(), so we provide FreeBSDs implementation */ /* OpenBSD does not provide memmem(), so we provide FreeBSDs implementation */
void *memmem(const void *l, size_t l_len, const void *s, size_t s_len); void *memmem(const void *l, size_t l_len, const void *s, size_t s_len);

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
@ -11,7 +11,7 @@
#include <xcb/xcb.h> #include <xcb/xcb.h>
#include "data.h" #include "data.h"
#include "xinerama.h" #include "randr.h"
#ifndef _WORKSPACE_H #ifndef _WORKSPACE_H
#define _WORKSPACE_H #define _WORKSPACE_H
@ -44,6 +44,17 @@ bool workspace_is_visible(Workspace *ws);
/** Switches to the given workspace */ /** Switches to the given workspace */
void workspace_show(xcb_connection_t *conn, int 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 * 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 * 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. * 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 * Gets the first unused workspace for the given screen, taking into account
* the preferred_screen setting of every workspace (workspace assignments). * 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. * Unmaps all clients (and stack windows) of the given workspace.

View File

@ -61,7 +61,10 @@ enum { _NET_SUPPORTED = 0,
WM_DELETE_WINDOW, WM_DELETE_WINDOW,
UTF8_STRING, UTF8_STRING,
WM_STATE, WM_STATE,
WM_CLIENT_LEADER WM_CLIENT_LEADER,
_NET_CURRENT_DESKTOP,
_NET_ACTIVE_WINDOW,
_NET_WORKAREA
}; };
extern unsigned int xcb_numlock_mask; 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 predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *text,
int length); 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 #endif

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * 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. * See file LICENSE for license information.
* *
@ -13,16 +13,6 @@
#ifndef _XINERAMA_H #ifndef _XINERAMA_H
#define _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 * We have just established a connection to the X server and need the initial
* Xinerama information to setup workspaces for each screen. * 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); 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 #endif

View File

@ -1,6 +1,18 @@
all: A2M:=a2x -f manpage --asciidoc-opts="-f asciidoc.conf"
a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3.man
a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3-msg.man all: i3.1 i3-msg.1 i3-input.1 i3-wsbar.1
a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3-input.man
%.1: %.man asciidoc.conf
${A2M} $<
i3-wsbar.1: ../i3-wsbar
pod2man $^ > $@
clean: 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

View File

@ -7,7 +7,7 @@ template::[header-declarations]
<refentrytitle>{mantitle}</refentrytitle> <refentrytitle>{mantitle}</refentrytitle>
<manvolnum>{manvolnum}</manvolnum> <manvolnum>{manvolnum}</manvolnum>
<refmiscinfo class="source">i3</refmiscinfo> <refmiscinfo class="source">i3</refmiscinfo>
<refmiscinfo class="version">delta</refmiscinfo> <refmiscinfo class="version">epsilon</refmiscinfo>
<refmiscinfo class="manual">i3 Manual</refmiscinfo> <refmiscinfo class="manual">i3 Manual</refmiscinfo>
</refmeta> </refmeta>
<refnamediv> <refnamediv>

View File

@ -5,7 +5,7 @@ v3.delta, November 2009
== NAME == NAME
i3-input - interactively take a command for i3 i3-input - interactively take a command for i3 window manager
== SYNOPSIS == SYNOPSIS
@ -13,8 +13,9 @@ i3-input [-s <socket>] [-p <prefix>] [-l <limit>] [-P <prompt>] [-v]
== DESCRIPTION == DESCRIPTION
i3-input is a tool to take commands (or parts of a command) and then send it i3-input is a tool to take commands (or parts of a command) composed by
to i3. This is useful for example for the mark/goto command. the user, and send it/them to i3. This is useful, for example, for the
mark/goto command.
== EXAMPLE == EXAMPLE

View File

@ -5,7 +5,7 @@ v3.delta, November 2009
== NAME == NAME
i3-msg - send messages to i3 i3-msg - send messages to i3 window manager
== SYNOPSIS == SYNOPSIS

View File

@ -1,7 +1,7 @@
i3(1) i3(1)
===== =====
Michael Stapelberg <michael+i3@stapelberg.de> Michael Stapelberg <michael+i3@stapelberg.de>
v3.delta, November 2009 v3.epsilon, March 2010
== NAME == NAME
@ -9,65 +9,88 @@ i3 - an improved dynamic, tiling window manager
== SYNOPSIS == SYNOPSIS
i3 [-c configfile] [-a] i3 [-a] [-c configfile] [-C] [-d <loglevel>] [-v] [-V]
== OPTIONS == OPTIONS
-c::
Specifies an alternate configuration file path
-a:: -a::
Disables autostart. 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 == DESCRIPTION
=== INTRODUCTION === INTRODUCTION
i3 was created because wmii, our favorite window manager at the time, didnt i3 was created because wmii, our favorite window manager at the time, didnt
provide some features we wanted (Xinerama done right, for example), had some provide some features we wanted (multi-monitor done right, for example), had
bugs, didnt progress since quite some time and wasnt easy to hack at all some bugs, didnt progress since quite some time and wasnt easy to hack at all
(source code comments/documentation completely lacking). Still, we think the (source code comments/documentation completely lacking). Still, we think the
wmii developers and contributors did a great job. Thank you for inspiring us to wmii developers and contributors did a great job. Thank you for inspiring us to
create i3. create i3.
Please be aware that i3 is primarily targeted at advanced users and developers. 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 === TERMINOLOGY
Client:: Client::
A client is X11-speak for a window. A client is X11-speak for a window.
Table:: Table::
Your workspace is managed using a table. You can move windows around and create new columns Your workspace is managed using a table. You can move windows around and create
(move a client to the right) or rows (move it to the bottom) implicitly. 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. By "snapping" a client in a specific direction, you increase its colspan/rowspan.
Container:: 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 Containers can be used in various modes. The default mode is called "default"
resizes each client equally so that it fits. and just resizes each client equally so that it fits.
Workspace:: Workspace::
A workspace is a set of clients (technically speaking, its just a table). Other window A workspace is a set of clients (technically speaking, its just a table).
managers call this "Virtual Desktops". Other window managers call this "Virtual Desktops".
+ +
In i3, each workspace is assigned to a specific virtual screen. By default, screen 1 In i3, each workspace is assigned to a specific virtual screen. By default,
has workspace 1, screen 2 has workspace 2 and so on… However, when you create a new screen 1 has workspace 1, screen 2 has workspace 2 and so on… However, when you
workspace (by simply switching to it), itll be assigned the screen you are currently create a new workspace (by simply switching to it), itll be assigned the
on. screen you are currently on.
Virtual Screen:: Output::
Using Xinerama, you can have an X11 screen spanning multiple real monitors. Furthermore, Using XRandR, you can have an X11 screen spanning multiple real monitors.
you can set them up in cloning mode or with positions (monitor 1 is left of monitor 2). 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 i3 uses the RandR API to query which outputs are available and which screens
two real monitors (lets say your laptop screen and a video projector) and enabled cloning, i3 are connected to these outputs.
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.
== KEYBINDINGS == KEYBINDINGS
@ -114,31 +137,34 @@ Mod1+t::
Select the first tiling window if the current window is floating and vice-versa. Select the first tiling window if the current window is floating and vice-versa.
Mod1+Shift+q:: Mod1+Shift+q::
Kills the current window. This is equivalent to "clicking on the close button", meaning a polite Kills the current window. This is equivalent to "clicking on the close button",
request to the application to close this window. For example, Firefox will save its session meaning a polite request to the application to close this window. For example,
upon such a request. If the application does not support that, the window will be killed and Firefox will save its session upon such a request. If the application does not
it depends on the application what happens. support that, the window will be killed and it depends on the application what
happens.
Mod1+Shift+r:: 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:: Mod1+Shift+e::
Exits i3. Exits i3.
== FILES == 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, When starting, i3 looks for configuration files in the following order:
i3 tries /etc/i3/config. You can specify a custom path using the -c option.
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 .Sample configuration
------------------------------------------------------------- -------------------------------------------------------------
terminal /usr/bin/urxvt
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
# Start terminal (Mod1+Enter) # Start terminal (Mod1+Enter)
@ -265,22 +291,25 @@ ulimit -c unlimited
# Start i3 and log to ~/.i3/logfile # Start i3 and log to ~/.i3/logfile
echo "Starting at $(date)" >> ~/.i3/logfile echo "Starting at $(date)" >> ~/.i3/logfile
exec /usr/bin/i3 >> ~/.i3/logfile exec /usr/bin/i3 -V -d all >> ~/.i3/logfile
------------------------------------------------------------- -------------------------------------------------------------
== TODO == TODO
There is still lot of work to do. Please check our bugtracker for up-to-date information There is still lot of work to do. Please check our bugtracker for up-to-date
about tasks which are still not finished. information about tasks which are still not finished.
== SEE ALSO == SEE ALSO
You should have a copy of the userguide (featuring nice screenshots/graphics which is why this You should have a copy of the userguide (featuring nice screenshots/graphics
is not integrated into this manpage), the debugging guide and the "how to hack" guide. If you which is why this is not integrated into this manpage), the debugging guide,
are building from source, run +make -C docs+. 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/ You can also access these documents online at http://i3.zekjur.net/
i3-input(1), i3-msg(1), i3-wsbar(1)
== AUTHOR == AUTHOR
Michael Stapelberg and contributors Michael Stapelberg and contributors

View File

@ -1,5 +1,7 @@
%option nounput %option nounput
%option noinput %option noinput
%option noyy_top_state
%option stack
%{ %{
/* /*
@ -13,20 +15,59 @@
#include "data.h" #include "data.h"
#include "config.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 EOL (\r?\n)
%Start BINDSYM_COND
%Start BIND_AWS_COND %s BIND_COND
%Start BINDSYM_AWS_COND %s BINDSYM_COND
%Start BIND_A2WS_COND %s BIND_AWS_COND
%Start ASSIGN_COND %s BINDSYM_AWS_COND
%Start COLOR_COND %s BIND_A2WS_COND
%Start SCREEN_COND %s ASSIGN_COND
%Start SCREEN_AWS_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; } <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; } ^[ \t]*#[^\n]* { return TOKCOMMENT; }
<COLOR_COND>[0-9a-fA-F]+ { yylval.string = strdup(yytext); return HEX; } <COLOR_COND>[0-9a-fA-F]+ { yylval.string = strdup(yytext); return HEX; }
[0-9]+ { yylval.number = atoi(yytext); return NUMBER; } [0-9]+ { yylval.number = atoi(yytext); return NUMBER; }
@ -35,7 +76,14 @@ bind { BEGIN(BIND_COND); return TOKBIND; }
bindsym { BEGIN(BINDSYM_COND); return TOKBINDSYM; } bindsym { BEGIN(BINDSYM_COND); return TOKBINDSYM; }
floating_modifier { BEGIN(INITIAL); return TOKFLOATING_MODIFIER; } floating_modifier { BEGIN(INITIAL); return TOKFLOATING_MODIFIER; }
workspace { BEGIN(INITIAL); return TOKWORKSPACE; } 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; } terminal { BEGIN(BIND_AWS_COND); return TOKTERMINAL; }
font { BEGIN(BIND_AWS_COND); return TOKFONT; } font { BEGIN(BIND_AWS_COND); return TOKFONT; }
assign { BEGIN(ASSIGN_COND); return TOKASSIGN; } 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; } ipc_socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; }
new_container { return TOKNEWCONTAINER; } new_container { return TOKNEWCONTAINER; }
new_window { return TOKNEWWINDOW; } new_window { return TOKNEWWINDOW; }
focus_follows_mouse { return TOKFOCUSFOLLOWSMOUSE; }
workspace_bar { return TOKWORKSPACEBAR; }
default { yylval.number = MODE_DEFAULT; return TOKCONTAINERMODE; } default { yylval.number = MODE_DEFAULT; return TOKCONTAINERMODE; }
stacking { yylval.number = MODE_STACK; return TOKCONTAINERMODE; } stacking { yylval.number = MODE_STACK; return TOKCONTAINERMODE; }
tabbed { yylval.number = MODE_TABBED; 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; } Mod5 { yylval.number = BIND_MOD5; return MODIFIER; }
Mode_switch { yylval.number = BIND_MODE_SWITCH; return MODIFIER; } Mode_switch { yylval.number = BIND_MODE_SWITCH; return MODIFIER; }
control { return TOKCONTROL; } control { return TOKCONTROL; }
ctrl { return TOKCONTROL; }
shift { return TOKSHIFT; } shift { return TOKSHIFT; }
→ { return TOKARROW; } → { return TOKARROW; }
\n /* ignore end of line */; {EOL} {
<SCREEN_AWS_COND>x { return (int)yytext[0]; } FREE(context->line_copy);
context->line_number++;
BEGIN(INITIAL);
yy_push_state(BUFFER_LINE);
}
<BIND_COND>[ \t]+ { BEGIN(BIND_AWS_COND); return WHITESPACE; } <BIND_COND>[ \t]+ { BEGIN(BIND_AWS_COND); return WHITESPACE; }
<BINDSYM_COND>[ \t]+ { BEGIN(BINDSYM_AWS_COND); return WHITESPACE; } <BINDSYM_COND>[ \t]+ { BEGIN(BINDSYM_AWS_COND); return WHITESPACE; }
<BIND_AWS_COND>[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; } <BIND_AWS_COND>[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
<BINDSYM_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; } <OUTPUT_COND>[ \t]+ { BEGIN(OUTPUT_AWS_COND); return WHITESPACE; }
<SCREEN_AWS_COND>[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; } <OUTPUT_AWS_COND>[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; }
[ \t]+ { return WHITESPACE; } [ \t]+ { return WHITESPACE; }
\"[^\"]+\" { \"[^\"]+\" {
/* if ASSIGN_COND then */ /* if ASSIGN_COND then */
@ -89,4 +144,11 @@ shift { return TOKSHIFT; }
<BINDSYM_AWS_COND>[a-zA-Z0-9_]+ { yylval.string = strdup(yytext); return WORD; } <BINDSYM_AWS_COND>[a-zA-Z0-9_]+ { yylval.string = strdup(yytext); return WORD; }
[a-zA-Z]+ { yylval.string = strdup(yytext); return WORD; } [a-zA-Z]+ { yylval.string = strdup(yytext); return WORD; }
. { return (int)yytext[0]; } . { return (int)yytext[0]; }
<<EOF>> {
while (yy_start_stack_ptr > 0)
yy_pop_state();
yyterminate();
}
%% %%

View File

@ -21,20 +21,35 @@
#include "table.h" #include "table.h"
#include "workspace.h" #include "workspace.h"
#include "xcb.h" #include "xcb.h"
#include "log.h"
typedef struct yy_buffer_state *YY_BUFFER_STATE; typedef struct yy_buffer_state *YY_BUFFER_STATE;
extern int yylex(void); extern int yylex(struct context *context);
extern int yyparse(void); extern int yyparse(void);
extern FILE *yyin; extern FILE *yyin;
YY_BUFFER_STATE yy_scan_string(const char *); YY_BUFFER_STATE yy_scan_string(const char *);
static struct bindings_head *current_bindings; static struct bindings_head *current_bindings;
static struct context *context;
int yydebug = 1; /* We dont 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) { void yyerror(const char *error_message) {
fprintf(stderr,"error: %s\n",str); 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() { int yywrap() {
@ -55,7 +70,7 @@ void parse_file(const char *f) {
if (fstat(fd, &stbuf) == -1) if (fstat(fd, &stbuf) == -1)
die("Could not fstat file: %s\n", strerror(errno)); 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) { while (read_bytes < stbuf.st_size) {
if ((ret = read(fd, buf + read_bytes, (stbuf.st_size - read_bytes))) < 0) if ((ret = read(fd, buf + read_bytes, (stbuf.st_size - read_bytes))) < 0)
die("Could not read(): %s\n", strerror(errno)); die("Could not read(): %s\n", strerror(errno));
@ -95,7 +110,7 @@ void parse_file(const char *f) {
new->key = sstrdup(v_key); new->key = sstrdup(v_key);
new->value = sstrdup(v_value); new->value = sstrdup(v_value);
SLIST_INSERT_HEAD(&variables, new, variables); 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; continue;
} }
} }
@ -149,18 +164,33 @@ void parse_file(const char *f) {
yy_scan_string(new); yy_scan_string(new);
context = scalloc(sizeof(struct context));
context->filename = f;
if (yyparse() != 0) { if (yyparse() != 0) {
fprintf(stderr, "Could not parse configfile\n"); fprintf(stderr, "Could not parse configfile\n");
exit(1); exit(1);
} }
FREE(context->line_copy);
free(context);
free(new); free(new);
free(buf); 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 %expect 1
%error-verbose
%lex-param { struct context *context }
%union { %union {
int number; int number;
@ -170,40 +200,44 @@ void parse_file(const char *f) {
struct Binding *binding; struct Binding *binding;
} }
%token <number>NUMBER %token <number>NUMBER "<number>"
%token <string>WORD %token <string>WORD "<word>"
%token <string>STR %token <string>STR "<string>"
%token <string>STR_NG %token <string>STR_NG "<string (non-greedy)>"
%token <string>HEX %token <string>HEX "<hex>"
%token <string>OUTPUT "<RandR output>"
%token TOKBIND %token TOKBIND
%token TOKTERMINAL %token TOKTERMINAL
%token TOKCOMMENT %token TOKCOMMENT "<comment>"
%token TOKFONT %token TOKFONT "font"
%token TOKBINDSYM %token TOKBINDSYM "bindsym"
%token MODIFIER %token MODIFIER "<modifier>"
%token TOKCONTROL %token TOKCONTROL "control"
%token TOKSHIFT %token TOKSHIFT "shift"
%token WHITESPACE %token WHITESPACE "<whitespace>"
%token TOKFLOATING_MODIFIER %token TOKFLOATING_MODIFIER "floating_modifier"
%token QUOTEDSTRING %token QUOTEDSTRING "<quoted string>"
%token TOKWORKSPACE %token TOKWORKSPACE "workspace"
%token TOKSCREEN %token TOKOUTPUT "output"
%token TOKASSIGN %token TOKASSIGN "assign"
%token TOKSET %token TOKSET
%token TOKIPCSOCKET %token TOKIPCSOCKET "ipc_socket"
%token TOKEXEC %token TOKEXEC "exec"
%token TOKCOLOR %token TOKCOLOR
%token TOKARROW %token TOKARROW "→"
%token TOKMODE %token TOKMODE "mode"
%token TOKNEWCONTAINER %token TOKNEWCONTAINER "new_container"
%token TOKNEWWINDOW %token TOKNEWWINDOW "new_window"
%token TOKCONTAINERMODE %token TOKFOCUSFOLLOWSMOUSE "focus_follows_mouse"
%token TOKSTACKLIMIT %token TOKWORKSPACEBAR "workspace_bar"
%token TOKCONTAINERMODE "default/stacking/tabbed"
%token TOKSTACKLIMIT "stack-limit"
%% %%
lines: /* empty */ lines: /* empty */
| lines WHITESPACE line | lines WHITESPACE line
| lines error
| lines line | lines line
; ;
@ -213,6 +247,8 @@ line:
| floating_modifier | floating_modifier
| new_container | new_container
| new_window | new_window
| focus_follows_mouse
| workspace_bar
| workspace | workspace
| assign | assign
| ipcsocket | ipcsocket
@ -251,7 +287,7 @@ bind:
new->keycode = $<number>2; new->keycode = $<number>2;
new->mods = $<number>1; new->mods = $<number>1;
new->command = sstrdup($<string>4); new->command = $<string>4;
$<binding>$ = new; $<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); printf("\tFound symbolic mod%d with key %s and command %s\n", $<number>1, $<string>2, $<string>4);
Binding *new = scalloc(sizeof(Binding)); Binding *new = scalloc(sizeof(Binding));
new->symbol = sstrdup($<string>2); new->symbol = $<string>2;
new->mods = $<number>1; new->mods = $<number>1;
new->command = sstrdup($<string>4); new->command = $<string>4;
$<binding>$ = new; $<binding>$ = new;
} }
@ -295,7 +331,7 @@ mode:
} }
struct Mode *mode = scalloc(sizeof(struct Mode)); struct Mode *mode = scalloc(sizeof(struct Mode));
mode->name = strdup($<string>3); mode->name = $<string>3;
mode->bindings = current_bindings; mode->bindings = current_bindings;
current_bindings = NULL; current_bindings = NULL;
SLIST_INSERT_HEAD(&modes, mode, modes); SLIST_INSERT_HEAD(&modes, mode, modes);
@ -325,7 +361,7 @@ modeline:
floating_modifier: floating_modifier:
TOKFLOATING_MODIFIER WHITESPACE binding_modifiers TOKFLOATING_MODIFIER WHITESPACE binding_modifiers
{ {
LOG("floating modifier = %d\n", $<number>3); DLOG("floating modifier = %d\n", $<number>3);
config.floating_modifier = $<number>3; config.floating_modifier = $<number>3;
} }
; ;
@ -333,7 +369,7 @@ floating_modifier:
new_container: new_container:
TOKNEWCONTAINER WHITESPACE TOKCONTAINERMODE 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; config.container_mode = $<number>3;
/* We also need to change the layout of the already existing /* We also need to change the layout of the already existing
@ -355,7 +391,7 @@ new_container:
} }
| TOKNEWCONTAINER WHITESPACE TOKSTACKLIMIT WHITESPACE TOKSTACKLIMIT WHITESPACE NUMBER | 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 = $<number>5;
config.container_stack_limit_value = $<number>7; config.container_stack_limit_value = $<number>7;
@ -374,32 +410,69 @@ new_container:
new_window: new_window:
TOKNEWWINDOW WHITESPACE WORD TOKNEWWINDOW WHITESPACE WORD
{ {
LOG("new windows should start in mode %s\n", $<string>3); DLOG("new windows should start in mode %s\n", $<string>3);
config.default_border = strdup($<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: 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; int ws_num = $<number>3;
if (ws_num < 1) { 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 { } else {
Workspace *ws = workspace_get(ws_num - 1); Workspace *ws = workspace_get(ws_num - 1);
ws->preferred_screen = sstrdup($<string>7); ws->preferred_output = $<string>7;
if ($<string>8 != NULL) if ($<string>8 != NULL) {
workspace_set_name(ws, $<string>8); workspace_set_name(ws, $<string>8);
free($<string>8);
}
} }
} }
| TOKWORKSPACE WHITESPACE NUMBER WHITESPACE workspace_name | TOKWORKSPACE WHITESPACE NUMBER WHITESPACE workspace_name
{ {
int ws_num = $<number>3; int ws_num = $<number>3;
if (ws_num < 1) { 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 { } 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); workspace_set_name(workspace_get(ws_num - 1), $<string>5);
free($<string>5);
}
} }
} }
; ;
@ -415,13 +488,6 @@ workspace_name:
| WORD { $<string>$ = $<string>1; } | 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: assign:
TOKASSIGN WHITESPACE window_class WHITESPACE optional_arrow assign_target TOKASSIGN WHITESPACE window_class WHITESPACE optional_arrow assign_target
{ {
@ -430,7 +496,7 @@ assign:
struct Assignment *new = $<assignment>6; struct Assignment *new = $<assignment>6;
printf(" to %d\n", new->workspace); printf(" to %d\n", new->workspace);
printf(" floating = %d\n", new->floating); printf(" floating = %d\n", new->floating);
new->windowclass_title = strdup($<string>3); new->windowclass_title = $<string>3;
TAILQ_INSERT_TAIL(&assignments, new, assignments); TAILQ_INSERT_TAIL(&assignments, new, assignments);
} }
; ;
@ -471,7 +537,7 @@ optional_arrow:
ipcsocket: ipcsocket:
TOKIPCSOCKET WHITESPACE STR TOKIPCSOCKET WHITESPACE STR
{ {
config.ipc_socket_path = sstrdup($<string>3); config.ipc_socket_path = $<string>3;
} }
; ;
@ -479,7 +545,7 @@ exec:
TOKEXEC WHITESPACE STR TOKEXEC WHITESPACE STR
{ {
struct Autostart *new = smalloc(sizeof(struct Autostart)); struct Autostart *new = smalloc(sizeof(struct Autostart));
new->command = sstrdup($<string>3); new->command = $<string>3;
TAILQ_INSERT_TAIL(&autostarts, new, autostarts); TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
} }
; ;
@ -487,15 +553,15 @@ exec:
terminal: terminal:
TOKTERMINAL WHITESPACE STR TOKTERMINAL WHITESPACE STR
{ {
config.terminal = sstrdup($<string>3); ELOG("The terminal option is DEPRECATED and has no effect. "
printf("terminal %s\n", config.terminal); "Please remove it from your configuration file.\n");
} }
; ;
font: font:
TOKFONT WHITESPACE STR TOKFONT WHITESPACE STR
{ {
config.font = sstrdup($<string>3); config.font = $<string>3;
printf("font %s\n", config.font); printf("font %s\n", config.font);
} }
; ;

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
@ -36,6 +36,8 @@
#include "commands.h" #include "commands.h"
#include "floating.h" #include "floating.h"
#include "resize.h" #include "resize.h"
#include "log.h"
#include "randr.h"
static struct Stack_Window *get_stack_window(xcb_window_t window_id) { static struct Stack_Window *get_stack_window(xcb_window_t window_id) {
struct Stack_Window *current; 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 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_column = (event->event_x / (stack_win->rect.width / container->stack_limit_value));
int clicked_row = (event->event_y / decoration_height); 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; destination = (wrap * clicked_column) + clicked_row;
} else { } else {
int width = (stack_win->rect.width / ceil((float)num_clients / container->stack_limit_value)); int width = (stack_win->rect.width / ceil((float)num_clients / container->stack_limit_value));
int clicked_column = (event->event_x / width); int clicked_column = (event->event_x / width);
int clicked_row = (event->event_y / decoration_height); 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; 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) CIRCLEQ_FOREACH(client, &(stack_win->container->clients), clients)
if (c++ == destination) { if (c++ == destination) {
set_focus(conn, client, true); 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) { static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *event) {
i3Screen *screen; Output *output;
TAILQ_FOREACH(screen, virtual_screens, screens) { TAILQ_FOREACH(output, &outputs, outputs) {
if (screen->bar != event->event) if (output->bar != event->event)
continue; 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) */ /* 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) { if (event->detail == XCB_BUTTON_INDEX_4 || event->detail == XCB_BUTTON_INDEX_5) {
Workspace *ws = c_ws; Workspace *ws = c_ws;
if (event->detail == XCB_BUTTON_INDEX_5) { if (event->detail == XCB_BUTTON_INDEX_5) {
while ((ws = TAILQ_NEXT(ws, workspaces)) != TAILQ_END(workspaces_head)) { while ((ws = TAILQ_NEXT(ws, workspaces)) != TAILQ_END(workspaces_head)) {
if (ws->screen == screen) { if (ws->output == output) {
workspace_show(conn, ws->num + 1); workspace_show(conn, ws->num + 1);
return true; return true;
} }
} }
} else { } else {
while ((ws = TAILQ_PREV(ws, workspaces_head, workspaces)) != TAILQ_END(workspaces)) { 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); workspace_show(conn, ws->num + 1);
return true; return true;
} }
@ -152,13 +154,13 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e
return true; return true;
} }
int drawn = 0; int drawn = 0;
/* Because workspaces can be on different screens, we need to loop /* Because workspaces can be on different outputs, we need to loop
through all of them and decide to count it based on its ->screen */ through all of them and decide to count it based on its ->output */
Workspace *ws; Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) { TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->screen != screen) if (ws->output != output)
continue; 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); ws->num, drawn, ws->text_width);
if (event->event_x > (drawn + 1) && if (event->event_x > (drawn + 1) &&
event->event_x <= (drawn + 1 + ws->text_width + 5 + 5)) { 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; Workspace *ws = con->workspace;
int first = 0, second = 0; 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); to_right, to_left, to_top, to_bottom);
if (to_right < to_left && 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) { to_right < to_bottom) {
/* …right border */ /* …right border */
first = con->col + (con->colspan - 1); first = con->col + (con->colspan - 1);
LOG("column %d\n", first); DLOG("column %d\n", first);
if (!cell_exists(ws, first, con->row) || if (!cell_exists(ws, first, con->row) ||
(first == (ws->cols-1))) (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) { 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 clients parent (= titlebar)… */ /* This was either a focus for a clients parent (= titlebar)… */
Client *client = table_get(&by_child, event->event); Client *client = table_get(&by_child, event->event);
bool border_click = false; 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 * to move around the client if it was floating. if not, we just process
* as usual. */ * as usual. */
if (config.floating_modifier != 0 && if (config.floating_modifier != 0 &&
(event->state & config.floating_modifier) != 0) { (event->state & config.floating_modifier) == config.floating_modifier) {
if (client == NULL) { 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; return 1;
} }
if (client->fullscreen) { 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; return 1;
} }
if (client_is_floating(client)) { if (client_is_floating(client)) {
LOG("button %d pressed\n", event->detail); DLOG("button %d pressed\n", event->detail);
if (event->detail == 1) { if (event->detail == 1) {
LOG("left mouse button, dragging\n"); DLOG("left mouse button, dragging\n");
floating_drag_window(conn, client, event); floating_drag_window(conn, client, event);
} else if (event->detail == 3) { } else if (event->detail == 3) {
LOG("right mouse button\n"); bool proportional = (event->state & BIND_SHIFT);
floating_resize_window(conn, client, event); DLOG("right mouse button\n");
floating_resize_window(conn, client, proportional, event);
} }
return 1; 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)) if (button_press_bar(conn, event))
return 1; return 1;
LOG("Could not handle this button press\n"); DLOG("Could not handle this button press\n");
return 1; return 1;
} }
@ -309,19 +316,19 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
set_focus(conn, client, true); set_focus(conn, client, true);
/* Lets see if this was on the borders (= resize). If not, were done */ /* Lets see if this was on the borders (= resize). If not, were 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; resize_orientation_t orientation = O_VERTICAL;
Container *con = client->container; Container *con = client->container;
int first, second; int first, second;
if (client->dock) { if (client->dock) {
LOG("dock. done.\n"); DLOG("dock. done.\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
xcb_flush(conn); xcb_flush(conn);
return 1; 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 /* Some clients (xfontsel for example) seem to pass clicks on their
* window to the parent window, thus we receive an event here which in * 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_x <= (client->child_rect.x + client->child_rect.width) &&
event->event_y >= client->child_rect.y && event->event_y >= client->child_rect.y &&
event->event_y <= (client->child_rect.y + client->child_rect.height)) { 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; border_click = false;
} }
if (!border_click) { if (!border_click) {
LOG("client. done.\n"); DLOG("client. done.\n");
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
/* Floating clients should be raised on click */ /* Floating clients should be raised on click */
if (client_is_floating(client)) if (client_is_floating(client))
@ -348,7 +355,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
/* Dont handle events inside the titlebar, only borders are interesting */ /* Dont handle events inside the titlebar, only borders are interesting */
i3Font *font = load_font(conn, config.font); i3Font *font = load_font(conn, config.font);
if (event->event_y >= 2 && event->event_y <= (font->height + 2 + 2)) { 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 */ /* Floating clients can be dragged by grabbing their titlebar */
if (client_is_floating(client)) { 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) { } else if (event->event_x > 2) {
/* …right border */ /* …right border */
first = con->col + (con->colspan - 1); first = con->col + (con->colspan - 1);
LOG("column %d\n", first); DLOG("column %d\n", first);
if (!cell_exists(ws, first, con->row) || if (!cell_exists(ws, first, con->row) ||
(first == (ws->cols-1))) (first == (ws->cols-1)))

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
@ -13,6 +13,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <assert.h> #include <assert.h>
#include <limits.h>
#include <xcb/xcb.h> #include <xcb/xcb.h>
#include <xcb/xcb_icccm.h> #include <xcb/xcb_icccm.h>
@ -26,6 +27,8 @@
#include "client.h" #include "client.h"
#include "table.h" #include "table.h"
#include "workspace.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 * 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)) && if (CIRCLEQ_EMPTY(&(container->clients)) &&
(container->mode == MODE_STACK || (container->mode == MODE_STACK ||
container->mode == MODE_TABBED)) { container->mode == MODE_TABBED)) {
LOG("Unmapping stack window\n"); DLOG("Unmapping stack window\n");
struct Stack_Window *stack_win = &(container->stack_win); struct Stack_Window *stack_win = &(container->stack_win);
stack_win->rect.height = 0; stack_win->rect.height = 0;
xcb_unmap_window(conn, stack_win->window); 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. * 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) {
Workspace *workspace = client->workspace; Workspace *workspace;
Output *output;
Rect r;
if (workspace->fullscreen_client != NULL) { 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"); LOG("Not entering fullscreen mode, there already is a fullscreen client.\n");
return; return;
} }
client->fullscreen = true;
workspace->fullscreen_client = client; workspace->fullscreen_client = client;
r = workspace->rect;
LOG("Entering fullscreen mode...\n"); LOG("Entering fullscreen mode...\n");
}
client->fullscreen = true;
/* We just entered fullscreen mode, lets configure the window */ /* We just entered fullscreen mode, lets configure the window */
uint32_t mask = XCB_CONFIG_WINDOW_X | DLOG("child itself will be at %dx%d with size %dx%d\n",
XCB_CONFIG_WINDOW_Y | r.x, r.y, r.width, r.height);
XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT;
uint32_t values[4] = {workspace->rect.x,
workspace->rect.y,
workspace->rect.width,
workspace->rect.height};
LOG("child itself will be at %dx%d with size %dx%d\n", xcb_set_window_rect(conn, client->frame, r);
values[0], values[1], values[2], values[3]);
xcb_configure_window(conn, client->frame, mask, values);
/* Childs coordinates are relative to the parent (=frame) */ /* Childs coordinates are relative to the parent (=frame) */
values[0] = 0; r.x = 0;
values[1] = 0; r.y = 0;
xcb_configure_window(conn, client->child, mask, values); xcb_set_window_rect(conn, client->child, r);
/* Raise the window */ /* 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); xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
Rect child_rect = workspace->rect; fake_configure_notify(conn, r, client->child);
child_rect.x = child_rect.y = 0;
fake_configure_notify(conn, child_rect, client->child);
xcb_flush(conn); xcb_flush(conn);
} }
@ -234,34 +275,26 @@ void client_toggle_fullscreen(xcb_connection_t *conn, Client *client) {
/* dock clients cannot enter fullscreen mode */ /* dock clients cannot enter fullscreen mode */
assert(!client->dock); 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) { if (!client->fullscreen) {
client_enter_fullscreen(conn, client); client_enter_fullscreen(conn, client, true);
return;
}
LOG("leaving fullscreen mode\n");
client->fullscreen = false;
workspace->fullscreen_client = NULL;
if (client_is_floating(client)) {
/* For floating clients its 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);
} else { } else {
client_set_below_floating(conn, client); client_leave_fullscreen(conn, client);
/* Because the coordinates of the window havent changed, it would not be
re-configured if we dont 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);
} }
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))) if (first_floating == TAILQ_END(&(ws->floating_clients)))
return; return;
LOG("Setting below floating\n"); DLOG("Setting below floating\n");
uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW }; 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); xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
if (client->workspace->fullscreen_client == NULL) if (client->workspace->fullscreen_client == NULL)
return; return;
LOG("(and below fullscreen)\n"); DLOG("(and below fullscreen)\n");
/* Ensure that the window is still below the fullscreen window */ /* Ensure that the window is still below the fullscreen window */
values[0] = client->workspace->fullscreen_client->frame; values[0] = client->workspace->fullscreen_client->frame;
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); 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; 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 clients 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;
}

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
@ -22,7 +22,7 @@
#include "table.h" #include "table.h"
#include "layout.h" #include "layout.h"
#include "i3.h" #include "i3.h"
#include "xinerama.h" #include "randr.h"
#include "client.h" #include "client.h"
#include "floating.h" #include "floating.h"
#include "xcb.h" #include "xcb.h"
@ -30,6 +30,10 @@
#include "workspace.h" #include "workspace.h"
#include "commands.h" #include "commands.h"
#include "resize.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) { bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) {
/* If this container is empty, were done */ /* If this container is empty, were done */
@ -45,7 +49,7 @@ bool focus_window_in_container(xcb_connection_t *conn, Container *container, dir
else if (direction == D_DOWN) { else if (direction == D_DOWN) {
if ((candidate = CIRCLEQ_NEXT_OR_NULL(&(container->clients), container->currently_focused, clients)) == NULL) if ((candidate = CIRCLEQ_NEXT_OR_NULL(&(container->clients), container->currently_focused, clients)) == NULL)
candidate = CIRCLEQ_FIRST(&(container->clients)); 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 we could not switch, the container contains exactly one client. We return false */
if (candidate == container->currently_focused) 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) if (current->mark == NULL || strcmp(current->mark, mark) != 0)
continue; continue;
workspace_show(conn, current->workspace->num + 1);
set_focus(conn, current, true); set_focus(conn, current, true);
workspace_show(conn, current->workspace->num + 1);
return; 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) { 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, int new_row = current_row,
new_col = current_col; 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; Workspace *t_ws = c_ws;
/* Makes sure new_col and new_row are within bounds of the new workspace */ /* Makes sure new_col and new_row are within bounds of the new workspace */
void check_colrow_boundaries() { #define CHECK_COLROW_BOUNDARIES \
if (new_col >= t_ws->cols) do { \
new_col = (t_ws->cols - 1); if (new_col >= t_ws->cols) \
if (new_row >= t_ws->rows) new_col = (t_ws->cols - 1); \
new_row = (t_ws->rows - 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 */ /* There always is a container. If not, current_col or current_row is wrong */
assert(container != NULL); assert(container != NULL);
if (container->workspace->fullscreen_client != NULL) { if (container->workspace->fullscreen_client != NULL) {
LOG("You're in fullscreen mode. Won't switch focus\n"); LOG("You're in fullscreen mode. Forcing focus to operate on whole screens\n");
return; thing = THING_SCREEN;
} }
/* For focusing screens, situation is different: we get the rect /* 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 * right/left/bottom/top and just switch to the workspace on
* the target screen. */ * the target screen. */
if (thing == THING_SCREEN) { if (thing == THING_SCREEN) {
i3Screen *cs = c_ws->screen; Output *cs = c_ws->output;
assert(cs != NULL); assert(cs != NULL);
Rect bounds = cs->rect; 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; bounds.y -= bounds.height;
else 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) { if (target == NULL) {
LOG("Target screen NULL\n"); DLOG("Target output NULL\n");
/* Wrap around if the target screen is out of bounds */ /* Wrap around if the target screen is out of bounds */
if (direction == D_RIGHT) if (direction == D_RIGHT)
target = get_screen_most(D_LEFT, cs); target = get_output_most(D_LEFT, cs);
else if (direction == D_LEFT) else if (direction == D_LEFT)
target = get_screen_most(D_RIGHT, cs); target = get_output_most(D_RIGHT, cs);
else if (direction == D_UP) else if (direction == D_UP)
target = get_screen_most(D_DOWN, cs); target = get_output_most(D_DOWN, cs);
else target = get_screen_most(D_UP, 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); workspace_show(conn, target->current_workspace->num + 1);
return; return;
} }
@ -159,31 +164,48 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t
} }
} else { } else {
/* Lets see if there is a screen down/up there to which we can switch */ /* Lets 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); DLOG("container is at %d with height %d\n", container->y, container->height);
i3Screen *screen; Output *output;
int destination_y = (direction == D_UP ? (container->y - 1) : (container->y + container->height + 1)); int destination_y = (direction == D_UP ? (container->y - 1) : (container->y + container->height + 1));
if ((screen = get_screen_containing(container->x, destination_y)) == NULL) { if ((output = get_output_containing(container->x, destination_y)) == NULL) {
LOG("Wrapping screen around vertically\n"); DLOG("Wrapping screen around vertically\n");
/* No screen found? Then wrap */ /* 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); 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) { 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) { 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))) if (new_col > (cols + (t_ws->table[cols][new_row]->colspan - 1)))
continue; continue;
new_col = cols; 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; break;
} }
LOG("Fixed it to new col %d\n", new_col);
} }
} else if (direction == D_LEFT || direction == D_RIGHT) { } else if (direction == D_LEFT || direction == D_RIGHT) {
if (direction == D_RIGHT && cell_exists(t_ws, current_col+1, current_row)) 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 { } else {
/* Lets see if there is a screen left/right here to which we can switch */ /* Lets 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); DLOG("container is at %d with width %d\n", container->x, container->width);
i3Screen *screen; Output *output;
int destination_x = (direction == D_LEFT ? (container->x - 1) : (container->x + container->width + 1)); int destination_x = (direction == D_LEFT ? (container->x - 1) : (container->x + container->width + 1));
if ((screen = get_screen_containing(destination_x, container->y)) == NULL) { if ((output = get_output_containing(destination_x, container->y)) == NULL) {
LOG("Wrapping screen around horizontally\n"); DLOG("Wrapping screen around horizontally\n");
screen = get_screen_most((direction == D_LEFT ? D_RIGHT : D_LEFT), container->workspace->screen); 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); 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) { 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) { 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))) if (new_row > (rows + (t_ws->table[new_col][rows]->rowspan - 1)))
continue; continue;
new_row = rows; new_row = rows;
DLOG("Fixed it to new row %d\n", new_row);
break; 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 { } else {
LOG("direction unhandled\n"); ELOG("direction unhandled\n");
return; return;
} }
check_colrow_boundaries(); CHECK_COLROW_BOUNDARIES;
if (t_ws->table[new_col][new_row]->currently_focused != NULL) if (t_ws->table[new_col][new_row]->currently_focused != NULL)
set_focus(conn, t_ws->table[new_col][new_row]->currently_focused, true); 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))) if (other == CIRCLEQ_END(&(client->container->clients)))
return false; return false;
LOG("i can do that\n"); DLOG("i can do that\n");
/* We can move the client inside its current container */ /* We can move the client inside its current container */
CIRCLEQ_REMOVE(&(client->container->clients), client, clients); CIRCLEQ_REMOVE(&(client->container->clients), client, clients);
if (direction == D_UP) if (direction == D_UP)
@ -357,7 +397,7 @@ static void move_current_window(xcb_connection_t *conn, direction_t direction) {
/* Fix colspan/rowspan if itd overlap */ /* Fix colspan/rowspan if itd overlap */
fix_colrowspan(conn, workspace); fix_colrowspan(conn, workspace);
render_workspace(conn, workspace->screen, workspace); render_workspace(conn, workspace->output, workspace);
xcb_flush(conn); xcb_flush(conn);
set_focus(conn, current_client, true); set_focus(conn, current_client, true);
@ -411,7 +451,7 @@ static void move_current_container(xcb_connection_t *conn, direction_t direction
return; 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 */ /* Swap the containers */
int col = new->col; 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 */ /* 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) || if (!cell_exists(container->workspace, container->col - 1, container->row) ||
CUR_TABLE[container->col-1][container->row]->currently_focused != NULL) { 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; 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++) for (int i = 0; i < container->rowspan; i++)
if (!cell_exists(container->workspace, new_col, container->row + i) || if (!cell_exists(container->workspace, new_col, container->row + i) ||
CUR_TABLE[new_col][container->row + i]->currently_focused != NULL) { 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; return;
} }
/* Check if there are other cells with rowspan, which are in our way. /* Check if there are other cells with rowspan, which are in our way.
* If so, reduce their rowspan. */ * If so, reduce their rowspan. */
for (int i = container->row-1; i >= 0; i--) { 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); new_col, i, CUR_TABLE[new_col][i]->rowspan);
while ((CUR_TABLE[new_col][i]->rowspan-1) >= (container->row - i)) while ((CUR_TABLE[new_col][i]->rowspan-1) >= (container->row - i))
CUR_TABLE[new_col][i]->rowspan--; 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++; container->colspan++;
@ -486,7 +526,7 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction
case D_UP: case D_UP:
if (!cell_exists(container->workspace, container->col, container->row - 1) || if (!cell_exists(container->workspace, container->col, container->row - 1) ||
CUR_TABLE[container->col][container->row-1]->currently_focused != NULL) { 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; return;
} }
@ -494,21 +534,21 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction
snap_current_container(conn, D_DOWN); snap_current_container(conn, D_DOWN);
return; return;
case D_DOWN: { case D_DOWN: {
LOG("snapping down\n"); DLOG("snapping down\n");
int new_row = container->row + container->rowspan; int new_row = container->row + container->rowspan;
for (int i = 0; i < container->colspan; i++) for (int i = 0; i < container->colspan; i++)
if (!cell_exists(container->workspace, container->col + i, new_row) || if (!cell_exists(container->workspace, container->col + i, new_row) ||
CUR_TABLE[container->col + i][new_row]->currently_focused != NULL) { 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; return;
} }
for (int i = container->col-1; i >= 0; i--) { 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); i, new_row, CUR_TABLE[i][new_row]->colspan);
while ((CUR_TABLE[i][new_row]->colspan-1) >= (container->col - i)) while ((CUR_TABLE[i][new_row]->colspan-1) >= (container->col - i))
CUR_TABLE[i][new_row]->colspan--; 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"); 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 /* Check if there is already a fullscreen client on the destination workspace and
* stop moving if so. */ * stop moving if so. */
if (client->fullscreen && (t_ws->fullscreen_client != NULL)) { 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; return;
} }
@ -543,22 +583,27 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl
/* If were moving it to an invisible screen, we need to unmap it */ /* If were moving it to an invisible screen, we need to unmap it */
if (!workspace_is_visible(t_ws)) { 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); client_unmap(conn, client);
} else { } else {
/* If this is not the case, we move the window to a workspace /* 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 * which is on another screen, so we also need to adjust its
* coordinates. */ * 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, uint32_t relative_x = client->rect.x - old_ws->rect.x,
relative_y = client->rect.y - old_ws->rect.y; relative_y = client->rect.y - old_ws->rect.y;
LOG("rel_x = %d, rel_y = %d\n", relative_x, relative_y); 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.x = t_ws->rect.x + relative_x;
client->rect.y = t_ws->rect.y + relative_y; client->rect.y = t_ws->rect.y + relative_y;
LOG("after x = %d, y = %d\n", client->rect.x, client->rect.y); DLOG("after x = %d, y = %d\n", client->rect.x, client->rect.y);
reposition_client(conn, client); reposition_client(conn, client);
xcb_flush(conn); xcb_flush(conn);
} }
}
/* Configure the window above all tiling windows (or below a fullscreen /* Configure the window above all tiling windows (or below a fullscreen
* window, if any) */ * window, if any) */
@ -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); 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); 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; Client *current_client = container->currently_focused;
if (current_client == NULL) { if (current_client == NULL) {
LOG("No currently focused client in current container.\n"); ELOG("No currently focused client in current container.\n");
return; return;
} }
Client *to_focus = CIRCLEQ_NEXT_OR_NULL(&(container->clients), current_client, clients); Client *to_focus = CIRCLEQ_NEXT_OR_NULL(&(container->clients), current_client, clients);
if (to_focus == NULL) if (to_focus == NULL)
to_focus = CIRCLEQ_PREV_OR_NULL(&(container->clients), current_client, clients); 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 /* Check if there is already a fullscreen client on the destination workspace and
* stop moving if so. */ * stop moving if so. */
if (current_client->fullscreen && (t_ws->fullscreen_client != NULL)) { 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; 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); CIRCLEQ_INSERT_TAIL(&(to_container->clients), current_client, clients);
SLIST_INSERT_HEAD(&(to_container->workspace->focus_stack), current_client, focus_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->container = to_container;
current_client->workspace = to_container->workspace; current_client->workspace = to_container->workspace;
@ -636,12 +683,12 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
/* If were moving it to an invisible screen, we need to unmap it */ /* If were moving it to an invisible screen, we need to unmap it */
if (!workspace_is_visible(to_container->workspace)) { 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); client_unmap(conn, current_client);
} else { } else {
if (current_client->fullscreen) { if (current_client->fullscreen) {
LOG("Calling client_enter_fullscreen again\n"); DLOG("Calling client_enter_fullscreen again\n");
client_enter_fullscreen(conn, current_client); 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); 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); 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) { if ((client = get_matching_client(conn, classtitle, NULL)) == NULL) {
free(classtitle); free(classtitle);
LOG("No matching client found.\n"); ELOG("No matching client found.\n");
return; return;
} }
free(classtitle); free(classtitle);
workspace_show(conn, client->workspace->num + 1);
set_focus(conn, client, true); 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 */ /* No match? Either no arguments were specified, or no numbers */
if (result < 1) { if (result < 1) {
LOG("At least one valid argument required\n"); ELOG("At least one valid argument required\n");
return; return;
} }
@ -704,7 +754,7 @@ static void jump_to_container(xcb_connection_t *conn, const char *arguments) {
if (result < 3) if (result < 3)
return; 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 */ /* Move to row/col */
if (row >= c_ws->rows) 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) if (col >= c_ws->cols)
col = c_ws->cols - 1; 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) if (c_ws->table[col][row]->currently_focused != NULL)
set_focus(conn, c_ws->table[col][row]->currently_focused, true); 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) { } else if (strcasecmp(arguments, "ft") == 0) {
Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
if (last_focused == SLIST_END(&(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; return;
} }
@ -749,17 +799,17 @@ static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) {
} else { } else {
/* …or a number was specified */ /* …or a number was specified */
if (sscanf(arguments, "%u", &times) != 1) { if (sscanf(arguments, "%u", &times) != 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; times = 1;
} }
SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients) { SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients) {
if (++count < times) { if (++count < times) {
LOG("Skipping\n"); DLOG("Skipping\n");
continue; continue;
} }
LOG("Focussing\n"); DLOG("Focussing\n");
set_focus(conn, current, true); set_focus(conn, current, true);
break; 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 * 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; Workspace *ws = c_ws;
if (direction == 'n') { if (direction == 'n') {
while ((ws = TAILQ_NEXT(ws, workspaces)) != TAILQ_END(workspaces_head)) { while (1) {
if (ws->screen == NULL) ws = TAILQ_NEXT(ws, workspaces);
if (ws == TAILQ_END(workspaces))
ws = TAILQ_FIRST(workspaces);
if (ws == c_ws)
return;
if (ws->output == NULL)
continue; continue;
workspace_show(conn, ws->num + 1); workspace_show(conn, ws->num + 1);
return; return;
} }
} else if (direction == 'p') { } else if (direction == 'p') {
while ((ws = TAILQ_PREV(ws, workspaces_head, workspaces)) != TAILQ_END(workspaces)) { while (1) {
if (ws->screen == NULL) 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; continue;
workspace_show(conn, ws->num + 1); 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; int first, second;
resize_orientation_t orientation = O_VERTICAL; resize_orientation_t orientation = O_VERTICAL;
Container *con = last_focused->container; 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 (STARTS_WITH(command, "left")) {
if (con->col == 0) if (con->col == 0)
@ -837,7 +907,7 @@ static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, c
command += strlen("left"); command += strlen("left");
} else if (STARTS_WITH(command, "right")) { } else if (STARTS_WITH(command, "right")) {
first = con->col + (con->colspan - 1); first = con->col + (con->colspan - 1);
LOG("column %d\n", first); DLOG("column %d\n", first);
if (!cell_exists(ws, first, con->row) || if (!cell_exists(ws, first, con->row) ||
(first == (ws->cols-1))) (first == (ws->cols-1)))
@ -862,7 +932,7 @@ static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, c
orientation = O_HORIZONTAL; orientation = O_HORIZONTAL;
command += strlen("bottom"); command += strlen("bottom");
} else { } else {
LOG("Syntax: resize <left|right|top|bottom> [+|-]<pixels>\n"); ELOG("Syntax: resize <left|right|top|bottom> [+|-]<pixels>\n");
return; return;
} }
@ -898,14 +968,14 @@ void parse_command(xcb_connection_t *conn, const char *command) {
if (STARTS_WITH(command, "mark")) { if (STARTS_WITH(command, "mark")) {
if (last_focused == NULL) { if (last_focused == NULL) {
LOG("There is no window to mark\n"); ELOG("There is no window to mark\n");
return; return;
} }
const char *rest = command + strlen("mark"); const char *rest = command + strlen("mark");
while (*rest == ' ') while (*rest == ' ')
rest++; rest++;
if (*rest == '\0') { if (*rest == '\0') {
LOG("interactive mark starting\n"); DLOG("interactive mark starting\n");
start_application("i3-input -p 'mark ' -l 1 -P 'Mark: '"); start_application("i3-input -p 'mark ' -l 1 -P 'Mark: '");
} else { } else {
LOG("mark with \"%s\"\n", rest); LOG("mark with \"%s\"\n", rest);
@ -919,7 +989,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
while (*rest == ' ') while (*rest == ' ')
rest++; rest++;
if (*rest == '\0') { 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: '"); start_application("i3-input -p 'goto ' -l 1 -P 'Goto: '");
} else { } else {
LOG("go to \"%s\"\n", rest); 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 (STARTS_WITH(command, "stack-limit ")) {
if (last_focused == NULL || client_is_floating(last_focused)) { if (last_focused == NULL || client_is_floating(last_focused)) {
LOG("No container focused\n"); ELOG("No container focused\n");
return; return;
} }
const char *rest = command + strlen("stack-limit "); 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; last_focused->container->stack_limit = STACK_LIMIT_COLS;
rest += strlen("cols "); rest += strlen("cols ");
} else { } else {
LOG("Syntax: stack-limit <cols|rows> <limit>\n"); ELOG("Syntax: stack-limit <cols|rows> <limit>\n");
return; return;
} }
@ -969,28 +1039,28 @@ void parse_command(xcb_connection_t *conn, const char *command) {
/* Is it an <exit>? */ /* Is it an <exit>? */
if (STARTS_WITH(command, "exit")) { if (STARTS_WITH(command, "exit")) {
LOG("User issued exit-command, exiting without error.\n"); LOG("User issued exit-command, exiting without error.\n");
restore_geometry(global_conn);
ipc_shutdown();
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
} }
/* Is it a <reload>? */ /* Is it a <reload>? */
if (STARTS_WITH(command, "reload")) { if (STARTS_WITH(command, "reload")) {
load_configuration(conn, NULL, true); 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; return;
} }
/* Is it <restart>? Then restart in place. */ /* Is it <restart>? Then restart in place. */
if (STARTS_WITH(command, "restart")) { if (STARTS_WITH(command, "restart")) {
LOG("restarting \"%s\"...\n", start_argv[0]); i3_restart();
/* 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 (STARTS_WITH(command, "kill")) { if (STARTS_WITH(command, "kill")) {
if (last_focused == NULL) { if (last_focused == NULL) {
LOG("There is no window to kill\n"); ELOG("There is no window to kill\n");
return; return;
} }
@ -1015,10 +1085,13 @@ void parse_command(xcb_connection_t *conn, const char *command) {
return; return;
} }
/* Is it 'f' for fullscreen? */ /* Is it 'f' for fullscreen, or 'fg' for fullscreen_global? */
if (command[0] == 'f') { if (command[0] == 'f') {
if (last_focused == NULL) if (last_focused == NULL)
return; return;
if (command[1] == 'g')
client_toggle_fullscreen_global(conn, last_focused);
else
client_toggle_fullscreen(conn, last_focused); client_toggle_fullscreen(conn, last_focused);
return; return;
} }
@ -1026,14 +1099,14 @@ void parse_command(xcb_connection_t *conn, const char *command) {
/* Is it just 's' for stacking or 'd' for default? */ /* Is it just 's' for stacking or 'd' for default? */
if ((command[0] == 's' || command[0] == 'd' || command[0] == 'T') && (command[1] == '\0')) { if ((command[0] == 's' || command[0] == 'd' || command[0] == 'T') && (command[1] == '\0')) {
if (last_focused != NULL && client_is_floating(last_focused)) { 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; return;
} }
LOG("Switching mode for current container\n"); LOG("Switching mode for current container\n");
int new_mode = MODE_DEFAULT; int new_mode = MODE_DEFAULT;
if (command[0] == 's') if (command[0] == 's' && CUR_CELL->mode != MODE_STACK)
new_mode = MODE_STACK; new_mode = MODE_STACK;
if (command[0] == 'T') if (command[0] == 'T' && CUR_CELL->mode != MODE_TABBED)
new_mode = MODE_TABBED; new_mode = MODE_TABBED;
switch_layout_mode(conn, CUR_CELL, new_mode); switch_layout_mode(conn, CUR_CELL, new_mode);
return; return;
@ -1043,7 +1116,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
/* or even 'bt' (toggle border: 'bp' -> 'bb' -> 'bn' ) */ /* or even 'bt' (toggle border: 'bp' -> 'bb' -> 'bn' ) */
if (command[0] == 'b') { if (command[0] == 'b') {
if (last_focused == NULL) { if (last_focused == NULL) {
LOG("No window focused, cannot change border type\n"); ELOG("No window focused, cannot change border type\n");
return; return;
} }
@ -1084,7 +1157,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
with = WITH_SCREEN; with = WITH_SCREEN;
command++; command++;
} else { } else {
LOG("not yet implemented.\n"); ELOG("not yet implemented.\n");
return; return;
} }
} }
@ -1097,7 +1170,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
return; return;
} }
if (last_focused == NULL) { if (last_focused == NULL) {
LOG("Cannot toggle tiling/floating: workspace empty\n"); ELOG("Cannot toggle tiling/floating: workspace empty\n");
return; return;
} }
@ -1113,7 +1186,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
/* Fix colspan/rowspan if itd overlap */ /* Fix colspan/rowspan if itd overlap */
fix_colrowspan(conn, ws); 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 /* Re-focus the client because cleanup_table sets the focus to the last
* focused client inside a container only. */ * focused client inside a container only. */
@ -1134,7 +1207,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
direction_t direction; direction_t direction;
int times = strtol(command, &rest, 10); int times = strtol(command, &rest, 10);
if (rest == NULL) { if (rest == NULL) {
LOG("Invalid command (\"%s\")\n", command); ELOG("Invalid command (\"%s\")\n", command);
return; return;
} }
@ -1152,7 +1225,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
int workspace = strtol(rest, &rest, 10); int workspace = strtol(rest, &rest, 10);
if (rest == NULL) { if (rest == NULL) {
LOG("Invalid command (\"%s\")\n", command); ELOG("Invalid command (\"%s\")\n", command);
return; return;
} }
@ -1164,13 +1237,13 @@ void parse_command(xcb_connection_t *conn, const char *command) {
} }
if (last_focused == NULL) { if (last_focused == NULL) {
LOG("Not performing (no window found)\n"); ELOG("Not performing (no window found)\n");
return; return;
} }
if (client_is_floating(last_focused) && if (client_is_floating(last_focused) &&
(action != ACTION_FOCUS && action != ACTION_MOVE)) { (action != ACTION_FOCUS && action != ACTION_MOVE)) {
LOG("Not performing (floating)\n"); ELOG("Not performing (floating)\n");
return; return;
} }
@ -1185,7 +1258,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
else if (*rest == 'l') else if (*rest == 'l')
direction = D_RIGHT; direction = D_RIGHT;
else { else {
LOG("unknown direction: %c\n", *rest); ELOG("unknown direction: %c\n", *rest);
return; return;
} }
rest++; rest++;
@ -1208,7 +1281,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
/* TODO: this should swap the screens contents /* TODO: this should swap the screens contents
* (e.g. all workspaces) with the next/previous/… * (e.g. all workspaces) with the next/previous/…
* screen */ * screen */
LOG("Not yet implemented\n"); ELOG("Not yet implemented\n");
continue; continue;
} }
if (client_is_floating(last_focused)) { 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 (action == ACTION_SNAP) {
if (with == WITH_SCREEN) { 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; continue;
} }
snap_current_container(conn, direction); snap_current_container(conn, direction);

View File

@ -3,16 +3,23 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * 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 <stdio.h>
#include <string.h> #include <string.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h>
#include <stdlib.h> #include <stdlib.h>
#include <glob.h> #include <glob.h>
#include <wordexp.h>
#include <unistd.h>
/* We need Xlib for XStringToKeysym */ /* We need Xlib for XStringToKeysym */
#include <X11/Xlib.h> #include <X11/Xlib.h>
@ -25,48 +32,44 @@
#include "xcb.h" #include "xcb.h"
#include "table.h" #include "table.h"
#include "workspace.h" #include "workspace.h"
#include "log.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);
Config config; Config config;
struct modes_head modes; struct modes_head modes;
bool config_use_lexer = false;
/* /*
* This function resolves ~ in pathnames. * This function resolves ~ in pathnames.
* *
*/ */
static char *glob_path(const char *path) { char *glob_path(const char *path) {
static glob_t globbuf; static glob_t globbuf;
if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0) if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0)
die("glob() failed"); die("glob() failed");
char *result = sstrdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path); char *result = sstrdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path);
globfree(&globbuf); 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; 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) { bool path_exists(const char *path) {
char *pos; struct stat buf;
/* To prevent endless recursions when the user makes an error configuring, return (stat(path, &buf) == 0);
* 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 its just a pointer.
* So we need to hard-code the dimensions here. */
strncpy(buffer, replaced, 1026);
free(replaced);
}
} }
/** /**
@ -75,57 +78,88 @@ static void replace_variable(char *buffer, const char *key, const char *value) {
* *
*/ */
void ungrab_all_keys(xcb_connection_t *conn) { 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); 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) { static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint32_t keycode) {
LOG("Grabbing %d\n", keycode); DLOG("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 */ /* Grab the key in all combinations */
#define GRAB_KEY(modifier) xcb_grab_key(conn, 0, root, modifier, keycode, \ #define GRAB_KEY(modifier) \
XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC) do { \
GRAB_KEY(bind->mods); xcb_grab_key(conn, 0, root, modifier, keycode, \
GRAB_KEY(bind->mods | xcb_numlock_mask); XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); \
GRAB_KEY(bind->mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK); } 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 events 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; Binding *bind;
TAILQ_FOREACH(bind, bindings, bindings) { TAILQ_FOREACH(bind, bindings, bindings) {
/* The easy case: the user specified a keycode directly. */ if (bind->keycode > 0)
if (bind->keycode > 0) {
grab_keycode_for_binding(conn, bind, bind->keycode);
continue; continue;
}
/* We need to translate the symbol to a keycode */ /* We need to translate the symbol to a keycode */
xcb_keysym_t keysym = XStringToKeysym(bind->symbol); xcb_keysym_t keysym = XStringToKeysym(bind->symbol);
if (keysym == NoSymbol) { 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; continue;
} }
#ifdef OLD_XCB_KEYSYMS_API #ifdef OLD_XCB_KEYSYMS_API
bind->number_keycodes = 1; bind->number_keycodes = 1;
xcb_keycode_t code = xcb_key_symbols_get_keycode(keysyms, keysym); xcb_keycode_t code = xcb_key_symbols_get_keycode(keysyms, keysym);
LOG("Translated symbol \"%s\" to 1 keycode (%d)\n", bind->symbol, code); DLOG("Translated symbol \"%s\" to 1 keycode (%d)\n", bind->symbol, code);
grab_keycode_for_binding(conn, bind, code); grab_keycode_for_binding(global_conn, bind, code);
bind->translated_to = smalloc(sizeof(xcb_keycode_t)); bind->translated_to = smalloc(sizeof(xcb_keycode_t));
memcpy(bind->translated_to, &code, sizeof(xcb_keycode_t)); memcpy(bind->translated_to, &code, sizeof(xcb_keycode_t));
#else #else
uint32_t last_keycode = 0; uint32_t last_keycode = 0;
xcb_keycode_t *keycodes = xcb_key_symbols_get_keycode(keysyms, keysym); xcb_keycode_t *keycodes = xcb_key_symbols_get_keycode(keysyms, keysym);
if (keycodes == NULL) { if (keycodes == NULL) {
LOG("Could not translate symbol \"%s\"\n", bind->symbol); DLOG("Could not translate symbol \"%s\"\n", bind->symbol);
continue; continue;
} }
@ -136,11 +170,10 @@ void grab_all_keys(xcb_connection_t *conn) {
* and skip them */ * and skip them */
if (last_keycode == *walk) if (last_keycode == *walk)
continue; continue;
grab_keycode_for_binding(conn, bind, *walk);
last_keycode = *walk; last_keycode = *walk;
bind->number_keycodes++; 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)); bind->translated_to = smalloc(bind->number_keycodes * sizeof(xcb_keycode_t));
memcpy(bind->translated_to, keycodes, bind->number_keycodes * sizeof(xcb_keycode_t)); memcpy(bind->translated_to, keycodes, bind->number_keycodes * sizeof(xcb_keycode_t));
free(keycodes); 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 * 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); ungrab_all_keys(conn);
bindings = mode->bindings; bindings = mode->bindings;
grab_all_keys(conn); translate_keysyms();
grab_all_keys(conn, false);
return; 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 users 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) { 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); TAILQ_REMOVE(&assignments, assign, assignments);
FREE(assign); FREE(assign);
} }
/* Clear workspace names */
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces)
workspace_set_name(ws, NULL);
} }
SLIST_INIT(&modes); SLIST_INIT(&modes);
@ -220,388 +352,36 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
bindings = default_mode->bindings; 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) \ #define REQUIRED_OPTION(name) \
if (config.name == NULL) \ if (config.name == NULL) \
die("You did not specify required configuration option " #name "\n"); 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 */ /* Clear the old config or initialize the data structure */
memset(&config, 0, sizeof(config)); memset(&config, 0, sizeof(config));
SLIST_INIT(&variables);
/* Initialize default colors */ /* Initialize default colors */
config.client.focused.border = get_colorpixel(conn, "#4c7899"); #define INIT_COLOR(x, cborder, cbackground, ctext) \
config.client.focused.background = get_colorpixel(conn, "#285577"); do { \
config.client.focused.text = get_colorpixel(conn, "#ffffff"); 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"); INIT_COLOR(config.client.focused, "#4c7899", "#285577", "#ffffff");
config.client.focused_inactive.background = get_colorpixel(conn, "#5f676a"); INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff");
config.client.focused_inactive.text = get_colorpixel(conn, "#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"); parse_configuration(override_configpath);
config.client.unfocused.background = get_colorpixel(conn, "#222222");
config.client.unfocused.text = get_colorpixel(conn, "#888888");
config.client.urgent.border = get_colorpixel(conn, "#2f343a"); if (reload) {
config.client.urgent.background = get_colorpixel(conn, "#900000"); translate_keysyms();
config.client.urgent.text = get_colorpixel(conn, "#ffffff"); grab_all_keys(conn, false);
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 were 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 were 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);
}
}
REQUIRED_OPTION(terminal);
REQUIRED_OPTION(font); REQUIRED_OPTION(font);
/* Set an empty name for every workspace which got no name */ /* 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); workspace_set_name(ws, NULL);
} }
return;
} }

43
src/container.c Normal file
View File

@ -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;
}

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
@ -14,6 +14,8 @@
#include <stdio.h> #include <stdio.h>
#include <xcb/xcb.h> #include <xcb/xcb.h>
#include "log.h"
static const char *labelError[] = { static const char *labelError[] = {
"Success", "Success",
"BadRequest", "BadRequest",
@ -219,19 +221,21 @@ int format_event(xcb_generic_event_t *e) {
switch(e->response_type) { switch(e->response_type) {
case 0: case 0:
printf("Error %s on seqnum %d (%s).\n", DLOG("Error %s on seqnum %d (%s).\n",
labelError[*((uint8_t *) e + 1)], labelError[*((uint8_t *) e + 1)],
seqnum, seqnum,
labelRequest[*((uint8_t *) e + 10)]); labelRequest[*((uint8_t *) e + 10)]);
break; break;
default: 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], labelEvent[e->response_type],
seqnum, seqnum,
labelSendEvent[sendEvent]); labelSendEvent[sendEvent]);
break; break;
case XCB_KEYMAP_NOTIFY: case XCB_KEYMAP_NOTIFY:
printf("Event %s%s.\n", DLOG("Event %s%s.\n",
labelEvent[e->response_type], labelEvent[e->response_type],
labelSendEvent[sendEvent]); labelSendEvent[sendEvent]);
break; break;

103
src/ewmh.c Normal file
View File

@ -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,
&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) {
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);
}

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
@ -27,6 +27,7 @@
#include "client.h" #include "client.h"
#include "floating.h" #include "floating.h"
#include "workspace.h" #include "workspace.h"
#include "log.h"
/* /*
* Toggles floating mode for the given client. * 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); i3Font *font = load_font(conn, config.font);
if (client->dock) { if (client->dock) {
LOG("Not putting dock client into floating mode\n"); DLOG("Not putting dock client into floating mode\n");
return; return;
} }
if (con == NULL) { 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; Client *next_tiling;
Workspace *ws = client->workspace; Workspace *ws = client->workspace;
SLIST_FOREACH(next_tiling, &(ws->focus_stack), focus_clients) 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 */ /* Remove the client from the list of floating clients */
TAILQ_REMOVE(&(ws->floating_clients), client, 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; Client *old_focused = con->currently_focused;
/* Preserve position/size */ /* Preserve position/size */
memcpy(&(client->floating_rect), &(client->rect), sizeof(Rect)); 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); CIRCLEQ_INSERT_AFTER(&(con->clients), old_focused, client, clients);
else CIRCLEQ_INSERT_TAIL(&(con->clients), 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; con->currently_focused = client;
client_set_below_floating(conn, client); client_set_below_floating(conn, client);
@ -85,7 +86,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic
return; 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 */ /* Remove the client of its container */
client_remove_from_container(conn, client, con, false); 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); TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients);
if (con->currently_focused == client) { 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 */ /* Get the next client in the focus stack for this particular container */
con->currently_focused = get_last_focused_client(conn, con, NULL); 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.width = client->child_rect.width + 2 + 2;
client->rect.height = client->child_rect.height + (font->height + 2 + 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); client->floating_rect.width, client->floating_rect.height);
} else { } else {
/* If the client was already in floating before we restore the old position / size */ /* 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); client->floating_rect.width, client->floating_rect.height);
memcpy(&(client->rect), &(client->floating_rect), sizeof(Rect)); memcpy(&(client->rect), &(client->floating_rect), sizeof(Rect));
} }
@ -162,23 +163,25 @@ void floating_assign_to_workspace(Client *client, Workspace *new_workspace) {
} }
/* /*
* Called whenever the user clicks on a border (not the titlebar!) of a floating window. * This is an ugly data structure which we need because there is no standard
* Determines on which border the user clicked and launches the drag_pointer function * way of having nested functions (only available as a gcc extension at the
* with the resize_callback. * moment, clang doesnt support it) or blocks (only available as a clang
* extension and only on Mac OS X systems at the moment).
* *
*/ */
int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) { struct resize_callback_params {
LOG("floating border click\n");
border_t border; border_t border;
xcb_button_press_event_t *event;
};
void resize_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) { DRAGGING_CB(resize_callback) {
switch (border) { struct resize_callback_params *params = extra;
xcb_button_press_event_t *event = params->event;
switch (params->border) {
case BORDER_RIGHT: { case BORDER_RIGHT: {
int new_width = old_rect->width + (new_x - event->root_x); int new_width = old_rect->width + (new_x - event->root_x);
if ((new_width < 0) || if ((new_width < 0) ||
(new_width < 50 && client->rect.width >= new_width)) (new_width < client_min_width(client) && client->rect.width >= new_width))
return; return;
client->rect.width = new_width; client->rect.width = new_width;
break; break;
@ -187,7 +190,7 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre
case BORDER_BOTTOM: { case BORDER_BOTTOM: {
int new_height = old_rect->height + (new_y - event->root_y); int new_height = old_rect->height + (new_y - event->root_y);
if ((new_height < 0) || if ((new_height < 0) ||
(new_height < 20 && client->rect.height >= new_height)) (new_height < client_min_height(client) && client->rect.height >= new_height))
return; return;
client->rect.height = old_rect->height + (new_y - event->root_y); client->rect.height = old_rect->height + (new_y - event->root_y);
break; break;
@ -196,7 +199,7 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre
case BORDER_TOP: { case BORDER_TOP: {
int new_height = old_rect->height + (event->root_y - new_y); int new_height = old_rect->height + (event->root_y - new_y);
if ((new_height < 0) || if ((new_height < 0) ||
(new_height < 20 && client->rect.height >= new_height)) (new_height < client_min_height(client) && client->rect.height >= new_height))
return; return;
client->rect.y = old_rect->y + (new_y - event->root_y); client->rect.y = old_rect->y + (new_y - event->root_y);
@ -207,7 +210,7 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre
case BORDER_LEFT: { case BORDER_LEFT: {
int new_width = old_rect->width + (event->root_x - new_x); int new_width = old_rect->width + (event->root_x - new_x);
if ((new_width < 0) || if ((new_width < 0) ||
(new_width < 50 && client->rect.width >= new_width)) (new_width < client_min_width(client) && client->rect.width >= new_width))
return; return;
client->rect.x = old_rect->x + (new_x - event->root_x); client->rect.x = old_rect->x + (new_x - event->root_x);
client->rect.width = new_width; client->rect.width = new_width;
@ -219,7 +222,19 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre
reposition_client(conn, client); reposition_client(conn, client);
resize_client(conn, client); resize_client(conn, client);
xcb_flush(conn); 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
* with the resize_callback.
*
*/
int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
DLOG("floating border click\n");
border_t border;
if (event->event_y < 2) if (event->event_y < 2)
border = BORDER_TOP; border = BORDER_TOP;
@ -230,27 +245,22 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre
else if (event->event_x >= (client->rect.width - 2)) else if (event->event_x >= (client->rect.width - 2))
border = BORDER_RIGHT; border = BORDER_RIGHT;
else { else {
LOG("Not on any border, not doing anything.\n"); DLOG("Not on any border, not doing anything.\n");
return 1; 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, &params);
return 1; return 1;
} }
DRAGGING_CB(drag_window_callback) {
struct xcb_button_press_event_t *event = extra;
/*
* Called when the user clicked on the titlebar of a floating window.
* Calls the drag_pointer function with the drag_window callback
*
*/
void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
LOG("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 */ /* Reposition the client correctly while moving */
client->rect.x = old_rect->x + (new_x - event->root_x); client->rect.x = old_rect->x + (new_x - event->root_x);
client->rect.y = old_rect->y + (new_y - event->root_y); client->rect.y = old_rect->y + (new_y - event->root_y);
@ -259,36 +269,103 @@ void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_pre
* we need to initiate that on our own */ * we need to initiate that on our own */
fake_absolute_configure_notify(conn, client); fake_absolute_configure_notify(conn, client);
/* fake_absolute_configure_notify flushes */ /* fake_absolute_configure_notify flushes */
}
drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback);
} }
/* /*
* Called when the user right-clicked on the titlebar of a floating window to * Called when the user clicked on the titlebar of a floating window.
* resize it. * Calls the drag_pointer function with the drag_window callback
* 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) { void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) {
LOG("floating_resize_window\n"); DLOG("floating_drag_window\n");
void resize_window_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) { drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback, event);
int32_t new_width = old_rect->width + (new_x - event->root_x); }
int32_t new_height = old_rect->height + (new_y - event->root_y);
/* Obey minimum window size and reposition the client */ /*
if (new_width >= 50) * This is an ugly data structure which we need because there is no standard
client->rect.width = new_width; * way of having nested functions (only available as a gcc extension at the
* moment, clang doesnt 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;
};
if (new_height >= 20) DRAGGING_CB(resize_window_callback) {
client->rect.height = new_height; 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 flushes */
resize_client(conn, client); resize_client(conn, client);
} }
drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback); /*
* 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,
bool proportional, xcb_button_press_event_t *event) {
DLOG("floating_resize_window\n");
/* 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;
if (event->event_x <= (client->rect.width / 2))
corner |= BORDER_LEFT;
else corner |= BORDER_RIGHT;
if (event->event_y <= (client->rect.height / 2))
corner |= BORDER_TOP;
else corner |= BORDER_RIGHT;
struct resize_window_callback_params params = { corner, proportional, event };
drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback, &params);
} }
@ -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, 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; xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
uint32_t new_x, new_y; uint32_t new_x, new_y;
Rect old_rect; Rect old_rect;
@ -351,12 +428,12 @@ void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event
break; break;
case XCB_UNMAP_NOTIFY: case XCB_UNMAP_NOTIFY:
LOG("Unmap-notify, aborting\n"); DLOG("Unmap-notify, aborting\n");
xcb_event_handle(&evenths, inside_event); xcb_event_handle(&evenths, inside_event);
goto done; goto done;
default: default:
LOG("Passing to original handler\n"); DLOG("Passing to original handler\n");
/* Use original handler */ /* Use original handler */
xcb_event_handle(&evenths, inside_event); xcb_event_handle(&evenths, inside_event);
break; 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_x = ((xcb_motion_notify_event_t*)last_motion_notify)->root_x;
new_y = ((xcb_motion_notify_event_t*)last_motion_notify)->root_y; 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); FREE(last_motion_notify);
} }
done: done:
@ -387,7 +464,7 @@ done:
* *
*/ */
void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, direction_t direction) { 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) { if (direction == D_LEFT || direction == D_RIGHT) {
/* Go to the next/previous floating client */ /* 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) { 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 destination = currently_focused->rect;
Rect *screen = &(currently_focused->workspace->screen->rect); Rect *screen = &(currently_focused->workspace->output->rect);
switch (direction) { switch (direction) {
case D_LEFT: 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.x + 5) >= (int32_t)(screen->x + screen->width) ||
(int32_t)(destination.y + destination.height - 5) <= (int32_t)screen->y || (int32_t)(destination.y + destination.height - 5) <= (int32_t)screen->y ||
(int32_t)(destination.y + 5) >= (int32_t)(screen->y + screen->height)) { (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; return;
} }
@ -459,7 +541,7 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace) {
Client *client; Client *client;
workspace->floating_hidden = !workspace->floating_hidden; 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) { TAILQ_FOREACH(client, &(workspace->floating_clients), floating_clients) {
if (workspace->floating_hidden) if (workspace->floating_hidden)
client_unmap(conn, client); client_unmap(conn, client);

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
@ -17,6 +17,7 @@
#include <xcb/xcb.h> #include <xcb/xcb.h>
#include <xcb/xcb_atom.h> #include <xcb/xcb_atom.h>
#include <xcb/xcb_icccm.h> #include <xcb/xcb_icccm.h>
#include <xcb/randr.h>
#include <X11/XKBlib.h> #include <X11/XKBlib.h>
@ -28,7 +29,7 @@
#include "data.h" #include "data.h"
#include "xcb.h" #include "xcb.h"
#include "util.h" #include "util.h"
#include "xinerama.h" #include "randr.h"
#include "config.h" #include "config.h"
#include "queue.h" #include "queue.h"
#include "resize.h" #include "resize.h"
@ -36,6 +37,9 @@
#include "manage.h" #include "manage.h"
#include "floating.h" #include "floating.h"
#include "workspace.h" #include "workspace.h"
#include "log.h"
#include "container.h"
#include "ipc.h"
/* After mapping/unmapping windows, a notify event is generated. However, we dont want it, /* After mapping/unmapping windows, a notify event is generated. However, we dont want it,
since itd trigger an infinite loop of switching between the different windows when since itd 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; 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 * There was a key press. We compare this key code with our bindings table and pass
* the bound action to parse_command(). * the bound action to parse_command().
* *
*/ */
int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) { 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 */ /* 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); 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 /* Only use the lower 8 bits of the state (modifier masks) so that mouse
* button masks are filtered out */ * button masks are filtered out */
state_filtered &= 0xFF; 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 if (xkb_current_group == XkbGroup2Index)
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)
state_filtered |= BIND_MODE_SWITCH; 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 */ /* Find the binding */
Binding *bind; Binding *bind = get_binding(state_filtered, event->detail);
TAILQ_FOREACH(bind, bindings, bindings) {
/* First compare the modifiers */
if (bind->mods != state_filtered)
continue;
/* If a symbol was specified by the user, we need to look in /* No match? Then the user has Mode_switch enabled but does not have a
* the array of translated keycodes for the events keycode */ * specific keybinding. Fall back to the default keybindings (without
if (bind->symbol != NULL) { * Mode_switch). Makes it much more convenient for users of a hybrid
if (memmem(bind->translated_to, * layout (like us, ru). */
bind->number_keycodes * sizeof(xcb_keycode_t), if (bind == NULL) {
&(event->detail), sizeof(xcb_keycode_t)) != NULL) state_filtered &= ~(BIND_MODE_SWITCH);
break; DLOG("no match, new state_filtered = %d\n", state_filtered);
} else { if ((bind = get_binding(state_filtered, event->detail)) == NULL) {
/* This case is easier: The user specified a keycode */ ELOG("Could not lookup key binding (modifiers %d, keycode %d)\n",
if (bind->keycode == event->detail) state_filtered, event->detail);
break;
}
}
/* 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; return 1;
} }
}
parse_command(conn, bind->command); 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; 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) { static void check_crossing_screen_boundary(uint32_t x, uint32_t y) {
i3Screen *screen; Output *output;
if ((screen = get_screen_containing(x, y)) == NULL) { if ((output = get_output_containing(x, y)) == NULL) {
LOG("ERROR: No such screen\n"); ELOG("ERROR: No such screen\n");
return; return;
} }
if (screen == c_ws->screen) if (output == c_ws->output)
return; return;
c_ws->current_row = current_row; c_ws->current_row = current_row;
c_ws->current_col = current_col; c_ws->current_col = current_col;
c_ws = screen->current_workspace; c_ws = output->current_workspace;
current_row = c_ws->current_row; current_row = c_ws->current_row;
current_col = c_ws->current_col; 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 its 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) { 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) { 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; return 1;
} }
/* Some events are not interesting, because they were not generated actively by the /* 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 not, then the user moved his cursor to the root window. In that case, we adjust c_ws */
if (client == NULL) { 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); check_crossing_screen_boundary(event->root_x, event->root_y);
return 1; return 1;
} }
@ -220,18 +210,19 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_
if (client->container != NULL && if (client->container != NULL &&
client->container->mode == MODE_STACK && client->container->mode == MODE_STACK &&
client->container->currently_focused != client) { client->container->currently_focused != client) {
LOG("Plausibility check says: no\n"); DLOG("Plausibility check says: no\n");
return 1; 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 /* 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, * the current one (see src/mainx.c:reparent_window). Shortly after it was created,
* an enter_notify will follow. */ * 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; return 1;
} }
if (!config.disable_focus_follows_mouse)
set_focus(conn, client, false); set_focus(conn, client, false);
return 1; return 1;
@ -264,13 +255,14 @@ int handle_mapping_notify(void *ignored, xcb_connection_t *conn, xcb_mapping_not
event->request != XCB_MAPPING_MODIFIER) event->request != XCB_MAPPING_MODIFIER)
return 0; 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_refresh_keyboard_mapping(keysyms, event);
xcb_get_numlock_mask(conn); xcb_get_numlock_mask(conn);
ungrab_all_keys(conn); ungrab_all_keys(conn);
grab_all_keys(conn); translate_keysyms();
grab_all_keys(conn, false);
return 0; 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); 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); add_ignore_event(event->sequence);
manage_window(prophs, conn, event->window, cookie, false); 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) { 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); event->window, event->x, event->y, event->width, event->height);
Client *client = table_get(&by_child, event->window); 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) { if (client->fullscreen) {
LOG("Client is in fullscreen mode\n"); DLOG("Client is in fullscreen mode\n");
Rect child_rect = client->workspace->rect; Rect child_rect = client->workspace->rect;
child_rect.x = child_rect.y = 0; 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); client->rect.x, client->rect.y, client->rect.width, client->rect.height);
/* Push the new position/size to X11 */ /* 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 */ /* Dock clients can be reconfigured in their height */
if (client->dock) { 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)) { 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; return 1;
} }
client->desired_height = event->height; client->desired_height = event->height;
render_workspace(conn, c_ws->screen, c_ws); render_workspace(conn, c_ws->output, c_ws);
xcb_flush(conn); xcb_flush(conn);
return 1; return 1;
} }
if (client->fullscreen) { if (client->fullscreen) {
LOG("Client is in fullscreen mode\n"); DLOG("Client is in fullscreen mode\n");
Rect child_rect = client->container->workspace->rect; Rect child_rect = client->container->workspace->rect;
child_rect.x = child_rect.y = 0; 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 * Configuration notifies are only handled because we need to set up ignore for
* enter notify events * the following enter notify events.
* *
*/ */
int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_notify_event_t *event) { 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 */ /* We ignore this sequence twice because events for child and frame should be ignored */
add_ignore_event(event->sequence); add_ignore_event(event->sequence);
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; return 1;
} }
@ -474,10 +470,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
return 1; return 1;
} }
LOG("event->window = %08x, event->event = %08x\n", event->window, event->event); DLOG("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("UnmapNotify for 0x%08x (received from 0x%08x)\n", event->window, event->event);
if (client == NULL) { 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 /* This was most likely the destroyed frame of a client which is
* currently being unmapped, so we add this sequence (again!) to * 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); client = table_remove(&by_child, event->window);
/* If this was the fullscreen client, we need to unset it */ /* If this was the fullscreen client, we need to unset it from all
if (client->fullscreen) * workspaces it was on (global fullscreen) */
client->workspace->fullscreen_client = NULL; 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 */ /* Clients without a container are either floating or dock windows */
if (client->container != NULL) { 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)) if ((con->currently_focused != NULL) && ((con == CUR_CELL) || client->fullscreen))
set_focus(conn, con->currently_focused, true); set_focus(conn, con->currently_focused, true);
} else if (client_is_floating(client)) { } 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); TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients);
SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
} }
if (client->dock) { if (client->dock) {
LOG("Removing from dock clients\n"); DLOG("Removing from dock clients\n");
SLIST_REMOVE(&(client->workspace->screen->dock_clients), client, Client, dock_clients); 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); xcb_reparent_window(conn, client->child, root, 0, 0);
client_unmap(conn, client); 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)) if (workspace_is_visible(client->workspace))
workspace_empty = false; workspace_empty = false;
if (workspace_empty) if (workspace_empty) {
client->workspace->screen = NULL; client->workspace->output = NULL;
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}");
}
/* Remove the urgency flag if set */ /* Remove the urgency flag if set */
client->urgent = false; 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) if (to_focus != NULL)
set_focus(conn, to_focus, true); set_focus(conn, to_focus, true);
else { 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_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
xcb_flush(conn); xcb_flush(conn);
} }
@ -573,6 +576,26 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
return 1; 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 * 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, 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) { xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
if (prop == NULL || xcb_get_property_value_length(prop) == 0) { 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; return 1;
} }
Client *client = table_get(&by_child, window); 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) if (client->dock)
return 1; return 1;
if (client->container != NULL && if (!workspace_is_visible(client->workspace))
(client->container->mode == MODE_STACK || return 1;
client->container->mode == MODE_TABBED))
int mode = container_mode(client->container, true);
if (mode == MODE_STACK || mode == MODE_TABBED)
render_container(conn, client->container); render_container(conn, client->container);
else decorate_window(conn, client, client->frame, client->titlegc, 0, 0); else decorate_window(conn, client, client->frame, client->titlegc, 0, 0);
xcb_flush(conn); 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, 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) { xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
if (prop == NULL || xcb_get_property_value_length(prop) == 0) { if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
LOG("prop == NULL\n"); DLOG("prop == NULL\n");
return 1; return 1;
} }
Client *client = table_get(&by_child, window); 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; char *new_name;
if (asprintf(&new_name, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)) == -1) { if (asprintf(&new_name, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)) == -1) {
perror("Could not get old name"); perror("Could not get old name");
LOG("Could not get old name\n"); DLOG("Could not get old name\n");
return 1; return 1;
} }
/* Convert it to UCS-2 here for not having to convert it later every time we want to pass it to X */ /* 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) if (client->dock)
return 1; return 1;
if (!workspace_is_visible(client->workspace))
return 1;
if (client->container != NULL && if (client->container != NULL &&
(client->container->mode == MODE_STACK || (client->container->mode == MODE_STACK ||
client->container->mode == MODE_TABBED)) 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, 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) { xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
if (prop == NULL || xcb_get_property_value_length(prop) == 0) { if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
LOG("prop == NULL\n"); DLOG("prop == NULL\n");
return 1; return 1;
} }
Client *client = table_get(&by_child, window); 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 */ skip all events but the last one */
if (event->count != 0) if (event->count != 0)
return 1; return 1;
LOG("window = %08x\n", event->window); DLOG("window = %08x\n", event->window);
Client *client = table_get(&by_parent, event->window); Client *client = table_get(&by_parent, event->window);
if (client == NULL) { 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? */ /* …or one of the bars? */
i3Screen *screen; Output *output;
TAILQ_FOREACH(screen, virtual_screens, screens) TAILQ_FOREACH(output, &outputs, outputs)
if (screen->bar == event->window) if (output->bar == event->window)
render_layout(conn); render_layout(conn);
return 1; return 1;
} }
@ -760,9 +788,7 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
if (client->dock) if (client->dock)
return 1; return 1;
if (client->container == NULL || if (container_mode(client->container, true) == MODE_DEFAULT)
(client->container->mode != MODE_STACK &&
client->container->mode != MODE_TABBED))
decorate_window(conn, client, client->frame, client->titlegc, 0, 0); decorate_window(conn, client, client->frame, client->titlegc, 0, 0);
else { else {
uint32_t background_color; 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 */ /* Draw a black background */
xcb_change_gc_single(conn, client->titlegc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); 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_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); xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
} else { } 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))) event->data.data32[0] == _NET_WM_STATE_TOGGLE)))
client_toggle_fullscreen(conn, client); client_toggle_fullscreen(conn, client);
} else { } else {
LOG("unhandled clientmessage\n"); ELOG("unhandled clientmessage\n");
return 0; 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) { 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 /* TODO: Implement this one. To do this, implement a little test program which sleep(1)s
before changing this property. */ 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; 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) { xcb_atom_t name, xcb_get_property_reply_t *reply) {
Client *client = table_get(&by_child, window); Client *client = table_get(&by_child, window);
if (client == NULL) { if (client == NULL) {
LOG("Received WM_SIZE_HINTS for unknown client\n"); DLOG("Received WM_SIZE_HINTS for unknown client\n");
return 1; return 1;
} }
xcb_size_hints_t size_hints; 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)) { if ((size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE)) {
// TODO: Minimum size is not yet implemented // 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);
} }
if ((size_hints.flags & XCB_SIZE_HINT_P_RESIZE_INC)) {
bool changed = false; bool changed = false;
if ((size_hints.flags & XCB_SIZE_HINT_P_RESIZE_INC)) {
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) { if (client->width_increment != size_hints.width_inc) {
client->width_increment = size_hints.width_inc; client->width_increment = size_hints.width_inc;
changed = true; 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) { if (client->height_increment != size_hints.height_inc) {
client->height_increment = size_hints.height_inc; client->height_increment = size_hints.height_inc;
changed = true; changed = true;
} }
if (changed) { if (changed)
resize_client(conn, client); DLOG("resize increments changed\n");
xcb_flush(conn);
}
} }
int base_width = 0, base_height = 0; 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. /* base_width/height are the desired size of the window.
We check if either the program-specified size or the program-specified We check if either the program-specified size or the program-specified
min-size is available */ 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_width = size_hints.base_width;
base_height = size_hints.base_height; base_height = size_hints.base_height;
} else if (size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE) { } 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_width = size_hints.min_width;
base_height = size_hints.min_height; 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) { base_height != client->base_height) {
client->base_width = base_width; client->base_width = base_width;
client->base_height = base_height; 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) if (client->fullscreen)
LOG("Not resizing client, it is in fullscreen mode\n"); DLOG("Not resizing client, it is in fullscreen mode\n");
else else {
resize_client(conn, client); resize_client(conn, client);
xcb_flush(conn);
}
} }
/* If no aspect ratio was set or if it was invalid, we ignore the hints */ /* 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 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; 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); DLOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect);
LOG("width = %f, height = %f\n", width, height); DLOG("width = %f, height = %f\n", width, height);
/* Sanity checks, this is user-input, in a way */ /* Sanity checks, this is user-input, in a way */
if (max_aspect <= 0 || min_aspect <= 0 || height == 0 || (width / height) <= 0) 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; client->force_reconfigure = true;
if (client->container != NULL) { if (client->container != NULL && workspace_is_visible(client->workspace)) {
render_container(conn, client->container); render_container(conn, client->container);
xcb_flush(conn); 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) { xcb_atom_t name, xcb_get_property_reply_t *reply) {
Client *client = table_get(&by_child, window); Client *client = table_get(&by_child, window);
if (client == NULL) { if (client == NULL) {
LOG("Received WM_HINTS for unknown client\n"); DLOG("Received WM_HINTS for unknown client\n");
return 1; return 1;
} }
xcb_wm_hints_t hints; 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)); Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
if (!client->urgent && client == last_focused) { if (!client->urgent && client == last_focused) {
LOG("Ignoring urgency flag for current client\n"); DLOG("Ignoring urgency flag for current client\n");
return 1; 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); LOG("Urgency flag changed to %d\n", client->urgent);
workspace_update_urgent_flag(client->workspace); workspace_update_urgent_flag(client->workspace);
redecorate_window(conn, client);
/* If the workspace this client is on is not visible, we need to redraw /* If the workspace this client is on is not visible, we need to redraw
* the workspace bar */ * the workspace bar */
if (!workspace_is_visible(client->workspace)) { if (!workspace_is_visible(client->workspace)) {
i3Screen *screen = client->workspace->screen; Output *output = client->workspace->output;
render_workspace(conn, screen, screen->current_workspace); render_workspace(conn, output, output->current_workspace);
xcb_flush(conn); xcb_flush(conn);
} else {
redecorate_window(conn, client);
} }
return 1; 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) { xcb_atom_t name, xcb_get_property_reply_t *reply) {
Client *client = table_get(&by_child, window); Client *client = table_get(&by_child, window);
if (client == NULL) { if (client == NULL) {
LOG("No such client\n"); DLOG("No such client\n");
return 1; 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) { 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); 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; return 1;
xcb_window_t *leader = xcb_get_property_value(prop); xcb_window_t *leader = xcb_get_property_value(prop);
if (leader == NULL || *leader == 0) if (leader == NULL)
return 1; return 1;
LOG("Client leader changed to %08x\n", *leader); DLOG("Client leader changed to %08x\n", *leader);
client->leader = *leader; client->leader = *leader;

391
src/ipc.c
View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
@ -12,6 +12,7 @@
*/ */
#include <sys/types.h> #include <sys/types.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h> #include <sys/un.h>
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
@ -21,19 +22,24 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <libgen.h>
#include <ev.h> #include <ev.h>
#include <yajl/yajl_gen.h>
#include <yajl/yajl_parse.h>
#include "queue.h" #include "queue.h"
#include "i3/ipc.h" #include "ipc.h"
#include "i3.h" #include "i3.h"
#include "util.h" #include "util.h"
#include "commands.h" #include "commands.h"
#include "log.h"
#include "table.h"
#include "randr.h"
#include "config.h"
typedef struct ipc_client { /* Shorter names for all those yajl_gen_* functions */
int fd; #define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__)
#define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str))
TAILQ_ENTRY(ipc_client) clients;
} ipc_client;
TAILQ_HEAD(ipc_client_head, ipc_client) all_clients = TAILQ_HEAD_INITIALIZER(all_clients); TAILQ_HEAD(ipc_client_head, ipc_client) all_clients = TAILQ_HEAD_INITIALIZER(all_clients);
@ -50,34 +56,106 @@ static void set_nonblock(int sockfd) {
err(-1, "Could not set O_NONBLOCK"); 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. * Emulates mkdir -p (creates any missing folders)
*
* 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.
* *
*/ */
static void ipc_handle_message(uint8_t *message, int size, static bool mkdirp(const char *path) {
uint32_t message_size, uint32_t message_type) { if (mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == 0)
LOG("handling message of size %d\n", size); return true;
LOG("sender specified size %d\n", message_size); if (errno != ENOENT) {
LOG("sender specified type %d\n", message_type); ELOG("mkdir(%s) failed: %s\n", path, strerror(errno));
LOG("payload as a string = %s\n", message); return false;
}
char *copy = strdup(path);
/* strip trailing slashes, if any */
while (copy[strlen(copy)-1] == '/')
copy[strlen(copy)-1] = '\0';
switch (message_type) { char *sep = strrchr(copy, '/');
case I3_IPC_MESSAGE_TYPE_COMMAND: { if (sep == NULL)
return false;
*sep = '\0';
bool result = false;
if (mkdirp(copy))
result = mkdirp(path);
free(copy);
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;
}
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 /* To get a properly terminated buffer, we copy
* message_size bytes out of the buffer */ * message_size bytes out of the buffer */
char *command = scalloc(message_size); char *command = scalloc(message_size);
@ -85,14 +163,211 @@ static void ipc_handle_message(uint8_t *message, int size,
parse_command(global_conn, (const char*)command); parse_command(global_conn, (const char*)command);
free(command); free(command);
break; /* For now, every command gets a positive acknowledge
} * (will change with the new command parser) */
default: const char *reply = "{\"success\":true}";
LOG("unhandled ipc message\n"); ipc_send_message(fd, (const unsigned char*)reply,
break; 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 * Handler for activity on a client connection, receives a message from a
* client. * client.
@ -122,11 +397,13 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
close(w->fd); close(w->fd);
/* Delete the client from the list of clients */ /* Delete the client from the list of clients */
struct ipc_client *current; ipc_client *current;
TAILQ_FOREACH(current, &all_clients, clients) { TAILQ_FOREACH(current, &all_clients, clients) {
if (current->fd != w->fd) if (current->fd != w->fd)
continue; 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 /* We can call TAILQ_REMOVE because we break out of the
* TAILQ_FOREACH afterwards */ * TAILQ_FOREACH afterwards */
TAILQ_REMOVE(&all_clients, current, clients); 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); ev_io_stop(EV_A_ w);
LOG("IPC: client disconnected\n"); DLOG("IPC: client disconnected\n");
return; 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 */ /* Check if the message starts with the i3 IPC magic code */
if (n < strlen(I3_IPC_MAGIC)) { if (n < strlen(I3_IPC_MAGIC)) {
LOG("IPC: message too short, ignoring\n"); DLOG("IPC: message too short, ignoring\n");
return; return;
} }
if (strncmp(buf, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) { 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; return;
} }
uint8_t *message = (uint8_t*)buf; uint8_t *message = (uint8_t*)buf;
while (n > 0) { while (n > 0) {
LOG("IPC: n = %d\n", n); DLOG("IPC: n = %d\n", n);
message += strlen(I3_IPC_MAGIC); message += strlen(I3_IPC_MAGIC);
n -= 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); n -= sizeof(uint32_t);
if (message_size > n) { 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; return;
} }
@ -174,7 +451,12 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) {
message += sizeof(uint32_t); message += sizeof(uint32_t);
n -= 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; n -= message_size;
message += message_size; message += message_size;
} }
@ -200,13 +482,13 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) {
set_nonblock(client); 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_init(package, ipc_receive_message, client, EV_READ);
ev_io_start(EV_A_ package); 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; new->fd = client;
TAILQ_INSERT_TAIL(&all_clients, new, clients); 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 ipc_create_socket(const char *filename) {
int sockfd; 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 the unix domain socket before */
unlink(filename); unlink(globbed);
if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) { if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) {
perror("socket()"); perror("socket()");
free(globbed);
return -1; return -1;
} }
@ -233,12 +524,14 @@ int ipc_create_socket(const char *filename) {
struct sockaddr_un addr; struct sockaddr_un addr;
memset(&addr, 0, sizeof(struct sockaddr_un)); memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_LOCAL; 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) { if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) {
perror("bind()"); perror("bind()");
free(globbed);
return -1; return -1;
} }
free(globbed);
set_nonblock(sockfd); set_nonblock(sockfd);
if (listen(sockfd, 5) < 0) { if (listen(sockfd, 5) < 0) {

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
@ -22,23 +22,14 @@
#include "xcb.h" #include "xcb.h"
#include "table.h" #include "table.h"
#include "util.h" #include "util.h"
#include "xinerama.h" #include "randr.h"
#include "layout.h" #include "layout.h"
#include "client.h" #include "client.h"
#include "floating.h" #include "floating.h"
#include "handlers.h" #include "handlers.h"
#include "workspace.h" #include "workspace.h"
#include "log.h"
/* #include "container.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);
}
/* /*
* Gets the unoccupied space (= space which is available for windows which were resized by the user) * 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 unoccupied = workspace->rect.width;
double default_factor = ((float)workspace->rect.width / workspace->cols) / 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++) { 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) if (workspace->width_factor[cols] == 0)
unoccupied -= workspace->rect.width * default_factor; unoccupied -= workspace->rect.width * default_factor;
} }
LOG("unoccupied space: %f\n", unoccupied); DLOG("unoccupied space: %f\n", unoccupied);
return unoccupied; return unoccupied;
} }
@ -69,15 +60,15 @@ int get_unoccupied_y(Workspace *workspace) {
double unoccupied = height; double unoccupied = height;
double default_factor = ((float)height / workspace->rows) / 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++) { 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) if (workspace->height_factor[rows] == 0)
unoccupied -= height * default_factor; unoccupied -= height * default_factor;
} }
LOG("unoccupied space: %f\n", unoccupied); DLOG("unoccupied space: %f\n", unoccupied);
return 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 two lines in a lighter color
- Draw the windows title - Draw the windows title
*/ */
int mode = container_mode(client->container, true);
/* Draw a rectangle in background color around the window */ /* Draw a rectangle in background color around the window */
if (client->borderless && (client->container == NULL || if (client->borderless && mode == MODE_DEFAULT)
(client->container->mode != MODE_STACK &&
client->container->mode != MODE_TABBED)))
xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); 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); else xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, color->background);
/* In stacking mode, we only render the rect for this specific decoration */ /* 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 containers width because it is the more recent value - when /* We need to use the containers width because it is the more recent value - when
in stacking mode, clients get reconfigured only on demand (the not active client in stacking mode, clients get reconfigured only on demand (the not active client
is not reconfigured), so the clients rect.width would be wrong */ is not reconfigured), so the clients 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) /* 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 */ 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")); 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_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); xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect);
} else { } else {
@ -174,13 +167,11 @@ 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) { if (client->titlebar_position != TITLEBAR_OFF) {
/* Draw the lines */ /* Draw the lines */
xcb_draw_line(conn, drawable, gc, color->border, offset_x, offset_y, offset_x + client->rect.width, offset_y); 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, xcb_draw_line(conn, drawable, gc, color->border,
offset_x + 2, /* x */ offset_x + 2, /* x */
offset_y + font->height + 3, /* y */ offset_y + font->height + 3, /* y */
@ -189,7 +180,8 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
} }
/* If the client has a title, we draw it */ /* 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 */ /* Draw the font */
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT; uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
uint32_t values[] = { color->text, color->background, font->id }; 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) { 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 /* Note: We can use a pointer to client->x like an array of uint32_ts
because it is followed by client->y by definition */ 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)); 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; return;
/* If the client is floating, we need to check if we moved it to a different workspace */ /* 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)); client->rect.y + (client->rect.height / 2));
if (client->workspace->screen == screen) if (client->workspace->output == output)
return; return;
if (screen == NULL) { if (output == NULL) {
LOG("Boundary checking disabled, no screen found for (%d, %d)\n", client->rect.x, client->rect.y); DLOG("Boundary checking disabled, no output found for (%d, %d)\n", client->rect.x, client->rect.y);
return; return;
} }
LOG("Client is on workspace %p with screen %p\n", client->workspace, client->workspace->screen); if (output->current_workspace == NULL) {
LOG("but screen at %d, %d is %p\n", client->rect.x, client->rect.y, screen); DLOG("Boundary checking deferred, no current workspace on output\n");
floating_assign_to_workspace(client, screen->current_workspace); 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); 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) { void resize_client(xcb_connection_t *conn, Client *client) {
i3Font *font = load_font(conn, config.font); 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); DLOG("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); DLOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height);
xcb_configure_window(conn, client->frame, xcb_set_window_rect(conn, client->frame, client->rect);
XCB_CONFIG_WINDOW_X |
XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT,
&(client->rect.x));
/* Adjust the position of the child inside its frame. /* Adjust the position of the child inside its frame.
* The coordinates of the child are relative to its frame, we * The coordinates of the child are relative to its frame, we
* add a border of 2 pixel to each value */ * 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); 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_STACK:
case MODE_TABBED: case MODE_TABBED:
rect->x = 2; rect->x = 2;
@ -301,7 +290,7 @@ void resize_client(xcb_connection_t *conn, Client *client) {
/* Obey the ratio, if any */ /* Obey the ratio, if any */
if (client->proportional_height != 0 && if (client->proportional_height != 0 &&
client->proportional_width != 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; double new_height = rect->height + 1;
int new_width = rect->width; int new_width = rect->width;
@ -317,26 +306,26 @@ void resize_client(xcb_connection_t *conn, Client *client) {
rect->height = new_height; rect->height = new_height;
rect->width = new_width; 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) { if (client->height_increment > 1) {
int old_height = rect->height; int old_height = rect->height;
rect->height -= (rect->height - client->base_height) % client->height_increment; 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); old_height - rect->height, client->height_increment, client->base_height);
} }
if (client->width_increment > 1) { if (client->width_increment > 1) {
int old_width = rect->width; int old_width = rect->width;
rect->width -= (rect->width - client->base_width) % client->width_increment; 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); 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). /* 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, * 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++; num_clients++;
if (container->mode == MODE_DEFAULT) { 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) { CIRCLEQ_FOREACH(client, &(container->clients), clients) {
/* If the client is in fullscreen mode, it does not get reconfigured */ /* If the client is in fullscreen mode, it does not get reconfigured */
if (container->workspace->fullscreen_client == client) { if (container->workspace->fullscreen_client == client) {
@ -371,6 +364,13 @@ void render_container(xcb_connection_t *conn, Container *container) {
continue; 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. /* 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 */ * Note the bitwise OR instead of logical OR to force evaluation of both statements */
if (client->force_reconfigure | 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 + update_if_necessary(&(client->rect.y), container->y +
(container->height / num_clients) * current_client) | (container->height / num_clients) * current_client) |
update_if_necessary(&(client->rect.width), container->width) | 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); resize_client(conn, client);
/* TODO: vertical default layout */ /* 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 /* Check if we need to remap our stack title window, it gets unmapped when the container
is empty in src/handlers.c:unmap_notify() */ is empty in src/handlers.c:unmap_notify() */
if (stack_win->rect.height == 0 && num_clients > 0) { if (stack_win->rect.height == 0 && num_clients > 1) {
LOG("remapping stack win\n"); DLOG("remapping stack win\n");
xcb_map_window(conn, stack_win->window); 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); stack_win->rect.height, num_clients);
if (container->mode == MODE_TABBED) { if (container->mode == MODE_TABBED) {
/* By setting num_clients to 1 we force that the stack window will be only one line /* 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. */ * 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) if (stack_lines > 1)
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); 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 */ /* Check if we need to reconfigure our stack title window */
if (update_if_necessary(&(stack_win->rect.x), container->x) | 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.y), container->y) |
update_if_necessary(&(stack_win->rect.width), container->width) | update_if_necessary(&(stack_win->rect.width), container->width) |
update_if_necessary(&(stack_win->rect.height), decoration_height * stack_lines)) { update_if_necessary(&(stack_win->rect.height), height))) {
/* Configuration can happen in two slightly different ways: /* Configuration can happen in two slightly different ways:
@ -456,6 +466,7 @@ void render_container(xcb_connection_t *conn, Container *container) {
} }
/* Prepare the pixmap for usage */ /* Prepare the pixmap for usage */
if (num_clients > 1)
cached_pixmap_prepare(conn, &(stack_win->pixmap)); cached_pixmap_prepare(conn, &(stack_win->pixmap));
int current_row = 0, current_col = 0; int current_row = 0, current_col = 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 */ * Note the bitwise OR instead of logical OR to force evaluation of all statements */
if (client->force_reconfigure | if (client->force_reconfigure |
update_if_necessary(&(client->rect.x), container->x) | 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.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); resize_client(conn, client);
client->force_reconfigure = false; client->force_reconfigure = false;
@ -520,13 +531,15 @@ void render_container(xcb_connection_t *conn, Container *container) {
current_client++; current_client++;
} else if (container->mode == MODE_TABBED) { } else if (container->mode == MODE_TABBED) {
if (container->stack_limit == STACK_LIMIT_ROWS) { 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"); "This makes no sense in tabbing mode.\n");
} }
offset_x = current_client++ * size_each; offset_x = current_client++ * size_each;
} }
decorate_window(conn, client, stack_win->pixmap.id, stack_win->pixmap.gc, if (stack_win->pixmap.id == XCB_NONE)
offset_x, offset_y); 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 /* 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, 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); 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) { static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int *height) {
Client *client; Client *client;
SLIST_FOREACH(client, &(r_ws->screen->dock_clients), dock_clients) { SLIST_FOREACH(client, &(r_ws->output->dock_clients), dock_clients) {
LOG("client is at %d, should be at %d\n", client->rect.y, *height); DLOG("client is at %d, should be at %d\n", client->rect.y, *height);
if (client->force_reconfigure | if (client->force_reconfigure |
update_if_necessary(&(client->rect.x), r_ws->rect.x) | update_if_necessary(&(client->rect.x), r_ws->rect.x) |
update_if_necessary(&(client->rect.y), *height)) 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); resize_client(conn, client);
client->force_reconfigure = false; client->force_reconfigure = false;
LOG("desired_height = %d\n", client->desired_height); DLOG("desired_height = %d\n", client->desired_height);
*height += client->desired_height; *height += client->desired_height;
} }
} }
static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int width, int height) { static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int width, int height) {
i3Font *font = load_font(conn, config.font); i3Font *font = load_font(conn, config.font);
i3Screen *screen = r_ws->screen; Output *output = r_ws->output;
enum { SET_NORMAL = 0, SET_FOCUSED = 1 }; enum { SET_NORMAL = 0, SET_FOCUSED = 1 };
/* Fill the whole bar in black */ /* 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_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 */ /* 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; int drawn = 0;
Workspace *ws; Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) { TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->screen != screen) if (ws->output != output)
continue; continue;
struct Colortriple *color; struct Colortriple *color;
if (screen->current_workspace == ws) if (output->current_workspace == ws)
color = &(config.bar.focused); color = &(config.bar.focused);
else if (ws->urgent) else if (ws->urgent)
color = &(config.bar.urgent); color = &(config.bar.urgent);
else color = &(config.bar.unfocused); else color = &(config.bar.unfocused);
/* Draw the outer rect */ /* 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 */ drawn, /* x */
1, /* y */ 1, /* y */
ws->text_width + 5 + 5, /* width = text width + 5 px left + 5px right */ 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 */); height - 2 /* height = max. height - 1 px upper and 1 px bottom border */);
/* Draw the background of this rect */ /* 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, drawn + 1,
2, 2,
ws->text_width + 4 + 4, ws->text_width + 4 + 4,
height - 4); height - 4);
xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, color->text); xcb_change_gc_single(conn, output->bargc, XCB_GC_FOREGROUND, color->text);
xcb_change_gc_single(conn, screen->bargc, XCB_GC_BACKGROUND, color->background); xcb_change_gc_single(conn, output->bargc, XCB_GC_BACKGROUND, color->background);
xcb_image_text_16(conn, ws->name_len, screen->bar, screen->bargc, drawn + 5 /* X */, xcb_image_text_16(conn, ws->name_len, output->bar, output->bargc, drawn + 5 /* X */,
font->height + 1 /* Y = baseline of font */, font->height + 1 /* Y = baseline of font */,
(xcb_char2b_t*)ws->name); (xcb_char2b_t*)ws->name);
drawn += ws->text_width + 12; drawn += ws->text_width + 12;
@ -662,17 +677,18 @@ void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace, bo
* Renders the given workspace on the given screen * 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); i3Font *font = load_font(conn, config.font);
int width = r_ws->rect.width; int width = r_ws->rect.width;
int height = r_ws->rect.height; int height = r_ws->rect.height;
/* Reserve space for dock clients */ /* Reserve space for dock clients */
Client *client; Client *client;
SLIST_FOREACH(client, &(screen->dock_clients), dock_clients) SLIST_FOREACH(client, &(output->dock_clients), dock_clients)
height -= client->desired_height; height -= client->desired_height;
/* Space for the internal bar */ /* Space for the internal bar */
if (!config.disable_workspace_bar)
height -= (font->height + 6); height -= (font->height + 6);
int xoffset[r_ws->rows]; int xoffset[r_ws->rows];
@ -707,7 +723,7 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws)
single_width = container->width; single_width = container->width;
} }
LOG("height is %d\n", height); DLOG("height is %d\n", height);
container->height = 0; container->height = 0;
@ -727,9 +743,20 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws)
yoffset[cols] += single_height; 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); ignore_enter_notify_forall(conn, r_ws, false);
render_bars(conn, r_ws, width, &height); render_bars(conn, r_ws, width, &height);
if (!config.disable_workspace_bar)
render_internal_bar(conn, r_ws, width, font->height + 6); 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) { void render_layout(xcb_connection_t *conn) {
i3Screen *screen; Output *output;
if (virtual_screens == NULL) TAILQ_FOREACH(output, &outputs, outputs)
return; if (output->current_workspace != NULL)
render_workspace(conn, output, output->current_workspace);
TAILQ_FOREACH(screen, virtual_screens, screens)
if (screen->current_workspace != NULL)
render_workspace(conn, screen, screen->current_workspace);
xcb_flush(conn); xcb_flush(conn);
} }

121
src/log.c Normal file
View File

@ -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);
}

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
@ -30,7 +30,6 @@
#include <xcb/xcb_property.h> #include <xcb/xcb_property.h>
#include <xcb/xcb_keysyms.h> #include <xcb/xcb_keysyms.h>
#include <xcb/xcb_icccm.h> #include <xcb/xcb_icccm.h>
#include <xcb/xinerama.h>
#include <ev.h> #include <ev.h>
@ -45,9 +44,16 @@
#include "table.h" #include "table.h"
#include "util.h" #include "util.h"
#include "xcb.h" #include "xcb.h"
#include "randr.h"
#include "xinerama.h" #include "xinerama.h"
#include "manage.h" #include "manage.h"
#include "ipc.h" #include "ipc.h"
#include "log.h"
#include "sighandler.h"
static int xkb_event_base;
int xkb_current_group;
xcb_connection_t *global_conn; 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) */ /* The depth of the root screen (used e.g. for creating new pixmaps later) */
uint8_t root_depth; 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. * 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 * 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) { static void xkb_got_event(EV_P_ struct ev_io *w, int revents) {
LOG("got xkb event, yay\n"); DLOG("Handling XKB event\n");
XEvent ev; XkbEvent ev;
/* When using xmodmap, every change (!) gets an own event. /* When using xmodmap, every change (!) gets an own event.
* Therefore, we just read all events and only handle the * Therefore, we just read all events and only handle the
* mapping_notify once (we do not receive any other XKB * mapping_notify once. */
* events anyway). */ bool mapping_changed = false;
while (XPending(xkbdpy)) while (XPending(xkbdpy)) {
XNextEvent(xkbdpy, &ev); 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); xcb_key_symbols_free(keysyms);
keysyms = xcb_key_symbols_alloc(global_conn); keysyms = xcb_key_symbols_alloc(global_conn);
xcb_get_numlock_mask(global_conn); xcb_get_numlock_mask(global_conn);
ungrab_all_keys(global_conn); ungrab_all_keys(global_conn);
LOG("Re-grabbing...\n"); DLOG("Re-grabbing...\n");
grab_all_keys(global_conn); translate_keysyms();
LOG("Done\n"); 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; int i, screens, opt;
char *override_configpath = NULL; char *override_configpath = NULL;
bool autostart = true; bool autostart = true;
bool only_check_config = false;
bool force_xinerama = false;
xcb_connection_t *conn; xcb_connection_t *conn;
xcb_property_handlers_t prophs; xcb_property_handlers_t prophs;
xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS]; 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'}, {"config", required_argument, 0, 'c'},
{"version", no_argument, 0, 'v'}, {"version", no_argument, 0, 'v'},
{"help", no_argument, 0, 'h'}, {"help", no_argument, 0, 'h'},
{"force-xinerama", no_argument, 0, 0},
{0, 0, 0, 0} {0, 0, 0, 0}
}; };
int option_index = 0; int option_index = 0;
@ -165,7 +217,7 @@ int main(int argc, char *argv[], char *env[]) {
start_argv = argv; 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) { switch (opt) {
case 'a': case 'a':
LOG("Autostart disabled using -a\n"); LOG("Autostart disabled using -a\n");
@ -174,18 +226,46 @@ int main(int argc, char *argv[], char *env[]) {
case 'c': case 'c':
override_configpath = sstrdup(optarg); override_configpath = sstrdup(optarg);
break; break;
case 'v': case 'C':
printf("i3 version " I3_VERSION " © 2009 Michael Stapelberg and contributors\n"); LOG("Checking configuration file only (-C)\n");
exit(EXIT_SUCCESS); only_check_config = true;
case 'l':
config_use_lexer = true;
break; 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: 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, "\n");
fprintf(stderr, "-a: disable autostart\n"); fprintf(stderr, "-a: disable autostart\n");
fprintf(stderr, "-v: display version and exit\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 <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); exit(EXIT_FAILURE);
} }
} }
@ -204,6 +284,10 @@ int main(int argc, char *argv[], char *env[]) {
die("Cannot open display\n"); die("Cannot open display\n");
load_configuration(conn, override_configpath, false); 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 /* Create the initial container on the first workspace. This used to
* be part of init_table, but since it possibly requires an X * 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(UTF8_STRING);
REQUEST_ATOM(WM_STATE); REQUEST_ATOM(WM_STATE);
REQUEST_ATOM(WM_CLIENT_LEADER); 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 */ /* TODO: this has to be more beautiful somewhen */
int major, minor, error; int major, minor, error;
@ -241,29 +328,33 @@ int main(int argc, char *argv[], char *env[]) {
major = XkbMajorVersion; major = XkbMajorVersion;
minor = XkbMinorVersion; minor = XkbMinorVersion;
int evBase, errBase; int errBase;
if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) { if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &xkb_event_base, &errBase, &major, &minor, &error)) == NULL) {
fprintf(stderr, "XkbOpenDisplay() failed\n"); ELOG("ERROR: XkbOpenDisplay() failed, disabling XKB support\n");
return 1; xkb_supported = false;
} }
if (xkb_supported) {
if (fcntl(ConnectionNumber(xkbdpy), F_SETFD, FD_CLOEXEC) == -1) { if (fcntl(ConnectionNumber(xkbdpy), F_SETFD, FD_CLOEXEC) == -1) {
fprintf(stderr, "Could not set FD_CLOEXEC on xkbdpy\n"); fprintf(stderr, "Could not set FD_CLOEXEC on xkbdpy\n");
return 1; return 1;
} }
int i1; int i1;
if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) { if (!XkbQueryExtension(xkbdpy,&i1,&xkb_event_base,&errBase,&major,&minor)) {
fprintf(stderr, "XKB not supported by X-server\n"); fprintf(stderr, "XKB not supported by X-server\n");
return 1; return 1;
} }
/* end of ugliness */ /* end of ugliness */
if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd, XkbMapNotifyMask, XkbMapNotifyMask)) { if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd,
XkbMapNotifyMask | XkbStateNotifyMask,
XkbMapNotifyMask | XkbStateNotifyMask)) {
fprintf(stderr, "Could not set XKB event mask\n"); fprintf(stderr, "Could not set XKB event mask\n");
return 1; return 1;
} }
}
/* Initialize event loop using libev */ /* Initialize event loop using libev */
struct ev_loop *loop = ev_loop_new(0); struct ev_loop *loop = ev_loop_new(0);
@ -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_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
ev_io_start(loop, xcb_watcher); ev_io_start(loop, xcb_watcher);
if (xkb_supported) {
ev_io_init(xkb, xkb_got_event, ConnectionNumber(xkbdpy), EV_READ); ev_io_init(xkb, xkb_got_event, ConnectionNumber(xkbdpy), EV_READ);
ev_io_start(loop, xkb); ev_io_start(loop, xkb);
/* Flush the buffer so that libev can properly get new events */ /* Flush the buffer so that libev can properly get new events */
XFlush(xkbdpy); XFlush(xkbdpy);
}
ev_check_init(xcb_check, xcb_check_cb); ev_check_init(xcb_check, xcb_check_cb);
ev_check_start(loop, xcb_check); 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 its our titlebars. */ /* Expose = an Application should redraw itself, in this case its our titlebars. */
xcb_event_set_expose_handler(&evenths, handle_expose_event, NULL); 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_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 */ /* Enter window = user moved his mouse over the window */
xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, NULL); 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. */ it any longer. Usually, the client destroys the window shortly afterwards. */
xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, NULL); 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 = windows configuration (geometry, stacking, …). We only need /* Configure notify = windows configuration (geometry, stacking, …). We only need
it to set up ignore the following enter_notify events */ it to set up ignore the following enter_notify events */
xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, NULL); 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_POINTER_MOTION |
XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_PROPERTY_CHANGE |
XCB_EVENT_MASK_ENTER_WINDOW }; 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 */ /* Setup NetWM atoms */
#define GET_ATOM(name) { \ #define GET_ATOM(name) { \
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, atom_cookies[name], NULL); \ xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, atom_cookies[name], NULL); \
if (!reply) { \ if (!reply) { \
LOG("Could not get atom " #name "\n"); \ ELOG("Could not get atom " #name "\n"); \
exit(-1); \ exit(-1); \
} \ } \
atoms[name] = reply->atom; \ atoms[name] = reply->atom; \
@ -390,6 +487,9 @@ int main(int argc, char *argv[], char *env[]) {
GET_ATOM(UTF8_STRING); GET_ATOM(UTF8_STRING);
GET_ATOM(WM_STATE); GET_ATOM(WM_STATE);
GET_ATOM(WM_CLIENT_LEADER); 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); 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 */ /* 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); xcb_get_numlock_mask(conn);
grab_all_keys(conn); translate_keysyms();
grab_all_keys(conn, false);
/* Autostarting exec-lines */ int randr_base;
struct Autostart *exec; if (force_xinerama) {
if (autostart) {
TAILQ_FOREACH(exec, &autostarts, autostarts) {
LOG("auto-starting %s\n", exec->command);
start_application(exec->command);
}
}
/* check for Xinerama */
LOG("Checking for Xinerama...\n");
initialize_xinerama(conn); 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);
}
xcb_flush(conn); xcb_flush(conn);
/* Get pointer position to see on which screen were starting */ /* Get pointer position to see on which screen were starting */
xcb_query_pointer_reply_t *reply; xcb_query_pointer_reply_t *reply;
if ((reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL)) == NULL) { 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; 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) { 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); 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; c_ws = screen->current_workspace;
manage_existing_windows(conn, &prophs, root); manage_existing_windows(conn, &prophs, root);
@ -463,7 +564,7 @@ int main(int argc, char *argv[], char *env[]) {
if (config.ipc_socket_path != NULL) { if (config.ipc_socket_path != NULL) {
int ipc_socket = ipc_create_socket(config.ipc_socket_path); int ipc_socket = ipc_create_socket(config.ipc_socket_path);
if (ipc_socket == -1) { 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 { } else {
struct ev_io *ipc_io = scalloc(sizeof(struct ev_io)); struct ev_io *ipc_io = scalloc(sizeof(struct ev_io));
ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ); 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 */ /* Handle the events which arrived until now */
xcb_check_cb(NULL, NULL, 0); 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 libevs eventloop */ /* Ungrab the server to receive events and enter libevs eventloop */
xcb_ungrab_server(conn); 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); ev_loop(loop, 0);
/* not reached */ /* not reached */

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
@ -30,6 +30,8 @@
#include "floating.h" #include "floating.h"
#include "client.h" #include "client.h"
#include "workspace.h" #include "workspace.h"
#include "log.h"
#include "ewmh.h"
/* /*
* Go through all existing windows (if the window manager is restarted) and manage them * 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); 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. * 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 /* Check if the window is mapped (it could be not mapped when intializing and
calling manage_window() for every window) */ calling manage_window() for every window) */
if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) { if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) {
LOG("Could not get attributes\n"); ELOG("Could not get attributes\n");
return; 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() */ /* Events for already managed windows should already be filtered in manage_window() */
assert(new == NULL); assert(new == NULL);
LOG("Reparenting window 0x%08x\n", child); LOG("Managing window 0x%08x\n", child);
LOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height); DLOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
new = calloc(sizeof(Client), 1); new = scalloc(sizeof(Client));
new->force_reconfigure = true; new->force_reconfigure = true;
/* Update the data structures */ /* Update the data structures */
@ -220,7 +244,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
new->awaiting_useless_unmap = true; new->awaiting_useless_unmap = true;
xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height); xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
if (xcb_request_check(conn, cookie) != NULL) { 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); xcb_destroy_window(conn, new->frame);
free(new); free(new);
return; 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))) { 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++) for (int i = 0; i < xcb_get_property_value_length(preply); i++)
if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) { 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->dock = true;
new->borderless = true; new->borderless = true;
new->titlebar_position = TITLEBAR_OFF; new->titlebar_position = TITLEBAR_OFF;
new->force_reconfigure = true; new->force_reconfigure = true;
new->container = NULL; 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 its a dock we cant make it float, so we break */ /* If its a dock we cant make it float, so we break */
new->floating = FLOATING_AUTO_OFF; new->floating = FLOATING_AUTO_OFF;
break; break;
@ -263,19 +293,19 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) { atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
/* Set the dialog window to automatically floating, will be used below */ /* Set the dialog window to automatically floating, will be used below */
new->floating = FLOATING_AUTO_ON; 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 */ /* All clients which have a leader should be floating */
if (!new->dock && !client_is_floating(new) && new->leader != 0) { 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; new->floating = FLOATING_AUTO_ON;
} }
if (new->workspace->auto_float) { if (new->workspace->auto_float) {
new->floating = FLOATING_AUTO_ON; 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) { if (new->dock) {
@ -289,12 +319,12 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
TODO: bars at the top */ TODO: bars at the top */
new->desired_height = strut[3]; new->desired_height = strut[3];
if (new->desired_height == 0) { 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; 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 { } 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; new->desired_height = original_height;
} }
} else { } 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); preply = xcb_get_property_reply(conn, leader_cookie, NULL);
handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply); 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; struct Assignment *assign;
TAILQ_FOREACH(assign, &assignments, assignments) { TAILQ_FOREACH(assign, &assignments, assignments) {
if (get_matching_client(conn, assign->windowclass_title, new) == NULL) 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", LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
assign->windowclass_title, assign->workspace); assign->windowclass_title, assign->workspace);
if (c_ws->screen->current_workspace->num == (assign->workspace-1)) { if (c_ws->output->current_workspace->num == (assign->workspace-1)) {
LOG("We are already there, no need to do anything\n"); DLOG("We are already there, no need to do anything\n");
break; 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 *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->container = t_ws->table[t_ws->current_col][t_ws->current_row];
new->workspace = t_ws; 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) { 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 /* If we are in fullscreen, we should place the window below
* the fullscreen window to not be annoying */ * 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 * to (0, 0), so we push them to a reasonable position
* (centered over their leader) */ * (centered over their leader) */
if (new->leader != 0 && x == 0 && y == 0) { 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); Client *leader = table_get(&by_child, new->leader);
if (leader == NULL) { 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); 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); 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.x = new->rect.x = x;
new->floating_rect.y = new->rect.y = y; 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.x, new->floating_rect.y,
new->floating_rect.width, new->floating_rect.height); 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); new->rect.x, new->rect.y, new->rect.width, new->rect.height);
/* Make sure it is on top of the other windows */ /* Make sure it is on top of the other windows */
@ -455,8 +508,10 @@ map:
if (map_frame) if (map_frame)
render_container(conn, new->container); 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); xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
ewmh_update_active_window(new->child);
}
} }
} }

532
src/randr.c Normal file
View File

@ -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 dont 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);
}

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
@ -24,10 +24,49 @@
#include "xcb.h" #include "xcb.h"
#include "debug.h" #include "debug.h"
#include "layout.h" #include "layout.h"
#include "xinerama.h" #include "randr.h"
#include "config.h" #include "config.h"
#include "floating.h" #include "floating.h"
#include "workspace.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 doesnt 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 * 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, int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, int second,
resize_orientation_t orientation, xcb_button_press_event_t *event) { resize_orientation_t orientation, xcb_button_press_event_t *event) {
int new_position; uint32_t new_position;
i3Screen *screen = get_screen_containing(event->root_x, event->root_y); Output *screen = get_output_containing(event->root_x, event->root_y);
if (screen == NULL) { 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; 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 * screens during runtime. Instead, we just use the most right and most
* bottom Xinerama screen and use their position + width/height to get * bottom Xinerama screen and use their position + width/height to get
* the area of pixels currently in use */ * the area of pixels currently in use */
i3Screen *most_right = get_screen_most(D_RIGHT, screen), Output *most_right = get_output_most(D_RIGHT, screen),
*most_bottom = get_screen_most(D_DOWN, 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 mask = 0;
uint32_t values[2]; 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, xcb_window_t helpwin = create_window(conn, helprect, XCB_WINDOW_CLASS_INPUT_OUTPUT,
(orientation == O_VERTICAL ? (orientation == O_VERTICAL ?
XCB_CURSOR_SB_V_DOUBLE_ARROW : XCB_CURSOR_SB_H_DOUBLE_ARROW :
XCB_CURSOR_SB_H_DOUBLE_ARROW), true, mask, values); XCB_CURSOR_SB_V_DOUBLE_ARROW), true, mask, values);
xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin); xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin);
xcb_flush(conn); xcb_flush(conn);
void resize_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) { struct callback_params params = { orientation, screen, helpwin, &new_position };
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;
values[0] = new_position = new_x; drag_pointer(conn, NULL, event, grabwin, BORDER_TOP, resize_callback, &params);
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);
xcb_destroy_window(conn, helpwin); xcb_destroy_window(conn, helpwin);
xcb_destroy_window(conn, grabwin); 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) if (ws->width_factor[second] == 0)
new_unoccupied_x += default_width; new_unoccupied_x += default_width;
LOG("\n\n\n"); DLOG("\n\n\n");
LOG("old = %d, new = %d\n", old_unoccupied_x, new_unoccupied_x); 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 /* If the space used for customly resized columns has changed we need to adapt the
* other customly resized columns, if any */ * 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) if (ws->width_factor[col] == 0)
continue; 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; 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); DLOG("Updating first (before = %f)\n", ws->width_factor[first]);
LOG("Updating first (before = %f)\n", ws->width_factor[first]);
/* Convert 0 (for default width_factor) to actual numbers */ /* Convert 0 (for default width_factor) to actual numbers */
if (ws->width_factor[first] == 0) if (ws->width_factor[first] == 0)
ws->width_factor[first] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x; ws->width_factor[first] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x;
LOG("middle = %f\n", ws->width_factor[first]); DLOG("first->width = %d, pixels = %d\n", old_width, pixels);
int old_width = ws->width_factor[first] * old_unoccupied_x;
LOG("first->width = %d, pixels = %d\n", pixels);
ws->width_factor[first] *= (float)(old_width + pixels) / old_width; 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) if (ws->width_factor[second] == 0)
ws->width_factor[second] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x; 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 { } else {
int ws_height = workspace_height(ws); int ws_height = workspace_height(ws);
int default_height = ws_height / ws->rows; 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) if (ws->height_factor[row] == 0)
cols_without_hf++; 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 */ /* Convert 0 (for default width_factor) to actual numbers */
if (ws->height_factor[first] == 0) if (ws->height_factor[first] == 0)
old_height = (old_unoccupied_y / max(cols_without_hf, 1)); old_height = (old_unoccupied_y / max(cols_without_hf, 1));
else old_height = ws->height_factor[first] * old_unoccupied_y; 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) if (ws->height_factor[second] == 0)
old_second_height = (old_unoccupied_y / max(cols_without_hf, 1)); old_second_height = (old_unoccupied_y / max(cols_without_hf, 1));
else old_second_height = ws->height_factor[second] * old_unoccupied_y; 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"); DLOG("\n\n\n");
LOG("old = %d, new = %d\n", old_unoccupied_y, new_unoccupied_y); 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 /* If the space used for customly resized columns has changed we need to adapt the
* other customly resized columns, if any */ * 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) if (ws->height_factor[row] == 0)
continue; 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; 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 */ /* Convert 0 (for default width_factor) to actual numbers */
if (ws->height_factor[first] == 0) if (ws->height_factor[first] == 0)
ws->height_factor[first] = ((float)ws_height / ws->rows) / new_unoccupied_y; 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; 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) if (ws->height_factor[second] == 0)
ws->height_factor[second] = ((float)ws_height / ws->rows) / new_unoccupied_y; ws->height_factor[second] = ((float)ws_height / ws->rows) / new_unoccupied_y;
LOG("middle = %f\n", ws->height_factor[second]); DLOG("middle = %f\n", ws->height_factor[second]);
LOG("second->width = %d, pixels = %d\n", old_second_height, pixels); DLOG("second->width = %d, pixels = %d\n", old_second_height, pixels);
ws->height_factor[second] *= (float)(old_second_height - pixels) / old_second_height; 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); render_layout(conn);

224
src/sighandler.c Normal file
View File

@ -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");
}

View File

@ -27,6 +27,7 @@
#include "layout.h" #include "layout.h"
#include "config.h" #include "config.h"
#include "workspace.h" #include "workspace.h"
#include "log.h"
int current_workspace = 0; int current_workspace = 0;
int num_workspaces = 1; 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) { static void new_container(Workspace *workspace, Container **container, int col, int row, bool skip_layout_switch) {
Container *new; Container *new;
new = *container = calloc(sizeof(Container), 1); new = *container = scalloc(sizeof(Container));
CIRCLEQ_INIT(&(new->clients)); CIRCLEQ_INIT(&(new->clients));
new->colspan = 1; new->colspan = 1;
new->rowspan = 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); 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--) { 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]; 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 */ /* Move the other rows */
for (int cols = 0; cols < workspace->cols; cols++) for (int cols = 0; cols < workspace->cols; cols++)
for (int rows = workspace->rows - 1; rows > 0; rows--) { 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] = workspace->table[cols][rows-1];
workspace->table[cols][rows]->row = rows; workspace->table[cols][rows]->row = rows;
} }
@ -130,7 +131,7 @@ void expand_table_cols(Workspace *workspace) {
workspace->width_factor[workspace->cols-1] = 0; workspace->width_factor[workspace->cols-1] = 0;
workspace->table = realloc(workspace->table, sizeof(Container**) * workspace->cols); 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++) for (int c = 0; c < workspace->rows; c++)
new_container(workspace, &(workspace->table[workspace->cols-1][c]), workspace->cols-1, c, true); 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); 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--) { 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[cols] = workspace->width_factor[cols-1];
} }
workspace->width_factor[0] = 0; workspace->width_factor[0] = 0;
workspace->table = realloc(workspace->table, sizeof(Container**) * workspace->cols); 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 */ /* Move the other columns */
for (int rows = 0; rows < workspace->rows; rows++) for (int rows = 0; rows < workspace->rows; rows++)
for (int cols = workspace->cols - 1; cols > 0; cols--) { 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] = workspace->table[cols-1][rows];
workspace->table[cols][rows]->col = cols; workspace->table[cols][rows]->col = cols;
} }
@ -201,7 +202,7 @@ static void shrink_table_cols(Workspace *workspace) {
if (workspace->width_factor[cols] == 0) if (workspace->width_factor[cols] == 0)
continue; 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]);
workspace->width_factor[cols] += free_space; workspace->width_factor[cols] += free_space;
break; break;
@ -230,7 +231,7 @@ static void shrink_table_rows(Workspace *workspace) {
if (workspace->height_factor[rows] == 0) if (workspace->height_factor[rows] == 0)
continue; 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]);
workspace->height_factor[rows] += free_space; workspace->height_factor[rows] += free_space;
break; 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) { 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 */ /* Free the columns which are cleaned up */
for (int rows = 0; rows < workspace->rows; rows++) 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 (; cols < workspace->cols; cols++)
for (int rows = 0; rows < workspace->rows; rows++) { 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]; 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; workspace->table[cols-1][rows] = new_container;
new_container->row = rows; 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++) { for (int cols = 0; cols < workspace->cols; cols++) {
Container *new_container = workspace->table[cols][rows]; 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; workspace->table[cols][rows-1] = new_container;
new_container->row = rows-1; 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) { void dump_table(xcb_connection_t *conn, Workspace *workspace) {
LOG("dump_table()\n"); DLOG("dump_table()\n");
FOR_TABLE(workspace) { FOR_TABLE(workspace) {
Container *con = workspace->table[cols][rows]; Container *con = workspace->table[cols][rows];
LOG("----\n"); DLOG("----\n");
LOG("at col=%d, row=%d\n", cols, rows); DLOG("at col=%d, row=%d\n", cols, rows);
LOG("currently_focused = %p\n", con->currently_focused); DLOG("currently_focused = %p\n", con->currently_focused);
Client *loop; Client *loop;
CIRCLEQ_FOREACH(loop, &(con->clients), clients) { 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) { 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 */ /* Check for empty columns if we got more than one column */
for (int cols = 0; (workspace->cols > 1) && (cols < workspace->cols);) { for (int cols = 0; (workspace->cols > 1) && (cols < workspace->cols);) {
@ -327,7 +328,7 @@ void cleanup_table(xcb_connection_t *conn, Workspace *workspace) {
break; break;
} }
if (completely_empty) { if (completely_empty) {
LOG("Removing completely empty column %d\n", cols); DLOG("Removing completely empty column %d\n", cols);
if (cols < (workspace->cols - 1)) if (cols < (workspace->cols - 1))
move_columns_from(conn, workspace, cols+1); move_columns_from(conn, workspace, cols+1);
else { 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 */ /* Check for empty rows if we got more than one row */
for (int rows = 0; (workspace->rows > 1) && (rows < workspace->rows);) { for (int rows = 0; (workspace->rows > 1) && (rows < workspace->rows);) {
bool completely_empty = true; bool completely_empty = true;
LOG("Checking row %d\n", rows); DLOG("Checking row %d\n", rows);
for (int cols = 0; cols < workspace->cols; cols++) for (int cols = 0; cols < workspace->cols; cols++)
if (workspace->table[cols][rows]->currently_focused != NULL) { if (workspace->table[cols][rows]->currently_focused != NULL) {
completely_empty = false; completely_empty = false;
break; break;
} }
if (completely_empty) { if (completely_empty) {
LOG("Removing completely empty row %d\n", rows); DLOG("Removing completely empty row %d\n", rows);
if (rows < (workspace->rows - 1)) if (rows < (workspace->rows - 1))
move_rows_from(conn, workspace, rows+1); move_rows_from(conn, workspace, rows+1);
else { else {
@ -381,25 +382,25 @@ void cleanup_table(xcb_connection_t *conn, Workspace *workspace) {
* *
*/ */
void fix_colrowspan(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) { FOR_TABLE(workspace) {
Container *con = workspace->table[cols][rows]; Container *con = workspace->table[cols][rows];
if (con->colspan > 1) { 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 && while (con->colspan > 1 &&
(!cell_exists(workspace, cols + (con->colspan-1), rows) && (!cell_exists(workspace, cols + (con->colspan-1), rows) &&
workspace->table[cols + (con->colspan - 1)][rows]->currently_focused != NULL)) workspace->table[cols + (con->colspan - 1)][rows]->currently_focused != NULL))
con->colspan--; con->colspan--;
LOG("fixed it to %d\n", con->colspan); DLOG("fixed it to %d\n", con->colspan);
} }
if (con->rowspan > 1) { 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 && while (con->rowspan > 1 &&
(!cell_exists(workspace, cols, rows + (con->rowspan - 1)) && (!cell_exists(workspace, cols, rows + (con->rowspan - 1)) &&
workspace->table[cols][rows + (con->rowspan - 1)]->currently_focused != NULL)) workspace->table[cols][rows + (con->rowspan - 1)]->currently_focused != NULL))
con->rowspan--; con->rowspan--;
LOG("fixed it to %d\n", con->rowspan); DLOG("fixed it to %d\n", con->rowspan);
} }
} }
} }

View File

@ -31,6 +31,11 @@
#include "util.h" #include "util.h"
#include "xcb.h" #include "xcb.h"
#include "client.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; static iconv_t conversion_descriptor = 0;
struct keyvalue_table_head by_parent = TAILQ_HEAD_INITIALIZER(by_parent); 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. * Updates *destination with new_value and returns true if it was changed or false
* This is to be called by LOG() which includes filename/linenumber * if it was the same
* *
*/ */
void slog(char *fmt, ...) { bool update_if_necessary(uint32_t *destination, const uint32_t new_value) {
va_list args; uint32_t old_value = *destination;
char timebuf[64];
va_start(args, fmt); return ((*destination = new_value) != old_value);
/* 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);
} }
/* /*
@ -148,7 +143,7 @@ void start_application(const char *command) {
shell = "/bin/sh"; shell = "/bin/sh";
/* This is the child */ /* This is the child */
execl(shell, shell, "-c", command, NULL); execl(shell, shell, "-c", command, (void*)NULL);
/* not reached */ /* not reached */
} }
exit(0); 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) { 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); xcb_generic_error_t *error = xcb_request_check(conn, cookie);
if (error != NULL) { 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); xcb_disconnect(conn);
exit(-1); exit(-1);
} }
@ -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_row = current_row;
c_ws->current_col = current_col; c_ws->current_col = current_col;
c_ws = client->workspace; c_ws = client->workspace;
ewmh_update_current_desktop();
/* Load current_col/current_row if we switch to a client without a container */ /* Load current_col/current_row if we switch to a client without a container */
current_col = c_ws->current_col; current_col = c_ws->current_col;
current_row = c_ws->current_row; 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); CLIENT_LOG(client);
/* Set focus to the entered window, and flush xcb buffer immediately */ /* 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); 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); //xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, 10, 10);
if (client->container != NULL) { 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); Client *last_focused = get_last_focused_client(conn, client->container, client);
if (last_focused != NULL) { 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 }; 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); 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 /* If the last client was a floating client, we need to go to the next
* tiling client in stack and re-decorate it. */ * tiling client in stack and re-decorate it. */
if (old_client != NULL && client_is_floating(old_client)) { 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; Client *current;
SLIST_FOREACH(current, &(client->workspace->focus_stack), focus_clients) { SLIST_FOREACH(current, &(client->workspace->focus_stack), focus_clients) {
if (client_is_floating(current)) if (client_is_floating(current))
continue; 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); redecorate_window(conn, current);
break; break;
} }
} }
SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients);
SLIST_INSERT_HEAD(&(client->workspace->focus_stack), 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 were in stacking mode, this renders the container to update changes in the title /* If were in stacking mode, this renders the container to update changes in the title
bars and to raise the focused client */ bars and to raise the focused client */
if ((old_client != NULL) && (old_client != client) && !old_client->dock) if ((old_client != NULL) && (old_client != client) && !old_client->dock)
@ -411,14 +413,14 @@ after_stackwin:
if (client == container->currently_focused || client == last_focused) if (client == container->currently_focused || client == last_focused)
continue; 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 }; uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW };
xcb_configure_window(conn, client->frame, xcb_configure_window(conn, client->frame,
XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
} }
if (last_focused != NULL) { 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 }; uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW };
xcb_configure_window(conn, last_focused->frame, xcb_configure_window(conn, last_focused->frame,
XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); 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; 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; Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) { TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->screen == NULL) if (ws->output == NULL)
continue; continue;
Client *client; Client *client;
SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) { 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); client->window_class_class, client->name);
if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len)) if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len))
continue; continue;
@ -481,6 +483,47 @@ done:
return matching; 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__) #if defined(__OpenBSD__)
/* /*

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
@ -22,10 +22,13 @@
#include "config.h" #include "config.h"
#include "xcb.h" #include "xcb.h"
#include "table.h" #include "table.h"
#include "xinerama.h" #include "randr.h"
#include "layout.h" #include "layout.h"
#include "workspace.h" #include "workspace.h"
#include "client.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), * 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. */ /* If we are still there, we could not find the requested workspace. */
int last_ws = TAILQ_LAST(workspaces, workspaces_head)->num; 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++) { for (int c = last_ws; c < number; c++) {
LOG("Creating new ws\n"); DLOG("Creating new ws\n");
ws = scalloc(sizeof(Workspace)); ws = scalloc(sizeof(Workspace));
ws->num = c+1; ws->num = c+1;
@ -55,8 +58,12 @@ Workspace *workspace_get(int number) {
workspace_set_name(ws, NULL); workspace_set_name(ws, NULL);
TAILQ_INSERT_TAIL(workspaces, ws, workspaces); 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; return ws;
} }
@ -80,13 +87,13 @@ void workspace_set_name(Workspace *ws, const char *name) {
errx(1, "asprintf() failed"); errx(1, "asprintf() failed");
FREE(ws->name); FREE(ws->name);
FREE(ws->utf8_name);
ws->name = convert_utf8_to_ucs2(label, &(ws->name_len)); ws->name = convert_utf8_to_ucs2(label, &(ws->name_len));
if (config.font != NULL) if (config.font != NULL)
ws->text_width = predict_text_width(global_conn, config.font, ws->name, ws->name_len); ws->text_width = predict_text_width(global_conn, config.font, ws->name, ws->name_len);
else ws->text_width = 0; else ws->text_width = 0;
ws->utf8_name = label;
free(label);
} }
/* /*
@ -96,7 +103,7 @@ void workspace_set_name(Workspace *ws, const char *name) {
* *
*/ */
bool workspace_is_visible(Workspace *ws) { 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 were switching to */ /* t_ws (to workspace) is just a convenience pointer to the workspace were switching to */
Workspace *t_ws = workspace_get(workspace-1); Workspace *t_ws = workspace_get(workspace-1);
LOG("show_workspace(%d)\n", workspace); DLOG("show_workspace(%d)\n", workspace);
/* Store current_row/current_col */ /* Store current_row/current_col */
c_ws->current_row = current_row; c_ws->current_row = current_row;
c_ws->current_col = current_col; c_ws->current_col = current_col;
/* Check if the workspace has not been used yet */ /* 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) { if (c_ws->output != t_ws->output) {
/* We need to switch to the other screen first */ /* We need to switch to the other output first */
LOG("moving over to other screen.\n"); DLOG("moving over to other output.\n");
/* Store the old client */ /* Store the old client */
Client *old_client = CUR_CELL->currently_focused; 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_col = c_ws->current_col;
current_row = c_ws->current_row; current_row = c_ws->current_row;
if (CUR_CELL->currently_focused != NULL) if (CUR_CELL->currently_focused != NULL)
need_warp = true; need_warp = true;
else { else {
Rect *dims = &(c_ws->screen->rect); Rect *dims = &(c_ws->output->rect);
xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0,
dims->x + (dims->width / 2), dims->y + (dims->height / 2)); 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) if ((old_client != NULL) && !old_client->dock)
redecorate_window(conn, old_client); redecorate_window(conn, old_client);
else xcb_flush(conn); 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 were already there */ /* Check if we need to change something or if were 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)); Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
if (last_focused != SLIST_END(&(c_ws->focus_stack))) if (last_focused != SLIST_END(&(c_ws->focus_stack)))
set_focus(conn, last_focused, true); set_focus(conn, last_focused, true);
@ -152,24 +168,28 @@ void workspace_show(xcb_connection_t *conn, int workspace) {
xcb_flush(conn); xcb_flush(conn);
} }
ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
return; return;
} }
Workspace *old_workspace = c_ws; 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 */ /* Unmap all clients of the old workspace */
workspace_unmap_clients(conn, old_workspace); workspace_unmap_clients(conn, old_workspace);
current_row = c_ws->current_row; current_row = c_ws->current_row;
current_col = c_ws->current_col; 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); workspace_map_clients(conn, c_ws);
/* POTENTIAL TO IMPROVE HERE: due to the call to _map_clients first and /* POTENTIAL TO IMPROVE HERE: due to the call to _map_clients first and
* render_layout afterwards, there is a short flickering on the source * 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, youll see the * client on ws 4, move it to ws 3, switch to ws 3, youll see the
* flickering). */ * 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 /* We can warp the pointer only after the window has been
* reconfigured in render_layout, otherwise the pointer will * reconfigured in render_layout, otherwise the pointer will
* be warped to the old position, which will not work when we * 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) { if (last_focused != SLIST_END(&(c_ws->focus_stack)) && need_warp) {
client_warp_pointer_into(conn, last_focused); client_warp_pointer_into(conn, last_focused);
xcb_flush(conn); xcb_flush(conn);
} }
} }
/* /*
* Parses the preferred_screen property of a workspace. You can either specify * Assigns the given workspace to the given output by correctly updating its
* the screen number (it is not given that the screen numbering always stays * state and reconfiguring all the clients on this workspace.
* 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 * This is called when initializing a output and when re-assigning it to a
* either specify an x coordinate ("1280") or an y coordinate ("x800") or both * different output which just got available (if you configured it to be on
* ("1280x800"). * output 1 and you just plugged in output 1).
* *
*/ */
static i3Screen *get_screen_from_preference(struct screens_head *slist, char *preference) { void workspace_assign_to(Workspace *ws, Output *output, bool hide_it) {
i3Screen *screen; Client *client;
char *rest; bool empty = true;
int preferred_screen = strtol(preference, &rest, 10); 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)) { /* Copy the dimensions from the virtual output */
int x = INT_MAX, y = INT_MAX; memcpy(&(ws->rect), &(ws->output->rect), sizeof(Rect));
if (strchr(preference, 'x') != NULL) {
/* Check if only the y coordinate was specified */ ewmh_update_workarea();
if (*preference == 'x')
y = atoi(preference+1); /* Force reconfiguration for each client on that workspace */
else { SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) {
x = atoi(preference); client->force_reconfigure = true;
y = atoi(strchr(preference, 'x') + 1); empty = false;
}
} else {
x = atoi(preference);
} }
LOG("Looking for screen at %d x %d\n", x, y); if (empty)
return;
TAILQ_FOREACH(screen, slist, screens) /* Render the workspace to reconfigure the clients. However, they will be visible now, so… */
if ((x == INT_MAX || screen->rect.x == x) && render_workspace(global_conn, output, ws);
(y == INT_MAX || screen->rect.y == y)) {
LOG("found %p\n", screen); /* …unless we want to see them at the moment, we should hide that workspace */
return screen; if (visible && !hide_it)
return;
/* however, if this is the current workspace, we only need to adjust
* the outputs 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;
} }
LOG("none found\n"); workspace_unmap_clients(global_conn, ws);
return NULL;
} else {
int c = 0;
TAILQ_FOREACH(screen, slist, screens)
if (c++ == preferred_screen)
return screen;
}
return NULL;
} }
/* /*
@ -250,35 +268,43 @@ static i3Screen *get_screen_from_preference(struct screens_head *slist, char *pr
* the screen is not attached at the moment. * the screen is not attached at the moment.
* *
*/ */
void workspace_initialize(Workspace *ws, i3Screen *screen) { void workspace_initialize(Workspace *ws, Output *output, bool recheck) {
if (ws->screen != NULL) { Output *old_output;
LOG("Workspace already initialized\n");
if (ws->output != NULL && !recheck) {
DLOG("Workspace already initialized\n");
return; return;
} }
/* If this workspace has no preferred screen or if the screen it wants old_output = ws->output;
* 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;
/* Copy the dimensions from the virtual screen */ /* If this workspace has no preferred output or if the output it wants
memcpy(&(ws->rect), &(ws->screen->rect), sizeof(Rect)); * 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 * 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 *result = NULL;
Workspace *ws; Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) { TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->preferred_screen == NULL || if (ws->preferred_output == NULL ||
!screens_are_equal(get_screen_from_preference(slist, ws->preferred_screen), screen)) get_output_by_name(ws->preferred_output) != output)
continue; continue;
result = ws; result = ws;
@ -287,9 +313,8 @@ Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen *
if (result == NULL) { if (result == NULL) {
/* No assignment found, returning first unused workspace */ /* No assignment found, returning first unused workspace */
Workspace *ws;
TAILQ_FOREACH(ws, workspaces, workspaces) { TAILQ_FOREACH(ws, workspaces, workspaces) {
if (ws->screen != NULL) if (ws->output != NULL)
continue; continue;
result = ws; result = ws;
@ -298,16 +323,15 @@ Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen *
} }
if (result == NULL) { 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; int last_ws = 0;
TAILQ_FOREACH(ws, workspaces, workspaces) TAILQ_FOREACH(ws, workspaces, workspaces)
last_ws = ws->num; last_ws = ws->num;
result = workspace_get(last_ws + 1); result = workspace_get(last_ws + 1);
} }
workspace_initialize(result, screen); workspace_initialize(result, output, false);
return result; return result;
} }
@ -359,7 +383,7 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) {
int unmapped_clients = 0; int unmapped_clients = 0;
FOR_TABLE(u_ws) FOR_TABLE(u_ws)
CIRCLEQ_FOREACH(client, &(u_ws->table[cols][rows]->clients), clients) { 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); client_unmap(conn, client);
unmapped_clients++; unmapped_clients++;
} }
@ -369,7 +393,7 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) {
if (!client_is_floating(client)) if (!client_is_floating(client))
continue; 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); client_unmap(conn, client);
unmapped_clients++; 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) { if (unmapped_clients == 0 && u_ws != c_ws) {
/* Re-assign the workspace of all dock clients which use this workspace */ /* Re-assign the workspace of all dock clients which use this workspace */
Client *dock; Client *dock;
LOG("workspace %p is empty\n", u_ws); DLOG("workspace %p is empty\n", u_ws);
SLIST_FOREACH(dock, &(u_ws->screen->dock_clients), dock_clients) { SLIST_FOREACH(dock, &(u_ws->output->dock_clients), dock_clients) {
if (dock->workspace != u_ws) if (dock->workspace != u_ws)
continue; 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; dock->workspace = c_ws;
} }
u_ws->screen = NULL; u_ws->output = NULL;
} }
/* Unmap the stack windows on the given workspace, if any */ /* 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) { void workspace_update_urgent_flag(Workspace *ws) {
Client *current; Client *current;
bool old_flag = ws->urgent;
bool urgent = false;
SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) { SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) {
if (!current->urgent) if (!current->urgent)
continue; continue;
ws->urgent = true; urgent = true;
return; 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;
} }
/* /*

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
@ -21,6 +21,7 @@
#include "i3.h" #include "i3.h"
#include "util.h" #include "util.h"
#include "xcb.h" #include "xcb.h"
#include "log.h"
TAILQ_HEAD(cached_fonts_head, Font) cached_fonts = TAILQ_HEAD_INITIALIZER(cached_fonts); TAILQ_HEAD(cached_fonts_head, Font) cached_fonts = TAILQ_HEAD_INITIALIZER(cached_fonts);
unsigned int xcb_numlock_mask; 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 */ /* 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); 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, xcb_create_window(conn,
depth, depth,
result, /* the window id */ result, /* the window id */
@ -117,8 +110,14 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_cl
mask, mask,
values); values);
if (cursor > -1) /* 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_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id);
xcb_free_cursor(conn, cursor_id);
/* Map the window (= make it visible) */ /* Map the window (= make it visible) */
if (map) 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) { 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 the Rect did not change, the pixmap does not need to be recreated */
if (memcmp(&(pixmap->rect), pixmap->referred_rect, sizeof(Rect)) == 0) 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)); memcpy(&(pixmap->rect), pixmap->referred_rect, sizeof(Rect));
if (pixmap->id == 0 || pixmap->gc == 0) { if (pixmap->id == 0 || pixmap->gc == 0) {
LOG("Creating new pixmap...\n"); DLOG("Creating new pixmap...\n");
pixmap->id = xcb_generate_id(conn); pixmap->id = xcb_generate_id(conn);
pixmap->gc = xcb_generate_id(conn); pixmap->gc = xcb_generate_id(conn);
} else { } else {
LOG("Re-creating this pixmap...\n"); DLOG("Re-creating this pixmap...\n");
xcb_free_gc(conn, pixmap->gc); xcb_free_gc(conn, pixmap->gc);
xcb_free_pixmap(conn, pixmap->id); 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); cookie = xcb_query_text_extents(conn, font->id, length, (xcb_char2b_t*)text);
if ((reply = xcb_query_text_extents_reply(conn, cookie, &error)) == NULL) { 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); error->error_code);
/* We return the rather safe guess of 7 pixels, because a /* We return the rather safe guess of 7 pixels, because a
* rendering error is better than a crash. Plus, the user will * 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); free(reply);
return width; 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));
}

View File

@ -3,222 +3,87 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * 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 <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdbool.h>
#include <assert.h>
#include <time.h>
#include <xcb/xcb.h> #include <xcb/xcb.h>
#include <xcb/xinerama.h> #include <xcb/xinerama.h>
#include "queue.h" #include "queue.h"
#include "i3.h"
#include "data.h" #include "data.h"
#include "table.h"
#include "util.h" #include "util.h"
#include "xinerama.h" #include "xinerama.h"
#include "layout.h"
#include "xcb.h"
#include "config.h"
#include "workspace.h" #include "workspace.h"
#include "log.h"
#include "randr.h"
/* This TAILQ of i3Screens stores the virtual screens, used for handling overlapping screens static int num_screens;
* (xrandr --same-as) */
struct screens_head *virtual_screens;
static bool xinerama_enabled = true;
/* /*
* Returns true if both screen objects describe the same screen (checks their * Looks in outputs for the Output whose start coordinates are x, y
* size and position).
* *
*/ */
bool screens_are_equal(i3Screen *screen1, i3Screen *screen2) { static Output *get_screen_at(int x, int y) {
/* If one of both objects (or both) are NULL, we cannot compare them */ Output *output;
if (screen1 == NULL || screen2 == NULL) TAILQ_FOREACH(output, &outputs, outputs)
return false; if (output->rect.x == x && output->rect.y == y)
return output;
/* 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;
return NULL; return NULL;
} }
/* /*
* Looks in virtual_screens for the i3Screen which contains coordinates x, y * Gets the Xinerama screens and converts them to virtual Outputs (only one screen for two
*
*/
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
* Xinerama screen which are configured in clone mode) in the given screenlist * 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_query_screens_reply_t *reply;
xcb_xinerama_screen_info_t *screen_info; 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); reply = xcb_xinerama_query_screens_reply(conn, xcb_xinerama_query_screens_unchecked(conn), NULL);
if (!reply) { if (!reply) {
LOG("Couldn't get Xinerama screens\n"); ELOG("Couldn't get Xinerama screens\n");
return; return;
} }
screen_info = xcb_xinerama_query_screens_screen_info(reply); screen_info = xcb_xinerama_query_screens_screen_info(reply);
int screens = xcb_xinerama_query_screens_screen_info_length(reply); int screens = xcb_xinerama_query_screens_screen_info_length(reply);
num_screens = 0;
for (int screen = 0; screen < screens; screen++) { for (int screen = 0; screen < screens; screen++) {
i3Screen *s = get_screen_at(screen_info[screen].x_org, screen_info[screen].y_org, screenlist); Output *s = get_screen_at(screen_info[screen].x_org, screen_info[screen].y_org);
if (s != NULL) { 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 /* This screen already exists. We use the littlest screen so that the user
can always see the complete workspace */ can always see the complete workspace */
s->rect.width = min(s->rect.width, screen_info[screen].width); s->rect.width = min(s->rect.width, screen_info[screen].width);
s->rect.height = min(s->rect.height, screen_info[screen].height); s->rect.height = min(s->rect.height, screen_info[screen].height);
} else { } else {
s = calloc(sizeof(i3Screen), 1); 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.x = screen_info[screen].x_org;
s->rect.y = screen_info[screen].y_org; s->rect.y = screen_info[screen].y_org;
s->rect.width = screen_info[screen].width; s->rect.width = screen_info[screen].width;
s->rect.height = screen_info[screen].height; s->rect.height = screen_info[screen].height;
/* We always treat the screen at 0x0 as the primary screen */ /* We always treat the screen at 0x0 as the primary screen */
if (s->rect.x == 0 && s->rect.y == 0) if (s->rect.x == 0 && s->rect.y == 0)
TAILQ_INSERT_HEAD(screenlist, s, screens); TAILQ_INSERT_HEAD(&outputs, s, outputs);
else TAILQ_INSERT_TAIL(screenlist, s, screens); else TAILQ_INSERT_TAIL(&outputs, s, outputs);
num_screens++; num_screens++;
} }
LOG("found Xinerama screen: %d x %d at %d x %d\n", DLOG("found Xinerama screen: %d x %d at %d x %d\n",
screen_info[screen].width, screen_info[screen].height, screen_info[screen].width, screen_info[screen].height,
screen_info[screen].x_org, screen_info[screen].y_org); screen_info[screen].x_org, screen_info[screen].y_org);
} }
@ -226,11 +91,8 @@ static void query_screens(xcb_connection_t *conn, struct screens_head *screenlis
free(reply); free(reply);
if (num_screens == 0) { if (num_screens == 0) {
LOG("No screens found. This is weird. Trying again...\n"); ELOG("No screens found. Please fix your setup. i3 will exit now.\n");
continue; exit(0);
}
break;
} }
} }
@ -240,198 +102,27 @@ static void query_screens(xcb_connection_t *conn, struct screens_head *screenlis
* *
*/ */
void initialize_xinerama(xcb_connection_t *conn) { 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) { if (!xcb_get_extension_data(conn, &xcb_xinerama_id)->present) {
LOG("Xinerama extension not found, disabling.\n"); DLOG("Xinerama extension not found, disabling.\n");
disable_xinerama(conn); disable_randr(conn);
} else { } else {
xcb_xinerama_is_active_reply_t *reply; xcb_xinerama_is_active_reply_t *reply;
reply = xcb_xinerama_is_active_reply(conn, xcb_xinerama_is_active(conn), NULL); reply = xcb_xinerama_is_active_reply(conn, xcb_xinerama_is_active(conn), NULL);
if (reply == NULL || !reply->state) { if (reply == NULL || !reply->state) {
LOG("Xinerama is not active (in your X-Server), disabling.\n"); DLOG("Xinerama is not active (in your X-Server), disabling.\n");
disable_xinerama(conn); disable_randr(conn);
} else } else
query_screens(conn, virtual_screens); query_screens(conn);
FREE(reply); FREE(reply);
} }
i3Screen *screen; Output *output;
num_screens = 0; Workspace *ws;
/* Just go through each workspace and associate as many screens as we can. */ /* Just go through each active output and associate one workspace */
TAILQ_FOREACH(screen, virtual_screens, screens) { TAILQ_FOREACH(output, &outputs, outputs) {
screen->num = num_screens; ws = get_first_workspace_for_output(output);
num_screens++; initialize_output(conn, output, ws);
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 */
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++;
}
/* 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);
}

View File

@ -1,7 +1,7 @@
#!perl #!perl
# vim:ts=4:sw=4:expandtab # vim:ts=4:sw=4:expandtab
use Test::More tests => 4; use Test::More tests => 3;
use Test::Deep; use Test::Deep;
use X11::XCB qw(:all); use X11::XCB qw(:all);
use Data::Dumper; use Data::Dumper;
@ -9,6 +9,7 @@ use Time::HiRes qw(sleep);
use FindBin; use FindBin;
use lib "$FindBin::Bin/lib"; use lib "$FindBin::Bin/lib";
use i3test; use i3test;
use AnyEvent::I3;
BEGIN { BEGIN {
use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); 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 $x = X11::XCB::Connection->new;
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); my $i3 = i3;
isa_ok($sock, 'IO::Socket::UNIX');
##################################################################### #####################################################################
# Ensure IPC works by switching workspaces # Ensure IPC works by switching workspaces
##################################################################### #####################################################################
# Switch to the first workspace to get a clean testing environment # Switch to the first workspace to get a clean testing environment
$sock->write(i3test::format_ipc_command("1")); $i3->command('1')->recv;
sleep(0.25);
# Create a window so we can get a focus different from NULL # Create a window so we can get a focus different from NULL
my $window = i3test::open_standard_window($x); my $window = i3test::open_standard_window($x);
@ -41,9 +37,7 @@ my $focus = $x->input_focus;
diag("old focus = $focus"); diag("old focus = $focus");
# Switch to the nineth workspace # Switch to the nineth workspace
$sock->write(i3test::format_ipc_command("9")); $i3->command('9')->recv;
sleep(0.25);
my $new_focus = $x->input_focus; my $new_focus = $x->input_focus;
isnt($focus, $new_focus, "Focus changed"); isnt($focus, $new_focus, "Focus changed");

View File

@ -4,7 +4,7 @@
# the workspace to be empty). # the workspace to be empty).
# TODO: skip it by default? # TODO: skip it by default?
use Test::More tests => 8; use Test::More tests => 13;
use Test::Deep; use Test::Deep;
use X11::XCB qw(:all); use X11::XCB qw(:all);
use Data::Dumper; use Data::Dumper;
@ -12,29 +12,25 @@ use Time::HiRes qw(sleep);
use FindBin; use FindBin;
use lib "$FindBin::Bin/lib"; use lib "$FindBin::Bin/lib";
use i3test; use i3test;
use AnyEvent::I3;
BEGIN { 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'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
} }
my $x = X11::XCB::Connection->new; my $x = X11::XCB::Connection->new;
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); my $i3 = i3;
isa_ok($sock, 'IO::Socket::UNIX');
# Switch to the nineth workspace # Switch to the nineth workspace
$sock->write(i3test::format_ipc_command("9")); $i3->command('9')->recv;
sleep(0.25);
##################################################################### #####################################################################
# Create two windows and make sure focus switching works # Create two windows and make sure focus switching works
##################################################################### #####################################################################
# Change mode of the container to "default" for following tests # Change mode of the container to "default" for following tests
$sock->write(i3test::format_ipc_command("d")); $i3->command('d')->recv;
sleep(0.25);
my $top = i3test::open_standard_window($x); my $top = i3test::open_standard_window($x);
my $mid = i3test::open_standard_window($x); my $mid = i3test::open_standard_window($x);
@ -52,8 +48,7 @@ diag("bottom id = " . $bottom->id);
sub focus_after { sub focus_after {
my $msg = shift; my $msg = shift;
$sock->write(i3test::format_ipc_command($msg)); $i3->command($msg)->recv;
sleep(0.5);
return $x->input_focus; return $x->input_focus;
} }
@ -76,4 +71,48 @@ is($focus, $bottom->id, "Bottom window focused (wrapping to the top works)");
$focus = focus_after("j"); $focus = focus_after("j");
is($focus, $top->id, "Top window focused (wrapping to the bottom works)"); 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" ); diag( "Testing i3, Perl $], $^X" );

View File

@ -4,7 +4,7 @@
# the workspace to be empty). # the workspace to be empty).
# TODO: skip it by default? # TODO: skip it by default?
use Test::More tests => 10; use Test::More tests => 8;
use Test::Deep; use Test::Deep;
use X11::XCB qw(:all); use X11::XCB qw(:all);
use Data::Dumper; use Data::Dumper;
@ -12,21 +12,18 @@ use Time::HiRes qw(sleep);
use FindBin; use FindBin;
use lib "$FindBin::Bin/lib"; use lib "$FindBin::Bin/lib";
use i3test; use i3test;
use AnyEvent::I3;
BEGIN { 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'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
} }
my $x = X11::XCB::Connection->new; my $x = X11::XCB::Connection->new;
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); my $i3 = i3;
isa_ok($sock, 'IO::Socket::UNIX');
# Switch to the nineth workspace # Switch to the nineth workspace
$sock->write(i3test::format_ipc_command("9")); $i3->command('9')->recv;
sleep(0.25);
##################################################################### #####################################################################
# Create two windows and make sure focus switching works # Create two windows and make sure focus switching works
@ -50,8 +47,7 @@ diag("bottom id = " . $bottom->id);
sub focus_after { sub focus_after {
my $msg = shift; my $msg = shift;
$sock->write(i3test::format_ipc_command($msg)); $i3->command($msg)->recv;
sleep(0.5);
return $x->input_focus; return $x->input_focus;
} }
@ -82,9 +78,7 @@ is($focus, $top->id, "Top window focused");
# Move window cross-workspace # Move window cross-workspace
##################################################################### #####################################################################
$sock->write(i3test::format_ipc_command("m12")); for my $cmd (qw(m12 t m13 12 13)) {
$sock->write(i3test::format_ipc_command("t")); $i3->command($cmd)->recv;
$sock->write(i3test::format_ipc_command("m13")); }
$sock->write(i3test::format_ipc_command("12"));
$sock->write(i3test::format_ipc_command("13"));
ok(1, "Still living"); ok(1, "Still living");

View File

@ -3,7 +3,7 @@
# Checks if the focus is correctly restored, when creating a floating client # Checks if the focus is correctly restored, when creating a floating client
# over an unfocused tiling client and destroying the floating one again. # 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 Test::Deep;
use X11::XCB qw(:all); use X11::XCB qw(:all);
use Data::Dumper; use Data::Dumper;
@ -11,28 +11,25 @@ use Time::HiRes qw(sleep);
use FindBin; use FindBin;
use lib "$FindBin::Bin/lib"; use lib "$FindBin::Bin/lib";
use i3test; use i3test;
use AnyEvent::I3;
BEGIN { 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'); use_ok('X11::XCB::Window') or BAIL_OUT('Could not load X11::XCB::Window');
} }
my $x = X11::XCB::Connection->new; my $x = X11::XCB::Connection->new;
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); my $i3 = i3;
isa_ok($sock, 'IO::Socket::UNIX');
# Switch to the nineth workspace # Switch to the nineth workspace
$sock->write(i3test::format_ipc_command("9")); $i3->command('9')->recv;
sleep(0.25);
my $tiled_left = i3test::open_standard_window($x); my $tiled_left = i3test::open_standard_window($x);
my $tiled_right = i3test::open_standard_window($x); my $tiled_right = i3test::open_standard_window($x);
sleep(0.25); sleep(0.25);
$sock->write(i3test::format_ipc_command("ml")); $i3->command('ml')->recv;
# Get input focus before creating the floating window # Get input focus before creating the floating window
my $focus = $x->input_focus; my $focus = $x->input_focus;

View File

@ -4,7 +4,7 @@
# the workspace to be empty). # the workspace to be empty).
# TODO: skip it by default? # TODO: skip it by default?
use Test::More tests => 24; use Test::More tests => 22;
use Test::Deep; use Test::Deep;
use X11::XCB qw(:all); use X11::XCB qw(:all);
use Data::Dumper; use Data::Dumper;
@ -12,21 +12,18 @@ use Time::HiRes qw(sleep);
use FindBin; use FindBin;
use lib "$FindBin::Bin/lib"; use lib "$FindBin::Bin/lib";
use i3test; use i3test;
use AnyEvent::I3;
BEGIN { 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'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
} }
my $x = X11::XCB::Connection->new; my $x = X11::XCB::Connection->new;
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); my $i3 = i3;
isa_ok($sock, 'IO::Socket::UNIX');
# Switch to the nineth workspace # Switch to the nineth workspace
$sock->write(i3test::format_ipc_command("9")); $i3->command('9')->recv;
sleep(0.25);
##################################################################### #####################################################################
# Create two windows and make sure focus switching works # Create two windows and make sure focus switching works
@ -50,8 +47,7 @@ diag("bottom id = " . $bottom->id);
sub focus_after { sub focus_after {
my $msg = shift; my $msg = shift;
$sock->write(i3test::format_ipc_command($msg)); $i3->command($msg)->recv;
sleep(0.25);
return $x->input_focus; return $x->input_focus;
} }

View File

@ -12,7 +12,6 @@ use i3test;
use List::Util qw(first); use List::Util qw(first);
BEGIN { 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'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
} }
@ -43,4 +42,17 @@ sleep 0.25;
my $rect = $window->rect; my $rect = $window->rect;
is($rect->width, $primary->rect->width, 'dock client is as wide as the screen'); 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" ); diag( "Testing i3, Perl $], $^X" );

View File

@ -4,7 +4,7 @@
# the workspace to be empty). # the workspace to be empty).
# TODO: skip it by default? # TODO: skip it by default?
use Test::More tests => 9; use Test::More tests => 7;
use Test::Deep; use Test::Deep;
use X11::XCB qw(:all); use X11::XCB qw(:all);
use Data::Dumper; use Data::Dumper;
@ -13,21 +13,18 @@ use FindBin;
use Digest::SHA1 qw(sha1_base64); use Digest::SHA1 qw(sha1_base64);
use lib "$FindBin::Bin/lib"; use lib "$FindBin::Bin/lib";
use i3test; use i3test;
use AnyEvent::I3;
BEGIN { 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'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
} }
my $x = X11::XCB::Connection->new; my $x = X11::XCB::Connection->new;
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); my $i3 = i3;
isa_ok($sock, 'IO::Socket::UNIX');
# Switch to the nineth workspace # Switch to the nineth workspace
$sock->write(i3test::format_ipc_command("9")); $i3->command('9')->recv;
sleep(0.25);
##################################################################### #####################################################################
# Create two windows and make sure focus switching works # Create two windows and make sure focus switching works
@ -51,8 +48,7 @@ diag("bottom id = " . $bottom->id);
sub focus_after { sub focus_after {
my $msg = shift; my $msg = shift;
$sock->write(i3test::format_ipc_command($msg)); $i3->command($msg)->recv;
sleep(0.5);
return $x->input_focus; return $x->input_focus;
} }
@ -74,7 +70,7 @@ my $random_mark = sha1_base64(rand());
$focus = focus_after("goto $random_mark"); $focus = focus_after("goto $random_mark");
is($focus, $mid->id, "focus unchanged"); 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"); $focus = focus_after("k");
is($focus, $top->id, "Top window focused"); is($focus, $top->id, "Top window focused");

View File

@ -4,7 +4,7 @@
# the workspace to be empty). # the workspace to be empty).
# TODO: skip it by default? # TODO: skip it by default?
use Test::More tests => 17; use Test::More tests => 15;
use Test::Deep; use Test::Deep;
use X11::XCB qw(:all); use X11::XCB qw(:all);
use Data::Dumper; use Data::Dumper;
@ -13,21 +13,18 @@ use FindBin;
use Digest::SHA1 qw(sha1_base64); use Digest::SHA1 qw(sha1_base64);
use lib "$FindBin::Bin/lib"; use lib "$FindBin::Bin/lib";
use i3test; use i3test;
use AnyEvent::I3;
BEGIN { 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'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection');
} }
my $x = X11::XCB::Connection->new; my $x = X11::XCB::Connection->new;
my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); my $i3 = i3;
isa_ok($sock, 'IO::Socket::UNIX');
# Switch to the nineth workspace # Switch to the nineth workspace
$sock->write(i3test::format_ipc_command("9")); $i3->command('9')->recv;
sleep 0.25;
##################################################################### #####################################################################
# Create a floating window and see if resizing works # Create a floating window and see if resizing works
@ -78,13 +75,11 @@ sub test_resize {
test_resize; test_resize;
# Test borderless # Test borderless
$sock->write(i3test::format_ipc_command("bb")); $i3->command('bb')->recv;
sleep 0.25;
test_resize; test_resize;
# Test with 1-px-border # Test with 1-px-border
$sock->write(i3test::format_ipc_command("bp")); $i3->command('bp')->recv;
sleep 0.25;
test_resize; test_resize;

53
testcases/t/13-urgent.t Normal file
View File

@ -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;

View File

@ -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");

View File

@ -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" );

View File

@ -29,15 +29,4 @@ sub open_standard_window {
return $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 1