From 1bc41edb6daa5e92ee58a6ffd896e4f538f6dea7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 19 Sep 2012 18:44:13 +0200 Subject: [PATCH 001/146] update debian packaging --- debian/changelog | 10 ++++++++-- .../patches/manpage-x-terminal-emulator.patch | 8 ++++++++ debian/patches/use-x-terminal-emulator.patch | 18 +++++++++++++----- debian/rules | 2 +- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/debian/changelog b/debian/changelog index 89184c13..c4c2681c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,14 @@ -i3-wm (4.2.1-0) unstable; urgency=low +i3-wm (4.3.1-0) unstable; urgency=low * NOT YET RELEASED - -- Michael Stapelberg Fri, 27 Jan 2012 19:34:11 +0000 + -- Michael Stapelberg Wed, 19 Sep 2012 18:13:15 +0200 + +i3-wm (4.3-1) experimental; urgency=low + + * New upstream release + + -- Michael Stapelberg Wed, 19 Sep 2012 18:13:40 +0200 i3-wm (4.2-1) unstable; urgency=low diff --git a/debian/patches/manpage-x-terminal-emulator.patch b/debian/patches/manpage-x-terminal-emulator.patch index ed6cb465..17b8ee4c 100644 --- a/debian/patches/manpage-x-terminal-emulator.patch +++ b/debian/patches/manpage-x-terminal-emulator.patch @@ -1,3 +1,11 @@ +Description: list x-terminal-emulator as one of i3-sensible-terminal’s choices +Author: Michael Stapelberg +Origin: vendor +Forwarded: not-needed +Last-Update: 2011-12-28 + +--- + Index: i3-4.1.1/man/i3-sensible-terminal.man =================================================================== --- i3-4.1.1.orig/man/i3-sensible-terminal.man 2011-12-28 23:56:55.487581000 +0100 diff --git a/debian/patches/use-x-terminal-emulator.patch b/debian/patches/use-x-terminal-emulator.patch index 0d71f7c4..96161624 100644 --- a/debian/patches/use-x-terminal-emulator.patch +++ b/debian/patches/use-x-terminal-emulator.patch @@ -1,7 +1,15 @@ -Index: i3-4.1.1/i3-sensible-terminal +Description: i3-sensible-terminal: try x-terminal-emulator first +Author: Michael Stapelberg +Origin: vendor +Forwarded: not-needed +Last-Update: 2012-09-19 + +--- + +Index: i3-4.3/i3-sensible-terminal =================================================================== ---- i3-4.1.1.orig/i3-sensible-terminal 2011-12-28 23:51:52.455610236 +0100 -+++ i3-4.1.1/i3-sensible-terminal 2011-12-28 23:52:00.826775027 +0100 +--- i3-4.3.orig/i3-sensible-terminal 2012-09-19 18:08:09.000000000 +0200 ++++ i3-4.3/i3-sensible-terminal 2012-09-19 18:32:06.393883488 +0200 @@ -4,11 +4,7 @@ # # This script tries to exec a terminal emulator by trying some known terminal @@ -10,8 +18,8 @@ Index: i3-4.1.1/i3-sensible-terminal -# Distributions/packagers should enhance this script with a -# distribution-specific mechanism to find the preferred terminal emulator. On -# Debian, there is the x-terminal-emulator symlink for example. --for terminal in $TERMINAL urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm; do -+for terminal in $TERMINAL x-terminal-emulator urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm; do +-for terminal in $TERMINAL urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal; do ++for terminal in $TERMINAL x-terminal-emulator urxvt rxvt terminator Eterm aterm xterm gnome-terminal roxterm xfce4-terminal; do if which $terminal > /dev/null 2>&1; then exec $terminal "$@" fi diff --git a/debian/rules b/debian/rules index 6bae8165..011119d0 100755 --- a/debian/rules +++ b/debian/rules @@ -38,7 +38,7 @@ override_dh_auto_build: $(MAKE) -C docs override_dh_installchangelogs: - dh_installchangelogs RELEASE-NOTES-4.2 + dh_installchangelogs RELEASE-NOTES-4.3 override_dh_install: $(MAKE) DESTDIR=$(CURDIR)/debian/i3-wm/ install From f9c903ba62ab3d6c468b4fe44eca7b9b30b02481 Mon Sep 17 00:00:00 2001 From: Sascha Kruse Date: Tue, 18 Sep 2012 15:36:40 +0200 Subject: [PATCH 002/146] complete-run.pl: Check for missing executables --- testcases/complete-run.pl | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index 5ea9d078..d73bb332 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -66,6 +66,22 @@ my $result = GetOptions( pod2usage(-verbose => 2, -exitcode => 0) if $help; +# Check for missing executables +my @binaries = qw( + ../i3 + ../i3bar/i3bar + ../i3-config-wizard/i3-config-wizard + ../i3-dump-log/i3-dump-log + ../i3-input/i3-input + ../i3-msg/i3-msg + ../i3-nagbar/i3-nagbar + ); + +foreach my $binary (@binaries) { + die "$binary executable not found" unless -e $binary; + die "$binary is not an executable" unless -x $binary; +} + @displays = split(/,/, join(',', @displays)); @displays = map { s/ //g; $_ } @displays; From d638e3029afb0f48360f928332f61a8fafa3032c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 21 Sep 2012 15:36:25 +0200 Subject: [PATCH 003/146] =?UTF-8?q?don=E2=80=99t=20use=20reversed=20identi?= =?UTF-8?q?fiers=20for=20include=20guards=20(Thanks=20Markus)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Done with: sed -in 's/\(ifndef\|define\) _\([0-9A-Z_]*\)$/\1 I3_\2/' include/**/*.h fixes #804 --- include/all.h | 4 ++-- include/assignments.h | 4 ++-- include/click.h | 4 ++-- include/cmdparse.h | 4 ++-- include/commands.h | 4 ++-- include/commands_parser.h | 4 ++-- include/con.h | 4 ++-- include/config.h | 4 ++-- include/data.h | 4 ++-- include/debug.h | 4 ++-- include/display_version.h | 4 ++-- include/ewmh.h | 4 ++-- include/fake_outputs.h | 4 ++-- include/floating.h | 4 ++-- include/handlers.h | 4 ++-- include/i3.h | 4 ++-- include/i3/ipc.h | 4 ++-- include/ipc.h | 4 ++-- include/key_press.h | 4 ++-- include/libi3.h | 4 ++-- include/load_layout.h | 4 ++-- include/log.h | 4 ++-- include/manage.h | 4 ++-- include/match.h | 4 ++-- include/move.h | 4 ++-- include/output.h | 4 ++-- include/randr.h | 4 ++-- include/regex.h | 4 ++-- include/render.h | 4 ++-- include/resize.h | 4 ++-- include/scratchpad.h | 4 ++-- include/shmlog.h | 4 ++-- include/sighandler.h | 4 ++-- include/startup.h | 4 ++-- include/tree.h | 4 ++-- include/util.h | 4 ++-- include/window.h | 4 ++-- include/workspace.h | 4 ++-- include/x.h | 4 ++-- include/xcb.h | 4 ++-- include/xcb_compat.h | 4 ++-- include/xcursor.h | 4 ++-- include/xinerama.h | 4 ++-- 43 files changed, 86 insertions(+), 86 deletions(-) diff --git a/include/all.h b/include/all.h index b83b9f4e..48ca6621 100644 --- a/include/all.h +++ b/include/all.h @@ -10,8 +10,8 @@ * compile-time. * */ -#ifndef _ALL_H -#define _ALL_H +#ifndef I3_ALL_H +#define I3_ALL_H #include #include diff --git a/include/assignments.h b/include/assignments.h index f4ef8e88..570375cf 100644 --- a/include/assignments.h +++ b/include/assignments.h @@ -7,8 +7,8 @@ * assignments.c: Assignments for specific windows (for_window). * */ -#ifndef _ASSIGNMENTS_H -#define _ASSIGNMENTS_H +#ifndef I3_ASSIGNMENTS_H +#define I3_ASSIGNMENTS_H /** * Checks the list of assignments for the given window and runs all matching diff --git a/include/click.h b/include/click.h index 6261613b..3c4d5288 100644 --- a/include/click.h +++ b/include/click.h @@ -7,8 +7,8 @@ * click.c: Button press (mouse click) events. * */ -#ifndef _CLICK_H -#define _CLICK_H +#ifndef I3_CLICK_H +#define I3_CLICK_H /** * The button press X callback. This function determines whether the floating diff --git a/include/cmdparse.h b/include/cmdparse.h index d619b97c..4a87c39c 100644 --- a/include/cmdparse.h +++ b/include/cmdparse.h @@ -7,8 +7,8 @@ * cmdparse.y: the parser for commands you send to i3 (or bind on keys) * */ -#ifndef _CMDPARSE_H -#define _CMDPARSE_H +#ifndef I3_CMDPARSE_H +#define I3_CMDPARSE_H char *parse_cmd(const char *new); diff --git a/include/commands.h b/include/commands.h index 37ee98d9..c971bb48 100644 --- a/include/commands.h +++ b/include/commands.h @@ -7,8 +7,8 @@ * commands.c: all command functions (see commands_parser.c) * */ -#ifndef _COMMANDS_H -#define _COMMANDS_H +#ifndef I3_COMMANDS_H +#define I3_COMMANDS_H #include "commands_parser.h" diff --git a/include/commands_parser.h b/include/commands_parser.h index 795cb026..6ff8d54e 100644 --- a/include/commands_parser.h +++ b/include/commands_parser.h @@ -7,8 +7,8 @@ * commands.c: all command functions (see commands_parser.c) * */ -#ifndef _COMMANDS_PARSER_H -#define _COMMANDS_PARSER_H +#ifndef I3_COMMANDS_PARSER_H +#define I3_COMMANDS_PARSER_H #include diff --git a/include/con.h b/include/con.h index 20e83df9..f741dee0 100644 --- a/include/con.h +++ b/include/con.h @@ -9,8 +9,8 @@ * …). * */ -#ifndef _CON_H -#define _CON_H +#ifndef I3_CON_H +#define I3_CON_H /** * Create a new container (and attach it to the given parent, if not NULL). diff --git a/include/config.h b/include/config.h index 1a48016a..669cfe44 100644 --- a/include/config.h +++ b/include/config.h @@ -10,8 +10,8 @@ * mode). * */ -#ifndef _CONFIG_H -#define _CONFIG_H +#ifndef I3_CONFIG_H +#define I3_CONFIG_H #include #include "queue.h" diff --git a/include/data.h b/include/data.h index 02f781c9..a2c6859e 100644 --- a/include/data.h +++ b/include/data.h @@ -7,8 +7,8 @@ * include/data.h: This file defines all data structures used by i3 * */ -#ifndef _DATA_H -#define _DATA_H +#ifndef I3_DATA_H +#define I3_DATA_H #define SN_API_NOT_YET_FROZEN 1 #include diff --git a/include/debug.h b/include/debug.h index abf9c76d..44c95c6d 100644 --- a/include/debug.h +++ b/include/debug.h @@ -8,8 +8,8 @@ * events. This code is from xcb-util. * */ -#ifndef _DEBUG_H -#define _DEBUG_H +#ifndef I3_DEBUG_H +#define I3_DEBUG_H int handle_event(void *ignored, xcb_connection_t *c, xcb_generic_event_t *e); diff --git a/include/display_version.h b/include/display_version.h index 97b3902c..88a1abc1 100644 --- a/include/display_version.h +++ b/include/display_version.h @@ -7,8 +7,8 @@ * display_version.c: displays the running i3 version, runs as part of * i3 --moreversion. */ -#ifndef _DISPLAY_VERSION_H -#define _DISPLAY_VERSION_H +#ifndef I3_DISPLAY_VERSION_H +#define I3_DISPLAY_VERSION_H /** * Connects to i3 to find out the currently running version. Useful since it diff --git a/include/ewmh.h b/include/ewmh.h index a786069a..07ef6614 100644 --- a/include/ewmh.h +++ b/include/ewmh.h @@ -7,8 +7,8 @@ * ewmh.c: Get/set certain EWMH properties easily. * */ -#ifndef _EWMH_C -#define _EWMH_C +#ifndef I3_EWMH_C +#define I3_EWMH_C /** * Updates _NET_CURRENT_DESKTOP with the current desktop number. diff --git a/include/fake_outputs.h b/include/fake_outputs.h index adb10a0d..bfeba292 100644 --- a/include/fake_outputs.h +++ b/include/fake_outputs.h @@ -8,8 +8,8 @@ * which don’t support multi-monitor in a useful way) and for our testsuite. * */ -#ifndef _FAKE_OUTPUTS_H -#define _FAKE_OUTPUTS_H +#ifndef I3_FAKE_OUTPUTS_H +#define I3_FAKE_OUTPUTS_H /** * Creates outputs according to the given specification. diff --git a/include/floating.h b/include/floating.h index 43137c9c..a2f501c5 100644 --- a/include/floating.h +++ b/include/floating.h @@ -7,8 +7,8 @@ * floating.c: Floating windows. * */ -#ifndef _FLOATING_H -#define _FLOATING_H +#ifndef I3_FLOATING_H +#define I3_FLOATING_H #include "tree.h" diff --git a/include/handlers.h b/include/handlers.h index bcebf9ff..b2e7ce2e 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -8,8 +8,8 @@ * …). * */ -#ifndef _HANDLERS_H -#define _HANDLERS_H +#ifndef I3_HANDLERS_H +#define I3_HANDLERS_H #include diff --git a/include/i3.h b/include/i3.h index bd40f16b..1bc8b55d 100644 --- a/include/i3.h +++ b/include/i3.h @@ -7,8 +7,8 @@ * i3.h: global variables that are used all over i3. * */ -#ifndef _I3_H -#define _I3_H +#ifndef I3_I3_H +#define I3_I3_H #include #include diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 0906b7f9..86fa1b4b 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -8,8 +8,8 @@ * for the IPC interface to i3 (see docs/ipc for more information). * */ -#ifndef _I3_IPC_H -#define _I3_IPC_H +#ifndef I3_I3_IPC_H +#define I3_I3_IPC_H /* * Messages from clients to i3 diff --git a/include/ipc.h b/include/ipc.h index af80fa4b..ef50ba86 100644 --- a/include/ipc.h +++ b/include/ipc.h @@ -7,8 +7,8 @@ * ipc.c: UNIX domain socket IPC (initialization, client handling, protocol). * */ -#ifndef _IPC_H -#define _IPC_H +#ifndef I3_IPC_H +#define I3_IPC_H #include #include diff --git a/include/key_press.h b/include/key_press.h index 4d469bab..417843a1 100644 --- a/include/key_press.h +++ b/include/key_press.h @@ -7,8 +7,8 @@ * key_press.c: key press handler * */ -#ifndef _KEY_PRESS_H -#define _KEY_PRESS_H +#ifndef I3_KEY_PRESS_H +#define I3_KEY_PRESS_H /** * There was a key press. We compare this key code with our bindings table and pass diff --git a/include/libi3.h b/include/libi3.h index d4df901f..7547845b 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -8,8 +8,8 @@ * as i3-msg, i3-config-wizard, … * */ -#ifndef _LIBI3_H -#define _LIBI3_H +#ifndef I3_LIBI3_H +#define I3_LIBI3_H #include #include diff --git a/include/load_layout.h b/include/load_layout.h index a2cd6d14..282512b2 100644 --- a/include/load_layout.h +++ b/include/load_layout.h @@ -8,8 +8,8 @@ * restart. * */ -#ifndef _LOAD_LAYOUT_H -#define _LOAD_LAYOUT_H +#ifndef I3_LOAD_LAYOUT_H +#define I3_LOAD_LAYOUT_H void tree_append_json(const char *filename); diff --git a/include/log.h b/include/log.h index 7822fba5..6fabeca3 100644 --- a/include/log.h +++ b/include/log.h @@ -7,8 +7,8 @@ * log.c: Logging functions. * */ -#ifndef _LOG_H -#define _LOG_H +#ifndef I3_LOG_H +#define I3_LOG_H #include #include diff --git a/include/manage.h b/include/manage.h index c16d295f..d50f64d4 100644 --- a/include/manage.h +++ b/include/manage.h @@ -7,8 +7,8 @@ * manage.c: Initially managing new windows (or existing ones on restart). * */ -#ifndef _MANAGE_H -#define _MANAGE_H +#ifndef I3_MANAGE_H +#define I3_MANAGE_H #include "data.h" diff --git a/include/match.h b/include/match.h index 6d9cb915..e1d25904 100644 --- a/include/match.h +++ b/include/match.h @@ -11,8 +11,8 @@ * match_matches_window() to find the windows affected by this command. * */ -#ifndef _MATCH_H -#define _MATCH_H +#ifndef I3_MATCH_H +#define I3_MATCH_H /* * Initializes the Match data structure. This function is necessary because the diff --git a/include/move.h b/include/move.h index 22b6e809..d45e676e 100644 --- a/include/move.h +++ b/include/move.h @@ -7,8 +7,8 @@ * move.c: Moving containers into some direction. * */ -#ifndef _MOVE_H -#define _MOVE_H +#ifndef I3_MOVE_H +#define I3_MOVE_H /** * Moves the current container in the given direction (TOK_LEFT, TOK_RIGHT, diff --git a/include/output.h b/include/output.h index d488ad30..e87da22e 100644 --- a/include/output.h +++ b/include/output.h @@ -7,8 +7,8 @@ * output.c: Output (monitor) related functions. * */ -#ifndef _OUTPUT_H -#define _OUTPUT_H +#ifndef I3_OUTPUT_H +#define I3_OUTPUT_H /** * Returns the output container below the given output container. diff --git a/include/randr.h b/include/randr.h index ac527bcc..019e1f74 100644 --- a/include/randr.h +++ b/include/randr.h @@ -9,8 +9,8 @@ * (take your time to read it completely, it answers all questions). * */ -#ifndef _RANDR_H -#define _RANDR_H +#ifndef I3_RANDR_H +#define I3_RANDR_H #include "data.h" #include diff --git a/include/regex.h b/include/regex.h index fe1e9f95..7403abef 100644 --- a/include/regex.h +++ b/include/regex.h @@ -7,8 +7,8 @@ * regex.c: Interface to libPCRE (perl compatible regular expressions). * */ -#ifndef _REGEX_H -#define _REGEX_H +#ifndef I3_REGEX_H +#define I3_REGEX_H /** * Creates a new 'regex' struct containing the given pattern and a PCRE diff --git a/include/render.h b/include/render.h index 1f31fb0f..0a5949f9 100644 --- a/include/render.h +++ b/include/render.h @@ -8,8 +8,8 @@ * various rects. Needs to be pushed to X11 (see x.c) to be visible. * */ -#ifndef _RENDER_H -#define _RENDER_H +#ifndef I3_RENDER_H +#define I3_RENDER_H /** * "Renders" the given container (and its children), meaning that all rects are diff --git a/include/resize.h b/include/resize.h index 99646ea0..fa0216c8 100644 --- a/include/resize.h +++ b/include/resize.h @@ -7,8 +7,8 @@ * resize.c: Interactive resizing. * */ -#ifndef _RESIZE_H -#define _RESIZE_H +#ifndef I3_RESIZE_H +#define I3_RESIZE_H int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const xcb_button_press_event_t *event); diff --git a/include/scratchpad.h b/include/scratchpad.h index 4d553327..c6157052 100644 --- a/include/scratchpad.h +++ b/include/scratchpad.h @@ -7,8 +7,8 @@ * scratchpad.c: Scratchpad functions (TODO: more description) * */ -#ifndef _SCRATCHPAD_H -#define _SCRATCHPAD_H +#ifndef I3_SCRATCHPAD_H +#define I3_SCRATCHPAD_H /** * Moves the specified window to the __i3_scratch workspace, making it floating diff --git a/include/shmlog.h b/include/shmlog.h index e755d2f1..fd3f53eb 100644 --- a/include/shmlog.h +++ b/include/shmlog.h @@ -8,8 +8,8 @@ * default (ringbuffer for storing the debug log). * */ -#ifndef _I3_SHMLOG_H -#define _I3_SHMLOG_H +#ifndef I3_I3_SHMLOG_H +#define I3_I3_SHMLOG_H #include #include diff --git a/include/sighandler.h b/include/sighandler.h index 571070b0..25d3385b 100644 --- a/include/sighandler.h +++ b/include/sighandler.h @@ -9,8 +9,8 @@ * to restart inplace). * */ -#ifndef _SIGHANDLER_H -#define _SIGHANDLER_H +#ifndef I3_SIGHANDLER_H +#define I3_SIGHANDLER_H /** * Setup signal handlers to safely handle SIGSEGV and SIGFPE diff --git a/include/startup.h b/include/startup.h index 290c8d21..bcc59a0a 100644 --- a/include/startup.h +++ b/include/startup.h @@ -10,8 +10,8 @@ * the appropriate workspace. * */ -#ifndef _STARTUP_H -#define _STARTUP_H +#ifndef I3_STARTUP_H +#define I3_STARTUP_H #define SN_API_NOT_YET_FROZEN 1 #include diff --git a/include/tree.h b/include/tree.h index 8816b19a..2799afee 100644 --- a/include/tree.h +++ b/include/tree.h @@ -7,8 +7,8 @@ * tree.c: Everything that primarily modifies the layout tree data structure. * */ -#ifndef _TREE_H -#define _TREE_H +#ifndef I3_TREE_H +#define I3_TREE_H extern Con *croot; /* TODO: i am not sure yet how much access to the focused container should diff --git a/include/util.h b/include/util.h index cd88863c..e397a4e8 100644 --- a/include/util.h +++ b/include/util.h @@ -8,8 +8,8 @@ * also libi3). * */ -#ifndef _UTIL_H -#define _UTIL_H +#ifndef I3_UTIL_H +#define I3_UTIL_H #include diff --git a/include/window.h b/include/window.h index 60198b87..59d3b1bb 100644 --- a/include/window.h +++ b/include/window.h @@ -7,8 +7,8 @@ * window.c: Updates window attributes (X11 hints/properties). * */ -#ifndef _WINDOW_H -#define _WINDOW_H +#ifndef I3_WINDOW_H +#define I3_WINDOW_H /** * Updates the WM_CLASS (consisting of the class and instance) for the diff --git a/include/workspace.h b/include/workspace.h index 1b25b425..ad780f1f 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -8,8 +8,8 @@ * workspaces. * */ -#ifndef _WORKSPACE_H -#define _WORKSPACE_H +#ifndef I3_WORKSPACE_H +#define I3_WORKSPACE_H #include "data.h" #include "tree.h" diff --git a/include/x.h b/include/x.h index cb4a8a96..c3d4ffc7 100644 --- a/include/x.h +++ b/include/x.h @@ -8,8 +8,8 @@ * render.c). Basically a big state machine. * */ -#ifndef _X_H -#define _X_H +#ifndef I3_X_H +#define I3_X_H /** Stores the X11 window ID of the currently focused window */ extern xcb_window_t focused_id; diff --git a/include/xcb.h b/include/xcb.h index 269038da..15d3e28f 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -7,8 +7,8 @@ * xcb.c: Helper functions for easier usage of XCB * */ -#ifndef _XCB_H -#define _XCB_H +#ifndef I3_XCB_H +#define I3_XCB_H #include "data.h" #include "xcursor.h" diff --git a/include/xcb_compat.h b/include/xcb_compat.h index 9c2660b7..fc09a254 100644 --- a/include/xcb_compat.h +++ b/include/xcb_compat.h @@ -9,8 +9,8 @@ * older versions. * */ -#ifndef _XCB_COMPAT_H -#define _XCB_COMPAT_H +#ifndef I3_XCB_COMPAT_H +#define I3_XCB_COMPAT_H #define xcb_icccm_get_wm_protocols_reply_t xcb_get_wm_protocols_reply_t #define xcb_icccm_get_wm_protocols xcb_get_wm_protocols diff --git a/include/xcursor.h b/include/xcursor.h index c341f90b..fba82ad3 100644 --- a/include/xcursor.h +++ b/include/xcursor.h @@ -7,8 +7,8 @@ * xcursor.c: libXcursor support for themed cursors. * */ -#ifndef _XCURSOR_CURSOR_H -#define _XCURSOR_CURSOR_H +#ifndef I3_XCURSOR_CURSOR_H +#define I3_XCURSOR_CURSOR_H #include diff --git a/include/xinerama.h b/include/xinerama.h index 8c879c07..ca7c2ab5 100644 --- a/include/xinerama.h +++ b/include/xinerama.h @@ -9,8 +9,8 @@ * driver which does not support RandR in 2011 *sigh*. * */ -#ifndef _XINERAMA_H -#define _XINERAMA_H +#ifndef I3_XINERAMA_H +#define I3_XINERAMA_H #include "data.h" From 514265b529ac78b7778eeee2db3dddb6f3a1c24c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 21 Sep 2012 16:35:25 +0200 Subject: [PATCH 004/146] Use ev_signal to avoid async-unsafe functions (Thanks Markus) Functions such as fprintf() might be unsafe to use in a signal handler, see http://stackoverflow.com/questions/3941271/#answer-3941563 By using ev_signal, libev will use a tiny signal handler which just passes on the information and then calls (outside of the signal handler) our callback function which can use fprintf() and other unsafe functions. fixes #803 --- src/main.c | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/main.c b/src/main.c index 8bae9957..a4bd1b9b 100644 --- a/src/main.c +++ b/src/main.c @@ -232,13 +232,15 @@ static void i3_exit(void) { * Unlinks the SHM log and re-raises the signal. * */ -static void handle_signal(int sig, siginfo_t *info, void *data) { +static void handle_signal(struct ev_loop *loop, ev_signal *w, int revents) { + int sig = w->signum; fprintf(stderr, "Received signal %d, terminating\n", sig); if (*shmlogname != '\0') { fprintf(stderr, "Closing SHM log \"%s\"\n", shmlogname); shm_unlink(shmlogname); } fflush(stderr); + ev_signal_stop(loop, w); raise(sig); } @@ -780,31 +782,32 @@ int main(int argc, char *argv[]) { } xcb_ungrab_server(conn); - struct sigaction action; - action.sa_sigaction = handle_signal; - action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO; - sigemptyset(&action.sa_mask); +#define HANDLE_SIGNAL_EV(signum) \ + do { \ + struct ev_signal *signal_watcher = scalloc(sizeof(struct ev_signal)); \ + ev_signal_init(signal_watcher, handle_signal, signum); \ + ev_signal_start(main_loop, signal_watcher); \ + } while (0) if (!disable_signalhandler) setup_signal_handler(); else { /* Catch all signals with default action "Core", see signal(7) */ - if (sigaction(SIGQUIT, &action, NULL) == -1 || - sigaction(SIGILL, &action, NULL) == -1 || - sigaction(SIGABRT, &action, NULL) == -1 || - sigaction(SIGFPE, &action, NULL) == -1 || - sigaction(SIGSEGV, &action, NULL) == -1) - ELOG("Could not setup signal handler"); + HANDLE_SIGNAL_EV(SIGQUIT); + HANDLE_SIGNAL_EV(SIGILL); + HANDLE_SIGNAL_EV(SIGABRT); + HANDLE_SIGNAL_EV(SIGFPE); + HANDLE_SIGNAL_EV(SIGSEGV); } /* Catch all signals with default action "Term", see signal(7) */ - if (sigaction(SIGHUP, &action, NULL) == -1 || - sigaction(SIGINT, &action, NULL) == -1 || - sigaction(SIGALRM, &action, NULL) == -1 || - sigaction(SIGUSR1, &action, NULL) == -1 || - sigaction(SIGUSR2, &action, NULL) == -1) - ELOG("Could not setup signal handler"); + HANDLE_SIGNAL_EV(SIGHUP); + HANDLE_SIGNAL_EV(SIGINT); + HANDLE_SIGNAL_EV(SIGALRM); + HANDLE_SIGNAL_EV(SIGTERM); + HANDLE_SIGNAL_EV(SIGUSR1); + HANDLE_SIGNAL_EV(SIGUSR2); /* Ignore SIGPIPE to survive errors when an IPC client disconnects * while we are sending him a message */ From 8c8fce82e5e357c407533d8f74f469271cf61d4d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 21 Sep 2012 16:47:43 +0200 Subject: [PATCH 005/146] add proper error handling for in-place restarts (Thanks Markus) fixes #806 --- src/ipc.c | 2 +- src/load_layout.c | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/ipc.c b/src/ipc.c index 7dfbc871..169c659f 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -52,7 +52,7 @@ static bool mkdirp(const char *path) { ELOG("mkdir(%s) failed: %s\n", path, strerror(errno)); return false; } - char *copy = strdup(path); + char *copy = sstrdup(path); /* strip trailing slashes, if any */ while (copy[strlen(copy)-1] == '/') copy[strlen(copy)-1] = '\0'; diff --git a/src/load_layout.c b/src/load_layout.c index cce1a712..fc513f51 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -350,11 +350,22 @@ void tree_append_json(const char *filename) { /* TODO: percent of other windows are not correctly fixed at the moment */ FILE *f; if ((f = fopen(filename, "r")) == NULL) { - LOG("Cannot open file\n"); + LOG("Cannot open file \"%s\"\n", filename); + return; + } + struct stat stbuf; + if (fstat(fileno(f), &stbuf) != 0) { + LOG("Cannot fstat() the file\n"); + fclose(f); + return; + } + char *buf = smalloc(stbuf.st_size); + int n = fread(buf, 1, stbuf.st_size, f); + if (n != stbuf.st_size) { + LOG("File \"%s\" could not be read entirely, not loading.\n", filename); + fclose(f); return; } - char *buf = malloc(65535); /* TODO */ - int n = fread(buf, 1, 65535, f); LOG("read %d bytes\n", n); yajl_gen g; yajl_handle hand; From 7cffd79140b276e74d32500eb8c0c2522ae929a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20L=C3=B6bl?= Date: Sat, 22 Sep 2012 00:21:39 +0200 Subject: [PATCH 006/146] Add new subscribe event 'mode' for binding mode changes Introducing a new event to subscribe called mode. It's fired up when i3 changes binding mode (like switching from default to resize). IPC guide adjusted also. --- docs/ipc | 14 ++++++++++++++ include/i3/ipc.h | 3 +++ src/config.c | 7 +++++++ 3 files changed, 24 insertions(+) diff --git a/docs/ipc b/docs/ipc index f8dfa78e..2716f180 100644 --- a/docs/ipc +++ b/docs/ipc @@ -610,6 +610,8 @@ workspace (0):: output (1):: Sent when RandR issues a change notification (of either screens, outputs, CRTCs or output properties). +mode (2):: + Sent whenever i3 changes its binding mode. *Example:* -------------------------------------------------------------------- @@ -651,6 +653,18 @@ This event consists of a single serialized map containing a property { "change": "unspecified" } --------------------------- +=== mode event + +This event consists of a single serialized map containing a property ++change (string)+ which holds the name of current mode in use. The name +is the same as specified in config when creating a mode. The default +mode is simply named default. + +*Example:* +--------------------------- +{ "change": "default" } +--------------------------- + == See also For some languages, libraries are available (so you don’t have to implement diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 86fa1b4b..93b2ae87 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -84,4 +84,7 @@ /* The output event will be triggered upon changes in the output list */ #define I3_IPC_EVENT_OUTPUT (I3_IPC_EVENT_MASK | 1) +/* The output event will be triggered upon mode changes */ +#define I3_IPC_EVENT_MODE (I3_IPC_EVENT_MASK | 2) + #endif diff --git a/src/config.c b/src/config.c index fcf3841e..0bd6811a 100644 --- a/src/config.c +++ b/src/config.c @@ -194,6 +194,13 @@ void switch_mode(const char *new_mode) { bindings = mode->bindings; translate_keysyms(); grab_all_keys(conn, false); + + char *event_msg; + sasprintf(&event_msg, "{\"change\":\"%s\"}", mode->name); + + ipc_send_event("mode", I3_IPC_EVENT_MODE, event_msg); + FREE(event_msg); + return; } From 77134c0dbf47450a8962eaa27f192a47a784cc3b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 22 Sep 2012 12:56:01 +0200 Subject: [PATCH 007/146] tests: add missing boilerplate --- testcases/t/198-regression-scratchpad-crash.t | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/testcases/t/198-regression-scratchpad-crash.t b/testcases/t/198-regression-scratchpad-crash.t index 8f732bac..2448452e 100644 --- a/testcases/t/198-regression-scratchpad-crash.t +++ b/testcases/t/198-regression-scratchpad-crash.t @@ -1,5 +1,19 @@ #!perl # vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# # When using a command which moves a window to scratchpad from an invisible # (e.g. unfocused) workspace and immediately shows that window again, i3 # crashed. From 7a2e1059311288247cd844cb306b1fc370c7da6a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 22 Sep 2012 13:10:01 +0200 Subject: [PATCH 008/146] tests: add testcase for the 'mode' IPC event --- testcases/Makefile.PL | 2 +- testcases/t/199-ipc-mode-event.t | 57 ++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 testcases/t/199-ipc-mode-event.t diff --git a/testcases/Makefile.PL b/testcases/Makefile.PL index b1e698ae..b522fc30 100755 --- a/testcases/Makefile.PL +++ b/testcases/Makefile.PL @@ -8,7 +8,7 @@ WriteMakefile( MIN_PERL_VERSION => '5.010000', # 5.10.0 PREREQ_PM => { 'AnyEvent' => 0, - 'AnyEvent::I3' => '0.09', + 'AnyEvent::I3' => '0.14', 'X11::XCB' => '0.03', 'Inline' => 0, 'ExtUtils::PkgConfig' => 0, diff --git a/testcases/t/199-ipc-mode-event.t b/testcases/t/199-ipc-mode-event.t new file mode 100644 index 00000000..43f7b178 --- /dev/null +++ b/testcases/t/199-ipc-mode-event.t @@ -0,0 +1,57 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Verifies that the IPC 'mode' event is sent when modes are changed. +use i3test i3_autostart => 0; + +my $config = <connect->recv; + +my $cv = AnyEvent->condvar; + +$i3->subscribe({ + mode => sub { + my ($event) = @_; + $cv->send($event->{change} eq 'm1'); + } +})->recv; + +cmd 'mode "m1"'; + +# Timeout after 0.5s +my $t; +$t = AnyEvent->timer(after => 0.5, cb => sub { $cv->send(0); }); + +ok($cv->recv, 'Mode event received'); + +exit_gracefully($pid); + +done_testing; From a01bac13fec0a15e326ac57fb4a15a85fc6a885c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 22 Sep 2012 13:31:08 +0200 Subject: [PATCH 009/146] =?UTF-8?q?don=E2=80=99t=20use=20reserved=20identi?= =?UTF-8?q?fiers=20for=20include=20guards=20(left-overs)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes #804 --- i3-config-wizard/xcb.h | 4 ++-- i3-input/i3-input.h | 4 ++-- i3-nagbar/i3-nagbar.h | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/i3-config-wizard/xcb.h b/i3-config-wizard/xcb.h index 4ed182c4..372ed161 100644 --- a/i3-config-wizard/xcb.h +++ b/i3-config-wizard/xcb.h @@ -1,5 +1,5 @@ -#ifndef _XCB_H -#define _XCB_H +#ifndef I3_XCB_H +#define I3_XCB_H /* from X11/keysymdef.h */ #define XCB_NUM_LOCK 0xff7f diff --git a/i3-input/i3-input.h b/i3-input/i3-input.h index f494cbd5..f1d5f077 100644 --- a/i3-input/i3-input.h +++ b/i3-input/i3-input.h @@ -1,5 +1,5 @@ -#ifndef _I3_INPUT -#define _I3_INPUT +#ifndef I3_INPUT +#define I3_INPUT #include diff --git a/i3-nagbar/i3-nagbar.h b/i3-nagbar/i3-nagbar.h index 5a21226b..379a7f6f 100644 --- a/i3-nagbar/i3-nagbar.h +++ b/i3-nagbar/i3-nagbar.h @@ -1,5 +1,5 @@ -#ifndef _I3_NAGBAR -#define _I3_NAGBAR +#ifndef I3_NAGBAR +#define I3_NAGBAR #include From e15e37f9228f798ca892bcdc9da6bd2f2462ab6a Mon Sep 17 00:00:00 2001 From: chrysn Date: Tue, 4 Sep 2012 10:51:18 +0200 Subject: [PATCH 010/146] fixes #776 this implements both the "move container to workspace back_and_forth" command and movements to the same workspace when auto_back_and_forth is set. it includes documentation and test suite additions by michael. it also simplifies the workspace_show_by_name function (making use of workspace_get accepting NULL pointers). --- docs/userguide | 4 ++- include/commands.h | 6 ++++ include/workspace.h | 6 ++++ parser-specs/commands.spec | 2 ++ src/commands.c | 59 +++++++++++++++++++++++++++++++++ src/workspace.c | 19 +++++++++-- testcases/t/176-workspace-baf.t | 40 ++++++++++++++++++++++ 7 files changed, 133 insertions(+), 3 deletions(-) diff --git a/docs/userguide b/docs/userguide index 2214f016..525cfd77 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1395,7 +1395,8 @@ RandR output. [[back_and_forth]] To switch back to the previously focused workspace, use +workspace -back_and_forth+. +back_and_forth+; likewise, you can move containers to the previously focused +workspace using +move container to workspace back_and_forth+. *Syntax*: ----------------------------------- @@ -1421,6 +1422,7 @@ bindsym mod+Shift+2 move container to workspace 2 # switch between the current and the previously focused one bindsym mod+b workspace back_and_forth +bindsym mod+Shift+b move container to workspace back_and_forth # move the whole workspace to the next output bindsym mod+x move workspace to output right diff --git a/include/commands.h b/include/commands.h index c971bb48..43bd0b0a 100644 --- a/include/commands.h +++ b/include/commands.h @@ -55,6 +55,12 @@ void cmd_criteria_add(I3_CMD, char *ctype, char *cvalue); */ void cmd_move_con_to_workspace(I3_CMD, char *which); +/** + * Implementation of 'move [window|container] [to] workspace back_and_forth'. + * + */ +void cmd_move_con_to_workspace_back_and_forth(I3_CMD); + /** * Implementation of 'move [window|container] [to] workspace '. * diff --git a/include/workspace.h b/include/workspace.h index ad780f1f..a7f2d13b 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -95,6 +95,12 @@ Con* workspace_prev_on_output(void); */ void workspace_back_and_forth(void); +/** + * Returns the previously focused workspace con, or NULL if unavailable. + * + */ +Con *workspace_back_and_forth_get(void); + #if 0 /** diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index b4c9e005..2c46e66c 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -243,6 +243,8 @@ state MOVE_WORKSPACE: -> MOVE_WORKSPACE_TO_OUTPUT workspace = 'next', 'prev', 'next_on_output', 'prev_on_output', 'current' -> call cmd_move_con_to_workspace($workspace) + 'back_and_forth' + -> call cmd_move_con_to_workspace_back_and_forth() 'number' -> MOVE_WORKSPACE_NUMBER workspace = string diff --git a/src/commands.c b/src/commands.c index 2d8fce3c..34f0f9db 100644 --- a/src/commands.c +++ b/src/commands.c @@ -99,6 +99,29 @@ static bool maybe_back_and_forth(struct CommandResult *cmd_output, char *name) { return true; } +/* + * Return the passed workspace unless it is the current one and auto back and + * forth is enabled, in which case the back_and_forth workspace is returned. + */ +static Con *maybe_auto_back_and_forth_workspace(Con *workspace) { + Con *current, *baf; + + if (!config.workspace_auto_back_and_forth) + return workspace; + + current = con_get_workspace(focused); + + if (current == workspace) { + baf = workspace_back_and_forth_get(); + if (baf != NULL) { + DLOG("Substituting workspace with back_and_forth, as it is focused.\n"); + return baf; + } + } + + return workspace; +} + // This code is commented out because we might recycle it for popping up error // messages on parser errors. #if 0 @@ -400,6 +423,38 @@ void cmd_move_con_to_workspace(I3_CMD, char *which) { ysuccess(true); } +/** + * Implementation of 'move [window|container] [to] workspace back_and_forth'. + * + */ +void cmd_move_con_to_workspace_back_and_forth(I3_CMD) { + owindow *current; + Con *ws; + + ws = workspace_back_and_forth_get(); + + if (ws == NULL) { + y(map_open); + ystr("success"); + y(bool, false); + ystr("error"); + ystr("No workspace was previously active."); + y(map_close); + return; + } + + HANDLE_EMPTY_MATCH; + + TAILQ_FOREACH(current, &owindows, owindows) { + DLOG("matching: %p / %s\n", current->con, current->con->name); + con_move_to_workspace(current->con, ws, true, false); + } + + cmd_output->needs_tree_render = true; + // XXX: default reply for now, make this a better reply + ysuccess(true); +} + /* * Implementation of 'move [window|container] [to] workspace '. * @@ -432,6 +487,8 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) { /* get the workspace */ Con *ws = workspace_get(name, NULL); + ws = maybe_auto_back_and_forth_workspace(ws); + HANDLE_EMPTY_MATCH; TAILQ_FOREACH(current, &owindows, owindows) { @@ -489,6 +546,8 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) { workspace = workspace_get(which, NULL); } + workspace = maybe_auto_back_and_forth_workspace(workspace); + HANDLE_EMPTY_MATCH; TAILQ_FOREACH(current, &owindows, owindows) { diff --git a/src/workspace.c b/src/workspace.c index 94efd47b..6f560ad9 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -392,8 +392,7 @@ void workspace_show(Con *workspace) { */ void workspace_show_by_name(const char *num) { Con *workspace; - bool changed_num_workspaces; - workspace = workspace_get(num, &changed_num_workspaces); + workspace = workspace_get(num, NULL); _workspace_show(workspace); } @@ -664,6 +663,22 @@ void workspace_back_and_forth(void) { workspace_show_by_name(previous_workspace_name); } +/* + * Returns the previously focused workspace con, or NULL if unavailable. + * + */ +Con *workspace_back_and_forth_get(void) { + if (!previous_workspace_name) { + DLOG("no previous workspace name set."); + return NULL; + } + + Con *workspace; + workspace = workspace_get(previous_workspace_name, NULL); + + return workspace; +} + static bool get_urgency_flag(Con *con) { Con *child; TAILQ_FOREACH(child, &(con->nodes_head), nodes) diff --git a/testcases/t/176-workspace-baf.t b/testcases/t/176-workspace-baf.t index 07c3c84a..8ed33030 100644 --- a/testcases/t/176-workspace-baf.t +++ b/testcases/t/176-workspace-baf.t @@ -47,6 +47,33 @@ ok(get_ws($second_ws)->{focused}, 'second workspace focused'); cmd qq|workspace "$second_ws"|; ok(get_ws($second_ws)->{focused}, 'second workspace still focused'); +################################################################################ +# verify that 'move workspace back_and_forth' works as expected +################################################################################ + +cmd qq|workspace "$first_ws"|; +my $first_win = open_window; + +cmd qq|workspace "$second_ws"|; +my $second_win = open_window; + +is(@{get_ws_content($first_ws)}, 1, 'one container on ws 1 before moving'); +cmd 'move workspace back_and_forth'; +is(@{get_ws_content($first_ws)}, 2, 'two containers on ws 1 before moving'); + +my $third_win = open_window; + +################################################################################ +# verify that moving to the current ws is a no-op without +# workspace_auto_back_and_forth. +################################################################################ + +cmd qq|workspace "$first_ws"|; + +is(@{get_ws_content($second_ws)}, 1, 'one container on ws 2 before moving'); +cmd qq|move workspace "$first_ws"|; +is(@{get_ws_content($second_ws)}, 1, 'still one container'); + exit_gracefully($pid); ##################################################################### @@ -72,6 +99,19 @@ ok(get_ws($third_ws)->{focused}, 'third workspace focused'); cmd qq|workspace "$third_ws"|; ok(get_ws($second_ws)->{focused}, 'second workspace focused'); +$first_win = open_window; + +################################################################################ +# verify that moving to the current ws moves to the previous one with +# workspace_auto_back_and_forth. +################################################################################ + +cmd qq|workspace "$first_ws"|; +$second_win = open_window; + +is(@{get_ws_content($second_ws)}, 1, 'one container on ws 2 before moving'); +cmd qq|move workspace "$first_ws"|; +is(@{get_ws_content($second_ws)}, 2, 'two containers on ws 2'); ################################################################################ # Now see if "workspace number " also works as expected with From 28104a480ca9a2ab135488ab98545d67e81a9f9d Mon Sep 17 00:00:00 2001 From: Simon Elsbrock Date: Sat, 22 Sep 2012 13:48:22 +0200 Subject: [PATCH 011/146] implement delayed urgency hint reset If there is a client with an urgency hint on another workspace and switching to this workspace would cause the urgency to be reset (by moving the focusing to the client), delay the reset by some time. This gives the user the chance to see it. This commit adds the possibility to configure the urgency delay timer duration using the 'force_display_urgency_hint' directive. Also, documentation and a testcase was added to allow for automated checks of the intended behavior. fixes #482 --- docs/userguide | 24 +++++++++ include/config.h | 7 +++ include/data.h | 3 ++ src/cfgparse.l | 2 + src/cfgparse.y | 16 ++++++ src/config.c | 4 ++ src/handlers.c | 8 ++- src/tree.c | 9 ++++ src/workspace.c | 58 ++++++++++++++++++-- testcases/t/198-urgency-timer.t | 95 +++++++++++++++++++++++++++++++++ 10 files changed, 221 insertions(+), 5 deletions(-) create mode 100644 testcases/t/198-urgency-timer.t diff --git a/docs/userguide b/docs/userguide index 525cfd77..bc208652 100644 --- a/docs/userguide +++ b/docs/userguide @@ -874,6 +874,30 @@ workspace_auto_back_and_forth workspace_auto_back_and_forth yes --------------------------------- +=== Delaying urgency hint reset on workspace change + +If an application on another workspace sets an urgency hint, switching to this +workspace may lead to immediate focus of the application, which also means the +window decoration color would be immediately resetted to +client.focused+. This +may make it unnecessarily hard to tell which window originally raised the +event. + +In order to prevent this, you can tell i3 to delay resetting the urgency state +by a certain time using the +force_display_urgency_hint+ directive. Setting the +value to 0 disables this feature. + +The default is 500ms. + +*Syntax*: +--------------------------------------- +force_display_urgency_hint ms +--------------------------------------- + +*Example*: +--------------------------------- +force_display_urgency_hint 500 ms +--------------------------------- + == Configuring i3bar The bar at the bottom of your monitor is drawn by a separate process called diff --git a/include/config.h b/include/config.h index 669cfe44..056aa5ae 100644 --- a/include/config.h +++ b/include/config.h @@ -149,6 +149,13 @@ struct Config { * between two workspaces. */ bool workspace_auto_back_and_forth; + /** By default, urgency is cleared immediately when switching to another + * workspace leads to focusing the con with the urgency hint. When having + * multiple windows on that workspace, the user needs to guess which + * application raised the event. To prevent this, the reset of the urgency + * flag can be delayed using an urgency timer. */ + float workspace_urgency_timer; + /** The default border style for new windows. */ border_style_t default_border; diff --git a/include/data.h b/include/data.h index a2c6859e..e78354f4 100644 --- a/include/data.h +++ b/include/data.h @@ -496,6 +496,9 @@ struct Con { * inside this container (if any) sets the urgency hint, for example. */ bool urgent; + /* timer used for disabling urgency */ + struct ev_timer *urgency_timer; + /* ids/pixmap/graphics context for the frame window */ xcb_window_t frame; xcb_pixmap_t pixmap; diff --git a/src/cfgparse.l b/src/cfgparse.l index 8ee2a1da..b752851b 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -212,6 +212,8 @@ force-xinerama { return TOK_FORCE_XINERAMA; } fake_outputs { WS_STRING; return TOK_FAKE_OUTPUTS; } fake-outputs { WS_STRING; return TOK_FAKE_OUTPUTS; } workspace_auto_back_and_forth { return TOK_WORKSPACE_AUTO_BAF; } +force_display_urgency_hint { return TOK_WORKSPACE_URGENCY_TIMER; } +ms { return TOK_TIME_MS; } workspace_bar { return TOKWORKSPACEBAR; } popup_during_fullscreen { return TOK_POPUP_DURING_FULLSCREEN; } ignore { return TOK_IGNORE; } diff --git a/src/cfgparse.y b/src/cfgparse.y index 29c519f0..bcd7d20c 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -728,6 +728,7 @@ void parse_file(const char *f) { %token TOKCOLOR %token TOKARROW "→" %token TOKMODE "mode" +%token TOK_TIME_MS "ms" %token TOK_BAR "bar" %token TOK_ORIENTATION "default_orientation" %token TOK_HORIZ "horizontal" @@ -746,6 +747,7 @@ void parse_file(const char *f) { %token TOK_FORCE_XINERAMA "force_xinerama" %token TOK_FAKE_OUTPUTS "fake_outputs" %token TOK_WORKSPACE_AUTO_BAF "workspace_auto_back_and_forth" +%token TOK_WORKSPACE_URGENCY_TIMER "force_display_urgency_hint" %token TOKWORKSPACEBAR "workspace_bar" %token TOK_DEFAULT "default" %token TOK_STACKING "stacking" @@ -819,6 +821,7 @@ void parse_file(const char *f) { %type optional_release %type command %type word_or_number +%type duration %type qstring_or_number %type optional_workspace_name %type workspace_name @@ -848,6 +851,7 @@ line: | force_focus_wrapping | force_xinerama | fake_outputs + | force_display_urgency_hint | workspace_back_and_forth | workspace_bar | workspace @@ -1052,6 +1056,10 @@ word_or_number: } ; +duration: + NUMBER TOK_TIME_MS { sasprintf(&$$, "%d", $1); } + ; + mode: TOKMODE QUOTEDSTRING '{' modelines '}' { @@ -1548,6 +1556,14 @@ workspace_back_and_forth: } ; +force_display_urgency_hint: + TOK_WORKSPACE_URGENCY_TIMER duration + { + DLOG("workspace urgency_timer = %f\n", atoi($2) / 1000.0); + config.workspace_urgency_timer = atoi($2) / 1000.0; + } + ; + workspace_bar: TOKWORKSPACEBAR bool { diff --git a/src/config.c b/src/config.c index 0bd6811a..0cfa8eb6 100644 --- a/src/config.c +++ b/src/config.c @@ -420,6 +420,10 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, /* Set default_orientation to NO_ORIENTATION for auto orientation. */ config.default_orientation = NO_ORIENTATION; + /* Set default urgency reset delay to 500ms */ + if (config.workspace_urgency_timer == 0) + config.workspace_urgency_timer = 0.5; + parse_configuration(override_configpath); if (reload) { diff --git a/src/handlers.c b/src/handlers.c index 21a87342..f9099cc1 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -836,7 +836,13 @@ static bool handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_ } /* Update the flag on the client directly */ - con->urgent = (xcb_icccm_wm_hints_get_urgency(&hints) != 0); + bool hint_urgent = (xcb_icccm_wm_hints_get_urgency(&hints) != 0); + + if (con->urgency_timer == NULL) { + con->urgent = hint_urgent; + } else + DLOG("Discarding urgency WM_HINT because timer is running\n"); + //CLIENT_LOG(con); if (con->window) { if (con->urgent) { diff --git a/src/tree.c b/src/tree.c index 321bc78a..4f34946c 100644 --- a/src/tree.c +++ b/src/tree.c @@ -255,6 +255,15 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool x_con_kill(con); con_detach(con); + + /* disable urgency timer, if needed */ + if (con->urgency_timer != NULL) { + DLOG("Removing urgency timer of con %p\n", con); + workspace_update_urgent_flag(con_get_workspace(con)); + ev_timer_stop(main_loop, con->urgency_timer); + FREE(con->urgency_timer); + } + if (con->type != CT_FLOATING_CON) { /* If the container is *not* floating, we might need to re-distribute * percentage values for the resized containers. */ diff --git a/src/workspace.c b/src/workspace.c index 6f560ad9..71102e5c 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -311,6 +311,23 @@ static void workspace_reassign_sticky(Con *con) { workspace_reassign_sticky(current); } +/* + * Callback to reset the urgent flag of the given con to false. May be started by + * _workspace_show to avoid urgency hints being lost by switching to a workspace + * focusing the con. + * + */ +static void workspace_defer_update_urgent_hint_cb(EV_P_ ev_timer *w, int revents) { + Con *con = w->data; + + DLOG("Resetting urgency flag of con %p by timer\n", con); + con->urgent = false; + workspace_update_urgent_flag(con_get_workspace(con)); + tree_render(); + + ev_timer_stop(main_loop, con->urgency_timer); + FREE(con->urgency_timer); +} static void _workspace_show(Con *workspace) { Con *current, *old = NULL; @@ -350,6 +367,43 @@ static void _workspace_show(Con *workspace) { LOG("switching to %p\n", workspace); Con *next = con_descend_focused(workspace); + /* Memorize current output */ + Con *old_output = con_get_output(focused); + + /* Display urgency hint for a while if the newly visible workspace would + * focus and thereby immediately destroy it */ + if (next->urgent && (int)(config.workspace_urgency_timer * 1000) > 0) { + /* focus for now… */ + con_focus(next); + + /* … but immediately reset urgency flags; they will be set to false by + * the timer callback in case the container is focused at the time of + * its expiration */ + focused->urgent = true; + workspace->urgent = true; + + if (focused->urgency_timer == NULL) { + DLOG("Deferring reset of urgency flag of con %p on newly shown workspace %p\n", + focused, workspace); + focused->urgency_timer = scalloc(sizeof(struct ev_timer)); + /* use a repeating timer to allow for easy resets */ + ev_timer_init(focused->urgency_timer, workspace_defer_update_urgent_hint_cb, + config.workspace_urgency_timer, config.workspace_urgency_timer); + focused->urgency_timer->data = focused; + ev_timer_start(main_loop, focused->urgency_timer); + } else { + DLOG("Resetting urgency timer of con %p on workspace %p\n", + focused, workspace); + ev_timer_again(main_loop, focused->urgency_timer); + } + } else + con_focus(next); + + /* Close old workspace if necessary. This must be done *after* doing + * urgency handling, because tree_close() will do a con_focus() on the next + * client, which will clear the urgency flag too early. Also, there is no + * way for con_focus() to know about when to clear urgency immediately and + * when to defer it. */ if (old && TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))) { /* check if this workspace is currently visible */ if (!workspace_is_visible(old)) { @@ -359,10 +413,6 @@ static void _workspace_show(Con *workspace) { } } - /* Memorize current output */ - Con *old_output = con_get_output(focused); - - con_focus(next); workspace->fullscreen_mode = CF_OUTPUT; LOG("focused now = %p / %s\n", focused, focused->name); diff --git a/testcases/t/198-urgency-timer.t b/testcases/t/198-urgency-timer.t new file mode 100644 index 00000000..d3cdb3d9 --- /dev/null +++ b/testcases/t/198-urgency-timer.t @@ -0,0 +1,95 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Tests whether the urgency timer works as expected and does not break +# urgency handling. +# + +use List::Util qw(first); +use i3test i3_autostart => 0; +use Time::HiRes qw(sleep); + +# Ensure the pointer is at (0, 0) so that we really start on the first +# (the left) workspace. +$x->root->warp_pointer(0, 0); + +my $config = <add_hint('urgency'); +sync_with_i3; + +####################################################################### +# Create a window on ws1, then switch to ws2, set urgency, switch back +####################################################################### + +isnt($x->input_focus, $w->id, 'window not focused'); + +my @content = @{get_ws_content($tmp1)}; +my @urgent = grep { $_->{urgent} } @content; +is(@urgent, 1, "window marked as urgent"); + +# switch to ws1 +cmd "workspace $tmp1"; + +# this will start the timer +sleep(0.1); +@content = @{get_ws_content($tmp1)}; +@urgent = grep { $_->{urgent} } @content; +is(@urgent, 1, 'window still marked as urgent'); + +# now check if the timer was triggered +cmd "workspace $tmp2"; + +sleep(0.1); +@content = @{get_ws_content($tmp1)}; +@urgent = grep { $_->{urgent} } @content; +is(@urgent, 0, 'window not marked as urgent anymore'); + +####################################################################### +# Create another window on ws1, focus it, switch to ws2, make the other +# window urgent, and switch back. This should not trigger the timer. +####################################################################### + +cmd "workspace $tmp1"; +my $w2 = open_window; +is($x->input_focus, $w2->id, 'window 2 focused'); + +cmd "workspace $tmp2"; +$w->add_hint('urgency'); +sync_with_i3; + +@content = @{get_ws_content($tmp1)}; +@urgent = grep { $_->{urgent} } @content; +is(@urgent, 1, 'window 1 marked as urgent'); + +# Switch back to ws1. This should focus w2. +cmd "workspace $tmp1"; +is($x->input_focus, $w2->id, 'window 2 focused'); + +@content = @{get_ws_content($tmp1)}; +@urgent = grep { $_->{urgent} } @content; +is(@urgent, 1, 'window 1 still marked as urgent'); + +# explicitly focusing the window should result in immediate urgency reset +cmd '[id="' . $w->id . '"] focus'; +@content = @{get_ws_content($tmp1)}; +@urgent = grep { $_->{urgent} } @content; +is(@urgent, 0, 'window 1 not marked as urgent anymore'); + +done_testing; From 9dbbe400a16d483fa5a42c00c71697866977a9d5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 22 Sep 2012 14:19:20 +0200 Subject: [PATCH 012/146] tests: fix t/113-urgent.t after previous commit --- testcases/t/113-urgent.t | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/testcases/t/113-urgent.t b/testcases/t/113-urgent.t index 10368532..58eff694 100644 --- a/testcases/t/113-urgent.t +++ b/testcases/t/113-urgent.t @@ -14,9 +14,17 @@ # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf # (unless you are already familiar with Perl) -use i3test; +use i3test i3_autostart => 0; use List::Util qw(first); +my $config = <input_focus, $bottom->id, 'oldest urgent window focused'); $bottom->delete_hint('urgency'); sync_with_i3; +exit_gracefully($pid); + done_testing; From 66d786762aef77f6228e5359a2d2f1c324a40508 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 22 Sep 2012 14:20:19 +0200 Subject: [PATCH 013/146] tests: rename double t/198, add boilerplate --- .../t/{198-urgency-timer.t => 200-urgency-timer.t} | 14 ++++++++++++++ 1 file changed, 14 insertions(+) rename testcases/t/{198-urgency-timer.t => 200-urgency-timer.t} (86%) diff --git a/testcases/t/198-urgency-timer.t b/testcases/t/200-urgency-timer.t similarity index 86% rename from testcases/t/198-urgency-timer.t rename to testcases/t/200-urgency-timer.t index d3cdb3d9..d6055114 100644 --- a/testcases/t/198-urgency-timer.t +++ b/testcases/t/200-urgency-timer.t @@ -1,6 +1,20 @@ #!perl # vim:ts=4:sw=4:expandtab # +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# # Tests whether the urgency timer works as expected and does not break # urgency handling. # From c69fba36627fda5000ce777bda922a77c9c9ff1e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 22 Sep 2012 14:21:35 +0200 Subject: [PATCH 014/146] cfgparse: be forgiving about a missing "ms" after a duration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While it’s certainly better and clearer to specify it, we should do the right thing when the unit is missing, just like CSS for example (margin: 0; is okay, margin: 0px; too). --- src/cfgparse.y | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cfgparse.y b/src/cfgparse.y index bcd7d20c..c3efb063 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -1057,7 +1057,8 @@ word_or_number: ; duration: - NUMBER TOK_TIME_MS { sasprintf(&$$, "%d", $1); } + NUMBER { sasprintf(&$$, "%d", $1); } + | NUMBER TOK_TIME_MS { sasprintf(&$$, "%d", $1); } ; mode: From 236f9f45e3ad35d8e549bc09b2999ce67e8ca292 Mon Sep 17 00:00:00 2001 From: Sebastian Ullrich Date: Fri, 31 Aug 2012 20:19:27 +0200 Subject: [PATCH 015/146] Make "[move] workspace number" accept a default ws name after the ws number --- docs/userguide | 7 ++++--- src/commands.c | 14 ++++++-------- testcases/t/117-workspace.t | 28 ++++++++++++++++++++++++++++ testcases/t/132-move-workspace.t | 22 ++++++++++++++++++++++ 4 files changed, 60 insertions(+), 11 deletions(-) diff --git a/docs/userguide b/docs/userguide index bc208652..e37b468f 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1427,10 +1427,10 @@ workspace using +move container to workspace back_and_forth+. workspace workspace back_and_forth workspace -workspace number +workspace number move [window|container] [to] workspace -move [window|container] [to] workspace number +move [window|container] [to] workspace number move [window|container] [to] workspace ----------------------------------- @@ -1482,7 +1482,8 @@ workspaces are ordered the way they appeared. When they start with a number, i3 will order them numerically. Also, you will be able to use +workspace number 1+ to switch to the workspace which begins with number 1, regardless of which name it has. This is useful in case you are changing the workspace’s name -dynamically. +dynamically. To combine both commands you can use +workspace number 1: mail+ to +specify a default name if there's currently no workspace starting with a "1". ==== Renaming workspaces diff --git a/src/commands.c b/src/commands.c index 34f0f9db..51ac28b1 100644 --- a/src/commands.c +++ b/src/commands.c @@ -502,7 +502,7 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) { } /* - * Implementation of 'move [window|container] [to] workspace number '. + * Implementation of 'move [window|container] [to] workspace number '. * */ void cmd_move_con_to_workspace_number(I3_CMD, char *which) { @@ -526,8 +526,8 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) { if (parsed_num == LONG_MIN || parsed_num == LONG_MAX || parsed_num < 0 || - *endptr != '\0') { - LOG("Could not parse \"%s\" as a number.\n", which); + endptr == which) { + LOG("Could not parse initial part of \"%s\" as a number.\n", which); y(map_open); ystr("success"); y(bool, false); @@ -866,7 +866,7 @@ void cmd_workspace(I3_CMD, char *which) { } /* - * Implementation of 'workspace number ' + * Implementation of 'workspace number ' * */ void cmd_workspace_number(I3_CMD, char *which) { @@ -877,8 +877,8 @@ void cmd_workspace_number(I3_CMD, char *which) { if (parsed_num == LONG_MIN || parsed_num == LONG_MAX || parsed_num < 0 || - *endptr != '\0') { - LOG("Could not parse \"%s\" as a number.\n", which); + endptr == which) { + LOG("Could not parse initial part of \"%s\" as a number.\n", which); y(map_open); ystr("success"); y(bool, false); @@ -897,8 +897,6 @@ void cmd_workspace_number(I3_CMD, char *which) { if (!workspace) { LOG("There is no workspace with number %ld, creating a new one.\n", parsed_num); ysuccess(true); - /* terminate the which string after the endposition of the number */ - *endptr = '\0'; workspace_show_by_name(which); cmd_output->needs_tree_render = true; return; diff --git a/testcases/t/117-workspace.t b/testcases/t/117-workspace.t index 7991abe5..2283ddc1 100644 --- a/testcases/t/117-workspace.t +++ b/testcases/t/117-workspace.t @@ -156,6 +156,34 @@ ok(!workspace_exists('5'), 'workspace 5 does not exist'); cmd 'workspace number 5'; ok(workspace_exists('5'), 'workspace 5 was created'); +################################################################################ +# Check that we can go to workspace "7: foo" with the command +# "workspace number 7: bar", i.e. the additional workspace name is ignored. +################################################################################ + +ok(!workspace_exists('7'), 'workspace 7 does not exist'); +ok(!workspace_exists('7: bar'), 'workspace 7: bar does not exist'); +ok(!workspace_exists('7: foo'), 'workspace 7: foo does not exist yet'); +cmd 'workspace 7: foo'; +ok(workspace_exists('7: foo'), 'workspace 7: foo was created'); +cmd 'open'; + +cmd 'workspace 6'; +ok(workspace_exists('7: foo'), 'workspace 7: foo still open'); +cmd 'workspace number 7: bar'; +is(focused_ws(), '7: foo', 'now on workspace 7: foo'); +ok(!workspace_exists('7'), 'workspace 7 still does not exist'); +ok(!workspace_exists('7: bar'), 'workspace 7: bar still does not exist'); + +################################################################################ +# Check that "workspace number 8: foo" will create workspace "8: foo" if it +# does not yet exist (just like "workspace 8: foo" would). +################################################################################ + +ok(!workspace_exists('8: foo'), 'workspace 8: foo does not exist'); +cmd 'workspace number 8: foo'; +ok(workspace_exists('8: foo'), 'workspace 8: foo was created'); + ################################################################################ # Verify that renaming workspaces works. ################################################################################ diff --git a/testcases/t/132-move-workspace.t b/testcases/t/132-move-workspace.t index ba26c85f..a4f6b608 100644 --- a/testcases/t/132-move-workspace.t +++ b/testcases/t/132-move-workspace.t @@ -79,6 +79,28 @@ is_num_children('12', 0, 'no container on 12 anymore'); ok(!workspace_exists('13'), 'workspace 13 does still not exist'); +################################################################################ +# Check that 'move to workspace number ' works to move a window to +# named workspaces which start with . +################################################################################ + +cmd 'workspace 15: meh'; +cmd 'open'; +is_num_children('15: meh', 1, 'one container on 15: meh'); + +ok(!workspace_exists('15'), 'workspace 15 does not exist yet'); +ok(!workspace_exists('15: duh'), 'workspace 15: duh does not exist yet'); + +cmd 'workspace 14'; +cmd 'open'; + +cmd 'move to workspace number 15: duh'; +is_num_children('15: meh', 2, 'two containers on 15: meh'); +is_num_children('14', 0, 'no container on 14 anymore'); + +ok(!workspace_exists('15'), 'workspace 15 does still not exist'); +ok(!workspace_exists('15: duh'), 'workspace 15 does still not exist'); + ################################################################### # check if 'move workspace next' and 'move workspace prev' work ################################################################### From 2fba607e4dbd7b346dcc1c5831df549d0da325c5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 22 Sep 2012 14:51:38 +0200 Subject: [PATCH 016/146] tests: Bugfix: use exit_gracefully() in t/200-urgency-timer --- testcases/t/200-urgency-timer.t | 2 ++ 1 file changed, 2 insertions(+) diff --git a/testcases/t/200-urgency-timer.t b/testcases/t/200-urgency-timer.t index d6055114..9e1a90a5 100644 --- a/testcases/t/200-urgency-timer.t +++ b/testcases/t/200-urgency-timer.t @@ -106,4 +106,6 @@ cmd '[id="' . $w->id . '"] focus'; @urgent = grep { $_->{urgent} } @content; is(@urgent, 0, 'window 1 not marked as urgent anymore'); +exit_gracefully($pid); + done_testing; From 2e5838fb49f6e3ed2a0311dedcd73aaca64c0ed1 Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Tue, 21 Aug 2012 13:49:48 +0200 Subject: [PATCH 017/146] i3bar: Rework unhide/hide on workspace urgency We now check globally for workspace urgency instead of per-output since the result is the same but we call unhide_bars/hide_bars only once this way --- i3bar/src/xcb.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 861925b9..aedf6392 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -1404,6 +1404,10 @@ void draw_bars(void) { refresh_statusline(); + static char *last_urgent_ws = NULL; + bool unhide = false; + bool walks_away = true; + i3_output *outputs_walk; SLIST_FOREACH(outputs_walk, outputs, slist) { if (!outputs_walk->active) { @@ -1460,8 +1464,6 @@ void draw_bars(void) { } i3_ws *ws_walk; - static char *last_urgent_ws = NULL; - bool has_urgent = false, walks_away = true; TAILQ_FOREACH(ws_walk, outputs_walk->workspaces, tailq) { DLOG("Drawing Button for WS %s at x = %d, len = %d\n", i3string_as_utf8(ws_walk->name), i, ws_walk->name_width); @@ -1486,13 +1488,11 @@ void draw_bars(void) { fg_color = colors.urgent_ws_fg; bg_color = colors.urgent_ws_bg; border_color = colors.urgent_ws_border; - has_urgent = true; + unhide = true; if (!ws_walk->focused) { FREE(last_urgent_ws); last_urgent_ws = sstrdup(i3string_as_utf8(ws_walk->name)); } - /* The urgent-hint should get noticed, so we unhide the bars shortly */ - unhide_bars(); } uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND; uint32_t vals_border[] = { border_color, border_color }; @@ -1522,12 +1522,17 @@ void draw_bars(void) { i += 10 + ws_walk->name_width + 1; } - if (!has_urgent && !mod_pressed && walks_away) { + i = 0; + } + + if (!mod_pressed) { + if (unhide) { + /* The urgent-hint should get noticed, so we unhide the bars shortly */ + unhide_bars(); + } else if (walks_away) { FREE(last_urgent_ws); hide_bars(); } - - i = 0; } redraw_bars(); From f1f0de1c49ab9a5de16c08292b1c2e277cb45a8a Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Mon, 20 Aug 2012 20:43:06 +0200 Subject: [PATCH 018/146] docs/i3bar-protocol: Document stop/cont_signal --- docs/i3bar-protocol | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/i3bar-protocol b/docs/i3bar-protocol index 21ba9aa0..29ce5713 100644 --- a/docs/i3bar-protocol +++ b/docs/i3bar-protocol @@ -44,11 +44,16 @@ understand the old protocol version, but in order to use the new one, you need to provide the correct version. The header block is terminated by a newline and consists of a single JSON hash: -*Example*: +*Minimal example*: ---------------- { "version": 1 } ---------------- +*All features example*: +---------------- +{ "version": 1, "stop_signal": 10, "cont_signal": 12 } +---------------- + (Note that before i3 v4.3 the precise format had to be +{"version":1}+, byte-for-byte.) @@ -93,6 +98,19 @@ You can find an example of a shell script which can be used as your +status_command+ in the bar configuration at http://code.stapelberg.de/git/i3/tree/contrib/trivial-bar-script.sh?h=next +=== Header in detail + +version:: + The version number (as an integer) of the i3bar protocol you will use. +stop_signal:: + Specify to i3bar the signal (as an integer) to send to stop your + processing. + The default value (if none is specified) is SIGSTOP. +cont_signal:: + Specify to i3bar the signal (as an integer)to send to continue your + processing. + The default value (if none is specified) is SIGCONT. + === Blocks in detail full_text:: From 13ecc79fcc1f91271617d531237780e7f4a06012 Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Wed, 22 Aug 2012 17:02:02 +0200 Subject: [PATCH 019/146] i3bar: Rename determine_json_version to parse_json_header --- i3bar/include/common.h | 2 +- i3bar/include/{determine_json_version.h => parse_json_header.h} | 0 i3bar/src/{determine_json_version.c => parse_json_header.c} | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename i3bar/include/{determine_json_version.h => parse_json_header.h} (100%) rename i3bar/src/{determine_json_version.c => parse_json_header.c} (100%) diff --git a/i3bar/include/common.h b/i3bar/include/common.h index 6f8a7b2d..0893e953 100644 --- a/i3bar/include/common.h +++ b/i3bar/include/common.h @@ -52,6 +52,6 @@ TAILQ_HEAD(statusline_head, status_block) statusline_head; #include "xcb.h" #include "config.h" #include "libi3.h" -#include "determine_json_version.h" +#include "parse_json_header.h" #endif diff --git a/i3bar/include/determine_json_version.h b/i3bar/include/parse_json_header.h similarity index 100% rename from i3bar/include/determine_json_version.h rename to i3bar/include/parse_json_header.h diff --git a/i3bar/src/determine_json_version.c b/i3bar/src/parse_json_header.c similarity index 100% rename from i3bar/src/determine_json_version.c rename to i3bar/src/parse_json_header.c From 3732cef764eef75e30ead4d385dac7718ed2cef2 Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Mon, 3 Sep 2012 09:45:59 +0200 Subject: [PATCH 020/146] i3bar: Split stdin reading logic to get_buffer --- i3bar/src/child.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/i3bar/src/child.c b/i3bar/src/child.c index 058ddb7a..2ef22845 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -148,11 +148,10 @@ static int stdin_end_array(void *context) { } /* - * Callbalk for stdin. We read a line from stdin and store the result - * in statusline + * Helper function to read stdin * */ -void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) { +static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) { int fd = watcher->fd; int n = 0; int rec = 0; @@ -174,7 +173,8 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) { ELOG("stdin: received EOF\n"); cleanup(); draw_bars(); - return; + *ret_buffer_len = -1; + return NULL; } rec += n; @@ -185,9 +185,22 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) { } if (*buffer == '\0') { FREE(buffer); - return; + rec = -1; } + *ret_buffer_len = rec; + return buffer; +} +/* + * Callbalk for stdin. We read a line from stdin and store the result + * in statusline + * + */ +void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) { + int rec; + unsigned char *buffer = get_buffer(watcher, &rec); + if (buffer == NULL) + return; unsigned char *json_input = buffer; if (first_line) { DLOG("Detecting input type based on buffer *%.*s*\n", rec, buffer); @@ -195,7 +208,7 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) { unsigned int consumed = 0; /* At the moment, we don’t care for the version. This might change * in the future, but for now, we just discard it. */ - plaintext = (determine_json_version(buffer, buffer_len, &consumed) == -1); + plaintext = (determine_json_version(buffer, rec, &consumed) == -1); if (plaintext) { /* In case of plaintext, we just add a single block and change its * full_text pointer later. */ From 103b1a3f3a12cb3c18e29671cc02acbaa755efdb Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Mon, 3 Sep 2012 09:52:17 +0200 Subject: [PATCH 021/146] i3bar: Split flat line logic to read_flat_input --- i3bar/src/child.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/i3bar/src/child.c b/i3bar/src/child.c index 2ef22845..227a1435 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -191,6 +191,18 @@ static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) { return buffer; } +static void read_flat_input(char *buffer, int length) { + struct status_block *first = TAILQ_FIRST(&statusline_head); + /* Clear the old buffer if any. */ + I3STRING_FREE(first->full_text); + /* Remove the trailing newline and terminate the string at the same + * time. */ + if (buffer[length-1] == '\n' || buffer[length-1] == '\r') + buffer[length-1] = '\0'; + else buffer[length] = '\0'; + first->full_text = i3string_from_utf8(buffer); +} + /* * Callbalk for stdin. We read a line from stdin and store the result * in statusline @@ -231,15 +243,7 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) { status, rec, json_input); } } else { - struct status_block *first = TAILQ_FIRST(&statusline_head); - /* Clear the old buffer if any. */ - I3STRING_FREE(first->full_text); - /* Remove the trailing newline and terminate the string at the same - * time. */ - if (buffer[rec-1] == '\n' || buffer[rec-1] == '\r') - buffer[rec-1] = '\0'; - else buffer[rec] = '\0'; - first->full_text = i3string_from_utf8((const char *)buffer); + read_flat_input((char*)buffer, rec); } free(buffer); draw_bars(); From f691927aa7e857ea834698cefef6f9c230b87f17 Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Mon, 3 Sep 2012 09:52:17 +0200 Subject: [PATCH 022/146] i3bar: Split JSON line logic to read_json_input --- i3bar/src/child.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/i3bar/src/child.c b/i3bar/src/child.c index 227a1435..9236de3f 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -203,6 +203,18 @@ static void read_flat_input(char *buffer, int length) { first->full_text = i3string_from_utf8(buffer); } +static void read_json_input(unsigned char *input, int length) { + yajl_status status = yajl_parse(parser, input, length); +#if YAJL_MAJOR >= 2 + if (status != yajl_status_ok) { +#else + if (status != yajl_status_ok && status != yajl_status_insufficient_data) { +#endif + fprintf(stderr, "[i3bar] Could not parse JSON input (code %d): %.*s\n", + status, length, input); + } +} + /* * Callbalk for stdin. We read a line from stdin and store the result * in statusline @@ -233,15 +245,7 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) { first_line = false; } if (!plaintext) { - yajl_status status = yajl_parse(parser, json_input, rec); -#if YAJL_MAJOR >= 2 - if (status != yajl_status_ok) { -#else - if (status != yajl_status_ok && status != yajl_status_insufficient_data) { -#endif - fprintf(stderr, "[i3bar] Could not parse JSON input (code %d): %.*s\n", - status, rec, json_input); - } + read_json_input(json_input, rec); } else { read_flat_input((char*)buffer, rec); } From 310ae2d0b5df345b06bc8014721691bc0f521e2f Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Mon, 3 Sep 2012 10:11:01 +0200 Subject: [PATCH 023/146] i3bar: Handle the first line with another callback --- i3bar/src/child.c | 55 ++++++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/i3bar/src/child.c b/i3bar/src/child.c index 9236de3f..4c91da3d 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -32,7 +32,6 @@ ev_io *stdin_io; ev_child *child_sig; /* JSON parser for stdin */ -bool first_line = true; bool plaintext = false; yajl_callbacks callbacks; yajl_handle parser; @@ -225,27 +224,8 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) { unsigned char *buffer = get_buffer(watcher, &rec); if (buffer == NULL) return; - unsigned char *json_input = buffer; - if (first_line) { - DLOG("Detecting input type based on buffer *%.*s*\n", rec, buffer); - /* Detect whether this is JSON or plain text. */ - unsigned int consumed = 0; - /* At the moment, we don’t care for the version. This might change - * in the future, but for now, we just discard it. */ - plaintext = (determine_json_version(buffer, rec, &consumed) == -1); - if (plaintext) { - /* In case of plaintext, we just add a single block and change its - * full_text pointer later. */ - struct status_block *new_block = scalloc(sizeof(struct status_block)); - TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks); - } else { - json_input += consumed; - rec -= consumed; - } - first_line = false; - } if (!plaintext) { - read_json_input(json_input, rec); + read_json_input(buffer, rec); } else { read_flat_input((char*)buffer, rec); } @@ -253,6 +233,37 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) { draw_bars(); } +/* + * Callbalk for stdin first line. We read the first line to detect + * whether this is JSON or plain text + * + */ +void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) { + int rec; + unsigned char *buffer = get_buffer(watcher, &rec); + if (buffer == NULL) + return; + DLOG("Detecting input type based on buffer *%.*s*\n", rec, buffer); + /* Detect whether this is JSON or plain text. */ + unsigned int consumed = 0; + /* At the moment, we don’t care for the version. This might change + * in the future, but for now, we just discard it. */ + plaintext = (determine_json_version(buffer, rec, &consumed) == -1); + if (plaintext) { + /* In case of plaintext, we just add a single block and change its + * full_text pointer later. */ + struct status_block *new_block = scalloc(sizeof(struct status_block)); + TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks); + read_flat_input((char*)buffer, rec); + } else { + read_json_input(buffer + consumed, rec - consumed); + } + free(buffer); + ev_io_stop(main_loop, stdin_io); + ev_io_init(stdin_io, &stdin_io_cb, STDIN_FILENO, EV_READ); + ev_io_start(main_loop, stdin_io); +} + /* * We received a sigchild, meaning, that the child-process terminated. * We simply free the respective data-structures and don't care for input @@ -333,7 +344,7 @@ void start_child(char *command) { fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); stdin_io = smalloc(sizeof(ev_io)); - ev_io_init(stdin_io, &stdin_io_cb, STDIN_FILENO, EV_READ); + ev_io_init(stdin_io, &stdin_io_first_line_cb, STDIN_FILENO, EV_READ); ev_io_start(main_loop, stdin_io); /* We must cleanup, if the child unexpectedly terminates */ From 34dc6d4d64a91acd9979327142885cada3bbfa3c Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Mon, 3 Sep 2012 10:23:25 +0200 Subject: [PATCH 024/146] i3bar: Introduce i3bar_child struct --- i3bar/include/child.h | 11 ++++++++++ i3bar/src/child.c | 47 +++++++++++++++++++++---------------------- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/i3bar/include/child.h b/i3bar/include/child.h index c0b56a01..38132e5d 100644 --- a/i3bar/include/child.h +++ b/i3bar/include/child.h @@ -12,6 +12,17 @@ #define STDIN_CHUNK_SIZE 1024 +typedef struct { + pid_t pid; + + /** + * The version number is an uint32_t to avoid machines with different sizes of + * 'int' to allow different values here. It’s highly unlikely we ever exceed + * even an int8_t, but still… + */ + uint32_t version; +} i3bar_child; + /* * Start a child-process with the specified command and reroute stdin. * We actually start a $SHELL to execute the command so we don't have to care diff --git a/i3bar/src/child.c b/i3bar/src/child.c index 4c91da3d..22a028a6 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -25,14 +25,13 @@ #include "common.h" /* Global variables for child_*() */ -pid_t child_pid; +i3bar_child child = { 0 }; /* stdin- and sigchild-watchers */ ev_io *stdin_io; ev_child *child_sig; /* JSON parser for stdin */ -bool plaintext = false; yajl_callbacks callbacks; yajl_handle parser; @@ -68,6 +67,8 @@ void cleanup(void) { ev_child_stop(main_loop, child_sig); FREE(child_sig); } + + memset(&child, 0, sizeof(i3bar_child)); } /* @@ -224,7 +225,7 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) { unsigned char *buffer = get_buffer(watcher, &rec); if (buffer == NULL) return; - if (!plaintext) { + if (child.version > 0) { read_json_input(buffer, rec); } else { read_flat_input((char*)buffer, rec); @@ -248,15 +249,15 @@ void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) { unsigned int consumed = 0; /* At the moment, we don’t care for the version. This might change * in the future, but for now, we just discard it. */ - plaintext = (determine_json_version(buffer, rec, &consumed) == -1); - if (plaintext) { + child.version = determine_json_version(buffer, rec, &consumed); + if (child.version > 0) { + read_json_input(buffer + consumed, rec - consumed); + } else { /* In case of plaintext, we just add a single block and change its * full_text pointer later. */ struct status_block *new_block = scalloc(sizeof(struct status_block)); TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks); read_flat_input((char*)buffer, rec); - } else { - read_json_input(buffer + consumed, rec - consumed); } free(buffer); ev_io_stop(main_loop, stdin_io); @@ -272,7 +273,7 @@ void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) { */ void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) { ELOG("Child (pid: %d) unexpectedly exited with status %d\n", - child_pid, + child.pid, watcher->rstatus); cleanup(); } @@ -300,14 +301,13 @@ void start_child(char *command) { parser = yajl_alloc(&callbacks, NULL, &parser_context); #endif - child_pid = 0; if (command != NULL) { int fd[2]; if (pipe(fd) == -1) err(EXIT_FAILURE, "pipe(fd)"); - child_pid = fork(); - switch (child_pid) { + child.pid = fork(); + switch (child.pid) { case -1: ELOG("Couldn't fork(): %s\n", strerror(errno)); exit(EXIT_FAILURE); @@ -349,7 +349,7 @@ void start_child(char *command) { /* We must cleanup, if the child unexpectedly terminates */ child_sig = smalloc(sizeof(ev_child)); - ev_child_init(child_sig, &child_sig_cb, child_pid, 0); + ev_child_init(child_sig, &child_sig_cb, child.pid, 0); ev_child_start(main_loop, child_sig); atexit(kill_child_at_exit); @@ -360,9 +360,9 @@ void start_child(char *command) { * */ void kill_child_at_exit(void) { - if (child_pid != 0) { - kill(child_pid, SIGCONT); - kill(child_pid, SIGTERM); + if (child.pid > 0) { + kill(child.pid, SIGCONT); + kill(child.pid, SIGTERM); } } @@ -372,12 +372,11 @@ void kill_child_at_exit(void) { * */ void kill_child(void) { - if (child_pid != 0) { - kill(child_pid, SIGCONT); - kill(child_pid, SIGTERM); + if (child.pid > 0) { + kill(child.pid, SIGCONT); + kill(child.pid, SIGTERM); int status; - waitpid(child_pid, &status, 0); - child_pid = 0; + waitpid(child.pid, &status, 0); cleanup(); } } @@ -387,8 +386,8 @@ void kill_child(void) { * */ void stop_child(void) { - if (child_pid != 0) { - kill(child_pid, SIGSTOP); + if (child.pid > 0) { + kill(child.pid, SIGSTOP); } } @@ -397,7 +396,7 @@ void stop_child(void) { * */ void cont_child(void) { - if (child_pid != 0) { - kill(child_pid, SIGCONT); + if (child.pid > 0) { + kill(child.pid, SIGCONT); } } From 1e114d7ab5296a6d9edb58653039bd65098b412c Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Mon, 3 Sep 2012 10:43:29 +0200 Subject: [PATCH 025/146] i3bar: Fully parse the JSON header --- i3bar/include/parse_json_header.h | 10 ++-- i3bar/include/util.h | 2 + i3bar/src/child.c | 2 +- i3bar/src/parse_json_header.c | 77 ++++++++++++++++++------------- 4 files changed, 54 insertions(+), 37 deletions(-) diff --git a/i3bar/include/parse_json_header.h b/i3bar/include/parse_json_header.h index 52c6f5d2..6495eeb1 100644 --- a/i3bar/include/parse_json_header.h +++ b/i3bar/include/parse_json_header.h @@ -4,12 +4,12 @@ * i3bar - an xcb-based status- and ws-bar for i3 * © 2010-2012 Axel Wagner and contributors (see also: LICENSE) * - * determine_json_version.c: Determines the JSON protocol version based on the - * first line of input from a child program. + * parse_json_header.c: Parse the JSON protocol header to determine + * protocol version and features. * */ -#ifndef DETERMINE_JSON_VERSION_H_ -#define DETERMINE_JSON_VERSION_H_ +#ifndef PARSE_JSON_HEADER_H_ +#define PARSE_JSON_HEADER_H_ #include @@ -24,6 +24,6 @@ * even an int8_t, but still… * */ -int32_t determine_json_version(const unsigned char *buffer, int length, unsigned int *consumed); +void parse_json_header(i3bar_child *child, const unsigned char *buffer, int length, unsigned int *consumed); #endif diff --git a/i3bar/include/util.h b/i3bar/include/util.h index 43c56c58..6ae97815 100644 --- a/i3bar/include/util.h +++ b/i3bar/include/util.h @@ -16,6 +16,8 @@ #undef MIN #define MIN(x,y) ((x) < (y) ? (x) : (y)) +#define STARTS_WITH(string, len, needle) ((len >= strlen(needle)) && strncasecmp(string, needle, strlen(needle)) == 0) + /* Securely free p */ #define FREE(p) do { \ if (p != NULL) { \ diff --git a/i3bar/src/child.c b/i3bar/src/child.c index 22a028a6..f3832d9c 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -249,7 +249,7 @@ void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) { unsigned int consumed = 0; /* At the moment, we don’t care for the version. This might change * in the future, but for now, we just discard it. */ - child.version = determine_json_version(buffer, rec, &consumed); + parse_json_header(&child, buffer, rec, &consumed); if (child.version > 0) { read_json_input(buffer + consumed, rec - consumed); } else { diff --git a/i3bar/src/parse_json_header.c b/i3bar/src/parse_json_header.c index abd43038..a3fefb8a 100644 --- a/i3bar/src/parse_json_header.c +++ b/i3bar/src/parse_json_header.c @@ -4,8 +4,8 @@ * i3bar - an xcb-based status- and ws-bar for i3 * © 2010-2012 Axel Wagner and contributors (see also: LICENSE) * - * determine_json_version.c: Determines the JSON protocol version based on the - * first line of input from a child program. + * parse_json_header.c: Parse the JSON protocol header to determine + * protocol version and features. * */ #include @@ -25,72 +25,89 @@ #include #include -static bool version_key; -static int32_t version_number; +#include "common.h" + +static enum { + KEY_VERSION, + NO_KEY +} current_key; #if YAJL_MAJOR >= 2 -static int version_integer(void *ctx, long long val) { +static int header_integer(void *ctx, long long val) { #else -static int version_integer(void *ctx, long val) { +static int header_integer(void *ctx, long val) { #endif - if (version_key) - version_number = (uint32_t)val; + i3bar_child *child = ctx; + + switch (current_key) { + case KEY_VERSION: + child->version = val; + break; + default: + break; + } return 1; } +#define CHECK_KEY(name) (stringlen == strlen(name) && \ + STARTS_WITH((const char*)stringval, stringlen, name)) + #if YAJL_MAJOR >= 2 -static int version_map_key(void *ctx, const unsigned char *stringval, size_t stringlen) { +static int header_map_key(void *ctx, const unsigned char *stringval, size_t stringlen) { #else -static int version_map_key(void *ctx, const unsigned char *stringval, unsigned int stringlen) { +static int header_map_key(void *ctx, const unsigned char *stringval, unsigned int stringlen) { #endif - version_key = (stringlen == strlen("version") && - strncmp((const char*)stringval, "version", strlen("version")) == 0); + if (CHECK_KEY("version")) { + current_key = KEY_VERSION; + } return 1; } static yajl_callbacks version_callbacks = { NULL, /* null */ NULL, /* boolean */ - &version_integer, + &header_integer, NULL, /* double */ NULL, /* number */ NULL, /* string */ NULL, /* start_map */ - &version_map_key, + &header_map_key, NULL, /* end_map */ NULL, /* start_array */ NULL /* end_array */ }; +static void child_init(i3bar_child *child) { + child->version = 0; +} + /* - * Determines the JSON i3bar protocol version from the given buffer. In case - * the buffer does not contain valid JSON, or no version field is found, this - * function returns -1. The amount of bytes consumed by parsing the header is - * returned in *consumed (if non-NULL). - * - * The return type is an int32_t to avoid machines with different sizes of - * 'int' to allow different values here. It’s highly unlikely we ever exceed - * even an int8_t, but still… + * Parse the JSON protocol header to determine protocol version and features. + * In case the buffer does not contain a valid header (invalid JSON, or no + * version field found), the 'correct' field of the returned header is set to + * false. The amount of bytes consumed by parsing the header is returned in + * *consumed (if non-NULL). * */ -int32_t determine_json_version(const unsigned char *buffer, int length, unsigned int *consumed) { +void parse_json_header(i3bar_child *child, const unsigned char *buffer, int length, unsigned int *consumed) { + child_init(child); + + current_key = NO_KEY; + #if YAJL_MAJOR >= 2 - yajl_handle handle = yajl_alloc(&version_callbacks, NULL, NULL); + yajl_handle handle = yajl_alloc(&version_callbacks, NULL, child); /* Allow trailing garbage. yajl 1 always behaves that way anyways, but for * yajl 2, we need to be explicit. */ yajl_config(handle, yajl_allow_trailing_garbage, 1); #else yajl_parser_config parse_conf = { 0, 0 }; - yajl_handle handle = yajl_alloc(&version_callbacks, &parse_conf, NULL, NULL); + yajl_handle handle = yajl_alloc(&version_callbacks, &parse_conf, NULL, child); #endif - version_key = false; - version_number = -1; - yajl_status state = yajl_parse(handle, buffer, length); if (state != yajl_status_ok) { - version_number = -1; + child_init(child); if (consumed != NULL) *consumed = 0; } else { @@ -99,6 +116,4 @@ int32_t determine_json_version(const unsigned char *buffer, int length, unsigned } yajl_free(handle); - - return version_number; } From 8210c6be7999d8614a9fd0814bab750fd5f5c4d5 Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Mon, 20 Aug 2012 22:20:37 +0200 Subject: [PATCH 026/146] i3bar: Allow child to specify signals to use We now wait for the child process to send the first line before stopping it to use the signal which might be specified in the i3bar protocol header Since clients might use the same signal for both stop and cont, we also save the stopped state of the child to avoid stopping it while hidden! --- i3bar/include/child.h | 12 ++++++++++++ i3bar/include/parse_json_header.h | 15 ++++++--------- i3bar/src/child.c | 27 +++++++++++++++------------ i3bar/src/parse_json_header.c | 14 ++++++++++++++ 4 files changed, 47 insertions(+), 21 deletions(-) diff --git a/i3bar/include/child.h b/i3bar/include/child.h index 38132e5d..d1c46890 100644 --- a/i3bar/include/child.h +++ b/i3bar/include/child.h @@ -10,6 +10,8 @@ #ifndef CHILD_H_ #define CHILD_H_ +#include + #define STDIN_CHUNK_SIZE 1024 typedef struct { @@ -21,6 +23,16 @@ typedef struct { * even an int8_t, but still… */ uint32_t version; + + bool stopped; + /** + * The signal requested by the client to inform it of the hidden state of i3bar + */ + int stop_signal; + /** + * The signal requested by the client to inform it of theun hidden state of i3bar + */ + int cont_signal; } i3bar_child; /* diff --git a/i3bar/include/parse_json_header.h b/i3bar/include/parse_json_header.h index 6495eeb1..79efddc6 100644 --- a/i3bar/include/parse_json_header.h +++ b/i3bar/include/parse_json_header.h @@ -13,15 +13,12 @@ #include -/* - * Determines the JSON i3bar protocol version from the given buffer. In case - * the buffer does not contain valid JSON, or no version field is found, this - * function returns -1. The amount of bytes consumed by parsing the header is - * returned in *consumed (if non-NULL). - * - * The return type is an int32_t to avoid machines with different sizes of - * 'int' to allow different values here. It’s highly unlikely we ever exceed - * even an int8_t, but still… +/** + * Parse the JSON protocol header to determine protocol version and features. + * In case the buffer does not contain a valid header (invalid JSON, or no + * version field found), the 'correct' field of the returned header is set to + * false. The amount of bytes consumed by parsing the header is returned in + * *consumed (if non-NULL). * */ void parse_json_header(i3bar_child *child, const unsigned char *buffer, int length, unsigned int *consumed); diff --git a/i3bar/src/child.c b/i3bar/src/child.c index f3832d9c..e4c0f357 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -251,6 +251,11 @@ void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) { * in the future, but for now, we just discard it. */ parse_json_header(&child, buffer, rec, &consumed); if (child.version > 0) { + /* If hide-on-modifier is set, we start of by sending the + * child a SIGSTOP, because the bars aren't mapped at start */ + if (config.hide_on_modifier) { + stop_child(); + } read_json_input(buffer + consumed, rec - consumed); } else { /* In case of plaintext, we just add a single block and change its @@ -330,12 +335,6 @@ void start_child(char *command) { dup2(fd[0], STDIN_FILENO); - /* If hide-on-modifier is set, we start of by sending the - * child a SIGSTOP, because the bars aren't mapped at start */ - if (config.hide_on_modifier) { - stop_child(); - } - break; } } @@ -361,7 +360,8 @@ void start_child(char *command) { */ void kill_child_at_exit(void) { if (child.pid > 0) { - kill(child.pid, SIGCONT); + if (child.cont_signal > 0 && child.stopped) + kill(child.pid, child.cont_signal); kill(child.pid, SIGTERM); } } @@ -373,7 +373,8 @@ void kill_child_at_exit(void) { */ void kill_child(void) { if (child.pid > 0) { - kill(child.pid, SIGCONT); + if (child.cont_signal > 0 && child.stopped) + kill(child.pid, child.cont_signal); kill(child.pid, SIGTERM); int status; waitpid(child.pid, &status, 0); @@ -386,8 +387,9 @@ void kill_child(void) { * */ void stop_child(void) { - if (child.pid > 0) { - kill(child.pid, SIGSTOP); + if (child.stop_signal > 0 && !child.stopped) { + child.stopped = true; + kill(child.pid, child.stop_signal); } } @@ -396,7 +398,8 @@ void stop_child(void) { * */ void cont_child(void) { - if (child.pid > 0) { - kill(child.pid, SIGCONT); + if (child.cont_signal > 0 && child.stopped) { + child.stopped = false; + kill(child.pid, child.cont_signal); } } diff --git a/i3bar/src/parse_json_header.c b/i3bar/src/parse_json_header.c index a3fefb8a..80ec5af8 100644 --- a/i3bar/src/parse_json_header.c +++ b/i3bar/src/parse_json_header.c @@ -29,6 +29,8 @@ static enum { KEY_VERSION, + KEY_STOP_SIGNAL, + KEY_CONT_SIGNAL, NO_KEY } current_key; @@ -43,6 +45,12 @@ static int header_integer(void *ctx, long val) { case KEY_VERSION: child->version = val; break; + case KEY_STOP_SIGNAL: + child->stop_signal = val; + break; + case KEY_CONT_SIGNAL: + child->cont_signal = val; + break; default: break; } @@ -59,6 +67,10 @@ static int header_map_key(void *ctx, const unsigned char *stringval, unsigned in #endif if (CHECK_KEY("version")) { current_key = KEY_VERSION; + } else if (CHECK_KEY("stop_signal")) { + current_key = KEY_STOP_SIGNAL; + } else if (CHECK_KEY("cont_signal")) { + current_key = KEY_CONT_SIGNAL; } return 1; } @@ -79,6 +91,8 @@ static yajl_callbacks version_callbacks = { static void child_init(i3bar_child *child) { child->version = 0; + child->stop_signal = SIGSTOP; + child->cont_signal = SIGCONT; } /* From 830829922b1f2c47f4c7d7065ee58162bed14c93 Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Tue, 21 Aug 2012 13:51:40 +0200 Subject: [PATCH 027/146] i3bar: Allow to force unhide with draw_bars --- i3bar/include/xcb.h | 2 +- i3bar/src/child.c | 4 ++-- i3bar/src/ipc.c | 4 ++-- i3bar/src/xcb.c | 11 +++++------ 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/i3bar/include/xcb.h b/i3bar/include/xcb.h index 6c7bc567..dcc4d781 100644 --- a/i3bar/include/xcb.h +++ b/i3bar/include/xcb.h @@ -110,7 +110,7 @@ void reconfig_windows(void); * Render the bars, with buttons and statusline * */ -void draw_bars(void); +void draw_bars(bool force_unhide); /* * Redraw the bars, i.e. simply copy the buffer to the barwindow diff --git a/i3bar/src/child.c b/i3bar/src/child.c index e4c0f357..d6e74176 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -172,7 +172,7 @@ static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) { /* end of file, kill the watcher */ ELOG("stdin: received EOF\n"); cleanup(); - draw_bars(); + draw_bars(false); *ret_buffer_len = -1; return NULL; } @@ -231,7 +231,7 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) { read_flat_input((char*)buffer, rec); } free(buffer); - draw_bars(); + draw_bars(false); } /* diff --git a/i3bar/src/ipc.c b/i3bar/src/ipc.c index 2cc80cf7..fc8c6492 100644 --- a/i3bar/src/ipc.c +++ b/i3bar/src/ipc.c @@ -42,7 +42,7 @@ void got_command_reply(char *reply) { void got_workspace_reply(char *reply) { DLOG("Got Workspace-Data!\n"); parse_workspaces_json(reply); - draw_bars(); + draw_bars(false); } /* @@ -71,7 +71,7 @@ void got_output_reply(char *reply) { kick_tray_clients(o_walk); } - draw_bars(); + draw_bars(false); } /* diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index aedf6392..405eefdb 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -531,7 +531,7 @@ static void handle_client_message(xcb_client_message_event_t* event) { /* Trigger an update to copy the statusline text to the appropriate * position */ configure_trayclients(); - draw_bars(); + draw_bars(false); } } } @@ -559,7 +559,7 @@ static void handle_unmap_notify(xcb_unmap_notify_event_t* event) { /* Trigger an update, we now have more space for the statusline */ configure_trayclients(); - draw_bars(); + draw_bars(false); return; } } @@ -624,13 +624,13 @@ static void handle_property_notify(xcb_property_notify_event_t *event) { xcb_unmap_window(xcb_connection, trayclient->win); trayclient->mapped = map_it; configure_trayclients(); - draw_bars(); + draw_bars(false); } else if (!trayclient->mapped && map_it) { /* need to map the window */ xcb_map_window(xcb_connection, trayclient->win); trayclient->mapped = map_it; configure_trayclients(); - draw_bars(); + draw_bars(false); } free(xembedr); } @@ -1398,14 +1398,13 @@ void reconfig_windows(void) { * Render the bars, with buttons and statusline * */ -void draw_bars(void) { +void draw_bars(bool unhide) { DLOG("Drawing Bars...\n"); int i = 0; refresh_statusline(); static char *last_urgent_ws = NULL; - bool unhide = false; bool walks_away = true; i3_output *outputs_walk; From 89ca48be20c4d9fb386e2bcdfa38efdcdeab9d83 Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Tue, 21 Aug 2012 13:52:15 +0200 Subject: [PATCH 028/146] i3bar: Honor "urgent" protocol hint by unhiding --- i3bar/include/common.h | 2 ++ i3bar/src/child.c | 25 ++++++++++++++++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/i3bar/include/common.h b/i3bar/include/common.h index 0893e953..e2582a02 100644 --- a/i3bar/include/common.h +++ b/i3bar/include/common.h @@ -34,6 +34,8 @@ struct status_block { char *color; + bool urgent; + /* The amount of pixels necessary to render this block. This variable is * only temporarily used in refresh_statusline(). */ uint32_t width; diff --git a/i3bar/src/child.c b/i3bar/src/child.c index d6e74176..9a89d3c6 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -36,6 +36,9 @@ yajl_callbacks callbacks; yajl_handle parser; typedef struct parser_ctx { + /* True if one of the parsed blocks was urgent */ + bool has_urgent; + /* A copy of the last JSON map key. */ char *last_map_key; @@ -109,6 +112,14 @@ static int stdin_map_key(void *context, const unsigned char *key, unsigned int l return 1; } +static int stdin_boolean(void *context, int val) { + parser_ctx *ctx = context; + if (strcasecmp(ctx->last_map_key, "urgent") == 0) { + ctx->block.urgent = val; + } + return 1; +} + #if YAJL_MAJOR >= 2 static int stdin_string(void *context, const unsigned char *val, size_t len) { #else @@ -132,6 +143,8 @@ static int stdin_end_map(void *context) { * i3bar doesn’t crash and the user gets an annoying message. */ if (!new_block->full_text) new_block->full_text = i3string_from_utf8("SPEC VIOLATION (null)"); + if (new_block->urgent) + ctx->has_urgent = true; TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks); return 1; } @@ -203,8 +216,9 @@ static void read_flat_input(char *buffer, int length) { first->full_text = i3string_from_utf8(buffer); } -static void read_json_input(unsigned char *input, int length) { +static bool read_json_input(unsigned char *input, int length) { yajl_status status = yajl_parse(parser, input, length); + bool has_urgent = false; #if YAJL_MAJOR >= 2 if (status != yajl_status_ok) { #else @@ -212,7 +226,10 @@ static void read_json_input(unsigned char *input, int length) { #endif fprintf(stderr, "[i3bar] Could not parse JSON input (code %d): %.*s\n", status, length, input); + } else if (parser_context.has_urgent) { + has_urgent = true; } + return has_urgent; } /* @@ -225,13 +242,14 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) { unsigned char *buffer = get_buffer(watcher, &rec); if (buffer == NULL) return; + bool has_urgent = false; if (child.version > 0) { - read_json_input(buffer, rec); + has_urgent = read_json_input(buffer, rec); } else { read_flat_input((char*)buffer, rec); } free(buffer); - draw_bars(false); + draw_bars(has_urgent); } /* @@ -293,6 +311,7 @@ void start_child(char *command) { /* Allocate a yajl parser which will be used to parse stdin. */ memset(&callbacks, '\0', sizeof(yajl_callbacks)); callbacks.yajl_map_key = stdin_map_key; + callbacks.yajl_boolean = stdin_boolean; callbacks.yajl_string = stdin_string; callbacks.yajl_start_array = stdin_start_array; callbacks.yajl_end_array = stdin_end_array; From 19883108a9973bd1dfef15600e8fd7b1f55a7ca9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 22 Sep 2012 16:49:37 +0200 Subject: [PATCH 029/146] Make get_output_next() work with non-aligned RandR setups (+test) (Thanks Feh, swh, Moritz) A good visualization of the new algorithm is this: +--------+ | | +--------+=| S1 |======================== | | | | | S0 | +--------+ | | +--------+ +--------+=========| |================ | S2 | +--------+ | | | | +--------+ | S3 | | | +--------+ When focus is on S0, 'focus output right' will first match S1 (the closest output which overlaps in the highlighted area), then S2, but not S3 (since S3 does not overlap into the highlighted area). fixes #669 fixes #771 --- include/randr.h | 7 +- src/commands.c | 16 ++-- src/randr.c | 119 +++++++++++------------ src/tree.c | 2 +- testcases/t/506-focus-right.t | 174 ++++++++++++++++++++++++++++++++++ 5 files changed, 242 insertions(+), 76 deletions(-) create mode 100644 testcases/t/506-focus-right.t diff --git a/include/randr.h b/include/randr.h index 019e1f74..b5c02144 100644 --- a/include/randr.h +++ b/include/randr.h @@ -18,6 +18,11 @@ TAILQ_HEAD(outputs_head, xoutput); extern struct outputs_head outputs; +typedef enum { + CLOSEST_OUTPUT = 0, + FARTHEST_OUTPUT = 1 +} output_close_far_t; + /** * We have just established a connection to the X server and need the initial * XRandR information to setup workspaces for each screen. @@ -96,6 +101,6 @@ Output *get_output_most(direction_t direction, Output *current); * Gets the output which is the next one in the given direction. * */ -Output *get_output_next(direction_t direction, Output *current); +Output *get_output_next(direction_t direction, Output *current, output_close_far_t close_far); #endif diff --git a/src/commands.c b/src/commands.c index 51ac28b1..4a8258e2 100644 --- a/src/commands.c +++ b/src/commands.c @@ -57,19 +57,19 @@ static Output *get_output_from_string(Output *current_output, const char *output Output *output; if (strcasecmp(output_str, "left") == 0) { - output = get_output_next(D_LEFT, current_output); + output = get_output_next(D_LEFT, current_output, CLOSEST_OUTPUT); if (!output) output = get_output_most(D_RIGHT, current_output); } else if (strcasecmp(output_str, "right") == 0) { - output = get_output_next(D_RIGHT, current_output); + output = get_output_next(D_RIGHT, current_output, CLOSEST_OUTPUT); if (!output) output = get_output_most(D_LEFT, current_output); } else if (strcasecmp(output_str, "up") == 0) { - output = get_output_next(D_UP, current_output); + output = get_output_next(D_UP, current_output, CLOSEST_OUTPUT); if (!output) output = get_output_most(D_DOWN, current_output); } else if (strcasecmp(output_str, "down") == 0) { - output = get_output_next(D_DOWN, current_output); + output = get_output_next(D_DOWN, current_output, CLOSEST_OUTPUT); if (!output) output = get_output_most(D_UP, current_output); } else output = get_output_by_name(output_str); @@ -1006,13 +1006,13 @@ void cmd_move_con_to_output(I3_CMD, char *name) { // TODO: clean this up with commands.spec as soon as we switched away from the lex/yacc command parser if (strcasecmp(name, "up") == 0) - output = get_output_next(D_UP, current_output); + output = get_output_next(D_UP, current_output, CLOSEST_OUTPUT); else if (strcasecmp(name, "down") == 0) - output = get_output_next(D_DOWN, current_output); + output = get_output_next(D_DOWN, current_output, CLOSEST_OUTPUT); else if (strcasecmp(name, "left") == 0) - output = get_output_next(D_LEFT, current_output); + output = get_output_next(D_LEFT, current_output, CLOSEST_OUTPUT); else if (strcasecmp(name, "right") == 0) - output = get_output_next(D_RIGHT, current_output); + output = get_output_next(D_RIGHT, current_output, CLOSEST_OUTPUT); else output = get_output_by_name(name); diff --git a/src/randr.c b/src/randr.c index 8b6ba1d9..97915704 100644 --- a/src/randr.c +++ b/src/randr.c @@ -101,89 +101,76 @@ Output *get_output_containing(int x, int y) { * */ Output *get_output_most(direction_t direction, Output *current) { - Output *output, *candidate = NULL; - int position = 0; - TAILQ_FOREACH(output, &outputs, outputs) { - if (!output->active) - continue; - - /* Repeated calls of WIN determine the winner of the comparison */ - #define WIN(variable, condition) \ - if (variable condition) { \ - candidate = output; \ - position = variable; \ - } \ - break; - - if (((direction == D_UP) || (direction == D_DOWN)) && - (current->rect.x != output->rect.x)) - continue; - - if (((direction == D_LEFT) || (direction == D_RIGHT)) && - (current->rect.y != output->rect.y)) - continue; - - switch (direction) { - case D_UP: - WIN(output->rect.y, <= position); - case D_DOWN: - WIN(output->rect.y, >= position); - case D_LEFT: - WIN(output->rect.x, <= position); - case D_RIGHT: - WIN(output->rect.x, >= position); - } - } - - assert(candidate != NULL); - - return candidate; + Output *best = get_output_next(direction, current, FARTHEST_OUTPUT); + if (!best) + best = current; + DLOG("current = %s, best = %s\n", current->name, best->name); + return best; } /* * Gets the output which is the next one in the given direction. * */ -Output *get_output_next(direction_t direction, Output *current) { - Output *output, *candidate = NULL; - +Output *get_output_next(direction_t direction, Output *current, output_close_far_t close_far) { + Rect *cur = &(current->rect), + *other; + Output *output, + *best = NULL; TAILQ_FOREACH(output, &outputs, outputs) { if (!output->active) continue; - if (((direction == D_UP) || (direction == D_DOWN)) && - (current->rect.x != output->rect.x)) + other = &(output->rect); + + if ((direction == D_RIGHT && other->x > cur->x) || + (direction == D_LEFT && other->x < cur->x)) { + /* Skip the output when it doesn’t overlap the other one’s y + * coordinate at all. */ + if ((other->y + other->height) < cur->y && + (cur->y + cur->height) < other->y) + continue; + } else if ((direction == D_DOWN && other->y > cur->y) || + (direction == D_UP && other->y < cur->y)) { + /* Skip the output when it doesn’t overlap the other one’s x + * coordinate at all. */ + if ((other->x + other->width) < cur->x && + (cur->x + cur->width) < other->x) + continue; + } else continue; - if (((direction == D_LEFT) || (direction == D_RIGHT)) && - (current->rect.y != output->rect.y)) + /* No candidate yet? Start with this one. */ + if (!best) { + best = output; continue; + } - switch (direction) { - case D_UP: - if (output->rect.y < current->rect.y && - (!candidate || output->rect.y < candidate->rect.y)) - candidate = output; - break; - case D_DOWN: - if (output->rect.y > current->rect.y && - (!candidate || output->rect.y > candidate->rect.y)) - candidate = output; - break; - case D_LEFT: - if (output->rect.x < current->rect.x && - (!candidate || output->rect.x > candidate->rect.x)) - candidate = output; - break; - case D_RIGHT: - if (output->rect.x > current->rect.x && - (!candidate || output->rect.x < candidate->rect.x)) - candidate = output; - break; + if (close_far == CLOSEST_OUTPUT) { + /* Is this output better (closer to the current output) than our + * current best bet? */ + if ((direction == D_RIGHT && other->x < best->rect.x) || + (direction == D_LEFT && other->x > best->rect.x) || + (direction == D_DOWN && other->y < best->rect.y) || + (direction == D_UP && other->y > best->rect.y)) { + best = output; + continue; + } + } else { + /* Is this output better (farther to the current output) than our + * current best bet? */ + if ((direction == D_RIGHT && other->x > best->rect.x) || + (direction == D_LEFT && other->x < best->rect.x) || + (direction == D_DOWN && other->y > best->rect.y) || + (direction == D_UP && other->y < best->rect.y)) { + best = output; + continue; + } } } - return candidate; + DLOG("current = %s, best = %s\n", current->name, (best ? best->name : "NULL")); + return best; } /* diff --git a/src/tree.c b/src/tree.c index 4f34946c..3d598d50 100644 --- a/src/tree.c +++ b/src/tree.c @@ -486,7 +486,7 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) else return false; - next_output = get_output_next(direction, current_output); + next_output = get_output_next(direction, current_output, CLOSEST_OUTPUT); if (!next_output) return false; DLOG("Next output is %s\n", next_output->name); diff --git a/testcases/t/506-focus-right.t b/testcases/t/506-focus-right.t new file mode 100644 index 00000000..1b8be06d --- /dev/null +++ b/testcases/t/506-focus-right.t @@ -0,0 +1,174 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Verifies that focus output right works with monitor setups that don’t line up +# on their x/y coordinates. +# +# ticket #771, bug still present in commit dd743f3b55b2f86d9f1f69ef7952ae8ece4de504 +# +use i3test i3_autostart => 0; + +sub test_focus_left_right { + my ($config) = @_; + + my $pid = launch_with_config($config); + + my $i3 = i3(get_socket_path(0)); + + $x->root->warp_pointer(0, 0); + sync_with_i3; + + ############################################################################ + # Ensure that moving left and right works. + ############################################################################ + + # First ensure both workspaces have something to focus + cmd "workspace 1"; + my $left_win = open_window; + + cmd "workspace 2"; + my $right_win = open_window; + + is($x->input_focus, $right_win->id, 'right window focused'); + + cmd "focus output left"; + is($x->input_focus, $left_win->id, 'left window focused'); + + cmd "focus output right"; + is($x->input_focus, $right_win->id, 'right window focused'); + + cmd "focus output right"; + is($x->input_focus, $left_win->id, 'left window focused (wrapping)'); + + cmd "focus output left"; + is($x->input_focus, $right_win->id, 'right window focused (wrapping)'); + + ############################################################################ + # Ensure that moving down from S0 doesn’t crash i3. + ############################################################################ + + my $second = fresh_workspace(output => 1); + + cmd "focus output down"; + + does_i3_live; + + exit_gracefully($pid); +} + +# Screen setup looks like this: +# +----+ +# | |--------+ +# | S1 | S2 | +# | |--------+ +# +----+ +# +my $config = <root->warp_pointer(0, 0); +sync_with_i3; + +############################################################################ +# Ensure that focusing right/left works in the expected order. +############################################################################ + +sub get_focused_output { + my $tree = i3(get_socket_path())->get_tree->recv; + my ($focused_id) = @{$tree->{focus}}; + my ($output) = grep { $_->{id} == $focused_id } @{$tree->{nodes}}; + return $output->{name}; +} + +is(get_focused_output(), 'fake-0', 'focus on fake-0'); + +cmd 'focus output right'; +is(get_focused_output(), 'fake-1', 'focus on fake-1'); + +cmd 'focus output right'; +is(get_focused_output(), 'fake-2', 'focus on fake-2'); + +cmd 'focus output left'; +is(get_focused_output(), 'fake-1', 'focus on fake-1'); + +cmd 'focus output left'; +is(get_focused_output(), 'fake-0', 'focus on fake-0'); + +cmd 'focus output left'; +is(get_focused_output(), 'fake-2', 'focus on fake-2 (wrapping)'); + +cmd 'focus output right'; +is(get_focused_output(), 'fake-0', 'focus on fake-0 (wrapping)'); + +exit_gracefully($pid); + +done_testing; From 11378b7012fef21d6649b8c0f1a76594f5e608ac Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 22 Sep 2012 17:30:44 +0200 Subject: [PATCH 030/146] t/506-focus-right: also verify that focus up/down is a no-op (Thanks swh) --- testcases/t/506-focus-right.t | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/testcases/t/506-focus-right.t b/testcases/t/506-focus-right.t index 1b8be06d..3f58b00c 100644 --- a/testcases/t/506-focus-right.t +++ b/testcases/t/506-focus-right.t @@ -57,12 +57,17 @@ sub test_focus_left_right { is($x->input_focus, $right_win->id, 'right window focused (wrapping)'); ############################################################################ - # Ensure that moving down from S0 doesn’t crash i3. + # Ensure that moving down/up from S0 doesn’t crash i3 and is a no-op. ############################################################################ my $second = fresh_workspace(output => 1); + my $third_win = open_window; cmd "focus output down"; + is($x->input_focus, $third_win->id, 'right window still focused'); + + cmd "focus output up"; + is($x->input_focus, $third_win->id, 'right window still focused'); does_i3_live; From eb4a7f725d6cd04df785c0874147017b308b7a86 Mon Sep 17 00:00:00 2001 From: Deiz Date: Sat, 22 Sep 2012 12:05:22 -0400 Subject: [PATCH 031/146] In get_output_next(), avoid an off-by-one for adjacent outputs and || mutually-exclusive failure conditions. --- src/randr.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/randr.c b/src/randr.c index 97915704..a73a94c9 100644 --- a/src/randr.c +++ b/src/randr.c @@ -127,15 +127,15 @@ Output *get_output_next(direction_t direction, Output *current, output_close_far (direction == D_LEFT && other->x < cur->x)) { /* Skip the output when it doesn’t overlap the other one’s y * coordinate at all. */ - if ((other->y + other->height) < cur->y && - (cur->y + cur->height) < other->y) + if ((other->y + other->height) <= cur->y || + (cur->y + cur->height) <= other->y) continue; } else if ((direction == D_DOWN && other->y > cur->y) || (direction == D_UP && other->y < cur->y)) { /* Skip the output when it doesn’t overlap the other one’s x * coordinate at all. */ - if ((other->x + other->width) < cur->x && - (cur->x + cur->width) < other->x) + if ((other->x + other->width) <= cur->x || + (cur->x + cur->width) <= other->x) continue; } else continue; From 9aff503a55e0725b68d8f6ccf2fbf444246b744d Mon Sep 17 00:00:00 2001 From: darkraven Date: Thu, 6 Sep 2012 23:27:23 +0800 Subject: [PATCH 032/146] remove unnecessary code in render_con(). The removed code was add by commit 61b8a62 to fix #564. That bug is cause by rendering the mplayer window again as a floating window (even if it has been rendered before as a fullscreen window, at line 202). So simply checking for fullscreen window is enough to solve this problem. Treating floating/tiling fullscreen window differently is not needed. --- src/render.c | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/render.c b/src/render.c index da993a57..0eda1a97 100644 --- a/src/render.c +++ b/src/render.c @@ -246,26 +246,13 @@ void render_con(Con *con, bool render_fullscreen) { Con *content = output_get_content(output); Con *workspace = TAILQ_FIRST(&(content->focus_head)); - /* Check for (floating!) fullscreen nodes */ + /* Check for fullscreen nodes */ /* XXX: This code duplication is unfortunate. Keep in mind to fix * this when we clean up the whole render.c */ Con *fullscreen = NULL; fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT); - if (fullscreen) { - /* Either the fullscreen window is inside the floating - * container, then we need to render and raise it now… */ - if (con_inside_floating(fullscreen)) { - fullscreen->rect = output->rect; - x_raise_con(fullscreen); - render_con(fullscreen, true); + if (fullscreen) continue; - } else { - /* …or it’s a tiling window, in which case the floating - * windows should not overlap it, so we skip rendering this - * output. */ - continue; - } - } Con *child; TAILQ_FOREACH(child, &(workspace->floating_head), floating_windows) { From 1e143feab1e7f6cee1a3a48dc70eb1b353adc108 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 22 Sep 2012 19:03:19 +0200 Subject: [PATCH 033/146] Close empty workspaces after cross-output move (+test) (Thanks chrysn) fixes #795 --- src/commands.c | 10 +++++++++- testcases/t/504-move-workspace-to-output.t | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/commands.c b/src/commands.c index 4a8258e2..2bbde9bf 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1099,8 +1099,12 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) { Con *content = output_get_content(output->con); LOG("got output %p with content %p\n", output, content); + Con *previously_visible_ws = TAILQ_FIRST(&(content->nodes_head)); + LOG("Previously visible workspace = %p / %s\n", previously_visible_ws, previously_visible_ws->name); + Con *ws = con_get_workspace(current->con); LOG("should move workspace %p / %s\n", ws, ws->name); + bool workspace_was_visible = workspace_is_visible(ws); if (con_num_children(ws->parent) == 1) { LOG("Creating a new workspace to replace \"%s\" (last on its output).\n", ws->name); @@ -1137,7 +1141,6 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) { } /* detach from the old output and attach to the new output */ - bool workspace_was_visible = workspace_is_visible(ws); Con *old_content = ws->parent; con_detach(ws); if (workspace_was_visible) { @@ -1159,6 +1162,11 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) { /* Focus the moved workspace on the destination output. */ workspace_show(ws); } + + /* Call the on_remove_child callback of the workspace which previously + * was visible on the destination output. Since it is no longer + * visible, it might need to get cleaned up. */ + CALL(previously_visible_ws, on_remove_child); } cmd_output->needs_tree_render = true; diff --git a/testcases/t/504-move-workspace-to-output.t b/testcases/t/504-move-workspace-to-output.t index 7a976271..c087f9f5 100644 --- a/testcases/t/504-move-workspace-to-output.t +++ b/testcases/t/504-move-workspace-to-output.t @@ -133,6 +133,26 @@ is($old_rect->{y}, $new_rect->{y}, 'y coordinate unchanged'); is($old_rect->{width}, $new_rect->{width}, 'width unchanged'); is($old_rect->{height}, $new_rect->{height}, 'height unchanged'); +################################################################################ +# Verify that empty workspaces get cleaned up when moving a different workspace +# to that output. +################################################################################ + +my $empty_ws = fresh_workspace(output => 0); +my $other_output_ws = fresh_workspace(output => 1); +cmd 'open'; + +($x0, $x1) = workspaces_per_screen(); +ok($empty_ws ~~ @$x0, 'empty_ws on fake-0'); +ok($other_output_ws ~~ @$x1, 'other_output_ws on fake-1'); + +cmd 'move workspace to output left'; + +($x0, $x1) = workspaces_per_screen(); +ok(!($empty_ws ~~ @$x0), 'empty_ws not on fake-0'); +ok(!($empty_ws ~~ @$x1), 'empty_ws not on fake-1'); +ok($other_output_ws ~~ @$x0, 'other_output_ws on fake-0'); + exit_gracefully($pid); done_testing; From b41ab04ecd8167412176ae31a3ee88c8cac367ff Mon Sep 17 00:00:00 2001 From: Pauli Ervi Date: Sat, 22 Sep 2012 19:19:53 +0300 Subject: [PATCH 034/146] Userguide: Added link from 7.2 to 6.6 The multiple monitors section (7) is probably the most likely place for someone to go look for how to move workspaces between monitors (6.6). --- docs/userguide | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/userguide b/docs/userguide index e37b468f..6a602fa8 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1845,6 +1845,8 @@ have more than one monitor: 3. If you have many workspaces on many monitors, it might get hard to keep track of which window you put where. Thus, you can use vim-like marks to quickly switch between windows. See <>. +4. For information on how to move existing workspaces between monitors, + see <<_moving_containers_workspaces_to_randr_outputs>>. == i3 and the rest of your software world From d8a036d776d9df091c0e9f3388639513b11119d6 Mon Sep 17 00:00:00 2001 From: Sascha Kruse Date: Mon, 3 Sep 2012 16:05:44 +0200 Subject: [PATCH 035/146] mark parents of urgent container also as urgent --- include/con.h | 13 +++++++++++++ src/con.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++- src/handlers.c | 3 +++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/include/con.h b/include/con.h index f741dee0..7436b2d9 100644 --- a/include/con.h +++ b/include/con.h @@ -293,4 +293,17 @@ Rect con_minimum_size(Con *con); */ bool con_fullscreen_permits_focusing(Con *con); +/** + * Checks if the given container has an urgent child. + * + */ +bool con_has_urgent_child(Con *con); + +/** + * Make all parent containers urgent if con is urgent or clear the urgent flag + * of all parent containers if there are no more urgent children left. + * + */ +void con_update_parents_urgency(Con *con); + #endif diff --git a/src/con.c b/src/con.c index f5ccfcdd..c966daf9 100644 --- a/src/con.c +++ b/src/con.c @@ -195,8 +195,14 @@ void con_focus(Con *con) { con_focus(con->parent); focused = con; - if (con->urgent) { + /* We can't blindly reset non-leaf containers since they might have + * other urgent children. Therefore we only reset leafs and propagate + * the changes upwards via con_update_parents_urgency() which does proper + * checks before resetting the urgency. + */ + if (con->urgent && con_is_leaf(con)) { con->urgent = false; + con_update_parents_urgency(con); workspace_update_urgent_flag(con_get_workspace(con)); } } @@ -1371,3 +1377,46 @@ bool con_fullscreen_permits_focusing(Con *con) { /* Focusing con would hide it behind a fullscreen window, disallow it. */ return false; } + +/* + * + * Checks if the given container has an urgent child. + * + */ +bool con_has_urgent_child(Con *con) { + Con *child; + + if (con_is_leaf(con)) + return con->urgent; + + /* We are not interested in floating windows since they can only be + * attached to a workspace → nodes_head instead of focus_head */ + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + if (con_has_urgent_child(child)) + return true; + } + + return false; +} + +/* + * Make all parent containers urgent if con is urgent or clear the urgent flag + * of all parent containers if there are no more urgent children left. + * + */ +void con_update_parents_urgency(Con *con) { + Con *parent = con->parent; + + bool new_urgency_value = con->urgent; + while (parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) { + if (new_urgency_value) { + parent->urgent = true; + } else { + /* We can only reset the urgency when the parent + * has no other urgent children */ + if (!con_has_urgent_child(parent)) + parent->urgent = false; + } + parent = parent->parent; + } +} diff --git a/src/handlers.c b/src/handlers.c index f9099cc1..7fa29e12 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -852,6 +852,9 @@ static bool handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_ con->window->urgent.tv_usec = 0; } } + + con_update_parents_urgency(con); + LOG("Urgency flag changed to %d\n", con->urgent); Con *ws; From b741c492120bde20a289dc7a044d10c429f84a32 Mon Sep 17 00:00:00 2001 From: Sascha Kruse Date: Sun, 9 Sep 2012 18:08:55 +0200 Subject: [PATCH 036/146] testcase for propagating urgency --- testcases/t/113-urgent.t | 60 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/testcases/t/113-urgent.t b/testcases/t/113-urgent.t index 58eff694..85d2035d 100644 --- a/testcases/t/113-urgent.t +++ b/testcases/t/113-urgent.t @@ -153,6 +153,66 @@ is($x->input_focus, $bottom->id, 'oldest urgent window focused'); $bottom->delete_hint('urgency'); sync_with_i3; +################################################################################ +# Check if urgent flag gets propagated to parent containers +################################################################################ + +cmd 'split v'; + + + +sub count_urgent { + my ($con) = @_; + + my @children = (@{$con->{nodes}}, @{$con->{floating_nodes}}); + my $urgent = grep { $_->{urgent} } @children; + $urgent += count_urgent($_) for @children; + return $urgent; +} + +$tmp = fresh_workspace; + +my $win1 = open_window; +my $win2 = open_window; +cmd 'layout stacked'; +cmd 'split vertical'; +my $win3 = open_window; +my $win4 = open_window; +cmd 'split horizontal' ; +my $win5 = open_window; +my $win6 = open_window; + +sync_with_i3; + + +my $urgent = count_urgent(get_ws($tmp)); +is($urgent, 0, 'no window got the urgent flag'); + +cmd '[id="' . $win2->id . '"] focus'; +sync_with_i3; +$win5->add_hint('urgency'); +$win6->add_hint('urgency'); +sync_with_i3; + +# we should have 5 urgent cons. win5, win6 and their 3 split parents. + +$urgent = count_urgent(get_ws($tmp)); +is($urgent, 5, '2 windows and 3 split containers got the urgent flag'); + +cmd '[id="' . $win5->id . '"] focus'; +sync_with_i3; + +# now win5 and still the split parents should be urgent. +$urgent = count_urgent(get_ws($tmp)); +is($urgent, 4, '1 window and 3 split containers got the urgent flag'); + +cmd '[id="' . $win6->id . '"] focus'; +sync_with_i3; + +# now now window should be urgent. +$urgent = count_urgent(get_ws($tmp)); +is($urgent, 0, 'All urgent flags got cleared'); + exit_gracefully($pid); done_testing; From 1806c9802e507d26c9483a493cbd00e2a4266988 Mon Sep 17 00:00:00 2001 From: Sascha Kruse Date: Sun, 9 Sep 2012 06:37:51 +0200 Subject: [PATCH 037/146] add descriptive titles to split containers --- include/con.h | 6 ++++ src/con.c | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/x.c | 14 +++++++-- 3 files changed, 95 insertions(+), 3 deletions(-) diff --git a/include/con.h b/include/con.h index 7436b2d9..7bd5b087 100644 --- a/include/con.h +++ b/include/con.h @@ -306,4 +306,10 @@ bool con_has_urgent_child(Con *con); */ void con_update_parents_urgency(Con *con); +/** + * Create a string representing the subtree under con. + * + */ +char *con_get_tree_representation(Con *con); + #endif diff --git a/src/con.c b/src/con.c index c966daf9..8bc9badc 100644 --- a/src/con.c +++ b/src/con.c @@ -28,6 +28,20 @@ char *colors[] = { static void con_on_remove_child(Con *con); +/* + * force parent split containers to be redrawn + * + */ +static void con_force_split_parents_redraw(Con *con) { + Con *parent = con; + + while (parent && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) { + if (parent->split) + FREE(parent->deco_render_params); + parent = parent->parent; + } +} + /* * Create a new container (and attach it to the given parent, if not NULL). * This function initializes the data structures and creates the appropriate @@ -162,6 +176,7 @@ add_to_focus_head: * This way, we have the option to insert Cons without having * to focus them. */ TAILQ_INSERT_TAIL(focus_head, con, focused); + con_force_split_parents_redraw(con); } /* @@ -169,6 +184,7 @@ add_to_focus_head: * */ void con_detach(Con *con) { + con_force_split_parents_redraw(con); if (con->type == CT_FLOATING_CON) { TAILQ_REMOVE(&(con->parent->floating_head), con, floating_windows); TAILQ_REMOVE(&(con->parent->focus_head), con, focused); @@ -1148,6 +1164,7 @@ void con_set_layout(Con *con, int layout) { tree_flatten(croot); } + con_force_split_parents_redraw(con); return; } @@ -1168,6 +1185,7 @@ void con_set_layout(Con *con, int layout) { } else { con->layout = layout; } + con_force_split_parents_redraw(con); } /* @@ -1247,6 +1265,8 @@ static void con_on_remove_child(Con *con) { return; } + con_force_split_parents_redraw(con); + /* TODO: check if this container would swallow any other client and * don’t close it automatically. */ int children = con_num_children(con); @@ -1420,3 +1440,61 @@ void con_update_parents_urgency(Con *con) { parent = parent->parent; } } + +/* + * Create a string representing the subtree under con. + * + */ +char *con_get_tree_representation(Con *con) { + /* this code works as follows: + * 1) create a string with the layout type (D/V/H/T/S) and an opening bracket + * 2) append the tree representation of the children to the string + * 3) add closing bracket + * + * The recursion ends when we hit a leaf, in which case we return the + * class_instance of the contained window. + */ + + /* end of recursion */ + if (con_is_leaf(con)) { + if (!con->window) + return sstrdup("nowin"); + + if (!con->window->class_instance) + return sstrdup("noinstance"); + + return sstrdup(con->window->class_instance); + } + + char *buf; + /* 1) add the Layout type to buf */ + if (con->layout == L_DEFAULT) + buf = sstrdup("D["); + else if (con->layout == L_SPLITV) + buf = sstrdup("V["); + else if (con->layout == L_SPLITH) + buf = sstrdup("H["); + else if (con->layout == L_TABBED) + buf = sstrdup("T["); + else if (con->layout == L_STACKED) + buf = sstrdup("S["); + + /* 2) append representation of children */ + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + char *child_txt = con_get_tree_representation(child); + + char *tmp_buf; + sasprintf(&tmp_buf, "%s%s%s", buf, + (TAILQ_FIRST(&(con->nodes_head)) == child ? "" : " "), child_txt); + free(buf); + buf = tmp_buf; + } + + /* 3) close the brackets */ + char *complete_buf; + sasprintf(&complete_buf, "%s]", buf); + free(buf); + + return complete_buf; +} diff --git a/src/x.c b/src/x.c index 24fd0eac..0be83ad0 100644 --- a/src/x.c +++ b/src/x.c @@ -482,12 +482,20 @@ void x_draw_decoration(Con *con) { struct Window *win = con->window; if (win == NULL || win->name == NULL) { - /* this is a non-leaf container, we need to make up a good description */ - // TODO: use a good description instead of just "another container" - draw_text_ascii("another container", + /* we have a split container which gets a representation + * of its children as title + */ + char *title; + char *tree = con_get_tree_representation(con); + sasprintf(&title, "i3: %s", tree); + free(tree); + + draw_text_ascii(title, parent->pixmap, parent->pm_gc, con->deco_rect.x + 2, con->deco_rect.y + text_offset_y, con->deco_rect.width - 2); + free(title); + goto copy_pixmaps; } From 2a7359e449785e20c2ef014cb7ebf4d2a6f3c581 Mon Sep 17 00:00:00 2001 From: Sascha Kruse Date: Sun, 23 Sep 2012 06:09:34 +0200 Subject: [PATCH 038/146] draw empty title if WM_NAME is empty fixes #811 --- src/x.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/x.c b/src/x.c index 0be83ad0..f1bfeb71 100644 --- a/src/x.c +++ b/src/x.c @@ -481,7 +481,7 @@ void x_draw_decoration(Con *con) { int text_offset_y = (con->deco_rect.height - config.font.height) / 2; struct Window *win = con->window; - if (win == NULL || win->name == NULL) { + if (win == NULL) { /* we have a split container which gets a representation * of its children as title */ @@ -499,6 +499,9 @@ void x_draw_decoration(Con *con) { goto copy_pixmaps; } + if (win->name == NULL) + goto copy_pixmaps; + int indent_level = 0, indent_mult = 0; Con *il_parent = parent; From a080794e59bfff0f825902e53f2bddc988ffb4f8 Mon Sep 17 00:00:00 2001 From: Deiz Date: Sun, 23 Sep 2012 08:56:56 -0400 Subject: [PATCH 039/146] Replace duplicate "__" workspace prefix checks with a single function. --- include/con.h | 6 ++++++ src/con.c | 13 ++++++++++--- src/ipc.c | 2 +- src/render.c | 4 ++-- src/workspace.c | 14 +++++++------- 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/include/con.h b/include/con.h index 7bd5b087..b4624373 100644 --- a/include/con.h +++ b/include/con.h @@ -66,6 +66,12 @@ Con *con_parent_with_orientation(Con *con, orientation_t orientation); */ Con *con_get_fullscreen_con(Con *con, int fullscreen_mode); +/** + * Returns true if the container is internal, such as __i3_scratch + * + */ +bool con_is_internal(Con *con); + /** * Returns true if the node is floating. * diff --git a/src/con.c b/src/con.c index 8bc9badc..acc5e8af 100644 --- a/src/con.c +++ b/src/con.c @@ -359,6 +359,14 @@ Con *con_get_fullscreen_con(Con *con, int fullscreen_mode) { return NULL; } +/** + * Returns true if the container is internal, such as __i3_scratch + * + */ +bool con_is_internal(Con *con) { + return (con->name[0] == '_' && con->name[1] == '_'); +} + /* * Returns true if the node is floating. * @@ -692,7 +700,7 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool * calling tree_render(), so for the "real" focus this is a no-op). * We don’t focus the con for i3 pseudo workspaces like __i3_scratch and * we don’t focus when there is a fullscreen con on that workspace. */ - if ((workspace->name[0] != '_' || workspace->name[1] != '_') && + if (!con_is_internal(workspace) && con_get_fullscreen_con(workspace, CF_OUTPUT) == NULL) con_focus(con_descend_focused(con)); @@ -701,8 +709,7 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool * don’t want to focus invisible workspaces */ if (source_output != dest_output && workspace_is_visible(workspace) && - workspace->name[0] != '_' && - workspace->name[1] != '_') { + !con_is_internal(workspace)) { DLOG("Moved to a different output, focusing target\n"); } else { /* Descend focus stack in case focus_next is a workspace which can diff --git a/src/ipc.c b/src/ipc.c index 169c659f..6f8e962d 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -405,7 +405,7 @@ IPC_HANDLER(get_workspaces) { Con *output; TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { - if (output->name[0] == '_' && output->name[1] == '_') + if (con_is_internal(output)) continue; Con *ws; TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) { diff --git a/src/render.c b/src/render.c index 0eda1a97..6f0880d8 100644 --- a/src/render.c +++ b/src/render.c @@ -225,7 +225,7 @@ void render_con(Con *con, bool render_fullscreen) { if (con->layout == L_OUTPUT) { /* Skip i3-internal outputs */ - if (con->name[0] == '_' && con->name[1] == '_') + if (con_is_internal(con)) return; render_l_output(con); } else if (con->type == CT_ROOT) { @@ -240,7 +240,7 @@ void render_con(Con *con, bool render_fullscreen) { * windows/containers so that they overlap on another output. */ DLOG("Rendering floating windows:\n"); TAILQ_FOREACH(output, &(con->nodes_head), nodes) { - if (output->name[0] == '_' && output->name[1] == '_') + if (con_is_internal(output)) continue; /* Get the active workspace of that output */ Con *content = output_get_content(output); diff --git a/src/workspace.c b/src/workspace.c index 71102e5c..3f09ea99 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -333,7 +333,7 @@ static void _workspace_show(Con *workspace) { Con *current, *old = NULL; /* safe-guard against showing i3-internal workspaces like __i3_scratch */ - if (workspace->name[0] == '_' && workspace->name[1] == '_') + if (con_is_internal(workspace)) return; /* disable fullscreen for the other workspaces and get the workspace we are @@ -462,7 +462,7 @@ Con* workspace_next(void) { /* If currently a numbered workspace, find next numbered workspace. */ TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { /* Skip outputs starting with __, they are internal. */ - if (output->name[0] == '_' && output->name[1] == '_') + if (con_is_internal(output)) continue; NODES_FOREACH(output_get_content(output)) { if (child->type != CT_WORKSPACE) @@ -483,7 +483,7 @@ Con* workspace_next(void) { bool found_current = false; TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { /* Skip outputs starting with __, they are internal. */ - if (output->name[0] == '_' && output->name[1] == '_') + if (con_is_internal(output)) continue; NODES_FOREACH(output_get_content(output)) { if (child->type != CT_WORKSPACE) @@ -502,7 +502,7 @@ Con* workspace_next(void) { if (!next) { TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { /* Skip outputs starting with __, they are internal. */ - if (output->name[0] == '_' && output->name[1] == '_') + if (con_is_internal(output)) continue; NODES_FOREACH(output_get_content(output)) { if (child->type != CT_WORKSPACE) @@ -534,7 +534,7 @@ Con* workspace_prev(void) { /* If numbered workspace, find previous numbered workspace. */ TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) { /* Skip outputs starting with __, they are internal. */ - if (output->name[0] == '_' && output->name[1] == '_') + if (con_is_internal(output)) continue; NODES_FOREACH_REVERSE(output_get_content(output)) { if (child->type != CT_WORKSPACE || child->num == -1) @@ -553,7 +553,7 @@ Con* workspace_prev(void) { bool found_current = false; TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) { /* Skip outputs starting with __, they are internal. */ - if (output->name[0] == '_' && output->name[1] == '_') + if (con_is_internal(output)) continue; NODES_FOREACH_REVERSE(output_get_content(output)) { if (child->type != CT_WORKSPACE) @@ -572,7 +572,7 @@ Con* workspace_prev(void) { if (!prev) { TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) { /* Skip outputs starting with __, they are internal. */ - if (output->name[0] == '_' && output->name[1] == '_') + if (con_is_internal(output)) continue; NODES_FOREACH_REVERSE(output_get_content(output)) { if (child->type != CT_WORKSPACE) From e582e19ffd90dee05c3580911b9ffe5e6794d728 Mon Sep 17 00:00:00 2001 From: Deiz Date: Sun, 23 Sep 2012 10:35:52 -0400 Subject: [PATCH 040/146] Clicking the root window should try to focus the relevant workspace. --- src/click.c | 19 +++++++++++++++++++ src/main.c | 1 + 2 files changed, 20 insertions(+) diff --git a/src/click.c b/src/click.c index 23b6be4f..a1da00ac 100644 --- a/src/click.c +++ b/src/click.c @@ -309,6 +309,25 @@ int handle_button_press(xcb_button_press_event_t *event) { return route_click(con, event, mod_pressed, CLICK_INSIDE); if (!(con = con_by_frame_id(event->event))) { + /* If the root window is clicked, find the relevant output from the + * click coordinates and focus the output's active workspace. */ + if (event->event == root) { + Con *output, *ws; + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + if (con_is_internal(output) || + !rect_contains(output->rect, event->event_x, event->event_y)) + continue; + + ws = TAILQ_FIRST(&(output_get_content(output)->focus_head)); + if (ws != con_get_workspace(focused)) { + workspace_show(ws); + tree_render(); + } + return 1; + } + return 0; + } + ELOG("Clicked into unknown window?!\n"); xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); xcb_flush(conn); diff --git a/src/main.c b/src/main.c index a4bd1b9b..65db1711 100644 --- a/src/main.c +++ b/src/main.c @@ -544,6 +544,7 @@ int main(int argc, char *argv[]) { uint32_t mask = XCB_CW_EVENT_MASK; uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | + XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* when the user adds a screen (e.g. video projector), the root window gets a ConfigureNotify */ From b235c469c16df64092d4476cb82fd26becb32417 Mon Sep 17 00:00:00 2001 From: Deiz Date: Sun, 23 Sep 2012 15:43:43 -0400 Subject: [PATCH 041/146] Display appropriate cursors when resizing or moving floating windows. --- include/floating.h | 4 ++-- include/xcursor.h | 5 +++++ src/floating.c | 23 +++++++++++++++++------ src/resize.c | 2 +- src/xcursor.c | 13 +++++++++---- 5 files changed, 34 insertions(+), 13 deletions(-) diff --git a/include/floating.h b/include/floating.h index a2f501c5..884d3cf1 100644 --- a/include/floating.h +++ b/include/floating.h @@ -134,8 +134,8 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace); * */ void drag_pointer(Con *con, const xcb_button_press_event_t *event, - xcb_window_t confine_to, border_t border, callback_t callback, - const void *extra); + xcb_window_t confine_to, border_t border, int cursor, + callback_t callback, const void *extra); /** * Repositions the CT_FLOATING_CON to have the coordinates specified by diff --git a/include/xcursor.h b/include/xcursor.h index fba82ad3..bfe37c39 100644 --- a/include/xcursor.h +++ b/include/xcursor.h @@ -16,7 +16,12 @@ enum xcursor_cursor_t { XCURSOR_CURSOR_POINTER = 0, XCURSOR_CURSOR_RESIZE_HORIZONTAL, XCURSOR_CURSOR_RESIZE_VERTICAL, + XCURSOR_CURSOR_TOP_LEFT_CORNER, + XCURSOR_CURSOR_TOP_RIGHT_CORNER, + XCURSOR_CURSOR_BOTTOM_LEFT_CORNER, + XCURSOR_CURSOR_BOTTOM_RIGHT_CORNER, XCURSOR_CURSOR_WATCH, + XCURSOR_CURSOR_MOVE, XCURSOR_CURSOR_MAX }; diff --git a/src/floating.c b/src/floating.c index 3d2c1d31..ce81e5fa 100644 --- a/src/floating.c +++ b/src/floating.c @@ -395,7 +395,7 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event) { tree_render(); /* Drag the window */ - drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback, event); + drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, XCURSOR_CURSOR_MOVE, drag_window_callback, event); tree_render(); } @@ -479,13 +479,21 @@ void floating_resize_window(Con *con, const bool proportional, corner |= BORDER_LEFT; else corner |= BORDER_RIGHT; - if (event->event_y <= (con->rect.height / 2)) + int cursor = 0; + if (event->event_y <= (con->rect.height / 2)) { corner |= BORDER_TOP; - else corner |= BORDER_BOTTOM; + cursor = (corner & BORDER_LEFT) ? + XCURSOR_CURSOR_TOP_LEFT_CORNER : XCURSOR_CURSOR_TOP_RIGHT_CORNER; + } + else { + corner |= BORDER_BOTTOM; + cursor = (corner & BORDER_LEFT) ? + XCURSOR_CURSOR_BOTTOM_LEFT_CORNER : XCURSOR_CURSOR_BOTTOM_RIGHT_CORNER; + } struct resize_window_callback_params params = { corner, proportional, event }; - drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback, ¶ms); + drag_pointer(con, event, XCB_NONE, BORDER_TOP /* irrelevant */, cursor, resize_window_callback, ¶ms); } /* @@ -497,13 +505,16 @@ void floating_resize_window(Con *con, const bool proportional, * */ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t - confine_to, border_t border, callback_t callback, const void *extra) + confine_to, border_t border, int cursor, callback_t callback, const void *extra) { uint32_t new_x, new_y; Rect old_rect = { 0, 0, 0, 0 }; if (con != NULL) memcpy(&old_rect, &(con->rect), sizeof(Rect)); + Cursor xcursor = (cursor && xcursor_supported) ? + xcursor_get_cursor(cursor) : XCB_NONE; + /* Grab the pointer */ xcb_grab_pointer_cookie_t cookie; xcb_grab_pointer_reply_t *reply; @@ -514,7 +525,7 @@ void drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */ XCB_GRAB_MODE_ASYNC, /* keyboard mode */ confine_to, /* confine_to = in which window should the cursor stay */ - XCB_NONE, /* don’t display a special cursor */ + xcursor, /* possibly display a special cursor */ XCB_CURRENT_TIME); if ((reply = xcb_grab_pointer_reply(conn, cookie, NULL)) == NULL) { diff --git a/src/resize.c b/src/resize.c index b65344a2..268dc3fb 100644 --- a/src/resize.c +++ b/src/resize.c @@ -106,7 +106,7 @@ int resize_graphical_handler(Con *first, Con *second, orientation_t orientation, const struct callback_params params = { orientation, output, helpwin, &new_position }; - drag_pointer(NULL, event, grabwin, BORDER_TOP, resize_callback, ¶ms); + drag_pointer(NULL, event, grabwin, BORDER_TOP, 0, resize_callback, ¶ms); xcb_destroy_window(conn, helpwin); xcb_destroy_window(conn, grabwin); diff --git a/src/xcursor.c b/src/xcursor.c index 7683b0d3..90fd69dd 100644 --- a/src/xcursor.c +++ b/src/xcursor.c @@ -34,10 +34,15 @@ static Cursor load_cursor(const char *name) { } void xcursor_load_cursors(void) { - cursors[XCURSOR_CURSOR_POINTER] = load_cursor("left_ptr"); - cursors[XCURSOR_CURSOR_RESIZE_HORIZONTAL] = load_cursor("sb_h_double_arrow"); - cursors[XCURSOR_CURSOR_RESIZE_VERTICAL] = load_cursor("sb_v_double_arrow"); - cursors[XCURSOR_CURSOR_WATCH] = load_cursor("watch"); + cursors[XCURSOR_CURSOR_POINTER] = load_cursor("left_ptr"); + cursors[XCURSOR_CURSOR_RESIZE_HORIZONTAL] = load_cursor("sb_h_double_arrow"); + cursors[XCURSOR_CURSOR_RESIZE_VERTICAL] = load_cursor("sb_v_double_arrow"); + cursors[XCURSOR_CURSOR_WATCH] = load_cursor("watch"); + cursors[XCURSOR_CURSOR_MOVE] = load_cursor("fleur"); + cursors[XCURSOR_CURSOR_TOP_LEFT_CORNER] = load_cursor("top_left_corner"); + cursors[XCURSOR_CURSOR_TOP_RIGHT_CORNER] = load_cursor("top_right_corner"); + cursors[XCURSOR_CURSOR_BOTTOM_LEFT_CORNER] = load_cursor("bottom_left_corner"); + cursors[XCURSOR_CURSOR_BOTTOM_RIGHT_CORNER] = load_cursor("bottom_right_corner"); } /* From 04c58c7325202a2b0faf4ee47a4e311c6e915d65 Mon Sep 17 00:00:00 2001 From: Yaroslav Molochko Date: Mon, 24 Sep 2012 02:14:00 +0300 Subject: [PATCH 042/146] Implement variable border widths for pixel/normal fixes #325 --- docs/ipc | 2 + docs/userguide | 16 +++++- include/commands.h | 2 +- include/con.h | 2 +- include/config.h | 1 + include/data.h | 3 +- parser-specs/commands.spec | 16 +++++- src/cfgparse.l | 5 +- src/cfgparse.y | 31 +++++++++- src/commands.c | 30 +++++++--- src/con.c | 69 ++++++++++------------- src/config.c | 1 + src/ipc.c | 7 ++- src/load_layout.c | 10 +++- src/render.c | 6 +- src/x.c | 2 +- testcases/t/116-nestedcons.t | 1 + testcases/t/161-regress-borders-restart.t | 4 +- testcases/t/169-border-toggle.t | 4 +- testcases/t/174-border-config.t | 5 +- 20 files changed, 144 insertions(+), 73 deletions(-) diff --git a/docs/ipc b/docs/ipc index 2716f180..a6666ef3 100644 --- a/docs/ipc +++ b/docs/ipc @@ -274,6 +274,8 @@ name (string):: border (string):: Can be either "normal", "none" or "1pixel", dependending on the container’s border style. +current_border_width (integer):: + Number of pixels of the border width. layout (string):: Can be either "splith", "splitv", "stacked", "tabbed", "dockarea" or "output". diff --git a/docs/userguide b/docs/userguide index 6a602fa8..9ffb678e 100644 --- a/docs/userguide +++ b/docs/userguide @@ -483,14 +483,26 @@ This option determines which border style new windows will have. The default is *Syntax*: --------------------------------------------- -new_window +new_window --------------------------------------------- - *Example*: --------------------- new_window 1pixel --------------------- +The "normal" and "pixel" border styles support an optional border width in +pixels: + +*Example*: +--------------------- +# The same as new_window none +new_window pixel 0 + +# A 3 px border +new_window pixel 3 +--------------------- + + === Hiding vertical borders You can hide vertical borders adjacent to the screen edges using diff --git a/include/commands.h b/include/commands.h index 43bd0b0a..6d195a09 100644 --- a/include/commands.h +++ b/include/commands.h @@ -83,7 +83,7 @@ void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resiz * Implementation of 'border normal|none|1pixel|toggle'. * */ -void cmd_border(I3_CMD, char *border_style_str); +void cmd_border(I3_CMD, char *border_style_str, char *border_width); /** * Implementation of 'nop '. diff --git a/include/con.h b/include/con.h index b4624373..5bf82487 100644 --- a/include/con.h +++ b/include/con.h @@ -250,7 +250,7 @@ int con_border_style(Con *con); * floating window. * */ -void con_set_border_style(Con *con, int border_style); +void con_set_border_style(Con *con, int border_style, int border_width); /** * This function changes the layout of a given container. Use it to handle diff --git a/include/config.h b/include/config.h index 056aa5ae..76fee94d 100644 --- a/include/config.h +++ b/include/config.h @@ -98,6 +98,7 @@ struct Config { int default_layout; int container_stack_limit; int container_stack_limit_value; + int default_border_width; /** Default orientation for new containers */ int default_orientation; diff --git a/include/data.h b/include/data.h index e78354f4..3cf22f61 100644 --- a/include/data.h +++ b/include/data.h @@ -55,7 +55,7 @@ typedef struct Window i3Window; *****************************************************************************/ typedef enum { D_LEFT, D_RIGHT, D_UP, D_DOWN } direction_t; typedef enum { NO_ORIENTATION = 0, HORIZ, VERT } orientation_t; -typedef enum { BS_NORMAL = 0, BS_NONE = 1, BS_1PIXEL = 2 } border_style_t; +typedef enum { BS_NORMAL = 0, BS_NONE = 1, BS_PIXEL = 2 } border_style_t; /** parameter to specify whether tree_close() and x_window_kill() should kill * only this specific window or the whole X11 client */ @@ -485,6 +485,7 @@ struct Con { /* the x11 border pixel attribute */ int border_width; + int current_border_width; /* minimum increment size specified for the window (in pixels) */ int width_increment; diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index 2c46e66c..4224707c 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -61,10 +61,20 @@ state EXEC: command = string -> call cmd_exec($nosn, $command) -# border normal|none|1pixel|toggle +# border normal|none|1pixel|toggle|1pixel state BORDER: - border_style = 'normal', 'none', '1pixel', 'toggle' - -> call cmd_border($border_style) + border_style = 'normal', 'pixel' + -> BORDER_WIDTH + border_style = 'none', 'toggle' + -> call cmd_border($border_style, "0") + border_style = '1pixel' + -> call cmd_border($border_style, "1") + +state BORDER_WIDTH: + end + -> call cmd_border($border_style, "2") + border_width = word + -> call cmd_border($border_style, $border_width) # layout default|stacked|stacking|tabbed|splitv|splith # layout toggle [split|all] diff --git a/src/cfgparse.l b/src/cfgparse.l index b752851b..6eef8a5a 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -56,6 +56,7 @@ EOL (\r?\n) %s OUTPUT_COND %s FOR_WINDOW_COND %s EAT_WHITESPACE +%s BORDER_WIDTH %x BUFFER_LINE %x BAR @@ -171,6 +172,7 @@ EOL (\r?\n) } [ \t]*→[ \t]* { BEGIN(WANT_STRING); } [ \t]+ { BEGIN(WANT_STRING); } +[^\n][0-9]+ { printf("Border width set to: %s\n", yytext); yylval.number = atoi(yytext); return NUMBER;} --no-startup-id { printf("no startup id\n"); yy_pop_state(); return TOK_NO_STARTUP_ID; } . { printf("anything else: *%s*\n", yytext); yyless(0); yy_pop_state(); yy_pop_state(); } --release { printf("--release\n"); yy_pop_state(); return TOK_RELEASE; } @@ -200,9 +202,10 @@ auto { return TOK_AUTO; } workspace_layout { return TOK_WORKSPACE_LAYOUT; } new_window { return TOKNEWWINDOW; } new_float { return TOKNEWFLOAT; } -normal { return TOK_NORMAL; } +normal { yy_push_state(BORDER_WIDTH); return TOK_NORMAL; } none { return TOK_NONE; } 1pixel { return TOK_1PIXEL; } +pixel { yy_push_state(BORDER_WIDTH); return TOK_PIXEL; } hide_edge_borders { return TOK_HIDE_EDGE_BORDERS; } both { return TOK_BOTH; } focus_follows_mouse { return TOKFOCUSFOLLOWSMOUSE; } diff --git a/src/cfgparse.y b/src/cfgparse.y index c3efb063..8bc7990e 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -108,6 +108,7 @@ static int detect_version(char *buf) { strncasecmp(bind, "focus down", strlen("focus down")) == 0 || strncasecmp(bind, "border normal", strlen("border normal")) == 0 || strncasecmp(bind, "border 1pixel", strlen("border 1pixel")) == 0 || + strncasecmp(bind, "border pixel", strlen("border pixel")) == 0 || strncasecmp(bind, "border borderless", strlen("border borderless")) == 0 || strncasecmp(bind, "--no-startup-id", strlen("--no-startup-id")) == 0 || strncasecmp(bind, "bar", strlen("bar")) == 0) { @@ -739,6 +740,7 @@ void parse_file(const char *f) { %token TOKNEWFLOAT "new_float" %token TOK_NORMAL "normal" %token TOK_NONE "none" +%token TOK_PIXEL "pixel" %token TOK_1PIXEL "1pixel" %token TOK_HIDE_EDGE_BORDERS "hide_edge_borders" %token TOK_BOTH "both" @@ -818,6 +820,7 @@ void parse_file(const char *f) { %type bar_mode_mode %type bar_modifier_modifier %type optional_no_startup_id +%type optional_border_width %type optional_release %type command %type word_or_number @@ -1480,9 +1483,26 @@ new_float: ; border_style: - TOK_NORMAL { $$ = BS_NORMAL; } - | TOK_NONE { $$ = BS_NONE; } - | TOK_1PIXEL { $$ = BS_1PIXEL; } + TOK_NORMAL optional_border_width + { + config.default_border_width = $2; + $$ = BS_NORMAL; + } + | TOK_1PIXEL + { + config.default_border_width = 1; + $$ = BS_PIXEL; + } + | TOK_NONE + { + config.default_border_width = 0; + $$ = BS_NONE; + } + | TOK_PIXEL optional_border_width + { + config.default_border_width = $2; + $$ = BS_PIXEL; + } ; bool: @@ -1753,6 +1773,11 @@ exec_always: } ; +optional_border_width: + /* empty */ { $$ = 2; } // 2 pixels is the default value for any type of border + | NUMBER { $$ = $1; } + ; + optional_no_startup_id: /* empty */ { $$ = false; } | TOK_NO_STARTUP_ID { $$ = true; } diff --git a/src/commands.c b/src/commands.c index 2bbde9bf..000dd208 100644 --- a/src/commands.c +++ b/src/commands.c @@ -776,11 +776,11 @@ void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resiz } /* - * Implementation of 'border normal|none|1pixel|toggle'. + * Implementation of 'border normal|none|1pixel|toggle|pixel'. * */ -void cmd_border(I3_CMD, char *border_style_str) { - DLOG("border style should be changed to %s\n", border_style_str); +void cmd_border(I3_CMD, char *border_style_str, char *border_width ) { + DLOG("border style should be changed to %s with border width %s\n", border_style_str, border_width); owindow *current; HANDLE_EMPTY_MATCH; @@ -788,23 +788,39 @@ void cmd_border(I3_CMD, char *border_style_str) { TAILQ_FOREACH(current, &owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); int border_style = current->con->border_style; + char *end; + int tmp_border_width = -1; + tmp_border_width = strtol(border_width, &end, 10); + if (end == border_width) { + /* no valid digits found */ + tmp_border_width = -1; + } if (strcmp(border_style_str, "toggle") == 0) { border_style++; border_style %= 3; + if (border_style == BS_NORMAL) + current->con->current_border_width = 2; + else if (border_style == BS_NONE) + current->con->current_border_width = 0; + else if (border_style == BS_PIXEL) + current->con->current_border_width = 1; } else { if (strcmp(border_style_str, "normal") == 0) border_style = BS_NORMAL; - else if (strcmp(border_style_str, "none") == 0) + else if (strcmp(border_style_str, "pixel") == 0) + border_style = BS_PIXEL; + else if (strcmp(border_style_str, "1pixel") == 0){ + border_style = BS_PIXEL; + tmp_border_width = 1; + } else if (strcmp(border_style_str, "none") == 0) border_style = BS_NONE; - else if (strcmp(border_style_str, "1pixel") == 0) - border_style = BS_1PIXEL; else { ELOG("BUG: called with border_style=%s\n", border_style_str); ysuccess(false); return; } } - con_set_border_style(current->con, border_style); + con_set_border_style(current->con, border_style, tmp_border_width); } cmd_output->needs_tree_render = true; diff --git a/src/con.c b/src/con.c index acc5e8af..db9ad5ee 100644 --- a/src/con.c +++ b/src/con.c @@ -55,6 +55,7 @@ Con *con_new(Con *parent, i3Window *window) { new->type = CT_CON; new->window = window; new->border_style = config.default_border; + new->current_border_width = -1; static int cnt = 0; DLOG("opening window %d\n", cnt); @@ -968,52 +969,38 @@ Con *con_descend_direction(Con *con, direction_t direction) { */ Rect con_border_style_rect(Con *con) { adjacent_t borders_to_hide = ADJ_NONE; + int border_width = con->current_border_width; + DLOG("The border width for con is set to: %d\n", con->current_border_width); Rect result; + if (con->current_border_width < 0) + border_width = config.default_border_width; + DLOG("Effective border width is set to: %d\n", border_width); /* Shortcut to avoid calling con_adjacent_borders() on dock containers. */ int border_style = con_border_style(con); if (border_style == BS_NONE) return (Rect){ 0, 0, 0, 0 }; borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders; - switch (border_style) { - case BS_NORMAL: - result = (Rect){2, 0, -(2 * 2), -2}; - if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) { - result.x -= 2; - result.width += 2; - } - if (borders_to_hide & ADJ_RIGHT_SCREEN_EDGE) { - result.width += 2; - } - /* With normal borders we never hide the upper border */ - if (borders_to_hide & ADJ_LOWER_SCREEN_EDGE) { - result.height += 2; - } - return result; - - case BS_1PIXEL: - result = (Rect){1, 1, -2, -2}; - if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) { - result.x -= 1; - result.width += 1; - } - if (borders_to_hide & ADJ_RIGHT_SCREEN_EDGE) { - result.width += 1; - } - if (borders_to_hide & ADJ_UPPER_SCREEN_EDGE) { - result.y -= 1; - result.height += 1; - } - if (borders_to_hide & ADJ_LOWER_SCREEN_EDGE) { - result.height += 1; - } - return result; - - case BS_NONE: - return (Rect){0, 0, 0, 0}; - - default: - assert(false); + if (border_style == BS_NORMAL) { + result = (Rect){border_width, 0 , -(2 * border_width), -(border_width)}; + } else { + result = (Rect){border_width, border_width, -(2 * border_width), -(2 * border_width)}; } + if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) { + result.x -= border_width; + result.width += border_width; + } + if (borders_to_hide & ADJ_RIGHT_SCREEN_EDGE) { + result.width += border_width; + } + if (borders_to_hide & ADJ_UPPER_SCREEN_EDGE && (border_style != BS_NORMAL)) { + result.y -= border_width; + result.height += border_width; + } + if (borders_to_hide & ADJ_LOWER_SCREEN_EDGE) { + result.height += border_width; + } + return result; + } /* @@ -1068,10 +1055,11 @@ int con_border_style(Con *con) { * floating window. * */ -void con_set_border_style(Con *con, int border_style) { +void con_set_border_style(Con *con, int border_style, int border_width) { /* Handle the simple case: non-floating containerns */ if (!con_is_floating(con)) { con->border_style = border_style; + con->current_border_width = border_width; return; } @@ -1090,6 +1078,7 @@ void con_set_border_style(Con *con, int border_style) { /* Change the border style, get new border/decoration values. */ con->border_style = border_style; + con->current_border_width = border_width; bsr = con_border_style_rect(con); int deco_height = (con->border_style == BS_NORMAL ? config.font.height + 5 : 0); diff --git a/src/config.c b/src/config.c index 0cfa8eb6..9e47d74b 100644 --- a/src/config.c +++ b/src/config.c @@ -417,6 +417,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, config.default_border = BS_NORMAL; config.default_floating_border = BS_NORMAL; + config.default_border_width = 2; /* Set default_orientation to NO_ORIENTATION for auto orientation. */ config.default_orientation = NO_ORIENTATION; diff --git a/src/ipc.c b/src/ipc.c index 6f8e962d..84ef2c36 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -266,11 +266,14 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { case BS_NONE: ystr("none"); break; - case BS_1PIXEL: - ystr("1pixel"); + case BS_PIXEL: + ystr("pixel"); break; } + ystr("current_border_width"); + y(integer, con->current_border_width); + dump_rect(gen, "rect", con->rect); dump_rect(gen, "window_rect", con->window_rect); dump_rect(gen, "geometry", con->geometry); diff --git a/src/load_layout.c b/src/load_layout.c index fc513f51..d5735885 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -183,8 +183,11 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) { sasprintf(&buf, "%.*s", (int)len, val); if (strcasecmp(buf, "none") == 0) json_node->border_style = BS_NONE; - else if (strcasecmp(buf, "1pixel") == 0) - json_node->border_style = BS_1PIXEL; + else if (strcasecmp(buf, "1pixel") == 0) { + json_node->border_style = BS_PIXEL; + json_node->current_border_width = 1; + } else if (strcasecmp(buf, "pixel") == 0) + json_node->border_style = BS_PIXEL; else if (strcasecmp(buf, "normal") == 0) json_node->border_style = BS_NORMAL; else LOG("Unhandled \"border\": %s\n", buf); @@ -278,6 +281,9 @@ static int json_int(void *ctx, long val) { if (strcasecmp(last_key, "num") == 0) json_node->num = val; + if (strcasecmp(last_key, "current_border_width") == 0) + json_node->current_border_width = val; + if (!parsing_swallows && strcasecmp(last_key, "id") == 0) json_node->old_id = val; diff --git a/src/render.c b/src/render.c index 6f0880d8..369273c8 100644 --- a/src/render.c +++ b/src/render.c @@ -311,7 +311,7 @@ void render_con(Con *con, bool render_fullscreen) { child->deco_rect.width = child->rect.width; child->deco_rect.height = deco_height; - if (children > 1 || (child->border_style != BS_1PIXEL && child->border_style != BS_NONE)) { + if (children > 1 || (child->border_style != BS_PIXEL && child->border_style != BS_NONE)) { child->rect.y += (deco_height * children); child->rect.height -= (deco_height * children); } @@ -328,12 +328,12 @@ void render_con(Con *con, bool render_fullscreen) { child->deco_rect.x = x - con->rect.x + i * child->deco_rect.width; child->deco_rect.y = y - con->rect.y; - if (children > 1 || (child->border_style != BS_1PIXEL && child->border_style != BS_NONE)) { + if (children > 1 || (child->border_style != BS_PIXEL && child->border_style != BS_NONE)) { child->rect.y += deco_height; child->rect.height -= deco_height; child->deco_rect.height = deco_height; } else { - child->deco_rect.height = (child->border_style == BS_1PIXEL ? 1 : 0); + child->deco_rect.height = (child->border_style == BS_PIXEL ? 1 : 0); } } diff --git a/src/x.c b/src/x.c index f1bfeb71..2c785750 100644 --- a/src/x.c +++ b/src/x.c @@ -431,7 +431,7 @@ void x_draw_decoration(Con *con) { xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &bottomline); } /* 1pixel border needs an additional line at the top */ - if (p->border_style == BS_1PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) { + if (p->border_style == BS_PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) { xcb_rectangle_t topline = { br.x, 0, con->rect.width + br.width + br.x, br.y }; xcb_poly_fill_rectangle(conn, con->pixmap, con->pm_gc, 1, &topline); } diff --git a/testcases/t/116-nestedcons.t b/testcases/t/116-nestedcons.t index 70008801..fc0b742a 100644 --- a/testcases/t/116-nestedcons.t +++ b/testcases/t/116-nestedcons.t @@ -69,6 +69,7 @@ my $expected = { border => 'normal', 'floating_nodes' => $ignore, workspace_layout => 'default', + current_border_width => -1, }; # a shallow copy is sufficient, since we only ignore values at the root diff --git a/testcases/t/161-regress-borders-restart.t b/testcases/t/161-regress-borders-restart.t index 1db64575..de1b7c81 100644 --- a/testcases/t/161-regress-borders-restart.t +++ b/testcases/t/161-regress-borders-restart.t @@ -36,13 +36,13 @@ is(get_border_style(), 'normal', 'border style normal'); cmd 'border 1pixel'; -is(get_border_style(), '1pixel', 'border style 1pixel after changing'); +is(get_border_style(), 'pixel', 'border style 1pixel after changing'); # perform an inplace-restart cmd 'restart'; does_i3_live; -is(get_border_style(), '1pixel', 'border style still 1pixel after restart'); +is(get_border_style(), 'pixel', 'border style still 1pixel after restart'); done_testing; diff --git a/testcases/t/169-border-toggle.t b/testcases/t/169-border-toggle.t index 7377194d..33f3a8ec 100644 --- a/testcases/t/169-border-toggle.t +++ b/testcases/t/169-border-toggle.t @@ -28,7 +28,7 @@ is($nodes[0]->{border}, 'normal', 'border style normal'); cmd 'border 1pixel'; @nodes = @{get_ws_content($tmp)}; -is($nodes[0]->{border}, '1pixel', 'border style 1pixel'); +is($nodes[0]->{border}, 'pixel', 'border style 1pixel'); cmd 'border none'; @nodes = @{get_ws_content($tmp)}; @@ -44,7 +44,7 @@ is($nodes[0]->{border}, 'none', 'border style none'); cmd 'border toggle'; @nodes = @{get_ws_content($tmp)}; -is($nodes[0]->{border}, '1pixel', 'border style 1pixel'); +is($nodes[0]->{border}, 'pixel', 'border style 1pixel'); cmd 'border toggle'; @nodes = @{get_ws_content($tmp)}; diff --git a/testcases/t/174-border-config.t b/testcases/t/174-border-config.t index 6e837cf0..56ad865a 100644 --- a/testcases/t/174-border-config.t +++ b/testcases/t/174-border-config.t @@ -65,7 +65,8 @@ $first = open_window; @content = @{get_ws_content($tmp)}; ok(@content == 1, 'one container opened'); -is($content[0]->{border}, '1pixel', 'border normal by default'); +is($content[0]->{border}, 'pixel', 'border pixel by default'); +is($content[0]->{current_border_width}, -1, 'border width pixels -1 (default)'); exit_gracefully($pid); @@ -119,7 +120,7 @@ $wscontent = get_ws($tmp); @floating = @{$wscontent->{floating_nodes}}; ok(@floating == 1, 'one floating container opened'); $floatingcon = $floating[0]; -is($floatingcon->{nodes}->[0]->{border}, '1pixel', 'border normal by default'); +is($floatingcon->{nodes}->[0]->{border}, 'pixel', 'border pixel by default'); exit_gracefully($pid); From 14e6fc77ada66108f3c87fe610cab61beaf5a218 Mon Sep 17 00:00:00 2001 From: noxxun Date: Sat, 22 Sep 2012 17:59:33 +0200 Subject: [PATCH 043/146] rendering: fix bottom line of tabbed borders decoration not continuous fixes #814 Signed-off-by: noxxun --- src/x.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/x.c b/src/x.c index 2c785750..d316277f 100644 --- a/src/x.c +++ b/src/x.c @@ -467,12 +467,20 @@ void x_draw_decoration(Con *con) { /* 5: draw two unconnected lines in border color */ xcb_change_gc(conn, parent->pm_gc, XCB_GC_FOREGROUND, (uint32_t[]){ p->color->border }); Rect *dr = &(con->deco_rect); + int deco_diff_l = 2; + int deco_diff_r = 2; + if (parent->layout == L_TABBED) { + if (TAILQ_PREV(con, nodes_head, nodes) != NULL) + deco_diff_l = 0; + if (TAILQ_NEXT(con, nodes) != NULL) + deco_diff_r = 0; + } xcb_segment_t segments[] = { { dr->x, dr->y, dr->x + dr->width - 1, dr->y }, - { dr->x + 2, dr->y + dr->height - 1, - dr->x + dr->width - 3, dr->y + dr->height - 1 } + { dr->x + deco_diff_l, dr->y + dr->height - 1, + dr->x - deco_diff_r + dr->width - 1, dr->y + dr->height - 1 } }; xcb_poly_segment(conn, parent->pixmap, parent->pm_gc, 2, segments); From c31b3b296a9c30073ac133ccfe8e02fb57a37c3f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 25 Sep 2012 15:40:08 +0200 Subject: [PATCH 044/146] Bugfix: Correctly clear the urgency hint when the window is underneath a split-con (+test) Previously, when you had an urgent container in a stack on some invisible workspace (say urxvt) and you switched to it, the urgency hint was not properly cleared. --- src/workspace.c | 1 + testcases/t/200-urgency-timer.t | 38 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/workspace.c b/src/workspace.c index 3f09ea99..3a5844cb 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -322,6 +322,7 @@ static void workspace_defer_update_urgent_hint_cb(EV_P_ ev_timer *w, int revents DLOG("Resetting urgency flag of con %p by timer\n", con); con->urgent = false; + con_update_parents_urgency(con); workspace_update_urgent_flag(con_get_workspace(con)); tree_render(); diff --git a/testcases/t/200-urgency-timer.t b/testcases/t/200-urgency-timer.t index 9e1a90a5..730a950a 100644 --- a/testcases/t/200-urgency-timer.t +++ b/testcases/t/200-urgency-timer.t @@ -106,6 +106,44 @@ cmd '[id="' . $w->id . '"] focus'; @urgent = grep { $_->{urgent} } @content; is(@urgent, 0, 'window 1 not marked as urgent anymore'); +################################################################################ +# open a stack, mark one window as urgent, switch to that workspace and verify +# it’s cleared correctly. +################################################################################ + +sub count_total_urgent { + my ($con) = @_; + + my $urgent = ($con->{urgent} ? 1 : 0); + $urgent += count_total_urgent($_) for (@{$con->{nodes}}, @{$con->{floating_nodes}}); + return $urgent; +} + +my $tmp3 = fresh_workspace; +open_window; +open_window; +cmd 'split v'; +my $split_left = open_window; +cmd 'layout stacked'; + +cmd "workspace $tmp2"; + +is(count_total_urgent(get_ws($tmp3)), 0, "no urgent windows on workspace $tmp3"); + +$split_left->add_hint('urgency'); +sync_with_i3; + +cmp_ok(count_total_urgent(get_ws($tmp3)), '>=', 0, "more than one urgent window on workspace $tmp3"); + +cmd "workspace $tmp3"; + +# Remove the urgency hint. +$split_left->delete_hint('urgency'); +sync_with_i3; + +sleep(0.2); +is(count_total_urgent(get_ws($tmp3)), 0, "no more urgent windows on workspace $tmp3"); + exit_gracefully($pid); done_testing; From 87525ad2d6759b728f8345cc0ee87b3557c81797 Mon Sep 17 00:00:00 2001 From: Simon Elsbrock Date: Thu, 27 Sep 2012 08:35:09 +0200 Subject: [PATCH 045/146] fix crash: urgent floating con on separate workspace (thanks Piotr) If there is a single floating con on a separate workspace that is not focused, and this con becomes urgent, switching back to that workspace may result in a crash of i3. This is because while setting the urgency of parent containers, 'parent' may become NULL in case of floating containers. This commit checks the validity of parent. fixes #821 --- src/con.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/con.c b/src/con.c index db9ad5ee..0539c7ab 100644 --- a/src/con.c +++ b/src/con.c @@ -1424,7 +1424,7 @@ void con_update_parents_urgency(Con *con) { Con *parent = con->parent; bool new_urgency_value = con->urgent; - while (parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) { + while (parent && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) { if (new_urgency_value) { parent->urgent = true; } else { From 9c01bdeef7c9ae217e5710639efe4fa02781d9ee Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 27 Sep 2012 12:34:09 +0200 Subject: [PATCH 046/146] Revert "Use ev_signal to avoid async-unsafe functions (Thanks Markus)" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes our signal handler useless and leads to infinite SIGSEGV loops because the ev callback handler gets called only from within the event loop, and control doesn’t necessary get to the event loop… This reverts commit 514265b529ac78b7778eeee2db3dddb6f3a1c24c. --- src/main.c | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/src/main.c b/src/main.c index 65db1711..95498e34 100644 --- a/src/main.c +++ b/src/main.c @@ -232,15 +232,13 @@ static void i3_exit(void) { * Unlinks the SHM log and re-raises the signal. * */ -static void handle_signal(struct ev_loop *loop, ev_signal *w, int revents) { - int sig = w->signum; +static void handle_signal(int sig, siginfo_t *info, void *data) { fprintf(stderr, "Received signal %d, terminating\n", sig); if (*shmlogname != '\0') { fprintf(stderr, "Closing SHM log \"%s\"\n", shmlogname); shm_unlink(shmlogname); } fflush(stderr); - ev_signal_stop(loop, w); raise(sig); } @@ -783,32 +781,31 @@ int main(int argc, char *argv[]) { } xcb_ungrab_server(conn); + struct sigaction action; -#define HANDLE_SIGNAL_EV(signum) \ - do { \ - struct ev_signal *signal_watcher = scalloc(sizeof(struct ev_signal)); \ - ev_signal_init(signal_watcher, handle_signal, signum); \ - ev_signal_start(main_loop, signal_watcher); \ - } while (0) + action.sa_sigaction = handle_signal; + action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO; + sigemptyset(&action.sa_mask); if (!disable_signalhandler) setup_signal_handler(); else { /* Catch all signals with default action "Core", see signal(7) */ - HANDLE_SIGNAL_EV(SIGQUIT); - HANDLE_SIGNAL_EV(SIGILL); - HANDLE_SIGNAL_EV(SIGABRT); - HANDLE_SIGNAL_EV(SIGFPE); - HANDLE_SIGNAL_EV(SIGSEGV); + if (sigaction(SIGQUIT, &action, NULL) == -1 || + sigaction(SIGILL, &action, NULL) == -1 || + sigaction(SIGABRT, &action, NULL) == -1 || + sigaction(SIGFPE, &action, NULL) == -1 || + sigaction(SIGSEGV, &action, NULL) == -1) + ELOG("Could not setup signal handler"); } /* Catch all signals with default action "Term", see signal(7) */ - HANDLE_SIGNAL_EV(SIGHUP); - HANDLE_SIGNAL_EV(SIGINT); - HANDLE_SIGNAL_EV(SIGALRM); - HANDLE_SIGNAL_EV(SIGTERM); - HANDLE_SIGNAL_EV(SIGUSR1); - HANDLE_SIGNAL_EV(SIGUSR2); + if (sigaction(SIGHUP, &action, NULL) == -1 || + sigaction(SIGINT, &action, NULL) == -1 || + sigaction(SIGALRM, &action, NULL) == -1 || + sigaction(SIGUSR1, &action, NULL) == -1 || + sigaction(SIGUSR2, &action, NULL) == -1) + ELOG("Could not setup signal handler"); /* Ignore SIGPIPE to survive errors when an IPC client disconnects * while we are sending him a message */ From 1cbf6655816e50e6729de53671d3b5dca404120a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 27 Sep 2012 12:37:27 +0200 Subject: [PATCH 047/146] remove async-unsafe functions from signal handler --- src/main.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main.c b/src/main.c index 95498e34..3b29f7a8 100644 --- a/src/main.c +++ b/src/main.c @@ -233,12 +233,9 @@ static void i3_exit(void) { * */ static void handle_signal(int sig, siginfo_t *info, void *data) { - fprintf(stderr, "Received signal %d, terminating\n", sig); if (*shmlogname != '\0') { - fprintf(stderr, "Closing SHM log \"%s\"\n", shmlogname); shm_unlink(shmlogname); } - fflush(stderr); raise(sig); } From 0610964c222b81d2ea932152da5ae235f2033f0e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 27 Sep 2012 12:37:38 +0200 Subject: [PATCH 048/146] add testcase for commit 87525ad --- testcases/t/113-urgent.t | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/testcases/t/113-urgent.t b/testcases/t/113-urgent.t index 85d2035d..ff44e0ea 100644 --- a/testcases/t/113-urgent.t +++ b/testcases/t/113-urgent.t @@ -213,6 +213,24 @@ sync_with_i3; $urgent = count_urgent(get_ws($tmp)); is($urgent, 0, 'All urgent flags got cleared'); +################################################################################ +# Regression test: Check that urgent floating containers work properly (ticket +# #821) +################################################################################ + +$tmp = fresh_workspace; +my $floating_win = open_floating_window; + +# switch away +fresh_workspace; + +$floating_win->add_hint('urgency'); +sync_with_i3; + +cmd "workspace $tmp"; + +does_i3_live; + exit_gracefully($pid); done_testing; From 064be457e59603e1d4a4b45b4bf138dcc231a849 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 27 Sep 2012 12:41:38 +0200 Subject: [PATCH 049/146] raise floating windows when focusing (Thanks Marcos) --- src/tree.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tree.c b/src/tree.c index 3d598d50..657c8bfa 100644 --- a/src/tree.c +++ b/src/tree.c @@ -529,6 +529,7 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) if (!next) return false; + floating_raise_con(next); con_focus(con_descend_focused(next)); return true; } else { From 584a6b6b5951b5acd6839baae74c8306fd048fd1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 28 Sep 2012 17:57:17 +0200 Subject: [PATCH 050/146] Revert "raise floating windows when focusing (Thanks Marcos)" This commit breaks floating window keyboard focus order (t/135-floating-focus.t) when you have > 2 floating windows. Since keyboard focus is more important than saving one click to raise floating windows, I revert the commit. Note that we cannot implement this without keeping a third list (beneath floating_windows and focus) for the z coordinate of a floating window. This seems not worth it. This reverts commit 064be457e59603e1d4a4b45b4bf138dcc231a849. --- src/tree.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tree.c b/src/tree.c index 657c8bfa..3d598d50 100644 --- a/src/tree.c +++ b/src/tree.c @@ -529,7 +529,6 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) if (!next) return false; - floating_raise_con(next); con_focus(con_descend_focused(next)); return true; } else { From da2b47c1e5d145c5029421129264dfaa986d94f5 Mon Sep 17 00:00:00 2001 From: Simon Elsbrock Date: Fri, 28 Sep 2012 00:38:02 +0200 Subject: [PATCH 051/146] fix crash: send non-floating window with floating parent to scratchpad (thanks pkordy) Fix a crash that occured when moving a window to the scratchpad that is seemingly floating to the user, but actually a descendant of a floating parent con (and itself non-floating). If that is the case, move the floating parent container to scratchpad instead of the window. fixes #740 --- src/scratchpad.c | 16 +++++--- testcases/t/185-scratchpad.t | 76 +++++++++++++++++++++--------------- 2 files changed, 55 insertions(+), 37 deletions(-) diff --git a/src/scratchpad.c b/src/scratchpad.c index 16e26cee..7b309095 100644 --- a/src/scratchpad.c +++ b/src/scratchpad.c @@ -39,11 +39,17 @@ void scratchpad_move(Con *con) { return; } - /* 1: Ensure the window is floating. From now on, we deal with the - * CT_FLOATING_CON. We use automatic == false because the user made the - * choice that this window should be a scratchpad (and floating). */ - floating_enable(con, false); - con = con->parent; + /* 1: Ensure the window or any parent is floating. From now on, we deal + * with the CT_FLOATING_CON. We use automatic == false because the user + * made the choice that this window should be a scratchpad (and floating). + */ + Con *maybe_floating_con = con_inside_floating(con); + if (maybe_floating_con == NULL) { + floating_enable(con, false); + con = con->parent; + } else { + con = maybe_floating_con; + } /* 2: Send the window to the __i3_scratch workspace, mainting its * coordinates and not warping the pointer. */ diff --git a/testcases/t/185-scratchpad.t b/testcases/t/185-scratchpad.t index 87bda529..dafe51e0 100644 --- a/testcases/t/185-scratchpad.t +++ b/testcases/t/185-scratchpad.t @@ -323,39 +323,51 @@ does_i3_live; # 11: focus a workspace and move all of its children to the scratchpad area ################################################################################ +sub verify_scratchpad_move_multiple_win { + my $floating = shift; + + my $first = open_window; + my $second = open_window; + + if ($floating) { + cmd 'floating toggle'; + cmd 'focus tiling'; + } + + cmd 'focus parent'; + cmd 'move scratchpad'; + + does_i3_live; + + $ws = get_ws($tmp); + is(scalar @{$ws->{nodes}}, 0, 'no windows on ws'); + is(scalar @{$ws->{floating_nodes}}, 0, 'no floating windows on ws'); + + # show the first window. + cmd 'scratchpad show'; + + $ws = get_ws($tmp); + is(scalar @{$ws->{nodes}}, 0, 'no windows on ws'); + is(scalar @{$ws->{floating_nodes}}, 1, 'one floating windows on ws'); + + $old_focus = get_focused($tmp); + + cmd 'scratchpad show'; + + # show the second window. + cmd 'scratchpad show'; + + $ws = get_ws($tmp); + is(scalar @{$ws->{nodes}}, 0, 'no windows on ws'); + is(scalar @{$ws->{floating_nodes}}, 1, 'one floating windows on ws'); + + isnt(get_focused($tmp), $old_focus, 'focus changed'); +} + $tmp = fresh_workspace; - -my $first = open_window; -my $second = open_window; - -cmd 'focus parent'; -cmd 'move scratchpad'; - -does_i3_live; - -$ws = get_ws($tmp); -is(scalar @{$ws->{nodes}}, 0, 'no windows on ws'); -is(scalar @{$ws->{floating_nodes}}, 0, 'no floating windows on ws'); - -# show the first window. -cmd 'scratchpad show'; - -$ws = get_ws($tmp); -is(scalar @{$ws->{nodes}}, 0, 'no windows on ws'); -is(scalar @{$ws->{floating_nodes}}, 1, 'one floating windows on ws'); - -$old_focus = get_focused($tmp); - -cmd 'scratchpad show'; - -# show the second window. -cmd 'scratchpad show'; - -$ws = get_ws($tmp); -is(scalar @{$ws->{nodes}}, 0, 'no windows on ws'); -is(scalar @{$ws->{floating_nodes}}, 1, 'one floating windows on ws'); - -isnt(get_focused($tmp), $old_focus, 'focus changed'); +verify_scratchpad_move_multiple_win(0); +$tmp = fresh_workspace; +verify_scratchpad_move_multiple_win(1); # TODO: make i3bar display *something* when a window on the scratchpad has the urgency hint From a2daf229fbd8318b4f6e7bf6900f2c5c5fec76c8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 28 Sep 2012 18:03:36 +0200 Subject: [PATCH 052/146] userguide: document how to "un-scratchpad" a window (Thanks knopwob) --- docs/userguide | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/userguide b/docs/userguide index 9ffb678e..cde7bfc4 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1724,7 +1724,9 @@ invisible until you show it again. There is no way to open that workspace. Instead, when using +scratchpad show+, the window will be shown again, as a floating window, centered on your current workspace (using +scratchpad show+ on a visible scratchpad window will make it hidden again, so you can have a -keybinding to toggle). +keybinding to toggle). Note that this is just a normal floating window, so if +you want to "remove it from scratchpad", you can simple make it tiling again +(+floating toggle+). As the name indicates, this is useful for having a window with your favorite editor always at hand. However, you can also use this for other permanently From 5d8e3f58f68749f2e88997e4deb94a2ef3dc1945 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 28 Sep 2012 19:29:14 +0200 Subject: [PATCH 053/146] Fix 'border toggle' (it "skipped" 1px border) (Thanks joepd) fixes #818 --- src/commands.c | 6 +++--- testcases/t/169-border-toggle.t | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/commands.c b/src/commands.c index 000dd208..607e1c11 100644 --- a/src/commands.c +++ b/src/commands.c @@ -799,11 +799,11 @@ void cmd_border(I3_CMD, char *border_style_str, char *border_width ) { border_style++; border_style %= 3; if (border_style == BS_NORMAL) - current->con->current_border_width = 2; + tmp_border_width = 2; else if (border_style == BS_NONE) - current->con->current_border_width = 0; + tmp_border_width = 0; else if (border_style == BS_PIXEL) - current->con->current_border_width = 1; + tmp_border_width = 1; } else { if (strcmp(border_style_str, "normal") == 0) border_style = BS_NORMAL; diff --git a/testcases/t/169-border-toggle.t b/testcases/t/169-border-toggle.t index 33f3a8ec..c89dcc76 100644 --- a/testcases/t/169-border-toggle.t +++ b/testcases/t/169-border-toggle.t @@ -29,25 +29,31 @@ is($nodes[0]->{border}, 'normal', 'border style normal'); cmd 'border 1pixel'; @nodes = @{get_ws_content($tmp)}; is($nodes[0]->{border}, 'pixel', 'border style 1pixel'); +is($nodes[0]->{current_border_width}, 1, 'border width = 1px'); cmd 'border none'; @nodes = @{get_ws_content($tmp)}; is($nodes[0]->{border}, 'none', 'border style none'); +is($nodes[0]->{current_border_width}, 0, 'border width = 0px'); cmd 'border normal'; @nodes = @{get_ws_content($tmp)}; is($nodes[0]->{border}, 'normal', 'border style back to normal'); +is($nodes[0]->{current_border_width}, 2, 'border width = 2px'); cmd 'border toggle'; @nodes = @{get_ws_content($tmp)}; is($nodes[0]->{border}, 'none', 'border style none'); +is($nodes[0]->{current_border_width}, 0, 'border width = 0px'); cmd 'border toggle'; @nodes = @{get_ws_content($tmp)}; is($nodes[0]->{border}, 'pixel', 'border style 1pixel'); +is($nodes[0]->{current_border_width}, 1, 'border width = 1px'); cmd 'border toggle'; @nodes = @{get_ws_content($tmp)}; is($nodes[0]->{border}, 'normal', 'border style back to normal'); +is($nodes[0]->{current_border_width}, 2, 'border width = 2px'); done_testing; From 66b389cba1a197020be91cb67cadf793efdc68e4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 28 Sep 2012 20:36:25 +0200 Subject: [PATCH 054/146] Make the resize command honor criteria (Thanks Tblue) fixes #816 --- src/commands.c | 31 +++++++++++++++++-------------- testcases/t/141-resize.t | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/src/commands.c b/src/commands.c index 607e1c11..070f6353 100644 --- a/src/commands.c +++ b/src/commands.c @@ -575,10 +575,9 @@ static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floatin } } -static bool cmd_resize_tiling_direction(I3_CMD, char *way, char *direction, int ppt) { +static bool cmd_resize_tiling_direction(I3_CMD, Con *current, char *way, char *direction, int ppt) { LOG("tiling resize\n"); /* get the appropriate current container (skip stacked/tabbed cons) */ - Con *current = focused; Con *other = NULL; double percentage = 0; while (current->parent->layout == L_STACKED || @@ -658,10 +657,9 @@ static bool cmd_resize_tiling_direction(I3_CMD, char *way, char *direction, int return true; } -static bool cmd_resize_tiling_width_height(I3_CMD, char *way, char *direction, int ppt) { +static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, char *way, char *direction, int ppt) { LOG("width/height resize\n"); /* get the appropriate current container (skip stacked/tabbed cons) */ - Con *current = focused; while (current->parent->layout == L_STACKED || current->parent->layout == L_TABBED) current = current->parent; @@ -756,17 +754,22 @@ void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resiz ppt *= -1; } - Con *floating_con; - if ((floating_con = con_inside_floating(focused))) { - cmd_resize_floating(current_match, cmd_output, way, direction, floating_con, px); - } else { - if (strcmp(direction, "width") == 0 || - strcmp(direction, "height") == 0) { - if (!cmd_resize_tiling_width_height(current_match, cmd_output, way, direction, ppt)) - return; + HANDLE_EMPTY_MATCH; + + owindow *current; + TAILQ_FOREACH(current, &owindows, owindows) { + Con *floating_con; + if ((floating_con = con_inside_floating(current->con))) { + cmd_resize_floating(current_match, cmd_output, way, direction, floating_con, px); } else { - if (!cmd_resize_tiling_direction(current_match, cmd_output, way, direction, ppt)) - return; + if (strcmp(direction, "width") == 0 || + strcmp(direction, "height") == 0) { + if (!cmd_resize_tiling_width_height(current_match, cmd_output, current->con, way, direction, ppt)) + return; + } else { + if (!cmd_resize_tiling_direction(current_match, cmd_output, current->con, way, direction, ppt)) + return; + } } } diff --git a/testcases/t/141-resize.t b/testcases/t/141-resize.t index e038a87b..97315c3d 100644 --- a/testcases/t/141-resize.t +++ b/testcases/t/141-resize.t @@ -255,4 +255,38 @@ cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y the same as before'); cmp_ok($content[0]->{rect}->{height}, '<', $oldrect->{height}, 'height smaller than before'); cmp_ok($content[0]->{rect}->{width}, '==', $oldrect->{width}, 'width the same as before'); +################################################################################ +# Check that resizing with criteria works +################################################################################ + +$tmp = fresh_workspace; + +my $left = open_floating_window; +my $right = open_floating_window; + +sub get_floating_rect { + my ($window_id) = @_; + + my $floating_nodes = get_ws($tmp)->{floating_nodes}; + for my $floating_node (@$floating_nodes) { + # Get all the windows within that floating container + my @window_ids = map { $_->{window} } @{$floating_node->{nodes}}; + if ($window_id ~~ @window_ids) { + return $floating_node->{rect}; + } + } + + return undef; +} + +# focus is on the right window, so we resize the left one using criteria +my $leftold = get_floating_rect($left->id); +my $rightold = get_floating_rect($right->id); +cmd '[id="' . $left->id . '"] resize shrink height 10px or 10ppt'; + +my $leftnew = get_floating_rect($left->id); +my $rightnew = get_floating_rect($right->id); +is($rightnew->{height}, $rightold->{height}, 'height of right container unchanged'); +is($leftnew->{height}, $leftold->{height} - 10, 'height of left container changed'); + done_testing; From 43d486441d11c51078a6bf8b1ba2653216664c71 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 28 Sep 2012 23:04:37 +0200 Subject: [PATCH 055/146] =?UTF-8?q?Bugfix:=20with=20one=20ws=20per=20outpu?= =?UTF-8?q?t,=20don=E2=80=99t=20crash=20on=20cross-output=20moves=20(Thank?= =?UTF-8?q?s=20moju)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes #827 --- src/commands.c | 20 ++++++++--- src/workspace.c | 3 +- testcases/t/507-workspace-move-crash.t | 48 ++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 testcases/t/507-workspace-move-crash.t diff --git a/src/commands.c b/src/commands.c index 070f6353..0263802f 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1158,6 +1158,7 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) { /* notify the IPC listeners */ ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); } + DLOG("Detaching\n"); /* detach from the old output and attach to the new output */ Con *old_content = ws->parent; @@ -1182,10 +1183,21 @@ void cmd_move_workspace_to_output(I3_CMD, char *name) { workspace_show(ws); } - /* Call the on_remove_child callback of the workspace which previously - * was visible on the destination output. Since it is no longer - * visible, it might need to get cleaned up. */ - CALL(previously_visible_ws, on_remove_child); + /* NB: We cannot simply work with previously_visible_ws since it might + * have been cleaned up by workspace_show() already, depending on the + * focus order/number of other workspaces on the output. + * Instead, we loop through the available workspaces and only work with + * previously_visible_ws if we still find it. */ + TAILQ_FOREACH(ws, &(content->nodes_head), nodes) { + if (ws != previously_visible_ws) + continue; + + /* Call the on_remove_child callback of the workspace which previously + * was visible on the destination output. Since it is no longer + * visible, it might need to get cleaned up. */ + CALL(previously_visible_ws, on_remove_child); + break; + } } cmd_output->needs_tree_render = true; diff --git a/src/workspace.c b/src/workspace.c index 3a5844cb..14840e4a 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -365,7 +365,7 @@ static void _workspace_show(Con *workspace) { workspace_reassign_sticky(workspace); - LOG("switching to %p\n", workspace); + DLOG("switching to %p / %s\n", workspace, workspace->name); Con *next = con_descend_focused(workspace); /* Memorize current output */ @@ -400,6 +400,7 @@ static void _workspace_show(Con *workspace) { } else con_focus(next); + DLOG("old = %p / %s\n", old, (old ? old->name : "(null)")); /* Close old workspace if necessary. This must be done *after* doing * urgency handling, because tree_close() will do a con_focus() on the next * client, which will clear the urgency flag too early. Also, there is no diff --git a/testcases/t/507-workspace-move-crash.t b/testcases/t/507-workspace-move-crash.t new file mode 100644 index 00000000..9e80553b --- /dev/null +++ b/testcases/t/507-workspace-move-crash.t @@ -0,0 +1,48 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Tests whether i3 crashes on cross-output moves with one workspace per output. +# Ticket: #827 +# Bug still in: 4.3-78-g66b389c +# +use List::Util qw(first); +use i3test i3_autostart => 0; + +# Ensure the pointer is at (0, 0) so that we really start on the first +# (the left) workspace. +$x->root->warp_pointer(0, 0); + +my $config = < Date: Fri, 28 Sep 2012 13:54:24 -0400 Subject: [PATCH 056/146] Implement moving workspaces as if they're regular containers --- include/workspace.h | 7 ++ src/commands.c | 11 +-- src/con.c | 20 ++++-- src/workspace.c | 31 ++++++++ testcases/t/132-move-workspace.t | 120 +++++++++++++++++++++++++++++++ 5 files changed, 179 insertions(+), 10 deletions(-) diff --git a/include/workspace.h b/include/workspace.h index a7f2d13b..907e959f 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -174,4 +174,11 @@ void ws_force_orientation(Con *ws, orientation_t orientation); */ Con *workspace_attach_to(Con *ws); +/** + * Creates a new container and re-parents all of children from the given + * workspace into it. + * + * The container inherits the layout from the workspace. + */ +Con *workspace_encapsulate(Con *ws); #endif diff --git a/src/commands.c b/src/commands.c index 0263802f..53532435 100644 --- a/src/commands.c +++ b/src/commands.c @@ -388,7 +388,8 @@ void cmd_move_con_to_workspace(I3_CMD, char *which) { * when criteria was specified but didn't match any window or * when criteria wasn't specified and we don't have any window focused. */ if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) || - (match_is_empty(current_match) && focused->type == CT_WORKSPACE)) { + (match_is_empty(current_match) && focused->type == CT_WORKSPACE && + con_is_leaf(focused))) { ysuccess(false); return; } @@ -476,9 +477,8 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) { ysuccess(false); return; } - - if (match_is_empty(current_match) && focused->type == CT_WORKSPACE) { - ELOG("No window to move, you have focused a workspace.\n"); + else if (match_is_empty(current_match) && focused->type == CT_WORKSPACE && + con_is_leaf(focused)) { ysuccess(false); return; } @@ -512,7 +512,8 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) { * when criteria was specified but didn't match any window or * when criteria wasn't specified and we don't have any window focused. */ if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) || - (match_is_empty(current_match) && focused->type == CT_WORKSPACE)) { + (match_is_empty(current_match) && focused->type == CT_WORKSPACE && + con_is_leaf(focused))) { ysuccess(false); return; } diff --git a/src/con.c b/src/con.c index 1140fe80..5bba8c7c 100644 --- a/src/con.c +++ b/src/con.c @@ -607,11 +607,6 @@ void con_toggle_fullscreen(Con *con, int fullscreen_mode) { * */ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool dont_warp) { - if (con->type == CT_WORKSPACE) { - DLOG("Moving workspaces is not yet implemented.\n"); - return; - } - /* Prevent moving if this would violate the fullscreen focus restrictions. */ if (!con_fullscreen_permits_focusing(workspace)) { LOG("Cannot move out of a fullscreen container"); @@ -629,6 +624,21 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool return; } + if (con->type == CT_WORKSPACE) { + con = workspace_encapsulate(con); + if (con == NULL) { + ELOG("Workspace failed to move its contents into a container!\n"); + return; + } + + /* Re-parent all of the old workspace's floating windows. */ + Con *child; + while (!TAILQ_EMPTY(&(source_ws->floating_head))) { + child = TAILQ_FIRST(&(source_ws->floating_head)); + con_move_to_workspace(child, workspace, true, true); + } + } + /* Save the current workspace. So we can call workspace_show() by the end * of this function. */ Con *current_ws = con_get_workspace(focused); diff --git a/src/workspace.c b/src/workspace.c index 14840e4a..3fdb7db8 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -830,3 +830,34 @@ Con *workspace_attach_to(Con *ws) { return new; } + +/** + * Creates a new container and re-parents all of children from the given + * workspace into it. + * + * The container inherits the layout from the workspace. + */ +Con *workspace_encapsulate(Con *ws) { + if (TAILQ_EMPTY(&(ws->nodes_head))) { + ELOG("Workspace %p / %s has no children to encapsulate\n", ws, ws->name); + return NULL; + } + + Con *new = con_new(NULL, NULL); + new->parent = ws; + new->layout = ws->layout; + + DLOG("Moving children of workspace %p / %s into container %p\n", + ws, ws->name, new); + + Con *child; + while (!TAILQ_EMPTY(&(ws->nodes_head))) { + child = TAILQ_FIRST(&(ws->nodes_head)); + con_detach(child); + con_attach(child, new, true); + } + + con_attach(new, ws, true); + + return new; +} diff --git a/testcases/t/132-move-workspace.t b/testcases/t/132-move-workspace.t index a4f6b608..730041a6 100644 --- a/testcases/t/132-move-workspace.t +++ b/testcases/t/132-move-workspace.t @@ -198,4 +198,124 @@ cmd 'move workspace number 17'; ok(workspace_exists('17'), 'workspace 17 created by moving'); is(@{get_ws('17')->{nodes}}, 1, 'one node on ws 16'); +################################################################################ +# The following four tests verify the various 'move workspace' commands when +# the selection is itself a workspace. +################################################################################ + +# borrowed from 122-split.t +# recursively sums up all nodes and their children +sub sum_nodes { + my ($nodes) = @_; + + return 0 if !@{$nodes}; + + my @children = (map { @{$_->{nodes}} } @{$nodes}, + map { @{$_->{'floating_nodes'}} } @{$nodes}); + + return @{$nodes} + sum_nodes(\@children); +} + +############################################################ +# move workspace 'next|prev' +############################################################ +$tmp = get_unused_workspace(); +$tmp2 = get_unused_workspace(); + +cmd "workspace $tmp"; +cmd 'open'; +is_num_children($tmp, 1, 'one container on first ws'); + +cmd "workspace $tmp2"; +cmd 'open'; +is_num_children($tmp2, 1, 'one container on second ws'); +cmd 'open'; +is_num_children($tmp2, 2, 'two containers on second ws'); + +cmd 'focus parent'; +cmd 'move workspace prev'; + +is_num_children($tmp, 2, 'two child containers on first ws'); +is(sum_nodes(get_ws_content($tmp)), 4, 'four total containers on first ws'); +is_num_children($tmp2, 0, 'no containers on second ws'); + +############################################################ +# move workspace current +# This is a special case that should be a no-op. +############################################################ +$tmp = fresh_workspace(); + +cmd 'open'; +is_num_children($tmp, 1, 'one container on first ws'); +my $tmpcount = sum_nodes(get_ws_content($tmp)); + +cmd 'focus parent'; +cmd "move workspace $tmp"; + +is(sum_nodes(get_ws_content($tmp)), $tmpcount, 'number of containers in first ws unchanged'); + +############################################################ +# move workspace '' +############################################################ +$tmp2 = get_unused_workspace(); +$tmp = fresh_workspace(); + +cmd 'open'; +is_num_children($tmp, 1, 'one container on first ws'); + +cmd "workspace $tmp2"; +cmd 'open'; +is_num_children($tmp2, 1, 'one container on second ws'); +cmd 'open'; +is_num_children($tmp2, 2, 'two containers on second ws'); + +cmd 'focus parent'; +cmd "move workspace $tmp"; + +is_num_children($tmp, 2, 'two child containers on first ws'); +is(sum_nodes(get_ws_content($tmp)), 4, 'four total containers on first ws'); +is_num_children($tmp2, 0, 'no containers on second ws'); + +############################################################ +# move workspace number '' +############################################################ +cmd 'workspace 18'; +cmd 'open'; +is_num_children('18', 1, 'one container on ws 18'); + +cmd 'workspace 19'; +cmd 'open'; +is_num_children('19', 1, 'one container on ws 19'); +cmd 'open'; +is_num_children('19', 2, 'two containers on ws 19'); + +cmd 'focus parent'; +cmd 'move workspace number 18'; + +is_num_children('18', 2, 'two child containers on ws 18'); +is(sum_nodes(get_ws_content('18')), 4, 'four total containers on ws 18'); +is_num_children('19', 0, 'no containers on ws 19'); + +################################################################### +# move workspace '' with a floating child +################################################################### +$tmp2 = get_unused_workspace(); +$tmp = fresh_workspace(); +cmd 'open'; +cmd 'floating toggle'; +cmd 'open'; +cmd 'floating toggle'; +cmd 'open'; + +$ws = get_ws($tmp); +is_num_children($tmp, 1, 'one container on first workspace'); +is(@{$ws->{floating_nodes}}, 2, 'two floating nodes on first workspace'); + +cmd 'focus parent'; +cmd "move workspace $tmp2"; + +$ws = get_ws($tmp2); +is_num_children($tmp2, 1, 'one container on second workspace'); +is(@{$ws->{floating_nodes}}, 2, 'two floating nodes on second workspace'); + done_testing; From a9d859f84e607745aa73ff9cbbd72111a4be4113 Mon Sep 17 00:00:00 2001 From: Deiz Date: Sun, 30 Sep 2012 22:44:19 -0400 Subject: [PATCH 057/146] Only re-focus the workspace when moving a con if the target ws is hidden. --- src/con.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/con.c b/src/con.c index 5bba8c7c..77608489 100644 --- a/src/con.c +++ b/src/con.c @@ -714,12 +714,15 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool * we don’t focus when there is a fullscreen con on that workspace. */ if (!con_is_internal(workspace) && con_get_fullscreen_con(workspace, CF_OUTPUT) == NULL) { - /* We need to save focus on workspace level and restore it afterwards. - * Otherwise, we might focus a different workspace without actually - * switching workspaces. */ + /* We need to save the focused workspace on the output in case the + * new workspace is hidden and it's necessary to immediately switch + * back to the originally-focused workspace. */ Con *old_focus = TAILQ_FIRST(&(output_get_content(dest_output)->focus_head)); con_focus(con_descend_focused(con)); - con_focus(old_focus); + + /* Restore focus if the output's focused workspace has changed. */ + if (con_get_workspace(focused) != old_focus) + con_focus(old_focus); } /* 8: when moving to a visible workspace on a different output, we keep the From 029d9040e184ca22872e780d57fd58b221f353eb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 3 Oct 2012 00:04:05 +0200 Subject: [PATCH 058/146] add test for previous commit --- testcases/t/508-move-workspace-focus.t | 46 ++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 testcases/t/508-move-workspace-focus.t diff --git a/testcases/t/508-move-workspace-focus.t b/testcases/t/508-move-workspace-focus.t new file mode 100644 index 00000000..7d42ff4e --- /dev/null +++ b/testcases/t/508-move-workspace-focus.t @@ -0,0 +1,46 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Regression test: Verify that focus is correct after moving a floating window +# to a workspace on a different visible output. +# Bug still in: 4.3-83-ge89a25f +use i3test i3_autostart => 0; + +# Ensure the pointer is at (0, 0) so that we really start on the first +# (the left) workspace. +$x->root->warp_pointer(0, 0); + +my $config = < 0); +open_window; + +my $right_ws = fresh_workspace(output => 1); +open_window; +my $right_float = open_floating_window; + +cmd "move workspace $left_ws"; +is($x->input_focus, $right_float->id, 'floating window still focused'); + +exit_gracefully($pid); + +done_testing; From 85dc1b0865da04a30647704da866fa95ecc964d7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 3 Oct 2012 00:13:30 +0200 Subject: [PATCH 059/146] testsuite: add 'new-test' helper script --- testcases/new-test | 100 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100755 testcases/new-test diff --git a/testcases/new-test b/testcases/new-test new file mode 100755 index 00000000..2849301e --- /dev/null +++ b/testcases/new-test @@ -0,0 +1,100 @@ +#!/usr/bin/env perl +# vim:ts=4:sw=4:expandtab +# © 2012 Michael Stapelberg and contributors +# Script to create a new testcase from a template. +# +# # Create (and edit) a new test for moving floating windows +# ./new-test floating move +# +# # Create (and edit) a multi-monitor test for moving workspaces +# ./new-test -m move workspaces + +use strict; +use warnings; +use File::Basename qw(basename); +use Getopt::Long; +use v5.10; + +my $multi_monitor; + +my $result = GetOptions( + 'multi-monitor|mm' => \$multi_monitor +); + +my $testname = join(' ', @ARGV); +$testname =~ s/ /-/g; + +my $header = <<'EOF'; +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# TODO: Description of this file. +# Ticket: #999 +# Bug still in: #GITREV# +EOF + +# Figure out the next test filename. +my @files; +if ($multi_monitor) { + @files = grep { basename($_) =~ /^5/ } ; +} else { + @files = grep { basename($_) !~ /^5/ } ; +} +my ($latest) = sort { $b cmp $a } @files; +my ($num) = (basename($latest) =~ /^([0-9]+)/); +my $filename = "t/" . ($num+1) . "-" . lc($testname) . ".t"; + +# Get the current git revision. +chomp(my $gitrev = qx(git describe --tags)); +$header =~ s/#GITREV#/$gitrev/g; + +# Create the file based on our template. +open(my $fh, '>', $filename); +print $fh $header; +if ($multi_monitor) { + print $fh <<'EOF'; +use i3test i3_autostart => 0; + +# Ensure the pointer is at (0, 0) so that we really start on the first +# (the left) workspace. +$x->root->warp_pointer(0, 0); + +my $config = < Date: Sun, 30 Sep 2012 00:30:03 -0400 Subject: [PATCH 060/146] Maintain relative positioning when moving floating windows between outputs. --- src/floating.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/floating.c b/src/floating.c index d978c643..17fe74cc 100644 --- a/src/floating.c +++ b/src/floating.c @@ -220,17 +220,22 @@ void floating_enable(Con *con, bool automatic) { /* Sanity check: Are the coordinates on the appropriate output? If not, we * need to change them */ - Output *current_output = get_output_containing(nc->rect.x, nc->rect.y); + Output *current_output = get_output_containing(nc->rect.x + + (nc->rect.width / 2), nc->rect.y + (nc->rect.height / 2)); + Con *correct_output = con_get_output(ws); if (!current_output || current_output->con != correct_output) { DLOG("This floating window is on the wrong output, fixing coordinates (currently (%d, %d))\n", nc->rect.x, nc->rect.y); - /* Take the relative coordinates of the current output, then add them - * to the coordinate space of the correct output */ - uint32_t rel_x = (nc->rect.x - (current_output ? current_output->con->rect.x : 0)); - uint32_t rel_y = (nc->rect.y - (current_output ? current_output->con->rect.y : 0)); - nc->rect.x = correct_output->rect.x + rel_x; - nc->rect.y = correct_output->rect.y + rel_y; + + /* If moving from one output to another, keep the relative position + * consistent (e.g. a centered dialog will remain centered). */ + if (current_output) + floating_fix_coordinates(nc, ¤t_output->con->rect, &correct_output->rect); + else { + nc->rect.x = correct_output->rect.x; + nc->rect.y = correct_output->rect.y; + } } DLOG("Floating rect: (%d, %d) with %d x %d\n", nc->rect.x, nc->rect.y, nc->rect.width, nc->rect.height); @@ -634,16 +639,18 @@ void floating_fix_coordinates(Con *con, Rect *old_rect, Rect *new_rect) { new_rect->x, new_rect->y, new_rect->width, new_rect->height); /* First we get the x/y coordinates relative to the x/y coordinates * of the output on which the window is on */ - int32_t rel_x = (con->rect.x - old_rect->x); - int32_t rel_y = (con->rect.y - old_rect->y); + int32_t rel_x = con->rect.x - old_rect->x + (int32_t)(con->rect.width / 2); + int32_t rel_y = con->rect.y - old_rect->y + (int32_t)(con->rect.height / 2); /* Then we calculate a fraction, for example 0.63 for a window * which is at y = 1212 of a 1920 px high output */ DLOG("rel_x = %d, rel_y = %d, fraction_x = %f, fraction_y = %f, output->w = %d, output->h = %d\n", rel_x, rel_y, (double)rel_x / old_rect->width, (double)rel_y / old_rect->height, old_rect->width, old_rect->height); /* Here we have to multiply at first. Or we will lose precision when not compiled with -msse2 */ - con->rect.x = (int32_t)new_rect->x + (double)(rel_x * (int32_t)new_rect->width) / (int32_t)old_rect->width; - con->rect.y = (int32_t)new_rect->y + (double)(rel_y * (int32_t)new_rect->height) / (int32_t)old_rect->height; + con->rect.x = (int32_t)new_rect->x + (double)(rel_x * (int32_t)new_rect->width) + / (int32_t)old_rect->width - (int32_t)(con->rect.width / 2); + con->rect.y = (int32_t)new_rect->y + (double)(rel_y * (int32_t)new_rect->height) + / (int32_t)old_rect->height - (int32_t)(con->rect.height / 2); DLOG("Resulting coordinates: x = %d, y = %d\n", con->rect.x, con->rect.y); } From f89bbe0746a75d6694fc7d305bef36171bac89f6 Mon Sep 17 00:00:00 2001 From: Deiz Date: Sun, 30 Sep 2012 05:26:38 -0400 Subject: [PATCH 061/146] Focus the relevant workspace when clicking any container. --- src/click.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/click.c b/src/click.c index a1da00ac..13f51acf 100644 --- a/src/click.c +++ b/src/click.c @@ -179,6 +179,22 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod DLOG("--> OUTCOME = %p\n", con); DLOG("type = %d, name = %s\n", con->type, con->name); + /* Any click in a workspace should focus that workspace. If the + * workspace is on another output we need to do a workspace_show in + * order for i3bar (and others) to notice the change in workspace. */ + Con *ws = con_get_workspace(con); + Con *focused_workspace = con_get_workspace(focused); + + if (!ws) { + ws = TAILQ_FIRST(&(output_get_content(con_get_output(con))->focus_head)); + if (!ws) + goto done; + } + + if (ws != focused_workspace) + workspace_show(ws); + focused_id = XCB_NONE; + /* don’t handle dockarea cons, they must not be focused */ if (con->parent->type == CT_DOCKAREA) goto done; @@ -207,15 +223,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod goto done; } - /* 2: focus this con. If the workspace is on another output we need to - * do a workspace_show in order for i3bar (and others) to notice the - * change in workspace. */ - Con *ws = con_get_workspace(con); - Con *focused_workspace = con_get_workspace(focused); - - if (ws != focused_workspace) - workspace_show(ws); - focused_id = XCB_NONE; + /* 2: focus this con. */ con_focus(con); /* 3: For floating containers, we also want to raise them on click. From f406f7187c9ed91ceb3029a8ce8e112576283a72 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 3 Oct 2012 23:54:35 +0200 Subject: [PATCH 062/146] docs/ipc: remove unnecessary newline (Thanks Merovius) --- docs/ipc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ipc b/docs/ipc index a6666ef3..0cfb0320 100644 --- a/docs/ipc +++ b/docs/ipc @@ -1,7 +1,7 @@ IPC interface (interprocess communication) ========================================== Michael Stapelberg -August 2012 +October 2012 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 @@ -82,7 +82,7 @@ So, a typical message could look like this: Or, as a hexdump: ------------------------------------------------------------------------------ 00000000 69 33 2d 69 70 63 04 00 00 00 00 00 00 00 65 78 |i3-ipc........ex| -00000010 69 74 0a |it.| +00000010 69 74 |it| ------------------------------------------------------------------------------ To generate and send such a message, you could use the following code in Perl: From 34bd8af634434e324514a7ee416b59c7b9974608 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 3 Oct 2012 23:59:33 +0200 Subject: [PATCH 063/146] docs/ipc: add a warning to use an existing library (Thanks slowpoke) --- docs/ipc | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/ipc b/docs/ipc index 0cfb0320..6bdccd0b 100644 --- a/docs/ipc +++ b/docs/ipc @@ -19,6 +19,13 @@ calling +i3 --get-socketpath+. All i3 utilities, like +i3-msg+ and +i3-input+ will read the +I3_SOCKET_PATH+ X11 property, stored on the X11 root window. +[WARNING] +.Use an existing library! +There are existing libraries for many languages. You can have a look at +<> or search the web if your language of choice is not mentioned. +Usually, it is not necessary to implement low-level communication with i3 +directly. + == Establishing a connection To establish a connection, simply open the IPC socket. The following code @@ -667,7 +674,9 @@ mode is simply named default. { "change": "default" } --------------------------- -== See also +== See also (existing libraries) + +[[libraries]] For some languages, libraries are available (so you don’t have to implement all this on your own). This list names some (if you wrote one, please let me From b943e3807d46c72667b70cafd686780de716b440 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 4 Oct 2012 17:04:37 +0200 Subject: [PATCH 064/146] release-notes: s/mod+l/mod+e/ (Thanks hax404) --- RELEASE-NOTES-4.3 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES-4.3 b/RELEASE-NOTES-4.3 index ca77397a..68fce10a 100644 --- a/RELEASE-NOTES-4.3 +++ b/RELEASE-NOTES-4.3 @@ -22,7 +22,7 @@ We also made the orientation (horizontal/vertical) part of the layout To change a splith container into a splitv container, use either "layout splitv" or "layout toggle split". The latter command is used in the - default config as mod+l (formerly "layout default"). In case you have + default config as mod+e (formerly "layout default"). In case you have "layout default" in your config file, it is recommended to just replace it by "layout toggle split", which will work as "layout default" did before when pressing it once, but toggle between horizontal/vertical From 2eeb2a10672802f8a485d061f7d265ec0619af7c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 4 Oct 2012 17:05:08 +0200 Subject: [PATCH 065/146] shmlog: Remove O_TRUNC flag for shm_open, we truncate ourselves --- src/log.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/log.c b/src/log.c index 16fa0bed..42e714ef 100644 --- a/src/log.c +++ b/src/log.c @@ -108,7 +108,7 @@ void init_logging(void) { #endif logbuffer_size = min(physical_mem_bytes * 0.01, shmlog_size); sasprintf(&shmlogname, "/i3-log-%d", getpid()); - logbuffer_shm = shm_open(shmlogname, O_RDWR | O_CREAT | O_TRUNC, S_IREAD | S_IWRITE); + logbuffer_shm = shm_open(shmlogname, O_RDWR | O_CREAT, S_IREAD | S_IWRITE); if (logbuffer_shm == -1) { ELOG("Could not shm_open SHM segment for the i3 log: %s\n", strerror(errno)); return; From d7e5da8b39ec8279588cc60c28dabd2272a6795d Mon Sep 17 00:00:00 2001 From: Deiz Date: Thu, 4 Oct 2012 04:22:41 -0400 Subject: [PATCH 066/146] Un-fullscreen as needed when moving fullscreen containers This avoids a case where a fullscreen container could be moved onto a workspace that already had its own fullscreen container, leading to two fullscreen containers on top of each other. --- src/con.c | 11 +++++++++-- testcases/t/100-fullscreen.t | 28 +++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/con.c b/src/con.c index 77608489..1389bf53 100644 --- a/src/con.c +++ b/src/con.c @@ -696,6 +696,14 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool } } + /* If moving a fullscreen container and the destination already has a + * fullscreen window on it, un-fullscreen the target's fullscreen con. */ + Con *fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT); + if (con->fullscreen_mode != CF_NONE && fullscreen != NULL) { + con_toggle_fullscreen(fullscreen, CF_OUTPUT); + fullscreen = NULL; + } + DLOG("Re-attaching container to %p / %s\n", next, next->name); /* 5: re-attach the con to the parent of this focused container */ Con *parent = con->parent; @@ -712,8 +720,7 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool * invisible. * We don’t focus the con for i3 pseudo workspaces like __i3_scratch and * we don’t focus when there is a fullscreen con on that workspace. */ - if (!con_is_internal(workspace) && - con_get_fullscreen_con(workspace, CF_OUTPUT) == NULL) { + if (!con_is_internal(workspace) && !fullscreen) { /* We need to save the focused workspace on the output in case the * new workspace is hidden and it's necessary to immediately switch * back to the originally-focused workspace. */ diff --git a/testcases/t/100-fullscreen.t b/testcases/t/100-fullscreen.t index 81a97d06..cec7000a 100644 --- a/testcases/t/100-fullscreen.t +++ b/testcases/t/100-fullscreen.t @@ -22,7 +22,10 @@ my $i3 = i3(get_socket_path()); my $tmp = fresh_workspace; sub fullscreen_windows { - scalar grep { $_->{fullscreen_mode} != 0 } @{get_ws_content($tmp)} + my $ws = $tmp; + $ws = shift if @_; + + scalar grep { $_->{fullscreen_mode} != 0 } @{get_ws_content($ws)} } # get the output of this workspace @@ -188,4 +191,27 @@ is($x->input_focus, $window->id, 'fullscreen window focused'); cmd 'focus left'; is($x->input_focus, $window->id, 'fullscreen window still focused'); +################################################################################ +# Verify that fullscreening a window on a second workspace and moving it onto +# the first workspace unfullscreens the first window. +################################################################################ + +my $tmp2 = fresh_workspace; +$swindow = open_window; + +cmd 'fullscreen'; + +is(fullscreen_windows($tmp2), 1, 'one fullscreen window on second ws'); + +cmd "move workspace $tmp"; + +is(fullscreen_windows($tmp2), 0, 'no fullscreen windows on second ws'); +is(fullscreen_windows($tmp), 1, 'one fullscreen window on first ws'); + +$swindow->fullscreen(0); +sync_with_i3; + +# Verify that $swindow was the one that initially remained fullscreen. +is(fullscreen_windows($tmp), 0, 'no fullscreen windows on first ws'); + done_testing; From fdcba7b91aed15f43d4fb44c7083eed92549fb3a Mon Sep 17 00:00:00 2001 From: Deiz Date: Wed, 3 Oct 2012 13:10:48 -0400 Subject: [PATCH 067/146] Replace the discrete 'split' Con property with a simple function. --- include/con.h | 6 ++++++ include/data.h | 2 -- src/con.c | 27 ++++++++++++++++++++++----- src/floating.c | 1 - src/ipc.c | 5 +---- src/load_layout.c | 14 ++------------ src/tree.c | 5 ++--- src/workspace.c | 2 -- testcases/t/116-nestedcons.t | 1 - 9 files changed, 33 insertions(+), 30 deletions(-) diff --git a/include/con.h b/include/con.h index 5bf82487..8b9ae9c7 100644 --- a/include/con.h +++ b/include/con.h @@ -33,6 +33,12 @@ void con_focus(Con *con); */ bool con_is_leaf(Con *con); +/* + * Returns true if a container should be considered split. + * + */ +bool con_is_split(Con *con); + /** * Returns true if this node accepts a window (if the node swallows windows, * it might already have swallowed enough and cannot hold any more). diff --git a/include/data.h b/include/data.h index 3cf22f61..810709cd 100644 --- a/include/data.h +++ b/include/data.h @@ -440,8 +440,6 @@ struct Assignment { */ struct Con { bool mapped; - /** whether this is a split container or not */ - bool split; enum { CT_ROOT = 0, CT_OUTPUT = 1, diff --git a/src/con.c b/src/con.c index 1389bf53..d872858b 100644 --- a/src/con.c +++ b/src/con.c @@ -36,7 +36,7 @@ static void con_force_split_parents_redraw(Con *con) { Con *parent = con; while (parent && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) { - if (parent->split) + if (!con_is_leaf(parent)) FREE(parent->deco_render_params); parent = parent->parent; } @@ -232,6 +232,24 @@ bool con_is_leaf(Con *con) { return TAILQ_EMPTY(&(con->nodes_head)); } +/* + * Returns true if a container should be considered split. + * + */ +bool con_is_split(Con *con) { + if (con_is_leaf(con)) + return false; + + switch (con->layout) { + case L_DOCKAREA: + case L_OUTPUT: + return false; + + default: + return true; + } +} + /* * Returns true if this node accepts a window (if the node swallows windows, * it might already have swallowed enough and cannot hold any more). @@ -242,7 +260,7 @@ bool con_accepts_window(Con *con) { if (con->type == CT_WORKSPACE) return false; - if (con->split) { + if (con_is_split(con)) { DLOG("container %p does not accept windows, it is a split container.\n", con); return false; } @@ -1163,7 +1181,6 @@ void con_set_layout(Con *con, int layout) { * split. */ new->layout = layout; new->last_split_layout = con->last_split_layout; - new->split = true; Con *old_focused = TAILQ_FIRST(&(con->focus_head)); if (old_focused == TAILQ_END(&(con->focus_head))) @@ -1336,7 +1353,7 @@ Rect con_minimum_size(Con *con) { /* For horizontal/vertical split containers we sum up the width (h-split) * or height (v-split) and use the maximum of the height (h-split) or width * (v-split) as minimum size. */ - if (con->split) { + if (con_is_split(con)) { uint32_t width = 0, height = 0; Con *child; TAILQ_FOREACH(child, &(con->nodes_head), nodes) { @@ -1354,7 +1371,7 @@ Rect con_minimum_size(Con *con) { } ELOG("Unhandled case, type = %d, layout = %d, split = %d\n", - con->type, con->layout, con->split); + con->type, con->layout, con_is_split(con)); assert(false); } diff --git a/src/floating.c b/src/floating.c index 17fe74cc..b884a182 100644 --- a/src/floating.c +++ b/src/floating.c @@ -99,7 +99,6 @@ void floating_enable(Con *con, bool automatic) { * otherwise. */ Con *ws = con_get_workspace(con); nc->parent = ws; - nc->split = true; nc->type = CT_FLOATING_CON; nc->layout = L_SPLITH; /* We insert nc already, even though its rect is not yet calculated. This diff --git a/src/ipc.c b/src/ipc.c index 84ef2c36..5232acf2 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -165,7 +165,7 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { /* provided for backwards compatibility only. */ ystr("orientation"); - if (!con->split) + if (!con_is_split(con)) ystr("none"); else { if (con_orientation(con) == HORIZ) @@ -202,9 +202,6 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { ystr("focused"); y(bool, (con == focused)); - ystr("split"); - y(bool, con->split); - ystr("layout"); switch (con->layout) { case L_DEFAULT: diff --git a/src/load_layout.c b/src/load_layout.c index d5735885..ca4c87ef 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -172,11 +172,6 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) { else if (strcasecmp(buf, "vertical") == 0) json_node->last_split_layout = L_SPLITV; else LOG("Unhandled orientation: %s\n", buf); - - /* What used to be an implicit check whether orientation != - * NO_ORIENTATION is now a proper separate flag. */ - if (strcasecmp(buf, "none") != 0) - json_node->split = true; free(buf); } else if (strcasecmp(last_key, "border") == 0) { char *buf = NULL; @@ -202,11 +197,9 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) { json_node->layout = L_STACKED; else if (strcasecmp(buf, "tabbed") == 0) json_node->layout = L_TABBED; - else if (strcasecmp(buf, "dockarea") == 0) { + else if (strcasecmp(buf, "dockarea") == 0) json_node->layout = L_DOCKAREA; - /* Necessary for migrating from older versions of i3. */ - json_node->split = false; - } else if (strcasecmp(buf, "output") == 0) + else if (strcasecmp(buf, "output") == 0) json_node->layout = L_OUTPUT; else if (strcasecmp(buf, "splith") == 0) json_node->layout = L_SPLITH; @@ -333,9 +326,6 @@ static int json_bool(void *ctx, int val) { to_focus = json_node; } - if (strcasecmp(last_key, "split") == 0) - json_node->split = val; - if (parsing_swallows) { if (strcasecmp(last_key, "restart_mode") == 0) current_swallow->restart_mode = val; diff --git a/src/tree.c b/src/tree.c index 3d598d50..d4794e4d 100644 --- a/src/tree.c +++ b/src/tree.c @@ -382,7 +382,6 @@ void tree_split(Con *con, orientation_t orientation) { TAILQ_REPLACE(&(parent->focus_head), con, new, focused); new->parent = parent; new->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV; - new->split = true; /* 3: swap 'percent' (resize factor) */ new->percent = con->percent; @@ -626,8 +625,8 @@ void tree_flatten(Con *con) { /* The child must have a different orientation than the con but the same as * the con’s parent to be redundant */ - if (!con->split || - !child->split || + if (!con_is_split(con) || + !con_is_split(child) || con_orientation(con) == con_orientation(child) || con_orientation(child) != con_orientation(parent)) goto recurse; diff --git a/src/workspace.c b/src/workspace.c index ef6a2add..872ec768 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -768,7 +768,6 @@ void ws_force_orientation(Con *ws, orientation_t orientation) { /* 1: create a new split container */ Con *split = con_new(NULL, NULL); split->parent = ws; - split->split = true; /* 2: copy layout from workspace */ split->layout = ws->layout; @@ -820,7 +819,6 @@ Con *workspace_attach_to(Con *ws) { /* 1: create a new split container */ Con *new = con_new(NULL, NULL); new->parent = ws; - new->split = true; /* 2: set the requested layout on the split con */ new->layout = ws->workspace_layout; diff --git a/testcases/t/116-nestedcons.t b/testcases/t/116-nestedcons.t index fc0b742a..84e86879 100644 --- a/testcases/t/116-nestedcons.t +++ b/testcases/t/116-nestedcons.t @@ -52,7 +52,6 @@ my $expected = { name => 'root', orientation => $ignore, type => 0, - split => JSON::XS::false, id => $ignore, rect => $ignore, window_rect => $ignore, From cae6fb627f1020b8b4fabaf7e6cb7a7ec7baffff Mon Sep 17 00:00:00 2001 From: Deiz Date: Wed, 3 Oct 2012 21:06:04 -0400 Subject: [PATCH 068/146] Improve startup sequence termination conditions If a window with _NET_STARTUP_ID set is moved to another workspace, it will delete any associated startup sequence immediately. This will also occur if a window has a leader with _NET_STARTUP_ID set, if the leader has no container (never been mapped). A startup sequence may also be deleted if it's matched by startup_workspace_for_window() and its 30-second timeout has elapsed. --- include/startup.h | 15 ++++ src/con.c | 32 +++++++ src/startup.c | 112 ++++++++++++++++++------- testcases/t/175-startup-notification.t | 39 ++++++++- 4 files changed, 166 insertions(+), 32 deletions(-) diff --git a/include/startup.h b/include/startup.h index bcc59a0a..e39fe63b 100644 --- a/include/startup.h +++ b/include/startup.h @@ -31,12 +31,27 @@ */ void start_application(const char *command, bool no_startup_id); +/** + * Deletes a startup sequence, ignoring whether its timeout has elapsed. + * Useful when e.g. a window is moved between workspaces and its children + * shouldn't spawn on the original workspace. + * + */ +void startup_sequence_delete(struct Startup_Sequence *sequence); + /** * Called by libstartup-notification when something happens * */ void startup_monitor_event(SnMonitorEvent *event, void *userdata); +/** + * Gets the stored startup sequence for the _NET_STARTUP_ID of a given window. + * + */ +struct Startup_Sequence *startup_sequence_get(i3Window *cwindow, + xcb_get_property_reply_t *startup_id_reply, bool ignore_mapped_leader); + /** * Checks if the given window belongs to a startup notification by checking if * the _NET_STARTUP_ID property is set on the window (or on its leader, if it’s diff --git a/src/con.c b/src/con.c index d872858b..493707d6 100644 --- a/src/con.c +++ b/src/con.c @@ -769,6 +769,38 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool con_focus(con_descend_focused(focus_next)); } + /* If anything within the container is associated with a startup sequence, + * delete it so child windows won't be created on the old workspace. */ + struct Startup_Sequence *sequence; + xcb_get_property_cookie_t cookie; + xcb_get_property_reply_t *startup_id_reply; + + if (!con_is_leaf(con)) { + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + if (!child->window) + continue; + + cookie = xcb_get_property(conn, false, child->window->id, + A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512); + startup_id_reply = xcb_get_property_reply(conn, cookie, NULL); + + sequence = startup_sequence_get(child->window, startup_id_reply, true); + if (sequence != NULL) + startup_sequence_delete(sequence); + } + } + + if (con->window) { + cookie = xcb_get_property(conn, false, con->window->id, + A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512); + startup_id_reply = xcb_get_property_reply(conn, cookie, NULL); + + sequence = startup_sequence_get(con->window, startup_id_reply, true); + if (sequence != NULL) + startup_sequence_delete(sequence); + } + CALL(parent, on_remove_child); } diff --git a/src/startup.c b/src/startup.c index 89324dbd..ee51664f 100644 --- a/src/startup.c +++ b/src/startup.c @@ -58,7 +58,7 @@ static void startup_timeout(EV_P_ ev_timer *w, int revents) { } /* - * Some applications (such as Firefox) mark a startup sequence as completede + * Some applications (such as Firefox) mark a startup sequence as completed * *before* they even map a window. Therefore, we cannot entirely delete the * startup sequence once it’s marked as complete. Instead, we’ll mark it for * deletion in 30 seconds and use that chance to delete old sequences. @@ -68,15 +68,10 @@ static void startup_timeout(EV_P_ ev_timer *w, int revents) { * the root window cursor. * */ -static int _delete_startup_sequence(struct Startup_Sequence *sequence) { +static int _prune_startup_sequences(void) { time_t current_time = time(NULL); int active_sequences = 0; - /* Mark the given sequence for deletion in 30 seconds. */ - sequence->delete_at = current_time + 30; - DLOG("Will delete startup sequence %s at timestamp %ld\n", - sequence->id, sequence->delete_at); - /* Traverse the list and delete everything which was marked for deletion 30 * seconds ago or earlier. */ struct Startup_Sequence *current, *next; @@ -94,20 +89,35 @@ static int _delete_startup_sequence(struct Startup_Sequence *sequence) { if (current_time <= current->delete_at) continue; - DLOG("Deleting startup sequence %s, delete_at = %ld, current_time = %ld\n", - current->id, current->delete_at, current_time); - - /* Unref the context, will be free()d */ - sn_launcher_context_unref(current->context); - - /* Delete our internal sequence */ - TAILQ_REMOVE(&startup_sequences, current, sequences); + startup_sequence_delete(current); } return active_sequences; } +/** + * Deletes a startup sequence, ignoring whether its timeout has elapsed. + * Useful when e.g. a window is moved between workspaces and its children + * shouldn't spawn on the original workspace. + * + */ +void startup_sequence_delete(struct Startup_Sequence *sequence) { + assert(sequence != NULL); + DLOG("Deleting startup sequence %s, delete_at = %ld, current_time = %ld\n", + sequence->id, sequence->delete_at, time(NULL)); + + /* Unref the context, will be free()d */ + sn_launcher_context_unref(sequence->context); + + /* Delete our internal sequence */ + TAILQ_REMOVE(&startup_sequences, sequence, sequences); + + free(sequence->id); + free(sequence->workspace); + FREE(sequence); +} + /* * 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), @@ -233,7 +243,13 @@ void startup_monitor_event(SnMonitorEvent *event, void *userdata) { case SN_MONITOR_EVENT_COMPLETED: DLOG("startup sequence %s completed\n", sn_startup_sequence_get_id(snsequence)); - if (_delete_startup_sequence(sequence) == 0) { + /* Mark the given sequence for deletion in 30 seconds. */ + time_t current_time = time(NULL); + sequence->delete_at = current_time + 30; + DLOG("Will delete startup sequence %s at timestamp %ld\n", + sequence->id, sequence->delete_at); + + if (_prune_startup_sequences() == 0) { DLOG("No more startup sequences running, changing root window cursor to default pointer.\n"); /* Change the pointer of the root window to indicate progress */ if (xcursor_supported) @@ -247,32 +263,44 @@ void startup_monitor_event(SnMonitorEvent *event, void *userdata) { } } -/* - * Checks if the given window belongs to a startup notification by checking if - * the _NET_STARTUP_ID property is set on the window (or on its leader, if it’s - * unset). - * - * If so, returns the workspace on which the startup was initiated. - * Returns NULL otherwise. +/** + * Gets the stored startup sequence for the _NET_STARTUP_ID of a given window. * */ -char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply) { +struct Startup_Sequence *startup_sequence_get(i3Window *cwindow, + xcb_get_property_reply_t *startup_id_reply, bool ignore_mapped_leader) { /* The _NET_STARTUP_ID is only needed during this function, so we get it * here and don’t save it in the 'cwindow'. */ if (startup_id_reply == NULL || xcb_get_property_value_length(startup_id_reply) == 0) { FREE(startup_id_reply); - DLOG("No _NET_STARTUP_ID set on this window\n"); + DLOG("No _NET_STARTUP_ID set on window 0x%08x\n", cwindow->id); if (cwindow->leader == XCB_NONE) return NULL; - xcb_get_property_cookie_t cookie; - cookie = xcb_get_property(conn, false, cwindow->leader, A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512); + /* This is a special case that causes the leader's startup sequence + * to only be returned if it has never been mapped, useful primarily + * when trying to delete a sequence. + * + * It's generally inappropriate to delete a leader's sequence when + * moving a child window, but if the leader has no container, it's + * likely permanently unmapped and the child is the "real" window. */ + if (ignore_mapped_leader && con_by_window_id(cwindow->leader) != NULL) { + DLOG("Ignoring leader window 0x%08x\n", cwindow->leader); + return NULL; + } + DLOG("Checking leader window 0x%08x\n", cwindow->leader); + + xcb_get_property_cookie_t cookie; + + cookie = xcb_get_property(conn, false, cwindow->leader, + A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512); startup_id_reply = xcb_get_property_reply(conn, cookie, NULL); - if (startup_id_reply == NULL || xcb_get_property_value_length(startup_id_reply) == 0) { - DLOG("No _NET_STARTUP_ID set on the leader either\n"); + if (startup_id_reply == NULL || + xcb_get_property_value_length(startup_id_reply) == 0) { FREE(startup_id_reply); + DLOG("No _NET_STARTUP_ID set on the leader either\n"); return NULL; } } @@ -304,5 +332,31 @@ char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t * free(startup_id); free(startup_id_reply); + + return sequence; +} + +/* + * Checks if the given window belongs to a startup notification by checking if + * the _NET_STARTUP_ID property is set on the window (or on its leader, if it’s + * unset). + * + * If so, returns the workspace on which the startup was initiated. + * Returns NULL otherwise. + * + */ +char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply) { + struct Startup_Sequence *sequence = startup_sequence_get(cwindow, startup_id_reply, false); + if (sequence == NULL) + return NULL; + + /* If the startup sequence's time span has elapsed, delete it. */ + time_t current_time = time(NULL); + if (sequence->delete_at > 0 && current_time > sequence->delete_at) { + DLOG("Deleting expired startup sequence %s\n", sequence->id); + startup_sequence_delete(sequence); + return NULL; + } + return sequence->workspace; } diff --git a/testcases/t/175-startup-notification.t b/testcases/t/175-startup-notification.t index b27a9a70..7160399d 100644 --- a/testcases/t/175-startup-notification.t +++ b/testcases/t/175-startup-notification.t @@ -136,16 +136,49 @@ is_num_children($second_ws, 0, 'still no containers on the second workspace'); is_num_children($first_ws, 2, 'two containers on the first workspace'); ###################################################################### -# 2) open another window after the startup process is completed -# (should be placed on the current workspace) +# verifies that finishing startup doesn't immediately stop windows +# from being placed on the sequence's workspace, but that moving +# the leader actually deletes the startup sequence mapping ###################################################################### complete_startup(); sync_with_i3; -my $otherwin = open_window; +# Startup has completed but the 30-second deletion time hasn't elapsed, +# so this window should still go on the leader's initial workspace. +$win = open_window({ dont_map => 1, client_leader => $leader }); +$win->map; +sync_with_i3; + +is_num_children($first_ws, 3, 'three containers on the first workspace'); + +# Switch to the first workspace and move the focused window to the +# second workspace. +cmd "workspace $first_ws"; +cmd "move workspace $second_ws"; + is_num_children($second_ws, 1, 'one container on the second workspace'); +# Create and switch to a new workspace, just to be safe. +my $third_ws = fresh_workspace; + +# Moving the window between workspaces should have immediately +# removed the startup workspace mapping. New windows with that +# leader should be created on the current workspace. +$win = open_window({ dont_map => 1, client_leader => $leader }); +$win->map; +sync_with_i3; + +is_num_children($third_ws, 1, 'one container on the third workspace'); + +###################################################################### +# 2) open another window after the startup process is completed +# (should be placed on the current workspace) +###################################################################### + +my $otherwin = open_window; +is_num_children($third_ws, 2, 'two containers on the third workspace'); + ###################################################################### # 3) test that the --no-startup-id flag for exec leads to no DESKTOP_STARTUP_ID # environment variable. From a6e1b75b1890f3ffb5280785fe9f572dc9a22db9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 4 Oct 2012 18:50:33 +0200 Subject: [PATCH 069/146] allow floating cons to be reached using 'focus parent' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I suppose this was just an oversight. Let’s see if it causes any issues. fixes #831 --- src/tree.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tree.c b/src/tree.c index d4794e4d..157b6671 100644 --- a/src/tree.c +++ b/src/tree.c @@ -398,7 +398,8 @@ void tree_split(Con *con, orientation_t orientation) { bool level_up(void) { /* We can focus up to the workspace, but not any higher in the tree */ if ((focused->parent->type != CT_CON && - focused->parent->type != CT_WORKSPACE) || + focused->parent->type != CT_FLOATING_CON && + focused->parent->type != CT_WORKSPACE) || focused->type == CT_WORKSPACE) { ELOG("'focus parent': Focus is already on the workspace, cannot go higher than that.\n"); return false; From 69a77d182e5d7f6ac0b7734559fd4a4fc40669cf Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 6 Oct 2012 22:27:57 +0200 Subject: [PATCH 070/146] i3-msg.man: fix reference to the "reply section" (Thanks slowpoke) --- man/i3-msg.man | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/man/i3-msg.man b/man/i3-msg.man index 6b548d36..2f6c2aab 100644 --- a/man/i3-msg.man +++ b/man/i3-msg.man @@ -24,7 +24,8 @@ workspaces. get_outputs:: Gets the current outputs. The reply will be a JSON-encoded list of outputs (see -the reply section). +the reply section of docs/ipc, e.g. at +http://i3wm.org/docs/ipc.html#_receiving_replies_from_i3). get_tree:: Gets the layout tree. i3 uses a tree as data structure which includes every From d36264e403050d6c9dc564b90debe5bdb175488b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 7 Oct 2012 16:30:34 +0200 Subject: [PATCH 071/146] generate-command-parser: make input/output configurable --- generate-command-parser.pl | 22 +++++++++++++++------- src/commands_parser.c | 6 +++--- src/i3.mk | 2 +- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/generate-command-parser.pl b/generate-command-parser.pl index 01cbe462..ed05efd4 100755 --- a/generate-command-parser.pl +++ b/generate-command-parser.pl @@ -12,8 +12,18 @@ use strict; use warnings; use Data::Dumper; +use Getopt::Long; use v5.10; +my $input = ''; +my $prefix = ''; +my $result = GetOptions( + 'input=s' => \$input, + 'prefix=s' => \$prefix +); + +die qq|Input file "$input" does not exist!| unless -e $input; + # reads in a whole file sub slurp { open my $fh, '<', shift; @@ -24,8 +34,6 @@ sub slurp { # Stores the different states. my %states; -# XXX: don’t hardcode input and output -my $input = '../parser-specs/commands.spec'; my @raw_lines = split("\n", slurp($input)); my @lines; @@ -103,7 +111,7 @@ for my $line (@lines) { # It is important to keep the order the same, so we store the keys once. my @keys = keys %states; -open(my $enumfh, '>', 'GENERATED_enums.h'); +open(my $enumfh, '>', "GENERATED_${prefix}_enums.h"); # XXX: we might want to have a way to do this without a trailing comma, but gcc # seems to eat it. @@ -117,7 +125,7 @@ say $enumfh '} cmdp_state;'; close($enumfh); # Third step: Generate the call function. -open(my $callfh, '>', 'GENERATED_call.h'); +open(my $callfh, '>', "GENERATED_${prefix}_call.h"); say $callfh 'static void GENERATED_call(const int call_identifier, struct CommandResult *result) {'; say $callfh ' switch (call_identifier) {'; my $call_id = 0; @@ -168,11 +176,11 @@ close($callfh); # Fourth step: Generate the token datastructures. -open(my $tokfh, '>', 'GENERATED_tokens.h'); +open(my $tokfh, '>', "GENERATED_${prefix}_tokens.h"); for my $state (@keys) { my $tokens = $states{$state}; - say $tokfh 'cmdp_token tokens_' . $state . '[' . scalar @$tokens . '] = {'; + say $tokfh 'static cmdp_token tokens_' . $state . '[' . scalar @$tokens . '] = {'; for my $token (@$tokens) { my $call_identifier = 0; my $token_name = $token->{token}; @@ -192,7 +200,7 @@ for my $state (@keys) { say $tokfh '};'; } -say $tokfh 'cmdp_token_ptr tokens[' . scalar @keys . '] = {'; +say $tokfh 'static cmdp_token_ptr tokens[' . scalar @keys . '] = {'; for my $state (@keys) { my $tokens = $states{$state}; say $tokfh ' { tokens_' . $state . ', ' . scalar @$tokens . ' },'; diff --git a/src/commands_parser.c b/src/commands_parser.c index d739f4e1..bbba2c44 100644 --- a/src/commands_parser.c +++ b/src/commands_parser.c @@ -46,7 +46,7 @@ * input parser-specs/commands.spec. ******************************************************************************/ -#include "GENERATED_enums.h" +#include "GENERATED_commands_enums.h" typedef struct token { char *name; @@ -63,7 +63,7 @@ typedef struct tokenptr { int n; } cmdp_token_ptr; -#include "GENERATED_tokens.h" +#include "GENERATED_commands_tokens.h" /******************************************************************************* * The (small) stack where identified literals are stored during the parsing @@ -182,7 +182,7 @@ static Match current_match; static struct CommandResult subcommand_output; static struct CommandResult command_output; -#include "GENERATED_call.h" +#include "GENERATED_commands_call.h" static void next_state(const cmdp_token *token) { diff --git a/src/i3.mk b/src/i3.mk index 78e19890..94a988ff 100644 --- a/src/i3.mk +++ b/src/i3.mk @@ -55,7 +55,7 @@ src/commands_parser.o: src/commands_parser.c $(i3_HEADERS_DEP) i3-command-parser i3-command-parser.stamp: generate-command-parser.pl parser-specs/commands.spec echo "[i3] Generating command parser" - (cd include; ../generate-command-parser.pl) + (cd include; ../generate-command-parser.pl --input=../parser-specs/commands.spec --prefix=commands) touch $@ i3: libi3.a $(i3_OBJECTS) From 00fca2dabd483633abdd47d1a3518b6884ff23af Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 7 Oct 2012 16:32:50 +0200 Subject: [PATCH 072/146] add first bits of a (custom) config parser --- include/all.h | 2 + include/config_directives.h | 29 +++ parser-specs/config.spec | 60 +++++ src/config_directives.c | 56 ++++ src/config_parser.c | 446 ++++++++++++++++++++++++++++++++ src/i3.mk | 15 +- testcases/Makefile.PL | 1 + testcases/t/201-config-parser.t | 79 ++++++ 8 files changed, 687 insertions(+), 1 deletion(-) create mode 100644 include/config_directives.h create mode 100644 parser-specs/config.spec create mode 100644 src/config_directives.c create mode 100644 src/config_parser.c create mode 100644 testcases/t/201-config-parser.t diff --git a/include/all.h b/include/all.h index 48ca6621..9ac6a54f 100644 --- a/include/all.h +++ b/include/all.h @@ -79,6 +79,8 @@ #include "scratchpad.h" #include "commands.h" #include "commands_parser.h" +#include "config_directives.h" +//#include "config_parser.h" #include "fake_outputs.h" #include "display_version.h" diff --git a/include/config_directives.h b/include/config_directives.h new file mode 100644 index 00000000..5922144f --- /dev/null +++ b/include/config_directives.h @@ -0,0 +1,29 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) + * + * commands.c: all command functions (see commands_parser.c) + * + */ +#ifndef I3_CONFIG_DIRECTIVES_H +#define I3_CONFIG_DIRECTIVES_H + +//#include "config_parser.h" + +/** The beginning of the prototype for every cmd_ function. */ +#define I3_CFG Match *current_match, struct CommandResult *cmd_output + +/** + * + */ +void cfg_font(I3_CFG, const char *font); + +void cfg_mode_binding(I3_CFG, const char *bindtype, const char *modifiers, const char *key, const char *command); + +void cfg_enter_mode(I3_CFG, const char *mode); + +void cfg_exec(I3_CFG, const char *exectype, const char *no_startup_id, const char *command); + +#endif diff --git a/parser-specs/config.spec b/parser-specs/config.spec new file mode 100644 index 00000000..75c07232 --- /dev/null +++ b/parser-specs/config.spec @@ -0,0 +1,60 @@ +# vim:ts=2:sw=2:expandtab +# +# i3 - an improved dynamic tiling window manager +# © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) +# +# parser-specs/config.spec: Specification file for generate-command-parser.pl +# which will generate the appropriate header files for our C parser. +# +# Use :source highlighting.vim in vim to get syntax highlighting +# for this file. + +# TODO: get it to parse the default config :) +# TODO: comment handling (on their own line, at the end of a line) + +state INITIAL: + # We have an end token here for all the commands which just call some + # function without using an explicit 'end' token. + end -> + #'[' -> call cmd_criteria_init(); CRITERIA + 'font' -> FONT + 'mode' -> MODENAME + exectype = 'exec_always', 'exec' + -> EXEC + +# [--no-startup-id] command +state EXEC: + no_startup_id = '--no-startup-id' + -> + command = string + -> call cfg_exec($exectype, $no_startup_id, $command) + +state MODENAME: + modename = word + -> call cfg_enter_mode($modename); MODEBRACE + +state MODEBRACE: + '{' + -> MODE + +state MODE: + bindtype = 'bindsym', 'bindcode' + -> MODE_BINDING + '}' + -> INITIAL + +state MODE_BINDING: + modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control' + -> + '+' + -> + key = word + -> MODE_BINDCOMMAND + +state MODE_BINDCOMMAND: + command = string + -> call cfg_mode_binding($bindtype, $modifiers, $key, $command); MODE + +state FONT: + font = string + -> call cfg_font($font) diff --git a/src/config_directives.c b/src/config_directives.c new file mode 100644 index 00000000..9660866e --- /dev/null +++ b/src/config_directives.c @@ -0,0 +1,56 @@ +#undef I3__FILE__ +#define I3__FILE__ "config_directives.c" +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) + * + * config_directives.c: all command functions (see config_parser.c) + * + */ +#include +#include + +#include "all.h" + +// Macros to make the YAJL API a bit easier to use. +#define y(x, ...) yajl_gen_ ## x (cmd_output->json_gen, ##__VA_ARGS__) +#define ystr(str) yajl_gen_string(cmd_output->json_gen, (unsigned char*)str, strlen(str)) +#define ysuccess(success) do { \ + y(map_open); \ + ystr("success"); \ + y(bool, success); \ + y(map_close); \ +} while (0) + +static char *font_pattern; + +void cfg_font(I3_CFG, const char *font) { + config.font = load_font(font, true); + set_font(&config.font); + + /* Save the font pattern for using it as bar font later on */ + FREE(font_pattern); + font_pattern = sstrdup(font); +} + +void cfg_mode_binding(I3_CFG, const char *bindtype, const char *modifiers, const char *key, const char *command) { + printf("cfg_mode_binding: got bindtype\n"); +} + +void cfg_enter_mode(I3_CFG, const char *mode) { + // TODO: error handling: if mode == '{', the mode name is missing + printf("mode name: %s\n", mode); +} + +void cfg_exec(I3_CFG, const char *exectype, const char *no_startup_id, const char *command) { + struct Autostart *new = smalloc(sizeof(struct Autostart)); + new->command = sstrdup(command); + new->no_startup_id = (no_startup_id != NULL); + if (strcmp(exectype, "exec") == 0) { + TAILQ_INSERT_TAIL(&autostarts, new, autostarts); + } else { + TAILQ_INSERT_TAIL(&autostarts_always, new, autostarts_always); + } +} diff --git a/src/config_parser.c b/src/config_parser.c new file mode 100644 index 00000000..19d1d168 --- /dev/null +++ b/src/config_parser.c @@ -0,0 +1,446 @@ +#undef I3__FILE__ +#define I3__FILE__ "config_parser.c" +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) + * + * config_parser.c: hand-written parser to parse configuration directives. + * + * See also src/commands_parser.c for rationale on why we use a custom parser. + * + */ +#include +#include +#include +#include +#include +#include + +#include "all.h" + +// Macros to make the YAJL API a bit easier to use. +#define y(x, ...) yajl_gen_ ## x (command_output.json_gen, ##__VA_ARGS__) +#define ystr(str) yajl_gen_string(command_output.json_gen, (unsigned char*)str, strlen(str)) + +/******************************************************************************* + * The data structures used for parsing. Essentially the current state and a + * list of tokens for that state. + * + * The GENERATED_* files are generated by generate-commands-parser.pl with the + * input parser-specs/configs.spec. + ******************************************************************************/ + +#include "GENERATED_config_enums.h" + +typedef struct token { + char *name; + char *identifier; + /* This might be __CALL */ + cmdp_state next_state; + union { + uint16_t call_identifier; + } extra; +} cmdp_token; + +typedef struct tokenptr { + cmdp_token *array; + int n; +} cmdp_token_ptr; + +#include "GENERATED_config_tokens.h" + +/******************************************************************************* + * The (small) stack where identified literals are stored during the parsing + * of a single command (like $workspace). + ******************************************************************************/ + +struct stack_entry { + /* Just a pointer, not dynamically allocated. */ + const char *identifier; + char *str; +}; + +/* 10 entries should be enough for everybody. */ +static struct stack_entry stack[10]; + +/* + * Pushes a string (identified by 'identifier') on the stack. We simply use a + * single array, since the number of entries we have to store is very small. + * + */ +static void push_string(const char *identifier, char *str) { + for (int c = 0; c < 10; c++) { + if (stack[c].identifier != NULL && + strcmp(stack[c].identifier, identifier) != 0) + continue; + if (stack[c].identifier == NULL) { + /* Found a free slot, let’s store it here. */ + stack[c].identifier = identifier; + stack[c].str = str; + } else { + /* Append the value. */ + sasprintf(&(stack[c].str), "%s,%s", stack[c].str, str); + } + return; + } + + /* When we arrive here, the stack is full. This should not happen and + * means there’s either a bug in this parser or the specification + * contains a command with more than 10 identified tokens. */ + fprintf(stderr, "BUG: commands_parser stack full. This means either a bug " + "in the code, or a new command which contains more than " + "10 identified tokens.\n"); + exit(1); +} + +// XXX: ideally, this would be const char. need to check if that works with all +// called functions. +static char *get_string(const char *identifier) { + for (int c = 0; c < 10; c++) { + if (stack[c].identifier == NULL) + break; + if (strcmp(identifier, stack[c].identifier) == 0) + return stack[c].str; + } + return NULL; +} + +static void clear_stack(void) { + for (int c = 0; c < 10; c++) { + if (stack[c].str != NULL) + free(stack[c].str); + stack[c].identifier = NULL; + stack[c].str = NULL; + } +} + +// TODO: remove this if it turns out we don’t need it for testing. +#if 0 +/******************************************************************************* + * A dynamically growing linked list which holds the criteria for the current + * command. + ******************************************************************************/ + +typedef struct criterion { + char *type; + char *value; + + TAILQ_ENTRY(criterion) criteria; +} criterion; + +static TAILQ_HEAD(criteria_head, criterion) criteria = + TAILQ_HEAD_INITIALIZER(criteria); + +/* + * Stores the given type/value in the list of criteria. + * Accepts a pointer as first argument, since it is 'call'ed by the parser. + * + */ +static void push_criterion(void *unused_criteria, const char *type, + const char *value) { + struct criterion *criterion = malloc(sizeof(struct criterion)); + criterion->type = strdup(type); + criterion->value = strdup(value); + TAILQ_INSERT_TAIL(&criteria, criterion, criteria); +} + +/* + * Clears the criteria linked list. + * Accepts a pointer as first argument, since it is 'call'ed by the parser. + * + */ +static void clear_criteria(void *unused_criteria) { + struct criterion *criterion; + while (!TAILQ_EMPTY(&criteria)) { + criterion = TAILQ_FIRST(&criteria); + free(criterion->type); + free(criterion->value); + TAILQ_REMOVE(&criteria, criterion, criteria); + free(criterion); + } +} +#endif + +/******************************************************************************* + * The parser itself. + ******************************************************************************/ + +static cmdp_state state; +#ifndef TEST_PARSER +static Match current_match; +#endif +static struct CommandResult subcommand_output; +static struct CommandResult command_output; + +#include "GENERATED_config_call.h" + + +static void next_state(const cmdp_token *token) { + //printf("token = name %s identifier %s\n", token->name, token->identifier); + //printf("next_state = %d\n", token->next_state); + if (token->next_state == __CALL) { + subcommand_output.json_gen = command_output.json_gen; + subcommand_output.needs_tree_render = false; + GENERATED_call(token->extra.call_identifier, &subcommand_output); + /* If any subcommand requires a tree_render(), we need to make the + * whole parser result request a tree_render(). */ + if (subcommand_output.needs_tree_render) + command_output.needs_tree_render = true; + clear_stack(); + return; + } + + state = token->next_state; + if (state == INITIAL) { + clear_stack(); + } +} + +struct CommandResult *parse_config(const char *input) { + DLOG("COMMAND: *%s*\n", input); + state = INITIAL; + +/* A YAJL JSON generator used for formatting replies. */ +#if YAJL_MAJOR >= 2 + command_output.json_gen = yajl_gen_alloc(NULL); +#else + command_output.json_gen = yajl_gen_alloc(NULL, NULL); +#endif + + y(array_open); + command_output.needs_tree_render = false; + + const char *walk = input; + const size_t len = strlen(input); + int c; + const cmdp_token *token; + bool token_handled; + + // TODO: make this testable +#ifndef TEST_PARSER + cmd_criteria_init(¤t_match, &subcommand_output); +#endif + + /* The "<=" operator is intentional: We also handle the terminating 0-byte + * explicitly by looking for an 'end' token. */ + while ((walk - input) <= len) { + /* skip whitespace and newlines before every token */ + while ((*walk == ' ' || *walk == '\t' || + *walk == '\r' || *walk == '\n') && *walk != '\0') + walk++; + + //printf("remaining input: %s\n", walk); + + cmdp_token_ptr *ptr = &(tokens[state]); + token_handled = false; + for (c = 0; c < ptr->n; c++) { + token = &(ptr->array[c]); + + /* A literal. */ + if (token->name[0] == '\'') { + if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) { + if (token->identifier != NULL) + push_string(token->identifier, sstrdup(token->name + 1)); + walk += strlen(token->name) - 1; + next_state(token); + token_handled = true; + break; + } + continue; + } + + if (strcmp(token->name, "string") == 0 || + strcmp(token->name, "word") == 0) { + const char *beginning = walk; + /* Handle quoted strings (or words). */ + if (*walk == '"') { + beginning++; + walk++; + while (*walk != '\0' && (*walk != '"' || *(walk-1) == '\\')) + walk++; + } else { + if (token->name[0] == 's') { + /* For a string (starting with 's'), the delimiters are + * comma (,) and semicolon (;) which introduce a new + * operation or command, respectively. Also, newlines + * end a command. */ + while (*walk != ';' && *walk != ',' && + *walk != '\0' && *walk != '\r' && + *walk != '\n') + walk++; + } else { + /* For a word, the delimiters are white space (' ' or + * '\t'), closing square bracket (]), comma (,) and + * semicolon (;). */ + while (*walk != ' ' && *walk != '\t' && + *walk != ']' && *walk != ',' && + *walk != ';' && *walk != '\r' && + *walk != '\n' && *walk != '\0') + walk++; + } + } + if (walk != beginning) { + char *str = scalloc(walk-beginning + 1); + /* We copy manually to handle escaping of characters. */ + int inpos, outpos; + for (inpos = 0, outpos = 0; + inpos < (walk-beginning); + inpos++, outpos++) { + /* We only handle escaped double quotes to not break + * backwards compatibility with people using \w in + * regular expressions etc. */ + if (beginning[inpos] == '\\' && beginning[inpos+1] == '"') + inpos++; + str[outpos] = beginning[inpos]; + } + if (token->identifier) + push_string(token->identifier, str); + /* If we are at the end of a quoted string, skip the ending + * double quote. */ + if (*walk == '"') + walk++; + next_state(token); + token_handled = true; + break; + } + } + + if (strcmp(token->name, "end") == 0) { + if (*walk == '\0' || *walk == ',' || *walk == ';') { + next_state(token); + token_handled = true; + /* To make sure we start with an appropriate matching + * datastructure for commands which do *not* specify any + * criteria, we re-initialize the criteria system after + * every command. */ + // TODO: make this testable +#ifndef TEST_PARSER + if (*walk == '\0' || *walk == ';') + cmd_criteria_init(¤t_match, &subcommand_output); +#endif + walk++; + break; + } + } + } + + if (!token_handled) { + /* Figure out how much memory we will need to fill in the names of + * all tokens afterwards. */ + int tokenlen = 0; + for (c = 0; c < ptr->n; c++) + tokenlen += strlen(ptr->array[c].name) + strlen("'', "); + + /* Build up a decent error message. We include the problem, the + * full input, and underline the position where the parser + * currently is. */ + char *errormessage; + char *possible_tokens = smalloc(tokenlen + 1); + char *tokenwalk = possible_tokens; + for (c = 0; c < ptr->n; c++) { + token = &(ptr->array[c]); + if (token->name[0] == '\'') { + /* A literal is copied to the error message enclosed with + * single quotes. */ + *tokenwalk++ = '\''; + strcpy(tokenwalk, token->name + 1); + tokenwalk += strlen(token->name + 1); + *tokenwalk++ = '\''; + } else { + /* Any other token is copied to the error message enclosed + * with angle brackets. */ + *tokenwalk++ = '<'; + strcpy(tokenwalk, token->name); + tokenwalk += strlen(token->name); + *tokenwalk++ = '>'; + } + if (c < (ptr->n - 1)) { + *tokenwalk++ = ','; + *tokenwalk++ = ' '; + } + } + *tokenwalk = '\0'; + sasprintf(&errormessage, "Expected one of these tokens: %s", + possible_tokens); + free(possible_tokens); + + /* Contains the same amount of characters as 'input' has, but with + * the unparseable part highlighted using ^ characters. */ + char *position = smalloc(len + 1); + for (const char *copywalk = input; *copywalk != '\0'; copywalk++) + position[(copywalk - input)] = (copywalk >= walk ? '^' : ' '); + position[len] = '\0'; + + ELOG("%s\n", errormessage); + ELOG("Your command: %s\n", input); + ELOG(" %s\n", position); + + /* Format this error message as a JSON reply. */ + y(map_open); + ystr("success"); + y(bool, false); + /* We set parse_error to true to distinguish this from other + * errors. i3-nagbar is spawned upon keypresses only for parser + * errors. */ + ystr("parse_error"); + y(bool, true); + ystr("error"); + ystr(errormessage); + ystr("input"); + ystr(input); + ystr("errorposition"); + ystr(position); + y(map_close); + + free(position); + free(errormessage); + clear_stack(); + break; + } + } + + y(array_close); + + return &command_output; +} + +/******************************************************************************* + * Code for building the stand-alone binary test.commands_parser which is used + * by t/187-commands-parser.t. + ******************************************************************************/ + +#ifdef TEST_PARSER + +/* + * Logs the given message to stdout while prefixing the current time to it, + * but only if debug logging was activated. + * This is to be called by DLOG() which includes filename/linenumber + * + */ +void debuglog(char *fmt, ...) { + va_list args; + + va_start(args, fmt); + fprintf(stdout, "# "); + vfprintf(stdout, fmt, args); + va_end(args); +} + +void errorlog(char *fmt, ...) { + va_list args; + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); +} + +int main(int argc, char *argv[]) { + if (argc < 2) { + fprintf(stderr, "Syntax: %s \n", argv[0]); + return 1; + } + parse_config(argv[1]); +} +#endif diff --git a/src/i3.mk b/src/i3.mk index 94a988ff..fd4afdb7 100644 --- a/src/i3.mk +++ b/src/i3.mk @@ -53,11 +53,24 @@ src/commands_parser.o: src/commands_parser.c $(i3_HEADERS_DEP) i3-command-parser $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) $(I3_LDFLAGS) $(LDFLAGS) -DTEST_PARSER -o test.commands_parser $< $(LIBS) $(i3_LIBS) $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ ${canonical_path}/$< +# This target compiles the command parser twice: +# Once with -DTEST_PARSER, creating a stand-alone executable used for tests, +# and once as an object file for i3. +src/config_parser.o: src/config_parser.c $(i3_HEADERS_DEP) i3-config-parser.stamp + echo "[i3] CC $<" + $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) $(I3_LDFLAGS) $(LDFLAGS) -DTEST_PARSER -o test.config_parser $< $(LIBS) $(i3_LIBS) + $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ ${canonical_path}/$< + i3-command-parser.stamp: generate-command-parser.pl parser-specs/commands.spec echo "[i3] Generating command parser" (cd include; ../generate-command-parser.pl --input=../parser-specs/commands.spec --prefix=commands) touch $@ +i3-config-parser.stamp: generate-command-parser.pl parser-specs/config.spec + echo "[i3] Generating config parser" + (cd include; ../generate-command-parser.pl --input=../parser-specs/config.spec --prefix=config) + touch $@ + i3: libi3.a $(i3_OBJECTS) echo "[i3] Link i3" $(CC) $(I3_LDFLAGS) $(LDFLAGS) -o $@ $(filter-out libi3.a,$^) $(LIBS) $(i3_LIBS) @@ -82,4 +95,4 @@ install-i3: i3 clean-i3: echo "[i3] Clean" - rm -f $(i3_OBJECTS) $(i3_SOURCES_GENERATED) $(i3_HEADERS_CMDPARSER) include/loglevels.h loglevels.tmp include/all.h.pch i3-command-parser.stamp i3 src/*.gcno src/cfgparse.{output,dot,tab.h,y.o} src/cmdparse.* + rm -f $(i3_OBJECTS) $(i3_SOURCES_GENERATED) $(i3_HEADERS_CMDPARSER) include/loglevels.h loglevels.tmp include/all.h.pch i3-command-parser.stamp i3-config-parser.stamp i3 src/*.gcno src/cfgparse.{output,dot,tab.h,y.o} src/cmdparse.* diff --git a/testcases/Makefile.PL b/testcases/Makefile.PL index b522fc30..28036d1f 100755 --- a/testcases/Makefile.PL +++ b/testcases/Makefile.PL @@ -13,6 +13,7 @@ WriteMakefile( 'Inline' => 0, 'ExtUtils::PkgConfig' => 0, 'Test::More' => '0.94', + 'IPC::Run' => 0, }, PM => {}, # do not install any files from this directory clean => { diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t new file mode 100644 index 00000000..74ea9538 --- /dev/null +++ b/testcases/t/201-config-parser.t @@ -0,0 +1,79 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Tests the standalone parser binary to see if it calls the right code when +# confronted with various commands, if it prints proper error messages for +# wrong commands and if it terminates in every case. +# +use i3test i3_autostart => 0; +use IPC::Run qw(run); + +sub parser_calls { + my ($command) = @_; + + my $stdout; + run [ '../test.config_parser', $command ], + '>&-', + '2>', \$stdout; + # TODO: use a timeout, so that we can error out if it doesn’t terminate + + # Filter out all debugging output. + my @lines = split("\n", $stdout); + @lines = grep { not /^# / } @lines; + + ## The criteria management calls are irrelevant and not what we want to test + ## in the first place. + #@lines = grep { !(/cmd_criteria_init()/ || /cmd_criteria_match_windows/) } @lines; + return join("\n", @lines) . "\n"; +} + +my $config = <<'EOT'; +mode "meh" { + bindsym Mod1 + Shift + x resize grow + bindcode Mod1+44 resize shrink +} +EOT + +my $expected = <<'EOT'; +cfg_enter_mode(meh) +cfg_mode_binding(bindsym, Mod1,Shift, x, resize grow) +cfg_mode_binding(bindcode, Mod1, 44, resize shrink) +EOT + +is(parser_calls($config), + $expected, + 'single number (move workspace 3) ok'); + +$config = <<'EOT'; +exec geeqie +exec --no-startup-id /tmp/foo.sh +exec_always firefox +exec_always --no-startup-id /tmp/bar.sh +EOT + +$expected = <<'EOT'; +cfg_exec(exec, (null), geeqie) +cfg_exec(exec, --no-startup-id, /tmp/foo.sh) +cfg_exec(exec_always, (null), firefox) +cfg_exec(exec_always, --no-startup-id, /tmp/bar.sh) +EOT + +is(parser_calls($config), + $expected, + 'single number (move workspace 3) ok'); + + +done_testing; From 85018de433fc5d59659298f9d337e155eeca9259 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 8 Oct 2012 13:19:33 +0200 Subject: [PATCH 073/146] =?UTF-8?q?generate-command-parser:=20support=20s,=20state=20ID=20replacing=20and=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …determining the next state within a function like cfg_criteria_pop() by passing next_state in the ConfigResult (or CommandResult) and using it after calling. --- generate-command-parser.pl | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/generate-command-parser.pl b/generate-command-parser.pl index ed05efd4..f69f715e 100755 --- a/generate-command-parser.pl +++ b/generate-command-parser.pl @@ -109,16 +109,21 @@ for my $line (@lines) { # Second step: Generate the enum values for all states. # It is important to keep the order the same, so we store the keys once. -my @keys = keys %states; +# We sort descendingly by length to be able to replace occurences of the state +# name even when one state’s name is included in another one’s (like FOR_WINDOW +# is in FOR_WINDOW_COMMAND). +my @keys = sort { length($b) <=> length($a) } keys %states; open(my $enumfh, '>', "GENERATED_${prefix}_enums.h"); # XXX: we might want to have a way to do this without a trailing comma, but gcc # seems to eat it. +my %statenum; say $enumfh 'typedef enum {'; my $cnt = 0; for my $state (@keys, '__CALL') { say $enumfh " $state = $cnt,"; + $statenum{$state} = $cnt; $cnt++; } say $enumfh '} cmdp_state;'; @@ -126,7 +131,8 @@ close($enumfh); # Third step: Generate the call function. open(my $callfh, '>', "GENERATED_${prefix}_call.h"); -say $callfh 'static void GENERATED_call(const int call_identifier, struct CommandResult *result) {'; +my $resultname = uc(substr($prefix, 0, 1)) . substr($prefix, 1) . 'Result'; +say $callfh "static void GENERATED_call(const int call_identifier, struct $resultname *result) {"; say $callfh ' switch (call_identifier) {'; my $call_id = 0; for my $state (@keys) { @@ -140,13 +146,24 @@ for my $state (@keys) { $next_state ||= 'INITIAL'; my $fmt = $cmd; # Replace the references to identified literals (like $workspace) with - # calls to get_string(). + # calls to get_string(). Also replaces state names (like FOR_WINDOW) + # with their ID (useful for cfg_criteria_init(FOR_WINDOW) e.g.). + $cmd =~ s/$_/$statenum{$_}/g for @keys; $cmd =~ s/\$([a-z_]+)/get_string("$1")/g; - # Used only for debugging/testing. + $cmd =~ s/\&([a-z_]+)/get_long("$1")/g; + # For debugging/testing, we print the call using printf() and thus need + # to generate a format string. The format uses %d for s, + # literal numbers or state IDs and %s for NULL, s and literal + # strings. + $fmt =~ s/$_/%d/g for @keys; $fmt =~ s/\$([a-z_]+)/%s/g; + $fmt =~ s/\&([a-z_]+)/%ld/g; + $fmt =~ s/NULL/%s/g; $fmt =~ s/"([a-z0-9_]+)"/%s/g; + $fmt =~ s/(?:-?|\b)[0-9]+\b/%d/g; say $callfh " case $call_id:"; + say $callfh " result->next_state = $next_state;"; say $callfh '#ifndef TEST_PARSER'; my $real_cmd = $cmd; if ($real_cmd =~ /\(\)/) { @@ -161,8 +178,12 @@ for my $state (@keys) { $cmd =~ s/\)$//; $cmd = ", $cmd" if length($cmd) > 0; say $callfh qq| fprintf(stderr, "$fmt\\n"$cmd);|; + # The cfg_criteria functions have side-effects which are important for + # testing. They are implemented as stubs in the test parser code. + if ($real_cmd =~ /^cfg_criteria/) { + say $callfh qq| $real_cmd;|; + } say $callfh '#endif'; - say $callfh " state = $next_state;"; say $callfh " break;"; $token->{next_state} = "call $call_id"; $call_id++; @@ -170,7 +191,9 @@ for my $state (@keys) { } say $callfh ' default:'; say $callfh ' printf("BUG in the parser. state = %d\n", call_identifier);'; +say $callfh ' assert(false);'; say $callfh ' }'; +say $callfh ' state = result->next_state;'; say $callfh '}'; close($callfh); From a635945f85d6d2daee45fa6ac79c63e4fae0273b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 8 Oct 2012 13:21:48 +0200 Subject: [PATCH 074/146] tests: use new assign syntax, drop legacy test --- testcases/t/166-assign.t | 33 ++------------------------ testcases/t/173-regress-focus-assign.t | 2 +- 2 files changed, 3 insertions(+), 32 deletions(-) diff --git a/testcases/t/166-assign.t b/testcases/t/166-assign.t index a06bb59d..6af13fa5 100644 --- a/testcases/t/166-assign.t +++ b/testcases/t/166-assign.t @@ -86,7 +86,7 @@ $window->destroy; $config = <destroy; exit_gracefully($pid); -##################################################################### -# make sure that assignments are case-insensitive in the old syntax. -##################################################################### - -$config = < 'SPEcial'); -wait_for_map $window; - -$content = get_ws($tmp); -ok(@{$content->{nodes}} == 0, 'no tiling cons'); -ok(@{$content->{floating_nodes}} == 1, 'one floating con'); - -$window->destroy; - -exit_gracefully($pid); - ##################################################################### # regression test: dock clients with floating assignments should not crash # (instead, nothing should happen - dock clients can’t float) diff --git a/testcases/t/173-regress-focus-assign.t b/testcases/t/173-regress-focus-assign.t index 91d367d1..f27ec0e4 100644 --- a/testcases/t/173-regress-focus-assign.t +++ b/testcases/t/173-regress-focus-assign.t @@ -62,7 +62,7 @@ sub open_special { my $config = < Date: Mon, 8 Oct 2012 13:23:06 +0200 Subject: [PATCH 075/146] config_parser: implement s, proper error handling --- src/config_parser.c | 254 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 203 insertions(+), 51 deletions(-) diff --git a/src/config_parser.c b/src/config_parser.c index 19d1d168..cb65b79b 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -10,6 +10,17 @@ * * See also src/commands_parser.c for rationale on why we use a custom parser. * + * This parser works VERY MUCH like src/commands_parser.c, so read that first. + * The differences are: + * + * 1. config_parser supports the 'number' token type (in addition to 'word' and + * 'string'). Numbers are referred to using &num (like $str). + * + * 2. Criteria are not executed immediately, they are just stored. + * + * 3. config_parser recognizes \n and \r as 'end' token, while commands_parser + * ignores them. + * */ #include #include @@ -59,7 +70,14 @@ typedef struct tokenptr { struct stack_entry { /* Just a pointer, not dynamically allocated. */ const char *identifier; - char *str; + enum { + STACK_STR = 0, + STACK_LONG = 1, + } type; + union { + char *str; + long num; + } val; }; /* 10 entries should be enough for everybody. */ @@ -73,16 +91,17 @@ static struct stack_entry stack[10]; static void push_string(const char *identifier, char *str) { for (int c = 0; c < 10; c++) { if (stack[c].identifier != NULL && - strcmp(stack[c].identifier, identifier) != 0) + strcmp(stack[c].identifier, identifier) != 0) continue; - if (stack[c].identifier == NULL) { - /* Found a free slot, let’s store it here. */ - stack[c].identifier = identifier; - stack[c].str = str; - } else { - /* Append the value. */ - sasprintf(&(stack[c].str), "%s,%s", stack[c].str, str); - } + if (stack[c].identifier == NULL) { + /* Found a free slot, let’s store it here. */ + stack[c].identifier = identifier; + stack[c].val.str = str; + stack[c].type = STACK_STR; + } else { + /* Append the value. */ + sasprintf(&(stack[c].val.str), "%s,%s", stack[c].val.str, str); + } return; } @@ -95,24 +114,54 @@ static void push_string(const char *identifier, char *str) { exit(1); } -// XXX: ideally, this would be const char. need to check if that works with all -// called functions. -static char *get_string(const char *identifier) { +static void push_long(const char *identifier, long num) { + for (int c = 0; c < 10; c++) { + if (stack[c].identifier != NULL) + continue; + /* Found a free slot, let’s store it here. */ + stack[c].identifier = identifier; + stack[c].val.num = num; + stack[c].type = STACK_LONG; + return; + } + + /* When we arrive here, the stack is full. This should not happen and + * means there’s either a bug in this parser or the specification + * contains a command with more than 10 identified tokens. */ + fprintf(stderr, "BUG: commands_parser stack full. This means either a bug " + "in the code, or a new command which contains more than " + "10 identified tokens.\n"); + exit(1); + +} + +static const char *get_string(const char *identifier) { for (int c = 0; c < 10; c++) { if (stack[c].identifier == NULL) break; if (strcmp(identifier, stack[c].identifier) == 0) - return stack[c].str; + return stack[c].val.str; } return NULL; } +static const long get_long(const char *identifier) { + for (int c = 0; c < 10; c++) { + if (stack[c].identifier == NULL) + break; + if (strcmp(identifier, stack[c].identifier) == 0) + return stack[c].val.num; + } + return 0; +} + static void clear_stack(void) { for (int c = 0; c < 10; c++) { - if (stack[c].str != NULL) - free(stack[c].str); + if (stack[c].type == STACK_STR && stack[c].val.str != NULL) + free(stack[c].val.str); stack[c].identifier = NULL; - stack[c].str = NULL; + stack[c].val.str = NULL; + stack[c].val.num = 0; } } @@ -168,11 +217,9 @@ static void clear_criteria(void *unused_criteria) { ******************************************************************************/ static cmdp_state state; -#ifndef TEST_PARSER static Match current_match; -#endif -static struct CommandResult subcommand_output; -static struct CommandResult command_output; +static struct ConfigResult subcommand_output; +static struct ConfigResult command_output; #include "GENERATED_config_call.h" @@ -182,12 +229,7 @@ static void next_state(const cmdp_token *token) { //printf("next_state = %d\n", token->next_state); if (token->next_state == __CALL) { subcommand_output.json_gen = command_output.json_gen; - subcommand_output.needs_tree_render = false; GENERATED_call(token->extra.call_identifier, &subcommand_output); - /* If any subcommand requires a tree_render(), we need to make the - * whole parser result request a tree_render(). */ - if (subcommand_output.needs_tree_render) - command_output.needs_tree_render = true; clear_stack(); return; } @@ -198,8 +240,49 @@ static void next_state(const cmdp_token *token) { } } -struct CommandResult *parse_config(const char *input) { - DLOG("COMMAND: *%s*\n", input); +/* + * Returns a pointer to the start of the line (one byte after the previous \r, + * \n) or the start of the input, if this is the first line. + * + */ +static const char *start_of_line(const char *walk, const char *beginning) { + while (*walk != '\n' && *walk != '\r' && walk >= beginning) { + walk--; + } + + return walk + 1; +} + +/* + * Copies the line and terminates it at the next \n, if any. + * + * The caller has to free() the result. + * + */ +static char *single_line(const char *start) { + char *result = sstrdup(start); + char *end = strchr(result, '\n'); + if (end != NULL) + *end = '\0'; + return result; +} + +struct ConfigResult *parse_config(const char *input, struct context *context) { + /* Dump the entire config file into the debug log. We cannot just use + * DLOG("%s", input); because one log message must not exceed 4 KiB. */ + const char *dumpwalk = input; + int linecnt = 1; + while (*dumpwalk != '\0') { + char *next_nl = strchr(dumpwalk, '\n'); + if (next_nl != NULL) { + DLOG("CONFIG(line %3d): %.*s\n", linecnt, (next_nl - dumpwalk), dumpwalk); + dumpwalk = next_nl + 1; + } else { + DLOG("CONFIG(line %3d): %s\n", linecnt, dumpwalk); + break; + } + linecnt++; + } state = INITIAL; /* A YAJL JSON generator used for formatting replies. */ @@ -210,25 +293,25 @@ struct CommandResult *parse_config(const char *input) { #endif y(array_open); - command_output.needs_tree_render = false; const char *walk = input; const size_t len = strlen(input); int c; const cmdp_token *token; bool token_handled; + linecnt = 1; // TODO: make this testable #ifndef TEST_PARSER - cmd_criteria_init(¤t_match, &subcommand_output); + cfg_criteria_init(¤t_match, &subcommand_output, INITIAL); #endif /* The "<=" operator is intentional: We also handle the terminating 0-byte * explicitly by looking for an 'end' token. */ while ((walk - input) <= len) { - /* skip whitespace and newlines before every token */ - while ((*walk == ' ' || *walk == '\t' || - *walk == '\r' || *walk == '\n') && *walk != '\0') + /* Skip whitespace before every token, newlines are relevant since they + * separate configuration directives. */ + while ((*walk == ' ' || *walk == '\t') && *walk != '\0') walk++; //printf("remaining input: %s\n", walk); @@ -251,6 +334,29 @@ struct CommandResult *parse_config(const char *input) { continue; } + if (strcmp(token->name, "number") == 0) { + /* Handle numbers. We only accept decimal numbers for now. */ + char *end = NULL; + errno = 0; + long int num = strtol(walk, &end, 10); + if ((errno == ERANGE && (num == LONG_MIN || num == LONG_MAX)) || + (errno != 0 && num == 0)) + continue; + + /* No valid numbers found */ + if (end == walk) + continue; + + if (token->identifier != NULL) + push_long(token->identifier, num); + + /* Set walk to the first non-number character */ + walk = end; + next_state(token); + token_handled = true; + break; + } + if (strcmp(token->name, "string") == 0 || strcmp(token->name, "word") == 0) { const char *beginning = walk; @@ -262,13 +368,7 @@ struct CommandResult *parse_config(const char *input) { walk++; } else { if (token->name[0] == 's') { - /* For a string (starting with 's'), the delimiters are - * comma (,) and semicolon (;) which introduce a new - * operation or command, respectively. Also, newlines - * end a command. */ - while (*walk != ';' && *walk != ',' && - *walk != '\0' && *walk != '\r' && - *walk != '\n') + while (*walk != '\0' && *walk != '\r' && *walk != '\n') walk++; } else { /* For a word, the delimiters are white space (' ' or @@ -308,7 +408,8 @@ struct CommandResult *parse_config(const char *input) { } if (strcmp(token->name, "end") == 0) { - if (*walk == '\0' || *walk == ',' || *walk == ';') { + //printf("checking for end: *%s*\n", walk); + if (*walk == '\0' || *walk == '\n' || *walk == '\r') { next_state(token); token_handled = true; /* To make sure we start with an appropriate matching @@ -317,9 +418,9 @@ struct CommandResult *parse_config(const char *input) { * every command. */ // TODO: make this testable #ifndef TEST_PARSER - if (*walk == '\0' || *walk == ';') - cmd_criteria_init(¤t_match, &subcommand_output); + cfg_criteria_init(¤t_match, &subcommand_output, INITIAL); #endif + linecnt++; walk++; break; } @@ -366,16 +467,52 @@ struct CommandResult *parse_config(const char *input) { possible_tokens); free(possible_tokens); + + /* Go back to the beginning of the line */ + const char *error_line = start_of_line(walk, input); + /* Contains the same amount of characters as 'input' has, but with * the unparseable part highlighted using ^ characters. */ - char *position = smalloc(len + 1); - for (const char *copywalk = input; *copywalk != '\0'; copywalk++) - position[(copywalk - input)] = (copywalk >= walk ? '^' : ' '); - position[len] = '\0'; + char *position = scalloc(strlen(error_line) + 1); + const char *copywalk; + for (copywalk = error_line; + *copywalk != '\n' && *copywalk != '\r' && *copywalk != '\0'; + copywalk++) + position[(copywalk - error_line)] = (copywalk >= walk ? '^' : (*copywalk == '\t' ? '\t' : ' ')); + position[(copywalk - error_line)] = '\0'; - ELOG("%s\n", errormessage); - ELOG("Your command: %s\n", input); - ELOG(" %s\n", position); + ELOG("CONFIG: %s\n", errormessage); + ELOG("CONFIG: (in file %s)\n", context->filename); + char *error_copy = single_line(error_line); + + /* Print context lines *before* the error, if any. */ + if (linecnt > 1) { + const char *context_p1_start = start_of_line(error_line-2, input); + char *context_p1_line = single_line(context_p1_start); + if (linecnt > 2) { + const char *context_p2_start = start_of_line(context_p1_start-2, input); + char *context_p2_line = single_line(context_p2_start); + ELOG("CONFIG: Line %3d: %s\n", linecnt - 2, context_p2_line); + free(context_p2_line); + } + ELOG("CONFIG: Line %3d: %s\n", linecnt - 1, context_p1_line); + free(context_p1_line); + } + ELOG("CONFIG: Line %3d: %s\n", linecnt, error_copy); + ELOG("CONFIG: %s\n", position); + free(error_copy); + /* Print context lines *after* the error, if any. */ + for (int i = 0; i < 2; i++) { + char *error_line_end = strchr(error_line, '\n'); + if (error_line_end != NULL && *(error_line_end + 1) != '\0') { + error_line = error_line_end + 1; + error_copy = single_line(error_line); + ELOG("CONFIG: Line %3d: %s\n", linecnt + i + 1, error_copy); + free(error_copy); + } + } + + context->has_errors = true; /* Format this error message as a JSON reply. */ y(map_open); @@ -436,11 +573,26 @@ void errorlog(char *fmt, ...) { va_end(args); } +static int criteria_next_state; + +void cfg_criteria_init(I3_CFG, int _state) { + criteria_next_state = _state; +} + +void cfg_criteria_add(I3_CFG, const char *ctype, const char *cvalue) { +} + +void cfg_criteria_pop_state(I3_CFG) { + result->next_state = criteria_next_state; +} + int main(int argc, char *argv[]) { if (argc < 2) { fprintf(stderr, "Syntax: %s \n", argv[0]); return 1; } - parse_config(argv[1]); + struct context context; + context.filename = ""; + parse_config(argv[1], &context); } #endif From 40c624e1c4d62b62b4930e09b24558eaa8874fc9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 8 Oct 2012 13:23:48 +0200 Subject: [PATCH 076/146] port the entire old config parser to the new one --- include/config_directives.h | 69 ++++- parser-specs/config.spec | 399 +++++++++++++++++++++++++- parser-specs/highlighting.vim | 2 +- src/config_directives.c | 510 +++++++++++++++++++++++++++++++++- 4 files changed, 950 insertions(+), 30 deletions(-) diff --git a/include/config_directives.h b/include/config_directives.h index 5922144f..1faaa973 100644 --- a/include/config_directives.h +++ b/include/config_directives.h @@ -4,26 +4,75 @@ * i3 - an improved dynamic tiling window manager * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) * - * commands.c: all command functions (see commands_parser.c) + * config_directives.h: all config storing functions (see config_parser.c) * */ #ifndef I3_CONFIG_DIRECTIVES_H #define I3_CONFIG_DIRECTIVES_H -//#include "config_parser.h" +#include "config_parser.h" -/** The beginning of the prototype for every cmd_ function. */ -#define I3_CFG Match *current_match, struct CommandResult *cmd_output +/** The beginning of the prototype for every cfg_ function. */ +#define I3_CFG Match *current_match, struct ConfigResult *result -/** +/* Defines a configuration function, that is, anything that can be called by + * using 'call cfg_foo()' in parser-specs/.*.spec. Useful so that we don’t need + * to repeat the definition all the time. */ +#define CFGFUN(name, ...) \ + void cfg_ ## name (I3_CFG, ## __VA_ARGS__ ) + +/* The following functions are called by the config parser, see + * parser-specs/config.spec. They get the parsed parameters and store them in + * our data structures, e.g. cfg_font gets a font name and stores it in + * config.font. * - */ -void cfg_font(I3_CFG, const char *font); + * Since they are so similar, individual comments were omitted. */ -void cfg_mode_binding(I3_CFG, const char *bindtype, const char *modifiers, const char *key, const char *command); +CFGFUN(criteria_init, int _state); +CFGFUN(criteria_add, const char *ctype, const char *cvalue); +CFGFUN(criteria_pop_state); -void cfg_enter_mode(I3_CFG, const char *mode); +CFGFUN(font, const char *font); +CFGFUN(exec, const char *exectype, const char *no_startup_id, const char *command); +CFGFUN(for_window, const char *command); +CFGFUN(floating_minimum_size, const long width, const long height); +CFGFUN(floating_maximum_size, const long width, const long height); +CFGFUN(default_orientation, const char *orientation); +CFGFUN(workspace_layout, const char *layout); +CFGFUN(workspace_back_and_forth, const char *value); +CFGFUN(focus_follows_mouse, const char *value); +CFGFUN(force_focus_wrapping, const char *value); +CFGFUN(force_xinerama, const char *value); +CFGFUN(fake_outputs, const char *outputs); +CFGFUN(force_display_urgency_hint, const long duration_ms); +CFGFUN(hide_edge_borders, const char *borders); +CFGFUN(assign, const char *workspace); +CFGFUN(ipc_socket, const char *path); +CFGFUN(restart_state, const char *path); +CFGFUN(popup_during_fullscreen, const char *value); +CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator); +CFGFUN(color_single, const char *colorclass, const char *color); +CFGFUN(floating_modifier, const char *modifiers); +CFGFUN(new_window, const char *windowtype, const char *border, const long width); +CFGFUN(workspace, const char *workspace, const char *output); +CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *command); -void cfg_exec(I3_CFG, const char *exectype, const char *no_startup_id, const char *command); +CFGFUN(enter_mode, const char *mode); +CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *command); + +CFGFUN(bar_font, const char *font); +CFGFUN(bar_mode, const char *mode); +CFGFUN(bar_output, const char *output); +CFGFUN(bar_verbose, const char *verbose); +CFGFUN(bar_modifier, const char *modifier); +CFGFUN(bar_position, const char *position); +CFGFUN(bar_i3bar_command, const char *i3bar_command); +CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text); +CFGFUN(bar_socket_path, const char *socket_path); +CFGFUN(bar_tray_output, const char *output); +CFGFUN(bar_color_single, const char *colorclass, const char *color); +CFGFUN(bar_status_command, const char *command); +CFGFUN(bar_workspace_buttons, const char *value); +CFGFUN(bar_finish); #endif diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 75c07232..a5b3b6b6 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -9,18 +9,251 @@ # Use :source highlighting.vim in vim to get syntax highlighting # for this file. -# TODO: get it to parse the default config :) -# TODO: comment handling (on their own line, at the end of a line) +# TODO: should we implement an include statement for the criteria part so we DRY? state INITIAL: # We have an end token here for all the commands which just call some # function without using an explicit 'end' token. end -> - #'[' -> call cmd_criteria_init(); CRITERIA - 'font' -> FONT - 'mode' -> MODENAME - exectype = 'exec_always', 'exec' - -> EXEC + '#' -> IGNORE_LINE + 'set' -> IGNORE_LINE + bindtype = 'bindsym', 'bindcode' -> BINDING + 'bar' -> BARBRACE + 'font' -> FONT + 'mode' -> MODENAME + 'floating_minimum_size' -> FLOATING_MINIMUM_SIZE_WIDTH + 'floating_maximum_size' -> FLOATING_MAXIMUM_SIZE_WIDTH + 'floating_modifier' -> FLOATING_MODIFIER + 'default_orientation' -> DEFAULT_ORIENTATION + 'workspace_layout' -> WORKSPACE_LAYOUT + windowtype = 'new_window', 'new_float' -> NEW_WINDOW + 'hide_edge_borders' -> HIDE_EDGE_BORDERS + 'for_window' -> FOR_WINDOW + 'assign' -> ASSIGN + 'focus_follows_mouse' -> FOCUS_FOLLOWS_MOUSE + 'force_focus_wrapping' -> FORCE_FOCUS_WRAPPING + 'force_xinerama', 'force-xinerama' -> FORCE_XINERAMA + 'workspace_auto_back_and_forth' -> WORKSPACE_BACK_AND_FORTH + 'fake_outputs', 'fake-outputs' -> FAKE_OUTPUTS + 'force_display_urgency_hint' -> FORCE_DISPLAY_URGENCY_HINT + 'workspace' -> WORKSPACE + 'ipc_socket', 'ipc-socket' -> IPC_SOCKET + 'restart_state' -> RESTART_STATE + 'popup_during_fullscreen' -> POPUP_DURING_FULLSCREEN + exectype = 'exec_always', 'exec' -> EXEC + colorclass = 'client.background' + -> COLOR_SINGLE + colorclass = 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent' + -> COLOR_BORDER + +# We ignore comments and 'set' lines (variables). +state IGNORE_LINE: + end, string + -> INITIAL + +# floating_minimum_size x +state FLOATING_MINIMUM_SIZE_WIDTH: + width = number + -> FLOATING_MINIMUM_SIZE_X + +state FLOATING_MINIMUM_SIZE_X: + 'x' + -> FLOATING_MINIMUM_SIZE_HEIGHT + +state FLOATING_MINIMUM_SIZE_HEIGHT: + height = number + -> call cfg_floating_minimum_size(&width, &height) + +# floating_maximum_size x +state FLOATING_MAXIMUM_SIZE_WIDTH: + width = number + -> FLOATING_MAXIMUM_SIZE_X + +state FLOATING_MAXIMUM_SIZE_X: + 'x' + -> FLOATING_MAXIMUM_SIZE_HEIGHT + +state FLOATING_MAXIMUM_SIZE_HEIGHT: + height = number + -> call cfg_floating_maximum_size(&width, &height) + +# floating_modifier +state FLOATING_MODIFIER: + modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control' + -> + '+' + -> + end + -> call cfg_floating_modifier($modifiers) + +# default_orientation +state DEFAULT_ORIENTATION: + orientation = 'horizontal', 'vertical', 'auto' + -> call cfg_default_orientation($orientation) + +# workspace_layout +state WORKSPACE_LAYOUT: + layout = 'default', 'stacking', 'stacked', 'tabbed' + -> call cfg_workspace_layout($layout) + +# new_window +# new_float +# TODO: new_float is not in the userguide yet +# TODO: pixel is not in the userguide yet +state NEW_WINDOW: + border = 'normal', 'pixel' + -> NEW_WINDOW_PIXELS + border = '1pixel', 'none' + -> call cfg_new_window($windowtype, $border, -1) + +state NEW_WINDOW_PIXELS: + end + -> call cfg_new_window($windowtype, $border, 2) + width = number + -> NEW_WINDOW_PIXELS_PX + +state NEW_WINDOW_PIXELS_PX: + 'px' + -> + end + -> call cfg_new_window($windowtype, $border, &width) + +# hide_edge_borders +# also hide_edge_borders for compatibility +state HIDE_EDGE_BORDERS: + hide_borders = 'none', 'vertical', 'horizontal', 'both' + -> call cfg_hide_edge_borders($hide_borders) + hide_borders = '1', 'yes', 'true', 'on', 'enable', 'active' + -> call cfg_hide_edge_borders($hide_borders) + +# for_window command +state FOR_WINDOW: + '[' + -> call cfg_criteria_init(FOR_WINDOW_COMMAND); CRITERIA + +state FOR_WINDOW_COMMAND: + command = string + -> call cfg_for_window($command) + +# assign [→] workspace +state ASSIGN: + '[' + -> call cfg_criteria_init(ASSIGN_WORKSPACE); CRITERIA + +state ASSIGN_WORKSPACE: + '→' + -> + workspace = string + -> call cfg_assign($workspace) + +# Criteria: Used by for_window and assign. +state CRITERIA: + ctype = 'class' -> CRITERION + ctype = 'instance' -> CRITERION + ctype = 'window_role' -> CRITERION + ctype = 'con_id' -> CRITERION + ctype = 'id' -> CRITERION + ctype = 'con_mark' -> CRITERION + ctype = 'title' -> CRITERION + ctype = 'urgent' -> CRITERION + ']' + -> call cfg_criteria_pop_state() + +state CRITERION: + '=' -> CRITERION_STR + +state CRITERION_STR: + cvalue = word + -> call cfg_criteria_add($ctype, $cvalue); CRITERIA + +# focus_follows_mouse bool +state FOCUS_FOLLOWS_MOUSE: + value = word + -> call cfg_focus_follows_mouse($value) + +# force_focus_wrapping +state FORCE_FOCUS_WRAPPING: + value = word + -> call cfg_force_focus_wrapping($value) + +# force_xinerama +state FORCE_XINERAMA: + value = word + -> call cfg_force_xinerama($value) + +# workspace_back_and_forth +state WORKSPACE_BACK_AND_FORTH: + value = word + -> call cfg_workspace_back_and_forth($value) + + +# fake_outputs (for testcases) +state FAKE_OUTPUTS: + outputs = string + -> call cfg_fake_outputs($outputs) + +# force_display_urgency_hint ms +state FORCE_DISPLAY_URGENCY_HINT: + duration_ms = number + -> FORCE_DISPLAY_URGENCY_HINT_MS + +state FORCE_DISPLAY_URGENCY_HINT_MS: + 'ms' + -> + end + -> call cfg_force_display_urgency_hint(&duration_ms) + +# workspace output +state WORKSPACE: + workspace = word + -> WORKSPACE_OUTPUT + +state WORKSPACE_OUTPUT: + 'output' + -> WORKSPACE_OUTPUT_STR + +state WORKSPACE_OUTPUT_STR: + output = string + -> call cfg_workspace($workspace, $output) + +# ipc-socket +state IPC_SOCKET: + path = string + -> call cfg_ipc_socket($path) + +# restart_state (for testcases) +state RESTART_STATE: + path = string + -> call cfg_restart_state($path) + +# popup_during_fullscreen +state POPUP_DURING_FULLSCREEN: + value = 'ignore', 'leave_fullscreen' + -> call cfg_popup_during_fullscreen($value) + +# client.background +state COLOR_SINGLE: + color = word + -> call cfg_color_single($colorclass, $color) + +# colorclass border background text indicator +state COLOR_BORDER: + border = word + -> COLOR_BACKGROUND + +state COLOR_BACKGROUND: + background = word + -> COLOR_TEXT + +state COLOR_TEXT: + text = word + -> COLOR_INDICATOR + +state COLOR_INDICATOR: + indicator = word + -> call cfg_color($colorclass, $border, $background, $text, $indicator) + end + -> call cfg_color($colorclass, $border, $background, $text, NULL) # [--no-startup-id] command state EXEC: @@ -29,6 +262,30 @@ state EXEC: command = string -> call cfg_exec($exectype, $no_startup_id, $command) +# font font +state FONT: + font = string + -> call cfg_font($font) + +# bindsym/bindcode +state BINDING: + modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Mode_switch' + -> + '+' + -> + key = word + -> BINDCOMMAND + +state BINDCOMMAND: + release = '--release' + -> + command = string + -> call cfg_binding($bindtype, $modifiers, $key, $release, $command) + +################################################################################ +# Mode configuration +################################################################################ + state MODENAME: modename = word -> call cfg_enter_mode($modename); MODEBRACE @@ -38,13 +295,21 @@ state MODEBRACE: -> MODE state MODE: + end -> + '#' -> MODE_IGNORE_LINE + 'set' -> MODE_IGNORE_LINE bindtype = 'bindsym', 'bindcode' -> MODE_BINDING '}' -> INITIAL +# We ignore comments and 'set' lines (variables). +state MODE_IGNORE_LINE: + end, string + -> MODE + state MODE_BINDING: - modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control' + modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Mode_switch' -> '+' -> @@ -52,9 +317,121 @@ state MODE_BINDING: -> MODE_BINDCOMMAND state MODE_BINDCOMMAND: + release = '--release' + -> command = string - -> call cfg_mode_binding($bindtype, $modifiers, $key, $command); MODE + -> call cfg_mode_binding($bindtype, $modifiers, $key, $release, $command); MODE -state FONT: +################################################################################ +# Bar configuration (i3bar) +################################################################################ + +state BARBRACE: + '{' + -> BAR + +state BAR: + end -> + '#' -> BAR_IGNORE_LINE + 'set' -> BAR_IGNORE_LINE + 'i3bar_command' -> BAR_BAR_COMMAND + 'status_command' -> BAR_STATUS_COMMAND + 'socket_path' -> BAR_SOCKET_PATH + 'mode' -> BAR_MODE + 'modifier' -> BAR_MODIFIER + 'position' -> BAR_POSITION + 'output' -> BAR_OUTPUT + 'tray_output' -> BAR_TRAY_OUTPUT + 'font' -> BAR_FONT + 'workspace_buttons' -> BAR_WORKSPACE_BUTTONS + 'verbose' -> BAR_VERBOSE + 'colors' -> BAR_COLORS_BRACE + '}' + -> call cfg_bar_finish(); INITIAL + +# We ignore comments and 'set' lines (variables). +state BAR_IGNORE_LINE: + end, string + -> BAR + +state BAR_BAR_COMMAND: + command = string + -> call cfg_bar_i3bar_command($command); BAR + +state BAR_STATUS_COMMAND: + command = string + -> call cfg_bar_status_command($command); BAR + +state BAR_SOCKET_PATH: + path = string + -> call cfg_bar_socket_path($path); BAR + +state BAR_MODE: + mode = 'dock', 'hide' + -> call cfg_bar_mode($mode); BAR + +state BAR_MODIFIER: + modifier = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Control', 'Shift' + -> call cfg_bar_modifier($modifier); BAR + +state BAR_POSITION: + position = 'top', 'bottom' + -> call cfg_bar_position($position); BAR + +state BAR_OUTPUT: + output = string + -> call cfg_bar_output($output); BAR + +state BAR_TRAY_OUTPUT: + output = string + -> call cfg_bar_tray_output($output); BAR + +state BAR_FONT: font = string - -> call cfg_font($font) + -> call cfg_bar_font($font); BAR + +state BAR_WORKSPACE_BUTTONS: + value = word + -> call cfg_bar_workspace_buttons($value); BAR + +state BAR_VERBOSE: + value = word + -> call cfg_bar_verbose($value); BAR + +state BAR_COLORS_BRACE: + '{' + -> BAR_COLORS + +state BAR_COLORS: + end -> + '#' -> BAR_COLORS_IGNORE_LINE + 'set' -> BAR_COLORS_IGNORE_LINE + colorclass = 'background', 'statusline' + -> BAR_COLORS_SINGLE + colorclass = 'focused_workspace', 'active_workspace', 'inactive_workspace', 'urgent_workspace' + -> BAR_COLORS_BORDER + '}' + -> BAR + +# We ignore comments and 'set' lines (variables). +state BAR_COLORS_IGNORE_LINE: + end, string + -> BAR_COLORS + +state BAR_COLORS_SINGLE: + color = word + -> call cfg_bar_color_single($colorclass, $color); BAR_COLORS + +state BAR_COLORS_BORDER: + border = word + -> BAR_COLORS_BACKGROUND + +state BAR_COLORS_BACKGROUND: + background = word + -> BAR_COLORS_TEXT + +state BAR_COLORS_TEXT: + end + -> call cfg_bar_color($colorclass, $border, $background, NULL); BAR_COLORS + text = word + -> call cfg_bar_color($colorclass, $border, $background, $text); BAR_COLORS diff --git a/parser-specs/highlighting.vim b/parser-specs/highlighting.vim index f3d1aaba..e0966d57 100644 --- a/parser-specs/highlighting.vim +++ b/parser-specs/highlighting.vim @@ -9,7 +9,7 @@ syntax match i3specComment /#.*/ highlight link i3specComment Comment syntax region i3specLiteral start=/'/ end=/'/ -syntax keyword i3specToken string word end +syntax keyword i3specToken string word number end highlight link i3specLiteral String highlight link i3specToken String diff --git a/src/config_directives.c b/src/config_directives.c index 9660866e..70e5792a 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -6,7 +6,7 @@ * i3 - an improved dynamic tiling window manager * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) * - * config_directives.c: all command functions (see config_parser.c) + * config_directives.c: all config storing functions (see config_parser.c) * */ #include @@ -24,9 +24,150 @@ y(map_close); \ } while (0) +/******************************************************************************* + * Criteria functions. + ******************************************************************************/ + +static int criteria_next_state; + +/* + * Initializes the specified 'Match' data structure and the initial state of + * commands.c for matching target windows of a command. + * + */ +CFGFUN(criteria_init, int _state) { + criteria_next_state = _state; + + DLOG("Initializing criteria, current_match = %p, state = %d\n", current_match, _state); + match_init(current_match); +} + +CFGFUN(criteria_pop_state) { + result->next_state = criteria_next_state; +} + +/* + * Interprets a ctype=cvalue pair and adds it to the current match + * specification. + * + */ +CFGFUN(criteria_add, const char *ctype, const char *cvalue) { + DLOG("ctype=*%s*, cvalue=*%s*\n", ctype, cvalue); + + if (strcmp(ctype, "class") == 0) { + current_match->class = regex_new(cvalue); + return; + } + + if (strcmp(ctype, "instance") == 0) { + current_match->instance = regex_new(cvalue); + return; + } + + if (strcmp(ctype, "window_role") == 0) { + current_match->role = regex_new(cvalue); + return; + } + + if (strcmp(ctype, "con_id") == 0) { + char *end; + long parsed = strtol(cvalue, &end, 10); + if (parsed == LONG_MIN || + parsed == LONG_MAX || + parsed < 0 || + (end && *end != '\0')) { + ELOG("Could not parse con id \"%s\"\n", cvalue); + } else { + current_match->con_id = (Con*)parsed; + printf("id as int = %p\n", current_match->con_id); + } + return; + } + + if (strcmp(ctype, "id") == 0) { + char *end; + long parsed = strtol(cvalue, &end, 10); + if (parsed == LONG_MIN || + parsed == LONG_MAX || + parsed < 0 || + (end && *end != '\0')) { + ELOG("Could not parse window id \"%s\"\n", cvalue); + } else { + current_match->id = parsed; + printf("window id as int = %d\n", current_match->id); + } + return; + } + + if (strcmp(ctype, "con_mark") == 0) { + current_match->mark = regex_new(cvalue); + return; + } + + if (strcmp(ctype, "title") == 0) { + current_match->title = regex_new(cvalue); + return; + } + + if (strcmp(ctype, "urgent") == 0) { + if (strcasecmp(cvalue, "latest") == 0 || + strcasecmp(cvalue, "newest") == 0 || + strcasecmp(cvalue, "recent") == 0 || + strcasecmp(cvalue, "last") == 0) { + current_match->urgent = U_LATEST; + } else if (strcasecmp(cvalue, "oldest") == 0 || + strcasecmp(cvalue, "first") == 0) { + current_match->urgent = U_OLDEST; + } + return; + } + + ELOG("Unknown criterion: %s\n", ctype); +} + +/* TODO: refactor the above criteria code into a single file (with src/commands.c). */ + +/******************************************************************************* + * Utility functions + ******************************************************************************/ + +static bool eval_boolstr(const char *str) { + return (strcasecmp(str, "1") == 0 || + strcasecmp(str, "yes") == 0 || + strcasecmp(str, "true") == 0 || + strcasecmp(str, "on") == 0 || + strcasecmp(str, "enable") == 0 || + strcasecmp(str, "active") == 0); +} + +static uint32_t modifiers_from_str(const char *str) { + /* It might be better to use strtok() here, but the simpler strstr() should + * do for now. */ + uint32_t result = 0; + if (str == NULL) + return result; + if (strstr(str, "Mod1") != NULL) + result |= BIND_MOD1; + if (strstr(str, "Mod2") != NULL) + result |= BIND_MOD2; + if (strstr(str, "Mod3") != NULL) + result |= BIND_MOD3; + if (strstr(str, "Mod4") != NULL) + result |= BIND_MOD4; + if (strstr(str, "Mod5") != NULL) + result |= BIND_MOD5; + if (strstr(str, "Control") != NULL) + result |= BIND_CONTROL; + if (strstr(str, "Shift") != NULL) + result |= BIND_SHIFT; + if (strstr(str, "Mode_switch") != NULL) + result |= BIND_MODE_SWITCH; + return result; +} + static char *font_pattern; -void cfg_font(I3_CFG, const char *font) { +CFGFUN(font, const char *font) { config.font = load_font(font, true); set_font(&config.font); @@ -35,16 +176,57 @@ void cfg_font(I3_CFG, const char *font) { font_pattern = sstrdup(font); } -void cfg_mode_binding(I3_CFG, const char *bindtype, const char *modifiers, const char *key, const char *command) { - printf("cfg_mode_binding: got bindtype\n"); +// TODO: refactor with mode_binding +CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *command) { + Binding *new_binding = scalloc(sizeof(Binding)); + new_binding->release = (release != NULL ? B_UPON_KEYRELEASE : B_UPON_KEYPRESS); + if (strcmp(bindtype, "bindsym") == 0) { + new_binding->symbol = sstrdup(key); + } else { + // TODO: strtol with proper error handling + new_binding->keycode = atoi(key); + } + new_binding->mods = modifiers_from_str(modifiers); + new_binding->command = sstrdup(command); + TAILQ_INSERT_TAIL(bindings, new_binding, bindings); } -void cfg_enter_mode(I3_CFG, const char *mode) { - // TODO: error handling: if mode == '{', the mode name is missing - printf("mode name: %s\n", mode); + +/******************************************************************************* + * Mode handling + ******************************************************************************/ + +static struct bindings_head *current_bindings; + +CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *command) { + Binding *new_binding = scalloc(sizeof(Binding)); + new_binding->release = (release != NULL ? B_UPON_KEYRELEASE : B_UPON_KEYPRESS); + if (strcmp(bindtype, "bindsym") == 0) { + new_binding->symbol = sstrdup(key); + } else { + // TODO: strtol with proper error handling + new_binding->keycode = atoi(key); + } + new_binding->mods = modifiers_from_str(modifiers); + new_binding->command = sstrdup(command); + TAILQ_INSERT_TAIL(current_bindings, new_binding, bindings); } -void cfg_exec(I3_CFG, const char *exectype, const char *no_startup_id, const char *command) { +CFGFUN(enter_mode, const char *modename) { + if (strcasecmp(modename, "default") == 0) { + ELOG("You cannot use the name \"default\" for your mode\n"); + exit(1); + } + DLOG("\t now in mode %s\n", modename); + struct Mode *mode = scalloc(sizeof(struct Mode)); + mode->name = sstrdup(modename); + mode->bindings = scalloc(sizeof(struct bindings_head)); + TAILQ_INIT(mode->bindings); + current_bindings = mode->bindings; + SLIST_INSERT_HEAD(&modes, mode, modes); +} + +CFGFUN(exec, const char *exectype, const char *no_startup_id, const char *command) { struct Autostart *new = smalloc(sizeof(struct Autostart)); new->command = sstrdup(command); new->no_startup_id = (no_startup_id != NULL); @@ -54,3 +236,315 @@ void cfg_exec(I3_CFG, const char *exectype, const char *no_startup_id, const cha TAILQ_INSERT_TAIL(&autostarts_always, new, autostarts_always); } } + +CFGFUN(for_window, const char *command) { + if (match_is_empty(current_match)) { + ELOG("Match is empty, ignoring this for_window statement\n"); + return; + } + DLOG("\t should execute command %s for the criteria mentioned above\n", command); + Assignment *assignment = scalloc(sizeof(Assignment)); + assignment->type = A_COMMAND; + match_copy(&(assignment->match), current_match); + assignment->dest.command = sstrdup(command); + TAILQ_INSERT_TAIL(&assignments, assignment, assignments); +} + +CFGFUN(floating_minimum_size, const long width, const long height) { + config.floating_minimum_width = width; + config.floating_minimum_height = height; +} + +CFGFUN(floating_maximum_size, const long width, const long height) { + config.floating_maximum_width = width; + config.floating_maximum_height = height; +} + +CFGFUN(floating_modifier, const char *modifiers) { + config.floating_modifier = modifiers_from_str(modifiers); +} + +CFGFUN(default_orientation, const char *orientation) { + if (strcmp(orientation, "horizontal") == 0) + config.default_orientation = HORIZ; + else if (strcmp(orientation, "vertical") == 0) + config.default_orientation = VERT; + else config.default_orientation = NO_ORIENTATION; +} + +CFGFUN(workspace_layout, const char *layout) { + if (strcmp(layout, "default") == 0) + config.default_layout = L_DEFAULT; + else if (strcmp(layout, "stacking") == 0 || + strcmp(layout, "stacked") == 0) + config.default_layout = L_STACKED; + else config.default_layout = L_TABBED; +} + +CFGFUN(new_window, const char *windowtype, const char *border, const long width) { + // FIXME: when using new_float *and* new_window with different border + // types, this breaks because default_border_width gets overwritten. + + int border_style; + int border_width; + + if (strcmp(border, "1pixel") == 0) { + border_style = BS_PIXEL; + border_width = 1; + } else if (strcmp(border, "none") == 0) { + border_style = BS_NONE; + border_width = 0; + } else if (strcmp(border, "pixel") == 0) { + border_style = BS_PIXEL; + border_width = width; + } else { + border_style = BS_NORMAL; + border_width = width; + } + + if (strcmp(windowtype, "new_window") == 0) { + config.default_border = border_style; + } else { + config.default_floating_border = border_style; + } +} + +CFGFUN(hide_edge_borders, const char *borders) { + if (strcmp(borders, "vertical") == 0) + config.hide_edge_borders = ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE; + else if (strcmp(borders, "horizontal") == 0) + config.hide_edge_borders = ADJ_UPPER_SCREEN_EDGE | ADJ_LOWER_SCREEN_EDGE; + else if (strcmp(borders, "both") == 0) + config.hide_edge_borders = ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE | ADJ_UPPER_SCREEN_EDGE | ADJ_LOWER_SCREEN_EDGE; + else if (strcmp(borders, "none") == 0) + config.hide_edge_borders = ADJ_NONE; + else if (eval_boolstr(borders)) + config.hide_edge_borders = ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE; + else config.hide_edge_borders = ADJ_NONE; +} + +CFGFUN(focus_follows_mouse, const char *value) { + config.disable_focus_follows_mouse = !eval_boolstr(value); +} + +CFGFUN(force_xinerama, const char *value) { + config.force_xinerama = eval_boolstr(value); +} + +CFGFUN(force_focus_wrapping, const char *value) { + config.force_focus_wrapping = eval_boolstr(value); +} + +CFGFUN(workspace_back_and_forth, const char *value) { + config.workspace_auto_back_and_forth = eval_boolstr(value); +} + +CFGFUN(fake_outputs, const char *outputs) { + config.fake_outputs = sstrdup(outputs); +} + +CFGFUN(force_display_urgency_hint, const long duration_ms) { + config.workspace_urgency_timer = duration_ms / 1000.0; +} + +CFGFUN(workspace, const char *workspace, const char *output) { + DLOG("Assigning workspace \"%s\" to output \"%s\"\n", workspace, output); + /* Check for earlier assignments of the same workspace so that we + * don’t have assignments of a single workspace to different + * outputs */ + struct Workspace_Assignment *assignment; + bool duplicate = false; + TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { + if (strcasecmp(assignment->name, workspace) == 0) { + ELOG("You have a duplicate workspace assignment for workspace \"%s\"\n", + workspace); + assignment->output = sstrdup(output); + duplicate = true; + } + } + if (!duplicate) { + assignment = scalloc(sizeof(struct Workspace_Assignment)); + assignment->name = sstrdup(workspace); + assignment->output = sstrdup(output); + TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments); + } +} + +CFGFUN(ipc_socket, const char *path) { + config.ipc_socket_path = sstrdup(path); +} + +CFGFUN(restart_state, const char *path) { + config.restart_state_path = sstrdup(path); +} + +CFGFUN(popup_during_fullscreen, const char *value) { + config.popup_during_fullscreen = + (strcmp(value, "ignore") == 0 ? PDF_IGNORE : PDF_LEAVE_FULLSCREEN); +} + +CFGFUN(color_single, const char *colorclass, const char *color) { + /* used for client.background only currently */ + config.client.background = get_colorpixel(color); +} + +CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator) { +#define APPLY_COLORS(classname) \ + do { \ + if (strcmp(colorclass, "client." #classname) == 0) { \ + config.client.classname.border = get_colorpixel(border); \ + config.client.classname.background = get_colorpixel(background); \ + config.client.classname.text = get_colorpixel(text); \ + if (indicator != NULL) { \ + config.client. classname .indicator = get_colorpixel(indicator); \ + } \ + } \ + } while (0) + + APPLY_COLORS(focused_inactive); + APPLY_COLORS(focused); + APPLY_COLORS(unfocused); + APPLY_COLORS(urgent); + +#undef APPLY_COLORS +} + +CFGFUN(assign, const char *workspace) { + if (match_is_empty(current_match)) { + ELOG("Match is empty, ignoring this assignment\n"); + return; + } + DLOG("new assignment, using above criteria, to workspace %s\n", workspace); + Assignment *assignment = scalloc(sizeof(Assignment)); + match_copy(&(assignment->match), current_match); + assignment->type = A_TO_WORKSPACE; + assignment->dest.workspace = sstrdup(workspace); + TAILQ_INSERT_TAIL(&assignments, assignment, assignments); +} + +/******************************************************************************* + * Bar configuration (i3bar) + ******************************************************************************/ + +static Barconfig current_bar; + +CFGFUN(bar_font, const char *font) { + FREE(current_bar.font); + current_bar.font = sstrdup(font); +} + +CFGFUN(bar_mode, const char *mode) { + current_bar.mode = (strcmp(mode, "hide") == 0 ? M_HIDE : M_DOCK); +} + +CFGFUN(bar_output, const char *output) { + int new_outputs = current_bar.num_outputs + 1; + current_bar.outputs = srealloc(current_bar.outputs, sizeof(char*) * new_outputs); + current_bar.outputs[current_bar.num_outputs] = sstrdup(output); + current_bar.num_outputs = new_outputs; +} + +CFGFUN(bar_verbose, const char *verbose) { + current_bar.verbose = eval_boolstr(verbose); +} + +CFGFUN(bar_modifier, const char *modifier) { + if (strcmp(modifier, "Mod1") == 0) + current_bar.modifier = M_MOD1; + else if (strcmp(modifier, "Mod2") == 0) + current_bar.modifier = M_MOD2; + else if (strcmp(modifier, "Mod3") == 0) + current_bar.modifier = M_MOD3; + else if (strcmp(modifier, "Mod4") == 0) + current_bar.modifier = M_MOD4; + else if (strcmp(modifier, "Mod5") == 0) + current_bar.modifier = M_MOD5; + else if (strcmp(modifier, "Control") == 0) + current_bar.modifier = M_CONTROL; + else if (strcmp(modifier, "Shift") == 0) + current_bar.modifier = M_SHIFT; +} + +CFGFUN(bar_position, const char *position) { + current_bar.position = (strcmp(position, "top") == 0 ? P_TOP : P_BOTTOM); +} + +CFGFUN(bar_i3bar_command, const char *i3bar_command) { + FREE(current_bar.i3bar_command); + current_bar.i3bar_command = sstrdup(i3bar_command); +} + +CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text) { +#define APPLY_COLORS(classname) \ + do { \ + if (strcmp(colorclass, #classname) == 0) { \ + if (text != NULL) { \ + /* New syntax: border, background, text */ \ + current_bar.colors. classname ## _border = sstrdup(border); \ + current_bar.colors. classname ## _bg = sstrdup(background); \ + current_bar.colors. classname ## _text = sstrdup(text); \ + } else { \ + /* Old syntax: text, background */ \ + current_bar.colors. classname ## _bg = sstrdup(background); \ + current_bar.colors. classname ## _text = sstrdup(border); \ + } \ + } \ + } while (0) + + APPLY_COLORS(focused_workspace); + APPLY_COLORS(active_workspace); + APPLY_COLORS(inactive_workspace); + APPLY_COLORS(urgent_workspace); + +#undef APPLY_COLORS +} + +CFGFUN(bar_socket_path, const char *socket_path) { + FREE(current_bar.socket_path); + current_bar.socket_path = sstrdup(socket_path); +} + +CFGFUN(bar_tray_output, const char *output) { + FREE(current_bar.tray_output); + current_bar.tray_output = sstrdup(output); +} + +CFGFUN(bar_color_single, const char *colorclass, const char *color) { + if (strcmp(colorclass, "background") == 0) + current_bar.colors.background = sstrdup(color); + else current_bar.colors.statusline = sstrdup(color); +} + +CFGFUN(bar_status_command, const char *command) { + FREE(current_bar.status_command); + current_bar.status_command = sstrdup(command); +} + +CFGFUN(bar_workspace_buttons, const char *value) { + current_bar.hide_workspace_buttons = !eval_boolstr(value); +} + +CFGFUN(bar_finish) { + DLOG("\t new bar configuration finished, saving.\n"); + /* Generate a unique ID for this bar */ + current_bar.id = sstrdup("bar-XXXXXX"); + /* This works similar to mktemp in that it replaces the last six X with + * random letters, but without the restriction that the given buffer + * has to contain a valid path name. */ + char *x = current_bar.id + strlen("bar-"); + while (*x != '\0') { + *(x++) = (rand() % 26) + 'a'; + } + + /* If no font was explicitly set, we use the i3 font as default */ + if (!current_bar.font && font_pattern) + current_bar.font = sstrdup(font_pattern); + + /* Copy the current (static) structure into a dynamically allocated + * one, then cleanup our static one. */ + Barconfig *bar_config = scalloc(sizeof(Barconfig)); + memcpy(bar_config, ¤t_bar, sizeof(Barconfig)); + TAILQ_INSERT_TAIL(&barconfigs, bar_config, configs); + + memset(¤t_bar, '\0', sizeof(Barconfig)); +} From 587b92b7a30375235513302d4c73dbb8227ca3b2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 8 Oct 2012 13:24:47 +0200 Subject: [PATCH 077/146] t/201-config-parser: test more directives, test error handling --- testcases/t/201-config-parser.t | 466 +++++++++++++++++++++++++++++++- 1 file changed, 460 insertions(+), 6 deletions(-) diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t index 74ea9538..711b689d 100644 --- a/testcases/t/201-config-parser.t +++ b/testcases/t/201-config-parser.t @@ -34,9 +34,9 @@ sub parser_calls { my @lines = split("\n", $stdout); @lines = grep { not /^# / } @lines; - ## The criteria management calls are irrelevant and not what we want to test - ## in the first place. - #@lines = grep { !(/cmd_criteria_init()/ || /cmd_criteria_match_windows/) } @lines; + # The criteria management calls are irrelevant and not what we want to test + # in the first place. + @lines = grep { !(/cfg_criteria_init/ || /cfg_criteria_pop_state/) } @lines; return join("\n", @lines) . "\n"; } @@ -49,14 +49,18 @@ EOT my $expected = <<'EOT'; cfg_enter_mode(meh) -cfg_mode_binding(bindsym, Mod1,Shift, x, resize grow) -cfg_mode_binding(bindcode, Mod1, 44, resize shrink) +cfg_mode_binding(bindsym, Mod1,Shift, x, (null), resize grow) +cfg_mode_binding(bindcode, Mod1, 44, (null), resize shrink) EOT is(parser_calls($config), $expected, 'single number (move workspace 3) ok'); +################################################################################ +# exec and exec_always +################################################################################ + $config = <<'EOT'; exec geeqie exec --no-startup-id /tmp/foo.sh @@ -73,7 +77,457 @@ EOT is(parser_calls($config), $expected, - 'single number (move workspace 3) ok'); + 'exec okay'); +################################################################################ +# for_window +################################################################################ + +$config = <<'EOT'; +for_window [class="^Chrome"] floating enable +EOT + +$expected = <<'EOT'; +cfg_criteria_add(class, ^Chrome) +cfg_for_window(floating enable) +EOT + +is(parser_calls($config), + $expected, + 'for_window okay'); + +################################################################################ +# assign +################################################################################ + +$config = <<'EOT'; +assign [class="^Chrome"] 4 +assign [class="^Chrome"] named workspace +assign [class="^Chrome"] "quoted named workspace" +assign [class="^Chrome"] → "quoted named workspace" +EOT + +$expected = <<'EOT'; +cfg_criteria_add(class, ^Chrome) +cfg_assign(4) +cfg_criteria_add(class, ^Chrome) +cfg_assign(named workspace) +cfg_criteria_add(class, ^Chrome) +cfg_assign(quoted named workspace) +cfg_criteria_add(class, ^Chrome) +cfg_assign(quoted named workspace) +EOT + +is(parser_calls($config), + $expected, + 'for_window okay'); + +################################################################################ +# floating_minimum_size / floating_maximum_size +################################################################################ + +$config = <<'EOT'; +floating_minimum_size 80x55 +floating_minimum_size 80 x 55 +floating_maximum_size 73 x 10 +EOT + +$expected = <<'EOT'; +cfg_floating_minimum_size(80, 55) +cfg_floating_minimum_size(80, 55) +cfg_floating_maximum_size(73, 10) +EOT + +is(parser_calls($config), + $expected, + 'floating_minimum_size ok'); + +################################################################################ +# floating_modifier +################################################################################ + +$config = <<'EOT'; +floating_modifier Mod1 +floating_modifier mOd1 +EOT + +$expected = <<'EOT'; +cfg_floating_modifier(Mod1) +cfg_floating_modifier(Mod1) +EOT + +is(parser_calls($config), + $expected, + 'floating_modifier ok'); + +################################################################################ +# default_orientation +################################################################################ + +$config = <<'EOT'; +default_orientation horizontal +default_orientation vertical +default_orientation auto +EOT + +$expected = <<'EOT'; +cfg_default_orientation(horizontal) +cfg_default_orientation(vertical) +cfg_default_orientation(auto) +EOT + +is(parser_calls($config), + $expected, + 'default_orientation ok'); + +################################################################################ +# workspace_layout +################################################################################ + +$config = <<'EOT'; +workspace_layout default +workspace_layout stacked +workspace_layout stacking +workspace_layout tabbed +EOT + +$expected = <<'EOT'; +cfg_workspace_layout(default) +cfg_workspace_layout(stacked) +cfg_workspace_layout(stacking) +cfg_workspace_layout(tabbed) +EOT + +is(parser_calls($config), + $expected, + 'workspace_layout ok'); + +################################################################################ +# new_window +################################################################################ + +$config = <<'EOT'; +new_window 1pixel +new_window normal +new_window none +new_float 1pixel +new_float normal +new_float none +EOT + +$expected = <<'EOT'; +cfg_new_window(new_window, 1pixel, -1) +cfg_new_window(new_window, normal, 2) +cfg_new_window(new_window, none, -1) +cfg_new_window(new_float, 1pixel, -1) +cfg_new_window(new_float, normal, 2) +cfg_new_window(new_float, none, -1) +EOT + +is(parser_calls($config), + $expected, + 'new_window ok'); + +################################################################################ +# hide_edge_borders +################################################################################ + +$config = <<'EOT'; +hide_edge_borders none +hide_edge_borders vertical +hide_edge_borders horizontal +hide_edge_borders both +EOT + +$expected = <<'EOT'; +cfg_hide_edge_borders(none) +cfg_hide_edge_borders(vertical) +cfg_hide_edge_borders(horizontal) +cfg_hide_edge_borders(both) +EOT + +is(parser_calls($config), + $expected, + 'hide_edge_borders ok'); + +################################################################################ +# focus_follows_mouse +################################################################################ + +$config = <<'EOT'; +focus_follows_mouse yes +focus_follows_mouse no +EOT + +$expected = <<'EOT'; +cfg_focus_follows_mouse(yes) +cfg_focus_follows_mouse(no) +EOT + +is(parser_calls($config), + $expected, + 'focus_follows_mouse ok'); + +################################################################################ +# force_display_urgency_hint +################################################################################ + +is(parser_calls('force_display_urgency_hint 300'), + "cfg_force_display_urgency_hint(300)\n", + 'force_display_urgency_hint ok'); + +is(parser_calls('force_display_urgency_hint 500 ms'), + "cfg_force_display_urgency_hint(500)\n", + 'force_display_urgency_hint ok'); + +is(parser_calls('force_display_urgency_hint 700ms'), + "cfg_force_display_urgency_hint(700)\n", + 'force_display_urgency_hint ok'); + +$config = <<'EOT'; +force_display_urgency_hint 300 +force_display_urgency_hint 500 ms +force_display_urgency_hint 700ms +force_display_urgency_hint 700 +EOT + +$expected = <<'EOT'; +cfg_force_display_urgency_hint(300) +cfg_force_display_urgency_hint(500) +cfg_force_display_urgency_hint(700) +cfg_force_display_urgency_hint(700) +EOT + +is(parser_calls($config), + $expected, + 'force_display_urgency_hint ok'); + +################################################################################ +# workspace +################################################################################ + +$config = <<'EOT'; +workspace 3 output VGA-1 +workspace "4: output" output VGA-2 +workspace bleh output LVDS1/I_1 +EOT + +$expected = <<'EOT'; +cfg_workspace(3, VGA-1) +cfg_workspace(4: output, VGA-2) +cfg_workspace(bleh, LVDS1/I_1) +EOT + +is(parser_calls($config), + $expected, + 'workspace ok'); + +################################################################################ +# ipc-socket +################################################################################ + +$config = <<'EOT'; +ipc-socket /tmp/i3.sock +ipc_socket ~/.i3/i3.sock +EOT + +$expected = <<'EOT'; +cfg_ipc_socket(/tmp/i3.sock) +cfg_ipc_socket(~/.i3/i3.sock) +EOT + +is(parser_calls($config), + $expected, + 'ipc-socket ok'); + +################################################################################ +# colors +################################################################################ + +$config = <<'EOT'; +client.focused #4c7899 #285577 #ffffff #2e9ef4 +client.focused_inactive #333333 #5f676a #ffffff #484e50 +client.unfocused #333333 #222222 #888888 #292d2e +client.urgent #2f343a #900000 #ffffff #900000 +EOT + +$expected = <<'EOT'; +cfg_color(client.focused, #4c7899, #285577, #ffffff, #2e9ef4) +cfg_color(client.focused_inactive, #333333, #5f676a, #ffffff, #484e50) +cfg_color(client.unfocused, #333333, #222222, #888888, #292d2e) +cfg_color(client.urgent, #2f343a, #900000, #ffffff, #900000) +EOT + +is(parser_calls($config), + $expected, + 'colors ok'); + +################################################################################ +# Error message with 2+2 lines of context +################################################################################ + +$config = <<'EOT'; +# i3 config file (v4) + +font foobar + +unknown qux + +# yay +# this should not show up +EOT + +$expected = <<'EOT'; +cfg_font(foobar) +ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'bindsym', 'bindcode', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent' +ERROR: CONFIG: (in file ) +ERROR: CONFIG: Line 3: font foobar +ERROR: CONFIG: Line 4: +ERROR: CONFIG: Line 5: unknown qux +ERROR: CONFIG: ^^^^^^^^^^^ +ERROR: CONFIG: Line 6: +ERROR: CONFIG: Line 7: # yay +EOT + +is(parser_calls($config), + $expected, + 'error message (2+2 context) ok'); + +################################################################################ +# Error message with 0+0 lines of context +################################################################################ + +$config = <<'EOT'; +unknown qux +EOT + +$expected = <<'EOT'; +ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'bindsym', 'bindcode', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent' +ERROR: CONFIG: (in file ) +ERROR: CONFIG: Line 1: unknown qux +ERROR: CONFIG: ^^^^^^^^^^^ +EOT + +is(parser_calls($config), + $expected, + 'error message (0+0 context) ok'); + +################################################################################ +# Error message with 1+0 lines of context +################################################################################ + +$config = <<'EOT'; +# context before +unknown qux +EOT + +$expected = <<'EOT'; +ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'bindsym', 'bindcode', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent' +ERROR: CONFIG: (in file ) +ERROR: CONFIG: Line 1: # context before +ERROR: CONFIG: Line 2: unknown qux +ERROR: CONFIG: ^^^^^^^^^^^ +EOT + +is(parser_calls($config), + $expected, + 'error message (1+0 context) ok'); + +################################################################################ +# Error message with 0+1 lines of context +################################################################################ + +$config = <<'EOT'; +unknown qux +# context after +EOT + +$expected = <<'EOT'; +ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'bindsym', 'bindcode', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent' +ERROR: CONFIG: (in file ) +ERROR: CONFIG: Line 1: unknown qux +ERROR: CONFIG: ^^^^^^^^^^^ +ERROR: CONFIG: Line 2: # context after +EOT + +is(parser_calls($config), + $expected, + 'error message (0+1 context) ok'); + +################################################################################ +# Error message with 0+2 lines of context +################################################################################ + +$config = <<'EOT'; +unknown qux +# context after +# context 2 after +EOT + +$expected = <<'EOT'; +ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'bindsym', 'bindcode', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent' +ERROR: CONFIG: (in file ) +ERROR: CONFIG: Line 1: unknown qux +ERROR: CONFIG: ^^^^^^^^^^^ +ERROR: CONFIG: Line 2: # context after +ERROR: CONFIG: Line 3: # context 2 after +EOT + +is(parser_calls($config), + $expected, + 'error message (0+2 context) ok'); + +################################################################################ +# Error message within mode blocks +################################################################################ + +$config = <<'EOT'; +mode "yo" { + bindsym x resize shrink left + unknown qux +} +EOT + +$expected = <<'EOT'; +cfg_enter_mode(yo) +cfg_mode_binding(bindsym, (null), x, (null), resize shrink left) +ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'bindsym', 'bindcode', '}' +ERROR: CONFIG: (in file ) +ERROR: CONFIG: Line 1: mode "yo" { +ERROR: CONFIG: Line 2: bindsym x resize shrink left +ERROR: CONFIG: Line 3: unknown qux +ERROR: CONFIG: ^^^^^^^^^^^ +ERROR: CONFIG: Line 4: } +EOT + +is(parser_calls($config), + $expected, + 'error message (mode block) ok'); + +################################################################################ +# Error message within bar blocks +################################################################################ + +$config = <<'EOT'; +bar { + output LVDS-1 + unknown qux +} +EOT + +$expected = <<'EOT'; +cfg_bar_output(LVDS-1) +ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'modifier', 'position', 'output', 'tray_output', 'font', 'workspace_buttons', 'verbose', 'colors', '}' +ERROR: CONFIG: (in file ) +ERROR: CONFIG: Line 1: bar { +ERROR: CONFIG: Line 2: output LVDS-1 +ERROR: CONFIG: Line 3: unknown qux +ERROR: CONFIG: ^^^^^^^^^^^ +ERROR: CONFIG: Line 4: } +EOT + +is(parser_calls($config), + $expected, + 'error message (bar block) ok'); done_testing; From c2b699f3dc4fb9ff4dfb2c7409bc9d031aeddf71 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 8 Oct 2012 13:25:32 +0200 Subject: [PATCH 078/146] change the commands_parser prefix to 'command' for consistency --- src/commands_parser.c | 6 +++--- src/i3.mk | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands_parser.c b/src/commands_parser.c index bbba2c44..1720fd6f 100644 --- a/src/commands_parser.c +++ b/src/commands_parser.c @@ -46,7 +46,7 @@ * input parser-specs/commands.spec. ******************************************************************************/ -#include "GENERATED_commands_enums.h" +#include "GENERATED_command_enums.h" typedef struct token { char *name; @@ -63,7 +63,7 @@ typedef struct tokenptr { int n; } cmdp_token_ptr; -#include "GENERATED_commands_tokens.h" +#include "GENERATED_command_tokens.h" /******************************************************************************* * The (small) stack where identified literals are stored during the parsing @@ -182,7 +182,7 @@ static Match current_match; static struct CommandResult subcommand_output; static struct CommandResult command_output; -#include "GENERATED_commands_call.h" +#include "GENERATED_command_call.h" static void next_state(const cmdp_token *token) { diff --git a/src/i3.mk b/src/i3.mk index fd4afdb7..d0ff1e88 100644 --- a/src/i3.mk +++ b/src/i3.mk @@ -63,7 +63,7 @@ src/config_parser.o: src/config_parser.c $(i3_HEADERS_DEP) i3-config-parser.stam i3-command-parser.stamp: generate-command-parser.pl parser-specs/commands.spec echo "[i3] Generating command parser" - (cd include; ../generate-command-parser.pl --input=../parser-specs/commands.spec --prefix=commands) + (cd include; ../generate-command-parser.pl --input=../parser-specs/commands.spec --prefix=command) touch $@ i3-config-parser.stamp: generate-command-parser.pl parser-specs/config.spec From 68e3e582325dd5b2ca9ecb9cc36247c451ca3f6e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 8 Oct 2012 13:25:57 +0200 Subject: [PATCH 079/146] link the parser test binaries with -g --- src/i3.mk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i3.mk b/src/i3.mk index d0ff1e88..81916394 100644 --- a/src/i3.mk +++ b/src/i3.mk @@ -50,7 +50,7 @@ src/cfgparse.tab.c: src/cfgparse.y $(i3_HEADERS_DEP) # and once as an object file for i3. src/commands_parser.o: src/commands_parser.c $(i3_HEADERS_DEP) i3-command-parser.stamp echo "[i3] CC $<" - $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) $(I3_LDFLAGS) $(LDFLAGS) -DTEST_PARSER -o test.commands_parser $< $(LIBS) $(i3_LIBS) + $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) $(I3_LDFLAGS) $(LDFLAGS) -DTEST_PARSER -g -o test.commands_parser $< $(LIBS) $(i3_LIBS) $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ ${canonical_path}/$< # This target compiles the command parser twice: @@ -58,7 +58,7 @@ src/commands_parser.o: src/commands_parser.c $(i3_HEADERS_DEP) i3-command-parser # and once as an object file for i3. src/config_parser.o: src/config_parser.c $(i3_HEADERS_DEP) i3-config-parser.stamp echo "[i3] CC $<" - $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) $(I3_LDFLAGS) $(LDFLAGS) -DTEST_PARSER -o test.config_parser $< $(LIBS) $(i3_LIBS) + $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) $(I3_LDFLAGS) $(LDFLAGS) -DTEST_PARSER -g -o test.config_parser $< $(LIBS) $(i3_LIBS) $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) -c -o $@ ${canonical_path}/$< i3-command-parser.stamp: generate-command-parser.pl parser-specs/commands.spec From 2738f137985254371dab279f6063164fbb42e8e0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 8 Oct 2012 13:26:24 +0200 Subject: [PATCH 080/146] move owindow definition into the command parser --- include/commands.h | 12 ------------ src/commands.c | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/include/commands.h b/include/commands.h index 6d195a09..5da9fc07 100644 --- a/include/commands.h +++ b/include/commands.h @@ -15,18 +15,6 @@ /** The beginning of the prototype for every cmd_ function. */ #define I3_CMD Match *current_match, struct CommandResult *cmd_output -/* - * Helper data structure for an operation window (window on which the operation - * will be performed). Used to build the TAILQ owindows. - * - */ -typedef struct owindow { - Con *con; - TAILQ_ENTRY(owindow) owindows; -} owindow; - -typedef TAILQ_HEAD(owindows_head, owindow) owindows_head; - /** * Initializes the specified 'Match' data structure and the initial state of * commands.c for matching target windows of a command. diff --git a/src/commands.c b/src/commands.c index 53532435..e323a6ec 100644 --- a/src/commands.c +++ b/src/commands.c @@ -38,7 +38,6 @@ } \ } while (0) -static owindows_head owindows; /* * Returns true if a is definitely greater than b (using the given epsilon) @@ -222,6 +221,20 @@ void cmd_MIGRATION_start_nagbar(void) { * Criteria functions. ******************************************************************************/ +/* + * Helper data structure for an operation window (window on which the operation + * will be performed). Used to build the TAILQ owindows. + * + */ +typedef struct owindow { + Con *con; + TAILQ_ENTRY(owindow) owindows; +} owindow; + +typedef TAILQ_HEAD(owindows_head, owindow) owindows_head; + +static owindows_head owindows; + /* * Initializes the specified 'Match' data structure and the initial state of * commands.c for matching target windows of a command. From 20c0fa7cfbb8ce29de4eafa9c926e163b757d5c6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 8 Oct 2012 13:26:42 +0200 Subject: [PATCH 081/146] use the new parser by default you can force the old parser with the command line flag --force-old-config-parser-v4.4-only (which will be present in v4.4 only, as the name suggests) --- include/all.h | 2 +- include/commands_parser.h | 5 +++++ include/config.h | 2 ++ src/cfgparse.y | 21 +++++++++++++++------ src/main.c | 5 +++++ 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/include/all.h b/include/all.h index 9ac6a54f..c9c4bbbe 100644 --- a/include/all.h +++ b/include/all.h @@ -80,7 +80,7 @@ #include "commands.h" #include "commands_parser.h" #include "config_directives.h" -//#include "config_parser.h" +#include "config_parser.h" #include "fake_outputs.h" #include "display_version.h" diff --git a/include/commands_parser.h b/include/commands_parser.h index 6ff8d54e..fcc14ff5 100644 --- a/include/commands_parser.h +++ b/include/commands_parser.h @@ -27,6 +27,11 @@ struct CommandResult { /* Whether the command requires calling tree_render. */ bool needs_tree_render; + + /* The next state to transition to. Passed to the function so that we can + * determine the next state as a result of a function call, like + * cfg_criteria_pop_state() does. */ + int next_state; }; struct CommandResult *parse_command(const char *input); diff --git a/include/config.h b/include/config.h index 76fee94d..fd9c7303 100644 --- a/include/config.h +++ b/include/config.h @@ -24,6 +24,8 @@ extern char *current_configpath; extern Config config; extern SLIST_HEAD(modes_head, Mode) modes; extern TAILQ_HEAD(barconfig_head, Barconfig) barconfigs; +/* defined in src/cfgparse.y */ +extern bool force_old_config_parser; /** * Used during the config file lexing/parsing to keep the state of the lexer diff --git a/src/cfgparse.y b/src/cfgparse.y index 8bc7990e..2a22aae4 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -13,6 +13,8 @@ #include "all.h" +bool force_old_config_parser = false; + static pid_t configerror_pid = -1; static Match current_match; @@ -625,15 +627,20 @@ void parse_file(const char *f) { } } - /* now lex/parse it */ - yy_scan_string(new); context = scalloc(sizeof(struct context)); context->filename = f; - if (yyparse() != 0) { - fprintf(stderr, "Could not parse configfile\n"); - exit(1); + if (force_old_config_parser) { + /* now lex/parse it */ + yy_scan_string(new); + if (yyparse() != 0) { + fprintf(stderr, "Could not parse configfile\n"); + exit(1); + } + } else { + struct ConfigResult *config_output = parse_config(new, context); + yajl_gen_free(config_output->json_gen); } check_for_duplicate_bindings(context); @@ -669,7 +676,8 @@ void parse_file(const char *f) { start_configerror_nagbar(f); } - yylex_destroy(); + if (force_old_config_parser) + yylex_destroy(); FREE(context->line_copy); free(context); FREE(font_pattern); @@ -1485,6 +1493,7 @@ new_float: border_style: TOK_NORMAL optional_border_width { + /* FIXME: the whole border_style thing actually screws up when new_float is used because it overwrites earlier values :-/ */ config.default_border_width = $2; $$ = BS_NORMAL; } diff --git a/src/main.c b/src/main.c index 3b29f7a8..6d0f80cb 100644 --- a/src/main.c +++ b/src/main.c @@ -269,6 +269,7 @@ int main(int argc, char *argv[]) { {"get_socketpath", no_argument, 0, 0}, {"fake_outputs", required_argument, 0, 0}, {"fake-outputs", required_argument, 0, 0}, + {"force-old-config-parser-v4.4-only", no_argument, 0, 0}, {0, 0, 0, 0} }; int option_index = 0, opt; @@ -372,6 +373,10 @@ int main(int argc, char *argv[]) { LOG("Initializing fake outputs: %s\n", optarg); fake_outputs = sstrdup(optarg); break; + } else if (strcmp(long_options[option_index].name, "force-old-config-parser-v4.4-only") == 0) { + LOG("FORCING OLD CONFIG PARSER!\n"); + force_old_config_parser = true; + break; } /* fall-through */ default: From 44c16063ed17fa6a4a3732e7b4a27d99ad426127 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 8 Oct 2012 13:27:42 +0200 Subject: [PATCH 082/146] add test.config_parser to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e50eb4fb..26c170f2 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ testcases/_Inline testcases/inc testcases/META.yml test.commands_parser +test.config_parser *.output *.tab.* *.yy.c From 040a441101f41975402d4b147b2d2f2fccc0169c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 8 Oct 2012 13:30:14 +0200 Subject: [PATCH 083/146] fix warning about printf() field length --- src/config_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config_parser.c b/src/config_parser.c index cb65b79b..889179a9 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -275,7 +275,7 @@ struct ConfigResult *parse_config(const char *input, struct context *context) { while (*dumpwalk != '\0') { char *next_nl = strchr(dumpwalk, '\n'); if (next_nl != NULL) { - DLOG("CONFIG(line %3d): %.*s\n", linecnt, (next_nl - dumpwalk), dumpwalk); + DLOG("CONFIG(line %3d): %.*s\n", linecnt, (int)(next_nl - dumpwalk), dumpwalk); dumpwalk = next_nl + 1; } else { DLOG("CONFIG(line %3d): %s\n", linecnt, dumpwalk); From ee36c8507ee4f0e37ff5bdf6244bfa0bb3fd38c1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 8 Oct 2012 13:40:44 +0200 Subject: [PATCH 084/146] bugfix: config-parser: bind is a synonym for bindcode --- parser-specs/config.spec | 52 ++++++++++++++++++++-------------------- src/config_directives.c | 2 ++ 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/parser-specs/config.spec b/parser-specs/config.spec index a5b3b6b6..6602dcae 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -15,32 +15,32 @@ state INITIAL: # We have an end token here for all the commands which just call some # function without using an explicit 'end' token. end -> - '#' -> IGNORE_LINE - 'set' -> IGNORE_LINE - bindtype = 'bindsym', 'bindcode' -> BINDING - 'bar' -> BARBRACE - 'font' -> FONT - 'mode' -> MODENAME - 'floating_minimum_size' -> FLOATING_MINIMUM_SIZE_WIDTH - 'floating_maximum_size' -> FLOATING_MAXIMUM_SIZE_WIDTH - 'floating_modifier' -> FLOATING_MODIFIER - 'default_orientation' -> DEFAULT_ORIENTATION - 'workspace_layout' -> WORKSPACE_LAYOUT - windowtype = 'new_window', 'new_float' -> NEW_WINDOW - 'hide_edge_borders' -> HIDE_EDGE_BORDERS - 'for_window' -> FOR_WINDOW - 'assign' -> ASSIGN - 'focus_follows_mouse' -> FOCUS_FOLLOWS_MOUSE - 'force_focus_wrapping' -> FORCE_FOCUS_WRAPPING - 'force_xinerama', 'force-xinerama' -> FORCE_XINERAMA - 'workspace_auto_back_and_forth' -> WORKSPACE_BACK_AND_FORTH - 'fake_outputs', 'fake-outputs' -> FAKE_OUTPUTS - 'force_display_urgency_hint' -> FORCE_DISPLAY_URGENCY_HINT - 'workspace' -> WORKSPACE - 'ipc_socket', 'ipc-socket' -> IPC_SOCKET - 'restart_state' -> RESTART_STATE - 'popup_during_fullscreen' -> POPUP_DURING_FULLSCREEN - exectype = 'exec_always', 'exec' -> EXEC + '#' -> IGNORE_LINE + 'set' -> IGNORE_LINE + bindtype = 'bindsym', 'bindcode', 'bind' -> BINDING + 'bar' -> BARBRACE + 'font' -> FONT + 'mode' -> MODENAME + 'floating_minimum_size' -> FLOATING_MINIMUM_SIZE_WIDTH + 'floating_maximum_size' -> FLOATING_MAXIMUM_SIZE_WIDTH + 'floating_modifier' -> FLOATING_MODIFIER + 'default_orientation' -> DEFAULT_ORIENTATION + 'workspace_layout' -> WORKSPACE_LAYOUT + windowtype = 'new_window', 'new_float' -> NEW_WINDOW + 'hide_edge_borders' -> HIDE_EDGE_BORDERS + 'for_window' -> FOR_WINDOW + 'assign' -> ASSIGN + 'focus_follows_mouse' -> FOCUS_FOLLOWS_MOUSE + 'force_focus_wrapping' -> FORCE_FOCUS_WRAPPING + 'force_xinerama', 'force-xinerama' -> FORCE_XINERAMA + 'workspace_auto_back_and_forth' -> WORKSPACE_BACK_AND_FORTH + 'fake_outputs', 'fake-outputs' -> FAKE_OUTPUTS + 'force_display_urgency_hint' -> FORCE_DISPLAY_URGENCY_HINT + 'workspace' -> WORKSPACE + 'ipc_socket', 'ipc-socket' -> IPC_SOCKET + 'restart_state' -> RESTART_STATE + 'popup_during_fullscreen' -> POPUP_DURING_FULLSCREEN + exectype = 'exec_always', 'exec' -> EXEC colorclass = 'client.background' -> COLOR_SINGLE colorclass = 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent' diff --git a/src/config_directives.c b/src/config_directives.c index 70e5792a..c885b256 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -179,6 +179,7 @@ CFGFUN(font, const char *font) { // TODO: refactor with mode_binding CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *command) { Binding *new_binding = scalloc(sizeof(Binding)); + DLOG("bindtype %s, modifiers %s, key %s, release %s\n", bindtype, modifiers, key, release); new_binding->release = (release != NULL ? B_UPON_KEYRELEASE : B_UPON_KEYPRESS); if (strcmp(bindtype, "bindsym") == 0) { new_binding->symbol = sstrdup(key); @@ -200,6 +201,7 @@ static struct bindings_head *current_bindings; CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *command) { Binding *new_binding = scalloc(sizeof(Binding)); + DLOG("bindtype %s, modifiers %s, key %s, release %s\n", bindtype, modifiers, key, release); new_binding->release = (release != NULL ? B_UPON_KEYRELEASE : B_UPON_KEYPRESS); if (strcmp(bindtype, "bindsym") == 0) { new_binding->symbol = sstrdup(key); From 94d95f2b8c80a1af03f632187a8a7c38abcd1986 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 8 Oct 2012 16:28:32 +0200 Subject: [PATCH 085/146] add missing include/config_parser.h (Thanks slowpoke) --- include/config_parser.h | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 include/config_parser.h diff --git a/include/config_parser.h b/include/config_parser.h new file mode 100644 index 00000000..0daf8118 --- /dev/null +++ b/include/config_parser.h @@ -0,0 +1,32 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) + * + * config_parser.h: config parser-related definitions + * + */ +#ifndef I3_CONFIG_PARSER_H +#define I3_CONFIG_PARSER_H + +#include + +/* + * The result of a parse_config call. Currently unused, but the JSON output + * will be useful in the future when we implement a config parsing IPC command. + * + */ +struct ConfigResult { + /* The JSON generator to append a reply to. */ + yajl_gen json_gen; + + /* The next state to transition to. Passed to the function so that we can + * determine the next state as a result of a function call, like + * cfg_criteria_pop_state() does. */ + int next_state; +}; + +struct ConfigResult *parse_config(const char *input, struct context *context); + +#endif From 80492c8304757d8d5375b8cf1c7fef6db6f32df2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 9 Oct 2012 14:05:37 +0200 Subject: [PATCH 086/146] error out instead of accepting invalid key bindings (Thanks SardemFF7) --- src/config_directives.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/config_directives.c b/src/config_directives.c index c885b256..7847376d 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -186,6 +186,10 @@ CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, co } else { // TODO: strtol with proper error handling new_binding->keycode = atoi(key); + if (new_binding->keycode == 0) { + ELOG("Could not parse \"%s\" as a keycode, ignoring this binding.\n", key); + return; + } } new_binding->mods = modifiers_from_str(modifiers); new_binding->command = sstrdup(command); @@ -208,6 +212,10 @@ CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *ke } else { // TODO: strtol with proper error handling new_binding->keycode = atoi(key); + if (new_binding->keycode == 0) { + ELOG("Could not parse \"%s\" as a keycode, ignoring this binding.\n", key); + return; + } } new_binding->mods = modifiers_from_str(modifiers); new_binding->command = sstrdup(command); From 00ac2c4c9c51dc9012016fd839b9f2d8fcbad971 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 9 Oct 2012 14:05:49 +0200 Subject: [PATCH 087/146] accept ctrl as synonym of control (Thanks SardemFF7) --- parser-specs/config.spec | 8 ++++---- src/config_directives.c | 6 ++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 6602dcae..7b9e9cdc 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -79,7 +79,7 @@ state FLOATING_MAXIMUM_SIZE_HEIGHT: # floating_modifier state FLOATING_MODIFIER: - modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control' + modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl' -> '+' -> @@ -269,7 +269,7 @@ state FONT: # bindsym/bindcode state BINDING: - modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Mode_switch' + modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch' -> '+' -> @@ -309,7 +309,7 @@ state MODE_IGNORE_LINE: -> MODE state MODE_BINDING: - modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Mode_switch' + modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch' -> '+' -> @@ -371,7 +371,7 @@ state BAR_MODE: -> call cfg_bar_mode($mode); BAR state BAR_MODIFIER: - modifier = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Control', 'Shift' + modifier = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Control', 'Ctrl', 'Shift' -> call cfg_bar_modifier($modifier); BAR state BAR_POSITION: diff --git a/src/config_directives.c b/src/config_directives.c index 7847376d..80439ca4 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -156,7 +156,8 @@ static uint32_t modifiers_from_str(const char *str) { result |= BIND_MOD4; if (strstr(str, "Mod5") != NULL) result |= BIND_MOD5; - if (strstr(str, "Control") != NULL) + if (strstr(str, "Control") != NULL || + strstr(str, "Ctrl") != NULL) result |= BIND_CONTROL; if (strstr(str, "Shift") != NULL) result |= BIND_SHIFT; @@ -469,7 +470,8 @@ CFGFUN(bar_modifier, const char *modifier) { current_bar.modifier = M_MOD4; else if (strcmp(modifier, "Mod5") == 0) current_bar.modifier = M_MOD5; - else if (strcmp(modifier, "Control") == 0) + else if (strcmp(modifier, "Control") == 0 || + strcmp(modifier, "Ctrl") == 0) current_bar.modifier = M_CONTROL; else if (strcmp(modifier, "Shift") == 0) current_bar.modifier = M_SHIFT; From 3528d991757df223b7310e7891205122967da794 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 9 Oct 2012 14:08:14 +0200 Subject: [PATCH 088/146] Fix warning: exclude NULL parameters from format string (Thanks knopwob) --- generate-command-parser.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generate-command-parser.pl b/generate-command-parser.pl index f69f715e..66e44b6c 100755 --- a/generate-command-parser.pl +++ b/generate-command-parser.pl @@ -158,7 +158,6 @@ for my $state (@keys) { $fmt =~ s/$_/%d/g for @keys; $fmt =~ s/\$([a-z_]+)/%s/g; $fmt =~ s/\&([a-z_]+)/%ld/g; - $fmt =~ s/NULL/%s/g; $fmt =~ s/"([a-z0-9_]+)"/%s/g; $fmt =~ s/(?:-?|\b)[0-9]+\b/%d/g; @@ -177,6 +176,7 @@ for my $state (@keys) { $cmd =~ s/[^(]+\(//; $cmd =~ s/\)$//; $cmd = ", $cmd" if length($cmd) > 0; + $cmd =~ s/, NULL//g; say $callfh qq| fprintf(stderr, "$fmt\\n"$cmd);|; # The cfg_criteria functions have side-effects which are important for # testing. They are implemented as stubs in the test parser code. From a06bf27c24b059fecf55f787f73fab63ed8c2209 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 9 Oct 2012 14:11:35 +0200 Subject: [PATCH 089/146] tests: fix error messages, add 'bind' as synonym to mode blocks --- parser-specs/config.spec | 2 +- testcases/t/201-config-parser.t | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 7b9e9cdc..a69cfe1e 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -298,7 +298,7 @@ state MODE: end -> '#' -> MODE_IGNORE_LINE 'set' -> MODE_IGNORE_LINE - bindtype = 'bindsym', 'bindcode' + bindtype = 'bindsym', 'bindcode', 'bind' -> MODE_BINDING '}' -> INITIAL diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t index 711b689d..a850b7e8 100644 --- a/testcases/t/201-config-parser.t +++ b/testcases/t/201-config-parser.t @@ -379,7 +379,7 @@ EOT $expected = <<'EOT'; cfg_font(foobar) -ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'bindsym', 'bindcode', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent' +ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent' ERROR: CONFIG: (in file ) ERROR: CONFIG: Line 3: font foobar ERROR: CONFIG: Line 4: @@ -402,7 +402,7 @@ unknown qux EOT $expected = <<'EOT'; -ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'bindsym', 'bindcode', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent' +ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent' ERROR: CONFIG: (in file ) ERROR: CONFIG: Line 1: unknown qux ERROR: CONFIG: ^^^^^^^^^^^ @@ -422,7 +422,7 @@ unknown qux EOT $expected = <<'EOT'; -ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'bindsym', 'bindcode', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent' +ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent' ERROR: CONFIG: (in file ) ERROR: CONFIG: Line 1: # context before ERROR: CONFIG: Line 2: unknown qux @@ -443,7 +443,7 @@ unknown qux EOT $expected = <<'EOT'; -ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'bindsym', 'bindcode', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent' +ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent' ERROR: CONFIG: (in file ) ERROR: CONFIG: Line 1: unknown qux ERROR: CONFIG: ^^^^^^^^^^^ @@ -465,7 +465,7 @@ unknown qux EOT $expected = <<'EOT'; -ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'bindsym', 'bindcode', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent' +ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent' ERROR: CONFIG: (in file ) ERROR: CONFIG: Line 1: unknown qux ERROR: CONFIG: ^^^^^^^^^^^ @@ -491,7 +491,7 @@ EOT $expected = <<'EOT'; cfg_enter_mode(yo) cfg_mode_binding(bindsym, (null), x, (null), resize shrink left) -ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'bindsym', 'bindcode', '}' +ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'bindsym', 'bindcode', 'bind', '}' ERROR: CONFIG: (in file ) ERROR: CONFIG: Line 1: mode "yo" { ERROR: CONFIG: Line 2: bindsym x resize shrink left From 7a280f56916038722e471168eb593b02cb4d3441 Mon Sep 17 00:00:00 2001 From: Deiz Date: Tue, 9 Oct 2012 13:26:33 -0400 Subject: [PATCH 090/146] Grab keys with all permutations of lock and numlock This should prevent all cases of caps lock (or shift lock, on some keyboards) from interfering with i3 key bindings. --- src/config.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config.c b/src/config.c index 9e47d74b..ce9adca5 100644 --- a/src/config.c +++ b/src/config.c @@ -46,6 +46,7 @@ static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint } GRAB_KEY(mods); GRAB_KEY(mods | xcb_numlock_mask); + GRAB_KEY(mods | XCB_MOD_MASK_LOCK); GRAB_KEY(mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK); } From e07803999f04d38bcf3beb6ed4ad9377a2e2e6cb Mon Sep 17 00:00:00 2001 From: Deiz Date: Sat, 6 Oct 2012 16:41:04 -0400 Subject: [PATCH 091/146] Fix fullscreen focus bug and corresponding test flaw As the workspace might be reached via recursion (e.g. moving from the edge of a fullscreen split container), it's necessary to check for a fullscreen container whenever a workspace is reached. --- src/commands.c | 16 ---------------- src/tree.c | 5 +++++ testcases/t/156-fullscreen-focus.t | 17 ++++++++++++++++- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/commands.c b/src/commands.c index e323a6ec..2b429cb3 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1297,14 +1297,6 @@ void cmd_exec(I3_CMD, char *nosn, char *command) { * */ void cmd_focus_direction(I3_CMD, char *direction) { - if (focused && - focused->type != CT_WORKSPACE && - focused->fullscreen_mode != CF_NONE) { - LOG("Cannot change focus while in fullscreen mode.\n"); - ysuccess(false); - return; - } - DLOG("direction = *%s*\n", direction); if (strcmp(direction, "left") == 0) @@ -1331,14 +1323,6 @@ void cmd_focus_direction(I3_CMD, char *direction) { * */ void cmd_focus_window_mode(I3_CMD, char *window_mode) { - if (focused && - focused->type != CT_WORKSPACE && - focused->fullscreen_mode != CF_NONE) { - LOG("Cannot change focus while in fullscreen mode.\n"); - ysuccess(false); - return; - } - DLOG("window_mode = %s\n", window_mode); Con *ws = con_get_workspace(focused); diff --git a/src/tree.c b/src/tree.c index 157b6671..f233fd69 100644 --- a/src/tree.c +++ b/src/tree.c @@ -466,6 +466,11 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) /* Stop recursing at workspaces after attempting to switch to next * workspace if possible. */ if (con->type == CT_WORKSPACE) { + if (con_get_fullscreen_con(con, CF_GLOBAL) || + con_get_fullscreen_con(con, CF_OUTPUT)) { + DLOG("Cannot change workspace while in fullscreen mode.\n"); + return false; + } Output *current_output = get_output_containing(con->rect.x, con->rect.y); Output *next_output; diff --git a/testcases/t/156-fullscreen-focus.t b/testcases/t/156-fullscreen-focus.t index 29a410d2..23be014a 100644 --- a/testcases/t/156-fullscreen-focus.t +++ b/testcases/t/156-fullscreen-focus.t @@ -18,7 +18,20 @@ # the time of launching the new one. Also make sure that focusing containers # in other workspaces work even when there is a fullscreen container. # -use i3test; +use i3test i3_autostart => 0; + +# Screen setup looks like this: +# +----+----+ +# | S1 | S2 | +# +----+----+ +my $config = < Date: Thu, 4 Oct 2012 14:10:10 -0400 Subject: [PATCH 092/146] Allow workspace contents to be moved if there are only floating children --- include/con.h | 6 ++++++ src/commands.c | 6 +++--- src/con.c | 24 ++++++++++++++++++------ testcases/t/132-move-workspace.t | 19 +++++++++++++++++++ 4 files changed, 46 insertions(+), 9 deletions(-) diff --git a/include/con.h b/include/con.h index 8b9ae9c7..5c104ebd 100644 --- a/include/con.h +++ b/include/con.h @@ -39,6 +39,12 @@ bool con_is_leaf(Con *con); */ bool con_is_split(Con *con); +/** + * Returns true if this node has regular or floating children. + * + */ +bool con_has_children(Con *con); + /** * Returns true if this node accepts a window (if the node swallows windows, * it might already have swallowed enough and cannot hold any more). diff --git a/src/commands.c b/src/commands.c index 2b429cb3..dfb4e236 100644 --- a/src/commands.c +++ b/src/commands.c @@ -402,7 +402,7 @@ void cmd_move_con_to_workspace(I3_CMD, char *which) { * when criteria wasn't specified and we don't have any window focused. */ if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) || (match_is_empty(current_match) && focused->type == CT_WORKSPACE && - con_is_leaf(focused))) { + !con_has_children(focused))) { ysuccess(false); return; } @@ -491,7 +491,7 @@ void cmd_move_con_to_workspace_name(I3_CMD, char *name) { return; } else if (match_is_empty(current_match) && focused->type == CT_WORKSPACE && - con_is_leaf(focused)) { + !con_has_children(focused)) { ysuccess(false); return; } @@ -526,7 +526,7 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) { * when criteria wasn't specified and we don't have any window focused. */ if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) || (match_is_empty(current_match) && focused->type == CT_WORKSPACE && - con_is_leaf(focused))) { + !con_has_children(focused))) { ysuccess(false); return; } diff --git a/src/con.c b/src/con.c index 493707d6..609a0d36 100644 --- a/src/con.c +++ b/src/con.c @@ -232,6 +232,14 @@ bool con_is_leaf(Con *con) { return TAILQ_EMPTY(&(con->nodes_head)); } +/** + * Returns true if this node has regular or floating children. + * + */ +bool con_has_children(Con *con) { + return (!con_is_leaf(con) || !TAILQ_EMPTY(&(con->floating_head))); +} + /* * Returns true if a container should be considered split. * @@ -643,18 +651,22 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool } if (con->type == CT_WORKSPACE) { - con = workspace_encapsulate(con); - if (con == NULL) { - ELOG("Workspace failed to move its contents into a container!\n"); - return; - } - /* Re-parent all of the old workspace's floating windows. */ Con *child; while (!TAILQ_EMPTY(&(source_ws->floating_head))) { child = TAILQ_FIRST(&(source_ws->floating_head)); con_move_to_workspace(child, workspace, true, true); } + + /* If there are no non-floating children, ignore the workspace. */ + if (con_is_leaf(con)) + return; + + con = workspace_encapsulate(con); + if (con == NULL) { + ELOG("Workspace failed to move its contents into a container!\n"); + return; + } } /* Save the current workspace. So we can call workspace_show() by the end diff --git a/testcases/t/132-move-workspace.t b/testcases/t/132-move-workspace.t index 730041a6..d0991039 100644 --- a/testcases/t/132-move-workspace.t +++ b/testcases/t/132-move-workspace.t @@ -318,4 +318,23 @@ $ws = get_ws($tmp2); is_num_children($tmp2, 1, 'one container on second workspace'); is(@{$ws->{floating_nodes}}, 2, 'two floating nodes on second workspace'); +################################################################### +# same as the above, but with only floating children +################################################################### +$tmp2 = get_unused_workspace(); +$tmp = fresh_workspace(); +cmd 'open'; +cmd 'floating toggle'; + +$ws = get_ws($tmp); +is_num_children($tmp, 0, 'no regular nodes on first workspace'); +is(@{$ws->{floating_nodes}}, 1, 'one floating node on first workspace'); + +cmd 'focus parent'; +cmd "move workspace $tmp2"; + +$ws = get_ws($tmp2); +is_num_children($tmp2, 0, 'no regular nodes on second workspace'); +is(@{$ws->{floating_nodes}}, 1, 'one floating node on second workspace'); + done_testing; From 47de7375dd50f67c5b6ccc52b1db8b542123a830 Mon Sep 17 00:00:00 2001 From: Deiz Date: Mon, 8 Oct 2012 12:28:08 -0400 Subject: [PATCH 093/146] Allow 'focus $dir' to move out of non-global fullscreen containers --- src/tree.c | 19 ++++++++++++++++--- testcases/t/156-fullscreen-focus.t | 25 ++++++++++++++++++------- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/tree.c b/src/tree.c index f233fd69..8681a3b9 100644 --- a/src/tree.c +++ b/src/tree.c @@ -463,12 +463,18 @@ void tree_render(void) { * */ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) { + /* When dealing with fullscreen containers, it's necessary to go up to the + * workspace level, because 'focus $dir' will start at the con's real + * position in the tree, and it may not be possible to get to the edge + * normally due to fullscreen focusing restrictions. */ + if (con->fullscreen_mode == CF_OUTPUT && con->type != CT_WORKSPACE) + con = con_get_workspace(con); + /* Stop recursing at workspaces after attempting to switch to next * workspace if possible. */ if (con->type == CT_WORKSPACE) { - if (con_get_fullscreen_con(con, CF_GLOBAL) || - con_get_fullscreen_con(con, CF_OUTPUT)) { - DLOG("Cannot change workspace while in fullscreen mode.\n"); + if (con_get_fullscreen_con(con, CF_GLOBAL)) { + DLOG("Cannot change workspace while in global fullscreen mode.\n"); return false; } Output *current_output = get_output_containing(con->rect.x, con->rect.y); @@ -505,6 +511,13 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) return false; workspace_show(workspace); + + /* If a workspace has an active fullscreen container, one of its + * children should always be focused. The above workspace_show() + * should be adequate for that, so return. */ + if (con_get_fullscreen_con(workspace, CF_OUTPUT)) + return true; + Con *focus = con_descend_direction(workspace, direction); if (focus) { con_focus(focus); diff --git a/testcases/t/156-fullscreen-focus.t b/testcases/t/156-fullscreen-focus.t index 23be014a..170c641d 100644 --- a/testcases/t/156-fullscreen-focus.t +++ b/testcases/t/156-fullscreen-focus.t @@ -125,10 +125,18 @@ is($nodes->[0]->{focused}, 1, 'fullscreen window focused'); cmd 'fullscreen'; +# Focus screen 1 +$x->root->warp_pointer(1025, 0); +sync_with_i3; + $tmp = fresh_workspace; cmd "workspace $tmp"; my $diff_ws = open_window; +# Focus screen 0 +$x->root->warp_pointer(0, 0); +sync_with_i3; + $tmp2 = fresh_workspace; cmd "workspace $tmp2"; cmd 'split h'; @@ -207,21 +215,24 @@ is($x->input_focus, $right1->id, 'allowed focus up'); cmd 'focus down'; is($x->input_focus, $right2->id, 'allowed focus down'); -cmd 'focus left'; -is($x->input_focus, $right2->id, 'prevented focus left'); - -cmd 'focus right'; -is($x->input_focus, $right2->id, 'prevented focus right'); - cmd 'focus down'; is($x->input_focus, $right1->id, 'allowed focus wrap (down)'); cmd 'focus up'; is($x->input_focus, $right2->id, 'allowed focus wrap (up)'); -cmd '[id="' . $diff_ws->id . '"] focus'; +cmd 'focus left'; +is($x->input_focus, $right2->id, 'focus left wrapped (no-op)'); + +cmd 'focus right'; is($x->input_focus, $diff_ws->id, 'allowed focus change to different ws'); +cmd 'focus left'; +is($x->input_focus, $right2->id, 'focused back into fullscreen container'); + +cmd '[id="' . $diff_ws->id . '"] focus'; +is($x->input_focus, $diff_ws->id, 'allowed focus change to different ws by id'); + ################################################################################ # More testing of the interaction between wrapping and the fullscreen focus # restrictions. From 39ba9559194c95997b93c93e30314962f914b01e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 9 Oct 2012 22:06:36 +0200 Subject: [PATCH 094/146] Bugfix: Actually set border width in config_directives.c (Thanks strcat) --- src/config_directives.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/config_directives.c b/src/config_directives.c index 80439ca4..8b636c02 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -318,6 +318,8 @@ CFGFUN(new_window, const char *windowtype, const char *border, const long width) } else { config.default_floating_border = border_style; } + + config.default_border_width = border_width; } CFGFUN(hide_edge_borders, const char *borders) { From e964e7b6cdbcc19e5b7d69044583f9abc0c6c451 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 10 Oct 2012 08:18:15 +0200 Subject: [PATCH 095/146] config parser: make newlines okay before opening braces (Thanks aksr) --- parser-specs/config.spec | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/parser-specs/config.spec b/parser-specs/config.spec index a69cfe1e..1c11bf9d 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -291,6 +291,8 @@ state MODENAME: -> call cfg_enter_mode($modename); MODEBRACE state MODEBRACE: + end + -> '{' -> MODE @@ -327,6 +329,8 @@ state MODE_BINDCOMMAND: ################################################################################ state BARBRACE: + end + -> '{' -> BAR @@ -399,6 +403,8 @@ state BAR_VERBOSE: -> call cfg_bar_verbose($value); BAR state BAR_COLORS_BRACE: + end + -> '{' -> BAR_COLORS From 34a5bbb7e9779a7576fdd1e41dd677248c23749d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 14 Oct 2012 20:56:13 +0200 Subject: [PATCH 096/146] exit with a proper error message when there are no outputs available (Thanks flo) fixes #842 --- src/randr.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/randr.c b/src/randr.c index a73a94c9..267d6e41 100644 --- a/src/randr.c +++ b/src/randr.c @@ -69,7 +69,7 @@ Output *get_first_output(void) { if (output->active) return output; - return NULL; + die("No usable outputs available.\n"); } /* @@ -647,8 +647,7 @@ void randr_query_outputs(void) { output->active = false; DLOG("Output %s disabled, re-assigning workspaces/docks\n", output->name); - if ((first = get_first_output()) == NULL) - die("No usable outputs available\n"); + first = get_first_output(); /* TODO: refactor the following code into a nice function. maybe * use an on_destroy callback which is implement differently for @@ -736,6 +735,9 @@ void randr_query_outputs(void) { disable_randr(conn); } + /* Verifies that there is at least one active output as a side-effect. */ + get_first_output(); + /* Just go through each active output and assign one workspace */ TAILQ_FOREACH(output, &outputs, outputs) { if (!output->active) From b9885ff21e499f1fc963267d24632ce00d9ffeb5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 14 Oct 2012 21:05:44 +0200 Subject: [PATCH 097/146] =?UTF-8?q?bugfix:=20don=E2=80=99t=20send=20worksp?= =?UTF-8?q?ace=20command=20when=20at=20beginning/end=20of=20workspaces=20(?= =?UTF-8?q?Thanks=20whitequark)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes #843 --- i3bar/src/xcb.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 405eefdb..2c0d2a6a 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -302,16 +302,24 @@ void handle_button(xcb_button_press_event_t *event) { } break; case 4: - /* Mouse wheel down. We select the next ws */ - if (cur_ws != TAILQ_FIRST(walk->workspaces)) { - cur_ws = TAILQ_PREV(cur_ws, ws_head, tailq); - } + /* Mouse wheel up. We select the previous ws, if any. + * If there is no more workspace, don’t even send the workspace + * command, otherwise (with workspace auto_back_and_forth) we’d end + * up on the wrong workspace. */ + if (cur_ws == TAILQ_FIRST(walk->workspaces)) + return; + + cur_ws = TAILQ_PREV(cur_ws, ws_head, tailq); break; case 5: - /* Mouse wheel up. We select the previos ws */ - if (cur_ws != TAILQ_LAST(walk->workspaces, ws_head)) { - cur_ws = TAILQ_NEXT(cur_ws, tailq); - } + /* Mouse wheel down. We select the next ws, if any. + * If there is no more workspace, don’t even send the workspace + * command, otherwise (with workspace auto_back_and_forth) we’d end + * up on the wrong workspace. */ + if (cur_ws == TAILQ_LAST(walk->workspaces, ws_head)) + return; + + cur_ws = TAILQ_NEXT(cur_ws, tailq); break; } From c406b4c2fe0f09f70e2b943b73bd71ca19f1a859 Mon Sep 17 00:00:00 2001 From: Deiz Date: Tue, 16 Oct 2012 11:50:39 -0400 Subject: [PATCH 098/146] Skip floating cons in focus (child|parent) and stop them from being split Focusing child from a workspace should now skip over the floating con and go directly to its child. Focusing parent from that grandchild should leave the workspace focused again. --- src/tree.c | 26 ++++++++++++++++++++++++-- testcases/t/135-floating-focus.t | 19 +++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/tree.c b/src/tree.c index 8681a3b9..7a5fb9f0 100644 --- a/src/tree.c +++ b/src/tree.c @@ -356,6 +356,10 @@ void tree_split(Con *con, orientation_t orientation) { con->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV; return; } + else if (con->type == CT_FLOATING_CON) { + DLOG("Floating containers can't be split.\n"); + return; + } Con *parent = con->parent; @@ -396,9 +400,15 @@ void tree_split(Con *con, orientation_t orientation) { * */ bool level_up(void) { + /* Skip over floating containers and go directly to the grandparent + * (which should always be a workspace) */ + if (focused->parent->type == CT_FLOATING_CON) { + con_focus(focused->parent->parent); + return true; + } + /* We can focus up to the workspace, but not any higher in the tree */ if ((focused->parent->type != CT_CON && - focused->parent->type != CT_FLOATING_CON && focused->parent->type != CT_WORKSPACE) || focused->type == CT_WORKSPACE) { ELOG("'focus parent': Focus is already on the workspace, cannot go higher than that.\n"); @@ -416,9 +426,21 @@ bool level_down(void) { /* Go down the focus stack of the current node */ Con *next = TAILQ_FIRST(&(focused->focus_head)); if (next == TAILQ_END(&(focused->focus_head))) { - printf("cannot go down\n"); + DLOG("cannot go down\n"); return false; } + else if (next->type == CT_FLOATING_CON) { + /* Floating cons shouldn't be directly focused; try immediately + * going to the grandchild of the focused con. */ + Con *child = TAILQ_FIRST(&(next->focus_head)); + if (child == TAILQ_END(&(next->focus_head))) { + DLOG("cannot go down\n"); + return false; + } + else + next = TAILQ_FIRST(&(next->focus_head)); + } + con_focus(next); return true; } diff --git a/testcases/t/135-floating-focus.t b/testcases/t/135-floating-focus.t index f38a1472..f23dabae 100644 --- a/testcases/t/135-floating-focus.t +++ b/testcases/t/135-floating-focus.t @@ -197,4 +197,23 @@ cmd 'focus right'; is($x->input_focus, $second->id, 'focus on second container'); +############################################################################# +# 7: verify that focusing the parent of a window inside a floating con goes +# up to the grandparent (workspace) and that focusing child from the ws +# goes back down to the child of the floating con +############################################################################# + +$tmp = fresh_workspace; + +my $tiled = open_window; +my $floating = open_floating_window; +is($x->input_focus, $floating->id, 'floating window focused'); + +cmd 'focus parent'; + +is(get_ws($tmp)->{focused}, 1, 'workspace is focused'); +cmd 'focus child'; + +is($x->input_focus, $floating->id, 'floating window focused'); + done_testing; From fdfbc53c0b308f49b26fab85a8a0bfeaf7e34fb5 Mon Sep 17 00:00:00 2001 From: Deiz Date: Sun, 14 Oct 2012 14:24:37 -0400 Subject: [PATCH 099/146] Focus windows when middle-clicking (X paste) As with most click-based focusing, this only has an effect when focus_follows_mouse is disabled. --- src/manage.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/manage.c b/src/manage.c index d3f5ca8d..b07a9c8a 100644 --- a/src/manage.c +++ b/src/manage.c @@ -172,9 +172,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki xcb_grab_button(conn, false, window, XCB_EVENT_MASK_BUTTON_PRESS, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE, - 3 /* right mouse button */, + 2 /* middle mouse button */, XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */); + xcb_grab_button(conn, false, window, XCB_EVENT_MASK_BUTTON_PRESS, + XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE, + 3 /* right mouse button */, + XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */); /* update as much information as possible so far (some replies may be NULL) */ window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL), true); From aefcb0b66882c9a9cba66db549c7e80836d4d6f1 Mon Sep 17 00:00:00 2001 From: Deiz Date: Sun, 14 Oct 2012 20:40:34 -0400 Subject: [PATCH 100/146] Skip floating windows in the focus stack when moving through the tree Includes a test for the new behaviour --- src/con.c | 15 ++- testcases/t/510-focus-across-outputs.t | 146 +++++++++++++++++++++++++ 2 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 testcases/t/510-focus-across-outputs.t diff --git a/src/con.c b/src/con.c index 609a0d36..ad5025a9 100644 --- a/src/con.c +++ b/src/con.c @@ -1005,6 +1005,7 @@ Con *con_descend_tiling_focused(Con *con) { */ Con *con_descend_direction(Con *con, direction_t direction) { Con *most = NULL; + Con *current; int orientation = con_orientation(con); DLOG("con_descend_direction(%p, orientation %d, direction %d)\n", con, orientation, direction); if (direction == D_LEFT || direction == D_RIGHT) { @@ -1018,7 +1019,12 @@ Con *con_descend_direction(Con *con, direction_t direction) { /* Wrong orientation. We use the last focused con. Within that con, * we recurse to chose the left/right con or at least the last * focused one. */ - most = TAILQ_FIRST(&(con->focus_head)); + TAILQ_FOREACH(current, &(con->focus_head), focused) { + if (current->type != CT_FLOATING_CON) { + most = current; + break; + } + } } else { /* If the con has no orientation set, it’s not a split container * but a container with a client window, so stop recursing */ @@ -1037,7 +1043,12 @@ Con *con_descend_direction(Con *con, direction_t direction) { /* Wrong orientation. We use the last focused con. Within that con, * we recurse to chose the top/bottom con or at least the last * focused one. */ - most = TAILQ_FIRST(&(con->focus_head)); + TAILQ_FOREACH(current, &(con->focus_head), focused) { + if (current->type != CT_FLOATING_CON) { + most = current; + break; + } + } } else { /* If the con has no orientation set, it’s not a split container * but a container with a client window, so stop recursing */ diff --git a/testcases/t/510-focus-across-outputs.t b/testcases/t/510-focus-across-outputs.t new file mode 100644 index 00000000..7f68a2d5 --- /dev/null +++ b/testcases/t/510-focus-across-outputs.t @@ -0,0 +1,146 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Tests that switching workspaces via 'focus $dir' never leaves a floating +# window focused. +# +use i3test i3_autostart => 0; + +# Ensure the pointer is at (0, 0) so that we really start on the first +# (the left) workspace. +$x->root->warp_pointer(0, 0); + +my $config = <root->warp_pointer(1025, 0); +my $s1_ws = fresh_workspace; +sync_with_i3; + +my $fourth = open_window; + +# Focus screen 2 +$x->root->warp_pointer(0, 769); +my $s2_ws = fresh_workspace; +sync_with_i3; + +my $fifth = open_window; + +# Focus screen 3 +$x->root->warp_pointer(1025, 769); +my $s3_ws = fresh_workspace; +sync_with_i3; + +my $sixth = open_window; +my $seventh = open_window; +my $eighth = open_window; +cmd 'floating toggle'; + +# Layout should look like this (windows 3 and 8 are floating): +# S0 S1 +# ┌───┬───┬───────┐ +# │ ┌─┴─┐ │ │ +# │1│ 3 │2│ 4 │ +# │ └─┬─┘ │ │ +# ├───┴───┼───┬───┤ +# │ │ ┌─┴─┐ │ +# │ 5 │6│ 8 │7│ +# │ │ └─┬─┘ │ +# └───────┴───┴───┘ +# S2 S3 +# +################################################################### +# Test that focus (left|down|right|up) doesn't focus floating +# windows when moving into horizontally-split workspaces. +################################################################### + +sub reset_focus { + my $ws = shift; + cmd "workspace $ws; focus floating"; +} + +cmd "workspace $s1_ws"; +cmd 'focus left'; +is($x->input_focus, $second->id, 'second window focused'); +reset_focus $s0_ws; + +cmd "workspace $s1_ws"; +cmd 'focus down'; +is($x->input_focus, $seventh->id, 'seventh window focused'); +reset_focus $s3_ws; + +cmd "workspace $s2_ws"; +cmd 'focus right'; +is($x->input_focus, $sixth->id, 'sixth window focused'); +reset_focus $s3_ws; + +cmd "workspace $s2_ws"; +cmd 'focus up'; +is($x->input_focus, $second->id, 'second window focused'); +reset_focus $s0_ws; + +################################################################### +# Put the workspaces on screens 0 and 3 into vertical split mode +# and test focus (left|down|right|up) again. +################################################################### + +cmd "workspace $s0_ws"; +is($x->input_focus, $third->id, 'third window focused'); +cmd 'focus parent'; +cmd 'focus parent'; +cmd 'split v'; +reset_focus $s0_ws; + +cmd "workspace $s3_ws"; +is($x->input_focus, $eighth->id, 'eighth window focused'); +cmd 'focus parent'; +cmd 'focus parent'; +cmd 'split v'; +reset_focus $s3_ws; + +cmd "workspace $s1_ws"; +cmd 'focus left'; +is($x->input_focus, $second->id, 'second window focused'); +reset_focus $s0_ws; + +cmd "workspace $s1_ws"; +cmd 'focus down'; +is($x->input_focus, $sixth->id, 'sixth window focused'); +reset_focus $s3_ws; + +cmd "workspace $s2_ws"; +cmd 'focus right'; +is($x->input_focus, $sixth->id, 'sixth window focused'); + +cmd "workspace $s2_ws"; +cmd 'focus up'; +is($x->input_focus, $second->id, 'second window focused'); + +exit_gracefully($pid); + +done_testing; From 19fef3b4b8426791759aa171057dd67e30de37ad Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 16 Oct 2012 23:02:04 +0200 Subject: [PATCH 101/146] docs/userguide: use $mod consistently (Thanks Conley) --- docs/userguide | 164 ++++++++++++++++++++++++------------------------- 1 file changed, 82 insertions(+), 82 deletions(-) diff --git a/docs/userguide b/docs/userguide index cde7bfc4..e76e4e07 100644 --- a/docs/userguide +++ b/docs/userguide @@ -12,28 +12,28 @@ contact us on IRC (preferred) or post your question(s) on the mailing list. 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 mod (alt):* +*Keys to use with $mod (alt):* -image:keyboard-layer1.png["Keys to use with mod (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+mod:* +*Keys to use with Shift+$mod:* -image:keyboard-layer2.png["Keys to use with Shift+mod",width=600,link="keyboard-layer2.png"] +image:keyboard-layer2.png["Keys to use with Shift+$mod",width=600,link="keyboard-layer2.png"] 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 +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 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 +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] @@ -48,9 +48,9 @@ image:two_terminals.png[Two terminals] 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. +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. At the moment, your workspace is split (it contains two terminals) in a specific direction (horizontal by default). Every window can be split @@ -61,8 +61,8 @@ windows. TODO: picture of the tree -To split a window vertically, press +mod+v+ before you create the new window. -To split it horizontally, press +mod+h+. +To split a window vertically, press +$mod+v+ before you create the new window. +To split it horizontally, press +$mod+h+. === Changing the container layout @@ -80,15 +80,15 @@ 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 +mod+e+ for splith/splitv (it toggles), +mod+s+ for -stacking and +mod+w+ for tabbed. +To switch modes, press +$mod+e+ for splith/splitv (it toggles), +$mod+s+ for +stacking and +$mod+w+ for tabbed. image:modes.png[Container modes] === Toggling fullscreen mode for a window To display a window in fullscreen mode or to go out of fullscreen mode again, -press +mod+f+. +press +$mod+f+. There is also a global fullscreen mode in i3 in which the client will span all available outputs (the command is +fullscreen global+). @@ -96,7 +96,7 @@ available outputs (the command is +fullscreen global+). === Opening other applications Aside from opening applications from a terminal, you can also use the handy -+dmenu+ which is opened by pressing +mod+d+ by default. Just type the name ++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. @@ -108,7 +108,7 @@ create a keybinding for starting the application directly. See the section 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 +mod+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 @@ -118,7 +118,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 +mod+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 @@ -132,7 +132,7 @@ focus to that screen. === Moving windows to workspaces -To move a window to another workspace, simply press +mod+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. @@ -148,11 +148,11 @@ 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 +mod+Shift+r+. +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 +mod+Shift+e+. +To cleanly exit i3 without killing your X server, you can use +$mod+Shift+e+. === Floating @@ -162,7 +162,7 @@ paradigm but can be useful for some corner cases like "Save as" dialog windows, or toolbar windows (GIMP or similar). Those windows usually set the appropriate hint and are opened in floating mode by default. -You can toggle floating mode for a window by pressing +mod+Shift+Space+. By +You can toggle 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 <>. @@ -202,7 +202,7 @@ orientation (horizontal, vertical or unspecified) and the orientation depends on the layout the container is in (vertical for splitv and stacking, horizontal for splith and tabbed). So, in our example with the workspace, the default layout of the workspace +Container+ is splith (most monitors are widescreen -nowadays). If you change the layout to splitv (+mod+l+ in the default config) +nowadays). If you change the layout to splitv (+$mod+l+ 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"] @@ -212,8 +212,8 @@ Let’s assume you have two terminals on a workspace (with splith layout, that i 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 splith layout. -Instead, press +mod+v+ to split the container with the splitv layout (to -open a +Horizontal Split Container+, use +mod+h+). Now you can open a new +Instead, press +$mod+v+ to split the container with the splitv layout (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"] @@ -248,7 +248,7 @@ single workspace on which you open three terminal windows. All these terminal windows are directly attached to one node inside i3’s layout tree, the workspace node. By default, the workspace node’s orientation is +horizontal+. -Now you move one of these terminals down (+mod+k+ by default). The workspace +Now you move one of these terminals down (+$mod+k+ by default). The workspace node’s orientation will be changed to +vertical+. The terminal window you moved down is directly attached to the workspace and appears on the bottom of the screen. A new (horizontal) container was created to accomodate the other two @@ -362,10 +362,10 @@ bindcode [--release] [Modifiers+]keycode command *Examples*: -------------------------------- # Fullscreen -bindsym mod+f fullscreen +bindsym $mod+f fullscreen # Restart -bindsym mod+Shift+r restart +bindsym $mod+Shift+r restart # Notebook-specific hotkeys bindcode 214 exec --no-startup-id /home/michael/toggle_beamer.sh @@ -874,7 +874,7 @@ This configuration directive enables automatic +workspace back_and_forth+ (see For instance: Assume you are on workspace "1: www" and switch to "2: IM" using mod+2 because somebody sent you a message. You don’t need to remember where you -came from now, you can just press mod+2 again to switch back to "1: www". +came from now, you can just press $mod+2 again to switch back to "1: www". *Syntax*: -------------------------------------- @@ -1206,7 +1206,7 @@ the following keybinding: *Example*: -------------------------------------------------------- -bindsym mod+x move container to workspace 3; workspace 3 +bindsym $mod+x move container to workspace 3; workspace 3 -------------------------------------------------------- [[command_criteria]] @@ -1218,10 +1218,10 @@ which have the class Firefox, use: *Example*: ------------------------------------ -bindsym mod+x [class="Firefox"] kill +bindsym $mod+x [class="Firefox"] kill # same thing, but case-insensitive -bindsym mod+x [class="(?i)firefox"] kill +bindsym $mod+x [class="(?i)firefox"] kill ------------------------------------ The criteria which are currently implemented are: @@ -1267,10 +1267,10 @@ exec [--no-startup-id] command *Example*: ------------------------------ # Start the GIMP -bindsym mod+g exec gimp +bindsym $mod+g exec gimp # Start the terminal emulator urxvt which is not yet startup-notification-aware -bindsym mod+Return exec --no-startup-id urxvt +bindsym $mod+Return exec --no-startup-id urxvt ------------------------------ The +--no-startup-id+ parameter disables startup-notification support for this @@ -1301,8 +1301,8 @@ split *Example*: ------------------------------ -bindsym mod+v split vertical -bindsym mod+h split horizontal +bindsym $mod+v split vertical +bindsym $mod+h split horizontal ------------------------------ === Manipulating layout @@ -1323,21 +1323,21 @@ layout toggle [split|all] *Examples*: -------------- -bindsym mod+s layout stacking -bindsym mod+l layout toggle split -bindsym mod+w layout tabbed +bindsym $mod+s layout stacking +bindsym $mod+l layout toggle split +bindsym $mod+w layout tabbed # Toggle between stacking/tabbed/split: -bindsym mod+x layout toggle +bindsym $mod+x layout toggle # Toggle between stacking/tabbed/splith/splitv: -bindsym mod+x layout toggle all +bindsym $mod+x layout toggle all # Toggle fullscreen -bindsym mod+f fullscreen +bindsym $mod+f fullscreen # Toggle floating/tiling -bindsym mod+t floating toggle +bindsym $mod+t floating toggle -------------- === Focusing/Moving containers @@ -1379,36 +1379,36 @@ relevant for floating containers. The default amount is 10 pixels. *Examples*: ---------------------- # Focus container on the left, bottom, top, right: -bindsym mod+j focus left -bindsym mod+k focus down -bindsym mod+l focus up -bindsym mod+semicolon focus right +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 +bindsym $mod+u focus parent # Focus last floating/tiling container -bindsym mod+g focus mode_toggle +bindsym $mod+g focus mode_toggle # Focus the output right to the current one -bindsym mod+x focus output right +bindsym $mod+x focus output right # Focus the big output -bindsym mod+x focus output HDMI-2 +bindsym $mod+x focus output HDMI-2 # Move container to the left, bottom, top, right: -bindsym mod+j move left -bindsym mod+k move down -bindsym mod+l move up -bindsym mod+semicolon move right +bindsym $mod+j move left +bindsym $mod+k move down +bindsym $mod+l move up +bindsym $mod+semicolon move right # Move container, but make floating containers # move more than the default -bindsym mod+j move left 20 px +bindsym $mod+j move left 20 px # Move floating container to the center # of all outputs -bindsym mod+c move absolute position center +bindsym $mod+c move absolute position center ---------------------- === Changing (named) workspaces/moving to workspaces @@ -1448,23 +1448,23 @@ move [window|container] [to] workspace *Examples*: ------------------------- -bindsym mod+1 workspace 1 -bindsym mod+2 workspace 2 +bindsym $mod+1 workspace 1 +bindsym $mod+2 workspace 2 ... -bindsym mod+Shift+1 move container to workspace 1 -bindsym mod+Shift+2 move container to workspace 2 +bindsym $mod+Shift+1 move container to workspace 1 +bindsym $mod+Shift+2 move container to workspace 2 ... # switch between the current and the previously focused one -bindsym mod+b workspace back_and_forth -bindsym mod+Shift+b move container to workspace back_and_forth +bindsym $mod+b workspace back_and_forth +bindsym $mod+Shift+b move container to workspace back_and_forth # move the whole workspace to the next output -bindsym mod+x move workspace to output right +bindsym $mod+x move workspace to output right # move firefox to current workspace -bindsym mod+F1 [class="Firefox"] move workspace current +bindsym $mod+F1 [class="Firefox"] move workspace current ------------------------- ==== Named workspaces @@ -1474,7 +1474,7 @@ workspace command, you can use an arbitrary name: *Example*: ------------------------- -bindsym mod+1 workspace mail +bindsym $mod+1 workspace mail ... ------------------------- @@ -1483,8 +1483,8 @@ number, like this: *Example*: ------------------------- -bindsym mod+1 workspace 1: mail -bindsym mod+2 workspace 2: www +bindsym $mod+1 workspace 1: mail +bindsym $mod+2 workspace 2: www ... ------------------------- @@ -1533,10 +1533,10 @@ move workspace to output <|> -------------------------------------------------------- # Move the current workspace to the next output # (effectively toggles when you only have two outputs) -bindsym mod+x move workspace to output right +bindsym $mod+x move workspace to output right # Put this window on the presentation output. -bindsym mod+x move container to output VGA1 +bindsym $mod+x move container to output VGA1 -------------------------------------------------------- [[resizingconfig]] @@ -1587,7 +1587,7 @@ mode "resize" { } # Enter resize mode -bindsym mod+r mode "resize" +bindsym $mod+r mode "resize" ---------------------------------------------------------------------- === Jumping to specific windows @@ -1608,7 +1608,7 @@ with criteria for that. *Examples*: ------------------------------------------------ # Get me to the next open VIM instance -bindsym mod+a [class="urxvt" title="VIM"] focus +bindsym $mod+a [class="urxvt" title="VIM"] focus ------------------------------------------------ === VIM-like marks (mark/goto) @@ -1644,10 +1644,10 @@ TODO: make i3-input replace %s *Examples*: --------------------------------------- # Read 1 character and mark the current window with this character -bindsym mod+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 mod+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 @@ -1664,9 +1664,9 @@ There is also +border toggle+ which will toggle the different border styles. *Examples*: ---------------------------- -bindsym mod+t border normal -bindsym mod+y border 1pixel -bindsym mod+u border none +bindsym $mod+t border normal +bindsym $mod+y border 1pixel +bindsym $mod+u border none ---------------------------- [[stack-limit]] @@ -1711,9 +1711,9 @@ however you don’t need to (simply killing your X session is fine as well). *Examples*: ---------------------------- -bindsym mod+Shift+r restart -bindsym mod+Shift+w reload -bindsym mod+Shift+e exit +bindsym $mod+Shift+r restart +bindsym $mod+Shift+w reload +bindsym $mod+Shift+e exit ---------------------------- === Scratchpad @@ -1743,10 +1743,10 @@ scratchpad show *Examples*: ------------------------------------------------ # Make the currently focused window a scratchpad -bindsym mod+Shift+minus move scratchpad +bindsym $mod+Shift+minus move scratchpad # Show the first scratchpad window -bindsym mod+minus scratchpad show +bindsym $mod+minus scratchpad show # Show the sup-mail scratchpad window, if any. bindsym mod4+s [title="^Sup ::"] scratchpad show From f500f9d82c7401d6c541e2b094a91fd117c2f4cd Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 19 Oct 2012 19:26:05 +0200 Subject: [PATCH 102/146] keycode default config: s/bindcode/bindsym (Thanks Tim) --- i3.config.keycodes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i3.config.keycodes b/i3.config.keycodes index 162660d3..890afcb7 100644 --- a/i3.config.keycodes +++ b/i3.config.keycodes @@ -79,7 +79,7 @@ bindcode $mod+65 focus mode_toggle bindcode $mod+38 focus parent # focus the child container -#bindcode $mod+d focus child +#bindsym $mod+d focus child # switch to workspace bindcode $mod+10 workspace 1 From 29b19a746811b8ba03ba888191d4de738b965909 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Oct 2012 07:58:03 +0200 Subject: [PATCH 103/146] spelling error: s/implementaiton/implementation/g --- include/commands.h | 10 +++++----- src/commands.c | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/commands.h b/include/commands.h index 5da9fc07..a517d83e 100644 --- a/include/commands.h +++ b/include/commands.h @@ -146,7 +146,7 @@ void cmd_move_workspace_to_output(I3_CMD, char *name); void cmd_split(I3_CMD, char *direction); /** - * Implementaiton of 'kill [window|client]'. + * Implementation of 'kill [window|client]'. * */ void cmd_kill(I3_CMD, char *kill_mode_str); @@ -206,25 +206,25 @@ void cmd_layout(I3_CMD, char *layout_str); void cmd_layout_toggle(I3_CMD, char *toggle_mode); /** - * Implementaiton of 'exit'. + * Implementation of 'exit'. * */ void cmd_exit(I3_CMD); /** - * Implementaiton of 'reload'. + * Implementation of 'reload'. * */ void cmd_reload(I3_CMD); /** - * Implementaiton of 'restart'. + * Implementation of 'restart'. * */ void cmd_restart(I3_CMD); /** - * Implementaiton of 'open'. + * Implementation of 'open'. * */ void cmd_open(I3_CMD); diff --git a/src/commands.c b/src/commands.c index dfb4e236..4b2b2d2a 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1242,7 +1242,7 @@ void cmd_split(I3_CMD, char *direction) { } /* - * Implementaiton of 'kill [window|client]'. + * Implementation of 'kill [window|client]'. * */ void cmd_kill(I3_CMD, char *kill_mode_str) { @@ -1572,7 +1572,7 @@ void cmd_layout_toggle(I3_CMD, char *toggle_mode) { } /* - * Implementaiton of 'exit'. + * Implementation of 'exit'. * */ void cmd_exit(I3_CMD) { @@ -1584,7 +1584,7 @@ void cmd_exit(I3_CMD) { } /* - * Implementaiton of 'reload'. + * Implementation of 'reload'. * */ void cmd_reload(I3_CMD) { @@ -1601,7 +1601,7 @@ void cmd_reload(I3_CMD) { } /* - * Implementaiton of 'restart'. + * Implementation of 'restart'. * */ void cmd_restart(I3_CMD) { @@ -1613,7 +1613,7 @@ void cmd_restart(I3_CMD) { } /* - * Implementaiton of 'open'. + * Implementation of 'open'. * */ void cmd_open(I3_CMD) { From 9b87b2c8ec74d74f05e87ce8be8ef96e9322459b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Oct 2012 19:59:09 +0200 Subject: [PATCH 104/146] Implement smart popup_during_fullscreen mode With this commit, the default behavior is to display popups while there is a fullscreen application only if the popup belongs to that application (as determined by the WM_TRANSIENT_FOR hint which applications have to set properly). fixes #663 --- docs/userguide | 12 +++++++----- include/config.h | 11 +++++++++-- src/click.c | 2 +- src/manage.c | 6 ++++++ src/render.c | 31 +++++++++++++++++++++---------- 5 files changed, 44 insertions(+), 18 deletions(-) diff --git a/docs/userguide b/docs/userguide index e76e4e07..4de69d7a 100644 --- a/docs/userguide +++ b/docs/userguide @@ -802,21 +802,23 @@ focus_follows_mouse no 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: +There are three 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 +1. Display the popup if it belongs to the fullscreen application only. This is + the default and should be reasonable behavior for most users. +2. 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. +3. Leave fullscreen mode. *Syntax*: ------------------------------------------------- -popup_during_fullscreen +popup_during_fullscreen ------------------------------------------------- *Example*: ------------------------------ -popup_during_fullscreen ignore +popup_during_fullscreen smart ------------------------------ === Focus wrapping diff --git a/include/config.h b/include/config.h index fd9c7303..04f1c85f 100644 --- a/include/config.h +++ b/include/config.h @@ -191,8 +191,15 @@ struct Config { /** What should happen when a new popup is opened during fullscreen mode */ enum { - PDF_LEAVE_FULLSCREEN = 0, - PDF_IGNORE = 1 + /* display (and focus) the popup when it belongs to the fullscreen + * window only. */ + PDF_SMART = 0, + + /* leave fullscreen mode unconditionally */ + PDF_LEAVE_FULLSCREEN = 1, + + /* just ignore the popup, that is, don’t map it */ + PDF_IGNORE = 2, } popup_during_fullscreen; }; diff --git a/src/click.c b/src/click.c index 13f51acf..5eedd000 100644 --- a/src/click.c +++ b/src/click.c @@ -229,7 +229,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod /* 3: For floating containers, we also want to raise them on click. * We will skip handling events on floating cons in fullscreen mode */ Con *fs = (ws ? con_get_fullscreen_con(ws, CF_OUTPUT) : NULL); - if (floatingcon != NULL && fs == NULL) { + if (floatingcon != NULL && fs != con) { floating_raise_con(floatingcon); /* 4: floating_modifier plus left mouse button drags */ diff --git a/src/manage.c b/src/manage.c index b07a9c8a..9835aa2a 100644 --- a/src/manage.c +++ b/src/manage.c @@ -347,6 +347,12 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki fs != NULL) { LOG("There is a fullscreen window, leaving fullscreen mode\n"); con_toggle_fullscreen(fs, CF_OUTPUT); + } else if (config.popup_during_fullscreen == PDF_SMART && + fs != NULL && + fs->window != NULL && + fs->window->id == cwindow->transient_for) { + LOG("This floating window belongs to the fullscreen window (popup_during_fullscreen == smart)\n"); + con_focus(nc); } } diff --git a/src/render.c b/src/render.c index 369273c8..1216241b 100644 --- a/src/render.c +++ b/src/render.c @@ -245,18 +245,29 @@ void render_con(Con *con, bool render_fullscreen) { /* Get the active workspace of that output */ Con *content = output_get_content(output); Con *workspace = TAILQ_FIRST(&(content->focus_head)); - - /* Check for fullscreen nodes */ - /* XXX: This code duplication is unfortunate. Keep in mind to fix - * this when we clean up the whole render.c */ - Con *fullscreen = NULL; - fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT); - if (fullscreen) - continue; - + Con *fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT); Con *child; TAILQ_FOREACH(child, &(workspace->floating_head), floating_windows) { - DLOG("floating child at (%d,%d) with %d x %d\n", child->rect.x, child->rect.y, child->rect.width, child->rect.height); + /* Don’t render floating windows when there is a fullscreen window + * on that workspace. Necessary to make floating fullscreen work + * correctly (ticket #564). */ + if (fullscreen != NULL) { + Con *floating_child = con_descend_focused(child); + /* Exception to the above rule: smart + * popup_during_fullscreen handling (popups belonging to + * the fullscreen app will be rendered). */ + if (floating_child->window == NULL || + fullscreen->window == NULL || + floating_child->window->transient_for != fullscreen->window->id) + continue; + else { + DLOG("Rendering floating child even though in fullscreen mode: " + "floating->transient_for (0x%08x) == fullscreen->id (0x%08x)\n", + floating_child->window->transient_for, fullscreen->window->id); + } + } + DLOG("floating child at (%d,%d) with %d x %d\n", + child->rect.x, child->rect.y, child->rect.width, child->rect.height); x_raise_con(child); render_con(child, false); } From 7c94e32819ab940d3c49714a922a8981a4c22881 Mon Sep 17 00:00:00 2001 From: Conley Moorhous Date: Thu, 18 Oct 2012 22:12:11 -0500 Subject: [PATCH 105/146] docs/userguide: s/alt/Alt/ --- docs/userguide | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/userguide b/docs/userguide index 4de69d7a..72555b53 100644 --- a/docs/userguide +++ b/docs/userguide @@ -12,9 +12,9 @@ contact us on IRC (preferred) or post your question(s) on the mailing list. 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 $mod (alt):* +*Keys to use with $mod (Alt):* -image:keyboard-layer1.png["Keys to use with $mod (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+$mod:* @@ -26,7 +26,7 @@ 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) +configured modifier. This is the Alt key (Mod1) by default, with windows (Mod4) being a popular alternative. === Opening terminals and moving around From 2f90321d16a52749e0abd5a27fcb483b64631f1f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 30 Oct 2012 19:14:11 +0100 Subject: [PATCH 106/146] docs/testsuite: add "Installing the dependencies" section (Thanks bitonic) fixes #863 --- docs/testsuite | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/testsuite b/docs/testsuite index 4dcf1670..9b7485bb 100644 --- a/docs/testsuite +++ b/docs/testsuite @@ -63,6 +63,35 @@ For several reasons, the i3 testsuite has been implemented in Perl: Please do not start programming language flamewars at this point. +=== Installing the dependencies + +As usual with Perl programs, the testsuite ships with a +Makefile.PL+. +This file specifies which Perl modules the testsuite depends on and can be used +to install all of them. + +Perl modules are distributed via CPAN, and there is the official, standard CPAN +client, simply called +cpan+. It comes with every Perl installation and can be +used to install the testsuite. Many users prefer to use the more modern ++cpanminus+ instead, though (because it asks no questions and just works): + +.Installing testsuite dependencies using cpanminus (preferred) +-------------------------------------------------------------------------------- +$ cd ~/i3/testcases +$ sudo apt-get install cpanminus +$ sudo cpanm . +-------------------------------------------------------------------------------- + +If you don’t want to use cpanminus for some reason, the same works with cpan: + +.Installing testsuite dependencies using cpan +-------------------------------------------------------------------------------- +$ cd ~/i3/testcases +$ sudo cpan . +-------------------------------------------------------------------------------- + +In case you don’t have root permissions, you can also install into your home +directory, see http://michael.stapelberg.de/cpan/ + === Mechanisms ==== Script: complete-run From ae14fe9141f93dd97a5bebabfa584444e6743206 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 29 Oct 2012 22:42:20 +0100 Subject: [PATCH 107/146] introduce new command to rename focused workspace The corresponding command is 'rename workspace to '. As a side-effect this fixes the command 'rename workspace 1 to to'. Signed-off-by: Michael Walle --- docs/userguide | 11 ++++++++--- parser-specs/commands.spec | 15 ++++++++++++++- src/commands.c | 18 +++++++++++++----- testcases/t/117-workspace.t | 31 +++++++++++++++++++++++++++++-- 4 files changed, 64 insertions(+), 11 deletions(-) diff --git a/docs/userguide b/docs/userguide index 72555b53..f78b4913 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1503,19 +1503,24 @@ specify a default name if there's currently no workspace starting with a "1". You can rename workspaces. This might be useful to start with the default numbered workspaces, do your work, and rename the workspaces afterwards to -reflect what’s actually on them. +reflect what’s actually on them. You can also omit the old name to rename +the currently focused workspace. This is handy if you wan't to use the +rename command with +i3-input+. *Syntax*: ---------------------------------------------------- rename workspace to +rename workspace to ---------------------------------------------------- *Examples*: ------------------------------------------------- +-------------------------------------------------------------------------- i3-msg 'rename workspace 5 to 6' i3-msg 'rename workspace 1 to "1: www"' i3-msg 'rename workspace "1: www" to "10: www"' ------------------------------------------------- +i3-msg 'rename workspace to "2: mail" +bindsym $mod+r exec i3-input -F 'rename workspace to %s' -P 'New name: ' +-------------------------------------------------------------------------- === Moving containers/workspaces to RandR outputs diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index 4224707c..c9b881b3 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -195,17 +195,30 @@ state RESIZE_TILING_OR: -> call cmd_resize($way, $direction, $resize_px, $resize_ppt) # rename workspace to +# rename workspace to state RENAME: 'workspace' -> RENAME_WORKSPACE state RENAME_WORKSPACE: + old_name = 'to' + -> RENAME_WORKSPACE_LIKELY_TO old_name = word -> RENAME_WORKSPACE_TO +state RENAME_WORKSPACE_LIKELY_TO: + 'to' + -> RENAME_WORKSPACE_NEW_NAME + new_name = word + -> call cmd_rename_workspace(NULL, $new_name) + state RENAME_WORKSPACE_TO: 'to' - -> + -> RENAME_WORKSPACE_NEW_NAME + +state RENAME_WORKSPACE_NEW_NAME: + end + -> call cmd_rename_workspace(NULL, "to") new_name = string -> call cmd_rename_workspace($old_name, $new_name) diff --git a/src/commands.c b/src/commands.c index 4b2b2d2a..cb53a31e 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1802,16 +1802,24 @@ void cmd_scratchpad_show(I3_CMD) { } /* - * Implementation of 'rename workspace to ' + * Implementation of 'rename workspace [] to ' * */ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) { - LOG("Renaming workspace \"%s\" to \"%s\"\n", old_name, new_name); + if (old_name) { + LOG("Renaming workspace \"%s\" to \"%s\"\n", old_name, new_name); + } else { + LOG("Renaming current workspace to \"%s\"\n", new_name); + } Con *output, *workspace = NULL; - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) - GREP_FIRST(workspace, output_get_content(output), - !strcasecmp(child->name, old_name)); + if (old_name) { + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) + GREP_FIRST(workspace, output_get_content(output), + !strcasecmp(child->name, old_name)); + } else { + workspace = con_get_workspace(focused); + } if (!workspace) { // TODO: we should include the old workspace name here and use yajl for diff --git a/testcases/t/117-workspace.t b/testcases/t/117-workspace.t index 2283ddc1..d8a8733b 100644 --- a/testcases/t/117-workspace.t +++ b/testcases/t/117-workspace.t @@ -254,11 +254,38 @@ $ws = get_ws('qux'); is($ws->{num}, -1, 'number correctly changed'); workspace_numbers_sorted(); -# 5: already existing workspace +# 4: rename current workspace +cmd 'workspace 4711'; +is(focused_ws(), '4711', 'now on workspace 4711'); + +ok(!workspace_exists('42'), 'workspace 42 does not exist yet'); +cmd 'rename workspace to 42'; +ok(!workspace_exists('4711'), 'workspace 4711 does not exist anymore'); +is(focused_ws(), '42', 'now on workspace 42'); +$ws = get_ws('42'); +is($ws->{num}, 42, 'number correctly changed'); +workspace_numbers_sorted(); + +# 5: special cases +cmd 'workspace bla'; +is(focused_ws(), 'bla', 'now on workspace to'); + +ok(!workspace_exists('to'), 'workspace to does not exist yet'); +cmd 'rename workspace bla to to'; +ok(!workspace_exists('bla'), 'workspace bla does not exist anymore'); +is(focused_ws(), 'to', 'now on workspace to'); +cmd 'rename workspace to to bla'; +ok(!workspace_exists('to'), 'workspace to does not exist anymore'); +is(focused_ws(), 'bla', 'now on workspace bla'); +cmd 'rename workspace to to'; +ok(!workspace_exists('bla'), 'workspace bla does not exist anymore'); +is(focused_ws(), 'to', 'now on workspace to'); + +# 6: already existing workspace my $result = cmd 'rename workspace qux to 11: bar'; ok(!$result->[0]->{success}, 'renaming workspace to an already existing one failed'); -# 6: non-existing old workspace (verify command result) +# 7: non-existing old workspace (verify command result) $result = cmd 'rename workspace notexistant to bleh'; ok(!$result->[0]->{success}, 'renaming workspace which does not exist failed'); From 783fd66b580e0b5c830737a3a112e0396286b145 Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Sat, 11 Aug 2012 22:17:23 +0200 Subject: [PATCH 108/146] complete-run: Unset I3SOCK --- testcases/lib/SocketActivation.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/testcases/lib/SocketActivation.pm b/testcases/lib/SocketActivation.pm index 0a062be4..ea6ae97d 100644 --- a/testcases/lib/SocketActivation.pm +++ b/testcases/lib/SocketActivation.pm @@ -52,6 +52,7 @@ sub activate_i3 { $ENV{LISTEN_PID} = $$; $ENV{LISTEN_FDS} = 1; delete $ENV{DESKTOP_STARTUP_ID}; + delete $ENV{I3SOCK}; unless ($args{dont_create_temp_dir}) { $ENV{XDG_RUNTIME_DIR} = '/tmp/i3-testsuite/'; mkdir $ENV{XDG_RUNTIME_DIR}; From f0d2d84b1c7168341466352eb72d0796fba91fa6 Mon Sep 17 00:00:00 2001 From: Quentin Glidic Date: Wed, 7 Nov 2012 09:54:17 +0100 Subject: [PATCH 109/146] libi3/font: Use "pango:" prefix to avoid confusion Also add a user-friendly font description syntax to userguide --- docs/userguide | 8 +++++--- i3.config | 4 ++-- i3.config.keycodes | 4 ++-- libi3/font.c | 6 +++++- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/docs/userguide b/docs/userguide index f78b4913..99fb072c 100644 --- a/docs/userguide +++ b/docs/userguide @@ -316,13 +316,15 @@ and fall back to a working font. *Syntax*: ------------------------------ font -font xft: +font pango:[family list] [style options] [size] ------------------------------ *Examples*: -------------------------------------------------------------- font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 -font xft:DejaVu Sans Mono 10 +font pango:DejaVu Sans Mono 10 +font pango:DejaVu Sans Mono, Terminus Bold Semi-Condensed 11 +font pango:Terminus 11x -------------------------------------------------------------- [[keybindings]] @@ -1117,7 +1119,7 @@ font -------------------------------------------------------------- bar { font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 - font xft:DejaVu Sans Mono 10 + font pango:DejaVu Sans Mono 10 } -------------------------------------------------------------- diff --git a/i3.config b/i3.config index e45b31ba..05ffb8f8 100644 --- a/i3.config +++ b/i3.config @@ -15,8 +15,8 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 # The font above is very space-efficient, that is, it looks good, sharp and # clear in small sizes. However, if you need a lot of unicode glyphs or # right-to-left text rendering, you should instead use pango for rendering and -# chose an xft font, such as: -# font xft:DejaVu Sans Mono 10 +# chose a FreeType font, such as: +# font pango:DejaVu Sans Mono 10 # use Mouse+Mod1 to drag floating windows to their wanted position floating_modifier Mod1 diff --git a/i3.config.keycodes b/i3.config.keycodes index 890afcb7..21229208 100644 --- a/i3.config.keycodes +++ b/i3.config.keycodes @@ -16,8 +16,8 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 # The font above is very space-efficient, that is, it looks good, sharp and # clear in small sizes. However, if you need a lot of unicode glyphs or # right-to-left text rendering, you should instead use pango for rendering and -# chose an xft font, such as: -# font xft:DejaVu Sans Mono 10 +# chose a FreeType font, such as: +# font pango:DejaVu Sans Mono 10 # Use Mouse+$mod to drag floating windows to their wanted position floating_modifier $mod diff --git a/libi3/font.c b/libi3/font.c index 23d7420d..a2162c47 100644 --- a/libi3/font.c +++ b/libi3/font.c @@ -142,7 +142,11 @@ i3Font load_font(const char *pattern, const bool fallback) { #if PANGO_SUPPORT /* Try to load a pango font if specified */ - if (strlen(pattern) > strlen("xft:") && !strncmp(pattern, "xft:", strlen("xft:"))) { + if (strlen(pattern) > strlen("pango:") && !strncmp(pattern, "pango:", strlen("pango:"))) { + pattern += strlen("pango:"); + if (load_pango_font(&font, pattern)) + return font; + } else if (strlen(pattern) > strlen("xft:") && !strncmp(pattern, "xft:", strlen("xft:"))) { pattern += strlen("xft:"); if (load_pango_font(&font, pattern)) return font; From b67eedf71aa8e026c435ffc9ca6a8212116ac8d0 Mon Sep 17 00:00:00 2001 From: Francesco Mazzoli Date: Sat, 3 Nov 2012 11:17:28 +0000 Subject: [PATCH 110/146] simplify yajl related code Specifically, put the version dependent code in some macros, and put that plus the `y' and `ystr' macros in a separate file, `yajl_utils.h'. --- include/yajl_utils.h | 31 +++++++++++++ src/ipc.c | 103 +++++++------------------------------------ 2 files changed, 48 insertions(+), 86 deletions(-) create mode 100644 include/yajl_utils.h diff --git a/include/yajl_utils.h b/include/yajl_utils.h new file mode 100644 index 00000000..d8a53d3a --- /dev/null +++ b/include/yajl_utils.h @@ -0,0 +1,31 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) + * + * yajl_utils.h + * + */ +#ifndef I3_YAJL_UTILS_H +#define I3_YAJL_UTILS_H + +#include +#include +#include + +/* Shorter names for all those yajl_gen_* functions */ +#define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__) +#define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str)) + +#if YAJL_MAJOR >= 2 +#define ygenalloc() yajl_gen_alloc(NULL) +#define yalloc(callbacks, client) yajl_alloc(callbacks, NULL, client) +typedef size_t ylength; +#else +#define ygenalloc() yajl_gen_alloc(NULL, NULL); +#define yalloc(callbacks, client) yajl_alloc(callbacks, NULL, NULL, client) +typedef unsigned int ylength; +#endif + +#endif diff --git a/src/ipc.c b/src/ipc.c index 5232acf2..17c15b9e 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -10,6 +10,7 @@ * */ #include "all.h" +#include "yajl_utils.h" #include #include @@ -18,14 +19,9 @@ #include #include #include -#include char *current_socketpath = NULL; -/* Shorter names for all those yajl_gen_* functions */ -#define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__) -#define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str)) - TAILQ_HEAD(ipc_client_head, ipc_client) all_clients = TAILQ_HEAD_INITIALIZER(all_clients); /* @@ -128,11 +124,7 @@ IPC_HANDLER(command) { tree_render(); const unsigned char *reply; -#if YAJL_MAJOR >= 2 - size_t length; -#else - unsigned int length; -#endif + ylength length; yajl_gen_get_buf(command_output->json_gen, &reply, &length); ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_COMMAND, @@ -367,20 +359,12 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { IPC_HANDLER(tree) { setlocale(LC_NUMERIC, "C"); -#if YAJL_MAJOR >= 2 - yajl_gen gen = yajl_gen_alloc(NULL); -#else - yajl_gen gen = yajl_gen_alloc(NULL, NULL); -#endif + yajl_gen gen = ygenalloc(); dump_node(gen, croot, false); setlocale(LC_NUMERIC, ""); const unsigned char *payload; -#if YAJL_MAJOR >= 2 - size_t length; -#else - unsigned int length; -#endif + ylength length; y(get_buf, &payload, &length); ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_TREE, payload); @@ -394,11 +378,7 @@ IPC_HANDLER(tree) { * */ IPC_HANDLER(get_workspaces) { -#if YAJL_MAJOR >= 2 - yajl_gen gen = yajl_gen_alloc(NULL); -#else - yajl_gen gen = yajl_gen_alloc(NULL, NULL); -#endif + yajl_gen gen = ygenalloc(); y(array_open); Con *focused_ws = con_get_workspace(focused); @@ -451,11 +431,7 @@ IPC_HANDLER(get_workspaces) { y(array_close); const unsigned char *payload; -#if YAJL_MAJOR >= 2 - size_t length; -#else - unsigned int length; -#endif + ylength length; y(get_buf, &payload, &length); ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_WORKSPACES, payload); @@ -468,11 +444,7 @@ IPC_HANDLER(get_workspaces) { * */ IPC_HANDLER(get_outputs) { -#if YAJL_MAJOR >= 2 - yajl_gen gen = yajl_gen_alloc(NULL); -#else - yajl_gen gen = yajl_gen_alloc(NULL, NULL); -#endif + yajl_gen gen = ygenalloc(); y(array_open); Output *output; @@ -512,11 +484,7 @@ IPC_HANDLER(get_outputs) { y(array_close); const unsigned char *payload; -#if YAJL_MAJOR >= 2 - size_t length; -#else - unsigned int length; -#endif + ylength length; y(get_buf, &payload, &length); ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_OUTPUTS, payload); @@ -529,11 +497,7 @@ IPC_HANDLER(get_outputs) { * */ IPC_HANDLER(get_marks) { -#if YAJL_MAJOR >= 2 - yajl_gen gen = yajl_gen_alloc(NULL); -#else - yajl_gen gen = yajl_gen_alloc(NULL, NULL); -#endif + yajl_gen gen = ygenalloc(); y(array_open); Con *con; @@ -544,11 +508,7 @@ IPC_HANDLER(get_marks) { y(array_close); const unsigned char *payload; -#if YAJL_MAJOR >= 2 - size_t length; -#else - unsigned int length; -#endif + ylength length; y(get_buf, &payload, &length); ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_MARKS, payload); @@ -560,11 +520,7 @@ IPC_HANDLER(get_marks) { * */ IPC_HANDLER(get_version) { -#if YAJL_MAJOR >= 2 - yajl_gen gen = yajl_gen_alloc(NULL); -#else - yajl_gen gen = yajl_gen_alloc(NULL, NULL); -#endif + yajl_gen gen = ygenalloc(); y(map_open); ystr("major"); @@ -582,11 +538,7 @@ IPC_HANDLER(get_version) { y(map_close); const unsigned char *payload; -#if YAJL_MAJOR >= 2 - size_t length; -#else - unsigned int length; -#endif + ylength length; y(get_buf, &payload, &length); ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_VERSION, payload); @@ -599,11 +551,7 @@ IPC_HANDLER(get_version) { * */ IPC_HANDLER(get_bar_config) { -#if YAJL_MAJOR >= 2 - yajl_gen gen = yajl_gen_alloc(NULL); -#else - yajl_gen gen = yajl_gen_alloc(NULL, NULL); -#endif + yajl_gen gen = ygenalloc(); /* If no ID was passed, we return a JSON array with all IDs */ if (message_size == 0) { @@ -615,11 +563,7 @@ IPC_HANDLER(get_bar_config) { y(array_close); const unsigned char *payload; -#if YAJL_MAJOR >= 2 - size_t length; -#else - unsigned int length; -#endif + ylength length; y(get_buf, &payload, &length); ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_BAR_CONFIG, payload); @@ -753,11 +697,7 @@ IPC_HANDLER(get_bar_config) { y(map_close); const unsigned char *payload; -#if YAJL_MAJOR >= 2 - size_t length; -#else - unsigned int length; -#endif + ylength length; y(get_buf, &payload, &length); ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_BAR_CONFIG, payload); @@ -768,13 +708,8 @@ IPC_HANDLER(get_bar_config) { * Callback for the YAJL parser (will be called when a string is parsed). * */ -#if YAJL_MAJOR < 2 static int add_subscription(void *extra, const unsigned char *s, - unsigned int len) { -#else -static int add_subscription(void *extra, const unsigned char *s, - size_t len) { -#endif + ylength len) { ipc_client *client = extra; DLOG("should add subscription to extra %p, sub %.*s\n", client, (int)len, s); @@ -824,11 +759,7 @@ IPC_HANDLER(subscribe) { memset(&callbacks, 0, sizeof(yajl_callbacks)); callbacks.yajl_string = add_subscription; -#if YAJL_MAJOR >= 2 - p = yajl_alloc(&callbacks, NULL, (void*)client); -#else - p = yajl_alloc(&callbacks, NULL, NULL, (void*)client); -#endif + p = yalloc(&callbacks, (void*)client); stat = yajl_parse(p, (const unsigned char*)message, message_size); if (stat != yajl_status_ok) { unsigned char *err; From 74d596e0fcf9e790e45a52ca8a1e704b2b77635f Mon Sep 17 00:00:00 2001 From: Francesco Mazzoli Date: Sat, 3 Nov 2012 11:17:29 +0000 Subject: [PATCH 111/146] more informative `workspace' events Add a `current' and `old' properties to the `focus' change type, containing the current and old workspace respectively. These additions are not necessary anywhere else because `focus' is always triggered when changing ws. --- docs/ipc | 18 ++++++++++++++++-- src/workspace.c | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/docs/ipc b/docs/ipc index 6bdccd0b..ae833c9f 100644 --- a/docs/ipc +++ b/docs/ipc @@ -644,11 +644,25 @@ if ($is_event) { This event consists of a single serialized map containing a property +change (string)+ which indicates the type of the change ("focus", "init", -"empty", "urgent"). +"empty", "urgent"). Additionally, when the change is "focus", an +old +(object)+ and a +current (object)+ properties will be present with the +previous and current workspace respectively. *Example:* --------------------- -{ "change": "focus" } +{ + "change": "focus", + "current": { + "id": 28489712, + "type":4, + ... + } + "old": { + "id": 28489715, + "type": 4, + ... + } +} --------------------- === output event diff --git a/src/workspace.c b/src/workspace.c index ed00c9a7..d4354898 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -11,6 +11,9 @@ * */ #include "all.h" +#include "yajl_utils.h" + +#include /* Stores a copy of the name of the last used workspace for the workspace * back-and-forth switching. */ @@ -331,6 +334,34 @@ static void workspace_defer_update_urgent_hint_cb(EV_P_ ev_timer *w, int revents FREE(con->urgency_timer); } +/* + * For the "focus" event we send, along the usual "change" field, also the + * current and previous workspace, in "current" and "old" respectively. + */ +static void _workspace_focus_event(Con *current, Con *old) { + yajl_gen gen = ygenalloc(); + + y(map_open); + + ystr("change"); + ystr("focus"); + + ystr("current"); + dump_node(gen, current, false); + + ystr("old"); + dump_node(gen, old, false); + + y(map_close); + + const unsigned char *payload; + ylength length; + y(get_buf, &payload, &length); + + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, (const char *)payload); + y(free); +} + static void _workspace_show(Con *workspace) { Con *current, *old = NULL; @@ -433,7 +464,7 @@ static void _workspace_show(Con *workspace) { /* Update the EWMH hints */ ewmh_update_current_desktop(); - ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}"); + _workspace_focus_event(workspace, current); } /* From 464d387044a7b2d2127bbd8ce20bcca160bee24f Mon Sep 17 00:00:00 2001 From: Francesco Mazzoli Date: Sat, 3 Nov 2012 11:17:30 +0000 Subject: [PATCH 112/146] take care of non-existant old workspaces --- docs/ipc | 4 +++- src/workspace.c | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/ipc b/docs/ipc index ae833c9f..ff60dbd1 100644 --- a/docs/ipc +++ b/docs/ipc @@ -646,7 +646,9 @@ This event consists of a single serialized map containing a property +change (string)+ which indicates the type of the change ("focus", "init", "empty", "urgent"). Additionally, when the change is "focus", an +old (object)+ and a +current (object)+ properties will be present with the -previous and current workspace respectively. +previous and current workspace respectively. When the first switch +occurs (when i3 focuses the workspace visible at the beginning) there is +no previous workspace, and the +old+ property will be set to +null+. *Example:* --------------------- diff --git a/src/workspace.c b/src/workspace.c index d4354898..fbd68dba 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -350,7 +350,10 @@ static void _workspace_focus_event(Con *current, Con *old) { dump_node(gen, current, false); ystr("old"); - dump_node(gen, old, false); + if (old == NULL) + y(null); + else + dump_node(gen, old, false); y(map_close); From 1055973f66bc7c94ea9a05a9cbb7d1b83a338c38 Mon Sep 17 00:00:00 2001 From: Francesco Mazzoli Date: Sat, 3 Nov 2012 11:17:31 +0000 Subject: [PATCH 113/146] refactor, name changes We need to send the workspace event earlier, because otherwise 'old' might already be destroyed (if it was empty). --- src/workspace.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/workspace.c b/src/workspace.c index fbd68dba..e277f072 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -338,7 +338,7 @@ static void workspace_defer_update_urgent_hint_cb(EV_P_ ev_timer *w, int revents * For the "focus" event we send, along the usual "change" field, also the * current and previous workspace, in "current" and "old" respectively. */ -static void _workspace_focus_event(Con *current, Con *old) { +static void ipc_send_workspace_focus_event(Con *current, Con *old) { yajl_gen gen = ygenalloc(); y(map_open); @@ -440,6 +440,8 @@ static void _workspace_show(Con *workspace) { } else con_focus(next); + ipc_send_workspace_focus_event(workspace, old); + DLOG("old = %p / %s\n", old, (old ? old->name : "(null)")); /* Close old workspace if necessary. This must be done *after* doing * urgency handling, because tree_close() will do a con_focus() on the next @@ -466,8 +468,6 @@ static void _workspace_show(Con *workspace) { /* Update the EWMH hints */ ewmh_update_current_desktop(); - - _workspace_focus_event(workspace, current); } /* From 16c8832a7bceadf6ad5fcae22e9a5edf6864ec37 Mon Sep 17 00:00:00 2001 From: Francesco Mazzoli Date: Sat, 3 Nov 2012 11:17:32 +0000 Subject: [PATCH 114/146] better docs for the focus workspace ipc event --- docs/ipc | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/ipc b/docs/ipc index ff60dbd1..17397548 100644 --- a/docs/ipc +++ b/docs/ipc @@ -644,11 +644,15 @@ if ($is_event) { This event consists of a single serialized map containing a property +change (string)+ which indicates the type of the change ("focus", "init", -"empty", "urgent"). Additionally, when the change is "focus", an +old -(object)+ and a +current (object)+ properties will be present with the -previous and current workspace respectively. When the first switch -occurs (when i3 focuses the workspace visible at the beginning) there is -no previous workspace, and the +old+ property will be set to +null+. +"empty", "urgent"). + +Moreover, when the change is "focus", an +old (object)+ and a +current +(object)+ properties will be present with the previous and current +workspace respectively. When the first switch occurs (when i3 focuses +the workspace visible at the beginning) there is no previous +workspace, and the +old+ property will be set to +null+. Also note +that if the previous is empty it will get destroyed when switching, +but will still be present in the "old" property. *Example:* --------------------- From a6b6bb670a373b632c84bcf9998793ceb2a24099 Mon Sep 17 00:00:00 2001 From: Francesco Mazzoli Date: Sat, 3 Nov 2012 11:17:33 +0000 Subject: [PATCH 115/146] workspace events test --- testcases/t/115-ipc-workspaces.t | 58 +++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/testcases/t/115-ipc-workspaces.t b/testcases/t/115-ipc-workspaces.t index ec2ec9d2..1dd12417 100644 --- a/testcases/t/115-ipc-workspaces.t +++ b/testcases/t/115-ipc-workspaces.t @@ -18,20 +18,62 @@ use i3test; my $i3 = i3(get_socket_path()); -#################### -# Request workspaces -#################### - -SKIP: { - skip "IPC API not yet stabilized", 2; +################################ +# Workspaces requests and events +################################ my $workspaces = $i3->get_workspaces->recv; ok(@{$workspaces} > 0, "More than zero workspaces found"); -#my $name_exists = all { defined($_->{name}) } @{$workspaces}; -#ok($name_exists, "All workspaces have a name"); +# my $name_exists = all { defined($_->{name}) } @{$workspaces}; +# ok($name_exists, "All workspaces have a name"); +# Focused workspace +my $focused; +foreach (@{$workspaces}) { + $focused = $_ if $_->{focused}; } +ok($focused, "At least one workspace is focused"); + +# Events + +# We are switching to an empty workpspace from an empty workspace, so we expect +# to receive "init", "focus", and "empty". +my $init = AnyEvent->condvar; +my $focus = AnyEvent->condvar; +my $empty = AnyEvent->condvar; +$i3->subscribe({ + workspace => sub { + my ($event) = @_; + if ($event->{change} eq 'init') { + $init->send(1); + } elsif ($event->{change} eq 'focus') { + # Check that we have the old and new workspace + $focus->send( + $event->{current}->{name} == '2' && + $event->{old}->{name} == $focused->{name} + ); + } elsif ($event->{change} eq 'empty') { + $empty->send(1); + } + } +})->recv; + +cmd 'workspace 2'; + +my $t; +$t = AnyEvent->timer( + after => 0.5, + cb => sub { + $init->send(0); + $focus->send(0); + $empty->send(0); + } +); + +ok($init->recv, 'Workspace "init" event received'); +ok($focus->recv, 'Workspace "focus" event received'); +ok($empty->recv, 'Workspace "empty" event received'); done_testing; From e809bff1abdfaefeb6b2b7e2a63cb229534a64c7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 13 Nov 2012 09:39:42 +0100 Subject: [PATCH 116/146] t/115: make the test a little shorter by using helper functions --- testcases/t/115-ipc-workspaces.t | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/testcases/t/115-ipc-workspaces.t b/testcases/t/115-ipc-workspaces.t index 1dd12417..cffcc455 100644 --- a/testcases/t/115-ipc-workspaces.t +++ b/testcases/t/115-ipc-workspaces.t @@ -22,19 +22,7 @@ my $i3 = i3(get_socket_path()); # Workspaces requests and events ################################ -my $workspaces = $i3->get_workspaces->recv; - -ok(@{$workspaces} > 0, "More than zero workspaces found"); - -# my $name_exists = all { defined($_->{name}) } @{$workspaces}; -# ok($name_exists, "All workspaces have a name"); - -# Focused workspace -my $focused; -foreach (@{$workspaces}) { - $focused = $_ if $_->{focused}; -} -ok($focused, "At least one workspace is focused"); +my $focused = get_ws(focused_ws()); # Events From 305b6ddf2fc069b68295f72195de9c2fd0042b48 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 13 Nov 2012 09:49:08 +0100 Subject: [PATCH 117/146] set LC_NUMERIC=C when dumping nodes in the workspace event Otherwise the resulting JSON might be invalid because it uses the locale-specific comma separator, e.g. "16,666" instead of "16.666". --- src/workspace.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/workspace.c b/src/workspace.c index e277f072..5a0913bf 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -339,6 +339,7 @@ static void workspace_defer_update_urgent_hint_cb(EV_P_ ev_timer *w, int revents * current and previous workspace, in "current" and "old" respectively. */ static void ipc_send_workspace_focus_event(Con *current, Con *old) { + setlocale(LC_NUMERIC, "C"); yajl_gen gen = ygenalloc(); y(map_open); @@ -363,6 +364,7 @@ static void ipc_send_workspace_focus_event(Con *current, Con *old) { ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, (const char *)payload); y(free); + setlocale(LC_NUMERIC, ""); } static void _workspace_show(Con *workspace) { From a3324a5e42be62e5d5f0aeb8c320da4d10401ef7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 13 Nov 2012 21:03:25 +0100 Subject: [PATCH 118/146] tests: 115-ipc-workspaces: ensure the i3 ipc socket is connected --- testcases/t/115-ipc-workspaces.t | 1 + 1 file changed, 1 insertion(+) diff --git a/testcases/t/115-ipc-workspaces.t b/testcases/t/115-ipc-workspaces.t index cffcc455..29837430 100644 --- a/testcases/t/115-ipc-workspaces.t +++ b/testcases/t/115-ipc-workspaces.t @@ -17,6 +17,7 @@ use i3test; my $i3 = i3(get_socket_path()); +$i3->connect()->recv; ################################ # Workspaces requests and events From 5fb9b8ffb80da46688f48349d1cfc10ddc700e74 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 13 Nov 2012 21:03:44 +0100 Subject: [PATCH 119/146] tests: 000-load-deps: bail out when dependencies are not found, test more of them This should be a better hint for people who forgot to install the testsuite dependencies :). --- testcases/t/000-load-deps.t | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/testcases/t/000-load-deps.t b/testcases/t/000-load-deps.t index 5dfc5c69..ab93233a 100644 --- a/testcases/t/000-load-deps.t +++ b/testcases/t/000-load-deps.t @@ -1,10 +1,22 @@ #!perl +# vim:ts=4:sw=4:expandtab -use Test::More tests => 2; +use Test::More; BEGIN { - use_ok( 'X11::XCB::Connection' ); - use_ok( 'X11::XCB::Window' ); + my @deps = qw( + X11::XCB::Connection + X11::XCB::Window + AnyEvent + AnyEvent::I3 + IPC::Run + ExtUtils::PkgConfig + Inline + Test::More + ); + for my $dep (@deps) { + use_ok($dep) or BAIL_OUT(qq|The Perl module "$dep" could not be loaded. Please see http://build.i3wm.org/docs/testsuite.html#_installing_the_dependencies|); + } } -diag( "Testing i3, Perl $], $^X" ); +done_testing; From 773654dbb85f420c8292d6455acfb9dd4309f21f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 13 Nov 2012 21:04:13 +0100 Subject: [PATCH 120/146] complete-run: run 000-load-deps as early as possible --- testcases/complete-run.pl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index d73bb332..b8121802 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -137,6 +137,12 @@ my $timingsjson = StartXDummy::slurp('.last_run_timings.json'); sort { $b->[1] <=> $a->[1] } map { [$_, $timings{$_} // 999] } @testfiles; +# Run 000-load-deps.t first to bail out early when dependencies are missing. +my $loadtest = "t/000-load-deps.t"; +if ($loadtest ~~ @testfiles) { + @testfiles = ($loadtest, grep { $_ ne $loadtest } @testfiles); +} + printf("\nRough time estimate for this run: %.2f seconds\n\n", $timings{GLOBAL}) if exists($timings{GLOBAL}); From 6148136e7ce470a888eda29894115df700144428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20L=C3=B6bl?= Date: Sat, 10 Nov 2012 13:41:39 +0100 Subject: [PATCH 121/146] i3bar: Add current binding mode indicator --- i3bar/include/common.h | 1 + i3bar/include/mode.h | 31 +++++++++ i3bar/include/xcb.h | 6 ++ i3bar/src/ipc.c | 18 +++++- i3bar/src/mode.c | 141 +++++++++++++++++++++++++++++++++++++++++ i3bar/src/xcb.c | 48 ++++++++++++++ 6 files changed, 242 insertions(+), 3 deletions(-) create mode 100644 i3bar/include/mode.h create mode 100644 i3bar/src/mode.c diff --git a/i3bar/include/common.h b/i3bar/include/common.h index e2582a02..870e6dbe 100644 --- a/i3bar/include/common.h +++ b/i3bar/include/common.h @@ -50,6 +50,7 @@ TAILQ_HEAD(statusline_head, status_block) statusline_head; #include "outputs.h" #include "util.h" #include "workspaces.h" +#include "mode.h" #include "trayclients.h" #include "xcb.h" #include "config.h" diff --git a/i3bar/include/mode.h b/i3bar/include/mode.h new file mode 100644 index 00000000..a8491aa9 --- /dev/null +++ b/i3bar/include/mode.h @@ -0,0 +1,31 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3bar - an xcb-based status- and ws-bar for i3 + * © 2010-2012 Axel Wagner and contributors (see also: LICENSE) + * + * mode.c: Handle mode-event and show current binding mode in the bar + * + */ +#ifndef MODE_H_ +#define MODE_H_ + +#include + +#include "common.h" + +/* Name of current binding mode and its render width */ +struct mode { + i3String *name; + int width; +}; + +typedef struct mode mode; + +/* + * Start parsing the received json-string + * + */ +void parse_mode_json(char *json); + +#endif diff --git a/i3bar/include/xcb.h b/i3bar/include/xcb.h index dcc4d781..75019c8d 100644 --- a/i3bar/include/xcb.h +++ b/i3bar/include/xcb.h @@ -118,4 +118,10 @@ void draw_bars(bool force_unhide); */ void redraw_bars(void); +/* + * Set the current binding mode + * + */ +void set_current_mode(struct mode *mode); + #endif diff --git a/i3bar/src/ipc.c b/i3bar/src/ipc.c index fc8c6492..2170e509 100644 --- a/i3bar/src/ipc.c +++ b/i3bar/src/ipc.c @@ -138,10 +138,22 @@ void got_output_event(char *event) { } } +/* + * Called, when a mode-event arrives (i3 changed binding mode). + * + */ +void got_mode_event(char *event) { + DLOG("Got Mode Event!\n"); + parse_mode_json(event); + draw_bars(false); +} + + /* Data-structure to easily call the reply-handlers later */ handler_t event_handlers[] = { &got_workspace_event, - &got_output_event + &got_output_event, + &got_mode_event }; /* @@ -297,8 +309,8 @@ void destroy_connection(void) { */ void subscribe_events(void) { if (config.disable_ws) { - i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"output\" ]"); + i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"output\", \"mode\" ]"); } else { - i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"workspace\", \"output\" ]"); + i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"workspace\", \"output\", \"mode\" ]"); } } diff --git a/i3bar/src/mode.c b/i3bar/src/mode.c new file mode 100644 index 00000000..7363971a --- /dev/null +++ b/i3bar/src/mode.c @@ -0,0 +1,141 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3bar - an xcb-based status- and ws-bar for i3 + * © 2010-2012 Axel Wagner and contributors (see also: LICENSE) + * + * mode.c: Handle mode-event and show current binding mode in the bar + * + */ +#include +#include +#include +#include +#include +#include + +#include "common.h" + +/* A datatype to pass through the callbacks to save the state */ +struct mode_json_params { + char *json; + char *cur_key; + mode *mode; +}; + +/* + * Parse a string (change) + * + */ +#if YAJL_MAJOR >= 2 +static int mode_string_cb(void *params_, const unsigned char *val, size_t len) { +#else +static int mode_string_cb(void *params_, const unsigned char *val, unsigned int len) { +#endif + struct mode_json_params *params = (struct mode_json_params*) params_; + + if (!strcmp(params->cur_key, "change")) { + + /* Save the name */ + params->mode->name = i3string_from_utf8_with_length((const char *)val, len); + /* Save its rendered width */ + params->mode->width = predict_text_width(params->mode->name); + + DLOG("Got mode change: %s\n", i3string_as_utf8(params->mode->name)); + FREE(params->cur_key); + + return 1; + } + + return 0; +} + +/* + * Parse a key. + * + * Essentially we just save it in the parsing-state + * + */ +#if YAJL_MAJOR >= 2 +static int mode_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) { +#else +static int mode_map_key_cb(void *params_, const unsigned char *keyVal, unsigned int keyLen) { +#endif + struct mode_json_params *params = (struct mode_json_params*) params_; + FREE(params->cur_key); + + params->cur_key = smalloc(sizeof(unsigned char) * (keyLen + 1)); + strncpy(params->cur_key, (const char*) keyVal, keyLen); + params->cur_key[keyLen] = '\0'; + + return 1; +} + +/* A datastructure to pass all these callbacks to yajl */ +yajl_callbacks mode_callbacks = { + NULL, + NULL, + NULL, + NULL, + NULL, + &mode_string_cb, + NULL, + &mode_map_key_cb, + NULL, + NULL, + NULL +}; + +/* + * Start parsing the received json-string + * + */ +void parse_mode_json(char *json) { + /* FIXME: Fasciliate stream-processing, i.e. allow starting to interpret + * JSON in chunks */ + struct mode_json_params params; + + mode binding; + + params.cur_key = NULL; + params.json = json; + params.mode = &binding; + + yajl_handle handle; + yajl_status state; + +#if YAJL_MAJOR < 2 + yajl_parser_config parse_conf = { 0, 0 }; + + handle = yajl_alloc(&mode_callbacks, &parse_conf, NULL, (void*) ¶ms); +#else + handle = yajl_alloc(&mode_callbacks, NULL, (void*) ¶ms); +#endif + + state = yajl_parse(handle, (const unsigned char*) json, strlen(json)); + + /* FIXME: Propper errorhandling for JSON-parsing */ + switch (state) { + case yajl_status_ok: + break; + case yajl_status_client_canceled: +#if YAJL_MAJOR < 2 + case yajl_status_insufficient_data: +#endif + case yajl_status_error: + ELOG("Could not parse mode-event!\n"); + exit(EXIT_FAILURE); + break; + } + + /* We don't want to indicate default binding mode */ + if (strcmp("default", i3string_as_utf8(params.mode->name)) == 0) + I3STRING_FREE(params.mode->name); + + /* Set the new binding mode */ + set_current_mode(&binding); + + yajl_free(handle); + + FREE(params.cur_key); +} diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 2c0d2a6a..5ae8023a 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -74,6 +74,9 @@ ev_check *xcb_chk; ev_io *xcb_io; ev_io *xkb_io; +/* The name of current binding mode */ +static mode binding; + /* The parsed colors */ struct xcb_colors_t { uint32_t bar_fg; @@ -1527,6 +1530,41 @@ void draw_bars(bool unhide) { set_font_colors(outputs_walk->bargc, fg_color, bg_color); draw_text(ws_walk->name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 2, ws_walk->name_width); i += 10 + ws_walk->name_width + 1; + + } + + if (binding.name) { + + uint32_t fg_color = colors.urgent_ws_fg; + uint32_t bg_color = colors.urgent_ws_bg; + uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND; + + uint32_t vals_border[] = { colors.urgent_ws_border, colors.urgent_ws_border }; + xcb_change_gc(xcb_connection, + outputs_walk->bargc, + mask, + vals_border); + xcb_rectangle_t rect_border = { i, 0, binding.width + 10, font.height + 4 }; + xcb_poly_fill_rectangle(xcb_connection, + outputs_walk->buffer, + outputs_walk->bargc, + 1, + &rect_border); + + uint32_t vals[] = { bg_color, bg_color }; + xcb_change_gc(xcb_connection, + outputs_walk->bargc, + mask, + vals); + xcb_rectangle_t rect = { i + 1, 1, binding.width + 8, font.height + 2 }; + xcb_poly_fill_rectangle(xcb_connection, + outputs_walk->buffer, + outputs_walk->bargc, + 1, + &rect); + + set_font_colors(outputs_walk->bargc, fg_color, bg_color); + draw_text(binding.name, outputs_walk->buffer, outputs_walk->bargc, i + 5, 2, binding.width); } i = 0; @@ -1566,3 +1604,13 @@ void redraw_bars(void) { xcb_flush(xcb_connection); } } + +/* + * Set the current binding mode + * + */ +void set_current_mode(struct mode *current) { + I3STRING_FREE(binding.name); + binding = *current; + return; +} From 3cb909fa62b0e57df26f9ae7bc0174e5751e7c12 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 20 Nov 2012 17:09:03 +0100 Subject: [PATCH 122/146] config parser: recover after invalid input This is done by ignoring the rest of the current line and jumping to the nearest token. fixes #879 --- generate-command-parser.pl | 1 - parser-specs/config.spec | 3 ++ src/commands_parser.c | 1 + src/config_parser.c | 57 +++++++++++++++++++++++++++++++-- testcases/t/201-config-parser.t | 42 ++++++++++++++++++++++++ 5 files changed, 100 insertions(+), 4 deletions(-) diff --git a/generate-command-parser.pl b/generate-command-parser.pl index 66e44b6c..5cdebf34 100755 --- a/generate-command-parser.pl +++ b/generate-command-parser.pl @@ -193,7 +193,6 @@ say $callfh ' default:'; say $callfh ' printf("BUG in the parser. state = %d\n", call_identifier);'; say $callfh ' assert(false);'; say $callfh ' }'; -say $callfh ' state = result->next_state;'; say $callfh '}'; close($callfh); diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 1c11bf9d..8622c62e 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -15,6 +15,7 @@ state INITIAL: # We have an end token here for all the commands which just call some # function without using an explicit 'end' token. end -> + error -> '#' -> IGNORE_LINE 'set' -> IGNORE_LINE bindtype = 'bindsym', 'bindcode', 'bind' -> BINDING @@ -298,6 +299,7 @@ state MODEBRACE: state MODE: end -> + error -> '#' -> MODE_IGNORE_LINE 'set' -> MODE_IGNORE_LINE bindtype = 'bindsym', 'bindcode', 'bind' @@ -336,6 +338,7 @@ state BARBRACE: state BAR: end -> + error -> '#' -> BAR_IGNORE_LINE 'set' -> BAR_IGNORE_LINE 'i3bar_command' -> BAR_BAR_COMMAND diff --git a/src/commands_parser.c b/src/commands_parser.c index 1720fd6f..93ee3889 100644 --- a/src/commands_parser.c +++ b/src/commands_parser.c @@ -190,6 +190,7 @@ static void next_state(const cmdp_token *token) { subcommand_output.json_gen = command_output.json_gen; subcommand_output.needs_tree_render = false; GENERATED_call(token->extra.call_identifier, &subcommand_output); + state = subcommand_output.next_state; /* If any subcommand requires a tree_render(), we need to make the * whole parser result request a tree_render(). */ if (subcommand_output.needs_tree_render) diff --git a/src/config_parser.c b/src/config_parser.c index 889179a9..6f96cc5f 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -21,6 +21,9 @@ * 3. config_parser recognizes \n and \r as 'end' token, while commands_parser * ignores them. * + * 4. config_parser skips the current line on invalid inputs and follows the + * nearest token. + * */ #include #include @@ -221,23 +224,46 @@ static Match current_match; static struct ConfigResult subcommand_output; static struct ConfigResult command_output; +/* A list which contains the states that lead to the current state, e.g. + * INITIAL, WORKSPACE_LAYOUT. + * When jumping back to INITIAL, statelist_idx will simply be set to 1 + * (likewise for other states, e.g. MODE or BAR). + * This list is used to process the nearest error token. */ +static cmdp_state statelist[10] = { INITIAL }; +/* NB: statelist_idx points to where the next entry will be inserted */ +static int statelist_idx = 1; + #include "GENERATED_config_call.h" static void next_state(const cmdp_token *token) { + cmdp_state _next_state = token->next_state; + //printf("token = name %s identifier %s\n", token->name, token->identifier); //printf("next_state = %d\n", token->next_state); if (token->next_state == __CALL) { subcommand_output.json_gen = command_output.json_gen; GENERATED_call(token->extra.call_identifier, &subcommand_output); + _next_state = subcommand_output.next_state; clear_stack(); - return; } - state = token->next_state; + state = _next_state; if (state == INITIAL) { clear_stack(); } + + /* See if we are jumping back to a state in which we were in previously + * (statelist contains INITIAL) and just move statelist_idx accordingly. */ + for (int i = 0; i < statelist_idx; i++) { + if (statelist[i] != _next_state) + continue; + statelist_idx = i+1; + return; + } + + /* Otherwise, the state is new and we add it to the list */ + statelist[statelist_idx++] = _next_state; } /* @@ -284,6 +310,7 @@ struct ConfigResult *parse_config(const char *input, struct context *context) { linecnt++; } state = INITIAL; + statelist_idx = 1; /* A YAJL JSON generator used for formatting replies. */ #if YAJL_MAJOR >= 2 @@ -450,6 +477,10 @@ struct ConfigResult *parse_config(const char *input, struct context *context) { tokenwalk += strlen(token->name + 1); *tokenwalk++ = '\''; } else { + /* Skip error tokens in error messages, they are used + * internally only and might confuse users. */ + if (strcmp(token->name, "error") == 0) + continue; /* Any other token is copied to the error message enclosed * with angle brackets. */ *tokenwalk++ = '<'; @@ -531,10 +562,30 @@ struct ConfigResult *parse_config(const char *input, struct context *context) { ystr(position); y(map_close); + /* Skip the rest of this line, but continue parsing. */ + while ((walk - input) <= len && *walk != '\n') + walk++; + free(position); free(errormessage); clear_stack(); - break; + + /* To figure out in which state to go (e.g. MODE or INITIAL), + * we find the nearest state which contains an token + * and follow that one. */ + bool error_token_found = false; + for (int i = statelist_idx-1; (i >= 0) && !error_token_found; i--) { + cmdp_token_ptr *errptr = &(tokens[statelist[i]]); + for (int j = 0; j < errptr->n; j++) { + if (strcmp(errptr->array[j].name, "error") != 0) + continue; + next_state(&(errptr->array[j])); + error_token_found = true; + break; + } + } + + assert(error_token_found); } } diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t index a850b7e8..ab3d7314 100644 --- a/testcases/t/201-config-parser.t +++ b/testcases/t/201-config-parser.t @@ -362,6 +362,47 @@ is(parser_calls($config), $expected, 'colors ok'); +################################################################################ +# Verify that errors don’t harm subsequent valid statements +################################################################################ + +$config = <<'EOT'; +hide_edge_border both +client.focused #4c7899 #285577 #ffffff #2e9ef4 +EOT + +$expected = <<'EOT'; +ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'focus_follows_mouse', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent' +ERROR: CONFIG: (in file ) +ERROR: CONFIG: Line 1: hide_edge_border both +ERROR: CONFIG: ^^^^^^^^^^^^^^^^^^^^^ +ERROR: CONFIG: Line 2: client.focused #4c7899 #285577 #ffffff #2e9ef4 +cfg_color(client.focused, #4c7899, #285577, #ffffff, #2e9ef4) +EOT + +is(parser_calls($config), + $expected, + 'errors dont harm subsequent statements'); + +$config = <<'EOT'; +hide_edge_borders FOOBAR +client.focused #4c7899 #285577 #ffffff #2e9ef4 +EOT + +$expected = <<'EOT'; +ERROR: CONFIG: Expected one of these tokens: 'none', 'vertical', 'horizontal', 'both', '1', 'yes', 'true', 'on', 'enable', 'active' +ERROR: CONFIG: (in file ) +ERROR: CONFIG: Line 1: hide_edge_borders FOOBAR +ERROR: CONFIG: ^^^^^^ +ERROR: CONFIG: Line 2: client.focused #4c7899 #285577 #ffffff #2e9ef4 +cfg_color(client.focused, #4c7899, #285577, #ffffff, #2e9ef4) +EOT + +is(parser_calls($config), + $expected, + 'errors dont harm subsequent statements'); + + ################################################################################ # Error message with 2+2 lines of context ################################################################################ @@ -524,6 +565,7 @@ ERROR: CONFIG: Line 2: output LVDS-1 ERROR: CONFIG: Line 3: unknown qux ERROR: CONFIG: ^^^^^^^^^^^ ERROR: CONFIG: Line 4: } +cfg_bar_finish() EOT is(parser_calls($config), From eaf13eace2a3adddac14576d0ffe709f66e038a6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 20 Nov 2012 17:11:54 +0100 Subject: [PATCH 123/146] userguide: add section with synonym "screen" for "RandR output" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While the docs describe the feature, plenty of people ask for it on IRC. Let’s see if this makes it easier to find. --- docs/userguide | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/userguide b/docs/userguide index 99fb072c..b684a622 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1524,6 +1524,11 @@ i3-msg 'rename workspace to "2: mail" bindsym $mod+r exec i3-input -F 'rename workspace to %s' -P 'New name: ' -------------------------------------------------------------------------- +=== Moving workspaces to a different screen + +See <> for how to move a container/workspace to a different +RandR output. + === Moving containers/workspaces to RandR outputs [[move_to_outputs]] From d2b533328d4b9f6148dfe1997ae8517d6ea0ca68 Mon Sep 17 00:00:00 2001 From: Emil Mikulic Date: Sat, 24 Nov 2012 16:02:20 +1100 Subject: [PATCH 124/146] Fix memory leaks in config_parser. push_token() doesn't take ownership of its str argument. --- src/config_parser.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/config_parser.c b/src/config_parser.c index 6f96cc5f..80d436b1 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -91,7 +91,7 @@ static struct stack_entry stack[10]; * single array, since the number of entries we have to store is very small. * */ -static void push_string(const char *identifier, char *str) { +static void push_string(const char *identifier, const char *str) { for (int c = 0; c < 10; c++) { if (stack[c].identifier != NULL && strcmp(stack[c].identifier, identifier) != 0) @@ -99,11 +99,13 @@ static void push_string(const char *identifier, char *str) { if (stack[c].identifier == NULL) { /* Found a free slot, let’s store it here. */ stack[c].identifier = identifier; - stack[c].val.str = str; + stack[c].val.str = sstrdup(str); stack[c].type = STACK_STR; } else { /* Append the value. */ - sasprintf(&(stack[c].val.str), "%s,%s", stack[c].val.str, str); + char *prev = stack[c].val.str; + sasprintf(&(stack[c].val.str), "%s,%s", prev, str); + free(prev); } return; } @@ -352,7 +354,7 @@ struct ConfigResult *parse_config(const char *input, struct context *context) { if (token->name[0] == '\'') { if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) { if (token->identifier != NULL) - push_string(token->identifier, sstrdup(token->name + 1)); + push_string(token->identifier, token->name + 1); walk += strlen(token->name) - 1; next_state(token); token_handled = true; @@ -424,6 +426,7 @@ struct ConfigResult *parse_config(const char *input, struct context *context) { } if (token->identifier) push_string(token->identifier, str); + free(str); /* If we are at the end of a quoted string, skip the ending * double quote. */ if (*walk == '"') From f41fa1baa1043c59519f5376f65dd4804edb2183 Mon Sep 17 00:00:00 2001 From: "Adrien \\\"schischi\\\" Schildknecht" Date: Thu, 22 Nov 2012 05:15:49 +0100 Subject: [PATCH 125/146] The command to resize a floating window now checks the minimum and maximum size. --- include/floating.h | 8 +++ src/commands.c | 3 + src/floating.c | 78 ++++++++++++++------------ testcases/t/189-floating-constraints.t | 48 ++++++++++++++++ 4 files changed, 100 insertions(+), 37 deletions(-) diff --git a/include/floating.h b/include/floating.h index 884d3cf1..e07897a0 100644 --- a/include/floating.h +++ b/include/floating.h @@ -99,6 +99,14 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event); */ void floating_resize_window(Con *con, const bool proportional, const xcb_button_press_event_t *event); +/** + * Called when the windows is created or resized + * This function resize the windows if is size if higher or lower to the + * limits. + * + */ +void floating_checkSize(Con *floating_con); + #if 0 /** * Changes focus in the given direction for floating clients. diff --git a/src/commands.c b/src/commands.c index cb53a31e..0bfc4689 100644 --- a/src/commands.c +++ b/src/commands.c @@ -575,6 +575,7 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) { } static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floating_con, int px) { + LOG("floating resize\n"); if (strcmp(direction, "up") == 0) { floating_con->rect.y -= px; @@ -587,6 +588,8 @@ static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floatin } else { floating_con->rect.width += px; } + + floating_checkSize(floating_con); } static bool cmd_resize_tiling_direction(I3_CMD, Con *current, char *way, char *direction, int ppt) { diff --git a/src/floating.c b/src/floating.c index b884a182..b72e7fc0 100644 --- a/src/floating.c +++ b/src/floating.c @@ -28,6 +28,46 @@ static Rect total_outputs_dimensions(void) { return outputs_dimensions; } +void floating_checkSize(Con *floating_con) { + + /* Define reasonable minimal and maximal sizes for floating windows */ + const int floating_sane_min_height = 50; + const int floating_sane_min_width = 75; + Rect floating_sane_max_dimensions; + + /* Unless user requests otherwise (-1), ensure width/height do not exceed + * configured maxima or, if unconfigured, limit to combined width of all + * outputs */ + if (config.floating_minimum_height != -1) { + if (config.floating_minimum_height == 0) + floating_con->rect.height = max(floating_con->rect.height, floating_sane_min_height); + else + floating_con->rect.height = max(floating_con->rect.height, config.floating_minimum_height); + } + if (config.floating_minimum_width != -1) { + if (config.floating_minimum_width == 0) + floating_con->rect.width = max(floating_con->rect.width, floating_sane_min_width); + else + floating_con->rect.width = max(floating_con->rect.width, config.floating_minimum_width); + } + + /* Unless user requests otherwise (-1), raise the width/height to + * reasonable minimum dimensions */ + floating_sane_max_dimensions = total_outputs_dimensions(); + if (config.floating_maximum_height != -1) { + if (config.floating_maximum_height == 0) + floating_con->rect.height = min(floating_con->rect.height, floating_sane_max_dimensions.height); + else + floating_con->rect.height = min(floating_con->rect.height, config.floating_maximum_height); + } + if (config.floating_maximum_width != -1) { + if (config.floating_maximum_width == 0) + floating_con->rect.width = min(floating_con->rect.width, floating_sane_max_dimensions.width); + else + floating_con->rect.width = min(floating_con->rect.width, config.floating_maximum_width); + } +} + void floating_enable(Con *con, bool automatic) { bool set_focus = (con == focused); @@ -138,43 +178,7 @@ void floating_enable(Con *con, bool automatic) { } } - /* Define reasonable minimal and maximal sizes for floating windows */ - const int floating_sane_min_height = 50; - const int floating_sane_min_width = 75; - - Rect floating_sane_max_dimensions; - floating_sane_max_dimensions = total_outputs_dimensions(); - - /* Unless user requests otherwise (-1), ensure width/height do not exceed - * configured maxima or, if unconfigured, limit to combined width of all - * outputs */ - if (config.floating_maximum_height != -1) { - if (config.floating_maximum_height == 0) - nc->rect.height = min(nc->rect.height, floating_sane_max_dimensions.height); - else - nc->rect.height = min(nc->rect.height, config.floating_maximum_height); - } - if (config.floating_maximum_width != -1) { - if (config.floating_maximum_width == 0) - nc->rect.width = min(nc->rect.width, floating_sane_max_dimensions.width); - else - nc->rect.width = min(nc->rect.width, config.floating_maximum_width); - } - - /* Unless user requests otherwise (-1), raise the width/height to - * reasonable minimum dimensions */ - if (config.floating_minimum_height != -1) { - if (config.floating_minimum_height == 0) - nc->rect.height = max(nc->rect.height, floating_sane_min_height); - else - nc->rect.height = max(nc->rect.height, config.floating_minimum_height); - } - if (config.floating_minimum_width != -1) { - if (config.floating_minimum_width == 0) - nc->rect.width = max(nc->rect.width, floating_sane_min_width); - else - nc->rect.width = max(nc->rect.width, config.floating_minimum_width); - } + floating_checkSize(nc); /* 3: attach the child to the new parent container. We need to do this * because con_border_style_rect() needs to access con->parent. */ diff --git a/testcases/t/189-floating-constraints.t b/testcases/t/189-floating-constraints.t index a3ce8476..d907ddcc 100644 --- a/testcases/t/189-floating-constraints.t +++ b/testcases/t/189-floating-constraints.t @@ -128,4 +128,52 @@ is($rect->{height}, 2048, 'height = 2048'); exit_gracefully($pid); +################################################################################ +# 5: check floating_minimum_size with cmd_resize +################################################################################ + +$config = < [ 0, 0, 100, 100 ]); +cmd 'border none'; +cmd 'resize shrink height 80px or 80ppt'; +cmd 'resize shrink width 80px or 80ppt'; +$rect = $window->rect; +is($rect->{width}, 60, 'width = 60'); +is($rect->{height}, 50, 'height = 50'); + +exit_gracefully($pid); + +################################################################################ +# 6: check floating_maximum_size with cmd_resize +################################################################################ + +$config = < [ 200, 200, 50, 50 ]); +cmd 'border none'; +cmd 'resize grow height 100px or 100ppt'; +cmd 'resize grow width 100px or 100ppt'; +$rect = $window->rect; +is($rect->{width}, 100, 'width = 100'); +is($rect->{height}, 100, 'height = 100'); + +exit_gracefully($pid); + done_testing; From 19cbd3cbeca8f043830a80a35fdcb28c971d4da5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 25 Nov 2012 20:55:49 +0100 Subject: [PATCH 126/146] code style fixes for the previous commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • our function names use underscores • rewrote the function’s comment • function comments must be in the source _and_ in the header • no blank lines after function signatures --- include/floating.h | 8 ++++---- src/commands.c | 3 +-- src/floating.c | 11 ++++++++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/include/floating.h b/include/floating.h index e07897a0..c8586527 100644 --- a/include/floating.h +++ b/include/floating.h @@ -100,12 +100,12 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event); void floating_resize_window(Con *con, const bool proportional, const xcb_button_press_event_t *event); /** - * Called when the windows is created or resized - * This function resize the windows if is size if higher or lower to the - * limits. + * Called when a floating window is created or resized. + * This function resizes the window if its size is higher or lower than the + * configured maximum/minimum size, respectively. * */ -void floating_checkSize(Con *floating_con); +void floating_check_size(Con *floating_con); #if 0 /** diff --git a/src/commands.c b/src/commands.c index 0bfc4689..0f36cd38 100644 --- a/src/commands.c +++ b/src/commands.c @@ -575,7 +575,6 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) { } static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floating_con, int px) { - LOG("floating resize\n"); if (strcmp(direction, "up") == 0) { floating_con->rect.y -= px; @@ -589,7 +588,7 @@ static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floatin floating_con->rect.width += px; } - floating_checkSize(floating_con); + floating_check_size(floating_con); } static bool cmd_resize_tiling_direction(I3_CMD, Con *current, char *way, char *direction, int ppt) { diff --git a/src/floating.c b/src/floating.c index b72e7fc0..761d207d 100644 --- a/src/floating.c +++ b/src/floating.c @@ -28,8 +28,13 @@ static Rect total_outputs_dimensions(void) { return outputs_dimensions; } -void floating_checkSize(Con *floating_con) { - +/** + * Called when a floating window is created or resized. + * This function resizes the window if its size is higher or lower than the + * configured maximum/minimum size, respectively. + * + */ +void floating_check_size(Con *floating_con) { /* Define reasonable minimal and maximal sizes for floating windows */ const int floating_sane_min_height = 50; const int floating_sane_min_width = 75; @@ -178,7 +183,7 @@ void floating_enable(Con *con, bool automatic) { } } - floating_checkSize(nc); + floating_check_size(nc); /* 3: attach the child to the new parent container. We need to do this * because con_border_style_rect() needs to access con->parent. */ From 1ae08b196a84de61547497a067277ebb217049c4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 27 Nov 2012 09:26:31 +0100 Subject: [PATCH 127/146] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20move=20floati?= =?UTF-8?q?ng=20windows=20when=20their=20size=20constraints=20forbid=20res?= =?UTF-8?q?izing=20(Thanks=20aksr)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes #883 --- src/commands.c | 15 +++++++++++++-- testcases/t/189-floating-constraints.t | 7 +++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/commands.c b/src/commands.c index 0f36cd38..2ca8387c 100644 --- a/src/commands.c +++ b/src/commands.c @@ -576,19 +576,30 @@ void cmd_move_con_to_workspace_number(I3_CMD, char *which) { static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floating_con, int px) { LOG("floating resize\n"); + Rect old_rect = floating_con->rect; + if (strcmp(direction, "up") == 0) { - floating_con->rect.y -= px; floating_con->rect.height += px; } else if (strcmp(direction, "down") == 0 || strcmp(direction, "height") == 0) { floating_con->rect.height += px; } else if (strcmp(direction, "left") == 0) { - floating_con->rect.x -= px; floating_con->rect.width += px; } else { floating_con->rect.width += px; } floating_check_size(floating_con); + + /* Did we actually resize anything or did the size constraints prevent us? + * If we could not resize, exit now to not move the window. */ + if (memcmp(&old_rect, &(floating_con->rect), sizeof(Rect)) == 0) + return; + + if (strcmp(direction, "up") == 0) { + floating_con->rect.y -= px; + } else if (strcmp(direction, "left") == 0) { + floating_con->rect.x -= px; + } } static bool cmd_resize_tiling_direction(I3_CMD, Con *current, char *way, char *direction, int ppt) { diff --git a/testcases/t/189-floating-constraints.t b/testcases/t/189-floating-constraints.t index d907ddcc..0d3d2268 100644 --- a/testcases/t/189-floating-constraints.t +++ b/testcases/t/189-floating-constraints.t @@ -174,6 +174,13 @@ $rect = $window->rect; is($rect->{width}, 100, 'width = 100'); is($rect->{height}, 100, 'height = 100'); +my $old_x = $rect->{x}; +my $old_y = $rect->{y}; +cmd 'resize grow up 10px or 10ppt'; +$rect = $window->rect; +is($rect->{x}, $old_x, 'window did not move when trying to resize'); +is($rect->{y}, $old_y, 'window did not move when trying to resize'); + exit_gracefully($pid); done_testing; From 23f00ee7d15b1224bec80f08beeaf33d9d159c9c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 23 Nov 2012 20:28:38 +0100 Subject: [PATCH 128/146] dump-asy: let AnyEvent::I3 figure out the socket path --- contrib/dump-asy.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/dump-asy.pl b/contrib/dump-asy.pl index a8eab04c..5af9bb42 100755 --- a/contrib/dump-asy.pl +++ b/contrib/dump-asy.pl @@ -9,7 +9,7 @@ use AnyEvent::I3; use File::Temp; use v5.10; -my $i3 = i3("/tmp/nestedcons"); +my $i3 = i3(); my $tree = $i3->get_tree->recv; From 0560fc7964a1d0864b0371b5874ea0ad44564275 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 23 Nov 2012 20:29:08 +0100 Subject: [PATCH 129/146] dump-asy: start gv without widgets and fit the drawing to window --- contrib/dump-asy.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/dump-asy.pl b/contrib/dump-asy.pl index 5af9bb42..3b989bd7 100755 --- a/contrib/dump-asy.pl +++ b/contrib/dump-asy.pl @@ -42,4 +42,4 @@ 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"); +system("cd /tmp && asy $tmp && gv --scale=-1000 --noresize --widgetless $rep && rm $rep"); From e025f3b9e63c6e737e848bc824f7bcdaef66dd58 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 23 Nov 2012 20:29:52 +0100 Subject: [PATCH 130/146] dump-asy: implement filtering by name, present nodes better where "better" means that we no longer use (name, orientation, window-id), but "name" (leaf) or "name" (horizontal-split) for example. --- contrib/dump-asy.pl | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/contrib/dump-asy.pl b/contrib/dump-asy.pl index 3b989bd7..47239f2d 100755 --- a/contrib/dump-asy.pl +++ b/contrib/dump-asy.pl @@ -1,6 +1,12 @@ #!/usr/bin/env perl # vim:ts=4:sw=4:expandtab # renders the layout tree using asymptote +# +# ./dump-asy.pl +# will render the entire tree +# ./dump-asy.pl 'name' +# will render the tree starting from the node with the specified name, +# e.g. ./dump-asy.pl 2 will render workspace 2 and below use strict; use warnings; @@ -26,7 +32,13 @@ sub dump_node { my $w = (defined($n->{window}) ? $n->{window} : "N"); my $na = $n->{name}; $na =~ s/#/\\#/g; - my $name = "($na, $o, $w)"; + $na =~ s/_/\\_/g; + $na =~ s/~/\\textasciitilde{}/g; + my $type = 'leaf'; + if (!defined($n->{window})) { + $type = $n->{orientation} . '-split'; + } + my $name = qq|\\"$na\\" ($type)|; print $tmp "TreeNode n" . $n->{id} . " = makeNode("; @@ -36,8 +48,27 @@ sub dump_node { dump_node($_, $n) for @{$n->{nodes}}; } -dump_node($tree); -say $tmp "draw(n" . $tree->{id} . ", (0, 0));"; +sub find_node_with_name { + my ($node, $name) = @_; + + return $node if ($node->{name} eq $name); + for my $child (@{$node->{nodes}}) { + my $res = find_node_with_name($child, $name); + return $res if defined($res); + } + return undef; +} + +my $start = shift; +my $root; +if ($start) { + # Find the specified node in the tree + $root = find_node_with_name($tree, $start); +} else { + $root = $tree; +} +dump_node($root); +say $tmp "draw(n" . $root->{id} . ", (0, 0));"; close($tmp); my $rep = "$tmp"; From aa69b9fc5f8571f60c5be6574bf9b370df135266 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 2 Dec 2012 17:57:24 +0100 Subject: [PATCH 131/146] userguide: document new_float option (like new_window) --- docs/userguide | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/userguide b/docs/userguide index b684a622..5880541c 100644 --- a/docs/userguide +++ b/docs/userguide @@ -481,12 +481,15 @@ workspace_layout tabbed === Border style for new windows This option determines which border style new windows will have. The default is -"normal". +"normal". Note that new_float applies only to windows which are starting out as +floating windows, e.g. dialog windows. *Syntax*: --------------------------------------------- new_window +new_float --------------------------------------------- + *Example*: --------------------- new_window 1pixel From fdf14b8f5872b002ba3f26b412f6e46deebb5525 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 2 Dec 2012 17:58:59 +0100 Subject: [PATCH 132/146] docs/i3bar-protocol: fix example formatting --- docs/i3bar-protocol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/i3bar-protocol b/docs/i3bar-protocol index 29ce5713..e3e42d10 100644 --- a/docs/i3bar-protocol +++ b/docs/i3bar-protocol @@ -45,14 +45,14 @@ to provide the correct version. The header block is terminated by a newline and consists of a single JSON hash: *Minimal example*: ----------------- +------------------------------ { "version": 1 } ----------------- +------------------------------ *All features example*: ----------------- +------------------------------ { "version": 1, "stop_signal": 10, "cont_signal": 12 } ----------------- +------------------------------ (Note that before i3 v4.3 the precise format had to be +{"version":1}+, byte-for-byte.) From e8149c77b3bd12e0b371e7c1fe55dc1deca52893 Mon Sep 17 00:00:00 2001 From: Antoine Millet Date: Sun, 2 Dec 2012 13:20:06 +0100 Subject: [PATCH 133/146] i3bar: add min_width and align keys to blocks --- docs/i3bar-protocol | 12 ++++++++++++ i3bar/include/common.h | 12 +++++++++++- i3bar/src/child.c | 22 ++++++++++++++++++++++ i3bar/src/xcb.c | 27 ++++++++++++++++++++++++--- 4 files changed, 69 insertions(+), 4 deletions(-) diff --git a/docs/i3bar-protocol b/docs/i3bar-protocol index e3e42d10..2cf6dd0a 100644 --- a/docs/i3bar-protocol +++ b/docs/i3bar-protocol @@ -134,6 +134,16 @@ color:: when it is associated. Colors are specified in hex (like in HTML), starting with a leading hash sign. For example, +#ff0000+ means red. +min_width:: + The minimum width (in pixels) of the block. If the content of the + +full_text+ key take less space than the specified min_width, the block + will be padded to the left and/or the right side, according to the +align+ + key. This is useful when you want to prevent the whole status line to shift + when value take more or less space between each iteration. +align:: + Align text on the +center+ (default), +right+ or +left+ of the block, when + the minimum width of the latter, specified by the +min_width+ key, is not + reached. name and instance:: Every block should have a unique +name+ (string) entry so that it can be easily identified in scripts which process the output. i3bar @@ -166,6 +176,8 @@ An example of a block which uses all possible entries follows: "full_text": "E: 10.0.0.1 (1000 Mbit/s)", "short_text": "10.0.0.1", "color": "#00ff00", + "min_width": 300, + "align": "right", "urgent": false, "name": "ethernet", "instance": "eth0" diff --git a/i3bar/include/common.h b/i3bar/include/common.h index 870e6dbe..05fb5aa1 100644 --- a/i3bar/include/common.h +++ b/i3bar/include/common.h @@ -27,18 +27,28 @@ struct rect_t { int h; }; +typedef enum { + ALIGN_LEFT, + ALIGN_CENTER, + ALIGN_RIGHT +} blockalign_t; + /* This data structure represents one JSON dictionary, multiple of these make * up one status line. */ struct status_block { i3String *full_text; char *color; + uint32_t min_width; + blockalign_t align; bool urgent; - /* The amount of pixels necessary to render this block. This variable is + /* The amount of pixels necessary to render this block. These variables are * only temporarily used in refresh_statusline(). */ uint32_t width; + uint32_t x_offset; + uint32_t x_append; TAILQ_ENTRY(status_block) blocks; }; diff --git a/i3bar/src/child.c b/i3bar/src/child.c index 9a89d3c6..bea1d58e 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -132,6 +132,27 @@ static int stdin_string(void *context, const unsigned char *val, unsigned int le if (strcasecmp(ctx->last_map_key, "color") == 0) { sasprintf(&(ctx->block.color), "%.*s", len, val); } + if (strcasecmp(ctx->last_map_key, "align") == 0) { + if (len == strlen("left") && !strncmp((const char*)val, "left", strlen("left"))) { + ctx->block.align = ALIGN_LEFT; + } else if (len == strlen("right") && !strncmp((const char*)val, "right", strlen("right"))) { + ctx->block.align = ALIGN_RIGHT; + } else { + ctx->block.align = ALIGN_CENTER; + } + } + return 1; +} + +#if YAJL_MAJOR >= 2 +static int stdin_integer(void *context, long long val) { +#else +static int stdin_integer(void *context, long val) { +#endif + parser_ctx *ctx = context; + if (strcasecmp(ctx->last_map_key, "min_width") == 0) { + ctx->block.min_width = (uint32_t)val; + } return 1; } @@ -313,6 +334,7 @@ void start_child(char *command) { callbacks.yajl_map_key = stdin_map_key; callbacks.yajl_boolean = stdin_boolean; callbacks.yajl_string = stdin_string; + callbacks.yajl_integer = stdin_integer; callbacks.yajl_start_array = stdin_start_array; callbacks.yajl_end_array = stdin_end_array; callbacks.yajl_start_map = stdin_start_map; diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 5ae8023a..6bd8039b 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -123,10 +123,31 @@ void refresh_statusline(void) { continue; block->width = predict_text_width(block->full_text); + + /* Compute offset and append for text aligment in min_width. */ + if (block->min_width <= block->width) { + block->x_offset = 0; + block->x_append = 0; + } else { + uint32_t padding_width = block->min_width - block->width; + switch (block->align) { + case ALIGN_LEFT: + block->x_append = padding_width; + break; + case ALIGN_RIGHT: + block->x_offset = padding_width; + break; + case ALIGN_CENTER: + block->x_offset = padding_width / 2; + block->x_append = padding_width / 2 + padding_width % 2; + break; + } + } + /* If this is not the last block, add some pixels for a separator. */ if (TAILQ_NEXT(block, blocks) != NULL) block->width += 9; - statusline_width += block->width; + statusline_width += block->width + block->x_offset + block->x_append; } /* If the statusline is bigger than our screen we need to make sure that @@ -147,8 +168,8 @@ void refresh_statusline(void) { uint32_t colorpixel = (block->color ? get_colorpixel(block->color) : colors.bar_fg); set_font_colors(statusline_ctx, colorpixel, colors.bar_bg); - draw_text(block->full_text, statusline_pm, statusline_ctx, x, 0, block->width); - x += block->width; + draw_text(block->full_text, statusline_pm, statusline_ctx, x + block->x_offset, 0, block->width); + x += block->width + block->x_offset + block->x_append; if (TAILQ_NEXT(block, blocks) != NULL) { /* This is not the last block, draw a separator. */ From 69e15bba5df807809a91baec08bc45b193ab8e85 Mon Sep 17 00:00:00 2001 From: David Coppa Date: Thu, 6 Dec 2012 12:30:14 +0100 Subject: [PATCH 134/146] OpenBSD has getline() now --- i3-config-wizard/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c index f033f9fa..1eb738f6 100644 --- a/i3-config-wizard/main.c +++ b/i3-config-wizard/main.c @@ -13,7 +13,7 @@ #endif /* For systems without getline, fall back to fgetln */ -#if defined(__APPLE__) || (defined(__FreeBSD__) && __FreeBSD_version < 800000) || defined(__OpenBSD__) +#if defined(__APPLE__) || (defined(__FreeBSD__) && __FreeBSD_version < 800000) #define USE_FGETLN #elif defined(__FreeBSD__) /* Defining this macro before including stdio.h is necessary in order to have From 8fc4660140e8c5bdf1a031fb9e8a9aaf1f6facfd Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 9 Dec 2012 12:07:25 +0100 Subject: [PATCH 135/146] docs/debugging: simplify, merge with release version instructions --- docs/debugging | 90 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 35 deletions(-) diff --git a/docs/debugging b/docs/debugging index e0c812cd..26c9d0d4 100644 --- a/docs/debugging +++ b/docs/debugging @@ -1,7 +1,7 @@ Debugging i3: How To ==================== Michael Stapelberg -February 2012 +December 2012 This document describes how to debug i3 suitably for sending us useful bug reports, even if you have no clue of C programming. @@ -21,25 +21,41 @@ i3 version 4.1.2-248-g51728ba (2012-02-12, branch "next") Your version can look like this: -4.1.2:: -You are using a release version. Please -upgrade to a development version first, or read -link:debugging-release-version.html[Debugging i3: How To (release version)]. +4.1.2 (release version):: +You are using a release version. In many cases, bugs are already +fixed in the development version of i3. If they aren’t, we will still ask you +to reproduce your error with the most recent development version of i3. +Therefore, please upgrade to a development version if you can. -4.1.2-248-g51728ba:: +4.1.2-248-g51728ba (development version):: Your version is 248 commits newer than 4.1.2, and the git revision of your version is +51728ba+. Go to http://code.i3wm.org/i3/commit/?h=next and see if the line "commit" starts with the same revision. If so, you are using the latest version. -Development versions of i3 have several properties which make debugging easier: +Development versions of i3 have logging enabled by default and are compiled +with debug symbols. -1. Shared memory debug logging is enabled by default. You do not have to enable - logging explicitly. -2. Core dumps are enabled by default. -3. If you are using a version from the Debian/Ubuntu autobuilder, it is - compiled without optimization. Debug symbols are available in the i3-wm-dbg - package. When compiling i3 yourself, debug mode is the default. +== Enabling logging + +If you are using a development version (see previous section), you don’t need +to do anything -- skip to section 3. + +If you are using a release version with a custom +~/.xsession+ (or xinitrc) +file, execute i3 with a line like this: + +---------------------------------- +# Use 25 MiB of RAM for debug logs +exec i3 --shmlog-size=26214400 +---------------------------------- + +If you are *NOT* using an +~/.xsession+ file but you just chose "i3" from the +list of sessions in your desktop manager (gdm, lxdm, …), edit ++/usr/share/xsessions/i3.desktop+ and replace the +Exec=i3+ line with: + +------------------------------ +Exec=i3 --shmlog-size=26214400 +------------------------------ == Obtaining the debug logfile @@ -48,35 +64,39 @@ crashed, the logfile provides all information necessary to debug the problem. To save a compressed version of the logfile (suitable for attaching it to a bugreport), use: --------------------------------------------------------------------- -i3-dump-log | gzip -9c > /tmp/i3.log.gz --------------------------------------------------------------------- - -This command does not depend on i3 (it also works when i3 currently displays -the crash dialog), but it requires a working X11 connection. When running it -from a virtual console (Ctrl-Alt-F1), use: - -------------------------------------------------------------------- DISPLAY=:0 i3-dump-log | gzip -9c > /tmp/i3.log.gz -------------------------------------------------------------------- +This command does not depend on i3 (it also works while i3 displays +the crash dialog), but it requires a working X11 connection. + +== Compiling with debug symbols + +To actually get useful backtraces, you should make sure that your version of i3 +is compiled with debug symbols: + +------------------------------------------------------------------------------ +$ file `which i3` +/usr/bin/i3: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically +linked (uses shared libs), for GNU/Linux 2.6.18, not stripped +------------------------------------------------------------------------------ + +Notice the +not stripped+, which is the important part. If you have a version +which is stripped, please have a look if your distribution provides debug +symbols (package +i3-wm-dbg+ on Debian for example) or if you can turn off +stripping. If nothing helps, please build i3 from source. + == Obtaining a backtrace -When i3 displays its crash dialog, do the following: +Once you have made sure that your i3 is compiled with debug symbols and the C +debugger +gdb+ is installed on your machine, you can let i3 generate a +backtrace in the crash dialog. -1. Switch to a virtual console (Ctrl-Alt-F1) or login from a different computer -2. Generate a backtrace (see below) -3. Switch back to the crash dialog (Ctrl-Alt-F7) -4. Restart i3 in-place (you will keep your session), continue working - -This is how you get a backtrace from a running i3 process: - ------------------ -I3PID=$(pidof i3) -gdb /proc/$I3PID/exe $I3PID \ - --batch --quiet \ - --ex 'backtrace full' > /tmp/i3-backtrace.txt 2>&1 ------------------ +After pressing "b" in the crash dialog, you will get a file called ++/tmp/i3-backtrace.%d.%d.txt+ where the first +%d+ is replaced by i3’s process +id (PID) and the second one is incremented each time you generate a backtrace, +starting at 0. == Sending bug reports/debugging on IRC From 4eb6b48c8eccd70250833118461bffd4425a47ea Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 9 Dec 2012 17:04:01 +0100 Subject: [PATCH 136/146] add contrib/i3-dmenu-desktop, a script which runs .desktop files via dmenu See "pod2man --utf8 contrib/i3-dmenu-desktop | man /dev/stdin" for documentation. Use a line like this in your i3 config file: bindsym Mod1+p exec --no-startup-id ~/i3/contrib/i3-dmenu-desktop --- contrib/i3-dmenu-desktop | 445 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 445 insertions(+) create mode 100755 contrib/i3-dmenu-desktop diff --git a/contrib/i3-dmenu-desktop b/contrib/i3-dmenu-desktop new file mode 100755 index 00000000..13b616ee --- /dev/null +++ b/contrib/i3-dmenu-desktop @@ -0,0 +1,445 @@ +#!/usr/bin/env perl +# vim:ts=4:sw=4:expandtab +# +# © 2012 Michael Stapelberg +# +# No dependencies except for perl ≥ v5.10 + +use strict; +use warnings; +use Data::Dumper; +use IPC::Open2; +use POSIX qw(locale_h); +use File::Find; +use File::Basename qw(basename); +use File::Temp qw(tempfile); +use Getopt::Long; +use Pod::Usage; +use v5.10; +use utf8; +use open ':encoding(utf8)'; + +binmode STDOUT, ':utf8'; +binmode STDERR, ':utf8'; + +# reads in a whole file +sub slurp { + open(my $fh, '<', shift) or die "$!"; + local $/; + <$fh>; +} + +my $dmenu_cmd = 'dmenu -i'; +my $result = GetOptions( + 'dmenu=s' => \$dmenu_cmd, + 'version' => sub { + say "dmenu-desktop 1.0 © 2012 Michael Stapelberg"; + exit 0; + }, + 'help' => sub { + pod2usage(-exitval => 0); + }); + +die "Could not parse command line options" unless $result; + +# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +# ┃ Convert LC_MESSAGES into an ordered list of suffixes to search for in the ┃ +# ┃ .desktop files (e.g. “Name[de_DE@euro]” for LC_MESSAGES=de_DE.UTF-8@euro ┃ +# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +# For details on how the transformation of LC_MESSAGES to a list of keys that +# should be looked up works, refer to “Localized values for keys” of the +# “Desktop Entry Specification”: +# http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s04.html +my $lc_messages = setlocale(LC_MESSAGES); + +# Ignore the encoding (e.g. .UTF-8) +$lc_messages =~ s/\.[^@]+//g; + +my @suffixes = ($lc_messages); + +# _COUNTRY and @MODIFIER are present +if ($lc_messages =~ /_[^@]+@/) { + my $no_modifier = $lc_messages; + $no_modifier =~ s/@.*//g; + push @suffixes, $no_modifier; + + my $no_country = $lc_messages; + $no_country =~ s/_[^@]+//g; + push @suffixes, $no_country; +} + +# Strip _COUNTRY and @MODIFIER if present +$lc_messages =~ s/[_@].*//g; +push @suffixes, $lc_messages; + +# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +# ┃ Read all .desktop files and store the values in which we are interested. ┃ +# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +my %desktops; +# See http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html#variables +my $xdg_data_home = $ENV{XDG_DATA_HOME}; +$xdg_data_home = $ENV{HOME} . '/.local/share' if + !defined($xdg_data_home) || + $xdg_data_home eq '' || + ! -d $xdg_data_home; + +my $xdg_data_dirs = $ENV{XDG_DATA_DIRS}; +$xdg_data_dirs = '/usr/local/share/:/usr/share/' if + !defined($xdg_data_dirs) || + $xdg_data_dirs eq ''; + +my @searchdirs = ("$xdg_data_home/applications/"); +for my $dir (split(':', $xdg_data_dirs)) { + push @searchdirs, "$dir/applications/"; +} + +# Cleanup the paths, maybe some application does not cope with double slashes +# (the field code %k is replaced with the .desktop file location). +@searchdirs = map { s,//,/,g; $_ } @searchdirs; + +# To avoid errors by File::Find’s find(), only pass existing directories. +@searchdirs = grep { -d $_ } @searchdirs; + +find( + { + wanted => sub { + return unless substr($_, -1 * length('.desktop')) eq '.desktop'; + my $relative = $File::Find::name; + + # + 1 for the trailing /, which is missing in ::topdir. + substr($relative, 0, length($File::Find::topdir) + 1) = ''; + + # Don’t overwrite files with the same relative path, we search in + # descending order of importance. + return if exists($desktops{$relative}); + + $desktops{$relative} = $File::Find::name; + }, + no_chdir => 1, + }, + @searchdirs +); + +my %apps; + +for my $file (values %desktops) { + my $base = basename($file); + + # _ is an invalid character for a key, so we can use it for our own keys. + $apps{$base}->{_Location} = $file; + + # Extract all “Name” and “Exec” keys from the [Desktop Entry] group + # and store them in $apps{$base}. + my %names; + my @lines = split("\n", slurp($file)); + for my $line (@lines) { + my $first = substr($line, 0, 1); + next if $line eq '' || $first eq '#'; + next unless ($line eq '[Desktop Entry]' .. + ($first eq '[' && + substr($line, -1) eq ']' && + $line ne '[Desktop Entry]')); + next if $first eq '['; + + my ($key, $value) = ($line =~ /^ + ( + [A-Za-z0-9-]+ # the spec specifies these as valid key characters + (?:\[[^]]+\])? # possibly, there as a locale suffix + ) + \s* = \s* # whitespace around = should be ignored + (.*) # no restrictions on the values + $/x); + + if ($key =~ /^Name/) { + $names{$key} = $value; + } elsif ($key eq 'Exec' || + $key eq 'TryExec') { + $apps{$base}->{$key} = $value; + } elsif ($key eq 'NoDisplay' || + $key eq 'Hidden' || + $key eq 'StartupNotify' || + $key eq 'Terminal') { + # Values of type boolean must either be string true or false, + # see “Possible value types”: + # http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s03.html + $apps{$base}->{$key} = ($value eq 'true'); + } + } + + for my $suffix (@suffixes) { + next unless exists($names{"Name[$suffix]"}); + $apps{$base}->{Name} = $names{"Name[$suffix]"}; + last; + } + + # Fallback to unlocalized “Name”. + $apps{$base}->{Name} = $names{Name} unless exists($apps{$base}->{Name}); +} + +# %apps now looks like this: +# +# %apps = { +# 'evince.desktop' => { +# 'Exec' => 'evince %U', +# 'Name' => 'Dokumentenbetrachter', +# '_Location' => '/usr/share/applications/evince.desktop' +# }, +# 'gedit.desktop' => { +# 'Exec' => 'gedit %U', +# 'Name' => 'gedit', +# '_Location' => '/usr/share/applications/gedit.desktop' +# } +# }; + +# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +# ┃ Turn %apps inside out to provide Name → filename lookup. ┃ +# ┃ The Name is what we display in dmenu later. ┃ +# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +my %choices; +for my $app (keys %apps) { + my $name = $apps{$app}->{Name}; + + # Don’t offer apps which have NoDisplay == true or Hidden == true. + # See http://wiki.xfce.org/howto/customize-menu#hide_menu_entries + # for the difference between NoDisplay and Hidden. + next if (exists($apps{$app}->{NoDisplay}) && $apps{$app}->{NoDisplay}) || + (exists($apps{$app}->{Hidden}) && $apps{$app}->{Hidden}); + + if (exists($apps{$app}->{TryExec})) { + my $tryexec = $apps{$app}->{TryExec}; + if (substr($tryexec, 0, 1) eq '/') { + # Skip if absolute path is not executable. + next unless -x $tryexec; + } else { + # Search in $PATH for the executable. + my $found = 0; + for my $path (split(':', $ENV{PATH})) { + next unless -x "$path/$tryexec"; + $found = 1; + last; + } + next unless $found; + } + } + + if (exists($choices{$name})) { + # There are two .desktop files which contain the same “Name” value. + # I’m not sure if that is allowed to happen, but we disambiguate the + # situation by appending “ (2)”, “ (3)”, etc. to the name. + # + # An example of this happening is exo-file-manager.desktop and + # thunar-settings.desktop, both of which contain “Name=File Manager”. + my $inc = 2; + $inc++ while exists($choices{"$name ($inc)"}); + $name = "$name ($inc)"; + } + + $choices{$name} = $app; +} + +# %choices now looks like this: +# +# %choices = { +# 'Dokumentenbetrachter' => 'evince.desktop', +# 'gedit' => 'gedit.desktop' +# }; + +# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +# ┃ Run dmenu to ask the user for her choice ┃ +# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +# open2 will just make dmenu’s STDERR go to our own STDERR. +my ($dmenu_out, $dmenu_in); +my $pid = open2($dmenu_out, $dmenu_in, $dmenu_cmd); +binmode $dmenu_in, ':utf8'; +binmode $dmenu_out, ':utf8'; + +# Feed dmenu the possible choices. +say $dmenu_in $_ for sort keys %choices; +close($dmenu_in); + +waitpid($pid, 0); +my $status = ($? >> 8); + +# Pass on dmenu’s exit status if there was an error. +exit $status unless $status == 0; + +my $choice = <$dmenu_out>; +my $app; +# Exact match: the user chose “Avidemux (GTK+)” +if (exists($choices{$choice})) { + $app = $apps{$choices{$choice}}; + $choice = ''; +} else { + # Not an exact match: the user entered “Avidemux (GTK+) ~/movie.mp4” + for my $possibility (keys %choices) { + next unless substr($choice, 0, length($possibility)) eq $possibility; + $app = $apps{$choices{$possibility}}; + substr($choice, 0, length($possibility)) = ''; + # Remove whitespace separating the entry and arguments. + $choice =~ s/^\s//g; + last; + } + if (!defined($app)) { + die "Invalid input: “$choice” does not match any application."; + } +} + +# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +# ┃ Make i3 start the chosen application. ┃ +# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +my $name = $app->{Name}; +my $exec = $app->{Exec}; +my $location = $app->{_Location}; + +# Quote as described by “The Exec key”: +# http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html +sub quote { + my ($str) = @_; + $str =~ s/("|`|\$|\\)/\\$1/g; + $str = qq|"$str"| if $str ne ""; + return $str; +} + +$choice = quote($choice); +$location = quote($location); + +# Remove deprecated field codes, as the spec dictates. +$exec =~ s/%[dDnNvm]//g; + +# Replace filename field codes with the rest of the command line. +# Note that we assume the user uses precisely one file name, +# not multiple file names. +$exec =~ s/%[fF]/$choice/g; + +# If the program works with URLs, +# we assume the user provided a URL instead of a filename. +# As per the spec, there must be at most one of %f, %u, %F or %U present. +$exec =~ s/%[uU]/$choice/g; + +# The translated name of the application. +$exec =~ s/%c/$name/g; + +# XXX: Icons are not implemented. Is the complexity (looking up the path if +# only a name is given) actually worth it? +#$exec =~ s/%i/--icon $icon/g; + +# location of .desktop file +$exec =~ s/%k/$location/g; + +# Literal % characters are represented as %%. +$exec =~ s/%%/%/g; + +my $nosn = ''; +my $cmd; +if (exists($app->{Terminal}) && $app->{Terminal}) { + # For applications which specify “Terminal=true” (e.g. htop.desktop), + # we need to create a temporary script that contains the full command line + # as the syntax for starting commands with arguments varies from terminal + # emulator to terminal emulator. + # Then, we launch that script with i3-sensible-terminal. + my ($fh, $filename) = tempfile(); + binmode($fh, ':utf8'); + say $fh <{StartupNotify}) && !$app->{StartupNotify}) { + $nosn = '--no-startup-id'; + } + $cmd = qq|exec $nosn "$exec"|; +} + +system('i3-msg', $cmd) == 0 or die "Could not launch i3-msg: $?"; + +=encoding utf-8 + +=head1 NAME + + i3-dmenu-desktop - run .desktop files with dmenu + +=head1 SYNOPSIS + + i3-dmenu-desktop [--dmenu='dmenu -i'] + +=head1 DESCRIPTION + +i3-dmenu-desktop is a script which extracts the (localized) name from +application .desktop files, offers the user a choice via dmenu(1) and then +starts the chosen application via i3 (for startup notification support). +The advantage of using .desktop files instead of dmenu_run(1) is that dmenu_run +offers B binaries in your $PATH, including non-interactive utilities like +"sed". Also, .desktop files contain a proper name, information about whether +the application runs in a terminal and whether it supports startup +notifications. + +The .desktop files are searched in $XDG_DATA_HOME/applications (by default +$HOME/.local/share/applications) and in the "applications" subdirectory of each +entry of $XDG_DATA_DIRS (by default /usr/local/share/:/usr/share/). + +Files with the same name in $XDG_DATA_HOME/applications take precedence over +files in $XDG_DATA_DIRS, so that you can overwrite parts of the system-wide +.desktop files by copying them to your local directory and making changes. + +i3-dmenu-desktop displays the "Name" value in the localized version depending +on LC_MESSAGES as specified in the Desktop Entry Specification. + +You can pass a filename or URL (%f/%F and %u/%U field codes in the .desktop +file respectively) by appending it to the name of the application. E.g., if you +want to launch "GNU Emacs 24" with the patch /tmp/foobar.txt, you would type +"emacs", press TAB, type " /tmp/foobar.txt" and press ENTER. + +.desktop files with Terminal=true are started using i3-sensible-terminal(1). + +.desktop files with NoDisplay=true or Hidden=true are skipped. + +UTF-8 is supported, of course, but dmenu does not support displaying all +glyphs. E.g., xfce4-terminal.desktop's Name[fi]=Pääte will be displayed just +fine, but not its Name[ru]=Терминал. + +=head1 OPTIONS + +=over + +=item B<--dmenu=command> + +Execute command instead of 'dmenu -i'. This option can be used to pass custom +parameters to dmenu, or to make i3-dmenu-desktop start a custom (patched?) +version of dmenu. + +=back + +=head1 VERSION + +Version 1.0 + +=head1 AUTHOR + +Michael Stapelberg, C<< >> + +=head1 LICENSE AND COPYRIGHT + +Copyright 2012 Michael Stapelberg. + +This program is free software; you can redistribute it and/or modify it +under the terms of the BSD license. + +=cut From 66f7a607f66b1e9541be2ca696c4b8c8868ab853 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 9 Dec 2012 17:56:55 +0100 Subject: [PATCH 137/146] docs/debugging: grammar improvements (Thanks cian) --- docs/debugging | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/debugging b/docs/debugging index 26c9d0d4..b258789d 100644 --- a/docs/debugging +++ b/docs/debugging @@ -3,12 +3,12 @@ Debugging i3: How To Michael Stapelberg December 2012 -This document describes how to debug i3 suitably for sending us useful bug -reports, even if you have no clue of C programming. +This document describes how to debug i3 to send us useful bug +reports, even if you have no knowledge of C programming. -First of all: Thank you for being interested in debugging i3. It really means +Thank you for being interested in debugging i3. It really means something to us to get your bug fixed. If you have any questions about the -debugging and/or need further help, do not hesitate to contact us! +process and/or need further help, do not hesitate to contact us! == Verify you are using the latest (development) version @@ -83,7 +83,7 @@ linked (uses shared libs), for GNU/Linux 2.6.18, not stripped ------------------------------------------------------------------------------ Notice the +not stripped+, which is the important part. If you have a version -which is stripped, please have a look if your distribution provides debug +which is stripped, please check whether your distribution provides debug symbols (package +i3-wm-dbg+ on Debian for example) or if you can turn off stripping. If nothing helps, please build i3 from source. From f044eb9e9048a5612a1a1894cbe7da487b41026c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 10 Dec 2012 00:28:32 +0100 Subject: [PATCH 138/146] i3-dmenu-desktop: add --entry-type=[name|command|both] The new default is 'both' to add "GNU Image Manipulation Program" as well as "gimp-2.8" to the menu. --- contrib/i3-dmenu-desktop | 45 +++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/contrib/i3-dmenu-desktop b/contrib/i3-dmenu-desktop index 13b616ee..af525ae2 100755 --- a/contrib/i3-dmenu-desktop +++ b/contrib/i3-dmenu-desktop @@ -29,11 +29,13 @@ sub slurp { <$fh>; } +my $entry_type = 'both'; my $dmenu_cmd = 'dmenu -i'; my $result = GetOptions( 'dmenu=s' => \$dmenu_cmd, + 'entry-type=s' => \$entry_type, 'version' => sub { - say "dmenu-desktop 1.0 © 2012 Michael Stapelberg"; + say "dmenu-desktop 1.1 © 2012 Michael Stapelberg"; exit 0; }, 'help' => sub { @@ -225,19 +227,26 @@ for my $app (keys %apps) { } } - if (exists($choices{$name})) { - # There are two .desktop files which contain the same “Name” value. - # I’m not sure if that is allowed to happen, but we disambiguate the - # situation by appending “ (2)”, “ (3)”, etc. to the name. - # - # An example of this happening is exo-file-manager.desktop and - # thunar-settings.desktop, both of which contain “Name=File Manager”. - my $inc = 2; - $inc++ while exists($choices{"$name ($inc)"}); - $name = "$name ($inc)"; + if ($entry_type eq 'name' || $entry_type eq 'both') { + if (exists($choices{$name})) { + # There are two .desktop files which contain the same “Name” value. + # I’m not sure if that is allowed to happen, but we disambiguate the + # situation by appending “ (2)”, “ (3)”, etc. to the name. + # + # An example of this happening is exo-file-manager.desktop and + # thunar-settings.desktop, both of which contain “Name=File Manager”. + my $inc = 2; + $inc++ while exists($choices{"$name ($inc)"}); + $name = "$name ($inc)"; + } + + $choices{$name} = $app; } - $choices{$name} = $app; + if ($entry_type eq 'command' || $entry_type eq 'both') { + my ($command) = split(' ', $apps{$app}->{Exec}); + $choices{basename($command)} = $app; + } } # %choices now looks like this: @@ -378,7 +387,7 @@ system('i3-msg', $cmd) == 0 or die "Could not launch i3-msg: $?"; =head1 SYNOPSIS - i3-dmenu-desktop [--dmenu='dmenu -i'] + i3-dmenu-desktop [--dmenu='dmenu -i'] [--entry-type=both] =head1 DESCRIPTION @@ -425,11 +434,19 @@ Execute command instead of 'dmenu -i'. This option can be used to pass custom parameters to dmenu, or to make i3-dmenu-desktop start a custom (patched?) version of dmenu. +=item B<--entry-type=type> + +Display the (localized) "Name" (type = name) or the command (type = command) or +both (type = both) in dmenu. + +Examples are "GNU Image Manipulation Program" (type = name), "gimp" (type = +command) and both (type = both). + =back =head1 VERSION -Version 1.0 +Version 1.1 =head1 AUTHOR From 9f7b4b9a43b238b81351124827c923f04142639e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 11 Dec 2012 22:10:22 +0100 Subject: [PATCH 139/146] move i3-dmenu-desktop from contrib to / --- contrib/i3-dmenu-desktop => i3-dmenu-desktop | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename contrib/i3-dmenu-desktop => i3-dmenu-desktop (100%) diff --git a/contrib/i3-dmenu-desktop b/i3-dmenu-desktop similarity index 100% rename from contrib/i3-dmenu-desktop rename to i3-dmenu-desktop From 18e46ffae50a7361454d4efa1bbe4649ab7e915f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 11 Dec 2012 22:31:44 +0100 Subject: [PATCH 140/146] install i3-dmenu-desktop --- Makefile | 2 +- common.mk | 1 + debian/i3-wm.manpages | 1 + man/man.mk | 15 ++++++++++++--- src/i3.mk | 1 + 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 3b675034..b0f49e34 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ dist: distclean [ ! -d i3-${VERSION} ] || rm -rf i3-${VERSION} [ ! -e i3-${VERSION}.tar.bz2 ] || rm i3-${VERSION}.tar.bz2 mkdir i3-${VERSION} - cp i3-migrate-config-to-v4 generate-command-parser.pl i3-sensible-* i3.config.keycodes DEPENDS LICENSE PACKAGE-MAINTAINER RELEASE-NOTES-${VERSION} i3.config i3.xsession.desktop i3.applications.desktop pseudo-doc.doxygen common.mk Makefile i3-${VERSION} + cp i3-migrate-config-to-v4 generate-command-parser.pl i3-sensible-* i3-dmenu-desktop i3.config.keycodes DEPENDS LICENSE PACKAGE-MAINTAINER RELEASE-NOTES-${VERSION} i3.config i3.xsession.desktop i3.applications.desktop pseudo-doc.doxygen common.mk Makefile i3-${VERSION} cp -r src libi3 i3-msg i3-nagbar i3-config-wizard i3bar i3-dump-log yajl-fallback include man parser-specs testcases i3-${VERSION} # Only copy toplevel documentation (important stuff) mkdir i3-${VERSION}/docs diff --git a/common.mk b/common.mk index bb5cf793..de5c7e98 100644 --- a/common.mk +++ b/common.mk @@ -195,6 +195,7 @@ ifeq ($(V),0) # echo-ing vars V_ASCIIDOC = echo ASCIIDOC $@; V_POD2HTML = echo POD2HTML $@; +V_POD2MAN = echo POD2MAN $@; V_A2X = echo A2X $@; endif diff --git a/debian/i3-wm.manpages b/debian/i3-wm.manpages index a1b05bd3..58569b87 100644 --- a/debian/i3-wm.manpages +++ b/debian/i3-wm.manpages @@ -8,4 +8,5 @@ man/i3-migrate-config-to-v4.1 man/i3-sensible-pager.1 man/i3-sensible-editor.1 man/i3-sensible-terminal.1 +man/i3-dmenu-desktop.1 man/i3bar.1 diff --git a/man/man.mk b/man/man.mk index f999dc78..7c5c9858 100644 --- a/man/man.mk +++ b/man/man.mk @@ -1,10 +1,12 @@ DISTCLEAN_TARGETS += clean-mans A2X = a2x +POD2MAN = pod2man A2X_MAN_CALL = $(V_A2X)$(A2X) -f manpage --asciidoc-opts="-f man/asciidoc.conf" $(A2X_FLAGS) $< +POD2MAN_CALL = $(V_POD2MAN)$(POD2MAN) --utf8 $< > $@ -MANS_1 = \ +MANS_ASCIIDOC = \ man/i3.1 \ man/i3bar.1 \ man/i3-msg.1 \ @@ -17,14 +19,21 @@ MANS_1 = \ man/i3-sensible-terminal.1 \ man/i3-dump-log.1 +MANS_POD = \ + man/i3-dmenu-desktop.1 + MANS = \ - $(MANS_1) + $(MANS_ASCIIDOC) \ + $(MANS_POD) mans: $(MANS) -$(MANS_1): %.1: %.man man/asciidoc.conf +$(MANS_ASCIIDOC): %.1: %.man man/asciidoc.conf $(A2X_MAN_CALL) +$(MANS_POD): %.1: i3-dmenu-desktop + $(POD2MAN_CALL) + clean-mans: for file in $(notdir $(MANS)); \ do \ diff --git a/src/i3.mk b/src/i3.mk index 81916394..dedf4e27 100644 --- a/src/i3.mk +++ b/src/i3.mk @@ -87,6 +87,7 @@ install-i3: i3 $(INSTALL) -m 0755 i3-sensible-editor $(DESTDIR)$(PREFIX)/bin/ $(INSTALL) -m 0755 i3-sensible-pager $(DESTDIR)$(PREFIX)/bin/ $(INSTALL) -m 0755 i3-sensible-terminal $(DESTDIR)$(PREFIX)/bin/ + $(INSTALL) -m 0755 i3-dmenu-desktop $(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.xsession.desktop $(DESTDIR)$(PREFIX)/share/xsessions/i3.desktop From f506e35395d75c0c61b6a6b9d12455fe09e254f8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 11 Dec 2012 22:32:14 +0100 Subject: [PATCH 141/146] drop docs/debugging-release-version, it was integrated into docs/debugging --- debian/i3-wm.docs | 1 - docs/debugging-release-version | 126 --------------------------------- 2 files changed, 127 deletions(-) delete mode 100644 docs/debugging-release-version diff --git a/debian/i3-wm.docs b/debian/i3-wm.docs index 6bd9c5ba..b83b8da7 100644 --- a/debian/i3-wm.docs +++ b/debian/i3-wm.docs @@ -1,5 +1,4 @@ docs/debugging.html -docs/debugging-release-version.html docs/hacking-howto.html docs/i3bar-protocol.html docs/userguide.html diff --git a/docs/debugging-release-version b/docs/debugging-release-version deleted file mode 100644 index 71aa7737..00000000 --- a/docs/debugging-release-version +++ /dev/null @@ -1,126 +0,0 @@ -Debugging i3: How To (release version) -====================================== -Michael Stapelberg -February 2012 - -This document describes how to debug i3 suitably for sending us useful bug -reports, even if you have no clue of C programming. - -First of all: Thank you for being interested in debugging i3. It really means -something to us to get your bug fixed. If you have any questions about the -debugging and/or need further help, do not hesitate to contact us! - -NOTE: This document is for the release version of i3. If you are using a -development version, please see link:debugging.html[Debugging i3: How To] -instead. - -== Consider using the development version - -This document is for the release version of i3. In many cases, bugs are already -fixed in the development version of i3. If they aren’t, we will still ask you -to reproduce your error with the most recent development version of i3. -Therefore, please upgrade to a development version and continue reading at -link:debugging.html[Debugging i3: How To]. - -If you absolutely cannot upgrade to a development version of i3, you may -continue reading this document. - -== Enabling logging - -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 >~/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 - -When i3 crashes, often you have the chance of getting a 'core dump' (an image -of the memory of the i3 process which can be loaded into a debugger). To get a -core dump, you have to make sure that the user limit for core dump files is set -high enough. Many systems ship with a default value which even forbids core -dumps completely. To disable the limit completely and thus enable core dumps, -use the following command (in your +~/.xsession+, before starting i3): - -------------------- -ulimit -c unlimited -------------------- - -Furthermore, to easily recognize core dumps and allow multiple of them, you -should set a custom core dump filename pattern, using a command like the -following: - ---------------------------------------------- -sudo sysctl -w kernel.core_pattern=core.%e.%p ---------------------------------------------- - -This will generate files which have the executable’s file name (%e) and the -process id (%p) in it. You can save this setting across reboots using -+/etc/sysctl.conf+. - -== 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 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) ----------------- - -You should get an output like this: ------------------------------------------------------------------------------- -/usr/bin/i3: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically -linked (uses shared libs), for GNU/Linux 2.6.18, not stripped ------------------------------------------------------------------------------- - -Notice the +not stripped+, which is the important part. If you have a version -which is stripped, please have a look if your distribution provides debug -symbols (package +i3-wm-dbg+ on Debian for example) or if you can turn off -stripping. If nothing helps, please build i3 from source. - -== Generating a backtrace - -Once you have made sure that your i3 is compiled with debug symbols and that -core dumps are enabled, you can start making sense out of the core dumps. - -Because the core dump depends on the original executable (and its debug -symbols), please do this as soon as you encounter the problem. If you -re-compile i3, your core dump might be useless afterwards. - -Please install +gdb+, a debugger for C. No worries, you don’t need to learn it -now. Start gdb using the following command (replacing the actual name of the -core dump of course): - ----------------------------- -gdb $(which i3) core.i3.3849 ----------------------------- - -Then, generate a backtrace using: - --------------- -backtrace full --------------- - -== Sending bug reports/debugging on IRC - -When sending bug reports, please paste the relevant part of the log (if in -doubt, please send us rather too much information than too less) and the whole -backtrace (if there was a core dump). - -When debugging with us in IRC, be prepared to use a so called nopaste service -such as http://nopaste.info or http://pastebin.com because pasting large -amounts of text in IRC sometimes leads to incomplete lines (servers have line -length limitations) or flood kicks. From 60db534a08976ab47e1d1525e52818763c8b7f33 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 11 Dec 2012 22:36:29 +0100 Subject: [PATCH 142/146] recommend i3-dmenu-desktop in the default config We might replace dmenu_run in v4.5, depending on the feedback we get for v4.4. --- i3.config | 4 ++++ i3.config.keycodes | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/i3.config b/i3.config index 05ffb8f8..ff6c9dbe 100644 --- a/i3.config +++ b/i3.config @@ -29,6 +29,10 @@ bindsym Mod1+Shift+q kill # start dmenu (a program launcher) bindsym Mod1+d exec dmenu_run +# There also is the (new) i3-dmenu-desktop which only displays applications +# shipping a .desktop file. It is a wrapper around dmenu, so you need that +# installed. +# bindsym Mod1+d exec --no-startup-id i3-dmenu-desktop # change focus bindsym Mod1+j focus left diff --git a/i3.config.keycodes b/i3.config.keycodes index 21229208..0da3d77a 100644 --- a/i3.config.keycodes +++ b/i3.config.keycodes @@ -30,6 +30,10 @@ bindcode $mod+Shift+24 kill # start dmenu (a program launcher) bindcode $mod+40 exec dmenu_run +# There also is the (new) i3-dmenu-desktop which only displays applications +# shipping a .desktop file. It is a wrapper around dmenu, so you need that +# installed. +# bindsym $mod+d exec --no-startup-id i3-dmenu-desktop # change focus bindcode $mod+44 focus left From 5a63b64fb81429da0486713aaae47da945403b9f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 11 Dec 2012 22:43:06 +0100 Subject: [PATCH 143/146] i3-dmenu-desktop: skip .desktop files with Type != Application --- i3-dmenu-desktop | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/i3-dmenu-desktop b/i3-dmenu-desktop index af525ae2..20c7fbea 100755 --- a/i3-dmenu-desktop +++ b/i3-dmenu-desktop @@ -35,7 +35,7 @@ my $result = GetOptions( 'dmenu=s' => \$dmenu_cmd, 'entry-type=s' => \$entry_type, 'version' => sub { - say "dmenu-desktop 1.1 © 2012 Michael Stapelberg"; + say "dmenu-desktop 1.2 © 2012 Michael Stapelberg"; exit 0; }, 'help' => sub { @@ -157,7 +157,8 @@ for my $file (values %desktops) { if ($key =~ /^Name/) { $names{$key} = $value; } elsif ($key eq 'Exec' || - $key eq 'TryExec') { + $key eq 'TryExec' || + $key eq 'Type') { $apps{$base}->{$key} = $value; } elsif ($key eq 'NoDisplay' || $key eq 'Hidden' || @@ -204,6 +205,10 @@ my %choices; for my $app (keys %apps) { my $name = $apps{$app}->{Name}; + # Don’t try to use .desktop files which don’t have Type=application + next if (!exists($apps{$app}->{Type}) || + $apps{$app}->{Type} ne 'Application'); + # Don’t offer apps which have NoDisplay == true or Hidden == true. # See http://wiki.xfce.org/howto/customize-menu#hide_menu_entries # for the difference between NoDisplay and Hidden. @@ -446,7 +451,7 @@ command) and both (type = both). =head1 VERSION -Version 1.1 +Version 1.2 =head1 AUTHOR From fa2da352d165bf7ecb702cdb4192b79128a32c34 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 11 Dec 2012 22:45:34 +0100 Subject: [PATCH 144/146] add release notes for v4.4 --- RELEASE-NOTES-4.4 | 107 ++++++++++++++++++++++++++++++++++++++++++++++ man/asciidoc.conf | 2 +- 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 RELEASE-NOTES-4.4 diff --git a/RELEASE-NOTES-4.4 b/RELEASE-NOTES-4.4 new file mode 100644 index 00000000..ea872619 --- /dev/null +++ b/RELEASE-NOTES-4.4 @@ -0,0 +1,107 @@ + + ┌──────────────────────────────┐ + │ Release notes for i3 v4.4 │ + └──────────────────────────────┘ + +This is the i3 v4.4. This version is considered stable. All users of i3 are +strongly encouraged to upgrade. + +An important under-the-hood change is that we now use the same parser + infrastructure for the configuration file as we do for the commands. This + makes maintenance and contributions easier and lets us finally escape the + insanity that is bison/flex. + + In case there is a bug and your existing config does not work as expected + anymore, try using the --force-old-config-parser-v4.4-only flag when starting + i3 and please report a bug. This option will only be present in v4.4, so if + you don’t report a bug, you are willingly breaking your own config file. + +Apart from that, there have been several little fixes and additions which make + i3 pay more attention to detail, particularly in the floating window area of + the code. See the changes/bugfixes list for more information. + + ┌────────────────────────────┐ + │ Changes in v4.4 │ + └────────────────────────────┘ + + • add i3-dmenu-desktop, a dmenu wrapper which parses application .desktop + files and executes them. + • also use a custom parser for the config file + • i3.xsession.desktop is now standards-compliant + • ipc: you can now subscribe to an event called 'mode' (for binding modes) + • implement "move container to workspace back_and_forth" + • implement delayed urgency hint reset + • make "move workspace number" accept a default workspace name after the + number + • i3bar: allow child to specify start/stop signals to use in hide mode + • i3bar: add "urgent" to protocol, it unhides i3bar when in hide mode + • make parent of urgent containers also urgent + • add descriptive title to split containers (no more "another container") + • click to focus: clicking the root window focuses the relevant workspace + • display appropriate cursors when resizing or moving floating windows + • implement variable border widths for pixel/normal + • Implement moving workspaces as if they’re regular containers + • Maintain relative positioning when moving floating windows between outputs + • Focus the relevant workspace when clicking any container + • docs/ipc: remove unnecessary newline + • docs/ipc: add a warning to use an existing library + • shmlog: remove O_TRUNC flag for shm_open, we truncate on our own + • un-fullscreen as needed when moving fullscreen containers + • improve startup sequence termination conditions + • allow floating cons to be reached using 'focus parent' + • grab keys with all permutations of lock and numlock + • allow workspace contents to be moved if there are only floating children + • allow 'focus ' to move out of non-global fullscreen containers + • exit with a proper error message when there are no outputs available + • skip floating cons in focus and stop them from being split + • focus windows when middle-clicking + • skip floating windows in the focus stack when moving through the tree + • docs/userguide: use $mod consistently + • keycode default config: s/bindcode/bindsym/ + • implement smart popup_during_fullscreen mode + • docs/testsuite: add "installing the dependencies" section + • introduce new command to rename focused workspace + • libi3: use "pango:" prefix instead of "xft:" to avoid confusion + • ipc: add "current" and "old" containers to workspace events + • i3bar: add current binding mode indicator + • resizing floating windows now obeys the minimum/maximum size + • docs/userguide: document new_float option + + ┌────────────────────────────┐ + │ Bugfixes │ + └────────────────────────────┘ + + • Bugfix: get_output_next() now works with non-aligned RandR setups + • Bugfix: close empty workspaces after cross-output move + • Bugfix: fix bottom line of tabbed decoration not continuous + • Bugfix: use correct coordinates for windows which are opened on a newly + created workspace due to assignments + • Bugfix: properly react to windows being unmapped before we can reparent + • Bugfix: send non-floating window with floating parent to scratchpad + • docs/userguide: document how to "un-scratchpad" a window + • Bugfix: don’t crash when dragged floating window closes + • Bugfix: draw h-split indicator at the correct position + • make the resize command honor criteria + • Bugfix: with one ws per output, don’t crash on cross-output moves + • Bugfix: correctly move floating windows to invisible workspaces + cross-output + • Bugfix: set workspace_layout in create_workspace_on_output + • fix fullscreen focus bug and corresponding test flaw + • i3bar: bugfix: don’t send workspace command when at beginning/end of workspace + • Bugfix: force rendering when the parent’s orientation changed + • Bugfix: fix workspace back_and_forth after displaying a scratchpad window + + ┌────────────────────────────┐ + │ Thanks! │ + └────────────────────────────┘ + +Thanks for testing, bugfixes, discussions and everything I forgot go out to: + + Adrien Schildknecht, aksr, bitonic, chrysn, Conley Moorhous, darkraven, Deiz, + Emil Mikulic, Feh, flo, Francesco Mazzoli, hax404, joepd, Kacper Kowalik, + Markus, meaneye, Merovius, Michael Walle, moju, Moritz, noxxun, Oliver + Kiddle, Pauli Ervi, Pavel Löbl, Piotr, pkordy, Quentin Glidic, Sascha Kruse, + Sebastian Ullrich, Simon Elsbrock, slowpoke, strcat, Tblue, Tim, whitequark, + xeen, Yaroslav Molochko + +-- Michael Stapelberg, 2012-12-12 diff --git a/man/asciidoc.conf b/man/asciidoc.conf index 32d2186f..c28f2274 100644 --- a/man/asciidoc.conf +++ b/man/asciidoc.conf @@ -7,7 +7,7 @@ template::[header-declarations] {mantitle} {manvolnum} i3 -4.3 +4.4 i3 Manual From 8c3acbb67fc3749d3e3e974d712e44e193b42c47 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 11 Dec 2012 22:46:35 +0100 Subject: [PATCH 145/146] drop debugging-release-version from docs makefile --- docs/docs.mk | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/docs.mk b/docs/docs.mk index c0daed64..8a522cce 100644 --- a/docs/docs.mk +++ b/docs/docs.mk @@ -5,8 +5,7 @@ ASCIIDOC = asciidoc I3POD2HTML = ./docs/i3-pod2html ASCIIDOC_NOTOC_TARGETS = \ - docs/debugging.html \ - docs/debugging-release-version.html + docs/debugging.html ASCIIDOC_TOC_TARGETS = \ docs/hacking-howto.html \ From 970ced9a0314e7aea3914057c64bef8547399968 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 12 Dec 2012 00:05:51 +0100 Subject: [PATCH 146/146] update debian packaging for 4.4 --- debian/changelog | 6 +++--- debian/rules | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/debian/changelog b/debian/changelog index c4c2681c..dcb966ae 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,8 @@ -i3-wm (4.3.1-0) unstable; urgency=low +i3-wm (4.4-1) experimental; urgency=low - * NOT YET RELEASED + * New upstream release - -- Michael Stapelberg Wed, 19 Sep 2012 18:13:15 +0200 + -- Michael Stapelberg Tue, 11 Dec 2012 22:52:56 +0100 i3-wm (4.3-1) experimental; urgency=low diff --git a/debian/rules b/debian/rules index 011119d0..b119c47e 100755 --- a/debian/rules +++ b/debian/rules @@ -38,7 +38,7 @@ override_dh_auto_build: $(MAKE) -C docs override_dh_installchangelogs: - dh_installchangelogs RELEASE-NOTES-4.3 + dh_installchangelogs RELEASE-NOTES-4.4 override_dh_install: $(MAKE) DESTDIR=$(CURDIR)/debian/i3-wm/ install