diff --git a/.gitignore b/.gitignore index eec7fb0f..454b2e3a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,37 @@ *.o -i3 -i3-input/i3-input -i3-msg/i3-msg +tags include/loglevels.h loglevels.tmp -src/*.output -src/*.tab.* -src/*.yy.c +*.swp +*.gcda +*.gcno +testcases/testsuite-* +testcases/latest +*.output +*.tab.* +*.yy.c man/i3-msg.1 man/i3-msg.xml man/i3-msg.html +man/i3-nagbar.1 +man/i3-nagbar.xml +man/i3-nagbar.html man/i3-wsbar.1 man/i3-wsbar.xml man/i3-wsbar.html +man/i3-input.1 +man/i3-input.xml +man/i3-input.html man/i3.1 man/i3.xml man/i3.html -tags +*.tar.bz2* +i3 +i3-input/i3-input +i3-nagbar/i3-nagbar +i3-msg/i3-msg +i3-config-wizard/i3-config-wizard +docs/*.html +docs/*.aux +docs/*.out +docs/*.pdf diff --git a/DEPENDS b/DEPENDS index b7a6fefb..71abc3b2 100644 --- a/DEPENDS +++ b/DEPENDS @@ -1,32 +1,39 @@ -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 - * x11-utils for xmessage (only for displaying the welcome message, so this is - mainly interesting for distributors) + i3 has the following dependencies: -Recommendations: - * i3lock for locking your screen - * dmenu for launching applications + "min" means minimum required version + "lkgv" means last known good version -Get the libraries from: -http://xcb.freedesktop.org/dist/xcb-proto-1.5.tar.bz2 -http://xcb.freedesktop.org/dist/libxcb-1.1.93.tar.bz2 -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/ +┌─────────────┬────────┬────────┬────────────────────────────────────────┐ +│ dependency │ min. │ lkgv │ URL │ +├─────────────┼────────┼────────┼────────────────────────────────────────┤ +│ pkg-config │ 0.25 │ 0.26 │ http://pkgconfig.freedesktop.org/ │ +│ xcb-proto │ 1.3 │ 1.6 │ http://xcb.freedesktop.org/dist/ │ +│ libxcb │ 1.1.93 │ 1.7 │ http://xcb.freedesktop.org/dist/ │ +│ xcb-util │ 0.3.3 │ 0.3.8 │ http://xcb.freedesktop.org/dist/ │ +│ libev │ 3.0 │ 4.04 │ http://libev.schmorp.de/ │ +│ flex │ 2.5.35 │ 2.5.35 │ http://flex.sourceforge.net/ │ +│ bison │ 2.4.1 │ 2.4.1 │ http://www.gnu.org/software/bison/ │ +│ yajl │ 1.0.8 │ 2.0.1 │ http://lloyd.github.com/yajl/ │ +│ asciidoc │ 8.3.0 │ 8.6.4 │ http://www.methods.co.nz/asciidoc/ │ +│ xmlto │ 0.0.23 │ 0.0.23 │ http://www.methods.co.nz/asciidoc/ │ +│ docbook-xml │ 4.5 │ 4.5 │ http://www.methods.co.nz/asciidoc/ │ +│ libxcursor │ 1.1.11 │ 1.1.11 │ http://ftp.x.org/pub/current/src/lib/ │ +│ Xlib │ 1.3.3 │ 1.4.3 │ http://ftp.x.org/pub/current/src/lib/ │ +└─────────────┴────────┴────────┴────────────────────────────────────────┘ -http://i3.zekjur.net/i3lock/ -http://tools.suckless.org/dmenu + i3-msg, i3-input, i3-nagbar and i3-config-wizard do not introduce any new + dependencies. + + i3-wsbar is implemented in Perl and has the following dependencies: + + • IPC::Run + • Try::Tiny + • AnyEvent + • AnyEvent::I3 + + All of them are available at CPAN, see http://search.cpan.org/ + Use your distribution’s packages or cpan(1) to install them. + + i3-migrate-config-to-v4.pl is implemented in Perl, but it has no dependencies + besides Perl 5.10. diff --git a/Makefile b/Makefile index 9e615d6d..a29fe59e 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ TOPDIR=$(shell pwd) include $(TOPDIR)/common.mk # Depend on the object files of all source-files in src/*.c and on all header files -AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c +AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c src/cmdparse.tab.c src/cmdparse.yy.c FILES:=$(filter-out $(AUTOGENERATED),$(wildcard src/*.c)) FILES:=$(FILES:.c=.o) HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h)) @@ -13,24 +13,30 @@ HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h)) # 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) +#UNUSED:=$(warning Generating loglevels.h) else UNUSED:=$(shell $(MAKE) loglevels.h) endif +SUBDIRS=i3-msg i3-input i3-nagbar i3-config-wizard + # Depend on the specific file (.c for each .o) and on all headers src/%.o: src/%.c ${HEADERS} echo "CC $<" - $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/$(shell basename $< .c)/ { print NR }' loglevels.tmp))" -c -o $@ $< + $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="((uint64_t)1 << $(shell awk '/$(shell basename $< .c)/ { print NR; exit 0; }' loglevels.tmp))" -c -o $@ $< -all: src/cfgparse.y.o src/cfgparse.yy.o ${FILES} +all: i3 subdirs + +i3: src/cfgparse.y.o src/cfgparse.yy.o src/cmdparse.y.o src/cmdparse.yy.o ${FILES} echo "LINK i3" - $(CC) -o i3 ${FILES} src/cfgparse.y.o src/cfgparse.yy.o $(LDFLAGS) - echo "" - echo "SUBDIR i3-msg" - $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg - echo "SUBDIR i3-input" - $(MAKE) TOPDIR=$(TOPDIR) -C i3-input + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +subdirs: + for dir in $(SUBDIRS); do \ + echo ""; \ + echo "MAKE $$dir"; \ + $(MAKE) -C $$dir; \ + done loglevels.h: echo "LOGLEVELS" @@ -47,12 +53,24 @@ loglevels.h: src/cfgparse.yy.o: src/cfgparse.l src/cfgparse.y.o ${HEADERS} echo "LEX $<" flex -i -o$(@:.o=.c) $< - $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c) + $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c) + +src/cmdparse.yy.o: src/cmdparse.l src/cmdparse.y.o ${HEADERS} + echo "LEX $<" + flex -Pcmdyy -i -o$(@:.o=.c) $< + $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cmdparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c) + src/cfgparse.y.o: src/cfgparse.y ${HEADERS} echo "YACC $<" bison --debug --verbose -b $(basename $< .y) -d $< - $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c) + $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c) + +src/cmdparse.y.o: src/cmdparse.y ${HEADERS} + echo "YACC $<" + bison -p cmdyy --debug --verbose -b $(basename $< .y) -d $< + $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cmdparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c) + install: all echo "INSTALL" @@ -61,20 +79,22 @@ install: all $(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/ + $(INSTALL) -m 0755 i3-migrate-config-to-v4.pl $(DESTDIR)$(PREFIX)/bin/ test -e $(DESTDIR)$(SYSCONFDIR)/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)$(SYSCONFDIR)/i3/config + test -e $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes || $(INSTALL) -m 0644 i3.config.keycodes $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes $(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 + for dir in $(SUBDIRS); do \ + $(MAKE) -C $$dir install; \ + done 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 i3-wsbar pseudo-doc.doxygen Makefile i3-${VERSION} - cp -r src i3-msg include man i3-${VERSION} + cp i3-migrate-config-to-v4.pl i3.config.keycodes DEPENDS GOALS LICENSE PACKAGE-MAINTAINER TODO RELEASE-NOTES-${VERSION} i3.config i3.desktop i3.welcome pseudo-doc.doxygen i3-wsbar Makefile i3-${VERSION} + cp -r src i3-msg i3-nagbar i3-config-wizard yajl-fallback include man i3-${VERSION} # Only copy toplevel documentation (important stuff) mkdir i3-${VERSION}/docs # Pre-generate documentation @@ -83,21 +103,32 @@ dist: distclean # Only copy source code from i3-input mkdir i3-${VERSION}/i3-input find i3-input -maxdepth 1 -type f \( -name "*.c" -or -name "*.h" -or -name "Makefile" \) -exec cp '{}' i3-${VERSION}/i3-input \; - sed -e 's/^GIT_VERSION:=\(.*\)/GIT_VERSION=${GIT_VERSION}/g;s/^VERSION:=\(.*\)/VERSION=${VERSION}/g' common.mk > i3-${VERSION}/common.mk + sed -e 's/^GIT_VERSION:=\(.*\)/GIT_VERSION:=$(shell echo '${GIT_VERSION}' | sed 's/\\/\\\\/g')/g;s/^VERSION:=\(.*\)/VERSION:=${VERSION}/g' common.mk > i3-${VERSION}/common.mk # Pre-generate a manpage to allow distributors to skip this step and save some dependencies - make -C man + $(MAKE) -C man cp man/*.1 i3-${VERSION}/man/ tar cfj i3-${VERSION}.tar.bz2 i3-${VERSION} rm -rf i3-${VERSION} clean: - rm -f src/*.o src/cfgparse.tab.{c,h} src/cfgparse.{output,dot} src/cfgparse.yy.c loglevels.tmp include/loglevels.h + rm -f src/*.o src/*.gcno src/cfgparse.tab.{c,h} src/cfgparse.yy.c src/cfgparse.{output,dot} src/cmdparse.tab.{c,h} src/cmdparse.yy.c src/cmdparse.{output,dot} loglevels.tmp include/loglevels.h + (which lcov >/dev/null && lcov -d . --zerocounters) || true $(MAKE) -C docs clean $(MAKE) -C man clean $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg clean $(MAKE) TOPDIR=$(TOPDIR) -C i3-input clean + $(MAKE) TOPDIR=$(TOPDIR) -C i3-nagbar clean + $(MAKE) TOPDIR=$(TOPDIR) -C i3-config-wizard clean distclean: clean rm -f i3 $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg distclean $(MAKE) TOPDIR=$(TOPDIR) -C i3-input distclean + $(MAKE) TOPDIR=$(TOPDIR) -C i3-nagbar distclean + $(MAKE) TOPDIR=$(TOPDIR) -C i3-config-wizard distclean + +coverage: + rm -f /tmp/i3-coverage.info + rm -rf /tmp/i3-coverage + lcov -d . -b . --capture -o /tmp/i3-coverage.info + genhtml -o /tmp/i3-coverage/ /tmp/i3-coverage.info diff --git a/PACKAGE-MAINTAINER b/PACKAGE-MAINTAINER index 40222803..633b2d7e 100644 --- a/PACKAGE-MAINTAINER +++ b/PACKAGE-MAINTAINER @@ -10,16 +10,27 @@ packages for them. Please make sure the manpage for i3 will be properly created and installed in your package. +Also please provide the path to a suitable terminal emulator which is installed +as a dependency of your package (e.g. urxvt). On systems which have a special +commend to launch the best available terminal emulator, please use this one +(e.g. x-terminal-emulator on debian). + On debian, this looks like this: # Compilation - $(MAKE) + $(MAKE) TERM_EMU=x-terminal-emulator $(MAKE) -C man # Installation $(MAKE) DESTDIR=$(CURDIR)/debian/i3-wm/ install mkdir -p $(CURDIR)/debian/i3-wm/usr/share/man/man1 - cp man/i3.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1 + cp man/*.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1 + +Please make sure that i3-migrate-config-to-v4.pl and i3-config-wizard are +installed with i3. The Perl script is necessary to (automatically) convert v3 +configs to v4. The wizard provides the possibility to create a keysym-based +config with the user’s preferred modifier and should be started on the first +start of i3 (it will automatically exit if it finds a config file). 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 diff --git a/RELEASE-NOTES-3.d-bf1 b/RELEASE-NOTES-3.d-bf1 deleted file mode 100644 index 58c9a901..00000000 --- a/RELEASE-NOTES-3.d-bf1 +++ /dev/null @@ -1,29 +0,0 @@ -Release notes for i3 v3.δ-bf1 ------------------------------ - -This is the first bugfix release (bf1) for version 3.δ (transcribed 3.d) of -i3. Because many bugs were fixed after the release of version 3.δ, we thought -users of the stable releases might profit from this additional bugfix release. - -Thanks for this release go out to msi, merovius, Grauwolf, jace, Syntropy, -Mirko, helgiks and Moredread. - -A list of changes follows: - - * Bugfix: Don’t draw window title when titlebar is disabled - * Bugfix: Correctly switch border types for floating windows - * Bugfix: Correctly replay pointer if the click handler does not trigger - * Bugfix: Also allow WORDs as workspace names - * Bugfix: Correctly clear the urgency hint if a window gets unmapped without - clearing it - * Bugfix: Fix resizing of floating windows in borderless/1-px-border mode - * Bugfix: Accept underscores in bindsym - * Bugfix: Don’t set the urgency flag if the window is focused - * Bugfix: Handle stack-limit cols on tabbed containers - * Bugfix: Resize client after updating base_width/base_height - * Bugfix: Force render containers after setting the client active - * Bugfix: Fix two problems in resizing floating windows with right mouse - * Bugfix: Use more precise floating point arithmetics - * Bugfix: Correctly place new windows below fullscreen windows - - -- Michael Stapelberg, 2009-12-21 diff --git a/RELEASE-NOTES-3.e b/RELEASE-NOTES-3.e deleted file mode 100644 index 4bf590fe..00000000 --- a/RELEASE-NOTES-3.e +++ /dev/null @@ -1,128 +0,0 @@ -Release notes for i3 v3.ε ------------------------------ - -This is the fifth version (3.ε, transcribed 3.e) of i3. It is considered -stable. - -A really big change in this release is the support of RandR instead of -Xinerama. The Xinerama API is a subset of RandR and its limitations clearly -showed when you reconfigured outputs using xrandr(1) during runtime (it was -not designed to handle such changes). The implementation of RandR fixes some -long-standing bugs (workspaces were messed up when reconfiguring outputs) -and cleans up some code. Furthermore, you are now able to assign workspaces -to outputs (like LVDS1, VGA1, …) instead of the formerly used heuristics -like "the screen at position (x, y)" or "the second screen in the list". - -Furthermore, another big change is the separation of debug output (the -so-called logfile): you now need to enable verbose output (parameter -V) -and you need to specify which (if any) debug output you want to see (parameter --d ). When starting without -V, i3 will only log errors. This is -what you usually want for a production system. When enabling verbose output, -you will see the names and window classes of new windows (useful for creating -assignments in your configuration file) and other useful messages. For an -explanation of the debuglevels, please see the "How to debug" document (for -the impatient: "-d all" gives you full output). - -In 3.δ, a new parser/lexer was introduced and available using the -l option. -The old parser/lexer has been removed in the meantime, so in 3.ε, the "new" -parser/lexer is always used and you do not need the -l option anymore. To -make debugging errors in your configuration easier, the error messages have -been very much improved. Also, the parser tries to skip invalid lines (though -it may not always succeed, it usually works and does not crash i3). - -Starting from version 3.ε, i3 obeys the XDG base directory specification, -meaning that you can now put your configuration file into ~/.config/i3/config, -which might be useful if you manage your ~/.config directory in some way (git, -…). The old configuration file path is still supported (there are no plans -to change this), but using ~/.config seems reasonable for clean setups. - -You can disable the internal workspace bar in this release. Instead of the -internal bar, you can use dzen2 (or similar) in dock mode (-dock for dzen2, -but you need an svn revision). The sample implementation i3-wsbar takes -stdin, generates a combined bar (workspaces + stdin) and starts dzen2 on -your outputs as needed (does the right thing when you reconfigure your -monitors dynamically). - -To accomplish the external workspace bar feature, the IPC interface has -seen much love: requests and replies now use JSON for serialization of -data structures and provide a nice and simple way to get information (like -the current workspaces or outputs) from i3 or send commands to it. You can -also subscribe to certain types of events (workspace or output changes). -See the AnyEvent::I3 module for a sample implementation of a library. - -Thanks for this release go out to Merovius, badboy, xeen, Atsutane, Ciprian, -dirkson, Mirko, sur5r, artoj, Scytale, fallen, Thomas, Sasha, dothebart, msi -and all other people who reported bugs/made suggestions. - -A complete list of changes follows: - - * Implement RandR instead of Xinerama - * Obey the XDG Base Directory Specification for config file paths - * lexer/parser: proper error messages - * Add new options -V for verbose mode and -d for debug log levels - * Implement resize command for floating clients - * Include date of the last commit in version string - * Fixed cursor orientation when resizing - * Added focus_follows_mouse config option - * Feature: Cycle through workspaces - * Fix bindings using the cursor keys in default config - * added popup for handling SIGSEGV or SIGFPE - * Correctly exit when another window manager is already running - * Take into account the window’s base_{width,height} when resizing - * Disable XKB instead of quitting with an error - * Make containers containing exactly one window behave like default containers - * Also warp the pointer when moving a window to a another visible workspace - * work around clients setting 0xFFFF as resize increments - * Move autostart after creating the IPC socket in start process - * Restore geometry of all windows before exiting/restarting - * When in fullscreen mode, focus whole screens instead of denying to focus - * draw consistent borders for each frame in a tabbed/stacked container - * Update fullscreen client position/size when an output changes - * i3-input: Bugfix: repeatedly grab the keyboard if it does not succeed - * put windows with WM_CLIENT_LEADER on the workspace of their leader - * use real functions instead of nested functions (enables compilation with - llvm-clang) - * implement screen-spanning fullscreen mode - * floating resize now uses arbitrary corners - * floating resize now works proportionally when pressing shift - * Don’t use SYNC key bindings for mode_switch but re-grab keys - * support PREFIX and SYSCONFDIR in Makefile - * make pointer follow the focus when moving to a different screen also for - floating clients - * start dock clients on the output they request to be started on according - to their geometry - * handle destroy notify events like unmap notify events - * ewmh: correctly set _NET_CURRENT_DESKTOP to the number of the active - workspace - * ewmh: correctly set _NET_ACTIVE_WINDOW - * ewmh: implement support for _NET_WORKAREA (rdesktop can use that) - * default ipc-socket path is now ~/.i3/ipc.sock, enabled in the default config - * Bugfix: Containers could lose their snap state - * Bugfix: Use ev_loop_new to not block SIGCHLD - * Bugfix: if a font provides no per-char info for width, fall back to default - * Bugfix: lexer: return to INITIAL state after floating_modifier - * Bugfix: Don’t leak IPC socket to launched processes - * Bugfix: Use both parts of WM_CLASS (it contains instance and class) - * Bugfix: Correctly do boundary checking/moving to other workspaces when - moving floating clients via keyboard - * Bugfix: checked for wrong flag in size hints - * Bugfix: Correctly render workspace names containing some non-ascii chars - * Bugfix: Correctly position floating windows sending configure requests - * Bugfix: Don’t remap stack windows errnously when changing workspaces - * Bugfix: configure floating windows above tiling windows when moving them - to another workspace - * Bugfix: Take window out of fullscreen mode before entering floating mode - * Bugfix: Don’t enter BIND_A2WS_COND state too early - * Bugfix: only restore focus if the workspace is focused, not if it is visible - * Bugfix: numlock state will now be filtered in i3-input and signal handler - * Bugfix: Don’t unmap windows when current workspace gets reassigned - * Bugfix: correctly translate coordinates for floating windows when outputs - change - * Bugfix: Correctly switch workspace when using the "jump" command - * Bugfix: Fix rendering of workspace names after "reload" - * Bugfix: Correctly ignore clicks when in fullscreen mode - * Bugfix: Don’t allow fullscreen floating windows to be moved - * Bugfix: Don’t render containers which are not visible on hint changes - * Some memory leaks/invalid accesses have been fixed - - -- Michael Stapelberg, 2010-03-30 diff --git a/RELEASE-NOTES-4.0 b/RELEASE-NOTES-4.0 new file mode 100644 index 00000000..38d2088e --- /dev/null +++ b/RELEASE-NOTES-4.0 @@ -0,0 +1,135 @@ + + ┌────────────────────────────┐ + │ Release notes for i3 v4.0 │ + └────────────────────────────┘ + +This is the first release of the new major version of i3, v4.0. It has been a +long time since v3.ε was released (over one year). A lot has been happening +since then, we made 736 commits – compare that to the total number of 1664 +commits for i3. + +The reason for the high number of commits and long time for this release is the +big refactoring we have been doing. Instead of using several lists and a table +as data structures, we now use a single tree of containers. These containers +represent invisible entities like your X11 root window, your different monitors +and workspaces, but also visible entities like actual windows. + +Using a tree has made a lot of things cleaner and easier – in the code *and* in +the user interface. Admittedly though, you will probably need a day or two to +get used to a few more advanced movement commands if you are used to v3.ε right +now. + + ┌────────────────────────────┐ + │ New features │ + └────────────────────────────┘ + + • In addition to the proper flex/bison based parser for the config file + introduced in 3.δ, we now also have a flex/bison parser for commands. What + this means is that we can have more human-readable, beautiful command names + instead of cryptic commands like 'f' for fullscreen or 'mh' for move left. + In fact, the commands for the aforementioned functions *are* 'fullscreen' + and 'move left'! + + • You can now chain commands using ';' (a semicolon). One example for that is + 'workspace 3 ; exec /usr/bin/urxvt' to switch to a new workspace and open a + terminal. + + • You can specify which windows should be affected by your command by using + different criteria. A good example is '[class="Firefox"] kill' to get rid + of all Firefox windows. + + • As the configuration file needs new commands (and a few options are + obsolete), you need to change it. To make this process a little bit easier + for you, this release comes with the script i3-migrate-config-to-v4.pl. Just + run it on your current config file and it will spit out a v4 config file to + stdout. To make things even better, i3 automatically detects v3 config files + and calls that script, so you never end up with a non-working config :). + + • Similarly to the criteria when using commands, we now have a 'for_window' + configuration directive, which lets you automatically apply certain commands + to certain windows. Use it to set border styles per window, for example with + 'for_window [class="XTerm"] border 1pixel'. + + • Since dock clients (like dzen2) are now part of the layout tree (as opposed + to a custom data structure as before), it was easy to implement top and + bottom dock areas. Programs which properly specify the dock hint get placed + on the edge of the screen they request. i3bar has the -dtop and -dbottom + parameters, for example. + + • The internal workspace bar is obsolete. Use i3bar instead. + + • Resizing now works between all windows! + + • Fullscreen now works for everything! + + • Floating now works for everything! + + • Your layout is now preserved when doing an inplace restart. + + • When you have an error in your config file, a new program called i3-nagbar + will tell you so. It offers you two buttons: One to view the error in your + $PAGER and one to edit your config in your $EDITOR. + + • The default config used key symbols (like 'bind Mod1+f fullscreen') instead + of key codes. If you use a non-qwerty layout, the program i3-config-wizard + can create a key symbol based config file based on your current layout. You + can also chose between Windows (Mod4) and Alt (Mod1) as your default + modifier. i3-config-wizard will automatically be started as long as you + don’t have a configuration file for i3. + + • Custom X cursor themes are now supported. + + • The RandR backend now respects the primary output. + + • A wrong 'font' configuration in your config file will no longer make i3 + exit. Instead, it will fall back to a different font and tell you about the + error in its log. + + • The default split direction (whether a new window gets placed right next to + the current one or below the current one) is now automatically set to + horizontal if you have a monitor that is wider than high or vertical if you + a monitor which is higher than wide. This works great with rotated monitors. + + • Sockets and temporary files are now placed in XDG_RUNTIME_DIR, if set (this + is used on systemd based systems). + + • Tools like i3bar, i3-msg etc. use the I3_SOCKET_PATH property which is set + to the X11 root window, so you don’t have to configure your socket path + anywhere. + + • The kill command kills single windows by default now. To kill a whole + application, use 'kill client'. + + • IPC: Commands can now have custom replies. When the parser encounters an + error, a proper error reply is sent. + + • There is now an 'exec_always' configuration directive which works like + 'exec' but will also be run when restarting. + + ┌────────────────────────────┐ + │ Future features │ + └────────────────────────────┘ + +Our plans were big but our time and manpower is limited. Therefore, the +following features did not make it into this release. However, the foundation +is now in place and implementing them is possible, so stay tuned! + + • Saving/Restoring specific parts of your layout + + • Session saving + + • Sticky windows + + ┌────────────────────────────┐ + │ Thanks! │ + └────────────────────────────┘ + +Thanks for testing, bugfixes, discussions and everything I forgot go out to: + + aniou, artoj, badboy, cloud, cradle, David Coppa, dbp, dothebart, eeemsi, + eelvex, f8l, fernando, jan, jimdigriz, jon, julien, kacper, ktosiek, + lexszero, litemotiv, lourens, madroach, marcus, merovius, mike, mirko, mseed, + mxf, phnom, quaec, rogutes, sardemff7, smartass, thepub, tiago, tucos, + woddf2, xpt, ys + +-- Michael Stapelberg, 2011-07-24 diff --git a/RELEASE-NOTES-tree-pr1 b/RELEASE-NOTES-tree-pr1 new file mode 100644 index 00000000..dec817fa --- /dev/null +++ b/RELEASE-NOTES-tree-pr1 @@ -0,0 +1,45 @@ +Release notes for i3 tree-pr1 +----------------------------- + +This is a PREVIEW RELEASE for the tree branch. It is *NOT* part of i3’s regular +releases and should *NOT* be packaged in the usual way for distributions. +Instead, provide a separate, unofficial package if possible. + +The so called tree branch is the place where the next version of i3 is +developed. This time, we did a major code refactoring bringing many changes. +The idea is to use a tree as datastructure instead of separate lists (like one +for outputs, workspaces and a table for storing your window layout). + +Quite a few advantages arise from this new data structure. The most prominent +ones will be a slightly different look and feel, the possibility to store your +layout and restore it later, correct resizing, a much cleaner command parser +and more little improvements. + +As this is a preview release, some things are not working yet. Generally, +though, the core developers are using it already and think it’s good enough to +try it out. With this release, we want to gather feedback from you, so please +report any bugs you encounter in our bugtracker at http://i3.zekjur.net/bugs + +What should be working in this release? +--------------------------------------- + + • Basic window management, navigation, moving + • Fullscreen mode, correct aspect ratio + • Stacked/Tabbed layout, floating mode + • Splitting (for fancy layouts), resizing + • Restarts, preserving the layout + • i3bar, get it from http://git.merovius.de/ + +If any of these features do not work (correctly), please file a bugreport. + +What is not working in this release? +------------------------------------ + + • RandR changes (i3 needs to be restarted) + • Assignments + • Configfile compatibility + • Workspace switching is sometimes not working. If you find a pattern, please + report it. + • There are still some bugs in resizing. Please report! + + -- Michael Stapelberg, 2010-12-06 diff --git a/RELEASE-NOTES-tree-pr2 b/RELEASE-NOTES-tree-pr2 new file mode 100644 index 00000000..533c3c76 --- /dev/null +++ b/RELEASE-NOTES-tree-pr2 @@ -0,0 +1,43 @@ +Release notes for i3 tree-pr2 +----------------------------- + +This is the second PREVIEW RELEASE for the tree branch. It is *NOT* part of +i3’s regular releases and should *NOT* be packaged in the usual way for +distributions. Instead, provide a separate, unofficial package if possible. + +The so called tree branch is the place where the next version of i3 is +developed. This time, we did a major code refactoring bringing many changes. +The idea is to use a tree as datastructure instead of separate lists (like one +for outputs, workspaces and a table for storing your window layout). + +Quite a few advantages arise from this new data structure. The most prominent +ones will be a slightly different look and feel, the possibility to store your +layout and restore it later, correct resizing, a much cleaner command parser +and more little improvements. + +As this is a preview release, some things are not working yet. Generally, +though, the core developers are using it already and think it’s good enough to +try it out. With this release, we want to gather feedback from you, so please +report any bugs you encounter in our bugtracker at http://i3.zekjur.net/bugs + +What should be working in this release? +--------------------------------------- + + • Basic window management, navigation, moving + • Fullscreen mode, correct aspect ratio + • Stacked/Tabbed layout, floating mode + • Splitting (for fancy layouts), resizing + • Restarts (preserving the layout), crash handler + • RandR changes, keyboard layout changes + • Dock clients + • i3bar, get it from http://git.merovius.de/ + +If any of these features do not work (correctly), please file a bugreport. + +What is not working in this release? +------------------------------------ + + • Assignments + • Configfile compatibility + + -- Michael Stapelberg, 2011-03-07 diff --git a/RELEASE-NOTES-tree-pr3 b/RELEASE-NOTES-tree-pr3 new file mode 100644 index 00000000..fd8f4c3f --- /dev/null +++ b/RELEASE-NOTES-tree-pr3 @@ -0,0 +1,43 @@ +Release notes for i3 tree-pr3 +----------------------------- + +This is the third PREVIEW RELEASE for the tree branch. It is *NOT* part of +i3’s regular releases and should *NOT* be packaged in the usual way for +distributions. Instead, provide a separate, unofficial package if possible. + +The so called tree branch is the place where the next version of i3 is +developed. This time, we did a major code refactoring bringing many changes. +The idea is to use a tree as datastructure instead of separate lists (like one +for outputs, workspaces and a table for storing your window layout). + +Quite a few advantages arise from this new data structure. The most prominent +ones will be a slightly different look and feel, the possibility to store your +layout and restore it later, correct resizing, a much cleaner command parser +and more little improvements. + +As this is a preview release, some things are not working yet. Generally, +though, the core developers are using it already and think it’s good enough to +try it out. With this release, we want to gather feedback from you, so please +report any bugs you encounter in our bugtracker at http://bugs.i3wm.org/ + +What should be working in this release? +--------------------------------------- + + • Basic window management, navigation, moving + • Fullscreen mode, correct aspect ratio + • Stacked/Tabbed layout, floating mode + • Splitting (for fancy layouts), resizing + • Restarts (preserving the layout), crash handler + • RandR changes, keyboard layout changes + • Dock clients + • Assignments + • i3bar, get it from http://git.merovius.de/ + +If any of these features do not work (correctly), please file a bugreport. + +What is not working in this release? +------------------------------------ + + • Configfile compatibility + + -- Michael Stapelberg, 2011-05-28 diff --git a/RELEASE-NOTES-tree-pr4 b/RELEASE-NOTES-tree-pr4 new file mode 100644 index 00000000..9316bbaf --- /dev/null +++ b/RELEASE-NOTES-tree-pr4 @@ -0,0 +1,37 @@ +Release notes for i3 tree-pr4 +----------------------------- + +This is the fourth PREVIEW RELEASE for the tree branch. It is *NOT* part of +i3’s regular releases and should *NOT* be packaged in the usual way for +distributions. Instead, provide a separate, unofficial package if possible. + +The so called tree branch is the place where the next version of i3 is +developed. This time, we did a major code refactoring bringing many changes. +The idea is to use a tree as datastructure instead of separate lists (like one +for outputs, workspaces and a table for storing your window layout). + +Quite a few advantages arise from this new data structure. The most prominent +ones will be a slightly different look and feel, the possibility to store your +layout and restore it later, correct resizing, a much cleaner command parser +and more little improvements. + +This release is considered a release candidate for i3 v4.0. We will not make +big changes and plan to release v4.0 in a few weeks. +With this release, we want to gather feedback from you, so please +report any bugs you encounter in our bugtracker at http://bugs.i3wm.org/ + +What should be working in this release? +--------------------------------------- + + • Basic window management, navigation, moving + • Fullscreen mode, correct aspect ratio + • Stacked/Tabbed layout, floating mode + • Splitting (for fancy layouts), resizing + • Restarts (preserving the layout), crash handler + • RandR changes, keyboard layout changes + • Dock clients + • Assignments + • Config file compatibility + • i3bar, get it from http://git.merovius.de/ + + -- Michael Stapelberg, 2011-07-15 diff --git a/common.mk b/common.mk index d45286a8..0a695e7d 100644 --- a/common.mk +++ b/common.mk @@ -1,5 +1,6 @@ UNAME=$(shell uname) DEBUG=1 +COVERAGE=0 INSTALL=install ifndef PREFIX PREFIX=/usr @@ -11,10 +12,20 @@ ifndef SYSCONFDIR SYSCONFDIR=$(PREFIX)/etc endif endif +TERM_EMU=xterm # The escaping is absurd, but we need to escape for shell, sed, make, define -GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1), branch $(shell [ -f .git/HEAD ] && sed 's/ref: refs\/heads\/\(.*\)/\\\\\\"\1\\\\\\"/g' .git/HEAD || echo 'unknown'))" +GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1), branch $(shell [ -f $(TOPDIR)/.git/HEAD ] && sed 's/ref: refs\/heads\/\(.*\)/\\\\\\"\1\\\\\\"/g' $(TOPDIR)/.git/HEAD || echo 'unknown'))" VERSION:=$(shell git describe --tags --abbrev=0) +ifeq ($(shell which pkg-config 2>/dev/null 1>/dev/null || echo 1),1) +$(error "pkg-config was not found") +endif + +# An easier way to get CFLAGS and LDFLAGS falling back in case there's +# no pkg-config support for certain libraries +cflags_for_lib = $(shell pkg-config --silence-errors --cflags $(1)) +ldflags_for_lib = $(shell pkg-config --exists $(1) && pkg-config --libs $(1) || echo -l$(2)) + CFLAGS += -std=c99 CFLAGS += -pipe CFLAGS += -Wall @@ -22,40 +33,43 @@ CFLAGS += -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}\" -CFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\" - -# Check if pkg-config is installed, because without pkg-config, the following -# check for the version of libxcb cannot be done. -ifeq ($(shell which pkg-config 2>/dev/null 1>/dev/null || echo 1),1) -$(error "pkg-config was not found") +CFLAGS += $(call cflags_for_lib, xcb-keysyms) +ifeq ($(shell pkg-config --exists xcb-util || echo 1),1) +CPPFLAGS += -DXCB_COMPAT +CFLAGS += $(call cflags_for_lib, xcb-atom) +CFLAGS += $(call cflags_for_lib, xcb-aux) +else +CFLAGS += $(call cflags_for_lib, xcb-util) endif +CFLAGS += $(call cflags_for_lib, xcb-icccm) +CFLAGS += $(call cflags_for_lib, xcb-xinerama) +CFLAGS += $(call cflags_for_lib, xcb-randr) +CFLAGS += $(call cflags_for_lib, xcb) +CFLAGS += $(call cflags_for_lib, xcursor) +CFLAGS += $(call cflags_for_lib, x11) +CFLAGS += $(call cflags_for_lib, yajl) +CFLAGS += $(call cflags_for_lib, libev) +CPPFLAGS += -DI3_VERSION=\"${GIT_VERSION}\" +CPPFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\" +CPPFLAGS += -DTERM_EMU=\"$(TERM_EMU)\" -ifeq ($(shell pkg-config --exists xcb-keysyms || echo 1),1) -$(error "pkg-config could not find xcb-keysyms.pc") +LIBS += -lm +LIBS += $(call ldflags_for_lib, xcb-event, xcb-event) +LIBS += $(call ldflags_for_lib, xcb-keysyms, xcb-keysyms) +ifeq ($(shell pkg-config --exists xcb-util || echo 1),1) +LIBS += $(call ldflags_for_lib, xcb-atom, xcb-atom) +LIBS += $(call ldflags_for_lib, xcb-aux, xcb-aux) +else +LIBS += $(call ldflags_for_lib, xcb-util) endif - -ifeq ($(shell pkg-config --exact-version=0.3.3 xcb-keysyms && echo 1),1) -# xcb-keysyms fixed API from 0.3.3 to 0.3.4, so for some months, we will -# have this here. Distributions should upgrade their libxcb in the meantime. -CFLAGS += -DOLD_XCB_KEYSYMS_API -endif - -LDFLAGS += -lm -LDFLAGS += -lxcb-event -LDFLAGS += -lxcb-property -LDFLAGS += -lxcb-keysyms -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 +LIBS += $(call ldflags_for_lib, xcb-icccm, xcb-icccm) +LIBS += $(call ldflags_for_lib, xcb-xinerama, xcb-xinerama) +LIBS += $(call ldflags_for_lib, xcb-randr, xcb-randr) +LIBS += $(call ldflags_for_lib, xcb, xcb) +LIBS += $(call ldflags_for_lib, xcursor, Xcursor) +LIBS += $(call ldflags_for_lib, x11, X11) +LIBS += $(call ldflags_for_lib, yajl, yajl) +LIBS += $(call ldflags_for_lib, libev, ev) ifeq ($(UNAME),NetBSD) # We need -idirafter instead of -I to prefer the system’s iconv over GNU libiconv @@ -65,12 +79,16 @@ endif ifeq ($(UNAME),OpenBSD) CFLAGS += -I${X11BASE}/include -LDFLAGS += -liconv +LIBS += -liconv LDFLAGS += -L${X11BASE}/lib endif ifeq ($(UNAME),FreeBSD) -LDFLAGS += -liconv +LIBS += -liconv +endif + +ifeq ($(UNAME),Darwin) +LIBS += -liconv endif # Fallback for libyajl 1 which did not include yajl_version.h. We need @@ -78,7 +96,7 @@ endif CFLAGS += -idirafter yajl-fallback ifneq (,$(filter Linux GNU GNU/%, $(UNAME))) -CFLAGS += -D_GNU_SOURCE +CPPFLAGS += -D_GNU_SOURCE endif ifeq ($(DEBUG),1) @@ -87,6 +105,12 @@ CFLAGS += -gdwarf-2 CFLAGS += -g3 else CFLAGS += -O2 +CFLAGS += -freorder-blocks-and-partition +endif + +ifeq ($(COVERAGE),1) +CFLAGS += -fprofile-arcs -ftest-coverage +LIBS += -lgcov endif # Don’t print command lines which are run diff --git a/debian/changelog b/debian/changelog index e20d2690..a638750e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +i3-wm (4.0-0) unstable; urgency=low + + * NOT YET RELEASED + + -- Michael Stapelberg Sun, 24 Jul 2011 00:10:30 +0200 + i3-wm (3.e-bf1-3) unstable; urgency=low * include keyboard-layer{1,2}.png in docs (Closes: #595295) diff --git a/debian/control b/debian/control index d396ea77..a2a83726 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Section: utils Priority: extra Maintainer: Michael Stapelberg DM-Upload-Allowed: yes -Build-Depends: debhelper (>= 6), 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 +Build-Depends: debhelper (>= 6), libx11-dev, libxcb-util0-dev (>= 0.3.8), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-icccm4-dev, libxcursor-dev, asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev, perl, texlive-latex-base, texlive-latex-recommended, texlive-latex-extra Standards-Version: 3.9.1 Homepage: http://i3.zekjur.net/ @@ -27,12 +27,12 @@ Provides: x-window-manager Suggests: rxvt-unicode | x-terminal-emulator Recommends: xfonts-base, libanyevent-i3-perl, libanyevent-perl, libipc-run-perl Description: an improved dynamic tiling window manager - Key features of i3 are good support of multi-monitor setups (workspaces are + Key features of i3 are correct implementation of Xinerama (workspaces are assigned to virtual screens, i3 does the right thing when attaching new - 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. + 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. . Please be aware i3 is primarily targeted at advanced users and developers. diff --git a/debian/rules b/debian/rules index 464722c0..d6393980 100755 --- a/debian/rules +++ b/debian/rules @@ -45,7 +45,6 @@ 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 379f07d1..90abed28 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,5 +1,5 @@ -all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html wsbar.html refcard.pdf +all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html wsbar.html tree-migrating.html refcard.pdf hacking-howto.html: hacking-howto asciidoc -a toc -n $< @@ -10,6 +10,10 @@ debugging.html: debugging userguide.html: userguide asciidoc -a toc -n $< +tree-migrating.html: tree-migrating + asciidoc -a toc -n $< + + ipc.html: ipc asciidoc -a toc -n $< @@ -23,5 +27,4 @@ refcard.pdf: refcard.tex pdflatex refcard.tex && pdflatex refcard.tex clean: - rm -f */*.{aux,log,toc,bm,pdf,dvi} - rm -f *.log *.html + find . -regex ".*\.\(aux\|out\|log\|toc\|bm\|pdf\|dvi\|log\|html\)" -exec rm '{}' \; diff --git a/docs/debugging b/docs/debugging index b4bda670..5e71ecf0 100644 --- a/docs/debugging +++ b/docs/debugging @@ -1,7 +1,7 @@ Debugging i3: How To ==================== Michael Stapelberg -March 2010 +July 2011 This document describes how to debug i3 suitably for sending us useful bug reports, even if you have no clue of C programming. @@ -12,14 +12,21 @@ debugging and/or need further help, do not hesitate to contact us! == Enabling logging -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 seperate -log file with date/time in it is a good idea to not get confused about the -different log files later on. +i3 logs useful information to stdout. To have a clearly defined place where log +files will be saved, you should redirect stdout and stderr in your ++~/.xsession+. While you’re at it, putting each run of i3 in a separate log +file with date/time in its filename is a good idea to not get confused about +the different log files later on. -------------------------------------------------------------------- -exec /usr/bin/i3 -V -d all >/home/michael/i3/i3log-$(date +'%F-%k-%M-%S') 2>&1 +exec /usr/bin/i3 >~/i3log-$(date +'%F-%k-%M-%S') 2>&1 +-------------------------------------------------------------------- + +To enable verbose output and all levels of debug output (required when +attaching logfiles to bugreports), add the parameters +-V -d all+, like this: + +-------------------------------------------------------------------- +exec /usr/bin/i3 -V -d all >~/i3log-$(date +'%F-%k-%M-%S') 2>&1 -------------------------------------------------------------------- == Enabling core dumps @@ -29,7 +36,7 @@ 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): +use the following command (in your +~/.xsession+, before starting i3): ------------------- ulimit -c unlimited @@ -50,9 +57,9 @@ process id (%p) in it. You can save this setting across reboots using == Compiling with debug symbols 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: +is compiled with debug symbols, that is, that the symbols are not stripped +during the build process. You can check whether your executable contains +symbols by issuing the following command: ---------------- file $(which i3) diff --git a/docs/hacking-howto b/docs/hacking-howto index 8a778546..9a7ec9d4 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -1,7 +1,7 @@ Hacking i3: How To ================== Michael Stapelberg -December 2009 +July 2011 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 @@ -65,6 +65,13 @@ the layout you need at the moment. === The layout table +********************************************************************************* +This section has not been updated for v4.0 yet, sorry! We wanted to release on +time, but we will update this soon. Please talk to us on IRC if you need to +know stuff *NOW* :). +********************************************************************************* + +///////////////////////////////////////////////////////////////////////////////// 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 @@ -106,9 +113,15 @@ window). |======== Furthermore, you can freely resize table cells. +///////////////////////////////////////////////////////////////////////////////// == Files +include/atoms.xmacro:: +A file containing all X11 atoms which i3 uses. This file will be included +various times (for defining, requesting and receiving the atoms), each time +with a different definition of xmacro(). + include/data.h:: Contains data definitions used by nearly all files. You really need to read this first. @@ -128,19 +141,27 @@ 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/cmdparse.l:: +Contains the lexer for i3 commands, written for +flex(1)+. -src/commands.c:: -Parsing commands and actually executing them (focusing, moving, …). +src/cmdparse.y:: +Contains the parser for i3 commands, written for +bison(1)+. + +src/con.c:: +Contains all functions which deal with containers directly (creating +containers, searching containers, getting specific properties from containers, +…). src/config.c:: -Parses the configuration file. +Contains all functions handling the configuration file (calling the parser +(src/cfgparse.y) with the correct path, switching key bindings mode). src/debug.c:: Contains debugging functions to print unhandled X events. +src/ewmh.c:: +iFunctions to get/set certain EWMH properties easily. + src/floating.c:: Contains functions for floating mode (mostly resizing/dragging). @@ -151,88 +172,162 @@ unmapping, key presses, button presses, …). src/ipc.c:: Contains code for the IPC interface. -src/layout.c:: -Renders your layout (screens, workspaces, containers). +src/load_layout.c:: +Contains code for loading layouts from JSON files. -src/mainx.c:: +src/log.c:: +Handles the setting of loglevels, contains the logging functions. + +src/main.c:: Initializes the window manager. src/manage.c:: Looks at existing or new windows and decides whether to manage them. If so, it reparents the window and inserts it into our data structures. -src/resize.c:: -Contains the functions to resize columns/rows in the table. +src/match.c:: +A "match" is a data structure which acts like a mask or expression to match +certain windows or not. For example, when using commands, you can specify a +command like this: [title="*Firefox*"] kill. The title member of the match +data structure will then be filled and i3 will check each window using +match_matches_window() to find the windows affected by this command. -src/table.c:: -Manages the most important internal data structure, the design table. +src/move.c:: +Contains code to move a container in a specific direction. + +src/output.c:: +Functions to handle CT_OUTPUT cons. + +src/randr.c:: +The RandR API is used to get (and re-query) the configured outputs (monitors, +…). + +src/render.c:: +Renders the tree data structure by assigning coordinates to every node. These +values will later be pushed to X11 in +src/x.c+. + +src/resize.c:: +Contains the functions to resize containers. + +src/sighandler.c:: +Handles +SIGSEGV+, +SIGABRT+ and +SIGFPE+ by showing a dialog that i3 crashed. +You can chose to let it dump core, to restart it in-place or to restart it +in-place but forget about the layout. + +src/tree.c:: +Contains functions which open or close containers in the tree, change focus or +cleanup ("flatten") the tree. See also +src/move.c+ for another similar +function, which was moved into its own file because it is so long. src/util.c:: Contains useful functions which are not really dependant on anything. +src/window.c:: +Handlers to update X11 window properties like +WM_CLASS+, +_NET_WM_NAME+, ++CLIENT_LEADER+, etc. + src/workspace.c:: Contains all functions related to workspaces (displaying, hiding, renaming…) +src/x.c:: +Transfers our in-memory tree (see +src/render.c+) to X11. + src/xcb.c:: Contains wrappers to use xcb more easily. +src/xcursor.c:: +XCursor functions (for cursor themes). + src/xinerama.c:: -(Re-)initializes the available screens and converts them to virtual screens -(see below). +Legacy support for Xinerama. See +src/randr.c+ for the preferred API. == Data structures +********************************************************************************* +This section has not been updated for v4.0 yet, sorry! We wanted to release on +time, but we will update this soon. Please talk to us on IRC if you need to +know stuff *NOW* :). +********************************************************************************* + +///////////////////////////////////////////////////////////////////////////////// + See include/data.h for documented data structures. The most important ones are explained right here. image:bigpicture.png[The Big Picture] +///////////////////////////////////////////////////////////////////////////////// + So, the hierarchy is: +. *X11 root window*, the root container . *Virtual screens* (Screen 0 in this example) -. *Workspaces* (Workspace 1 in this example) -. *Table* (There can only be one table per Workspace) -. *Container* (left and right in this example) -. *Client* (The two clients in the left container) +. *Content container* (there are also containers for dock windows) +. *Workspaces* (Workspace 1 in this example, with horizontal orientation) +. *Split container* (vertically split) +. *X11 window containers* + +The data type is +Con+, in all cases. === 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 +A virtual screen (type `i3Screen`) is generated from the connected outputs +obtained through RandR. The difference to the raw RandR outputs as seen when using +xrandr(1)+ is that it falls back to the lowest common resolution of -the logical screens. +the actual enabled outputs. -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 a screen resolution of 1280x800 px and you +connect a video projector with a resolution of 1024x768 px, set it up in clone +mode (+xrandr \--output VGA1 \--mode 1024x768 \--same-as LVDS1+), 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 +However, if you configure it using +xrandr \--output VGA1 \--mode 1024x768 +\--right-of LVDS1+, 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 +A workspace is identified by its name. 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 -seperate of each other. Other window managers also call this ``Virtual +separate of each other. Other window managers also call this ``Virtual desktops''. === The layout table +********************************************************************************* +This section has not been updated for v4.0 yet, sorry! We wanted to release on +time, but we will update this soon. Please talk to us on IRC if you need to +know stuff *NOW* :). +********************************************************************************* + +///////////////////////////////////////////////////////////////////////////////// + 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 +********************************************************************************* +This section has not been updated for v4.0 yet, sorry! We wanted to release on +time, but we will update this soon. Please talk to us on IRC if you need to +know stuff *NOW* :). +********************************************************************************* + +///////////////////////////////////////////////////////////////////////////////// + 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 A client is x11-speak for a window. @@ -244,11 +339,11 @@ 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), `CIRCLEQ` (circular -queues) and TAILQ (tail queues). Usually, only forward traversal is necessary, +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 +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 @@ -258,14 +353,14 @@ should be chosen for those: * ``conn'' is the xcb_connection_t * ``event'' is the event of the particular type - * ``container'' names a container - * ``client'' names a client, for example when using a +CIRCLEQ_FOREACH+ + * ``con'' names a container + * ``current'' is a loop variable when using +TAILQ_FOREACH+ etc. == Startup (src/mainx.c, main()) * Establish the xcb connection - * Check for XKB extension on the seperate X connection - * Check for Xinerama screens + * Check for XKB extension on the separate X connection, load Xcursor + * Check for RandR screens (with a fall-back to Xinerama) * Grab the keycodes for which bindings exist * Manage all existing windows * Enter the event loop @@ -303,9 +398,10 @@ the correct state. Then, it looks through all bindings and gets the one which matches the received event. -The bound command is parsed directly in command mode. +The bound command is parsed by the cmdparse lexer/parser, see +parse_cmd+ in ++src/cmdparse.y+. -== Manage windows (src/mainx.c, manage_window() and reparent_window()) +== Manage windows (src/main.c, manage_window() and reparent_window()) `manage_window()` does some checks to decide whether the window should be managed at all: @@ -325,7 +421,7 @@ 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, +of the screen. To get the height which needs 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 @@ -339,7 +435,7 @@ 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 +After reparenting the window, `render_tree()` 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) @@ -388,6 +484,15 @@ src/layout.c, function resize_client(). == Rendering (src/layout.c, render_layout() and render_container()) +********************************************************************************* +This section has not been updated for v4.0 yet, sorry! We wanted to release on +time, but we will update this soon. Please talk to us on IRC if you need to +know stuff *NOW* :). +********************************************************************************* + +///////////////////////////////////////////////////////////////////////////////// + + 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 @@ -460,7 +565,18 @@ floating windows: * The new width_factor for each involved column (respectively row) will be calculated. -== User commands / commandmode (src/commands.c) +///////////////////////////////////////////////////////////////////////////////// + +== User commands / commandmode (src/cmdparse.{l,y}) + +********************************************************************************* +This section has not been updated for v4.0 yet, sorry! We wanted to release on +time, but we will update this soon. Please talk to us on IRC if you need to +know stuff *NOW* :). +********************************************************************************* + +///////////////////////////////////////////////////////////////////////////////// + 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 @@ -485,6 +601,148 @@ 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. +///////////////////////////////////////////////////////////////////////////////// + +== Moving containers + +The movement code is pretty delicate. You need to consider all cases before +making any changes or before being able to fully understand how it works. + +=== Case 1: Moving inside the same container + +The reference layout for this case is a single workspace in horizontal +orientation with two containers on it. Focus is on the left container (1). + + +[width="15%",cols="^,^"] +|======== +| 1 | 2 +|======== + +When moving the left window to the right (command +move right+), tree_move will +look for a container with horizontal orientation and finds the parent of the +left container, that is, the workspace. Afterwards, it runs the code branch +commented with "the easy case": it calls TAILQ_NEXT to get the container right +of the current one and swaps both containers. + +=== Case 2: Move a container into a split container + +The reference layout for this case is a horizontal workspace with two +containers. The right container is a v-split with two containers. Focus is on +the left container (1). + +[width="15%",cols="^,^"] +|======== +1.2+^.^| 1 | 2 +| 3 +|======== + +When moving to the right (command +move right+), i3 will work like in case 1 +("the easy case"). However, as the right container is not a leaf container, but +a v-split, the left container (1) will be inserted at the right position (below +2, assuming that 2 is focused inside the v-split) by calling +insert_con_into+. + ++insert_con_into+ detaches the container from its parent and inserts it +before/after the given target container. Afterwards, the on_remove_child +callback is called on the old parent container which will then be closed, if +empty. + +Afterwards, +con_focus+ will be called to fix the focus stack and the tree will +be flattened. + +=== Case 3: Moving to non-existant top/bottom + +Like in case 1, the reference layout for this case is a single workspace in +horizontal orientation with two containers on it. Focus is on the left +container: + +[width="15%",cols="^,^"] +|======== +| 1 | 2 +|======== + +This time however, the command is +move up+ or +move down+. tree_move will look +for a container with vertical orientation. As it will not find any, ++same_orientation+ is NULL and therefore i3 will perform a forced orientation +change on the workspace by creating a new h-split container, moving the +workspace contents into it and then changing the workspace orientation to +vertical. Now it will again search for parent containers with vertical +orientation and it will find the workspace. + +This time, the easy case code path will not be run as we are not moving inside +the same container. Instead, +insert_con_into+ will be called with the focused +container and the container above/below the current one (on the level of ++same_orientation+). + +Now, +con_focus+ will be called to fix the focus stack and the tree will be +flattened. + +=== Case 4: Moving to existant top/bottom + +The reference layout for this case is a vertical workspace with two containers. +The bottom one is a h-split containing two containers (1 and 2). Focus is on +the bottom left container (1). + +[width="15%",cols="^,^"] +|======== +2+| 3 +| 1 | 2 +|======== + +This case is very much like case 3, only this time the forced workspace +orientation change does not need to be performed because the workspace already +is in vertical orientation. + +=== Case 5: Moving in one-child h-split + +The reference layout for this case is a horizontal workspace with two +containers having a v-split on the left side with a one-child h-split on the +bottom. Focus is on the bottom left container (2(h)): + +[width="15%",cols="^,^"] +|======== +| 1 1.2+^.^| 3 +| 2(h) +|======== + +In this case, +same_orientation+ will be set to the h-split container around +the focused container. However, when trying the easy case, the next/previous +container +swap+ will be NULL. Therefore, i3 will search again for a ++same_orientation+ container, this time starting from the parent of the h-split +container. + +After determining a new +same_orientation+ container (if it is NULL, the +orientation will be force-changed), this case is equivalent to case 2 or case +4. + + +=== Case 6: Floating containers + +The reference layout for this case is a horizontal workspace with two +containers plus one floating h-split container. Focus is on the floating +container. + +TODO: nice illustration. table not possible? + +When moving up/down, the container needs to leave the floating container and it +needs to be placed on the workspace (at workspace level). This is accomplished +by calling the function +attach_to_workspace+. + +== Click handling + +Without much ado, here is the list of cases which need to be considered: + +* click to focus (tiling + floating) and raise (floating) +* click to focus/raise when in stacked/tabbed mode +* floating_modifier + left mouse button to drag a floating con +* floating_modifier + right mouse button to resize a floating con +* click on decoration in a floating con to either initiate a resize (if there + is more than one child in the floating con) or to drag the + floating con (if it’s the one at the top). +* click on border in a floating con to resize the floating con +* floating_modifier + right mouse button to resize a tiling con +* click on border/decoration to resize a tiling con + == Gotchas * Forgetting to call `xcb_flush(conn);` after sending a request. This usually diff --git a/docs/ipc b/docs/ipc index 36a5d2b9..7e713260 100644 --- a/docs/ipc +++ b/docs/ipc @@ -3,16 +3,19 @@ IPC interface (interprocess communication) Michael Stapelberg March 2010 -This document describes how to interface with i3 from a seperate process. This +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+. +most languages. In the default configuration file, the ipc-socket gets created +in +/tmp/i3-%u/ipc-socket.%p+ where +%u+ is your UNIX username and +%p+ is the +PID of i3. + +All i3 utilities, like +i3-msg+ and +i3-input+ will read the +I3_SOCKET_PATH+ +X11 property, stored on the X11 root window. == Establishing a connection @@ -21,7 +24,7 @@ snippet illustrates this in Perl: ------------------------------------------------------------- use IO::Socket::UNIX; -my $sock = IO::Socket::UNIX->new(Peer => '~/.i3/ipc.sock'); +my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); ------------------------------------------------------------- == Sending messages to i3 @@ -52,6 +55,10 @@ SUBSCRIBE (2):: GET_OUTPUTS (3):: Gets the current outputs. The reply will be a JSON-encoded list of outputs (see the reply section). +GET_TREE (4):: + Gets the layout tree. i3 uses a tree as data structure which includes + every container. The reply will be the JSON-encoded tree (see the reply + section). So, a typical message could look like this: -------------------------------------------------- @@ -101,6 +108,8 @@ SUBSCRIBE (2):: Confirmation/Error code for the SUBSCRIBE message. GET_OUTPUTS (3):: Reply to the GET_OUTPUTS message. +GET_TREE (4):: + Reply to the GET_TREE message. === COMMAND reply @@ -226,6 +235,190 @@ rect (map):: ] ------------------- +=== GET_TREE reply + +The reply consists of a serialized tree. Each node in the tree (representing +one container) has at least the properties listed below. While the nodes might +have more properties, please do not use any properties which are not documented +here. They are not yet finalized and will probably change! + +id (integer):: + The internal ID (actually a C pointer value) of this container. Do not + make any assumptions about it. You can use it to (re-)identify and + address containers when talking to i3. +name (string):: + The internal name of this container. For all containers which are part + of the tree structure down to the workspace contents, this is set to a + nice human-readable name of the container. + For all other containers, the content is not defined (yet). +border (string):: + Can be either "normal", "none" or "1pixel", dependending on the + container’s border style. +layout (string):: + Can be either "default", "stacked", "tabbed", "dockarea" or "output". + Other values might be possible in the future, should we add new + layouts. +orientation (string):: + Can be either "none" (for non-split containers), "horizontal" or + "vertical". +percent (float):: + The percentage which this container takes in its parent. A value of + +null+ means that the percent property does not make sense for this + container, for example for the root container. +rect (map):: + The absolute display coordinates for this container. Display + coordinates means that when you have two 1600x1200 monitors on a single + X11 Display (the standard way), the coordinates of the first window on + the second monitor are +{ "x": 1600, "y": 0, "width": 1600, "height": + 1200 }+. +window_rect (map):: + The coordinates of the *actual client window* inside its container. + These coordinates are relative to the container and do not include the + window decoration (which is actually rendered on the parent container). + So, when using the +default+ layout, you will have a 2 pixel border on + each side, making the window_rect +{ "x": 2, "y": 0, "width": 632, + "height": 366 }+ (for example). +geometry (map):: + The original geometry the window specified when i3 mapped it. Used when + switching a window to floating mode, for example. +urgent (bool):: + Whether this container (window or workspace) has the urgency hint set. +focused (bool):: + Whether this container is currently focused. + +Please note that in the following example, I have left out some keys/values +which are not relevant for the type of the node. Otherwise, the example would +be by far too long (it already is quite long, despite showing only 1 window and +one dock window). + +It is useful to have an overview of the structure before taking a look at the +JSON dump: + +* root +** LVDS1 +*** topdock +*** content +**** workspace 1 +***** window 1 +*** bottomdock +**** dock window 1 +** VGA1 + +*Example:* +----------------------- +{ + "id": 6875648, + "name": "root", + "rect": { + "x": 0, + "y": 0, + "width": 1280, + "height": 800 + }, + "nodes": [ + + { + "id": 6878320, + "name": "LVDS1", + "layout": "output", + "rect": { + "x": 0, + "y": 0, + "width": 1280, + "height": 800 + }, + "nodes": [ + + { + "id": 6878784, + "name": "topdock", + "layout": "dockarea", + "orientation": "vertical", + "rect": { + "x": 0, + "y": 0, + "width": 1280, + "height": 0 + }, + }, + + { + "id": 6879344, + "name": "content", + "rect": { + "x": 0, + "y": 0, + "width": 1280, + "height": 782 + }, + "nodes": [ + + { + "id": 6880464, + "name": "1", + "orientation": "horizontal", + "rect": { + "x": 0, + "y": 0, + "width": 1280, + "height": 782 + }, + "floating_nodes": [], + "nodes": [ + + { + "id": 6929968, + "name": "#aa0000", + "border": "normal", + "percent": 1, + "rect": { + "x": 0, + "y": 18, + "width": 1280, + "height": 782 + } + } + + ] + } + + ] + }, + + { + "id": 6880208, + "name": "bottomdock", + "layout": "dockarea", + "orientation": "vertical", + "rect": { + "x": 0, + "y": 782, + "width": 1280, + "height": 18 + }, + "nodes": [ + + { + "id": 6931312, + "name": "#00aa00", + "percent": 1, + "rect": { + "x": 0, + "y": 782, + "width": 1280, + "height": 18 + } + } + + ] + } + ] + } + ] +} +------------------------ + + == Events [[events]] @@ -242,7 +435,7 @@ 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 -seperate connection to receive events. +separate connection to receive events. === Subscribing to events @@ -290,8 +483,8 @@ if ($is_event) { === workspace event This event consists of a single serialized map containing a property -+change (string)+ which indicates the type of the change ("focus", "create", -"init", "empty", "urgent"). ++change (string)+ which indicates the type of the change ("focus", "init", +"empty", "urgent"). *Example:* --------------------- diff --git a/docs/tree-migrating b/docs/tree-migrating new file mode 100644 index 00000000..d356bbea --- /dev/null +++ b/docs/tree-migrating @@ -0,0 +1,192 @@ +Tree branch: Migrating +====================== +Michael Stapelberg +November 2010 + +== Introduction + +The tree branch (referring to a branch of i3 in the git repository) is the new +version of i3. Due to the very deep changes and heavy refactoring of the source +source, we decided to develop it in a separate branch (instead of using the +next/master-branch system like before). + +== Current status + +Currently, the code is mostly working. Some of the i3 core developers have been +using the tree branch version for a few weeks now. So, if you are eager to try +out the new features and help us find bugs, give it a try! + +At the same time, a word of warning is appropriate: This version of i3 might +crash unexpectedly, so please be careful with important data (do not work for +two days without saving…). + +== Getting the latest tree branch version + +Check out the latest version: +--------------------------------------------- +$ git clone -b tree git://code.stapelberg.de/i3 +--------------------------------------------- + +Then build and install it (has the same dependencies as the latest stable i3 +version): +----------------------------- +$ cd i3 +$ make +$ sudo cp i3 /usr/bin/i3-tree +----------------------------- + +…and execute +i3-tree+ instead of +i3+ in your Xsession. + +*IMPORTANT:* Please note that configuration file compatibility is not yet done. +So, make sure you use/customize the provided +i3.config+ file. + +== Tree + +The most important change and reason for the name is that i3 stores all +information about the X11 outputs, workspaces and layout of the windows on them +in a tree. The root node is the X11 root window, followed by the X11 outputs, +then workspaces and finally the windows themselve. In previous versions of i3 +we had multiple lists (of outputs, workspaces) and a table for each workspace. +That approach turned out to be complicated to use (snapping), understand and +implement. + +=== The tree consists of Containers + +The building blocks of our tree are so called +Containers+. A +Container+ can +host a window (meaning an X11 window, one that you can actually see and use, +like a browser). Alternatively, it could contain one or more +Containers+. A +simple example is the workspace: When you start i3 with a single monitor, a +single workspace and you open two terminal windows, you will end up with a tree +like this: + +image::tree-layout2.png["layout2",float="right"] +image::tree-shot4.png["shot4",title="Two terminals on standard workspace"] + +=== Orientation and Split Containers + +[[OrientationSplit]] + +It is only natural to use so-called +Split Containers+ in order to build a +layout when using a tree as data structure. In i3, every +Container+ has an +orientation (horizontal, vertical or unspecified). So, in our example with the +workspace, the default orientation of the workspace +Container+ is horizontal +(most monitors are widescreen nowadays). If you change the orientation to +vertical (+Alt+v+ in the default config) and *then* open two terminals, i3 will +configure your windows like this: + +image::tree-shot2.png["shot2",title="Vertical Workspace Orientation"] + +An interesting new feature of the tree branch is the ability to split anything: +Let’s assume you have two terminals on a workspace (with horizontal +orientation), focus is on the right terminal. Now you want to open another +terminal window below the current one. If you would just open a new terminal +window, it would show up to the right due to the horizontal workspace +orientation. Instead, press +Alt+v+ to create a +Vertical Split Container+ (to +open a +Horizontal Split Container+, use +Alt+h+). Now you can open a new +terminal and it will open below the current one: + +image::tree-layout1.png["Layout",float="right"] +image::tree-shot1.png["shot",title="Vertical Split Container"] + +unfloat::[] + +You probably guessed it already: There is no limit on how deep your hierarchy +of splits can be. + +=== Level up + +Let’s stay with our example from above. We have a terminal on the left and two +vertically split terminals on the right, focus is on the bottom right one. When +you open a new terminal, it will open below the current one. + +So, how can you open a new terminal window to the *right* of the current one? +The solution is to use +level up+, which will focus the +Parent Container+ of +the current +Container+. In this case, you would focus the +Vertical Split +Container+ which is *inside* the horizontally oriented workspace. Thus, now new +windows will be opened to the right of the +Vertical Split Container+: + +image::tree-shot3.png["shot3",title="Level Up, then open new terminal"] + +== Commands + +The authoritive reference for commands is +src/cmdparse.y+. You can also find +most commands in +i3.config+. Here comes a short overview over the important +commands: + +=== Manipulating layout + +------------------------------- +layout +------------------------------- + +=== Changing Focus + +-------------------------- +next +prev +-------------------------- + +.Examples: +------------------------- +bindsym Mod1+Left prev h +bindsym Mod1+Right next h +bindsym Mod1+Down next v +bindsym Mod1+Up prev v +------------------------- + +=== Moving + +----------------------------------------- +move +----------------------------------------- + +.Examples: +----------------------------------------- +bindsym Mod1+Shift+Left move before h +bindsym Mod1+Shift+Right move after h +bindsym Mod1+Shift+Down move before v +bindsym Mod1+Shift+Up move after v +----------------------------------------- + +=== Changing workspace + +--------------------------- +workspace +--------------------------- + +.Examples: +--------------------------- +bindsym Mod1+1 workspace 1 +bindsym Mod1+2 workspace 2 +… +--------------------------- + +=== Moving Containers to workspaces + +--------------------- +move workspace +--------------------- + +------------------------------------- +bindsym Mod1+Shift+1 move workspace 1 +bindsym Mod1+Shift+2 move workspace 2 +… +------------------------------------- + +=== Changing border style + +--------------------------- +border +--------------------------- + +=== Changing container mode + +----------------------------- +mode +----------------------------- + +== The rest + +What is not mentioned here explicitly is either unchanged and can be read in +the http://i3.zekjur.net/docs/userguide.html[i3 User’s Guide] or it is not yet +implemented. diff --git a/docs/userguide b/docs/userguide index 7402f64b..5c24dbf2 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1,24 +1,30 @@ i3 User’s Guide =============== Michael Stapelberg -March 2010 +July 2011 + +********************************************************************************* +This document is not yet finished. The tree branch is still in development. The +information provided here should be correct, just not complete yet. +********************************************************************************* 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. +window manager. If it does not, please contact us on IRC (preferred) or post your +question(s) on the mailing list. +////////////////////////////////////////////////////////////////////////////// == 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):* +*Keys to use with mod (alt):* -image:keyboard-layer1.png["Keys to use with Mod1 (alt)",width=600,link="keyboard-layer1.png"] +image:keyboard-layer1.png["Keys to use with mod (alt)",width=600,link="keyboard-layer1.png"] -*Keys to use with Shift+Mod1:* +*Keys to use with Shift+mod:* -image:keyboard-layer2.png["Keys to use with Shift+Mod1",width=600,link="keyboard-layer2.png"] +image:keyboard-layer2.png["Keys to use with Shift+mod",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 @@ -26,46 +32,52 @@ 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 +Throughout this guide, the keyword +mod+ will be used to refer to the +configured modifier. This is the alt key (Mod1) by default, with windows (Mod4) +being a popular alternative. + === Opening terminals and moving around 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 +for this is mod+Enter, that is Alt+Enter in the default configuration. By +pressing mod+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, which is where your new terminal is -opened. - -If you now open another terminal, you still have only one cell. However, the -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. +If you now open another terminal, i3 will place it next to the current one, +splitting the screen size in half. Depending on your monitor, i3 will put the +created window beside the existing window (on wide displays) or below the +existing window (rotated displays). 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+. +To move the focus between the two terminals, you can 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, +mod+J+ is left, +mod+K+ +is down, +mod+L+ is up and `mod+;` is right. So, to switch between the +terminals, use +mod+K+ or +mod+L+. Of course, you can also use the arrow keys. -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+;`. +At the moment, your workspace is split (it contains two terminals) in a +specific direction (horizontal by default). Every window can be split +horizontally or vertically again, just like the workspace. The terminology is +"window" for a container that actually contains an X11 window (like a terminal +or browser) and "split container" for containers that consist of one or more +windows. -image:two_columns.png[Two columns] +TODO: picture of the tree -=== Changing container modes +To split a window vertically, press +mod+v+. To split it horizontally, press ++mod+h+. -A container can have the following modes: +=== Changing the container layout + +A split container can have one of the following layouts: default:: Windows are sized so that every window gets an equal amount of space in the @@ -77,35 +89,35 @@ tabbed:: The same principle as +stacking+, but the list of windows at the top is only a single line which is vertically split. -To switch modes, press +Mod1+e+ for default, +Mod1+h+ for stacking and -+Mod1+w+ for tabbed. +To switch modes, press +mod+e+ for default, +mod+h+ for stacking and ++mod+w+ for tabbed. image:modes.png[Container modes] === Toggling fullscreen mode for a window -To display a window fullscreen or to go out of fullscreen mode again, press -+Mod1+f+. +To display a window in fullscreen mode or to go out of fullscreen mode again, +press +mod+f+. -There is also a global fullscreen mode in i3 in which the client will use all +There is also a global fullscreen mode in i3 in which the client will span all available outputs. === Opening other applications 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. The application -typed has to be in your +$PATH+ for this to work. ++dmenu+ which is opened by pressing +mod+d+ by default. Just type the name +(or a part of it) of the application which you want to open. The corresponding +application has to be in your +$PATH+ for this to work. 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. +<> for details. === Closing windows 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 +can press +mod+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 the WM_DELETE protocol your X server will kill the window and the behaviour @@ -115,7 +127,7 @@ depends on the application. Workspaces are an easy way to group a set of windows. By default, you are on the first workspace, as the bar on the bottom left indicates. To switch to -another workspace, press +Mod1+num+ where +num+ is the number of the workspace +another workspace, press +mod+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 @@ -129,18 +141,15 @@ focus to that screen. === Moving windows to workspaces -To move a window to another workspace, simply press +Mod1+Shift+num+ where +To move a window to another workspace, simply press +mod+Shift+num+ where +num+ is (like when switching workspaces) the number of the target workspace. Similarly to switching workspaces, the target workspace will be created if it does not yet exist. -=== Resizing columns/rows +=== Resizing -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. If -you need applications with different horizontal sizes, place them in seperate -cells one above the other. +The easiest way to resize a container is by using the mouse: Grab the border +and move it to the wanted size. See <> for how to configure i3 to be able to resize columns/rows with your keyboard. @@ -148,35 +157,21 @@ columns/rows with your keyboard. === Restarting i3 inplace 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. +to upgrade to a newer version of i3) you can use +mod+Shift+r+. === Exiting i3 -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 number of columns/rows a specific cell of the table -consumes. This is easier explained by giving an example, so take the following -layout: - -image:snapping.png[Snapping example] - -To use the full size of your screen, you can now snap container 3 downwards -by pressing +Mod1+Control+k+ (or snap container 2 rightwards). +To cleanly exit i3 without killing your X server, you can use +mod+Shift+e+. === Floating 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). Those windows usually set the +appropriate hint and are opened in floating mode by default. -You can enable floating mode for a window by pressing +Mod1+Shift+Space+. By +You can enable floating mode for a window by pressing +mod+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 <>. @@ -185,6 +180,73 @@ For resizing floating windows with your keyboard, see <>. Floating windows are always on top of tiling windows. +== Tree + +i3 stores all information about the X11 outputs, workspaces and layout of the +windows on them in a tree. The root node is the X11 root window, followed by +the X11 outputs, then dock areas and a content container, then workspaces and +finally the windows themselve. In previous versions of i3 we had multiple lists +(of outputs, workspaces) and a table for each workspace. That approach turned +out to be complicated to use (snapping), understand and implement. + +=== The tree consists of Containers + +The building blocks of our tree are so called +Containers+. A +Container+ can +host a window (meaning an X11 window, one that you can actually see and use, +like a browser). Alternatively, it could contain one or more +Containers+. A +simple example is the workspace: When you start i3 with a single monitor, a +single workspace and you open two terminal windows, you will end up with a tree +like this: + +image::tree-layout2.png["layout2",float="right"] +image::tree-shot4.png["shot4",title="Two terminals on standard workspace"] + +=== Orientation and Split Containers + +[[OrientationSplit]] + +It is only natural to use so-called +Split Containers+ in order to build a +layout when using a tree as data structure. In i3, every +Container+ has an +orientation (horizontal, vertical or unspecified). So, in our example with the +workspace, the default orientation of the workspace +Container+ is horizontal +(most monitors are widescreen nowadays). If you change the orientation to +vertical (+mod+v+ in the default config) and *then* open two terminals, i3 will +configure your windows like this: + +image::tree-shot2.png["shot2",title="Vertical Workspace Orientation"] + +An interesting new feature of the tree branch is the ability to split anything: +Let’s assume you have two terminals on a workspace (with horizontal +orientation), focus is on the right terminal. Now you want to open another +terminal window below the current one. If you would just open a new terminal +window, it would show up to the right due to the horizontal workspace +orientation. Instead, press +mod+v+ to create a +Vertical Split Container+ (to +open a +Horizontal Split Container+, use +mod+h+). Now you can open a new +terminal and it will open below the current one: + +image::tree-layout1.png["Layout",float="right"] +image::tree-shot1.png["shot",title="Vertical Split Container"] + +unfloat::[] + +You probably guessed it already: There is no limit on how deep your hierarchy +of splits can be. + +=== Focus parent + +Let’s stay with our example from above. We have a terminal on the left and two +vertically split terminals on the right, focus is on the bottom right one. When +you open a new terminal, it will open below the current one. + +So, how can you open a new terminal window to the *right* of the current one? +The solution is to use +focus parent+, which will focus the +Parent Container+ of +the current +Container+. In this case, you would focus the +Vertical Split +Container+ which is *inside* the horizontally oriented workspace. Thus, now new +windows will be opened to the right of the +Vertical Split Container+: + +image::tree-shot3.png["shot3",title="Focus parent, then open new terminal"] + +[[configuring]] == Configuring i3 This is where the real fun begins ;-). Most things are very dependant on your @@ -203,6 +265,14 @@ 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. +On first start (and on all following starts, unless you have a configuration +file), i3 will offer you to create a configuration file. You can tell the +wizard to use either Alt (Mod1) or Windows (Mod4) as modifier in the config +file. Also, the created config file will use the key symbols of your current +keyboard layout. To start the wizard, use the command +i3-config-wizard+. +Please note that you must not have +~/.i3/config+, otherwise the wizard will +exit. + === Comments It is possible and recommended to use comments in your configuration file to @@ -216,10 +286,12 @@ a # and can only be used at the beginning of a line: === 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. +i3 uses X core fonts (not Xft) for rendering window titles. 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. + +If i3 cannot open the configured font, it will output an error in the logfile +and fall back to a working font. *Syntax*: ------------------------------ @@ -244,9 +316,9 @@ also mix your bindings, though i3 will not protect you from overlapping ones). are the ones you use in Xmodmap to remap your keys. To get the current mapping of your keys, use +xmodmap -pke+. -* 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 (when using +xmodmap+). +* Keycodes do not need to have a symbol assigned (handy for custom vendor + hotkeys on some notebooks) and they will not change their meaning as you + switch to a different keyboard layout (when using +xmodmap+). 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. @@ -256,19 +328,19 @@ keysyms. *Syntax*: ---------------------------------- bindsym [Modifiers+]keysym command -bind [Modifiers+]keycode command +bindcode [Modifiers+]keycode command ---------------------------------- *Examples*: -------------------------------- # Fullscreen -bindsym Mod1+f f +bindsym mod+f f # Restart -bindsym Mod1+Shift+r restart +bindsym mod+Shift+r restart # Notebook-specific hotkeys -bind 214 exec /home/michael/toggle_beamer.sh +bindcode 214 exec /home/michael/toggle_beamer.sh -------------------------------- Available Modifiers: @@ -303,41 +375,63 @@ you hold the shift button as well, the resize will be proportional. floating_modifier -------------------------------- -*Examples*: +*Example*: -------------------------------- floating_modifier Mod1 -------------------------------- +=== Orientation for new workspaces + +New workspaces get a reasonable default orientation: Wide-screen monitors +(anything wider than high) get horizontal orientation, rotated monitors +(anything higher than wide) get vertical orientation. + +With the +default_orientation+ configuration directive, you can override that +behaviour. + +*Syntax*: +---------------------------------------------- +default_orientation +---------------------------------------------- + +*Example*: +---------------------------- +default_orientation vertical +---------------------------- + === Layout mode for new containers -This option determines in which mode new containers will start. See also -<>. +This option determines in which mode new containers on workspace level will +start. +/////////////////////////////// +See also <>. +////////////////////////////// *Syntax*: --------------------------------------------- -new_container -new_container stack-limit +workspace_layout --------------------------------------------- +///////////////////////////////////////////// +new_container stack-limit +///////////////////////////////////////////// -*Examples*: +*Example*: --------------------- -new_container tabbed +workspace_layout tabbed --------------------- === Border style for new windows -This option determines which border style new windows will have: +bn+ for -the normal border (including window title), +bp+ for a 1-pixel border (no -window title) and +bb+ to make the window borderless. +This option determines which border style new windows will have. *Syntax*: --------------------------------------------- -new_window +new_window --------------------------------------------- -*Examples*: +*Example*: --------------------- -new_window bp +new_window 1pixel --------------------- === Variables @@ -352,7 +446,7 @@ variables can be handy. set $name value -------------- -*Examples*: +*Example*: ------------------------ set $m Mod1 bindsym $m+Shift+r restart @@ -362,19 +456,20 @@ 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 which generates a configuration file and run it before starting i3 (for example in your -+.xsession+ file). ++~/.xsession+ file). === Automatically putting clients on specific workspaces [[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. +Specific windows can be matched by window class and/or window title. It is +recommended that you match on window classes instead of window titles whenever +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 @@ -382,36 +477,40 @@ not be put onto any workspace, but will be set floating on the current one. *Syntax*: ------------------------------------------------------------ -assign ["]window class[/window title]["] [→] [~ | workspace] +assign ["]window class[/window title]["] [→] [workspace] ------------------------------------------------------------ *Examples*: ---------------------- assign urxvt 2 assign urxvt → 2 +assign urxvt → work assign "urxvt" → 2 assign "urxvt/VIM" → 3 -assign "gecko" → ~4 -assign "xv/MPlayer" → ~ +assign "gecko" → 4 ---------------------- 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. +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 initial startup (not when restarting i3 -in-place however). These commands will be run in order. +By using the +exec+ keyword outside a keybinding, you can configure +which commands will be performed by i3 on initial startup. +exec+ +commands will not run when restarting i3, if you need a command to run +also when restarting i3 you should use the +exec_always+ +keyword. These commands will be run in order. *Syntax*: ------------- +------------------- exec command ------------- +exec_always command +------------------- *Examples*: -------------------------------- -exec sudo i3status | dzen2 -dock +exec i3status | dzen2 -dock +exec_always ~/my_script.sh -------------------------------- [[workspace_screen]] @@ -439,30 +538,9 @@ workspace 1 output LVDS1 workspace 5 output VGA1 --------------------------- -=== Named workspaces - -If you always have a certain arrangement of workspaces, you might want to give -them names (of course UTF-8 is supported): - -*Syntax*: ---------------------------------------- -workspace -workspace output name ---------------------------------------- - -For more details about the 'output' part of this command, see above. - -*Examples*: --------------------------- -workspace 1 www -workspace 2 work -workspace 3 i ♥ workspaces --------------------------- - === Changing colors -You can change all colors which i3 uses to draw the window decorations and the -bottom bar. +You can change all colors which i3 uses to draw the window decorations. *Syntax*: -------------------------------------------- @@ -520,18 +598,22 @@ 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+. +The IPC socket is enabled by default and will be created in ++/tmp/i3-%u/ipc-socket.%p+ where +%u+ is your UNIX username and +%p+ is the PID +of i3. + +You can override the default path through the environment-variable +I3SOCK+ or +by specifying the +ipc-socket+ directive. *Examples*: ---------------------------- -ipc-socket ~/.i3/ipc.sock +ipc-socket /tmp/i3-ipc.sock ---------------------------- You can then use the +i3-msg+ application to perform any command listed in the next section. -=== Disable focus follows mouse +=== 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 @@ -544,119 +626,146 @@ to click on links in your browser window). focus_follows_mouse ---------------------------- -*Examples*: +*Example*: ---------------------- focus_follows_mouse no ---------------------- -=== Internal workspace bar +=== Popups during fullscreen mode -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 seperate program to -display it (see +i3-wsbar+, a sample implementation of such a program): +When you are in fullscreen mode, some applications still open popup windows +(take Xpdf for example). This is because these applications may not be aware +that they are in fullscreen mode (they do not check the corresponding hint). +There are two things which are possible to do in this situation: + +1. Just ignore the popup (don’t map it). This won’t interrupt you while you are + in fullscreen. However, some apps might react badly to this (deadlock until + you go out of fullscreen). +2. Leave fullscreen mode. This is the default. *Syntax*: ----------------------- -workspace_bar ----------------------- +------------------------------------------------- +popup_during_fullscreen +------------------------------------------------- -*Examples*: ----------------- -workspace_bar no ----------------- +*Example*: +------------------------------ +popup_during_fullscreen ignore +------------------------------ == List of commands +=== Splitting containers + +The split command makes the current window a split container. Split containers +can contain multiple windows. Every split container has an orientation, it is +either split horizontally (a new window gets placed to the right of the current +one) or vertically (a new window gets placed below the current one). + +If you apply this command to a split container with the same orientation, +nothing will happen. If you use a different orientation, the split container’s +orientation will be changed (if it does not have more than one window). + +*Syntax*: +--------------------------- +split +--------------------------- + +*Example*: +-------------- +split vertical +-------------- + === Manipulating layout -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 span all outputs, use +fg+, to make it floating (or -tiling again) use +t+: +Use +layout default+, +layout stacking+ or +layout tabbed+ to change the +current container layout to default, stacking or tabbed layout, respectively. + +To make the current window (!) fullscreen, use +fullscreen+, to make +it floating (or tiling again) use +floating enable+ respectively +floating disable+ +(or +floating toggle+): *Examples*: -------------- -bindsym Mod1+s s -bindsym Mod1+l d -bindsym Mod1+w T +bindsym mod+s layout stacking +bindsym mod+l layout default +bindsym mod+w layout tabbed # Toggle fullscreen -bindsym Mod1+f f - -# Toggle global fullscreen -bindsym Mod1+Shift+f fg +bindsym mod+f fullscreen # Toggle floating/tiling -bindsym Mod1+t t +bindsym mod+t floating toggle -------------- -=== Focusing/Moving/Snapping clients/containers/screens +=== Focusing/Moving containers -To change the focus, use one of the +h+, +j+, +k+ and +l+ commands, meaning -left, down, up, right (respectively). To focus a container, prefix it with -+wc+. To focus a screen, prefix it with +ws+. +To change the focus, use the focus command: +focus left+, +focus right+, +focus down+ and +focus up+. -The same principle applies for moving and snapping: just prefix the command -with +m+ when moving and with +s+ when snapping: +There are a few special parameters you can use for the focus command: + +parent:: + Sets focus to the +Parent Container+ of the current +Container+. +child:: + The opposite of +focus parent+, sets the focus to the last focused + child container. +floating:: + Sets focus to the last focused floating container. +tiling:: + Sets focus to the last focused tiling container. +mode_toggle:: + Toggles between floating/tiling containers. + +For moving, use +move left+, +move right+, +move down+ and +move up+. *Examples*: ---------------------- # Focus clients on the left, bottom, top, right: -bindsym Mod1+j h -bindsym Mod1+k j -bindsym Mod1+j k -bindsym Mod1+semicolon l +bindsym mod+j focus left +bindsym mod+k focus down +bindsym mod+l focus up +bindsym mod+semicolon focus right + +# Focus parent container +bindsym mod+u focus parent + +# Focus last floating/tiling container +bindsym mod+g focus mode_toggle # Move client to the left, bottom, top, right: -bindsym Mod1+j mh -bindsym Mod1+k mj -bindsym Mod1+j mk -bindsym Mod1+semicolon ml - -# Snap client to the left, bottom, top, right: -bindsym Mod1+j sh -bindsym Mod1+k sj -bindsym Mod1+j sk -bindsym Mod1+semicolon sl - -# Focus container on the left, bottom, top, right: -bindsym Mod3+j wch -… +bindsym mod+j move left +bindsym mod+k move down +bindsym mod+l move up +bindsym mod+semicolon move right ---------------------- -=== Changing workspaces/moving clients to workspaces +=== Changing workspaces/moving containers to workspaces -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+. +To change to a specific workspace, use the +workspace+ command, followed by the +number or name of the workspace. To move containers to specific workspaces, use ++move workspace+. -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. +You can also switch to the next and previous workspace with the commands ++workspace next+ and +workspace prev+, 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*: ------------------------- -bindsym Mod1+1 1 -bindsym Mod1+2 2 +bindsym mod+1 workspace 1 +bindsym mod+2 workspace 2 ... -bindsym Mod1+Shift+1 m1 -bindsym Mod1+Shift+2 m2 +bindsym mod+Shift+1 move workspace 1 +bindsym mod+Shift+2 move workspace 2 ... - -bindsym Mod1+o nw -bindsym Mod1+p pw ------------------------- [[resizingconfig]] -=== Resizing columns/rows +=== Resizing containers/windows -If you want to resize columns/rows using your keyboard, you can use the +If you want to resize containers/windows using your keyboard, you can use the +resize+ command, I recommend using it inside a so called +mode+: .Example: Configuration file, defining a mode for resizing @@ -668,23 +777,23 @@ mode "resize" { # when pressing left, the window is resized so that it has # more space on its left - bindsym n resize left -10 - bindsym Shift+n resize left +10 + bindsym j resize shrink left + bindsym Shift+j resize grow left - bindsym r resize bottom +10 - bindsym Shift+r resize bottom -10 + bindsym k resize grow bottom + bindsym Shift+k resize shrink bottom - bindsym t resize top -10 - bindsym Shift+t resize top +10 + bindsym l resize shrink top + bindsym Shift+l resize grow top - bindsym d resize right +10 - bindsym Shift+d resize right -10 + bindsym semicolon resize grow right + bindsym Shift+semicolon resize shrink right - bind 36 mode default + bindcode 36 mode default } # Enter resize mode -bindsym Mod1+r mode resize +bindsym mod+r mode "resize" ---------------------------------------------------------------------- === Jumping to specific windows @@ -693,23 +802,20 @@ 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. +it would be more convenient to have a shortcut. You can use the +focus+ command +with criteria for that. *Syntax*: ---------------------------------------------------- -jump ["]window class[/window title]["] -jump workspace [ column row ] +[class="class"] focus +[title="title"] focus ---------------------------------------------------- -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*: --------------------------------------- +------------------------------------------------ # Get me to the next open VIM instance -bindsym Mod1+a jump "urxvt/VIM" --------------------------------------- +bindsym mod+a [class="urxvt" title="VIM"] focus +------------------------------------------------ === VIM-like marks (mark/goto) @@ -728,68 +834,57 @@ 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*: ------------------ -mark -goto ------------------ +------------------------------ +mark identifier +[con_mark="identifier"] focus +------------------------------ +*Example (in a terminal)*: +------------------------------ +$ i3-msg mark irssi +$ i3-msg '[con_mark="irssi"] focus' +------------------------------ + +/////////////////////////////////////////////////////////////////// +TODO: make i3-input replace %s *Examples*: --------------------------------------- # Read 1 character and mark the current window with this character -bindsym Mod1+m exec i3-input -p 'mark ' -l 1 -P 'Mark: ' +bindsym mod+m exec i3-input -p 'mark ' -l 1 -P 'Mark: ' # Read 1 character and go to the window with the character -bindsym Mod1+g exec i3-input -p 'goto ' -l 1 -P 'Goto: ' +bindsym mod+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 which had focus previously. - -*Syntax*: --------------- -focus [number] | floating | tiling | ft --------------- - -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: - -floating:: - The next floating window is selected. -tiling:: - The next tiling window is selected. -ft:: - 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 is also +bt+ which will toggle -the different border styles. +To change the border of the current client, you can use +border normal+ to use the normal +border (including window title), +border 1pixel+ to use a 1-pixel border (no window title) +and +border none+ to make the client borderless. + +There is also +border toggle+ which will toggle the different border styles. *Examples*: ------------------- -bindsym Mod1+t bn -bindsym Mod1+y bp -bindsym Mod1+u bb ------------------- +---------------------------- +bindsym mod+t border normal +bindsym mod+y border 1pixel +bindsym mod+u border none +---------------------------- [[stack-limit]] +/////////////////////////////////////////////////////////////////////////////// +TODO: not yet implemented === Changing the stack-limit of a container 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 seeing only half of the -titlebars for each window in the container. +Depending on your screen’s size, you might end up with only half of the title +lines being actually used, wasting a lot of screen space. 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 @@ -810,22 +905,21 @@ stack-limit rows 5 ------------------- image:stacklimit.png[Container limited to two columns] +/////////////////////////////////////////////////////////////////////////////// === Reloading/Restarting/Exiting 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 end up in a single container in default layout -after the restart. To exit i3 properly, you can use the +exit+ command, +your X session. 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*: ---------------------------- -bindsym Mod1+Shift+r restart -bindsym Mod1+Shift+w reload -bindsym Mod1+Shift+e exit +bindsym mod+Shift+r restart +bindsym mod+Shift+w reload +bindsym mod+Shift+e exit ---------------------------- [[multi_monitor]] @@ -947,24 +1041,16 @@ 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. +possible to reduce the time your CPU is woken up from sleep states. Because +i3status only spits out text, you need to combine it with some other tool, like +i3bar. Use a pipe to connect them: +i3status | i3bar -d+. -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. +Regardless of which application you use to display the status line, you +want to make sure that it registers as a dock window using EWMH hints. i3 will +position the window either at the top or at the bottom of the screen, depending +on which hint the application sets. With i3bar, you can use +-d+ or +-dbottom+ +for positioning it at the bottom and +-dtop+ to position it at the top of the +screen. === Giving presentations (multi-monitor) diff --git a/dump-asy.pl b/dump-asy.pl new file mode 100755 index 00000000..a8eab04c --- /dev/null +++ b/dump-asy.pl @@ -0,0 +1,45 @@ +#!/usr/bin/env perl +# vim:ts=4:sw=4:expandtab +# renders the layout tree using asymptote + +use strict; +use warnings; +use Data::Dumper; +use AnyEvent::I3; +use File::Temp; +use v5.10; + +my $i3 = i3("/tmp/nestedcons"); + +my $tree = $i3->get_tree->recv; + +my $tmp = File::Temp->new(UNLINK => 0, SUFFIX => '.asy'); + +say $tmp "import drawtree;"; + +say $tmp "treeLevelStep = 2cm;"; + +sub dump_node { + my ($n, $parent) = @_; + + my $o = ($n->{orientation} eq 'none' ? "u" : ($n->{orientation} eq 'horizontal' ? "h" : "v")); + my $w = (defined($n->{window}) ? $n->{window} : "N"); + my $na = $n->{name}; + $na =~ s/#/\\#/g; + my $name = "($na, $o, $w)"; + + print $tmp "TreeNode n" . $n->{id} . " = makeNode("; + + print $tmp "n" . $parent->{id} . ", " if defined($parent); + print $tmp "\"" . $name . "\");\n"; + + dump_node($_, $n) for @{$n->{nodes}}; +} + +dump_node($tree); +say $tmp "draw(n" . $tree->{id} . ", (0, 0));"; + +close($tmp); +my $rep = "$tmp"; +$rep =~ s/asy$/eps/; +system("cd /tmp && asy $tmp && gv $rep && rm $rep"); diff --git a/gtk-tree-watch.pl b/gtk-tree-watch.pl new file mode 100755 index 00000000..f15d0c18 --- /dev/null +++ b/gtk-tree-watch.pl @@ -0,0 +1,219 @@ +#!/usr/bin/env perl +# vim:ts=4:sw=4:expandtab +# renders the layout tree using asymptote + +use strict; +use warnings; + +use JSON::XS; +use Data::Dumper; +use AnyEvent::I3; +use v5.10; + +use Gtk2 '-init'; +use Gtk2::SimpleMenu; +use Glib qw/TRUE FALSE/; + +my $window = Gtk2::Window->new('toplevel'); +$window->signal_connect('delete_event' => sub { Gtk2->main_quit; }); + +my $tree_store = Gtk2::TreeStore->new(qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/, qw/Glib::String/); + +my $i3 = i3("/tmp/nestedcons"); + +my $tree_view = Gtk2::TreeView->new($tree_store); + +my $layout_box = undef; + +sub copy_node { + my ($n, $parent, $piter, $pbox) = @_; + + my $o = ($n->{orientation} == 0 ? "u" : ($n->{orientation} == 1 ? "h" : "v")); + my $w = (defined($n->{window}) ? $n->{window} : "N"); + + # convert a rectangle struct to X11 notation (WxH+X+Y) + my $r = $n->{rect}; + my $x = $r->{x}; + my $y = $r->{y}; + my $dim = $r->{width}."x".$r->{height}.($x<0?$x:"+$x").($y<0?$y:"+$y"); + + # add node to the tree with all known properties + my $iter = $tree_store->append($piter); + $tree_store->set($iter, 0 => $n->{name}, 1 => $w, 2 => $o, 3 => sprintf("0x%08x", $n->{id}), 4 => $n->{urgent}, 5 => $n->{focused}, 6 => $n->{layout}, 7 => $dim); + + # also create a box for the node, each node has a vbox + # for combining the title (and properties) with the + # container itself, the container will be empty in case + # of no children, a vbox or hbox + my $box; + if($n->{orientation} == 1) { + $box = Gtk2::HBox->new(1, 5); + } else { + $box = Gtk2::VBox->new(1, 5); + } + + # combine label and container + my $node = Gtk2::Frame->new($n->{name}.",".$o.",".$w); + $node->set_shadow_type('etched-out'); + $node->add($box); + + # the parent is added onto a scrolled window, so add it with a viewport + if(defined($pbox)) { + $pbox->pack_start($node, 1, 1, 0); + } else { + $layout_box = $node; + } + + # recurse into children + copy_node($_, $n, $iter, $box) for @{$n->{nodes}}; + + # if it is a window draw a nice color + if(defined($n->{window})) { + # use a drawing area to fill a colored rectangle + my $area = Gtk2::DrawingArea->new(); + + # the color is stored as hex in the name + $area->{"user-data"} = $n->{name}; + + $area->signal_connect(expose_event => sub { + my ($widget, $event) = @_; + + # fetch a cairo context and it width/height to start drawing nodes + my $cr = Gtk2::Gdk::Cairo::Context->create($widget->window()); + + my $w = $widget->allocation->width; + my $h = $widget->allocation->height; + + my $hc = $widget->{"user-data"}; + my $r = hex(substr($hc, 1, 2)) / 255.0; + my $g = hex(substr($hc, 3, 2)) / 255.0; + my $b = hex(substr($hc, 5, 2)) / 255.0; + + $cr->set_source_rgb($r, $g, $b); + $cr->rectangle(0, 0, $w, $h); + $cr->fill(); + + return FALSE; + }); + + $box->pack_end($area, 1, 1, 0); + } +} + +# Replaced by Gtk2 Boxes: +#sub draw_node { +# my ($n, $cr, $x, $y, $w, $h) = @_; +# +# $cr->set_source_rgb(1.0, 1.0, 1.0); +# $cr->rectangle($x, $y, $w/2, $h/2); +# $cr->fill(); +#} + +my $json_prev = ""; + +my $layout_sw = Gtk2::ScrolledWindow->new(undef, undef); +my $layout_container = Gtk2::HBox->new(0, 0); +$layout_sw->add_with_viewport($layout_container); + +sub copy_tree { + my $tree = $i3->get_tree->recv; + + # convert the tree back to json so we only rebuild/redraw when the tree is changed + my $json = encode_json($tree); + if ($json ne $json_prev) { + $json_prev = $json; + + # rebuild the tree and the layout + $tree_store->clear(); + if(defined($layout_box)) { + $layout_container->remove($layout_box); + } + copy_node($tree); + $layout_container->add($layout_box); + $layout_container->show_all(); + + # keep things expanded, otherwise the tree collapses every reload which is more annoying then this :-) + $tree_view->expand_all(); + } + + return(TRUE); +} + +sub new_column { + my $tree_column = Gtk2::TreeViewColumn->new(); + $tree_column->set_title(shift); + + my $renderer = Gtk2::CellRendererText->new(); + $tree_column->pack_start($renderer, FALSE); + $tree_column->add_attribute($renderer, text => shift); + + return($tree_column); +} + +my $col = 0; +$tree_view->append_column(new_column("Name", $col++)); +$tree_view->append_column(new_column("Window", $col++)); +$tree_view->append_column(new_column("Orientation", $col++)); +$tree_view->append_column(new_column("ID", $col++)); +$tree_view->append_column(new_column("Urgent", $col++)); +$tree_view->append_column(new_column("Focused", $col++)); +$tree_view->append_column(new_column("Layout", $col++)); +$tree_view->append_column(new_column("Rect", $col++)); + +$tree_view->set_grid_lines("both"); + +my $tree_sw = Gtk2::ScrolledWindow->new(undef, undef); +$tree_sw->add($tree_view); + +# Replaced by Gtk2 Boxes: +#my $area = Gtk2::DrawingArea->new(); +#$area->signal_connect(expose_event => sub { +# my ($widget, $event) = @_; +# +# # fetch a cairo context and it width/height to start drawing nodes +# my $cr = Gtk2::Gdk::Cairo::Context->create($widget->window()); +# +# my $w = $widget->allocation->width; +# my $h = $widget->allocation->height; +# +# draw_node($gtree, $cr, 0, 0, $w, $h); +# +# return FALSE; +#}); + +sub menu_export { + print("TODO: EXPORT\n"); +} + +my $menu_tree = [ + _File => { + item_type => '', + children => [ + _Export => { + callback => \&menu_export, + accelerator => 'E', + }, + _Quit => { + callback => sub { Gtk2->main_quit; }, + accelerator => 'Q', + }, + ], + }, +]; + +my $menu = Gtk2::SimpleMenu->new(menu_tree => $menu_tree); + +my $vbox = Gtk2::VBox->new(0, 0); +$vbox->pack_start($menu->{widget}, 0, 0, 0); +$vbox->pack_end($tree_sw, 1, 1, 0); +$vbox->pack_end($layout_sw, 1, 1, 0); + +$window->add($vbox); +$window->show_all(); +$window->set_size_request(500,500); + +Glib::Timeout->add(1000, "copy_tree", undef, Glib::G_PRIORITY_DEFAULT); +copy_tree(); + +Gtk2->main(); + diff --git a/i3-config-wizard/Makefile b/i3-config-wizard/Makefile new file mode 100644 index 00000000..43c3a1ff --- /dev/null +++ b/i3-config-wizard/Makefile @@ -0,0 +1,42 @@ +# Default value so one can compile i3-input standalone +TOPDIR=.. + +include $(TOPDIR)/common.mk + +# Depend on the object files of all source-files in src/*.c and on all header files +AUTOGENERATED:=cfgparse.tab.c cfgparse.yy.c +FILES:=$(patsubst %.c,%.o,$(filter-out $(AUTOGENERATED),$(wildcard *.c))) +HEADERS:=$(wildcard *.h) + +# Depend on the specific file (.c for each .o) and on all headers +%.o: %.c ${HEADERS} + echo "CC $<" + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< + +all: i3-config-wizard + +i3-config-wizard: cfgparse.y.o cfgparse.yy.o ${FILES} + echo "LINK i3-config-wizard" + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +cfgparse.yy.o: cfgparse.l cfgparse.y.o ${HEADERS} + echo "LEX $<" + flex -i -o$(@:.o=.c) $< + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(@:.o=.c) + +cfgparse.y.o: cfgparse.y ${HEADERS} + echo "YACC $<" + bison --debug --verbose -b $(basename $< .y) -d $< + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(<:.y=.tab.c) + + +install: all + echo "INSTALL" + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -m 0755 i3-config-wizard $(DESTDIR)$(PREFIX)/bin/ + +clean: + rm -f *.o cfgparse.tab.{c,h} cfgparse.output cfgparse.yy.c + +distclean: clean + rm -f i3-config-wizard diff --git a/i3-config-wizard/atoms.xmacro b/i3-config-wizard/atoms.xmacro new file mode 100644 index 00000000..8f94ceaa --- /dev/null +++ b/i3-config-wizard/atoms.xmacro @@ -0,0 +1,6 @@ +xmacro(_NET_WM_NAME) +xmacro(_NET_WM_WINDOW_TYPE) +xmacro(_NET_WM_WINDOW_TYPE_DIALOG) +xmacro(ATOM) +xmacro(CARDINAL) +xmacro(UTF8_STRING) diff --git a/i3-config-wizard/cfgparse.l b/i3-config-wizard/cfgparse.l new file mode 100644 index 00000000..772b8470 --- /dev/null +++ b/i3-config-wizard/cfgparse.l @@ -0,0 +1,105 @@ +%option nounput +%option noinput +%option noyy_top_state +%option stack + +%{ +/* + * vim:ts=8:expandtab + * + */ +#include +#include +#include +#include +#include "cfgparse.tab.h" + +int yycolumn = 1; + +struct context { + int line_number; + char *line_copy; + + char *compact_error; + + /* These are the same as in YYLTYPE */ + int first_column; + int last_column; +}; + + +#define YY_DECL int yylex (struct context *context) + +#define YY_USER_ACTION { \ + context->first_column = yycolumn; \ + context->last_column = yycolumn+yyleng-1; \ + yycolumn += yyleng; \ +} + +%} + +EOL (\r?\n) + +%s BINDCODE_COND +%s BIND_AWS_COND +%s BIND_A2WS_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; } +[0-9]+ { yylval.number = atoi(yytext); return NUMBER; } +bind { BEGIN(BINDCODE_COND); return TOKBINDCODE; } +bindcode { BEGIN(BINDCODE_COND); return TOKBINDCODE; } +Mod1 { yylval.number = (1 << 3); return MODIFIER; } +Mod2 { yylval.number = (1 << 4); return MODIFIER; } +Mod3 { yylval.number = (1 << 5); return MODIFIER; } +Mod4 { yylval.number = (1 << 6); return MODIFIER; } +Mod5 { yylval.number = (1 << 7); return MODIFIER; } +Mode_switch { yylval.number = (1 << 8); return MODIFIER; } +$mod { yylval.number = (1 << 9); return TOKMODVAR; } +control { return TOKCONTROL; } +ctrl { return TOKCONTROL; } +shift { return TOKSHIFT; } +{EOL} { + if (context->line_copy) { + free(context->line_copy); + context->line_copy = NULL; + } + context->line_number++; + BEGIN(INITIAL); + yy_push_state(BUFFER_LINE); + } +[ \t]+ { BEGIN(BIND_AWS_COND); return WHITESPACE; } +[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; } +[ \t]+ { return WHITESPACE; } +. { return (int)yytext[0]; } + +<> { + while (yy_start_stack_ptr > 0) + yy_pop_state(); + yyterminate(); +} + +%% diff --git a/i3-config-wizard/cfgparse.y b/i3-config-wizard/cfgparse.y new file mode 100644 index 00000000..018b37b2 --- /dev/null +++ b/i3-config-wizard/cfgparse.y @@ -0,0 +1,161 @@ +%{ +/* + * vim:ts=4:sw=4:expandtab + * + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +extern Display *dpy; + +struct context { + int line_number; + char *line_copy; + + char *compact_error; + + /* These are the same as in YYLTYPE */ + int first_column; + int last_column; + + char *result; +}; + +typedef struct yy_buffer_state *YY_BUFFER_STATE; +extern int yylex(struct context *context); +extern int yyparse(void); +extern FILE *yyin; +YY_BUFFER_STATE yy_scan_string(const char *); + +static struct context *context; + +/* 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 *error_message) { + fprintf(stderr, "\n"); + fprintf(stderr, "CONFIG: %s\n", error_message); + fprintf(stderr, "CONFIG: line %d:\n", + context->line_number); + fprintf(stderr, "CONFIG: %s\n", context->line_copy); + fprintf(stderr, "CONFIG: "); + for (int c = 1; c <= context->last_column; c++) + if (c >= context->first_column) + fprintf(stderr, "^"); + else fprintf(stderr, " "); + fprintf(stderr, "\n"); + fprintf(stderr, "\n"); +} + +int yywrap() { + return 1; +} + +char *rewrite_binding(const char *bindingline) { + char *result = NULL; + + context = calloc(sizeof(struct context), 1); + + yy_scan_string(bindingline); + + if (yyparse() != 0) { + fprintf(stderr, "Could not parse configfile\n"); + exit(1); + } + + result = context->result; + + if (context->line_copy) + free(context->line_copy); + free(context); + + return result; +} + +/* XXX: does not work for combinations of modifiers yet */ +static char *modifier_to_string(int modifiers) { + //printf("should convert %d to string\n", modifiers); + if (modifiers == (1 << 3)) + return strdup("$mod+"); + else if (modifiers == ((1 << 3) | (1 << 0))) + return strdup("$mod+Shift+"); + else if (modifiers == (1 << 9)) + return strdup("$mod+"); + else if (modifiers == ((1 << 9) | (1 << 0))) + return strdup("$mod+Shift+"); + else if (modifiers == (1 << 0)) + return strdup("Shift+"); + else return strdup(""); +} + +%} + +%error-verbose +%lex-param { struct context *context } + +%union { + int number; + char *string; +} + +%token NUMBER "" +%token STR "" +%token TOKBINDCODE +%token TOKMODVAR "$mod" +%token MODIFIER "" +%token TOKCONTROL "control" +%token TOKSHIFT "shift" +%token WHITESPACE "" + +%% + +lines: /* empty */ + | lines WHITESPACE bindcode + | lines error + | lines bindcode + ; + +bindcode: + TOKBINDCODE WHITESPACE binding_modifiers NUMBER WHITESPACE STR + { + //printf("\tFound keycode binding mod%d with key %d and command %s\n", $3, $4, $6); + int level = 0; + if (($3 & (1 << 0))) { + /* When shift is included, we really need to use the second-level + * symbol (upper-case). The lower-case symbol could be on a + * different key than the upper-case one (unlikely for letters, but + * more likely for special characters). */ + level = 1; + } + KeySym sym = XKeycodeToKeysym(dpy, $4, level); + char *str = XKeysymToString(sym); + char *modifiers = modifier_to_string($3); + // TODO: modifier to string + asprintf(&(context->result), "bindsym %s%s %s\n", modifiers, str, $6); + free(modifiers); + } + ; + +binding_modifiers: + /* NULL */ { $$ = 0; } + | binding_modifier + | binding_modifiers '+' binding_modifier { $$ = $1 | $3; } + | binding_modifiers '+' { $$ = $1; } + ; + +binding_modifier: + MODIFIER { $$ = $1; } + | TOKMODVAR { $$ = $1; } + | TOKCONTROL { $$ = (1 << 2); } + | TOKSHIFT { $$ = (1 << 0); } + ; diff --git a/i3-config-wizard/ipc.c b/i3-config-wizard/ipc.c new file mode 100644 index 00000000..597a86ef --- /dev/null +++ b/i3-config-wizard/ipc.c @@ -0,0 +1,68 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ +#include +#include +#include +#include +#include +#include +#include + +/* + * Formats a message (payload) of the given size and type and sends it to i3 via + * the given socket file descriptor. + * + */ +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; + 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(sockfd, msg + sent_bytes, bytes_to_go); + if (n == -1) + err(EXIT_FAILURE, "write() failed"); + + sent_bytes += n; + bytes_to_go -= n; + } +} + +/* + * Connects to the i3 IPC socket and returns the file descriptor for the + * socket. die()s if anything goes wrong. + * + */ +int connect_ipc(char *socket_path) { + int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (sockfd == -1) + err(EXIT_FAILURE, "Could not create socket"); + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_LOCAL; + strcpy(addr.sun_path, socket_path); + if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) + err(EXIT_FAILURE, "Could not connect to i3"); + + return sockfd; +} diff --git a/i3-config-wizard/ipc.h b/i3-config-wizard/ipc.h new file mode 100644 index 00000000..c40c909d --- /dev/null +++ b/i3-config-wizard/ipc.h @@ -0,0 +1,9 @@ +#ifndef _IPC_H +#define _IPC_H + +void ipc_send_message(int sockfd, uint32_t message_size, + uint32_t message_type, uint8_t *payload); + +int connect_ipc(char *socket_path); + +#endif diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c new file mode 100644 index 00000000..d3a26721 --- /dev/null +++ b/i3-config-wizard/main.c @@ -0,0 +1,546 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2011 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + * i3-config-wizard: Program to convert configs using keycodes to configs using + * keysyms. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +/* We need SYSCONFDIR for the path to the keycode config template, so raise an + * error if it’s not defined for whatever reason */ +#ifndef SYSCONFDIR +#error "SYSCONFDIR not defined" +#endif + +#define FREE(pointer) do { \ + if (pointer != NULL) { \ + free(pointer); \ + pointer = NULL; \ + } \ +} \ +while (0) + +#include "xcb.h" +#include "ipc.h" + +enum { STEP_WELCOME, STEP_GENERATE } current_step = STEP_WELCOME; +enum { MOD_ALT, MOD_SUPER } modifier = MOD_SUPER; + +static char *config_path; +static xcb_connection_t *conn; +static uint32_t font_id; +static uint32_t font_bold_id; +static char *socket_path; +static int font_height; +static int font_bold_height; +static xcb_window_t win; +static xcb_pixmap_t pixmap; +static xcb_gcontext_t pixmap_gc; +static xcb_key_symbols_t *symbols; +xcb_window_t root; +Display *dpy; + +char *rewrite_binding(const char *bindingline); +static void finish(); + +#if defined(__APPLE__) + +/* + * Taken from FreeBSD + * Returns a pointer to a new string which is a duplicate of the + * string, but only copies at most n characters. + * + */ +char *strndup(const char *str, size_t n) { + size_t len; + char *copy; + + for (len = 0; len < n && str[len]; len++) + continue; + + if ((copy = malloc(len + 1)) == NULL) + return (NULL); + memcpy(copy, str, len); + copy[len] = '\0'; + return (copy); +} + +#endif + +/* + * This function resolves ~ in pathnames. + * It may resolve wildcards in the first part of the path, but if no match + * or multiple matches are found, it just returns a copy of path as given. + * + */ +static char *resolve_tilde(const char *path) { + static glob_t globbuf; + char *head, *tail, *result; + + tail = strchr(path, '/'); + head = strndup(path, tail ? tail - path : strlen(path)); + + int res = glob(head, GLOB_TILDE, NULL, &globbuf); + free(head); + /* no match, or many wildcard matches are bad */ + if (res == GLOB_NOMATCH || globbuf.gl_pathc != 1) + result = strdup(path); + else if (res != 0) { + err(1, "glob() failed"); + } else { + head = globbuf.gl_pathv[0]; + result = calloc(1, strlen(head) + (tail ? strlen(tail) : 0) + 1); + strncpy(result, head, strlen(head)); + if (tail) + strncat(result, tail, strlen(tail)); + } + globfree(&globbuf); + + return result; +} + +/* + * Try to get the socket path from X11 and return NULL if it doesn’t work. + * As i3-msg is a short-running tool, we don’t bother with cleaning up the + * connection and leave it up to the operating system on exit. + * + */ +static char *socket_path_from_x11() { + xcb_connection_t *conn; + int screen; + if ((conn = xcb_connect(NULL, &screen)) == NULL || + xcb_connection_has_error(conn)) + return NULL; + xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen); + xcb_window_t root = root_screen->root; + + xcb_intern_atom_cookie_t atom_cookie; + xcb_intern_atom_reply_t *atom_reply; + + atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH"); + atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL); + if (atom_reply == NULL) + return NULL; + + xcb_get_property_cookie_t prop_cookie; + xcb_get_property_reply_t *prop_reply; + prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom, + XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX); + prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); + if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0) + return NULL; + if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply), + (char*)xcb_get_property_value(prop_reply)) == -1) + return NULL; + return socket_path; +} + +/* + * Handles expose events, that is, draws the window contents. + * + */ +static int handle_expose() { + /* re-draw the background */ + xcb_rectangle_t border = {0, 0, 300, (15*font_height) + 8}; + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); + xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border); + + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id); + +#define txt(x, row, text) xcb_image_text_8(conn, strlen(text), pixmap, pixmap_gc, x, (row * font_height) + 2, text) + + if (current_step == STEP_WELCOME) { + /* restore font color */ + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF")); + + txt(10, 2, "You have not configured i3 yet."); + txt(10, 3, "Do you want me to generate ~/.i3/config?"); + txt(85, 5, "Yes, generate ~/.i3/config"); + txt(85, 7, "No, I will use the defaults"); + + /* green */ + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#00FF00")); + txt(25, 5, ""); + + /* red */ + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000")); + txt(31, 7, ""); + } + + if (current_step == STEP_GENERATE) { + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF")); + + txt(10, 2, "Please choose either:"); + txt(85, 4, "Win as default modifier"); + txt(85, 5, "Alt as default modifier"); + txt(10, 7, "Afterwards, press"); + txt(85, 9, "to write ~/.i3/config"); + txt(85, 10, "to abort"); + + /* the not-selected modifier */ + if (modifier == MOD_SUPER) + txt(31, 5, ""); + else txt(31, 4, ""); + + /* the selected modifier */ + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_bold_id); + if (modifier == MOD_SUPER) + txt(31, 4, ""); + else txt(31, 5, ""); + + /* green */ + uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_FONT; + uint32_t values[] = { get_colorpixel(conn, "#00FF00"), font_id }; + xcb_change_gc(conn, pixmap_gc, mask, values); + + txt(25, 9, ""); + + /* red */ + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000")); + txt(31, 10, ""); + } + + /* Copy the contents of the pixmap to the real window */ + xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, /* */ 500, 500); + xcb_flush(conn); + + return 1; +} + +static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) { + printf("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); + /* Only use the lower 8 bits of the state (modifier masks) so that mouse + * button masks are filtered out */ + state_filtered &= 0xFF; + + xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, state_filtered); + + printf("sym = %c (%d)\n", sym, sym); + + if (sym == XK_Return || sym == XK_KP_Enter) { + if (current_step == STEP_WELCOME) { + current_step = STEP_GENERATE; + /* Set window title */ + xcb_change_property(conn, + XCB_PROP_MODE_REPLACE, + win, + A__NET_WM_NAME, + A_UTF8_STRING, + 8, + strlen("i3: generate config"), + "i3: generate config"); + xcb_flush(conn); + } + else finish(); + } + + /* cancel any time */ + if (sym == XK_Escape) + exit(0); + + if (sym == XK_Alt_L) + modifier = MOD_ALT; + + if (sym == XK_Super_L) + modifier = MOD_SUPER; + + handle_expose(); + return 1; +} + +/* + * Creates the config file and tells i3 to reload. + * + */ +static void finish() { + printf("creating \"%s\"...\n", config_path); + + if (!(dpy = XOpenDisplay(NULL))) + errx(1, "Could not connect to X11"); + + FILE *kc_config = fopen(SYSCONFDIR "/i3/config.keycodes", "r"); + if (kc_config == NULL) + err(1, "Could not open input file \"%s\"", SYSCONFDIR "/i3/config.keycodes"); + + FILE *ks_config = fopen(config_path, "w"); + if (ks_config == NULL) + err(1, "Could not open output config file \"%s\"", config_path); + + char *line = NULL; + size_t len = 0; +#if !defined(__APPLE__) + ssize_t read; +#endif + bool head_of_file = true; + + /* write a header about auto-generation to the output file */ + fputs("# This file has been auto-generated by i3-config-wizard(1).\n", ks_config); + fputs("# It will not be overwritten, so edit it as you like.\n", ks_config); + fputs("#\n", ks_config); + fputs("# Should you change your keyboard layout somewhen, delete\n", ks_config); + fputs("# this file and re-run i3-config-wizard(1).\n", ks_config); + fputs("#\n", ks_config); + +#if defined(__APPLE__) + while ((line = fgetln(kc_config, &len)) != NULL) { +#else + while ((read = getline(&line, &len, kc_config)) != -1) { +#endif + /* skip the warning block at the beginning of the input file */ + if (head_of_file && + strncmp("# WARNING", line, strlen("# WARNING")) == 0) + continue; + + head_of_file = false; + + /* Skip leading whitespace */ + char *walk = line; + while (isspace(*walk) && walk < (line + len)) + walk++; + + /* Set the modifier the user chose */ + if (strncmp(walk, "set $mod ", strlen("set $mod ")) == 0) { + if (modifier == MOD_ALT) + fputs("set $mod Mod1\n", ks_config); + else fputs("set $mod Mod4\n", ks_config); + continue; + } + + /* Check for 'bindcode'. If it’s not a bindcode line, we + * just copy it to the output file */ + if (strncmp(walk, "bindcode", strlen("bindcode")) != 0) { + fputs(line, ks_config); + continue; + } + char *result = rewrite_binding(walk); + fputs(result, ks_config); + free(result); + } + + /* sync to do our best in order to have the file really stored on disk */ + fflush(ks_config); + fsync(fileno(ks_config)); + + free(line); + fclose(kc_config); + fclose(ks_config); + + /* tell i3 to reload the config file */ + int sockfd = connect_ipc(socket_path); + ipc_send_message(sockfd, strlen("reload"), 0, (uint8_t*)"reload"); + close(sockfd); + + exit(0); +} + +int main(int argc, char *argv[]) { + config_path = resolve_tilde("~/.i3/config"); + socket_path = getenv("I3SOCK"); + char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; + char *patternbold = "-misc-fixed-bold-r-normal--13-120-75-75-C-70-iso10646-1"; + int o, option_index = 0; + + static struct option long_options[] = { + {"socket", required_argument, 0, 's'}, + {"version", no_argument, 0, 'v'}, + {"limit", required_argument, 0, 'l'}, + {"prompt", required_argument, 0, 'P'}, + {"prefix", required_argument, 0, 'p'}, + {"font", required_argument, 0, 'f'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + char *options_string = "s:vh"; + + while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { + switch (o) { + case 's': + FREE(socket_path); + socket_path = strdup(optarg); + break; + case 'v': + printf("i3-config-wizard " I3_VERSION "\n"); + return 0; + case 'h': + printf("i3-config-wizard " I3_VERSION "\n"); + printf("i3-config-wizard [-s ] [-v]\n"); + return 0; + } + } + + /* Check if the destination config file does not exist but the path is + * writable. If not, exit now, this program is not useful in that case. */ + struct stat stbuf; + if (stat(config_path, &stbuf) == 0) { + printf("The config file \"%s\" already exists. Exiting.\n", config_path); + return 0; + } + + /* Create ~/.i3 if it does not yet exist */ + char *config_dir = resolve_tilde("~/.i3"); + if (stat(config_dir, &stbuf) != 0) + if (mkdir(config_dir, 0755) == -1) + err(1, "mkdir(%s) failed", config_dir); + free(config_dir); + + int fd; + if ((fd = open(config_path, O_CREAT | O_RDWR, 0644)) == -1) { + printf("Cannot open file \"%s\" for writing: %s. Exiting.\n", config_path, strerror(errno)); + return 0; + } + close(fd); + unlink(config_path); + + if (socket_path == NULL) + socket_path = socket_path_from_x11(); + + if (socket_path == NULL) + socket_path = "/tmp/i3-ipc.sock"; + + int screens; + if ((conn = xcb_connect(NULL, &screens)) == NULL || + xcb_connection_has_error(conn)) + errx(1, "Cannot open display\n"); + + /* Place requests for the atoms we need as soon as possible */ + #define xmacro(atom) \ + xcb_intern_atom_cookie_t atom ## _cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom); + #include "atoms.xmacro" + #undef xmacro + + xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens); + root = root_screen->root; + + xcb_get_numlock_mask(conn); + + symbols = xcb_key_symbols_alloc(conn); + + font_id = get_font_id(conn, pattern, &font_height); + font_bold_id = get_font_id(conn, patternbold, &font_bold_height); + + /* Open an input window */ + win = open_input_window(conn, 300, 205); + + /* Setup NetWM atoms */ + #define xmacro(name) \ + do { \ + xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, name ## _cookie, NULL); \ + if (!reply) \ + errx(EXIT_FAILURE, "Could not get atom " # name "\n"); \ + \ + A_ ## name = reply->atom; \ + free(reply); \ + } while (0); + #include "atoms.xmacro" + #undef xmacro + + /* Set dock mode */ + xcb_change_property(conn, + XCB_PROP_MODE_REPLACE, + win, + A__NET_WM_WINDOW_TYPE, + A_ATOM, + 32, + 1, + (unsigned char*) &A__NET_WM_WINDOW_TYPE_DIALOG); + + /* Set window title */ + xcb_change_property(conn, + XCB_PROP_MODE_REPLACE, + win, + A__NET_WM_NAME, + A_UTF8_STRING, + 8, + strlen("i3: first configuration"), + "i3: first configuration"); + + /* Create pixmap */ + pixmap = xcb_generate_id(conn); + pixmap_gc = xcb_generate_id(conn); + xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, 500); + xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); + + /* Grab the keyboard to get all input */ + 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); + + xcb_generic_event_t *event; + while ((event = xcb_wait_for_event(conn)) != NULL) { + if (event->response_type == 0) { + fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence); + continue; + } + + /* Strip off the highest bit (set if the event is generated) */ + int type = (event->response_type & 0x7F); + + switch (type) { + case XCB_KEY_PRESS: + handle_key_press(NULL, conn, (xcb_key_press_event_t*)event); + break; + + /* TODO: handle mappingnotify */ + + case XCB_EXPOSE: + handle_expose(); + break; + } + + free(event); + } + + return 0; +} diff --git a/i3-config-wizard/xcb.c b/i3-config-wizard/xcb.c new file mode 100644 index 00000000..d1753d18 --- /dev/null +++ b/i3-config-wizard/xcb.c @@ -0,0 +1,221 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "xcb.h" + +extern xcb_window_t root; +unsigned int xcb_numlock_mask; + +/* + * Convenience-wrapper around xcb_change_gc which saves us declaring a variable + * + */ +void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value) { + xcb_change_gc(conn, gc, mask, &value); +} + +/* + * Returns the colorpixel to use for the given hex color (think of HTML). + * + * The hex_color has to start with #, for example #FF00FF. + * + * NOTE that get_colorpixel() does _NOT_ check the given color code for validity. + * This has to be done by the caller. + * + */ +uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) { + char strgroups[3][3] = {{hex[1], hex[2], '\0'}, + {hex[3], hex[4], '\0'}, + {hex[5], hex[6], '\0'}}; + uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)), + (strtol(strgroups[1], NULL, 16)), + (strtol(strgroups[2], NULL, 16))}; + + return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2]; +} + +/* + * Returns the mask for Mode_switch (to be used for looking up keysymbols by + * keycode). + * + */ +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, keycode); + if (modeswitchcodes == NULL) + return 0; + + modmap_r = xcb_get_modifier_mapping_reply(conn, xcb_get_modifier_mapping(conn), NULL); + 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++) { + kc = modmap[i * modmap_r->keycodes_per_modifier + j]; + for (xcb_keycode_t *ktest = modeswitchcodes; *ktest; ktest++) { + if (*ktest != kc) + continue; + + free(modeswitchcodes); + free(modmap_r); + return (1 << i); + } + } + + return 0; +} + +/* + * Opens the window we use for input/output and maps it + * + */ +xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height) { + xcb_window_t win = xcb_generate_id(conn); + //xcb_cursor_t cursor_id = xcb_generate_id(conn); + +#if 0 + /* 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); + } +#endif + + uint32_t mask = 0; + uint32_t values[3]; + + mask |= XCB_CW_BACK_PIXEL; + values[0] = 0; + + mask |= XCB_CW_EVENT_MASK; + values[1] = XCB_EVENT_MASK_EXPOSURE; + + xcb_create_window(conn, + XCB_COPY_FROM_PARENT, + win, /* the window id */ + root, /* parent == root */ + 490, 297, 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); + +#if 0 + if (cursor > -1) + xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id); +#endif + + /* Map the window (= make it visible) */ + xcb_map_window(conn, win); + + return win; +} + +/* + * Returns the ID of the font matching the given pattern and stores the height + * of the font (in pixels) in *font_height. die()s if no font matches. + * + */ +int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height) { + xcb_void_cookie_t font_cookie; + xcb_list_fonts_with_info_cookie_t info_cookie; + + /* Send all our requests first */ + int result; + result = xcb_generate_id(conn); + font_cookie = xcb_open_font_checked(conn, result, strlen(pattern), pattern); + info_cookie = xcb_list_fonts_with_info(conn, 1, strlen(pattern), pattern); + + xcb_generic_error_t *error = xcb_request_check(conn, font_cookie); + if (error != NULL) { + fprintf(stderr, "ERROR: Could not open font: %d\n", error->error_code); + exit(1); + } + + /* Get information (height/name) for this font */ + xcb_list_fonts_with_info_reply_t *reply = xcb_list_fonts_with_info_reply(conn, info_cookie, NULL); + if (reply == NULL) + errx(1, "Could not load font \"%s\"\n", pattern); + + *font_height = reply->font_ascent + reply->font_descent; + + return result; +} + +/* + * Finds out which modifier mask is the one for numlock, as the user may change this. + * + */ +void xcb_get_numlock_mask(xcb_connection_t *conn) { + xcb_key_symbols_t *keysyms; + xcb_get_modifier_mapping_cookie_t cookie; + xcb_get_modifier_mapping_reply_t *reply; + xcb_keycode_t *modmap; + int mask, i; + const int masks[8] = { XCB_MOD_MASK_SHIFT, + XCB_MOD_MASK_LOCK, + XCB_MOD_MASK_CONTROL, + XCB_MOD_MASK_1, + XCB_MOD_MASK_2, + XCB_MOD_MASK_3, + XCB_MOD_MASK_4, + XCB_MOD_MASK_5 }; + + /* Request the modifier map */ + cookie = xcb_get_modifier_mapping_unchecked(conn); + + /* Get the keysymbols */ + keysyms = xcb_key_symbols_alloc(conn); + + if ((reply = xcb_get_modifier_mapping_reply(conn, cookie, NULL)) == NULL) { + xcb_key_symbols_free(keysyms); + return; + } + + modmap = xcb_get_modifier_mapping_keycodes(reply); + + /* Get the keycode for numlock */ +#ifdef OLD_XCB_KEYSYMS_API + xcb_keycode_t numlock = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK); +#else + /* For now, we only use the first keysymbol. */ + xcb_keycode_t *numlock_syms = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK); + if (numlock_syms == NULL) + return; + xcb_keycode_t numlock = *numlock_syms; + free(numlock_syms); +#endif + + /* Check all modifiers (Mod1-Mod5, Shift, Control, Lock) */ + for (mask = 0; mask < 8; mask++) + for (i = 0; i < reply->keycodes_per_modifier; i++) + if (modmap[(mask * reply->keycodes_per_modifier) + i] == numlock) + xcb_numlock_mask = masks[mask]; + + xcb_key_symbols_free(keysyms); + free(reply); +} + diff --git a/i3-config-wizard/xcb.h b/i3-config-wizard/xcb.h new file mode 100644 index 00000000..40aeec88 --- /dev/null +++ b/i3-config-wizard/xcb.h @@ -0,0 +1,24 @@ +#ifndef _XCB_H +#define _XCB_H + +/* from X11/keysymdef.h */ +#define XCB_NUM_LOCK 0xff7f + +#define xmacro(atom) xcb_atom_t A_ ## atom; +#include "atoms.xmacro" +#undef xmacro + +extern unsigned int xcb_numlock_mask; + +void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value); +uint32_t get_colorpixel(xcb_connection_t *conn, char *hex); +uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode); +xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height); +int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height); +/** + * Finds out which modifier mask is the one for numlock, as the user may change this. + * + */ +void xcb_get_numlock_mask(xcb_connection_t *conn); + +#endif diff --git a/i3-input/Makefile b/i3-input/Makefile index 74f3f8da..45653dad 100644 --- a/i3-input/Makefile +++ b/i3-input/Makefile @@ -10,11 +10,13 @@ HEADERS=$(wildcard *.h) # Depend on the specific file (.c for each .o) and on all headers %.o: %.c ${HEADERS} echo "CC $<" - $(CC) $(CFLAGS) -c -o $@ $< + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< -all: ${FILES} +all: i3-input + +i3-input: ${FILES} echo "LINK i3-input" - $(CC) -o i3-input ${FILES} $(LDFLAGS) + $(CC) $(LDFLAGS) -o $@ ${FILES} $(LIBS) install: all echo "INSTALL" diff --git a/i3-input/i3-input.h b/i3-input/i3-input.h index 8d8b467f..581203d4 100644 --- a/i3-input/i3-input.h +++ b/i3-input/i3-input.h @@ -4,6 +4,15 @@ #include #define die(...) errx(EXIT_FAILURE, __VA_ARGS__); +#define FREE(pointer) do { \ + if (pointer != NULL) { \ + free(pointer); \ + pointer = NULL; \ + } \ +} \ +while (0) + +extern xcb_window_t root; char *convert_ucs_to_utf8(char *input); char *convert_utf8_to_ucs2(char *input, int *real_strlen); diff --git a/i3-input/main.c b/i3-input/main.c index 581386d2..fb2635a2 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009-2010 Michael Stapelberg and contributors + * © 2009 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -22,7 +22,7 @@ #include #include #include -#include +#include #include #include @@ -35,6 +35,7 @@ #include "i3-input.h" +static char *socket_path; static int sockfd; static xcb_key_symbols_t *symbols; static int modeswitchmask; @@ -51,20 +52,42 @@ static char *command_prefix; static char *prompt; static int prompt_len; static int limit; +xcb_window_t root; /* - * This function resolves ~ in pathnames (and more, see glob(3)). + * Try to get the socket path from X11 and return NULL if it doesn’t work. + * As i3-msg is a short-running tool, we don’t bother with cleaning up the + * connection and leave it up to the operating system on exit. * */ -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; +static char *socket_path_from_x11() { + xcb_connection_t *conn; + int screen; + if ((conn = xcb_connect(NULL, &screen)) == NULL || + xcb_connection_has_error(conn)) + return NULL; + xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen); + xcb_window_t root = root_screen->root; + + xcb_intern_atom_cookie_t atom_cookie; + xcb_intern_atom_reply_t *atom_reply; + + atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH"); + atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL); + if (atom_reply == NULL) + return NULL; + + xcb_get_property_cookie_t prop_cookie; + xcb_get_property_reply_t *prop_reply; + prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom, + XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX); + prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); + if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0) + return NULL; + if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply), + (char*)xcb_get_property_value(prop_reply)) == -1) + return NULL; + return socket_path; } /* @@ -257,7 +280,7 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press } int main(int argc, char *argv[]) { - char *socket_path = glob_path("~/.i3/ipc.sock"); + socket_path = getenv("I3SOCK"); char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; int o, option_index = 0; @@ -277,21 +300,25 @@ 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 = glob_path(optarg); + FREE(socket_path); + socket_path = strdup(optarg); break; case 'v': printf("i3-input " I3_VERSION); return 0; case 'p': + FREE(command_prefix); command_prefix = strdup(optarg); break; case 'l': limit = atoi(optarg); break; case 'P': + FREE(prompt); prompt = strdup(optarg); break; case 'f': + FREE(pattern); pattern = strdup(optarg); break; case 'h': @@ -301,6 +328,12 @@ int main(int argc, char *argv[]) { } } + if (socket_path == NULL) + socket_path = socket_path_from_x11(); + + if (socket_path == NULL) + socket_path = "/tmp/i3-ipc.sock"; + sockfd = connect_ipc(socket_path); if (prompt != NULL) @@ -311,13 +344,8 @@ int main(int argc, char *argv[]) { if (xcb_connection_has_error(conn)) die("Cannot open display\n"); - /* Set up event handlers for key press and key release */ - xcb_event_handlers_t evenths; - memset(&evenths, 0, sizeof(xcb_event_handlers_t)); - xcb_event_handlers_init(conn, &evenths); - xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL); - xcb_event_set_key_release_handler(&evenths, handle_key_release, NULL); - xcb_event_set_expose_handler(&evenths, handle_expose, NULL); + xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens); + root = root_screen->root; modeswitchmask = get_mod_mask(conn, XK_Mode_switch); numlockmask = get_mod_mask(conn, XK_Num_Lock); @@ -329,8 +357,6 @@ int main(int argc, char *argv[]) { win = open_input_window(conn, 500, font_height + 8); /* Create pixmap */ - xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens); - pixmap = xcb_generate_id(conn); pixmap_gc = xcb_generate_id(conn); xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font_height + 8); @@ -366,7 +392,32 @@ int main(int argc, char *argv[]) { xcb_flush(conn); - xcb_event_wait_for_event_loop(&evenths); + xcb_generic_event_t *event; + while ((event = xcb_wait_for_event(conn)) != NULL) { + if (event->response_type == 0) { + fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence); + continue; + } + + /* Strip off the highest bit (set if the event is generated) */ + int type = (event->response_type & 0x7F); + + switch (type) { + case XCB_KEY_PRESS: + handle_key_press(NULL, conn, (xcb_key_press_event_t*)event); + break; + + case XCB_KEY_RELEASE: + handle_key_release(NULL, conn, (xcb_key_release_event_t*)event); + break; + + case XCB_EXPOSE: + handle_expose(NULL, conn, (xcb_expose_event_t*)event); + break; + } + + free(event); + } return 0; } diff --git a/i3-input/xcb.c b/i3-input/xcb.c index 661d4863..3c1d99e1 100644 --- a/i3-input/xcb.c +++ b/i3-input/xcb.c @@ -86,7 +86,6 @@ uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode) { * */ xcb_window_t open_input_window(xcb_connection_t *conn, 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); //xcb_cursor_t cursor_id = xcb_generate_id(conn); diff --git a/i3-migrate-config-to-v4.pl b/i3-migrate-config-to-v4.pl new file mode 100755 index 00000000..5f20cba1 --- /dev/null +++ b/i3-migrate-config-to-v4.pl @@ -0,0 +1,358 @@ +#!/usr/bin/env perl +# vim:ts=4:sw=4:expandtab +# +# script to migrate an old config file (i3 < 4.0) to the new format (>= 4.0) +# this script only uses modules which come with perl 5.10 +# +# reads an i3 v3 config from stdin and spits out a v4 config on stdout +# exit codes: +# 0 = the input was a v3 config +# 1 = the input was already a v4 config +# (the config is printed to stdout nevertheless) +# +# © 2011 Michael Stapelberg and contributors, see LICENSE + +use strict; +use warnings; +use Getopt::Long; +use v5.10; + +# is this a version 3 config file? disables auto-detection +my $v3 = 0; +my $result = GetOptions('v3' => \$v3); + +# reads stdin +sub slurp { + local $/; + <>; +} + +my @unchanged = qw( + font + set + mode + exec + assign + floating_modifier + focus_follows_mouse + ipc-socket + ipc_socket + client.focused + client.focused_inactive + client.unfocused + client.urgent + client.background +); + +my %workspace_names; +my $workspace_bar = 1; + +my $input = slurp(); +my @lines = split /\n/, $input; + +# remove whitespaces in the beginning of lines +@lines = map { s/^[ \t]*//g; $_ } @lines; + +# Try to auto-detect if this is a v3 config file. +sub need_to_convert { + # If the user passed --v3, we need to convert in any case + return 1 if $v3; + + for my $line (@lines) { + # only v4 configfiles can use bindcode or workspace_layout + return 0 if $line =~ /^bindcode/ || + $line =~ /^workspace_layout/ || + $line =~ /^force_focus_wrapping/; + + # have a look at bindings + next unless $line =~ /^bind/; + + my ($statement, $key, $command) = ($line =~ /([a-zA-Z_-]+)[ \t]+([^ \t]+)[ \t]+(.*)/); + return 0 if $command =~ /^layout/ || + $command =~ /^floating/ || + $command =~ /^workspace/ || + $command =~ /^focus (left|right|up|down)/ || + $command =~ /^border (normal|1pixel|none)/; + } + + return 1; +} + +if (!need_to_convert()) { + # If this is already a v4 config file, we will spit out the lines + # and exit with return code 1 + print $input; + exit 1; +} + +# first pass: get workspace names +for my $line (@lines) { + next if $line =~ /^#/ || $line =~ /^$/; + + my ($statement, $parameters) = ($line =~ /([a-zA-Z._-]+)(.*)/); + + # skip everything but workspace lines + next unless defined($statement) and $statement eq 'workspace'; + + my ($number, $params) = ($parameters =~ /[ \t]+([0-9]+) (.+)/); + + # save workspace name (unless the line is actually a workspace assignment) + $workspace_names{$number} = $params unless $params =~ /^output/; +} + +for my $line (@lines) { + # directly use comments and empty lines + if ($line =~ /^#/ || $line =~ /^$/) { + print "$line\n"; + next; + } + + my ($statement, $parameters) = ($line =~ /([a-zA-Z._-]+)(.*)/); + + # directly use lines which have not changed between 3.x and 4.x + if (!defined($statement) || (lc $statement ~~ @unchanged)) { + print "$line\n"; + next; + } + + # new_container changed only the statement name to workspace_layout + if ($statement eq 'new_container') { + # TODO: new_container stack-limit + print "workspace_layout$parameters\n"; + next; + } + + # workspace_bar is gone, you should use i3bar now + if ($statement eq 'workspace_bar') { + $workspace_bar = ($parameters =~ /[ \t+](yes|true|on|enable|active)/); + print "# XXX: REMOVED workspace_bar line. There is no internal workspace bar in v4.\n"; + next; + } + + # new_window changed the parameters from bb to none etc. + if ($statement eq 'new_window') { + if ($parameters =~ /bb/) { + print "new_window none\n"; + } elsif ($parameters =~ /bp/) { + print "new_window 1pixel\n"; + } elsif ($parameters =~ /bn/) { + print "new_window normal\n"; + } else { + print "# XXX: Invalid parameter for new_window, not touching line:\n"; + print "$line\n"; + } + next; + } + + # bar colors are obsolete, need to be configured in i3bar + if ($statement =~ /^bar\./) { + print "# XXX: REMOVED $statement, configure i3bar instead.\n"; + print "# Old line: $line\n"; + next; + } + + # one form of this is still ok (workspace assignments), the other (named workspaces) isn’t + if ($statement eq 'workspace') { + my ($number, $params) = ($parameters =~ /[ \t]+([0-9]+) (.+)/); + if ($params =~ /^output/) { + print "$line\n"; + next; + } else { + print "# XXX: workspace name will end up in the corresponding bindings.\n"; + next; + } + } + + if ($statement eq 'bind' || $statement eq 'bindsym') { + convert_command($line); + next; + } + + print "# XXX: migration script could not handle line: $line\n"; +} + +# +# Converts a command (after bind/bindsym) +# +sub convert_command { + my ($line) = @_; + + my @unchanged_cmds = qw( + exec + mark + kill + restart + reload + exit + stack-limit + ); + + my ($statement, $key, $command) = ($line =~ /([a-zA-Z_-]+)[ \t]+([^ \t]+)[ \t]+(.*)/); + + # turn 'bind' to 'bindcode' + $statement = 'bindcode' if $statement eq 'bind'; + + # check if it’s one of the unchanged commands + my ($cmd) = ($command =~ /([a-zA-Z_-]+)/); + if ($cmd ~~ @unchanged_cmds) { + print "$statement $key $command\n"; + return; + } + + # simple replacements + my @replace = ( + qr/^s/ => 'layout stacking', + qr/^d/ => 'layout default', + qr/^T/ => 'layout tabbed', + qr/^f($|[^go])/ => 'fullscreen', + qr/^fg/ => 'fullscreen global', + qr/^t/ => 'floating toggle', + qr/^h/ => 'focus left', + qr/^j($|[^u])/ => 'focus down', + qr/^k/ => 'focus up', + qr/^l/ => 'focus right', + qr/^mh/ => 'move left', + qr/^mj/ => 'move down', + qr/^mk/ => 'move up', + qr/^ml/ => 'move right', + qr/^bn/ => 'border normal', + qr/^bp/ => 'border 1pixel', + qr/^bb/ => 'border none', + qr/^bt/ => 'border toggle', + qr/^pw/ => 'workspace prev', + qr/^nw/ => 'workspace next', + ); + + my $replaced = 0; + for (my $c = 0; $c < @replace; $c += 2) { + if ($command =~ $replace[$c]) { + $command = $replace[$c+1]; + $replaced = 1; + last; + } + } + + # goto command is now obsolete due to criteria + focus command + if ($command =~ /^goto/) { + my ($mark) = ($command =~ /^goto (.*)/); + print qq|$statement $key [con_mark="$mark"] focus\n|; + return; + } + + # the jump command is also obsolete due to criteria + focus + if ($command =~ /^jump/) { + my ($params) = ($command =~ /^jump (.*)/); + if ($params =~ /^"/) { + # jump ["]window class[/window title]["] + ($params) = ($params =~ /^"([^"]+)"/); + + # check if a window title was specified + if ($params =~ m,/,) { + my ($class, $title) = ($params =~ m,([^/]+)/(.+),); + print qq|$statement $key [class="$class" title="$title"] focus\n|; + } else { + print qq|$statement $key [class="$params"] focus\n|; + } + return; + } else { + # jump [ column row ] + print "# XXX: jump workspace is obsolete in 4.x: $line\n"; + return; + } + } + + if (!$replaced && $command =~ /^focus/) { + my ($what) = ($command =~ /^focus (.*)/); + $what =~ s/[ \t]//g; + if ($what eq 'ft') { + $what = 'mode_toggle'; + } elsif ($what eq 'floating' || $what eq 'tiling') { + # those are unchanged + } else { + print "# XXX: focus is obsolete in 4.x: $line\n"; + return; + } + print qq|$statement $key focus $what\n|; + return; + } + + if ($command =~ /^mode/) { + my ($parameters) = ($command =~ /^mode (.*)/); + print qq|$statement $key mode "$parameters"\n|; + return; + } + + # the parameters of the resize command have changed + if ($command =~ /^resize/) { + # OLD: resize [+|-]\n") + # NEW: resize [ px] [or ppt] + my ($direction, $sign, $px) = ($command =~ /^resize (left|right|top|bottom) ([+-])([0-9]+)/); + my $cmd = 'resize '; + $cmd .= ($sign eq '+' ? 'grow' : 'shrink') . ' '; + $cmd .= "$direction "; + $cmd .= "$px px"; + print qq|$statement $key $cmd\n|; + return; + } + + # switch workspace + if ($command =~ /^[0-9]+/) { + my ($number) = ($command =~ /^([0-9]+)/); + if (exists $workspace_names{$number}) { + print qq|$statement $key workspace $workspace_names{$number}\n|; + return; + } else { + print qq|$statement $key workspace $number\n|; + return; + } + } + + # move to workspace + if ($command =~ /^m[0-9]+/) { + my ($number) = ($command =~ /^m([0-9]+)/); + if (exists $workspace_names{$number}) { + print qq|$statement $key move workspace $workspace_names{$number}\n|; + return; + } else { + print qq|$statement $key move workspace $number\n|; + return; + } + } + + # With Container-commands are now obsolete due to different abstraction + if ($command =~ /^wc/) { + $command =~ s/^wc//g; + my $wc_replaced = 0; + for (my $c = 0; $c < @replace; $c += 2) { + if ($command =~ $replace[$c]) { + $command = $replace[$c+1]; + $wc_replaced = 1; + last; + } + } + if (!$wc_replaced) { + print "# XXX: migration script could not handle command: $line\n"; + } else { + # NOTE: This is not 100% accurate, as it only works for one level + # of nested containers. As this is a common use case, we use 'focus + # parent; $command' nevertheless. For advanced use cases, the user + # has to modify his config. + print "$statement $key focus parent; $command\n"; + } + return; + } + + if ($replaced) { + print "$statement $key $command\n"; + } else { + print "# XXX: migration script could not handle command: $line\n"; + } +} + + +# add an i3bar invocation automatically if no 'workspace_bar no' was found +if ($workspace_bar) { + print "\n"; + print "# XXX: Automatically added a call to i3bar to provide a workspace bar\n"; + print "exec i3status | i3bar -d\n"; +} diff --git a/i3-msg/Makefile b/i3-msg/Makefile index d75d807c..1b7c1c04 100644 --- a/i3-msg/Makefile +++ b/i3-msg/Makefile @@ -12,11 +12,13 @@ HEADERS=$(wildcard *.h) # Depend on the specific file (.c for each .o) and on all headers %.o: %.c ${HEADERS} echo "CC $<" - $(CC) $(CFLAGS) -c -o $@ $< + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< -all: ${FILES} +all: i3-msg + +i3-msg: ${FILES} echo "LINK i3-msg" - $(CC) -o i3-msg ${FILES} $(LDFLAGS) + $(CC) $(LDFLAGS) -o i3-msg ${FILES} $(LIBS) install: all echo "INSTALL" diff --git a/i3-msg/main.c b/i3-msg/main.c index 33bedc7c..630a345d 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -1,5 +1,5 @@ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager * @@ -27,23 +27,49 @@ #include #include #include -#include +#include + +#include +#include #include +static char *socket_path; + /* - * This function resolves ~ in pathnames (and more, see glob(3)). + * Try to get the socket path from X11 and return NULL if it doesn’t work. + * As i3-msg is a short-running tool, we don’t bother with cleaning up the + * connection and leave it up to the operating system on exit. * */ -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; +static char *socket_path_from_x11() { + xcb_connection_t *conn; + int screen; + if ((conn = xcb_connect(NULL, &screen)) == NULL || + xcb_connection_has_error(conn)) + return NULL; + xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen); + xcb_window_t root = root_screen->root; + + xcb_intern_atom_cookie_t atom_cookie; + xcb_intern_atom_reply_t *atom_reply; + + atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH"); + atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL); + if (atom_reply == NULL) + return NULL; + + xcb_get_property_cookie_t prop_cookie; + xcb_get_property_reply_t *prop_reply; + prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom, + XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX); + prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); + if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0) + return NULL; + if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply), + (char*)xcb_get_property_value(prop_reply)) == -1) + return NULL; + return socket_path; } /* @@ -53,144 +79,174 @@ static char *glob_path(const char *path) { */ static void ipc_send_message(int sockfd, uint32_t message_size, uint32_t message_type, uint8_t *payload) { - int buffer_size = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t) + message_size; - char msg[buffer_size]; - char *walk = msg; + 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_MAGIC); - walk += strlen(I3_IPC_MAGIC); - 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); + 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)); + 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(sockfd, msg + sent_bytes, bytes_to_go); - if (n == -1) - err(EXIT_FAILURE, "write() failed"); + int sent_bytes = 0; + int bytes_to_go = buffer_size; + while (sent_bytes < bytes_to_go) { + int n = write(sockfd, msg + sent_bytes, bytes_to_go); + if (n == -1) + err(EXIT_FAILURE, "write() failed"); - sent_bytes += n; - bytes_to_go -= n; - } + sent_bytes += n; + bytes_to_go -= n; + } } 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; + /* 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"); + 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; - } + read_bytes += n; + to_read -= n; + } - if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) - errx(EXIT_FAILURE, "invalid magic in reply"); + 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); + 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"); + *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"); + 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; - } + read_bytes += n; + to_read -= n; + } } int main(int argc, char *argv[]) { - 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; + socket_path = getenv("I3SOCK"); + int o, option_index = 0; + int message_type = I3_IPC_MESSAGE_TYPE_COMMAND; + char *payload = NULL; + 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} - }; + 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:vhq"; + char *options_string = "s:t:vhq"; - while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { - if (o == 's') { - socket_path = glob_path(optarg); - } else if (o == 't') { - 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; - } else if (o == 'h') { - printf("i3-msg " I3_VERSION); - printf("i3-msg [-s ] [-t ] \n"); - return 0; - } + while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { + if (o == 's') { + if (socket_path != NULL) + free(socket_path); + socket_path = strdup(optarg); + } else if (o == 't') { + 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 if (strcasecmp(optarg, "get_outputs") == 0) + message_type = I3_IPC_MESSAGE_TYPE_GET_OUTPUTS; + else if (strcasecmp(optarg, "get_tree") == 0) + message_type = I3_IPC_MESSAGE_TYPE_GET_TREE; + else { + printf("Unknown message type\n"); + printf("Known types: command, get_workspaces, get_outputs, get_tree\n"); + exit(EXIT_FAILURE); + } + } else if (o == 'q') { + quiet = true; + } else if (o == 'v') { + printf("i3-msg " I3_VERSION "\n"); + return 0; + } else if (o == 'h') { + printf("i3-msg " I3_VERSION "\n"); + printf("i3-msg [-s ] [-t ] \n"); + return 0; } + } - if (optind < argc) - payload = argv[optind]; + if (socket_path == NULL) + socket_path = socket_path_from_x11(); - int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); - if (sockfd == -1) - err(EXIT_FAILURE, "Could not create socket"); + /* Fall back to the default socket path */ + if (socket_path == NULL) + socket_path = strdup("/tmp/i3-ipc.sock"); - struct sockaddr_un addr; - memset(&addr, 0, sizeof(struct sockaddr_un)); - addr.sun_family = AF_LOCAL; - strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); - if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) - err(EXIT_FAILURE, "Could not connect to i3"); + /* Use all arguments, separated by whitespace, as payload. + * This way, you don’t have to do i3-msg 'mark foo', you can use + * i3-msg mark foo */ + while (optind < argc) { + if (!payload) { + if (!(payload = strdup(argv[optind]))) + err(EXIT_FAILURE, "strdup(argv[optind])"); + } else { + char *both; + if (asprintf(&both, "%s %s", payload, argv[optind]) == -1) + err(EXIT_FAILURE, "asprintf"); + free(payload); + payload = both; + } + optind++; + } - ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t*)payload); + if (!payload) + payload = ""; - if (quiet) - return 0; + int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (sockfd == -1) + err(EXIT_FAILURE, "Could not create socket"); - uint32_t reply_length; - uint8_t *reply; - ipc_recv_message(sockfd, message_type, &reply_length, &reply); - printf("%.*s", reply_length, reply); - free(reply); + struct sockaddr_un addr; + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_LOCAL; + strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); + if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) + err(EXIT_FAILURE, "Could not connect to i3"); - close(sockfd); + 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); + + return 0; } diff --git a/i3-nagbar/Makefile b/i3-nagbar/Makefile new file mode 100644 index 00000000..933ae76c --- /dev/null +++ b/i3-nagbar/Makefile @@ -0,0 +1,30 @@ +# Default value so one can compile i3-nagbar standalone +TOPDIR=.. + +include $(TOPDIR)/common.mk + +# 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) + +# Depend on the specific file (.c for each .o) and on all headers +%.o: %.c ${HEADERS} + echo "CC $<" + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< + +all: i3-nagbar + +i3-nagbar: ${FILES} + echo "LINK i3-nagbar" + $(CC) $(LDFLAGS) -o $@ ${FILES} $(LIBS) + +install: all + echo "INSTALL" + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -m 0755 i3-nagbar $(DESTDIR)$(PREFIX)/bin/ + +clean: + rm -f *.o + +distclean: clean + rm -f i3-nagbar diff --git a/i3-nagbar/atoms.xmacro b/i3-nagbar/atoms.xmacro new file mode 100644 index 00000000..333ba2d6 --- /dev/null +++ b/i3-nagbar/atoms.xmacro @@ -0,0 +1,6 @@ +xmacro(_NET_WM_WINDOW_TYPE) +xmacro(_NET_WM_WINDOW_TYPE_DOCK) +xmacro(_NET_WM_STRUT_PARTIAL) +xmacro(I3_SOCKET_PATH) +xmacro(ATOM) +xmacro(CARDINAL) diff --git a/i3-nagbar/i3-nagbar.h b/i3-nagbar/i3-nagbar.h new file mode 100644 index 00000000..2fbe3cbb --- /dev/null +++ b/i3-nagbar/i3-nagbar.h @@ -0,0 +1,26 @@ +#ifndef _I3_NAGBAR +#define _I3_NAGBAR + +#include + +#define die(...) errx(EXIT_FAILURE, __VA_ARGS__); +#define FREE(pointer) do { \ + if (pointer != NULL) { \ + free(pointer); \ + pointer = NULL; \ + } \ +} \ +while (0) + +#define xmacro(atom) xcb_atom_t A_ ## atom; +#include "atoms.xmacro" +#undef xmacro + +extern xcb_window_t root; + +uint32_t get_colorpixel(xcb_connection_t *conn, char *hex); +xcb_window_t open_input_window(xcb_connection_t *conn, uint32_t width, uint32_t height); +int get_font_id(xcb_connection_t *conn, char *pattern, int *font_height); +void xcb_change_gc_single(xcb_connection_t *conn, xcb_gcontext_t gc, uint32_t mask, uint32_t value); + +#endif diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c new file mode 100644 index 00000000..d0d7e77a --- /dev/null +++ b/i3-nagbar/main.c @@ -0,0 +1,395 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009-2011 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + * i3-nagbar is a utility which displays a nag message. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "i3-nagbar.h" + +typedef struct { + char *label; + char *action; + int16_t x; + uint16_t width; +} button_t; + +static xcb_window_t win; +static xcb_pixmap_t pixmap; +static xcb_gcontext_t pixmap_gc; +static xcb_rectangle_t rect = { 0, 0, 600, 20 }; +static int font_height; +static char *prompt = "Please do not run this program."; +static button_t *buttons; +static int buttoncnt; +xcb_window_t root; + +/* + * Starts the given application by passing it through a shell. We use double fork + * to avoid zombie processes. As the started application’s parent exits (immediately), + * the application is reparented to init (process-id 1), which correctly handles + * childs, so we don’t have to do it :-). + * + * The shell is determined by looking for the SHELL environment variable. If it + * does not exist, /bin/sh is used. + * + */ +static void start_application(const char *command) { + printf("executing: %s\n", command); + if (fork() == 0) { + /* Child process */ + setsid(); + if (fork() == 0) { + /* Stores the path of the shell */ + static const char *shell = NULL; + + if (shell == NULL) + if ((shell = getenv("SHELL")) == NULL) + shell = "/bin/sh"; + + /* This is the child */ + execl(shell, shell, "-c", command, (void*)NULL); + /* not reached */ + } + exit(0); + } + wait(0); +} + +static button_t *get_button_at(int16_t x, int16_t y) { + for (int c = 0; c < buttoncnt; c++) + if (x >= (buttons[c].x) && x <= (buttons[c].x + buttons[c].width)) + return &buttons[c]; + + return NULL; +} + +static void handle_button_press(xcb_connection_t *conn, xcb_button_press_event_t *event) { + printf("button pressed on x = %d, y = %d\n", + event->event_x, event->event_y); + /* TODO: set a flag for the button, re-render */ +} + +/* + * Called when the user releases the mouse button. Checks whether the + * coordinates are over a button and executes the appropriate action. + * + */ +static void handle_button_release(xcb_connection_t *conn, xcb_button_release_event_t *event) { + printf("button released on x = %d, y = %d\n", + event->event_x, event->event_y); + /* If the user hits the close button, we exit(0) */ + if (event->event_x >= (rect.width - 32)) + exit(0); + button_t *button = get_button_at(event->event_x, event->event_y); + if (!button) + return; + start_application(button->action); + + /* TODO: unset flag, re-render */ +} + +/* + * Handles expose events (redraws of the window) and rendering in general. Will + * be called from the code with event == NULL or from X with event != NULL. + * + */ +static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { + printf("expose!\n"); + + /* re-draw the background */ + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#900000")); + xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &rect); + + /* restore font color */ + uint32_t values[3]; + values[0] = get_colorpixel(conn, "#FFFFFF"); + values[1] = get_colorpixel(conn, "#900000"); + xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, values); + xcb_image_text_8(conn, strlen(prompt), pixmap, pixmap_gc, 4 + 4/* X */, + font_height + 2 + 4 /* Y = baseline of font */, prompt); + + /* render close button */ + int line_width = 4; + int w = 20; + int y = rect.width; + values[0] = get_colorpixel(conn, "#680a0a"); + values[1] = line_width; + xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values); + + xcb_rectangle_t close = { y - w - (2 * line_width), 0, w + (2 * line_width), rect.height }; + xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close); + + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#d92424")); + xcb_point_t points[] = { + { y - w - (2 * line_width), line_width / 2 }, + { y - (line_width / 2), line_width / 2 }, + { y - (line_width / 2), (rect.height - (line_width / 2)) - 2 }, + { y - w - (2 * line_width), (rect.height - (line_width / 2)) - 2 }, + { y - w - (2 * line_width), line_width / 2 } + }; + xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points); + + values[0] = get_colorpixel(conn, "#ffffff"); + values[1] = get_colorpixel(conn, "#680a0a"); + values[2] = 1; + xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_LINE_WIDTH, values); + xcb_image_text_8(conn, strlen("x"), pixmap, pixmap_gc, y - w - line_width + (w / 2) - 4/* X */, + font_height + 2 + 4 - 1/* Y = baseline of font */, "X"); + y -= w; + + y -= 20; + + /* render custom buttons */ + line_width = 1; + for (int c = 0; c < buttoncnt; c++) { + /* TODO: make w = text extents of the label */ + w = 90; + y -= 30; + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#680a0a")); + close = (xcb_rectangle_t){ y - w - (2 * line_width), 2, w + (2 * line_width), rect.height - 6 }; + xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close); + + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#d92424")); + buttons[c].x = y - w - (2 * line_width); + buttons[c].width = w; + xcb_point_t points2[] = { + { y - w - (2 * line_width), (line_width / 2) + 2 }, + { y - (line_width / 2), (line_width / 2) + 2 }, + { y - (line_width / 2), (rect.height - 4 - (line_width / 2)) }, + { y - w - (2 * line_width), (rect.height - 4 - (line_width / 2)) }, + { y - w - (2 * line_width), (line_width / 2) + 2 } + }; + xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points2); + + values[0] = get_colorpixel(conn, "#ffffff"); + values[1] = get_colorpixel(conn, "#680a0a"); + xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, values); + xcb_image_text_8(conn, strlen(buttons[c].label), pixmap, pixmap_gc, y - w - line_width + 6/* X */, + font_height + 2 + 3/* Y = baseline of font */, buttons[c].label); + + y -= w; + } + + /* border line at the bottom */ + line_width = 2; + values[0] = get_colorpixel(conn, "#470909"); + values[1] = line_width; + xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values); + xcb_point_t bottom[] = { + { 0, rect.height - 0 }, + { rect.width, rect.height - 0 } + }; + xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 2, bottom); + + + /* Copy the contents of the pixmap to the real window */ + xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, rect.width, rect.height); + xcb_flush(conn); + + return 1; +} + +int main(int argc, char *argv[]) { + char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; + int o, option_index = 0; + + static struct option long_options[] = { + {"version", no_argument, 0, 'v'}, + {"font", required_argument, 0, 'f'}, + {"button", required_argument, 0, 'b'}, + {"help", no_argument, 0, 'h'}, + {"message", no_argument, 0, 'm'}, + {0, 0, 0, 0} + }; + + char *options_string = "b:f:m:vh"; + + while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { + switch (o) { + case 'v': + printf("i3-nagbar " I3_VERSION); + return 0; + case 'f': + FREE(pattern); + pattern = strdup(optarg); + break; + case 'm': + prompt = strdup(optarg); + break; + case 'h': + printf("i3-nagbar " I3_VERSION "\n"); + printf("i3-nagbar [-m ] [-b