diff --git a/CMDMODE b/CMDMODE index 7d8f6f23..95cb5bc1 100644 --- a/CMDMODE +++ b/CMDMODE @@ -2,7 +2,8 @@ - Command mode --------------------- -This is the grammar for the command mode (your configuration file uses these commands, too). +This is the grammar for the 'command mode' (your configuration file +uses these commands, too). left := | right := | @@ -17,15 +18,17 @@ cmd := [ ] [ | ] with := { [ ] }+ jump := [ "[/]" | [ ] ] focus := focus [ | floating | tiling | ft ] -(travels the focus stack backwards the given amount of times (by default 1), so - it selects the window which had the focus before you focused the current one when - specifying "focus 1". - The special values 'floating' (select the next floating window), 'tiling' - (select the next tiling window), 'ft' (if the current window is floating, - select the next tiling window and vice-versa) are also valid) + (travels the focus stack backwards, number of times (by default 1). + So by specifying "focus 1" it selects the window which last had the focus + before you focused the current one window. + The following 3 special values are also valid: + 'floating' (select the next floating window). + 'tiling' (select the next tiling window). + 'ft' (toggle tiling/floating: if the current window is floating, + select the next tiling window and vice-versa) special := [ exec | kill | exit | restart ] -input := [ | | | | ] +input := [ | | | | ] you can cancel command mode by pressing escape anytime. diff --git a/DEPENDS b/DEPENDS index 47258068..b7a6fefb 100644 --- a/DEPENDS +++ b/DEPENDS @@ -1,12 +1,14 @@ -You need the following libraries. The version given is to be understand as the minimum -version. However, if any of these libraries changes the API, i3 may not compile anymore. -In that case, please try using the versions mentioned below until a fix is provided. +You need the following libraries. The version given is to be understood as the +minimum version required. However, if any of these libraries changes the API, +i3 may not compile anymore. In that case, please try using the versions +mentioned below until a fix is provided. * xcb-proto-1.3 (2008-12-10) * libxcb-1.1.93 (2008-12-11) * xcb-util-0.3.3 (2009-01-31) * libev * flex and bison + * yajl (the IPC interface uses JSON to serialize data) * asciidoc >= 8.3.0 for docs/hacking-howto * asciidoc, xmlto, docbook-xml for man/i3.man * Xlib, the one that comes with your X-Server @@ -24,6 +26,7 @@ http://xcb.freedesktop.org/dist/xcb-util-0.3.5.tar.bz2 http://libev.schmorp.de/ http://flex.sourceforge.net/ http://www.gnu.org/software/bison/ +http://lloyd.github.com/yajl/ http://i3.zekjur.net/i3lock/ http://tools.suckless.org/dmenu diff --git a/Makefile b/Makefile index 84f54cba..dfdc7a74 100644 --- a/Makefile +++ b/Makefile @@ -6,12 +6,22 @@ include $(TOPDIR)/common.mk AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c FILES:=$(filter-out $(AUTOGENERATED),$(wildcard src/*.c)) FILES:=$(FILES:.c=.o) -HEADERS=$(wildcard include/*.h) +HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h)) + +# Recursively generate loglevels.h by explicitly calling make +# We need this step because we need to ensure that loglevels.h will be +# updated if necessary, but we also want to save rebuilds of the object +# files, so we cannot let the object files depend on loglevels.h. +ifeq ($(MAKECMDGOALS),loglevels.h) +UNUSED:=$(warning Generating loglevels.h) +else +UNUSED:=$(shell $(MAKE) loglevels.h) +endif # Depend on the specific file (.c for each .o) and on all headers src/%.o: src/%.c ${HEADERS} echo "CC $<" - $(CC) $(CFLAGS) -c -o $@ $< + $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/$(shell basename $< .c)/ { print NR }' loglevels.tmp))" -c -o $@ $< all: src/cfgparse.y.o src/cfgparse.yy.o ${FILES} echo "LINK i3" @@ -22,25 +32,40 @@ all: src/cfgparse.y.o src/cfgparse.yy.o ${FILES} echo "SUBDIR i3-input" $(MAKE) TOPDIR=$(TOPDIR) -C i3-input -src/cfgparse.yy.o: src/cfgparse.l +loglevels.h: + echo "LOGLEVELS" + for file in $$(ls src/*.c src/*.y src/*.l | grep -v 'cfgparse.\(tab\|yy\).c'); \ + do \ + echo $$(basename $$file .c); \ + done > loglevels.tmp + (echo "char *loglevels[] = {"; for file in $$(cat loglevels.tmp); \ + do \ + echo "\"$$file\", "; \ + done; \ + echo "};") > include/loglevels.h; + +src/cfgparse.yy.o: src/cfgparse.l src/cfgparse.y.o ${HEADERS} echo "LEX $<" flex -i -o$(@:.o=.c) $< - $(CC) $(CFLAGS) -c -o $@ $(@:.o=.c) + $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c) -src/cfgparse.y.o: src/cfgparse.y +src/cfgparse.y.o: src/cfgparse.y ${HEADERS} echo "YACC $<" bison --debug --verbose -b $(basename $< .y) -d $< - $(CC) $(CFLAGS) -c -o $@ $(<:.y=.tab.c) + $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c) install: all echo "INSTALL" - $(INSTALL) -d -m 0755 $(DESTDIR)/usr/bin - $(INSTALL) -d -m 0755 $(DESTDIR)/etc/i3 - $(INSTALL) -d -m 0755 $(DESTDIR)/usr/share/xsessions - $(INSTALL) -m 0755 i3 $(DESTDIR)/usr/bin/ - test -e $(DESTDIR)/etc/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)/etc/i3/config - $(INSTALL) -m 0644 i3.welcome $(DESTDIR)/etc/i3/welcome - $(INSTALL) -m 0644 i3.desktop $(DESTDIR)/usr/share/xsessions/ + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -d -m 0755 $(DESTDIR)$(SYSCONFDIR)/i3 + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/include/i3 + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/xsessions + $(INSTALL) -m 0755 i3 $(DESTDIR)$(PREFIX)/bin/ + $(INSTALL) -m 0755 i3-wsbar $(DESTDIR)$(PREFIX)/bin/ + test -e $(DESTDIR)$(SYSCONFDIR)/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)$(SYSCONFDIR)/i3/config + $(INSTALL) -m 0644 i3.welcome $(DESTDIR)$(SYSCONFDIR)/i3/welcome + $(INSTALL) -m 0644 i3.desktop $(DESTDIR)$(PREFIX)/share/xsessions/ + $(INSTALL) -m 0644 include/i3/ipc.h $(DESTDIR)$(PREFIX)/include/i3/ $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg install $(MAKE) TOPDIR=$(TOPDIR) -C i3-input install @@ -48,7 +73,7 @@ dist: distclean [ ! -d i3-${VERSION} ] || rm -rf i3-${VERSION} [ ! -e i3-${VERSION}.tar.bz2 ] || rm i3-${VERSION}.tar.bz2 mkdir i3-${VERSION} - cp DEPENDS GOALS LICENSE PACKAGE-MAINTAINER TODO RELEASE-NOTES-${VERSION} i3.config i3.desktop i3.welcome pseudo-doc.doxygen Makefile i3-${VERSION} + cp DEPENDS GOALS LICENSE PACKAGE-MAINTAINER TODO RELEASE-NOTES-${VERSION} i3.config i3.desktop i3.welcome i3-wsbar pseudo-doc.doxygen Makefile i3-${VERSION} cp -r src i3-msg include man i3-${VERSION} # Only copy toplevel documentation (important stuff) mkdir i3-${VERSION}/docs @@ -64,7 +89,7 @@ dist: distclean rm -rf i3-${VERSION} clean: - rm -f src/*.o src/cfgparse.tab.{c,h} src/cfgparse.yy.c + rm -f src/*.o src/cfgparse.tab.{c,h} src/cfgparse.output src/cfgparse.yy.c loglevels.tmp include/loglevels.h $(MAKE) -C docs clean $(MAKE) -C man clean $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg clean diff --git a/PACKAGE-MAINTAINER b/PACKAGE-MAINTAINER index c5c10038..40222803 100644 --- a/PACKAGE-MAINTAINER +++ b/PACKAGE-MAINTAINER @@ -1,11 +1,15 @@ Dear package maintainer, -thanks for packaging i3. By doing so, you are improving your distribution and i3 in general. +thanks for packaging i3. By doing so, you are improving your distribution +and i3 in general. -Please read the file DEPENDS now, so you know which libraries are necessary and where to -get them from if your distribution does not already have packages for them. +Please read the file DEPENDS now, so you know which libraries are necessary +and where to get them from if your distribution does not already have +packages for them. + +Please make sure the manpage for i3 will be properly created and installed +in your package. -Please make sure the manpage for i3 will be properly created and installed in your package. On debian, this looks like this: # Compilation @@ -17,10 +21,11 @@ On debian, this looks like this: mkdir -p $(CURDIR)/debian/i3-wm/usr/share/man/man1 cp man/i3.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1 -If you got any questions, ideas, hints, problems or whatever, please do not -hesitate to contact me. I will help you out. Just drop me an E-Mail (find the address at -http://michael.stapelberg.de/Kontakt, scroll down to bottom), contact me using the same -address in jabber or ask on our IRC channel (#i3 on irc.twice-irc.de). +If you have any questions, ideas, hints, problems or whatever, please do not +hesitate to contact me. I will help you out. Just drop me an E-Mail (find the +address at http://michael.stapelberg.de/Kontakt, scroll down to bottom), +contact me using the same address in jabber or ask on our IRC channel: +(#i3 on irc.twice-irc.de). Thanks again for your efforts, Michael diff --git a/common.mk b/common.mk index 70305148..0334ac61 100644 --- a/common.mk +++ b/common.mk @@ -1,13 +1,21 @@ UNAME=$(shell uname) DEBUG=1 INSTALL=install -GIT_VERSION:=$(shell git describe --tags --always) +PREFIX=/usr +ifeq ($(PREFIX),/usr) +SYSCONFDIR=/etc +else +SYSCONFDIR=$(PREFIX)/etc +endif +GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1))" VERSION:=$(shell git describe --tags --abbrev=0) CFLAGS += -std=c99 CFLAGS += -pipe CFLAGS += -Wall -CFLAGS += -Wunused +# unused-function, unused-label, unused-variable are turned on by -Wall +# We don’t want unused-parameter because of the use of many callbacks +CFLAGS += -Wunused-value CFLAGS += -Iinclude CFLAGS += -I/usr/local/include CFLAGS += -DI3_VERSION=\"${GIT_VERSION}\" @@ -36,7 +44,9 @@ LDFLAGS += -lxcb-atom LDFLAGS += -lxcb-aux LDFLAGS += -lxcb-icccm LDFLAGS += -lxcb-xinerama +LDFLAGS += -lxcb-randr LDFLAGS += -lxcb +LDFLAGS += -lyajl LDFLAGS += -lX11 LDFLAGS += -lev LDFLAGS += -L/usr/local/lib -L/usr/pkg/lib @@ -48,7 +58,6 @@ LDFLAGS += -Wl,-rpath,/usr/local/lib -Wl,-rpath,/usr/pkg/lib endif ifeq ($(UNAME),OpenBSD) -CFLAGS += -ftrampolines CFLAGS += -I${X11BASE}/include LDFLAGS += -liconv LDFLAGS += -L${X11BASE}/lib diff --git a/debian/changelog b/debian/changelog index 769eb16b..3776e56e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +i3-wm (3.e-0) unstable; urgency=low + + * NOT YET RELEASED + + -- Michael Stapelberg Mon, 21 Dec 2009 23:08:01 +0100 + i3-wm (3.d-bf1-1) unstable; urgency=low * Bugfix: Don’t draw window title when titlebar is disabled diff --git a/debian/control b/debian/control index 0342b7d3..446f8a22 100644 --- a/debian/control +++ b/debian/control @@ -3,13 +3,12 @@ Section: utils Priority: extra Maintainer: Michael Stapelberg DM-Upload-Allowed: yes -Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison +Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev, perl Standards-Version: 3.8.3 Homepage: http://i3.zekjur.net/ Package: i3 Architecture: any -Priority: extra Section: x11 Depends: i3-wm, ${misc:Depends} Recommends: i3lock, dwm-tools, i3status @@ -22,25 +21,23 @@ Description: metapackage (i3 window manager, screen locker, menu, statusbar) Package: i3-wm Architecture: any -Priority: extra Section: x11 Depends: ${shlibs:Depends}, ${misc:Depends}, x11-utils Provides: x-window-manager Suggests: rxvt-unicode | x-terminal-emulator Recommends: xfonts-base Description: an improved dynamic tiling window manager - Key features of i3 are correct implementation of Xinerama (workspaces are + Key features of i3 are good support of multi-monitor setups (workspaces are assigned to virtual screens, i3 does the right thing when attaching new - monitors), XrandR support (not done yet), horizontal and vertical columns - (think of a table) in tiling. Also, special focus is on writing clean, - readable and well documented code. i3 uses xcb for asynchronous - communication with X11, and has several measures to be very fast. + monitors), XRandR support, horizontal and vertical columns (think of a table) + in tiling. Also, special focus is on writing clean, readable and well + documented code. i3 uses XCB for asynchronous communication with X11, and has + several measures to be very fast. . Please be aware i3 is primarily targeted at advanced users and developers. Package: i3-wm-dbg Architecture: any -Priority: extra Section: debug Depends: i3-wm (=${binary:Version}), ${misc:Depends} Description: Debugging symbols for the i3 window manager diff --git a/debian/i3-wm.docs b/debian/i3-wm.docs index 587c93a3..a8747817 100644 --- a/debian/i3-wm.docs +++ b/debian/i3-wm.docs @@ -8,3 +8,5 @@ docs/two_columns.png docs/two_terminals.png docs/modes.png docs/stacklimit.png +docs/ipc.html +docs/multi-monitor.html diff --git a/debian/rules b/debian/rules index 43545a01..981da126 100755 --- a/debian/rules +++ b/debian/rules @@ -45,6 +45,7 @@ install: build cp man/i3.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1 cp man/i3-msg.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1 cp man/i3-input.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1 + cp man/i3-wsbar.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1 # Build architecture-independent files here. diff --git a/docs/Makefile b/docs/Makefile index 123f839f..b17413ca 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,5 +1,5 @@ -all: hacking-howto.html debugging.html userguide.html +all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html hacking-howto.html: hacking-howto asciidoc -a toc -n $< @@ -10,6 +10,12 @@ debugging.html: debugging userguide.html: userguide asciidoc -a toc -n $< +ipc.html: ipc + asciidoc -a toc -n $< + +multi-monitor.html: multi-monitor + asciidoc -a toc -n $< + clean: rm -f */*.{aux,log,toc,bm,pdf,dvi} rm -f *.log *.html diff --git a/docs/debugging b/docs/debugging index d32329d4..d52edea9 100644 --- a/docs/debugging +++ b/docs/debugging @@ -1,54 +1,58 @@ Debugging i3: How To -================== +==================== Michael Stapelberg -April 2009 +March 2010 -This document describes how to debug i3 suitably for sending us useful bug reports, even -if you have no clue of C programming. +This document describes how to debug i3 suitably for sending us useful bug +reports, even if you have no clue of C programming. -First of all: Thank you for being interested in debugging i3. It really means something -to us to get your bug fixed. If you have any questions about the debugging and/or need -further help, do not hesitate to contact us! +First of all: Thank you for being interested in debugging i3. It really means +something to us to get your bug fixed. If you have any questions about the +debugging and/or need further help, do not hesitate to contact us! == Enabling logging -i3 spits out much information onto stdout. To have a clearly defined place where logfiles -will be saved, you should redirect stdout and stderr in xsession. While you’re at it, -putting each run of i3 in a separate logfile with date/time in it is a good idea to not -get confused about the different logfiles later on. +i3 spits out much information onto stdout, if told so. To have a clearly +defined place where log files will be saved, you should redirect stdout and +stderr in xsession. While you’re at it, putting each run of i3 in a separate +log file with date/time in it is a good idea to not get confused about the +different log files later on. -------------------------------------------------------------------- -exec /usr/bin/i3 >/home/michael/i3/i3log-$(date +'%F-%k-%M-%S') 2>&1 +exec /usr/bin/i3 -V -d all >/home/michael/i3/i3log-$(date +'%F-%k-%M-%S') 2>&1 -------------------------------------------------------------------- -== Enabling coredumps +== Enabling core dumps -When i3 crashes, often you have the chance of getting a coredump (an image of the memory -of the i3 process which can be loaded into a debugger). To get a core-dump, you have to -make sure that the user limit for core dump files is set high enough. Many systems ship -with a default value which even forbids core dumps completely. To disable the limit -completely and thus enable coredumps, use the following command (in your .xsession, before -starting i3): +When i3 crashes, often you have the chance of getting a 'core dump' (an image +of the memory of the i3 process which can be loaded into a debugger). To get a +core dump, you have to make sure that the user limit for core dump files is set +high enough. Many systems ship with a default value which even forbids core +dumps completely. To disable the limit completely and thus enable core dumps, +use the following command (in your .xsession, before starting i3): ------------------- ulimit -c unlimited ------------------- -Furthermore, to easily recognize core dumps and allow multiple of them, you should set -a custom core dump filename pattern, using a command like the following: +Furthermore, to easily recognize core dumps and allow multiple of them, you +should set a custom core dump filename pattern, using a command like the +following: --------------------------------------------- sudo sysctl -w kernel.core_pattern=core.%e.%p --------------------------------------------- -This will generate files which have the executable’s file name (%e) and the process id -(%p) in it. You can save this setting across reboots using +/etc/sysctl.conf+. +This will generate files which have the executable’s file name (%e) and the +process id (%p) in it. You can save this setting across reboots using ++/etc/sysctl.conf+. == Compiling with debug symbols -To actually get useful coredumps, you should make sure that your version of i3 is compiled -with debug symbols, that is, that they are not stripped during the build process. You -can check whether your executable contains symbols by issuing the following command: +To actually get useful core dumps, you should make sure that your version of i3 +is compiled with debug symbols, that is, that they are not stripped during the +build process. You can check whether your executable contains symbols by +issuing the following command: ---------------- file $(which i3) @@ -60,23 +64,23 @@ You should get an output like this: linked (uses shared libs), for GNU/Linux 2.6.18, not stripped ------------------------------------------------------------------------------ -Notice the +not stripped+, which is the important part. If you have a version which is -stripped, please have a look if your distribution provides debug symbols (package +i3-wm-dbg+ -on Debian for example) or if you can turn off stripping. If nothing helps, please build -i3 from source. +Notice the +not stripped+, which is the important part. If you have a version +which is stripped, please have a look if your distribution provides debug +symbols (package +i3-wm-dbg+ on Debian for example) or if you can turn off +stripping. If nothing helps, please build i3 from source. == Generating a backtrace -Once you have made sure that your i3 is compiled with debug symbols and that coredumps -are enabled, you can start getting some sense out of the coredumps. +Once you have made sure that your i3 is compiled with debug symbols and that +core dumps are enabled, you can start making sense out of the core dumps. -Because the coredump depends on the original executable (and its debug symbols), please -do this as soon as you encounter the problem. If you re-compile i3, your coredump might -be useless afterwards. +Because the core dump depends on the original executable (and its debug +symbols), please do this as soon as you encounter the problem. If you +re-compile i3, your core dump might be useless afterwards. -Please install +gdb+, a debugger for C. No worries, you don’t need to learn it now. -Start gdb using the following command (replacing the actual name of the coredump of -course): +Please install +gdb+, a debugger for C. No worries, you don’t need to learn it +now. Start gdb using the following command (replacing the actual name of the +core dump of course): ---------------------------- gdb $(which i3) core.i3.3849 @@ -88,11 +92,13 @@ Then, generate a backtrace using: backtrace full -------------- -== Sending bugreports/debugging on IRC +== Sending bug reports/debugging on IRC -When sending bugreports, please paste the relevant part of the log (if in doubt, please send us rather -too much information than too less) and the whole backtrace (if there was a coredump). +When sending bug reports, please paste the relevant part of the log (if in +doubt, please send us rather too much information than too less) and the whole +backtrace (if there was a core dump). -When debugging with us in IRC, be prepared to use a so called nopaste service such as http://nopaste.info -because pasting large amounts of text in IRC sometimes leads to incomplete lines (servers have line +When debugging with us in IRC, be prepared to use a so called nopaste service +such as http://nopaste.info or http://pastebin.com because pasting large +amounts of text in IRC sometimes leads to incomplete lines (servers have line length limitations) or flood kicks. diff --git a/docs/hacking-howto b/docs/hacking-howto index 82f0a941..dff074cb 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -1,72 +1,81 @@ Hacking i3: How To ================== Michael Stapelberg -May 2009 +December 2009 -This document is intended to be the first thing you read before looking and/or touching -i3’s source code. It should contain all important information to help you understand -why things are like they are. If it does not mention something you find necessary, please -do not hesitate to contact me. +This document is intended to be the first thing you read before looking and/or +touching i3’s source code. It should contain all important information to help +you understand why things are like they are. If it does not mention something +you find necessary, please do not hesitate to contact me. == Window Managers -A window manager is not necessarily needed to run X, but it is usually used in combination -to facilitate some things. The window manager's job is to take care of the placement of -windows, to provide the user some mechanisms to change the position/size of windows and -to communicate with clients to a certain extent (for example handle fullscreen requests -of clients such as MPlayer). +A window manager is not necessarily needed to run X, but it is usually used in +combination with X to facilitate some things. The window manager's job is to +take care of the placement of windows, to provide the user with some mechanisms +to change the position/size of windows and to communicate with clients to a +certain extent (for example handle fullscreen requests of clients such as +MPlayer). -There are no different contexts in which X11 clients run, so a window manager is just another -client, like all other X11 applications. However, it handles some events which normal clients -usually don’t handle. +There are no different contexts in which X11 clients run, so a window manager +is just another client, like all other X11 applications. However, it handles +some events which normal clients usually don’t handle. In the case of i3, the tasks (and order of them) are the following: . Grab the key bindings (events will be sent upon keypress/keyrelease) -. Iterate through all existing windows (if the window manager is not started as the first - client of X) and manage them (= reparent them, create window decorations) +. Iterate through all existing windows (if the window manager is not started as + the first client of X) and manage them (reparent them, create window + decorations, etc.) . When new windows are created, manage them . Handle the client’s `_WM_STATE` property, but only the `_WM_STATE_FULLSCREEN` . Handle the client’s `WM_NAME` property . Handle the client’s size hints to display them proportionally +. Handle the client’s urgency hint . Handle enter notifications (focus follows mouse) . Handle button (as in mouse buttons) presses for focus/raise on click . Handle expose events to re-draw own windows such as decorations . React to the user’s commands: Change focus, Move windows, Switch workspaces, -Change the layout mode of a container (default/stacking), Start a new application, -Restart the window manager + Change the layout mode of a container (default/stacking/tabbed), start a new + application, restart the window manager -In the following chapters, each of these tasks and their implementation details will be discussed. +In the following chapters, each of these tasks and their implementation details +will be discussed. === Tiling window managers -Traditionally, there are two approaches to managing windows: The most common one nowadays is -floating, which means the user can freely move/resize the windows. The other approach is called -tiling, which means that your window manager distributing windows to use as much space as -possible while not overlapping. +Traditionally, there are two approaches to managing windows: The most common +one nowadays is floating, which means the user can freely move/resize the +windows. The other approach is called tiling, which means that your window +manager distributes windows to use as much space as possible while not +overlapping each other. -The idea behind tiling is that you should not need to waste your time moving/resizing windows -while you usually want to get some work done. After all, most users sooner or later tend to -lay out their windows in a way which corresponds to tiling or stacking mode in i3. Therefore, -why not let i3 do this for you? Certainly, it’s faster than you could ever do it. +The idea behind tiling is that you should not need to waste your time +moving/resizing windows while you usually want to get some work done. After +all, most users sooner or later tend to lay out their windows in a way which +corresponds to tiling or stacking mode in i3. Therefore, why not let i3 do this +for you? Certainly, it’s faster than you could ever do it. -The problem with most tiling window managers is that they are too unflexible. In my opinion, a -window manager is just another tool, and similar to vim which can edit all kinds of text files -(like source code, HTML, …) and is not limited to a specific file type, a window manager should -not limit itself to a certain layout (like dwm, awesome, …) but provide mechanisms for you to -easily create the layout you need at the moment. +The problem with most tiling window managers is that they are too unflexible. +In my opinion, a window manager is just another tool, and similar to vim which +can edit all kinds of text files (like source code, HTML, …) and is not limited +to a specific file type, a window manager should not limit itself to a certain +layout (like dwm, awesome, …) but provide mechanisms for you to easily create +the layout you need at the moment. === The layout table -To accomplish flexible layouts, we decided to simply use a table. The table grows and shrinks -as you need it. Each cell holds a container which then holds windows (see picture below). You -can use different layouts for each container (default layout and stacking layout). +To accomplish flexible layouts, we decided to simply use a table. The table +grows and shrinks as you need it. Each cell holds a container which then holds +windows (see picture below). You can use different layouts for each container +(default layout and stacking layout). -So, when you open a terminal and immediately open another one, they reside in the same container, -in default layout. The layout table has exactly one column, one row and therefore one cell. -When you move one of the terminals to the right, the table needs to grow. It will be expanded -to two columns and one row. This enables you to have different layouts for each container. -The table then looks like this: +So, when you open a terminal and immediately open another one, they reside in +the same container, in default layout. The layout table has exactly one column, +one row and therefore one cell. When you move one of the terminals to the +right, the table needs to grow. It will be expanded to two columns and one row. +This enables you to have different layouts for each container. The table then +looks like this: [width="15%",cols="^,^"] |======== @@ -81,9 +90,10 @@ When moving terminal 2 to the bottom, the table will be expanded again. | | T2 |======== -You can really think of the layout table like a traditional HTML table, if you’ve ever -designed one. Especially col- and rowspan work equally. Below you see an example of -colspan=2 for the first container (which has T1 as window). +You can really think of the layout table like a traditional HTML table, if +you’ve ever designed one. Especially col- and rowspan work similarly. Below, +you see an example of colspan=2 for the first container (which has T1 as +window). [width="15%",cols="^asciidoc"] |======== @@ -100,19 +110,30 @@ Furthermore, you can freely resize table cells. == Files include/data.h:: -Contains data definitions used by nearly all files. You really need to read this first. +Contains data definitions used by nearly all files. You really need to read +this first. include/*.h:: -Contains forward definitions for all public functions, aswell as doxygen-compatible -comments (so if you want to get a bit more of the big picture, either browse all -header files or use doxygen if you prefer that). +Contains forward definitions for all public functions, as well as +doxygen-compatible comments (so if you want to get a bit more of the big +picture, either browse all header files or use doxygen if you prefer that). + +src/cfgparse.l:: +Contains the lexer for i3’s configuration file, written for +flex(1)+. + +src/cfgparse.y:: +Contains the parser for i3’s configuration file, written for +bison(1)+. + +src/click.c:: +Contains all functions which handle mouse button clicks (right mouse button +clicks initiate resizing and thus are relatively complex). src/client.c:: Contains all functions which are specific to a certain client (make it fullscreen, see if its class/name matches a pattern, kill it, …). src/commands.c:: -Parsing commands and actually execute them (focussing, moving, …). +Parsing commands and actually executing them (focusing, moving, …). src/config.c:: Parses the configuration file. @@ -124,7 +145,7 @@ src/floating.c:: Contains functions for floating mode (mostly resizing/dragging). src/handlers.c:: -Contains all handlers for all kind of X events (new window title, new hints, +Contains all handlers for all kinds of X events (new window title, new hints, unmapping, key presses, button presses, …). src/ipc.c:: @@ -143,9 +164,6 @@ reparents the window and inserts it into our data structures. src/resize.c:: Contains the functions to resize columns/rows in the table. -src/resize.c:: -Contains the functions to resize columns/rows in the table. - src/table.c:: Manages the most important internal data structure, the design table. @@ -159,12 +177,13 @@ src/xcb.c:: Contains wrappers to use xcb more easily. src/xinerama.c:: -(Re-)initializes the available screens and converts them to virtual screens (see below). +(Re-)initializes the available screens and converts them to virtual screens +(see below). == Data structures -See include/data.h for documented data structures. The most important ones are explained -right here. +See include/data.h for documented data structures. The most important ones are +explained right here. image:bigpicture.png[The Big Picture] @@ -178,37 +197,41 @@ So, the hierarchy is: === Virtual screens -A virtual screen (type `i3Screen`) is generated from the connected screens obtained -through Xinerama. The difference to the raw Xinerama monitors as seen when using +xrandr(1)+ -is that it falls back to the lowest common resolution of the logical screens. +A virtual screen (type `i3Screen`) is generated from the connected screens +obtained through Xinerama. The difference to the raw Xinerama monitors as seen +when using +xrandr(1)+ is that it falls back to the lowest common resolution of +the logical screens. -For example, if your notebook has 1280x800 and you connect a video projector with -1024x768, set up in clone mode (+xrandr \--output VGA \--mode 1024x768 \--same-as LVDS+), -i3 will have one virtual screen. +For example, if your notebook has 1280x800 and you connect a video projector +with 1024x768, set up in clone mode (+xrandr \--output VGA \--mode 1024x768 +\--same-as LVDS+), i3 will have one virtual screen. -However, if you configure it using +xrandr \--output VGA \--mode 1024x768 \--right-of LVDS+, -i3 will generate two virtual screens. For each virtual screen, a new workspace will be -assigned. New workspaces are created on the screen you are currently on. +However, if you configure it using +xrandr \--output VGA \--mode 1024x768 +\--right-of LVDS+, i3 will generate two virtual screens. For each virtual +screen, a new workspace will be assigned. New workspaces are created on the +screen you are currently on. === Workspace -A workspace is identified by its number. Basically, you could think of workspaces -as different desks in your bureau, if you like the desktop methaphor. They just contain -different sets of windows and are completely separate of each other. Other window -managers also call this ``Virtual desktops''. +A workspace is identified by its number. Basically, you could think of +workspaces as different desks in your office, if you like the desktop +methaphor. They just contain different sets of windows and are completely +separate of each other. Other window managers also call this ``Virtual +desktops''. === The layout table -Each workspace has a table, which is just a two-dimensional dynamic array containing -Containers (see below). This table grows and shrinks as you need it (by moving windows -to the right you can create a new column in the table, by moving them to the bottom -you create a new row). +Each workspace has a table, which is just a two-dimensional dynamic array +containing Containers (see below). This table grows and shrinks as you need it +(by moving windows to the right you can create a new column in the table, by +moving them to the bottom you create a new row). === Container -A container is the content of a table’s cell. It holds an arbitrary amount of windows -and has a specific layout (default layout or stack layout). Containers can consume -multiple table cells by modifying their colspan/rowspan attribute. +A container is the content of a table’s cell. It holds an arbitrary amount of +windows and has a specific layout (default layout, stack layout or tabbed +layout). Containers can consume multiple table cells by modifying their +colspan/rowspan attribute. === Client @@ -216,20 +239,22 @@ A client is x11-speak for a window. == List/queue macros -i3 makes heavy use of the list macros defined in BSD operating systems. To ensure -that the operating system on which i3 is compiled has all the awaited features, -i3 comes with `include/queue.h`. On BSD systems, you can use man `queue(3)`. On Linux, -you have to use google. +i3 makes heavy use of the list macros defined in BSD operating systems. To +ensure that the operating system on which i3 is compiled has all the expected +features, i3 comes with `include/queue.h`. On BSD systems, you can use man +`queue(3)`. On Linux, you have to use google (or read the source). -The lists used are `SLIST` (single linked lists) and `CIRCLEQ` (circular queues). -Usually, only forward traversal is necessary, so an `SLIST` works fine. However, -for the windows inside a container, a `CIRCLEQ` is necessary to go from the currently +The lists used are `SLIST` (single linked lists), `CIRCLEQ` (circular +queues) and TAILQ (tail queues). Usually, only forward traversal is necessary, +so an `SLIST` works fine. If inserting elements at arbitrary positions or at +the end of a list is necessary, a `TAILQ` is used instead. However, for the +windows inside a container, a `CIRCLEQ` is necessary to go from the currently selected window to the window above/below. == Naming conventions -There is a row of standard variables used in many events. The following names should be -chosen for those: +There is a row of standard variables used in many events. The following names +should be chosen for those: * ``conn'' is the xcb_connection_t * ``event'' is the event of the particular type @@ -249,116 +274,138 @@ chosen for those: === Grabbing the bindings -Grabbing the bindings is quite straight-forward. You pass X your combination of modifiers and -the keycode you want to grab and whether you want to grab them actively or passively. Most -bindings (everything except for bindings using Mode_switch) are grabbed passively, that is, -just the window manager gets the event and cannot replay it. +Grabbing the bindings is quite straight-forward. You pass X your combination of +modifiers and the keycode you want to grab and whether you want to grab them +actively or passively. Most bindings (everything except for bindings using +Mode_switch) are grabbed passively, that is, just the window manager gets the +event and cannot replay it. -We need to grab bindings that use Mode_switch actively because of a bug in X. When the window -manager receives the keypress/keyrelease event for an actively grabbed keycode, it has to decide -what to do with this event: It can either replay it so that other applications get it or it -can prevent other applications from receiving it. +We need to grab bindings that use Mode_switch actively because of a bug in X. +When the window manager receives the keypress/keyrelease event for an actively +grabbed keycode, it has to decide what to do with this event: It can either +replay it so that other applications get it or it can prevent other +applications from receiving it. -So, why do we need to grab keycodes actively? Because X does not set the state-property of -keypress/keyrelease events properly. The Mode_switch bit is not set and we need to get it -using XkbGetState. This means we cannot pass X our combination of modifiers containing Mode_switch -when grabbing the key and therefore need to grab the keycode itself without any modiffiers. -This means, if you bind Mode_switch + keycode 38 ("a"), i3 will grab keycode 38 ("a") and -check on each press of "a" if the Mode_switch bit is set using XKB. If yes, it will handle -the event, if not, it will replay the event. +So, why do we need to grab keycodes actively? Because X does not set the +state-property of keypress/keyrelease events properly. The Mode_switch bit is +not set and we need to get it using XkbGetState. This means we cannot pass X +our combination of modifiers containing Mode_switch when grabbing the key and +therefore need to grab the keycode itself without any modifiers. This means, +if you bind Mode_switch + keycode 38 ("a"), i3 will grab keycode 38 ("a") and +check on each press of "a" if the Mode_switch bit is set using XKB. If yes, it +will handle the event, if not, it will replay the event. === Handling a keypress -As mentioned in "Grabbing the bindings", upon a keypress event, i3 first gets the correct state. +As mentioned in "Grabbing the bindings", upon a keypress event, i3 first gets +the correct state. -Then, it looks through all bindings and gets the one which matches the received event. +Then, it looks through all bindings and gets the one which matches the received +event. The bound command is parsed directly in command mode. == Manage windows (src/mainx.c, manage_window() and reparent_window()) -`manage_window()` does some checks to decide whether the window should be managed at all: +`manage_window()` does some checks to decide whether the window should be +managed at all: * Windows have to be mapped, that is, visible on screen - * The override_redirect must not be set. Windows with override_redirect shall not be - managed by a window manager + * The override_redirect must not be set. Windows with override_redirect shall + not be managed by a window manager -Afterwards, i3 gets the intial geometry and reparents the window if it wasn’t already -managed. +Afterwards, i3 gets the intial geometry and reparents the window (see +`reparent_window()`) if it wasn’t already managed. -Reparenting means that for each window which is reparented, a new window, slightly larger -than the original one, is created. The original window is then reparented to the bigger one -(called "frame"). +Reparenting means that for each window which is reparented, a new window, +slightly larger than the original one, is created. The original window is then +reparented to the bigger one (called "frame"). -After reparenting, the window type (`_NET_WM_WINDOW_TYPE`) is checked to see whether this -window is a dock (`_NET_WM_WINDOW_TYPE_DOCK`), like dzen2 for example. Docks are handled -differently, they don’t have decorations and are not assigned to a specific container. -Instead, they are positioned at the bottom of the screen. To get the height which needsd -to be reserved for the window, the `_NET_WM_STRUT_PARTIAL` property is used. +After reparenting, the window type (`_NET_WM_WINDOW_TYPE`) is checked to see +whether this window is a dock (`_NET_WM_WINDOW_TYPE_DOCK`), like dzen2 for +example. Docks are handled differently, they don’t have decorations and are not +assigned to a specific container. Instead, they are positioned at the bottom +of the screen. To get the height which needsd to be reserved for the window, +the `_NET_WM_STRUT_PARTIAL` property is used. + +Furthermore, the list of assignments (to other workspaces, which may be on +other screens) is checked. If the window matches one of the user’s criteria, +it may either be put in floating mode or moved to a different workspace. If the +target workspace is not visible, the window will not be mapped. == What happens when an application is started? -i3 does not care for applications. All it notices is when new windows are mapped (see -`src/handlers.c`, `handle_map_request()`). The window is then reparented (see section -"Manage windows"). +i3 does not care for applications. All it notices is when new windows are +mapped (see `src/handlers.c`, `handle_map_request()`). The window is then +reparented (see section "Manage windows"). -After reparenting the window, `render_layout()` is called which renders the internal -layout table. The window was placed in the currently focused container and -therefore the new window and the old windows (if any) need to be moved/resized -so that the currently active layout (default mode/stacking mode) is rendered -correctly. To move/resize windows, a window is ``configured'' in X11-speak. +After reparenting the window, `render_layout()` is called which renders the +internal layout table. The new window has been placed in the currently focused +container and therefore the new window and the old windows (if any) need to be +moved/resized so that the currently active layout (default/stacking/tabbed mode) +is rendered correctly. To move/resize windows, a window is ``configured'' in +X11-speak. -Some applications, such as MPlayer obivously assume the window manager is stupid -and try to configure their windows by themselves. This generates an event called -configurerequest. i3 handles these events and tells the window the size it had -before the configurerequest (with the exception of not yet mapped windows, which -get configured like they want to, and floating windows, which can reconfigure -themselves). +Some applications, such as MPlayer obviously assume the window manager is +stupid and try to configure their windows by themselves. This generates an +event called configurerequest. i3 handles these events and tells the window the +size it had before the configurerequest (with the exception of not yet mapped +windows, which get configured like they want to, and floating windows, which +can reconfigure themselves). == _NET_WM_STATE -Only the _NET_WM_STATE_FULLSCREEN atom is handled. It calls ``toggle_fullscreen()'' for the -specific client which just configures the client to use the whole screen on which it -currently is. Also, it is set as fullscreen_client for the i3Screen. +Only the _NET_WM_STATE_FULLSCREEN atom is handled. It calls +``toggle_fullscreen()'' for the specific client which just configures the +client to use the whole screen on which it currently is. Also, it is set as +fullscreen_client for the i3Screen. == WM_NAME -When the WM_NAME property of a window changes, its decoration (containing the title) -is re-rendered. +When the WM_NAME property of a window changes, its decoration (containing the +title) is re-rendered. Note that WM_NAME is in COMPOUND_TEXT encoding which is +totally uncommon and cumbersome. Therefore, the _NET_WM_NAME atom will be used +if present. + +== _NET_WM_NAME + +Like WM_NAME, this atom contains the title of a window. However, _NET_WM_NAME +is encoded in UTF-8. i3 will recode it to UCS-2 in order to be able to pass it +to X. Using an appropriate font (ISO-10646), you can see most special +characters (every special character contained in your font). == Size hints -Size hints specify the minimum/maximum size for a given window aswell as its aspect ratio. -At the moment, as i3 does not have a floating mode yet, only the aspect ratio is parsed. -This is important for clients like mplayer, who only set the aspect ratio and resize their -window to be as small as possible (but only with some video outputs, for example in Xv, -while when using x11, mplayer does the necessary centering for itself). +Size hints specify the minimum/maximum size for a given window as well as its +aspect ratio. This is important for clients like mplayer, who only set the +aspect ratio and resize their window to be as small as possible (but only with +some video outputs, for example in Xv, while when using x11, mplayer does the +necessary centering for itself). -So, when an aspect ratio was specified, i3 adjusts the height of the window until the -size maintains the correct aspect ratio. For the code to do this, see src/layout.c, -function resize_client(). +So, when an aspect ratio was specified, i3 adjusts the height of the window +until the size maintains the correct aspect ratio. For the code to do this, see +src/layout.c, function resize_client(). == Rendering (src/layout.c, render_layout() and render_container()) -There are two entry points to rendering: render_layout() and render_container(). The -former one renders all virtual screens, the currently active workspace of each virtual -screen and all containers (inside the table cells) of these workspaces using -render_container(). Therefore, if you need to render only a single container, for -example because a window was removed, added or changed its title, you should directly -call render_container(). +There are several entry points to rendering: `render_layout()`, +`render_workspace()` and `render_container()`. The former one calls +`render_workspace()` for every screen, which in turn will call +`render_container()` for every container inside its layout table. Therefore, if +you need to render only a single container, for example because a window was +removed, added or changed its title, you should directly call +render_container(). -Rendering consists of two steps: In the first one, in render_layout(), each container -gets its position (screen offset + offset in the table) and size (container's width -times colspan/rowspan). Then, render_container() is called: - -render_container() then takes different approaches, depending on the mode the container -is in. +Rendering consists of two steps: In the first one, in `render_workspace()`, each +container gets its position (screen offset + offset in the table) and size +(container's width times colspan/rowspan). Then, `render_container()` is called, +which takes different approaches, depending on the mode the container is in: === Common parts -On the frame (the window which was created around the client’s window for the decorations), -a black rectangle is drawn as a background for windows like MPlayer, which don’t completely -fit into the frame. +On the frame (the window which was created around the client’s window for the +decorations), a black rectangle is drawn as a background for windows like +MPlayer, which do not completely fit into the frame. === Default mode @@ -366,100 +413,112 @@ Each clients gets the container’s width and an equal amount of height. === Stack mode -In stack mode, a window containing the decorations of all windows inside the container -is placed at the top. The currently focused window is then given the whole remaining -space. +In stack mode, a window containing the decorations of all windows inside the +container is placed at the top. The currently focused window is then given the +whole remaining space. + +=== Tabbed mode + +Tabbed mode is like stack mode, except that the window decorations are drawn +in one single line at the top of the container. === Window decorations -The window decorations consist of a rectangle in the appropriate color (depends on whether -this window is the currently focused one or the last focused one in a not focused container -or not focused at all) forming the background. Afterwards, two lighter lines are drawn -and the last step is drawing the window’s title (see WM_NAME) onto it. +The window decorations consist of a rectangle in the appropriate color (depends +on whether this window is the currently focused one, the last focused one in a +not focused container or not focused at all) forming the background. +Afterwards, two lighter lines are drawn and the last step is drawing the +window’s title (see WM_NAME) onto it. === Fullscreen windows -For fullscreen windows, the `rect` (x, y, width, height) is not changed to allow the client -to easily go back to its previous position. Instead, fullscreen windows are skipped -when rendering. +For fullscreen windows, the `rect` (x, y, width, height) is not changed to +allow the client to easily go back to its previous position. Instead, +fullscreen windows are skipped when rendering. === Resizing containers -By clicking and dragging the border of a container, you can resize the whole column -(respectively row) which this container is in. This is necessary to keep the table -layout working and consistent. +By clicking and dragging the border of a container, you can resize the whole +column (respectively row) which this container is in. This is necessary to keep +the table layout working and consistent. -Currently, only vertical resizing is implemented. +The resizing works similarly to the resizing of floating windows or movement of +floating windows: -The resizing works similarly to the resizing of floating windows or movement of floating -windows: - -* A new, invisible window with the size of the root window is created (+grabwin+) -* Another window, 2px width and as high as your screen (or vice versa for horizontal - resizing) is created. Its background color is the border color and it is only - there to signalize the user how big the container will be (it creates the impression - of dragging the border out of the container). -* The +drag_pointer+ function of +src/floating.c+ is called to grab the pointer and - enter an own event loop which will pass all events (expose events) but motion notify - events. This function then calls the specified callback (+resize_callback+) which - does some boundary checking and moves the helper window. As soon as the mouse - button is released, this loop will be terminated. -* The new width_factor for each involved column (respectively row) will be calculated. +* A new, invisible window with the size of the root window is created + (+grabwin+) +* Another window, 2px width and as high as your screen (or vice versa for + horizontal resizing) is created. Its background color is the border color and + it is only there to inform the user how big the container will be (it + creates the impression of dragging the border out of the container). +* The +drag_pointer+ function of +src/floating.c+ is called to grab the pointer + and enter its own event loop which will pass all events (expose events) but + motion notify events. This function then calls the specified callback + (+resize_callback+) which does some boundary checking and moves the helper + window. As soon as the mouse button is released, this loop will be + terminated. +* The new width_factor for each involved column (respectively row) will be + calculated. == User commands / commandmode (src/commands.c) -Like in vim, you can control i3 using commands. They are intended to be a powerful -alternative to lots of shortcuts, because they can be combined. There are a few special -commands, which are the following: +Like in vim, you can control i3 using commands. They are intended to be a +powerful alternative to lots of shortcuts, because they can be combined. There +are a few special commands, which are the following: exec :: Starts the given command by passing it to `/bin/sh`. restart:: -Restarts i3 by executing `argv[0]` (the path with which you started i3) without forking. +Restarts i3 by executing `argv[0]` (the path with which you started i3) without +forking. w:: -"With". This is used to select a bunch of windows. Currently, only selecting the whole -container in which the window is in, is supported by specifying "w". +"With". This is used to select a bunch of windows. Currently, only selecting +the whole container in which the window is in, is supported by specifying "w". f, s, d:: Toggle fullscreen, stacking, default mode for the current window/container. -The other commands are to be combined with a direction. The directions are h, j, k and l, -like in vim (h = left, j = down, k = up, l = right). When you just specify the direction -keys, i3 will move the focus in that direction. You can provide "m" or "s" before the -direction to move a window respectively or snap. +The other commands are to be combined with a direction. The directions are h, +j, k and l, like in vim (h = left, j = down, k = up, l = right). When you just +specify the direction keys, i3 will move the focus in that direction. You can +provide "m" or "s" before the direction to move a window respectively or snap. == Gotchas -* Forgetting to call `xcb_flush(conn);` after sending a request. This usually leads to - code which looks like it works fine but which does not work under certain conditions. +* Forgetting to call `xcb_flush(conn);` after sending a request. This usually + leads to code which looks like it works fine but which does not work under + certain conditions. == Using git / sending patches -For a short introduction into using git, see http://www.spheredev.org/wiki/Git_for_the_lazy -or, for more documentation, see http://git-scm.com/documentation +For a short introduction into using git, see +http://www.spheredev.org/wiki/Git_for_the_lazy or, for more documentation, see +http://git-scm.com/documentation -When you want to send a patch because you fixed a bug or implemented a cool feature (please -talk to us before working on features to see whether they are maybe already implemented, not -possible because of some reason or don’t fit into the concept), please use git to create -a patchfile. +When you want to send a patch because you fixed a bug or implemented a cool +feature (please talk to us before working on features to see whether they are +maybe already implemented, not possible for some some reason, or don’t fit +into the concept), please use git to create a patchfile. -First of all, update your working copy to the latest version of the master branch: +First of all, update your working copy to the latest version of the master +branch: -------- git pull -------- -Afterwards, make the necessary changes for your bugfix/feature. Then, review the changes -using +git diff+ (you might want to enable colors in the diff using +git config diff.color auto+). -When you are definitely done, use +git commit -a+ to commit all changes you’ve made. +Afterwards, make the necessary changes for your bugfix/feature. Then, review +the changes using +git diff+ (you might want to enable colors in the diff using ++git config diff.color auto+). When you are definitely done, use +git commit +-a+ to commit all changes you’ve made. -Then, use the following command to generate a patchfile which we can directly apply to -the branch, preserving your commit message and name: +Then, use the following command to generate a patchfile which we can directly +apply to the branch, preserving your commit message and name: ----------------------- git format-patch origin ----------------------- -Just send us the generated file via mail. +Just send us the generated file via email. diff --git a/docs/ipc b/docs/ipc new file mode 100644 index 00000000..f65ae484 --- /dev/null +++ b/docs/ipc @@ -0,0 +1,301 @@ +IPC interface (interprocess communication) +========================================== +Michael Stapelberg +March 2010 + +This document describes how to interface with i3 from a separate process. This +is useful for example to remote-control i3 (to write test cases for example) or +to get various information like the current workspaces to implement an external +workspace bar. + +The method of choice for IPC in our case is a unix socket because it has very +little overhead on both sides and is usually available without headaches in +most languages. In the default configuration file, no ipc-socket path is +specified and thus no socket is created. The standard path (which +i3-msg+ and ++i3-input+ use) is +~/.i3/ipc.sock+. + +== Establishing a connection + +To establish a connection, simply open the IPC socket. The following code +snippet illustrates this in Perl: + +------------------------------------------------------------- +use IO::Socket::UNIX; +my $sock = IO::Socket::UNIX->new(Peer => '~/.i3/ipc.sock'); +------------------------------------------------------------- + +== Sending messages to i3 + +To send a message to i3, you have to format in the binary message format which +i3 expects. This format specifies a magic string in the beginning to ensure +the integrity of messages (to prevent follow-up errors). Following the magic +string comes the length of the payload of the message as 32-bit integer, and +the type of the message as 32-bit integer (the integers are not converted, so +they are in native byte order). + +The magic string currently is "i3-ipc" and will only be changed when a change +in the IPC API is done which breaks compatibility (we hope that we don’t need +to do that). + +Currently implemented message types are the following: + +COMMAND (0):: + The payload of the message is a command for i3 (like the commands you + can bind to keys in the configuration file) and will be executed + directly after receiving it. There is no reply to this message. +GET_WORKSPACES (1):: + Gets the current workspaces. The reply will be a JSON-encoded list of + workspaces (see the reply section). +SUBSCRIBE (2):: + Subscribes your connection to certain events. See <> 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" +-------------------------------------------------- + +Or, as a hexdump: +------------------------------------------------------------------------------ +00000000 69 33 2d 69 70 63 04 00 00 00 00 00 00 00 65 78 |i3-ipc........ex| +00000010 69 74 0a |it.| +------------------------------------------------------------------------------ + +To generate and send such a message, you could use the following code in Perl: +------------------------------------------------------------ +sub format_ipc_command { + my ($msg) = @_; + my $len; + # Get the real byte count (vs. amount of characters) + { use bytes; $len = length($msg); } + return "i3-ipc" . pack("LL", $len, 0) . $msg; +} + +$sock->write(format_ipc_command("exit")); +------------------------------------------------------------------------------ + +== Receiving replies from i3 + +Replies from i3 usually consist of a simple string (the length of the string +is the message_length, so you can consider them length-prefixed) which in turn +contain the JSON serialization of a data structure. For example, the +GET_WORKSPACES message returns an array of workspaces (each workspace is a map +with certain attributes). + +=== Reply format + +The reply format is identical to the normal message format. There also is +the magic string, then the message length, then the message type and the +payload. + +The following reply types are implemented: + +COMMAND (0):: + Confirmation/Error code for the COMMAND message. +GET_WORKSPACES (1):: + Reply to the GET_WORKSPACES message. +SUBSCRIBE (2):: + Confirmation/Error code for the SUBSCRIBE message. +GET_OUTPUTS (3):: + Reply to the GET_OUTPUTS message. + +=== COMMAND reply + +The reply consists of a single serialized map. At the moment, the only +property is +success (bool)+, but this will be expanded in future versions. + +*Example:* +------------------- +{ "success": true } +------------------- + +=== GET_WORKSPACES reply + +The reply consists of a serialized list of workspaces. Each workspace has the +following properties: + +num (integer):: + The logical number of the workspace. Corresponds to the command + to switch to this workspace. +name (string):: + The name of this workspace (by default num+1), as changed by the + user. Encoded in UTF-8. +visible (boolean):: + Whether this workspace is currently visible on an output (multiple + workspaces can be visible at the same time). +focused (boolean):: + Whether this workspace currently has the focus (only one workspace + can have the focus at the same time). +urgent (boolean):: + Whether a window on this workspace has the "urgent" flag set. +rect (map):: + The rectangle of this workspace (equals the rect of the output it + is on), consists of x, y, width, height. +output (string):: + The video output this workspace is on (LVDS1, VGA1, …). + +*Example:* +------------------- +[ + { + "num": 0, + "name": "1", + "visible": true, + "focused": true, + "urgent": false, + "rect": { + "x": 0, + "y": 0, + "width": 1280, + "height": 800 + }, + "output": "LVDS1" + }, + { + "num": 1, + "name": "2", + "visible": false, + "focused": false, + "urgent": false, + "rect": { + "x": 0, + "y": 0, + "width": 1280, + "height": 800 + }, + "output": "LVDS1" + } +] +------------------- + +=== SUBSCRIBE reply + +The reply consists of a single serialized map. The only property is ++success (bool)+, indicating whether the subscription was successful (the +default) or whether a JSON parse error occurred. + +*Example:* +------------------- +{ "success": true } +------------------- + +=== GET_OUTPUTS reply + +The reply consists of a serialized list of outputs. Each output has the +following properties: + +name (string):: + The name of this output (as seen in +xrandr(1)+). Encoded in UTF-8. +active (boolean):: + Whether this output is currently active (has a valid mode). +current_workspace (integer):: + The current workspace which is visible on this output. +null+ if the + output is not active. +rect (map):: + The rectangle of this output (equals the rect of the output it + is on), consists of x, y, width, height. + +*Example:* +------------------- +[ + { + "name": "LVDS1", + "active": true, + "current_workspace": 4, + "rect": { + "x": 0, + "y": 0, + "width": 1280, + "height": 800 + } + }, + { + "name": "VGA1", + "active": true, + "current_workspace": 1, + "rect": { + "x": 1280, + "y": 0, + "width": 1280, + "height": 1024 + }, + } +] +------------------- + +== Events + +[[events]] + +To get informed when certain things happen in i3, clients can subscribe to +events. Events consist of a name (like "workspace") and an event reply type +(like I3_IPC_EVENT_WORKSPACE). The events sent by i3 are in the same format +as replies to specific commands. + +Caveat: As soon as you subscribe to an event, it is not guaranteed any longer +that the requests to i3 are processed in order. This means, the following +situation can happen: You send a GET_WORKSPACES request but you receive a +"workspace" event before receiving the reply to GET_WORKSPACES. If your +program does not want to cope which such kinds of race conditions (an +event based library may not have a problem here), I suggest you create a +separate connection to receive events. + +=== Subscribing to events + +By sending a message of type SUBSCRIBE with a JSON-encoded array as payload +you can register to an event. + +*Example:* +--------------------------------- +type: SUBSCRIBE +payload: [ "workspace", "focus" ] +--------------------------------- + +=== Available events + +workspace:: + Sent when the user switches to a different workspace, when a new + workspace is initialized or when a workspace is removed (because the + last client vanished). +output:: + Sent when RandR issues a change notification (of either screens, + outputs, CRTCs or output properties). + +=== workspace event + +This event consists of a single serialized map containing a property ++change (string)+ which indicates the type of the change ("focus", "init", +"empty", "urgent"). + +*Example:* +--------------------- +{ "change": "focus" } +--------------------- + +=== output event + +This event consists of a single serialized map containing a property ++change (string)+ which indicates the type of the change (currently only +"unspecified"). + +*Example:* +--------------------------- +{ "change": "unspecified" } +--------------------------- + +== See also + +For some languages, libraries are available (so you don’t have to implement +all this on your own). This list names some (if you wrote one, please let me +know): + +C:: + i3 includes a headerfile +i3/ipc.h+ which provides you all constants. + However, there is no library yet. +Ruby:: + http://github.com/badboy/i3-ipc +Perl:: + http://search.cpan.org/search?query=AnyEvent::I3 diff --git a/docs/keyboard-layer1.png b/docs/keyboard-layer1.png new file mode 100644 index 00000000..88268f7b Binary files /dev/null and b/docs/keyboard-layer1.png differ diff --git a/docs/keyboard-layer1.svg b/docs/keyboard-layer1.svg new file mode 100644 index 00000000..18e9ae4b --- /dev/null +++ b/docs/keyboard-layer1.svg @@ -0,0 +1,915 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Caps Lock + + + + + + + + + + + + + + + + + + Shift + + + + Shift + + + + + + + + + + + + fullscreen + tabbedlayout + defaultlayout + dmenu + focusfloating/tiling + left + down + up + right + stackinglayout + Mod1 + + diff --git a/docs/keyboard-layer2.png b/docs/keyboard-layer2.png new file mode 100644 index 00000000..85a7d216 Binary files /dev/null and b/docs/keyboard-layer2.png differ diff --git a/docs/keyboard-layer2.svg b/docs/keyboard-layer2.svg new file mode 100644 index 00000000..70cc602b --- /dev/null +++ b/docs/keyboard-layer2.svg @@ -0,0 +1,896 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Caps Lock + + + + + + + + + + + + + + + + + + + + + Shift + + + + Shift + + + + + + + + + + + + toggle tiling/floating + killwindow + exiti3 + restarti3 + moveleft + movedown + moveup + moveright + Mod1 + + diff --git a/docs/multi-monitor b/docs/multi-monitor new file mode 100644 index 00000000..ec0256c0 --- /dev/null +++ b/docs/multi-monitor @@ -0,0 +1,60 @@ +The multi-monitor situation +=========================== +Michael Stapelberg +March 2010 + +…or: oh no, I have an nVidia graphics card! + +== The quick fix + +If you are using the nVidia binary graphics driver (also known as 'blob') +you need to use the +--force-xinerama+ flag (in your .xsession) when starting +i3, like so: + +.Example: +---------------------------------------------- +exec i3 --force-xinerama -V >>~/.i3/i3log 2>&1 +---------------------------------------------- + +== The explanation + +Starting with version 3.ε, i3 uses the RandR (Rotate and Resize) API instead +of Xinerama. The reason for this, is that RandR provides more information +about your outputs and connected screens than Xinerama does. To be specific, +the code which handled on-the-fly screen reconfiguration (meaning without +restarting the X server) was a very messy heuristic and most of the time did +not work correctly -- that is just not possible with the little information +Xinerama offers (just a list of screen resolutions, no identifiers for the +screens or any additional information). Xinerama simply was not designed +for dynamic configuration. + +So RandR came along, as a more powerful alternative (RandR 1.2 to be specific). +It offers all of Xinerama’s possibilities and lots more. Using the RandR API +made our code much more robust and clean. Also, you can now reliably assign +workspaces to output names instead of some rather unreliable screen identifier +(position inside the list of screens, which could change, and so on…). + +As RandR has been around for about three years as of this writing, it seemed +like a very good idea to us, and it still is a very good one. What we did not +expect, however, was the nVidia binary driver. It still does not support RandR +(as of March 2010), even though nVidia has announced that it will support RandR +eventually. What does this mean for you, if you are stuck with the binary +driver for some reason (say the free drivers don’t work with your card)? First +of all, you are stuck with TwinView and cannot use +xrandr+. While this ruins +the user experience, the more grave problem is that the nVidia driver not only +does not support dynamic configuration using RandR, it also does not expose +correct multi-monitor information via the RandR API. So, in some setups, i3 +will not find any screens; in others, it will find one large screen which +actually contains both of your physical screens (but it will not know that +these are two screens). + +For this very reason, we decided to implement the following workaround: As +long as the nVidia driver does not support RandR, an option called ++--force-xinerama+ is available in i3. This option gets the list of screens +*once* when starting, and never updates it. As the nVidia driver cannot do +dynamic configuration anyways, this is not a big deal. + +== See also + +For more information on how to use multi-monitor setups, see the i3 User’s +Guide. diff --git a/docs/userguide b/docs/userguide index f7eb8ab1..c706c631 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1,64 +1,83 @@ i3 User’s Guide =============== Michael Stapelberg -August 2009 +March 2010 -This document contains all information you need to configuring and using the i3 +This document contains all the information you need to configure and use the i3 window manager. If it does not, please contact me on IRC, Jabber or E-Mail and I’ll help you out. -For a complete listing of the default keybindings, please see the manpage. +== Default keybindings + +For the "too long; didn’t read" people, here is an overview of the default +keybindings (click to see the full size image): + +*Keys to use with Mod1 (alt):* + +image:keyboard-layer1.png["Keys to use with Mod1 (alt)",width=600,link="keyboard-layer1.png"] + +*Keys to use with Shift+Mod1:* + +image:keyboard-layer2.png["Keys to use with Shift+Mod1",width=600,link="keyboard-layer2.png"] + +As i3 uses keycodes in the default configuration, it does not matter which +keyboard layout you actually use. The key positions are what matters (of course +you can also use keysymbols, see <>). + +The red keys are the modifiers you need to press (by default), the blue keys +are your homerow. == Using i3 -=== Creating terminals and moving around +=== Opening terminals and moving around -A very basic operation is to create a new terminal. By default, the keybinding -for that is Mod1+Enter, that is Alt+Enter in the default configuration. By -pressing Mod1+Enter, a new terminal will be created and it will fill the whole -space which is available on your screen. +One very basic operation is opening a new terminal. By default, the keybinding +for this is Mod1+Enter, that is Alt+Enter in the default configuration. By +pressing Mod1+Enter, a new terminal will be opened. It will fill the whole +space available on your screen. image:single_terminal.png[Single terminal] It is important to keep in mind that i3 uses a table to manage your windows. At the moment, you have exactly one column and one row which leaves you with one -cell. In this cell, there is a container in which your newly opened terminal is. +cell. In this cell there is a container, which is where your new terminal is +opened. If you now open another terminal, you still have only one cell. However, the -container has both of your terminals. So, a container is just a group of clients -with a specific layout. You can resize containers as they directly resemble -columns/rows of the layout table. +container in that cell holds both of your terminals. So, a container is just a +group of clients with a specific layout. Containers can be resized by adjusting +the size of the cell that holds them. image:two_terminals.png[Two terminals] To move the focus between the two terminals, you use the direction keys which you may know from the editor +vi+. However, in i3, your homerow is used for these keys (in +vi+, the keys are shifted to the left by one for compatibility -with most keyboard layouts). Therefore, +Mod1+J+ is left, +Mod1+K+ is down, +Mod1+L+ -is up and `Mod1+;` is right. So, to switch between the terminals, use +Mod1+K+ or -+Mod1+L+. +with most keyboard layouts). Therefore, +Mod1+J+ is left, +Mod1+K+ is down, ++Mod1+L+ is up and `Mod1+;` is right. So, to switch between the terminals, +use +Mod1+K+ or +Mod1+L+. -To create a new row/column, you can simply move a terminal (or any other window) -to the direction you want to expand your table. So, let’s expand the table to -the right by pressing `Mod1+Shift+;`. +To create a new row/column (and a new cell), you can simply move a terminal (or +any other window) in the direction you want to expand your table. So, let’s +expand the table to the right by pressing `Mod1+Shift+;`. image:two_columns.png[Two columns] -=== Changing mode of containers +=== Changing container modes -A container can be in different modes: +A container can have the following modes: default:: -Windows are sized so that every window gets an equal amount of space of the +Windows are sized so that every window gets an equal amount of space in the container. stacking:: -Only the focused client of the container is displayed and you get a list of +Only the focused window in the container is displayed. You get a list of windows at the top of the container. tabbed:: The same principle as +stacking+, but the list of windows at the top is only -a single line which will be vertically split. +a single line which is vertically split. -To switch the mode, press +Mod1+e+ for default, +Mod1+h+ for stacking and +To switch modes, press +Mod1+e+ for default, +Mod1+h+ for stacking and +Mod1+w+ for tabbed. image:modes.png[Container modes] @@ -68,25 +87,29 @@ image:modes.png[Container modes] To display a window fullscreen or to go out of fullscreen mode again, press +Mod1+f+. +There is also a global fullscreen mode in i3 in which the client will use all +available outputs. To use it, or to get out of it again, press +Mod1+Shift+f+. + === Opening other applications -Aside from opening applicatios from a terminal, you can also use the handy +Aside from opening applications from a terminal, you can also use the handy +dmenu+ which is opened by pressing +Mod1+v+ by default. Just type the name -(or a part of it) of the application which you want to open. It has to be in -your +$PATH+ for that to work. +(or a part of it) of the application which you want to open. The application +typed has to be in your +$PATH+ for this to work. -Furthermore, if you have applications you open very frequently, you can also -create a keybinding for it. See the section "Configuring i3" for details. +Additionally, if you have applications you open very frequently, you can +create a keybinding for starting the application directly. See the section +"Configuring i3" for details. === Closing windows -If an application does not provide a mechanism to close (most applications +If an application does not provide a mechanism for closing (most applications provide a menu, the escape key or a shortcut like +Control+W+ to close), you can press +Mod1+Shift+q+ to kill a window. For applications which support the WM_DELETE protocol, this will correctly close the application (saving any modifications or doing other cleanup). If the application doesn’t support -it, your X server will kill the window and the behaviour depends on the -application. +the WM_DELETE protocol your X server will kill the window and the behaviour +depends on the application. === Using workspaces @@ -96,13 +119,13 @@ another workspace, press +Mod1+num+ where +num+ is the number of the workspace you want to use. If the workspace does not exist yet, it will be created. A common paradigm is to put the web browser on one workspace, communication -applications (+mutt+, +irssi+, ...) on another one and the ones with which you -work on the third one. Of course, there is no need to follow this approach. +applications (+mutt+, +irssi+, ...) on another one, and the ones with which you +work, on the third one. Of course, there is no need to follow this approach. -If you have multiple screens, a workspace will be created on each screen. If -you open a new workspace, it will be bound to the screen you created it on. -When you switch to a workspace on another screen, i3 will set focus to this -screen. +If you have multiple screens, a workspace will be created on each screen at +startup. If you open a new workspace, it will be bound to the screen you +created it on. When you switch to a workspace on another screen, i3 will set +focus to that screen. === Moving windows to workspaces @@ -113,20 +136,22 @@ it does not yet exist. === Resizing columns/rows -To resize columns or rows just grab the border between the two columns/rows +To resize columns or rows, just grab the border between the two columns/rows and move it to the wanted size. Please keep in mind that each cell of the table -holds a +container+ and thus you cannot horizontally resize single windows. +holds a +container+ and thus you cannot horizontally resize single windows. If +you need applications with different horizontal sizes, place them in seperate +cells one above the other. See <> for how to configure i3 to be able to resize columns/rows with your keyboard. === Restarting i3 inplace -To restart i3 inplace (and thus get it into a clean state if it has a bug, to -reload your configuration or even to upgrade to a newer version of i3) you -can use +Mod1+Shift+r+. Be aware, though, that this kills your current layout -and all the windows you have opened will be put in a default container in only -one cell. Saving the layout will be implemented in a later version. +To restart i3 inplace (and thus get into a clean state if there is a bug, or +to upgrade to a newer version of i3) you can use +Mod1+Shift+r+. Be aware, +though, that this kills your current layout and all the windows you have opened +will be put in a default container in only one cell. Saving layouts will be +implemented in a later version. === Exiting i3 @@ -135,7 +160,7 @@ To cleanly exit i3 without killing your X server, you can use +Mod1+Shift+e+. === Snapping Snapping is a mechanism to increase/decrease the colspan/rowspan of a container. -Colspan/rowspan is the amount of columns/rows a specific cell of the table +Colspan/rowspan is the number of columns/rows a specific cell of the table consumes. This is easier explained by giving an example, so take the following layout: @@ -146,47 +171,67 @@ by pressing +Mod1+Control+k+ (or snap container 2 rightwards). === Floating -Floating is the opposite of tiling mode. The position and size of a window -are then not managed by i3, but by you. Using this mode violates the tiling +Floating mode is the opposite of tiling mode. The position and size of a window +are not managed by i3, but by you. Using this mode violates the tiling paradigm but can be useful for some corner cases like "Save as" dialog -windows or toolbar windows (GIMP or similar). +windows, or toolbar windows (GIMP or similar). -You can enable floating for a window by pressing +Mod1+Shift+Space+. By -dragging the window’s titlebar with your mouse, you can move the window -around. By grabbing the borders and moving them you can resize the window. +You can enable floating mode for a window by pressing +Mod1+Shift+Space+. By +dragging the window’s titlebar with your mouse you can move the window +around. By grabbing the borders and moving them you can resize the window. You +can also do that by using the <>. -Bindings for doing this with your keyboard will follow. +For resizing floating windows with your keyboard, see <>. -Floating clients are always on top of tiling clients. +Floating windows are always on top of tiling windows. == Configuring i3 This is where the real fun begins ;-). Most things are very dependant on your -ideal working environment, so we can’t make reasonable defaults for them. +ideal working environment so we can’t make reasonable defaults for them. While not using a programming language for the configuration, i3 stays -quite flexible regarding to the things you usually want your window manager +quite flexible in regards to the things you usually want your window manager to do. For example, you can configure bindings to jump to specific windows, -you can set specific applications to start on a specific workspace, you can -automatically start applications, you can change the colors of i3 or bind -your keys to do useful stuff. +you can set specific applications to start on specific workspaces, you can +automatically start applications, you can change the colors of i3, and you +can bind your keys to do useful things. -To change the configuration of i3, copy +/etc/i3/config+ to +~/.i3/config+ -and edit it with a text editor. +To change the configuration of i3, copy +/etc/i3/config+ to +\~/.i3/config+ +(or +~/.config/i3/config+ if you like the XDG directory scheme) and edit it +with a text editor. -=== General configuration +=== Comments -terminal:: - Specifies the terminal emulator program you prefer. It will be started - by default when you press Mod1+Enter, but you can overwrite this. Refer - to it as +$terminal+ to keep things modular. -font:: - Specifies the default font you want i3 to use. Use an X core font - descriptor here, like - +-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1+. You can - use +xfontsel(1)+ to pick one. +It is possible and recommended to use comments in your configuration file to +properly document your setup for later reference. Comments are started with +a # and can only be used at the beginning of a line: + +*Examples*: +------------------- +# This is a comment +------------------- + +=== Fonts + +i3 uses X core fonts (not Xft) for rendering window titles and the internal +workspace bar. You can use +xfontsel(1)+ to generate such a font description. +To see special characters (Unicode), you need to use a font which supports +the ISO-10646 encoding. + +*Syntax*: +------------------------------ +font +------------------------------ + +*Examples*: +-------------------------------------------------------------- +font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 +-------------------------------------------------------------- + +[[keybindings]] === Keyboard bindings @@ -194,19 +239,19 @@ A keyboard binding makes i3 execute a command (see below) upon pressing a specific key. i3 allows you to bind either on keycodes or on keysyms (you can also mix your bindings, though i3 will not protect you from overlapping ones). -* A keysym (key symbol) is a description for a specific symbol, like "a" or "b", - but also more strange ones like "underscore" instead of "_". These are the ones - you also use in Xmodmap to remap your keys. To get the current mapping of your - keys, use +xmodmap -pke+. +* A keysym (key symbol) is a description for a specific symbol, like "a" + or "b", but also more strange ones like "underscore" instead of "_". These + are the ones you use in Xmodmap to remap your keys. To get the current + mapping of your keys, use +xmodmap -pke+. -* Keycodes however do not need to have a symbol assigned (handy for some hotkeys +* Keycodes do not need to have a symbol assigned (handy for some hotkeys on some notebooks) and they will not change their meaning as you switch to a - different keyboard layout. + different keyboard layout (when using +xmodmap+). -My recommendation is: If you often switch keyboard layouts because you try to -learn a different one, but you want to keep your bindings at the same place, -use keycodes. If you don’t switch layouts and like a clean and simple config -file, use keysyms. +My recommendation is: If you often switch keyboard layouts but you want to keep +your bindings in the same physical location on the keyboard, use keycodes. +If you don’t switch layouts, and want a clean and simple config file, use +keysyms. *Syntax*: ---------------------------------- @@ -217,10 +262,10 @@ bind [Modifiers+]keycode command *Examples*: -------------------------------- # Fullscreen -bind Mod1+f f +bindsym Mod1+f f # Restart -bind Mod1+Shift+r restart +bindsym Mod1+Shift+r restart # Notebook-specific hotkeys bind 214 exec /home/michael/toggle_beamer.sh @@ -238,14 +283,20 @@ umlauts or special characters 'and' having some comfortably reachable key bindings. For example, when typing, capslock+1 or capslock+2 for switching workspaces is totally convenient. Try it :-). +[[floating_modifier]] + === The floating modifier To move floating windows with your mouse, you can either grab their titlebar or configure the so called floating modifier which you can then press and -click anywhere in the window itself. The most common setup is to configure -it as the same one you use for managing windows (Mod1 for example). Afterwards, -you can press Mod1, click into a window using your left mouse button and drag -it to the position you want it at. +click anywhere in the window itself to move it. The most common setup is to +use the same key you use for managing windows (Mod1 for example). Then +you can press Mod1, click into a window using your left mouse button, and drag +it to the position you want. + +When holding the floating modifier, you can resize a floating window by +pressing the right mouse button on it and moving around while holding it. If +you hold the shift button as well, the resize will be proportional. *Syntax*: -------------------------------- @@ -259,8 +310,7 @@ floating_modifier Mod1 === Layout mode for new containers -This option is only available when using the new lexer/parser (pass +-l+ to i3 -when starting). It determines in which mode new containers will start. See also +This option determines in which mode new containers will start. See also <>. *Syntax*: @@ -276,8 +326,7 @@ new_container tabbed === Border style for new windows -This option is only available when using the new lexer/parser (pass +-l+ to i3 -when starting). It determines which border new windows will have. +This option determines which border style new windows will have. *Syntax*: --------------------------------------------- @@ -291,10 +340,10 @@ new_window bp === Variables -As you learned in the previous section about keyboard bindings, you will have +As you learned in the section about keyboard bindings, you will have to configure lots of bindings containing modifier keys. If you want to save -yourself some typing and have the possibility to change the modifier you want -to use later, variables can be handy. +yourself some typing and be able to change the modifier you use later, +variables can be handy. *Syntax*: -------------- @@ -307,20 +356,23 @@ set $m Mod1 bindsym $m+Shift+r restart ------------------------ -Variables are directly replaced in the file when parsing, there is no fancy +Variables are directly replaced in the file when parsing. There is no fancy handling and there are absolutely no plans to change this. If you need a more -dynamic configuration, you should create a little script, like when configuring -wmii. +dynamic configuration you should create a little script which generates a +configuration file and run it before starting i3 (for example in your ++.xsession+ file). === Automatically putting clients on specific workspaces -It is recommended that you match on window classes whereever possible because -some applications first create their window and then care about setting the -correct title. Firefox with Vimperator comes to mind, as the window starts up -being named Firefox and only when Vimperator is loaded, the title changes. As -i3 will get the title as soon as the application maps the window (mapping means -actually displaying it on the screen), you’d need to have to match on Firefox -in this case. +[[assign_workspace]] + +It is recommended that you match on window classes wherever possible because +some applications first create their window, and then worry about setting the +correct title. Firefox with Vimperator comes to mind. The window starts up +being named Firefox, and only when Vimperator is loaded does the title change. +As i3 will get the title as soon as the application maps the window (mapping +means actually displaying it on the screen), you’d need to have to match on +'Firefox' in this case. You can prefix or suffix workspaces with a `~` to specify that matching clients should be put into floating mode. If you specify only a `~`, the client will @@ -341,11 +393,14 @@ assign "gecko" → ~4 assign "xv/MPlayer" → ~ ---------------------- -=== Automatically starting applications on startup +Note that the arrow is not required, it just looks good :-). If you decide to +use it, it has to be a UTF-8 encoded arrow, not "->" or something like that. + +=== Automatically starting applications on i3 startup By using the +exec+ keyword outside a keybinding, you can configure which -commands will be performed by i3 on the first start (not when reloading inplace -however). The commands will be run in order. +commands will be performed by i3 on initial startup (not when restarting i3 +in-place however). These commands will be run in order. *Syntax*: ------------ @@ -357,35 +412,29 @@ exec command exec sudo i3status | dzen2 -dock -------------------------------- +[[workspace_screen]] + === Automatically putting workspaces on specific screens -If you use the assigning of clients to workspaces and start some clients -automatically, it might be handy to put the workspaces on specific screens. -Also, the assignment of workspaces to screens will determine the workspace -which i3 uses for a new screen when adding screens or when starting (e.g., by -default it will use 1 for the first screen, 2 for the second screen and so on). +If you assign clients to workspaces, it might be handy to put the +workspaces on specific screens. Also, the assignment of workspaces to screens +will determine which workspace i3 uses for a new screen when adding screens +or when starting (e.g., by default it will use 1 for the first screen, 2 for +the second screen and so on). *Syntax*: ---------------------------------- -workspace screen +workspace output ---------------------------------- -Screen can be either a number (starting at 0 for the first screen) or a -position. When using numbers, it is not guaranteed that your screens always -get the same number. Though, unless you upgrade your X server or drivers, the -order usually stays the same. When using positions, you have to specify the -exact pixel where the screen *starts*, not a pixel which is contained by the -screen. Thus, if your first screen has the dimensions 1280x800, you can match -the second screen right of it by specifying 1280. You cannot use 1281. +The 'output' is the name of the RandR output you attach your screen to. On a +laptop, you might have VGA1 and LVDS1 as output names. You can see the +available outputs by running +xrandr --current+. *Examples*: --------------------------- -workspace 1 screen 0 -workspace 5 screen 1 - -workspace 1 screen 1280 -workspace 2 screen x800 -workspace 3 screen 1280x800 +workspace 1 output LVDS1 +workspace 5 output VGA1 --------------------------- === Named workspaces @@ -396,10 +445,10 @@ them names (of course UTF-8 is supported): *Syntax*: --------------------------------------- workspace -workspace screen name +workspace output name --------------------------------------- -For more details about the screen-part of this command, see above. +For more details about the 'output' part of this command, see above. *Examples*: -------------------------- @@ -436,7 +485,7 @@ bar.unfocused:: bar.urgent:: A workspace which has at least one client with an activated urgency hint. -Colors are in HTML hex format, see below. +Colors are in HTML hex format (#rrggbb), see the following example: *Examples*: -------------------------------------- @@ -444,19 +493,64 @@ Colors are in HTML hex format, see below. client.focused #2F343A #900000 #FFFFFF -------------------------------------- +Note that for the window decorations, the color around the child window is the +background color, and the border color is only the two thin lines at the top of +the window. + === Interprocess communication -i3 uses unix sockets to provide an IPC interface. At the moment, this interface -is only useful for sending commands. To enable it, you have to configure a path -where the unix socket will be stored. The default path is +/tmp/i3-ipc.sock+. +i3 uses unix sockets to provide an IPC interface. This allows third-party +programs to get information from i3, such as the current workspaces +(to display a workspace bar), and to control i3. + +To enable it, you have to configure a path where the unix socket will be +stored. The default path is +~/.i3/ipc.sock+. *Examples*: ---------------------------- -ipc-socket /tmp/i3-ipc.sock +ipc-socket ~/.i3/ipc.sock ---------------------------- -You can then use the i3-msg command to perform any command listed in the next -section. +You can then use the +i3-msg+ application to perform any command listed in +the next section. + +=== Disable focus follows mouse + +If you have a setup where your mouse usually is in your way (like a touchpad +on your laptop which you do not want to disable completely), you might want +to disable 'focus follows mouse' and control focus only by using your keyboard. +The mouse will still be useful inside the currently active window (for example +to click on links in your browser window). + +*Syntax*: +---------------------------- +focus_follows_mouse +---------------------------- + +*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 +---------------------- + +*Examples*: +---------------- +workspace_bar no +---------------- == List of commands @@ -464,7 +558,8 @@ section. To change the layout of the current container to stacking, use +s+, for default use +d+ and for tabbed, use +T+. To make the current client (!) fullscreen, -use +f+, to make it floating (or tiling again) use +t+: +use +f+, to make it span all outputs, use +fg+, to make it floating (or +tiling again) use +t+: *Examples*: -------------- @@ -475,17 +570,20 @@ bindsym Mod1+w T # Toggle fullscreen bindsym Mod1+f f +# Toggle global fullscreen +bindsym Mod1+Shift+f fg + # Toggle floating/tiling bindsym Mod1+t t -------------- -=== Focussing/Moving/Snapping clients/containers/screens +=== Focusing/Moving/Snapping clients/containers/screens To change the focus, use one of the +h+, +j+, +k+ and +l+ commands, meaning -respectively left, down, up, right. To focus a container, prefix it with +wc+, -to focus a screen, prefix it with +ws+. +left, down, up, right (respectively). To focus a container, prefix it with ++wc+. To focus a screen, prefix it with +ws+. -The same principle applies for moving and snapping, just prefix the command +The same principle applies for moving and snapping: just prefix the command with +m+ when moving and with +s+ when snapping: *Examples*: @@ -519,9 +617,9 @@ To change to a specific workspace, the command is just the number of the workspace, e.g. +1+ or +3+. To move the current client to a specific workspace, prefix the number with an +m+. -Furthermore, you can switch to the next and previous workspace with the -commands +nw+ and +pw+, which is handy for example if you have workspace -1, 3, 4 and 9 and you want to cycle through them with a single key combination. +You can also switch to the next and previous workspace with the commands +nw+ +and +pw+, which is handy, for example, if you have workspace 1, 3, 4 and 9 and +you want to cycle through them with a single key combination. *Examples*: ------------------------- @@ -542,8 +640,7 @@ bindsym Mod1+p pw === Resizing columns/rows If you want to resize columns/rows using your keyboard, you can use the -+resize+ command, I recommend using it a +mode+ (you need to use the new -lexer/parser for that, so pass +-l+ to i3 when starting): ++resize+ command, I recommend using it inside a so called +mode+: .Example: Configuration file, defining a mode for resizing ---------------------------------------------------------------------- @@ -568,15 +665,18 @@ mode "resize" { bind 36 mode default } + +# Enter resize mode +bindsym Mod1+r mode resize ---------------------------------------------------------------------- === Jumping to specific windows -Especially when in a multi-monitor environment, you want to quickly jump to a specific -window, for example while currently working on workspace 3 you may want to jump to -your mailclient to mail your boss that you’ve achieved some important goal. Instead -of figuring out how to navigate to your mailclient, it would be more convenient to -have a shortcut. +Often when in a multi-monitor environment, you want to quickly jump to a +specific window. For example, while working on workspace 3 you may want to +jump to your mail client to email your boss that you’ve achieved some +important goal. Instead of figuring out how to navigate to your mailclient, +it would be more convenient to have a shortcut. *Syntax*: ---------------------------------------------------- @@ -584,8 +684,9 @@ jump ["]window class[/window title]["] jump workspace [ column row ] ---------------------------------------------------- -You can either use the same matching algorithm as in the +assign+ command (see above) -or you can specify the position of the client if you always use the same layout. +You can either use the same matching algorithm as in the +assign+ command +(see above) or you can specify the position of the client if you always use +the same layout. *Examples*: -------------------------------------- @@ -595,19 +696,19 @@ bindsym Mod1+a jump "urxvt/VIM" === VIM-like marks (mark/goto) +[[vim_like_marks]] + This feature is like the jump feature: It allows you to directly jump to a specific window (this means switching to the appropriate workspace and setting focus to the windows). However, you can directly mark a specific window with -an arbitrary label and use it afterwards, that is, you do not need to ensure -that your windows have unique classes or titles and you do not need to change -your configuration file. +an arbitrary label and use it afterwards. You do not need to ensure that your +windows have unique classes or titles, and you do not need to change your +configuration file. As the command needs to include the label with which you want to mark the -window, you cannot simply bind it to a key (or, you could bind it to a key and -only use the set of labels for which you created bindings). +i3-input+ is a -tool created for this purpose: It lets you input a command and sends the -command to i3. It can also prefix this command and display a custom prompt for -the input dialog. +window, you cannot simply bind it to a key. +i3-input+ is a tool created +for this purpose: It lets you input a command and sends the command to i3. It +can also prefix this command and display a custom prompt for the input dialog. *Syntax*: ----------------- @@ -624,18 +725,21 @@ bindsym Mod1+m exec i3-input -p 'mark ' -l 1 -P 'Mark: ' bindsym Mod1+g exec i3-input -p 'goto ' -l 1 -P 'Goto: ' --------------------------------------- +Alternatively, if you do not want to mess with +i3-input+, you could create +seperate bindings for a specific set of labels and then only use those labels. + === Traveling the focus stack -This mechanism can be thought of as the opposite of the +jump+ command. It travels -the focus stack and jumps to the window you focused before. +This mechanism can be thought of as the opposite of the +jump+ command. +It travels the focus stack and jumps to the window which had focus previously. *Syntax*: -------------- -focus [number] | floating | tilling | ft +focus [number] | floating | tiling | ft -------------- -Where +number+ by default is 1 meaning that the next client in the focus stack will -be selected. +Where +number+ by default is 1 meaning that the next client in the focus stack +will be selected. The special values have the following meaning: @@ -644,14 +748,14 @@ floating:: tiling:: The next tiling window is selected. ft:: - If the current window is floating, the next tiling window will be selected - and vice-versa. + If the current window is floating, the next tiling window will be + selected; and vice-versa. === Changing border style To change the border of the current client, you can use +bn+ to use the normal border (including window title), +bp+ to use a 1-pixel border (no window title) -and +bb+ to make the client borderless. There also is +bt+ which will toggle +and +bb+ to make the client borderless. There is also +bt+ which will toggle the different border styles. *Examples*: @@ -665,12 +769,12 @@ bindsym Mod1+u bb === Changing the stack-limit of a container -If you have a single container with a lot of windows inside (say, more than +If you have a single container with a lot of windows inside it (say, more than 10), the default layout of a stacking container can get a little unhandy. -Depending on your screen’s size, you might end up only using half of the -titlebars of each window in the container. +Depending on your screen’s size, you might end up seeing only half of the +titlebars for each window in the container. -Using the +stack-limit+ command, you can limit the amount of rows or columns +Using the +stack-limit+ command, you can limit the number of rows or columns in a stacking container. i3 will create columns or rows (depending on what you limited) automatically as needed. @@ -696,9 +800,9 @@ You can make i3 reload its configuration file with +reload+. You can also restart i3 inplace with the +restart+ command to get it out of some weird state (if that should ever happen) or to perform an upgrade without having to restart your X session. However, your layout is not preserved at the moment, meaning -that all open windows will be in a single container in default layout. To exit -i3 properly, you can use the +exit+ command, however you don’t need to (e.g., -simply killing your X session is fine aswell). +that all open windows will end up in a single container in default layout +after the restart. To exit i3 properly, you can use the +exit+ command, +however you don’t need to (simply killing your X session is fine as well). *Examples*: ---------------------------- @@ -706,3 +810,174 @@ bindsym Mod1+Shift+r restart bindsym Mod1+Shift+w reload bindsym Mod1+Shift+e exit ---------------------------- + +[[multi_monitor]] + +== Multiple monitors + +As you can see in the goal list on the website, i3 was specifically developed +with support for multiple monitors in mind. This section will explain how to +handle multiple monitors. + +When you have only one monitor, things are simple. You usually start with +workspace 1 on your monitor and open new ones as you need them. + +When you have more than one monitor, each monitor will get an initial +workspace. The first monitor gets 1, the second gets 2 and a possible third +would get 3. When you switch to a workspace on a different monitor, i3 will +switch to that monitor and then switch to the workspace. This way, you don’t +need shortcuts to switch to a specific monitor, and you don’t need to remember +where you put which workspace. New workspaces will be opened on the currently +active monitor. It is not possible to have a monitor without a workspace. + +The idea of making workspaces global is based on the observation that most +users have a very limited set of workspaces on their additional monitors. +They are often used for a specific task (browser, shell) or for monitoring +several things (mail, IRC, syslog, …). Thus, using one workspace on one monitor +and "the rest" on the other monitors often makes sense. However, as you can +create an unlimited number of workspaces in i3 and tie them to specific +screens, you can have the "traditional" approach of having X workspaces per +screen by changing your configuration (using modes, for example). + +=== Configuring your monitors + +To help you get going if you have never used multiple monitors before, here is +a short overview of the xrandr options which will probably be of interest to +you. It is always useful to get an overview of the current screen configuration. +Just run "xrandr" and you will get an output like the following: +------------------------------------------------------------------------------- +$ xrandr +Screen 0: minimum 320 x 200, current 1280 x 800, maximum 8192 x 8192 +VGA1 disconnected (normal left inverted right x axis y axis) +LVDS1 connected 1280x800+0+0 (normal left inverted right x axis y axis) 261mm x 163mm + 1280x800 60.0*+ 50.0 + 1024x768 85.0 75.0 70.1 60.0 + 832x624 74.6 + 800x600 85.1 72.2 75.0 60.3 56.2 + 640x480 85.0 72.8 75.0 59.9 + 720x400 85.0 + 640x400 85.1 + 640x350 85.1 +-------------------------------------------------------------------------------------- + +Several things are important here: You can see that +LVDS1+ is connected (of +course, it is the internal flat panel) but +VGA1+ is not. If you have a monitor +connected to one of the ports but xrandr still says "disconnected", you should +check your cable, monitor or graphics driver. + +The maximum resolution you can see at the end of the first line is the maximum +combined resolution of your monitors. By default, it is usually too low and has +to be increased by editing +/etc/X11/xorg.conf+. + +So, say you connected VGA1 and want to use it as an additional screen: +------------------------------------------- +xrandr --output VGA1 --auto --left-of LVDS1 +------------------------------------------- +This command makes xrandr try to find the native resolution of the device +connected to +VGA1+ and configures it to the left of your internal flat panel. +When running "xrandr" again, the output looks like this: +------------------------------------------------------------------------------- +$ xrandr +Screen 0: minimum 320 x 200, current 2560 x 1024, maximum 8192 x 8192 +VGA1 connected 1280x1024+0+0 (normal left inverted right x axis y axis) 338mm x 270mm + 1280x1024 60.0*+ 75.0 + 1280x960 60.0 + 1152x864 75.0 + 1024x768 75.1 70.1 60.0 + 832x624 74.6 + 800x600 72.2 75.0 60.3 56.2 + 640x480 72.8 75.0 66.7 60.0 + 720x400 70.1 +LVDS1 connected 1280x800+1280+0 (normal left inverted right x axis y axis) 261mm x 163mm + 1280x800 60.0*+ 50.0 + 1024x768 85.0 75.0 70.1 60.0 + 832x624 74.6 + 800x600 85.1 72.2 75.0 60.3 56.2 + 640x480 85.0 72.8 75.0 59.9 + 720x400 85.0 + 640x400 85.1 + 640x350 85.1 +------------------------------------------------------------------------------- +Please note that i3 uses exactly the same API as xrandr does, so it will see +only what you can see in xrandr. + +See also <> 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 + <>. +2. If you want some applications to generally open on the bigger screen + (MPlayer, Firefox, …), you can assign them to a specific workspace, see + <>. +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 <>. + +== i3 and the rest of your software world + +=== Displaying a status line + +A very common thing amongst users of exotic window managers is a status line at +some corner of the screen. It is an often superior replacement to the widget +approach you have in the task bar of a traditional desktop environment. + +If you don’t already have your favorite way of generating such a status line +(self-written scripts, conky, …), then i3status is the recommended tool for +this task. It was written in C with the goal of using as few syscalls as +possible to reduce the time your CPU is woken up from sleep states. + +Regardless of which application you use to generate the status line, you +want to make sure that the application does one of the following things: + +1. Register as a dock window using EWMH hints. This will make i3 position the + window above the workspace bar but below every other client. This is the + recommended way, but in case of dzen2, for example, you need to check out + the source of dzen2 from subversion, as the -dock option is not present + in the released versions. +2. Overlay the internal workspace bar. This method will not waste any space + on the workspace bar, however, it is rather hackish. Just configure + the output window to be over the workspace bar (say -x 200 and -y 780 if + your screen is 800 px height). + +The planned solution for this problem is to make the workspace bar optional +and switch to a third party application completely (dzen2 for example) +which will then contain the workspace bar. + +=== Giving presentations (multi-monitor) + +When giving a presentation, you typically want the audience to see what you see +on your screen and then go through a series of slides (if the presentation is +simple). For more complex presentations, you might want to have some notes +which only you can see on your screen, while the audience can only see the +slides. + +[[presentations]] +==== Case 1: everybody gets the same output +This is the simple case. You connect your computer to the video projector, +turn on both (computer and video projector) and configure your X server to +clone the internal flat panel of your computer to the video output: +----------------------------------------------------- +xrandr --output VGA1 --mode 1024x768 --same-as LVDS1 +----------------------------------------------------- +i3 will then use the lowest common subset of screen resolutions, the rest of +your screen will be left untouched (it will show the X background). So, in +our example, this would be 1024x768 (my notebook has 1280x800). + +==== Case 2: you can see more than your audience +This case is a bit harder. First of all, you should configure the VGA output +somewhere near your internal flat panel, say right of it: +----------------------------------------------------- +xrandr --output VGA1 --mode 1024x768 --right-of LVDS1 +----------------------------------------------------- +Now, i3 will put a new workspace (depending on your settings) on the new screen +and you are in multi-monitor mode (see <>). + +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). diff --git a/i3-input/Makefile b/i3-input/Makefile index c8881654..74f3f8da 100644 --- a/i3-input/Makefile +++ b/i3-input/Makefile @@ -18,8 +18,8 @@ all: ${FILES} install: all echo "INSTALL" - $(INSTALL) -d -m 0755 $(DESTDIR)/usr/bin - $(INSTALL) -m 0755 i3-input $(DESTDIR)/usr/bin/ + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -m 0755 i3-input $(DESTDIR)$(PREFIX)/bin/ clean: rm -f *.o diff --git a/i3-input/i3-input.h b/i3-input/i3-input.h index 6c982bc5..8d8b467f 100644 --- a/i3-input/i3-input.h +++ b/i3-input/i3-input.h @@ -8,7 +8,7 @@ char *convert_ucs_to_utf8(char *input); char *convert_utf8_to_ucs2(char *input, int *real_strlen); uint32_t get_colorpixel(xcb_connection_t *conn, char *hex); -uint32_t get_mode_switch_mask(xcb_connection_t *conn); +uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode); int connect_ipc(char *socket_path); void ipc_send_message(int sockfd, uint32_t message_size, uint32_t message_type, uint8_t *payload); diff --git a/i3-input/main.c b/i3-input/main.c index 8cfc1e84..2a3f02fd 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -37,6 +38,7 @@ static int sockfd; static xcb_key_symbols_t *symbols; static int modeswitchmask; +static int numlockmask; static bool modeswitch_active = false; static xcb_window_t win; static xcb_pixmap_t pixmap; @@ -50,6 +52,21 @@ static char *prompt; static int prompt_len; static int limit; +/* + * This function resolves ~ in pathnames (and more, see glob(3)). + * + */ +static char *glob_path(const char *path) { + static glob_t globbuf; + if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0) + errx(EXIT_FAILURE, "glob() failed"); + char *result = strdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path); + if (result == NULL) + err(EXIT_FAILURE, "malloc() failed"); + globfree(&globbuf); + return result; +} + /* * Concats the glyphs (either UCS-2 or UTF-8) to a single string, suitable for * rendering it (UCS-2) or sending it to i3 (UTF-8). @@ -119,6 +136,9 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t static int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_release_event_t *event) { printf("releasing %d, state raw = %d\n", event->detail, event->state); + /* fix state */ + event->state &= ~numlockmask; + xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state); if (sym == XK_Mode_switch) { printf("Mode switch disabled\n"); @@ -163,6 +183,11 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press if (modeswitch_active) event->state |= modeswitchmask; + /* Apparantly, after activating numlock once, the numlock modifier + * stays turned on (use xev(1) to verify). So, to resolve useful + * keysyms, we remove the numlock flag from the event state */ + event->state &= ~numlockmask; + xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state); if (sym == XK_Mode_switch) { printf("Mode switch enabled\n"); @@ -232,7 +257,7 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press } int main(int argc, char *argv[]) { - char *socket_path = "/tmp/i3-ipc.sock"; + char *socket_path = glob_path("~/.i3/ipc.sock"); char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; int o, option_index = 0; @@ -251,7 +276,7 @@ int main(int argc, char *argv[]) { while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { switch (o) { case 's': - socket_path = strdup(optarg); + socket_path = glob_path(optarg); break; case 'v': printf("i3-input " I3_VERSION); @@ -290,7 +315,8 @@ int main(int argc, char *argv[]) { xcb_event_set_key_release_handler(&evenths, handle_key_release, NULL); xcb_event_set_expose_handler(&evenths, handle_expose, NULL); - modeswitchmask = get_mode_switch_mask(conn); + modeswitchmask = get_mod_mask(conn, XK_Mode_switch); + numlockmask = get_mod_mask(conn, XK_Num_Lock); symbols = xcb_key_symbols_alloc(conn); uint32_t font_id = get_font_id(conn, pattern, &font_height); @@ -306,11 +332,33 @@ int main(int argc, char *argv[]) { xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font_height + 8); xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); + /* Set input focus (we have override_redirect=1, so the wm will not do + * this for us) */ + xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME); + /* Create graphics context */ xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id); /* Grab the keyboard to get all input */ - xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); + xcb_flush(conn); + + /* Try (repeatedly, if necessary) to grab the keyboard. We might not + * get the keyboard at the first attempt because of the keybinding + * still being active when started via a wm’s keybinding. */ + xcb_grab_keyboard_cookie_t cookie; + xcb_grab_keyboard_reply_t *reply = NULL; + + int count = 0; + while ((reply == NULL || reply->status != XCB_GRAB_STATUS_SUCCESS) && (count++ < 500)) { + cookie = xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); + reply = xcb_grab_keyboard_reply(conn, cookie, NULL); + usleep(1000); + } + + if (reply->status != XCB_GRAB_STATUS_SUCCESS) { + fprintf(stderr, "Could not grab keyboard, status = %d\n", reply->status); + exit(-1); + } xcb_flush(conn); diff --git a/i3-input/xcb.c b/i3-input/xcb.c index 71189a97..661d4863 100644 --- a/i3-input/xcb.c +++ b/i3-input/xcb.c @@ -53,12 +53,12 @@ uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) { * keycode). * */ -uint32_t get_mode_switch_mask(xcb_connection_t *conn) { +uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode) { xcb_key_symbols_t *symbols = xcb_key_symbols_alloc(conn); xcb_get_modifier_mapping_reply_t *modmap_r; xcb_keycode_t *modmap, kc; - xcb_keycode_t *modeswitchcodes = xcb_key_symbols_get_keycode(symbols, XK_Mode_switch); + xcb_keycode_t *modeswitchcodes = xcb_key_symbols_get_keycode(symbols, keycode); if (modeswitchcodes == NULL) return 0; @@ -66,7 +66,7 @@ uint32_t get_mode_switch_mask(xcb_connection_t *conn) { modmap = xcb_get_modifier_mapping_keycodes(modmap_r); for (int i = 0; i < 8; i++) - for(int j = 0; j < modmap_r->keycodes_per_modifier; j++) { + for (int j = 0; j < modmap_r->keycodes_per_modifier; j++) { kc = modmap[i * modmap_r->keycodes_per_modifier + j]; for (xcb_keycode_t *ktest = modeswitchcodes; *ktest; ktest++) { if (*ktest != kc) diff --git a/i3-msg/Makefile b/i3-msg/Makefile index a5e15b6e..d75d807c 100644 --- a/i3-msg/Makefile +++ b/i3-msg/Makefile @@ -3,6 +3,8 @@ TOPDIR=.. include $(TOPDIR)/common.mk +CFLAGS += -I$(TOPDIR)/include + # Depend on the object files of all source-files in src/*.c and on all header files FILES=$(patsubst %.c,%.o,$(wildcard *.c)) HEADERS=$(wildcard *.h) @@ -18,8 +20,8 @@ all: ${FILES} install: all echo "INSTALL" - $(INSTALL) -d -m 0755 $(DESTDIR)/usr/bin - $(INSTALL) -m 0755 i3-msg $(DESTDIR)/usr/bin/ + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -m 0755 i3-msg $(DESTDIR)$(PREFIX)/bin/ clean: rm -f *.o diff --git a/i3-msg/main.c b/i3-msg/main.c index 197cceb6..b22d550e 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -16,6 +16,7 @@ */ #include #include +#include #include #include #include @@ -26,6 +27,24 @@ #include #include #include +#include + +#include + +/* + * This function resolves ~ in pathnames (and more, see glob(3)). + * + */ +static char *glob_path(const char *path) { + static glob_t globbuf; + if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0) + errx(EXIT_FAILURE, "glob() failed"); + char *result = strdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path); + if (result == NULL) + err(EXIT_FAILURE, "malloc() failed"); + globfree(&globbuf); + return result; +} /* * Formats a message (payload) of the given size and type and sends it to i3 via @@ -34,12 +53,12 @@ */ static void ipc_send_message(int sockfd, uint32_t message_size, uint32_t message_type, uint8_t *payload) { - int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) + sizeof(uint32_t) + message_size; + int buffer_size = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t) + message_size; char msg[buffer_size]; char *walk = msg; - strcpy(walk, "i3-ipc"); - walk += strlen("i3-ipc"); + strcpy(walk, I3_IPC_MAGIC); + walk += strlen(I3_IPC_MAGIC); memcpy(walk, &message_size, sizeof(uint32_t)); walk += sizeof(uint32_t); memcpy(walk, &message_type, sizeof(uint32_t)); @@ -58,26 +77,84 @@ static void ipc_send_message(int sockfd, uint32_t message_size, } } +static void ipc_recv_message(int sockfd, uint32_t message_type, + uint32_t *reply_length, uint8_t **reply) { + /* Read the message header first */ + uint32_t to_read = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t); + char msg[to_read]; + char *walk = msg; + + uint32_t read_bytes = 0; + while (read_bytes < to_read) { + int n = read(sockfd, msg + read_bytes, to_read); + if (n == -1) + err(EXIT_FAILURE, "read() failed"); + if (n == 0) + errx(EXIT_FAILURE, "received EOF instead of reply"); + + read_bytes += n; + to_read -= n; + } + + if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) + errx(EXIT_FAILURE, "invalid magic in reply"); + + walk += strlen(I3_IPC_MAGIC); + *reply_length = *((uint32_t*)walk); + walk += sizeof(uint32_t); + if (*((uint32_t*)walk) != message_type) + errx(EXIT_FAILURE, "unexpected reply type (got %d, expected %d)", *((uint32_t*)walk), message_type); + walk += sizeof(uint32_t); + + *reply = malloc(*reply_length); + if ((*reply) == NULL) + err(EXIT_FAILURE, "malloc() failed"); + + to_read = *reply_length; + read_bytes = 0; + while (read_bytes < to_read) { + int n = read(sockfd, *reply + read_bytes, to_read); + if (n == -1) + err(EXIT_FAILURE, "read() failed"); + + read_bytes += n; + to_read -= n; + } +} + int main(int argc, char *argv[]) { - char *socket_path = "/tmp/i3-ipc.sock"; + char *socket_path = glob_path("~/.i3/ipc.sock"); int o, option_index = 0; + int message_type = I3_IPC_MESSAGE_TYPE_COMMAND; + char *payload = ""; + bool quiet = false; static struct option long_options[] = { {"socket", required_argument, 0, 's'}, {"type", required_argument, 0, 't'}, {"version", no_argument, 0, 'v'}, + {"quiet", no_argument, 0, 'q'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; - char *options_string = "s:t:vh"; + char *options_string = "s:t:vhq"; while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { if (o == 's') { - socket_path = strdup(optarg); - break; + socket_path = glob_path(optarg); } else if (o == 't') { - printf("currently only commands are implemented\n"); + if (strcasecmp(optarg, "command") == 0) + message_type = I3_IPC_MESSAGE_TYPE_COMMAND; + else if (strcasecmp(optarg, "get_workspaces") == 0) + message_type = I3_IPC_MESSAGE_TYPE_GET_WORKSPACES; + else { + printf("Unknown message type\n"); + printf("Known types: command, get_workspaces\n"); + exit(EXIT_FAILURE); + } + } else if (o == 'q') { + quiet = true; } else if (o == 'v') { printf("i3-msg " I3_VERSION); return 0; @@ -88,11 +165,8 @@ int main(int argc, char *argv[]) { } } - if (optind >= argc) { - fprintf(stderr, "Error: missing message\n"); - fprintf(stderr, "i3-msg [-s ] [-t ] \n"); - return 1; - } + if (optind < argc) + payload = argv[optind]; int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); if (sockfd == -1) @@ -105,7 +179,16 @@ int main(int argc, char *argv[]) { if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) err(EXIT_FAILURE, "Could not connect to i3"); - ipc_send_message(sockfd, strlen(argv[optind]), 0, (uint8_t*)argv[optind]); + ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t*)payload); + + if (quiet) + return 0; + + uint32_t reply_length; + uint8_t *reply; + ipc_recv_message(sockfd, message_type, &reply_length, &reply); + printf("%.*s", reply_length, reply); + free(reply); close(sockfd); diff --git a/i3-wsbar b/i3-wsbar new file mode 100755 index 00000000..b1a50c40 --- /dev/null +++ b/i3-wsbar @@ -0,0 +1,244 @@ +#!/usr/bin/env perl +# vim:ts=4:sw=4:expandtab:ft=perl +# © 2010 Michael Stapelberg, see LICENSE for license information + +use strict; +use warnings; +use Getopt::Long; +use Pod::Usage; +use IPC::Run qw(start pump); +use AnyEvent::I3; +use AnyEvent; +use v5.10; + +my $stdin; +my $i3 = i3; +my ($workspaces, $outputs) = ([], {}); +my $last_line = ""; + +my $command = ""; +my $input_on = ""; +my $output_on = ""; +my $show_all = 0; + +my $result = GetOptions( + 'command=s' => \$command, + 'input-on=s' => \$input_on, + 'output-on=s' => \$output_on, + 'show-all' => \$show_all, + 'help' => sub { pod2usage(1); exit 0 }, +); + +if ($command eq '') { + say "i3-wsbar is only useful in combination with dzen2."; + say "Please specify -c (command)"; + exit 1; +} + +my @input_on = split(/,/, $input_on); +my @output_on = split(/,/, $output_on); + +# Disable buffering +$| = 1; + +# Wait a short amount of time and try to connect to i3 again +sub reconnect { + my $timer; + my $c = sub { + $timer = AnyEvent->timer( + after => 0.01, + cb => sub { $i3->connect->cb(\&connected) } + ); + }; + $c->(); +} + +# Connection attempt succeeded or failed +sub connected { + my ($cv) = @_; + + if (!$cv->recv) { + reconnect(); + return; + } + + $i3->subscribe({ + workspace => \&ws_change, + output => \&output_change, + _error => sub { reconnect() } + }); + ws_change(); + output_change(); +} + +# Called when a ws changes +sub ws_change { + # Request the current workspaces and update the output afterwards + $i3->get_workspaces->cb( + sub { + my ($cv) = @_; + $workspaces = $cv->recv; + update_output(); + }); +} + +# Called when the reply to the GET_OUTPUTS message arrives +# Compares old outputs with new outputs and starts/kills +# $command for each output (if specified) +sub got_outputs { + my $reply = shift->recv; + my %old = %{$outputs}; + my %new = map { ($_->{name}, $_) } grep { $_->{active} } @{$reply}; + + # If no command was given, we do not need to compare outputs + if ($command eq '') { + update_output(); + return; + } + + # Handle new outputs + for my $name (keys %new) { + next if @output_on and !($name ~~ @output_on); + + if (defined($old{$name})) { + # Check if the mode changed (by reversing the hashes so + # that we can check for equality using the smartmatch op) + my %oldrect = reverse %{$old{$name}->{rect}}; + my %newrect = reverse %{$new{$name}->{rect}}; + next if (%oldrect ~~ %newrect); + + # On mode changes, we re-start the command + $outputs->{$name}->{cmd}->finish; + delete $outputs->{$name}; + } + + my $x = $new{$name}->{rect}->{x}; + my $launch = $command; + $launch =~ s/([^%])%x/$1$x/g; + $launch =~ s/%%x/%x/g; + + $new{$name}->{cmd_input} = ''; + my @cmd = ('/bin/sh', '-c', $launch); + $new{$name}->{cmd} = start \@cmd, \$new{$name}->{cmd_input}; + $outputs->{$name} = $new{$name}; + } + + # Handle old outputs + for my $name (keys %old) { + next if defined($new{$name}); + + $outputs->{$name}->{cmd}->finish; + delete $outputs->{$name}; + } + + update_output(); +} + +sub output_change { + $i3->get_outputs->cb(\&got_outputs) +} + +sub update_output { + my $dzen_bg = "#111111"; + my $out; + + for my $name (keys %{$outputs}) { + my $width = $outputs->{$name}->{rect}->{width}; + + $out = qq|^pa(;2)|; + for my $ws (@{$workspaces}) { + next if $ws->{output} ne $name and !$show_all; + + my ($bg, $fg) = qw(333333 888888); + ($bg, $fg) = qw(4c7899 ffffff) if $ws->{visible}; + ($bg, $fg) = qw(900000 ffffff) if $ws->{urgent}; + + my $cmd = q|i3-msg "| . $ws->{num} . q|"|; + my $name = $ws->{name}; + + # Begin the clickable area + $out .= qq|^ca(1,$cmd)|; + + # Draw the rest of the bar in the background color, but + # don’t move the "cursor" + $out .= qq|^p(_LOCK_X)^fg(#$bg)^r(${width}x17)^p(_UNLOCK_X)|; + # Draw the name of the workspace without overwriting the + # background color + $out .= qq|^p(+3)^fg(#$fg)^ib(1)$name^ib(0)^p(+5)|; + # Draw the rest of the bar in the normal background color + # without moving the "cursor" + $out .= qq|^p(_LOCK_X)^fg($dzen_bg)^r(${width}x17)^p(_UNLOCK_X)|; + + # End the clickable area + $out .= qq|^ca()|; + + # Move to the next rect, reset Y coordinate + $out .= qq|^p(2)^pa(;2)|; + } + + $out .= qq|^p(_LOCK_X)^fg($dzen_bg)^r(${width}x17)^p(_UNLOCK_X)^fg(white)|; + $out .= qq|^p(+5)|; + $out .= $last_line if (!@input_on or $name ~~ @input_on); + $out .= "\n"; + + $outputs->{$name}->{cmd_input} = $out; + pump $outputs->{$name}->{cmd} while length $outputs->{$name}->{cmd_input}; + } +} + +$i3->connect->cb(\&connected); + +$stdin = AnyEvent->io( + fh => \*STDIN, + poll => 'r', + cb => sub { + chomp (my $line = ); + $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 [options] + +=head1 OPTIONS + +=over 4 + +=item B<--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> + +Specifies on which outputs the contents of stdin should be appended to the +workspace bar. + +Example: + --input-on "LVDS1" + +=item B<--output-on> + +Specifies for which outputs i3-wsbar should start C. + +=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 diff --git a/i3.config b/i3.config index 48091eac..11bd9066 100644 --- a/i3.config +++ b/i3.config @@ -1,11 +1,6 @@ # This configuration uses Mod1 and Mod3. Make sure they are mapped properly using xev(1) # and xmodmap(1). Usually, Mod1 is Alt (Alt_L) and Mod3 is Windows (Super_L) -# Tell i3 about your preferred terminal. You can refer to this as $terminal -# later. It is recommended to set this option to allow i3 to open a terminal -# containing the introduction on first start. -terminal /usr/bin/urxvt - # ISO 10646 = Unicode font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 @@ -112,7 +107,7 @@ bind Mod1+36 exec /usr/bin/urxvt bind Mod1+Shift+24 kill # Mod1+v starts dmenu and launches the selected application -# for now, we don’t have an own launcher +# for now, we don’t have a launcher of our own. bind Mod1+55 exec /usr/bin/dmenu_run # Mod1+Shift+e exits i3 @@ -121,6 +116,10 @@ bind Mod1+Shift+26 exit # Mod1+Shift+r restarts i3 inplace bind Mod1+Shift+27 restart +# The IPC interface allows programs like an external workspace bar +# (i3-wsbar) or i3-msg (can be used to "remote-control" i3) to work. +ipc-socket ~/.i3/ipc.sock + ############################################################# # DELETE THE FOLLOWING LINES TO DISABLE THE WELCOME MESSAGE # ############################################################# diff --git a/i3.welcome b/i3.welcome index 9c9861e9..72717ee7 100644 --- a/i3.welcome +++ b/i3.welcome @@ -1,6 +1,6 @@ 1.) Welcome to i3! -This message provides you with an overview of the default keybindings to use i3. +This message provides an overview of the default keybindings to use i3. Please also make sure to have a look at the man page and the user's guide: http://i3.zekjur.net/docs/userguide.html @@ -8,7 +8,8 @@ http://i3.zekjur.net/docs/userguide.html 2.) Configuration Files /etc/i3/config is the default configuration. It is recommended to copy it and -afterwards edit it to suit your needs (you can especially disable this message): +afterwards edit it to suit your needs (in particular, you may want to disable +this message): cp /etc/i3/config ~/.i3/config @@ -17,7 +18,7 @@ afterwards edit it to suit your needs (you can especially disable this message): The following explanation is related to the QWERTY layout, but as the default configuration uses keycodes instead of keysymbols for binding, you still have -to press the same keys, regardless of your keyboard layout. +to press the same physical keys, regardless of your keyboard layout. The Mod1 key is usually bound to the "Alt" key on your keyboard. @@ -28,15 +29,15 @@ The directional keys are j(left), k(down), l(up) and ;(right). You can also use the arrow keys on your keyboard, if you prefer them. Mod1+ moves the focus to the window in the given direction -Mod1+Shift+ moves the window to the given direction, +Mod1+Shift+ moves the window to the given direction Mod1+ opens the corresponding workspace -Mod1+Shift+ moves a window to the wished workspace +Mod1+Shift+ moves a window to the selected workspace Mod1+h sets the mode of a container to stacking Mod1+e sets the mode back to default Mod1+f toggles fullscreen mode for the current window Mod1+Shift+Space toggles floating mode for the current window Mod1+Shift+q closes a window -Mod1+Shift+r restarts i3 in-place (you will lose your layout, though) +Mod1+Shift+r restarts i3 in-place (at this time, you will lose your layout) Mod1+Shift+e exits i3 If you have any questions, please don't hesitate to ask! diff --git a/include/client.h b/include/client.h index 41bd5db3..45b8f4a7 100644 --- a/include/client.h +++ b/include/client.h @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * (c) 2009 Michael Stapelberg and contributors + * © 2009 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -51,7 +51,13 @@ bool client_matches_class_name(Client *client, char *to_class, char *to_title, * and when moving a fullscreen client to another screen. * */ -void client_enter_fullscreen(xcb_connection_t *conn, Client *client); +void client_enter_fullscreen(xcb_connection_t *conn, Client *client, bool global); + +/** + * Leaves fullscreen mode for the given client. This is called by toggle_fullscreen. + * + */ +void client_leave_fullscreen(xcb_connection_t *conn, Client *client); /** * Leaves fullscreen mode for the current client. This is called by toggle_fullscreen. @@ -68,6 +74,12 @@ void client_leave_fullscreen(xcb_connection_t *conn, Client *client); */ void client_toggle_fullscreen(xcb_connection_t *conn, Client *client); +/** + * Like client_toggle_fullscreen(), but putting it in global fullscreen-mode. + * + */ +void client_toggle_fullscreen_global(xcb_connection_t *conn, Client *client); + /** * Sets the position of the given client in the X stack to the highest (tiling * layer is always on the same position, so this doesn’t matter) below the @@ -118,12 +130,26 @@ void client_map(xcb_connection_t *conn, Client *client); */ void client_mark(xcb_connection_t *conn, Client *client, const char *mark); +/** + * Returns the minimum height of a specific window. The height is calculated + * by using 2 pixels (for the client window itself), possibly padding this to + * comply with the client’s base_height and then adding the decoration height. + * + */ +uint32_t client_min_height(Client *client); + +/** + * See client_min_height. + * + */ +uint32_t client_min_width(Client *client); + /** * Pretty-prints the client’s information into the logfile. * */ #define CLIENT_LOG(client) do { \ - LOG("Window: frame 0x%08x, child 0x%08x\n", client->frame, client->child); \ + DLOG("Window: frame 0x%08x, child 0x%08x\n", client->frame, client->child); \ } while (0) #endif diff --git a/include/config.h b/include/config.h index 32e29cf1..0c790bf2 100644 --- a/include/config.h +++ b/include/config.h @@ -3,12 +3,14 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * - * include/config.h: Contains all structs/variables for - * the configurable part of i3 + * include/config.h: Contains all structs/variables for the configurable + * part of i3 as well as functions handling the configuration file (calling + * the parser (src/cfgparse.y) with the correct path, switching key bindings + * mode). * */ @@ -21,9 +23,23 @@ typedef struct Config Config; extern Config config; -extern bool config_use_lexer; extern SLIST_HEAD(modes_head, Mode) modes; +/** + * Used during the config file lexing/parsing to keep the state of the lexer + * in order to provide useful error messages in yyerror(). + * + */ +struct context { + int line_number; + char *line_copy; + const char *filename; + + /* These are the same as in YYLTYPE */ + int first_column; + int last_column; +}; + /** * Part of the struct Config. It makes sense to group colors for background, * border and text as every element in i3 has them (window decorations, bar). @@ -76,6 +92,18 @@ struct Config { int container_stack_limit; int container_stack_limit_value; + /** By default, focus follows mouse. If the user explicitly wants to + * turn this off (and instead rely only on the keyboard for changing + * focus), we allow him to do this with this relatively special option. + * It is not planned to add any different focus models. */ + bool disable_focus_follows_mouse; + + /** By default, a workspace bar is drawn at the bottom of the screen. + * If you want to have a more fancy bar, it is recommended to replace + * the whole bar by dzen2, for example using the i3-wsbar script which + * comes with i3. Thus, you can turn it off entirely. */ + bool disable_workspace_bar; + const char *default_border; /** The modifier which needs to be pressed in combination with your mouse @@ -96,6 +124,18 @@ struct Config { } bar; }; +/** + * This function resolves ~ in pathnames. + * + */ +char *glob_path(const char *path); + +/** + * Checks if the given path exists by calling stat(). + * + */ +bool path_exists(const char *path); + /** * Reads the configuration from ~/.i3/config or /etc/i3/config if not found. * @@ -105,6 +145,12 @@ struct Config { */ void load_configuration(xcb_connection_t *conn, const char *override_configfile, bool reload); +/** + * Translates keysymbols to keycodes for all bindings which use keysyms. + * + */ +void translate_keysyms(); + /** * Ungrabs all keys, to be called before re-grabbing the keys because of a * mapping_notify event or a configuration file reload @@ -116,7 +162,7 @@ void ungrab_all_keys(xcb_connection_t *conn); * Grab the bound keys (tell X to send us keypress events for those keycodes) * */ -void grab_all_keys(xcb_connection_t *conn); +void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch); /** * Switches the key bindings to the given mode, if the mode exists @@ -124,4 +170,14 @@ void grab_all_keys(xcb_connection_t *conn); */ void switch_mode(xcb_connection_t *conn, const char *new_mode); +/** + * Returns a pointer to the Binding with the specified modifiers and keycode + * or NULL if no such binding exists. + * + */ +Binding *get_binding(uint16_t modifiers, xcb_keycode_t keycode); + +/* prototype for src/cfgparse.y */ +void parse_file(const char *f); + #endif diff --git a/include/container.h b/include/container.h new file mode 100644 index 00000000..78938508 --- /dev/null +++ b/include/container.h @@ -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 diff --git a/include/data.h b/include/data.h index 8e1a80cc..a8b31d3b 100644 --- a/include/data.h +++ b/include/data.h @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -11,6 +11,7 @@ * */ #include +#include #include #include @@ -25,11 +26,12 @@ * * Let’s start from the biggest to the smallest: * - * - An i3Screen is a virtual screen (Xinerama). This can be a single one, - * though two monitors might be connected, if you’re running clone - * mode. There can also be multiple of them. + * - An Output is a physical output on your graphics driver. Outputs which + * are currently in use have (output->active == true). Each output has a + * position and a mode. An output usually corresponds to one connected + * screen (except if you are running multiple screens in clone mode). * - * - Each i3Screen contains Workspaces. The concept is known from various + * - Each Output contains Workspaces. The concept is known from various * other window managers. Basically, a workspace is a specific set of * windows, usually grouped thematically (irc, www, work, …). You can switch * between these. @@ -54,7 +56,7 @@ typedef struct Client Client; typedef struct Binding Binding; typedef struct Workspace Workspace; typedef struct Rect Rect; -typedef struct Screen i3Screen; +typedef struct xoutput Output; /****************************************************************************** * Helper types @@ -75,6 +77,14 @@ enum { /** * Stores a rectangle, for example the size of a window, the child window etc. + * It needs to be packed so that the compiler will not add any padding bytes. + * (it is used in src/ewmh.c for example) + * + * Note that x and y can contain signed values in some cases (for example when + * used for the coordinates of a window, which can be set outside of the + * visible area, but not when specifying the position of a workspace for the + * _NET_WM_WORKAREA hint). Not declaring x/y as int32_t saves us a lot of + * typecasts. * * Note that x and y can contain signed values in some cases (for example when * used for the coordinates of a window, which can be set outside of the @@ -84,9 +94,11 @@ enum { * */ struct Rect { - uint32_t x, y; - uint32_t width, height; -}; + uint32_t x; + uint32_t y; + uint32_t width; + uint32_t height; +} __attribute__((packed)); /** * Defines a position in the table @@ -171,6 +183,9 @@ struct Workspace { /** Number of this workspace, starting from 0 */ int num; + /** Name of the workspace (in UTF-8) */ + char *utf8_name; + /** Name of the workspace (in UCS-2) */ char *name; @@ -200,12 +215,8 @@ struct Workspace { /** Are the floating clients on this workspace currently hidden? */ bool floating_hidden; - /** A specifier on which this workspace would like to be (if - * the screen is available). screen := | */ - char *preferred_screen; - - /** Temporary flag needed for re-querying xinerama screens */ - bool reassigned; + /** The name of the RandR output this screen should be on */ + char *preferred_output; /** True if any client on this workspace has its urgent flag set */ bool urgent; @@ -224,8 +235,8 @@ struct Workspace { * appended) */ TAILQ_HEAD(floating_clients_head, Client) floating_clients; - /** Backpointer to the screen this workspace is on */ - i3Screen *screen; + /** Backpointer to the output this workspace is on */ + Output *output; /** This is a two-dimensional dynamic array of * Container-pointers. I’ve always wanted to be a three-star @@ -492,14 +503,26 @@ struct Container { }; /** - * This is a virtual screen (Xinerama). This can be a single one, though two - * monitors might be connected, if you’re running clone mode. There can also - * be multiple of them. + * An Output is a physical output on your graphics driver. Outputs which + * are currently in use have (output->active == true). Each output has a + * position and a mode. An output usually corresponds to one connected + * screen (except if you are running multiple screens in clone mode). * */ -struct Screen { - /** Virtual screen number */ - int num; +struct xoutput { + /** Output id, so that we can requery the output directly later */ + xcb_randr_output_t id; + /** Name of the output */ + char *name; + + /** Whether the output is currently active (has a CRTC attached with a + * valid mode) */ + bool active; + + /** Internal flags, necessary for querying RandR screens (happens in + * two stages) */ + bool changed; + bool to_be_disabled; /** Current workspace selected on this virtual screen */ Workspace *current_workspace; @@ -515,7 +538,7 @@ struct Screen { * _NET_WM_WINDOW_TYPE_DOCK */ SLIST_HEAD(dock_clients_head, Client) dock_clients; - TAILQ_ENTRY(Screen) screens; + TAILQ_ENTRY(xoutput) outputs; }; #endif diff --git a/include/ewmh.h b/include/ewmh.h new file mode 100644 index 00000000..c73c4a45 --- /dev/null +++ b/include/ewmh.h @@ -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 diff --git a/include/floating.h b/include/floating.h index 5cf3dedd..aa9d9d5f 100644 --- a/include/floating.h +++ b/include/floating.h @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -12,10 +12,19 @@ #define _FLOATING_H /** Callback for dragging */ -typedef void(*callback_t)(Rect*, uint32_t, uint32_t); +typedef void(*callback_t)(xcb_connection_t*, Client*, Rect*, uint32_t, uint32_t, void*); + +/** Macro to create a callback function for dragging */ +#define DRAGGING_CB(name) \ + static void name(xcb_connection_t *conn, Client *client, \ + Rect *old_rect, uint32_t new_x, uint32_t new_y, \ + void *extra) /** On which border was the dragging initiated? */ -typedef enum { BORDER_LEFT, BORDER_RIGHT, BORDER_TOP, BORDER_BOTTOM} border_t; +typedef enum { BORDER_LEFT = (1 << 0), + BORDER_RIGHT = (1 << 1), + BORDER_TOP = (1 << 2), + BORDER_BOTTOM = (1 << 3)} border_t; /** * Enters floating mode for the given client. Correctly takes care of the @@ -56,13 +65,13 @@ void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event); /** - * Called when the user right-clicked on the titlebar of a floating window to - * resize it. + * Called when the user clicked on a floating window while holding the + * floating_modifier and the right mouse button. * Calls the drag_pointer function with the resize_window callback * */ void floating_resize_window(xcb_connection_t *conn, Client *client, - xcb_button_press_event_t *event); + bool proportional, xcb_button_press_event_t *event); /** * Changes focus in the given direction for floating clients. @@ -97,6 +106,7 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace); * */ void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event, - xcb_window_t confine_to, border_t border, callback_t callback); + xcb_window_t confine_to, border_t border, callback_t callback, + void *extra); #endif diff --git a/include/handlers.h b/include/handlers.h index 95194c14..c7cbb322 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * (c) 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -11,13 +11,7 @@ #ifndef _HANDLERS_H #define _HANDLERS_H -/** - * Due to bindings like Mode_switch + , we need to bind some keys in - * XCB_GRAB_MODE_SYNC. Therefore, we just replay all key presses. - * - */ -int handle_key_release(void *ignored, xcb_connection_t *conn, - xcb_key_release_event_t *event); +#include /** * There was a key press. We compare this key code with our bindings table and @@ -74,6 +68,14 @@ int handle_map_request(void *prophs, xcb_connection_t *conn, */ int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_notify_event_t *event); +/** + * Gets triggered upon a RandR screen change event, that is when the user + * changes the screen configuration in any way (mode, position, …) + * + */ +int handle_screen_change(void *prophs, xcb_connection_t *conn, + xcb_generic_event_t *e); + /** * Configure requests are received when the application wants to resize * windows on their own. @@ -92,6 +94,18 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, */ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_notify_event_t *event); +/** + * A destroy notify event is sent when the window is not unmapped, but + * immediately destroyed (for example when starting a window and immediately + * killing the program which started it). + * + * We just pass on the event to the unmap notify handler (by copying the + * important fields in the event data structure). + * + */ +int handle_destroy_notify_event(void *data, xcb_connection_t *conn, + xcb_destroy_notify_event_t *event); + /** * Called when a window changes its title * diff --git a/include/i3.h b/include/i3.h index e34c5da3..bf9d4b81 100644 --- a/include/i3.h +++ b/include/i3.h @@ -21,19 +21,20 @@ #ifndef _I3_H #define _I3_H -#define NUM_ATOMS 18 +#define NUM_ATOMS 21 extern xcb_connection_t *global_conn; extern xcb_key_symbols_t *keysyms; extern char **start_argv; extern Display *xkbdpy; +extern int xkb_current_group; extern TAILQ_HEAD(bindings_head, Binding) *bindings; extern TAILQ_HEAD(autostarts_head, Autostart) autostarts; extern TAILQ_HEAD(assignments_head, Assignment) assignments; extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins; extern xcb_event_handlers_t evenths; -extern int num_screens; extern uint8_t root_depth; +extern bool xkb_supported; extern xcb_atom_t atoms[NUM_ATOMS]; extern xcb_window_t root; diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 40e01158..1ea39182 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -15,10 +15,53 @@ #ifndef _I3_IPC_H #define _I3_IPC_H +/* + * Messages from clients to i3 + * + */ + /** Never change this, only on major IPC breakage (don’t do that) */ #define I3_IPC_MAGIC "i3-ipc" /** The payload of the message will be interpreted as a command */ -#define I3_IPC_MESSAGE_TYPE_COMMAND 0 +#define I3_IPC_MESSAGE_TYPE_COMMAND 0 + +/** Requests the current workspaces from i3 */ +#define I3_IPC_MESSAGE_TYPE_GET_WORKSPACES 1 + +/** Subscribe to the specified events */ +#define I3_IPC_MESSAGE_TYPE_SUBSCRIBE 2 + +/** Requests the current outputs from i3 */ +#define I3_IPC_MESSAGE_TYPE_GET_OUTPUTS 3 + +/* + * Messages from i3 to clients + * + */ + +/** Command reply type */ +#define I3_IPC_REPLY_TYPE_COMMAND 0 + +/** Workspaces reply type */ +#define I3_IPC_REPLY_TYPE_WORKSPACES 1 + +/** Subscription reply type */ +#define I3_IPC_REPLY_TYPE_SUBSCRIBE 2 + +/** Outputs reply type */ +#define I3_IPC_REPLY_TYPE_OUTPUTS 3 + +/* + * Events from i3 to clients. Events have the first bit set high. + * + */ +#define I3_IPC_EVENT_MASK (1 << 31) + +/* The workspace event will be triggered upon changes in the workspace list */ +#define I3_IPC_EVENT_WORKSPACE (I3_IPC_EVENT_MASK | 0) + +/* The output event will be triggered upon changes in the output list */ +#define I3_IPC_EVENT_OUTPUT (I3_IPC_EVENT_MASK | 1) #endif diff --git a/include/ipc.h b/include/ipc.h index de4e2264..63d59141 100644 --- a/include/ipc.h +++ b/include/ipc.h @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -16,6 +16,34 @@ #include "i3/ipc.h" +typedef struct ipc_client { + int fd; + + /* The events which this client wants to receive */ + int num_events; + char **events; + + TAILQ_ENTRY(ipc_client) clients; +} ipc_client; + +/* + * Callback type for the different message types. + * + * message is the raw packet, as received from the UNIX domain socket. size + * is the remaining size of bytes for this packet. + * + * message_size is the size of the message as the sender specified it. + * message_type is the type of the message as the sender specified it. + * + */ +typedef void(*handler_t)(int, uint8_t*, int, uint32_t, uint32_t); + +/* Macro to declare a callback */ +#define IPC_HANDLER(name) \ + static void handle_ ## name (int fd, uint8_t *message, \ + int size, uint32_t message_size, \ + uint32_t message_type) + /** * Handler for activity on the listening socket, meaning that a new client * has just connected and we should accept() him. Sets up the event handler @@ -32,4 +60,18 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents); */ int ipc_create_socket(const char *filename); +/** + * Sends the specified event to all IPC clients which are currently connected + * and subscribed to this kind of event. + * + */ +void ipc_send_event(const char *event, uint32_t message_type, const char *payload); + +/** + * Calls shutdown() on each socket and closes it. This function to be called + * when exiting or restarting only! + * + */ +void ipc_shutdown(); + #endif diff --git a/include/layout.h b/include/layout.h index a96aabc3..1cbb7837 100644 --- a/include/layout.h +++ b/include/layout.h @@ -79,7 +79,7 @@ void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace, * Renders the given workspace on the given screen * */ -void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws); +void render_workspace(xcb_connection_t *conn, Output *output, Workspace *r_ws); /** * Renders the whole layout, that is: Go through each screen, each workspace, diff --git a/include/log.h b/include/log.h new file mode 100644 index 00000000..6d529a00 --- /dev/null +++ b/include/log.h @@ -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 +#include + +/** ##__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 diff --git a/include/manage.h b/include/manage.h index 65542d91..9c87a08e 100644 --- a/include/manage.h +++ b/include/manage.h @@ -23,6 +23,16 @@ void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root); +/** + * Restores the geometry of each window by reparenting it to the root window + * at the position of its frame. + * + * This is to be called *only* before exiting/restarting i3 because of evil + * side-effects which are to be expected when continuing to run i3. + * + */ +void restore_geometry(xcb_connection_t *conn); + /** * Do some sanity checks and then reparent the window. * diff --git a/include/randr.h b/include/randr.h new file mode 100644 index 00000000..4832efe5 --- /dev/null +++ b/include/randr.h @@ -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 + +#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 diff --git a/include/sighandler.h b/include/sighandler.h new file mode 100644 index 00000000..49317438 --- /dev/null +++ b/include/sighandler.h @@ -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 diff --git a/include/util.h b/include/util.h index ed85d539..d1384962 100644 --- a/include/util.h +++ b/include/util.h @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * (c) 2009 Michael Stapelberg and contributors + * © 2009 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -34,10 +34,6 @@ } \ while (0) -/** ##__VA_ARGS__ means: leave out __VA_ARGS__ completely if it is empty, that - is, delete the preceding comma */ -#define LOG(fmt, ...) slog("%s:%s:%d - " fmt, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__) - TAILQ_HEAD(keyvalue_table_head, keyvalue_element); extern struct keyvalue_table_head by_parent; extern struct keyvalue_table_head by_child; @@ -46,11 +42,11 @@ int min(int a, int b); int max(int a, int b); /** - * Logs the given message to stdout while prefixing the current time to it. - * This is to be called by LOG() which includes filename/linenumber + * Updates *destination with new_value and returns true if it was changed or false + * if it was the same * */ -void slog(char *fmt, ...); +bool update_if_necessary(uint32_t *destination, const uint32_t new_value); /** * Safe-wrapper around malloc which exits if malloc returns NULL (meaning that @@ -161,6 +157,13 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode); Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitle, Client *specific); +/* + * Restart i3 in-place + * appends -a to argument list to disable autostart + * + */ +void i3_restart(); + #if defined(__OpenBSD__) /* OpenBSD does not provide memmem(), so we provide FreeBSD’s implementation */ void *memmem(const void *l, size_t l_len, const void *s, size_t s_len); diff --git a/include/workspace.h b/include/workspace.h index f3bdd565..dae245ce 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -11,7 +11,7 @@ #include #include "data.h" -#include "xinerama.h" +#include "randr.h" #ifndef _WORKSPACE_H #define _WORKSPACE_H @@ -44,6 +44,17 @@ bool workspace_is_visible(Workspace *ws); /** Switches to the given workspace */ void workspace_show(xcb_connection_t *conn, int workspace); +/** + * Assigns the given workspace to the given screen by correctly updating its + * state and reconfiguring all the clients on this workspace. + * + * This is called when initializing a screen and when re-assigning it to a + * different screen which just got available (if you configured it to be on + * screen 1 and you just plugged in screen 1). + * + */ +void workspace_assign_to(Workspace *ws, Output *screen, bool hide_it); + /** * Initializes the given workspace if it is not already initialized. The given * screen is to be understood as a fallback, if the workspace itself either @@ -51,14 +62,14 @@ void workspace_show(xcb_connection_t *conn, int workspace); * the screen is not attached at the moment. * */ -void workspace_initialize(Workspace *ws, i3Screen *screen); +void workspace_initialize(Workspace *ws, Output *screen, bool recheck); /** * Gets the first unused workspace for the given screen, taking into account * the preferred_screen setting of every workspace (workspace assignments). * */ -Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen *screen); +Workspace *get_first_workspace_for_output(Output *screen); /** * Unmaps all clients (and stack windows) of the given workspace. diff --git a/include/xcb.h b/include/xcb.h index d01f6da1..78e1373a 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -61,7 +61,10 @@ enum { _NET_SUPPORTED = 0, WM_DELETE_WINDOW, UTF8_STRING, WM_STATE, - WM_CLIENT_LEADER + WM_CLIENT_LEADER, + _NET_CURRENT_DESKTOP, + _NET_ACTIVE_WINDOW, + _NET_WORKAREA }; extern unsigned int xcb_numlock_mask; @@ -161,4 +164,10 @@ void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap) int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *text, int length); +/** + * Configures the given window to have the size/position specified by given rect + * + */ +void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r); + #endif diff --git a/include/xinerama.h b/include/xinerama.h index 135ab1ab..f1182349 100644 --- a/include/xinerama.h +++ b/include/xinerama.h @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * (c) 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -13,16 +13,6 @@ #ifndef _XINERAMA_H #define _XINERAMA_H -TAILQ_HEAD(screens_head, Screen); -extern struct screens_head *virtual_screens; - -/** - * Returns true if both screen objects describe the same screen (checks their - * size and position). - * - */ -bool screens_are_equal(i3Screen *screen1, i3Screen *screen2); - /** * We have just established a connection to the X server and need the initial * Xinerama information to setup workspaces for each screen. @@ -30,33 +20,4 @@ bool screens_are_equal(i3Screen *screen1, i3Screen *screen2); */ void initialize_xinerama(xcb_connection_t *conn); -/** - * This is called when the rootwindow receives a configure_notify event and - * therefore the number/position of the Xinerama screens could have changed. - * - */ -void xinerama_requery_screens(xcb_connection_t *conn); - -/** - * Looks in virtual_screens for the i3Screen whose start coordinates are x, y - * - */ -i3Screen *get_screen_at(int x, int y, struct screens_head *screenlist); - -/** - * Looks in virtual_screens for the i3Screen which contains coordinates x, y - * - */ -i3Screen *get_screen_containing(int x, int y); - -/** - * Gets the screen which is the last one in the given direction, for example - * the screen on the most bottom when direction == D_DOWN, the screen most - * right when direction == D_RIGHT and so on. - * - * This function always returns a screen. - * - */ -i3Screen *get_screen_most(direction_t direction, i3Screen *current); - #endif diff --git a/man/Makefile b/man/Makefile index 2bf15fc5..4d7836ec 100644 --- a/man/Makefile +++ b/man/Makefile @@ -1,6 +1,18 @@ -all: - a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3.man - a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3-msg.man - a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3-input.man +A2M:=a2x -f manpage --asciidoc-opts="-f asciidoc.conf" + +all: i3.1 i3-msg.1 i3-input.1 i3-wsbar.1 + +%.1: %.man asciidoc.conf + ${A2M} $< + +i3-wsbar.1: ../i3-wsbar + pod2man $^ > $@ + clean: - rm -f i3.{1,html,xml} i3-msg.{1,html,xml} i3-input.{1,html,xml} + for file in "i3 i3-msg i3-input"; \ + do \ + rm -f $${file}.1 $${file}.html $${file}.xml; \ + done + +distclean: clean + rm -f *.1 diff --git a/man/asciidoc.conf b/man/asciidoc.conf index d3d5cce8..e15695d2 100644 --- a/man/asciidoc.conf +++ b/man/asciidoc.conf @@ -7,7 +7,7 @@ template::[header-declarations] {mantitle} {manvolnum} i3 -delta +epsilon i3 Manual diff --git a/man/i3-input.man b/man/i3-input.man index b53406ff..5b7ce6d9 100644 --- a/man/i3-input.man +++ b/man/i3-input.man @@ -5,7 +5,7 @@ v3.delta, November 2009 == NAME -i3-input - interactively take a command for i3 +i3-input - interactively take a command for i3 window manager == SYNOPSIS @@ -13,8 +13,9 @@ i3-input [-s ] [-p ] [-l ] [-P ] [-v] == DESCRIPTION -i3-input is a tool to take commands (or parts of a command) and then send it -to i3. This is useful for example for the mark/goto command. +i3-input is a tool to take commands (or parts of a command) composed by +the user, and send it/them to i3. This is useful, for example, for the +mark/goto command. == EXAMPLE diff --git a/man/i3-msg.man b/man/i3-msg.man index a2b17683..c723bd1e 100644 --- a/man/i3-msg.man +++ b/man/i3-msg.man @@ -5,7 +5,7 @@ v3.delta, November 2009 == NAME -i3-msg - send messages to i3 +i3-msg - send messages to i3 window manager == SYNOPSIS diff --git a/man/i3.man b/man/i3.man index 70333805..109248e5 100644 --- a/man/i3.man +++ b/man/i3.man @@ -1,7 +1,7 @@ i3(1) ===== Michael Stapelberg -v3.delta, November 2009 +v3.epsilon, March 2010 == NAME @@ -9,65 +9,88 @@ i3 - an improved dynamic, tiling window manager == SYNOPSIS -i3 [-c configfile] [-a] +i3 [-a] [-c configfile] [-C] [-d ] [-v] [-V] == OPTIONS --c:: -Specifies an alternate configuration file path - -a:: Disables autostart. +-c:: +Specifies an alternate configuration file path. + +-C:: +Check the configuration file for validity and exit. + +-d:: +Specifies the debug loglevel. To see the most output, use -d all. + +-v:: +Display version number (and date of the last commit). + +-V:: +Be verbose. + == DESCRIPTION === INTRODUCTION i3 was created because wmii, our favorite window manager at the time, didn’t -provide some features we wanted (Xinerama done right, for example), had some -bugs, didn’t progress since quite some time and wasn’t easy to hack at all +provide some features we wanted (multi-monitor done right, for example), had +some bugs, didn’t progress since quite some time and wasn’t easy to hack at all (source code comments/documentation completely lacking). Still, we think the wmii developers and contributors did a great job. Thank you for inspiring us to create i3. Please be aware that i3 is primarily targeted at advanced users and developers. +=== IMPORTANT NOTE TO nVidia BINARY DRIVER USERS + +If you are using the nVidia binary graphics driver (also known as 'blob') +you need to use the +--force-xinerama+ flag (in your .xsession) when starting +i3, like so: + +---------------------------------------------- +exec i3 --force-xinerama -V >>~/.i3/i3log 2>&1 +---------------------------------------------- + +See also docs/multi-monitor for the full explanation. + === TERMINOLOGY Client:: A client is X11-speak for a window. Table:: -Your workspace is managed using a table. You can move windows around and create new columns -(move a client to the right) or rows (move it to the bottom) implicitly. +Your workspace is managed using a table. You can move windows around and create +new columns (move a client to the right) or rows (move it to the bottom) +implicitly. + By "snapping" a client in a specific direction, you increase its colspan/rowspan. Container:: -A container contains a variable number of clients. Each cell of the table is a container. +A container contains a variable number of clients. Each cell of the table is a +container. + -Containers can be used in various modes. The default mode is called "default" and just -resizes each client equally so that it fits. +Containers can be used in various modes. The default mode is called "default" +and just resizes each client equally so that it fits. Workspace:: -A workspace is a set of clients (technically speaking, it’s just a table). Other window -managers call this "Virtual Desktops". +A workspace is a set of clients (technically speaking, it’s just a table). +Other window managers call this "Virtual Desktops". + -In i3, each workspace is assigned to a specific virtual screen. By default, screen 1 -has workspace 1, screen 2 has workspace 2 and so on… However, when you create a new -workspace (by simply switching to it), it’ll be assigned the screen you are currently -on. +In i3, each workspace is assigned to a specific virtual screen. By default, +screen 1 has workspace 1, screen 2 has workspace 2 and so on… However, when you +create a new workspace (by simply switching to it), it’ll be assigned the +screen you are currently on. -Virtual Screen:: -Using Xinerama, you can have an X11 screen spanning multiple real monitors. Furthermore, -you can set them up in cloning mode or with positions (monitor 1 is left of monitor 2). +Output:: +Using XRandR, you can have an X11 screen spanning multiple real monitors. +Furthermore, you can set them up in cloning mode or with positions (monitor 1 +is left of monitor 2). + -A virtual screen is the result of your Xinerama setup. For example, if you have attached -two real monitors (let’s say your laptop screen and a video projector) and enabled cloning, i3 -will use one virtual screen with the size of the smallest screen you have attached (so -that you can see all your windows on each screen all the time). -If you have two monitors attached, one configured to be left of the other, i3 will use -two virtual screens. +i3 uses the RandR API to query which outputs are available and which screens +are connected to these outputs. == KEYBINDINGS @@ -114,31 +137,34 @@ Mod1+t:: Select the first tiling window if the current window is floating and vice-versa. Mod1+Shift+q:: -Kills the current window. This is equivalent to "clicking on the close button", meaning a polite -request to the application to close this window. For example, Firefox will save its session -upon such a request. If the application does not support that, the window will be killed and -it depends on the application what happens. +Kills the current window. This is equivalent to "clicking on the close button", +meaning a polite request to the application to close this window. For example, +Firefox will save its session upon such a request. If the application does not +support that, the window will be killed and it depends on the application what +happens. Mod1+Shift+r:: -Restarts i3 in place (without losing any windows, but the layout). +Restarts i3 in place (without losing any windows, but at this time, the layout +and placement of windows is not retained). Mod1+Shift+e:: Exits i3. == FILES -=== ~/.i3/config +=== \~/.i3/config (or ~/.config/i3/config) -When starting, i3 looks for ~/.i3/config and loads the configuration. If ~/.i3/config is not found, -i3 tries /etc/i3/config. You can specify a custom path using the -c option. +When starting, i3 looks for configuration files in the following order: -At the moment, you can specify only the path to your favorite terminal emulator, the font and keybindings. +1. ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set) +2. /etc/xdg/i3/config (or $XDG_CONFIG_DIRS/i3/config if set) +3. ~/.i3/config +4. /etc/i3/config -At the moment, you have to bind to keycodes (find them out via xev(1)). +You can specify a custom path using the -c option. .Sample configuration ------------------------------------------------------------- -terminal /usr/bin/urxvt font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 # Start terminal (Mod1+Enter) @@ -265,22 +291,25 @@ ulimit -c unlimited # Start i3 and log to ~/.i3/logfile echo "Starting at $(date)" >> ~/.i3/logfile -exec /usr/bin/i3 >> ~/.i3/logfile +exec /usr/bin/i3 -V -d all >> ~/.i3/logfile ------------------------------------------------------------- == TODO -There is still lot of work to do. Please check our bugtracker for up-to-date information -about tasks which are still not finished. +There is still lot of work to do. Please check our bugtracker for up-to-date +information about tasks which are still not finished. == SEE ALSO -You should have a copy of the userguide (featuring nice screenshots/graphics which is why this -is not integrated into this manpage), the debugging guide and the "how to hack" guide. If you -are building from source, run +make -C docs+. +You should have a copy of the userguide (featuring nice screenshots/graphics +which is why this is not integrated into this manpage), the debugging guide, +and the "how to hack" guide. If you are building from source, run: + +make -C docs+ You can also access these documents online at http://i3.zekjur.net/ +i3-input(1), i3-msg(1), i3-wsbar(1) + == AUTHOR Michael Stapelberg and contributors diff --git a/src/cfgparse.l b/src/cfgparse.l index 1b636f17..10a13076 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -1,5 +1,7 @@ %option nounput %option noinput +%option noyy_top_state +%option stack %{ /* @@ -13,20 +15,59 @@ #include "data.h" #include "config.h" +#include "log.h" +#include "util.h" + +int yycolumn = 1; + +#define YY_DECL int yylex (struct context *context) + +#define YY_USER_ACTION { \ + context->first_column = yycolumn; \ + context->last_column = yycolumn+yyleng-1; \ + yycolumn += yyleng; \ +} + %} -%Start BIND_COND -%Start BINDSYM_COND -%Start BIND_AWS_COND -%Start BINDSYM_AWS_COND -%Start BIND_A2WS_COND -%Start ASSIGN_COND -%Start COLOR_COND -%Start SCREEN_COND -%Start SCREEN_AWS_COND +EOL (\r?\n) + +%s BIND_COND +%s BINDSYM_COND +%s BIND_AWS_COND +%s BINDSYM_AWS_COND +%s BIND_A2WS_COND +%s ASSIGN_COND +%s COLOR_COND +%s OUTPUT_COND +%s OUTPUT_AWS_COND +%x BUFFER_LINE %% + + { + /* This is called when a new line is lexed. We only want the + * first line to match to go into state BUFFER_LINE */ + if (context->line_number == 0) { + context->line_number = 1; + BEGIN(INITIAL); + yy_push_state(BUFFER_LINE); + } + } + +^[^\r\n]*/{EOL}? { + /* save whole line */ + context->line_copy = strdup(yytext); + + yyless(0); + yy_pop_state(); + yy_set_bol(true); + yycolumn = 1; +} + + [^\n]+ { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR; } +[a-zA-Z0-9_-]+ { yylval.string = strdup(yytext); return OUTPUT; } ^[ \t]*#[^\n]* { return TOKCOMMENT; } [0-9a-fA-F]+ { yylval.string = strdup(yytext); return HEX; } [0-9]+ { yylval.number = atoi(yytext); return NUMBER; } @@ -35,7 +76,14 @@ bind { BEGIN(BIND_COND); return TOKBIND; } bindsym { BEGIN(BINDSYM_COND); return TOKBINDSYM; } floating_modifier { BEGIN(INITIAL); return TOKFLOATING_MODIFIER; } workspace { BEGIN(INITIAL); return TOKWORKSPACE; } -screen { BEGIN(SCREEN_COND); return TOKSCREEN; } +output { BEGIN(OUTPUT_COND); return TOKOUTPUT; } +screen { + /* for compatibility until v3.φ */ + ELOG("Assignments to screens are DEPRECATED and will not work. " \ + "Please replace them with assignments to outputs.\n"); + BEGIN(OUTPUT_COND); + return TOKOUTPUT; + } terminal { BEGIN(BIND_AWS_COND); return TOKTERMINAL; } font { BEGIN(BIND_AWS_COND); return TOKFONT; } assign { BEGIN(ASSIGN_COND); return TOKASSIGN; } @@ -44,6 +92,8 @@ ipc-socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; } ipc_socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; } new_container { return TOKNEWCONTAINER; } new_window { return TOKNEWWINDOW; } +focus_follows_mouse { return TOKFOCUSFOLLOWSMOUSE; } +workspace_bar { return TOKWORKSPACEBAR; } default { yylval.number = MODE_DEFAULT; return TOKCONTAINERMODE; } stacking { yylval.number = MODE_STACK; return TOKCONTAINERMODE; } tabbed { yylval.number = MODE_TABBED; return TOKCONTAINERMODE; } @@ -65,16 +115,21 @@ Mod4 { yylval.number = BIND_MOD4; return MODIFIER; } Mod5 { yylval.number = BIND_MOD5; return MODIFIER; } Mode_switch { yylval.number = BIND_MODE_SWITCH; return MODIFIER; } control { return TOKCONTROL; } +ctrl { return TOKCONTROL; } shift { return TOKSHIFT; } → { return TOKARROW; } -\n /* ignore end of line */; -x { return (int)yytext[0]; } +{EOL} { + FREE(context->line_copy); + context->line_number++; + BEGIN(INITIAL); + yy_push_state(BUFFER_LINE); + } [ \t]+ { BEGIN(BIND_AWS_COND); return WHITESPACE; } [ \t]+ { BEGIN(BINDSYM_AWS_COND); return WHITESPACE; } [ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; } [ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; } -[ \t]+ { BEGIN(SCREEN_AWS_COND); return WHITESPACE; } -[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; } +[ \t]+ { BEGIN(OUTPUT_AWS_COND); return WHITESPACE; } +[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; } [ \t]+ { return WHITESPACE; } \"[^\"]+\" { /* if ASSIGN_COND then */ @@ -89,4 +144,11 @@ shift { return TOKSHIFT; } [a-zA-Z0-9_]+ { yylval.string = strdup(yytext); return WORD; } [a-zA-Z]+ { yylval.string = strdup(yytext); return WORD; } . { return (int)yytext[0]; } + +<> { + while (yy_start_stack_ptr > 0) + yy_pop_state(); + yyterminate(); +} + %% diff --git a/src/cfgparse.y b/src/cfgparse.y index 704a415b..2774f05c 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -21,20 +21,35 @@ #include "table.h" #include "workspace.h" #include "xcb.h" - +#include "log.h" typedef struct yy_buffer_state *YY_BUFFER_STATE; -extern int yylex(void); +extern int yylex(struct context *context); extern int yyparse(void); extern FILE *yyin; YY_BUFFER_STATE yy_scan_string(const char *); static struct bindings_head *current_bindings; +static struct context *context; -int yydebug = 1; +/* We don’t need yydebug for now, as we got decent error messages using + * yyerror(). Should you ever want to extend the parser, it might be handy + * to just comment it in again, so it stays here. */ +//int yydebug = 1; -void yyerror(const char *str) { - fprintf(stderr,"error: %s\n",str); +void yyerror(const char *error_message) { + ELOG("\n"); + ELOG("CONFIG: %s\n", error_message); + ELOG("CONFIG: in file \"%s\", line %d:\n", + context->filename, context->line_number); + ELOG("CONFIG: %s\n", context->line_copy); + ELOG("CONFIG: "); + for (int c = 1; c <= context->last_column; c++) + if (c >= context->first_column) + printf("^"); + else printf(" "); + printf("\n"); + ELOG("\n"); } int yywrap() { @@ -55,7 +70,7 @@ void parse_file(const char *f) { if (fstat(fd, &stbuf) == -1) die("Could not fstat file: %s\n", strerror(errno)); - buf = smalloc(stbuf.st_size * sizeof(char)); + buf = scalloc((stbuf.st_size + 1) * sizeof(char)); while (read_bytes < stbuf.st_size) { if ((ret = read(fd, buf + read_bytes, (stbuf.st_size - read_bytes))) < 0) die("Could not read(): %s\n", strerror(errno)); @@ -95,7 +110,7 @@ void parse_file(const char *f) { new->key = sstrdup(v_key); new->value = sstrdup(v_value); SLIST_INSERT_HEAD(&variables, new, variables); - LOG("Got new variable %s = %s\n", v_key, v_value); + DLOG("Got new variable %s = %s\n", v_key, v_value); continue; } } @@ -149,18 +164,33 @@ void parse_file(const char *f) { yy_scan_string(new); + context = scalloc(sizeof(struct context)); + context->filename = f; + if (yyparse() != 0) { fprintf(stderr, "Could not parse configfile\n"); exit(1); } + FREE(context->line_copy); + free(context); free(new); free(buf); + + while (!SLIST_EMPTY(&variables)) { + current = SLIST_FIRST(&variables); + FREE(current->key); + FREE(current->value); + SLIST_REMOVE_HEAD(&variables, variables); + FREE(current); + } } %} %expect 1 +%error-verbose +%lex-param { struct context *context } %union { int number; @@ -170,40 +200,44 @@ void parse_file(const char *f) { struct Binding *binding; } -%token NUMBER -%token WORD -%token STR -%token STR_NG -%token HEX +%token NUMBER "" +%token WORD "" +%token STR "" +%token STR_NG "" +%token HEX "" +%token OUTPUT "" %token TOKBIND %token TOKTERMINAL -%token TOKCOMMENT -%token TOKFONT -%token TOKBINDSYM -%token MODIFIER -%token TOKCONTROL -%token TOKSHIFT -%token WHITESPACE -%token TOKFLOATING_MODIFIER -%token QUOTEDSTRING -%token TOKWORKSPACE -%token TOKSCREEN -%token TOKASSIGN +%token TOKCOMMENT "" +%token TOKFONT "font" +%token TOKBINDSYM "bindsym" +%token MODIFIER "" +%token TOKCONTROL "control" +%token TOKSHIFT "shift" +%token WHITESPACE "" +%token TOKFLOATING_MODIFIER "floating_modifier" +%token QUOTEDSTRING "" +%token TOKWORKSPACE "workspace" +%token TOKOUTPUT "output" +%token TOKASSIGN "assign" %token TOKSET -%token TOKIPCSOCKET -%token TOKEXEC +%token TOKIPCSOCKET "ipc_socket" +%token TOKEXEC "exec" %token TOKCOLOR -%token TOKARROW -%token TOKMODE -%token TOKNEWCONTAINER -%token TOKNEWWINDOW -%token TOKCONTAINERMODE -%token TOKSTACKLIMIT +%token TOKARROW "→" +%token TOKMODE "mode" +%token TOKNEWCONTAINER "new_container" +%token TOKNEWWINDOW "new_window" +%token TOKFOCUSFOLLOWSMOUSE "focus_follows_mouse" +%token TOKWORKSPACEBAR "workspace_bar" +%token TOKCONTAINERMODE "default/stacking/tabbed" +%token TOKSTACKLIMIT "stack-limit" %% lines: /* empty */ | lines WHITESPACE line + | lines error | lines line ; @@ -213,6 +247,8 @@ line: | floating_modifier | new_container | new_window + | focus_follows_mouse + | workspace_bar | workspace | assign | ipcsocket @@ -251,7 +287,7 @@ bind: new->keycode = $2; new->mods = $1; - new->command = sstrdup($4); + new->command = $4; $$ = new; } @@ -263,9 +299,9 @@ bindsym: printf("\tFound symbolic mod%d with key %s and command %s\n", $1, $2, $4); Binding *new = scalloc(sizeof(Binding)); - new->symbol = sstrdup($2); + new->symbol = $2; new->mods = $1; - new->command = sstrdup($4); + new->command = $4; $$ = new; } @@ -295,7 +331,7 @@ mode: } struct Mode *mode = scalloc(sizeof(struct Mode)); - mode->name = strdup($3); + mode->name = $3; mode->bindings = current_bindings; current_bindings = NULL; SLIST_INSERT_HEAD(&modes, mode, modes); @@ -325,7 +361,7 @@ modeline: floating_modifier: TOKFLOATING_MODIFIER WHITESPACE binding_modifiers { - LOG("floating modifier = %d\n", $3); + DLOG("floating modifier = %d\n", $3); config.floating_modifier = $3; } ; @@ -333,7 +369,7 @@ floating_modifier: new_container: TOKNEWCONTAINER WHITESPACE TOKCONTAINERMODE { - LOG("new containers will be in mode %d\n", $3); + DLOG("new containers will be in mode %d\n", $3); config.container_mode = $3; /* We also need to change the layout of the already existing @@ -355,7 +391,7 @@ new_container: } | TOKNEWCONTAINER WHITESPACE TOKSTACKLIMIT WHITESPACE TOKSTACKLIMIT WHITESPACE NUMBER { - LOG("stack-limit %d with val %d\n", $5, $7); + DLOG("stack-limit %d with val %d\n", $5, $7); config.container_stack_limit = $5; config.container_stack_limit_value = $7; @@ -374,32 +410,69 @@ new_container: new_window: TOKNEWWINDOW WHITESPACE WORD { - LOG("new windows should start in mode %s\n", $3); - config.default_border = strdup($3); + DLOG("new windows should start in mode %s\n", $3); + config.default_border = sstrdup($3); + } + ; + +bool: + NUMBER + { + $$ = ($1 == 1); + } + | WORD + { + DLOG("checking word \"%s\"\n", $1); + $$ = (strcasecmp($1, "yes") == 0 || + strcasecmp($1, "true") == 0 || + strcasecmp($1, "on") == 0 || + strcasecmp($1, "enable") == 0 || + strcasecmp($1, "active") == 0); + } + ; + +focus_follows_mouse: + TOKFOCUSFOLLOWSMOUSE WHITESPACE bool + { + DLOG("focus follows mouse = %d\n", $3); + config.disable_focus_follows_mouse = !($3); + } + ; + +workspace_bar: + TOKWORKSPACEBAR WHITESPACE bool + { + DLOG("workspace bar = %d\n", $3); + config.disable_workspace_bar = !($3); } ; workspace: - TOKWORKSPACE WHITESPACE NUMBER WHITESPACE TOKSCREEN WHITESPACE screen optional_workspace_name + TOKWORKSPACE WHITESPACE NUMBER WHITESPACE TOKOUTPUT WHITESPACE OUTPUT optional_workspace_name { int ws_num = $3; if (ws_num < 1) { - LOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); + DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); } else { Workspace *ws = workspace_get(ws_num - 1); - ws->preferred_screen = sstrdup($7); - if ($8 != NULL) + ws->preferred_output = $7; + if ($8 != NULL) { workspace_set_name(ws, $8); + free($8); + } } } | TOKWORKSPACE WHITESPACE NUMBER WHITESPACE workspace_name { int ws_num = $3; if (ws_num < 1) { - LOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); + DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); } else { - if ($5 != NULL) + DLOG("workspace name to: %s\n", $5); + if ($5 != NULL) { workspace_set_name(workspace_get(ws_num - 1), $5); + free($5); + } } } ; @@ -415,13 +488,6 @@ workspace_name: | WORD { $$ = $1; } ; -screen: - NUMBER { asprintf(&$$, "%d", $1); } - | NUMBER 'x' { asprintf(&$$, "%d", $1); } - | NUMBER 'x' NUMBER { asprintf(&$$, "%dx%d", $1, $3); } - | 'x' NUMBER { asprintf(&$$, "x%d", $2); } - ; - assign: TOKASSIGN WHITESPACE window_class WHITESPACE optional_arrow assign_target { @@ -430,7 +496,7 @@ assign: struct Assignment *new = $6; printf(" to %d\n", new->workspace); printf(" floating = %d\n", new->floating); - new->windowclass_title = strdup($3); + new->windowclass_title = $3; TAILQ_INSERT_TAIL(&assignments, new, assignments); } ; @@ -471,7 +537,7 @@ optional_arrow: ipcsocket: TOKIPCSOCKET WHITESPACE STR { - config.ipc_socket_path = sstrdup($3); + config.ipc_socket_path = $3; } ; @@ -479,7 +545,7 @@ exec: TOKEXEC WHITESPACE STR { struct Autostart *new = smalloc(sizeof(struct Autostart)); - new->command = sstrdup($3); + new->command = $3; TAILQ_INSERT_TAIL(&autostarts, new, autostarts); } ; @@ -487,15 +553,15 @@ exec: terminal: TOKTERMINAL WHITESPACE STR { - config.terminal = sstrdup($3); - printf("terminal %s\n", config.terminal); + ELOG("The terminal option is DEPRECATED and has no effect. " + "Please remove it from your configuration file.\n"); } ; font: TOKFONT WHITESPACE STR { - config.font = sstrdup($3); + config.font = $3; printf("font %s\n", config.font); } ; diff --git a/src/click.c b/src/click.c index dbbc4338..9f2a47ff 100644 --- a/src/click.c +++ b/src/click.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -36,6 +36,8 @@ #include "commands.h" #include "floating.h" #include "resize.h" +#include "log.h" +#include "randr.h" static struct Stack_Window *get_stack_window(xcb_window_t window_id) { struct Stack_Window *current; @@ -97,18 +99,18 @@ static bool button_press_stackwin(xcb_connection_t *conn, xcb_button_press_event int wrap = ceil((float)num_clients / container->stack_limit_value); int clicked_column = (event->event_x / (stack_win->rect.width / container->stack_limit_value)); int clicked_row = (event->event_y / decoration_height); - LOG("clicked on column %d, row %d\n", clicked_column, clicked_row); + DLOG("clicked on column %d, row %d\n", clicked_column, clicked_row); destination = (wrap * clicked_column) + clicked_row; } else { int width = (stack_win->rect.width / ceil((float)num_clients / container->stack_limit_value)); int clicked_column = (event->event_x / width); int clicked_row = (event->event_y / decoration_height); - LOG("clicked on column %d, row %d\n", clicked_column, clicked_row); + DLOG("clicked on column %d, row %d\n", clicked_column, clicked_row); destination = (container->stack_limit_value * clicked_column) + clicked_row; } } - LOG("Click on stack_win for client %d\n", destination); + DLOG("Click on stack_win for client %d\n", destination); CIRCLEQ_FOREACH(client, &(stack_win->container->clients), clients) if (c++ == destination) { set_focus(conn, client, true); @@ -124,26 +126,26 @@ static bool button_press_stackwin(xcb_connection_t *conn, xcb_button_press_event * */ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *event) { - i3Screen *screen; - TAILQ_FOREACH(screen, virtual_screens, screens) { - if (screen->bar != event->event) + Output *output; + TAILQ_FOREACH(output, &outputs, outputs) { + if (output->bar != event->event) continue; - LOG("Click on a bar\n"); + DLOG("Click on a bar\n"); /* Check if the button was one of button4 or button5 (scroll up / scroll down) */ if (event->detail == XCB_BUTTON_INDEX_4 || event->detail == XCB_BUTTON_INDEX_5) { Workspace *ws = c_ws; if (event->detail == XCB_BUTTON_INDEX_5) { while ((ws = TAILQ_NEXT(ws, workspaces)) != TAILQ_END(workspaces_head)) { - if (ws->screen == screen) { + if (ws->output == output) { workspace_show(conn, ws->num + 1); return true; } } } else { while ((ws = TAILQ_PREV(ws, workspaces_head, workspaces)) != TAILQ_END(workspaces)) { - if (ws->screen == screen) { + if (ws->output == output) { workspace_show(conn, ws->num + 1); return true; } @@ -152,13 +154,13 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e return true; } int drawn = 0; - /* Because workspaces can be on different screens, we need to loop - through all of them and decide to count it based on its ->screen */ + /* Because workspaces can be on different outputs, we need to loop + through all of them and decide to count it based on its ->output */ Workspace *ws; TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->screen != screen) + if (ws->output != output) continue; - LOG("Checking if click was on workspace %d with drawn = %d, tw = %d\n", + DLOG("Checking if click was on workspace %d with drawn = %d, tw = %d\n", ws->num, drawn, ws->text_width); if (event->event_x > (drawn + 1) && event->event_x <= (drawn + 1 + ws->text_width + 5 + 5)) { @@ -201,7 +203,7 @@ static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client, Workspace *ws = con->workspace; int first = 0, second = 0; - LOG("click was %d px to the right, %d px to the left, %d px to top, %d px to bottom\n", + DLOG("click was %d px to the right, %d px to the left, %d px to top, %d px to bottom\n", to_right, to_left, to_top, to_bottom); if (to_right < to_left && @@ -209,7 +211,7 @@ static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client, to_right < to_bottom) { /* …right border */ first = con->col + (con->colspan - 1); - LOG("column %d\n", first); + DLOG("column %d\n", first); if (!cell_exists(ws, first, con->row) || (first == (ws->cols-1))) @@ -251,7 +253,7 @@ static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client, } int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) { - LOG("Button %d pressed\n", event->state); + DLOG("Button %d pressed\n", event->state); /* This was either a focus for a client’s parent (= titlebar)… */ Client *client = table_get(&by_child, event->event); bool border_click = false; @@ -263,23 +265,28 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ * to move around the client if it was floating. if not, we just process * as usual. */ if (config.floating_modifier != 0 && - (event->state & config.floating_modifier) != 0) { + (event->state & config.floating_modifier) == config.floating_modifier) { if (client == NULL) { - LOG("Not handling, floating_modifier was pressed and no client found\n"); + DLOG("Not handling, floating_modifier was pressed and no client found\n"); + xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); + xcb_flush(conn); return 1; } if (client->fullscreen) { - LOG("Not handling, client is in fullscreen mode\n"); + DLOG("Not handling, client is in fullscreen mode\n"); + xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); + xcb_flush(conn); return 1; } if (client_is_floating(client)) { - LOG("button %d pressed\n", event->detail); + DLOG("button %d pressed\n", event->detail); if (event->detail == 1) { - LOG("left mouse button, dragging\n"); + DLOG("left mouse button, dragging\n"); floating_drag_window(conn, client, event); } else if (event->detail == 3) { - LOG("right mouse button\n"); - floating_resize_window(conn, client, event); + bool proportional = (event->state & BIND_SHIFT); + DLOG("right mouse button\n"); + floating_resize_window(conn, client, proportional, event); } return 1; } @@ -301,7 +308,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ if (button_press_bar(conn, event)) return 1; - LOG("Could not handle this button press\n"); + DLOG("Could not handle this button press\n"); return 1; } @@ -309,19 +316,19 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ set_focus(conn, client, true); /* Let’s see if this was on the borders (= resize). If not, we’re done */ - LOG("press button on x=%d, y=%d\n", event->event_x, event->event_y); + DLOG("press button on x=%d, y=%d\n", event->event_x, event->event_y); resize_orientation_t orientation = O_VERTICAL; Container *con = client->container; int first, second; if (client->dock) { - LOG("dock. done.\n"); + DLOG("dock. done.\n"); xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); xcb_flush(conn); return 1; } - LOG("event->event_x = %d, client->rect.width = %d\n", event->event_x, client->rect.width); + DLOG("event->event_x = %d, client->rect.width = %d\n", event->event_x, client->rect.width); /* Some clients (xfontsel for example) seem to pass clicks on their * window to the parent window, thus we receive an event here which in @@ -331,12 +338,12 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ event->event_x <= (client->child_rect.x + client->child_rect.width) && event->event_y >= client->child_rect.y && event->event_y <= (client->child_rect.y + client->child_rect.height)) { - LOG("Fixing border_click = false because of click in child\n"); + DLOG("Fixing border_click = false because of click in child\n"); border_click = false; } if (!border_click) { - LOG("client. done.\n"); + DLOG("client. done.\n"); xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); /* Floating clients should be raised on click */ if (client_is_floating(client)) @@ -348,7 +355,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ /* Don’t handle events inside the titlebar, only borders are interesting */ i3Font *font = load_font(conn, config.font); if (event->event_y >= 2 && event->event_y <= (font->height + 2 + 2)) { - LOG("click on titlebar\n"); + DLOG("click on titlebar\n"); /* Floating clients can be dragged by grabbing their titlebar */ if (client_is_floating(client)) { @@ -392,7 +399,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ } else if (event->event_x > 2) { /* …right border */ first = con->col + (con->colspan - 1); - LOG("column %d\n", first); + DLOG("column %d\n", first); if (!cell_exists(ws, first, con->row) || (first == (ws->cols-1))) diff --git a/src/client.c b/src/client.c index f8303ea4..9c136ca6 100644 --- a/src/client.c +++ b/src/client.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -26,6 +27,8 @@ #include "client.h" #include "table.h" #include "workspace.h" +#include "config.h" +#include "log.h" /* * Removes the given client from the container, either because it will be inserted into another @@ -43,7 +46,7 @@ void client_remove_from_container(xcb_connection_t *conn, Client *client, Contai if (CIRCLEQ_EMPTY(&(container->clients)) && (container->mode == MODE_STACK || container->mode == MODE_TABBED)) { - LOG("Unmapping stack window\n"); + DLOG("Unmapping stack window\n"); struct Stack_Window *stack_win = &(container->stack_win); stack_win->rect.height = 0; xcb_unmap_window(conn, stack_win->window); @@ -150,44 +153,82 @@ bool client_matches_class_name(Client *client, char *to_class, char *to_title, * and when moving a fullscreen client to another screen. * */ -void client_enter_fullscreen(xcb_connection_t *conn, Client *client) { - Workspace *workspace = client->workspace; +void client_enter_fullscreen(xcb_connection_t *conn, Client *client, bool global) { + Workspace *workspace; + Output *output; + Rect r; - if (workspace->fullscreen_client != NULL) { - LOG("Not entering fullscreen mode, there already is a fullscreen client.\n"); - return; + if (global) { + TAILQ_FOREACH(output, &outputs, outputs) { + if (!output->active) + continue; + + if (output->current_workspace->fullscreen_client == NULL) + continue; + + LOG("Not entering global fullscreen mode, there already " + "is a fullscreen client on output %s.\n", output->name); + return; + } + + r = (Rect) { UINT_MAX, UINT_MAX, 0,0 }; + Output *output; + + /* Set fullscreen_client for each active workspace. + * Expand the rectangle to contain all outputs. */ + TAILQ_FOREACH(output, &outputs, outputs) { + if (!output->active) + continue; + + output->current_workspace->fullscreen_client = client; + + /* Temporarily abuse width/heigth as coordinates of the lower right corner */ + if (r.x > output->rect.x) + r.x = output->rect.x; + if (r.y > output->rect.y) + r.y = output->rect.y; + if (r.x + r.width < output->rect.x + output->rect.width) + r.width = output->rect.x + output->rect.width; + if (r.y + r.height < output->rect.y + output->rect.height) + r.height = output->rect.y + output->rect.height; + } + + /* Putting them back to their original meaning */ + r.height -= r.x; + r.width -= r.y; + + LOG("Entering global fullscreen mode...\n"); + } else { + workspace = client->workspace; + if (workspace->fullscreen_client != NULL && workspace->fullscreen_client != client) { + LOG("Not entering fullscreen mode, there already is a fullscreen client.\n"); + return; + } + + workspace->fullscreen_client = client; + r = workspace->rect; + + LOG("Entering fullscreen mode...\n"); } client->fullscreen = true; - workspace->fullscreen_client = client; - LOG("Entering fullscreen mode...\n"); + /* We just entered fullscreen mode, let’s configure the window */ - uint32_t mask = XCB_CONFIG_WINDOW_X | - XCB_CONFIG_WINDOW_Y | - XCB_CONFIG_WINDOW_WIDTH | - XCB_CONFIG_WINDOW_HEIGHT; - uint32_t values[4] = {workspace->rect.x, - workspace->rect.y, - workspace->rect.width, - workspace->rect.height}; + DLOG("child itself will be at %dx%d with size %dx%d\n", + r.x, r.y, r.width, r.height); - LOG("child itself will be at %dx%d with size %dx%d\n", - values[0], values[1], values[2], values[3]); - - xcb_configure_window(conn, client->frame, mask, values); + xcb_set_window_rect(conn, client->frame, r); /* Child’s coordinates are relative to the parent (=frame) */ - values[0] = 0; - values[1] = 0; - xcb_configure_window(conn, client->child, mask, values); + r.x = 0; + r.y = 0; + xcb_set_window_rect(conn, client->child, r); /* Raise the window */ - values[0] = XCB_STACK_MODE_ABOVE; + uint32_t values[] = { XCB_STACK_MODE_ABOVE }; xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values); - Rect child_rect = workspace->rect; - child_rect.x = child_rect.y = 0; - fake_configure_notify(conn, child_rect, client->child); + fake_configure_notify(conn, r, client->child); xcb_flush(conn); } @@ -234,34 +275,26 @@ void client_toggle_fullscreen(xcb_connection_t *conn, Client *client) { /* dock clients cannot enter fullscreen mode */ assert(!client->dock); - Workspace *workspace = client->workspace; + if (!client->fullscreen) { + client_enter_fullscreen(conn, client, false); + } else { + client_leave_fullscreen(conn, client); + } +} + +/* + * Like client_toggle_fullscreen(), but putting it in global fullscreen-mode. + * + */ +void client_toggle_fullscreen_global(xcb_connection_t *conn, Client *client) { + /* dock clients cannot enter fullscreen mode */ + assert(!client->dock); if (!client->fullscreen) { - client_enter_fullscreen(conn, client); - return; - } - - LOG("leaving fullscreen mode\n"); - client->fullscreen = false; - workspace->fullscreen_client = NULL; - if (client_is_floating(client)) { - /* For floating clients it’s enough if we just reconfigure that window (in fact, - * re-rendering the layout will not update the client.) */ - reposition_client(conn, client); - resize_client(conn, client); - /* redecorate_window flushes */ - redecorate_window(conn, client); + client_enter_fullscreen(conn, client, true); } else { - client_set_below_floating(conn, client); - - /* Because the coordinates of the window haven’t changed, it would not be - re-configured if we don’t set the following flag */ - client->force_reconfigure = true; - /* We left fullscreen mode, redraw the whole layout to ensure enternotify events are disabled */ - render_layout(conn); + client_leave_fullscreen(conn, client); } - - xcb_flush(conn); } /* @@ -277,14 +310,14 @@ void client_set_below_floating(xcb_connection_t *conn, Client *client) { if (first_floating == TAILQ_END(&(ws->floating_clients))) return; - LOG("Setting below floating\n"); + DLOG("Setting below floating\n"); uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW }; xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); if (client->workspace->fullscreen_client == NULL) return; - LOG("(and below fullscreen)\n"); + DLOG("(and below fullscreen)\n"); /* Ensure that the window is still below the fullscreen window */ values[0] = client->workspace->fullscreen_client->frame; xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); @@ -407,3 +440,38 @@ void client_mark(xcb_connection_t *conn, Client *client, const char *mark) { break; } } + +/* + * Returns the minimum height of a specific window. The height is calculated + * by using 2 pixels (for the client window itself), possibly padding this to + * comply with the client’s base_height and then adding the decoration height. + * + */ +uint32_t client_min_height(Client *client) { + uint32_t height = max(2, client->base_height); + i3Font *font = load_font(global_conn, config.font); + + if (client->titlebar_position == TITLEBAR_OFF && client->borderless) + return height; + + if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) + return height + 2; + + return height + font->height + 2 + 2; +} + +/* + * See client_min_height. + * + */ +uint32_t client_min_width(Client *client) { + uint32_t width = max(2, client->base_width); + + if (client->titlebar_position == TITLEBAR_OFF && client->borderless) + return width; + + if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) + return width + 2; + + return width + 2 + 2; +} diff --git a/src/commands.c b/src/commands.c index 9ad66c0e..b2649bee 100644 --- a/src/commands.c +++ b/src/commands.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -22,7 +22,7 @@ #include "table.h" #include "layout.h" #include "i3.h" -#include "xinerama.h" +#include "randr.h" #include "client.h" #include "floating.h" #include "xcb.h" @@ -30,6 +30,10 @@ #include "workspace.h" #include "commands.h" #include "resize.h" +#include "log.h" +#include "sighandler.h" +#include "manage.h" +#include "ipc.h" bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) { /* If this container is empty, we’re done */ @@ -45,7 +49,7 @@ bool focus_window_in_container(xcb_connection_t *conn, Container *container, dir else if (direction == D_DOWN) { if ((candidate = CIRCLEQ_NEXT_OR_NULL(&(container->clients), container->currently_focused, clients)) == NULL) candidate = CIRCLEQ_FIRST(&(container->clients)); - } else LOG("Direction not implemented!\n"); + } else ELOG("Direction not implemented!\n"); /* If we could not switch, the container contains exactly one client. We return false */ if (candidate == container->currently_focused) @@ -69,16 +73,16 @@ static void jump_to_mark(xcb_connection_t *conn, const char *mark) { if (current->mark == NULL || strcmp(current->mark, mark) != 0) continue; - workspace_show(conn, current->workspace->num + 1); set_focus(conn, current, true); + workspace_show(conn, current->workspace->num + 1); return; } - LOG("No window with this mark found\n"); + ELOG("No window with this mark found\n"); } static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t thing) { - LOG("focusing direction %d\n", direction); + DLOG("focusing direction %d\n", direction); int new_row = current_row, new_col = current_col; @@ -86,19 +90,20 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t Workspace *t_ws = c_ws; /* Makes sure new_col and new_row are within bounds of the new workspace */ - void check_colrow_boundaries() { - if (new_col >= t_ws->cols) - new_col = (t_ws->cols - 1); - if (new_row >= t_ws->rows) - new_row = (t_ws->rows - 1); - } +#define CHECK_COLROW_BOUNDARIES \ + do { \ + if (new_col >= t_ws->cols) \ + new_col = (t_ws->cols - 1); \ + if (new_row >= t_ws->rows) \ + new_row = (t_ws->rows - 1); \ + } while (0) /* There always is a container. If not, current_col or current_row is wrong */ assert(container != NULL); if (container->workspace->fullscreen_client != NULL) { - LOG("You're in fullscreen mode. Won't switch focus\n"); - return; + LOG("You're in fullscreen mode. Forcing focus to operate on whole screens\n"); + thing = THING_SCREEN; } /* For focusing screens, situation is different: we get the rect @@ -106,7 +111,7 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t * right/left/bottom/top and just switch to the workspace on * the target screen. */ if (thing == THING_SCREEN) { - i3Screen *cs = c_ws->screen; + Output *cs = c_ws->output; assert(cs != NULL); Rect bounds = cs->rect; @@ -118,20 +123,20 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t bounds.y -= bounds.height; else bounds.y += bounds.height; - i3Screen *target = get_screen_containing(bounds.x, bounds.y); + Output *target = get_output_containing(bounds.x, bounds.y); if (target == NULL) { - LOG("Target screen NULL\n"); + DLOG("Target output NULL\n"); /* Wrap around if the target screen is out of bounds */ if (direction == D_RIGHT) - target = get_screen_most(D_LEFT, cs); + target = get_output_most(D_LEFT, cs); else if (direction == D_LEFT) - target = get_screen_most(D_RIGHT, cs); + target = get_output_most(D_RIGHT, cs); else if (direction == D_UP) - target = get_screen_most(D_DOWN, cs); - else target = get_screen_most(D_UP, cs); + target = get_output_most(D_DOWN, cs); + else target = get_output_most(D_UP, cs); } - LOG("Switching to ws %d\n", target->current_workspace + 1); + DLOG("Switching to ws %d\n", target->current_workspace + 1); workspace_show(conn, target->current_workspace->num + 1); return; } @@ -159,31 +164,48 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t } } else { /* Let’s see if there is a screen down/up there to which we can switch */ - LOG("container is at %d with height %d\n", container->y, container->height); - i3Screen *screen; + DLOG("container is at %d with height %d\n", container->y, container->height); + Output *output; int destination_y = (direction == D_UP ? (container->y - 1) : (container->y + container->height + 1)); - if ((screen = get_screen_containing(container->x, destination_y)) == NULL) { - LOG("Wrapping screen around vertically\n"); + if ((output = get_output_containing(container->x, destination_y)) == NULL) { + DLOG("Wrapping screen around vertically\n"); /* No screen found? Then wrap */ - screen = get_screen_most((direction == D_UP ? D_DOWN : D_UP), container->workspace->screen); + output = get_output_most((direction == D_UP ? D_DOWN : D_UP), container->workspace->output); } - t_ws = screen->current_workspace; + t_ws = output->current_workspace; new_row = (direction == D_UP ? (t_ws->rows - 1) : 0); } - check_colrow_boundaries(); + CHECK_COLROW_BOUNDARIES; - LOG("new_col = %d, new_row = %d\n", new_col, new_row); + DLOG("new_col = %d, new_row = %d\n", new_col, new_row); if (t_ws->table[new_col][new_row]->currently_focused == NULL) { - LOG("Cell empty, checking for colspanned client above...\n"); + DLOG("Cell empty, checking for colspanned client above...\n"); for (int cols = 0; cols < new_col; cols += t_ws->table[cols][new_row]->colspan) { if (new_col > (cols + (t_ws->table[cols][new_row]->colspan - 1))) continue; new_col = cols; + DLOG("Fixed it to new col %d\n", new_col); + break; + } + } + + if (t_ws->table[new_col][new_row]->currently_focused == NULL) { + DLOG("Cell still empty, checking for full cols above spanned width...\n"); + DLOG("new_col = %d\n", new_col); + DLOG("colspan = %d\n", container->colspan); + for (int cols = new_col; + cols < container->col + container->colspan; + cols += t_ws->table[cols][new_row]->colspan) { + DLOG("candidate: new_row = %d, cols = %d\n", new_row, cols); + if (t_ws->table[cols][new_row]->currently_focused == NULL) + continue; + + new_col = cols; + DLOG("Fixed it to new col %d\n", new_col); break; } - LOG("Fixed it to new col %d\n", new_col); } } else if (direction == D_LEFT || direction == D_RIGHT) { if (direction == D_RIGHT && cell_exists(t_ws, current_col+1, current_row)) @@ -202,37 +224,55 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t } } else { /* Let’s see if there is a screen left/right here to which we can switch */ - LOG("container is at %d with width %d\n", container->x, container->width); - i3Screen *screen; + DLOG("container is at %d with width %d\n", container->x, container->width); + Output *output; int destination_x = (direction == D_LEFT ? (container->x - 1) : (container->x + container->width + 1)); - if ((screen = get_screen_containing(destination_x, container->y)) == NULL) { - LOG("Wrapping screen around horizontally\n"); - screen = get_screen_most((direction == D_LEFT ? D_RIGHT : D_LEFT), container->workspace->screen); + if ((output = get_output_containing(destination_x, container->y)) == NULL) { + DLOG("Wrapping screen around horizontally\n"); + output = get_output_most((direction == D_LEFT ? D_RIGHT : D_LEFT), container->workspace->output); } - t_ws = screen->current_workspace; + t_ws = output->current_workspace; new_col = (direction == D_LEFT ? (t_ws->cols - 1) : 0); } - check_colrow_boundaries(); + CHECK_COLROW_BOUNDARIES; - LOG("new_col = %d, new_row = %d\n", new_col, new_row); + DLOG("new_col = %d, new_row = %d\n", new_col, new_row); if (t_ws->table[new_col][new_row]->currently_focused == NULL) { - LOG("Cell empty, checking for rowspanned client above...\n"); + DLOG("Cell empty, checking for rowspanned client above...\n"); for (int rows = 0; rows < new_row; rows += t_ws->table[new_col][rows]->rowspan) { if (new_row > (rows + (t_ws->table[new_col][rows]->rowspan - 1))) continue; new_row = rows; + DLOG("Fixed it to new row %d\n", new_row); break; } - LOG("Fixed it to new row %d\n", new_row); } + + if (t_ws->table[new_col][new_row]->currently_focused == NULL) { + DLOG("Cell still empty, checking for full cols near full spanned height...\n"); + DLOG("new_row = %d\n", new_row); + DLOG("rowspan = %d\n", container->rowspan); + for (int rows = new_row; + rows < container->row + container->rowspan; + rows += t_ws->table[new_col][rows]->rowspan) { + DLOG("candidate: new_col = %d, rows = %d\n", new_col, rows); + if (t_ws->table[new_col][rows]->currently_focused == NULL) + continue; + + new_row = rows; + DLOG("Fixed it to new col %d\n", new_row); + break; + } + } + } else { - LOG("direction unhandled\n"); + ELOG("direction unhandled\n"); return; } - check_colrow_boundaries(); + CHECK_COLROW_BOUNDARIES; if (t_ws->table[new_col][new_row]->currently_focused != NULL) set_focus(conn, t_ws->table[new_col][new_row]->currently_focused, true); @@ -254,7 +294,7 @@ static bool move_current_window_in_container(xcb_connection_t *conn, Client *cli if (other == CIRCLEQ_END(&(client->container->clients))) return false; - LOG("i can do that\n"); + DLOG("i can do that\n"); /* We can move the client inside its current container */ CIRCLEQ_REMOVE(&(client->container->clients), client, clients); if (direction == D_UP) @@ -357,7 +397,7 @@ static void move_current_window(xcb_connection_t *conn, direction_t direction) { /* Fix colspan/rowspan if it’d overlap */ fix_colrowspan(conn, workspace); - render_workspace(conn, workspace->screen, workspace); + render_workspace(conn, workspace->output, workspace); xcb_flush(conn); set_focus(conn, current_client, true); @@ -411,7 +451,7 @@ static void move_current_container(xcb_connection_t *conn, direction_t direction return; } - LOG("old = %d,%d and new = %d,%d\n", container->col, container->row, new->col, new->row); + DLOG("old = %d,%d and new = %d,%d\n", container->col, container->row, new->col, new->row); /* Swap the containers */ int col = new->col; @@ -453,7 +493,7 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction /* Snap to the left is actually a move to the left and then a snap right */ if (!cell_exists(container->workspace, container->col - 1, container->row) || CUR_TABLE[container->col-1][container->row]->currently_focused != NULL) { - LOG("cannot snap to left - the cell is already used\n"); + ELOG("cannot snap to left - the cell is already used\n"); return; } @@ -466,18 +506,18 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction for (int i = 0; i < container->rowspan; i++) if (!cell_exists(container->workspace, new_col, container->row + i) || CUR_TABLE[new_col][container->row + i]->currently_focused != NULL) { - LOG("cannot snap to right - the cell is already used\n"); + ELOG("cannot snap to right - the cell is already used\n"); return; } /* Check if there are other cells with rowspan, which are in our way. * If so, reduce their rowspan. */ for (int i = container->row-1; i >= 0; i--) { - LOG("we got cell %d, %d with rowspan %d\n", + DLOG("we got cell %d, %d with rowspan %d\n", new_col, i, CUR_TABLE[new_col][i]->rowspan); while ((CUR_TABLE[new_col][i]->rowspan-1) >= (container->row - i)) CUR_TABLE[new_col][i]->rowspan--; - LOG("new rowspan = %d\n", CUR_TABLE[new_col][i]->rowspan); + DLOG("new rowspan = %d\n", CUR_TABLE[new_col][i]->rowspan); } container->colspan++; @@ -486,7 +526,7 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction case D_UP: if (!cell_exists(container->workspace, container->col, container->row - 1) || CUR_TABLE[container->col][container->row-1]->currently_focused != NULL) { - LOG("cannot snap to top - the cell is already used\n"); + ELOG("cannot snap to top - the cell is already used\n"); return; } @@ -494,21 +534,21 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction snap_current_container(conn, D_DOWN); return; case D_DOWN: { - LOG("snapping down\n"); + DLOG("snapping down\n"); int new_row = container->row + container->rowspan; for (int i = 0; i < container->colspan; i++) if (!cell_exists(container->workspace, container->col + i, new_row) || CUR_TABLE[container->col + i][new_row]->currently_focused != NULL) { - LOG("cannot snap down - the cell is already used\n"); + ELOG("cannot snap down - the cell is already used\n"); return; } for (int i = container->col-1; i >= 0; i--) { - LOG("we got cell %d, %d with colspan %d\n", + DLOG("we got cell %d, %d with colspan %d\n", i, new_row, CUR_TABLE[i][new_row]->colspan); while ((CUR_TABLE[i][new_row]->colspan-1) >= (container->col - i)) CUR_TABLE[i][new_row]->colspan--; - LOG("new colspan = %d\n", CUR_TABLE[i][new_row]->colspan); + DLOG("new colspan = %d\n", CUR_TABLE[i][new_row]->colspan); } @@ -530,12 +570,12 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl LOG("moving floating\n"); - workspace_initialize(t_ws, c_ws->screen); + workspace_initialize(t_ws, c_ws->output, false); /* Check if there is already a fullscreen client on the destination workspace and * stop moving if so. */ if (client->fullscreen && (t_ws->fullscreen_client != NULL)) { - LOG("Not moving: Fullscreen client already existing on destination workspace.\n"); + ELOG("Not moving: Fullscreen client already existing on destination workspace.\n"); return; } @@ -543,21 +583,26 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl /* If we’re moving it to an invisible screen, we need to unmap it */ if (!workspace_is_visible(t_ws)) { - LOG("This workspace is not visible, unmapping\n"); + DLOG("This workspace is not visible, unmapping\n"); client_unmap(conn, client); } else { /* If this is not the case, we move the window to a workspace * which is on another screen, so we also need to adjust its * coordinates. */ - LOG("before x = %d, y = %d\n", client->rect.x, client->rect.y); + DLOG("before x = %d, y = %d\n", client->rect.x, client->rect.y); uint32_t relative_x = client->rect.x - old_ws->rect.x, relative_y = client->rect.y - old_ws->rect.y; - LOG("rel_x = %d, rel_y = %d\n", relative_x, relative_y); - client->rect.x = t_ws->rect.x + relative_x; - client->rect.y = t_ws->rect.y + relative_y; - LOG("after x = %d, y = %d\n", client->rect.x, client->rect.y); - reposition_client(conn, client); - xcb_flush(conn); + DLOG("rel_x = %d, rel_y = %d\n", relative_x, relative_y); + if (client->fullscreen) { + client_enter_fullscreen(conn, client, false); + memcpy(&(client->rect), &(t_ws->rect), sizeof(Rect)); + } else { + client->rect.x = t_ws->rect.x + relative_x; + client->rect.y = t_ws->rect.y + relative_y; + DLOG("after x = %d, y = %d\n", client->rect.x, client->rect.y); + reposition_client(conn, client); + xcb_flush(conn); + } } /* Configure the window above all tiling windows (or below a fullscreen @@ -576,12 +621,14 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl } } - LOG("done\n"); + DLOG("done\n"); render_layout(conn); - if (workspace_is_visible(t_ws)) + if (workspace_is_visible(t_ws)) { + client_warp_pointer_into(conn, client); set_focus(conn, client, true); + } } /* @@ -600,18 +647,18 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa Client *current_client = container->currently_focused; if (current_client == NULL) { - LOG("No currently focused client in current container.\n"); + ELOG("No currently focused client in current container.\n"); return; } Client *to_focus = CIRCLEQ_NEXT_OR_NULL(&(container->clients), current_client, clients); if (to_focus == NULL) to_focus = CIRCLEQ_PREV_OR_NULL(&(container->clients), current_client, clients); - workspace_initialize(t_ws, container->workspace->screen); + workspace_initialize(t_ws, container->workspace->output, false); /* Check if there is already a fullscreen client on the destination workspace and * stop moving if so. */ if (current_client->fullscreen && (t_ws->fullscreen_client != NULL)) { - LOG("Not moving: Fullscreen client already existing on destination workspace.\n"); + ELOG("Not moving: Fullscreen client already existing on destination workspace.\n"); return; } @@ -627,7 +674,7 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa CIRCLEQ_INSERT_TAIL(&(to_container->clients), current_client, clients); SLIST_INSERT_HEAD(&(to_container->workspace->focus_stack), current_client, focus_clients); - LOG("Moved.\n"); + DLOG("Moved.\n"); current_client->container = to_container; current_client->workspace = to_container->workspace; @@ -636,12 +683,12 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa /* If we’re moving it to an invisible screen, we need to unmap it */ if (!workspace_is_visible(to_container->workspace)) { - LOG("This workspace is not visible, unmapping\n"); + DLOG("This workspace is not visible, unmapping\n"); client_unmap(conn, current_client); } else { if (current_client->fullscreen) { - LOG("Calling client_enter_fullscreen again\n"); - client_enter_fullscreen(conn, current_client); + DLOG("Calling client_enter_fullscreen again\n"); + client_enter_fullscreen(conn, current_client, false); } } @@ -650,8 +697,10 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa render_layout(conn); - if (workspace_is_visible(to_container->workspace)) + if (workspace_is_visible(to_container->workspace)) { + client_warp_pointer_into(conn, current_client); set_focus(conn, current_client, true); + } } /* @@ -672,11 +721,12 @@ static void jump_to_window(xcb_connection_t *conn, const char *arguments) { if ((client = get_matching_client(conn, classtitle, NULL)) == NULL) { free(classtitle); - LOG("No matching client found.\n"); + ELOG("No matching client found.\n"); return; } free(classtitle); + workspace_show(conn, client->workspace->num + 1); set_focus(conn, client, true); } @@ -694,7 +744,7 @@ static void jump_to_container(xcb_connection_t *conn, const char *arguments) { /* No match? Either no arguments were specified, or no numbers */ if (result < 1) { - LOG("At least one valid argument required\n"); + ELOG("At least one valid argument required\n"); return; } @@ -704,7 +754,7 @@ static void jump_to_container(xcb_connection_t *conn, const char *arguments) { if (result < 3) return; - LOG("Boundary-checking col %d, row %d... (max cols %d, max rows %d)\n", col, row, c_ws->cols, c_ws->rows); + DLOG("Boundary-checking col %d, row %d... (max cols %d, max rows %d)\n", col, row, c_ws->cols, c_ws->rows); /* Move to row/col */ if (row >= c_ws->rows) @@ -712,7 +762,7 @@ static void jump_to_container(xcb_connection_t *conn, const char *arguments) { if (col >= c_ws->cols) col = c_ws->cols - 1; - LOG("Jumping to col %d, row %d\n", col, row); + DLOG("Jumping to col %d, row %d\n", col, row); if (c_ws->table[col][row]->currently_focused != NULL) set_focus(conn, c_ws->table[col][row]->currently_focused, true); } @@ -741,7 +791,7 @@ static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) { } else if (strcasecmp(arguments, "ft") == 0) { Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); if (last_focused == SLIST_END(&(c_ws->focus_stack))) { - LOG("Cannot select the next floating/tiling client because there is no client at all\n"); + ELOG("Cannot select the next floating/tiling client because there is no client at all\n"); return; } @@ -749,17 +799,17 @@ static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) { } else { /* …or a number was specified */ if (sscanf(arguments, "%u", ×) != 1) { - LOG("No or invalid argument given (\"%s\"), using default of 1 times\n", arguments); + ELOG("No or invalid argument given (\"%s\"), using default of 1 times\n", arguments); times = 1; } SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients) { if (++count < times) { - LOG("Skipping\n"); + DLOG("Skipping\n"); continue; } - LOG("Focussing\n"); + DLOG("Focussing\n"); set_focus(conn, current, true); break; } @@ -774,29 +824,6 @@ static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) { } } -/* - * Goes through the list of arguments (for exec()) and checks if the given argument - * is present. If not, it copies the arguments (because we cannot realloc it) and - * appends the given argument. - * - */ -static char **append_argument(char **original, char *argument) { - int num_args; - for (num_args = 0; original[num_args] != NULL; num_args++) { - LOG("original argument: \"%s\"\n", original[num_args]); - /* If the argument is already present we return the original pointer */ - if (strcmp(original[num_args], argument) == 0) - return original; - } - /* Copy the original array */ - char **result = smalloc((num_args+2) * sizeof(char*)); - memcpy(result, original, num_args * sizeof(char*)); - result[num_args] = argument; - result[num_args+1] = NULL; - - return result; -} - /* * Switch to next or previous existing workspace * @@ -805,16 +832,32 @@ static void next_previous_workspace(xcb_connection_t *conn, int direction) { Workspace *ws = c_ws; if (direction == 'n') { - while ((ws = TAILQ_NEXT(ws, workspaces)) != TAILQ_END(workspaces_head)) { - if (ws->screen == NULL) + while (1) { + ws = TAILQ_NEXT(ws, workspaces); + + if (ws == TAILQ_END(workspaces)) + ws = TAILQ_FIRST(workspaces); + + if (ws == c_ws) + return; + + if (ws->output == NULL) continue; workspace_show(conn, ws->num + 1); return; } } else if (direction == 'p') { - while ((ws = TAILQ_PREV(ws, workspaces_head, workspaces)) != TAILQ_END(workspaces)) { - if (ws->screen == NULL) + while (1) { + ws = TAILQ_PREV(ws, workspaces_head, workspaces); + + if (ws == TAILQ_END(workspaces)) + ws = TAILQ_LAST(workspaces, workspaces_head); + + if (ws == c_ws) + return; + + if (ws->output == NULL) continue; workspace_show(conn, ws->num + 1); @@ -827,7 +870,34 @@ static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, c int first, second; resize_orientation_t orientation = O_VERTICAL; Container *con = last_focused->container; - Workspace *ws = con->workspace; + Workspace *ws = last_focused->workspace; + + if (client_is_floating(last_focused)) { + DLOG("Resizing a floating client\n"); + if (STARTS_WITH(command, "left")) { + command += strlen("left"); + last_focused->rect.width += atoi(command); + last_focused->rect.x -= atoi(command); + } else if (STARTS_WITH(command, "right")) { + command += strlen("right"); + last_focused->rect.width += atoi(command); + } else if (STARTS_WITH(command, "top")) { + command += strlen("top"); + last_focused->rect.height += atoi(command); + last_focused->rect.y -= atoi(command); + } else if (STARTS_WITH(command, "bottom")) { + command += strlen("bottom"); + last_focused->rect.height += atoi(command); + } else { + ELOG("Syntax: resize [+|-]\n"); + return; + } + + /* resize_client flushes */ + resize_client(conn, last_focused); + + return; + } if (STARTS_WITH(command, "left")) { if (con->col == 0) @@ -837,7 +907,7 @@ static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, c command += strlen("left"); } else if (STARTS_WITH(command, "right")) { first = con->col + (con->colspan - 1); - LOG("column %d\n", first); + DLOG("column %d\n", first); if (!cell_exists(ws, first, con->row) || (first == (ws->cols-1))) @@ -862,7 +932,7 @@ static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, c orientation = O_HORIZONTAL; command += strlen("bottom"); } else { - LOG("Syntax: resize [+|-]\n"); + ELOG("Syntax: resize [+|-]\n"); return; } @@ -898,14 +968,14 @@ void parse_command(xcb_connection_t *conn, const char *command) { if (STARTS_WITH(command, "mark")) { if (last_focused == NULL) { - LOG("There is no window to mark\n"); + ELOG("There is no window to mark\n"); return; } const char *rest = command + strlen("mark"); while (*rest == ' ') rest++; if (*rest == '\0') { - LOG("interactive mark starting\n"); + DLOG("interactive mark starting\n"); start_application("i3-input -p 'mark ' -l 1 -P 'Mark: '"); } else { LOG("mark with \"%s\"\n", rest); @@ -919,7 +989,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { while (*rest == ' ') rest++; if (*rest == '\0') { - LOG("interactive go to mark starting\n"); + DLOG("interactive go to mark starting\n"); start_application("i3-input -p 'goto ' -l 1 -P 'Goto: '"); } else { LOG("go to \"%s\"\n", rest); @@ -930,7 +1000,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { if (STARTS_WITH(command, "stack-limit ")) { if (last_focused == NULL || client_is_floating(last_focused)) { - LOG("No container focused\n"); + ELOG("No container focused\n"); return; } const char *rest = command + strlen("stack-limit "); @@ -941,7 +1011,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { last_focused->container->stack_limit = STACK_LIMIT_COLS; rest += strlen("cols "); } else { - LOG("Syntax: stack-limit \n"); + ELOG("Syntax: stack-limit \n"); return; } @@ -969,28 +1039,28 @@ void parse_command(xcb_connection_t *conn, const char *command) { /* Is it an ? */ if (STARTS_WITH(command, "exit")) { LOG("User issued exit-command, exiting without error.\n"); + restore_geometry(global_conn); + ipc_shutdown(); exit(EXIT_SUCCESS); } /* Is it a ? */ if (STARTS_WITH(command, "reload")) { load_configuration(conn, NULL, true); + render_layout(conn); + /* Send an IPC event just in case the ws names have changed */ + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"reload\"}"); return; } /* Is it ? Then restart in place. */ if (STARTS_WITH(command, "restart")) { - LOG("restarting \"%s\"...\n", start_argv[0]); - /* make sure -a is in the argument list or append it */ - start_argv = append_argument(start_argv, "-a"); - - execvp(start_argv[0], start_argv); - /* not reached */ + i3_restart(); } if (STARTS_WITH(command, "kill")) { if (last_focused == NULL) { - LOG("There is no window to kill\n"); + ELOG("There is no window to kill\n"); return; } @@ -1015,25 +1085,28 @@ void parse_command(xcb_connection_t *conn, const char *command) { return; } - /* Is it 'f' for fullscreen? */ + /* Is it 'f' for fullscreen, or 'fg' for fullscreen_global? */ if (command[0] == 'f') { if (last_focused == NULL) return; - client_toggle_fullscreen(conn, last_focused); + if (command[1] == 'g') + client_toggle_fullscreen_global(conn, last_focused); + else + client_toggle_fullscreen(conn, last_focused); return; } /* Is it just 's' for stacking or 'd' for default? */ if ((command[0] == 's' || command[0] == 'd' || command[0] == 'T') && (command[1] == '\0')) { if (last_focused != NULL && client_is_floating(last_focused)) { - LOG("not switching, this is a floating client\n"); + ELOG("not switching, this is a floating client\n"); return; } LOG("Switching mode for current container\n"); int new_mode = MODE_DEFAULT; - if (command[0] == 's') + if (command[0] == 's' && CUR_CELL->mode != MODE_STACK) new_mode = MODE_STACK; - if (command[0] == 'T') + if (command[0] == 'T' && CUR_CELL->mode != MODE_TABBED) new_mode = MODE_TABBED; switch_layout_mode(conn, CUR_CELL, new_mode); return; @@ -1043,7 +1116,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { /* or even 'bt' (toggle border: 'bp' -> 'bb' -> 'bn' ) */ if (command[0] == 'b') { if (last_focused == NULL) { - LOG("No window focused, cannot change border type\n"); + ELOG("No window focused, cannot change border type\n"); return; } @@ -1084,7 +1157,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { with = WITH_SCREEN; command++; } else { - LOG("not yet implemented.\n"); + ELOG("not yet implemented.\n"); return; } } @@ -1097,7 +1170,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { return; } if (last_focused == NULL) { - LOG("Cannot toggle tiling/floating: workspace empty\n"); + ELOG("Cannot toggle tiling/floating: workspace empty\n"); return; } @@ -1113,7 +1186,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { /* Fix colspan/rowspan if it’d overlap */ fix_colrowspan(conn, ws); - render_workspace(conn, ws->screen, ws); + render_workspace(conn, ws->output, ws); /* Re-focus the client because cleanup_table sets the focus to the last * focused client inside a container only. */ @@ -1134,7 +1207,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { direction_t direction; int times = strtol(command, &rest, 10); if (rest == NULL) { - LOG("Invalid command (\"%s\")\n", command); + ELOG("Invalid command (\"%s\")\n", command); return; } @@ -1152,7 +1225,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { int workspace = strtol(rest, &rest, 10); if (rest == NULL) { - LOG("Invalid command (\"%s\")\n", command); + ELOG("Invalid command (\"%s\")\n", command); return; } @@ -1164,13 +1237,13 @@ void parse_command(xcb_connection_t *conn, const char *command) { } if (last_focused == NULL) { - LOG("Not performing (no window found)\n"); + ELOG("Not performing (no window found)\n"); return; } if (client_is_floating(last_focused) && (action != ACTION_FOCUS && action != ACTION_MOVE)) { - LOG("Not performing (floating)\n"); + ELOG("Not performing (floating)\n"); return; } @@ -1185,7 +1258,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { else if (*rest == 'l') direction = D_RIGHT; else { - LOG("unknown direction: %c\n", *rest); + ELOG("unknown direction: %c\n", *rest); return; } rest++; @@ -1208,7 +1281,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { /* TODO: this should swap the screen’s contents * (e.g. all workspaces) with the next/previous/… * screen */ - LOG("Not yet implemented\n"); + ELOG("Not yet implemented\n"); continue; } if (client_is_floating(last_focused)) { @@ -1223,7 +1296,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { if (action == ACTION_SNAP) { if (with == WITH_SCREEN) { - LOG("You cannot snap a screen (it makes no sense).\n"); + ELOG("You cannot snap a screen (it makes no sense).\n"); continue; } snap_current_container(conn, direction); diff --git a/src/config.c b/src/config.c index 36760f9b..972e376c 100644 --- a/src/config.c +++ b/src/config.c @@ -3,16 +3,23 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * + * src/config.c: Contains all functions handling the configuration file (calling + * the parser (src/cfgparse.y) with the correct path, switching key bindings + * mode). + * */ #include #include #include +#include #include #include +#include +#include /* We need Xlib for XStringToKeysym */ #include @@ -25,48 +32,44 @@ #include "xcb.h" #include "table.h" #include "workspace.h" - -/* prototype for src/cfgparse.y, will be cleaned up as soon as we completely - * switched to the new scanner/parser. */ -void parse_file(const char *f); +#include "log.h" Config config; struct modes_head modes; -bool config_use_lexer = false; - /* * This function resolves ~ in pathnames. * */ -static char *glob_path(const char *path) { +char *glob_path(const char *path) { static glob_t globbuf; if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0) die("glob() failed"); char *result = sstrdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path); globfree(&globbuf); + + /* If the file does not exist yet, we still may need to resolve tilde, + * so call wordexp */ + if (strcmp(result, path) == 0) { + wordexp_t we; + wordexp(path, &we, WRDE_NOCMD); + if (we.we_wordc > 0) { + free(result); + result = sstrdup(we.we_wordv[0]); + } + wordfree(&we); + } + return result; } /* - * This function does a very simple replacement of each instance of key with value. + * Checks if the given path exists by calling stat(). * */ -static void replace_variable(char *buffer, const char *key, const char *value) { - char *pos; - /* To prevent endless recursions when the user makes an error configuring, - * we stop after 100 replacements. That should be vastly more than enough. */ - int c = 0; - while ((pos = strcasestr(buffer, key)) != NULL && c++ < 100) { - char *rest = pos + strlen(key); - *pos = '\0'; - char *replaced; - asprintf(&replaced, "%s%s%s", buffer, value, rest); - /* Hm, this is a bit ugly, but sizeof(buffer) = 4, as it’s just a pointer. - * So we need to hard-code the dimensions here. */ - strncpy(buffer, replaced, 1026); - free(replaced); - } +bool path_exists(const char *path) { + struct stat buf; + return (stat(path, &buf) == 0); } /** @@ -75,57 +78,88 @@ static void replace_variable(char *buffer, const char *key, const char *value) { * */ void ungrab_all_keys(xcb_connection_t *conn) { - LOG("Ungrabbing all keys\n"); + DLOG("Ungrabbing all keys\n"); xcb_ungrab_key(conn, XCB_GRAB_ANY, root, XCB_BUTTON_MASK_ANY); } static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint32_t keycode) { - LOG("Grabbing %d\n", keycode); - if ((bind->mods & BIND_MODE_SWITCH) != 0) - xcb_grab_key(conn, 0, root, 0, keycode, - XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC); - else { - /* Grab the key in all combinations */ - #define GRAB_KEY(modifier) xcb_grab_key(conn, 0, root, modifier, keycode, \ - XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC) - GRAB_KEY(bind->mods); - GRAB_KEY(bind->mods | xcb_numlock_mask); - GRAB_KEY(bind->mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK); + DLOG("Grabbing %d\n", keycode); + /* Grab the key in all combinations */ + #define GRAB_KEY(modifier) \ + do { \ + xcb_grab_key(conn, 0, root, modifier, keycode, \ + XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); \ + } while (0) + int mods = bind->mods; + if ((bind->mods & BIND_MODE_SWITCH) != 0) { + mods &= ~BIND_MODE_SWITCH; + if (mods == 0) + mods = XCB_MOD_MASK_ANY; } + GRAB_KEY(mods); + GRAB_KEY(mods | xcb_numlock_mask); + GRAB_KEY(mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK); } /* - * Grab the bound keys (tell X to send us keypress events for those keycodes) + * Returns a pointer to the Binding with the specified modifiers and keycode + * or NULL if no such binding exists. * */ -void grab_all_keys(xcb_connection_t *conn) { +Binding *get_binding(uint16_t modifiers, xcb_keycode_t keycode) { + Binding *bind; + + TAILQ_FOREACH(bind, bindings, bindings) { + /* First compare the modifiers */ + if (bind->mods != modifiers) + continue; + + /* If a symbol was specified by the user, we need to look in + * the array of translated keycodes for the event’s keycode */ + if (bind->symbol != NULL) { + if (memmem(bind->translated_to, + bind->number_keycodes * sizeof(xcb_keycode_t), + &keycode, sizeof(xcb_keycode_t)) != NULL) + break; + } else { + /* This case is easier: The user specified a keycode */ + if (bind->keycode == keycode) + break; + } + } + + return (bind == TAILQ_END(bindings) ? NULL : bind); +} + +/* + * Translates keysymbols to keycodes for all bindings which use keysyms. + * + */ +void translate_keysyms() { Binding *bind; TAILQ_FOREACH(bind, bindings, bindings) { - /* The easy case: the user specified a keycode directly. */ - if (bind->keycode > 0) { - grab_keycode_for_binding(conn, bind, bind->keycode); + if (bind->keycode > 0) continue; - } /* We need to translate the symbol to a keycode */ xcb_keysym_t keysym = XStringToKeysym(bind->symbol); if (keysym == NoSymbol) { - LOG("Could not translate string to key symbol: \"%s\"\n", bind->symbol); + ELOG("Could not translate string to key symbol: \"%s\"\n", bind->symbol); continue; } #ifdef OLD_XCB_KEYSYMS_API bind->number_keycodes = 1; xcb_keycode_t code = xcb_key_symbols_get_keycode(keysyms, keysym); - LOG("Translated symbol \"%s\" to 1 keycode (%d)\n", bind->symbol, code); - grab_keycode_for_binding(conn, bind, code); + DLOG("Translated symbol \"%s\" to 1 keycode (%d)\n", bind->symbol, code); + grab_keycode_for_binding(global_conn, bind, code); bind->translated_to = smalloc(sizeof(xcb_keycode_t)); memcpy(bind->translated_to, &code, sizeof(xcb_keycode_t)); #else uint32_t last_keycode = 0; xcb_keycode_t *keycodes = xcb_key_symbols_get_keycode(keysyms, keysym); if (keycodes == NULL) { - LOG("Could not translate symbol \"%s\"\n", bind->symbol); + DLOG("Could not translate symbol \"%s\"\n", bind->symbol); continue; } @@ -136,11 +170,10 @@ void grab_all_keys(xcb_connection_t *conn) { * and skip them */ if (last_keycode == *walk) continue; - grab_keycode_for_binding(conn, bind, *walk); last_keycode = *walk; bind->number_keycodes++; } - LOG("Translated symbol \"%s\" to %d keycode\n", bind->symbol, bind->number_keycodes); + DLOG("Translated symbol \"%s\" to %d keycode\n", bind->symbol, bind->number_keycodes); bind->translated_to = smalloc(bind->number_keycodes * sizeof(xcb_keycode_t)); memcpy(bind->translated_to, keycodes, bind->number_keycodes * sizeof(xcb_keycode_t)); free(keycodes); @@ -148,6 +181,29 @@ void grab_all_keys(xcb_connection_t *conn) { } } +/* + * Grab the bound keys (tell X to send us keypress events for those keycodes) + * + */ +void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch) { + Binding *bind; + TAILQ_FOREACH(bind, bindings, bindings) { + if ((bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) == 0) || + (!bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) != 0)) + continue; + + /* The easy case: the user specified a keycode directly. */ + if (bind->keycode > 0) { + grab_keycode_for_binding(conn, bind, bind->keycode); + continue; + } + + xcb_keycode_t *walk = bind->translated_to; + for (int i = 0; i < bind->number_keycodes; i++) + grab_keycode_for_binding(conn, bind, *walk); + } +} + /* * Switches the key bindings to the given mode, if the mode exists * @@ -163,18 +219,89 @@ void switch_mode(xcb_connection_t *conn, const char *new_mode) { ungrab_all_keys(conn); bindings = mode->bindings; - grab_all_keys(conn); + translate_keysyms(); + grab_all_keys(conn, false); return; } - LOG("ERROR: Mode not found\n"); + ELOG("ERROR: Mode not found\n"); } /* - * Reads the configuration from ~/.i3/config or /etc/i3/config if not found. + * Get the path of the first configuration file found. Checks the XDG folders + * first ($XDG_CONFIG_HOME, $XDG_CONFIG_DIRS), then the traditional paths. * - * If you specify override_configpath, only this path is used to look for a - * configuration file. + */ +static char *get_config_path() { + /* 1: check for $XDG_CONFIG_HOME/i3/config */ + char *xdg_config_home, *xdg_config_dirs, *config_path; + + if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) + xdg_config_home = "~/.config"; + + xdg_config_home = glob_path(xdg_config_home); + if (asprintf(&config_path, "%s/i3/config", xdg_config_home) == -1) + die("asprintf() failed"); + free(xdg_config_home); + + if (path_exists(config_path)) + return config_path; + free(config_path); + + /* 2: check for $XDG_CONFIG_DIRS/i3/config */ + if ((xdg_config_dirs = getenv("XDG_CONFIG_DIRS")) == NULL) + xdg_config_dirs = "/etc/xdg"; + + char *buf = strdup(xdg_config_dirs); + char *tok = strtok(buf, ":"); + while (tok != NULL) { + tok = glob_path(tok); + if (asprintf(&config_path, "%s/i3/config", tok) == -1) + die("asprintf() failed"); + free(tok); + if (path_exists(config_path)) { + free(buf); + return config_path; + } + free(config_path); + tok = strtok(NULL, ":"); + } + free(buf); + + /* 3: check traditional paths */ + config_path = glob_path("~/.i3/config"); + if (path_exists(config_path)) + return config_path; + + config_path = strdup("/etc/i3/config"); + if (!path_exists(config_path)) + die("Neither $XDG_CONFIG_HOME/i3/config, nor " + "$XDG_CONFIG_DIRS/i3/config, nor ~/.i3/config nor " + "/etc/i3/config exist."); + + return config_path; +} + +/* + * Finds the configuration file to use (either the one specified by + * override_configpath), the user’s one or the system default) and calls + * parse_file(). + * + */ +static void parse_configuration(const char *override_configpath) { + if (override_configpath != NULL) { + parse_file(override_configpath); + return; + } + + char *path = get_config_path(); + DLOG("Parsing configfile %s\n", path); + parse_file(path); + free(path); +} + +/* + * (Re-)loads the configuration file (sets useful defaults before). * */ void load_configuration(xcb_connection_t *conn, const char *override_configpath, bool reload) { @@ -208,6 +335,11 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, TAILQ_REMOVE(&assignments, assign, assignments); FREE(assign); } + + /* Clear workspace names */ + Workspace *ws; + TAILQ_FOREACH(ws, workspaces, workspaces) + workspace_set_name(ws, NULL); } SLIST_INIT(&modes); @@ -220,388 +352,36 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, bindings = default_mode->bindings; - SLIST_HEAD(variables_head, Variable) variables; - -#define OPTION_STRING(name) \ - if (strcasecmp(key, #name) == 0) { \ - config.name = sstrdup(value); \ - continue; \ - } - #define REQUIRED_OPTION(name) \ if (config.name == NULL) \ die("You did not specify required configuration option " #name "\n"); -#define OPTION_COLORTRIPLE(opt, name) \ - if (strcasecmp(key, opt) == 0) { \ - char border[8], background[8], text[8]; \ - memset(border, 0, sizeof(border)); \ - memset(background, 0, sizeof(background)); \ - memset(text, 0, sizeof(text)); \ - border[0] = background[0] = text[0] = '#'; \ - if (sscanf(value, "#%06[0-9a-fA-F] #%06[0-9a-fA-F] #%06[0-9a-fA-F]", \ - border + 1, background + 1, text + 1) != 3 || \ - strlen(border) != 7 || \ - strlen(background) != 7 || \ - strlen(text) != 7) \ - die("invalid color code line: %s\n", value); \ - config.name.border = get_colorpixel(conn, border); \ - config.name.background = get_colorpixel(conn, background); \ - config.name.text = get_colorpixel(conn, text); \ - continue; \ - } - /* Clear the old config or initialize the data structure */ memset(&config, 0, sizeof(config)); - SLIST_INIT(&variables); - /* Initialize default colors */ - config.client.focused.border = get_colorpixel(conn, "#4c7899"); - config.client.focused.background = get_colorpixel(conn, "#285577"); - config.client.focused.text = get_colorpixel(conn, "#ffffff"); +#define INIT_COLOR(x, cborder, cbackground, ctext) \ + do { \ + x.border = get_colorpixel(conn, cborder); \ + x.background = get_colorpixel(conn, cbackground); \ + x.text = get_colorpixel(conn, ctext); \ + } while (0) - config.client.focused_inactive.border = get_colorpixel(conn, "#333333"); - config.client.focused_inactive.background = get_colorpixel(conn, "#5f676a"); - config.client.focused_inactive.text = get_colorpixel(conn, "#ffffff"); + INIT_COLOR(config.client.focused, "#4c7899", "#285577", "#ffffff"); + INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff"); + INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888"); + INIT_COLOR(config.client.urgent, "#2f343a", "#900000", "#ffffff"); + INIT_COLOR(config.bar.focused, "#4c7899", "#285577", "#ffffff"); + INIT_COLOR(config.bar.unfocused, "#333333", "#222222", "#888888"); + INIT_COLOR(config.bar.urgent, "#2f343a", "#900000", "#ffffff"); - config.client.unfocused.border = get_colorpixel(conn, "#333333"); - config.client.unfocused.background = get_colorpixel(conn, "#222222"); - config.client.unfocused.text = get_colorpixel(conn, "#888888"); + parse_configuration(override_configpath); - config.client.urgent.border = get_colorpixel(conn, "#2f343a"); - config.client.urgent.background = get_colorpixel(conn, "#900000"); - config.client.urgent.text = get_colorpixel(conn, "#ffffff"); - - config.bar.focused.border = get_colorpixel(conn, "#4c7899"); - config.bar.focused.background = get_colorpixel(conn, "#285577"); - config.bar.focused.text = get_colorpixel(conn, "#ffffff"); - - config.bar.unfocused.border = get_colorpixel(conn, "#333333"); - config.bar.unfocused.background = get_colorpixel(conn, "#222222"); - config.bar.unfocused.text = get_colorpixel(conn, "#888888"); - - config.bar.urgent.border = get_colorpixel(conn, "#2f343a"); - config.bar.urgent.background = get_colorpixel(conn, "#900000"); - config.bar.urgent.text = get_colorpixel(conn, "#ffffff"); - - if (config_use_lexer) { - /* Yes, this will be cleaned up soon. */ - if (override_configpath != NULL) { - parse_file(override_configpath); - } else { - FILE *handle; - char *globbed = glob_path("~/.i3/config"); - if ((handle = fopen(globbed, "r")) == NULL) { - if ((handle = fopen("/etc/i3/config", "r")) == NULL) { - die("Neither \"%s\" nor /etc/i3/config could be opened\n", globbed); - } else { - parse_file("/etc/i3/config"); - } - } else { - parse_file(globbed); - } - } - if (reload) - grab_all_keys(conn); - } else { - - FILE *handle; - if (override_configpath != NULL) { - if ((handle = fopen(override_configpath, "r")) == NULL) - die("Could not open configfile \"%s\".\n", override_configpath); - } else { - /* We first check for ~/.i3/config, then for /etc/i3/config */ - char *globbed = glob_path("~/.i3/config"); - if ((handle = fopen(globbed, "r")) == NULL) - if ((handle = fopen("/etc/i3/config", "r")) == NULL) - die("Neither \"%s\" nor /etc/i3/config could be opened\n", globbed); - free(globbed); - } - char key[512], value[512], buffer[1026]; - - while (!feof(handle)) { - if (fgets(buffer, 1024, handle) == NULL) { - /* fgets returns NULL on EOF and on error, so see which one it is. */ - if (feof(handle)) - break; - die("Could not read configuration file\n"); - } - - if (config.terminal != NULL) - replace_variable(buffer, "$terminal", config.terminal); - - /* Replace all custom variables */ - struct Variable *current; - SLIST_FOREACH(current, &variables, variables) - replace_variable(buffer, current->key, current->value); - - /* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */ - if (sscanf(buffer, "%s %[^\n]", key, value) < 1 || - key[0] == '#' || strlen(key) < 3) - continue; - - OPTION_STRING(terminal); - OPTION_STRING(font); - - /* Colors */ - OPTION_COLORTRIPLE("client.focused", client.focused); - OPTION_COLORTRIPLE("client.focused_inactive", client.focused_inactive); - OPTION_COLORTRIPLE("client.unfocused", client.unfocused); - OPTION_COLORTRIPLE("client.urgent", client.urgent); - OPTION_COLORTRIPLE("bar.focused", bar.focused); - OPTION_COLORTRIPLE("bar.unfocused", bar.unfocused); - OPTION_COLORTRIPLE("bar.urgent", bar.urgent); - - /* exec-lines (autostart) */ - if (strcasecmp(key, "exec") == 0) { - struct Autostart *new = smalloc(sizeof(struct Autostart)); - new->command = sstrdup(value); - TAILQ_INSERT_TAIL(&autostarts, new, autostarts); - continue; - } - - /* key bindings */ - if (strcasecmp(key, "bind") == 0 || strcasecmp(key, "bindsym") == 0) { - #define CHECK_MODIFIER(name) \ - if (strncasecmp(walk, #name, strlen(#name)) == 0) { \ - modifiers |= BIND_##name; \ - walk += strlen(#name) + 1; \ - continue; \ - } - char *walk = value, *rest; - uint32_t modifiers = 0; - - while (*walk != '\0') { - /* Need to check for Mod1-5, Ctrl, Shift, Mode_switch */ - CHECK_MODIFIER(SHIFT); - CHECK_MODIFIER(CONTROL); - CHECK_MODIFIER(MODE_SWITCH); - CHECK_MODIFIER(MOD1); - CHECK_MODIFIER(MOD2); - CHECK_MODIFIER(MOD3); - CHECK_MODIFIER(MOD4); - CHECK_MODIFIER(MOD5); - - /* No modifier found? Then we’re done with this step */ - break; - } - - Binding *new = scalloc(sizeof(Binding)); - - /* Now check for the keycode or copy the symbol */ - if (strcasecmp(key, "bind") == 0) { - int keycode = strtol(walk, &rest, 10); - if (!rest || *rest != ' ') - die("Invalid binding (keycode)\n"); - new->keycode = keycode; - } else { - rest = walk; - char *sym = rest; - while (*rest != '\0' && *rest != ' ') - rest++; - if (*rest != ' ') - die("Invalid binding (keysym)\n"); -#if defined(__OpenBSD__) - size_t len = strlen(sym); - if (len > (rest - sym)) - len = (rest - sym); - new->symbol = smalloc(len + 1); - memcpy(new->symbol, sym, len+1); - new->symbol[len]='\0'; -#else - new->symbol = strndup(sym, (rest - sym)); -#endif - } - rest++; - LOG("keycode = %d, symbol = %s, modifiers = %d, command = *%s*\n", new->keycode, new->symbol, modifiers, rest); - new->mods = modifiers; - new->command = sstrdup(rest); - TAILQ_INSERT_TAIL(bindings, new, bindings); - continue; - } - - if (strcasecmp(key, "floating_modifier") == 0) { - char *walk = value; - uint32_t modifiers = 0; - - while (*walk != '\0') { - /* Need to check for Mod1-5, Ctrl, Shift, Mode_switch */ - CHECK_MODIFIER(SHIFT); - CHECK_MODIFIER(CONTROL); - CHECK_MODIFIER(MODE_SWITCH); - CHECK_MODIFIER(MOD1); - CHECK_MODIFIER(MOD2); - CHECK_MODIFIER(MOD3); - CHECK_MODIFIER(MOD4); - CHECK_MODIFIER(MOD5); - - /* No modifier found? Then we’re done with this step */ - break; - } - - LOG("Floating modifiers = %d\n", modifiers); - config.floating_modifier = modifiers; - continue; - } - - /* workspace "workspace number" [screen ] ["name of the workspace"] - * with screen := | , e.g. screen 1280 or screen 1 */ - if (strcasecmp(key, "name") == 0 || strcasecmp(key, "workspace") == 0) { - LOG("workspace: %s\n",value); - char *ws_str = sstrdup(value); - char *end = strchr(ws_str, ' '); - if (end == NULL) - die("Malformed name, couln't find terminating space\n"); - *end = '\0'; - - /* Strip trailing whitespace */ - while (strlen(value) > 0 && value[strlen(value)-1] == ' ') - value[strlen(value)-1] = '\0'; - - int ws_num = atoi(ws_str); - - if (ws_num < 1 || ws_num > 10) - die("Malformed name, invalid workspace number\n"); - - /* find the name */ - char *name = value; - name += strlen(ws_str) + 1; - - if (strncasecmp(name, "screen ", strlen("screen ")) == 0) { - char *screen = strdup(name + strlen("screen ")); - if ((end = strchr(screen, ' ')) != NULL) - *end = '\0'; - LOG("Setting preferred screen for workspace %d to \"%s\"\n", ws_num, screen); - workspace_get(ws_num-1)->preferred_screen = screen; - - name += strlen("screen ") + strlen(screen); - } - - /* Strip leading whitespace */ - while (*name != '\0' && *name == ' ') - name++; - - LOG("rest to parse = %s\n", name); - - if (name == '\0') { - free(ws_str); - continue; - } - - LOG("setting name to \"%s\"\n", name); - - if (*name != '\0') - workspace_set_name(workspace_get(ws_num - 1), name); - free(ws_str); - continue; - } - - /* assign window class[/window title] → workspace */ - if (strcasecmp(key, "assign") == 0) { - LOG("assign: \"%s\"\n", value); - char *class_title; - char *target; - char *end; - - /* If the window class/title is quoted we skip quotes */ - if (value[0] == '"') { - class_title = sstrdup(value+1); - end = strchr(class_title, '"'); - } else { - class_title = sstrdup(value); - /* If it is not quoted, we terminate it at the first space */ - end = strchr(class_title, ' '); - } - if (end == NULL) - die("Malformed assignment, couldn't find terminating quote\n"); - *end = '\0'; - - /* Strip trailing whitespace */ - while (strlen(value) > 0 && value[strlen(value)-1] == ' ') - value[strlen(value)-1] = '\0'; - - /* Strip trailing whitespace */ - while (strlen(value) > 0 && value[strlen(value)-1] == ' ') - value[strlen(value)-1] = '\0'; - - /* The target is the last argument separated by a space */ - if ((target = strrchr(value, ' ')) == NULL) - die("Malformed assignment, couldn't find target (\"%s\")\n", value); - target++; - - if (strchr(target, '~') == NULL && (atoi(target) < 1 || atoi(target) > 10)) - die("Malformed assignment, invalid workspace number\n"); - - LOG("assignment parsed: \"%s\" to \"%s\"\n", class_title, target); - - struct Assignment *new = scalloc(sizeof(struct Assignment)); - new->windowclass_title = class_title; - if (strchr(target, '~') != NULL) - new->floating = ASSIGN_FLOATING_ONLY; - - while (*target == '~') - target++; - - if (atoi(target) >= 1) { - if (new->floating == ASSIGN_FLOATING_ONLY) - new->floating = ASSIGN_FLOATING; - new->workspace = atoi(target); - } - TAILQ_INSERT_TAIL(&assignments, new, assignments); - - LOG("Assignment loaded: \"%s\":\n", class_title); - if (new->floating != ASSIGN_FLOATING_ONLY) - LOG(" to workspace %d\n", new->workspace); - - if (new->floating != ASSIGN_FLOATING_NO) - LOG(" will be floating\n"); - - continue; - } - - /* set a custom variable */ - if (strcasecmp(key, "set") == 0) { - if (value[0] != '$') - die("Malformed variable assignment, name has to start with $\n"); - - /* get key/value for this variable */ - char *v_key = value, *v_value; - if ((v_value = strstr(value, " ")) == NULL) - die("Malformed variable assignment, need a value\n"); - - *(v_value++) = '\0'; - - struct Variable *new = scalloc(sizeof(struct Variable)); - new->key = sstrdup(v_key); - new->value = sstrdup(v_value); - SLIST_INSERT_HEAD(&variables, new, variables); - LOG("Got new variable %s = %s\n", v_key, v_value); - continue; - } - - if (strcasecmp(key, "ipc-socket") == 0) { - config.ipc_socket_path = sstrdup(value); - continue; - } - - die("Unknown configfile option: %s\n", key); - } - /* now grab all keys again */ - if (reload) - grab_all_keys(conn); - fclose(handle); - - while (!SLIST_EMPTY(&variables)) { - struct Variable *v = SLIST_FIRST(&variables); - SLIST_REMOVE_HEAD(&variables, variables); - free(v->key); - free(v->value); - free(v); - } + if (reload) { + translate_keysyms(); + grab_all_keys(conn, false); } - REQUIRED_OPTION(terminal); REQUIRED_OPTION(font); /* Set an empty name for every workspace which got no name */ @@ -618,6 +398,4 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, workspace_set_name(ws, NULL); } - - return; } diff --git a/src/container.c b/src/container.c new file mode 100644 index 00000000..8533fd49 --- /dev/null +++ b/src/container.c @@ -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; +} diff --git a/src/debug.c b/src/debug.c index 1be47269..de47fca2 100644 --- a/src/debug.c +++ b/src/debug.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -14,6 +14,8 @@ #include #include +#include "log.h" + static const char *labelError[] = { "Success", "BadRequest", @@ -219,19 +221,21 @@ int format_event(xcb_generic_event_t *e) { switch(e->response_type) { case 0: - printf("Error %s on seqnum %d (%s).\n", + DLOG("Error %s on seqnum %d (%s).\n", labelError[*((uint8_t *) e + 1)], seqnum, labelRequest[*((uint8_t *) e + 10)]); break; default: - printf("Event %s following seqnum %d%s.\n", + if (e->response_type > sizeof(labelEvent) / sizeof(char*)) + break; + DLOG("Event %s following seqnum %d%s.\n", labelEvent[e->response_type], seqnum, labelSendEvent[sendEvent]); break; case XCB_KEYMAP_NOTIFY: - printf("Event %s%s.\n", + DLOG("Event %s%s.\n", labelEvent[e->response_type], labelSendEvent[sendEvent]); break; diff --git a/src/ewmh.c b/src/ewmh.c new file mode 100644 index 00000000..6bfa3096 --- /dev/null +++ b/src/ewmh.c @@ -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 +#include +#include + +#include "data.h" +#include "table.h" +#include "i3.h" +#include "xcb.h" +#include "util.h" +#include "log.h" + +/* + * Updates _NET_CURRENT_DESKTOP with the current desktop number. + * + * EWMH: The index of the current desktop. This is always an integer between 0 + * and _NET_NUMBER_OF_DESKTOPS - 1. + * + */ +void ewmh_update_current_desktop() { + uint32_t current_desktop = c_ws->num; + xcb_change_property(global_conn, XCB_PROP_MODE_REPLACE, root, + atoms[_NET_CURRENT_DESKTOP], CARDINAL, 32, 1, + ¤t_desktop); +} + +/* + * Updates _NET_ACTIVE_WINDOW with the currently focused window. + * + * EWMH: The window ID of the currently active window or None if no window has + * the focus. + * + */ +void ewmh_update_active_window(xcb_window_t window) { + xcb_change_property(global_conn, XCB_PROP_MODE_REPLACE, root, + atoms[_NET_ACTIVE_WINDOW], WINDOW, 32, 1, &window); +} + +/* + * Updates the workarea for each desktop. + * + * EWMH: Contains a geometry for each desktop. These geometries specify an area + * that is completely contained within the viewport. Work area SHOULD be used by + * desktop applications to place desktop icons appropriately. + * + */ +void ewmh_update_workarea() { + Workspace *ws; + int num_workspaces = 0, count = 0; + Rect last_rect = {0, 0, 0, 0}; + + /* Get the number of workspaces */ + TAILQ_FOREACH(ws, workspaces, workspaces) { + /* Check if we need to initialize last_rect. The case that the + * first workspace is all-zero may happen when the user + * assigned workspace 2 for his first screen, for example. Thus + * we need an initialized last_rect in the very first run of + * the following loop. */ + if (last_rect.width == 0 && last_rect.height == 0 && + ws->rect.width != 0 && ws->rect.height != 0) { + memcpy(&last_rect, &(ws->rect), sizeof(Rect)); + } + num_workspaces++; + } + + DLOG("Got %d workspaces\n", num_workspaces); + uint8_t *workarea = smalloc(sizeof(Rect) * num_workspaces); + TAILQ_FOREACH(ws, workspaces, workspaces) { + DLOG("storing %d: %dx%d with %d x %d\n", count, ws->rect.x, + ws->rect.y, ws->rect.width, ws->rect.height); + /* If a workspace is not yet initialized and thus its + * dimensions are zero, we will instead put the dimensions + * of the last workspace in the list. For example firefox + * intersects all workspaces and does not cope so well with + * an all-zero workspace. */ + if (ws->rect.width == 0 || ws->rect.height == 0) { + DLOG("re-using last_rect (%dx%d, %d, %d)\n", + last_rect.x, last_rect.y, last_rect.width, + last_rect.height); + memcpy(workarea + (sizeof(Rect) * count++), &last_rect, sizeof(Rect)); + continue; + } + memcpy(workarea + (sizeof(Rect) * count++), &(ws->rect), sizeof(Rect)); + memcpy(&last_rect, &(ws->rect), sizeof(Rect)); + } + xcb_change_property(global_conn, XCB_PROP_MODE_REPLACE, root, + atoms[_NET_WORKAREA], CARDINAL, 32, + num_workspaces * (sizeof(Rect) / sizeof(uint32_t)), + workarea); + free(workarea); + xcb_flush(global_conn); +} diff --git a/src/floating.c b/src/floating.c index dd6c86e7..7e62eea5 100644 --- a/src/floating.c +++ b/src/floating.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -27,6 +27,7 @@ #include "client.h" #include "floating.h" #include "workspace.h" +#include "log.h" /* * Toggles floating mode for the given client. @@ -42,12 +43,12 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic i3Font *font = load_font(conn, config.font); if (client->dock) { - LOG("Not putting dock client into floating mode\n"); + DLOG("Not putting dock client into floating mode\n"); return; } if (con == NULL) { - LOG("This client is already in floating (container == NULL), re-inserting\n"); + DLOG("This client is already in floating (container == NULL), re-inserting\n"); Client *next_tiling; Workspace *ws = client->workspace; SLIST_FOREACH(next_tiling, &(ws->focus_stack), focus_clients) @@ -62,7 +63,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic /* Remove the client from the list of floating clients */ TAILQ_REMOVE(&(ws->floating_clients), client, floating_clients); - LOG("destination container = %p\n", con); + DLOG("destination container = %p\n", con); Client *old_focused = con->currently_focused; /* Preserve position/size */ memcpy(&(client->floating_rect), &(client->rect), sizeof(Rect)); @@ -74,7 +75,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic CIRCLEQ_INSERT_AFTER(&(con->clients), old_focused, client, clients); else CIRCLEQ_INSERT_TAIL(&(con->clients), client, clients); - LOG("Re-inserted the client into the matrix.\n"); + DLOG("Re-inserted the window.\n"); con->currently_focused = client; client_set_below_floating(conn, client); @@ -85,7 +86,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic return; } - LOG("Entering floating for client %08x\n", client->child); + DLOG("Entering floating for client %08x\n", client->child); /* Remove the client of its container */ client_remove_from_container(conn, client, con, false); @@ -95,7 +96,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients); if (con->currently_focused == client) { - LOG("Need to re-adjust currently_focused\n"); + DLOG("Need to re-adjust currently_focused\n"); /* Get the next client in the focus stack for this particular container */ con->currently_focused = get_last_focused_client(conn, con, NULL); } @@ -118,11 +119,11 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic client->rect.width = client->child_rect.width + 2 + 2; client->rect.height = client->child_rect.height + (font->height + 2 + 2) + 2; - LOG("copying size from tiling (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y, + DLOG("copying size from tiling (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y, client->floating_rect.width, client->floating_rect.height); } else { /* If the client was already in floating before we restore the old position / size */ - LOG("using: (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y, + DLOG("using: (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y, client->floating_rect.width, client->floating_rect.height); memcpy(&(client->rect), &(client->floating_rect), sizeof(Rect)); } @@ -161,6 +162,69 @@ void floating_assign_to_workspace(Client *client, Workspace *new_workspace) { client->workspace->fullscreen_client = client; } +/* + * This is an ugly data structure which we need because there is no standard + * way of having nested functions (only available as a gcc extension at the + * moment, clang doesn’t support it) or blocks (only available as a clang + * extension and only on Mac OS X systems at the moment). + * + */ +struct resize_callback_params { + border_t border; + xcb_button_press_event_t *event; +}; + +DRAGGING_CB(resize_callback) { + struct resize_callback_params *params = extra; + xcb_button_press_event_t *event = params->event; + switch (params->border) { + case BORDER_RIGHT: { + int new_width = old_rect->width + (new_x - event->root_x); + if ((new_width < 0) || + (new_width < client_min_width(client) && client->rect.width >= new_width)) + return; + client->rect.width = new_width; + break; + } + + case BORDER_BOTTOM: { + int new_height = old_rect->height + (new_y - event->root_y); + if ((new_height < 0) || + (new_height < client_min_height(client) && client->rect.height >= new_height)) + return; + client->rect.height = old_rect->height + (new_y - event->root_y); + break; + } + + case BORDER_TOP: { + int new_height = old_rect->height + (event->root_y - new_y); + if ((new_height < 0) || + (new_height < client_min_height(client) && client->rect.height >= new_height)) + return; + + client->rect.y = old_rect->y + (new_y - event->root_y); + client->rect.height = new_height; + break; + } + + case BORDER_LEFT: { + int new_width = old_rect->width + (event->root_x - new_x); + if ((new_width < 0) || + (new_width < client_min_width(client) && client->rect.width >= new_width)) + return; + client->rect.x = old_rect->x + (new_x - event->root_x); + client->rect.width = new_width; + break; + } + } + + /* Push the new position/size to X11 */ + reposition_client(conn, client); + resize_client(conn, client); + xcb_flush(conn); +} + + /* * Called whenever the user clicks on a border (not the titlebar!) of a floating window. * Determines on which border the user clicked and launches the drag_pointer function @@ -168,59 +232,10 @@ void floating_assign_to_workspace(Client *client, Workspace *new_workspace) { * */ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) { - - LOG("floating border click\n"); + DLOG("floating border click\n"); border_t border; - void resize_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) { - switch (border) { - case BORDER_RIGHT: { - int new_width = old_rect->width + (new_x - event->root_x); - if ((new_width < 0) || - (new_width < 50 && client->rect.width >= new_width)) - return; - client->rect.width = new_width; - break; - } - - case BORDER_BOTTOM: { - int new_height = old_rect->height + (new_y - event->root_y); - if ((new_height < 0) || - (new_height < 20 && client->rect.height >= new_height)) - return; - client->rect.height = old_rect->height + (new_y - event->root_y); - break; - } - - case BORDER_TOP: { - int new_height = old_rect->height + (event->root_y - new_y); - if ((new_height < 0) || - (new_height < 20 && client->rect.height >= new_height)) - return; - - client->rect.y = old_rect->y + (new_y - event->root_y); - client->rect.height = new_height; - break; - } - - case BORDER_LEFT: { - int new_width = old_rect->width + (event->root_x - new_x); - if ((new_width < 0) || - (new_width < 50 && client->rect.width >= new_width)) - return; - client->rect.x = old_rect->x + (new_x - event->root_x); - client->rect.width = new_width; - break; - } - } - - /* Push the new position/size to X11 */ - reposition_client(conn, client); - resize_client(conn, client); - xcb_flush(conn); - } - if (event->event_y < 2) border = BORDER_TOP; else if (event->event_y >= (client->rect.height - 2)) @@ -230,17 +245,31 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre else if (event->event_x >= (client->rect.width - 2)) border = BORDER_RIGHT; else { - LOG("Not on any border, not doing anything.\n"); + DLOG("Not on any border, not doing anything.\n"); return 1; } - LOG("border = %d\n", border); + DLOG("border = %d\n", border); - drag_pointer(conn, client, event, XCB_NONE, border, resize_callback); + struct resize_callback_params params = { border, event }; + + drag_pointer(conn, client, event, XCB_NONE, border, resize_callback, ¶ms); return 1; } +DRAGGING_CB(drag_window_callback) { + struct xcb_button_press_event_t *event = extra; + + /* Reposition the client correctly while moving */ + client->rect.x = old_rect->x + (new_x - event->root_x); + client->rect.y = old_rect->y + (new_y - event->root_y); + reposition_client(conn, client); + /* Because reposition_client does not send a faked configure event (only resize does), + * we need to initiate that on our own */ + fake_absolute_configure_notify(conn, client); + /* fake_absolute_configure_notify flushes */ +} /* * Called when the user clicked on the titlebar of a floating window. @@ -248,47 +277,95 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre * */ void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) { - LOG("floating_drag_window\n"); + DLOG("floating_drag_window\n"); - void drag_window_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) { - /* Reposition the client correctly while moving */ - client->rect.x = old_rect->x + (new_x - event->root_x); - client->rect.y = old_rect->y + (new_y - event->root_y); - reposition_client(conn, client); - /* Because reposition_client does not send a faked configure event (only resize does), - * we need to initiate that on our own */ - fake_absolute_configure_notify(conn, client); - /* fake_absolute_configure_notify flushes */ - } - - drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback); + drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback, event); } /* - * Called when the user right-clicked on the titlebar of a floating window to - * resize it. + * This is an ugly data structure which we need because there is no standard + * way of having nested functions (only available as a gcc extension at the + * moment, clang doesn’t support it) or blocks (only available as a clang + * extension and only on Mac OS X systems at the moment). + * + */ +struct resize_window_callback_params { + border_t corner; + bool proportional; + xcb_button_press_event_t *event; +}; + +DRAGGING_CB(resize_window_callback) { + struct resize_window_callback_params *params = extra; + xcb_button_press_event_t *event = params->event; + border_t corner = params->corner; + + int32_t dest_x = client->rect.x; + int32_t dest_y = client->rect.y; + uint32_t dest_width; + uint32_t dest_height; + + double ratio = (double) old_rect->width / old_rect->height; + + /* First guess: We resize by exactly the amount the mouse moved, + * taking into account in which corner the client was grabbed */ + if (corner & BORDER_LEFT) + dest_width = old_rect->width - (new_x - event->root_x); + else dest_width = old_rect->width + (new_x - event->root_x); + + if (corner & BORDER_TOP) + dest_height = old_rect->height - (new_y - event->root_y); + else dest_height = old_rect->height + (new_y - event->root_y); + + /* Obey minimum window size */ + dest_width = max(dest_width, client_min_width(client)); + dest_height = max(dest_height, client_min_height(client)); + + /* User wants to keep proportions, so we may have to adjust our values */ + if (params->proportional) { + dest_width = max(dest_width, (int) (dest_height * ratio)); + dest_height = max(dest_height, (int) (dest_width / ratio)); + } + + /* If not the lower right corner is grabbed, we must also reposition + * the client by exactly the amount we resized it */ + if (corner & BORDER_LEFT) + dest_x = old_rect->x + (old_rect->width - dest_width); + + if (corner & BORDER_TOP) + dest_y = old_rect->y + (old_rect->height - dest_height); + + client->rect = (Rect) { dest_x, dest_y, dest_width, dest_height }; + + /* resize_client flushes */ + resize_client(conn, client); +} + +/* + * Called when the user clicked on a floating window while holding the + * floating_modifier and the right mouse button. * Calls the drag_pointer function with the resize_window callback * */ -void floating_resize_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) { - LOG("floating_resize_window\n"); +void floating_resize_window(xcb_connection_t *conn, Client *client, + bool proportional, xcb_button_press_event_t *event) { + DLOG("floating_resize_window\n"); - void resize_window_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) { - int32_t new_width = old_rect->width + (new_x - event->root_x); - int32_t new_height = old_rect->height + (new_y - event->root_y); + /* corner saves the nearest corner to the original click. It contains + * a bitmask of the nearest borders (BORDER_LEFT, BORDER_RIGHT, …) */ + border_t corner = 0; - /* Obey minimum window size and reposition the client */ - if (new_width >= 50) - client->rect.width = new_width; + if (event->event_x <= (client->rect.width / 2)) + corner |= BORDER_LEFT; + else corner |= BORDER_RIGHT; - if (new_height >= 20) - client->rect.height = new_height; + if (event->event_y <= (client->rect.height / 2)) + corner |= BORDER_TOP; + else corner |= BORDER_RIGHT; - /* resize_client flushes */ - resize_client(conn, client); - } + struct resize_window_callback_params params = { corner, proportional, event }; - drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback); + drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback, ¶ms); } @@ -301,7 +378,7 @@ void floating_resize_window(xcb_connection_t *conn, Client *client, xcb_button_p * */ void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event, - xcb_window_t confine_to, border_t border, callback_t callback) { + xcb_window_t confine_to, border_t border, callback_t callback, void *extra) { xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; uint32_t new_x, new_y; Rect old_rect; @@ -351,12 +428,12 @@ void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event break; case XCB_UNMAP_NOTIFY: - LOG("Unmap-notify, aborting\n"); + DLOG("Unmap-notify, aborting\n"); xcb_event_handle(&evenths, inside_event); goto done; default: - LOG("Passing to original handler\n"); + DLOG("Passing to original handler\n"); /* Use original handler */ xcb_event_handle(&evenths, inside_event); break; @@ -371,7 +448,7 @@ void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event new_x = ((xcb_motion_notify_event_t*)last_motion_notify)->root_x; new_y = ((xcb_motion_notify_event_t*)last_motion_notify)->root_y; - callback(&old_rect, new_x, new_y); + callback(conn, client, &old_rect, new_x, new_y, extra); FREE(last_motion_notify); } done: @@ -387,7 +464,7 @@ done: * */ void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, direction_t direction) { - LOG("floating focus\n"); + DLOG("floating focus\n"); if (direction == D_LEFT || direction == D_RIGHT) { /* Go to the next/previous floating client */ @@ -409,10 +486,15 @@ void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, * */ void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_t direction) { - LOG("floating move\n"); + DLOG("floating move\n"); + + if (currently_focused->fullscreen) { + DLOG("Cannot move fullscreen windows\n"); + return; + } Rect destination = currently_focused->rect; - Rect *screen = &(currently_focused->workspace->screen->rect); + Rect *screen = &(currently_focused->workspace->output->rect); switch (direction) { case D_LEFT: @@ -437,7 +519,7 @@ void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_ (int32_t)(destination.x + 5) >= (int32_t)(screen->x + screen->width) || (int32_t)(destination.y + destination.height - 5) <= (int32_t)screen->y || (int32_t)(destination.y + 5) >= (int32_t)(screen->y + screen->height)) { - LOG("boundary check failed, not moving\n"); + DLOG("boundary check failed, not moving\n"); return; } @@ -459,7 +541,7 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace) { Client *client; workspace->floating_hidden = !workspace->floating_hidden; - LOG("floating_hidden is now: %d\n", workspace->floating_hidden); + DLOG("floating_hidden is now: %d\n", workspace->floating_hidden); TAILQ_FOREACH(client, &(workspace->floating_clients), floating_clients) { if (workspace->floating_hidden) client_unmap(conn, client); diff --git a/src/handlers.c b/src/handlers.c index 3fe14abe..a173777c 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -17,6 +17,7 @@ #include #include #include +#include #include @@ -28,7 +29,7 @@ #include "data.h" #include "xcb.h" #include "util.h" -#include "xinerama.h" +#include "randr.h" #include "config.h" #include "queue.h" #include "resize.h" @@ -36,6 +37,9 @@ #include "manage.h" #include "floating.h" #include "workspace.h" +#include "log.h" +#include "container.h" +#include "ipc.h" /* After mapping/unmapping windows, a notify event is generated. However, we don’t want it, since it’d trigger an infinite loop of switching between the different windows when @@ -78,77 +82,45 @@ static bool event_is_ignored(const int sequence) { return false; } -/* - * Due to bindings like Mode_switch + , we need to bind some keys in XCB_GRAB_MODE_SYNC. - * Therefore, we just replay all key presses. - * - */ -int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_release_event_t *event) { - xcb_allow_events(conn, XCB_ALLOW_REPLAY_KEYBOARD, event->time); - xcb_flush(conn); - return 1; -} - /* * There was a key press. We compare this key code with our bindings table and pass * the bound action to parse_command(). * */ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) { - LOG("Keypress %d, state raw = %d\n", event->detail, event->state); + DLOG("Keypress %d, state raw = %d\n", event->detail, event->state); /* Remove the numlock bit, all other bits are modifiers we can bind to */ uint16_t state_filtered = event->state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK); - LOG("(removed numlock, state = %d)\n", state_filtered); + DLOG("(removed numlock, state = %d)\n", state_filtered); /* Only use the lower 8 bits of the state (modifier masks) so that mouse * button masks are filtered out */ state_filtered &= 0xFF; - LOG("(removed upper 8 bits, state = %d)\n", state_filtered); + DLOG("(removed upper 8 bits, state = %d)\n", state_filtered); - /* We need to get the keysym group (There are group 1 to group 4, each holding - two keysyms (without shift and with shift) using Xkb because X fails to - provide them reliably (it works in Xephyr, it does not in real X) */ - XkbStateRec state; - if (XkbGetState(xkbdpy, XkbUseCoreKbd, &state) == Success && (state.group+1) == 2) + if (xkb_current_group == XkbGroup2Index) state_filtered |= BIND_MODE_SWITCH; - LOG("(checked mode_switch, state %d)\n", state_filtered); + DLOG("(checked mode_switch, state %d)\n", state_filtered); /* Find the binding */ - Binding *bind; - TAILQ_FOREACH(bind, bindings, bindings) { - /* First compare the modifiers */ - if (bind->mods != state_filtered) - continue; + Binding *bind = get_binding(state_filtered, event->detail); - /* If a symbol was specified by the user, we need to look in - * the array of translated keycodes for the event’s keycode */ - if (bind->symbol != NULL) { - if (memmem(bind->translated_to, - bind->number_keycodes * sizeof(xcb_keycode_t), - &(event->detail), sizeof(xcb_keycode_t)) != NULL) - break; - } else { - /* This case is easier: The user specified a keycode */ - if (bind->keycode == event->detail) - break; + /* No match? Then the user has Mode_switch enabled but does not have a + * specific keybinding. Fall back to the default keybindings (without + * Mode_switch). Makes it much more convenient for users of a hybrid + * layout (like us, ru). */ + if (bind == NULL) { + state_filtered &= ~(BIND_MODE_SWITCH); + DLOG("no match, new state_filtered = %d\n", state_filtered); + if ((bind = get_binding(state_filtered, event->detail)) == NULL) { + ELOG("Could not lookup key binding (modifiers %d, keycode %d)\n", + state_filtered, event->detail); + return 1; } } - /* No match? Then it was an actively grabbed key, that is with Mode_switch, and - the user did not press Mode_switch, so just pass it… */ - if (bind == TAILQ_END(bindings)) { - xcb_allow_events(conn, ReplayKeyboard, event->time); - xcb_flush(conn); - return 1; - } - parse_command(conn, bind->command); - if (state_filtered & BIND_MODE_SWITCH) { - LOG("Mode_switch -> allow_events(SyncKeyboard)\n"); - xcb_allow_events(conn, SyncKeyboard, event->time); - xcb_flush(conn); - } return 1; } @@ -159,21 +131,39 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_ * */ static void check_crossing_screen_boundary(uint32_t x, uint32_t y) { - i3Screen *screen; + Output *output; - if ((screen = get_screen_containing(x, y)) == NULL) { - LOG("ERROR: No such screen\n"); + if ((output = get_output_containing(x, y)) == NULL) { + ELOG("ERROR: No such screen\n"); return; } - if (screen == c_ws->screen) + if (output == c_ws->output) return; c_ws->current_row = current_row; c_ws->current_col = current_col; - c_ws = screen->current_workspace; + c_ws = output->current_workspace; current_row = c_ws->current_row; current_col = c_ws->current_col; - LOG("We're now on virtual screen number %d\n", screen->num); + DLOG("We're now on output %p\n", output); + + /* While usually this function is only called when the user switches + * to a different output using his mouse (and thus the output is + * empty), it may be that the following race condition occurs: + * 1) the user actives a new output (say VGA1). + * 2) the cursor is sent to the first pixel of the new VGA1, thus + * generating an enter_notify for the screen (the enter_notify + * is not yet received by i3). + * 3) i3 requeries screen configuration and maps a workspace onto the + * new output. + * 4) the enter_notify event arrives and c_ws is set to the new + * workspace but the existing windows on the new workspace are not + * focused. + * + * Therefore, we re-set the focus here to be sure it’s correct. */ + Client *first_client = SLIST_FIRST(&(c_ws->focus_stack)); + if (first_client != NULL) + set_focus(global_conn, first_client, true); } /* @@ -181,9 +171,9 @@ static void check_crossing_screen_boundary(uint32_t x, uint32_t y) { * */ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_event_t *event) { - LOG("enter_notify for %08x, mode = %d, detail %d, serial %d\n", event->event, event->mode, event->detail, event->sequence); + DLOG("enter_notify for %08x, mode = %d, detail %d, serial %d\n", event->event, event->mode, event->detail, event->sequence); if (event->mode != XCB_NOTIFY_MODE_NORMAL) { - LOG("This was not a normal notify, ignoring\n"); + DLOG("This was not a normal notify, ignoring\n"); return 1; } /* Some events are not interesting, because they were not generated actively by the @@ -210,7 +200,7 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_ /* If not, then the user moved his cursor to the root window. In that case, we adjust c_ws */ if (client == NULL) { - LOG("Getting screen at %d x %d\n", event->root_x, event->root_y); + DLOG("Getting screen at %d x %d\n", event->root_x, event->root_y); check_crossing_screen_boundary(event->root_x, event->root_y); return 1; } @@ -220,19 +210,20 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_ if (client->container != NULL && client->container->mode == MODE_STACK && client->container->currently_focused != client) { - LOG("Plausibility check says: no\n"); + DLOG("Plausibility check says: no\n"); return 1; } - if (client->workspace != c_ws && client->workspace->screen == c_ws->screen) { + if (client->workspace != c_ws && client->workspace->output == c_ws->output) { /* This can happen when a client gets assigned to a different workspace than * the current one (see src/mainx.c:reparent_window). Shortly after it was created, * an enter_notify will follow. */ - LOG("enter_notify for a client on a different workspace but the same screen, ignoring\n"); + DLOG("enter_notify for a client on a different workspace but the same screen, ignoring\n"); return 1; } - set_focus(conn, client, false); + if (!config.disable_focus_follows_mouse) + set_focus(conn, client, false); return 1; } @@ -264,13 +255,14 @@ int handle_mapping_notify(void *ignored, xcb_connection_t *conn, xcb_mapping_not event->request != XCB_MAPPING_MODIFIER) return 0; - LOG("Received mapping_notify for keyboard or modifier mapping, re-grabbing keys\n"); + DLOG("Received mapping_notify for keyboard or modifier mapping, re-grabbing keys\n"); xcb_refresh_keyboard_mapping(keysyms, event); xcb_get_numlock_mask(conn); ungrab_all_keys(conn); - grab_all_keys(conn); + translate_keysyms(); + grab_all_keys(conn, false); return 0; } @@ -284,7 +276,7 @@ int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_eve cookie = xcb_get_window_attributes_unchecked(conn, event->window); - LOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence); + DLOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence); add_ignore_event(event->sequence); manage_window(prophs, conn, event->window, cookie, false); @@ -298,7 +290,7 @@ int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_eve * */ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure_request_event_t *event) { - LOG("window 0x%08x wants to be at %dx%d with %dx%d\n", + DLOG("window 0x%08x wants to be at %dx%d with %dx%d\n", event->window, event->x, event->y, event->width, event->height); Client *client = table_get(&by_child, event->window); @@ -328,7 +320,7 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure } if (client->fullscreen) { - LOG("Client is in fullscreen mode\n"); + DLOG("Client is in fullscreen mode\n"); Rect child_rect = client->workspace->rect; child_rect.x = child_rect.y = 0; @@ -389,7 +381,7 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure } } - LOG("Accepted new position/size for floating client: (%d, %d) size %d x %d\n", + DLOG("Accepted new position/size for floating client: (%d, %d) size %d x %d\n", client->rect.x, client->rect.y, client->rect.width, client->rect.height); /* Push the new position/size to X11 */ @@ -402,22 +394,22 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure /* Dock clients can be reconfigured in their height */ if (client->dock) { - LOG("Reconfiguring height of this dock client\n"); + DLOG("Reconfiguring height of this dock client\n"); if (!(event->value_mask & XCB_CONFIG_WINDOW_HEIGHT)) { - LOG("Ignoring configure request, no height given\n"); + DLOG("Ignoring configure request, no height given\n"); return 1; } client->desired_height = event->height; - render_workspace(conn, c_ws->screen, c_ws); + render_workspace(conn, c_ws->output, c_ws); xcb_flush(conn); return 1; } if (client->fullscreen) { - LOG("Client is in fullscreen mode\n"); + DLOG("Client is in fullscreen mode\n"); Rect child_rect = client->container->workspace->rect; child_rect.x = child_rect.y = 0; @@ -432,26 +424,30 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure } /* - * Configuration notifies are only handled because we need to set up ignore for the following - * enter notify events + * Configuration notifies are only handled because we need to set up ignore for + * the following enter notify events. * */ int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_notify_event_t *event) { - xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; - /* We ignore this sequence twice because events for child and frame should be ignored */ add_ignore_event(event->sequence); add_ignore_event(event->sequence); - if (event->event == root) { - LOG("event->x = %d, ->y = %d, ->width = %d, ->height = %d\n", event->x, event->y, event->width, event->height); - LOG("reconfigure of the root window, need to xinerama\n"); - /* FIXME: Somehow, this is occuring too often. Therefore, we check for 0/0, - but is there a better way? */ - if (event->x == 0 && event->y == 0) - xinerama_requery_screens(conn); - return 1; - } + return 1; +} + +/* + * Gets triggered upon a RandR screen change event, that is when the user + * changes the screen configuration in any way (mode, position, …) + * + */ +int handle_screen_change(void *prophs, xcb_connection_t *conn, + xcb_generic_event_t *e) { + DLOG("RandR screen change\n"); + + randr_query_outputs(conn); + + ipc_send_event("output", I3_IPC_EVENT_OUTPUT, "{\"change\":\"unspecified\"}"); return 1; } @@ -474,10 +470,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti return 1; } - LOG("event->window = %08x, event->event = %08x\n", event->window, event->event); - LOG("UnmapNotify for 0x%08x (received from 0x%08x)\n", event->window, event->event); + DLOG("event->window = %08x, event->event = %08x\n", event->window, event->event); + DLOG("UnmapNotify for 0x%08x (received from 0x%08x)\n", event->window, event->event); if (client == NULL) { - LOG("not a managed window. Ignoring.\n"); + DLOG("not a managed window. Ignoring.\n"); /* This was most likely the destroyed frame of a client which is * currently being unmapped, so we add this sequence (again!) to @@ -490,9 +486,14 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti client = table_remove(&by_child, event->window); - /* If this was the fullscreen client, we need to unset it */ - if (client->fullscreen) - client->workspace->fullscreen_client = NULL; + /* If this was the fullscreen client, we need to unset it from all + * workspaces it was on (global fullscreen) */ + if (client->fullscreen) { + Workspace *ws; + TAILQ_FOREACH(ws, workspaces, workspaces) + if (ws->fullscreen_client == client) + ws->fullscreen_client = NULL; + } /* Clients without a container are either floating or dock windows */ if (client->container != NULL) { @@ -508,17 +509,17 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti if ((con->currently_focused != NULL) && ((con == CUR_CELL) || client->fullscreen)) set_focus(conn, con->currently_focused, true); } else if (client_is_floating(client)) { - LOG("Removing from floating clients\n"); + DLOG("Removing from floating clients\n"); TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients); SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); } if (client->dock) { - LOG("Removing from dock clients\n"); - SLIST_REMOVE(&(client->workspace->screen->dock_clients), client, Client, dock_clients); + DLOG("Removing from dock clients\n"); + SLIST_REMOVE(&(client->workspace->output->dock_clients), client, Client, dock_clients); } - LOG("child of 0x%08x.\n", client->frame); + DLOG("child of 0x%08x.\n", client->frame); xcb_reparent_window(conn, client->child, root, 0, 0); client_unmap(conn, client); @@ -542,8 +543,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti if (workspace_is_visible(client->workspace)) workspace_empty = false; - if (workspace_empty) - client->workspace->screen = NULL; + if (workspace_empty) { + client->workspace->output = NULL; + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}"); + } /* Remove the urgency flag if set */ client->urgent = false; @@ -564,7 +567,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti if (to_focus != NULL) set_focus(conn, to_focus, true); else { - LOG("Restoring focus to root screen\n"); + DLOG("Restoring focus to root screen\n"); xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME); xcb_flush(conn); } @@ -573,6 +576,26 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti return 1; } +/* + * A destroy notify event is sent when the window is not unmapped, but + * immediately destroyed (for example when starting a window and immediately + * killing the program which started it). + * + * We just pass on the event to the unmap notify handler (by copying the + * important fields in the event data structure). + * + */ +int handle_destroy_notify_event(void *data, xcb_connection_t *conn, xcb_destroy_notify_event_t *event) { + DLOG("destroy notify for 0x%08x, 0x%08x\n", event->event, event->window); + + xcb_unmap_notify_event_t unmap; + unmap.sequence = event->sequence; + unmap.event = event->event; + unmap.window = event->window; + + return handle_unmap_notify_event(NULL, conn, &unmap); +} + /* * Called when a window changes its title * @@ -580,7 +603,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) { if (prop == NULL || xcb_get_property_value_length(prop) == 0) { - LOG("_NET_WM_NAME not specified, not changing\n"); + DLOG("_NET_WM_NAME not specified, not changing\n"); return 1; } Client *client = table_get(&by_child, window); @@ -618,9 +641,11 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, if (client->dock) return 1; - if (client->container != NULL && - (client->container->mode == MODE_STACK || - client->container->mode == MODE_TABBED)) + if (!workspace_is_visible(client->workspace)) + return 1; + + int mode = container_mode(client->container, true); + if (mode == MODE_STACK || mode == MODE_TABBED) render_container(conn, client->container); else decorate_window(conn, client, client->frame, client->titlegc, 0, 0); xcb_flush(conn); @@ -642,7 +667,7 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) { if (prop == NULL || xcb_get_property_value_length(prop) == 0) { - LOG("prop == NULL\n"); + DLOG("prop == NULL\n"); return 1; } Client *client = table_get(&by_child, window); @@ -657,7 +682,7 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t char *new_name; if (asprintf(&new_name, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)) == -1) { perror("Could not get old name"); - LOG("Could not get old name\n"); + DLOG("Could not get old name\n"); return 1; } /* Convert it to UCS-2 here for not having to convert it later every time we want to pass it to X */ @@ -685,6 +710,9 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t if (client->dock) return 1; + if (!workspace_is_visible(client->workspace)) + return 1; + if (client->container != NULL && (client->container->mode == MODE_STACK || client->container->mode == MODE_TABBED)) @@ -702,7 +730,7 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) { if (prop == NULL || xcb_get_property_value_length(prop) == 0) { - LOG("prop == NULL\n"); + DLOG("prop == NULL\n"); return 1; } Client *client = table_get(&by_child, window); @@ -736,7 +764,7 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t * skip all events but the last one */ if (event->count != 0) return 1; - LOG("window = %08x\n", event->window); + DLOG("window = %08x\n", event->window); Client *client = table_get(&by_parent, event->window); if (client == NULL) { @@ -750,9 +778,9 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t * } /* …or one of the bars? */ - i3Screen *screen; - TAILQ_FOREACH(screen, virtual_screens, screens) - if (screen->bar == event->window) + Output *output; + TAILQ_FOREACH(output, &outputs, outputs) + if (output->bar == event->window) render_layout(conn); return 1; } @@ -760,9 +788,7 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t * if (client->dock) return 1; - if (client->container == NULL || - (client->container->mode != MODE_STACK && - client->container->mode != MODE_TABBED)) + if (container_mode(client->container, true) == MODE_DEFAULT) decorate_window(conn, client, client->frame, client->titlegc, 0, 0); else { uint32_t background_color; @@ -787,7 +813,7 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t * /* Draw a black background */ xcb_change_gc_single(conn, client->titlegc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); - if (client->titlebar_position == TITLEBAR_OFF) { + if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) { xcb_rectangle_t crect = {1, 0, client->rect.width - (1 + 1), client->rect.height - 1}; xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect); } else { @@ -821,7 +847,7 @@ int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message event->data.data32[0] == _NET_WM_STATE_TOGGLE))) client_toggle_fullscreen(conn, client); } else { - LOG("unhandled clientmessage\n"); + ELOG("unhandled clientmessage\n"); return 0; } @@ -832,7 +858,7 @@ int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_wi xcb_atom_t atom, xcb_get_property_reply_t *property) { /* TODO: Implement this one. To do this, implement a little test program which sleep(1)s before changing this property. */ - LOG("_NET_WM_WINDOW_TYPE changed, this is not yet implemented.\n"); + ELOG("_NET_WM_WINDOW_TYPE changed, this is not yet implemented.\n"); return 0; } @@ -847,7 +873,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w xcb_atom_t name, xcb_get_property_reply_t *reply) { Client *client = table_get(&by_child, window); if (client == NULL) { - LOG("Received WM_SIZE_HINTS for unknown client\n"); + DLOG("Received WM_SIZE_HINTS for unknown client\n"); return 1; } xcb_size_hints_t size_hints; @@ -862,27 +888,24 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w if ((size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE)) { // TODO: Minimum size is not yet implemented - //LOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height); + DLOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height); } + bool changed = false; if ((size_hints.flags & XCB_SIZE_HINT_P_RESIZE_INC)) { - bool changed = false; - - if (size_hints.width_inc > 0) + if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF) if (client->width_increment != size_hints.width_inc) { client->width_increment = size_hints.width_inc; changed = true; } - if (size_hints.height_inc > 0) + if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF) if (client->height_increment != size_hints.height_inc) { client->height_increment = size_hints.height_inc; changed = true; } - if (changed) { - resize_client(conn, client); - xcb_flush(conn); - } + if (changed) + DLOG("resize increments changed\n"); } int base_width = 0, base_height = 0; @@ -890,10 +913,11 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w /* base_width/height are the desired size of the window. We check if either the program-specified size or the program-specified min-size is available */ - if (size_hints.flags & XCB_SIZE_HINT_P_SIZE) { + if (size_hints.flags & XCB_SIZE_HINT_BASE_SIZE) { base_width = size_hints.base_width; base_height = size_hints.base_height; } else if (size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE) { + /* TODO: is this right? icccm says not */ base_width = size_hints.min_width; base_height = size_hints.min_height; } @@ -902,11 +926,18 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w base_height != client->base_height) { client->base_width = base_width; client->base_height = base_height; - LOG("client's base_height changed to %d\n", base_height); + DLOG("client's base_height changed to %d\n", base_height); + DLOG("client's base_width changed to %d\n", base_width); + changed = true; + } + + if (changed) { if (client->fullscreen) - LOG("Not resizing client, it is in fullscreen mode\n"); - else + DLOG("Not resizing client, it is in fullscreen mode\n"); + else { resize_client(conn, client); + xcb_flush(conn); + } } /* If no aspect ratio was set or if it was invalid, we ignore the hints */ @@ -922,8 +953,8 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w double min_aspect = (double)size_hints.min_aspect_num / size_hints.min_aspect_den; double max_aspect = (double)size_hints.max_aspect_num / size_hints.min_aspect_den; - LOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect); - LOG("width = %f, height = %f\n", width, height); + DLOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect); + DLOG("width = %f, height = %f\n", width, height); /* Sanity checks, this is user-input, in a way */ if (max_aspect <= 0 || min_aspect <= 0 || height == 0 || (width / height) <= 0) @@ -940,7 +971,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w client->force_reconfigure = true; - if (client->container != NULL) { + if (client->container != NULL && workspace_is_visible(client->workspace)) { render_container(conn, client->container); xcb_flush(conn); } @@ -956,7 +987,7 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t xcb_atom_t name, xcb_get_property_reply_t *reply) { Client *client = table_get(&by_child, window); if (client == NULL) { - LOG("Received WM_HINTS for unknown client\n"); + DLOG("Received WM_HINTS for unknown client\n"); return 1; } xcb_wm_hints_t hints; @@ -971,7 +1002,7 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); if (!client->urgent && client == last_focused) { - LOG("Ignoring urgency flag for current client\n"); + DLOG("Ignoring urgency flag for current client\n"); return 1; } @@ -981,14 +1012,15 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t LOG("Urgency flag changed to %d\n", client->urgent); workspace_update_urgent_flag(client->workspace); - redecorate_window(conn, client); /* If the workspace this client is on is not visible, we need to redraw * the workspace bar */ if (!workspace_is_visible(client->workspace)) { - i3Screen *screen = client->workspace->screen; - render_workspace(conn, screen, screen->current_workspace); + Output *output = client->workspace->output; + render_workspace(conn, output, output->current_workspace); xcb_flush(conn); + } else { + redecorate_window(conn, client); } return 1; @@ -1005,7 +1037,7 @@ int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_ xcb_atom_t name, xcb_get_property_reply_t *reply) { Client *client = table_get(&by_child, window); if (client == NULL) { - LOG("No such client\n"); + DLOG("No such client\n"); return 1; } @@ -1021,7 +1053,7 @@ int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_ } if (client->floating == FLOATING_AUTO_OFF) { - LOG("This is a popup window, putting into floating\n"); + DLOG("This is a popup window, putting into floating\n"); toggle_floating_mode(conn, client, true); } @@ -1047,10 +1079,10 @@ int handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state return 1; xcb_window_t *leader = xcb_get_property_value(prop); - if (leader == NULL || *leader == 0) + if (leader == NULL) return 1; - LOG("Client leader changed to %08x\n", *leader); + DLOG("Client leader changed to %08x\n", *leader); client->leader = *leader; diff --git a/src/ipc.c b/src/ipc.c index c048cdf7..1937d55d 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -12,6 +12,7 @@ */ #include #include +#include #include #include #include @@ -21,19 +22,24 @@ #include #include #include +#include #include +#include +#include #include "queue.h" -#include "i3/ipc.h" +#include "ipc.h" #include "i3.h" #include "util.h" #include "commands.h" +#include "log.h" +#include "table.h" +#include "randr.h" +#include "config.h" -typedef struct ipc_client { - int fd; - - TAILQ_ENTRY(ipc_client) clients; -} ipc_client; +/* Shorter names for all those yajl_gen_* functions */ +#define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__) +#define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str)) TAILQ_HEAD(ipc_client_head, ipc_client) all_clients = TAILQ_HEAD_INITIALIZER(all_clients); @@ -50,49 +56,318 @@ static void set_nonblock(int sockfd) { err(-1, "Could not set O_NONBLOCK"); } -#if 0 -void broadcast(EV_P_ struct ev_timer *t, int revents) { - ipc_client *current; - TAILQ_FOREACH(current, &all_clients, clients) { - write(current->fd, "hi there!\n", strlen("hi there!\n")); - } -} -#endif - /* - * Decides what to do with the received message. - * - * message is the raw packet, as received from the UNIX domain socket. size - * is the remaining size of bytes for this packet. - * - * message_size is the size of the message as the sender specified it. - * message_type is the type of the message as the sender specified it. + * Emulates mkdir -p (creates any missing folders) * */ -static void ipc_handle_message(uint8_t *message, int size, - uint32_t message_size, uint32_t message_type) { - LOG("handling message of size %d\n", size); - LOG("sender specified size %d\n", message_size); - LOG("sender specified type %d\n", message_type); - LOG("payload as a string = %s\n", message); +static bool mkdirp(const char *path) { + if (mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == 0) + return true; + if (errno != ENOENT) { + ELOG("mkdir(%s) failed: %s\n", path, strerror(errno)); + return false; + } + char *copy = strdup(path); + /* strip trailing slashes, if any */ + while (copy[strlen(copy)-1] == '/') + copy[strlen(copy)-1] = '\0'; - switch (message_type) { - case I3_IPC_MESSAGE_TYPE_COMMAND: { - /* To get a properly terminated buffer, we copy - * message_size bytes out of the buffer */ - char *command = scalloc(message_size); - strncpy(command, (const char*)message, message_size); - parse_command(global_conn, (const char*)command); - free(command); + char *sep = strrchr(copy, '/'); + if (sep == NULL) + return false; + *sep = '\0'; + bool result = false; + if (mkdirp(copy)) + result = mkdirp(path); + free(copy); - break; + return result; +} + +static void ipc_send_message(int fd, const unsigned char *payload, + int message_type, int message_size) { + int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) + + sizeof(uint32_t) + message_size; + char msg[buffer_size]; + char *walk = msg; + + strcpy(walk, "i3-ipc"); + walk += strlen("i3-ipc"); + memcpy(walk, &message_size, sizeof(uint32_t)); + walk += sizeof(uint32_t); + memcpy(walk, &message_type, sizeof(uint32_t)); + walk += sizeof(uint32_t); + memcpy(walk, payload, message_size); + + int sent_bytes = 0; + int bytes_to_go = buffer_size; + while (sent_bytes < bytes_to_go) { + int n = write(fd, msg + sent_bytes, bytes_to_go); + if (n == -1) { + DLOG("write() failed: %s\n", strerror(errno)); + return; } - default: - LOG("unhandled ipc message\n"); - break; + + sent_bytes += n; + bytes_to_go -= n; } } +/* + * Sends the specified event to all IPC clients which are currently connected + * and subscribed to this kind of event. + * + */ +void ipc_send_event(const char *event, uint32_t message_type, const char *payload) { + ipc_client *current; + TAILQ_FOREACH(current, &all_clients, clients) { + /* see if this client is interested in this event */ + bool interested = false; + for (int i = 0; i < current->num_events; i++) { + if (strcasecmp(current->events[i], event) != 0) + continue; + interested = true; + break; + } + if (!interested) + continue; + + ipc_send_message(current->fd, (const unsigned char*)payload, + message_type, strlen(payload)); + } +} + +/* + * Calls shutdown() on each socket and closes it. This function to be called + * when exiting or restarting only! + * + */ +void ipc_shutdown() { + ipc_client *current; + TAILQ_FOREACH(current, &all_clients, clients) { + shutdown(current->fd, SHUT_RDWR); + close(current->fd); + } +} + +/* + * Executes the command and returns whether it could be successfully parsed + * or not (at the moment, always returns true). + * + */ +IPC_HANDLER(command) { + /* To get a properly terminated buffer, we copy + * message_size bytes out of the buffer */ + char *command = scalloc(message_size); + strncpy(command, (const char*)message, message_size); + parse_command(global_conn, (const char*)command); + free(command); + + /* For now, every command gets a positive acknowledge + * (will change with the new command parser) */ + const char *reply = "{\"success\":true}"; + ipc_send_message(fd, (const unsigned char*)reply, + I3_IPC_REPLY_TYPE_COMMAND, strlen(reply)); +} + +/* + * Formats the reply message for a GET_WORKSPACES request and sends it to the + * client + * + */ +IPC_HANDLER(get_workspaces) { + Workspace *ws; + + Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); + if (last_focused == SLIST_END(&(c_ws->focus_stack))) + last_focused = NULL; + + yajl_gen gen = yajl_gen_alloc(NULL, NULL); + y(array_open); + + TAILQ_FOREACH(ws, workspaces, workspaces) { + if (ws->output == NULL) + continue; + + y(map_open); + ystr("num"); + y(integer, ws->num + 1); + + ystr("name"); + ystr(ws->utf8_name); + + ystr("visible"); + y(bool, ws->output->current_workspace == ws); + + ystr("focused"); + y(bool, c_ws == ws); + + ystr("rect"); + y(map_open); + ystr("x"); + y(integer, ws->rect.x); + ystr("y"); + y(integer, ws->rect.y); + ystr("width"); + y(integer, ws->rect.width); + ystr("height"); + y(integer, ws->rect.height); + y(map_close); + + ystr("output"); + ystr(ws->output->name); + + ystr("urgent"); + y(bool, ws->urgent); + + y(map_close); + } + + y(array_close); + + const unsigned char *payload; + unsigned int length; + y(get_buf, &payload, &length); + + ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_WORKSPACES, length); + y(free); +} + +/* + * Formats the reply message for a GET_OUTPUTS request and sends it to the + * client + * + */ +IPC_HANDLER(get_outputs) { + Output *output; + + yajl_gen gen = yajl_gen_alloc(NULL, NULL); + y(array_open); + + TAILQ_FOREACH(output, &outputs, outputs) { + y(map_open); + + ystr("name"); + ystr(output->name); + + ystr("active"); + y(bool, output->active); + + ystr("rect"); + y(map_open); + ystr("x"); + y(integer, output->rect.x); + ystr("y"); + y(integer, output->rect.y); + ystr("width"); + y(integer, output->rect.width); + ystr("height"); + y(integer, output->rect.height); + y(map_close); + + ystr("current_workspace"); + if (output->current_workspace == NULL) + y(null); + else y(integer, output->current_workspace->num + 1); + + y(map_close); + } + + y(array_close); + + const unsigned char *payload; + unsigned int length; + y(get_buf, &payload, &length); + + ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_OUTPUTS, length); + y(free); +} + +/* + * Callback for the YAJL parser (will be called when a string is parsed). + * + */ +static int add_subscription(void *extra, const unsigned char *s, + unsigned int len) { + ipc_client *client = extra; + + DLOG("should add subscription to extra %p, sub %.*s\n", client, len, s); + int event = client->num_events; + + client->num_events++; + client->events = realloc(client->events, client->num_events * sizeof(char*)); + /* We copy the string because it is not null-terminated and strndup() + * is missing on some BSD systems */ + client->events[event] = scalloc(len+1); + memcpy(client->events[event], s, len); + + DLOG("client is now subscribed to:\n"); + for (int i = 0; i < client->num_events; i++) + DLOG("event %s\n", client->events[i]); + DLOG("(done)\n"); + + return 1; +} + +/* + * Subscribes this connection to the event types which were given as a JSON + * serialized array in the payload field of the message. + * + */ +IPC_HANDLER(subscribe) { + yajl_handle p; + yajl_callbacks callbacks; + yajl_status stat; + ipc_client *current, *client = NULL; + + /* Search the ipc_client structure for this connection */ + TAILQ_FOREACH(current, &all_clients, clients) { + if (current->fd != fd) + continue; + + client = current; + break; + } + + if (client == NULL) { + ELOG("Could not find ipc_client data structure for fd %d\n", fd); + return; + } + + /* Setup the JSON parser */ + memset(&callbacks, 0, sizeof(yajl_callbacks)); + callbacks.yajl_string = add_subscription; + + p = yajl_alloc(&callbacks, NULL, NULL, (void*)client); + stat = yajl_parse(p, (const unsigned char*)message, message_size); + if (stat != yajl_status_ok) { + unsigned char *err; + err = yajl_get_error(p, true, (const unsigned char*)message, + message_size); + ELOG("YAJL parse error: %s\n", err); + yajl_free_error(p, err); + + const char *reply = "{\"success\":false}"; + ipc_send_message(fd, (const unsigned char*)reply, + I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply)); + yajl_free(p); + return; + } + yajl_free(p); + const char *reply = "{\"success\":true}"; + ipc_send_message(fd, (const unsigned char*)reply, + I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply)); +} + +/* The index of each callback function corresponds to the numeric + * value of the message type (see include/i3/ipc.h) */ +handler_t handlers[4] = { + handle_command, + handle_get_workspaces, + handle_subscribe, + handle_get_outputs +}; + /* * Handler for activity on a client connection, receives a message from a * client. @@ -122,11 +397,13 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) { close(w->fd); /* Delete the client from the list of clients */ - struct ipc_client *current; + ipc_client *current; TAILQ_FOREACH(current, &all_clients, clients) { if (current->fd != w->fd) continue; + for (int i = 0; i < current->num_events; i++) + free(current->events[i]); /* We can call TAILQ_REMOVE because we break out of the * TAILQ_FOREACH afterwards */ TAILQ_REMOVE(&all_clients, current, clients); @@ -135,7 +412,7 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) { ev_io_stop(EV_A_ w); - LOG("IPC: client disconnected\n"); + DLOG("IPC: client disconnected\n"); return; } @@ -144,18 +421,18 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) { /* Check if the message starts with the i3 IPC magic code */ if (n < strlen(I3_IPC_MAGIC)) { - LOG("IPC: message too short, ignoring\n"); + DLOG("IPC: message too short, ignoring\n"); return; } if (strncmp(buf, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) { - LOG("IPC: message does not start with the IPC magic\n"); + DLOG("IPC: message does not start with the IPC magic\n"); return; } uint8_t *message = (uint8_t*)buf; while (n > 0) { - LOG("IPC: n = %d\n", n); + DLOG("IPC: n = %d\n", n); message += strlen(I3_IPC_MAGIC); n -= strlen(I3_IPC_MAGIC); @@ -165,7 +442,7 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) { n -= sizeof(uint32_t); if (message_size > n) { - LOG("IPC: Either the message size was wrong or the message was not read completely, dropping\n"); + DLOG("IPC: Either the message size was wrong or the message was not read completely, dropping\n"); return; } @@ -174,7 +451,12 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) { message += sizeof(uint32_t); n -= sizeof(uint32_t); - ipc_handle_message(message, n, message_size, message_type); + if (message_type >= (sizeof(handlers) / sizeof(handler_t))) + DLOG("Unhandled message type: %d\n", message_type); + else { + handler_t h = handlers[message_type]; + h(w->fd, message, n, message_size, message_type); + } n -= message_size; message += message_size; } @@ -200,13 +482,13 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) { set_nonblock(client); - struct ev_io *package = calloc(sizeof(struct ev_io), 1); + struct ev_io *package = scalloc(sizeof(struct ev_io)); ev_io_init(package, ipc_receive_message, client, EV_READ); ev_io_start(EV_A_ package); - LOG("IPC: new client connected\n"); + DLOG("IPC: new client connected\n"); - struct ipc_client *new = calloc(sizeof(struct ipc_client), 1); + ipc_client *new = scalloc(sizeof(ipc_client)); new->fd = client; TAILQ_INSERT_TAIL(&all_clients, new, clients); @@ -220,11 +502,20 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) { int ipc_create_socket(const char *filename) { int sockfd; + char *globbed = glob_path(filename); + DLOG("Creating IPC-socket at %s\n", globbed); + char *copy = sstrdup(globbed); + const char *dir = dirname(copy); + if (!path_exists(dir)) + mkdirp(dir); + free(copy); + /* Unlink the unix domain socket before */ - unlink(filename); + unlink(globbed); if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) { perror("socket()"); + free(globbed); return -1; } @@ -233,12 +524,14 @@ int ipc_create_socket(const char *filename) { struct sockaddr_un addr; memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_LOCAL; - strcpy(addr.sun_path, filename); + strcpy(addr.sun_path, globbed); if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) { perror("bind()"); + free(globbed); return -1; } + free(globbed); set_nonblock(sockfd); if (listen(sockfd, 5) < 0) { diff --git a/src/layout.c b/src/layout.c index 3092c5df..b1338040 100644 --- a/src/layout.c +++ b/src/layout.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -22,23 +22,14 @@ #include "xcb.h" #include "table.h" #include "util.h" -#include "xinerama.h" +#include "randr.h" #include "layout.h" #include "client.h" #include "floating.h" #include "handlers.h" #include "workspace.h" - -/* - * Updates *destination with new_value and returns true if it was changed or false - * if it was the same - * - */ -static bool update_if_necessary(uint32_t *destination, const uint32_t new_value) { - uint32_t old_value = *destination; - - return ((*destination = new_value) != old_value); -} +#include "log.h" +#include "container.h" /* * Gets the unoccupied space (= space which is available for windows which were resized by the user) @@ -50,16 +41,16 @@ int get_unoccupied_x(Workspace *workspace) { double unoccupied = workspace->rect.width; double default_factor = ((float)workspace->rect.width / workspace->cols) / workspace->rect.width; - LOG("get_unoccupied_x(), starting with %f, default_factor = %f\n", unoccupied, default_factor); + DLOG("get_unoccupied_x(), starting with %f, default_factor = %f\n", unoccupied, default_factor); for (int cols = 0; cols < workspace->cols; cols++) { - LOG("width_factor[%d] = %f, unoccupied = %f\n", cols, workspace->width_factor[cols], unoccupied); + DLOG("width_factor[%d] = %f, unoccupied = %f\n", cols, workspace->width_factor[cols], unoccupied); if (workspace->width_factor[cols] == 0) unoccupied -= workspace->rect.width * default_factor; } - LOG("unoccupied space: %f\n", unoccupied); + DLOG("unoccupied space: %f\n", unoccupied); return unoccupied; } @@ -69,15 +60,15 @@ int get_unoccupied_y(Workspace *workspace) { double unoccupied = height; double default_factor = ((float)height / workspace->rows) / height; - LOG("get_unoccupied_y(), starting with %f, default_factor = %f\n", unoccupied, default_factor); + DLOG("get_unoccupied_y(), starting with %f, default_factor = %f\n", unoccupied, default_factor); for (int rows = 0; rows < workspace->rows; rows++) { - LOG("height_factor[%d] = %f, unoccupied = %f\n", rows, workspace->height_factor[rows], unoccupied); + DLOG("height_factor[%d] = %f, unoccupied = %f\n", rows, workspace->height_factor[rows], unoccupied); if (workspace->height_factor[rows] == 0) unoccupied -= height * default_factor; } - LOG("unoccupied space: %f\n", unoccupied); + DLOG("unoccupied space: %f\n", unoccupied); return unoccupied; } @@ -140,16 +131,15 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw - Draw two lines in a lighter color - Draw the window’s title */ + int mode = container_mode(client->container, true); /* Draw a rectangle in background color around the window */ - if (client->borderless && (client->container == NULL || - (client->container->mode != MODE_STACK && - client->container->mode != MODE_TABBED))) + if (client->borderless && mode == MODE_DEFAULT) xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); else xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, color->background); /* In stacking mode, we only render the rect for this specific decoration */ - if (client->container != NULL && (client->container->mode == MODE_STACK || client->container->mode == MODE_TABBED)) { + if (mode == MODE_STACK || mode == MODE_TABBED) { /* We need to use the container’s width because it is the more recent value - when in stacking mode, clients get reconfigured only on demand (the not active client is not reconfigured), so the client’s rect.width would be wrong */ @@ -164,7 +154,10 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw /* Draw the inner background to have a black frame around clients (such as mplayer) which cannot be resized exactly in our frames and therefore are centered */ xcb_change_gc_single(conn, client->titlegc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); - if (client->titlebar_position == TITLEBAR_OFF) { + if (client->titlebar_position == TITLEBAR_OFF && client->borderless) { + xcb_rectangle_t crect = {0, 0, client->rect.width, client->rect.height}; + xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect); + } else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) { xcb_rectangle_t crect = {1, 1, client->rect.width - (1 + 1), client->rect.height - (1 + 1)}; xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect); } else { @@ -174,22 +167,21 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw } } + mode = container_mode(client->container, false); + if (client->titlebar_position != TITLEBAR_OFF) { /* Draw the lines */ xcb_draw_line(conn, drawable, gc, color->border, offset_x, offset_y, offset_x + client->rect.width, offset_y); - if ((client->container == NULL || - (client->container->mode != MODE_STACK && - client->container->mode != MODE_TABBED) || - CIRCLEQ_NEXT_OR_NULL(&(client->container->clients), client, clients) == NULL)) - xcb_draw_line(conn, drawable, gc, color->border, - offset_x + 2, /* x */ - offset_y + font->height + 3, /* y */ - offset_x + client->rect.width - 3, /* to_x */ - offset_y + font->height + 3 /* to_y */); + xcb_draw_line(conn, drawable, gc, color->border, + offset_x + 2, /* x */ + offset_y + font->height + 3, /* y */ + offset_x + client->rect.width - 3, /* to_x */ + offset_y + font->height + 3 /* to_y */); } /* If the client has a title, we draw it */ - if (client->name != NULL && client->titlebar_position != TITLEBAR_OFF) { + if (client->name != NULL && + (mode != MODE_DEFAULT || client->titlebar_position != TITLEBAR_OFF)) { /* Draw the font */ uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT; uint32_t values[] = { color->text, color->background, font->id }; @@ -212,9 +204,9 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw * */ void reposition_client(xcb_connection_t *conn, Client *client) { - i3Screen *screen; + Output *output; - LOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y); + DLOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y); /* Note: We can use a pointer to client->x like an array of uint32_ts because it is followed by client->y by definition */ xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x)); @@ -223,19 +215,25 @@ void reposition_client(xcb_connection_t *conn, Client *client) { return; /* If the client is floating, we need to check if we moved it to a different workspace */ - screen = get_screen_containing(client->rect.x + (client->rect.width / 2), + output = get_output_containing(client->rect.x + (client->rect.width / 2), client->rect.y + (client->rect.height / 2)); - if (client->workspace->screen == screen) + if (client->workspace->output == output) return; - if (screen == NULL) { - LOG("Boundary checking disabled, no screen found for (%d, %d)\n", client->rect.x, client->rect.y); + if (output == NULL) { + DLOG("Boundary checking disabled, no output found for (%d, %d)\n", client->rect.x, client->rect.y); return; } - LOG("Client is on workspace %p with screen %p\n", client->workspace, client->workspace->screen); - LOG("but screen at %d, %d is %p\n", client->rect.x, client->rect.y, screen); - floating_assign_to_workspace(client, screen->current_workspace); + if (output->current_workspace == NULL) { + DLOG("Boundary checking deferred, no current workspace on output\n"); + client->force_reconfigure = true; + return; + } + + DLOG("Client is on workspace %p with output %p\n", client->workspace, client->workspace->output); + DLOG("but output at %d, %d is %p\n", client->rect.x, client->rect.y, output); + floating_assign_to_workspace(client, output->current_workspace); set_focus(conn, client, true); } @@ -250,24 +248,15 @@ void reposition_client(xcb_connection_t *conn, Client *client) { void resize_client(xcb_connection_t *conn, Client *client) { i3Font *font = load_font(conn, config.font); - LOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y); - LOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height); - xcb_configure_window(conn, client->frame, - XCB_CONFIG_WINDOW_X | - XCB_CONFIG_WINDOW_Y | - XCB_CONFIG_WINDOW_WIDTH | - XCB_CONFIG_WINDOW_HEIGHT, - &(client->rect.x)); + DLOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y); + DLOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height); + xcb_set_window_rect(conn, client->frame, client->rect); /* Adjust the position of the child inside its frame. * The coordinates of the child are relative to its frame, we * add a border of 2 pixel to each value */ - uint32_t mask = XCB_CONFIG_WINDOW_X | - XCB_CONFIG_WINDOW_Y | - XCB_CONFIG_WINDOW_WIDTH | - XCB_CONFIG_WINDOW_HEIGHT; Rect *rect = &(client->child_rect); - switch ((client->container != NULL ? client->container->mode : MODE_DEFAULT)) { + switch (container_mode(client->container, true)) { case MODE_STACK: case MODE_TABBED: rect->x = 2; @@ -301,7 +290,7 @@ void resize_client(xcb_connection_t *conn, Client *client) { /* Obey the ratio, if any */ if (client->proportional_height != 0 && client->proportional_width != 0) { - LOG("proportional height = %d, width = %d\n", client->proportional_height, client->proportional_width); + DLOG("proportional height = %d, width = %d\n", client->proportional_height, client->proportional_width); double new_height = rect->height + 1; int new_width = rect->width; @@ -317,26 +306,26 @@ void resize_client(xcb_connection_t *conn, Client *client) { rect->height = new_height; rect->width = new_width; - LOG("new_height = %f, new_width = %d\n", new_height, new_width); + DLOG("new_height = %f, new_width = %d\n", new_height, new_width); } if (client->height_increment > 1) { int old_height = rect->height; rect->height -= (rect->height - client->base_height) % client->height_increment; - LOG("Lost %d pixel due to client's height_increment (%d px, base_height = %d)\n", + DLOG("Lost %d pixel due to client's height_increment (%d px, base_height = %d)\n", old_height - rect->height, client->height_increment, client->base_height); } if (client->width_increment > 1) { int old_width = rect->width; rect->width -= (rect->width - client->base_width) % client->width_increment; - LOG("Lost %d pixel due to client's width_increment (%d px, base_width = %d)\n", + DLOG("Lost %d pixel due to client's width_increment (%d px, base_width = %d)\n", old_width - rect->width, client->width_increment, client->base_width); } - LOG("child will be at %dx%d with size %dx%d\n", rect->x, rect->y, rect->width, rect->height); + DLOG("child will be at %dx%d with size %dx%d\n", rect->x, rect->y, rect->width, rect->height); - xcb_configure_window(conn, client->child, mask, &(rect->x)); + xcb_set_window_rect(conn, client->child, *rect); /* After configuring a child window we need to fake a configure_notify_event (see ICCCM 4.2.3). * This is necessary to inform the client of its position relative to the root window, @@ -364,6 +353,10 @@ void render_container(xcb_connection_t *conn, Container *container) { num_clients++; if (container->mode == MODE_DEFAULT) { + int height = (container->height / max(1, num_clients)); + int rest_pixels = (container->height % max(1, num_clients)); + DLOG("height per client = %d, rest = %d\n", height, rest_pixels); + CIRCLEQ_FOREACH(client, &(container->clients), clients) { /* If the client is in fullscreen mode, it does not get reconfigured */ if (container->workspace->fullscreen_client == client) { @@ -371,6 +364,13 @@ void render_container(xcb_connection_t *conn, Container *container) { continue; } + /* If we have some pixels left to distribute, add one + * pixel to each client as long as possible. */ + int this_height = height; + if (rest_pixels > 0) { + height++; + rest_pixels--; + } /* Check if we changed client->x or client->y by updating it. * Note the bitwise OR instead of logical OR to force evaluation of both statements */ if (client->force_reconfigure | @@ -378,7 +378,7 @@ void render_container(xcb_connection_t *conn, Container *container) { update_if_necessary(&(client->rect.y), container->y + (container->height / num_clients) * current_client) | update_if_necessary(&(client->rect.width), container->width) | - update_if_necessary(&(client->rect.height), container->height / num_clients)) + update_if_necessary(&(client->rect.height), this_height)) resize_client(conn, client); /* TODO: vertical default layout */ @@ -398,16 +398,16 @@ void render_container(xcb_connection_t *conn, Container *container) { /* Check if we need to remap our stack title window, it gets unmapped when the container is empty in src/handlers.c:unmap_notify() */ - if (stack_win->rect.height == 0 && num_clients > 0) { - LOG("remapping stack win\n"); + if (stack_win->rect.height == 0 && num_clients > 1) { + DLOG("remapping stack win\n"); xcb_map_window(conn, stack_win->window); - } else LOG("not remapping stackwin, height = %d, num_clients = %d\n", + } else DLOG("not remapping stackwin, height = %d, num_clients = %d\n", stack_win->rect.height, num_clients); if (container->mode == MODE_TABBED) { /* By setting num_clients to 1 we force that the stack window will be only one line * high. The rest of the code is useful in both cases. */ - LOG("tabbed mode, setting num_clients = 1\n"); + DLOG("tabbed mode, setting num_clients = 1\n"); if (stack_lines > 1) stack_lines = 1; } @@ -418,11 +418,21 @@ void render_container(xcb_connection_t *conn, Container *container) { stack_lines = min(num_clients, container->stack_limit_value); } + int height = decoration_height * stack_lines; + if (num_clients == 1) { + height = 0; + stack_win->rect.height = 0; + xcb_unmap_window(conn, stack_win->window); + + DLOG("Just one client, setting height to %d\n", height); + } + /* Check if we need to reconfigure our stack title window */ - if (update_if_necessary(&(stack_win->rect.x), container->x) | - update_if_necessary(&(stack_win->rect.y), container->y) | - update_if_necessary(&(stack_win->rect.width), container->width) | - update_if_necessary(&(stack_win->rect.height), decoration_height * stack_lines)) { + if (height > 0 && ( + update_if_necessary(&(stack_win->rect.x), container->x) | + update_if_necessary(&(stack_win->rect.y), container->y) | + update_if_necessary(&(stack_win->rect.width), container->width) | + update_if_necessary(&(stack_win->rect.height), height))) { /* Configuration can happen in two slightly different ways: @@ -456,7 +466,8 @@ void render_container(xcb_connection_t *conn, Container *container) { } /* Prepare the pixmap for usage */ - cached_pixmap_prepare(conn, &(stack_win->pixmap)); + if (num_clients > 1) + cached_pixmap_prepare(conn, &(stack_win->pixmap)); int current_row = 0, current_col = 0; int wrap = 0; @@ -486,9 +497,9 @@ void render_container(xcb_connection_t *conn, Container *container) { * Note the bitwise OR instead of logical OR to force evaluation of all statements */ if (client->force_reconfigure | update_if_necessary(&(client->rect.x), container->x) | - update_if_necessary(&(client->rect.y), container->y + (decoration_height * stack_lines)) | + update_if_necessary(&(client->rect.y), container->y + height) | update_if_necessary(&(client->rect.width), container->width) | - update_if_necessary(&(client->rect.height), container->height - (decoration_height * stack_lines))) + update_if_necessary(&(client->rect.height), container->height - height)) resize_client(conn, client); client->force_reconfigure = false; @@ -520,13 +531,15 @@ void render_container(xcb_connection_t *conn, Container *container) { current_client++; } else if (container->mode == MODE_TABBED) { if (container->stack_limit == STACK_LIMIT_ROWS) { - LOG("You limited this container in its rows. " + LOG("You limited a tabbed container in its rows. " "This makes no sense in tabbing mode.\n"); } offset_x = current_client++ * size_each; } - decorate_window(conn, client, stack_win->pixmap.id, stack_win->pixmap.gc, - offset_x, offset_y); + if (stack_win->pixmap.id == XCB_NONE) + continue; + decorate_window(conn, client, stack_win->pixmap.id, + stack_win->pixmap.gc, offset_x, offset_y); } /* Check if we need to fill one column because of an uneven @@ -553,6 +566,8 @@ void render_container(xcb_connection_t *conn, Container *container) { } } + if (stack_win->pixmap.id == XCB_NONE) + return; xcb_copy_area(conn, stack_win->pixmap.id, stack_win->window, stack_win->pixmap.gc, 0, 0, 0, 0, stack_win->rect.width, stack_win->rect.height); } @@ -560,8 +575,8 @@ void render_container(xcb_connection_t *conn, Container *container) { static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int *height) { Client *client; - SLIST_FOREACH(client, &(r_ws->screen->dock_clients), dock_clients) { - LOG("client is at %d, should be at %d\n", client->rect.y, *height); + SLIST_FOREACH(client, &(r_ws->output->dock_clients), dock_clients) { + DLOG("client is at %d, should be at %d\n", client->rect.y, *height); if (client->force_reconfigure | update_if_necessary(&(client->rect.x), r_ws->rect.x) | update_if_necessary(&(client->rect.y), *height)) @@ -573,55 +588,55 @@ static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int resize_client(conn, client); client->force_reconfigure = false; - LOG("desired_height = %d\n", client->desired_height); + DLOG("desired_height = %d\n", client->desired_height); *height += client->desired_height; } } static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int width, int height) { i3Font *font = load_font(conn, config.font); - i3Screen *screen = r_ws->screen; + Output *output = r_ws->output; enum { SET_NORMAL = 0, SET_FOCUSED = 1 }; /* Fill the whole bar in black */ - xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); + xcb_change_gc_single(conn, output->bargc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); xcb_rectangle_t rect = {0, 0, width, height}; - xcb_poly_fill_rectangle(conn, screen->bar, screen->bargc, 1, &rect); + xcb_poly_fill_rectangle(conn, output->bar, output->bargc, 1, &rect); /* Set font */ - xcb_change_gc_single(conn, screen->bargc, XCB_GC_FONT, font->id); + xcb_change_gc_single(conn, output->bargc, XCB_GC_FONT, font->id); int drawn = 0; Workspace *ws; TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->screen != screen) + if (ws->output != output) continue; struct Colortriple *color; - if (screen->current_workspace == ws) + if (output->current_workspace == ws) color = &(config.bar.focused); else if (ws->urgent) color = &(config.bar.urgent); else color = &(config.bar.unfocused); /* Draw the outer rect */ - xcb_draw_rect(conn, screen->bar, screen->bargc, color->border, + xcb_draw_rect(conn, output->bar, output->bargc, color->border, drawn, /* x */ 1, /* y */ ws->text_width + 5 + 5, /* width = text width + 5 px left + 5px right */ height - 2 /* height = max. height - 1 px upper and 1 px bottom border */); /* Draw the background of this rect */ - xcb_draw_rect(conn, screen->bar, screen->bargc, color->background, + xcb_draw_rect(conn, output->bar, output->bargc, color->background, drawn + 1, 2, ws->text_width + 4 + 4, height - 4); - xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, color->text); - xcb_change_gc_single(conn, screen->bargc, XCB_GC_BACKGROUND, color->background); - xcb_image_text_16(conn, ws->name_len, screen->bar, screen->bargc, drawn + 5 /* X */, + xcb_change_gc_single(conn, output->bargc, XCB_GC_FOREGROUND, color->text); + xcb_change_gc_single(conn, output->bargc, XCB_GC_BACKGROUND, color->background); + xcb_image_text_16(conn, ws->name_len, output->bar, output->bargc, drawn + 5 /* X */, font->height + 1 /* Y = baseline of font */, (xcb_char2b_t*)ws->name); drawn += ws->text_width + 12; @@ -662,18 +677,19 @@ void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace, bo * Renders the given workspace on the given screen * */ -void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws) { +void render_workspace(xcb_connection_t *conn, Output *output, Workspace *r_ws) { i3Font *font = load_font(conn, config.font); int width = r_ws->rect.width; int height = r_ws->rect.height; /* Reserve space for dock clients */ Client *client; - SLIST_FOREACH(client, &(screen->dock_clients), dock_clients) + SLIST_FOREACH(client, &(output->dock_clients), dock_clients) height -= client->desired_height; /* Space for the internal bar */ - height -= (font->height + 6); + if (!config.disable_workspace_bar) + height -= (font->height + 6); int xoffset[r_ws->rows]; int yoffset[r_ws->cols]; @@ -707,7 +723,7 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws) single_width = container->width; } - LOG("height is %d\n", height); + DLOG("height is %d\n", height); container->height = 0; @@ -727,10 +743,21 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws) yoffset[cols] += single_height; } + /* Reposition all floating clients with force_reconfigure == true */ + TAILQ_FOREACH(client, &(r_ws->floating_clients), floating_clients) { + if (!client->force_reconfigure) + continue; + + client->force_reconfigure = false; + reposition_client(conn, client); + resize_client(conn, client); + } + ignore_enter_notify_forall(conn, r_ws, false); render_bars(conn, r_ws, width, &height); - render_internal_bar(conn, r_ws, width, font->height + 6); + if (!config.disable_workspace_bar) + render_internal_bar(conn, r_ws, width, font->height + 6); } /* @@ -742,14 +769,11 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws) * */ void render_layout(xcb_connection_t *conn) { - i3Screen *screen; + Output *output; - if (virtual_screens == NULL) - return; - - TAILQ_FOREACH(screen, virtual_screens, screens) - if (screen->current_workspace != NULL) - render_workspace(conn, screen, screen->current_workspace); + TAILQ_FOREACH(output, &outputs, outputs) + if (output->current_workspace != NULL) + render_workspace(conn, output, output->current_workspace); xcb_flush(conn); } diff --git a/src/log.c b/src/log.c new file mode 100644 index 00000000..1fcf70cb --- /dev/null +++ b/src/log.c @@ -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 +#include +#include +#include + +#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); +} diff --git a/src/mainx.c b/src/mainx.c index d82ecae5..7e1b394b 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -30,7 +30,6 @@ #include #include #include -#include #include @@ -45,9 +44,16 @@ #include "table.h" #include "util.h" #include "xcb.h" +#include "randr.h" #include "xinerama.h" #include "manage.h" #include "ipc.h" +#include "log.h" +#include "sighandler.h" + +static int xkb_event_base; + +int xkb_current_group; xcb_connection_t *global_conn; @@ -82,6 +88,9 @@ int num_screens = 0; /* The depth of the root screen (used e.g. for creating new pixmaps later) */ uint8_t root_depth; +/* We hope that XKB is supported and set this to false */ +bool xkb_supported = true; + /* * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb. * See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop @@ -119,25 +128,65 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) { * */ static void xkb_got_event(EV_P_ struct ev_io *w, int revents) { - LOG("got xkb event, yay\n"); - XEvent ev; + DLOG("Handling XKB event\n"); + XkbEvent ev; + /* When using xmodmap, every change (!) gets an own event. * Therefore, we just read all events and only handle the - * mapping_notify once (we do not receive any other XKB - * events anyway). */ - while (XPending(xkbdpy)) - XNextEvent(xkbdpy, &ev); + * mapping_notify once. */ + bool mapping_changed = false; + while (XPending(xkbdpy)) { + XNextEvent(xkbdpy, (XEvent*)&ev); + /* While we should never receive a non-XKB event, + * better do sanity checking */ + if (ev.type != xkb_event_base) + continue; + if (ev.any.xkb_type == XkbMapNotify) { + mapping_changed = true; + continue; + } + + if (ev.any.xkb_type != XkbStateNotify) { + ELOG("Unknown XKB event received (type %d)\n", ev.any.xkb_type); + continue; + } + + /* See The XKB Extension: Library Specification, section 14.1 */ + /* We check if the current group (each group contains + * two levels) has been changed. Mode_switch activates + * group XkbGroup2Index */ + if (xkb_current_group == ev.state.group) + continue; + + xkb_current_group = ev.state.group; + + if (ev.state.group == XkbGroup2Index) { + DLOG("Mode_switch enabled\n"); + grab_all_keys(global_conn, true); + } + + if (ev.state.group == XkbGroup1Index) { + DLOG("Mode_switch disabled\n"); + ungrab_all_keys(global_conn); + grab_all_keys(global_conn, false); + } + } + + if (!mapping_changed) + return; + + DLOG("Keyboard mapping changed, updating keybindings\n"); xcb_key_symbols_free(keysyms); keysyms = xcb_key_symbols_alloc(global_conn); xcb_get_numlock_mask(global_conn); ungrab_all_keys(global_conn); - LOG("Re-grabbing...\n"); - grab_all_keys(global_conn); - LOG("Done\n"); - + DLOG("Re-grabbing...\n"); + translate_keysyms(); + grab_all_keys(global_conn, (xkb_current_group == XkbGroup2Index)); + DLOG("Done\n"); } @@ -145,6 +194,8 @@ int main(int argc, char *argv[], char *env[]) { int i, screens, opt; char *override_configpath = NULL; bool autostart = true; + bool only_check_config = false; + bool force_xinerama = false; xcb_connection_t *conn; xcb_property_handlers_t prophs; xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS]; @@ -153,6 +204,7 @@ int main(int argc, char *argv[], char *env[]) { {"config", required_argument, 0, 'c'}, {"version", no_argument, 0, 'v'}, {"help", no_argument, 0, 'h'}, + {"force-xinerama", no_argument, 0, 0}, {0, 0, 0, 0} }; int option_index = 0; @@ -165,7 +217,7 @@ int main(int argc, char *argv[], char *env[]) { start_argv = argv; - while ((opt = getopt_long(argc, argv, "c:vahl", long_options, &option_index)) != -1) { + while ((opt = getopt_long(argc, argv, "c:Cvahld:V", long_options, &option_index)) != -1) { switch (opt) { case 'a': LOG("Autostart disabled using -a\n"); @@ -174,18 +226,46 @@ int main(int argc, char *argv[], char *env[]) { case 'c': override_configpath = sstrdup(optarg); break; - case 'v': - printf("i3 version " I3_VERSION " © 2009 Michael Stapelberg and contributors\n"); - exit(EXIT_SUCCESS); - case 'l': - config_use_lexer = true; + case 'C': + LOG("Checking configuration file only (-C)\n"); + only_check_config = true; break; + case 'v': + printf("i3 version " I3_VERSION " © 2009-2010 Michael Stapelberg and contributors\n"); + exit(EXIT_SUCCESS); + case 'V': + set_verbosity(true); + break; + case 'd': + LOG("Enabling debug loglevel %s\n", optarg); + add_loglevel(optarg); + break; + case 'l': + /* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */ + break; + case 0: + if (strcmp(long_options[option_index].name, "force-xinerama") == 0) { + force_xinerama = true; + ELOG("Using Xinerama instead of RandR. This option should be " + "avoided at all cost because it does not refresh the list " + "of screens, so you cannot configure displays at runtime. " + "Please check if your driver really does not support RandR " + "and disable this option as soon as you can.\n"); + break; + } + /* fall-through */ default: - fprintf(stderr, "Usage: %s [-c configfile] [-a] [-v]\n", argv[0]); + fprintf(stderr, "Usage: %s [-c configfile] [-d loglevel] [-a] [-v] [-V] [-C]\n", argv[0]); fprintf(stderr, "\n"); fprintf(stderr, "-a: disable autostart\n"); fprintf(stderr, "-v: display version and exit\n"); + fprintf(stderr, "-V: enable verbose mode\n"); + fprintf(stderr, "-d : enable debug loglevel \n"); fprintf(stderr, "-c : use the provided configfile instead\n"); + fprintf(stderr, "-C: check configuration file and exit\n"); + fprintf(stderr, "--force-xinerama: Use Xinerama instead of RandR. This " + "option should only be used if you are stuck with the " + "nvidia closed source driver which does not support RandR.\n"); exit(EXIT_FAILURE); } } @@ -204,6 +284,10 @@ int main(int argc, char *argv[], char *env[]) { die("Cannot open display\n"); load_configuration(conn, override_configpath, false); + if (only_check_config) { + LOG("Done checking configuration file. Exiting.\n"); + exit(0); + } /* Create the initial container on the first workspace. This used to * be part of init_table, but since it possibly requires an X @@ -234,6 +318,9 @@ int main(int argc, char *argv[], char *env[]) { REQUEST_ATOM(UTF8_STRING); REQUEST_ATOM(WM_STATE); REQUEST_ATOM(WM_CLIENT_LEADER); + REQUEST_ATOM(_NET_CURRENT_DESKTOP); + REQUEST_ATOM(_NET_ACTIVE_WINDOW); + REQUEST_ATOM(_NET_WORKAREA); /* TODO: this has to be more beautiful somewhen */ int major, minor, error; @@ -241,28 +328,32 @@ int main(int argc, char *argv[], char *env[]) { major = XkbMajorVersion; minor = XkbMinorVersion; - int evBase, errBase; + int errBase; - if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) { - fprintf(stderr, "XkbOpenDisplay() failed\n"); - return 1; + if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &xkb_event_base, &errBase, &major, &minor, &error)) == NULL) { + ELOG("ERROR: XkbOpenDisplay() failed, disabling XKB support\n"); + xkb_supported = false; } - if (fcntl(ConnectionNumber(xkbdpy), F_SETFD, FD_CLOEXEC) == -1) { - fprintf(stderr, "Could not set FD_CLOEXEC on xkbdpy\n"); - return 1; - } + if (xkb_supported) { + if (fcntl(ConnectionNumber(xkbdpy), F_SETFD, FD_CLOEXEC) == -1) { + fprintf(stderr, "Could not set FD_CLOEXEC on xkbdpy\n"); + return 1; + } - int i1; - if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) { - fprintf(stderr, "XKB not supported by X-server\n"); - return 1; - } - /* end of ugliness */ + int i1; + if (!XkbQueryExtension(xkbdpy,&i1,&xkb_event_base,&errBase,&major,&minor)) { + fprintf(stderr, "XKB not supported by X-server\n"); + return 1; + } + /* end of ugliness */ - if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd, XkbMapNotifyMask, XkbMapNotifyMask)) { - fprintf(stderr, "Could not set XKB event mask\n"); - return 1; + if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd, + XkbMapNotifyMask | XkbStateNotifyMask, + XkbMapNotifyMask | XkbStateNotifyMask)) { + fprintf(stderr, "Could not set XKB event mask\n"); + return 1; + } } /* Initialize event loop using libev */ @@ -278,11 +369,13 @@ int main(int argc, char *argv[], char *env[]) { ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ); ev_io_start(loop, xcb_watcher); - ev_io_init(xkb, xkb_got_event, ConnectionNumber(xkbdpy), EV_READ); - ev_io_start(loop, xkb); + if (xkb_supported) { + ev_io_init(xkb, xkb_got_event, ConnectionNumber(xkbdpy), EV_READ); + ev_io_start(loop, xkb); - /* Flush the buffer so that libev can properly get new events */ - XFlush(xkbdpy); + /* Flush the buffer so that libev can properly get new events */ + XFlush(xkbdpy); + } ev_check_init(xcb_check, xcb_check_cb); ev_check_start(loop, xcb_check); @@ -305,9 +398,8 @@ int main(int argc, char *argv[], char *env[]) { /* Expose = an Application should redraw itself, in this case it’s our titlebars. */ xcb_event_set_expose_handler(&evenths, handle_expose_event, NULL); - /* Key presses/releases are pretty obvious, I think */ + /* Key presses are pretty obvious, I think */ xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL); - xcb_event_set_key_release_handler(&evenths, handle_key_release, NULL); /* Enter window = user moved his mouse over the window */ xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, NULL); @@ -322,6 +414,9 @@ int main(int argc, char *argv[], char *env[]) { it any longer. Usually, the client destroys the window shortly afterwards. */ xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, NULL); + /* Destroy notify is handled the same as unmap notify */ + xcb_event_set_destroy_notify_handler(&evenths, handle_destroy_notify_event, NULL); + /* Configure notify = window’s configuration (geometry, stacking, …). We only need it to set up ignore the following enter_notify events */ xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, NULL); @@ -359,13 +454,15 @@ int main(int argc, char *argv[], char *env[]) { XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_ENTER_WINDOW }; - xcb_change_window_attributes(conn, root, mask, values); + xcb_void_cookie_t cookie; + cookie = xcb_change_window_attributes_checked(conn, root, mask, values); + check_error(conn, cookie, "Another window manager seems to be running"); /* Setup NetWM atoms */ #define GET_ATOM(name) { \ xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, atom_cookies[name], NULL); \ if (!reply) { \ - LOG("Could not get atom " #name "\n"); \ + ELOG("Could not get atom " #name "\n"); \ exit(-1); \ } \ atoms[name] = reply->atom; \ @@ -390,6 +487,9 @@ int main(int argc, char *argv[], char *env[]) { GET_ATOM(UTF8_STRING); GET_ATOM(WM_STATE); GET_ATOM(WM_CLIENT_LEADER); + GET_ATOM(_NET_CURRENT_DESKTOP); + GET_ATOM(_NET_ACTIVE_WINDOW); + GET_ATOM(_NET_WORKAREA); xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, handle_window_type, NULL); /* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */ @@ -423,38 +523,39 @@ int main(int argc, char *argv[], char *env[]) { xcb_get_numlock_mask(conn); - grab_all_keys(conn); + translate_keysyms(); + grab_all_keys(conn, false); - /* Autostarting exec-lines */ - struct Autostart *exec; - if (autostart) { - TAILQ_FOREACH(exec, &autostarts, autostarts) { - LOG("auto-starting %s\n", exec->command); - start_application(exec->command); - } + int randr_base; + if (force_xinerama) { + initialize_xinerama(conn); + } else { + DLOG("Checking for XRandR...\n"); + initialize_randr(conn, &randr_base); + + xcb_event_set_handler(&evenths, + randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY, + handle_screen_change, + NULL); } - /* check for Xinerama */ - LOG("Checking for Xinerama...\n"); - initialize_xinerama(conn); - xcb_flush(conn); /* Get pointer position to see on which screen we’re starting */ xcb_query_pointer_reply_t *reply; if ((reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL)) == NULL) { - LOG("Could not get pointer position\n"); + ELOG("Could not get pointer position\n"); return 1; } - i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y); + Output *screen = get_output_containing(reply->root_x, reply->root_y); if (screen == NULL) { - LOG("ERROR: No screen at %d x %d, starting on the first screen\n", + ELOG("ERROR: No screen at %d x %d, starting on the first screen\n", reply->root_x, reply->root_y); - screen = TAILQ_FIRST(virtual_screens); + screen = get_first_output(); } - LOG("Starting on %d\n", screen->current_workspace); + DLOG("Starting on %p\n", screen->current_workspace); c_ws = screen->current_workspace; manage_existing_windows(conn, &prophs, root); @@ -463,7 +564,7 @@ int main(int argc, char *argv[], char *env[]) { if (config.ipc_socket_path != NULL) { int ipc_socket = ipc_create_socket(config.ipc_socket_path); if (ipc_socket == -1) { - LOG("Could not create the IPC socket, IPC disabled\n"); + ELOG("Could not create the IPC socket, IPC disabled\n"); } else { struct ev_io *ipc_io = scalloc(sizeof(struct ev_io)); ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ); @@ -474,8 +575,24 @@ int main(int argc, char *argv[], char *env[]) { /* Handle the events which arrived until now */ xcb_check_cb(NULL, NULL, 0); + setup_signal_handler(); + + /* Ignore SIGPIPE to survive errors when an IPC client disconnects + * while we are sending him a message */ + signal(SIGPIPE, SIG_IGN); + /* Ungrab the server to receive events and enter libev’s eventloop */ xcb_ungrab_server(conn); + + /* Autostarting exec-lines */ + struct Autostart *exec; + if (autostart) { + TAILQ_FOREACH(exec, &autostarts, autostarts) { + LOG("auto-starting %s\n", exec->command); + start_application(exec->command); + } + } + ev_loop(loop, 0); /* not reached */ diff --git a/src/manage.c b/src/manage.c index 6e39f51c..10cf74c7 100644 --- a/src/manage.c +++ b/src/manage.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -30,6 +30,8 @@ #include "floating.h" #include "client.h" #include "workspace.h" +#include "log.h" +#include "ewmh.h" /* * Go through all existing windows (if the window manager is restarted) and manage them @@ -61,6 +63,28 @@ void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *pr free(cookies); } +/* + * Restores the geometry of each window by reparenting it to the root window + * at the position of its frame. + * + * This is to be called *only* before exiting/restarting i3 because of evil + * side-effects which are to be expected when continuing to run i3. + * + */ +void restore_geometry(xcb_connection_t *conn) { + Workspace *ws; + Client *client; + DLOG("Restoring geometry\n"); + + TAILQ_FOREACH(ws, workspaces, workspaces) + SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) + xcb_reparent_window(conn, client->child, root, + client->rect.x, client->rect.y); + + /* Make sure our changes reach the X server, we restart/exit now */ + xcb_flush(conn); +} + /* * Do some sanity checks and then reparent the window. * @@ -78,7 +102,7 @@ void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, /* Check if the window is mapped (it could be not mapped when intializing and calling manage_window() for every window) */ if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) { - LOG("Could not get attributes\n"); + ELOG("Could not get attributes\n"); return; } @@ -156,9 +180,9 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, /* Events for already managed windows should already be filtered in manage_window() */ assert(new == NULL); - LOG("Reparenting window 0x%08x\n", child); - LOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height); - new = calloc(sizeof(Client), 1); + LOG("Managing window 0x%08x\n", child); + DLOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height); + new = scalloc(sizeof(Client)); new->force_reconfigure = true; /* Update the data structures */ @@ -220,7 +244,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, new->awaiting_useless_unmap = true; xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height); if (xcb_request_check(conn, cookie) != NULL) { - LOG("Could not reparent the window, aborting\n"); + DLOG("Could not reparent the window, aborting\n"); xcb_destroy_window(conn, new->frame); free(new); return; @@ -247,13 +271,19 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) { for (int i = 0; i < xcb_get_property_value_length(preply); i++) if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) { - LOG("Window is a dock.\n"); + DLOG("Window is a dock.\n"); + Output *t_out = get_output_containing(x, y); + if (t_out != c_ws->output) { + DLOG("Dock client requested to be on output %s by geometry (%d, %d)\n", + t_out->name, x, y); + new->workspace = t_out->current_workspace; + } new->dock = true; new->borderless = true; new->titlebar_position = TITLEBAR_OFF; new->force_reconfigure = true; new->container = NULL; - SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients); + SLIST_INSERT_HEAD(&(t_out->dock_clients), new, dock_clients); /* If it’s a dock we can’t make it float, so we break */ new->floating = FLOATING_AUTO_OFF; break; @@ -263,19 +293,19 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) { /* Set the dialog window to automatically floating, will be used below */ new->floating = FLOATING_AUTO_ON; - LOG("dialog/utility/toolbar/splash window, automatically floating\n"); + DLOG("dialog/utility/toolbar/splash window, automatically floating\n"); } } /* All clients which have a leader should be floating */ if (!new->dock && !client_is_floating(new) && new->leader != 0) { - LOG("Client has WM_CLIENT_LEADER hint set, setting floating\n"); + DLOG("Client has WM_CLIENT_LEADER hint set, setting floating\n"); new->floating = FLOATING_AUTO_ON; } if (new->workspace->auto_float) { new->floating = FLOATING_AUTO_ON; - LOG("workspace is in autofloat mode, setting floating\n"); + DLOG("workspace is in autofloat mode, setting floating\n"); } if (new->dock) { @@ -289,12 +319,12 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, TODO: bars at the top */ new->desired_height = strut[3]; if (new->desired_height == 0) { - LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height); + DLOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height); new->desired_height = original_height; } - LOG("the client wants to be %d pixels high\n", new->desired_height); + DLOG("the client wants to be %d pixels high\n", new->desired_height); } else { - LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height); + DLOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height); new->desired_height = original_height; } } else { @@ -316,6 +346,29 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, preply = xcb_get_property_reply(conn, leader_cookie, NULL); handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply); + /* if WM_CLIENT_LEADER is set, we put the new window on the + * same window as its leader. This might be overwritten by + * assignments afterwards. */ + if (new->leader != XCB_NONE) { + DLOG("client->leader is set (to 0x%08x)\n", new->leader); + Client *parent = table_get(&by_child, new->leader); + if (parent != NULL && parent->container != NULL) { + Workspace *t_ws = parent->workspace; + new->container = t_ws->table[parent->container->col][parent->container->row]; + new->workspace = t_ws; + old_focused = new->container->currently_focused; + map_frame = workspace_is_visible(t_ws); + new->urgent = true; + /* This is a little tricky: we cannot use + * workspace_update_urgent_flag() because the + * new window was not yet inserted into the + * focus stack on t_ws. */ + t_ws->urgent = true; + } else { + DLOG("parent is not usable\n"); + } + } + struct Assignment *assign; TAILQ_FOREACH(assign, &assignments, assignments) { if (get_matching_client(conn, assign->windowclass_title, new) == NULL) @@ -332,14 +385,14 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, LOG("Assignment \"%s\" matches, so putting it on workspace %d\n", assign->windowclass_title, assign->workspace); - if (c_ws->screen->current_workspace->num == (assign->workspace-1)) { - LOG("We are already there, no need to do anything\n"); + if (c_ws->output->current_workspace->num == (assign->workspace-1)) { + DLOG("We are already there, no need to do anything\n"); break; } - LOG("Changing container/workspace and unmapping the client\n"); + DLOG("Changing container/workspace and unmapping the client\n"); Workspace *t_ws = workspace_get(assign->workspace-1); - workspace_initialize(t_ws, c_ws->screen); + workspace_initialize(t_ws, c_ws->output, false); new->container = t_ws->table[t_ws->current_col][t_ws->current_row]; new->workspace = t_ws; @@ -351,7 +404,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, } if (new->workspace->fullscreen_client != NULL) { - LOG("Setting below fullscreen window\n"); + DLOG("Setting below fullscreen window\n"); /* If we are in fullscreen, we should place the window below * the fullscreen window to not be annoying */ @@ -394,10 +447,10 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, * to (0, 0), so we push them to a reasonable position * (centered over their leader) */ if (new->leader != 0 && x == 0 && y == 0) { - LOG("Floating client wants to (0x0), moving it over its leader instead\n"); + DLOG("Floating client wants to (0x0), moving it over its leader instead\n"); Client *leader = table_get(&by_child, new->leader); if (leader == NULL) { - LOG("leader is NULL, centering it over current workspace\n"); + DLOG("leader is NULL, centering it over current workspace\n"); x = c_ws->rect.x + (c_ws->rect.width / 2) - (new->rect.width / 2); y = c_ws->rect.y + (c_ws->rect.height / 2) - (new->rect.height / 2); @@ -408,10 +461,10 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, } new->floating_rect.x = new->rect.x = x; new->floating_rect.y = new->rect.y = y; - LOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n", + DLOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n", new->floating_rect.x, new->floating_rect.y, new->floating_rect.width, new->floating_rect.height); - LOG("outer rect (%d, %d) size (%d, %d)\n", + DLOG("outer rect (%d, %d) size (%d, %d)\n", new->rect.x, new->rect.y, new->rect.width, new->rect.height); /* Make sure it is on top of the other windows */ @@ -455,8 +508,10 @@ map: if (map_frame) render_container(conn, new->container); } - if (new->container == CUR_CELL || client_is_floating(new)) + if (new->container == CUR_CELL || client_is_floating(new)) { xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME); + ewmh_update_active_window(new->child); + } } } diff --git a/src/randr.c b/src/randr.c new file mode 100644 index 00000000..e61fd9b2 --- /dev/null +++ b/src/randr.c @@ -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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "queue.h" +#include "i3.h" +#include "data.h" +#include "table.h" +#include "util.h" +#include "layout.h" +#include "xcb.h" +#include "config.h" +#include "workspace.h" +#include "log.h" +#include "ewmh.h" +#include "ipc.h" +#include "client.h" + +/* While a clean namespace is usually a pretty good thing, we really need + * to use shorter names than the whole xcb_randr_* default names. */ +typedef xcb_randr_get_crtc_info_reply_t crtc_info; +typedef xcb_randr_mode_info_t mode_info; +typedef xcb_randr_get_screen_resources_current_reply_t resources_reply; + +/* Stores all outputs available in your current session. */ +struct outputs_head outputs = TAILQ_HEAD_INITIALIZER(outputs); + +static bool randr_disabled = false; + +/* + * Get a specific output by its internal X11 id. Used by randr_query_outputs + * to check if the output is new (only in the first scan) or if we are + * re-scanning. + * + */ +static Output *get_output_by_id(xcb_randr_output_t id) { + Output *output; + TAILQ_FOREACH(output, &outputs, outputs) + if (output->id == id) + return output; + + return NULL; +} + +/* + * Returns the output with the given name if it is active (!) or NULL. + * + */ +Output *get_output_by_name(const char *name) { + Output *output; + TAILQ_FOREACH(output, &outputs, outputs) + if (output->active && + strcasecmp(output->name, name) == 0) + return output; + + return NULL; +} + +/* + * Returns the first output which is active. + * + */ +Output *get_first_output() { + Output *output; + + TAILQ_FOREACH(output, &outputs, outputs) + if (output->active) + return output; + + return NULL; +} + +/* + * Returns the active (!) output which contains the coordinates x, y or NULL + * if there is no output which contains these coordinates. + * + */ +Output *get_output_containing(int x, int y) { + Output *output; + TAILQ_FOREACH(output, &outputs, outputs) { + if (!output->active) + continue; + DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n", + x, y, output->rect.x, output->rect.y, output->rect.width, output->rect.height); + if (x >= output->rect.x && x < (output->rect.x + output->rect.width) && + y >= output->rect.y && y < (output->rect.y + output->rect.height)) + return output; + } + + return NULL; +} + +/* + * Gets the output which is the last one in the given direction, for example + * the output on the most bottom when direction == D_DOWN, the output most + * right when direction == D_RIGHT and so on. + * + * This function always returns a output. + * + */ +Output *get_output_most(direction_t direction, Output *current) { + Output *output, *candidate = NULL; + int position = 0; + TAILQ_FOREACH(output, &outputs, outputs) { + if (!output->active) + continue; + + /* Repeated calls of WIN determine the winner of the comparison */ + #define WIN(variable, condition) \ + if (variable condition) { \ + candidate = output; \ + position = variable; \ + } \ + break; + + if (((direction == D_UP) || (direction == D_DOWN)) && + (current->rect.x != output->rect.x)) + continue; + + if (((direction == D_LEFT) || (direction == D_RIGHT)) && + (current->rect.y != output->rect.y)) + continue; + + switch (direction) { + case D_UP: + WIN(output->rect.y, <= position); + case D_DOWN: + WIN(output->rect.y, >= position); + case D_LEFT: + WIN(output->rect.x, <= position); + case D_RIGHT: + WIN(output->rect.x, >= position); + } + } + + assert(candidate != NULL); + + return candidate; +} + +/* + * Initializes the specified output, assigning the specified workspace to it. + * + */ +void initialize_output(xcb_connection_t *conn, Output *output, Workspace *workspace) { + i3Font *font = load_font(conn, config.font); + + workspace->output = output; + output->current_workspace = workspace; + + /* Copy rect for the workspace */ + memcpy(&(workspace->rect), &(output->rect), sizeof(Rect)); + + /* Map clients on the workspace, if any */ + workspace_map_clients(conn, workspace); + + /* Create a bar window on each output */ + if (!config.disable_workspace_bar) { + Rect bar_rect = {output->rect.x, + output->rect.y + output->rect.height - (font->height + 6), + output->rect.x + output->rect.width, + font->height + 6}; + uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; + uint32_t values[] = {1, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS}; + output->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, true, mask, values); + output->bargc = xcb_generate_id(conn); + xcb_create_gc(conn, output->bargc, output->bar, 0, 0); + } + + SLIST_INIT(&(output->dock_clients)); + + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); + DLOG("initialized output at (%d, %d) with %d x %d\n", + output->rect.x, output->rect.y, output->rect.width, output->rect.height); + + DLOG("assigning configured workspaces to this output...\n"); + Workspace *ws; + TAILQ_FOREACH(ws, workspaces, workspaces) { + if (ws == workspace) + continue; + if (ws->preferred_output == NULL || + get_output_by_name(ws->preferred_output) != output) + continue; + + DLOG("assigning ws %d\n", ws->num + 1); + workspace_assign_to(ws, output, true); + } +} + +/* + * Disables RandR support by creating exactly one output with the size of the + * X11 screen. + * + */ +void disable_randr(xcb_connection_t *conn) { + xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; + + DLOG("RandR extension unusable, disabling.\n"); + + Output *s = scalloc(sizeof(Output)); + + s->active = true; + s->rect.x = 0; + s->rect.y = 0; + s->rect.width = root_screen->width_in_pixels; + s->rect.height = root_screen->height_in_pixels; + s->name = "xroot-0"; + + TAILQ_INSERT_TAIL(&outputs, s, outputs); + + randr_disabled = true; +} + +/* + * This function needs to be called when changing the mode of an output when + * it already has some workspaces (or a bar window) assigned. + * + * It reconfigures the bar window for the new mode, copies the new rect into + * each workspace on this output and forces all windows on the affected + * workspaces to be reconfigured. + * + * It is necessary to call render_layout() afterwards. + * + */ +static void output_change_mode(xcb_connection_t *conn, Output *output) { + i3Font *font = load_font(conn, config.font); + Workspace *ws; + Client *client; + + DLOG("Output mode changed, reconfiguring bar, updating workspaces\n"); + Rect bar_rect = {output->rect.x, + output->rect.y + output->rect.height - (font->height + 6), + output->rect.x + output->rect.width, + font->height + 6}; + + xcb_set_window_rect(conn, output->bar, bar_rect); + + /* go through all workspaces and set force_reconfigure */ + TAILQ_FOREACH(ws, workspaces, workspaces) { + if (ws->output != output) + continue; + + SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) { + client->force_reconfigure = true; + if (!client_is_floating(client)) + continue; + /* For floating clients we need to translate the + * coordinates (old workspace to new workspace) */ + DLOG("old: (%x, %x)\n", client->rect.x, client->rect.y); + client->rect.x -= ws->rect.x; + client->rect.y -= ws->rect.y; + client->rect.x += ws->output->rect.x; + client->rect.y += ws->output->rect.y; + DLOG("new: (%x, %x)\n", client->rect.x, client->rect.y); + } + + /* Update dimensions from output */ + memcpy(&(ws->rect), &(ws->output->rect), sizeof(Rect)); + + /* Update the dimensions of a fullscreen client, if any */ + if (ws->fullscreen_client != NULL) { + DLOG("Updating fullscreen client size\n"); + client = ws->fullscreen_client; + Rect r = ws->rect; + xcb_set_window_rect(conn, client->frame, r); + + r.x = 0; + r.y = 0; + xcb_set_window_rect(conn, client->child, r); + } + } +} + +/* + * Gets called by randr_query_outputs() for each output. The function adds new + * outputs to the list of outputs, checks if the mode of existing outputs has + * been changed or if an existing output has been disabled. It will then change + * either the "changed" or the "to_be_deleted" flag of the output, if + * appropriate. + * + */ +static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, + xcb_randr_get_output_info_reply_t *output, + xcb_timestamp_t cts, resources_reply *res) { + /* each CRT controller has a position in which we are interested in */ + crtc_info *crtc; + + Output *new = get_output_by_id(id); + bool existing = (new != NULL); + if (!existing) + new = scalloc(sizeof(Output)); + new->id = id; + FREE(new->name); + asprintf(&new->name, "%.*s", + xcb_randr_get_output_info_name_length(output), + xcb_randr_get_output_info_name(output)); + + DLOG("found output with name %s\n", new->name); + + /* Even if no CRTC is used at the moment, we store the output so that + * we do not need to change the list ever again (we only update the + * position/size) */ + if (output->crtc == XCB_NONE) { + if (!existing) + TAILQ_INSERT_TAIL(&outputs, new, outputs); + else if (new->active) + new->to_be_disabled = true; + return; + } + + xcb_randr_get_crtc_info_cookie_t icookie; + icookie = xcb_randr_get_crtc_info(conn, output->crtc, cts); + if ((crtc = xcb_randr_get_crtc_info_reply(conn, icookie, NULL)) == NULL) { + DLOG("Skipping output %s: could not get CRTC (%p)\n", + new->name, crtc); + free(new); + return; + } + + bool updated = update_if_necessary(&(new->rect.x), crtc->x) | + update_if_necessary(&(new->rect.y), crtc->y) | + update_if_necessary(&(new->rect.width), crtc->width) | + update_if_necessary(&(new->rect.height), crtc->height); + free(crtc); + new->active = (new->rect.width != 0 && new->rect.height != 0); + if (!new->active) { + DLOG("width/height 0/0, disabling output\n"); + return; + } + + DLOG("mode: %dx%d+%d+%d\n", new->rect.width, new->rect.height, + new->rect.x, new->rect.y); + + /* If we don’t need to change an existing output or if the output + * does not exist in the first place, the case is simple: we either + * need to insert the new output or we are done. */ + if (!updated || !existing) { + if (!existing) + TAILQ_INSERT_TAIL(&outputs, new, outputs); + return; + } + + new->changed = true; +} + +/* + * (Re-)queries the outputs via RandR and stores them in the list of outputs. + * + */ +void randr_query_outputs(xcb_connection_t *conn) { + Workspace *ws; + Output *output, *other, *first; + xcb_randr_get_screen_resources_current_cookie_t rcookie; + resources_reply *res; + /* timestamp of the configuration so that we get consistent replies to all + * requests (if the configuration changes between our different calls) */ + xcb_timestamp_t cts; + + /* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */ + xcb_randr_output_t *randr_outputs; + + if (randr_disabled) + return; + + /* Get screen resources (crtcs, outputs, modes) */ + rcookie = xcb_randr_get_screen_resources_current(conn, root); + if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) { + disable_randr(conn); + return; + } + cts = res->config_timestamp; + + int len = xcb_randr_get_screen_resources_current_outputs_length(res); + randr_outputs = xcb_randr_get_screen_resources_current_outputs(res); + + /* Request information for each output */ + xcb_randr_get_output_info_cookie_t ocookie[len]; + for (int i = 0; i < len; i++) + ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts); + + /* Loop through all outputs available for this X11 screen */ + for (int i = 0; i < len; i++) { + xcb_randr_get_output_info_reply_t *output; + + if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL) + continue; + + handle_output(conn, randr_outputs[i], output, cts, res); + free(output); + } + + free(res); + /* Check for clones, disable the clones and reduce the mode to the + * lowest common mode */ + TAILQ_FOREACH(output, &outputs, outputs) { + if (!output->active || output->to_be_disabled) + continue; + DLOG("output %p, position (%d, %d), checking for clones\n", + output, output->rect.x, output->rect.y); + + for (other = output; + other != TAILQ_END(&outputs); + other = TAILQ_NEXT(other, outputs)) { + if (other == output || !other->active || other->to_be_disabled) + continue; + + if (other->rect.x != output->rect.x || + other->rect.y != output->rect.y) + continue; + + DLOG("output %p has the same position, his mode = %d x %d\n", + other, other->rect.width, other->rect.height); + uint32_t width = min(other->rect.width, output->rect.width); + uint32_t height = min(other->rect.height, output->rect.height); + + if (update_if_necessary(&(output->rect.width), width) | + update_if_necessary(&(output->rect.height), height)) + output->changed = true; + + update_if_necessary(&(other->rect.width), width); + update_if_necessary(&(other->rect.height), height); + + DLOG("disabling output %p (%s)\n", other, other->name); + other->to_be_disabled = true; + + DLOG("new output mode %d x %d, other mode %d x %d\n", + output->rect.width, output->rect.height, + other->rect.width, other->rect.height); + } + } + + /* Handle outputs which have a new mode or are disabled now (either + * because the user disabled them or because they are clones) */ + TAILQ_FOREACH(output, &outputs, outputs) { + if (output->to_be_disabled) { + output->active = false; + DLOG("Output %s disabled, re-assigning workspaces/docks\n", output->name); + + if ((first = get_first_output()) == NULL) + die("No usable outputs available\n"); + + bool needs_init = (first->current_workspace == NULL); + + TAILQ_FOREACH(ws, workspaces, workspaces) { + if (ws->output != output) + continue; + + workspace_assign_to(ws, first, true); + if (!needs_init) + continue; + initialize_output(conn, first, ws); + needs_init = false; + } + + Client *dock; + while (!SLIST_EMPTY(&(output->dock_clients))) { + dock = SLIST_FIRST(&(output->dock_clients)); + SLIST_REMOVE_HEAD(&(output->dock_clients), dock_clients); + SLIST_INSERT_HEAD(&(first->dock_clients), dock, dock_clients); + } + output->current_workspace = NULL; + output->to_be_disabled = false; + } else if (output->changed) { + output_change_mode(conn, output); + output->changed = false; + } + } + + if (TAILQ_EMPTY(&outputs)) { + ELOG("No outputs found via RandR, disabling\n"); + disable_randr(conn); + } + + ewmh_update_workarea(); + + /* Just go through each active output and associate one workspace */ + TAILQ_FOREACH(output, &outputs, outputs) { + if (!output->active || output->current_workspace != NULL) + continue; + ws = get_first_workspace_for_output(output); + initialize_output(conn, output, ws); + } + + /* render_layout flushes */ + render_layout(conn); +} + +/* + * We have just established a connection to the X server and need the initial + * XRandR information to setup workspaces for each screen. + * + */ +void initialize_randr(xcb_connection_t *conn, int *event_base) { + const xcb_query_extension_reply_t *extreply; + + extreply = xcb_get_extension_data(conn, &xcb_randr_id); + if (!extreply->present) + disable_randr(conn); + else randr_query_outputs(conn); + + if (event_base != NULL) + *event_base = extreply->first_event; + + xcb_randr_select_input(conn, root, + XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE | + XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE | + XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE | + XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY); + + xcb_flush(conn); +} diff --git a/src/resize.c b/src/resize.c index 6d505dcd..d48dbdbb 100644 --- a/src/resize.c +++ b/src/resize.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -24,10 +24,49 @@ #include "xcb.h" #include "debug.h" #include "layout.h" -#include "xinerama.h" +#include "randr.h" #include "config.h" #include "floating.h" #include "workspace.h" +#include "log.h" + +/* + * This is an ugly data structure which we need because there is no standard + * way of having nested functions (only available as a gcc extension at the + * moment, clang doesn’t support it) or blocks (only available as a clang + * extension and only on Mac OS X systems at the moment). + * + */ +struct callback_params { + resize_orientation_t orientation; + Output *screen; + xcb_window_t helpwin; + uint32_t *new_position; +}; + +DRAGGING_CB(resize_callback) { + struct callback_params *params = extra; + Output *screen = params->screen; + DLOG("new x = %d, y = %d\n", new_x, new_y); + if (params->orientation == O_VERTICAL) { + /* Check if the new coordinates are within screen boundaries */ + if (new_x > (screen->rect.x + screen->rect.width - 25) || + new_x < (screen->rect.x + 25)) + return; + + *(params->new_position) = new_x; + xcb_configure_window(conn, params->helpwin, XCB_CONFIG_WINDOW_X, params->new_position); + } else { + if (new_y > (screen->rect.y + screen->rect.height - 25) || + new_y < (screen->rect.y + 25)) + return; + + *(params->new_position) = new_y; + xcb_configure_window(conn, params->helpwin, XCB_CONFIG_WINDOW_Y, params->new_position); + } + + xcb_flush(conn); +} /* * Renders the resize window between the first/second container and resizes @@ -36,10 +75,10 @@ */ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, int second, resize_orientation_t orientation, xcb_button_press_event_t *event) { - int new_position; - i3Screen *screen = get_screen_containing(event->root_x, event->root_y); + uint32_t new_position; + Output *screen = get_output_containing(event->root_x, event->root_y); if (screen == NULL) { - LOG("BUG: No screen found at this position (%d, %d)\n", event->root_x, event->root_y); + ELOG("BUG: No screen found at this position (%d, %d)\n", event->root_x, event->root_y); return 1; } @@ -48,12 +87,12 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i * screens during runtime. Instead, we just use the most right and most * bottom Xinerama screen and use their position + width/height to get * the area of pixels currently in use */ - i3Screen *most_right = get_screen_most(D_RIGHT, screen), - *most_bottom = get_screen_most(D_DOWN, screen); + Output *most_right = get_output_most(D_RIGHT, screen), + *most_bottom = get_output_most(D_DOWN, screen); - LOG("event->event_x = %d, event->root_x = %d\n", event->event_x, event->root_x); + DLOG("event->event_x = %d, event->root_x = %d\n", event->event_x, event->root_x); - LOG("Screen dimensions: (%d, %d) %d x %d\n", screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height); + DLOG("Screen dimensions: (%d, %d) %d x %d\n", screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height); uint32_t mask = 0; uint32_t values[2]; @@ -92,36 +131,16 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i xcb_window_t helpwin = create_window(conn, helprect, XCB_WINDOW_CLASS_INPUT_OUTPUT, (orientation == O_VERTICAL ? - XCB_CURSOR_SB_V_DOUBLE_ARROW : - XCB_CURSOR_SB_H_DOUBLE_ARROW), true, mask, values); + XCB_CURSOR_SB_H_DOUBLE_ARROW : + XCB_CURSOR_SB_V_DOUBLE_ARROW), true, mask, values); xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin); xcb_flush(conn); - void resize_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) { - LOG("new x = %d, y = %d\n", new_x, new_y); - if (orientation == O_VERTICAL) { - /* Check if the new coordinates are within screen boundaries */ - if (new_x > (screen->rect.x + screen->rect.width - 25) || - new_x < (screen->rect.x + 25)) - return; + struct callback_params params = { orientation, screen, helpwin, &new_position }; - values[0] = new_position = new_x; - xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_X, values); - } else { - if (new_y > (screen->rect.y + screen->rect.height - 25) || - new_y < (screen->rect.y + 25)) - return; - - values[0] = new_position = new_y; - xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_Y, values); - } - - xcb_flush(conn); - } - - drag_pointer(conn, NULL, event, grabwin, BORDER_TOP, resize_callback); + drag_pointer(conn, NULL, event, grabwin, BORDER_TOP, resize_callback, ¶ms); xcb_destroy_window(conn, helpwin); xcb_destroy_window(conn, grabwin); @@ -163,8 +182,29 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco if (ws->width_factor[second] == 0) new_unoccupied_x += default_width; - LOG("\n\n\n"); - LOG("old = %d, new = %d\n", old_unoccupied_x, new_unoccupied_x); + DLOG("\n\n\n"); + DLOG("old = %d, new = %d\n", old_unoccupied_x, new_unoccupied_x); + + int cols_without_wf = 0; + int old_width, old_second_width; + for (int col = 0; col < ws->cols; col++) + if (ws->width_factor[col] == 0) + cols_without_wf++; + + DLOG("old_unoccupied_x = %d\n", old_unoccupied_x); + + DLOG("Updating first (before = %f)\n", ws->width_factor[first]); + /* Convert 0 (for default width_factor) to actual numbers */ + if (ws->width_factor[first] == 0) + old_width = (old_unoccupied_x / max(cols_without_wf, 1)); + else old_width = ws->width_factor[first] * old_unoccupied_x; + + DLOG("second (before = %f)\n", ws->width_factor[second]); + if (ws->width_factor[second] == 0) + old_second_width = (old_unoccupied_x / max(cols_without_wf, 1)); + else old_second_width = ws->width_factor[second] * old_unoccupied_x; + + DLOG("middle = %f\n", ws->width_factor[first]); /* If the space used for customly resized columns has changed we need to adapt the * other customly resized columns, if any */ @@ -173,37 +213,33 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco if (ws->width_factor[col] == 0) continue; - LOG("Updating other column (%d) (current width_factor = %f)\n", col, ws->width_factor[col]); + DLOG("Updating other column (%d) (current width_factor = %f)\n", col, ws->width_factor[col]); ws->width_factor[col] = (ws->width_factor[col] * old_unoccupied_x) / new_unoccupied_x; - LOG("to %f\n", ws->width_factor[col]); + DLOG("to %f\n", ws->width_factor[col]); } - LOG("old_unoccupied_x = %d\n", old_unoccupied_x); - - LOG("Updating first (before = %f)\n", ws->width_factor[first]); + DLOG("Updating first (before = %f)\n", ws->width_factor[first]); /* Convert 0 (for default width_factor) to actual numbers */ if (ws->width_factor[first] == 0) ws->width_factor[first] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x; - LOG("middle = %f\n", ws->width_factor[first]); - int old_width = ws->width_factor[first] * old_unoccupied_x; - LOG("first->width = %d, pixels = %d\n", pixels); + DLOG("first->width = %d, pixels = %d\n", old_width, pixels); ws->width_factor[first] *= (float)(old_width + pixels) / old_width; - LOG("-> %f\n", ws->width_factor[first]); + DLOG("-> %f\n", ws->width_factor[first]); - LOG("Updating second (before = %f)\n", ws->width_factor[second]); + DLOG("Updating second (before = %f)\n", ws->width_factor[second]); if (ws->width_factor[second] == 0) ws->width_factor[second] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x; - LOG("middle = %f\n", ws->width_factor[second]); - old_width = ws->width_factor[second] * old_unoccupied_x; - LOG("second->width = %d, pixels = %d\n", pixels); - ws->width_factor[second] *= (float)(old_width - pixels) / old_width; - LOG("-> %f\n", ws->width_factor[second]); - LOG("new unoccupied_x = %d\n", get_unoccupied_x(ws)); + DLOG("middle = %f\n", ws->width_factor[second]); + DLOG("second->width = %d, pixels = %d\n", old_second_width, pixels); + ws->width_factor[second] *= (float)(old_second_width - pixels) / old_second_width; + DLOG("-> %f\n", ws->width_factor[second]); - LOG("\n\n\n"); + DLOG("new unoccupied_x = %d\n", get_unoccupied_x(ws)); + + DLOG("\n\n\n"); } else { int ws_height = workspace_height(ws); int default_height = ws_height / ws->rows; @@ -228,24 +264,25 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco if (ws->height_factor[row] == 0) cols_without_hf++; - LOG("old_unoccupied_y = %d\n", old_unoccupied_y); + DLOG("old_unoccupied_y = %d\n", old_unoccupied_y); + + DLOG("Updating first (before = %f)\n", ws->height_factor[first]); - LOG("Updating first (before = %f)\n", ws->height_factor[first]); /* Convert 0 (for default width_factor) to actual numbers */ if (ws->height_factor[first] == 0) old_height = (old_unoccupied_y / max(cols_without_hf, 1)); else old_height = ws->height_factor[first] * old_unoccupied_y; - LOG("second (before = %f)\n", ws->height_factor[second]); + DLOG("second (before = %f)\n", ws->height_factor[second]); if (ws->height_factor[second] == 0) old_second_height = (old_unoccupied_y / max(cols_without_hf, 1)); else old_second_height = ws->height_factor[second] * old_unoccupied_y; - LOG("middle = %f\n", ws->height_factor[first]); + DLOG("middle = %f\n", ws->height_factor[first]); - LOG("\n\n\n"); - LOG("old = %d, new = %d\n", old_unoccupied_y, new_unoccupied_y); + DLOG("\n\n\n"); + DLOG("old = %d, new = %d\n", old_unoccupied_y, new_unoccupied_y); /* If the space used for customly resized columns has changed we need to adapt the * other customly resized columns, if any */ @@ -254,33 +291,33 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco if (ws->height_factor[row] == 0) continue; - LOG("Updating other column (%d) (current width_factor = %f)\n", row, ws->height_factor[row]); + DLOG("Updating other column (%d) (current width_factor = %f)\n", row, ws->height_factor[row]); ws->height_factor[row] = (ws->height_factor[row] * old_unoccupied_y) / new_unoccupied_y; - LOG("to %f\n", ws->height_factor[row]); + DLOG("to %f\n", ws->height_factor[row]); } - LOG("Updating first (before = %f)\n", ws->height_factor[first]); + DLOG("Updating first (before = %f)\n", ws->height_factor[first]); /* Convert 0 (for default width_factor) to actual numbers */ if (ws->height_factor[first] == 0) ws->height_factor[first] = ((float)ws_height / ws->rows) / new_unoccupied_y; - LOG("first->width = %d, pixels = %d\n", old_height, pixels); + DLOG("first->width = %d, pixels = %d\n", old_height, pixels); ws->height_factor[first] *= (float)(old_height + pixels) / old_height; - LOG("-> %f\n", ws->height_factor[first]); + DLOG("-> %f\n", ws->height_factor[first]); - LOG("Updating second (before = %f)\n", ws->height_factor[second]); + DLOG("Updating second (before = %f)\n", ws->height_factor[second]); if (ws->height_factor[second] == 0) ws->height_factor[second] = ((float)ws_height / ws->rows) / new_unoccupied_y; - LOG("middle = %f\n", ws->height_factor[second]); - LOG("second->width = %d, pixels = %d\n", old_second_height, pixels); + DLOG("middle = %f\n", ws->height_factor[second]); + DLOG("second->width = %d, pixels = %d\n", old_second_height, pixels); ws->height_factor[second] *= (float)(old_second_height - pixels) / old_second_height; - LOG("-> %f\n", ws->height_factor[second]); + DLOG("-> %f\n", ws->height_factor[second]); - LOG("new unoccupied_y = %d\n", get_unoccupied_y(ws)); + DLOG("new unoccupied_y = %d\n", get_unoccupied_y(ws)); - LOG("\n\n\n"); + DLOG("\n\n\n"); } render_layout(conn); diff --git a/src/sighandler.c b/src/sighandler.c new file mode 100644 index 00000000..92cbc5cb --- /dev/null +++ b/src/sighandler.c @@ -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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#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"); +} diff --git a/src/table.c b/src/table.c index 4330ca11..71081013 100644 --- a/src/table.c +++ b/src/table.c @@ -27,6 +27,7 @@ #include "layout.h" #include "config.h" #include "workspace.h" +#include "log.h" int current_workspace = 0; int num_workspaces = 1; @@ -52,7 +53,7 @@ void init_table() { static void new_container(Workspace *workspace, Container **container, int col, int row, bool skip_layout_switch) { Container *new; - new = *container = calloc(sizeof(Container), 1); + new = *container = scalloc(sizeof(Container)); CIRCLEQ_INIT(&(new->clients)); new->colspan = 1; new->rowspan = 1; @@ -96,9 +97,9 @@ void expand_table_rows_at_head(Workspace *workspace) { workspace->height_factor = realloc(workspace->height_factor, sizeof(float) * workspace->rows); - LOG("rows = %d\n", workspace->rows); + DLOG("rows = %d\n", workspace->rows); for (int rows = (workspace->rows - 1); rows >= 1; rows--) { - LOG("Moving height_factor %d (%f) to %d\n", rows-1, workspace->height_factor[rows-1], rows); + DLOG("Moving height_factor %d (%f) to %d\n", rows-1, workspace->height_factor[rows-1], rows); workspace->height_factor[rows] = workspace->height_factor[rows-1]; } @@ -110,7 +111,7 @@ void expand_table_rows_at_head(Workspace *workspace) { /* Move the other rows */ for (int cols = 0; cols < workspace->cols; cols++) for (int rows = workspace->rows - 1; rows > 0; rows--) { - LOG("Moving row %d to %d\n", rows-1, rows); + DLOG("Moving row %d to %d\n", rows-1, rows); workspace->table[cols][rows] = workspace->table[cols][rows-1]; workspace->table[cols][rows]->row = rows; } @@ -130,7 +131,7 @@ void expand_table_cols(Workspace *workspace) { workspace->width_factor[workspace->cols-1] = 0; workspace->table = realloc(workspace->table, sizeof(Container**) * workspace->cols); - workspace->table[workspace->cols-1] = calloc(sizeof(Container*) * workspace->rows, 1); + workspace->table[workspace->cols-1] = scalloc(sizeof(Container*) * workspace->rows); for (int c = 0; c < workspace->rows; c++) new_container(workspace, &(workspace->table[workspace->cols-1][c]), workspace->cols-1, c, true); @@ -148,21 +149,21 @@ void expand_table_cols_at_head(Workspace *workspace) { workspace->width_factor = realloc(workspace->width_factor, sizeof(float) * workspace->cols); - LOG("cols = %d\n", workspace->cols); + DLOG("cols = %d\n", workspace->cols); for (int cols = (workspace->cols - 1); cols >= 1; cols--) { - LOG("Moving width_factor %d (%f) to %d\n", cols-1, workspace->width_factor[cols-1], cols); + DLOG("Moving width_factor %d (%f) to %d\n", cols-1, workspace->width_factor[cols-1], cols); workspace->width_factor[cols] = workspace->width_factor[cols-1]; } workspace->width_factor[0] = 0; workspace->table = realloc(workspace->table, sizeof(Container**) * workspace->cols); - workspace->table[workspace->cols-1] = calloc(sizeof(Container*) * workspace->rows, 1); + workspace->table[workspace->cols-1] = scalloc(sizeof(Container*) * workspace->rows); /* Move the other columns */ for (int rows = 0; rows < workspace->rows; rows++) for (int cols = workspace->cols - 1; cols > 0; cols--) { - LOG("Moving col %d to %d\n", cols-1, cols); + DLOG("Moving col %d to %d\n", cols-1, cols); workspace->table[cols][rows] = workspace->table[cols-1][rows]; workspace->table[cols][rows]->col = cols; } @@ -201,7 +202,7 @@ static void shrink_table_cols(Workspace *workspace) { if (workspace->width_factor[cols] == 0) continue; - LOG("Added free space (%f) to %d (had %f)\n", free_space, cols, + DLOG("Added free space (%f) to %d (had %f)\n", free_space, cols, workspace->width_factor[cols]); workspace->width_factor[cols] += free_space; break; @@ -230,7 +231,7 @@ static void shrink_table_rows(Workspace *workspace) { if (workspace->height_factor[rows] == 0) continue; - LOG("Added free space (%f) to %d (had %f)\n", free_space, rows, + DLOG("Added free space (%f) to %d (had %f)\n", free_space, rows, workspace->height_factor[rows]); workspace->height_factor[rows] += free_space; break; @@ -256,7 +257,7 @@ static void free_container(xcb_connection_t *conn, Workspace *workspace, int col } static void move_columns_from(xcb_connection_t *conn, Workspace *workspace, int cols) { - LOG("firstly freeing \n"); + DLOG("firstly freeing \n"); /* Free the columns which are cleaned up */ for (int rows = 0; rows < workspace->rows; rows++) @@ -264,10 +265,10 @@ static void move_columns_from(xcb_connection_t *conn, Workspace *workspace, int for (; cols < workspace->cols; cols++) for (int rows = 0; rows < workspace->rows; rows++) { - LOG("at col = %d, row = %d\n", cols, rows); + DLOG("at col = %d, row = %d\n", cols, rows); Container *new_container = workspace->table[cols][rows]; - LOG("moving cols = %d to cols -1 = %d\n", cols, cols-1); + DLOG("moving cols = %d to cols -1 = %d\n", cols, cols-1); workspace->table[cols-1][rows] = new_container; new_container->row = rows; @@ -283,7 +284,7 @@ static void move_rows_from(xcb_connection_t *conn, Workspace *workspace, int row for (int cols = 0; cols < workspace->cols; cols++) { Container *new_container = workspace->table[cols][rows]; - LOG("moving rows = %d to rows -1 = %d\n", rows, rows - 1); + DLOG("moving rows = %d to rows -1 = %d\n", rows, rows - 1); workspace->table[cols][rows-1] = new_container; new_container->row = rows-1; @@ -296,19 +297,19 @@ static void move_rows_from(xcb_connection_t *conn, Workspace *workspace, int row * */ void dump_table(xcb_connection_t *conn, Workspace *workspace) { - LOG("dump_table()\n"); + DLOG("dump_table()\n"); FOR_TABLE(workspace) { Container *con = workspace->table[cols][rows]; - LOG("----\n"); - LOG("at col=%d, row=%d\n", cols, rows); - LOG("currently_focused = %p\n", con->currently_focused); + DLOG("----\n"); + DLOG("at col=%d, row=%d\n", cols, rows); + DLOG("currently_focused = %p\n", con->currently_focused); Client *loop; CIRCLEQ_FOREACH(loop, &(con->clients), clients) { - LOG("got client %08x / %s\n", loop->child, loop->name); + DLOG("got client %08x / %s\n", loop->child, loop->name); } - LOG("----\n"); + DLOG("----\n"); } - LOG("done\n"); + DLOG("done\n"); } /* @@ -316,7 +317,7 @@ void dump_table(xcb_connection_t *conn, Workspace *workspace) { * */ void cleanup_table(xcb_connection_t *conn, Workspace *workspace) { - LOG("cleanup_table()\n"); + DLOG("cleanup_table()\n"); /* Check for empty columns if we got more than one column */ for (int cols = 0; (workspace->cols > 1) && (cols < workspace->cols);) { @@ -327,7 +328,7 @@ void cleanup_table(xcb_connection_t *conn, Workspace *workspace) { break; } if (completely_empty) { - LOG("Removing completely empty column %d\n", cols); + DLOG("Removing completely empty column %d\n", cols); if (cols < (workspace->cols - 1)) move_columns_from(conn, workspace, cols+1); else { @@ -344,14 +345,14 @@ void cleanup_table(xcb_connection_t *conn, Workspace *workspace) { /* Check for empty rows if we got more than one row */ for (int rows = 0; (workspace->rows > 1) && (rows < workspace->rows);) { bool completely_empty = true; - LOG("Checking row %d\n", rows); + DLOG("Checking row %d\n", rows); for (int cols = 0; cols < workspace->cols; cols++) if (workspace->table[cols][rows]->currently_focused != NULL) { completely_empty = false; break; } if (completely_empty) { - LOG("Removing completely empty row %d\n", rows); + DLOG("Removing completely empty row %d\n", rows); if (rows < (workspace->rows - 1)) move_rows_from(conn, workspace, rows+1); else { @@ -381,25 +382,25 @@ void cleanup_table(xcb_connection_t *conn, Workspace *workspace) { * */ void fix_colrowspan(xcb_connection_t *conn, Workspace *workspace) { - LOG("Fixing col/rowspan\n"); + DLOG("Fixing col/rowspan\n"); FOR_TABLE(workspace) { Container *con = workspace->table[cols][rows]; if (con->colspan > 1) { - LOG("gots one with colspan %d (at %d c, %d r)\n", con->colspan, cols, rows); + DLOG("gots one with colspan %d (at %d c, %d r)\n", con->colspan, cols, rows); while (con->colspan > 1 && (!cell_exists(workspace, cols + (con->colspan-1), rows) && workspace->table[cols + (con->colspan - 1)][rows]->currently_focused != NULL)) con->colspan--; - LOG("fixed it to %d\n", con->colspan); + DLOG("fixed it to %d\n", con->colspan); } if (con->rowspan > 1) { - LOG("gots one with rowspan %d (at %d c, %d r)\n", con->rowspan, cols, rows); + DLOG("gots one with rowspan %d (at %d c, %d r)\n", con->rowspan, cols, rows); while (con->rowspan > 1 && (!cell_exists(workspace, cols, rows + (con->rowspan - 1)) && workspace->table[cols][rows + (con->rowspan - 1)]->currently_focused != NULL)) con->rowspan--; - LOG("fixed it to %d\n", con->rowspan); + DLOG("fixed it to %d\n", con->rowspan); } } } diff --git a/src/util.c b/src/util.c index a2b8cd67..cb37d30a 100644 --- a/src/util.c +++ b/src/util.c @@ -31,6 +31,11 @@ #include "util.h" #include "xcb.h" #include "client.h" +#include "log.h" +#include "ewmh.h" +#include "manage.h" +#include "workspace.h" +#include "ipc.h" static iconv_t conversion_descriptor = 0; struct keyvalue_table_head by_parent = TAILQ_HEAD_INITIALIZER(by_parent); @@ -45,24 +50,14 @@ int max(int a, int b) { } /* - * Logs the given message to stdout while prefixing the current time to it. - * This is to be called by LOG() which includes filename/linenumber + * Updates *destination with new_value and returns true if it was changed or false + * if it was the same * */ -void slog(char *fmt, ...) { - va_list args; - char timebuf[64]; +bool update_if_necessary(uint32_t *destination, const uint32_t new_value) { + uint32_t old_value = *destination; - va_start(args, fmt); - /* Get current time */ - time_t t = time(NULL); - /* Convert time to local time (determined by the locale) */ - struct tm *tmp = localtime(&t); - /* Generate time prefix */ - strftime(timebuf, sizeof(timebuf), "%x %X - ", tmp); - printf("%s", timebuf); - vprintf(fmt, args); - va_end(args); + return ((*destination = new_value) != old_value); } /* @@ -148,7 +143,7 @@ void start_application(const char *command) { shell = "/bin/sh"; /* This is the child */ - execl(shell, shell, "-c", command, NULL); + execl(shell, shell, "-c", command, (void*)NULL); /* not reached */ } exit(0); @@ -164,7 +159,7 @@ void start_application(const char *command) { void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_message) { xcb_generic_error_t *error = xcb_request_check(conn, cookie); if (error != NULL) { - fprintf(stderr, "ERROR: %s : %d\n", err_message , error->error_code); + fprintf(stderr, "ERROR: %s (X error %d)\n", err_message , error->error_code); xcb_disconnect(conn); exit(-1); } @@ -178,16 +173,16 @@ void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_mes * */ char *convert_utf8_to_ucs2(char *input, int *real_strlen) { - size_t input_size = strlen(input) + 1; - /* UCS-2 consumes exactly two bytes for each glyph */ - int buffer_size = input_size * 2; + size_t input_size = strlen(input) + 1; + /* UCS-2 consumes exactly two bytes for each glyph */ + int buffer_size = input_size * 2; - char *buffer = smalloc(buffer_size); - size_t output_size = buffer_size; - /* We need to use an additional pointer, because iconv() modifies it */ - char *output = buffer; + char *buffer = smalloc(buffer_size); + size_t output_size = buffer_size; + /* We need to use an additional pointer, because iconv() modifies it */ + char *output = buffer; - /* We convert the input into UCS-2 big endian */ + /* We convert the input into UCS-2 big endian */ if (conversion_descriptor == 0) { conversion_descriptor = iconv_open("UCS-2BE", "UTF-8"); if (conversion_descriptor == 0) { @@ -196,22 +191,22 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen) { } } - /* Get the conversion descriptor back to original state */ - iconv(conversion_descriptor, NULL, NULL, NULL, NULL); + /* Get the conversion descriptor back to original state */ + iconv(conversion_descriptor, NULL, NULL, NULL, NULL); - /* Convert our text */ - int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size); + /* Convert our text */ + int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size); if (rc == (size_t)-1) { perror("Converting to UCS-2 failed"); if (real_strlen != NULL) - *real_strlen = 0; + *real_strlen = 0; return NULL; - } + } if (real_strlen != NULL) - *real_strlen = ((buffer_size - output_size) / 2) - 1; + *real_strlen = ((buffer_size - output_size) / 2) - 1; - return buffer; + return buffer; } /* @@ -250,6 +245,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) { c_ws->current_row = current_row; c_ws->current_col = current_col; c_ws = client->workspace; + ewmh_update_current_desktop(); /* Load current_col/current_row if we switch to a client without a container */ current_col = c_ws->current_col; current_row = c_ws->current_row; @@ -265,6 +261,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) { CLIENT_LOG(client); /* Set focus to the entered window, and flush xcb buffer immediately */ xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, client->child, XCB_CURRENT_TIME); + ewmh_update_active_window(client->child); //xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, 10, 10); if (client->container != NULL) { @@ -280,7 +277,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) { Client *last_focused = get_last_focused_client(conn, client->container, client); if (last_focused != NULL) { - LOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child); + DLOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child); uint32_t values[] = { last_focused->frame, XCB_STACK_MODE_ABOVE }; xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); } @@ -294,22 +291,27 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) { /* If the last client was a floating client, we need to go to the next * tiling client in stack and re-decorate it. */ if (old_client != NULL && client_is_floating(old_client)) { - LOG("Coming from floating client, searching next tiling...\n"); + DLOG("Coming from floating client, searching next tiling...\n"); Client *current; SLIST_FOREACH(current, &(client->workspace->focus_stack), focus_clients) { if (client_is_floating(current)) continue; - LOG("Found window: %p / child %p\n", current->frame, current->child); + DLOG("Found window: %p / child %p\n", current->frame, current->child); redecorate_window(conn, current); break; } - } SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients); + /* Clear the urgency flag if set (necessary when i3 sets the flag, for + * example when automatically putting windows on the workspace of their + * leader) */ + client->urgent = false; + workspace_update_urgent_flag(client->workspace); + /* If we’re in stacking mode, this renders the container to update changes in the title bars and to raise the focused client */ if ((old_client != NULL) && (old_client != client) && !old_client->dock) @@ -411,14 +413,14 @@ after_stackwin: if (client == container->currently_focused || client == last_focused) continue; - LOG("setting %08x below %08x / %08x\n", client->frame, container->currently_focused->frame); + DLOG("setting %08x below %08x / %08x\n", client->frame, container->currently_focused->frame); uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW }; xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); } if (last_focused != NULL) { - LOG("Putting last_focused directly underneath the currently focused\n"); + DLOG("Putting last_focused directly underneath the currently focused\n"); uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW }; xcb_configure_window(conn, last_focused->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); @@ -457,15 +459,15 @@ Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitl goto done; } - LOG("Getting clients for class \"%s\" / title \"%s\"\n", to_class, to_title); + DLOG("Getting clients for class \"%s\" / title \"%s\"\n", to_class, to_title); Workspace *ws; TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->screen == NULL) + if (ws->output == NULL) continue; Client *client; SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) { - LOG("Checking client with class=%s / %s, name=%s\n", client->window_class_instance, + DLOG("Checking client with class=%s / %s, name=%s\n", client->window_class_instance, client->window_class_class, client->name); if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len)) continue; @@ -481,6 +483,47 @@ done: return matching; } +/* + * Goes through the list of arguments (for exec()) and checks if the given argument + * is present. If not, it copies the arguments (because we cannot realloc it) and + * appends the given argument. + * + */ +static char **append_argument(char **original, char *argument) { + int num_args; + for (num_args = 0; original[num_args] != NULL; num_args++) { + DLOG("original argument: \"%s\"\n", original[num_args]); + /* If the argument is already present we return the original pointer */ + if (strcmp(original[num_args], argument) == 0) + return original; + } + /* Copy the original array */ + char **result = smalloc((num_args+2) * sizeof(char*)); + memcpy(result, original, num_args * sizeof(char*)); + result[num_args] = argument; + result[num_args+1] = NULL; + + return result; +} + +/* + * Restart i3 in-place + * appends -a to argument list to disable autostart + * + */ +void i3_restart() { + restore_geometry(global_conn); + + ipc_shutdown(); + + LOG("restarting \"%s\"...\n", start_argv[0]); + /* make sure -a is in the argument list or append it */ + start_argv = append_argument(start_argv, "-a"); + + execvp(start_argv[0], start_argv); + /* not reached */ +} + #if defined(__OpenBSD__) /* diff --git a/src/workspace.c b/src/workspace.c index d58bbaab..6da6034b 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -22,10 +22,13 @@ #include "config.h" #include "xcb.h" #include "table.h" -#include "xinerama.h" +#include "randr.h" #include "layout.h" #include "workspace.h" #include "client.h" +#include "log.h" +#include "ewmh.h" +#include "ipc.h" /* * Returns a pointer to the workspace with the given number (starting at 0), @@ -42,10 +45,10 @@ Workspace *workspace_get(int number) { /* If we are still there, we could not find the requested workspace. */ int last_ws = TAILQ_LAST(workspaces, workspaces_head)->num; - LOG("We need to initialize that one, last ws = %d\n", last_ws); + DLOG("We need to initialize that one, last ws = %d\n", last_ws); for (int c = last_ws; c < number; c++) { - LOG("Creating new ws\n"); + DLOG("Creating new ws\n"); ws = scalloc(sizeof(Workspace)); ws->num = c+1; @@ -55,8 +58,12 @@ Workspace *workspace_get(int number) { workspace_set_name(ws, NULL); TAILQ_INSERT_TAIL(workspaces, ws, workspaces); + + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); } - LOG("done\n"); + DLOG("done\n"); + + ewmh_update_workarea(); return ws; } @@ -80,13 +87,13 @@ void workspace_set_name(Workspace *ws, const char *name) { errx(1, "asprintf() failed"); FREE(ws->name); + FREE(ws->utf8_name); ws->name = convert_utf8_to_ucs2(label, &(ws->name_len)); if (config.font != NULL) ws->text_width = predict_text_width(global_conn, config.font, ws->name, ws->name_len); else ws->text_width = 0; - - free(label); + ws->utf8_name = label; } /* @@ -96,7 +103,7 @@ void workspace_set_name(Workspace *ws, const char *name) { * */ bool workspace_is_visible(Workspace *ws) { - return (ws->screen->current_workspace == ws); + return (ws->output != NULL && ws->output->current_workspace == ws); } /* @@ -109,29 +116,29 @@ void workspace_show(xcb_connection_t *conn, int workspace) { /* t_ws (to workspace) is just a convenience pointer to the workspace we’re switching to */ Workspace *t_ws = workspace_get(workspace-1); - LOG("show_workspace(%d)\n", workspace); + DLOG("show_workspace(%d)\n", workspace); /* Store current_row/current_col */ c_ws->current_row = current_row; c_ws->current_col = current_col; /* Check if the workspace has not been used yet */ - workspace_initialize(t_ws, c_ws->screen); + workspace_initialize(t_ws, c_ws->output, false); - if (c_ws->screen != t_ws->screen) { - /* We need to switch to the other screen first */ - LOG("moving over to other screen.\n"); + if (c_ws->output != t_ws->output) { + /* We need to switch to the other output first */ + DLOG("moving over to other output.\n"); /* Store the old client */ Client *old_client = CUR_CELL->currently_focused; - c_ws = t_ws->screen->current_workspace; + c_ws = t_ws->output->current_workspace; current_col = c_ws->current_col; current_row = c_ws->current_row; if (CUR_CELL->currently_focused != NULL) need_warp = true; else { - Rect *dims = &(c_ws->screen->rect); + Rect *dims = &(c_ws->output->rect); xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, dims->x + (dims->width / 2), dims->y + (dims->height / 2)); } @@ -140,10 +147,19 @@ void workspace_show(xcb_connection_t *conn, int workspace) { if ((old_client != NULL) && !old_client->dock) redecorate_window(conn, old_client); else xcb_flush(conn); + + /* We need to check if a global fullscreen-client is blocking + * the t_ws and if necessary switch that to local fullscreen */ + Client* client = c_ws->fullscreen_client; + if (client != NULL && client->workspace != c_ws) { + if (c_ws->fullscreen_client->workspace != c_ws) + c_ws->fullscreen_client = NULL; + client_enter_fullscreen(conn, client, false); + } } /* Check if we need to change something or if we’re already there */ - if (c_ws->screen->current_workspace->num == (workspace-1)) { + if (c_ws->output->current_workspace->num == (workspace-1)) { Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); if (last_focused != SLIST_END(&(c_ws->focus_stack))) set_focus(conn, last_focused, true); @@ -152,24 +168,28 @@ void workspace_show(xcb_connection_t *conn, int workspace) { xcb_flush(conn); } + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}"); + return; } Workspace *old_workspace = c_ws; - c_ws = t_ws->screen->current_workspace = workspace_get(workspace-1); + c_ws = t_ws->output->current_workspace = workspace_get(workspace-1); /* Unmap all clients of the old workspace */ workspace_unmap_clients(conn, old_workspace); current_row = c_ws->current_row; current_col = c_ws->current_col; - LOG("new current row = %d, current col = %d\n", current_row, current_col); + DLOG("new current row = %d, current col = %d\n", current_row, current_col); + + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}"); workspace_map_clients(conn, c_ws); /* POTENTIAL TO IMPROVE HERE: due to the call to _map_clients first and * render_layout afterwards, there is a short flickering on the source - * workspace (assign ws 3 to screen 0, ws 4 to screen 1, create single + * workspace (assign ws 3 to output 0, ws 4 to output 1, create single * client on ws 4, move it to ws 3, switch to ws 3, you’ll see the * flickering). */ @@ -184,63 +204,61 @@ void workspace_show(xcb_connection_t *conn, int workspace) { /* We can warp the pointer only after the window has been * reconfigured in render_layout, otherwise the pointer will * be warped to the old position, which will not work when we - * moved it to another screen. */ + * moved it to another output. */ if (last_focused != SLIST_END(&(c_ws->focus_stack)) && need_warp) { client_warp_pointer_into(conn, last_focused); xcb_flush(conn); } } - /* - * Parses the preferred_screen property of a workspace. You can either specify - * the screen number (it is not given that the screen numbering always stays - * the same) or the screen coordinates (exact coordinates, e.g. 1280 will match - * the screen starting at x=1280, but 1281 will not). For coordinates, you can - * either specify an x coordinate ("1280") or an y coordinate ("x800") or both - * ("1280x800"). + * Assigns the given workspace to the given output by correctly updating its + * state and reconfiguring all the clients on this workspace. + * + * This is called when initializing a output and when re-assigning it to a + * different output which just got available (if you configured it to be on + * output 1 and you just plugged in output 1). * */ -static i3Screen *get_screen_from_preference(struct screens_head *slist, char *preference) { - i3Screen *screen; - char *rest; - int preferred_screen = strtol(preference, &rest, 10); +void workspace_assign_to(Workspace *ws, Output *output, bool hide_it) { + Client *client; + bool empty = true; + bool visible = workspace_is_visible(ws); - LOG("Getting screen for preference \"%s\" (%d)\n", preference, preferred_screen); + ws->output = output; - if ((rest == preference) || (preferred_screen >= num_screens)) { - int x = INT_MAX, y = INT_MAX; - if (strchr(preference, 'x') != NULL) { - /* Check if only the y coordinate was specified */ - if (*preference == 'x') - y = atoi(preference+1); - else { - x = atoi(preference); - y = atoi(strchr(preference, 'x') + 1); - } - } else { - x = atoi(preference); - } + /* Copy the dimensions from the virtual output */ + memcpy(&(ws->rect), &(ws->output->rect), sizeof(Rect)); - LOG("Looking for screen at %d x %d\n", x, y); + ewmh_update_workarea(); - TAILQ_FOREACH(screen, slist, screens) - if ((x == INT_MAX || screen->rect.x == x) && - (y == INT_MAX || screen->rect.y == y)) { - LOG("found %p\n", screen); - return screen; - } - - LOG("none found\n"); - return NULL; - } else { - int c = 0; - TAILQ_FOREACH(screen, slist, screens) - if (c++ == preferred_screen) - return screen; + /* Force reconfiguration for each client on that workspace */ + SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) { + client->force_reconfigure = true; + empty = false; } - return NULL; + if (empty) + return; + + /* Render the workspace to reconfigure the clients. However, they will be visible now, so… */ + render_workspace(global_conn, output, ws); + + /* …unless we want to see them at the moment, we should hide that workspace */ + if (visible && !hide_it) + return; + + /* however, if this is the current workspace, we only need to adjust + * the output’s current_workspace pointer (and must not unmap the + * windows) */ + if (c_ws == ws) { + DLOG("Need to adjust output->current_workspace...\n"); + output->current_workspace = c_ws; + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}"); + return; + } + + workspace_unmap_clients(global_conn, ws); } /* @@ -250,35 +268,43 @@ static i3Screen *get_screen_from_preference(struct screens_head *slist, char *pr * the screen is not attached at the moment. * */ -void workspace_initialize(Workspace *ws, i3Screen *screen) { - if (ws->screen != NULL) { - LOG("Workspace already initialized\n"); +void workspace_initialize(Workspace *ws, Output *output, bool recheck) { + Output *old_output; + + if (ws->output != NULL && !recheck) { + DLOG("Workspace already initialized\n"); return; } - /* If this workspace has no preferred screen or if the screen it wants - * to be on is not available at the moment, we initialize it with - * the screen which was given */ - if (ws->preferred_screen == NULL || - (ws->screen = get_screen_from_preference(virtual_screens, ws->preferred_screen)) == NULL) - ws->screen = screen; + old_output = ws->output; - /* Copy the dimensions from the virtual screen */ - memcpy(&(ws->rect), &(ws->screen->rect), sizeof(Rect)); + /* If this workspace has no preferred output or if the output it wants + * to be on is not available at the moment, we initialize it with + * the output which was given */ + if (ws->preferred_output == NULL || + (ws->output = get_output_by_name(ws->preferred_output)) == NULL) + ws->output = output; + + DLOG("old_output = %p, ws->output = %p\n", old_output, ws->output); + /* If the assignment did not change, we do not need to update anything */ + if (old_output != NULL && ws->output == old_output) + return; + + workspace_assign_to(ws, ws->output, false); } /* * Gets the first unused workspace for the given screen, taking into account - * the preferred_screen setting of every workspace (workspace assignments). + * the preferred_output setting of every workspace (workspace assignments). * */ -Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen *screen) { +Workspace *get_first_workspace_for_output(Output *output) { Workspace *result = NULL; Workspace *ws; TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->preferred_screen == NULL || - !screens_are_equal(get_screen_from_preference(slist, ws->preferred_screen), screen)) + if (ws->preferred_output == NULL || + get_output_by_name(ws->preferred_output) != output) continue; result = ws; @@ -287,9 +313,8 @@ Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen * if (result == NULL) { /* No assignment found, returning first unused workspace */ - Workspace *ws; TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->screen != NULL) + if (ws->output != NULL) continue; result = ws; @@ -298,16 +323,15 @@ Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen * } if (result == NULL) { - LOG("No existing free workspace found to assign, creating a new one\n"); + DLOG("No existing free workspace found to assign, creating a new one\n"); - Workspace *ws; int last_ws = 0; TAILQ_FOREACH(ws, workspaces, workspaces) last_ws = ws->num; result = workspace_get(last_ws + 1); } - workspace_initialize(result, screen); + workspace_initialize(result, output, false); return result; } @@ -359,7 +383,7 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) { int unmapped_clients = 0; FOR_TABLE(u_ws) CIRCLEQ_FOREACH(client, &(u_ws->table[cols][rows]->clients), clients) { - LOG("unmapping normal client %p / %p / %p\n", client, client->frame, client->child); + DLOG("unmapping normal client %p / %p / %p\n", client, client->frame, client->child); client_unmap(conn, client); unmapped_clients++; } @@ -369,7 +393,7 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) { if (!client_is_floating(client)) continue; - LOG("unmapping floating client %p / %p / %p\n", client, client->frame, client->child); + DLOG("unmapping floating client %p / %p / %p\n", client, client->frame, client->child); client_unmap(conn, client); unmapped_clients++; @@ -380,15 +404,15 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) { if (unmapped_clients == 0 && u_ws != c_ws) { /* Re-assign the workspace of all dock clients which use this workspace */ Client *dock; - LOG("workspace %p is empty\n", u_ws); - SLIST_FOREACH(dock, &(u_ws->screen->dock_clients), dock_clients) { + DLOG("workspace %p is empty\n", u_ws); + SLIST_FOREACH(dock, &(u_ws->output->dock_clients), dock_clients) { if (dock->workspace != u_ws) continue; - LOG("Re-assigning dock client to c_ws (%p)\n", c_ws); + DLOG("Re-assigning dock client to c_ws (%p)\n", c_ws); dock->workspace = c_ws; } - u_ws->screen = NULL; + u_ws->output = NULL; } /* Unmap the stack windows on the given workspace, if any */ @@ -406,16 +430,50 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) { */ void workspace_update_urgent_flag(Workspace *ws) { Client *current; + bool old_flag = ws->urgent; + bool urgent = false; SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) { if (!current->urgent) continue; - ws->urgent = true; - return; + urgent = true; + break; } - ws->urgent = false; + ws->urgent = urgent; + + if (old_flag != urgent) + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"urgent\"}"); +} + +/* + * Returns the width of the workspace. + * + */ +int workspace_width(Workspace *ws) { + return ws->rect.width; +} + +/* + * Returns the effective height of the workspace (without the internal bar and + * without dock clients). + * + */ +int workspace_height(Workspace *ws) { + int height = ws->rect.height; + i3Font *font = load_font(global_conn, config.font); + + /* Reserve space for dock clients */ + Client *client; + SLIST_FOREACH(client, &(ws->output->dock_clients), dock_clients) + height -= client->desired_height; + + /* Space for the internal bar */ + if (!config.disable_workspace_bar) + height -= (font->height + 6); + + return height; } /* diff --git a/src/xcb.c b/src/xcb.c index 9e803c84..ee3148ed 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -21,6 +21,7 @@ #include "i3.h" #include "util.h" #include "xcb.h" +#include "log.h" TAILQ_HEAD(cached_fonts_head, Font) cached_fonts = TAILQ_HEAD_INITIALIZER(cached_fonts); unsigned int xcb_numlock_mask; @@ -98,14 +99,6 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_cl /* If the window class is XCB_WINDOW_CLASS_INPUT_ONLY, depth has to be 0 */ uint16_t depth = (window_class == XCB_WINDOW_CLASS_INPUT_ONLY ? 0 : XCB_COPY_FROM_PARENT); - /* Use the default cursor (left pointer) */ - if (cursor > -1) { - i3Font *cursor_font = load_font(conn, "cursor"); - xcb_create_glyph_cursor(conn, cursor_id, cursor_font->id, cursor_font->id, - XCB_CURSOR_LEFT_PTR, XCB_CURSOR_LEFT_PTR + 1, - 0, 0, 0, 65535, 65535, 65535); - } - xcb_create_window(conn, depth, result, /* the window id */ @@ -117,8 +110,14 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_cl mask, values); - if (cursor > -1) - xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id); + /* Set the cursor */ + i3Font *cursor_font = load_font(conn, "cursor"); + xcb_create_glyph_cursor(conn, cursor_id, cursor_font->id, cursor_font->id, + (cursor == -1 ? XCB_CURSOR_LEFT_PTR : cursor), + (cursor == -1 ? XCB_CURSOR_LEFT_PTR : cursor) + 1, + 0, 0, 0, 65535, 65535, 65535); + xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id); + xcb_free_cursor(conn, cursor_id); /* Map the window (= make it visible) */ if (map) @@ -270,7 +269,7 @@ void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window) { * */ void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap) { - LOG("preparing pixmap\n"); + DLOG("preparing pixmap\n"); /* If the Rect did not change, the pixmap does not need to be recreated */ if (memcmp(&(pixmap->rect), pixmap->referred_rect, sizeof(Rect)) == 0) @@ -279,11 +278,11 @@ void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap) memcpy(&(pixmap->rect), pixmap->referred_rect, sizeof(Rect)); if (pixmap->id == 0 || pixmap->gc == 0) { - LOG("Creating new pixmap...\n"); + DLOG("Creating new pixmap...\n"); pixmap->id = xcb_generate_id(conn); pixmap->gc = xcb_generate_id(conn); } else { - LOG("Re-creating this pixmap...\n"); + DLOG("Re-creating this pixmap...\n"); xcb_free_gc(conn, pixmap->gc); xcb_free_pixmap(conn, pixmap->id); } @@ -309,7 +308,7 @@ int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *t cookie = xcb_query_text_extents(conn, font->id, length, (xcb_char2b_t*)text); if ((reply = xcb_query_text_extents_reply(conn, cookie, &error)) == NULL) { - LOG("Could not get text extents (X error code %d)\n", + ELOG("Could not get text extents (X error code %d)\n", error->error_code); /* We return the rather safe guess of 7 pixels, because a * rendering error is better than a crash. Plus, the user will @@ -321,3 +320,16 @@ int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *t free(reply); return width; } + +/* + * Configures the given window to have the size/position specified by given rect + * + */ +void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r) { + xcb_configure_window(conn, window, + XCB_CONFIG_WINDOW_X | + XCB_CONFIG_WINDOW_Y | + XCB_CONFIG_WINDOW_WIDTH | + XCB_CONFIG_WINDOW_HEIGHT, + &(r.x)); +} diff --git a/src/xinerama.c b/src/xinerama.c index 6c87e775..d7efff0d 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -3,234 +3,96 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * + * This is LEGACY code (we support RandR, which can do much more than + * Xinerama), but necessary for the poor users of the nVidia binary + * driver which does not support RandR in 2010 *sigh*. + * */ #include #include #include -#include -#include -#include #include #include #include "queue.h" -#include "i3.h" #include "data.h" -#include "table.h" #include "util.h" #include "xinerama.h" -#include "layout.h" -#include "xcb.h" -#include "config.h" #include "workspace.h" +#include "log.h" +#include "randr.h" -/* This TAILQ of i3Screens stores the virtual screens, used for handling overlapping screens - * (xrandr --same-as) */ -struct screens_head *virtual_screens; - -static bool xinerama_enabled = true; +static int num_screens; /* - * Returns true if both screen objects describe the same screen (checks their - * size and position). + * Looks in outputs for the Output whose start coordinates are x, y * */ -bool screens_are_equal(i3Screen *screen1, i3Screen *screen2) { - /* If one of both objects (or both) are NULL, we cannot compare them */ - if (screen1 == NULL || screen2 == NULL) - return false; - - /* If the pointers are equal, take the short-circuit */ - if (screen1 == screen2) - return true; - - /* Compare their size - other properties are not relevant to determine - * if a screen is equal to another one */ - return (memcmp(&(screen1->rect), &(screen2->rect), sizeof(Rect)) == 0); -} - -/* - * Looks in virtual_screens for the i3Screen whose start coordinates are x, y - * - */ -i3Screen *get_screen_at(int x, int y, struct screens_head *screenlist) { - i3Screen *screen; - TAILQ_FOREACH(screen, screenlist, screens) - if (screen->rect.x == x && screen->rect.y == y) - return screen; +static Output *get_screen_at(int x, int y) { + Output *output; + TAILQ_FOREACH(output, &outputs, outputs) + if (output->rect.x == x && output->rect.y == y) + return output; return NULL; } /* - * Looks in virtual_screens for the i3Screen which contains coordinates x, y - * - */ -i3Screen *get_screen_containing(int x, int y) { - i3Screen *screen; - TAILQ_FOREACH(screen, virtual_screens, screens) { - LOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n", - x, y, screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height); - if (x >= screen->rect.x && x < (screen->rect.x + screen->rect.width) && - y >= screen->rect.y && y < (screen->rect.y + screen->rect.height)) - return screen; - } - - return NULL; -} - -/* - * Gets the screen which is the last one in the given direction, for example the screen - * on the most bottom when direction == D_DOWN, the screen most right when direction == D_RIGHT - * and so on. - * - * This function always returns a screen. - * - */ -i3Screen *get_screen_most(direction_t direction, i3Screen *current) { - i3Screen *screen, *candidate = NULL; - int position = 0; - TAILQ_FOREACH(screen, virtual_screens, screens) { - /* Repeated calls of WIN determine the winner of the comparison */ - #define WIN(variable, condition) \ - if (variable condition) { \ - candidate = screen; \ - position = variable; \ - } \ - break; - - if (((direction == D_UP) || (direction == D_DOWN)) && - (current->rect.x != screen->rect.x)) - continue; - - if (((direction == D_LEFT) || (direction == D_RIGHT)) && - (current->rect.y != screen->rect.y)) - continue; - - switch (direction) { - case D_UP: - WIN(screen->rect.y, <= position); - case D_DOWN: - WIN(screen->rect.y, >= position); - case D_LEFT: - WIN(screen->rect.x, <= position); - case D_RIGHT: - WIN(screen->rect.x, >= position); - } - } - - assert(candidate != NULL); - - return candidate; -} - -static void initialize_screen(xcb_connection_t *conn, i3Screen *screen, Workspace *workspace) { - i3Font *font = load_font(conn, config.font); - - workspace->screen = screen; - screen->current_workspace = workspace; - - /* Create a bar for each screen */ - Rect bar_rect = {screen->rect.x, - screen->rect.y + screen->rect.height - (font->height + 6), - screen->rect.x + screen->rect.width, - font->height + 6}; - uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; - uint32_t values[] = {1, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS}; - screen->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, true, mask, values); - screen->bargc = xcb_generate_id(conn); - xcb_create_gc(conn, screen->bargc, screen->bar, 0, 0); - - SLIST_INIT(&(screen->dock_clients)); - - LOG("that is virtual screen at %d x %d with %d x %d\n", - screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height); -} - -/* - * Fills virtual_screens with exactly one screen with width/height of the whole X server. - * - */ -static void disable_xinerama(xcb_connection_t *conn) { - xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; - - i3Screen *s = calloc(sizeof(i3Screen), 1); - - s->rect.x = 0; - s->rect.y = 0; - s->rect.width = root_screen->width_in_pixels; - s->rect.height = root_screen->height_in_pixels; - - num_screens = 1; - s->num = 0; - - TAILQ_INSERT_TAIL(virtual_screens, s, screens); - - xinerama_enabled = false; -} - -/* - * Gets the Xinerama screens and converts them to virtual i3Screens (only one screen for two + * Gets the Xinerama screens and converts them to virtual Outputs (only one screen for two * Xinerama screen which are configured in clone mode) in the given screenlist * */ -static void query_screens(xcb_connection_t *conn, struct screens_head *screenlist) { +static void query_screens(xcb_connection_t *conn) { xcb_xinerama_query_screens_reply_t *reply; xcb_xinerama_screen_info_t *screen_info; - time_t before_trying = time(NULL); - /* Try repeatedly to find screens (there might be short timeframes in - * which the X server does not return any screens, such as when rotating - * screens), but not longer than 5 seconds (strictly speaking, only four - * seconds of trying are guaranteed due to the 1-second-resolution) */ - while ((time(NULL) - before_trying) < 5) { - reply = xcb_xinerama_query_screens_reply(conn, xcb_xinerama_query_screens_unchecked(conn), NULL); - if (!reply) { - LOG("Couldn't get Xinerama screens\n"); - return; - } - screen_info = xcb_xinerama_query_screens_screen_info(reply); - int screens = xcb_xinerama_query_screens_screen_info_length(reply); - num_screens = 0; + reply = xcb_xinerama_query_screens_reply(conn, xcb_xinerama_query_screens_unchecked(conn), NULL); + if (!reply) { + ELOG("Couldn't get Xinerama screens\n"); + return; + } + screen_info = xcb_xinerama_query_screens_screen_info(reply); + int screens = xcb_xinerama_query_screens_screen_info_length(reply); - for (int screen = 0; screen < screens; screen++) { - i3Screen *s = get_screen_at(screen_info[screen].x_org, screen_info[screen].y_org, screenlist); - if (s != NULL) { - /* This screen already exists. We use the littlest screen so that the user - can always see the complete workspace */ - s->rect.width = min(s->rect.width, screen_info[screen].width); - s->rect.height = min(s->rect.height, screen_info[screen].height); - } else { - s = calloc(sizeof(i3Screen), 1); - s->rect.x = screen_info[screen].x_org; - s->rect.y = screen_info[screen].y_org; - s->rect.width = screen_info[screen].width; - s->rect.height = screen_info[screen].height; - /* We always treat the screen at 0x0 as the primary screen */ - if (s->rect.x == 0 && s->rect.y == 0) - TAILQ_INSERT_HEAD(screenlist, s, screens); - else TAILQ_INSERT_TAIL(screenlist, s, screens); - num_screens++; - } - - LOG("found Xinerama screen: %d x %d at %d x %d\n", - screen_info[screen].width, screen_info[screen].height, - screen_info[screen].x_org, screen_info[screen].y_org); + for (int screen = 0; screen < screens; screen++) { + Output *s = get_screen_at(screen_info[screen].x_org, screen_info[screen].y_org); + if (s != NULL) { + DLOG("Re-used old Xinerama screen %p\n", s); + /* This screen already exists. We use the littlest screen so that the user + can always see the complete workspace */ + s->rect.width = min(s->rect.width, screen_info[screen].width); + s->rect.height = min(s->rect.height, screen_info[screen].height); + } else { + s = scalloc(sizeof(Output)); + asprintf(&(s->name), "xinerama-%d", num_screens); + DLOG("Created new Xinerama screen %s (%p)\n", s->name, s); + s->active = true; + s->rect.x = screen_info[screen].x_org; + s->rect.y = screen_info[screen].y_org; + s->rect.width = screen_info[screen].width; + s->rect.height = screen_info[screen].height; + /* We always treat the screen at 0x0 as the primary screen */ + if (s->rect.x == 0 && s->rect.y == 0) + TAILQ_INSERT_HEAD(&outputs, s, outputs); + else TAILQ_INSERT_TAIL(&outputs, s, outputs); + num_screens++; } - free(reply); + DLOG("found Xinerama screen: %d x %d at %d x %d\n", + screen_info[screen].width, screen_info[screen].height, + screen_info[screen].x_org, screen_info[screen].y_org); + } - if (num_screens == 0) { - LOG("No screens found. This is weird. Trying again...\n"); - continue; - } + free(reply); - break; + if (num_screens == 0) { + ELOG("No screens found. Please fix your setup. i3 will exit now.\n"); + exit(0); } } @@ -240,198 +102,27 @@ static void query_screens(xcb_connection_t *conn, struct screens_head *screenlis * */ void initialize_xinerama(xcb_connection_t *conn) { - virtual_screens = scalloc(sizeof(struct screens_head)); - TAILQ_INIT(virtual_screens); - if (!xcb_get_extension_data(conn, &xcb_xinerama_id)->present) { - LOG("Xinerama extension not found, disabling.\n"); - disable_xinerama(conn); + DLOG("Xinerama extension not found, disabling.\n"); + disable_randr(conn); } else { xcb_xinerama_is_active_reply_t *reply; reply = xcb_xinerama_is_active_reply(conn, xcb_xinerama_is_active(conn), NULL); if (reply == NULL || !reply->state) { - LOG("Xinerama is not active (in your X-Server), disabling.\n"); - disable_xinerama(conn); + DLOG("Xinerama is not active (in your X-Server), disabling.\n"); + disable_randr(conn); } else - query_screens(conn, virtual_screens); + query_screens(conn); FREE(reply); } - i3Screen *screen; - num_screens = 0; - /* Just go through each workspace and associate as many screens as we can. */ - TAILQ_FOREACH(screen, virtual_screens, screens) { - screen->num = num_screens; - num_screens++; - Workspace *ws = get_first_workspace_for_screen(virtual_screens, screen); - initialize_screen(conn, screen, ws); - } -} - -/* - * This is called when the rootwindow receives a configure_notify event and therefore the - * number/position of the Xinerama screens could have changed. - * - */ -void xinerama_requery_screens(xcb_connection_t *conn) { - i3Font *font = load_font(conn, config.font); - - /* POSSIBLE PROBLEM: Is the order of the Xinerama screens always constant? That is, can - it change when I move the --right-of video projector to --left-of? */ - - if (!xinerama_enabled) { - LOG("Xinerama is disabled\n"); - return; - } - - /* We use a separate copy to diff with the previous set of screens */ - struct screens_head *new_screens = scalloc(sizeof(struct screens_head)); - TAILQ_INIT(new_screens); - - query_screens(conn, new_screens); - - i3Screen *first = TAILQ_FIRST(new_screens), - *screen, - *old_screen; - int screen_count = 0; - /* Mark each workspace which currently is assigned to a screen, so we - * can garbage-collect afterwards */ + Output *output; Workspace *ws; - TAILQ_FOREACH(ws, workspaces, workspaces) - ws->reassigned = (ws->screen == NULL); - - TAILQ_FOREACH(screen, new_screens, screens) { - screen->num = screen_count; - screen->current_workspace = NULL; - - TAILQ_FOREACH(old_screen, virtual_screens, screens) { - if (old_screen->num != screen_count) - continue; - - LOG("Found a matching screen\n"); - /* Use the same workspace */ - screen->current_workspace = old_screen->current_workspace; - - /* Re-use the old bar window */ - screen->bar = old_screen->bar; - screen->bargc = old_screen->bargc; - LOG("old_screen->bar = %p\n", old_screen->bar); - - Rect bar_rect = {screen->rect.x, - screen->rect.y + screen->rect.height - (font->height + 6), - screen->rect.x + screen->rect.width, - font->height + 6}; - - LOG("configuring bar to be at %d x %d with %d x %d\n", - bar_rect.x, bar_rect.y, bar_rect.height, bar_rect.width); - xcb_configure_window(conn, screen->bar, XCB_CONFIG_WINDOW_X | - XCB_CONFIG_WINDOW_Y | - XCB_CONFIG_WINDOW_WIDTH | - XCB_CONFIG_WINDOW_HEIGHT, &(bar_rect.x)); - - /* Copy the list head for the dock clients */ - screen->dock_clients = old_screen->dock_clients; - SLIST_INIT(&(old_screen->dock_clients)); - - /* Update the dimensions */ - Workspace *ws; - TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->screen != old_screen) - continue; - - LOG("re-assigning ws %d\n", ws->num); - memcpy(&(ws->rect), &(screen->rect), sizeof(Rect)); - ws->screen = screen; - ws->reassigned = true; - } - - break; - } - if (screen->current_workspace == NULL) { - /* Find the first unused workspace, preferring the ones - * which are assigned to this screen and initialize - * the screen with it. */ - LOG("getting first ws for screen %p\n", screen); - Workspace *ws = get_first_workspace_for_screen(new_screens, screen); - initialize_screen(conn, screen, ws); - ws->reassigned = true; - - /* As this workspace just got visible (we got a new screen - * without workspace), we need to map its clients */ - workspace_map_clients(conn, ws); - } - screen_count++; + /* Just go through each active output and associate one workspace */ + TAILQ_FOREACH(output, &outputs, outputs) { + ws = get_first_workspace_for_output(output); + initialize_output(conn, output, ws); } - - /* check for dock_clients which are out of bounds */ - TAILQ_FOREACH(old_screen, virtual_screens, screens) { - if (SLIST_EMPTY(&(old_screen->dock_clients))) - continue; - - LOG("dock_clients out of bounds at screen %p, reassigning\n", old_screen); - if (SLIST_EMPTY(&(first->dock_clients))) { - first->dock_clients = old_screen->dock_clients; - continue; - } - - /* We need to merge the lists */ - Client *dock_client; - - while (!SLIST_EMPTY(&(old_screen->dock_clients))) { - dock_client = SLIST_FIRST(&(old_screen->dock_clients)); - SLIST_INSERT_HEAD(&(first->dock_clients), dock_client, dock_clients); - SLIST_REMOVE_HEAD(&(old_screen->dock_clients), dock_clients); - } - } - - /* Check for workspaces which are out of bounds */ - TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->reassigned) - continue; - - Client *client; - - LOG("Closing bar window (%p)\n", ws->screen->bar); - xcb_destroy_window(conn, ws->screen->bar); - - LOG("Workspace %d's screen out of bounds, assigning to first screen\n", ws->num + 1); - ws->screen = first; - memcpy(&(ws->rect), &(first->rect), sizeof(Rect)); - - /* Force reconfiguration for each client on that workspace */ - FOR_TABLE(ws) - CIRCLEQ_FOREACH(client, &(ws->table[cols][rows]->clients), clients) - client->force_reconfigure = true; - - /* Render the workspace to reconfigure the clients. However, they will be visible now, so… */ - render_workspace(conn, first, ws); - - /* …unless we want to see them at the moment, we should hide that workspace */ - if (workspace_is_visible(ws)) - continue; - - workspace_unmap_clients(conn, ws); - - if (c_ws == ws) { - LOG("Need to adjust c_ws...\n"); - c_ws = first->current_workspace; - } - } - xcb_flush(conn); - - /* Free the old list */ - while (!TAILQ_EMPTY(virtual_screens)) { - screen = TAILQ_FIRST(virtual_screens); - TAILQ_REMOVE(virtual_screens, screen, screens); - free(screen); - } - free(virtual_screens); - - virtual_screens = new_screens; - - LOG("Current workspace is now: %d\n", first->current_workspace); - - render_layout(conn); } diff --git a/testcases/t/05-ipc.t b/testcases/t/05-ipc.t index ac52911a..8f427938 100644 --- a/testcases/t/05-ipc.t +++ b/testcases/t/05-ipc.t @@ -1,7 +1,7 @@ #!perl # vim:ts=4:sw=4:expandtab -use Test::More tests => 4; +use Test::More tests => 3; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -9,6 +9,7 @@ use Time::HiRes qw(sleep); use FindBin; use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; BEGIN { use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); @@ -17,19 +18,14 @@ BEGIN { my $x = X11::XCB::Connection->new; -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); - -isa_ok($sock, 'IO::Socket::UNIX'); - +my $i3 = i3; ##################################################################### # Ensure IPC works by switching workspaces ##################################################################### # Switch to the first workspace to get a clean testing environment -$sock->write(i3test::format_ipc_command("1")); - -sleep(0.25); +$i3->command('1')->recv; # Create a window so we can get a focus different from NULL my $window = i3test::open_standard_window($x); @@ -41,9 +37,7 @@ my $focus = $x->input_focus; diag("old focus = $focus"); # Switch to the nineth workspace -$sock->write(i3test::format_ipc_command("9")); - -sleep(0.25); +$i3->command('9')->recv; my $new_focus = $x->input_focus; isnt($focus, $new_focus, "Focus changed"); diff --git a/testcases/t/06-focus.t b/testcases/t/06-focus.t index efdf5749..a95e0e40 100644 --- a/testcases/t/06-focus.t +++ b/testcases/t/06-focus.t @@ -4,7 +4,7 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 8; +use Test::More tests => 13; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -12,29 +12,25 @@ use Time::HiRes qw(sleep); use FindBin; use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; BEGIN { - use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } my $x = X11::XCB::Connection->new; -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); -isa_ok($sock, 'IO::Socket::UNIX'); +my $i3 = i3; # Switch to the nineth workspace -$sock->write(i3test::format_ipc_command("9")); - -sleep(0.25); +$i3->command('9')->recv; ##################################################################### # Create two windows and make sure focus switching works ##################################################################### # Change mode of the container to "default" for following tests -$sock->write(i3test::format_ipc_command("d")); -sleep(0.25); +$i3->command('d')->recv; my $top = i3test::open_standard_window($x); my $mid = i3test::open_standard_window($x); @@ -52,8 +48,7 @@ diag("bottom id = " . $bottom->id); sub focus_after { my $msg = shift; - $sock->write(i3test::format_ipc_command($msg)); - sleep(0.5); + $i3->command($msg)->recv; return $x->input_focus; } @@ -76,4 +71,48 @@ is($focus, $bottom->id, "Bottom window focused (wrapping to the top works)"); $focus = focus_after("j"); is($focus, $top->id, "Top window focused (wrapping to the bottom works)"); +############################################### +# Test focus with empty containers and colspan +############################################### + +# Switch to the 10. workspace +$i3->command('10')->recv; + +$top = i3test::open_standard_window($x); +$bottom = i3test::open_standard_window($x); +sleep 0.25; + +$focus = focus_after("mj"); +$focus = focus_after("mh"); +$focus = focus_after("k"); +is($focus, $bottom->id, "Selecting top window without snapping doesn't work"); + +$focus = focus_after("sl"); +is($focus, $bottom->id, "Bottom window focused"); + +$focus = focus_after("k"); +is($focus, $top->id, "Top window focused"); + +# Same thing, but left/right instead of top/bottom + +# Switch to the 11. workspace +$i3->command('11')->recv; + +my $left = i3test::open_standard_window($x); +my $right = i3test::open_standard_window($x); +sleep 0.25; + +$focus = focus_after("ml"); +$focus = focus_after("h"); +$focus = focus_after("mk"); +$focus = focus_after("l"); +is($focus, $left->id, "Selecting right window without snapping doesn't work"); + +$focus = focus_after("sj"); +is($focus, $left->id, "left window focused"); + +$focus = focus_after("l"); +is($focus, $right->id, "right window focused"); + + diag( "Testing i3, Perl $], $^X" ); diff --git a/testcases/t/07-move.t b/testcases/t/07-move.t index 10cb9830..efd3df15 100644 --- a/testcases/t/07-move.t +++ b/testcases/t/07-move.t @@ -4,7 +4,7 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 10; +use Test::More tests => 8; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -12,21 +12,18 @@ use Time::HiRes qw(sleep); use FindBin; use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; BEGIN { - use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } my $x = X11::XCB::Connection->new; -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); -isa_ok($sock, 'IO::Socket::UNIX'); +my $i3 = i3; # Switch to the nineth workspace -$sock->write(i3test::format_ipc_command("9")); - -sleep(0.25); +$i3->command('9')->recv; ##################################################################### # Create two windows and make sure focus switching works @@ -50,8 +47,7 @@ diag("bottom id = " . $bottom->id); sub focus_after { my $msg = shift; - $sock->write(i3test::format_ipc_command($msg)); - sleep(0.5); + $i3->command($msg)->recv; return $x->input_focus; } @@ -82,9 +78,7 @@ is($focus, $top->id, "Top window focused"); # Move window cross-workspace ##################################################################### -$sock->write(i3test::format_ipc_command("m12")); -$sock->write(i3test::format_ipc_command("t")); -$sock->write(i3test::format_ipc_command("m13")); -$sock->write(i3test::format_ipc_command("12")); -$sock->write(i3test::format_ipc_command("13")); +for my $cmd (qw(m12 t m13 12 13)) { + $i3->command($cmd)->recv; +} ok(1, "Still living"); diff --git a/testcases/t/08-focus-stack.t b/testcases/t/08-focus-stack.t index 370369d8..4ae92407 100644 --- a/testcases/t/08-focus-stack.t +++ b/testcases/t/08-focus-stack.t @@ -3,7 +3,7 @@ # Checks if the focus is correctly restored, when creating a floating client # over an unfocused tiling client and destroying the floating one again. -use Test::More tests => 6; +use Test::More tests => 4; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -11,28 +11,25 @@ use Time::HiRes qw(sleep); use FindBin; use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; BEGIN { - use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Window') or BAIL_OUT('Could not load X11::XCB::Window'); } my $x = X11::XCB::Connection->new; -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); -isa_ok($sock, 'IO::Socket::UNIX'); +my $i3 = i3; # Switch to the nineth workspace -$sock->write(i3test::format_ipc_command("9")); - -sleep(0.25); +$i3->command('9')->recv; my $tiled_left = i3test::open_standard_window($x); my $tiled_right = i3test::open_standard_window($x); sleep(0.25); -$sock->write(i3test::format_ipc_command("ml")); +$i3->command('ml')->recv; # Get input focus before creating the floating window my $focus = $x->input_focus; diff --git a/testcases/t/09-stacking.t b/testcases/t/09-stacking.t index 8f40047e..59d2e6f4 100644 --- a/testcases/t/09-stacking.t +++ b/testcases/t/09-stacking.t @@ -4,7 +4,7 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 24; +use Test::More tests => 22; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -12,21 +12,18 @@ use Time::HiRes qw(sleep); use FindBin; use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; BEGIN { - use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } my $x = X11::XCB::Connection->new; -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); -isa_ok($sock, 'IO::Socket::UNIX'); +my $i3 = i3; # Switch to the nineth workspace -$sock->write(i3test::format_ipc_command("9")); - -sleep(0.25); +$i3->command('9')->recv; ##################################################################### # Create two windows and make sure focus switching works @@ -50,8 +47,7 @@ diag("bottom id = " . $bottom->id); sub focus_after { my $msg = shift; - $sock->write(i3test::format_ipc_command($msg)); - sleep(0.25); + $i3->command($msg)->recv; return $x->input_focus; } diff --git a/testcases/t/10-dock.t b/testcases/t/10-dock.t index 97ac5f41..52063131 100644 --- a/testcases/t/10-dock.t +++ b/testcases/t/10-dock.t @@ -12,7 +12,6 @@ use i3test; use List::Util qw(first); BEGIN { - #use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } @@ -43,4 +42,17 @@ sleep 0.25; my $rect = $window->rect; is($rect->width, $primary->rect->width, 'dock client is as wide as the screen'); +my $fwindow = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30], + background_color => '#FF0000', + type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), +); + +$fwindow->transient_for($window); +$fwindow->map; + +sleep 0.25; + + diag( "Testing i3, Perl $], $^X" ); diff --git a/testcases/t/11-goto.t b/testcases/t/11-goto.t index 47675903..9b06112b 100644 --- a/testcases/t/11-goto.t +++ b/testcases/t/11-goto.t @@ -4,7 +4,7 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 9; +use Test::More tests => 7; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -13,21 +13,18 @@ use FindBin; use Digest::SHA1 qw(sha1_base64); use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; BEGIN { - use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } my $x = X11::XCB::Connection->new; -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); -isa_ok($sock, 'IO::Socket::UNIX'); +my $i3 = i3; # Switch to the nineth workspace -$sock->write(i3test::format_ipc_command("9")); - -sleep(0.25); +$i3->command('9')->recv; ##################################################################### # Create two windows and make sure focus switching works @@ -51,8 +48,7 @@ diag("bottom id = " . $bottom->id); sub focus_after { my $msg = shift; - $sock->write(i3test::format_ipc_command($msg)); - sleep(0.5); + $i3->command($msg)->recv; return $x->input_focus; } @@ -74,7 +70,7 @@ my $random_mark = sha1_base64(rand()); $focus = focus_after("goto $random_mark"); is($focus, $mid->id, "focus unchanged"); -$sock->write(i3test::format_ipc_command("mark $random_mark")); +$i3->command("mark $random_mark")->recv; $focus = focus_after("k"); is($focus, $top->id, "Top window focused"); diff --git a/testcases/t/12-floating-resize.t b/testcases/t/12-floating-resize.t index d908d345..74f66535 100644 --- a/testcases/t/12-floating-resize.t +++ b/testcases/t/12-floating-resize.t @@ -4,7 +4,7 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 17; +use Test::More tests => 15; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -13,21 +13,18 @@ use FindBin; use Digest::SHA1 qw(sha1_base64); use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; BEGIN { - use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } my $x = X11::XCB::Connection->new; -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); -isa_ok($sock, 'IO::Socket::UNIX'); +my $i3 = i3; # Switch to the nineth workspace -$sock->write(i3test::format_ipc_command("9")); - -sleep 0.25; +$i3->command('9')->recv; ##################################################################### # Create a floating window and see if resizing works @@ -78,13 +75,11 @@ sub test_resize { test_resize; # Test borderless -$sock->write(i3test::format_ipc_command("bb")); -sleep 0.25; +$i3->command('bb')->recv; test_resize; # Test with 1-px-border -$sock->write(i3test::format_ipc_command("bp")); -sleep 0.25; +$i3->command('bp')->recv; test_resize; diff --git a/testcases/t/13-urgent.t b/testcases/t/13-urgent.t new file mode 100644 index 00000000..5fce6aee --- /dev/null +++ b/testcases/t/13-urgent.t @@ -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; diff --git a/testcases/t/14-client-leader.t b/testcases/t/14-client-leader.t new file mode 100644 index 00000000..b9160131 --- /dev/null +++ b/testcases/t/14-client-leader.t @@ -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"); diff --git a/testcases/t/15-ipc-workspaces.t b/testcases/t/15-ipc-workspaces.t new file mode 100644 index 00000000..4e2c0e8d --- /dev/null +++ b/testcases/t/15-ipc-workspaces.t @@ -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" ); diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index a60fd6d2..eb4167a8 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -29,15 +29,4 @@ sub open_standard_window { return $window; } -sub format_ipc_command { - my $msg = shift; - my $len; - - { use bytes; $len = length($msg); } - - my $message = "i3-ipc" . pack("LL", $len, 0) . $msg; - - return $message; -} - 1