From 837bb50826d97b9d5c9cf5a39e486e384762629b Mon Sep 17 00:00:00 2001 From: Simon Kampe Date: Thu, 7 Apr 2011 12:58:45 +0200 Subject: [PATCH 001/333] Parsing workspace name so if the first part is a number, the workspace will get this number. If no number is found it will default to -1. --- src/workspace.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/workspace.c b/src/workspace.c index 2fe96304..d3d2a8e2 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -49,12 +49,11 @@ Con *workspace_get(const char *num, bool *created) { workspace->name = sstrdup(num); /* We set ->num to the number if this workspace’s name consists only of * a positive number. Otherwise it’s a named ws and num will be -1. */ - char *end; - long parsed_num = strtol(num, &end, 10); + + long parsed_num = strtol(num, NULL, 10); if (parsed_num == LONG_MIN || parsed_num == LONG_MAX || - parsed_num < 0 || - (end && *end != '\0')) + parsed_num <= 0) workspace->num = -1; else workspace->num = parsed_num; LOG("num = %d\n", workspace->num); From 9d101d847397c1145e66bd7f5f26076eaf1b63ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20T=C5=99=C3=AD=C5=A1ka?= Date: Thu, 4 Aug 2011 13:24:59 +0200 Subject: [PATCH 002/333] check_for_duplicate_bindings --- src/cfgparse.y | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/cfgparse.y b/src/cfgparse.y index 59b22c6c..226363cc 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -310,6 +310,35 @@ void kill_configerror_nagbar(bool wait_for_it) { waitpid(configerror_pid, NULL, 0); } +/* + check_for_duplicate_bindings is function looking for duplicated + key bindings loaded from configuration file. It goes trought all bindings + and tests if exists same key mapping in bindings visited before. + If exists, message is printed to "stderr" and error warning is set. +*/ +static bool check_for_duplicate_bindings(struct context *context) { + bool retval = true; + Binding *bind, *current; + TAILQ_FOREACH(current, bindings, bindings) { + bind = TAILQ_FIRST(bindings); + // test only bindings visited up to current binding + while ((bind != TAILQ_END(bindings)) && (bind != current)) { + // testing is not case sensitive + if ((strcasecmp(bind->symbol, current->symbol) == 0) && (bind->keycode == current->keycode) && (bind->mods == current->mods)) { + context->has_errors = true; + fprintf(stderr, "Duplicated keybinding in config file: mod%d with key %s", current->mods, current->symbol); + // if keycode is 0, it´s not necessary to print it. + if(current->keycode != 0) + fprintf(stderr, " and keycode %d", current->keycode); + fprintf(stderr, "\n"); + retval = false; + } + bind = TAILQ_NEXT(bind, bindings); + } + } + return retval; +} + void parse_file(const char *f) { SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables); int fd, ret, read_bytes = 0; @@ -463,6 +492,8 @@ void parse_file(const char *f) { exit(1); } + check_for_duplicate_bindings(context); + if (context->has_errors) { start_configerror_nagbar(f); } From 787dd4059f3b990cc11726277fded3426b2aeb49 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 4 Aug 2011 21:25:47 +0200 Subject: [PATCH 003/333] little style fixes for the previous patch --- src/cfgparse.y | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/cfgparse.y b/src/cfgparse.y index 226363cc..ae5a7a73 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -311,24 +311,27 @@ void kill_configerror_nagbar(bool wait_for_it) { } /* - check_for_duplicate_bindings is function looking for duplicated - key bindings loaded from configuration file. It goes trought all bindings - and tests if exists same key mapping in bindings visited before. - If exists, message is printed to "stderr" and error warning is set. -*/ + * Checks for duplicate key bindings (the same keycode or keysym is configured + * more than once). If a duplicate binding is found, a message is printed to + * stderr and the has_errors variable is set to true, which will start + * i3-nagbar. + * + */ static bool check_for_duplicate_bindings(struct context *context) { bool retval = true; Binding *bind, *current; TAILQ_FOREACH(current, bindings, bindings) { bind = TAILQ_FIRST(bindings); - // test only bindings visited up to current binding + /* test only bindings visited up to current binding */ while ((bind != TAILQ_END(bindings)) && (bind != current)) { - // testing is not case sensitive - if ((strcasecmp(bind->symbol, current->symbol) == 0) && (bind->keycode == current->keycode) && (bind->mods == current->mods)) { + /* testing is not case sensitive */ + if ((strcasecmp(bind->symbol, current->symbol) == 0) && + (bind->keycode == current->keycode) && + (bind->mods == current->mods)) { context->has_errors = true; fprintf(stderr, "Duplicated keybinding in config file: mod%d with key %s", current->mods, current->symbol); - // if keycode is 0, it´s not necessary to print it. - if(current->keycode != 0) + /* if keycode is 0, this is a keysym binding */ + if (current->keycode != 0) fprintf(stderr, " and keycode %d", current->keycode); fprintf(stderr, "\n"); retval = false; From 4e350664ae73104b3f4c65ffbdac31e1073cc78f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 4 Aug 2011 21:38:13 +0200 Subject: [PATCH 004/333] Bugfix: Check that ->symbol != NULL before using strcasecmp() --- src/cfgparse.y | 51 +++++++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/src/cfgparse.y b/src/cfgparse.y index ae5a7a73..b4ec94d2 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -317,29 +317,42 @@ void kill_configerror_nagbar(bool wait_for_it) { * i3-nagbar. * */ -static bool check_for_duplicate_bindings(struct context *context) { - bool retval = true; +static void check_for_duplicate_bindings(struct context *context) { Binding *bind, *current; TAILQ_FOREACH(current, bindings, bindings) { - bind = TAILQ_FIRST(bindings); - /* test only bindings visited up to current binding */ - while ((bind != TAILQ_END(bindings)) && (bind != current)) { - /* testing is not case sensitive */ - if ((strcasecmp(bind->symbol, current->symbol) == 0) && - (bind->keycode == current->keycode) && - (bind->mods == current->mods)) { - context->has_errors = true; - fprintf(stderr, "Duplicated keybinding in config file: mod%d with key %s", current->mods, current->symbol); - /* if keycode is 0, this is a keysym binding */ - if (current->keycode != 0) - fprintf(stderr, " and keycode %d", current->keycode); - fprintf(stderr, "\n"); - retval = false; - } - bind = TAILQ_NEXT(bind, bindings); + TAILQ_FOREACH(bind, bindings, bindings) { + /* Abort when we reach the current keybinding, only check the + * bindings before */ + if (bind == current) + break; + + /* Check if one is using keysym while the other is using bindsym. + * If so, skip. */ + /* XXX: It should be checked at a later place (when translating the + * keysym to keycodes) if there are any duplicates */ + if ((bind->symbol == NULL && current->symbol != NULL) || + (bind->symbol != NULL && current->symbol == NULL)) + continue; + + /* If bind is NULL, current has to be NULL, too (see above). + * If the keycodes differ, it can't be a duplicate. */ + if (bind->symbol != NULL && + strcasecmp(bind->symbol, current->symbol) != 0) + continue; + + /* Check if the keycodes or modifiers are different. If so, they + * can't be duplicate */ + if (bind->keycode != current->keycode || + bind->mods != current->mods) + continue; + context->has_errors = true; + fprintf(stderr, "Duplicated keybinding in config file: mod%d with key %s", current->mods, current->symbol); + /* if keycode is 0, this is a keysym binding */ + if (current->keycode != 0) + fprintf(stderr, " and keycode %d", current->keycode); + fprintf(stderr, "\n"); } } - return retval; } void parse_file(const char *f) { From ecc2cae3f7504e65a43e7be6fa56edc0b549f594 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 4 Aug 2011 21:43:55 +0200 Subject: [PATCH 005/333] Bugfix: use ELOG to actually get the error message into the logfile shown by i3-nagbar --- src/cfgparse.y | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/cfgparse.y b/src/cfgparse.y index b4ec94d2..8426de0b 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -346,11 +346,13 @@ static void check_for_duplicate_bindings(struct context *context) { bind->mods != current->mods) continue; context->has_errors = true; - fprintf(stderr, "Duplicated keybinding in config file: mod%d with key %s", current->mods, current->symbol); - /* if keycode is 0, this is a keysym binding */ - if (current->keycode != 0) - fprintf(stderr, " and keycode %d", current->keycode); - fprintf(stderr, "\n"); + if (current->keycode != 0) { + ELOG("Duplicate keybinding in config file:\n modmask %d with keycode %d, command \"%s\"\n", + current->mods, current->keycode, current->command); + } else { + ELOG("Duplicate keybinding in config file:\n modmask %d with keysym %s, command \"%s\"\n", + current->mods, current->symbol, current->command); + } } } } From 35d7ef0ddd6bcac5a46e9c6f3ef4e9e519ea1d37 Mon Sep 17 00:00:00 2001 From: Helgi Kristvin Sigurbjarnarson Date: Sat, 6 Aug 2011 18:23:18 +0000 Subject: [PATCH 006/333] Feature: implement GET_MARKS --- docs/ipc | 13 +++++++++++++ include/i3/ipc.h | 4 ++++ src/ipc.c | 34 ++++++++++++++++++++++++++++++++-- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/docs/ipc b/docs/ipc index 7e713260..2cc5261f 100644 --- a/docs/ipc +++ b/docs/ipc @@ -59,6 +59,9 @@ GET_TREE (4):: Gets the layout tree. i3 uses a tree as data structure which includes every container. The reply will be the JSON-encoded tree (see the reply section). +GET_MARKS (5):: + Gets a list of marks. The reply will be a JSON-encoded list of window marks + (see reply section). So, a typical message could look like this: -------------------------------------------------- @@ -110,6 +113,8 @@ GET_OUTPUTS (3):: Reply to the GET_OUTPUTS message. GET_TREE (4):: Reply to the GET_TREE message. +GET_MARKS (5):: + Reply to the GET_MARKS message. === COMMAND reply @@ -416,6 +421,14 @@ JSON dump: } ] } + + +=== GET_MARKS reply + +The reply consists of a single array of strings for each +window that has a mark. + +If no window has a mark the response will be the empty array []. ------------------------ diff --git a/include/i3/ipc.h b/include/i3/ipc.h index e81f9a15..30b2d304 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -38,6 +38,8 @@ /** Requests the tree layout from i3 */ #define I3_IPC_MESSAGE_TYPE_GET_TREE 4 +/** Request the current defined marks from i3 */ +#define I3_IPC_MESSAGE_TYPE_GET_MARKS 5 /* * Messages from i3 to clients @@ -59,6 +61,8 @@ /** Tree reply type */ #define I3_IPC_REPLY_TYPE_TREE 4 +/** Marks reply type*/ +#define I3_IPC_REPLY_TYPE_MARKS 5 /* * Events from i3 to clients. Events have the first bit set high. diff --git a/src/ipc.c b/src/ipc.c index b2cd482c..9ad89cbb 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -330,6 +330,7 @@ IPC_HANDLER(tree) { y(free); } + /* * Formats the reply message for a GET_WORKSPACES request and sends it to the * client @@ -460,6 +461,34 @@ IPC_HANDLER(get_outputs) { y(free); } +/* + * Formats the reply message for a GET_MARKS request and sends it to the + * client + * + */ +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 + y(array_open); + + Con *con; + TAILQ_FOREACH(con, &all_cons, all_cons) + if (con->mark != NULL) + ystr(con->mark); + + y(array_close); + + const unsigned char *payload; + unsigned int length; + y(get_buf, &payload, &length); + + ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_MARKS, length); + y(free); +} + /* * Callback for the YAJL parser (will be called when a string is parsed). * @@ -547,12 +576,13 @@ IPC_HANDLER(subscribe) { /* The index of each callback function corresponds to the numeric * value of the message type (see include/i3/ipc.h) */ -handler_t handlers[5] = { +handler_t handlers[6] = { handle_command, handle_get_workspaces, handle_subscribe, handle_get_outputs, - handle_tree + handle_tree, + handle_get_marks }; /* From 96ed68de160f902e183b89a5075b4506b7716966 Mon Sep 17 00:00:00 2001 From: Helgi Kristvin Sigurbjarnarson Date: Sun, 7 Aug 2011 17:03:10 +0000 Subject: [PATCH 007/333] Add support for get_marks in i3-msg --- i3-msg/main.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/i3-msg/main.c b/i3-msg/main.c index 630a345d..d8195e08 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -180,9 +180,11 @@ int main(int argc, char *argv[]) { message_type = I3_IPC_MESSAGE_TYPE_GET_OUTPUTS; else if (strcasecmp(optarg, "get_tree") == 0) message_type = I3_IPC_MESSAGE_TYPE_GET_TREE; + else if (strcasecmp(optarg, "get_marks") == 0) + message_type = I3_IPC_MESSAGE_TYPE_GET_MARKS; else { printf("Unknown message type\n"); - printf("Known types: command, get_workspaces, get_outputs, get_tree\n"); + printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks\n"); exit(EXIT_FAILURE); } } else if (o == 'q') { From 3cc22dcbe405c4b5c5d5f2cb04a8b7dc74961990 Mon Sep 17 00:00:00 2001 From: Helgi Kristvin Sigurbjarnarson Date: Sun, 7 Aug 2011 16:42:23 +0000 Subject: [PATCH 008/333] Preserve marks between restarts. --- src/ipc.c | 5 +++++ src/load_layout.c | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/src/ipc.c b/src/ipc.c index 9ad89cbb..a83e5fe4 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -200,6 +200,11 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { ystr("urgent"); y(bool, con->urgent); + if (con->mark != NULL) { + ystr("mark"); + ystr(con->mark); + } + ystr("focused"); y(bool, (con == focused)); diff --git a/src/load_layout.c b/src/load_layout.c index b61a0e5c..37322c4e 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -146,6 +146,10 @@ static int json_string(void *ctx, const unsigned char *val, unsigned int len) { json_node->layout = L_OUTPUT; else LOG("Unhandled \"layout\": %s\n", buf); free(buf); + } else if (strcasecmp(last_key, "mark") == 0) { + char *buf = NULL; + asprintf(&buf, "%.*s", (int)len, val); + json_node->mark = buf; } } return 1; From dab3a0b85a7c76f029c97d40c87c108c912df69f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 9 Aug 2011 08:19:48 +0200 Subject: [PATCH 009/333] tests: add testcase for the GET_MARKS ipc request --- testcases/t/73-get-marks.t | 63 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 testcases/t/73-get-marks.t diff --git a/testcases/t/73-get-marks.t b/testcases/t/73-get-marks.t new file mode 100644 index 00000000..86481747 --- /dev/null +++ b/testcases/t/73-get-marks.t @@ -0,0 +1,63 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# checks if the IPC message type get_marks works correctly +# +use i3test; + +# TODO: this will be available in AnyEvent::I3 soon +sub get_marks { + my $i3 = i3(get_socket_path()); + $i3->connect->recv; + my $cv = AnyEvent->condvar; + my $msg = $i3->message(5); + my $t; + $msg->cb(sub { + my ($_cv) = @_; + $cv->send($_cv->recv); + }); + $t = AnyEvent->timer(after => 2, cb => sub { + $cv->croak('timeout while waiting for the marks'); + }); + return $cv->recv; +} + +############################################################## +# 1: check that get_marks returns no marks yet +############################################################## + +my $tmp = fresh_workspace; + +my $marks = get_marks(); +cmp_deeply($marks, [], 'no marks set so far'); + +############################################################## +# 2: check that setting a mark is reflected in the get_marks reply +############################################################## + +cmd 'open'; +cmd 'mark foo'; + +cmp_deeply(get_marks(), [ 'foo' ], 'mark foo set'); + +############################################################## +# 3: check that the mark is gone after killing the container +############################################################## + +cmd 'kill'; + +cmp_deeply(get_marks(), [ ], 'mark gone'); + +############################################################## +# 4: check that duplicate marks are included twice in the get_marks reply +############################################################## + +cmd 'open'; +cmd 'mark bar'; + +cmd 'open'; +cmd 'mark bar'; + +cmp_deeply(get_marks(), [ 'bar', 'bar' ], 'duplicate mark found twice'); + +done_testing; From fadf9088db875f1c04de4dc00ec3448f7dc408e6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 9 Aug 2011 08:22:58 +0200 Subject: [PATCH 010/333] docs/ipc: make the GET_MARKS docs a little more precise --- docs/ipc | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/ipc b/docs/ipc index 2cc5261f..4093ffce 100644 --- a/docs/ipc +++ b/docs/ipc @@ -60,8 +60,9 @@ GET_TREE (4):: every container. The reply will be the JSON-encoded tree (see the reply section). GET_MARKS (5):: - Gets a list of marks. The reply will be a JSON-encoded list of window marks - (see reply section). + Gets a list of marks (identifiers for containers to easily jump to them + later). The reply will be a JSON-encoded list of window marks (see + reply section). So, a typical message could look like this: -------------------------------------------------- @@ -425,8 +426,10 @@ JSON dump: === GET_MARKS reply -The reply consists of a single array of strings for each -window that has a mark. +The reply consists of a single array of strings for each container that has a +mark. The order of that array is undefined. If more than one container has the +same mark, it will be represented multiple times in the reply (the array +contents are not unique). If no window has a mark the response will be the empty array []. ------------------------ From cd5ebc2dcac207665dd2f4ca6b388d3be63cb5ad Mon Sep 17 00:00:00 2001 From: Mateusz Poszwa Date: Mon, 8 Aug 2011 20:49:49 +0200 Subject: [PATCH 011/333] src/manage.c: properly set automatic flag when calling floating_enable() --- src/manage.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/manage.c b/src/manage.c index 68c91e72..18e94dd2 100644 --- a/src/manage.c +++ b/src/manage.c @@ -321,7 +321,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki if (want_floating) { DLOG("geometry = %d x %d\n", nc->geometry.width, nc->geometry.height); - floating_enable(nc, false); + floating_enable(nc, true); } /* to avoid getting an UnmapNotify event due to reparenting, we temporarily From 42db9de7ec1c0b82ced3fa08abd238c5ba12d844 Mon Sep 17 00:00:00 2001 From: Mateusz Poszwa Date: Mon, 8 Aug 2011 21:51:21 +0200 Subject: [PATCH 012/333] Add new_float config option. This option sets the default border style for containers automatically put into floating mode. Fixes #264 --- include/config.h | 3 +++ src/cfgparse.l | 1 + src/cfgparse.y | 11 +++++++++++ src/config.c | 1 + src/floating.c | 4 ++++ 5 files changed, 20 insertions(+) diff --git a/include/config.h b/include/config.h index 1021a612..3234b91e 100644 --- a/include/config.h +++ b/include/config.h @@ -126,6 +126,9 @@ struct Config { /** The default border style for new windows. */ border_style_t default_border; + /** The default border style for new floating windows. */ + border_style_t default_floating_border; + /** The modifier which needs to be pressed in combination with your mouse * buttons to do things with floating windows (move, resize) */ uint32_t floating_modifier; diff --git a/src/cfgparse.l b/src/cfgparse.l index 4cf1a1c3..e29f6efc 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -118,6 +118,7 @@ vertical { return TOK_VERT; } auto { return TOK_AUTO; } workspace_layout { return TOK_WORKSPACE_LAYOUT; } new_window { return TOKNEWWINDOW; } +new_float { return TOKNEWFLOAT; } normal { return TOK_NORMAL; } none { return TOK_NONE; } 1pixel { return TOK_1PIXEL; } diff --git a/src/cfgparse.y b/src/cfgparse.y index 68173757..4a4c26ca 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -579,6 +579,7 @@ void parse_file(const char *f) { %token TOK_AUTO "auto" %token TOK_WORKSPACE_LAYOUT "workspace_layout" %token TOKNEWWINDOW "new_window" +%token TOKNEWFLOAT "new_float" %token TOK_NORMAL "normal" %token TOK_NONE "none" %token TOK_1PIXEL "1pixel" @@ -610,6 +611,7 @@ void parse_file(const char *f) { %type layout_mode %type border_style %type new_window +%type new_float %type colorpixel %type bool %type popup_setting @@ -634,6 +636,7 @@ line: | orientation | workspace_layout | new_window + | new_float | focus_follows_mouse | force_focus_wrapping | workspace_bar @@ -924,6 +927,14 @@ new_window: } ; +new_float: + TOKNEWFLOAT border_style + { + DLOG("new floating windows should start with border style %d\n", $2); + config.default_floating_border = $2; + } + ; + border_style: TOK_NORMAL { $$ = BS_NORMAL; } | TOK_NONE { $$ = BS_NONE; } diff --git a/src/config.c b/src/config.c index 14fc6e02..b3a20442 100644 --- a/src/config.c +++ b/src/config.c @@ -333,6 +333,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, INIT_COLOR(config.bar.urgent, "#2f343a", "#900000", "#ffffff"); config.default_border = BS_NORMAL; + config.default_floating_border = BS_NORMAL; /* Set default_orientation to NO_ORIENTATION for auto orientation. */ config.default_orientation = NO_ORIENTATION; diff --git a/src/floating.c b/src/floating.c index b394f315..2ba08330 100644 --- a/src/floating.c +++ b/src/floating.c @@ -129,6 +129,10 @@ void floating_enable(Con *con, bool automatic) { con->percent = 1.0; con->floating = FLOATING_USER_ON; + /* 4: set the border style as specified with new_float */ + if (automatic) + con->border_style = config.default_floating_border; + /* Some clients (like GIMP’s color picker window) get mapped * to (0, 0), so we push them to a reasonable position * (centered over their leader) */ From 74687fa97c04cb1205bbc88cbd066bbe5fff8c0a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 9 Aug 2011 09:12:44 +0200 Subject: [PATCH 013/333] tests: add t/74-border-config which checks new_window and new_float --- testcases/t/74-border-config.t | 140 +++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 testcases/t/74-border-config.t diff --git a/testcases/t/74-border-config.t b/testcases/t/74-border-config.t new file mode 100644 index 00000000..c8a4d73d --- /dev/null +++ b/testcases/t/74-border-config.t @@ -0,0 +1,140 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3 +# +# Tests the new_window and new_float config option. +# + +use i3test; +use X11::XCB qw(:all); +use X11::XCB::Connection; + +my $x = X11::XCB::Connection->new; + +##################################################################### +# 1: check that new windows start with 'normal' border unless configured +# otherwise +##################################################################### + +my $config = <{border}, 'normal', 'border normal by default'); + +exit_gracefully($process->pid); + +##################################################################### +# 2: check that new tiling windows start with '1pixel' border when +# configured +##################################################################### + +$config = <{border}, '1pixel', 'border normal by default'); + +exit_gracefully($process->pid); + +##################################################################### +# 3: check that new floating windows start with 'normal' border unless +# configured otherwise +##################################################################### + +$config = <root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30], + background_color => '#C0C0C0', + # replace the type with 'utility' as soon as the coercion works again in X11::XCB + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), +); + +$first->map; + +sleep 0.25; + +my $wscontent = get_ws($tmp); +my @floating = @{$wscontent->{floating_nodes}}; +ok(@floating == 1, 'one floating container opened'); +my $floatingcon = $floating[0]; +is($floatingcon->{nodes}->[0]->{border}, 'normal', 'border normal by default'); + +exit_gracefully($process->pid); + +##################################################################### +# 4: check that new floating windows start with '1pixel' border when +# configured +##################################################################### + +$config = <root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30], + background_color => '#C0C0C0', + # replace the type with 'utility' as soon as the coercion works again in X11::XCB + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), +); + +$first->map; + +sleep 0.25; + +$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'); + +exit_gracefully($process->pid); + +done_testing; From 10f871b57b814d4415e56aa1d6a950b51031638e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 10 Aug 2011 15:55:27 +0200 Subject: [PATCH 014/333] implement "socket activation": passing IPC sockets to i3 --- include/sd-daemon.h | 265 +++++++++++++++++++++++++++ src/ipc.c | 2 +- src/main.c | 17 ++ src/sd-daemon.c | 436 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 719 insertions(+), 1 deletion(-) create mode 100644 include/sd-daemon.h create mode 100644 src/sd-daemon.c diff --git a/include/sd-daemon.h b/include/sd-daemon.h new file mode 100644 index 00000000..4b853a15 --- /dev/null +++ b/include/sd-daemon.h @@ -0,0 +1,265 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef foosddaemonhfoo +#define foosddaemonhfoo + +/*** + Copyright 2010 Lennart Poettering + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +***/ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + Reference implementation of a few systemd related interfaces for + writing daemons. These interfaces are trivial to implement. To + simplify porting we provide this reference implementation. + Applications are welcome to reimplement the algorithms described + here if they do not want to include these two source files. + + The following functionality is provided: + + - Support for logging with log levels on stderr + - File descriptor passing for socket-based activation + - Daemon startup and status notification + - Detection of systemd boots + + You may compile this with -DDISABLE_SYSTEMD to disable systemd + support. This makes all those calls NOPs that are directly related to + systemd (i.e. only sd_is_xxx() will stay useful). + + Since this is drop-in code we don't want any of our symbols to be + exported in any case. Hence we declare hidden visibility for all of + them. + + You may find an up-to-date version of these source files online: + + http://cgit.freedesktop.org/systemd/plain/src/sd-daemon.h + http://cgit.freedesktop.org/systemd/plain/src/sd-daemon.c + + This should compile on non-Linux systems, too, but with the + exception of the sd_is_xxx() calls all functions will become NOPs. + + See sd-daemon(7) for more information. +*/ + +#ifndef _sd_printf_attr_ +#if __GNUC__ >= 4 +#define _sd_printf_attr_(a,b) __attribute__ ((format (printf, a, b))) +#else +#define _sd_printf_attr_(a,b) +#endif +#endif + +#ifndef _sd_hidden_ +#if (__GNUC__ >= 4) && !defined(SD_EXPORT_SYMBOLS) +#define _sd_hidden_ __attribute__ ((visibility("hidden"))) +#else +#define _sd_hidden_ +#endif +#endif + +/* + Log levels for usage on stderr: + + fprintf(stderr, SD_NOTICE "Hello World!\n"); + + This is similar to printk() usage in the kernel. +*/ +#define SD_EMERG "<0>" /* system is unusable */ +#define SD_ALERT "<1>" /* action must be taken immediately */ +#define SD_CRIT "<2>" /* critical conditions */ +#define SD_ERR "<3>" /* error conditions */ +#define SD_WARNING "<4>" /* warning conditions */ +#define SD_NOTICE "<5>" /* normal but significant condition */ +#define SD_INFO "<6>" /* informational */ +#define SD_DEBUG "<7>" /* debug-level messages */ + +/* The first passed file descriptor is fd 3 */ +#define SD_LISTEN_FDS_START 3 + +/* + Returns how many file descriptors have been passed, or a negative + errno code on failure. Optionally, removes the $LISTEN_FDS and + $LISTEN_PID file descriptors from the environment (recommended, but + problematic in threaded environments). If r is the return value of + this function you'll find the file descriptors passed as fds + SD_LISTEN_FDS_START to SD_LISTEN_FDS_START+r-1. Returns a negative + errno style error code on failure. This function call ensures that + the FD_CLOEXEC flag is set for the passed file descriptors, to make + sure they are not passed on to child processes. If FD_CLOEXEC shall + not be set, the caller needs to unset it after this call for all file + descriptors that are used. + + See sd_listen_fds(3) for more information. +*/ +int sd_listen_fds(int unset_environment) _sd_hidden_; + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is a FIFO in the file system stored under the + specified path, 0 otherwise. If path is NULL a path name check will + not be done and the call only verifies if the file descriptor + refers to a FIFO. Returns a negative errno style error code on + failure. + + See sd_is_fifo(3) for more information. +*/ +int sd_is_fifo(int fd, const char *path) _sd_hidden_; + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is a socket of the specified family (AF_INET, + ...) and type (SOCK_DGRAM, SOCK_STREAM, ...), 0 otherwise. If + family is 0 a socket family check will not be done. If type is 0 a + socket type check will not be done and the call only verifies if + the file descriptor refers to a socket. If listening is > 0 it is + verified that the socket is in listening mode. (i.e. listen() has + been called) If listening is == 0 it is verified that the socket is + not in listening mode. If listening is < 0 no listening mode check + is done. Returns a negative errno style error code on failure. + + See sd_is_socket(3) for more information. +*/ +int sd_is_socket(int fd, int family, int type, int listening) _sd_hidden_; + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is an Internet socket, of the specified family + (either AF_INET or AF_INET6) and the specified type (SOCK_DGRAM, + SOCK_STREAM, ...), 0 otherwise. If version is 0 a protocol version + check is not done. If type is 0 a socket type check will not be + done. If port is 0 a socket port check will not be done. The + listening flag is used the same way as in sd_is_socket(). Returns a + negative errno style error code on failure. + + See sd_is_socket_inet(3) for more information. +*/ +int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) _sd_hidden_; + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is an AF_UNIX socket of the specified type + (SOCK_DGRAM, SOCK_STREAM, ...) and path, 0 otherwise. If type is 0 + a socket type check will not be done. If path is NULL a socket path + check will not be done. For normal AF_UNIX sockets set length to + 0. For abstract namespace sockets set length to the length of the + socket name (including the initial 0 byte), and pass the full + socket path in path (including the initial 0 byte). The listening + flag is used the same way as in sd_is_socket(). Returns a negative + errno style error code on failure. + + See sd_is_socket_unix(3) for more information. +*/ +int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) _sd_hidden_; + +/* + Informs systemd about changed daemon state. This takes a number of + newline separated environment-style variable assignments in a + string. The following variables are known: + + READY=1 Tells systemd that daemon startup is finished (only + relevant for services of Type=notify). The passed + argument is a boolean "1" or "0". Since there is + little value in signaling non-readiness the only + value daemons should send is "READY=1". + + STATUS=... Passes a single-line status string back to systemd + that describes the daemon state. This is free-from + and can be used for various purposes: general state + feedback, fsck-like programs could pass completion + percentages and failing programs could pass a human + readable error message. Example: "STATUS=Completed + 66% of file system check..." + + ERRNO=... If a daemon fails, the errno-style error code, + formatted as string. Example: "ERRNO=2" for ENOENT. + + BUSERROR=... If a daemon fails, the D-Bus error-style error + code. Example: "BUSERROR=org.freedesktop.DBus.Error.TimedOut" + + MAINPID=... The main pid of a daemon, in case systemd did not + fork off the process itself. Example: "MAINPID=4711" + + Daemons can choose to send additional variables. However, it is + recommended to prefix variable names not listed above with X_. + + Returns a negative errno-style error code on failure. Returns > 0 + if systemd could be notified, 0 if it couldn't possibly because + systemd is not running. + + Example: When a daemon finished starting up, it could issue this + call to notify systemd about it: + + sd_notify(0, "READY=1"); + + See sd_notifyf() for more complete examples. + + See sd_notify(3) for more information. +*/ +int sd_notify(int unset_environment, const char *state) _sd_hidden_; + +/* + Similar to sd_notify() but takes a format string. + + Example 1: A daemon could send the following after initialization: + + sd_notifyf(0, "READY=1\n" + "STATUS=Processing requests...\n" + "MAINPID=%lu", + (unsigned long) getpid()); + + Example 2: A daemon could send the following shortly before + exiting, on failure: + + sd_notifyf(0, "STATUS=Failed to start up: %s\n" + "ERRNO=%i", + strerror(errno), + errno); + + See sd_notifyf(3) for more information. +*/ +int sd_notifyf(int unset_environment, const char *format, ...) _sd_printf_attr_(2,3) _sd_hidden_; + +/* + Returns > 0 if the system was booted with systemd. Returns < 0 on + error. Returns 0 if the system was not booted with systemd. Note + that all of the functions above handle non-systemd boots just + fine. You should NOT protect them with a call to this function. Also + note that this function checks whether the system, not the user + session is controlled by systemd. However the functions above work + for both user and system services. + + See sd_booted(3) for more information. +*/ +int sd_booted(void) _sd_hidden_; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/ipc.c b/src/ipc.c index d798ffa0..4ea2e19a 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -678,7 +678,7 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) { ev_io_init(package, ipc_receive_message, client, EV_READ); ev_io_start(EV_A_ package); - DLOG("IPC: new client connected\n"); + DLOG("IPC: new client connected on fd %d\n", w->fd); ipc_client *new = scalloc(sizeof(ipc_client)); new->fd = client; diff --git a/src/main.c b/src/main.c index 69598b35..1307d015 100644 --- a/src/main.c +++ b/src/main.c @@ -5,6 +5,8 @@ #include #include "all.h" +#include "sd-daemon.h" + static int xkb_event_base; int xkb_current_group; @@ -441,6 +443,21 @@ int main(int argc, char *argv[]) { ev_io_start(main_loop, ipc_io); } + /* Also handle the UNIX domain sockets passed via socket activation */ + int fds = sd_listen_fds(1); + if (fds < 0) + ELOG("socket activation: Error in sd_listen_fds\n"); + else if (fds == 0) + DLOG("socket activation: no sockets passed\n"); + else { + for (int fd = SD_LISTEN_FDS_START; fd < (SD_LISTEN_FDS_START + fds); fd++) { + DLOG("socket activation: also listening on fd %d\n", fd); + struct ev_io *ipc_io = scalloc(sizeof(struct ev_io)); + ev_io_init(ipc_io, ipc_new_client, fd, EV_READ); + ev_io_start(main_loop, ipc_io); + } + } + /* Set up i3 specific atoms like I3_SOCKET_PATH and I3_CONFIG_PATH */ x_set_i3_atoms(); diff --git a/src/sd-daemon.c b/src/sd-daemon.c new file mode 100644 index 00000000..6d1eebff --- /dev/null +++ b/src/sd-daemon.c @@ -0,0 +1,436 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + Copyright 2010 Lennart Poettering + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +***/ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sd-daemon.h" + +int sd_listen_fds(int unset_environment) { + +#if defined(DISABLE_SYSTEMD) || !defined(__linux__) + return 0; +#else + int r, fd; + const char *e; + char *p = NULL; + unsigned long l; + + if (!(e = getenv("LISTEN_PID"))) { + r = 0; + goto finish; + } + + errno = 0; + l = strtoul(e, &p, 10); + + if (errno != 0) { + r = -errno; + goto finish; + } + + if (!p || *p || l <= 0) { + r = -EINVAL; + goto finish; + } + + /* Is this for us? */ + if (getpid() != (pid_t) l) { + r = 0; + goto finish; + } + + if (!(e = getenv("LISTEN_FDS"))) { + r = 0; + goto finish; + } + + errno = 0; + l = strtoul(e, &p, 10); + + if (errno != 0) { + r = -errno; + goto finish; + } + + if (!p || *p) { + r = -EINVAL; + goto finish; + } + + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + (int) l; fd ++) { + int flags; + + if ((flags = fcntl(fd, F_GETFD)) < 0) { + r = -errno; + goto finish; + } + + if (flags & FD_CLOEXEC) + continue; + + if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) { + r = -errno; + goto finish; + } + } + + r = (int) l; + +finish: + if (unset_environment) { + unsetenv("LISTEN_PID"); + unsetenv("LISTEN_FDS"); + } + + return r; +#endif +} + +int sd_is_fifo(int fd, const char *path) { + struct stat st_fd; + + if (fd < 0) + return -EINVAL; + + memset(&st_fd, 0, sizeof(st_fd)); + if (fstat(fd, &st_fd) < 0) + return -errno; + + if (!S_ISFIFO(st_fd.st_mode)) + return 0; + + if (path) { + struct stat st_path; + + memset(&st_path, 0, sizeof(st_path)); + if (stat(path, &st_path) < 0) { + + if (errno == ENOENT || errno == ENOTDIR) + return 0; + + return -errno; + } + + return + st_path.st_dev == st_fd.st_dev && + st_path.st_ino == st_fd.st_ino; + } + + return 1; +} + +static int sd_is_socket_internal(int fd, int type, int listening) { + struct stat st_fd; + + if (fd < 0 || type < 0) + return -EINVAL; + + if (fstat(fd, &st_fd) < 0) + return -errno; + + if (!S_ISSOCK(st_fd.st_mode)) + return 0; + + if (type != 0) { + int other_type = 0; + socklen_t l = sizeof(other_type); + + if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &other_type, &l) < 0) + return -errno; + + if (l != sizeof(other_type)) + return -EINVAL; + + if (other_type != type) + return 0; + } + + if (listening >= 0) { + int accepting = 0; + socklen_t l = sizeof(accepting); + + if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &accepting, &l) < 0) + return -errno; + + if (l != sizeof(accepting)) + return -EINVAL; + + if (!accepting != !listening) + return 0; + } + + return 1; +} + +union sockaddr_union { + struct sockaddr sa; + struct sockaddr_in in4; + struct sockaddr_in6 in6; + struct sockaddr_un un; + struct sockaddr_storage storage; +}; + +int sd_is_socket(int fd, int family, int type, int listening) { + int r; + + if (family < 0) + return -EINVAL; + + if ((r = sd_is_socket_internal(fd, type, listening)) <= 0) + return r; + + if (family > 0) { + union sockaddr_union sockaddr; + socklen_t l; + + memset(&sockaddr, 0, sizeof(sockaddr)); + l = sizeof(sockaddr); + + if (getsockname(fd, &sockaddr.sa, &l) < 0) + return -errno; + + if (l < sizeof(sa_family_t)) + return -EINVAL; + + return sockaddr.sa.sa_family == family; + } + + return 1; +} + +int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) { + union sockaddr_union sockaddr; + socklen_t l; + int r; + + if (family != 0 && family != AF_INET && family != AF_INET6) + return -EINVAL; + + if ((r = sd_is_socket_internal(fd, type, listening)) <= 0) + return r; + + memset(&sockaddr, 0, sizeof(sockaddr)); + l = sizeof(sockaddr); + + if (getsockname(fd, &sockaddr.sa, &l) < 0) + return -errno; + + if (l < sizeof(sa_family_t)) + return -EINVAL; + + if (sockaddr.sa.sa_family != AF_INET && + sockaddr.sa.sa_family != AF_INET6) + return 0; + + if (family > 0) + if (sockaddr.sa.sa_family != family) + return 0; + + if (port > 0) { + if (sockaddr.sa.sa_family == AF_INET) { + if (l < sizeof(struct sockaddr_in)) + return -EINVAL; + + return htons(port) == sockaddr.in4.sin_port; + } else { + if (l < sizeof(struct sockaddr_in6)) + return -EINVAL; + + return htons(port) == sockaddr.in6.sin6_port; + } + } + + return 1; +} + +int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) { + union sockaddr_union sockaddr; + socklen_t l; + int r; + + if ((r = sd_is_socket_internal(fd, type, listening)) <= 0) + return r; + + memset(&sockaddr, 0, sizeof(sockaddr)); + l = sizeof(sockaddr); + + if (getsockname(fd, &sockaddr.sa, &l) < 0) + return -errno; + + if (l < sizeof(sa_family_t)) + return -EINVAL; + + if (sockaddr.sa.sa_family != AF_UNIX) + return 0; + + if (path) { + if (length <= 0) + length = strlen(path); + + if (length <= 0) + /* Unnamed socket */ + return l == offsetof(struct sockaddr_un, sun_path); + + if (path[0]) + /* Normal path socket */ + return + (l >= offsetof(struct sockaddr_un, sun_path) + length + 1) && + memcmp(path, sockaddr.un.sun_path, length+1) == 0; + else + /* Abstract namespace socket */ + return + (l == offsetof(struct sockaddr_un, sun_path) + length) && + memcmp(path, sockaddr.un.sun_path, length) == 0; + } + + return 1; +} + +int sd_notify(int unset_environment, const char *state) { +#if defined(DISABLE_SYSTEMD) || !defined(__linux__) || !defined(SOCK_CLOEXEC) + return 0; +#else + int fd = -1, r; + struct msghdr msghdr; + struct iovec iovec; + union sockaddr_union sockaddr; + const char *e; + + if (!state) { + r = -EINVAL; + goto finish; + } + + if (!(e = getenv("NOTIFY_SOCKET"))) + return 0; + + /* Must be an abstract socket, or an absolute path */ + if ((e[0] != '@' && e[0] != '/') || e[1] == 0) { + r = -EINVAL; + goto finish; + } + + if ((fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) { + r = -errno; + goto finish; + } + + memset(&sockaddr, 0, sizeof(sockaddr)); + sockaddr.sa.sa_family = AF_UNIX; + strncpy(sockaddr.un.sun_path, e, sizeof(sockaddr.un.sun_path)); + + if (sockaddr.un.sun_path[0] == '@') + sockaddr.un.sun_path[0] = 0; + + memset(&iovec, 0, sizeof(iovec)); + iovec.iov_base = (char*) state; + iovec.iov_len = strlen(state); + + memset(&msghdr, 0, sizeof(msghdr)); + msghdr.msg_name = &sockaddr; + msghdr.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(e); + + if (msghdr.msg_namelen > sizeof(struct sockaddr_un)) + msghdr.msg_namelen = sizeof(struct sockaddr_un); + + msghdr.msg_iov = &iovec; + msghdr.msg_iovlen = 1; + + if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) < 0) { + r = -errno; + goto finish; + } + + r = 1; + +finish: + if (unset_environment) + unsetenv("NOTIFY_SOCKET"); + + if (fd >= 0) + close(fd); + + return r; +#endif +} + +int sd_notifyf(int unset_environment, const char *format, ...) { +#if defined(DISABLE_SYSTEMD) || !defined(__linux__) + return 0; +#else + va_list ap; + char *p = NULL; + int r; + + va_start(ap, format); + r = vasprintf(&p, format, ap); + va_end(ap); + + if (r < 0 || !p) + return -ENOMEM; + + r = sd_notify(unset_environment, p); + free(p); + + return r; +#endif +} + +int sd_booted(void) { +#if defined(DISABLE_SYSTEMD) || !defined(__linux__) + return 0; +#else + + struct stat a, b; + + /* We simply test whether the systemd cgroup hierarchy is + * mounted */ + + if (lstat("/sys/fs/cgroup", &a) < 0) + return 0; + + if (lstat("/sys/fs/cgroup/systemd", &b) < 0) + return 0; + + return a.st_dev != b.st_dev; +#endif +} From 5524785877354ac4f170b8eb903bdc75e9af6599 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 10 Aug 2011 15:56:39 +0200 Subject: [PATCH 015/333] testsuite: eliminate sleep, wait until i3 replies via IPC --- testcases/complete-run.pl | 196 ++++++++++++++++++++++++++++---------- 1 file changed, 144 insertions(+), 52 deletions(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index c05d6e5b..6f00877c 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -29,6 +29,18 @@ use Try::Tiny; use Getopt::Long; use Time::HiRes qw(sleep); use X11::XCB::Connection; +use IO::Socket::UNIX; # core +use POSIX; # core +use AnyEvent::Handle; + +# open a file so that we get file descriptor 3. we will later close it in the +# child and dup() the listening socket file descriptor to 3 to pass it to i3 +open(my $reserved, '<', '/dev/null'); +if (fileno($reserved) != 3) { + warn "Socket file descriptor is not 3."; + warn "Please don't start this script within a subshell of vim or something."; + exit 1; +} # install a dummy CHLD handler to overwrite the CHLD handler of AnyEvent / EV # XXX: we could maybe also use a different loop than the default loop in EV? @@ -72,7 +84,6 @@ for my $display (@displays) { }; } -my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler"; my $config = slurp('i3-test.config'); # 1: get a list of all testcases @@ -116,21 +127,90 @@ take_job($_) for @wdisplays; # sub take_job { my ($display) = @_; + + my $test = shift @testfiles; + return unless $test; + my $dont_start = (slurp($test) =~ /# !NO_I3_INSTANCE!/); + my $logpath = "$outdir/i3-log-for-" . basename($test); + my ($fh, $tmpfile) = tempfile(); say $fh $config; say $fh "ipc-socket /tmp/nested-$display"; close($fh); - my $test = shift @testfiles; - return unless $test; - my $logpath = "$outdir/i3-log-for-" . basename($test); - my $cmd = "export DISPLAY=$display; exec $i3cmd -c $tmpfile >$logpath 2>&1"; - my $dont_start = (slurp($test) =~ /# !NO_I3_INSTANCE!/); + my $activate_cv = AnyEvent->condvar; + my $start_i3 = sub { + # remove the old unix socket + unlink("/tmp/nested-$display-activation"); - my $process = Proc::Background->new($cmd) unless $dont_start; - say "[$display] Running $test with logfile $logpath"; + # pass all file descriptors up to three to the children. + # we need to set this flag before opening the socket. + open(my $fdtest, '<', '/dev/null'); + $^F = fileno($fdtest); + close($fdtest); + my $socket = IO::Socket::UNIX->new( + Listen => 1, + Local => "/tmp/nested-$display-activation", + ); + + my $pid = fork; + if (!defined($pid)) { + die "could not fork()"; + } + say "pid = $pid"; + if ($pid == 0) { + say "child!"; + $ENV{LISTEN_PID} = $$; + $ENV{LISTEN_FDS} = 1; + $ENV{DISPLAY} = $display; + $^F = 3; + + say "fileno is " . fileno($socket); + close($reserved); + POSIX::dup2(fileno($socket), 3); + + # now execute i3 + my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler"; + my $cmd = "exec $i3cmd -c $tmpfile >$logpath 2>&1"; + exec "/bin/sh", '-c', $cmd; + + # if we are still here, i3 could not be found or exec failed. bail out. + exit 1; + } + + my $child_watcher; + $child_watcher = AnyEvent->child(pid => $pid, cb => sub { + say "child died. pid = $pid"; + undef $child_watcher; + }); + + # close the socket, the child process should be the only one which keeps a file + # descriptor on the listening socket. + $socket->close; + + # We now connect (will succeed immediately) and send a request afterwards. + # As soon as the reply is there, i3 is considered ready. + my $cl = IO::Socket::UNIX->new(Peer => "/tmp/nested-$display-activation"); + my $hdl; + $hdl = AnyEvent::Handle->new(fh => $cl, on_error => sub { $activate_cv->send(0) }); + + # send a get_tree message without payload + $hdl->push_write('i3-ipc' . pack("LL", 0, 4)); + + # wait for the reply + $hdl->push_read(chunk => 1, => sub { + my ($h, $line) = @_; + say "read something from i3"; + $activate_cv->send(1); + undef $hdl; + }); + + return $pid; + }; + + my $pid; + $pid = $start_i3->() unless $dont_start; - sleep 0.5; my $kill_i3 = sub { # Don’t bother killing i3 when we haven’t started it return if $dont_start; @@ -148,53 +228,65 @@ sub take_job { } say "[$display] killing i3"; - kill(9, $process->pid) or die "could not kill i3"; + kill(9, $pid) or die "could not kill i3"; }; - my $output; - my $parser = TAP::Parser->new({ - exec => [ 'sh', '-c', "DISPLAY=$display /usr/bin/perl -It/lib $test" ], - spool => IO::Scalar->new(\$output), - merge => 1, + # This will be called as soon as i3 is running and answered to our + # IPC request + $activate_cv->cb(sub { + say "cb"; + my ($status) = $activate_cv->recv; + say "complete-run: status = $status"; + + say "[$display] Running $test with logfile $logpath"; + + my $output; + my $parser = TAP::Parser->new({ + exec => [ 'sh', '-c', "DISPLAY=$display /usr/bin/perl -It/lib $test" ], + spool => IO::Scalar->new(\$output), + merge => 1, + }); + + my @watchers; + my ($stdout, $stderr) = $parser->get_select_handles; + for my $handle ($parser->get_select_handles) { + my $w; + $w = AnyEvent->io( + fh => $handle, + poll => 'r', + cb => sub { + # Ignore activity on stderr (unnecessary with merge => 1, + # but let’s keep it in here if we want to use merge => 0 + # for some reason in the future). + return if defined($stderr) and $handle == $stderr; + + my $result = $parser->next; + if (defined($result)) { + # TODO: check if we should bail out + return; + } + + # $result is not defined, we are done parsing + say "[$display] $test finished"; + close($parser->delete_spool); + $aggregator->add($test, $parser); + push @done, [ $test, $output ]; + + $kill_i3->(); + + undef $_ for @watchers; + if (@done == $num) { + $cv->send; + } else { + take_job($display); + } + } + ); + push @watchers, $w; + } }); - my @watchers; - my ($stdout, $stderr) = $parser->get_select_handles; - for my $handle ($parser->get_select_handles) { - my $w; - $w = AnyEvent->io( - fh => $handle, - poll => 'r', - cb => sub { - # Ignore activity on stderr (unnecessary with merge => 1, - # but let’s keep it in here if we want to use merge => 0 - # for some reason in the future). - return if defined($stderr) and $handle == $stderr; - - my $result = $parser->next; - if (defined($result)) { - # TODO: check if we should bail out - return; - } - - # $result is not defined, we are done parsing - say "[$display] $test finished"; - close($parser->delete_spool); - $aggregator->add($test, $parser); - push @done, [ $test, $output ]; - - $kill_i3->(); - - undef $_ for @watchers; - if (@done == $num) { - $cv->send; - } else { - take_job($display); - } - } - ); - push @watchers, $w; - } + $activate_cv->send(1) if $dont_start; } $cv->recv; From 7cb7700b02a734fe739e0c3331720a004e671112 Mon Sep 17 00:00:00 2001 From: Axel Wagner Date: Fri, 12 Aug 2011 18:43:09 +0200 Subject: [PATCH 016/333] i3bar: Fixup indentions --- i3bar/include/common.h | 8 +++--- i3bar/include/outputs.h | 24 ++++++++-------- i3bar/src/child.c | 2 +- i3bar/src/ucs2_to_utf8.c | 60 ++++++++++++++++++++-------------------- 4 files changed, 47 insertions(+), 47 deletions(-) diff --git a/i3bar/include/common.h b/i3bar/include/common.h index 22e3ca43..644b777c 100644 --- a/i3bar/include/common.h +++ b/i3bar/include/common.h @@ -17,10 +17,10 @@ char *statusline; char *statusline_buffer; struct rect_t { - int x; - int y; - int w; - int h; + int x; + int y; + int w; + int h; }; #include "queue.h" diff --git a/i3bar/include/outputs.h b/i3bar/include/outputs.h index f74048da..41712ac3 100644 --- a/i3bar/include/outputs.h +++ b/i3bar/include/outputs.h @@ -22,33 +22,33 @@ struct outputs_head *outputs; * Start parsing the received json-string * */ -void parse_outputs_json(char* json); +void parse_outputs_json(char* json); /* * Initiate the output-list * */ -void init_outputs(); +void init_outputs(); /* * Returns the output with the given name * */ -i3_output* get_output_by_name(char* name); +i3_output* get_output_by_name(char* name); struct i3_output { - char* name; /* Name of the output */ - bool active; /* If the output is active */ - int ws; /* The number of the currently visible ws */ - rect rect; /* The rect (relative to the root-win) */ + char* name; /* Name of the output */ + bool active; /* If the output is active */ + int ws; /* The number of the currently visible ws */ + rect rect; /* The rect (relative to the root-win) */ - xcb_window_t bar; /* The id of the bar of the output */ - xcb_pixmap_t buffer; /* An extra pixmap for double-buffering */ - xcb_gcontext_t bargc; /* The graphical context of the bar */ + xcb_window_t bar; /* The id of the bar of the output */ + xcb_pixmap_t buffer; /* An extra pixmap for double-buffering */ + xcb_gcontext_t bargc; /* The graphical context of the bar */ - struct ws_head *workspaces; /* The workspaces on this output */ + struct ws_head *workspaces; /* The workspaces on this output */ - SLIST_ENTRY(i3_output) slist; /* Pointer for the SLIST-Macro */ + SLIST_ENTRY(i3_output) slist; /* Pointer for the SLIST-Macro */ }; #endif diff --git a/i3bar/src/child.c b/i3bar/src/child.c index 3f59d060..ae0f47ee 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -87,7 +87,7 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) { if (rec == buffer_len) { buffer_len += STDIN_CHUNK_SIZE; buffer = realloc(buffer, buffer_len); - } + } } if (*buffer == '\0') { FREE(buffer); diff --git a/i3bar/src/ucs2_to_utf8.c b/i3bar/src/ucs2_to_utf8.c index 8c79c3f9..68984227 100644 --- a/i3bar/src/ucs2_to_utf8.c +++ b/i3bar/src/ucs2_to_utf8.c @@ -23,18 +23,18 @@ static iconv_t conversion_descriptor2 = 0; * */ char *convert_ucs_to_utf8(char *input) { - size_t input_size = 2; - /* UTF-8 may consume up to 4 byte */ - int buffer_size = 8; + size_t input_size = 2; + /* UTF-8 may consume up to 4 byte */ + int buffer_size = 8; - char *buffer = calloc(buffer_size, 1); + char *buffer = calloc(buffer_size, 1); if (buffer == NULL) err(EXIT_FAILURE, "malloc() failed\n"); - size_t output_size = buffer_size; - /* We need to use an additional pointer, because iconv() modifies it */ - char *output = buffer; + size_t output_size = buffer_size; + /* We need to use an additional pointer, because iconv() modifies it */ + char *output = buffer; - /* We convert the input into UCS-2 big endian */ + /* We convert the input into UCS-2 big endian */ if (conversion_descriptor == 0) { conversion_descriptor = iconv_open("UTF-8", "UCS-2BE"); if (conversion_descriptor == 0) { @@ -43,17 +43,17 @@ char *convert_ucs_to_utf8(char *input) { } } - /* Get the conversion descriptor back to original state */ - iconv(conversion_descriptor, NULL, NULL, NULL, NULL); + /* Get the conversion descriptor back to original state */ + iconv(conversion_descriptor, NULL, NULL, NULL, NULL); - /* Convert our text */ - int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size); + /* Convert our text */ + int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size); if (rc == (size_t)-1) { perror("Converting to UCS-2 failed"); return NULL; - } + } - return buffer; + return buffer; } /* @@ -64,18 +64,18 @@ char *convert_ucs_to_utf8(char *input) { * */ char *convert_utf8_to_ucs2(char *input, int *real_strlen) { - size_t input_size = strlen(input) + 1; - /* UCS-2 consumes exactly two bytes for each glyph */ - int buffer_size = input_size * 2; + size_t input_size = strlen(input) + 1; + /* UCS-2 consumes exactly two bytes for each glyph */ + int buffer_size = input_size * 2; - char *buffer = malloc(buffer_size); + char *buffer = malloc(buffer_size); if (buffer == NULL) err(EXIT_FAILURE, "malloc() failed\n"); - size_t output_size = buffer_size; - /* We need to use an additional pointer, because iconv() modifies it */ - char *output = buffer; + size_t output_size = buffer_size; + /* We need to use an additional pointer, because iconv() modifies it */ + char *output = buffer; - /* We convert the input into UCS-2 big endian */ + /* We convert the input into UCS-2 big endian */ if (conversion_descriptor2 == 0) { conversion_descriptor2 = iconv_open("UCS-2BE", "UTF-8"); if (conversion_descriptor2 == 0) { @@ -84,20 +84,20 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen) { } } - /* Get the conversion descriptor back to original state */ - iconv(conversion_descriptor2, NULL, NULL, NULL, NULL); + /* Get the conversion descriptor back to original state */ + iconv(conversion_descriptor2, NULL, NULL, NULL, NULL); - /* Convert our text */ - int rc = iconv(conversion_descriptor2, (void*)&input, &input_size, &output, &output_size); + /* Convert our text */ + int rc = iconv(conversion_descriptor2, (void*)&input, &input_size, &output, &output_size); if (rc == (size_t)-1) { perror("Converting to UCS-2 failed"); if (real_strlen != NULL) - *real_strlen = 0; + *real_strlen = 0; return NULL; - } + } if (real_strlen != NULL) - *real_strlen = ((buffer_size - output_size) / 2) - 1; + *real_strlen = ((buffer_size - output_size) / 2) - 1; - return buffer; + return buffer; } From 7ddba4995f3272f252e4d26cc5dfabc8fa67ccd7 Mon Sep 17 00:00:00 2001 From: Peter Bui Date: Mon, 8 Aug 2011 17:28:19 -0400 Subject: [PATCH 017/333] BUG-396: Implement move output command Also add support for move output and update userguide about the new command. --- docs/userguide | 5 +++++ src/cmdparse.l | 1 + src/cmdparse.y | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/docs/userguide b/docs/userguide index 158ed553..e5533d0e 100644 --- a/docs/userguide +++ b/docs/userguide @@ -759,6 +759,11 @@ You can also switch to the next and previous workspace with the commands workspace 1, 3, 4 and 9 and you want to cycle through them with a single key combination. +To move a container to another xrandr output such as +LVDS1+ or +VGA1+, you can +use the +move output+ command followed by the name of the target output. You +may also use +left+, +right+, +up+, +down+ instead of the xrandr output name to +move to the the next output in the specified direction. + *Examples*: ------------------------- bindsym mod+1 workspace 1 diff --git a/src/cmdparse.l b/src/cmdparse.l index 6c756b0d..c7c64e35 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -123,6 +123,7 @@ floating { return TOK_FLOATING; } toggle { return TOK_TOGGLE; } mode_toggle { return TOK_MODE_TOGGLE; } workspace { WS_STRING; return TOK_WORKSPACE; } +output { WS_STRING; return TOK_OUTPUT; } focus { return TOK_FOCUS; } move { return TOK_MOVE; } open { return TOK_OPEN; } diff --git a/src/cmdparse.y b/src/cmdparse.y index 9b63ff0c..891f876a 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -149,6 +149,7 @@ bool definitelyGreaterThan(float a, float b, float epsilon) { %token TOK_ENABLE "enable" %token TOK_DISABLE "disable" %token TOK_WORKSPACE "workspace" +%token TOK_OUTPUT "output" %token TOK_TOGGLE "toggle" %token TOK_FOCUS "focus" %token TOK_MOVE "move" @@ -704,6 +705,51 @@ move: tree_render(); } + | TOK_MOVE TOK_OUTPUT STR + { + owindow *current; + + printf("should move window to output %s", $3); + + HANDLE_EMPTY_MATCH; + + /* get the output */ + Output *current_output = NULL; + Output *output; + + TAILQ_FOREACH(current, &owindows, owindows) + current_output = get_output_containing(current->con->rect.x, current->con->rect.y); + + assert(current_output != NULL); + + if (strcasecmp($3, "up") == 0) + output = get_output_next(D_UP, current_output); + else if (strcasecmp($3, "down") == 0) + output = get_output_next(D_DOWN, current_output); + else if (strcasecmp($3, "left") == 0) + output = get_output_next(D_LEFT, current_output); + else if (strcasecmp($3, "right") == 0) + output = get_output_next(D_RIGHT, current_output); + else + output = get_output_by_name($3); + free($3); + + if (!output) + return 0; + + /* get visible workspace on output */ + Con *ws = NULL; + GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child)); + if (!ws) + return 0; + + TAILQ_FOREACH(current, &owindows, owindows) { + printf("matching: %p / %s\n", current->con, current->con->name); + con_move_to_workspace(current->con, ws); + } + + tree_render(); + } ; append_layout: From ec317e78c1fa194eb8cba17358952dbd21a45c1a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 17 Aug 2011 16:12:48 +0200 Subject: [PATCH 018/333] use 'break' instead of 'return', the generated code will end up in a switch statement --- src/cmdparse.y | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cmdparse.y b/src/cmdparse.y index 891f876a..c38f660f 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -735,13 +735,13 @@ move: free($3); if (!output) - return 0; + break; /* get visible workspace on output */ Con *ws = NULL; GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child)); if (!ws) - return 0; + break; TAILQ_FOREACH(current, &owindows, owindows) { printf("matching: %p / %s\n", current->con, current->con->name); From 025dd68f628e35b865db9eb734e72cd43e980317 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Aug 2011 19:14:27 +0200 Subject: [PATCH 019/333] i3bar: quick & dirty systray implementation Works correctly only with exactly one dock client on exactly one output. Maybe not even then. You have been warned. Proof-of-concept code ;). --- i3bar/include/outputs.h | 2 + i3bar/include/xcb_atoms.def | 6 ++ i3bar/src/outputs.c | 1 + i3bar/src/xcb.c | 111 +++++++++++++++++++++++++++++++++++- 4 files changed, 118 insertions(+), 2 deletions(-) diff --git a/i3bar/include/outputs.h b/i3bar/include/outputs.h index 41712ac3..680261b1 100644 --- a/i3bar/include/outputs.h +++ b/i3bar/include/outputs.h @@ -46,6 +46,8 @@ struct i3_output { xcb_pixmap_t buffer; /* An extra pixmap for double-buffering */ xcb_gcontext_t bargc; /* The graphical context of the bar */ + int traypx; /* Amount of pixels reserved for tray icons */ + struct ws_head *workspaces; /* The workspaces on this output */ SLIST_ENTRY(i3_output) slist; /* Pointer for the SLIST-Macro */ diff --git a/i3bar/include/xcb_atoms.def b/i3bar/include/xcb_atoms.def index 5d168873..792ef9a9 100644 --- a/i3bar/include/xcb_atoms.def +++ b/i3bar/include/xcb_atoms.def @@ -2,4 +2,10 @@ ATOM_DO(_NET_WM_WINDOW_TYPE) ATOM_DO(_NET_WM_WINDOW_TYPE_DOCK) ATOM_DO(_NET_WM_STRUT_PARTIAL) ATOM_DO(I3_SOCKET_PATH) +ATOM_DO(_NET_SYSTEM_TRAY_S0) +ATOM_DO(MANAGER) +ATOM_DO(_NET_SYSTEM_TRAY_ORIENTATION) +ATOM_DO(_NET_SYSTEM_TRAY_VISUAL) +ATOM_DO(CARDINAL) +ATOM_DO(_NET_SYSTEM_TRAY_OPCODE) #undef ATOM_DO diff --git a/i3bar/src/outputs.c b/i3bar/src/outputs.c index 9daf328d..39bad192 100644 --- a/i3bar/src/outputs.c +++ b/i3bar/src/outputs.c @@ -157,6 +157,7 @@ static int outputs_start_map_cb(void *params_) { new_output->ws = 0, memset(&new_output->rect, 0, sizeof(rect)); new_output->bar = XCB_NONE; + new_output->traypx = 0; new_output->workspaces = malloc(sizeof(struct ws_head)); TAILQ_INIT(new_output->workspaces); diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 28ef3ea2..35d90f77 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -383,6 +383,45 @@ void handle_button(xcb_button_press_event_t *event) { i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, buffer); } +void handle_client_message(xcb_client_message_event_t* event) { + printf("got a client message, yay\n"); + if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] && + event->format == 32) { + printf("system tray message\n"); + /* event->data.data32[0] is the timestamp */ + uint32_t op = event->data.data32[1]; +#define SYSTEM_TRAY_REQUEST_DOCK 0 +#define SYSTEM_TRAY_BEGIN_MESSAGE 1 +#define SYSTEM_TRAY_CANCEL_MESSAGE 2 + if (op == SYSTEM_TRAY_REQUEST_DOCK) { + printf("docking requested of x window id %d\n", event->data.data32[2]); + /* TODO: correctly handle multiple dock clients */ + xcb_window_t client = event->data.data32[2]; + i3_output *walk, *output; + SLIST_FOREACH(walk, outputs, slist) { + if (!walk->active) + continue; + printf("using output %s\n", walk->name); + output = walk; + } + xcb_reparent_window(xcb_connection, + client, + output->bar, + output->rect.w - font_height - 2, /* TODO: why -2? */ + 0); + uint32_t mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; + uint32_t values[] = { font_height, font_height }; + xcb_configure_window(xcb_connection, + client, + mask, + values); + xcb_map_window(xcb_connection, client); + /* XXX: We assume that icons are quadratic. Is that so? */ + output->traypx += font_height; + } + } +} + /* * This function is called immediately before the main loop locks. We flush xcb * then (and only then) @@ -413,6 +452,11 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) { /* Button-press-events are mouse-buttons clicked on one of our bars */ handle_button((xcb_button_press_event_t*) event); break; + case XCB_CLIENT_MESSAGE: + /* Client messages are used for client-to-client communication, for + * example system tray widgets talk to us directly via client messages. */ + handle_client_message((xcb_client_message_event_t*) event); + break; } FREE(event); } @@ -634,6 +678,62 @@ char *init_xcb(char *fontname) { return path; } +void init_tray() { +/* tray support: we need a window to own the selection */ + xcb_void_cookie_t selwin_cookie; + xcb_window_t selwin = xcb_generate_id(xcb_connection); + uint32_t selmask = XCB_CW_OVERRIDE_REDIRECT; + uint32_t selval[] = { 1 }; + selwin_cookie = xcb_create_window_checked(xcb_connection, + xcb_screen->root_depth, + selwin, + xcb_root, + -1, -1, + 1, 1, + 1, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + xcb_screen->root_visual, + selmask, + selval); + +#define _NET_SYSTEM_TRAY_ORIENTATION_HORZ 0 +#define _NET_SYSTEM_TRAY_ORIENTATION_VERT 1 + uint32_t orientation = _NET_SYSTEM_TRAY_ORIENTATION_HORZ; + /* set the atoms */ + xcb_change_property(xcb_connection, + XCB_PROP_MODE_REPLACE, + selwin, + atoms[_NET_SYSTEM_TRAY_ORIENTATION], + atoms[CARDINAL], + 32, + 1, + &orientation); + + + xcb_set_selection_owner(xcb_connection, + selwin, + /* TODO: request this atom separately */ + atoms[_NET_SYSTEM_TRAY_S0], + XCB_CURRENT_TIME); + /* FIXME: don't use XCB_CURRENT_TIME */ + + /* TODO: check if we got the selection */ + void *event = calloc(32, 1); + xcb_client_message_event_t *ev = event; + ev->response_type = XCB_CLIENT_MESSAGE; + ev->window = xcb_root; + ev->type = atoms[MANAGER]; + ev->format = 32; + ev->data.data32[0] = XCB_CURRENT_TIME; + ev->data.data32[1] = atoms[_NET_SYSTEM_TRAY_S0]; + ev->data.data32[2] = selwin; + xcb_send_event(xcb_connection, + 0, + xcb_root, + XCB_EVENT_MASK_STRUCTURE_NOTIFY, + (char*)ev); +} + /* * Cleanup the xcb-stuff. * Called once, before the program terminates. @@ -757,6 +857,9 @@ void reconfig_windows() { if (walk->bar == XCB_NONE) { DLOG("Creating Window for output %s\n", walk->name); + /* TODO: only call init_tray() if the tray is configured for this output */ + init_tray(); + walk->bar = xcb_generate_id(xcb_connection); walk->buffer = xcb_generate_id(xcb_connection); mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; @@ -946,13 +1049,17 @@ void draw_bars() { /* Luckily we already prepared a seperate pixmap containing the rendered * statusline, we just have to copy the relevant parts to the relevant * position */ + int traypx = outputs_walk->traypx; + /* Add 2px of padding if there are any tray icons */ + if (traypx > 0) + traypx += 2; xcb_copy_area(xcb_connection, statusline_pm, outputs_walk->buffer, outputs_walk->bargc, MAX(0, (int16_t)(statusline_width - outputs_walk->rect.w + 4)), 0, - MAX(0, (int16_t)(outputs_walk->rect.w - statusline_width - 4)), 3, - MIN(outputs_walk->rect.w - 4, statusline_width), font_height); + MAX(0, (int16_t)(outputs_walk->rect.w - statusline_width - traypx - 4)), 3, + MIN(outputs_walk->rect.w - outputs_walk->traypx - 4, statusline_width), font_height); } if (config.disable_ws) { From 6efa7a754d5120a207b2dc8af0efdafdff64c450 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 15 Aug 2011 15:12:01 +0200 Subject: [PATCH 020/333] i3bar: trigger an update after docking a new client --- i3bar/src/xcb.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 35d90f77..29b31aef 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -418,6 +418,10 @@ void handle_client_message(xcb_client_message_event_t* event) { xcb_map_window(xcb_connection, client); /* XXX: We assume that icons are quadratic. Is that so? */ output->traypx += font_height; + + /* Trigger an update to copy the statusline text to the appropriate + * position */ + draw_bars(); } } } From 2046e4112f551db819e471b9553dee22f957eb20 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 15 Aug 2011 15:57:52 +0200 Subject: [PATCH 021/333] i3bar: Correctly handle removal of tray clients --- i3bar/include/common.h | 1 + i3bar/include/outputs.h | 3 +- i3bar/include/trayclients.h | 24 +++++++++++++++ i3bar/src/outputs.c | 4 ++- i3bar/src/xcb.c | 60 +++++++++++++++++++++++++++++++++---- 5 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 i3bar/include/trayclients.h diff --git a/i3bar/include/common.h b/i3bar/include/common.h index 644b777c..9737d22f 100644 --- a/i3bar/include/common.h +++ b/i3bar/include/common.h @@ -29,6 +29,7 @@ struct rect_t { #include "outputs.h" #include "util.h" #include "workspaces.h" +#include "trayclients.h" #include "xcb.h" #include "ucs2_to_utf8.h" #include "config.h" diff --git a/i3bar/include/outputs.h b/i3bar/include/outputs.h index 680261b1..c6402a5b 100644 --- a/i3bar/include/outputs.h +++ b/i3bar/include/outputs.h @@ -46,9 +46,8 @@ struct i3_output { xcb_pixmap_t buffer; /* An extra pixmap for double-buffering */ xcb_gcontext_t bargc; /* The graphical context of the bar */ - int traypx; /* Amount of pixels reserved for tray icons */ - struct ws_head *workspaces; /* The workspaces on this output */ + struct tc_head *trayclients; /* The tray clients on this output */ SLIST_ENTRY(i3_output) slist; /* Pointer for the SLIST-Macro */ }; diff --git a/i3bar/include/trayclients.h b/i3bar/include/trayclients.h new file mode 100644 index 00000000..3218578a --- /dev/null +++ b/i3bar/include/trayclients.h @@ -0,0 +1,24 @@ +/* + * i3bar - an xcb-based status- and ws-bar for i3 + * + * © 2010-2011 Axel Wagner and contributors + * + * See file LICNSE for license information + * + */ +#ifndef TRAYCLIENT_H_ +#define TRAYCLIENT_H_ + +#include "common.h" + +typedef struct trayclient trayclient; + +TAILQ_HEAD(tc_head, trayclient); + +struct trayclient { + xcb_window_t win; /* The window ID of the tray client */ + + TAILQ_ENTRY(trayclient) tailq; /* Pointer for the TAILQ-Macro */ +}; + +#endif diff --git a/i3bar/src/outputs.c b/i3bar/src/outputs.c index 39bad192..53544d17 100644 --- a/i3bar/src/outputs.c +++ b/i3bar/src/outputs.c @@ -157,11 +157,13 @@ static int outputs_start_map_cb(void *params_) { new_output->ws = 0, memset(&new_output->rect, 0, sizeof(rect)); new_output->bar = XCB_NONE; - new_output->traypx = 0; new_output->workspaces = malloc(sizeof(struct ws_head)); TAILQ_INIT(new_output->workspaces); + new_output->trayclients = malloc(sizeof(struct tc_head)); + TAILQ_INIT(new_output->trayclients); + params->outputs_walk = new_output; return 1; diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 29b31aef..3da23a81 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -408,16 +408,32 @@ void handle_client_message(xcb_client_message_event_t* event) { client, output->bar, output->rect.w - font_height - 2, /* TODO: why -2? */ - 0); + 2); + /* We reconfigure the window to use a reasonable size. The systray + * specification explicitly says: + * Tray icons may be assigned any size by the system tray, and + * should do their best to cope with any size effectively + */ uint32_t mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; uint32_t values[] = { font_height, font_height }; xcb_configure_window(xcb_connection, client, mask, values); + + /* Listen for PropertyNotify events to get the most recent value of + * the XEMBED_MAPPED atom, also listen for UnmapNotify events */ + mask = XCB_CW_EVENT_MASK; + values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE | + XCB_EVENT_MASK_STRUCTURE_NOTIFY; + xcb_change_window_attributes(xcb_connection, + client, + mask, + values); xcb_map_window(xcb_connection, client); - /* XXX: We assume that icons are quadratic. Is that so? */ - output->traypx += font_height; + trayclient *tc = malloc(sizeof(trayclient)); + tc->win = client; + TAILQ_INSERT_TAIL(output->trayclients, tc, tailq); /* Trigger an update to copy the statusline text to the appropriate * position */ @@ -426,6 +442,29 @@ void handle_client_message(xcb_client_message_event_t* event) { } } +void handle_unmap_notify(xcb_unmap_notify_event_t* event) { + DLOG("UnmapNotify for window = %08x, event = %08x\n", event->window, event->event); + + i3_output *walk; + SLIST_FOREACH(walk, outputs, slist) { + if (!walk->active) + continue; + DLOG("checking output %s\n", walk->name); + trayclient *trayclient; + TAILQ_FOREACH(trayclient, walk->trayclients, tailq) { + if (trayclient->win != event->window) + continue; + + DLOG("Removing tray client with window ID %08x\n", event->window); + TAILQ_REMOVE(walk->trayclients, trayclient, tailq); + + /* Trigger an update, we now have more space for the statusline */ + draw_bars(); + return; + } + } +} + /* * This function is called immediately before the main loop locks. We flush xcb * then (and only then) @@ -461,6 +500,10 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) { * example system tray widgets talk to us directly via client messages. */ handle_client_message((xcb_client_message_event_t*) event); break; + case XCB_UNMAP_NOTIFY: + /* UnmapNotifies are received when a tray window unmaps itself */ + handle_unmap_notify((xcb_unmap_notify_event_t*) event); + break; } FREE(event); } @@ -1053,7 +1096,14 @@ void draw_bars() { /* Luckily we already prepared a seperate pixmap containing the rendered * statusline, we just have to copy the relevant parts to the relevant * position */ - int traypx = outputs_walk->traypx; + trayclient *trayclient; + int traypx = 0; + TAILQ_FOREACH(trayclient, outputs_walk->trayclients, tailq) { + /* We assume the tray icons are quadratic (we use the font + * *height* as *width* of the icons) because we configured them + * like this. */ + traypx += font_height; + } /* Add 2px of padding if there are any tray icons */ if (traypx > 0) traypx += 2; @@ -1063,7 +1113,7 @@ void draw_bars() { outputs_walk->bargc, MAX(0, (int16_t)(statusline_width - outputs_walk->rect.w + 4)), 0, MAX(0, (int16_t)(outputs_walk->rect.w - statusline_width - traypx - 4)), 3, - MIN(outputs_walk->rect.w - outputs_walk->traypx - 4, statusline_width), font_height); + MIN(outputs_walk->rect.w - traypx - 4, statusline_width), font_height); } if (config.disable_ws) { From 7df43989c930a5b7c0f138d00f925836ec3b4453 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 16 Aug 2011 22:44:42 +0200 Subject: [PATCH 022/333] i3bar: correctly handle multiple tray clients --- i3bar/src/xcb.c | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 3da23a81..eb1ae0ab 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -383,25 +383,50 @@ void handle_button(xcb_button_press_event_t *event) { i3_send_msg(I3_IPC_MESSAGE_TYPE_COMMAND, buffer); } +/* + * Configures the x coordinate of all trayclients. To be called after adding a + * new tray client or removing an old one. + * + */ +static void configure_trayclients() { + trayclient *trayclient; + i3_output *output; + SLIST_FOREACH(output, outputs, slist) { + if (!output->active) + continue; + + int clients = 0; + TAILQ_FOREACH_REVERSE(trayclient, output->trayclients, tc_head, tailq) { + clients++; + + DLOG("Configuring tray window %08x to x=%d\n", + trayclient->win, output->rect.w - (clients * (font_height + 2))); + uint32_t x = output->rect.w - (clients * (font_height + 2)); + xcb_configure_window(xcb_connection, + trayclient->win, + XCB_CONFIG_WINDOW_X, + &x); + } + } +} + void handle_client_message(xcb_client_message_event_t* event) { - printf("got a client message, yay\n"); if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] && event->format == 32) { - printf("system tray message\n"); + DLOG("_NET_SYSTEM_TRAY_OPCODE received\n"); /* event->data.data32[0] is the timestamp */ uint32_t op = event->data.data32[1]; #define SYSTEM_TRAY_REQUEST_DOCK 0 #define SYSTEM_TRAY_BEGIN_MESSAGE 1 #define SYSTEM_TRAY_CANCEL_MESSAGE 2 if (op == SYSTEM_TRAY_REQUEST_DOCK) { - printf("docking requested of x window id %d\n", event->data.data32[2]); - /* TODO: correctly handle multiple dock clients */ xcb_window_t client = event->data.data32[2]; + DLOG("X window %08x requested docking\n", client); i3_output *walk, *output; SLIST_FOREACH(walk, outputs, slist) { if (!walk->active) continue; - printf("using output %s\n", walk->name); + DLOG("using output %s\n", walk->name); output = walk; } xcb_reparent_window(xcb_connection, @@ -437,6 +462,7 @@ 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(); } } @@ -459,6 +485,7 @@ void handle_unmap_notify(xcb_unmap_notify_event_t* event) { TAILQ_REMOVE(walk->trayclients, trayclient, tailq); /* Trigger an update, we now have more space for the statusline */ + configure_trayclients(); draw_bars(); return; } From 737cd10bdf17e66c71b037dff2b71396a14c5a5b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 17 Aug 2011 00:05:05 +0200 Subject: [PATCH 023/333] i3bar: properly handle the _XEMBED_INFO property --- i3bar/include/common.h | 3 +- i3bar/include/trayclients.h | 1 + i3bar/include/xcb.h | 5 ++ i3bar/include/xcb_atoms.def | 1 + i3bar/src/outputs.c | 2 +- i3bar/src/workspaces.c | 2 +- i3bar/src/xcb.c | 133 ++++++++++++++++++++++++++++++++---- 7 files changed, 129 insertions(+), 18 deletions(-) diff --git a/i3bar/include/common.h b/i3bar/include/common.h index 9737d22f..74bd2152 100644 --- a/i3bar/include/common.h +++ b/i3bar/include/common.h @@ -9,8 +9,9 @@ #ifndef COMMON_H_ #define COMMON_H_ +#include + typedef struct rect_t rect; -typedef int bool; struct ev_loop* main_loop; char *statusline; diff --git a/i3bar/include/trayclients.h b/i3bar/include/trayclients.h index 3218578a..277cd1cb 100644 --- a/i3bar/include/trayclients.h +++ b/i3bar/include/trayclients.h @@ -17,6 +17,7 @@ TAILQ_HEAD(tc_head, trayclient); struct trayclient { xcb_window_t win; /* The window ID of the tray client */ + bool mapped; /* Whether this window is mapped */ TAILQ_ENTRY(trayclient) tailq; /* Pointer for the TAILQ-Macro */ }; diff --git a/i3bar/include/xcb.h b/i3bar/include/xcb.h index 531fdfe9..3fadc4c5 100644 --- a/i3bar/include/xcb.h +++ b/i3bar/include/xcb.h @@ -12,6 +12,11 @@ #include //#include "outputs.h" +#define SYSTEM_TRAY_REQUEST_DOCK 0 +#define SYSTEM_TRAY_BEGIN_MESSAGE 1 +#define SYSTEM_TRAY_CANCEL_MESSAGE 2 +#define XEMBED_MAPPED (1 << 0) + struct xcb_color_strings_t { char *bar_fg; char *bar_bg; diff --git a/i3bar/include/xcb_atoms.def b/i3bar/include/xcb_atoms.def index 792ef9a9..3f2b9775 100644 --- a/i3bar/include/xcb_atoms.def +++ b/i3bar/include/xcb_atoms.def @@ -8,4 +8,5 @@ ATOM_DO(_NET_SYSTEM_TRAY_ORIENTATION) ATOM_DO(_NET_SYSTEM_TRAY_VISUAL) ATOM_DO(CARDINAL) ATOM_DO(_NET_SYSTEM_TRAY_OPCODE) +ATOM_DO(_XEMBED_INFO) #undef ATOM_DO diff --git a/i3bar/src/outputs.c b/i3bar/src/outputs.c index 53544d17..464f24a0 100644 --- a/i3bar/src/outputs.c +++ b/i3bar/src/outputs.c @@ -43,7 +43,7 @@ static int outputs_null_cb(void *params_) { * Parse a boolean value (active) * */ -static int outputs_boolean_cb(void *params_, bool val) { +static int outputs_boolean_cb(void *params_, int val) { struct outputs_json_params *params = (struct outputs_json_params*) params_; if (strcmp(params->cur_key, "active")) { diff --git a/i3bar/src/workspaces.c b/i3bar/src/workspaces.c index eeb9ca34..a84e152b 100644 --- a/i3bar/src/workspaces.c +++ b/i3bar/src/workspaces.c @@ -29,7 +29,7 @@ struct workspaces_json_params { * Parse a boolean value (visible, focused, urgent) * */ -static int workspaces_boolean_cb(void *params_, bool val) { +static int workspaces_boolean_cb(void *params_, int val) { struct workspaces_json_params *params = (struct workspaces_json_params*) params_; if (!strcmp(params->cur_key, "visible")) { diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index eb1ae0ab..c7ddfe88 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -416,11 +416,48 @@ void handle_client_message(xcb_client_message_event_t* event) { DLOG("_NET_SYSTEM_TRAY_OPCODE received\n"); /* event->data.data32[0] is the timestamp */ uint32_t op = event->data.data32[1]; -#define SYSTEM_TRAY_REQUEST_DOCK 0 -#define SYSTEM_TRAY_BEGIN_MESSAGE 1 -#define SYSTEM_TRAY_CANCEL_MESSAGE 2 + uint32_t mask; + uint32_t values[2]; if (op == SYSTEM_TRAY_REQUEST_DOCK) { xcb_window_t client = event->data.data32[2]; + + /* Listen for PropertyNotify events to get the most recent value of + * the XEMBED_MAPPED atom, also listen for UnmapNotify events */ + mask = XCB_CW_EVENT_MASK; + values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE | + XCB_EVENT_MASK_STRUCTURE_NOTIFY; + xcb_change_window_attributes(xcb_connection, + client, + mask, + values); + + /* Request the _XEMBED_INFO property. The XEMBED specification + * (which is referred by the tray specification) says this *has* to + * be set, but VLC does not set it… */ + bool map_it = true; + xcb_get_property_cookie_t xembedc; + xembedc = xcb_get_property_unchecked(xcb_connection, + 0, + client, + atoms[_XEMBED_INFO], + XCB_GET_PROPERTY_TYPE_ANY, + 0, + 2 * 32); + + xcb_get_property_reply_t *xembedr = xcb_get_property_reply(xcb_connection, + xembedc, + NULL); + if (xembedr != NULL && xembedr->length != 0) { + DLOG("xembed format = %d, len = %d\n", xembedr->format, xembedr->length); + uint32_t *xembed = xcb_get_property_value(xembedr); + DLOG("xembed version = %d\n", xembed[0]); + DLOG("xembed flags = %d\n", xembed[1]); + map_it = ((xembed[1] & XEMBED_MAPPED) == XEMBED_MAPPED); + free(xembedr); + } else { + ELOG("Window %08x violates the XEMBED protocol, _XEMBED_INFO not set\n", client); + } + DLOG("X window %08x requested docking\n", client); i3_output *walk, *output; SLIST_FOREACH(walk, outputs, slist) { @@ -439,25 +476,23 @@ void handle_client_message(xcb_client_message_event_t* event) { * Tray icons may be assigned any size by the system tray, and * should do their best to cope with any size effectively */ - uint32_t mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; - uint32_t values[] = { font_height, font_height }; + mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; + values[0] = font_height; + values[1] = font_height; xcb_configure_window(xcb_connection, client, mask, values); - /* Listen for PropertyNotify events to get the most recent value of - * the XEMBED_MAPPED atom, also listen for UnmapNotify events */ - mask = XCB_CW_EVENT_MASK; - values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE | - XCB_EVENT_MASK_STRUCTURE_NOTIFY; - xcb_change_window_attributes(xcb_connection, - client, - mask, - values); - xcb_map_window(xcb_connection, client); + if (map_it) { + DLOG("Mapping dock client\n"); + xcb_map_window(xcb_connection, client); + } else { + DLOG("Not mapping dock client yet\n"); + } trayclient *tc = malloc(sizeof(trayclient)); tc->win = client; + tc->mapped = map_it; TAILQ_INSERT_TAIL(output->trayclients, tc, tailq); /* Trigger an update to copy the statusline text to the appropriate @@ -492,6 +527,68 @@ void handle_unmap_notify(xcb_unmap_notify_event_t* event) { } } +static void handle_property_notify(xcb_property_notify_event_t *event) { + DLOG("PropertyNotify\n"); + if (event->atom == atoms[_XEMBED_INFO] && + event->state == XCB_PROPERTY_NEW_VALUE) { + DLOG("xembed_info updated\n"); + trayclient *trayclient = NULL, *walk; + i3_output *output; + SLIST_FOREACH(output, outputs, slist) { + if (!output->active) + continue; + + TAILQ_FOREACH(walk, output->trayclients, tailq) { + if (walk->win != event->window) + continue; + trayclient = walk; + break; + } + + if (trayclient) + break; + } + if (!trayclient) { + ELOG("PropertyNotify received for unknown window %08x\n", + event->window); + return; + } + xcb_get_property_cookie_t xembedc; + xembedc = xcb_get_property_unchecked(xcb_connection, + 0, + trayclient->win, + atoms[_XEMBED_INFO], + XCB_GET_PROPERTY_TYPE_ANY, + 0, + 2 * 32); + + xcb_get_property_reply_t *xembedr = xcb_get_property_reply(xcb_connection, + xembedc, + NULL); + if (xembedr == NULL || xembedr->length == 0) + return; + + DLOG("xembed format = %d, len = %d\n", xembedr->format, xembedr->length); + uint32_t *xembed = xcb_get_property_value(xembedr); + DLOG("xembed version = %d\n", xembed[0]); + DLOG("xembed flags = %d\n", xembed[1]); + bool map_it = ((xembed[1] & XEMBED_MAPPED) == XEMBED_MAPPED); + DLOG("map-state now %d\n", map_it); + if (trayclient->mapped && !map_it) { + /* need to unmap the window */ + xcb_unmap_window(xcb_connection, trayclient->win); + trayclient->mapped = map_it; + draw_bars(); + } else if (!trayclient->mapped && map_it) { + /* need to map the window */ + xcb_map_window(xcb_connection, trayclient->win); + trayclient->mapped = map_it; + draw_bars(); + } + free(xembedr); + } +} + /* * This function is called immediately before the main loop locks. We flush xcb * then (and only then) @@ -531,6 +628,10 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) { /* UnmapNotifies are received when a tray window unmaps itself */ handle_unmap_notify((xcb_unmap_notify_event_t*) event); break; + case XCB_PROPERTY_NOTIFY: + /* PropertyNotify */ + handle_property_notify((xcb_property_notify_event_t*) event); + break; } FREE(event); } @@ -1126,6 +1227,8 @@ void draw_bars() { trayclient *trayclient; int traypx = 0; TAILQ_FOREACH(trayclient, outputs_walk->trayclients, tailq) { + if (!trayclient->mapped) + continue; /* We assume the tray icons are quadratic (we use the font * *height* as *width* of the icons) because we configured them * like this. */ From 893878cbcc7306a6553cda51b3423c73c25e9e99 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 17 Aug 2011 00:44:03 +0200 Subject: [PATCH 024/333] i3bar: send XEMBED_EMBEDDED_NOTIFY after reparenting/mapping tray clients --- i3bar/include/trayclients.h | 1 + i3bar/include/xcb.h | 1 + i3bar/include/xcb_atoms.def | 1 + i3bar/src/xcb.c | 32 ++++++++++++++++++++++++++++---- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/i3bar/include/trayclients.h b/i3bar/include/trayclients.h index 277cd1cb..1113daeb 100644 --- a/i3bar/include/trayclients.h +++ b/i3bar/include/trayclients.h @@ -18,6 +18,7 @@ TAILQ_HEAD(tc_head, trayclient); struct trayclient { xcb_window_t win; /* The window ID of the tray client */ bool mapped; /* Whether this window is mapped */ + int xe_version; /* The XEMBED version supported by the client */ TAILQ_ENTRY(trayclient) tailq; /* Pointer for the TAILQ-Macro */ }; diff --git a/i3bar/include/xcb.h b/i3bar/include/xcb.h index 3fadc4c5..b70d29ef 100644 --- a/i3bar/include/xcb.h +++ b/i3bar/include/xcb.h @@ -16,6 +16,7 @@ #define SYSTEM_TRAY_BEGIN_MESSAGE 1 #define SYSTEM_TRAY_CANCEL_MESSAGE 2 #define XEMBED_MAPPED (1 << 0) +#define XEMBED_EMBEDDED_NOTIFY 0 struct xcb_color_strings_t { char *bar_fg; diff --git a/i3bar/include/xcb_atoms.def b/i3bar/include/xcb_atoms.def index 3f2b9775..625fc8a4 100644 --- a/i3bar/include/xcb_atoms.def +++ b/i3bar/include/xcb_atoms.def @@ -9,4 +9,5 @@ ATOM_DO(_NET_SYSTEM_TRAY_VISUAL) ATOM_DO(CARDINAL) ATOM_DO(_NET_SYSTEM_TRAY_OPCODE) ATOM_DO(_XEMBED_INFO) +ATOM_DO(_XEMBED) #undef ATOM_DO diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index c7ddfe88..81c00ac4 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -435,6 +435,7 @@ void handle_client_message(xcb_client_message_event_t* event) { * (which is referred by the tray specification) says this *has* to * be set, but VLC does not set it… */ bool map_it = true; + int xe_version = 1; xcb_get_property_cookie_t xembedc; xembedc = xcb_get_property_unchecked(xcb_connection, 0, @@ -453,6 +454,9 @@ void handle_client_message(xcb_client_message_event_t* event) { DLOG("xembed version = %d\n", xembed[0]); DLOG("xembed flags = %d\n", xembed[1]); map_it = ((xembed[1] & XEMBED_MAPPED) == XEMBED_MAPPED); + xe_version = xembed[0]; + if (xe_version > 1) + xe_version = 1; free(xembedr); } else { ELOG("Window %08x violates the XEMBED protocol, _XEMBED_INFO not set\n", client); @@ -484,6 +488,24 @@ void handle_client_message(xcb_client_message_event_t* event) { mask, values); + /* send the XEMBED_EMBEDDED_NOTIFY message */ + void *event = calloc(32, 1); + xcb_client_message_event_t *ev = event; + ev->response_type = XCB_CLIENT_MESSAGE; + ev->window = client; + ev->type = atoms[_XEMBED]; + ev->format = 32; + ev->data.data32[0] = XCB_CURRENT_TIME; + ev->data.data32[1] = atoms[XEMBED_EMBEDDED_NOTIFY]; + ev->data.data32[2] = output->bar; + ev->data.data32[3] = xe_version; + xcb_send_event(xcb_connection, + 0, + client, + XCB_EVENT_MASK_NO_EVENT, + (char*)ev); + free(event); + if (map_it) { DLOG("Mapping dock client\n"); xcb_map_window(xcb_connection, client); @@ -493,6 +515,7 @@ void handle_client_message(xcb_client_message_event_t* event) { trayclient *tc = malloc(sizeof(trayclient)); tc->win = client; tc->mapped = map_it; + tc->xe_version = xe_version; TAILQ_INSERT_TAIL(output->trayclients, tc, tailq); /* Trigger an update to copy the statusline text to the appropriate @@ -533,12 +556,12 @@ static void handle_property_notify(xcb_property_notify_event_t *event) { event->state == XCB_PROPERTY_NEW_VALUE) { DLOG("xembed_info updated\n"); trayclient *trayclient = NULL, *walk; - i3_output *output; - SLIST_FOREACH(output, outputs, slist) { - if (!output->active) + i3_output *o_walk; + SLIST_FOREACH(o_walk, outputs, slist) { + if (!o_walk->active) continue; - TAILQ_FOREACH(walk, output->trayclients, tailq) { + TAILQ_FOREACH(walk, o_walk->trayclients, tailq) { if (walk->win != event->window) continue; trayclient = walk; @@ -907,6 +930,7 @@ void init_tray() { xcb_root, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)ev); + free(event); } /* From 55e503c17b000cc2d6ff90a04d8085956f3ecdab Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 17 Aug 2011 00:58:00 +0200 Subject: [PATCH 025/333] i3bar: request the appropriate _NET_SYSTEM_TRAY atom for the display we are running on --- i3bar/include/xcb.h | 2 ++ i3bar/include/xcb_atoms.def | 1 - i3bar/src/xcb.c | 25 +++++++++++++++++-------- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/i3bar/include/xcb.h b/i3bar/include/xcb.h index b70d29ef..0276d3c7 100644 --- a/i3bar/include/xcb.h +++ b/i3bar/include/xcb.h @@ -12,6 +12,8 @@ #include //#include "outputs.h" +#define _NET_SYSTEM_TRAY_ORIENTATION_HORZ 0 +#define _NET_SYSTEM_TRAY_ORIENTATION_VERT 1 #define SYSTEM_TRAY_REQUEST_DOCK 0 #define SYSTEM_TRAY_BEGIN_MESSAGE 1 #define SYSTEM_TRAY_CANCEL_MESSAGE 2 diff --git a/i3bar/include/xcb_atoms.def b/i3bar/include/xcb_atoms.def index 625fc8a4..0c0b207e 100644 --- a/i3bar/include/xcb_atoms.def +++ b/i3bar/include/xcb_atoms.def @@ -2,7 +2,6 @@ ATOM_DO(_NET_WM_WINDOW_TYPE) ATOM_DO(_NET_WM_WINDOW_TYPE_DOCK) ATOM_DO(_NET_WM_STRUT_PARTIAL) ATOM_DO(I3_SOCKET_PATH) -ATOM_DO(_NET_SYSTEM_TRAY_S0) ATOM_DO(MANAGER) ATOM_DO(_NET_SYSTEM_TRAY_ORIENTATION) ATOM_DO(_NET_SYSTEM_TRAY_VISUAL) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 81c00ac4..0cb8bcc8 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -63,6 +63,7 @@ xcb_atom_t atoms[NUM_ATOMS]; /* Variables, that are the same for all functions at all times */ xcb_connection_t *xcb_connection; +int screen; xcb_screen_t *xcb_screen; xcb_window_t xcb_root; xcb_font_t xcb_font; @@ -712,7 +713,7 @@ void xkb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) { */ char *init_xcb(char *fontname) { /* FIXME: xcb_connect leaks Memory */ - xcb_connection = xcb_connect(NULL, NULL); + xcb_connection = xcb_connect(NULL, &screen); if (xcb_connection_has_error(xcb_connection)) { ELOG("Cannot open display\n"); exit(EXIT_FAILURE); @@ -877,7 +878,14 @@ char *init_xcb(char *fontname) { } void init_tray() { -/* tray support: we need a window to own the selection */ + /* request the tray manager atom for the X11 display we are running on */ + char atomname[strlen("_NET_SYSTEM_TRAY_S") + 11]; + snprintf(atomname, strlen("_NET_SYSTEM_TRAY_S") + 11, "_NET_SYSTEM_TRAY_S%d", screen); + xcb_intern_atom_cookie_t tray_cookie; + xcb_intern_atom_reply_t *tray_reply; + tray_cookie = xcb_intern_atom(xcb_connection, 0, strlen(atomname), atomname); + + /* tray support: we need a window to own the selection */ xcb_void_cookie_t selwin_cookie; xcb_window_t selwin = xcb_generate_id(xcb_connection); uint32_t selmask = XCB_CW_OVERRIDE_REDIRECT; @@ -894,8 +902,6 @@ void init_tray() { selmask, selval); -#define _NET_SYSTEM_TRAY_ORIENTATION_HORZ 0 -#define _NET_SYSTEM_TRAY_ORIENTATION_VERT 1 uint32_t orientation = _NET_SYSTEM_TRAY_ORIENTATION_HORZ; /* set the atoms */ xcb_change_property(xcb_connection, @@ -907,13 +913,15 @@ void init_tray() { 1, &orientation); + if (!(tray_reply = xcb_intern_atom_reply(xcb_connection, tray_cookie, NULL))) { + ELOG("Could not get atom %s\n", atomname); + exit(EXIT_FAILURE); + } xcb_set_selection_owner(xcb_connection, selwin, - /* TODO: request this atom separately */ - atoms[_NET_SYSTEM_TRAY_S0], + tray_reply->atom, XCB_CURRENT_TIME); - /* FIXME: don't use XCB_CURRENT_TIME */ /* TODO: check if we got the selection */ void *event = calloc(32, 1); @@ -923,7 +931,7 @@ void init_tray() { ev->type = atoms[MANAGER]; ev->format = 32; ev->data.data32[0] = XCB_CURRENT_TIME; - ev->data.data32[1] = atoms[_NET_SYSTEM_TRAY_S0]; + ev->data.data32[1] = tray_reply->atom; ev->data.data32[2] = selwin; xcb_send_event(xcb_connection, 0, @@ -931,6 +939,7 @@ void init_tray() { XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)ev); free(event); + free(tray_reply); } /* From bd2a3363c03dbea9bd10e754402bcd24a0d0e44d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 17 Aug 2011 01:12:42 +0200 Subject: [PATCH 026/333] i3bar: tray: little cleanups, more comments --- i3bar/src/xcb.c | 73 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 0cb8bcc8..e35c4355 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -411,7 +411,14 @@ static void configure_trayclients() { } } -void handle_client_message(xcb_client_message_event_t* event) { +/* + * Handles ClientMessages (messages sent from another client directly to us). + * + * At the moment, only the tray window will receive client messages. All + * supported client messages currently are _NET_SYSTEM_TRAY_OPCODE. + * + */ +static void handle_client_message(xcb_client_message_event_t* event) { if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] && event->format == 32) { DLOG("_NET_SYSTEM_TRAY_OPCODE received\n"); @@ -474,7 +481,7 @@ void handle_client_message(xcb_client_message_event_t* event) { xcb_reparent_window(xcb_connection, client, output->bar, - output->rect.w - font_height - 2, /* TODO: why -2? */ + output->rect.w - font_height - 2, 2); /* We reconfigure the window to use a reasonable size. The systray * specification explicitly says: @@ -527,7 +534,12 @@ void handle_client_message(xcb_client_message_event_t* event) { } } -void handle_unmap_notify(xcb_unmap_notify_event_t* event) { +/* + * Handles UnmapNotify events. These events happen when a tray window unmaps + * itself. We then update our data structure + * + */ +static void handle_unmap_notify(xcb_unmap_notify_event_t* event) { DLOG("UnmapNotify for window = %08x, event = %08x\n", event->window, event->event); i3_output *walk; @@ -551,6 +563,11 @@ void handle_unmap_notify(xcb_unmap_notify_event_t* event) { } } +/* + * Handle PropertyNotify messages. Currently only the _XEMBED_INFO property is + * handled, which tells us whether a dock client should be mapped or unmapped. + * + */ static void handle_property_notify(xcb_property_notify_event_t *event) { DLOG("PropertyNotify\n"); if (event->atom == atoms[_XEMBED_INFO] && @@ -877,6 +894,12 @@ char *init_xcb(char *fontname) { return path; } +/* + * Initializes tray support by requesting the appropriate _NET_SYSTEM_TRAY atom + * for the X11 display we are running on, then acquiring the selection for this + * atom. Afterwards, tray clients will send ClientMessages to our window. + * + */ void init_tray() { /* request the tray manager atom for the X11 display we are running on */ char atomname[strlen("_NET_SYSTEM_TRAY_S") + 11]; @@ -886,21 +909,20 @@ void init_tray() { tray_cookie = xcb_intern_atom(xcb_connection, 0, strlen(atomname), atomname); /* tray support: we need a window to own the selection */ - xcb_void_cookie_t selwin_cookie; xcb_window_t selwin = xcb_generate_id(xcb_connection); uint32_t selmask = XCB_CW_OVERRIDE_REDIRECT; uint32_t selval[] = { 1 }; - selwin_cookie = xcb_create_window_checked(xcb_connection, - xcb_screen->root_depth, - selwin, - xcb_root, - -1, -1, - 1, 1, - 1, - XCB_WINDOW_CLASS_INPUT_OUTPUT, - xcb_screen->root_visual, - selmask, - selval); + xcb_create_window(xcb_connection, + xcb_screen->root_depth, + selwin, + xcb_root, + -1, -1, + 1, 1, + 1, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + xcb_screen->root_visual, + selmask, + selval); uint32_t orientation = _NET_SYSTEM_TRAY_ORIENTATION_HORZ; /* set the atoms */ @@ -923,7 +945,26 @@ void init_tray() { tray_reply->atom, XCB_CURRENT_TIME); - /* TODO: check if we got the selection */ + /* Verify that we have the selection */ + xcb_get_selection_owner_cookie_t selcookie; + xcb_get_selection_owner_reply_t *selreply; + + selcookie = xcb_get_selection_owner(xcb_connection, tray_reply->atom); + if (!(selreply = xcb_get_selection_owner_reply(xcb_connection, selcookie, NULL))) { + ELOG("Could not get selection owner for %s\n", atomname); + exit(EXIT_FAILURE); + } + + if (selreply->owner != selwin) { + ELOG("Could not set the %s selection. " \ + "Maybe another tray is already running?\n", atomname); + /* NOTE that this error is not fatal. We just can’t provide tray + * functionality */ + free(selreply); + return; + } + + /* Inform clients waiting for a new _NET_SYSTEM_TRAY that we are here */ void *event = calloc(32, 1); xcb_client_message_event_t *ev = event; ev->response_type = XCB_CLIENT_MESSAGE; From 1c2c22d11756ad7eeae0a628f0bfb2ef749c9bea Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 17 Aug 2011 01:19:38 +0200 Subject: [PATCH 027/333] i3bar: properly end the XEMBED protocol by reparenting the dock clients to root, flush connection before disconnecting --- i3bar/src/xcb.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index e35c4355..00d25272 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -990,15 +990,27 @@ void init_tray() { */ void clean_xcb() { i3_output *o_walk; + trayclient *trayclient; free_workspaces(); SLIST_FOREACH(o_walk, outputs, slist) { + TAILQ_FOREACH(trayclient, o_walk->trayclients, tailq) { + /* Unmap, then reparent (to root) the tray client windows */ + xcb_unmap_window(xcb_connection, trayclient->win); + xcb_reparent_window(xcb_connection, + trayclient->win, + xcb_root, + 0, + 0); + } destroy_window(o_walk); + FREE(o_walk->trayclients); FREE(o_walk->workspaces); FREE(o_walk->name); } FREE_SLIST(outputs, i3_output); FREE(outputs); + xcb_flush(xcb_connection); xcb_disconnect(xcb_connection); ev_check_stop(main_loop, xcb_chk); From 06ba1c0e65a8bbf5b06a14c4bc0045e23f83619d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 17 Aug 2011 01:32:29 +0200 Subject: [PATCH 028/333] Fix compilation with xcb 0.3.6 --- i3bar/include/xcb.h | 4 ++++ i3bar/include/xcb_atoms.def | 1 - i3bar/src/xcb.c | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/i3bar/include/xcb.h b/i3bar/include/xcb.h index 0276d3c7..c1b7cc14 100644 --- a/i3bar/include/xcb.h +++ b/i3bar/include/xcb.h @@ -12,6 +12,10 @@ #include //#include "outputs.h" +#ifdef XCB_COMPAT +#define XCB_ATOM_CARDINAL CARDINAL +#endif + #define _NET_SYSTEM_TRAY_ORIENTATION_HORZ 0 #define _NET_SYSTEM_TRAY_ORIENTATION_VERT 1 #define SYSTEM_TRAY_REQUEST_DOCK 0 diff --git a/i3bar/include/xcb_atoms.def b/i3bar/include/xcb_atoms.def index 0c0b207e..b75ceabd 100644 --- a/i3bar/include/xcb_atoms.def +++ b/i3bar/include/xcb_atoms.def @@ -5,7 +5,6 @@ ATOM_DO(I3_SOCKET_PATH) ATOM_DO(MANAGER) ATOM_DO(_NET_SYSTEM_TRAY_ORIENTATION) ATOM_DO(_NET_SYSTEM_TRAY_VISUAL) -ATOM_DO(CARDINAL) ATOM_DO(_NET_SYSTEM_TRAY_OPCODE) ATOM_DO(_XEMBED_INFO) ATOM_DO(_XEMBED) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 00d25272..e25bc959 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -930,7 +930,7 @@ void init_tray() { XCB_PROP_MODE_REPLACE, selwin, atoms[_NET_SYSTEM_TRAY_ORIENTATION], - atoms[CARDINAL], + XCB_ATOM_CARDINAL, 32, 1, &orientation); From 27ade541a9cc93d25f0d705b2f1151111551e0ad Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Aug 2011 01:34:56 +0200 Subject: [PATCH 029/333] Initialize output (fixes compiler warning) --- i3bar/src/xcb.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index e25bc959..57d21f72 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -471,13 +471,17 @@ static void handle_client_message(xcb_client_message_event_t* event) { } DLOG("X window %08x requested docking\n", client); - i3_output *walk, *output; + i3_output *walk, *output = NULL; SLIST_FOREACH(walk, outputs, slist) { if (!walk->active) continue; DLOG("using output %s\n", walk->name); output = walk; } + if (output == NULL) { + ELOG("No output found\n"); + return; + } xcb_reparent_window(xcb_connection, client, output->bar, From 2acbf4d4a42e2c0eacb734463b64bba8f95103ad Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Aug 2011 23:06:49 +0200 Subject: [PATCH 030/333] Compilation fix: Add dont_warp=false --- src/cmdparse.y | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmdparse.y b/src/cmdparse.y index ab9672c0..51e0f7bc 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -745,7 +745,7 @@ move: TAILQ_FOREACH(current, &owindows, owindows) { printf("matching: %p / %s\n", current->con, current->con->name); - con_move_to_workspace(current->con, ws); + con_move_to_workspace(current->con, ws, false); } tree_render(); From 8e8b9b93f4fbd1680430c04904f560a67a0591c5 Mon Sep 17 00:00:00 2001 From: stfn Date: Fri, 26 Aug 2011 03:44:42 +0200 Subject: [PATCH 031/333] ipc.c: Fix warning --- src/ipc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ipc.c b/src/ipc.c index 7ec2592b..577538c2 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -490,7 +490,11 @@ IPC_HANDLER(get_marks) { y(array_close); const unsigned char *payload; +#if YAJL_MAJOR >= 2 + size_t length; +#else unsigned int length; +#endif y(get_buf, &payload, &length); ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_MARKS, length); From dfda878272a40bb9ed5d1d7af53b6a4fcbebe223 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Aug 2011 18:17:02 +0200 Subject: [PATCH 032/333] Bugfix: Correctly assign a number to workspaces starting with '0: ' (Thanks SardemFF7) --- src/workspace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/workspace.c b/src/workspace.c index c71fa511..963fa64b 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -53,7 +53,7 @@ Con *workspace_get(const char *num, bool *created) { long parsed_num = strtol(num, NULL, 10); if (parsed_num == LONG_MIN || parsed_num == LONG_MAX || - parsed_num <= 0) + parsed_num < 0) workspace->num = -1; else workspace->num = parsed_num; LOG("num = %d\n", workspace->num); From 8928b5f55de9ad2e13b3f50f9bec5d42221eeb30 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 5 Sep 2011 22:21:10 +0200 Subject: [PATCH 033/333] Bugfix: Correctly handle workspace names which do not start with a zero --- src/randr.c | 6 ++++-- src/workspace.c | 7 ++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/randr.c b/src/randr.c index 6b6cd67d..dd30925b 100644 --- a/src/randr.c +++ b/src/randr.c @@ -447,10 +447,12 @@ void init_ws_for_output(Output *output, Con *content) { if (!exists) { /* Set ->num to the number of the workspace, if the name actually * is a number or starts with a number */ - long parsed_num = strtol(ws->name, NULL, 10); + char *endptr = NULL; + long parsed_num = strtol(ws->name, &endptr, 10); if (parsed_num == LONG_MIN || parsed_num == LONG_MAX || - parsed_num <= 0) + parsed_num < 0 || + endptr == ws->name) ws->num = -1; else ws->num = parsed_num; LOG("Used number %d for workspace with name %s\n", ws->num, ws->name); diff --git a/src/workspace.c b/src/workspace.c index 963fa64b..27899a37 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -49,11 +49,12 @@ Con *workspace_get(const char *num, bool *created) { workspace->name = sstrdup(num); /* We set ->num to the number if this workspace’s name consists only of * a positive number. Otherwise it’s a named ws and num will be -1. */ - - long parsed_num = strtol(num, NULL, 10); + char *endptr = NULL; + long parsed_num = strtol(num, &endptr, 10); if (parsed_num == LONG_MIN || parsed_num == LONG_MAX || - parsed_num < 0) + parsed_num < 0 || + endptr == num) workspace->num = -1; else workspace->num = parsed_num; LOG("num = %d\n", workspace->num); From bf4d166330367b83e07b4656904c21d713984847 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 5 Sep 2011 22:24:28 +0200 Subject: [PATCH 034/333] extend t/17-workspace.t to check if the numbers are assigned correctly --- testcases/t/17-workspace.t | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/testcases/t/17-workspace.t b/testcases/t/17-workspace.t index 19e2df34..3c3b6cc6 100644 --- a/testcases/t/17-workspace.t +++ b/testcases/t/17-workspace.t @@ -98,4 +98,23 @@ cmd 'workspace "prev"'; ok(workspace_exists('prev'), 'workspace "prev" exists'); is(focused_ws(), 'prev', 'now on workspace prev'); +##################################################################### +# check that the numbers are assigned/recognized correctly +##################################################################### + +cmd "workspace 3: $tmp"; +my $ws = get_ws("3: $tmp"); +ok(defined($ws), "workspace 3: $tmp was created"); +is($ws->{num}, 3, 'workspace number is 3'); + +cmd "workspace 0: $tmp"; +my $ws = get_ws("0: $tmp"); +ok(defined($ws), "workspace 0: $tmp was created"); +is($ws->{num}, 0, 'workspace number is 0'); + +cmd "workspace aa: $tmp"; +my $ws = get_ws("aa: $tmp"); +ok(defined($ws), "workspace aa: $tmp was created"); +is($ws->{num}, -1, 'workspace number is -1'); + done_testing; From 77ae771476f663252f1751e0baf097f899942566 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 5 Sep 2011 22:36:04 +0200 Subject: [PATCH 035/333] Restrict 'resize' command to left/right for horizontal cons, up/down for vertical cons This makes the interface much clearer and avoids confusion about which key to press in which situation. --- src/cmdparse.y | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/cmdparse.y b/src/cmdparse.y index 51e0f7bc..72ffae6a 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -857,6 +857,17 @@ resize: double percentage = 1.0 / children; LOG("default percentage = %f\n", percentage); + orientation_t orientation = current->parent->orientation; + + if ((orientation == HORIZ && + (direction == TOK_UP || direction == TOK_DOWN)) || + (orientation == VERT && + (direction == TOK_LEFT || direction == TOK_RIGHT))) { + LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n", + (orientation == HORIZ ? "horizontal" : "vertical")); + break; + } + if (direction == TOK_UP || direction == TOK_LEFT) { other = TAILQ_PREV(current, nodes_head, nodes); } else { @@ -864,7 +875,7 @@ resize: } if (other == TAILQ_END(workspaces)) { LOG("No other container in this direction found, cannot resize.\n"); - return 0; + break; } LOG("other->percent = %f\n", other->percent); LOG("current->percent before = %f\n", current->percent); From 32ad9f7e3ad9b95a24a49d80a56d304b2d7ea219 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 5 Sep 2011 22:55:31 +0200 Subject: [PATCH 036/333] i3-msg: include newline at the end of the reply --- i3-msg/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i3-msg/main.c b/i3-msg/main.c index d8195e08..2d7cef0e 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -245,7 +245,7 @@ int main(int argc, char *argv[]) { uint32_t reply_length; uint8_t *reply; ipc_recv_message(sockfd, message_type, &reply_length, &reply); - printf("%.*s", reply_length, reply); + printf("%.*s\n", reply_length, reply); free(reply); close(sockfd); From 8e04867e5116d2fd95b82cc7101872b1c96ed7d2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 10 Sep 2011 23:52:39 +0100 Subject: [PATCH 037/333] extend t/19-match to also test regular expressions --- testcases/t/19-match.t | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/testcases/t/19-match.t b/testcases/t/19-match.t index 2332bc71..4d38c41f 100644 --- a/testcases/t/19-match.t +++ b/testcases/t/19-match.t @@ -34,9 +34,10 @@ my $win = $content->[0]; # first test that matches which should not match this window really do # not match it ###################################################################### -# TODO: use PCRE expressions # TODO: specify more match types -cmd q|[class="*"] kill|; +# we can match on any (non-empty) class here since that window does not have +# WM_CLASS set +cmd q|[class=".*"] kill|; cmd q|[con_id="99999"] kill|; $content = get_ws_content($tmp); @@ -118,4 +119,34 @@ sleep 0.25; $content = get_ws_content($tmp); is(@{$content}, 1, 'one window still there'); +###################################################################### +# check that regular expressions work +###################################################################### + +$tmp = fresh_workspace; + +$left = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30 ], + background_color => '#0000ff', +); + +$left->_create; +set_wm_class($left->id, 'special7', 'special7'); +$left->name('left'); +$left->map; +sleep 0.25; + +# two windows should be here +$content = get_ws_content($tmp); +ok(@{$content} == 1, 'two windows opened'); + +cmd '[class="^special[0-9]$"] kill'; + +sleep 0.25; + +$content = get_ws_content($tmp); +is(@{$content}, 0, 'window killed'); + + done_testing; From 2fc54aadf1ae7cbd4b08442b7a933950a22c66af Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 10 Sep 2011 23:53:11 +0100 Subject: [PATCH 038/333] Implement support for PCRE regular expressions for all criteria (for_window, commands, assignments) --- common.mk | 2 ++ include/all.h | 1 + include/data.h | 27 ++++++++++++++++----- include/regex.h | 28 ++++++++++++++++++++++ src/cfgparse.y | 26 ++++++++++++-------- src/cmdparse.y | 17 +++++++------ src/match.c | 28 ++++++++++++---------- src/regex.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 157 insertions(+), 35 deletions(-) create mode 100644 include/regex.h create mode 100644 src/regex.c diff --git a/common.mk b/common.mk index ce41f287..62eb9958 100644 --- a/common.mk +++ b/common.mk @@ -49,6 +49,7 @@ CFLAGS += $(call cflags_for_lib, xcursor) CFLAGS += $(call cflags_for_lib, x11) CFLAGS += $(call cflags_for_lib, yajl) CFLAGS += $(call cflags_for_lib, libev) +CFLAGS += $(call cflags_for_lib, libpcre) CPPFLAGS += -DI3_VERSION=\"${GIT_VERSION}\" CPPFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\" CPPFLAGS += -DTERM_EMU=\"$(TERM_EMU)\" @@ -70,6 +71,7 @@ LIBS += $(call ldflags_for_lib, xcursor, Xcursor) LIBS += $(call ldflags_for_lib, x11, X11) LIBS += $(call ldflags_for_lib, yajl, yajl) LIBS += $(call ldflags_for_lib, libev, ev) +LIBS += $(call ldflags_for_lib, libpcre, pcre) # Please test if -Wl,--as-needed works on your platform and send me a patch. # it is known not to work on Darwin (Mac OS X) diff --git a/include/all.h b/include/all.h index b87be518..9c08ebef 100644 --- a/include/all.h +++ b/include/all.h @@ -64,5 +64,6 @@ #include "output.h" #include "ewmh.h" #include "assignments.h" +#include "regex.h" #endif diff --git a/include/data.h b/include/data.h index 5797b7d8..122833c3 100644 --- a/include/data.h +++ b/include/data.h @@ -10,6 +10,7 @@ #include #include #include +#include #ifndef _DATA_H #define _DATA_H @@ -137,6 +138,21 @@ struct Ignore_Event { SLIST_ENTRY(Ignore_Event) ignore_events; }; +/** + * Regular expression wrapper. It contains the pattern itself as a string (like + * ^foo[0-9]$) as well as a pointer to the compiled PCRE expression and the + * pcre_extra data returned by pcre_study(). + * + * This makes it easier to have a useful logfile, including the matching or + * non-matching pattern. + * + */ +struct regex { + const char *pattern; + pcre *regex; + pcre_extra *extra; +}; + /****************************************************************************** * Major types *****************************************************************************/ @@ -277,12 +293,11 @@ struct Window { }; struct Match { - char *title; - int title_len; - char *application; - char *class; - char *instance; - char *mark; + struct regex *title; + struct regex *application; + struct regex *class; + struct regex *instance; + struct regex *mark; enum { M_DONTCHECK = -1, M_NODOCK = 0, diff --git a/include/regex.h b/include/regex.h new file mode 100644 index 00000000..2a6608a8 --- /dev/null +++ b/include/regex.h @@ -0,0 +1,28 @@ +/* + * vim:ts=4:sw=4:expandtab + * + */ +#ifndef _REGEX_H +#define _REGEX_H + +/** + * Creates a new 'regex' struct containing the given pattern and a PCRE + * compiled regular expression. Also, calls pcre_study because this regex will + * most likely be used often (like for every new window and on every relevant + * property change of existing windows). + * + * Returns NULL if the pattern could not be compiled into a regular expression + * (and ELOGs an appropriate error message). + * + */ +struct regex *regex_new(const char *pattern); + +/** + * Checks if the given regular expression matches the given input and returns + * true if it does. In either case, it logs the outcome using LOG(), so it will + * be visible without any debug loglevel. + * + */ +bool regex_matches(struct regex *regex, const char *input); + +#endif diff --git a/src/cfgparse.y b/src/cfgparse.y index 1cbcaf9e..868640e0 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -751,12 +751,14 @@ criterion: TOK_CLASS '=' STR { printf("criteria: class = %s\n", $3); - current_match.class = $3; + current_match.class = regex_new($3); + free($3); } | TOK_INSTANCE '=' STR { printf("criteria: instance = %s\n", $3); - current_match.instance = $3; + current_match.instance = regex_new($3); + free($3); } | TOK_CON_ID '=' STR { @@ -791,12 +793,14 @@ criterion: | TOK_MARK '=' STR { printf("criteria: mark = %s\n", $3); - current_match.mark = $3; + current_match.mark = regex_new($3); + free($3); } | TOK_TITLE '=' STR { printf("criteria: title = %s\n", $3); - current_match.title = $3; + current_match.title = regex_new($3); + free($3); } ; @@ -1054,6 +1058,8 @@ workspace_name: assign: TOKASSIGN window_class STR { + /* TODO: the assign command also needs some kind of new syntax where we + * just use criteria. Then deprecate the old form */ printf("assignment of %s to *%s*\n", $2, $3); char *workspace = $3; char *criteria = $2; @@ -1065,15 +1071,15 @@ assign: char *separator = NULL; if ((separator = strchr(criteria, '/')) != NULL) { *(separator++) = '\0'; - match->title = sstrdup(separator); + match->title = regex_new(separator); + printf(" title = %s\n", separator); + } + if (*criteria != '\0') { + match->class = regex_new(criteria); + printf(" class = %s\n", criteria); } - if (*criteria != '\0') - match->class = sstrdup(criteria); free(criteria); - printf(" class = %s\n", match->class); - printf(" title = %s\n", match->title); - /* Compatibility with older versions: If the assignment target starts * with ~, we create the equivalent of: * diff --git a/src/cmdparse.y b/src/cmdparse.y index 72ffae6a..83abbbf2 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -267,10 +267,9 @@ matchend: } } else if (current_match.mark != NULL && current->con->mark != NULL && - strcasecmp(current_match.mark, current->con->mark) == 0) { + regex_matches(current_match.mark, current->con->mark)) { printf("match by mark\n"); - TAILQ_INSERT_TAIL(&owindows, current, owindows); - + TAILQ_INSERT_TAIL(&owindows, current, owindows); } else { if (current->con->window == NULL) continue; @@ -300,12 +299,14 @@ criterion: TOK_CLASS '=' STR { printf("criteria: class = %s\n", $3); - current_match.class = $3; + current_match.class = regex_new($3); + free($3); } | TOK_INSTANCE '=' STR { printf("criteria: instance = %s\n", $3); - current_match.instance = $3; + current_match.instance = regex_new($3); + free($3); } | TOK_CON_ID '=' STR { @@ -340,12 +341,14 @@ criterion: | TOK_MARK '=' STR { printf("criteria: mark = %s\n", $3); - current_match.mark = $3; + current_match.mark = regex_new($3); + free($3); } | TOK_TITLE '=' STR { printf("criteria: title = %s\n", $3); - current_match.title = $3; + current_match.title = regex_new($3); + free($3); } ; diff --git a/src/match.c b/src/match.c index 3a346117..85d2eaab 100644 --- a/src/match.c +++ b/src/match.c @@ -52,16 +52,19 @@ bool match_is_empty(Match *match) { void match_copy(Match *dest, Match *src) { memcpy(dest, src, sizeof(Match)); -#define STRDUP(field) do { \ +/* The DUPLICATE_REGEX macro creates a new regular expression from the + * ->pattern of the old one. It therefore does use a little more memory then + * with a refcounting system, but it’s easier this way. */ +#define DUPLICATE_REGEX(field) do { \ if (src->field != NULL) \ - dest->field = sstrdup(src->field); \ + dest->field = regex_new(src->field->pattern); \ } while (0) - STRDUP(title); - STRDUP(mark); - STRDUP(application); - STRDUP(class); - STRDUP(instance); + DUPLICATE_REGEX(title); + DUPLICATE_REGEX(mark); + DUPLICATE_REGEX(application); + DUPLICATE_REGEX(class); + DUPLICATE_REGEX(instance); } /* @@ -71,9 +74,9 @@ void match_copy(Match *dest, Match *src) { bool match_matches_window(Match *match, i3Window *window) { LOG("checking window %d (%s)\n", window->id, window->class_class); - /* TODO: pcre, full matching, … */ if (match->class != NULL) { - if (window->class_class != NULL && strcasecmp(match->class, window->class_class) == 0) { + if (window->class_class != NULL && + regex_matches(match->class, window->class_class)) { LOG("window class matches (%s)\n", window->class_class); } else { LOG("window class does not match\n"); @@ -82,7 +85,8 @@ bool match_matches_window(Match *match, i3Window *window) { } if (match->instance != NULL) { - if (window->class_instance != NULL && strcasecmp(match->instance, window->class_instance) == 0) { + if (window->class_instance != NULL && + regex_matches(match->instance, window->class_instance)) { LOG("window instance matches (%s)\n", window->class_instance); } else { LOG("window instance does not match\n"); @@ -99,9 +103,9 @@ bool match_matches_window(Match *match, i3Window *window) { } } - /* TODO: pcre match */ if (match->title != NULL) { - if (window->name_json != NULL && strcasecmp(match->title, window->name_json) == 0) { + if (window->name_json != NULL && + regex_matches(match->title, window->name_json)) { LOG("title matches (%s)\n", window->name_json); } else { LOG("title does not match\n"); diff --git a/src/regex.c b/src/regex.c new file mode 100644 index 00000000..df01dae4 --- /dev/null +++ b/src/regex.c @@ -0,0 +1,63 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) + * + * + */ + +#include "all.h" + +/* + * Creates a new 'regex' struct containing the given pattern and a PCRE + * compiled regular expression. Also, calls pcre_study because this regex will + * most likely be used often (like for every new window and on every relevant + * property change of existing windows). + * + * Returns NULL if the pattern could not be compiled into a regular expression + * (and ELOGs an appropriate error message). + * + */ +struct regex *regex_new(const char *pattern) { + const char *error; + int offset; + + struct regex *re = scalloc(sizeof(struct regex)); + re->pattern = sstrdup(pattern); + if (!(re->regex = pcre_compile(pattern, 0, &error, &offset, NULL))) { + ELOG("PCRE regular expression compilation failed at %d: %s", + offset, error); + return NULL; + } + re->extra = pcre_study(re->regex, 0, &error); + return re; +} + +/* + * Checks if the given regular expression matches the given input and returns + * true if it does. In either case, it logs the outcome using LOG(), so it will + * be visible without any debug loglevel. + * + */ +bool regex_matches(struct regex *regex, const char *input) { + int rc; + + /* TODO: is strlen(input) correct for UTF-8 matching? */ + /* TODO: enable UTF-8 */ + if ((rc = pcre_exec(regex->regex, regex->extra, input, strlen(input), 0, 0, NULL, 0)) == 0) { + LOG("Regular expression \"%s\" matches \"%s\"\n", + regex->pattern, input); + return true; + } + + if (rc == PCRE_ERROR_NOMATCH) { + LOG("Regular expression \"%s\" does not match \"%s\"\n", + regex->pattern, input); + return false; + } + + /* TODO: handle the other error codes */ + LOG("PCRE error\n"); + return false; +} From 430dadfa34df3aedded96039ef9b14e22063179a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 10 Sep 2011 23:55:49 +0100 Subject: [PATCH 039/333] document the new dependency on libpcre in the DEPENDS file --- DEPENDS | 1 + 1 file changed, 1 insertion(+) diff --git a/DEPENDS b/DEPENDS index ea7133a5..a6c0986d 100644 --- a/DEPENDS +++ b/DEPENDS @@ -20,6 +20,7 @@ │ docbook-xml │ 4.5 │ 4.5 │ http://www.methods.co.nz/asciidoc/ │ │ libxcursor │ 1.1.11 │ 1.1.11 │ http://ftp.x.org/pub/current/src/lib/ │ │ Xlib │ 1.3.3 │ 1.4.3 │ http://ftp.x.org/pub/current/src/lib/ │ +│ PCRE │ 8.12 │ 8.12 │ http://www.pcre.org/ │ └─────────────┴────────┴────────┴────────────────────────────────────────┘ i3-msg, i3-input, i3-nagbar and i3-config-wizard do not introduce any new From 8f0e0dee58af7ae672cb3bb29c446121fc94d7c9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 10 Sep 2011 23:56:15 +0100 Subject: [PATCH 040/333] debian: add libpcre3-dev as build-dependency --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index b546e650..da13231f 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Section: utils Priority: extra Maintainer: Michael Stapelberg DM-Upload-Allowed: yes -Build-Depends: debhelper (>= 6), libx11-dev, libxcb-util0-dev (>= 0.3.8), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-icccm4-dev, libxcursor-dev, asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev, perl, texlive-latex-base, texlive-latex-recommended, texlive-latex-extra +Build-Depends: debhelper (>= 6), libx11-dev, libxcb-util0-dev (>= 0.3.8), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-icccm4-dev, libxcursor-dev, asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev, perl, texlive-latex-base, texlive-latex-recommended, texlive-latex-extra, libpcre3-dev Standards-Version: 3.9.2 Homepage: http://i3wm.org/ From 1a91c695e7abba449f27b6bd6c7897738dad1fae Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 11 Sep 2011 11:10:09 +0100 Subject: [PATCH 041/333] mention PCRE in docs/userguide --- docs/userguide | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/userguide b/docs/userguide index 865ee4b9..597cdb51 100644 --- a/docs/userguide +++ b/docs/userguide @@ -739,8 +739,9 @@ con_id:: Compares the i3-internal container ID, which you can get via the IPC interface. Handy for scripting. -Note that currently all criteria are compared case-insensitive and do not -support regular expressions. This is planned to change in the future. +The criteria +class+, +instance+, +title+ and +mark+ are actually regular +expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for information on +how to use them. === Splitting containers From c1c17305161dda72461e237c2058668aeafbcd5b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 11 Sep 2011 11:40:51 +0100 Subject: [PATCH 042/333] pcre: enable UCP, UTF-8 (if available), extend t/19-match --- src/regex.c | 29 ++++++++++++++++++++++------- testcases/t/19-match.t | 30 +++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/regex.c b/src/regex.c index df01dae4..da8f91d8 100644 --- a/src/regex.c +++ b/src/regex.c @@ -21,16 +21,31 @@ */ struct regex *regex_new(const char *pattern) { const char *error; - int offset; + int errorcode, offset; struct regex *re = scalloc(sizeof(struct regex)); re->pattern = sstrdup(pattern); - if (!(re->regex = pcre_compile(pattern, 0, &error, &offset, NULL))) { - ELOG("PCRE regular expression compilation failed at %d: %s", + /* We use PCRE_UCP so that \B, \b, \D, \d, \S, \s, \W, \w and some POSIX + * character classes play nicely with Unicode */ + int options = PCRE_UCP | PCRE_UTF8; + while (!(re->regex = pcre_compile2(pattern, options, &errorcode, &error, &offset, NULL))) { + /* If the error is that PCRE was not compiled with UTF-8 support we + * disable it and try again */ + if (errorcode == 32) { + options &= ~PCRE_UTF8; + continue; + } + ELOG("PCRE regular expression compilation failed at %d: %s\n", offset, error); return NULL; } re->extra = pcre_study(re->regex, 0, &error); + /* If an error happened, we print the error message, but continue. + * Studying the regular expression leads to faster matching, but it’s not + * absolutely necessary. */ + if (error) { + ELOG("PCRE regular expression studying failed: %s\n", error); + } return re; } @@ -43,8 +58,8 @@ struct regex *regex_new(const char *pattern) { bool regex_matches(struct regex *regex, const char *input) { int rc; - /* TODO: is strlen(input) correct for UTF-8 matching? */ - /* TODO: enable UTF-8 */ + /* We use strlen() because pcre_exec() expects the length of the input + * string in bytes */ if ((rc = pcre_exec(regex->regex, regex->extra, input, strlen(input), 0, 0, NULL, 0)) == 0) { LOG("Regular expression \"%s\" matches \"%s\"\n", regex->pattern, input); @@ -57,7 +72,7 @@ bool regex_matches(struct regex *regex, const char *input) { return false; } - /* TODO: handle the other error codes */ - LOG("PCRE error\n"); + ELOG("PCRE error %d while trying to use regular expression \"%s\" on input \"%s\", see pcreapi(3)\n", + rc, regex->pattern, input); return false; } diff --git a/testcases/t/19-match.t b/testcases/t/19-match.t index 4d38c41f..e4fc6ec0 100644 --- a/testcases/t/19-match.t +++ b/testcases/t/19-match.t @@ -139,7 +139,7 @@ sleep 0.25; # two windows should be here $content = get_ws_content($tmp); -ok(@{$content} == 1, 'two windows opened'); +ok(@{$content} == 1, 'window opened'); cmd '[class="^special[0-9]$"] kill'; @@ -148,5 +148,33 @@ sleep 0.25; $content = get_ws_content($tmp); is(@{$content}, 0, 'window killed'); +###################################################################### +# check that UTF-8 works when matching +###################################################################### + +$tmp = fresh_workspace; + +$left = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30 ], + background_color => '#0000ff', +); + +$left->_create; +set_wm_class($left->id, 'special7', 'special7'); +$left->name('ä 3'); +$left->map; +sleep 0.25; + +# two windows should be here +$content = get_ws_content($tmp); +ok(@{$content} == 1, 'window opened'); + +cmd '[title="^\w [3]$"] kill'; + +sleep 0.25; + +$content = get_ws_content($tmp); +is(@{$content}, 0, 'window killed'); done_testing; From b3e1fb1f3b04290bceaa3ab7915b7d1119be93ac Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 11 Sep 2011 21:16:45 +0100 Subject: [PATCH 043/333] Make the old 'assign' case-insensitive again (+test) (Thanks aksr) --- src/cfgparse.y | 16 ++++++++++++++-- testcases/t/66-assign.t | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/cfgparse.y b/src/cfgparse.y index 868640e0..7efecc92 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -1071,11 +1071,23 @@ assign: char *separator = NULL; if ((separator = strchr(criteria, '/')) != NULL) { *(separator++) = '\0'; - match->title = regex_new(separator); + char *pattern; + if (asprintf(&pattern, "(?i)%s", separator) == -1) { + ELOG("asprintf failed\n"); + break; + } + match->title = regex_new(pattern); + free(pattern); printf(" title = %s\n", separator); } if (*criteria != '\0') { - match->class = regex_new(criteria); + char *pattern; + if (asprintf(&pattern, "(?i)%s", criteria) == -1) { + ELOG("asprintf failed\n"); + break; + } + match->class = regex_new(pattern); + free(pattern); printf(" class = %s\n", criteria); } free(criteria); diff --git a/testcases/t/66-assign.t b/testcases/t/66-assign.t index 776710e7..25855b58 100644 --- a/testcases/t/66-assign.t +++ b/testcases/t/66-assign.t @@ -182,6 +182,46 @@ exit_gracefully($process->pid); sleep 0.25; +##################################################################### +# make sure that assignments are case-insensitive in the old syntax. +##################################################################### + +$config = <root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30 ], + background_color => '#0000ff', +); + +$window->_create; +set_wm_class($window->id, 'SPEcial', 'SPEcial'); +$window->name('special window'); +$window->map; +sleep 0.25; + +my $content = get_ws($tmp); +ok(@{$content->{nodes}} == 0, 'no tiling cons'); +ok(@{$content->{floating_nodes}} == 1, 'one floating con'); + +$window->destroy; + +exit_gracefully($process->pid); + +sleep 0.25; + ##################################################################### # regression test: dock clients with floating assignments should not crash # (instead, nothing should happen - dock clients can’t float) From 9b671bda83f62adeb69dcbfdd1c9feea7477e06e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 11 Sep 2011 21:17:13 +0100 Subject: [PATCH 044/333] docs/userguide: provide an example of case-insensitive matching with PCRE (Thanks stfn) --- docs/userguide | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/userguide b/docs/userguide index 597cdb51..0ee56567 100644 --- a/docs/userguide +++ b/docs/userguide @@ -721,6 +721,9 @@ which have the class Firefox, use: *Example*: ------------------------------------ bindsym mod+x [class="Firefox"] kill + +# same thing, but case-insensitive +bindsym mod+x [class="(?i)firefox"] kill ------------------------------------ The criteria which are currently implemented are: From d03dffe012ca1807b396270a4b145bfb0ae6d63a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 11 Sep 2011 21:49:35 +0100 Subject: [PATCH 045/333] Kill left-over i3-nagbar processes on 'exit' --- src/cfgparse.y | 18 ++++++++++++++++++ src/main.c | 12 ++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/cfgparse.y b/src/cfgparse.y index 7efecc92..ca2d0710 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -240,6 +240,18 @@ static void nagbar_exited(EV_P_ ev_child *watcher, int revents) { configerror_pid = -1; } +/* + * Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal + * SIGKILL (9) to make sure there are no left-over i3-nagbar processes. + * + */ +static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) { + if (configerror_pid != -1) { + LOG("Sending SIGKILL (9) to i3-nagbar with PID %d\n", configerror_pid); + kill(configerror_pid, SIGKILL); + } +} + /* * Starts an i3-nagbar process which alerts the user that his configuration * file contains one or more errors. Also offers two buttons: One to launch an @@ -283,6 +295,12 @@ static void start_configerror_nagbar(const char *config_path) { ev_child *child = smalloc(sizeof(ev_child)); ev_child_init(child, &nagbar_exited, configerror_pid, 0); ev_child_start(main_loop, child); + + /* install a cleanup watcher (will be called when i3 exits and i3-nagbar is + * still running) */ + ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup)); + ev_cleanup_init(cleanup, nagbar_cleanup); + ev_cleanup_start(main_loop, cleanup); } /* diff --git a/src/main.c b/src/main.c index aee95f75..ea02bb6e 100644 --- a/src/main.c +++ b/src/main.c @@ -163,6 +163,14 @@ static void xkb_got_event(EV_P_ struct ev_io *w, int revents) { DLOG("Done\n"); } +/* + * Exit handler which destroys the main_loop. Will trigger cleanup handlers. + * + */ +static void i3_exit() { + ev_loop_destroy(main_loop); +} + int main(int argc, char *argv[]) { //parse_cmd("[ foo ] attach, attach ; focus"); int screens; @@ -529,5 +537,9 @@ int main(int argc, char *argv[]) { start_application(exec_always->command); } + /* Make sure to destroy the event loop to invoke the cleeanup callbacks + * when calling exit() */ + atexit(i3_exit); + ev_loop(main_loop, 0); } From e47e1008193f1f65016a61e3beeb02adc2de0773 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 11 Sep 2011 21:54:13 +0100 Subject: [PATCH 046/333] Introduce a new syntax for the 'assign' command: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of using a quoted string to specify the class / title, the assign command now uses criteria, just like the for_window command or the command scopes. An example comes here: # Assign all Chromium windows (including popups) to workspace 1: www assign [class="^Chromium$"] → 1: www # Make the main browser window borderless for_window [class="^Chromium$" title=" - Chromium$"] border none This gives you more control over the matching process due to various reasons: 1) Criteria work case-sensitive by default. Use the (?i) option if you want a case-insensitive match, like this: assign [class="(?i)^ChroMIUM$"] → 1 2) class and instance of WM_CLASS can now be matched separately. For example, when starting urxvt -name irssi, xprop will report this: WM_CLASS(STRING) = "irssi", "URxvt" The first part of this is the instance ("irssi"), the second part is the class ("URxvt"). An appropriate assignment looks like this: assign [class="^URxvt$" instance="irssi"] → 2 3) You can now freely use a forward slash (/) in all strings since that is no longer used to separate class from title (in-band signaling is bad, mhkay?). --- src/cfgparse.l | 10 +++++++++- src/cfgparse.y | 9 +++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/cfgparse.l b/src/cfgparse.l index e29f6efc..12840f26 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -75,6 +75,14 @@ EOL (\r?\n) "]" { yy_pop_state(); return ']'; } +"[" { + /* this is the case for the new assign syntax + * that uses criteria */ + yy_pop_state(); + yy_push_state(FOR_WINDOW_COND); + /* afterwards we will be in ASSIGN_TARGET_COND */ + return '['; + } [ \t]* { yy_pop_state(); } \"[^\"]+\" { yy_pop_state(); @@ -194,7 +202,7 @@ title { yy_push_state(WANT_QSTRING); return TOK_TITLE; yylval.string = copy; return QUOTEDSTRING; } -[^ \t\"]+ { BEGIN(ASSIGN_TARGET_COND); yylval.string = sstrdup(yytext); return STR_NG; } +[^ \t\"\[]+ { BEGIN(ASSIGN_TARGET_COND); yylval.string = sstrdup(yytext); return STR_NG; } [a-zA-Z0-9_]+ { yylval.string = sstrdup(yytext); return WORD; } [a-zA-Z]+ { yylval.string = sstrdup(yytext); return WORD; } . { return (int)yytext[0]; } diff --git a/src/cfgparse.y b/src/cfgparse.y index ca2d0710..53d23815 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -1137,6 +1137,15 @@ assign: assignment->dest.workspace = workspace; TAILQ_INSERT_TAIL(&assignments, assignment, assignments); } + | TOKASSIGN match STR + { + printf("new assignment, using above criteria, to workspace %s\n", $3); + Assignment *assignment = scalloc(sizeof(Assignment)); + assignment->match = current_match; + assignment->type = A_TO_WORKSPACE; + assignment->dest.workspace = $3; + TAILQ_INSERT_TAIL(&assignments, assignment, assignments); + } ; window_class: From d82698efa7ffa11739fc524dc36e84c357691940 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 11 Sep 2011 22:01:36 +0100 Subject: [PATCH 047/333] =?UTF-8?q?make=20the=20old=20assign=20syntax=20tr?= =?UTF-8?q?igger=20an=20i3-nagbar=20warning=20(it=E2=80=99s=20deprecated),?= =?UTF-8?q?=20adjust=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cfgparse.y | 9 +++++++-- testcases/t/66-assign.t | 6 ++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/cfgparse.y b/src/cfgparse.y index 53d23815..572fe6d2 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -1076,8 +1076,13 @@ workspace_name: assign: TOKASSIGN window_class STR { - /* TODO: the assign command also needs some kind of new syntax where we - * just use criteria. Then deprecate the old form */ + /* This is the old, deprecated form of assignments. It’s provided for + * compatibility in version (4.1, 4.2, 4.3) and will be removed + * afterwards. It triggers an i3-nagbar warning starting from 4.1. */ + ELOG("You are using the old assign syntax (without criteria). " + "Please see the User's Guide for the new syntax and fix " + "your config file.\n"); + context->has_errors = true; printf("assignment of %s to *%s*\n", $2, $3); char *workspace = $3; char *criteria = $2; diff --git a/testcases/t/66-assign.t b/testcases/t/66-assign.t index 25855b58..b8366917 100644 --- a/testcases/t/66-assign.t +++ b/testcases/t/66-assign.t @@ -240,7 +240,9 @@ $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); my @docked = get_dock_clients; -is(@docked, 0, 'no dock clients yet'); +# We expect i3-nagbar as the first dock client due to using the old assign +# syntax +is(@docked, 1, 'one dock client yet'); my $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, @@ -259,7 +261,7 @@ my $content = get_ws($tmp); ok(@{$content->{nodes}} == 0, 'no tiling cons'); ok(@{$content->{floating_nodes}} == 0, 'one floating con'); @docked = get_dock_clients; -is(@docked, 1, 'no dock clients yet'); +is(@docked, 2, 'two dock clients now'); $window->destroy; From 332476cf0e797f22d179b6463b6da006e31f8e09 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 11 Sep 2011 22:15:05 +0100 Subject: [PATCH 048/333] docs/userguide: document the new assign syntax --- docs/userguide | 56 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/docs/userguide b/docs/userguide index 0ee56567..e38f0d12 100644 --- a/docs/userguide +++ b/docs/userguide @@ -478,37 +478,59 @@ configuration file and run it before starting i3 (for example in your [[assign_workspace]] -Specific windows can be matched by window class and/or window title. It is -recommended that you match on window classes instead of window titles whenever -possible because some applications first create their window, and then worry -about setting the correct title. Firefox with Vimperator comes to mind. The -window starts up being named Firefox, and only when Vimperator is loaded does -the title change. As i3 will get the title as soon as the application maps the +To automatically make a specific window show up on a specific workspace, you +can use an *assignment*. You can match windows by using any criteria, +see <>. It is recommended that you match on window classes +(and instances, when appropriate) instead of window titles whenever possible +because some applications first create their window, and then worry about +setting the correct title. Firefox with Vimperator comes to mind. The window +starts up being named Firefox, and only when Vimperator is loaded does the +title change. As i3 will get the title as soon as the application maps the window (mapping means actually displaying it on the screen), you’d need to have to match on 'Firefox' in this case. -You can prefix or suffix workspaces with a `~` to specify that matching clients -should be put into floating mode. If you specify only a `~`, the client will -not be put onto any workspace, but will be set floating on the current one. - *Syntax*: ------------------------------------------------------------ -assign ["]window class[/window title]["] [→] [workspace] +assign [→] workspace ------------------------------------------------------------ *Examples*: ---------------------- -assign urxvt 2 -assign urxvt → 2 -assign urxvt → work -assign "urxvt" → 2 -assign "urxvt/VIM" → 3 -assign "gecko" → 4 +# Assign URxvt terminals to workspace 2 +assign [class="URxvt"] 2 + +# Same thing, but more precise (exact match instead of substring) +assign [class="^URxvt$"] 2 + +# Same thing, but with a beautiful arrow :) +assign [class="^URxvt$"] → 2 + +# Assignment to a named workspace +assign [class="^URxvt$"] → work + +# Start urxvt -name irssi +assign [class="^URxvt$" instance="^irssi$"] → 3 ---------------------- Note that the arrow is not required, it just looks good :-). If you decide to use it, it has to be a UTF-8 encoded arrow, not `->` or something like that. +To get the class and instance, you can use +xprop+. After clicking on the +window, you will see the following output: + +*xwininfo*: +----------------------------------- +WM_CLASS(STRING) = "irssi", "URxvt" +----------------------------------- + +The first part of the WM_CLASS is the instance ("irssi" in this example), the +second part is the class ("URxvt" in this example). + +Should you have any problems with assignments, make sure to check the i3 +logfile first (see http://i3wm.org/docs/debugging.html). It includes more +details about the matching process and the window’s actual class, instance and +title when starting up. + === Automatically starting applications on i3 startup By using the +exec+ keyword outside a keybinding, you can configure From 24dbddc395c1b970b287bcc0add6c8983ef91289 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 11 Sep 2011 22:15:14 +0100 Subject: [PATCH 049/333] docs/userguide: fix the for_window syntax --- docs/userguide | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/userguide b/docs/userguide index e38f0d12..184848aa 100644 --- a/docs/userguide +++ b/docs/userguide @@ -431,7 +431,7 @@ change their border style, for example. *Syntax*: ----------------------------- -for_window [criteria] command +for_window command ----------------------------- *Examples*: From a41bfba47a1969cb55c7502a91fc4518cc13a506 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 11 Sep 2011 22:27:31 +0100 Subject: [PATCH 050/333] finally remove the deprecated 'screen' keyword from cfgparse.l --- src/cfgparse.l | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/cfgparse.l b/src/cfgparse.l index 12840f26..e5dd29c0 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -106,13 +106,6 @@ bindsym { yy_push_state(BINDSYM_COND); yy_push_state(EAT floating_modifier { BEGIN(INITIAL); return TOKFLOATING_MODIFIER; } workspace { BEGIN(INITIAL); return TOKWORKSPACE; } output { yy_push_state(OUTPUT_COND); yy_push_state(EAT_WHITESPACE); return TOKOUTPUT; } -screen { - /* for compatibility until v3.φ */ - ELOG("Assignments to screens are DEPRECATED and will not work. " \ - "Please replace them with assignments to outputs.\n"); - yy_push_state(OUTPUT_COND); yy_push_state(EAT_WHITESPACE); - return TOKOUTPUT; - } terminal { WS_STRING; return TOKTERMINAL; } font { WS_STRING; return TOKFONT; } assign { yy_push_state(ASSIGN_TARGET_COND); yy_push_state(ASSIGN_COND); return TOKASSIGN; } From 2c7148c46e4371742a4e1725f698a918967b825f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 11 Sep 2011 22:54:41 +0100 Subject: [PATCH 051/333] Same bugfix as 2a215fd, but for assignments with invalid criteria --- src/cfgparse.y | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/cfgparse.y b/src/cfgparse.y index 86e9ea98..8faeff85 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -1148,6 +1148,10 @@ assign: } | TOKASSIGN match STR { + if (match_is_empty(¤t_match)) { + ELOG("Match is empty, ignoring this assignment\n"); + break; + } printf("new assignment, using above criteria, to workspace %s\n", $3); Assignment *assignment = scalloc(sizeof(Assignment)); assignment->match = current_match; From 095970b67e813658eee35a815a3fa8dc150e30b0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 11 Sep 2011 23:19:59 +0100 Subject: [PATCH 052/333] update changelog (no, not releasing anytime soon) --- debian/changelog | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 990badb2..e670cd8f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,6 +1,18 @@ -i3-wm (4.0.3-0) unstable; urgency=low +i3-wm (4.1-0) unstable; urgency=low * NOT YET RELEASED! + * Implement system tray support in i3bar (for NetworkManager, Skype, …) + * Implement support for PCRE regular expressions in criteria + * Implement a new assign syntax which uses criteria + * Sort named workspaces whose name starts with a number accordingly + * Warn on duplicate bindings for the same key + * Restrict 'resize' command to left/right for horizontal containers, up/down + for vertical containers + * Implement the GET_MARKS IPC request to get all marks + * Implement the new_float config option (border style for floating windows) + * Implement passing IPC sockets to i3 (systemd-style socket activation) + * Implement the 'move output' command to move containers to a specific output + * Bugfix: Preserve marks when restarting -- Michael Stapelberg Sun, 28 Aug 2011 20:17:31 +0200 From f3716e45cf3bc673d29f7cfc82c74ad3b51f12f5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 11 Sep 2011 23:31:06 +0100 Subject: [PATCH 053/333] re-indent load_configuration() --- src/config.c | 176 +++++++++++++++++++++++++-------------------------- 1 file changed, 88 insertions(+), 88 deletions(-) diff --git a/src/config.c b/src/config.c index b3a20442..5dc52472 100644 --- a/src/config.c +++ b/src/config.c @@ -257,112 +257,112 @@ static void parse_configuration(const char *override_configpath) { * */ void load_configuration(xcb_connection_t *conn, const char *override_configpath, bool reload) { - if (reload) { - /* First ungrab the keys */ - ungrab_all_keys(conn); + if (reload) { + /* First ungrab the keys */ + ungrab_all_keys(conn); - struct Mode *mode; - Binding *bind; - while (!SLIST_EMPTY(&modes)) { - mode = SLIST_FIRST(&modes); - FREE(mode->name); + struct Mode *mode; + Binding *bind; + while (!SLIST_EMPTY(&modes)) { + mode = SLIST_FIRST(&modes); + FREE(mode->name); - /* Clear the old binding list */ - bindings = mode->bindings; - while (!TAILQ_EMPTY(bindings)) { - bind = TAILQ_FIRST(bindings); - TAILQ_REMOVE(bindings, bind, bindings); - FREE(bind->translated_to); - FREE(bind->command); - FREE(bind); - } - FREE(bindings); - SLIST_REMOVE(&modes, mode, Mode, modes); - } - -#if 0 - struct Assignment *assign; - while (!TAILQ_EMPTY(&assignments)) { - assign = TAILQ_FIRST(&assignments); - FREE(assign->windowclass_title); - TAILQ_REMOVE(&assignments, assign, assignments); - FREE(assign); - } -#endif - - /* Clear workspace names */ -#if 0 - Workspace *ws; - TAILQ_FOREACH(ws, workspaces, workspaces) - workspace_set_name(ws, NULL); -#endif + /* Clear the old binding list */ + bindings = mode->bindings; + while (!TAILQ_EMPTY(bindings)) { + bind = TAILQ_FIRST(bindings); + TAILQ_REMOVE(bindings, bind, bindings); + FREE(bind->translated_to); + FREE(bind->command); + FREE(bind); + } + FREE(bindings); + SLIST_REMOVE(&modes, mode, Mode, modes); } - SLIST_INIT(&modes); +#if 0 + struct Assignment *assign; + while (!TAILQ_EMPTY(&assignments)) { + assign = TAILQ_FIRST(&assignments); + FREE(assign->windowclass_title); + TAILQ_REMOVE(&assignments, assign, assignments); + FREE(assign); + } +#endif - struct Mode *default_mode = scalloc(sizeof(struct Mode)); - default_mode->name = sstrdup("default"); - default_mode->bindings = scalloc(sizeof(struct bindings_head)); - TAILQ_INIT(default_mode->bindings); - SLIST_INSERT_HEAD(&modes, default_mode, modes); + /* Clear workspace names */ +#if 0 + Workspace *ws; + TAILQ_FOREACH(ws, workspaces, workspaces) + workspace_set_name(ws, NULL); +#endif + } - bindings = default_mode->bindings; + SLIST_INIT(&modes); + + struct Mode *default_mode = scalloc(sizeof(struct Mode)); + default_mode->name = sstrdup("default"); + default_mode->bindings = scalloc(sizeof(struct bindings_head)); + TAILQ_INIT(default_mode->bindings); + SLIST_INSERT_HEAD(&modes, default_mode, modes); + + bindings = default_mode->bindings; #define REQUIRED_OPTION(name) \ - if (config.name == NULL) \ - die("You did not specify required configuration option " #name "\n"); + if (config.name == NULL) \ + die("You did not specify required configuration option " #name "\n"); - /* Clear the old config or initialize the data structure */ - memset(&config, 0, sizeof(config)); + /* Clear the old config or initialize the data structure */ + memset(&config, 0, sizeof(config)); - /* Initialize default colors */ + /* Initialize default colors */ #define INIT_COLOR(x, cborder, cbackground, ctext) \ - do { \ - x.border = get_colorpixel(cborder); \ - x.background = get_colorpixel(cbackground); \ - x.text = get_colorpixel(ctext); \ - } while (0) + do { \ + x.border = get_colorpixel(cborder); \ + x.background = get_colorpixel(cbackground); \ + x.text = get_colorpixel(ctext); \ + } while (0) - config.client.background = get_colorpixel("#000000"); - INIT_COLOR(config.client.focused, "#4c7899", "#285577", "#ffffff"); - INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff"); - INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888"); - INIT_COLOR(config.client.urgent, "#2f343a", "#900000", "#ffffff"); - INIT_COLOR(config.bar.focused, "#4c7899", "#285577", "#ffffff"); - INIT_COLOR(config.bar.unfocused, "#333333", "#222222", "#888888"); - INIT_COLOR(config.bar.urgent, "#2f343a", "#900000", "#ffffff"); + config.client.background = get_colorpixel("#000000"); + INIT_COLOR(config.client.focused, "#4c7899", "#285577", "#ffffff"); + INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff"); + INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888"); + INIT_COLOR(config.client.urgent, "#2f343a", "#900000", "#ffffff"); + INIT_COLOR(config.bar.focused, "#4c7899", "#285577", "#ffffff"); + INIT_COLOR(config.bar.unfocused, "#333333", "#222222", "#888888"); + INIT_COLOR(config.bar.urgent, "#2f343a", "#900000", "#ffffff"); - config.default_border = BS_NORMAL; - config.default_floating_border = BS_NORMAL; - /* Set default_orientation to NO_ORIENTATION for auto orientation. */ - config.default_orientation = NO_ORIENTATION; + config.default_border = BS_NORMAL; + config.default_floating_border = BS_NORMAL; + /* Set default_orientation to NO_ORIENTATION for auto orientation. */ + config.default_orientation = NO_ORIENTATION; - parse_configuration(override_configpath); + parse_configuration(override_configpath); - if (reload) { - translate_keysyms(); - grab_all_keys(conn, false); - } + if (reload) { + translate_keysyms(); + grab_all_keys(conn, false); + } - if (config.font.id == 0) { - ELOG("You did not specify required configuration option \"font\"\n"); - config.font = load_font("fixed", true); - } + if (config.font.id == 0) { + ELOG("You did not specify required configuration option \"font\"\n"); + config.font = load_font("fixed", true); + } #if 0 - /* Set an empty name for every workspace which got no name */ - Workspace *ws; - TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->name != NULL) { - /* If the font was not specified when the workspace name - * was loaded, we need to predict the text width now */ - if (ws->text_width == 0) - ws->text_width = predict_text_width(global_conn, - config.font, ws->name, ws->name_len); - continue; - } + /* Set an empty name for every workspace which got no name */ + Workspace *ws; + TAILQ_FOREACH(ws, workspaces, workspaces) { + if (ws->name != NULL) { + /* If the font was not specified when the workspace name + * was loaded, we need to predict the text width now */ + if (ws->text_width == 0) + ws->text_width = predict_text_width(global_conn, + config.font, ws->name, ws->name_len); + continue; + } - workspace_set_name(ws, NULL); - } + workspace_set_name(ws, NULL); + } #endif } From dc790cfa32e17cf975060f6575eead9b0bc3cfdb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 11 Sep 2011 23:41:46 +0100 Subject: [PATCH 054/333] Bugfix: Correctly free old assignments when reloading Fixes #516 --- include/data.h | 2 +- include/match.h | 6 ++++++ include/regex.h | 6 ++++++ src/config.c | 10 +++++++--- src/match.c | 20 ++++++++++++++++++++ src/regex.c | 12 ++++++++++++ 6 files changed, 52 insertions(+), 4 deletions(-) diff --git a/include/data.h b/include/data.h index 122833c3..1db7c442 100644 --- a/include/data.h +++ b/include/data.h @@ -148,7 +148,7 @@ struct Ignore_Event { * */ struct regex { - const char *pattern; + char *pattern; pcre *regex; pcre_extra *extra; }; diff --git a/include/match.h b/include/match.h index 2786c66a..6c0694ef 100644 --- a/include/match.h +++ b/include/match.h @@ -28,4 +28,10 @@ void match_copy(Match *dest, Match *src); */ bool match_matches_window(Match *match, i3Window *window); +/** + * Frees the given match. It must not be used afterwards! + * + */ +void match_free(Match *match); + #endif diff --git a/include/regex.h b/include/regex.h index 2a6608a8..adfa6656 100644 --- a/include/regex.h +++ b/include/regex.h @@ -17,6 +17,12 @@ */ struct regex *regex_new(const char *pattern); +/** + * Frees the given regular expression. It must not be used afterwards! + * + */ +void regex_free(struct regex *regex); + /** * Checks if the given regular expression matches the given input and returns * true if it does. In either case, it logs the outcome using LOG(), so it will diff --git a/src/config.c b/src/config.c index 5dc52472..c979d8cd 100644 --- a/src/config.c +++ b/src/config.c @@ -280,15 +280,19 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, SLIST_REMOVE(&modes, mode, Mode, modes); } -#if 0 struct Assignment *assign; while (!TAILQ_EMPTY(&assignments)) { assign = TAILQ_FIRST(&assignments); - FREE(assign->windowclass_title); + if (assign->type == A_TO_WORKSPACE) + FREE(assign->dest.workspace); + else if (assign->type == A_TO_OUTPUT) + FREE(assign->dest.output); + else if (assign->type == A_COMMAND) + FREE(assign->dest.command); + match_free(&(assign->match)); TAILQ_REMOVE(&assignments, assign, assignments); FREE(assign); } -#endif /* Clear workspace names */ #if 0 diff --git a/src/match.c b/src/match.c index 85d2eaab..3514acee 100644 --- a/src/match.c +++ b/src/match.c @@ -136,3 +136,23 @@ bool match_matches_window(Match *match, i3Window *window) { return true; } + +/* + * Frees the given match. It must not be used afterwards! + * + */ +void match_free(Match *match) { + /* First step: free the regex fields / patterns */ + regex_free(match->title); + regex_free(match->application); + regex_free(match->class); + regex_free(match->instance); + regex_free(match->mark); + + /* Second step: free the regex helper struct itself */ + FREE(match->title); + FREE(match->application); + FREE(match->class); + FREE(match->instance); + FREE(match->mark); +} diff --git a/src/regex.c b/src/regex.c index da8f91d8..f419e4bb 100644 --- a/src/regex.c +++ b/src/regex.c @@ -49,6 +49,18 @@ struct regex *regex_new(const char *pattern) { return re; } +/* + * Frees the given regular expression. It must not be used afterwards! + * + */ +void regex_free(struct regex *regex) { + if (!regex) + return; + FREE(regex->pattern); + FREE(regex->regex); + FREE(regex->extra); +} + /* * Checks if the given regular expression matches the given input and returns * true if it does. In either case, it logs the outcome using LOG(), so it will From 5a85c9efd2e77a1c113d00760aa6f5cccc61f406 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 14 Sep 2011 23:16:23 +0100 Subject: [PATCH 055/333] fix build in 'next' (Thanks thomasba) --- src/cmdparse.y | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmdparse.y b/src/cmdparse.y index 174b5e05..650a2eb8 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -748,7 +748,7 @@ move: TAILQ_FOREACH(current, &owindows, owindows) { printf("matching: %p / %s\n", current->con, current->con->name); - con_move_to_workspace(current->con, ws, false); + con_move_to_workspace(current->con, ws, true, false); } tree_render(); From 85063124a1ecfe6b8d33a06c277677d17f98fea3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Sep 2011 17:49:12 +0100 Subject: [PATCH 056/333] document the dependency on libev >=4.0 for ev_cleanup (Thanks phnom) libev 4.00 was released 2010-10-25, so nearly a year ago. --- DEPENDS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPENDS b/DEPENDS index a6c0986d..710637e9 100644 --- a/DEPENDS +++ b/DEPENDS @@ -11,7 +11,7 @@ │ xcb-proto │ 1.3 │ 1.6 │ http://xcb.freedesktop.org/dist/ │ │ libxcb │ 1.1.93 │ 1.7 │ http://xcb.freedesktop.org/dist/ │ │ xcb-util │ 0.3.3 │ 0.3.8 │ http://xcb.freedesktop.org/dist/ │ -│ libev │ 3.0 │ 4.04 │ http://libev.schmorp.de/ │ +│ libev │ 4.0 │ 4.04 │ http://libev.schmorp.de/ │ │ flex │ 2.5.35 │ 2.5.35 │ http://flex.sourceforge.net/ │ │ bison │ 2.4.1 │ 2.4.1 │ http://www.gnu.org/software/bison/ │ │ yajl │ 1.0.8 │ 2.0.1 │ http://lloyd.github.com/yajl/ │ From fe35c807419be98fe305607ac205c7c25d59dc3f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Sep 2011 17:55:36 +0100 Subject: [PATCH 057/333] Also make compilation possible on systems with libev 3 From the source: We need ev >= 4 for the following code. Since it is not *that* important (it only makes sure that there are no i3-nagbar instances left behind) we still support old systems with libev 3. --- src/cfgparse.y | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/cfgparse.y b/src/cfgparse.y index 8faeff85..57f943c8 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -240,6 +240,10 @@ static void nagbar_exited(EV_P_ ev_child *watcher, int revents) { configerror_pid = -1; } +/* We need ev >= 4 for the following code. Since it is not *that* important (it + * only makes sure that there are no i3-nagbar instances left behind) we still + * support old systems with libev 3. */ +#if EV_VERSION_MAJOR >= 4 /* * Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal * SIGKILL (9) to make sure there are no left-over i3-nagbar processes. @@ -251,6 +255,7 @@ static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) { kill(configerror_pid, SIGKILL); } } +#endif /* * Starts an i3-nagbar process which alerts the user that his configuration @@ -296,11 +301,16 @@ static void start_configerror_nagbar(const char *config_path) { ev_child_init(child, &nagbar_exited, configerror_pid, 0); ev_child_start(main_loop, child); +/* We need ev >= 4 for the following code. Since it is not *that* important (it + * only makes sure that there are no i3-nagbar instances left behind) we still + * support old systems with libev 3. */ +#if EV_VERSION_MAJOR >= 4 /* install a cleanup watcher (will be called when i3 exits and i3-nagbar is * still running) */ ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup)); ev_cleanup_init(cleanup, nagbar_cleanup); ev_cleanup_start(main_loop, cleanup); +#endif } /* From 172f3563f753353f13e9d6cec88f4c28f66987eb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Sep 2011 19:28:41 +0100 Subject: [PATCH 058/333] Implement focus switching (focus left/right) for floating windows Fixes: #475 --- src/tree.c | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/tree.c b/src/tree.c index 240c22a6..3f0705fc 100644 --- a/src/tree.c +++ b/src/tree.c @@ -421,13 +421,36 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) return true; } - if (con->type == CT_FLOATING_CON) { - /* TODO: implement focus for floating windows */ - return false; - } - Con *parent = con->parent; + if (con->type == CT_FLOATING_CON) { + /* left/right focuses the previous/next floating container */ + if (orientation == HORIZ) { + Con *next; + if (way == 'n') + next = TAILQ_NEXT(con, floating_windows); + else next = TAILQ_PREV(con, floating_head, floating_windows); + + /* If there is no next/previous container, wrap */ + if (!next) { + if (way == 'n') + next = TAILQ_FIRST(&(parent->floating_head)); + else next = TAILQ_LAST(&(parent->floating_head), floating_head); + } + + /* Still no next/previous container? bail out */ + if (!next) + return false; + + con_focus(con_descend_focused(next)); + return true; + } else { + /* up/down cycles through the Z-index */ + /* TODO: implement cycling through the z-index */ + return false; + } + } + /* If the orientation does not match or there is no other con to focus, we * need to go higher in the hierarchy */ if (con_orientation(parent) != orientation || From e6a854a742a999d6a9ee6f43914b9bd0d09141e8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Sep 2011 19:29:06 +0100 Subject: [PATCH 059/333] Remove obsolete code for floating focus --- src/floating.c | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/src/floating.c b/src/floating.c index 2fbf66bc..e225b8a6 100644 --- a/src/floating.c +++ b/src/floating.c @@ -520,31 +520,6 @@ void drag_pointer(Con *con, xcb_button_press_event_t *event, xcb_window_t } #if 0 -/* - * Changes focus in the given direction for floating clients. - * - * Changing to the left/right means going to the previous/next floating client, - * changing to top/bottom means cycling through the Z-index. - * - */ -void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, direction_t direction) { - DLOG("floating focus\n"); - - if (direction == D_LEFT || direction == D_RIGHT) { - /* Go to the next/previous floating client */ - Client *client; - - while ((client = (direction == D_LEFT ? TAILQ_PREV(currently_focused, floating_clients_head, floating_clients) : - TAILQ_NEXT(currently_focused, floating_clients))) != - TAILQ_END(&(currently_focused->workspace->floating_clients))) { - if (!client->floating) - continue; - set_focus(conn, client, true); - return; - } - } -} - /* * Moves the client 10px to the specified direction. * From c8c95030ad13900900a18c2326908132d134b055 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 17 Sep 2011 19:29:23 +0100 Subject: [PATCH 060/333] tests: extend t/35-floating-focus to use focus left/right on floating windows --- testcases/t/35-floating-focus.t | 41 +++++++++++++++++++++++++++++++++ testcases/t/lib/i3test.pm | 13 ++++++++--- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/testcases/t/35-floating-focus.t b/testcases/t/35-floating-focus.t index 3f820ea5..6adad246 100644 --- a/testcases/t/35-floating-focus.t +++ b/testcases/t/35-floating-focus.t @@ -170,5 +170,46 @@ sleep 0.25; is($x->input_focus, $second->id, 'second (floating) container focused'); +############################################################################# +# 6: see if switching floating focus using the focus left/right command works +############################################################################# + +$tmp = fresh_workspace; + +$first = open_standard_window($x, '#ff0000', 1); # window 10 +$second = open_standard_window($x, '#00ff00', 1); # window 11 +$third = open_standard_window($x, '#0000ff', 1); # window 12 + +is($x->input_focus, $third->id, 'third container focused'); + +cmd 'focus left'; + +sleep 0.25; + +is($x->input_focus, $second->id, 'second container focused'); + +cmd 'focus left'; + +sleep 0.25; + +is($x->input_focus, $first->id, 'first container focused'); + +cmd 'focus left'; + +sleep 0.25; + +is($x->input_focus, $third->id, 'focus wrapped to third container'); + +cmd 'focus right'; + +sleep 0.25; + +is($x->input_focus, $first->id, 'focus wrapped to first container'); + +cmd 'focus right'; + +sleep 0.25; + +is($x->input_focus, $second->id, 'focus on second container'); done_testing; diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 054bb2ae..749b89b7 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -48,16 +48,23 @@ use warnings; } sub open_standard_window { - my ($x, $color) = @_; + my ($x, $color, $floating) = @_; $color ||= '#c0c0c0'; - my $window = $x->root->create_child( + # We cannot use a hashref here because create_child expands the arguments into an array + my @args = ( class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30 ], + rect => X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30 ), background_color => $color, ); + if (defined($floating) && $floating) { + @args = (@args, window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY')); + } + + my $window = $x->root->create_child(@args); + $window->name('Window ' . counter_window()); $window->map; From 46ab86b88d686184abccab5d88aa4ab86c985ba4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 18 Sep 2011 13:20:59 +0100 Subject: [PATCH 061/333] i3-input: reformat main.c --- i3-input/main.c | 570 ++++++++++++++++++++++++------------------------ 1 file changed, 285 insertions(+), 285 deletions(-) diff --git a/i3-input/main.c b/i3-input/main.c index fb2635a2..ccc56a5d 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -1,9 +1,9 @@ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2011 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -61,33 +61,33 @@ xcb_window_t root; * */ static char *socket_path_from_x11() { - xcb_connection_t *conn; - int screen; - if ((conn = xcb_connect(NULL, &screen)) == NULL || - xcb_connection_has_error(conn)) - return NULL; - xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen); - xcb_window_t root = root_screen->root; + xcb_connection_t *conn; + int screen; + if ((conn = xcb_connect(NULL, &screen)) == NULL || + xcb_connection_has_error(conn)) + return NULL; + xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen); + xcb_window_t root = root_screen->root; - xcb_intern_atom_cookie_t atom_cookie; - xcb_intern_atom_reply_t *atom_reply; + xcb_intern_atom_cookie_t atom_cookie; + xcb_intern_atom_reply_t *atom_reply; - atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH"); - atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL); - if (atom_reply == NULL) - return NULL; + atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH"); + atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL); + if (atom_reply == NULL) + return NULL; - xcb_get_property_cookie_t prop_cookie; - xcb_get_property_reply_t *prop_reply; - prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom, - XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX); - prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); - if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0) - return NULL; - if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply), - (char*)xcb_get_property_value(prop_reply)) == -1) - return NULL; - return socket_path; + xcb_get_property_cookie_t prop_cookie; + xcb_get_property_reply_t *prop_reply; + prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom, + XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX); + prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); + if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0) + return NULL; + if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply), + (char*)xcb_get_property_value(prop_reply)) == -1) + return NULL; + return socket_path; } /* @@ -96,21 +96,21 @@ static char *socket_path_from_x11() { * */ static uint8_t *concat_strings(char **glyphs, int max) { - uint8_t *output = calloc(max+1, 4); - uint8_t *walk = output; - for (int c = 0; c < max; c++) { - printf("at %c\n", glyphs[c][0]); - /* if the first byte is 0, this has to be UCS2 */ - if (glyphs[c][0] == '\0') { - memcpy(walk, glyphs[c], 2); - walk += 2; - } else { - strcpy((char*)walk, glyphs[c]); - walk += strlen(glyphs[c]); - } + uint8_t *output = calloc(max+1, 4); + uint8_t *walk = output; + for (int c = 0; c < max; c++) { + printf("at %c\n", glyphs[c][0]); + /* if the first byte is 0, this has to be UCS2 */ + if (glyphs[c][0] == '\0') { + memcpy(walk, glyphs[c], 2); + walk += 2; + } else { + strcpy((char*)walk, glyphs[c]); + walk += strlen(glyphs[c]); } - printf("output = %s\n", output); - return output; + } + printf("output = %s\n", output); + return output; } /* @@ -119,37 +119,37 @@ static uint8_t *concat_strings(char **glyphs, int max) { * */ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t *event) { - printf("expose!\n"); + printf("expose!\n"); - /* re-draw the background */ - xcb_rectangle_t border = {0, 0, 500, font_height + 8}, inner = {2, 2, 496, font_height + 8 - 4}; - xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000")); - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border); - xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner); + /* re-draw the background */ + xcb_rectangle_t border = {0, 0, 500, font_height + 8}, inner = {2, 2, 496, font_height + 8 - 4}; + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000")); + xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border); + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); + xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner); - /* restore font color */ - xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF")); - uint8_t *con = concat_strings(glyphs_ucs, input_position); - char *full_text = (char*)con; - if (prompt != NULL) { - full_text = malloc((prompt_len + input_position) * 2 + 1); - if (full_text == NULL) - err(EXIT_FAILURE, "malloc() failed\n"); - memcpy(full_text, prompt, prompt_len * 2); - memcpy(full_text + (prompt_len * 2), con, input_position * 2); - } - xcb_image_text_16(conn, input_position + prompt_len, pixmap, pixmap_gc, 4 /* X */, - font_height + 2 /* Y = baseline of font */, (xcb_char2b_t*)full_text); + /* restore font color */ + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF")); + uint8_t *con = concat_strings(glyphs_ucs, input_position); + char *full_text = (char*)con; + if (prompt != NULL) { + full_text = malloc((prompt_len + input_position) * 2 + 1); + if (full_text == NULL) + err(EXIT_FAILURE, "malloc() failed\n"); + memcpy(full_text, prompt, prompt_len * 2); + memcpy(full_text + (prompt_len * 2), con, input_position * 2); + } + xcb_image_text_16(conn, input_position + prompt_len, pixmap, pixmap_gc, 4 /* X */, + font_height + 2 /* Y = baseline of font */, (xcb_char2b_t*)full_text); - /* Copy the contents of the pixmap to the real window */ - xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, /* */ 500, font_height + 8); - xcb_flush(conn); - free(con); - if (prompt != NULL) - free(full_text); + /* Copy the contents of the pixmap to the real window */ + xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, /* */ 500, font_height + 8); + xcb_flush(conn); + free(con); + if (prompt != NULL) + free(full_text); - return 1; + return 1; } /* @@ -157,37 +157,37 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t * */ static int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_release_event_t *event) { - printf("releasing %d, state raw = %d\n", event->detail, event->state); + printf("releasing %d, state raw = %d\n", event->detail, event->state); - /* fix state */ - event->state &= ~numlockmask; + /* fix state */ + event->state &= ~numlockmask; - xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state); - if (sym == XK_Mode_switch) { - printf("Mode switch disabled\n"); - modeswitch_active = false; - } + xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state); + if (sym == XK_Mode_switch) { + printf("Mode switch disabled\n"); + modeswitch_active = false; + } - return 1; + return 1; } static void finish_input() { - uint8_t *command = concat_strings(glyphs_utf8, input_position); - char *full_command = (char*)command; - /* prefix the command if a prefix was specified on commandline */ - if (command_prefix != NULL) { - if (asprintf(&full_command, "%s%s", command_prefix, command) == -1) - err(EXIT_FAILURE, "asprintf() failed\n"); - } - printf("command = %s\n", full_command); + uint8_t *command = concat_strings(glyphs_utf8, input_position); + char *full_command = (char*)command; + /* prefix the command if a prefix was specified on commandline */ + if (command_prefix != NULL) { + if (asprintf(&full_command, "%s%s", command_prefix, command) == -1) + err(EXIT_FAILURE, "asprintf() failed\n"); + } + printf("command = %s\n", full_command); - ipc_send_message(sockfd, strlen(full_command), 0, (uint8_t*)full_command); + ipc_send_message(sockfd, strlen(full_command), 0, (uint8_t*)full_command); #if 0 - free(command); - return 1; + free(command); + return 1; #endif - exit(0); + exit(0); } /* @@ -200,224 +200,224 @@ static void finish_input() { * */ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) { - printf("Keypress %d, state raw = %d\n", event->detail, event->state); + printf("Keypress %d, state raw = %d\n", event->detail, event->state); - /* fix state */ - if (modeswitch_active) - event->state |= modeswitchmask; + /* fix state */ + if (modeswitch_active) + event->state |= modeswitchmask; - /* Apparantly, after activating numlock once, the numlock modifier - * stays turned on (use xev(1) to verify). So, to resolve useful - * keysyms, we remove the numlock flag from the event state */ - event->state &= ~numlockmask; + /* Apparantly, after activating numlock once, the numlock modifier + * stays turned on (use xev(1) to verify). So, to resolve useful + * keysyms, we remove the numlock flag from the event state */ + event->state &= ~numlockmask; - xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state); - if (sym == XK_Mode_switch) { - printf("Mode switch enabled\n"); - modeswitch_active = true; - return 1; - } + xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state); + if (sym == XK_Mode_switch) { + printf("Mode switch enabled\n"); + modeswitch_active = true; + return 1; + } - if (sym == XK_Return) - finish_input(); + if (sym == XK_Return) + finish_input(); - if (sym == XK_BackSpace) { - if (input_position == 0) - return 1; + if (sym == XK_BackSpace) { + if (input_position == 0) + return 1; - input_position--; - free(glyphs_ucs[input_position]); - free(glyphs_utf8[input_position]); - - handle_expose(NULL, conn, NULL); - return 1; - } - if (sym == XK_Escape) { - exit(0); - } - - /* TODO: handle all of these? */ - printf("is_keypad_key = %d\n", xcb_is_keypad_key(sym)); - printf("is_private_keypad_key = %d\n", xcb_is_private_keypad_key(sym)); - printf("xcb_is_cursor_key = %d\n", xcb_is_cursor_key(sym)); - printf("xcb_is_pf_key = %d\n", xcb_is_pf_key(sym)); - printf("xcb_is_function_key = %d\n", xcb_is_function_key(sym)); - printf("xcb_is_misc_function_key = %d\n", xcb_is_misc_function_key(sym)); - printf("xcb_is_modifier_key = %d\n", xcb_is_modifier_key(sym)); - - if (xcb_is_modifier_key(sym) || xcb_is_cursor_key(sym)) - return 1; - - printf("sym = %c (%d)\n", sym, sym); - - /* convert the keysym to UCS */ - uint16_t ucs = keysym2ucs(sym); - if ((int16_t)ucs == -1) { - fprintf(stderr, "Keysym could not be converted to UCS, skipping\n"); - return 1; - } - - /* store the UCS into a string */ - uint8_t inp[3] = {(ucs & 0xFF00) >> 8, (ucs & 0xFF), 0}; - - printf("inp[0] = %02x, inp[1] = %02x, inp[2] = %02x\n", inp[0], inp[1], inp[2]); - /* convert it to UTF-8 */ - char *out = convert_ucs_to_utf8((char*)inp); - printf("converted to %s\n", out); - - glyphs_ucs[input_position] = malloc(3 * sizeof(uint8_t)); - if (glyphs_ucs[input_position] == NULL) - err(EXIT_FAILURE, "malloc() failed\n"); - memcpy(glyphs_ucs[input_position], inp, 3); - glyphs_utf8[input_position] = strdup(out); - input_position++; - - if (input_position == limit) - finish_input(); + input_position--; + free(glyphs_ucs[input_position]); + free(glyphs_utf8[input_position]); handle_expose(NULL, conn, NULL); return 1; + } + if (sym == XK_Escape) { + exit(0); + } + + /* TODO: handle all of these? */ + printf("is_keypad_key = %d\n", xcb_is_keypad_key(sym)); + printf("is_private_keypad_key = %d\n", xcb_is_private_keypad_key(sym)); + printf("xcb_is_cursor_key = %d\n", xcb_is_cursor_key(sym)); + printf("xcb_is_pf_key = %d\n", xcb_is_pf_key(sym)); + printf("xcb_is_function_key = %d\n", xcb_is_function_key(sym)); + printf("xcb_is_misc_function_key = %d\n", xcb_is_misc_function_key(sym)); + printf("xcb_is_modifier_key = %d\n", xcb_is_modifier_key(sym)); + + if (xcb_is_modifier_key(sym) || xcb_is_cursor_key(sym)) + return 1; + + printf("sym = %c (%d)\n", sym, sym); + + /* convert the keysym to UCS */ + uint16_t ucs = keysym2ucs(sym); + if ((int16_t)ucs == -1) { + fprintf(stderr, "Keysym could not be converted to UCS, skipping\n"); + return 1; + } + + /* store the UCS into a string */ + uint8_t inp[3] = {(ucs & 0xFF00) >> 8, (ucs & 0xFF), 0}; + + printf("inp[0] = %02x, inp[1] = %02x, inp[2] = %02x\n", inp[0], inp[1], inp[2]); + /* convert it to UTF-8 */ + char *out = convert_ucs_to_utf8((char*)inp); + printf("converted to %s\n", out); + + glyphs_ucs[input_position] = malloc(3 * sizeof(uint8_t)); + if (glyphs_ucs[input_position] == NULL) + err(EXIT_FAILURE, "malloc() failed\n"); + memcpy(glyphs_ucs[input_position], inp, 3); + glyphs_utf8[input_position] = strdup(out); + input_position++; + + if (input_position == limit) + finish_input(); + + handle_expose(NULL, conn, NULL); + return 1; } int main(int argc, char *argv[]) { - socket_path = getenv("I3SOCK"); - char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; - int o, option_index = 0; + socket_path = getenv("I3SOCK"); + char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; + int o, option_index = 0; - static struct option long_options[] = { - {"socket", required_argument, 0, 's'}, - {"version", no_argument, 0, 'v'}, - {"limit", required_argument, 0, 'l'}, - {"prompt", required_argument, 0, 'P'}, - {"prefix", required_argument, 0, 'p'}, - {"font", required_argument, 0, 'f'}, - {"help", no_argument, 0, 'h'}, - {0, 0, 0, 0} - }; + static struct option long_options[] = { + {"socket", required_argument, 0, 's'}, + {"version", no_argument, 0, 'v'}, + {"limit", required_argument, 0, 'l'}, + {"prompt", required_argument, 0, 'P'}, + {"prefix", required_argument, 0, 'p'}, + {"font", required_argument, 0, 'f'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; - char *options_string = "s:p:P:f:l:vh"; + char *options_string = "s:p:P:f:l:vh"; - while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { - switch (o) { - case 's': - FREE(socket_path); - socket_path = strdup(optarg); - break; - case 'v': - printf("i3-input " I3_VERSION); - return 0; - case 'p': - FREE(command_prefix); - command_prefix = strdup(optarg); - break; - case 'l': - limit = atoi(optarg); - break; - case 'P': - FREE(prompt); - prompt = strdup(optarg); - break; - case 'f': - FREE(pattern); - pattern = strdup(optarg); - break; - case 'h': - printf("i3-input " I3_VERSION); - printf("i3-input [-s ] [-p ] [-l ] [-P ] [-f ] [-v]\n"); - return 0; - } + while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { + switch (o) { + case 's': + FREE(socket_path); + socket_path = strdup(optarg); + break; + case 'v': + printf("i3-input " I3_VERSION); + return 0; + case 'p': + FREE(command_prefix); + command_prefix = strdup(optarg); + break; + case 'l': + limit = atoi(optarg); + break; + case 'P': + FREE(prompt); + prompt = strdup(optarg); + break; + case 'f': + FREE(pattern); + pattern = strdup(optarg); + break; + case 'h': + printf("i3-input " I3_VERSION); + printf("i3-input [-s ] [-p ] [-l ] [-P ] [-f ] [-v]\n"); + return 0; + } + } + + if (socket_path == NULL) + socket_path = socket_path_from_x11(); + + if (socket_path == NULL) + socket_path = "/tmp/i3-ipc.sock"; + + sockfd = connect_ipc(socket_path); + + if (prompt != NULL) + prompt = convert_utf8_to_ucs2(prompt, &prompt_len); + + int screens; + xcb_connection_t *conn = xcb_connect(NULL, &screens); + if (xcb_connection_has_error(conn)) + die("Cannot open display\n"); + + xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens); + root = root_screen->root; + + modeswitchmask = get_mod_mask(conn, XK_Mode_switch); + numlockmask = get_mod_mask(conn, XK_Num_Lock); + symbols = xcb_key_symbols_alloc(conn); + + uint32_t font_id = get_font_id(conn, pattern, &font_height); + + /* Open an input window */ + win = open_input_window(conn, 500, font_height + 8); + + /* Create pixmap */ + pixmap = xcb_generate_id(conn); + pixmap_gc = xcb_generate_id(conn); + xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font_height + 8); + xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); + + /* Set input focus (we have override_redirect=1, so the wm will not do + * this for us) */ + xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME); + + /* Create graphics context */ + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id); + + /* Grab the keyboard to get all input */ + xcb_flush(conn); + + /* Try (repeatedly, if necessary) to grab the keyboard. We might not + * get the keyboard at the first attempt because of the keybinding + * still being active when started via a wm’s keybinding. */ + xcb_grab_keyboard_cookie_t cookie; + xcb_grab_keyboard_reply_t *reply = NULL; + + int count = 0; + while ((reply == NULL || reply->status != XCB_GRAB_STATUS_SUCCESS) && (count++ < 500)) { + cookie = xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); + reply = xcb_grab_keyboard_reply(conn, cookie, NULL); + usleep(1000); + } + + if (reply->status != XCB_GRAB_STATUS_SUCCESS) { + fprintf(stderr, "Could not grab keyboard, status = %d\n", reply->status); + exit(-1); + } + + xcb_flush(conn); + + xcb_generic_event_t *event; + while ((event = xcb_wait_for_event(conn)) != NULL) { + if (event->response_type == 0) { + fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence); + continue; } - if (socket_path == NULL) - socket_path = socket_path_from_x11(); + /* Strip off the highest bit (set if the event is generated) */ + int type = (event->response_type & 0x7F); - if (socket_path == NULL) - socket_path = "/tmp/i3-ipc.sock"; + switch (type) { + case XCB_KEY_PRESS: + handle_key_press(NULL, conn, (xcb_key_press_event_t*)event); + break; - sockfd = connect_ipc(socket_path); + case XCB_KEY_RELEASE: + handle_key_release(NULL, conn, (xcb_key_release_event_t*)event); + break; - if (prompt != NULL) - prompt = convert_utf8_to_ucs2(prompt, &prompt_len); - - int screens; - xcb_connection_t *conn = xcb_connect(NULL, &screens); - if (xcb_connection_has_error(conn)) - die("Cannot open display\n"); - - xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens); - root = root_screen->root; - - modeswitchmask = get_mod_mask(conn, XK_Mode_switch); - numlockmask = get_mod_mask(conn, XK_Num_Lock); - symbols = xcb_key_symbols_alloc(conn); - - uint32_t font_id = get_font_id(conn, pattern, &font_height); - - /* Open an input window */ - win = open_input_window(conn, 500, font_height + 8); - - /* Create pixmap */ - pixmap = xcb_generate_id(conn); - pixmap_gc = xcb_generate_id(conn); - xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font_height + 8); - xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); - - /* Set input focus (we have override_redirect=1, so the wm will not do - * this for us) */ - xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME); - - /* Create graphics context */ - xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id); - - /* Grab the keyboard to get all input */ - xcb_flush(conn); - - /* Try (repeatedly, if necessary) to grab the keyboard. We might not - * get the keyboard at the first attempt because of the keybinding - * still being active when started via a wm’s keybinding. */ - xcb_grab_keyboard_cookie_t cookie; - xcb_grab_keyboard_reply_t *reply = NULL; - - int count = 0; - while ((reply == NULL || reply->status != XCB_GRAB_STATUS_SUCCESS) && (count++ < 500)) { - cookie = xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); - reply = xcb_grab_keyboard_reply(conn, cookie, NULL); - usleep(1000); + case XCB_EXPOSE: + handle_expose(NULL, conn, (xcb_expose_event_t*)event); + break; } - if (reply->status != XCB_GRAB_STATUS_SUCCESS) { - fprintf(stderr, "Could not grab keyboard, status = %d\n", reply->status); - exit(-1); - } + free(event); + } - xcb_flush(conn); - - xcb_generic_event_t *event; - while ((event = xcb_wait_for_event(conn)) != NULL) { - if (event->response_type == 0) { - fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence); - continue; - } - - /* Strip off the highest bit (set if the event is generated) */ - int type = (event->response_type & 0x7F); - - switch (type) { - case XCB_KEY_PRESS: - handle_key_press(NULL, conn, (xcb_key_press_event_t*)event); - break; - - case XCB_KEY_RELEASE: - handle_key_release(NULL, conn, (xcb_key_release_event_t*)event); - break; - - case XCB_EXPOSE: - handle_expose(NULL, conn, (xcb_expose_event_t*)event); - break; - } - - free(event); - } - - return 0; + return 0; } From fddee471e81308f01f18360d9a839f2897125636 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 18 Sep 2011 13:21:45 +0100 Subject: [PATCH 062/333] i3-input: add missing \n in --help --- i3-input/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i3-input/main.c b/i3-input/main.c index ccc56a5d..1c2c4caa 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -322,7 +322,7 @@ int main(int argc, char *argv[]) { pattern = strdup(optarg); break; case 'h': - printf("i3-input " I3_VERSION); + printf("i3-input " I3_VERSION "\n"); printf("i3-input [-s ] [-p ] [-l ] [-P ] [-f ] [-v]\n"); return 0; } From 1737a78fcd8025e11398dbcf5acd65c6a07ae86d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 18 Sep 2011 13:51:11 +0100 Subject: [PATCH 063/333] i3-input: implement -F (format) option, deprecate -p (prefix) This introduces the '-F format' parameter, which takes a format and replaces %s in it with the user input. An example: The user should enter the target workspace name. The appropriate i3-input invocation looks like this: i3-input -F 'workspace "%s"' -P 'Switch to workspace: ' --- i3-input/main.c | 69 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/i3-input/main.c b/i3-input/main.c index 1c2c4caa..7e1a18ff 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -35,6 +35,10 @@ #include "i3-input.h" +/* IPC format string. %s will be replaced with what the user entered, then + * the command will be sent to i3 */ +static char *format; + static char *socket_path; static int sockfd; static xcb_key_symbols_t *symbols; @@ -48,7 +52,6 @@ static char *glyphs_ucs[512]; static char *glyphs_utf8[512]; static int input_position; static int font_height; -static char *command_prefix; static char *prompt; static int prompt_len; static int limit; @@ -172,16 +175,41 @@ static int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_rel } static void finish_input() { - uint8_t *command = concat_strings(glyphs_utf8, input_position); - char *full_command = (char*)command; - /* prefix the command if a prefix was specified on commandline */ - if (command_prefix != NULL) { - if (asprintf(&full_command, "%s%s", command_prefix, command) == -1) - err(EXIT_FAILURE, "asprintf() failed\n"); - } - printf("command = %s\n", full_command); + char *command = (char*)concat_strings(glyphs_utf8, input_position); - ipc_send_message(sockfd, strlen(full_command), 0, (uint8_t*)full_command); + /* count the occurences of %s in the string */ + int c; + int len = strlen(format); + int cnt = 0; + for (c = 0; c < (len-1); c++) + if (format[c] == '%' && format[c+1] == 's') + cnt++; + printf("occurences = %d\n", cnt); + + /* allocate space for the output */ + int inputlen = strlen(command); + char *full = calloc(1, + strlen(format) - (2 * cnt) /* format without all %s */ + + (inputlen * cnt) /* replaced %s */ + + 1); /* trailing NUL */ + char *dest = full; + for (c = 0; c < len; c++) { + /* if this is not % or it is % but without a following 's', + * just copy the character */ + if (format[c] != '%' || (c == (len-1)) || format[c+1] != 's') + *(dest++) = format[c]; + else { + strncat(dest, command, inputlen); + dest += inputlen; + /* skip the following 's' of '%s' */ + c++; + } + } + + /* prefix the command if a prefix was specified on commandline */ + printf("command = %s\n", full); + + ipc_send_message(sockfd, strlen(full), 0, (uint8_t*)full); #if 0 free(command); @@ -280,6 +308,7 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press } int main(int argc, char *argv[]) { + format = strdup("%s"); socket_path = getenv("I3SOCK"); char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; int o, option_index = 0; @@ -290,12 +319,13 @@ int main(int argc, char *argv[]) { {"limit", required_argument, 0, 'l'}, {"prompt", required_argument, 0, 'P'}, {"prefix", required_argument, 0, 'p'}, + {"format", required_argument, 0, 'F'}, {"font", required_argument, 0, 'f'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; - char *options_string = "s:p:P:f:l:vh"; + char *options_string = "s:p:P:f:l:F:vh"; while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { switch (o) { @@ -307,8 +337,10 @@ int main(int argc, char *argv[]) { printf("i3-input " I3_VERSION); return 0; case 'p': - FREE(command_prefix); - command_prefix = strdup(optarg); + /* This option is deprecated, but will still work in i3 v4.1, 4.2 and 4.3 */ + fprintf(stderr, "i3-input: WARNING: the -p option is DEPRECATED in favor of the -F (format) option\n"); + FREE(format); + asprintf(&format, "%s%%s", optarg); break; case 'l': limit = atoi(optarg); @@ -321,13 +353,22 @@ int main(int argc, char *argv[]) { FREE(pattern); pattern = strdup(optarg); break; + case 'F': + FREE(format); + format = strdup(optarg); + break; case 'h': printf("i3-input " I3_VERSION "\n"); - printf("i3-input [-s ] [-p ] [-l ] [-P ] [-f ] [-v]\n"); + printf("i3-input [-s ] [-F ] [-l ] [-P ] [-f ] [-v]\n"); + printf("\n"); + printf("Example:\n"); + printf(" i3-input -F 'workspace \"%%s\"' -P 'Switch to workspace: '\n"); return 0; } } + printf("using format \"%s\"\n", format); + if (socket_path == NULL) socket_path = socket_path_from_x11(); From 12e096cf73222708406c74ccadc33b90f9f41f1c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 18 Sep 2011 13:57:39 +0100 Subject: [PATCH 064/333] i3-input: update manpage --- man/i3-input.man | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/man/i3-input.man b/man/i3-input.man index cd85c92c..df7c4d9c 100644 --- a/man/i3-input.man +++ b/man/i3-input.man @@ -1,7 +1,7 @@ i3-input(1) ========= Michael Stapelberg -v3.delta, November 2009 +v4.1, September 2011 == NAME @@ -9,7 +9,7 @@ i3-input - interactively take a command for i3 window manager == SYNOPSIS -i3-input [-s ] [-p ] [-l ] [-P ] [-f ] [-v] +i3-input [-s ] [-F ] [-l ] [-P ] [-f ] [-v] == DESCRIPTION @@ -17,19 +17,28 @@ i3-input is a tool to take commands (or parts of a command) composed by the user, and send it/them to i3. This is useful, for example, for the mark/goto command. +The -F option takes a format string. In this string, every occurence of %s is +replaced by the user input. + == EXAMPLE ------------------------------------------------ -i3-input -p 'mark ' -l 1 -P 'Mark: ' +i3-input -F 'mark %s' -l 1 -P 'Mark: ' ------------------------------------------------ == ENVIRONMENT === I3SOCK -If no ipc-socket is specified on the commandline, this variable is used -to determine the path, at wich the unix domain socket is expected, on which -to connect to i3. +i3-input handles the different sources of socket paths in the following order: + +* I3SOCK environment variable +* I3SOCK gets overwritten by the -s parameter, if specified +* if neither are available, i3-input reads the socket path from the X11 + property, which is the recommended way +* if everything fails, i3-input tries +/tmp/i3-ipc.sock+ + +The socket path is necessary to connect to i3 and actually issue the command. == SEE ALSO From 99168a84a973c38a00ce72243d738bf5015acef0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 18 Sep 2011 15:35:04 +0100 Subject: [PATCH 065/333] tests: also make launch_with_config() log to LOGPATH --- testcases/complete-run.pl | 2 +- testcases/t/lib/i3test.pm | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index 6f00877c..8f740d8c 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -242,7 +242,7 @@ sub take_job { my $output; my $parser = TAP::Parser->new({ - exec => [ 'sh', '-c', "DISPLAY=$display /usr/bin/perl -It/lib $test" ], + exec => [ 'sh', '-c', qq|DISPLAY=$display LOGPATH="$logpath" /usr/bin/perl -It/lib $test| ], spool => IO::Scalar->new(\$output), merge => 1, }); diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 749b89b7..3d2d85af 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -259,7 +259,10 @@ sub launch_with_config { say $fh "ipc-socket $tmp_socket_path"; close($fh); - my $i3cmd = "exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null"; + # Use $ENV{LOGPATH}, gets set in complete-run.pl. We append instead of + # overwrite because there might be multiple instances of i3 running during + # one test case. + my $i3cmd = "exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >>$ENV{LOGPATH} 2>&1"; my $process = Proc::Background->new($i3cmd); sleep 1; From b3adaa2983c7e609ae57a665f3f8015c64e4ead6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 18 Sep 2011 16:05:10 +0100 Subject: [PATCH 066/333] Implement the window_role criterion (checks WM_WINDOW_ROLE) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes: #446 This is handy for matching specific windows of a multi-window application, for example only Pidgin’s buddy list window. --- include/atoms.xmacro | 1 + include/data.h | 6 ++++++ include/window.h | 6 ++++++ src/cfgparse.l | 1 + src/cfgparse.y | 7 +++++++ src/cmdparse.l | 1 + src/cmdparse.y | 7 +++++++ src/handlers.c | 19 ++++++++++++++++++- src/manage.c | 5 ++++- src/match.c | 14 ++++++++++++++ src/window.c | 33 +++++++++++++++++++++++++++++++++ 11 files changed, 98 insertions(+), 2 deletions(-) diff --git a/include/atoms.xmacro b/include/atoms.xmacro index 300a8aba..7f83a7ee 100644 --- a/include/atoms.xmacro +++ b/include/atoms.xmacro @@ -21,5 +21,6 @@ xmacro(UTF8_STRING) xmacro(WM_STATE) xmacro(WM_CLIENT_LEADER) xmacro(WM_TAKE_FOCUS) +xmacro(WM_WINDOW_ROLE) xmacro(I3_SOCKET_PATH) xmacro(I3_CONFIG_PATH) diff --git a/include/data.h b/include/data.h index 1db7c442..f6052b9f 100644 --- a/include/data.h +++ b/include/data.h @@ -264,6 +264,11 @@ struct Window { * application supports _NET_WM_NAME, in COMPOUND_TEXT otherwise). */ char *name_x; + /** The WM_WINDOW_ROLE of this window (for example, the pidgin buddy window + * sets "buddy list"). Useful to match specific windows in assignments or + * for_window. */ + char *role; + /** Flag to force re-rendering the decoration upon changes */ bool name_x_changed; @@ -298,6 +303,7 @@ struct Match { struct regex *class; struct regex *instance; struct regex *mark; + struct regex *role; enum { M_DONTCHECK = -1, M_NODOCK = 0, diff --git a/include/window.h b/include/window.h index fe282aa0..cb9fbbc5 100644 --- a/include/window.h +++ b/include/window.h @@ -42,4 +42,10 @@ void window_update_transient_for(i3Window *win, xcb_get_property_reply_t *prop); */ void window_update_strut_partial(i3Window *win, xcb_get_property_reply_t *prop); +/** + * Updates the WM_WINDOW_ROLE + * + */ +void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt); + #endif diff --git a/src/cfgparse.l b/src/cfgparse.l index e5dd29c0..021e3516 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -170,6 +170,7 @@ shift { return TOKSHIFT; } class { yy_push_state(WANT_QSTRING); return TOK_CLASS; } instance { yy_push_state(WANT_QSTRING); return TOK_INSTANCE; } +window_role { yy_push_state(WANT_QSTRING); return TOK_WINDOW_ROLE; } id { yy_push_state(WANT_QSTRING); return TOK_ID; } con_id { yy_push_state(WANT_QSTRING); return TOK_CON_ID; } con_mark { yy_push_state(WANT_QSTRING); return TOK_MARK; } diff --git a/src/cfgparse.y b/src/cfgparse.y index 57f943c8..016953fd 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -632,6 +632,7 @@ void parse_file(const char *f) { %token TOK_MARK "mark" %token TOK_CLASS "class" %token TOK_INSTANCE "instance" +%token TOK_WINDOW_ROLE "window_role" %token TOK_ID "id" %token TOK_CON_ID "con_id" %token TOK_TITLE "title" @@ -792,6 +793,12 @@ criterion: current_match.instance = regex_new($3); free($3); } + | TOK_WINDOW_ROLE '=' STR + { + printf("criteria: window_role = %s\n", $3); + current_match.role = regex_new($3); + free($3); + } | TOK_CON_ID '=' STR { printf("criteria: id = %s\n", $3); diff --git a/src/cmdparse.l b/src/cmdparse.l index c7c64e35..968b7e52 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -155,6 +155,7 @@ no { return TOK_DISABLE; } class { BEGIN(WANT_QSTRING); return TOK_CLASS; } instance { BEGIN(WANT_QSTRING); return TOK_INSTANCE; } +window_role { BEGIN(WANT_QSTRING); return TOK_WINDOW_ROLE; } id { BEGIN(WANT_QSTRING); return TOK_ID; } con_id { BEGIN(WANT_QSTRING); return TOK_CON_ID; } con_mark { BEGIN(WANT_QSTRING); return TOK_MARK; } diff --git a/src/cmdparse.y b/src/cmdparse.y index 650a2eb8..0a99c224 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -177,6 +177,7 @@ bool definitelyGreaterThan(float a, float b, float epsilon) { %token TOK_CLASS "class" %token TOK_INSTANCE "instance" +%token TOK_WINDOW_ROLE "window_role" %token TOK_ID "id" %token TOK_CON_ID "con_id" %token TOK_TITLE "title" @@ -308,6 +309,12 @@ criterion: current_match.instance = regex_new($3); free($3); } + | TOK_WINDOW_ROLE '=' STR + { + printf("criteria: window_role = %s\n", $3); + current_match.role = regex_new($3); + free($3); + } | TOK_CON_ID '=' STR { printf("criteria: id = %s\n", $3); diff --git a/src/handlers.c b/src/handlers.c index b3467990..36cf039f 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -557,6 +557,21 @@ static bool handle_windowname_change_legacy(void *data, xcb_connection_t *conn, return true; } +/* + * Called when a window changes its WM_WINDOW_ROLE. + * + */ +static bool handle_windowrole_change(void *data, xcb_connection_t *conn, uint8_t state, + xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) { + Con *con; + if ((con = con_by_window_id(window)) == NULL || con->window == NULL) + return false; + + window_update_role(con->window, prop, false); + + return true; +} + #if 0 /* * Updates the client’s WM_CLASS property @@ -933,7 +948,8 @@ static struct property_handler_t property_handlers[] = { { 0, 128, handle_windowname_change_legacy }, { 0, UINT_MAX, handle_normal_hints }, { 0, UINT_MAX, handle_clientleader_change }, - { 0, UINT_MAX, handle_transient_for } + { 0, UINT_MAX, handle_transient_for }, + { 0, 128, handle_windowrole_change } }; #define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t)) @@ -949,6 +965,7 @@ void property_handlers_init() { property_handlers[3].atom = XCB_ATOM_WM_NORMAL_HINTS; property_handlers[4].atom = A_WM_CLIENT_LEADER; property_handlers[5].atom = XCB_ATOM_WM_TRANSIENT_FOR; + property_handlers[6].atom = A_WM_WINDOW_ROLE; } static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) { diff --git a/src/manage.c b/src/manage.c index 9ee3dd72..35055d17 100644 --- a/src/manage.c +++ b/src/manage.c @@ -83,7 +83,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie, utf8_title_cookie, title_cookie, - class_cookie, leader_cookie, transient_cookie; + class_cookie, leader_cookie, transient_cookie, + role_cookie; geomc = xcb_get_geometry(conn, d); @@ -145,6 +146,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki transient_cookie = GET_PROPERTY(XCB_ATOM_WM_TRANSIENT_FOR, UINT32_MAX); title_cookie = GET_PROPERTY(XCB_ATOM_WM_NAME, 128); class_cookie = GET_PROPERTY(XCB_ATOM_WM_CLASS, 128); + role_cookie = GET_PROPERTY(A_WM_WINDOW_ROLE, 128); /* TODO: also get wm_normal_hints here. implement after we got rid of xcb-event */ DLOG("reparenting!\n"); @@ -171,6 +173,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki window_update_leader(cwindow, xcb_get_property_reply(conn, leader_cookie, NULL)); window_update_transient_for(cwindow, xcb_get_property_reply(conn, transient_cookie, NULL)); window_update_strut_partial(cwindow, xcb_get_property_reply(conn, strut_cookie, NULL)); + window_update_role(cwindow, xcb_get_property_reply(conn, role_cookie, NULL), true); /* check if the window needs WM_TAKE_FOCUS */ cwindow->needs_take_focus = window_supports_protocol(cwindow->id, A_WM_TAKE_FOCUS); diff --git a/src/match.c b/src/match.c index 3514acee..745989ba 100644 --- a/src/match.c +++ b/src/match.c @@ -39,6 +39,7 @@ bool match_is_empty(Match *match) { match->application == NULL && match->class == NULL && match->instance == NULL && + match->role == NULL && match->id == XCB_NONE && match->con_id == NULL && match->dock == -1 && @@ -65,6 +66,7 @@ void match_copy(Match *dest, Match *src) { DUPLICATE_REGEX(application); DUPLICATE_REGEX(class); DUPLICATE_REGEX(instance); + DUPLICATE_REGEX(role); } /* @@ -113,6 +115,16 @@ bool match_matches_window(Match *match, i3Window *window) { } } + if (match->role != NULL) { + if (window->role != NULL && + regex_matches(match->role, window->role)) { + LOG("window_role matches (%s)\n", window->role); + } else { + LOG("window_role does not match\n"); + return false; + } + } + if (match->dock != -1) { LOG("match->dock = %d, window->dock = %d\n", match->dock, window->dock); if ((window->dock == W_DOCK_TOP && match->dock == M_DOCK_TOP) || @@ -148,6 +160,7 @@ void match_free(Match *match) { regex_free(match->class); regex_free(match->instance); regex_free(match->mark); + regex_free(match->role); /* Second step: free the regex helper struct itself */ FREE(match->title); @@ -155,4 +168,5 @@ void match_free(Match *match) { FREE(match->class); FREE(match->instance); FREE(match->mark); + FREE(match->role); } diff --git a/src/window.c b/src/window.c index 3dd66456..4b8b6614 100644 --- a/src/window.c +++ b/src/window.c @@ -215,3 +215,36 @@ void window_update_strut_partial(i3Window *win, xcb_get_property_reply_t *prop) free(prop); } + +/* + * Updates the WM_WINDOW_ROLE + * + */ +void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) { + if (prop == NULL || xcb_get_property_value_length(prop) == 0) { + DLOG("prop == NULL\n"); + FREE(prop); + return; + } + + char *new_role; + if (asprintf(&new_role, "%.*s", xcb_get_property_value_length(prop), + (char*)xcb_get_property_value(prop)) == -1) { + perror("asprintf()"); + DLOG("Could not get WM_WINDOW_ROLE\n"); + free(prop); + return; + } + FREE(win->role); + win->role = new_role; + LOG("WM_WINDOW_ROLE changed to \"%s\"\n", win->role); + + if (before_mgmt) { + free(prop); + return; + } + + run_assignments(win); + + free(prop); +} From 5542af316f2e67448630643314a490926239299f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 18 Sep 2011 16:05:54 +0100 Subject: [PATCH 067/333] t/65-for_window: add tests for window_role --- testcases/t/65-for_window.t | 103 ++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/testcases/t/65-for_window.t b/testcases/t/65-for_window.t index fb4c2812..1746d117 100644 --- a/testcases/t/65-for_window.t +++ b/testcases/t/65-for_window.t @@ -349,5 +349,108 @@ is($content[0]->{border}, 'normal', 'normal border'); exit_gracefully($process->pid); +############################################################## +# 8: check that the role criterion works properly +############################################################## + +# this configuration is broken because "asdf" is not a valid integer +# the for_window should therefore recognize this error and don’t add the +# assignment +$config = <root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30 ], + background_color => '#00ff00', +); + +$window->_create; + +my $atomname = $x->atom(name => 'WM_WINDOW_ROLE'); +my $atomtype = $x->atom(name => 'STRING'); +$x->change_property( + PROP_MODE_REPLACE, + $window->id, + $atomname->id, + $atomtype->id, + 8, + length("i3test") + 1, + "i3test\x00" +); + +$window->name('usethis'); +$window->map; +sleep 0.25; + +@content = @{get_ws_content($tmp)}; +cmp_ok(@content, '==', 1, 'one node on this workspace now'); +is($content[0]->{border}, 'none', 'no border (window_role)'); + +exit_gracefully($process->pid); + +############################################################## +# 9: another test for the window_role, but this time it changes +# *after* the window has been mapped +############################################################## + +# this configuration is broken because "asdf" is not a valid integer +# the for_window should therefore recognize this error and don’t add the +# assignment +$config = <root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30 ], + background_color => '#00ff00', +); + +$window->_create; + +$window->name('usethis'); +$window->map; +sleep 0.25; + +@content = @{get_ws_content($tmp)}; +cmp_ok(@content, '==', 1, 'one node on this workspace now'); +is($content[0]->{border}, 'normal', 'normal border (window_role 2)'); + +$atomname = $x->atom(name => 'WM_WINDOW_ROLE'); +$atomtype = $x->atom(name => 'STRING'); +$x->change_property( + PROP_MODE_REPLACE, + $window->id, + $atomname->id, + $atomtype->id, + 8, + length("i3test") + 1, + "i3test\x00" +); + +$x->flush; + +sleep 0.25; + +@content = @{get_ws_content($tmp)}; +cmp_ok(@content, '==', 1, 'one node on this workspace now'); +is($content[0]->{border}, 'none', 'no border (window_role 2)'); + +exit_gracefully($process->pid); + done_testing; From 9a76887a28b0ee009d658e442f351791480e8d0b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 18 Sep 2011 16:06:52 +0100 Subject: [PATCH 068/333] docs/userguide: document the window_role criterion --- docs/userguide | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/userguide b/docs/userguide index 184848aa..49a967c2 100644 --- a/docs/userguide +++ b/docs/userguide @@ -754,6 +754,8 @@ class:: Compares the window class (the second part of WM_CLASS) instance:: Compares the window instance (the first part of WM_CLASS) +window_role:: + Compares the window role (WM_WINDOW_ROLE). id:: Compares the X11 window ID, which you can get via +xwininfo+ for example. title:: @@ -764,9 +766,9 @@ con_id:: Compares the i3-internal container ID, which you can get via the IPC interface. Handy for scripting. -The criteria +class+, +instance+, +title+ and +mark+ are actually regular -expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for information on -how to use them. +The criteria +class+, +instance+, +role+, +title+ and +mark+ are actually +regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for +information on how to use them. === Splitting containers From 5aa43d61f84e69c9b46cfc3e37c6806b887f01d8 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Mon, 19 Sep 2011 22:43:25 +0200 Subject: [PATCH 069/333] Add force_xinerama configuration option The configuration option does the same as the commandline parameter, except it can be easily set by the user (e.g. you are using KDM and can't start a session through ~/.xsession). Signed-off-by: Michael Walle --- include/config.h | 10 ++++++++++ src/cfgparse.l | 1 + src/cfgparse.y | 10 ++++++++++ src/main.c | 5 ++++- 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/include/config.h b/include/config.h index 3234b91e..07391a6a 100644 --- a/include/config.h +++ b/include/config.h @@ -123,6 +123,16 @@ struct Config { * more often. */ bool force_focus_wrapping; + /** By default, use the RandR API for multi-monitor setups. + * Unfortunately, the nVidia binary graphics driver doesn't support + * this API. Instead, it only support the less powerful Xinerama API, + * which can be enabled by this option. + * + * Note: this option takes only effect on the initial startup (eg. + * reconfiguration is not possible). On startup, the list of screens + * is fetched once and never updated. */ + bool force_xinerama; + /** The default border style for new windows. */ border_style_t default_border; diff --git a/src/cfgparse.l b/src/cfgparse.l index 021e3516..dad5a915 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -125,6 +125,7 @@ none { return TOK_NONE; } 1pixel { return TOK_1PIXEL; } focus_follows_mouse { return TOKFOCUSFOLLOWSMOUSE; } force_focus_wrapping { return TOK_FORCE_FOCUS_WRAPPING; } +force_xinerama { return TOK_FORCE_XINERAMA; } 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 016953fd..53608948 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -619,6 +619,7 @@ void parse_file(const char *f) { %token TOK_1PIXEL "1pixel" %token TOKFOCUSFOLLOWSMOUSE "focus_follows_mouse" %token TOK_FORCE_FOCUS_WRAPPING "force_focus_wrapping" +%token TOK_FORCE_XINERAMA "force_xinerama" %token TOKWORKSPACEBAR "workspace_bar" %token TOK_DEFAULT "default" %token TOK_STACKING "stacking" @@ -674,6 +675,7 @@ line: | new_float | focus_follows_mouse | force_focus_wrapping + | force_xinerama | workspace_bar | workspace | assign @@ -1022,6 +1024,14 @@ force_focus_wrapping: } ; +force_xinerama: + TOK_FORCE_XINERAMA bool + { + DLOG("force xinerama = %d\n", $2); + config.force_xinerama = $2; + } + ; + workspace_bar: TOKWORKSPACEBAR bool { diff --git a/src/main.c b/src/main.c index ea02bb6e..cc60d69c 100644 --- a/src/main.c +++ b/src/main.c @@ -432,7 +432,10 @@ int main(int argc, char *argv[]) { free(greply); - if (force_xinerama) { + /* Force Xinerama (for drivers which don't support RandR yet, esp. the + * nVidia binary graphics driver), when specified either in the config + * file or on command-line */ + if (force_xinerama || config.force_xinerama) { xinerama_init(); } else { DLOG("Checking for XRandR...\n"); From ab04526aa1d4527035c682e7656196ccf831ed74 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 19 Sep 2011 23:18:40 +0100 Subject: [PATCH 070/333] mention the force_xinerama configfile directive in the userguide --- docs/userguide | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/docs/userguide b/docs/userguide index 49a967c2..7db9c4e7 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1,7 +1,7 @@ i3 User’s Guide =============== Michael Stapelberg -August 2011 +September 2011 This document contains all the information you need to configure and use the i3 window manager. If it does not, please contact us on IRC (preferred) or post your @@ -712,6 +712,31 @@ force_focus_wrapping force_focus_wrapping yes ------------------------ +=== Forcing Xinerama + +As explained in-depth in , some X11 +video drivers (especially the nVidia binary driver) only provide support for +Xinerama instead of RandR. In such a situation, i3 must be told to use the +inferior Xinerama API explicitly and therefore don’t provide support for +reconfiguring your screens on the fly (they are read only once on startup and +that’s it). + +For people who do cannot modify their +~/.xsession+ to add the ++--force-xinerama+ commandline parameter, a configuration option is provided: + +*Syntax*: +----------------------- +force_xinerama +----------------------- + +*Example*: +------------------ +force_xinerama yes +------------------ + +Also note that your output names are not descriptive (like +HDMI1+) when using +Xinerama, instead they are counted up, starting at 0: +xinerama-0+, +xinerama-1+, … + == List of commands Commands are what you bind to specific keypresses. You can also issue commands From 3073fe0bc88a8d7e9ffbeb7ec78f06b807104618 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 19 Sep 2011 23:21:38 +0100 Subject: [PATCH 071/333] update docs/multi-monitor to include the output names and refer to the force_xinerama config directive --- docs/multi-monitor | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/multi-monitor b/docs/multi-monitor index ec0256c0..a1fd6dc0 100644 --- a/docs/multi-monitor +++ b/docs/multi-monitor @@ -1,7 +1,7 @@ The multi-monitor situation =========================== Michael Stapelberg -March 2010 +September 2011 …or: oh no, I have an nVidia graphics card! @@ -16,6 +16,8 @@ i3, like so: exec i3 --force-xinerama -V >>~/.i3/i3log 2>&1 ---------------------------------------------- +…or use +force_xinerama yes+ in your configuration file. + == The explanation Starting with version 3.ε, i3 uses the RandR (Rotate and Resize) API instead @@ -50,9 +52,13 @@ these are two screens). For this very reason, we decided to implement the following workaround: As long as the nVidia driver does not support RandR, an option called -+--force-xinerama+ is available in i3. This option gets the list of screens -*once* when starting, and never updates it. As the nVidia driver cannot do -dynamic configuration anyways, this is not a big deal. ++--force-xinerama+ is available in i3 (alternatively, you can use the ++force_xinerama+ configuration file directive). This option gets the list of +screens *once* when starting, and never updates it. As the nVidia driver cannot +do dynamic configuration anyways, this is not a big deal. + +Also note that your output names are not descriptive (like +HDMI1+) when using +Xinerama, instead they are counted up, starting at 0: +xinerama-0+, +xinerama-1+, … == See also From 83560c85d8e972e6915cdc42d3f9b8ac8a315351 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 22 Sep 2011 20:30:24 +0100 Subject: [PATCH 072/333] =?UTF-8?q?lib/i3test.pm:=20Don=E2=80=99t=20sleep(?= =?UTF-8?q?0.25),=20but=20wait=20until=20the=20window=20was=20mapped?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes it faster and less racey --- testcases/t/lib/i3test.pm | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 3d2d85af..f8923667 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -7,6 +7,7 @@ use X11::XCB::Rect; use X11::XCB::Window; use X11::XCB qw(:all); use AnyEvent::I3; +use EV; use List::Util qw(first); use List::MoreUtils qw(lastval); use Time::HiRes qw(sleep); @@ -57,6 +58,7 @@ sub open_standard_window { class => WINDOW_CLASS_INPUT_OUTPUT, rect => X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30 ), background_color => $color, + event_mask => [ 'structure_notify' ], ); if (defined($floating) && $floating) { @@ -68,7 +70,29 @@ sub open_standard_window { $window->name('Window ' . counter_window()); $window->map; - sleep(0.25); + # wait for the mapped event with a timeout of 0.25s + my $cv = AE::cv; + + my $prep = EV::prepare sub { + $x->flush; + }; + + my $check = EV::check sub { + while (defined(my $event = $x->poll_for_event)) { + if ($event->{response_type} == MAP_NOTIFY) { + $cv->send(0) + } + } + }; + + my $watcher = EV::io $x->get_file_descriptor, EV::READ, sub { + # do nothing, we only need this watcher so that EV picks up the events + }; + + # Trigger timeout after 0.25s + my $timeout = AE::timer 0.5, 0, sub { say STDERR "timeout"; $cv->send(1) }; + + my $result = $cv->recv; return $window; } From 1481cd95c941e87fff8713a514ddc26700ce0b7d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 22 Sep 2011 22:08:42 +0100 Subject: [PATCH 073/333] Implement the I3_SYNC client protocol This is mainly useful for the testsuite. The tests can wait until i3 processed all X11 events and then continue. This eliminates sleep() calls which leads to a more robust and faster testsuite. --- include/atoms.xmacro | 1 + src/handlers.c | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/include/atoms.xmacro b/include/atoms.xmacro index 7f83a7ee..309a3325 100644 --- a/include/atoms.xmacro +++ b/include/atoms.xmacro @@ -24,3 +24,4 @@ xmacro(WM_TAKE_FOCUS) xmacro(WM_WINDOW_ROLE) xmacro(I3_SOCKET_PATH) xmacro(I3_CONFIG_PATH) +xmacro(I3_SYNC) diff --git a/src/handlers.c b/src/handlers.c index 267159ec..b3cb1df7 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -648,6 +648,25 @@ static int handle_client_message(xcb_client_message_event_t *event) { tree_render(); x_push_changes(croot); + } else if (event->type == A_I3_SYNC) { + DLOG("i3 sync, yay\n"); + xcb_window_t window = event->data.data32[0]; + uint32_t rnd = event->data.data32[1]; + DLOG("Sending random value %d back to X11 window 0x%08x\n", rnd, window); + + void *reply = scalloc(32); + xcb_client_message_event_t *ev = reply; + + ev->response_type = XCB_CLIENT_MESSAGE; + ev->window = window; + ev->type = A_I3_SYNC; + ev->format = 32; + ev->data.data32[0] = window; + ev->data.data32[1] = rnd; + + xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char*)ev); + xcb_flush(conn); + free(reply); } else { ELOG("unhandled clientmessage\n"); return 0; From 3167e9ad2d3c4b226f01ab2c65d0f8a85c1edaec Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 22 Sep 2011 22:11:11 +0100 Subject: [PATCH 074/333] lib/i3test.pm: reformat exports list --- testcases/t/lib/i3test.pm | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index f8923667..9ded4942 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -18,7 +18,25 @@ use Proc::Background; use v5.10; use Exporter (); -our @EXPORT = qw(get_workspace_names get_unused_workspace fresh_workspace get_ws_content get_ws get_focused open_empty_con open_standard_window get_dock_clients cmd does_i3_live exit_gracefully workspace_exists focused_ws get_socket_path launch_with_config); +our @EXPORT = qw( + get_workspace_names + get_unused_workspace + fresh_workspace + get_ws_content + get_ws + get_focused + open_empty_con + open_standard_window + get_dock_clients + cmd + sync_with_i3 + does_i3_live + exit_gracefully + workspace_exists + focused_ws + get_socket_path + launch_with_config +); my $tester = Test::Builder->new(); my $_cached_socket_path = undef; From 38a9eabff134b23bb0883561034dd2578e1231b1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 23 Sep 2011 20:37:45 +0100 Subject: [PATCH 075/333] tests: implement sync_with_i3 and use it instead of sleep() Also use open_standard_window() in a few more places where appropriate --- testcases/t/02-fullscreen.t | 10 +- testcases/t/05-ipc.t | 11 +-- testcases/t/06-focus.t | 2 +- testcases/t/08-focus-stack.t | 24 +---- testcases/t/09-stacking.t | 2 - testcases/t/11-goto.t | 4 +- testcases/t/12-floating-resize.t | 24 ++--- testcases/t/13-urgent.t | 6 +- testcases/t/19-match.t | 17 +--- testcases/t/33-size-hints.t | 2 +- testcases/t/35-floating-focus.t | 59 +++++++---- testcases/t/36-floating-ws-empty.t | 15 +-- testcases/t/37-floating-unmap.t | 17 +--- testcases/t/38-floating-attach.t | 43 +------- testcases/t/40-focus-lost.t | 6 +- testcases/t/41-resize.t | 7 +- testcases/t/45-flattening.t | 3 - testcases/t/46-floating-reinsert.t | 19 +--- testcases/t/47-regress-floatingmove.t | 3 - testcases/t/48-regress-floatingmovews.t | 8 +- testcases/t/53-floating-originalsize.t | 2 +- testcases/t/56-fullscreen-focus.t | 2 +- testcases/t/62-regress-dock-urgent.t | 2 +- testcases/t/63-wm-state.t | 15 +-- testcases/t/67-workspace_layout.t | 4 + testcases/t/70-force_focus_wrapping.t | 3 + testcases/t/lib/i3test.pm | 126 +++++++++++++++++++----- 27 files changed, 198 insertions(+), 238 deletions(-) diff --git a/testcases/t/02-fullscreen.t b/testcases/t/02-fullscreen.t index 34e5364e..e83e45ac 100644 --- a/testcases/t/02-fullscreen.t +++ b/testcases/t/02-fullscreen.t @@ -59,11 +59,9 @@ my $new_rect = $window->rect; ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned"); $original_rect = $new_rect; -sleep 0.25; - $window->fullscreen(1); -sleep 0.25; +sync_with_i3($x); $new_rect = $window->rect; ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned after fullscreen"); @@ -135,12 +133,12 @@ $new_rect = $swindow->rect; ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned"); $swindow->fullscreen(1); -sleep 0.25; +sync_with_i3($x); is(fullscreen_windows(), 1, 'amount of fullscreen windows'); $window->fullscreen(0); -sleep 0.25; +sync_with_i3($x); is(fullscreen_windows(), 0, 'amount of fullscreen windows'); ok($swindow->mapped, 'window mapped after other fullscreen ended'); @@ -152,7 +150,7 @@ ok($swindow->mapped, 'window mapped after other fullscreen ended'); ########################################################################### $swindow->fullscreen(0); -sleep 0.25; +sync_with_i3($x); is(fullscreen_windows(), 0, 'amount of fullscreen windows after disabling'); diff --git a/testcases/t/05-ipc.t b/testcases/t/05-ipc.t index a910c930..0d18040e 100644 --- a/testcases/t/05-ipc.t +++ b/testcases/t/05-ipc.t @@ -2,11 +2,6 @@ # vim:ts=4:sw=4:expandtab use i3test; -use X11::XCB qw(:all); - -BEGIN { - use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); -} my $x = X11::XCB::Connection->new; @@ -18,16 +13,14 @@ fresh_workspace; # Create a window so we can get a focus different from NULL my $window = open_standard_window($x); -diag("window->id = " . $window->id); - -sleep 0.25; +sync_with_i3($x); my $focus = $x->input_focus; -diag("old focus = $focus"); # Switch to another workspace fresh_workspace; +sync_with_i3($x); my $new_focus = $x->input_focus; isnt($focus, $new_focus, "Focus changed"); diff --git a/testcases/t/06-focus.t b/testcases/t/06-focus.t index d357c8a9..b3add322 100644 --- a/testcases/t/06-focus.t +++ b/testcases/t/06-focus.t @@ -24,7 +24,7 @@ cmd 'split v'; my $top = open_standard_window($x); my $mid = open_standard_window($x); my $bottom = open_standard_window($x); -sleep 0.25; +##sleep 0.25; diag("top id = " . $top->id); diag("mid id = " . $mid->id); diff --git a/testcases/t/08-focus-stack.t b/testcases/t/08-focus-stack.t index f8143979..33a5884a 100644 --- a/testcases/t/08-focus-stack.t +++ b/testcases/t/08-focus-stack.t @@ -4,11 +4,6 @@ # over an unfocused tiling client and destroying the floating one again. use i3test; -use X11::XCB qw(:all); - -BEGIN { - use_ok('X11::XCB::Window') or BAIL_OUT('Could not load X11::XCB::Window'); -} my $x = X11::XCB::Connection->new; @@ -19,30 +14,21 @@ cmd 'split h'; my $tiled_left = open_standard_window($x); my $tiled_right = open_standard_window($x); -sleep 0.25; +sync_with_i3($x); # Get input focus before creating the floating window my $focus = $x->input_focus; # Create a floating window which is smaller than the minimum enforced size of i3 -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 1, 1, 30, 30], - background_color => '#C0C0C0', - type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), -); +my $window = open_standard_window($x, undef, 1); +sync_with_i3($x); -isa_ok($window, 'X11::XCB::Window'); - -$window->map; - -sleep 1; -sleep 0.25; is($x->input_focus, $window->id, 'floating window focused'); $window->unmap; -sleep 0.25; +# TODO: wait for unmap +sync_with_i3($x); is($x->input_focus, $focus, 'Focus correctly restored'); diff --git a/testcases/t/09-stacking.t b/testcases/t/09-stacking.t index 1cb205ed..cc285f32 100644 --- a/testcases/t/09-stacking.t +++ b/testcases/t/09-stacking.t @@ -27,9 +27,7 @@ $i3->command('9')->recv; ##################################################################### my $top = i3test::open_standard_window($x); -sleep(0.25); my $mid = i3test::open_standard_window($x); -sleep(0.25); my $bottom = i3test::open_standard_window($x); sleep(0.25); diff --git a/testcases/t/11-goto.t b/testcases/t/11-goto.t index 542dc828..44cf55ab 100644 --- a/testcases/t/11-goto.t +++ b/testcases/t/11-goto.t @@ -21,11 +21,8 @@ cmd 'split h'; ##################################################################### my $top = open_standard_window($x); -sleep 0.25; my $mid = open_standard_window($x); -sleep 0.25; my $bottom = open_standard_window($x); -sleep 0.25; diag("top id = " . $top->id); diag("mid id = " . $mid->id); @@ -39,6 +36,7 @@ sub focus_after { my $msg = shift; cmd $msg; + sync_with_i3($x); return $x->input_focus; } diff --git a/testcases/t/12-floating-resize.t b/testcases/t/12-floating-resize.t index 09297df0..1aec9573 100644 --- a/testcases/t/12-floating-resize.t +++ b/testcases/t/12-floating-resize.t @@ -1,8 +1,5 @@ #!perl # vim:ts=4:sw=4:expandtab -# Beware that this test uses workspace 9 to perform some tests (it expects -# the workspace to be empty). -# TODO: skip it by default? use i3test; use X11::XCB qw(:all); @@ -20,30 +17,22 @@ fresh_workspace; ##################################################################### # Create a floating window -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#C0C0C0', - # replace the type with 'utility' as soon as the coercion works again in X11::XCB - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), -); - -isa_ok($window, 'X11::XCB::Window'); - -$window->map; -sleep 0.25; +my $window = open_standard_window($x, undef, 1); # See if configurerequests cause window movements (they should not) my ($a, $t) = $window->rect; $window->rect(X11::XCB::Rect->new(x => $a->x, y => $a->y, width => $a->width, height => $a->height)); -sleep 0.25; +sync_with_i3($x); + my ($na, $nt) = $window->rect; is_deeply($na, $a, 'Rects are equal after configurerequest'); sub test_resize { $window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 100, height => 100)); + sync_with_i3($x); + my ($absolute, $top) = $window->rect; # Make sure the width/height are different from what we’re gonna test, so @@ -52,7 +41,8 @@ sub test_resize { isnt($absolute->height, 500, 'height != 500'); $window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 300, height => 500)); - sleep 0.25; + + sync_with_i3($x); ($absolute, $top) = $window->rect; diff --git a/testcases/t/13-urgent.t b/testcases/t/13-urgent.t index f40b72fb..83c36a98 100644 --- a/testcases/t/13-urgent.t +++ b/testcases/t/13-urgent.t @@ -31,7 +31,7 @@ is(@urgent, 0, 'no window got the urgent flag'); # Add the urgency hint, switch to a different workspace and back again ##################################################################### $top->add_hint('urgency'); -sleep 0.5; +sync_with_i3($x); @content = @{get_ws_content($tmp)}; @urgent = grep { $_->{urgent} } @content; @@ -48,7 +48,7 @@ cmd '[id="' . $top->id . '"] focus'; is(@urgent, 0, 'no window got the urgent flag after focusing'); $top->add_hint('urgency'); -sleep 0.5; +sync_with_i3($x); @urgent = grep { $_->{urgent} } @{get_ws_content($tmp)}; is(@urgent, 0, 'no window got the urgent flag after re-setting urgency hint'); @@ -62,7 +62,7 @@ ok(!$ws->{urgent}, 'urgent flag not set on workspace'); my $otmp = fresh_workspace; $top->add_hint('urgency'); -sleep 0.5; +sync_with_i3($x); $ws = get_ws($tmp); ok($ws->{urgent}, 'urgent flag set on workspace'); diff --git a/testcases/t/19-match.t b/testcases/t/19-match.t index e4fc6ec0..93822f1f 100644 --- a/testcases/t/19-match.t +++ b/testcases/t/19-match.t @@ -12,20 +12,7 @@ ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); # Open a new window my $x = X11::XCB::Connection->new; -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30 ], - background_color => '#C0C0C0', -); - -$window->map; -# give it some time to be picked up by the window manager -# TODO: better check for $window->mapped or something like that? -# maybe we can even wait for getting mapped? -my $c = 0; -while (@{get_ws_content($tmp)} == 0 and $c++ < 5) { - sleep 0.25; -} +my $window = open_standard_window($x); my $content = get_ws_content($tmp); ok(@{$content} == 1, 'window mapped'); my $win = $content->[0]; @@ -114,7 +101,7 @@ ok(@{$content} == 2, 'two windows opened'); cmd '[class="special" title="left"] kill'; -sleep 0.25; +sync_with_i3($x); $content = get_ws_content($tmp); is(@{$content}, 1, 'one window still there'); diff --git a/testcases/t/33-size-hints.t b/testcases/t/33-size-hints.t index 05897c88..e212dc72 100644 --- a/testcases/t/33-size-hints.t +++ b/testcases/t/33-size-hints.t @@ -32,7 +32,7 @@ sleep 0.25; $win->hints->aspect($aspect); $x->flush; -sleep 0.25; +sync_with_i3($x); my $rect = $win->rect; my $ar = $rect->width / $rect->height; diff --git a/testcases/t/35-floating-focus.t b/testcases/t/35-floating-focus.t index 6adad246..fc94c440 100644 --- a/testcases/t/35-floating-focus.t +++ b/testcases/t/35-floating-focus.t @@ -16,6 +16,8 @@ my $tmp = fresh_workspace; my $first = open_standard_window($x); my $second = open_standard_window($x); +sync_with_i3($x); + is($x->input_focus, $second->id, 'second window focused'); cmd 'floating enable'; @@ -34,12 +36,16 @@ $first = open_standard_window($x); # window 2 $second = open_standard_window($x); # window 3 my $third = open_standard_window($x); # window 4 +sync_with_i3($x); + is($x->input_focus, $third->id, 'last container focused'); cmd 'floating enable'; cmd '[id="' . $second->id . '"] focus'; +sync_with_i3($x); + is($x->input_focus, $second->id, 'second con focused'); cmd 'floating enable'; @@ -47,7 +53,8 @@ cmd 'floating enable'; # now kill the third one (it's floating). focus should stay unchanged cmd '[id="' . $third->id . '"] kill'; -sleep 0.25; +# TODO: wait for unmapnotify +sync_with_i3($x); is($x->input_focus, $second->id, 'second con still focused after killing third'); @@ -63,12 +70,16 @@ $first = open_standard_window($x, '#ff0000'); # window 5 $second = open_standard_window($x, '#00ff00'); # window 6 my $third = open_standard_window($x, '#0000ff'); # window 7 +sync_with_i3($x); + is($x->input_focus, $third->id, 'last container focused'); cmd 'floating enable'; cmd '[id="' . $second->id . '"] focus'; +sync_with_i3($x); + is($x->input_focus, $second->id, 'second con focused'); cmd 'floating enable'; @@ -77,13 +88,14 @@ cmd 'floating enable'; # also floating cmd 'kill'; -sleep 0.25; +# TODO: wait for unmapnotify +sync_with_i3($x); is($x->input_focus, $third->id, 'third con focused'); cmd 'kill'; - -sleep 0.25; +# TODO: wait for unmapnotify +sync_with_i3($x); is($x->input_focus, $first->id, 'first con focused after killing all floating cons'); @@ -99,29 +111,34 @@ cmd 'layout stacked'; $second = open_standard_window($x, '#00ff00'); # window 6 $third = open_standard_window($x, '#0000ff'); # window 7 +sync_with_i3($x); + is($x->input_focus, $third->id, 'last container focused'); cmd 'floating enable'; cmd '[id="' . $second->id . '"] focus'; +sync_with_i3($x); + is($x->input_focus, $second->id, 'second con focused'); cmd 'floating enable'; -sleep 0.5; +sync_with_i3($x); # now kill the second one. focus should fall back to the third one, which is # also floating cmd 'kill'; -sleep 0.25; +# TODO: wait for unmapnotify +sync_with_i3($x); -is($x->input_focus, $third->id, 'second con focused'); +is($x->input_focus, $third->id, 'third con focused'); cmd 'kill'; - -sleep 0.25; +# TODO: wait for unmapnotify +sync_with_i3($x); is($x->input_focus, $first->id, 'first con focused after killing all floating cons'); @@ -134,6 +151,8 @@ $tmp = fresh_workspace; $first = open_standard_window($x, '#ff0000'); # window 8 $second = open_standard_window($x, '#00ff00'); # window 9 +sync_with_i3($x); + is($x->input_focus, $second->id, 'second container focused'); cmd 'floating enable'; @@ -142,31 +161,31 @@ is($x->input_focus, $second->id, 'second container focused'); cmd 'focus tiling'; -sleep 0.25; +sync_with_i3($x); is($x->input_focus, $first->id, 'first (tiling) container focused'); cmd 'focus floating'; -sleep 0.25; +sync_with_i3($x); is($x->input_focus, $second->id, 'second (floating) container focused'); cmd 'focus floating'; -sleep 0.25; +sync_with_i3($x); is($x->input_focus, $second->id, 'second (floating) container still focused'); cmd 'focus mode_toggle'; -sleep 0.25; +sync_with_i3($x); is($x->input_focus, $first->id, 'first (tiling) container focused'); cmd 'focus mode_toggle'; -sleep 0.25; +sync_with_i3($x); is($x->input_focus, $second->id, 'second (floating) container focused'); @@ -180,35 +199,37 @@ $first = open_standard_window($x, '#ff0000', 1); # window 10 $second = open_standard_window($x, '#00ff00', 1); # window 11 $third = open_standard_window($x, '#0000ff', 1); # window 12 +sync_with_i3($x); + is($x->input_focus, $third->id, 'third container focused'); cmd 'focus left'; -sleep 0.25; +sync_with_i3($x); is($x->input_focus, $second->id, 'second container focused'); cmd 'focus left'; -sleep 0.25; +sync_with_i3($x); is($x->input_focus, $first->id, 'first container focused'); cmd 'focus left'; -sleep 0.25; +sync_with_i3($x); is($x->input_focus, $third->id, 'focus wrapped to third container'); cmd 'focus right'; -sleep 0.25; +sync_with_i3($x); is($x->input_focus, $first->id, 'focus wrapped to first container'); cmd 'focus right'; -sleep 0.25; +sync_with_i3($x); is($x->input_focus, $second->id, 'focus on second container'); diff --git a/testcases/t/36-floating-ws-empty.t b/testcases/t/36-floating-ws-empty.t index f33d04db..91467ed3 100644 --- a/testcases/t/36-floating-ws-empty.t +++ b/testcases/t/36-floating-ws-empty.t @@ -22,20 +22,7 @@ ok(workspace_exists($tmp), "workspace $tmp exists"); my $x = X11::XCB::Connection->new; # Create a floating window which is smaller than the minimum enforced size of i3 -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#C0C0C0', - # replace the type with 'utility' as soon as the coercion works again in X11::XCB - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), -); - -isa_ok($window, 'X11::XCB::Window'); - -$window->map; - -sleep 0.25; - +my $window = open_standard_window($x, undef, 1); ok($window->mapped, 'Window is mapped'); # switch to a different workspace, see if the window is still mapped? diff --git a/testcases/t/37-floating-unmap.t b/testcases/t/37-floating-unmap.t index 3ae4b12d..b3be1348 100644 --- a/testcases/t/37-floating-unmap.t +++ b/testcases/t/37-floating-unmap.t @@ -21,27 +21,14 @@ my $tmp = fresh_workspace; my $x = X11::XCB::Connection->new; # Create a floating window which is smaller than the minimum enforced size of i3 -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#C0C0C0', - # replace the type with 'utility' as soon as the coercion works again in X11::XCB - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), -); - -isa_ok($window, 'X11::XCB::Window'); - -$window->map; - -sleep 0.25; - +my $window = open_standard_window($x, undef, 1); ok($window->mapped, 'Window is mapped'); # switch to a different workspace, see if the window is still mapped? my $otmp = fresh_workspace; -sleep 0.25; +sync_with_i3($x); ok(!$window->mapped, 'Window is not mapped after switching ws'); diff --git a/testcases/t/38-floating-attach.t b/testcases/t/38-floating-attach.t index 31bddafd..411ffa8e 100644 --- a/testcases/t/38-floating-attach.t +++ b/testcases/t/38-floating-attach.t @@ -5,7 +5,6 @@ use i3test; use X11::XCB qw(:all); -use Time::HiRes qw(sleep); BEGIN { use_ok('X11::XCB::Window'); @@ -22,20 +21,7 @@ my $tmp = fresh_workspace; my $x = X11::XCB::Connection->new; # Create a floating window -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#C0C0C0', - # replace the type with 'utility' as soon as the coercion works again in X11::XCB - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), -); - -isa_ok($window, 'X11::XCB::Window'); - -$window->map; - -sleep 0.25; - +my $window = open_standard_window($x, undef, 1); ok($window->mapped, 'Window is mapped'); my $ws = get_ws($tmp); @@ -45,17 +31,7 @@ is(@{$ws->{floating_nodes}}, 1, 'one floating node'); is(@{$nodes}, 0, 'no tiling nodes'); # Create a tiling window -my $twindow = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#C0C0C0', -); - -isa_ok($twindow, 'X11::XCB::Window'); - -$twindow->map; - -sleep 0.25; +my $twindow = open_standard_window($x); ($nodes, $focus) = get_ws_content($tmp); @@ -78,20 +54,7 @@ is(@{$ws->{floating_nodes}}, 0, 'no floating nodes so far'); is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)'); # Create a floating window -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#C0C0C0', - # replace the type with 'utility' as soon as the coercion works again in X11::XCB - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), -); - -isa_ok($window, 'X11::XCB::Window'); - -$window->map; - -sleep 0.25; - +my $window = open_standard_window($x, undef, 1); ok($window->mapped, 'Window is mapped'); $ws = get_ws($tmp); diff --git a/testcases/t/40-focus-lost.t b/testcases/t/40-focus-lost.t index 9df220d1..1121f124 100644 --- a/testcases/t/40-focus-lost.t +++ b/testcases/t/40-focus-lost.t @@ -4,7 +4,6 @@ # bug introduced by 77d0d42ed2d7ac8cafe267c92b35a81c1b9491eb use i3test; use X11::XCB qw(:all); -use Time::HiRes qw(sleep); BEGIN { use_ok('X11::XCB::Window'); @@ -26,11 +25,10 @@ sub check_order { my $tmp = fresh_workspace; my $left = open_standard_window($x); -sleep 0.25; my $mid = open_standard_window($x); -sleep 0.25; my $right = open_standard_window($x); -sleep 0.25; + +sync_with_i3($x); diag("left = " . $left->id . ", mid = " . $mid->id . ", right = " . $right->id); diff --git a/testcases/t/41-resize.t b/testcases/t/41-resize.t index 1d1b1206..2a3846d8 100644 --- a/testcases/t/41-resize.t +++ b/testcases/t/41-resize.t @@ -15,9 +15,9 @@ my $tmp = fresh_workspace; cmd 'split v'; my $top = open_standard_window($x); -sleep 0.25; my $bottom = open_standard_window($x); -sleep 0.25; + +sync_with_i3($x); diag("top = " . $top->id . ", bottom = " . $bottom->id); @@ -55,9 +55,7 @@ $tmp = fresh_workspace; cmd 'split v'; $top = open_standard_window($x); -sleep 0.25; $bottom = open_standard_window($x); -sleep 0.25; cmd 'split h'; cmd 'layout stacked'; @@ -79,7 +77,6 @@ is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%'); $tmp = fresh_workspace; $top = open_standard_window($x); -sleep 0.25; cmd 'floating enable'; diff --git a/testcases/t/45-flattening.t b/testcases/t/45-flattening.t index 98b93ef1..4a57f211 100644 --- a/testcases/t/45-flattening.t +++ b/testcases/t/45-flattening.t @@ -18,11 +18,8 @@ my $x = X11::XCB::Connection->new; my $tmp = fresh_workspace; my $left = open_standard_window($x); -sleep 0.25; my $mid = open_standard_window($x); -sleep 0.25; my $right = open_standard_window($x); -sleep 0.25; cmd 'move before v'; cmd 'move after h'; diff --git a/testcases/t/46-floating-reinsert.t b/testcases/t/46-floating-reinsert.t index a4e90d4d..07f78956 100644 --- a/testcases/t/46-floating-reinsert.t +++ b/testcases/t/46-floating-reinsert.t @@ -2,7 +2,6 @@ # vim:ts=4:sw=4:expandtab # use X11::XCB qw(:all); -use Time::HiRes qw(sleep); use i3test; BEGIN { @@ -14,13 +13,10 @@ my $x = X11::XCB::Connection->new; my $tmp = fresh_workspace; my $left = open_standard_window($x); -sleep 0.25; my $mid = open_standard_window($x); -sleep 0.25; cmd 'split v'; my $bottom = open_standard_window($x); -sleep 0.25; my ($nodes, $focus) = get_ws_content($tmp); @@ -31,20 +27,7 @@ my ($nodes, $focus) = get_ws_content($tmp); my $x = X11::XCB::Connection->new; # Create a floating window -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#C0C0C0', - # replace the type with 'utility' as soon as the coercion works again in X11::XCB - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), -); - -isa_ok($window, 'X11::XCB::Window'); - -$window->map; - -sleep 0.25; - +my $window = open_standard_window($x, undef, 1); ok($window->mapped, 'Window is mapped'); ($nodes, $focus) = get_ws_content($tmp); diff --git a/testcases/t/47-regress-floatingmove.t b/testcases/t/47-regress-floatingmove.t index 6e04916d..8425f6d9 100644 --- a/testcases/t/47-regress-floatingmove.t +++ b/testcases/t/47-regress-floatingmove.t @@ -17,11 +17,8 @@ my $x = X11::XCB::Connection->new; my $tmp = fresh_workspace; my $left = open_standard_window($x); -sleep 0.25; my $mid = open_standard_window($x); -sleep 0.25; my $right = open_standard_window($x); -sleep 0.25; # go to workspace level cmd 'level up'; diff --git a/testcases/t/48-regress-floatingmovews.t b/testcases/t/48-regress-floatingmovews.t index 0bec5418..355e697a 100644 --- a/testcases/t/48-regress-floatingmovews.t +++ b/testcases/t/48-regress-floatingmovews.t @@ -17,20 +17,20 @@ my $tmp = fresh_workspace; # open a tiling window on the first workspace open_standard_window($x); -sleep 0.25; +#sleep 0.25; my $first = get_focused($tmp); # on a different ws, open a floating window my $otmp = fresh_workspace; open_standard_window($x); -sleep 0.25; +#sleep 0.25; my $float = get_focused($otmp); cmd 'mode toggle'; -sleep 0.25; +#sleep 0.25; # move the floating con to first workspace cmd "move workspace $tmp"; -sleep 0.25; +#sleep 0.25; # switch to the first ws and check focus is(get_focused($tmp), $float, 'floating client correctly focused'); diff --git a/testcases/t/53-floating-originalsize.t b/testcases/t/53-floating-originalsize.t index 16e62c20..1daa2709 100644 --- a/testcases/t/53-floating-originalsize.t +++ b/testcases/t/53-floating-originalsize.t @@ -30,7 +30,7 @@ cmp_ok($absolute->{width}, '>', 400, 'i3 raised the width'); cmp_ok($absolute->{height}, '>', 150, 'i3 raised the height'); cmd 'floating toggle'; -sleep 0.25; +sync_with_i3($x); ($absolute, $top) = $window->rect; diff --git a/testcases/t/56-fullscreen-focus.t b/testcases/t/56-fullscreen-focus.t index ee60dc7a..89af6219 100644 --- a/testcases/t/56-fullscreen-focus.t +++ b/testcases/t/56-fullscreen-focus.t @@ -56,7 +56,7 @@ cmd "move workspace $tmp2"; # verify that the third window has the focus -sleep 0.25; +sync_with_i3($x); is($x->input_focus, $third->id, 'third window focused'); diff --git a/testcases/t/62-regress-dock-urgent.t b/testcases/t/62-regress-dock-urgent.t index 8d188738..5fb88129 100644 --- a/testcases/t/62-regress-dock-urgent.t +++ b/testcases/t/62-regress-dock-urgent.t @@ -52,7 +52,7 @@ is($docknode->{rect}->{height}, 30, 'dock node has unchanged height'); $window->add_hint('urgency'); -sleep 0.25; +sync_with_i3($x); does_i3_live; diff --git a/testcases/t/63-wm-state.t b/testcases/t/63-wm-state.t index 7e983289..b9161400 100644 --- a/testcases/t/63-wm-state.t +++ b/testcases/t/63-wm-state.t @@ -16,25 +16,18 @@ BEGIN { my $x = X11::XCB::Connection->new; -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30 ], - background_color => '#00ff00', - event_mask => [ 'structure_notify' ], -); +my $window = open_standard_window($x); -$window->name('Window 1'); -$window->map; +sync_with_i3($x); diag('window mapped'); -sleep 0.5; - is($window->state, ICCCM_WM_STATE_NORMAL, 'WM_STATE normal'); $window->unmap; -sleep 0.5; +# TODO: wait for unmapnotify +sync_with_i3($x); is($window->state, ICCCM_WM_STATE_WITHDRAWN, 'WM_STATE withdrawn'); diff --git a/testcases/t/67-workspace_layout.t b/testcases/t/67-workspace_layout.t index 2b9f6e56..0e07ebf7 100644 --- a/testcases/t/67-workspace_layout.t +++ b/testcases/t/67-workspace_layout.t @@ -30,6 +30,8 @@ ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); my $first = open_standard_window($x); my $second = open_standard_window($x); +sync_with_i3($x); + is($x->input_focus, $second->id, 'second window focused'); ok(@{get_ws_content($tmp)} == 2, 'two containers opened'); isnt($content[0]->{layout}, 'stacked', 'layout not stacked'); @@ -57,6 +59,8 @@ ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); $first = open_standard_window($x); $second = open_standard_window($x); +sync_with_i3($x); + is($x->input_focus, $second->id, 'second window focused'); my @content = @{get_ws_content($tmp)}; ok(@content == 1, 'one con at workspace level'); diff --git a/testcases/t/70-force_focus_wrapping.t b/testcases/t/70-force_focus_wrapping.t index f2dfc18e..cf1c3216 100644 --- a/testcases/t/70-force_focus_wrapping.t +++ b/testcases/t/70-force_focus_wrapping.t @@ -73,6 +73,9 @@ cmd 'layout tabbed'; cmd 'focus parent'; $third = open_standard_window($x); + +sync_with_i3($x); + is($x->input_focus, $third->id, 'third window focused'); cmd 'focus left'; diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 9ded4942..2a51dad6 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -40,6 +40,7 @@ our @EXPORT = qw( my $tester = Test::Builder->new(); my $_cached_socket_path = undef; +my $_sync_window = undef; my $tmp_socket_path = undef; BEGIN { @@ -66,6 +67,44 @@ use warnings; goto \&Exporter::import; } +# +# Waits for the next event and calls the given callback for every event to +# determine if this is the event we are waiting for. +# +# Can be used to wait until a window is mapped, until a ClientMessage is +# received, etc. +# +# wait_for_event $x, 0.25, sub { $_[0]->{response_type} == MAP_NOTIFY }; +# +sub wait_for_event { + my ($x, $timeout, $cb) = @_; + + my $cv = AE::cv; + + my $prep = EV::prepare sub { + $x->flush; + }; + + my $check = EV::check sub { + while (defined(my $event = $x->poll_for_event)) { + if ($cb->($event)) { + $cv->send(1); + last; + } + } + }; + + my $watcher = EV::io $x->get_file_descriptor, EV::READ, sub { + # do nothing, we only need this watcher so that EV picks up the events + }; + + # Trigger timeout after $timeout seconds (can be fractional) + my $timeout = AE::timer $timeout, 0, sub { say STDERR "timeout"; $cv->send(0) }; + + my $result = $cv->recv; + return $result; +} + sub open_standard_window { my ($x, $color, $floating) = @_; @@ -88,29 +127,7 @@ sub open_standard_window { $window->name('Window ' . counter_window()); $window->map; - # wait for the mapped event with a timeout of 0.25s - my $cv = AE::cv; - - my $prep = EV::prepare sub { - $x->flush; - }; - - my $check = EV::check sub { - while (defined(my $event = $x->poll_for_event)) { - if ($event->{response_type} == MAP_NOTIFY) { - $cv->send(0) - } - } - }; - - my $watcher = EV::io $x->get_file_descriptor, EV::READ, sub { - # do nothing, we only need this watcher so that EV picks up the events - }; - - # Trigger timeout after 0.25s - my $timeout = AE::timer 0.5, 0, sub { say STDERR "timeout"; $cv->send(1) }; - - my $result = $cv->recv; + wait_for_event $x, 0.5, sub { $_[0]->{response_type} == MAP_NOTIFY }; return $window; } @@ -239,6 +256,69 @@ sub focused_ws { } } +# +# Sends an I3_SYNC ClientMessage with a random value to the root window. +# i3 will reply with the same value, but, due to the order of events it +# processes, only after all other events are done. +# +# This can be used to ensure the results of a cmd 'focus left' are pushed to +# X11 and that $x->input_focus returns the correct value afterwards. +# +# See also docs/testsuite for a long explanation +# +sub sync_with_i3 { + my ($x) = @_; + + # Since we need a (mapped) window for receiving a ClientMessage, we create + # one on the first call of sync_with_i3. It will be re-used in all + # subsequent calls. + if (!defined($_sync_window)) { + $_sync_window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => X11::XCB::Rect->new(x => -15, y => -15, width => 10, height => 10 ), + override_redirect => 1, + background_color => '#ff0000', + event_mask => [ 'structure_notify' ], + ); + + $_sync_window->map; + + wait_for_event $x, 0.5, sub { $_[0]->{response_type} == MAP_NOTIFY }; + } + + my $root = $x->root->id; + # Generate a random number to identify this particular ClientMessage. + my $myrnd = int(rand(255)) + 1; + + # Generate a ClientMessage, see xcb_client_message_t + my $msg = pack "CCSLLLLLLL", + CLIENT_MESSAGE, # response_type + 32, # format + 0, # sequence + $root, # destination window + $x->atom(name => 'I3_SYNC')->id, + + $_sync_window->id, # data[0]: our own window id + $myrnd, # data[1]: a random value to identify the request + 0, + 0, + 0; + + # Send it to the root window -- since i3 uses the SubstructureRedirect + # event mask, it will get the ClientMessage. + $x->send_event(0, $root, EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg); + + # now wait until the reply is here + return wait_for_event $x, 1, sub { + my ($event) = @_; + # TODO: const + return 0 unless $event->{response_type} == 161; + + my ($win, $rnd) = unpack "LL", $event->{data}; + return ($rnd == $myrnd); + }; +} + sub does_i3_live { my $tree = i3(get_socket_path())->get_tree->recv; my @nodes = @{$tree->{nodes}}; From 4821b13cae173c7ee598811f82c6502841a73a55 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Sep 2011 13:07:05 +0100 Subject: [PATCH 076/333] tests: lib/i3test: provide wait_for_map and wait_for_unmap These functions should be used instead of calling wait_for_event directly when waiting for MAP_NOTIFY or UNMAP_NOTIFY --- testcases/t/lib/i3test.pm | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 2a51dad6..40e84044 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -36,6 +36,9 @@ our @EXPORT = qw( focused_ws get_socket_path launch_with_config + wait_for_event + wait_for_map + wait_for_unmap ); my $tester = Test::Builder->new(); @@ -99,12 +102,28 @@ sub wait_for_event { }; # Trigger timeout after $timeout seconds (can be fractional) - my $timeout = AE::timer $timeout, 0, sub { say STDERR "timeout"; $cv->send(0) }; + my $timeout = AE::timer $timeout, 0, sub { warn "timeout"; $cv->send(0) }; my $result = $cv->recv; return $result; } +# thin wrapper around wait_for_event which waits for MAP_NOTIFY +# make sure to include 'structure_notify' in the window’s event_mask attribute +sub wait_for_map { + my ($x) = @_; + wait_for_event $x, 1, sub { $_[0]->{response_type} == MAP_NOTIFY }; +} + +# Wrapper around wait_for_event which waits for UNMAP_NOTIFY. Also calls +# sync_with_i3 to make sure i3 also picked up and processed the UnmapNotify +# event. +sub wait_for_unmap { + my ($x) = @_; + wait_for_event $x, 1, sub { $_[0]->{response_type} == UNMAP_NOTIFY }; + sync_with_i3($x); +} + sub open_standard_window { my ($x, $color, $floating) = @_; @@ -286,7 +305,7 @@ sub sync_with_i3 { wait_for_event $x, 0.5, sub { $_[0]->{response_type} == MAP_NOTIFY }; } - my $root = $x->root->id; + my $root = $x->get_root_window(); # Generate a random number to identify this particular ClientMessage. my $myrnd = int(rand(255)) + 1; @@ -386,7 +405,7 @@ sub launch_with_config { # one test case. my $i3cmd = "exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >>$ENV{LOGPATH} 2>&1"; my $process = Proc::Background->new($i3cmd); - sleep 1; + sleep 1.25; # force update of the cached socket path in lib/i3test get_socket_path(0); From 4da5b7e784acb0a0daa5a24116c940510a81294c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Sep 2011 13:08:02 +0100 Subject: [PATCH 077/333] tests: use wait_for_{map,unmap} to eliminate more sleep()s --- testcases/t/02-fullscreen.t | 10 ++++-- testcases/t/04-floating.t | 9 +++-- testcases/t/10-dock.t | 24 +++++++------ testcases/t/14-client-leader.t | 33 +++++++++++------- testcases/t/19-match.t | 19 +++++----- testcases/t/33-size-hints.t | 5 ++- testcases/t/50-regress-dock-restart.t | 8 +++-- testcases/t/53-floating-originalsize.t | 3 +- testcases/t/54-regress-multiple-dock.t | 7 ++-- testcases/t/55-floating-split-size.t | 7 ++-- testcases/t/56-fullscreen-focus.t | 14 +++++++- testcases/t/64-kill-win-vs-client.t | 4 +-- testcases/t/65-for_window.t | 48 ++++++++++++++++---------- testcases/t/66-assign.t | 24 ++++++++----- testcases/t/74-border-config.t | 8 ++--- 15 files changed, 139 insertions(+), 84 deletions(-) diff --git a/testcases/t/02-fullscreen.t b/testcases/t/02-fullscreen.t index e83e45ac..ae8c63f6 100644 --- a/testcases/t/02-fullscreen.t +++ b/testcases/t/02-fullscreen.t @@ -42,6 +42,7 @@ my $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => $original_rect, background_color => '#C0C0C0', + event_mask => [ 'structure_notify' ], ); isa_ok($window, 'X11::XCB::Window'); @@ -50,7 +51,7 @@ is_deeply($window->rect, $original_rect, "rect unmodified before mapping"); $window->map; -sleep 0.25; +wait_for_map $x; # open another container to make the window get only half of the screen cmd 'open'; @@ -92,6 +93,7 @@ $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => $original_rect, background_color => 61440, + event_mask => [ 'structure_notify' ], ); is_deeply($window->rect, $original_rect, "rect unmodified before mapping"); @@ -99,7 +101,7 @@ is_deeply($window->rect, $original_rect, "rect unmodified before mapping"); $window->fullscreen(1); $window->map; -sleep(0.25); +wait_for_map $x; $new_rect = $window->rect; ok(!eq_deeply($new_rect, $original_rect), "Window got repositioned after fullscreen"); @@ -122,10 +124,12 @@ my $swindow = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => $original_rect, background_color => '#C0C0C0', + event_mask => [ 'structure_notify' ], ); $swindow->map; -sleep 0.25; + +sync_with_i3($x); ok(!$swindow->mapped, 'window not mapped while fullscreen window active'); diff --git a/testcases/t/04-floating.t b/testcases/t/04-floating.t index fcf73f08..d605328d 100644 --- a/testcases/t/04-floating.t +++ b/testcases/t/04-floating.t @@ -17,13 +17,14 @@ my $window = $x->root->create_child( background_color => '#C0C0C0', # replace the type with 'utility' as soon as the coercion works again in X11::XCB window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), + event_mask => [ 'structure_notify' ], ); isa_ok($window, 'X11::XCB::Window'); $window->map; -sleep 0.25; +wait_for_map $x; my ($absolute, $top) = $window->rect; @@ -40,13 +41,14 @@ $window = $x->root->create_child( rect => [ 1, 1, 80, 90], background_color => '#C0C0C0', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), + event_mask => [ 'structure_notify' ], ); isa_ok($window, 'X11::XCB::Window'); $window->map; -sleep 0.25; +wait_for_map $x; ($absolute, $top) = $window->rect; @@ -70,13 +72,14 @@ $window = $x->root->create_child( rect => [ 1, 1, 80, 90], background_color => '#C0C0C0', #window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), + event_mask => [ 'structure_notify' ], ); isa_ok($window, 'X11::XCB::Window'); $window->map; -sleep 0.25; +wait_for_map $x; cmd 'floating enable'; diff --git a/testcases/t/10-dock.t b/testcases/t/10-dock.t index 3f0a5195..988d92db 100644 --- a/testcases/t/10-dock.t +++ b/testcases/t/10-dock.t @@ -10,7 +10,6 @@ BEGIN { } my $x = X11::XCB::Connection->new; -my $i3 = i3(get_socket_path()); ##################################################################### # verify that there is no dock window yet @@ -36,11 +35,12 @@ my $window = $x->root->create_child( rect => [ 0, 0, 30, 30], background_color => '#FF0000', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + event_mask => [ 'structure_notify' ], ); $window->map; -sleep 0.25; +wait_for_map $x; my $rect = $window->rect; is($rect->width, $primary->rect->width, 'dock client is as wide as the screen'); @@ -67,7 +67,7 @@ is($docknode->{rect}->{height}, 30, 'dock node has unchanged height'); $window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 50, height => 40)); -sleep 0.25; +sync_with_i3 $x; @docked = get_dock_clients('top'); is(@docked, 1, 'one dock client found'); @@ -82,7 +82,7 @@ is($docknode->{rect}->{height}, 40, 'dock height changed'); $window->destroy; -sleep 0.25; +wait_for_unmap $x; @docked = get_dock_clients(); is(@docked, 0, 'no more dock clients'); @@ -96,11 +96,12 @@ $window = $x->root->create_child( rect => [ 0, 1000, 30, 30], background_color => '#FF0000', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + event_mask => [ 'structure_notify' ], ); $window->map; -sleep 0.25; +wait_for_map $x; my $rect = $window->rect; is($rect->width, $primary->rect->width, 'dock client is as wide as the screen'); @@ -111,7 +112,7 @@ is(@docked, 1, 'dock client on bottom'); $window->destroy; -sleep 0.25; +wait_for_unmap $x; @docked = get_dock_clients(); is(@docked, 0, 'no more dock clients'); @@ -125,6 +126,7 @@ $window = $x->root->create_child( rect => [ 0, 1000, 30, 30], background_color => '#FF0000', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + event_mask => [ 'structure_notify' ], ); $window->_create(); @@ -145,14 +147,14 @@ $x->change_property( $window->map; -sleep 0.25; +wait_for_map $x; @docked = get_dock_clients('top'); is(@docked, 1, 'dock client on top'); $window->destroy; -sleep 0.25; +wait_for_unmap $x; @docked = get_dock_clients(); is(@docked, 0, 'no more dock clients'); @@ -162,6 +164,7 @@ $window = $x->root->create_child( rect => [ 0, 1000, 30, 30], background_color => '#FF0000', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + event_mask => [ 'structure_notify' ], ); $window->_create(); @@ -182,7 +185,7 @@ $x->change_property( $window->map; -sleep 0.25; +wait_for_map $x; @docked = get_dock_clients('bottom'); is(@docked, 1, 'dock client on bottom'); @@ -199,12 +202,13 @@ my $fwindow = $x->root->create_child( rect => [ 0, 0, 30, 30], background_color => '#FF0000', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + event_mask => [ 'structure_notify' ], ); $fwindow->transient_for($window); $fwindow->map; -sleep 0.25; +wait_for_map $x; does_i3_live; diff --git a/testcases/t/14-client-leader.t b/testcases/t/14-client-leader.t index 98978eb3..08deabf8 100644 --- a/testcases/t/14-client-leader.t +++ b/testcases/t/14-client-leader.t @@ -9,7 +9,6 @@ BEGIN { } my $x = X11::XCB::Connection->new; -my $i3 = i3(get_socket_path()); my $tmp = fresh_workspace; @@ -25,6 +24,7 @@ my $left = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [0, 0, 30, 30], background_color => '#FF0000', + event_mask => [ 'structure_notify' ], ); $left->name('Left'); @@ -34,12 +34,14 @@ my $right = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [0, 0, 30, 30], background_color => '#FF0000', + event_mask => [ 'structure_notify' ], ); $right->name('Right'); $right->map; -sleep 0.25; +ok(wait_for_map($x), 'left window mapped'); +ok(wait_for_map($x), 'right window mapped'); my ($abs, $rgeom) = $right->rect; @@ -48,13 +50,14 @@ my $child = $x->root->create_child( rect => [ 0, 0, 30, 30 ], background_color => '#C0C0C0', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), + event_mask => [ 'structure_notify' ], ); $child->name('Child window'); $child->client_leader($right); $child->map; -sleep 0.25; +ok(wait_for_map($x), 'child window mapped'); my $cgeom; ($abs, $cgeom) = $child->rect; @@ -65,13 +68,14 @@ my $child2 = $x->root->create_child( rect => [ 0, 0, 30, 30 ], background_color => '#C0C0C0', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), + event_mask => [ 'structure_notify' ], ); $child2->name('Child window 2'); $child2->client_leader($left); $child2->map; -sleep 0.25; +ok(wait_for_map($x), 'second child window mapped'); ($abs, $cgeom) = $child2->rect; cmp_ok(($cgeom->x + $cgeom->width), '<', $rgeom->x, 'child above left window'); @@ -83,12 +87,13 @@ my $fwindow = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30], background_color => '#FF0000', + event_mask => [ 'structure_notify' ], ); $fwindow->transient_for($right); $fwindow->map; -sleep 0.25; +ok(wait_for_map($x), 'transient window mapped'); my ($absolute, $top) = $fwindow->rect; ok($absolute->{x} != 0 && $absolute->{y} != 0, 'i3 did not map it to (0x0)'); @@ -101,15 +106,16 @@ SKIP: { ##################################################################### my $window = $x->root->create_child( -class => WINDOW_CLASS_INPUT_OUTPUT, -rect => [ 0, 0, 30, 30 ], -background_color => '#C0C0C0', + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30 ], + background_color => '#C0C0C0', + event_mask => [ 'structure_notify' ], ); $window->name('Parent window'); $window->map; -sleep 0.25; +ok(wait_for_map($x), 'parent window mapped'); ######################################################################### # Switch to a different workspace and open a child window. It should be opened @@ -118,16 +124,17 @@ sleep 0.25; fresh_workspace; my $child = $x->root->create_child( -class => WINDOW_CLASS_INPUT_OUTPUT, -rect => [ 0, 0, 30, 30 ], -background_color => '#C0C0C0', + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30 ], + background_color => '#C0C0C0', + event_mask => [ 'structure_notify' ], ); $child->name('Child window'); $child->client_leader($window); $child->map; -sleep 0.25; +ok(wait_for_map($x), 'child window mapped'); isnt($x->input_focus, $child->id, "Child window focused"); diff --git a/testcases/t/19-match.t b/testcases/t/19-match.t index 93822f1f..aab54456 100644 --- a/testcases/t/19-match.t +++ b/testcases/t/19-match.t @@ -35,8 +35,7 @@ cmd 'nop now killing the window'; my $id = $win->{id}; cmd qq|[con_id="$id"] kill|; -# give i3 some time to pick up the UnmapNotify event -sleep 0.25; +wait_for_unmap $x; cmd 'nop checking if its gone'; $content = get_ws_content($tmp); @@ -75,25 +74,27 @@ my $left = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#0000ff', + event_mask => [ 'structure_notify' ], ); $left->_create; set_wm_class($left->id, 'special', 'special'); $left->name('left'); $left->map; -sleep 0.25; +ok(wait_for_map($x), 'left window mapped'); my $right = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#0000ff', + event_mask => [ 'structure_notify' ], ); $right->_create; set_wm_class($right->id, 'special', 'special'); $right->name('right'); $right->map; -sleep 0.25; +ok(wait_for_map($x), 'right window mapped'); # two windows should be here $content = get_ws_content($tmp); @@ -116,13 +117,14 @@ $left = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#0000ff', + event_mask => [ 'structure_notify' ], ); $left->_create; set_wm_class($left->id, 'special7', 'special7'); $left->name('left'); $left->map; -sleep 0.25; +ok(wait_for_map($x), 'left window mapped'); # two windows should be here $content = get_ws_content($tmp); @@ -130,7 +132,7 @@ ok(@{$content} == 1, 'window opened'); cmd '[class="^special[0-9]$"] kill'; -sleep 0.25; +wait_for_unmap $x; $content = get_ws_content($tmp); is(@{$content}, 0, 'window killed'); @@ -145,13 +147,14 @@ $left = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#0000ff', + event_mask => [ 'structure_notify' ], ); $left->_create; set_wm_class($left->id, 'special7', 'special7'); $left->name('ä 3'); $left->map; -sleep 0.25; +ok(wait_for_map($x), 'left window mapped'); # two windows should be here $content = get_ws_content($tmp); @@ -159,7 +162,7 @@ ok(@{$content} == 1, 'window opened'); cmd '[title="^\w [3]$"] kill'; -sleep 0.25; +wait_for_unmap $x; $content = get_ws_content($tmp); is(@{$content}, 0, 'window killed'); diff --git a/testcases/t/33-size-hints.t b/testcases/t/33-size-hints.t index e212dc72..0d607dbc 100644 --- a/testcases/t/33-size-hints.t +++ b/testcases/t/33-size-hints.t @@ -5,8 +5,6 @@ # use i3test; -my $i3 = i3(get_socket_path()); - my $x = X11::XCB::Connection->new; my $tmp = fresh_workspace; @@ -17,6 +15,7 @@ my $win = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30), background_color => '#C0C0C0', + event_mask => [ 'structure_notify' ], ); # XXX: we should check screen size. in screens with an AR of 2.0, @@ -28,7 +27,7 @@ $aspect->max_num(600); $aspect->max_den(300); $win->_create; $win->map; -sleep 0.25; +wait_for_map $x; $win->hints->aspect($aspect); $x->flush; diff --git a/testcases/t/50-regress-dock-restart.t b/testcases/t/50-regress-dock-restart.t index a4f7bebc..caadb9e3 100644 --- a/testcases/t/50-regress-dock-restart.t +++ b/testcases/t/50-regress-dock-restart.t @@ -31,11 +31,12 @@ my $window = $x->root->create_child( rect => [ 0, 0, 30, 30], background_color => '#FF0000', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + event_mask => [ 'structure_notify' ], ); $window->map; -sleep 0.25; +wait_for_map $x; ##################################################################### # check that we can find it in the layout tree at the expected position @@ -69,7 +70,7 @@ is($docknode->{rect}->{height}, 30, 'dock node has unchanged height after restar $window->destroy; -sleep 0.25; +wait_for_unmap $x; @docked = get_dock_clients; is(@docked, 0, 'no dock clients found'); @@ -84,11 +85,12 @@ $window = $x->root->create_child( rect => [ 0, 0, 30, 20], background_color => '#00FF00', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + event_mask => [ 'structure_notify' ], ); $window->map; -sleep 0.25; +wait_for_map $x; @docked = get_dock_clients; is(@docked, 1, 'one dock client found'); diff --git a/testcases/t/53-floating-originalsize.t b/testcases/t/53-floating-originalsize.t index 1daa2709..b1dc6199 100644 --- a/testcases/t/53-floating-originalsize.t +++ b/testcases/t/53-floating-originalsize.t @@ -15,13 +15,14 @@ my $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 400, 150], background_color => '#C0C0C0', + event_mask => [ 'structure_notify' ], ); isa_ok($window, 'X11::XCB::Window'); $window->map; -sleep 0.25; +wait_for_map $x; my ($absolute, $top) = $window->rect; diff --git a/testcases/t/54-regress-multiple-dock.t b/testcases/t/54-regress-multiple-dock.t index 36070db1..b1809493 100644 --- a/testcases/t/54-regress-multiple-dock.t +++ b/testcases/t/54-regress-multiple-dock.t @@ -11,7 +11,6 @@ BEGIN { } my $x = X11::XCB::Connection->new; -my $i3 = i3(get_socket_path()); my $tmp = fresh_workspace; @@ -33,11 +32,12 @@ my $first = $x->root->create_child( rect => [ 0, 0, 30, 30], background_color => '#FF0000', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + event_mask => [ 'structure_notify' ], ); $first->map; -sleep 0.25; +wait_for_map $x; ##################################################################### # Open a second dock client @@ -48,11 +48,12 @@ my $second = $x->root->create_child( rect => [ 0, 0, 30, 30], background_color => '#FF0000', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + event_mask => [ 'structure_notify' ], ); $second->map; -sleep 0.25; +wait_for_map $x; ##################################################################### # Kill the second dock client diff --git a/testcases/t/55-floating-split-size.t b/testcases/t/55-floating-split-size.t index ecffbb12..0ba5cdc4 100644 --- a/testcases/t/55-floating-split-size.t +++ b/testcases/t/55-floating-split-size.t @@ -12,7 +12,6 @@ BEGIN { } my $x = X11::XCB::Connection->new; -my $i3 = i3(get_socket_path()); my $tmp = fresh_workspace; @@ -24,11 +23,12 @@ my $first = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 200, 80], background_color => '#FF0000', + event_mask => [ 'structure_notify' ], ); $first->map; -sleep 0.25; +wait_for_map $x; ##################################################################### # Open a second window with 300x90 @@ -38,11 +38,12 @@ my $second = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 300, 90], background_color => '#00FF00', + event_mask => [ 'structure_notify' ], ); $second->map; -sleep 0.25; +wait_for_map $x; ##################################################################### # Set the parent to floating diff --git a/testcases/t/56-fullscreen-focus.t b/testcases/t/56-fullscreen-focus.t index 89af6219..2eb35a6b 100644 --- a/testcases/t/56-fullscreen-focus.t +++ b/testcases/t/56-fullscreen-focus.t @@ -44,7 +44,19 @@ cmd 'fullscreen'; # Open a third window ##################################################################### -my $third = open_standard_window($x, '#0000ff'); +#my $third = open_standard_window($x, '#0000ff'); + +my $third = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30 ), + background_color => '#0000ff', + event_mask => [ 'structure_notify' ], +); + +$third->name('Third window'); +$third->map; + +sync_with_i3 $x; diag("third = " . $third->id); diff --git a/testcases/t/64-kill-win-vs-client.t b/testcases/t/64-kill-win-vs-client.t index 2e0669ba..89077500 100644 --- a/testcases/t/64-kill-win-vs-client.t +++ b/testcases/t/64-kill-win-vs-client.t @@ -4,8 +4,6 @@ # Tests if WM_STATE is WM_STATE_NORMAL when mapped and WM_STATE_WITHDRAWN when # unmapped. # -use X11::XCB qw(:all); -use X11::XCB::Connection; use i3test; my $x = X11::XCB::Connection->new; @@ -18,6 +16,8 @@ sub two_windows { my $first = open_standard_window($x); my $second = open_standard_window($x); + sync_with_i3 $x; + is($x->input_focus, $second->id, 'second window focused'); ok(@{get_ws_content($tmp)} == 2, 'two containers opened'); diff --git a/testcases/t/65-for_window.t b/testcases/t/65-for_window.t index 1746d117..0dbe99c3 100644 --- a/testcases/t/65-for_window.t +++ b/testcases/t/65-for_window.t @@ -32,18 +32,19 @@ my $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#00ff00', + event_mask => [ 'structure_notify' ], ); $window->name('Border window'); $window->map; -sleep 0.25; +wait_for_map $x; my @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'normal', 'normal border'); $window->unmap; -sleep 0.25; +wait_for_unmap $x; my @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 0, 'no more nodes'); @@ -53,6 +54,7 @@ $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#00ff00', + event_mask => [ 'structure_notify' ], ); $window->_create; @@ -79,14 +81,14 @@ sub set_wm_class { set_wm_class($window->id, 'borderless', 'borderless'); $window->name('Borderless window'); $window->map; -sleep 0.25; +wait_for_map $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'none', 'no border'); $window->unmap; -sleep 0.25; +wait_for_unmap $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 0, 'no more nodes'); @@ -113,24 +115,25 @@ $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#00ff00', + event_mask => [ 'structure_notify' ], ); $window->name('special title'); $window->map; -sleep 0.25; +wait_for_map $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'normal', 'normal border'); $window->name('special borderless title'); -sleep 0.25; +sync_with_i3 $x; @content = @{get_ws_content($tmp)}; is($content[0]->{border}, 'none', 'no border'); $window->name('special title'); -sleep 0.25; +sync_with_i3 $x; cmd 'border normal'; @@ -138,13 +141,13 @@ cmd 'border normal'; is($content[0]->{border}, 'normal', 'border reset to normal'); $window->name('special borderless title'); -sleep 0.25; +sync_with_i3 $x; @content = @{get_ws_content($tmp)}; is($content[0]->{border}, 'normal', 'still normal border'); $window->unmap; -sleep 0.25; +wait_for_unmap $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 0, 'no more nodes'); @@ -172,11 +175,12 @@ $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#00ff00', + event_mask => [ 'structure_notify' ], ); $window->name('special mark title'); $window->map; -sleep 0.25; +wait_for_map $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 1, 'one node on this workspace now'); @@ -215,6 +219,7 @@ $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#00ff00', + event_mask => [ 'structure_notify' ], ); $window->_create; @@ -222,14 +227,14 @@ $window->_create; set_wm_class($window->id, 'borderless', 'borderless'); $window->name('usethis'); $window->map; -sleep 0.25; +wait_for_map $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'none', 'no border'); $window->unmap; -sleep 0.25; +wait_for_unmap $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 0, 'no nodes on this workspace now'); @@ -237,7 +242,7 @@ cmp_ok(@content, '==', 0, 'no nodes on this workspace now'); set_wm_class($window->id, 'borderless', 'borderless'); $window->name('notthis'); $window->map; -sleep 0.25; +wait_for_map $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 1, 'one node on this workspace now'); @@ -264,6 +269,7 @@ $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#00ff00', + event_mask => [ 'structure_notify' ], ); $window->_create; @@ -271,7 +277,7 @@ $window->_create; set_wm_class($window->id, 'bar', 'foo'); $window->name('usethis'); $window->map; -sleep 0.25; +wait_for_map $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 1, 'one node on this workspace now'); @@ -298,6 +304,7 @@ $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#00ff00', + event_mask => [ 'structure_notify' ], ); $window->_create; @@ -305,7 +312,7 @@ $window->_create; set_wm_class($window->id, 'bar', 'foo'); $window->name('usethis'); $window->map; -sleep 0.25; +wait_for_map $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 1, 'one node on this workspace now'); @@ -334,6 +341,7 @@ $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#00ff00', + event_mask => [ 'structure_notify' ], ); $window->_create; @@ -341,7 +349,7 @@ $window->_create; set_wm_class($window->id, 'bar', 'foo'); $window->name('usethis'); $window->map; -sleep 0.25; +wait_for_map $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 1, 'one node on this workspace now'); @@ -370,6 +378,7 @@ $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#00ff00', + event_mask => [ 'structure_notify' ], ); $window->_create; @@ -388,7 +397,7 @@ $x->change_property( $window->name('usethis'); $window->map; -sleep 0.25; +wait_for_map $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 1, 'one node on this workspace now'); @@ -418,13 +427,14 @@ $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#00ff00', + event_mask => [ 'structure_notify' ], ); $window->_create; $window->name('usethis'); $window->map; -sleep 0.25; +wait_for_map $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 1, 'one node on this workspace now'); @@ -444,7 +454,7 @@ $x->change_property( $x->flush; -sleep 0.25; +sync_with_i3 $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 1, 'one node on this workspace now'); diff --git a/testcases/t/66-assign.t b/testcases/t/66-assign.t index b8366917..cc41b7af 100644 --- a/testcases/t/66-assign.t +++ b/testcases/t/66-assign.t @@ -50,13 +50,14 @@ my $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#0000ff', + event_mask => [ 'structure_notify' ], ); $window->_create; set_wm_class($window->id, 'special', 'special'); $window->name('special window'); $window->map; -sleep 0.25; +wait_for_map $x; ok(@{get_ws_content($tmp)} == 1, 'special window got managed to current (random) workspace'); @@ -64,8 +65,6 @@ exit_gracefully($process->pid); $window->destroy; -sleep 0.25; - ##################################################################### # start a window and see that it gets assigned to a formerly unused # workspace @@ -89,13 +88,14 @@ my $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#0000ff', + event_mask => [ 'structure_notify' ], ); $window->_create; set_wm_class($window->id, 'special', 'special'); $window->name('special window'); $window->map; -sleep 0.25; +wait_for_map $x; ok(@{get_ws_content($tmp)} == 0, 'still no containers'); ok("targetws" ~~ @{get_workspace_names()}, 'targetws exists'); @@ -128,13 +128,18 @@ my $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#0000ff', + event_mask => [ 'structure_notify' ], ); $window->_create; set_wm_class($window->id, 'special', 'special'); $window->name('special window'); $window->map; -sleep 0.25; + +# We use sync_with_i3 instead of wait_for_map here because i3 will not actually +# map the window -- it will be assigned to a different workspace and will only +# be mapped once you switch to that workspace +sync_with_i3 $x; ok(@{get_ws_content($tmp)} == 0, 'still no containers'); ok(@{get_ws_content('targetws')} == 2, 'two containers on targetws'); @@ -164,13 +169,14 @@ my $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#0000ff', + event_mask => [ 'structure_notify' ], ); $window->_create; set_wm_class($window->id, 'special', 'special'); $window->name('special window'); $window->map; -sleep 0.25; +wait_for_map $x; my $content = get_ws($tmp); ok(@{$content->{nodes}} == 0, 'no tiling cons'); @@ -204,13 +210,14 @@ my $window = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, rect => [ 0, 0, 30, 30 ], background_color => '#0000ff', + event_mask => [ 'structure_notify' ], ); $window->_create; set_wm_class($window->id, 'SPEcial', 'SPEcial'); $window->name('special window'); $window->map; -sleep 0.25; +wait_for_map $x; my $content = get_ws($tmp); ok(@{$content->{nodes}} == 0, 'no tiling cons'); @@ -249,13 +256,14 @@ my $window = $x->root->create_child( rect => [ 0, 0, 30, 30 ], background_color => '#0000ff', window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + event_mask => [ 'structure_notify' ], ); $window->_create; set_wm_class($window->id, 'special', 'special'); $window->name('special window'); $window->map; -sleep 0.25; +wait_for_map $x; my $content = get_ws($tmp); ok(@{$content->{nodes}} == 0, 'no tiling cons'); diff --git a/testcases/t/74-border-config.t b/testcases/t/74-border-config.t index c8a4d73d..d66ef477 100644 --- a/testcases/t/74-border-config.t +++ b/testcases/t/74-border-config.t @@ -6,8 +6,6 @@ # use i3test; -use X11::XCB qw(:all); -use X11::XCB::Connection; my $x = X11::XCB::Connection->new; @@ -84,11 +82,12 @@ $first = $x->root->create_child( background_color => '#C0C0C0', # replace the type with 'utility' as soon as the coercion works again in X11::XCB window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), + event_mask => [ 'structure_notify' ], ); $first->map; -sleep 0.25; +wait_for_map $x; my $wscontent = get_ws($tmp); my @floating = @{$wscontent->{floating_nodes}}; @@ -123,11 +122,12 @@ $first = $x->root->create_child( background_color => '#C0C0C0', # replace the type with 'utility' as soon as the coercion works again in X11::XCB window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), + event_mask => [ 'structure_notify' ], ); $first->map; -sleep 0.25; +wait_for_map $x; $wscontent = get_ws($tmp); @floating = @{$wscontent->{floating_nodes}}; From e244a758015b8385f497701dfa94a91b0740fe99 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Sep 2011 13:09:20 +0100 Subject: [PATCH 078/333] tests: complete_run: directly use X11::XCB instead of ::Connection This saves about 0.5s wallclock time due to not starting up Moose/Mouse. This is worthwhile when you develop a new feature and you are often invoking complete_run for one specific test. --- testcases/complete-run.pl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index 8f740d8c..74bc507f 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -28,7 +28,7 @@ use AnyEvent::I3 qw(:all); use Try::Tiny; use Getopt::Long; use Time::HiRes qw(sleep); -use X11::XCB::Connection; +use X11::XCB; use IO::Socket::UNIX; # core use POSIX; # core use AnyEvent::Handle; @@ -75,13 +75,14 @@ my $result = GetOptions( my @conns; my @wdisplays; for my $display (@displays) { - try { - my $x = X11::XCB::Connection->new(display => $display); + my $screen; + my $x = X11::XCB->new($display, $screen); + if ($x->has_error) { + say STDERR "WARNING: Not using X11 display $display, could not connect"; + } else { push @conns, $x; push @wdisplays, $display; - } catch { - say STDERR "WARNING: Not using X11 display $display, could not connect"; - }; + } } my $config = slurp('i3-test.config'); From 1a438f12ede41fdfc3406e42c360b425a3fabf7b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Sep 2011 13:13:09 +0100 Subject: [PATCH 079/333] tests: complete-run: display time i3 took for starting up --- testcases/complete-run.pl | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index 74bc507f..b356e0d1 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -27,7 +27,7 @@ use File::Basename qw(basename); use AnyEvent::I3 qw(:all); use Try::Tiny; use Getopt::Long; -use Time::HiRes qw(sleep); +use Time::HiRes qw(sleep gettimeofday tv_interval); use X11::XCB; use IO::Socket::UNIX; # core use POSIX; # core @@ -140,6 +140,7 @@ sub take_job { close($fh); my $activate_cv = AnyEvent->condvar; + my $time_before_start = [gettimeofday]; my $start_i3 = sub { # remove the old unix socket unlink("/tmp/nested-$display-activation"); @@ -235,9 +236,14 @@ sub take_job { # This will be called as soon as i3 is running and answered to our # IPC request $activate_cv->cb(sub { - say "cb"; + my $time_activating = [gettimeofday]; + my $start_duration = tv_interval($time_before_start, $time_activating); my ($status) = $activate_cv->recv; - say "complete-run: status = $status"; + if ($dont_start) { + say "[$display] Not starting i3, testcase does that"; + } else { + say "[$display] i3 startup: took " . sprintf("%.2f", $start_duration) . "s, status = $status"; + } say "[$display] Running $test with logfile $logpath"; From c3eb9f6c45b92bf4755ac1696fdf44212e3fc0f4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Sep 2011 13:13:26 +0100 Subject: [PATCH 080/333] tests: complete-run: remove debugging messages --- testcases/complete-run.pl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index b356e0d1..3566d78b 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -159,15 +159,12 @@ sub take_job { if (!defined($pid)) { die "could not fork()"; } - say "pid = $pid"; if ($pid == 0) { - say "child!"; $ENV{LISTEN_PID} = $$; $ENV{LISTEN_FDS} = 1; $ENV{DISPLAY} = $display; $^F = 3; - say "fileno is " . fileno($socket); close($reserved); POSIX::dup2(fileno($socket), 3); @@ -202,7 +199,6 @@ sub take_job { # wait for the reply $hdl->push_read(chunk => 1, => sub { my ($h, $line) = @_; - say "read something from i3"; $activate_cv->send(1); undef $hdl; }); From de5286da594cf2dd7677ee7b0e1da3a2a4991013 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Sep 2011 15:11:37 +0100 Subject: [PATCH 081/333] tests: lib/i3test: Remove open_standard_window, introduce open_window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit open_window has a better API than open_standard_window. It uses named parameters and supplies default values for everything you don’t specify. This way, you can use every feature which X11::XCB::Window supports. --- testcases/t/05-ipc.t | 3 +- testcases/t/06-focus.t | 20 ++--- testcases/t/08-focus-stack.t | 13 +--- testcases/t/10-dock.t | 69 +++++++---------- testcases/t/11-goto.t | 13 +--- testcases/t/12-floating-resize.t | 3 +- testcases/t/13-urgent.t | 4 +- testcases/t/14-client-leader.t | 78 ++++---------------- testcases/t/19-match.t | 2 +- testcases/t/29-focus-after-close.t | 2 +- testcases/t/33-size-hints.t | 8 +- testcases/t/35-floating-focus.t | 40 ++++------ testcases/t/36-floating-ws-empty.t | 2 +- testcases/t/37-floating-unmap.t | 2 +- testcases/t/38-floating-attach.t | 12 +-- testcases/t/39-ws-numbers.t | 12 +-- testcases/t/40-focus-lost.t | 6 +- testcases/t/41-resize.t | 10 +-- testcases/t/45-flattening.t | 6 +- testcases/t/46-floating-reinsert.t | 10 +-- testcases/t/47-regress-floatingmove.t | 6 +- testcases/t/48-regress-floatingmovews.t | 4 +- testcases/t/50-regress-dock-restart.t | 34 +++------ testcases/t/53-floating-originalsize.t | 13 +--- testcases/t/54-regress-multiple-dock.t | 30 ++------ testcases/t/55-floating-split-size.t | 28 ++----- testcases/t/56-fullscreen-focus.t | 18 ++--- testcases/t/57-regress-fullscreen-level-up.t | 3 +- testcases/t/58-wm_take_focus.t | 1 - testcases/t/61-regress-borders-restart.t | 2 +- testcases/t/63-wm-state.t | 14 +--- testcases/t/64-kill-win-vs-client.t | 4 +- testcases/t/65-for_window.t | 2 +- testcases/t/67-workspace_layout.t | 12 +-- testcases/t/68-regress-fullscreen-restart.t | 4 +- testcases/t/70-force_focus_wrapping.t | 12 +-- testcases/t/74-border-config.t | 32 +------- testcases/t/lib/i3test.pm | 59 ++++++++++----- 38 files changed, 207 insertions(+), 386 deletions(-) diff --git a/testcases/t/05-ipc.t b/testcases/t/05-ipc.t index 0d18040e..982ece7e 100644 --- a/testcases/t/05-ipc.t +++ b/testcases/t/05-ipc.t @@ -12,8 +12,7 @@ fresh_workspace; ##################################################################### # Create a window so we can get a focus different from NULL -my $window = open_standard_window($x); -sync_with_i3($x); +my $window = open_window($x); my $focus = $x->input_focus; diff --git a/testcases/t/06-focus.t b/testcases/t/06-focus.t index b3add322..5ded494f 100644 --- a/testcases/t/06-focus.t +++ b/testcases/t/06-focus.t @@ -2,15 +2,9 @@ # vim:ts=4:sw=4:expandtab use i3test; -use X11::XCB qw(:all); - -BEGIN { - use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); -} my $x = X11::XCB::Connection->new; -my $i3 = i3(get_socket_path()); my $tmp = fresh_workspace; ##################################################################### @@ -21,14 +15,9 @@ my $tmp = fresh_workspace; cmd 'layout default'; cmd 'split v'; -my $top = open_standard_window($x); -my $mid = open_standard_window($x); -my $bottom = open_standard_window($x); -##sleep 0.25; - -diag("top id = " . $top->id); -diag("mid id = " . $mid->id); -diag("bottom id = " . $bottom->id); +my $top = open_window($x); +my $mid = open_window($x); +my $bottom = open_window($x); # # Returns the input focus after sending the given command to i3 via IPC @@ -37,7 +26,8 @@ diag("bottom id = " . $bottom->id); sub focus_after { my $msg = shift; - $i3->command($msg)->recv; + cmd $msg; + sync_with_i3 $x; return $x->input_focus; } diff --git a/testcases/t/08-focus-stack.t b/testcases/t/08-focus-stack.t index 33a5884a..b5be284c 100644 --- a/testcases/t/08-focus-stack.t +++ b/testcases/t/08-focus-stack.t @@ -7,28 +7,23 @@ use i3test; my $x = X11::XCB::Connection->new; -my $i3 = i3(get_socket_path()); fresh_workspace; cmd 'split h'; -my $tiled_left = open_standard_window($x); -my $tiled_right = open_standard_window($x); - -sync_with_i3($x); +my $tiled_left = open_window($x); +my $tiled_right = open_window($x); # Get input focus before creating the floating window my $focus = $x->input_focus; # Create a floating window which is smaller than the minimum enforced size of i3 -my $window = open_standard_window($x, undef, 1); -sync_with_i3($x); +my $window = open_floating_window($x); is($x->input_focus, $window->id, 'floating window focused'); $window->unmap; -# TODO: wait for unmap -sync_with_i3($x); +wait_for_unmap($x); is($x->input_focus, $focus, 'Focus correctly restored'); diff --git a/testcases/t/10-dock.t b/testcases/t/10-dock.t index 988d92db..cad54c26 100644 --- a/testcases/t/10-dock.t +++ b/testcases/t/10-dock.t @@ -29,18 +29,9 @@ my $screens = $x->screens; my $primary = first { $_->primary } @{$screens}; # TODO: focus the primary screen before - -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#FF0000', - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), - event_mask => [ 'structure_notify' ], -); - -$window->map; - -wait_for_map $x; +my $window = open_window($x, { + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + }); my $rect = $window->rect; is($rect->width, $primary->rect->width, 'dock client is as wide as the screen'); @@ -91,17 +82,11 @@ is(@docked, 0, 'no more dock clients'); # check if it gets placed on bottom (by coordinates) ##################################################################### -$window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 1000, 30, 30], - background_color => '#FF0000', - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), - event_mask => [ 'structure_notify' ], -); - -$window->map; - -wait_for_map $x; +$window = open_window($x, { + rect => [ 0, 1000, 30, 30 ], + background_color => '#FF0000', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + }); my $rect = $window->rect; is($rect->width, $primary->rect->width, 'dock client is as wide as the screen'); @@ -121,13 +106,12 @@ is(@docked, 0, 'no more dock clients'); # check if it gets placed on bottom (by hint) ##################################################################### -$window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 1000, 30, 30], - background_color => '#FF0000', - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), - event_mask => [ 'structure_notify' ], -); +$window = open_window($x, { + dont_map => 1, + rect => [ 0, 1000, 30, 30 ], + background_color => '#FF0000', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + }); $window->_create(); @@ -159,13 +143,12 @@ wait_for_unmap $x; @docked = get_dock_clients(); is(@docked, 0, 'no more dock clients'); -$window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 1000, 30, 30], - background_color => '#FF0000', - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), - event_mask => [ 'structure_notify' ], -); +$window = open_window($x, { + dont_map => 1, + rect => [ 0, 1000, 30, 30 ], + background_color => '#FF0000', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + }); $window->_create(); @@ -197,13 +180,11 @@ $window->destroy; # regression test: transient dock client ##################################################################### -my $fwindow = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#FF0000', - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), - event_mask => [ 'structure_notify' ], -); +$fwindow = open_window($x, { + dont_map => 1, + background_color => '#FF0000', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + }); $fwindow->transient_for($window); $fwindow->map; diff --git a/testcases/t/11-goto.t b/testcases/t/11-goto.t index 44cf55ab..5bec5892 100644 --- a/testcases/t/11-goto.t +++ b/testcases/t/11-goto.t @@ -11,7 +11,6 @@ BEGIN { my $x = X11::XCB::Connection->new; -my $i3 = i3(get_socket_path()); my $tmp = fresh_workspace; cmd 'split h'; @@ -20,13 +19,9 @@ cmd 'split h'; # Create two windows and make sure focus switching works ##################################################################### -my $top = open_standard_window($x); -my $mid = open_standard_window($x); -my $bottom = open_standard_window($x); - -diag("top id = " . $top->id); -diag("mid id = " . $mid->id); -diag("bottom id = " . $bottom->id); +my $top = open_window($x); +my $mid = open_window($x); +my $bottom = open_window($x); # # Returns the input focus after sending the given command to i3 via IPC @@ -55,7 +50,7 @@ my $random_mark = sha1_base64(rand()); $focus = focus_after(qq|[con_mark="$random_mark"] focus|); is($focus, $mid->id, "focus unchanged"); -$i3->command("mark $random_mark")->recv; +cmd "mark $random_mark"; $focus = focus_after('focus left'); is($focus, $top->id, "Top window focused"); diff --git a/testcases/t/12-floating-resize.t b/testcases/t/12-floating-resize.t index 1aec9573..ac3387a9 100644 --- a/testcases/t/12-floating-resize.t +++ b/testcases/t/12-floating-resize.t @@ -16,8 +16,7 @@ fresh_workspace; # Create a floating window and see if resizing works ##################################################################### -# Create a floating window -my $window = open_standard_window($x, undef, 1); +my $window = open_floating_window($x); # See if configurerequests cause window movements (they should not) my ($a, $t) = $window->rect; diff --git a/testcases/t/13-urgent.t b/testcases/t/13-urgent.t index 83c36a98..7954408f 100644 --- a/testcases/t/13-urgent.t +++ b/testcases/t/13-urgent.t @@ -19,8 +19,8 @@ my $tmp = fresh_workspace; cmd 'split v'; -my $top = open_standard_window($x); -my $bottom = open_standard_window($x); +my $top = open_window($x); +my $bottom = open_window($x); my @urgent = grep { $_->{urgent} } @{get_ws_content($tmp)}; is(@urgent, 0, 'no window got the urgent flag'); diff --git a/testcases/t/14-client-leader.t b/testcases/t/14-client-leader.t index 08deabf8..6f7ffce0 100644 --- a/testcases/t/14-client-leader.t +++ b/testcases/t/14-client-leader.t @@ -20,40 +20,15 @@ my $tmp = fresh_workspace; # one of both (depending on your screen resolution) will be positioned wrong. #################################################################################### -my $left = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [0, 0, 30, 30], - background_color => '#FF0000', - event_mask => [ 'structure_notify' ], -); - -$left->name('Left'); -$left->map; - -my $right = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [0, 0, 30, 30], - background_color => '#FF0000', - event_mask => [ 'structure_notify' ], -); - -$right->name('Right'); -$right->map; - -ok(wait_for_map($x), 'left window mapped'); -ok(wait_for_map($x), 'right window mapped'); +my $left = open_window($x, { name => 'Left' }); +my $right = open_window($x, { name => 'Right' }); my ($abs, $rgeom) = $right->rect; -my $child = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30 ], - background_color => '#C0C0C0', - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), - event_mask => [ 'structure_notify' ], -); - -$child->name('Child window'); +my $child = open_floating_window($x, { + dont_map => 1, + name => 'Child window', + }); $child->client_leader($right); $child->map; @@ -63,15 +38,10 @@ my $cgeom; ($abs, $cgeom) = $child->rect; cmp_ok($cgeom->x, '>=', $rgeom->x, 'Child X >= right container X'); -my $child2 = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30 ], - background_color => '#C0C0C0', - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), - event_mask => [ 'structure_notify' ], -); - -$child2->name('Child window 2'); +my $child2 = open_floating_window($x, { + dont_map => 1, + name => 'Child window 2', + }); $child2->client_leader($left); $child2->map; @@ -81,15 +51,7 @@ ok(wait_for_map($x), 'second child window mapped'); cmp_ok(($cgeom->x + $cgeom->width), '<', $rgeom->x, 'child above left window'); # check wm_transient_for - - -my $fwindow = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#FF0000', - event_mask => [ 'structure_notify' ], -); - +my $fwindow = open_window($x, { dont_map => 1 }); $fwindow->transient_for($right); $fwindow->map; @@ -105,14 +67,7 @@ SKIP: { # Create a parent window ##################################################################### -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30 ], - background_color => '#C0C0C0', - event_mask => [ 'structure_notify' ], -); - -$window->name('Parent window'); +my $window = open_window($x, { dont_map => 1, name => 'Parent window' }); $window->map; ok(wait_for_map($x), 'parent window mapped'); @@ -123,14 +78,7 @@ ok(wait_for_map($x), 'parent window mapped'); ######################################################################### fresh_workspace; -my $child = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30 ], - background_color => '#C0C0C0', - event_mask => [ 'structure_notify' ], -); - -$child->name('Child window'); +my $child = open_window($x, { dont_map => 1, name => 'Child window' }); $child->client_leader($window); $child->map; diff --git a/testcases/t/19-match.t b/testcases/t/19-match.t index aab54456..8b9d21d3 100644 --- a/testcases/t/19-match.t +++ b/testcases/t/19-match.t @@ -12,7 +12,7 @@ ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); # Open a new window my $x = X11::XCB::Connection->new; -my $window = open_standard_window($x); +my $window = open_window($x); my $content = get_ws_content($tmp); ok(@{$content} == 1, 'window mapped'); my $win = $content->[0]; diff --git a/testcases/t/29-focus-after-close.t b/testcases/t/29-focus-after-close.t index ac029eb1..8d225613 100644 --- a/testcases/t/29-focus-after-close.t +++ b/testcases/t/29-focus-after-close.t @@ -102,7 +102,7 @@ $first = open_empty_con($i3); $middle = open_empty_con($i3); # XXX: the $right empty con will be filled with the x11 window we are creating afterwards $right = open_empty_con($i3); -my $win = open_standard_window($x, '#00ff00'); +my $win = open_window($x, { background_color => '#00ff00' }); cmd qq|[con_id="$middle"] focus|; $win->destroy; diff --git a/testcases/t/33-size-hints.t b/testcases/t/33-size-hints.t index 0d607dbc..d2d77e8e 100644 --- a/testcases/t/33-size-hints.t +++ b/testcases/t/33-size-hints.t @@ -11,13 +11,7 @@ my $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); -my $win = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30), - background_color => '#C0C0C0', - event_mask => [ 'structure_notify' ], -); - +my $win = open_window($x, { dont_map => 1 }); # XXX: we should check screen size. in screens with an AR of 2.0, # this is not a good idea. my $aspect = X11::XCB::Sizehints::Aspect->new; diff --git a/testcases/t/35-floating-focus.t b/testcases/t/35-floating-focus.t index fc94c440..4c5b562f 100644 --- a/testcases/t/35-floating-focus.t +++ b/testcases/t/35-floating-focus.t @@ -13,10 +13,8 @@ my $tmp = fresh_workspace; # 1: see if focus stays the same when toggling tiling/floating mode ############################################################################# -my $first = open_standard_window($x); -my $second = open_standard_window($x); - -sync_with_i3($x); +my $first = open_window($x); +my $second = open_window($x); is($x->input_focus, $second->id, 'second window focused'); @@ -32,11 +30,9 @@ is($x->input_focus, $second->id, 'second window still focused after mode toggle' $tmp = fresh_workspace; -$first = open_standard_window($x); # window 2 -$second = open_standard_window($x); # window 3 -my $third = open_standard_window($x); # window 4 - -sync_with_i3($x); +$first = open_window($x); # window 2 +$second = open_window($x); # window 3 +my $third = open_window($x); # window 4 is($x->input_focus, $third->id, 'last container focused'); @@ -66,11 +62,9 @@ is($x->input_focus, $second->id, 'second con still focused after killing third') $tmp = fresh_workspace; -$first = open_standard_window($x, '#ff0000'); # window 5 -$second = open_standard_window($x, '#00ff00'); # window 6 -my $third = open_standard_window($x, '#0000ff'); # window 7 - -sync_with_i3($x); +$first = open_window($x, '#ff0000'); # window 5 +$second = open_window($x, '#00ff00'); # window 6 +my $third = open_window($x, '#0000ff'); # window 7 is($x->input_focus, $third->id, 'last container focused'); @@ -105,13 +99,11 @@ is($x->input_focus, $first->id, 'first con focused after killing all floating co $tmp = fresh_workspace; -$first = open_standard_window($x, '#ff0000'); # window 5 +$first = open_window($x, { background_color => '#ff0000' }); # window 5 cmd 'split v'; cmd 'layout stacked'; -$second = open_standard_window($x, '#00ff00'); # window 6 -$third = open_standard_window($x, '#0000ff'); # window 7 - -sync_with_i3($x); +$second = open_window($x, { background_color => '#00ff00' }); # window 6 +$third = open_window($x, { background_color => '#0000ff' }); # window 7 is($x->input_focus, $third->id, 'last container focused'); @@ -148,8 +140,8 @@ is($x->input_focus, $first->id, 'first con focused after killing all floating co $tmp = fresh_workspace; -$first = open_standard_window($x, '#ff0000'); # window 8 -$second = open_standard_window($x, '#00ff00'); # window 9 +$first = open_window($x, { background_color => '#ff0000' }); # window 8 +$second = open_window($x, { background_color => '#00ff00' }); # window 9 sync_with_i3($x); @@ -195,9 +187,9 @@ is($x->input_focus, $second->id, 'second (floating) container focused'); $tmp = fresh_workspace; -$first = open_standard_window($x, '#ff0000', 1); # window 10 -$second = open_standard_window($x, '#00ff00', 1); # window 11 -$third = open_standard_window($x, '#0000ff', 1); # window 12 +$first = open_floating_window($x, { background_color => '#ff0000' });# window 10 +$second = open_floating_window($x, { background_color => '#00ff00' }); # window 11 +$third = open_floating_window($x, { background_color => '#0000ff' }); # window 12 sync_with_i3($x); diff --git a/testcases/t/36-floating-ws-empty.t b/testcases/t/36-floating-ws-empty.t index 91467ed3..a6e0e405 100644 --- a/testcases/t/36-floating-ws-empty.t +++ b/testcases/t/36-floating-ws-empty.t @@ -22,7 +22,7 @@ ok(workspace_exists($tmp), "workspace $tmp exists"); my $x = X11::XCB::Connection->new; # Create a floating window which is smaller than the minimum enforced size of i3 -my $window = open_standard_window($x, undef, 1); +my $window = open_floating_window($x); ok($window->mapped, 'Window is mapped'); # switch to a different workspace, see if the window is still mapped? diff --git a/testcases/t/37-floating-unmap.t b/testcases/t/37-floating-unmap.t index b3be1348..ab1a33d3 100644 --- a/testcases/t/37-floating-unmap.t +++ b/testcases/t/37-floating-unmap.t @@ -21,7 +21,7 @@ my $tmp = fresh_workspace; my $x = X11::XCB::Connection->new; # Create a floating window which is smaller than the minimum enforced size of i3 -my $window = open_standard_window($x, undef, 1); +my $window = open_floating_window($x); ok($window->mapped, 'Window is mapped'); # switch to a different workspace, see if the window is still mapped? diff --git a/testcases/t/38-floating-attach.t b/testcases/t/38-floating-attach.t index 411ffa8e..b08190a2 100644 --- a/testcases/t/38-floating-attach.t +++ b/testcases/t/38-floating-attach.t @@ -21,7 +21,7 @@ my $tmp = fresh_workspace; my $x = X11::XCB::Connection->new; # Create a floating window -my $window = open_standard_window($x, undef, 1); +my $window = open_floating_window($x); ok($window->mapped, 'Window is mapped'); my $ws = get_ws($tmp); @@ -31,7 +31,7 @@ is(@{$ws->{floating_nodes}}, 1, 'one floating node'); is(@{$nodes}, 0, 'no tiling nodes'); # Create a tiling window -my $twindow = open_standard_window($x); +my $twindow = open_window($x); ($nodes, $focus) = get_ws_content($tmp); @@ -44,8 +44,8 @@ is(@{$nodes}, 1, 'one tiling node'); $tmp = fresh_workspace; -my $first = open_standard_window($x); -my $second = open_standard_window($x); +my $first = open_window($x); +my $second = open_window($x); cmd 'layout stacked'; @@ -54,14 +54,14 @@ is(@{$ws->{floating_nodes}}, 0, 'no floating nodes so far'); is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)'); # Create a floating window -my $window = open_standard_window($x, undef, 1); +my $window = open_floating_window($x); ok($window->mapped, 'Window is mapped'); $ws = get_ws($tmp); is(@{$ws->{floating_nodes}}, 1, 'one floating nodes'); is(@{$ws->{nodes}}, 1, 'one tiling node (stacked con)'); -my $third = open_standard_window($x); +my $third = open_window($x); $ws = get_ws($tmp); diff --git a/testcases/t/39-ws-numbers.t b/testcases/t/39-ws-numbers.t index 3afd281b..31f013ef 100644 --- a/testcases/t/39-ws-numbers.t +++ b/testcases/t/39-ws-numbers.t @@ -30,7 +30,7 @@ check_order('workspace order alright before testing'); cmd "workspace 93"; -open_standard_window($x); +open_window($x); my @ws = @{$i3->get_workspaces->recv}; my @f = grep { defined($_->{num}) && $_->{num} == 93 } @ws; @@ -38,23 +38,23 @@ is(@f, 1, 'ws 93 found by num'); check_order('workspace order alright after opening 93'); cmd "workspace 92"; -open_standard_window($x); +open_window($x); check_order('workspace order alright after opening 92'); cmd "workspace 94"; -open_standard_window($x); +open_window($x); check_order('workspace order alright after opening 94'); cmd "workspace 96"; -open_standard_window($x); +open_window($x); check_order('workspace order alright after opening 96'); cmd "workspace foo"; -open_standard_window($x); +open_window($x); check_order('workspace order alright after opening foo'); cmd "workspace 91"; -open_standard_window($x); +open_window($x); check_order('workspace order alright after opening 91'); done_testing; diff --git a/testcases/t/40-focus-lost.t b/testcases/t/40-focus-lost.t index 1121f124..fb77f01e 100644 --- a/testcases/t/40-focus-lost.t +++ b/testcases/t/40-focus-lost.t @@ -24,9 +24,9 @@ sub check_order { my $tmp = fresh_workspace; -my $left = open_standard_window($x); -my $mid = open_standard_window($x); -my $right = open_standard_window($x); +my $left = open_window($x); +my $mid = open_window($x); +my $right = open_window($x); sync_with_i3($x); diff --git a/testcases/t/41-resize.t b/testcases/t/41-resize.t index 2a3846d8..8691a044 100644 --- a/testcases/t/41-resize.t +++ b/testcases/t/41-resize.t @@ -14,8 +14,8 @@ my $tmp = fresh_workspace; cmd 'split v'; -my $top = open_standard_window($x); -my $bottom = open_standard_window($x); +my $top = open_window($x); +my $bottom = open_window($x); sync_with_i3($x); @@ -54,8 +54,8 @@ $tmp = fresh_workspace; cmd 'split v'; -$top = open_standard_window($x); -$bottom = open_standard_window($x); +$top = open_window($x); +$bottom = open_window($x); cmd 'split h'; cmd 'layout stacked'; @@ -76,7 +76,7 @@ is($nodes->[1]->{percent}, 0.75, 'bottom window got 75%'); $tmp = fresh_workspace; -$top = open_standard_window($x); +$top = open_window($x); cmd 'floating enable'; diff --git a/testcases/t/45-flattening.t b/testcases/t/45-flattening.t index 4a57f211..904252e7 100644 --- a/testcases/t/45-flattening.t +++ b/testcases/t/45-flattening.t @@ -17,9 +17,9 @@ my $x = X11::XCB::Connection->new; my $tmp = fresh_workspace; -my $left = open_standard_window($x); -my $mid = open_standard_window($x); -my $right = open_standard_window($x); +my $left = open_window($x); +my $mid = open_window($x); +my $right = open_window($x); cmd 'move before v'; cmd 'move after h'; diff --git a/testcases/t/46-floating-reinsert.t b/testcases/t/46-floating-reinsert.t index 07f78956..bc1302bb 100644 --- a/testcases/t/46-floating-reinsert.t +++ b/testcases/t/46-floating-reinsert.t @@ -12,11 +12,11 @@ my $x = X11::XCB::Connection->new; my $tmp = fresh_workspace; -my $left = open_standard_window($x); -my $mid = open_standard_window($x); +my $left = open_window($x); +my $mid = open_window($x); cmd 'split v'; -my $bottom = open_standard_window($x); +my $bottom = open_window($x); my ($nodes, $focus) = get_ws_content($tmp); @@ -24,10 +24,8 @@ my ($nodes, $focus) = get_ws_content($tmp); # 1: open a floating window, get it mapped ############################################################################# -my $x = X11::XCB::Connection->new; - # Create a floating window -my $window = open_standard_window($x, undef, 1); +my $window = open_floating_window($x); ok($window->mapped, 'Window is mapped'); ($nodes, $focus) = get_ws_content($tmp); diff --git a/testcases/t/47-regress-floatingmove.t b/testcases/t/47-regress-floatingmove.t index 8425f6d9..771ace32 100644 --- a/testcases/t/47-regress-floatingmove.t +++ b/testcases/t/47-regress-floatingmove.t @@ -16,9 +16,9 @@ my $x = X11::XCB::Connection->new; my $tmp = fresh_workspace; -my $left = open_standard_window($x); -my $mid = open_standard_window($x); -my $right = open_standard_window($x); +my $left = open_window($x); +my $mid = open_window($x); +my $right = open_window($x); # go to workspace level cmd 'level up'; diff --git a/testcases/t/48-regress-floatingmovews.t b/testcases/t/48-regress-floatingmovews.t index 355e697a..6f9dfc40 100644 --- a/testcases/t/48-regress-floatingmovews.t +++ b/testcases/t/48-regress-floatingmovews.t @@ -16,13 +16,13 @@ my $x = X11::XCB::Connection->new; my $tmp = fresh_workspace; # open a tiling window on the first workspace -open_standard_window($x); +open_window($x); #sleep 0.25; my $first = get_focused($tmp); # on a different ws, open a floating window my $otmp = fresh_workspace; -open_standard_window($x); +open_window($x); #sleep 0.25; my $float = get_focused($otmp); cmd 'mode toggle'; diff --git a/testcases/t/50-regress-dock-restart.t b/testcases/t/50-regress-dock-restart.t index caadb9e3..e294e6bd 100644 --- a/testcases/t/50-regress-dock-restart.t +++ b/testcases/t/50-regress-dock-restart.t @@ -11,7 +11,6 @@ BEGIN { } my $x = X11::XCB::Connection->new; -my $i3 = i3(get_socket_path()); my $tmp = fresh_workspace; @@ -26,17 +25,10 @@ is(@docked, 0, 'no dock clients yet'); # open a dock client -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#FF0000', - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), - event_mask => [ 'structure_notify' ], -); - -$window->map; - -wait_for_map $x; +my $window = open_window($x, { + background_color => '#FF0000', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + }); ##################################################################### # check that we can find it in the layout tree at the expected position @@ -79,18 +71,12 @@ is(@docked, 0, 'no dock clients found'); # create a dock client with a 1px border ##################################################################### -$window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - border => 1, - rect => [ 0, 0, 30, 20], - background_color => '#00FF00', - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), - event_mask => [ 'structure_notify' ], -); - -$window->map; - -wait_for_map $x; +$window = open_window($x, { + border => 1, + rect => [ 0, 0, 30, 20 ], + background_color => '#00FF00', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + }); @docked = get_dock_clients; is(@docked, 1, 'one dock client found'); diff --git a/testcases/t/53-floating-originalsize.t b/testcases/t/53-floating-originalsize.t index b1dc6199..db0b6e9c 100644 --- a/testcases/t/53-floating-originalsize.t +++ b/testcases/t/53-floating-originalsize.t @@ -11,18 +11,7 @@ my $tmp = fresh_workspace; my $x = X11::XCB::Connection->new; # Create a floating window which is smaller than the minimum enforced size of i3 -my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 400, 150], - background_color => '#C0C0C0', - event_mask => [ 'structure_notify' ], -); - -isa_ok($window, 'X11::XCB::Window'); - -$window->map; - -wait_for_map $x; +my $window = open_window($x, { rect => [ 0, 0, 400, 150 ] }); my ($absolute, $top) = $window->rect; diff --git a/testcases/t/54-regress-multiple-dock.t b/testcases/t/54-regress-multiple-dock.t index b1809493..21cb9696 100644 --- a/testcases/t/54-regress-multiple-dock.t +++ b/testcases/t/54-regress-multiple-dock.t @@ -27,33 +27,19 @@ is(@docked, 0, 'no dock clients yet'); # open a dock client ##################################################################### -my $first = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#FF0000', - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), - event_mask => [ 'structure_notify' ], -); - -$first->map; - -wait_for_map $x; +my $first = open_window($x, { + background_color => '#FF0000', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + }); ##################################################################### # Open a second dock client ##################################################################### -my $second = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#FF0000', - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), - event_mask => [ 'structure_notify' ], -); - -$second->map; - -wait_for_map $x; +my $second = open_window($x, { + background_color => '#FF0000', + window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), + }); ##################################################################### # Kill the second dock client diff --git a/testcases/t/55-floating-split-size.t b/testcases/t/55-floating-split-size.t index 0ba5cdc4..5de05e8b 100644 --- a/testcases/t/55-floating-split-size.t +++ b/testcases/t/55-floating-split-size.t @@ -19,31 +19,19 @@ my $tmp = fresh_workspace; # open a window with 200x80 ##################################################################### -my $first = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 200, 80], - background_color => '#FF0000', - event_mask => [ 'structure_notify' ], -); - -$first->map; - -wait_for_map $x; +my $first = open_window($x, { + rect => [ 0, 0, 200, 80], + background_color => '#FF0000', + }); ##################################################################### # Open a second window with 300x90 ##################################################################### -my $second = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 300, 90], - background_color => '#00FF00', - event_mask => [ 'structure_notify' ], -); - -$second->map; - -wait_for_map $x; +my $second = open_window($x, { + rect => [ 0, 0, 300, 90], + background_color => '#00FF00', + }); ##################################################################### # Set the parent to floating diff --git a/testcases/t/56-fullscreen-focus.t b/testcases/t/56-fullscreen-focus.t index 2eb35a6b..a559b5a5 100644 --- a/testcases/t/56-fullscreen-focus.t +++ b/testcases/t/56-fullscreen-focus.t @@ -20,7 +20,7 @@ my $tmp = fresh_workspace; # open the left window ##################################################################### -my $left = open_standard_window($x, '#ff0000'); +my $left = open_window($x, { background_color => '#ff0000' }); is($x->input_focus, $left->id, 'left window focused'); @@ -30,7 +30,7 @@ diag("left = " . $left->id); # Open the right window ##################################################################### -my $right = open_standard_window($x, '#00ff00'); +my $right = open_window($x, { background_color => '#00ff00' }); diag("right = " . $right->id); @@ -44,16 +44,12 @@ cmd 'fullscreen'; # Open a third window ##################################################################### -#my $third = open_standard_window($x, '#0000ff'); +my $third = open_window($x, { + background_color => '#0000ff', + name => 'Third window', + dont_map => 1, + }); -my $third = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30 ), - background_color => '#0000ff', - event_mask => [ 'structure_notify' ], -); - -$third->name('Third window'); $third->map; sync_with_i3 $x; diff --git a/testcases/t/57-regress-fullscreen-level-up.t b/testcases/t/57-regress-fullscreen-level-up.t index 3e0b2fe1..7a101dbc 100644 --- a/testcases/t/57-regress-fullscreen-level-up.t +++ b/testcases/t/57-regress-fullscreen-level-up.t @@ -11,7 +11,6 @@ BEGIN { } my $x = X11::XCB::Connection->new; -my $i3 = i3(get_socket_path()); my $tmp = fresh_workspace; @@ -19,7 +18,7 @@ my $tmp = fresh_workspace; # open a window, verify it’s not in fullscreen mode ##################################################################### -my $win = open_standard_window($x); +my $win = open_window($x); my $nodes = get_ws_content $tmp; is(@$nodes, 1, 'exactly one client'); diff --git a/testcases/t/58-wm_take_focus.t b/testcases/t/58-wm_take_focus.t index 04c785ae..372f41cd 100644 --- a/testcases/t/58-wm_take_focus.t +++ b/testcases/t/58-wm_take_focus.t @@ -17,7 +17,6 @@ BEGIN { } my $x = X11::XCB::Connection->new; -my $i3 = i3(get_socket_path()); subtest 'Window without WM_TAKE_FOCUS', sub { diff --git a/testcases/t/61-regress-borders-restart.t b/testcases/t/61-regress-borders-restart.t index 1acf6c66..c5e3ef80 100644 --- a/testcases/t/61-regress-borders-restart.t +++ b/testcases/t/61-regress-borders-restart.t @@ -12,7 +12,7 @@ use i3test; my $x = X11::XCB::Connection->new; my $i3 = i3(get_socket_path()); my $tmp = fresh_workspace; -my $window = open_standard_window($x); +my $window = open_window($x); sub get_border_style { my @content = @{get_ws_content($tmp)}; diff --git a/testcases/t/63-wm-state.t b/testcases/t/63-wm-state.t index b9161400..e55d8682 100644 --- a/testcases/t/63-wm-state.t +++ b/testcases/t/63-wm-state.t @@ -7,27 +7,17 @@ use X11::XCB qw(:all); use i3test; -BEGIN { - use_ok('X11::XCB::Window'); - use_ok('X11::XCB::Event::Generic'); - use_ok('X11::XCB::Event::MapNotify'); - use_ok('X11::XCB::Event::ClientMessage'); -} - my $x = X11::XCB::Connection->new; -my $window = open_standard_window($x); +my $window = open_window($x); sync_with_i3($x); -diag('window mapped'); - is($window->state, ICCCM_WM_STATE_NORMAL, 'WM_STATE normal'); $window->unmap; -# TODO: wait for unmapnotify -sync_with_i3($x); +wait_for_unmap $x; is($window->state, ICCCM_WM_STATE_WITHDRAWN, 'WM_STATE withdrawn'); diff --git a/testcases/t/64-kill-win-vs-client.t b/testcases/t/64-kill-win-vs-client.t index 89077500..ef45a789 100644 --- a/testcases/t/64-kill-win-vs-client.t +++ b/testcases/t/64-kill-win-vs-client.t @@ -13,8 +13,8 @@ sub two_windows { ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); - my $first = open_standard_window($x); - my $second = open_standard_window($x); + my $first = open_window($x); + my $second = open_window($x); sync_with_i3 $x; diff --git a/testcases/t/65-for_window.t b/testcases/t/65-for_window.t index 0dbe99c3..36a20ea9 100644 --- a/testcases/t/65-for_window.t +++ b/testcases/t/65-for_window.t @@ -186,7 +186,7 @@ wait_for_map $x; cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'none', 'no border'); -my $other = open_standard_window($x); +my $other = open_window($x); @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 2, 'two nodes'); diff --git a/testcases/t/67-workspace_layout.t b/testcases/t/67-workspace_layout.t index 0e07ebf7..6ff3f15b 100644 --- a/testcases/t/67-workspace_layout.t +++ b/testcases/t/67-workspace_layout.t @@ -27,8 +27,8 @@ my $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); -my $first = open_standard_window($x); -my $second = open_standard_window($x); +my $first = open_window($x); +my $second = open_window($x); sync_with_i3($x); @@ -56,8 +56,8 @@ $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); -$first = open_standard_window($x); -$second = open_standard_window($x); +$first = open_window($x); +$second = open_window($x); sync_with_i3($x); @@ -72,8 +72,8 @@ is($content[0]->{layout}, 'stacked', 'layout stacked'); ##################################################################### cmd 'focus parent'; -my $right_top = open_standard_window($x); -my $right_bot = open_standard_window($x); +my $right_top = open_window($x); +my $right_bot = open_window($x); @content = @{get_ws_content($tmp)}; is(@content, 2, 'two cons at workspace level after focus parent'); diff --git a/testcases/t/68-regress-fullscreen-restart.t b/testcases/t/68-regress-fullscreen-restart.t index fcc9ac7e..1418b402 100644 --- a/testcases/t/68-regress-fullscreen-restart.t +++ b/testcases/t/68-regress-fullscreen-restart.t @@ -11,8 +11,8 @@ my $x = X11::XCB::Connection->new; fresh_workspace; -open_standard_window($x); -open_standard_window($x); +open_window($x); +open_window($x); cmd 'layout stacking'; sleep 1; diff --git a/testcases/t/70-force_focus_wrapping.t b/testcases/t/70-force_focus_wrapping.t index cf1c3216..2aa5407d 100644 --- a/testcases/t/70-force_focus_wrapping.t +++ b/testcases/t/70-force_focus_wrapping.t @@ -25,13 +25,13 @@ my $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); -my $first = open_standard_window($x); -my $second = open_standard_window($x); +my $first = open_window($x); +my $second = open_window($x); cmd 'layout tabbed'; cmd 'focus parent'; -my $third = open_standard_window($x); +my $third = open_window($x); is($x->input_focus, $third->id, 'third window focused'); cmd 'focus left'; @@ -66,13 +66,13 @@ $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); -$first = open_standard_window($x); -$second = open_standard_window($x); +$first = open_window($x); +$second = open_window($x); cmd 'layout tabbed'; cmd 'focus parent'; -$third = open_standard_window($x); +$third = open_window($x); sync_with_i3($x); diff --git a/testcases/t/74-border-config.t b/testcases/t/74-border-config.t index d66ef477..617e37b9 100644 --- a/testcases/t/74-border-config.t +++ b/testcases/t/74-border-config.t @@ -25,7 +25,7 @@ my $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); -my $first = open_standard_window($x); +my $first = open_window($x); my @content = @{get_ws_content($tmp)}; ok(@content == 1, 'one container opened'); @@ -51,7 +51,7 @@ $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); -$first = open_standard_window($x); +$first = open_window($x); @content = @{get_ws_content($tmp)}; ok(@content == 1, 'one container opened'); @@ -75,19 +75,7 @@ $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); -# Create a floating window which is smaller than the minimum enforced size of i3 -$first = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#C0C0C0', - # replace the type with 'utility' as soon as the coercion works again in X11::XCB - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), - event_mask => [ 'structure_notify' ], -); - -$first->map; - -wait_for_map $x; +$first = open_floating_window($x); my $wscontent = get_ws($tmp); my @floating = @{$wscontent->{floating_nodes}}; @@ -115,19 +103,7 @@ $tmp = fresh_workspace; ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); -# Create a floating window which is smaller than the minimum enforced size of i3 -$first = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30], - background_color => '#C0C0C0', - # replace the type with 'utility' as soon as the coercion works again in X11::XCB - window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), - event_mask => [ 'structure_notify' ], -); - -$first->map; - -wait_for_map $x; +$first = open_floating_window($x); $wscontent = get_ws($tmp); @floating = @{$wscontent->{floating_nodes}}; diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 40e84044..87cb769d 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -26,7 +26,8 @@ our @EXPORT = qw( get_ws get_focused open_empty_con - open_standard_window + open_window + open_floating_window get_dock_clients cmd sync_with_i3 @@ -124,33 +125,53 @@ sub wait_for_unmap { sync_with_i3($x); } -sub open_standard_window { - my ($x, $color, $floating) = @_; +# +# Opens a new window (see X11::XCB::Window), maps it, waits until it got mapped +# and synchronizes with i3. +# +# set dont_map to a true value to avoid mapping +# +# default values: +# class => WINDOW_CLASS_INPUT_OUTPUT +# rect => [ 0, 0, 30, 30 ] +# background_color => '#c0c0c0' +# event_mask => [ 'structure_notify' ] +# name => 'Window ' +# +sub open_window { + my ($x, $args) = @_; + my %args = ($args ? %$args : ()); - $color ||= '#c0c0c0'; + my $dont_map = delete $args{dont_map}; - # We cannot use a hashref here because create_child expands the arguments into an array - my @args = ( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30 ), - background_color => $color, - event_mask => [ 'structure_notify' ], - ); + $args{class} = WINDOW_CLASS_INPUT_OUTPUT unless exists $args{class}; + $args{rect} = [ 0, 0, 30, 30 ] unless exists $args{rect}; + $args{background_color} = '#c0c0c0' unless exists $args{background_color}; + $args{event_mask} = [ 'structure_notify' ] unless exists $args{event_mask}; + $args{name} = 'Window ' . counter_window() unless exists $args{name}; - if (defined($floating) && $floating) { - @args = (@args, window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY')); - } + my $window = $x->root->create_child(%args); - my $window = $x->root->create_child(@args); + return $window if $dont_map; - $window->name('Window ' . counter_window()); $window->map; - - wait_for_event $x, 0.5, sub { $_[0]->{response_type} == MAP_NOTIFY }; - + wait_for_map($x); + # We sync with i3 here to make sure $x->input_focus is updated. + sync_with_i3($x); return $window; } +# Thin wrapper around open_window which sets window_type to +# _NET_WM_WINDOW_TYPE_UTILITY to make the window floating. +sub open_floating_window { + my ($x, $args) = @_; + my %args = ($args ? %$args : ()); + + $args{window_type} = $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'); + + return open_window($x, \%args); +} + sub open_empty_con { my ($i3) = @_; From 378611c11c2ab113b2bbd61f6b69a1dd7f610002 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Sep 2011 15:38:31 +0100 Subject: [PATCH 082/333] tests: refactor t/58-wm_take_focus to use wait_for_event --- testcases/t/58-wm_take_focus.t | 98 +++++----------------------------- 1 file changed, 14 insertions(+), 84 deletions(-) diff --git a/testcases/t/58-wm_take_focus.t b/testcases/t/58-wm_take_focus.t index 372f41cd..a90ce1c3 100644 --- a/testcases/t/58-wm_take_focus.t +++ b/testcases/t/58-wm_take_focus.t @@ -7,105 +7,35 @@ use X11::XCB qw(:all); use i3test; use v5.10; -BEGIN { - use_ok('EV'); - use_ok('AnyEvent'); - use_ok('X11::XCB::Window'); - use_ok('X11::XCB::Event::Generic'); - use_ok('X11::XCB::Event::MapNotify'); - use_ok('X11::XCB::Event::ClientMessage'); -} - my $x = X11::XCB::Connection->new; subtest 'Window without WM_TAKE_FOCUS', sub { + fresh_workspace; - my $tmp = fresh_workspace; + my $window = open_window($x); - my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30 ], - background_color => '#00ff00', - event_mask => [ 'structure_notify' ], - ); - - $window->name('Window 1'); - $window->map; - - my $cv = AE::cv; - - my $prep = EV::prepare sub { - $x->flush; - }; - - my $check = EV::check sub { - while (defined(my $event = $x->poll_for_event)) { - if ($event->response_type == 161) { - # clientmessage - $cv->send(0); - } - } - }; - - my $w = EV::io $x->get_file_descriptor, EV::READ, sub { - # do nothing, we only need this watcher so that EV picks up the events - }; - - # Trigger timeout after 1 second - my $t = AE::timer 1, 0, sub { - $cv->send(1); - }; - - my $result = $cv->recv; - ok($result, 'cv result'); + ok(!wait_for_event($x, 1, sub { $_[0]->{response_type} == 161 }), 'did not receive ClientMessage'); done_testing; }; subtest 'Window with WM_TAKE_FOCUS', sub { + fresh_workspace; - my $tmp = fresh_workspace; + my $take_focus = $x->atom(name => 'WM_TAKE_FOCUS'); - my $window = $x->root->create_child( - class => WINDOW_CLASS_INPUT_OUTPUT, - rect => [ 0, 0, 30, 30 ], - background_color => '#00ff00', - event_mask => [ 'structure_notify' ], - protocols => [ $x->atom(name => 'WM_TAKE_FOCUS') ], - ); + my $window = open_window($x, { + dont_map => 1, + protocols => [ $take_focus ], + }); - $window->name('Window 1'); $window->map; - my $cv = AE::cv; - - my $prep = EV::prepare sub { - $x->flush; - }; - - my $check = EV::check sub { - while (defined(my $event = $x->poll_for_event)) { - if ($event->response_type == 161) { - $cv->send($event->data); - } - } - }; - - my $w = EV::io $x->get_file_descriptor, EV::READ, sub { - # do nothing, we only need this watcher so that EV picks up the events - }; - - my $t = AE::timer 1, 0, sub { - say "timer!"; - $cv->send(undef); - }; - - my $result = $cv->recv; - ok(defined($result), 'got a ClientMessage'); - if (defined($result)) { - my ($data, $time) = unpack("L2", $result); - is($data, $x->atom(name => 'WM_TAKE_FOCUS')->id, 'first uint32_t contains WM_TAKE_FOCUS atom'); - } + ok(wait_for_event($x, 1, sub { + return 0 unless $_[0]->{response_type} == 161; + my ($data, $time) = unpack("L2", $_[0]->{data}); + return ($data == $take_focus->id); + }), 'got ClientMessage with WM_TAKE_FOCUS atom'); done_testing; }; From 761dac55143a469022f0556017126f96f2bd8c9b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Sep 2011 15:44:42 +0100 Subject: [PATCH 083/333] tests: lib/i3test: Use //= instead of unless exists $args{key} (Thanks mxf) --- testcases/t/lib/i3test.pm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 87cb769d..2685d695 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -144,11 +144,11 @@ sub open_window { my $dont_map = delete $args{dont_map}; - $args{class} = WINDOW_CLASS_INPUT_OUTPUT unless exists $args{class}; - $args{rect} = [ 0, 0, 30, 30 ] unless exists $args{rect}; - $args{background_color} = '#c0c0c0' unless exists $args{background_color}; - $args{event_mask} = [ 'structure_notify' ] unless exists $args{event_mask}; - $args{name} = 'Window ' . counter_window() unless exists $args{name}; + $args{class} //= WINDOW_CLASS_INPUT_OUTPUT; + $args{rect} //= [ 0, 0, 30, 30 ]; + $args{background_color} //= '#c0c0c0'; + $args{event_mask} //= [ 'structure_notify' ]; + $args{name} //= 'Window ' . counter_window(); my $window = $x->root->create_child(%args); From 10a9d2a439f681288d26f13f13ffe1bec2ead409 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Sep 2011 15:56:43 +0100 Subject: [PATCH 084/333] tests: Bugfix: 11-goto.t: use mktemp for generating a random mark, not base64 The base64 string could contain / and + which is treated specially since we implemented PCRE support :) --- testcases/t/11-goto.t | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/testcases/t/11-goto.t b/testcases/t/11-goto.t index 5bec5892..5ddef776 100644 --- a/testcases/t/11-goto.t +++ b/testcases/t/11-goto.t @@ -2,12 +2,7 @@ # vim:ts=4:sw=4:expandtab use i3test; -use X11::XCB qw(:all); -use Digest::SHA1 qw(sha1_base64); - -BEGIN { - use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); -} +use File::Temp; my $x = X11::XCB::Connection->new; @@ -45,7 +40,7 @@ is($focus, $mid->id, "Middle window focused"); # Now goto a mark which does not exist ##################################################################### -my $random_mark = sha1_base64(rand()); +my $random_mark = mktemp('mark.XXXXXX'); $focus = focus_after(qq|[con_mark="$random_mark"] focus|); is($focus, $mid->id, "focus unchanged"); From 1eb011aae12d60c07117a7a6a9b95a35f42eec5b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Sep 2011 16:28:21 +0100 Subject: [PATCH 085/333] tests: make sure to leave no tempfiles behind --- testcases/complete-run.pl | 2 +- testcases/t/59-socketpaths.t | 4 ++-- testcases/t/71-config-migrate.t | 2 +- testcases/t/lib/i3test.pm | 4 ++++ 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index 3566d78b..52038b33 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -134,7 +134,7 @@ sub take_job { my $dont_start = (slurp($test) =~ /# !NO_I3_INSTANCE!/); my $logpath = "$outdir/i3-log-for-" . basename($test); - my ($fh, $tmpfile) = tempfile(); + my ($fh, $tmpfile) = tempfile('i3-run-cfg.XXXXXX', UNLINK => 1); say $fh $config; say $fh "ipc-socket /tmp/nested-$display"; close($fh); diff --git a/testcases/t/59-socketpaths.t b/testcases/t/59-socketpaths.t index 33350927..36c99087 100644 --- a/testcases/t/59-socketpaths.t +++ b/testcases/t/59-socketpaths.t @@ -18,7 +18,7 @@ my $i3_path = abs_path("../i3"); # default case: socket will be created in /tmp/i3-/ipc-socket. ##################################################################### -my ($fh, $tmpfile) = tempfile(); +my ($fh, $tmpfile) = tempfile('/tmp/i3-test-config.XXXXXX', UNLINK => 1); say $fh "# i3 config file (v4)"; say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; close($fh); @@ -67,7 +67,7 @@ my $tmpdir = tempdir(CLEANUP => 1); $socketpath = $tmpdir . "/config.sock"; ok(! -e $socketpath, "$socketpath does not exist yet"); -($fh, $tmpfile) = tempfile(); +($fh, $tmpfile) = tempfile('/tmp/i3-test-config.XXXXXX', UNLINK => 1); say $fh "# i3 config file (v4)"; say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; say $fh "ipc-socket $socketpath"; diff --git a/testcases/t/71-config-migrate.t b/testcases/t/71-config-migrate.t index 6b41f2c1..561538e5 100644 --- a/testcases/t/71-config-migrate.t +++ b/testcases/t/71-config-migrate.t @@ -23,7 +23,7 @@ sub slurp { sub migrate_config { my ($config) = @_; - my ($fh, $tmpfile) = tempfile(); + my ($fh, $tmpfile) = tempfile('/tmp/i3-migrate-cfg.XXXXXX', UNLINK => 1); print $fh $config; close($fh); diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 2685d695..c890693c 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -382,6 +382,10 @@ sub exit_gracefully { if (!$exited) { kill(9, $pid) or die "could not kill i3"; } + + if ($socketpath =~ m,^/tmp/i3-test-socket-,) { + unlink($socketpath); + } } # Gets the socket path from the I3_SOCKET_PATH atom stored on the X11 root window From d174ff16edc462a7fc44cb1372789bb06ddaa543 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Sep 2011 17:20:36 +0100 Subject: [PATCH 086/333] Add docs/testsuite --- docs/Makefile | 5 +- docs/i3-sync-working.dia | Bin 0 -> 2762 bytes docs/i3-sync-working.png | Bin 0 -> 25072 bytes docs/i3-sync.dia | Bin 0 -> 2208 bytes docs/i3-sync.png | Bin 0 -> 17308 bytes docs/testsuite | 446 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 450 insertions(+), 1 deletion(-) create mode 100644 docs/i3-sync-working.dia create mode 100644 docs/i3-sync-working.png create mode 100644 docs/i3-sync.dia create mode 100644 docs/i3-sync.png create mode 100644 docs/testsuite diff --git a/docs/Makefile b/docs/Makefile index 9d70243d..990bba87 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,5 +1,5 @@ -all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html wsbar.html refcard.pdf +all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html wsbar.html refcard.pdf testsuite.html hacking-howto.html: hacking-howto asciidoc -a toc -n $< @@ -10,6 +10,9 @@ debugging.html: debugging userguide.html: userguide asciidoc -a toc -n $< +testsuite.html: testsuite + asciidoc -a toc -n $< + ipc.html: ipc asciidoc -a toc -n $< diff --git a/docs/i3-sync-working.dia b/docs/i3-sync-working.dia new file mode 100644 index 0000000000000000000000000000000000000000..9f1c3bc8cae13895ca87b89239036f7f14c4718b GIT binary patch literal 2762 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 QXOB1k2e*BCJ%PCZ0H|wW!2kdN literal 0 HcmV?d00001 diff --git a/docs/i3-sync-working.png b/docs/i3-sync-working.png new file mode 100644 index 0000000000000000000000000000000000000000..dce44ac5ac4d2efe4d1d1751b499586613eca33f GIT binary patch literal 25072 zcmeFZcU%_3Op|l! zhVGuybMAfbcklV;H}lT-$M>5VLN!!X@2LD5x`YgcE}w9FM(Pt z^tD_MaZ6%vgFv3xbS#N49Fy*VK#3I7(!kM=z7q;KL9MUTn#zo3nw(`D$j+Yyeeb9~ ztm$$-zk-!6%-2|%smp)4f*NHsy8BTn9>`V72^5l(k{A}A9wiTL=$?B%0)c*luky7d zNn}_~UiSPXsDz?cLnD*@1Kd8KKadqSv}8$Kj)QxXRR(DoV;V^wyb22|ZT;YZV}!S@D!iPQ$i9I^gaZPx^3V}lQJUTyU9y=$I|g(zLN0y$ zyUQzegsnm^eFZe{ms_P!rr*P74@ipql%r`BFdtR*LJ})-LtP^ zZHOJh5P+?LH)Jc&@PyW`qpRz?@jS{?xPPPEwp`of%^NtSi`N^m2J@8TJwjJHrp$_> zLr;N^+K&YUH0|%~vk(yzPnKz1gcJ5ta?E@1^bxGh^7lts#AaU$9VeRkb-VPQl3m=3 z%RB0*CsX+vv)Iug-E{ldIm%-CwO9k>0u#0^n%Tr#d(`pi?J8F$2I=*?JZ5-m7!!S= z8{W_({UBO*yspaYwMD1Q=CxT-7-)l2+mOHY4pNt)A`yjiN(q{y3hjU099x)+nIwufbK_ z@X)`G0?i@IIfY_@ov{DUwqo1(n#;5GImq^mW?14!l`fQ>EHbrXyXQ4mzC-Z=6$`|? zGG+WOxntj#R~e|8)jx5-m3g|l16Of79z&tFum3>^b>!0D~;h@S6Fy^_NZU` zx0Wl@&?#`cpE^YEs`j&b%Y6sLUT#q74h^|jTI3}r1LwvlT$J#eYX9p}mVI4;BHQ~hR?d2D zIZ9JhD*6aBD>VLKVX(&i`=yi3*I|=-v81(e^fo)4#KU>waR+O&6qw3GF*hEaBOD$R z5qIz3kEFdBFd{s5>^B7NlEG&R| zpfA@5GpNXG<5sPA-j!pdtztbJs_r)$3XqpJxkmM$t0moEqmU6f9gJ+o3TkEg<7fA4 zt}A8u=8F{(e5_c*HN~aly^Lw;g%$t9Hl)B;@1vddD9pU^%px5Y$ganiUg}qV@(8#F z{TOo(TD!(}rWt)85X~x_ZoyXajRP|w`kd@i`~^}jM#oiZf9NIQzfu^3SJISXC$Yrj-d(9xF~*r?fOO_0Ebv+qqiJ`+TBE>Rho$ctJl2+`#4{@S zScG^^On9$k^jcrJ*-r^ONUqc=_t5iVXn~&l*O%JTLw^Kp$yy7q+N|h1orLc`RABIX zFxIP>sQNtEo5EUJ_5N5}LzE+I{!9C1ZeU5|&u46KgX zLT7_xQkT`y_&9vQA=5S!o8jcp7l_mH89eUH#jME7-~fUGPWmKk0{Lcc?^72}{CpaE zdb|*n9@xpT%P=It3-6bGKLreW7cNQYF>d}0xfo^()4Pf_MAg4q<4ft~f`?Q`j8PVz z#D3kC9z%MLs1?eyG8?W;1HcCa?BNWJ!#TygHX4KfrKMotybqHRP;%l5Qz(7 zj`9)40D{&Y@UPJIzp?du^4vLdeVYaoWEs2g^!Cur-cD}Q^Hr*eqjM*N(By|nyU*T< z(;Zte6qEIv@Zf?BLxxGoyPwu*edCUMq=whcG54K}5;tU#&7#Ui4Z@eNsDEv5!>$z@fa+_qOVbnx z=8HH)PWIYNPY)DX>ADBaeFj1H?92z`zf{_ecN?2>g4NBxwv z`w{6(;*SqiW>U#@Gpg#Z7r)tAl+5iPz7nZ%I^r+;W*~r zQQ!*iy%?bTsDj35WZO4nVW+n z39#;TQ$P<>mc5nkV!&xbJDt~ZQhz9#gWkv7!wcE7xU_FX%Amj->dsMKd~2HAeQ@jK zq}fN{P=^fsYwrQi>BVBDy~l%>afpk|1)uYw;Re1I+gUWUh?~b%%ZZD)^sUohVq&U}#v0EG}!07J<;vNcMT3rfJlp znmj&S>RLf%*T2|yTWir$1bL&V?&$#xD7@^A_RmkjS=17K1s!DAns~Y{$KF-K(-&Cp z-Pjv9Dw;a!>F=ZaE{&e&=0?WeBh{yHzXQU2wcsOMh@gr!Ti4_!Yh2AH`H@?G)P6T$ zsUxb9>x-0hVj~G%i=)MlQO&%cJ6+ibxNx!})!2rg4dbBp)#9Zn@8i`>ztc4Z@m-Q> z6Ssgj&i1H5XF7lN{^-?2fa=m~3ht`VLf-Up0emq^e(&!?H6*b{7?@0(S;kR~GDhMjaG zWdk)?51P_>Ghysjj9w%L=)6-iR&p1lm;R zD_iTG*=OolO77yw!ovZoR=^3zc+tDJKWcQjCa@NMq(&^ z(#o?1=0Po*mXzS`IqX4<=pZc_=j*vzIn+|Te(Jc3-}Cmo!q7mo#m|~#O`gI_5`Q4U zt2(!)WE4X(CDC&dh$ZbiQ$7{Lw+|tZI;rBL4_hH?WOn7n+OI`rB&s$Iyw;9O_zOSf z3=DRI=T=Pxp|&EIC+X!)CiKsq(!cSJ&TtW)-udHe;YYWoc|P2GT%CMYr>iF!HT!!8 zm*$~DF0UnXy}_cKGx6(XosFEEOjY*O#MG(~LelaW-3!*dN%dPli}EzdQ`>Eo0$f&@_qk1+SL;)$lqQv#7@b ztgSvcZ2j9IPKm@5CZr%C+*WmOw-^Fn37&VwJ$dA1nNj5OK_h>mKC9^-64x?!71|$u z-@Z2>;Twnjw>Kc{jn{Yu#)LS68lWSd0ln-!;6$8ZJ$+%1y~R8q@Q|RN|+h`PLkrPnQ>3z}A;^tFQy}O11I_ydzX3eL4F7e~a`tsOinhxT} z%>?#Zx+s;sv@G#Z1uf(CfK{d78WBrAH*9McwRDXQI+oGj9y7K~A_`*9@$Z8gF)p2R z_O>jhv$G6>{IROqe!FI(`d<$EKezFM=zaFc&K!@sAkZrz=nc)Qx)6(KJeBqE>ujbR zpgW#`Fa2%A0E&YdfLWl%d)L`EBg283b;-m4OBB8UuoVBhZzV@@v4WtFKu7ZHWFU~_ zb70#iUl@+ZRNF3gf0^Zm~-pJPHx|ghcw3X1!J1R5zV`=_|qW|2&sQ8Zb zo$36gliu3eS$2MVv6VhY@lv|{FZx7Q~tp3cTSo@iG=G+%f{1ILZh&-9OfPL&bu&b z{Rx-VT0*v3TA|9_1fL>0SB06Eb&JhSOiP!2d9at`{@T|u=lxDH_Hyl;pslvo^J-Tj(PcrM+eE;6Z#9z{7QAtU@Po zZPA;sbo>&O<5Uuw6pG5WZheMtv`1%56t{5Z_o$lDG}o4;4AG=3|U_`!6lBC)`9e@R$j{-pt}>(sAG) zOqr1-;Vi&^z89A#I&CRxm#m9go4BId2xVH-YS%>9D56Sz%f*GociY07cXVS)Eo(BYqy#_6SU+=5y0i(tvLiWX~3!v27u)vU48KB**;q{6JVQu;gDBC>8ZDwwf?#I z(>poiZaHH8(mrdlR-pB?pNKHeT7Uy_oU9UU%NkwESZ zBy05_VQSeN1WT2duau%spzY@}>st{m9W9=N&Nq+GOOul8%!->|?n)q|R*z_2!H#KO zfi{Nz)1u7u;k;5OkFR0hA|j`rz(WyjJ5kr8joJmR|51 z@dObA#BJrh#Abe9(w6~0M)ntjoU5v^H7!5*#fFhM^tiHe+loozfD4gED*BO3o*m@* z;KDFF$Mb3}P^E@Oq!JBLifNoOyP)k(y1vNtU1H11e6DnDye~S%*}b^XBhr3N74V|| zsvCj@L*~x!-LheEZ`t_#aW8drW5ci9XkXaLWLRsniPIycUN3@3zi_FhbCI^mWqm}H zZnwcy?~t<9EFc4P*2PblNQ2y?Og(dPYVp%ue)n#F<$o{&`&md~aGo0jgs^;*@fusO zx4&My?$I@#dn^|o#MMgv{13w3y0Y)q38WOs0wfg1_H!jO=B1(LS@4y;i@s)=g{Q6j zgbqpR>>A(0M#HmeS5w1I>ljxD>-upIzWj?tiD4^Utl&AFmm%nwEP`&u1o*p+X_hdO z(XXy)fgzVK!=n!r`?qQBUnVQ9ELM*DvzKh9Ly@vYem+RZ?t}0 zN3Mi~CqHlVSSNOL_s><6y6U#|J5xI2(9Cp=#2rdJTqv+RSA0vzZ}KDB*B)K1k8Pvl zGCb&qDg{$b6jPI^7P|)x-R>$`v?q+)F8=PO=RBdgzZGvksXM5u7vAg>QvKK z;d=ybHoBaBB)Ux+*O`a*H%f)%cP|sUOcA4B3$+qrlcKi+hZ9~@}% zv_=;h-?mHAJAXb?bLpvR7~>$RU4T!b6cj~=^6$KG{F?w-cSXZ_{L+oC#`yq&CMG1z znoT2XhE}5wb>L=RaYrvyAYjIXy?LhFSn|75nHk23;OAuqQEZd2ch@g}+t5`BZgPr{ z;xcFklFa{P|CpCWr=Zuy@KC*a@)dSk>Qe4h#vABDf~UY^QKs$7XGW#z;|&JlGHNB% zMh_lNu{>V-&K$uHS!*u)85~|<&31SexqW?6DkJdAr+gW)egoBZS?>rgDue~Vmy%Un zZFEDlV2sGUMZRqd!r2N-+e+`aZ>;w2(&_-##(g8R|EWPmMs` z9Nfcrv9Vr#pRPE42Ue(wCYdo`32`5wrKBJ;_32CX@fw;j1aS7FIPRvdq55(YQh@KVKXBCjur@%FZ+ zCMPf~?Oc6LrPS4i&Uge1OMPUgv*@A2g)wTPhpKrBH&ICqCCR!jnk{@eRxiF5C=M#e z4k`Ba*76^(ov<7$?P6kv2t*tzlkPwk4=&2&XH3+qv~-Yegx3HX-IFU9dwT|$^* z@KnA|V&^fodUAJFHVeKHff5BgZ9C4!)}gB6>9lQsFvTTlE`(t zyRBT(>}FAWdYF33OIoheG%-@YXvy3Zo388YQOdJT8&qB-BOKd`EfSmG!HhSsfhJ+D z`AkRR;zbut``p~p(=`%mYzt=a=MeI3^4=u#RayV${u!f5>o9#L7jD*%x-^eYskcrB zf}*RR$gG-~vK#mh;qJ}BhsRb)9{B8AUj@`kmnMccB_^5M>bCcd+sOr0gb}G>H++=_ zhhUF@S`+KUCxg^;UI#T_+l)z?i^T=SHy~(Qes8}9^(7@%j>hD}C`47Okr6Hb)sVm- zD>h;dl9YL7JUB1W;Of3HvK7|cl1@QHV{5aqI_$tl>wDA@cNye|d!ck$Ov#7PR;*>k z$lb|l~t9E z)G545S^XEmD|36)uoq()NgI1g1fVHnfcDcz)h{K5f)=^vd( zs{YAj)EPpMH+}T&%&PgaHW6E|7OLK}a_+9@kjpMDj_I@f)CSj_e<5DFX)i%x6HM)+ zV^MB&H`$JCX^h>qKK3qe+rIlZCr6}aaFAA~!L?9mz8P$BdrtQzDs0)sC3_Spvec}9 z7st#$prs++%x7zP#?W)IaZep1X?~tIrkGsBw_QO-jfc~pkkWe|PUS)?WB*ytbNTz} zG*c|VuBFKR2J4$wYuyV(F;fN+*7N_vNS@Bpp9Eb|@xfqOPIxl)mKZ%U) ztrt9G;k~-jId%DZ)pGyA%3<24IC68ou(0^#)irWMp{O>v)y3pGC)D3};-o7Xzj;&o zE1b#y+G=AVkBpk~(;1T2T1~T$QQfn)l+$9a*}~FBrdNdu+Oy!kA9oCH?>C0hkJ<_> z^~tKzM|PfGNU40K6&x@}=m-i6J0o0Mp9V>h$Xr$>zxjt2AWGJ2olvLdg>;{ULtH6y z&3ycGv6?(;ZGQ7M2gv;bYUvXzTdN;%h_2_sf2ty;N zS)rFzzu@PE9YMqVpq{DzRap;L1p7e0$jUC=zHg#e3fUV;u*K7tBpY+vDmK#Y!YN4> zZ2FLq1yoX2hC_*brjURbArK#&RURNNIai+uaj8;3428X3s@cQ+ajrudXy*YQ?3-^D z1IvcBawfmv+E$jjemZOi6>!Zwexx*=yktG)}T1pyBhenY{ze-dlNtYd*DA z{A$@UZY$jd$NVA?zx7}IPJ6;Z;fheLHrbV0*ohk)FkK{B`_I!B_MfU}x(^XtC-E8&v@;oX=F#i3?z<3j?Y zm>uYV@g+$sad!xStO0C3HC@`mDj_iqNx{XEk>;jMihG>2*6lDg%2msJJmas&fYxfB z$xP35Tj_wN5b!gXA+{alB6yp^ zL+LL$*?(q+kI%`?lAAN4`J6^>NAGG9|d`xw;&|!lU(CYF;;b zM#WdfR<5)_s$E{^?6F+)){6q6V|0-~cHu&L!8ZU_6syUayF$>M+uT85Puq+k>KR1u z7x~-6?y653roa`A&8{eGUQYe$pVsigyKAtjyZy|t{fW)uoO8SXh2mLd3_kUTI-zpi zPRm5&cGEtR@Mv&O>h(=#Pvga?Pu_vAv^5l z5KhUY!~80zLxYQH_d>K^@*ZsZ2Zo@>QT9asBeKYei}wUJMsFNn>?%8;VZ6#Z)XS-N z`-beUbuNu|r#{YKR3?AUIyb3~h;^ZxIB-$wKyLdez3;#=3^Urr5(`TR#}vDJ3UFJO zdobeNtUBbz512<2vq{E`7_0SmtHoL&X?}Yn=KU{Oi*%=CL*pLMcK{ZGg&@-bQ47aO zNr4F4TL3wKKIQ&=p(n;-~H^c?l^LCoMyV64w70rICr3eo`C1gCfZ;+BqR|B0*qq*ng9 zh2Hqk0|fBl13<^JHgbS!gp1}o;tf+$W%dwXN~uT#jo|O+b~+T<^x6vS(%b?`zM-BI z|CRd#LErn0@NZcxPaW4*0=b%te^NC6y}Zr;hVP2<#s)g3&Q8|!_=wNEQevMHkUxD6 zY5jwqLG!k%@(ea$A>0E1+KSMVa+<*6T-*eD3$(0oCPIG6ju<-Ta=@q)y^sP}zQrtW zxGU=*TH35K*zEq|nnlIq}$~ zp6z-d$@%x|QMKZDAtn%p-M_M3JU=*6$Cf=*F+ilcu2oS~9)4a+5)@pLJJTMQg-@6& zP8S8rM`!@hE*Ws|o(a#QuVs1Sv_||_FwT#T&XCkwy8?EiE|Uklx&J9L+vs}rvGp|< zN$OKI7_Y4g3%me_>5N-WqlDV*fY;dA8g(8rX=IZHoQWXqoW|)F*)TSFXq0>q5tCkK0and)~+Sa8eXKQSh zcXm`84qpNy&vVJe^f#yGQCsmNsGskYtuIv9`QvV>?El8rm$uMKcHMY${QU*9&3z#B z;-J5VZXpIdz=EjD=@6-c=N1wi71dR5y_gSbK??>eE{us(_^th_nm7B1z@p_r}Eev`*Twdb9N1iqUAA1;g zw(#9wfwemQ=D1m;+mevbNsg0O4o79W(pvar?W7n+H{# zRhx#Hyfs=cOy%WGUzC=+aKLm+lVN!+=$mD%>b7S&R55y0GT=sQo#_Up_75QS7YPEK z97|U7%G__0;u!rjHr?wPZ-GW;-`*x7XVNTu7|mn*=|u{?4AkuFv{(z%*k$V7B`HkM zmhBY#@p0Trd*mSNvd(XNA>yfOJ@Ft?raz4}o3kVw1UfUVj!1wo_VE+Keh+qMXKA7j z9Td>LW-~YgvdRBDTYr^1fC2nvFeMePfIAmxp&C$lYiLby3IpWW0VG;)359z7Y7@^N zn-oY&#QwKa6N*fnD)9;D__rM20KRRxs8LL(QCqi~$jGSa8OFjxV0tFLP&3`vwHlX| zjgBudU=Y(k3w9cJGOd>lc)JJmaoQW4M+7S{WOEK*u#n}%%psv=O?fB-f*hg(9Q%m zd1hf5g}@6Q-bnxZfq186feq@HQgyPCWPPPAJ2ZEedk4&cMhE~g$2op$f5GXq+C|1t zdDpAYMA|^Jwu+=GFRdIPJAp!J=&*s~kPl4FWfGAax(BzF-FHyO+>5b>T zyLE<^)qQ8%KSD4)e|5&ATVjh6A2I8XA5?&oEj^$IlDsz|9Mv8QtS?~7=v1EJ-*t5O zHMS)48u66;>pg@`Ux1MXG{wK)n!96BsNpNbe>ih~!4y1oAy@iNo1KrUrq`eOW z?^;LN|7!Z{Jmoar5jS&0j`n|YDjM^Msk+V0{OqH-MY+MIREz4QLY!g#5+3JqmU;6^ z^;((te2B+v!xFEr%Zk0U$f6DnIC=Q^Dhv;gaY`0uUauruwR{Y_sq<=EP#p05Pm6Z;}g$Mq^Vxb*Z!635Am<5N?3M;cigB%QCX#t;8E_*_>(o8R6CO8Q3C7Vtjs zQwublhIqhFIXB(t+IaX!Ap&JFP8@xo&y>#bx3_{u<9&M=v{OjMPHp&hA*Uy_GuSWk zA&~#P%Dk&uK67|V!;0;j5O3BXZH>Oy#KQ30pht0*+&PfBarA?bqnc&oHmhTyqHfdi_SFUt z;&Ff(UhD}>AdE!f&eVFh?S#q_T;+?89k&8Q05_7DTFNc-%`LAMeM+IFfk7f`kCB~C zREncrtT`=x>nE`jVDJB3UZyshbX9w2nbG!e;(q~AF8el(We;0nsxyn2c4w-}Fp;Io zyYNDMY4Ivelk-8~G?20tdvVFKD-z}B);;QCL4ZD!u58aZ^y-z`kP)BrPoMMM{+0nN z(Z4>w=FFrRanyNAPN<;G>u1yU=|v$ueFVRYYugls4e^AA`70xV<&4`h_TbC8OwIDe ziaGIe|J_opA!=;|VkQelFornCIPzsc-ERQQlp?%y&$ALPtl*OAQzU~F`OJv^UcKY7 z12G>Y2#?5l5m`8Qj(l8WEeAc+<+uM4CS9GCX>@bJ`!^bmn$ldyNHgu}wXP5*=d#+( zKl$G>`~NBbYq#+a{`ZF;Xr~SFcC!K1+?^8WQT)l@=XB;=F)?5*(c;pUG&@$c$0z`q}` zRF3L>>me5sdgr|?bkwG;^=x66YUsJ&se^%y#=QsuLAnOZ-N6#JZ!&r_Q5KTK>twmm ziXaP^M}Zscli=l(%)y=^1q5s~gkyk2ZsBAi8xk_(_5mXe-QbX8XtvdZBNRc?xt=y*-e+SJsL$S6QhW5W=SOE>iP zaY$vhiH+wxg`cy8hfQq5_cE7;%`Cp!emc!I`cUtmMIHcOYebYOej%T*#wJ^!TjX9z zChkp+waY@u2brtbYxX-39A_aXhqq^i^YD>;cJg}I!RLn*jc2=A3F;+g2Ev!9b@$D; zHj^iGd0o@K%d?Ly)YXS+y}mtH(!1%bw!}$0#f3rKbP!z3{)- zcxDdg>ba@fE`C8d%;4z*3G0K&Mu?y7KZyAGfqxM3IYyCwCGuB`Sw z1t%5C9Nxxfi{QbeTTzoHdJvK0LCD@tz|reP6ne|(vXx*@u#m$xm3rx*eh~A1Ap&mL zS1m$(bWla@CJYe-Ty2HV)~=R@0j)N_@a1tQsR z#@PlXNLA~bek_2+juO^)^@ik8Gh_iy;JG$)vD>NnTlF$l!w%FAHUH@0#J(tUYc`SS zacr~T%E)$N;&>?0L3FcUm;E(aLV(w!e8kUmsw_$ytZpbW)^CV!-|0;|F=_Isd9LBr z`XqEkDa*;8*DHW}0e;z19sT2)Ur%jwqf};nw?E~=t^jAMv9R5waA@8`fn~fgn3tds z4xfYVV$ngWZ^oc1au-dt%F?f^L-wIpC+~yW`ddn<7Ya1aSs(2ze#6QFRf_?M!@lUf z+0?X`CXqEi0e`~C>TRz!cS_Zr2A&koVPp#1VO=t)_p0-F0N=q8G~5oJ{B}f(sY|WL z>3S8C5RqeQctg*3R&y$hX^+YB!zz{vU{zXebquHO8KEsW3c(aotGW8NKZo0NhnBmlF3B=NFq}S{}rPf*M1&s<~jXd9S4cXDry(%`U z(a@P|8oGT*>t6Buit;7xM}MH#?OLc6`g@x_FOEbLL}a1IsX=bL)w|*r8`Idt!=^Cv z3`ltZO5H;aic)+VE$Ueppq4cgnml*kWz1dhJ2iYF-K|@h!NRDi>@t*7&dov7^yiS6 z*pX)GmJgFX#G8@#dgpVNKY-Lx$v;@;Qs%XG4S(Az`%O2Gu8-yxOas^45kr@Avgo^JgS4k@A#EmKFP5_SbD z^1q=J=d2tFsxH(nf>U}SO4MhPYd)RBn}W3a98X{|8lT)Ph^< zv6x1)I`9@FX{UOx@%8igpf+_Xqn0oJ(URz8ZzWwwt!ek5uejjGdG|pnp?SpjY1^~L z;r(Vsb=Uu4cBPTE`u7v7N6hOM3=#4ROR+a*tkRRm0a_GIqwVnwo2rbO!lRwM78(}% z+Y*ZGH7)n~f3J(t;aYm+!dUiqWJZAkP+h@w zjhx_GJ=)zz z#@_~2dxb__jGc@NfA%=1ZdzTm_zq9H5e%aMqP8CJ*Y;1rhrkJ#^Vs6$2vNgS(IFo63ADvpFkjyN40_GME?mrJsd$GtrZ6gDc{CNJbYmQk_`UU%V3dEr>n9kY8D9z9-EI)3=EJyfD1Qz z+>^e_;t4xTA6kNd!Q>~b3!7w>_*wx{#c(VOjr{OykXnyv|Myom{__fZ^F2TtZ6@Vg zG*2;2evtqelEFVeH&va9h*A9)vFyTTJ@A`>2Z@n~UW|?H9_;}&l&nK}{ZyyVK>D%I z8hFpd#e7a1gDpCLDX%~!Ux9+19;c9Izs{!gzoe7@uPS_y%loiTaacx1Qc95cLbBlK_vsY<+ZY91EUGo!+)25*>223{VQZ_T^*$}XurT_YgCs_A`l}NEkV!0 z*2;E;U)J+D(nq?fHR_!c>xbV57YNprYV#(m_QJou{KYjYbC4}h=K0vb=M40;6Y@Z( zHnImgE$Vt*w9!R6PKlMvkpaK@rEamTGO&~c=y@i0EC1OFbd$~-^O89YUkFh4l#y|c z0m3%)(}4PvhB;U2Wcq2+`nBudUzw}d3U4^m)o!p z&{drw8^hCs!U7ImVJd}_g0IX+dR7VOFnLrepo$e^3X52@peuv<@8-LZ5 z90=(wKeTKyOQ!M3_`D{X^eO*UhZhcEuYx$d{c>JZT*!2lIM!Adt#R5*CE<-wCWKpQ zrqzrGG{cIQYND-7S_*FI^$&}n&%4=@!Fm@PnT1Uu_*C2`(!=iOcIQbsn^b%K>dwW+ zvO!_YURqI7M5vE0jr2Ig3Nvk}E7HY?L`xjK2S4cn^3`Px|Uwm*5aP6G}N?Uy-JT zhc;a6WVTbS2rA;Ni0|3;HU(1APKdPI;#N2X+T`H!OM@@a}%K=v?ZFEaq9gkElackswmY-Xgl zB1dT>5z8M$VmDw<&I>gB4oYwUB^Q*h*m`f|c|AOMx|m{mJ6JH8ruoBm_Vz8D;C>rd ze?-sh#6+cB=7Ii~{VD@%I4@qCcjIwqQdIhS!u7Ya@bbw&HORRg(>8k0#uQjqy_)i6~rMb3rEb zzE+hc`JzT4{8HHe#ymq0-M9c>wd1GrR2IJl@d;3xC*1p~*OYUZQHR`08Q3cf^`8@K z_IN(eijUvs4a`m{`_JOt+;od4D`B*k9j^!o|rOW3wyZP{06T-JHkK8yh)pkaey&u=mhOmzx0dDkqr)QkCFl z#&>$sklS@`nuS8}=Q#+JY={L=;{wWFU1jQPB5+{&+v=15tVj9RJwR}}msOTL~ zV3$x;1H=I(PWZb#1(X^2OKX4bLk~O=;&;je1C-5;zn|F#JV*k_s_z2HXCPSd`TIcR zcm2nIGl=--jVw_EM80j)8prUD(72 zN?x-~qjk<92jGb-v6=w<->*QLdBNG>iqg0ybt(r`j~@Scerf%3UsE6rJZodN@0-%1 z?j9k}YgHpjw!n00Ma&1Fy`t4kGK=*A)09SC%}DvM_fbjZDm}#J+&1%lx;od5VgavW zC_9c4#m*xdiJnN!2>=zydIj0_o!uwLOY06=8{ZXP5Sy92dm=3_EnJ<%R)1;=B}s_~ z&*J5-be}k-cO@xYgm>rOx1lthd5L&T-J^ByiHb!lni%ZWm z5~x6+Mh%%>ux$U9Mg^<h9!%2h-Bf zgv-niOz)m-vB7r?>$SS&KlY8a^cfYK_&tMudXnH81q|q*M&dP?h@rfmZ#cdvx5D*( z)QBjx4*hf56sh(8-F_l9A(2cy1sb4b5c`*=QB)6gFA(nYoZ}6EPagt;@R=lz0dU;>A5GG;?FKs@8L-f_Dl5&nn7)a7tF% zS4e-7E(X&K4-UI|jxr-VHJlZd+uWi7Qp36@n_iJi>$T=%Me(mh81mYlyf}2ckI-#C z;8+Y|xGJMAVW>XC#|CcD2J)1Qt}m3HM>?d=*yIuY;#a+Z?JSP%Q`1anIgn$f0LscO z|G1~rTIs8}kM z;e|&YH_Q-!jx3v#+MlNk=n8%1IFfnvjzjnSRb)Wj`_Si}qysVOaWsFWHxySeM(?rAweV70gu^hDn7c)%jJoZV}}( zKL-=t);Gm7yZ4^*7ULg(Fur}zBf)za+sYks2kX;-gZWis=FN&jii-q=-@v7Ci_1{# zI3~$@LY7K%G5+nqfs%BhZI_}!on6@8*6H=n9z6P<#|MUG_=b=<^yg+M(ZYI zI-)b>8T+C$$Ka)@=A9rFY>^IPI+<4_YPBxc`wP&IHFlP3p3@qyXf`Y{Fl%CXdW1SY zKcn^z*76?RrO2e{WZrLjL{C56CkoR`d`YQX2SMc~BGw#S)7dD7jyBk{dIz>&3_r3U zZ?HX=7MSIqm~p^ioVi%1&%CmCjeN`z_6LlyAjg_bFS*LSbXsa?q*AJ~@g^rE9NrkL?$m2M#~qF`9>Df}?fMaG-^@Y9 zdyYWqlgLP`ro!1b4+8HyM3%1`DXmd`Mp-cU)BDfv8MH>U;tFuZhK1EZ91NK%lA9z6 zydTo6E1HC7{CrOz@nfV@Q$_lLv1>jZd_^$Eso<+br+T3_|NBRW$52aIs*LB-(hC_L zF?vrD8iT=9vs4hxPUDt2J#xVpE{n{6IV;^S711J3-C0%t`)87}yV2Gg9S3;2( zFr-u1NQI%&DpJynhJiF9AyNYdNC`+uBP}sHrBgtJ5hDg{bSWLbkN)<@?w)6N=bm%! zx#xY){XF+BUUK_5?J3KT^n)j4yC8^~(Egs`%Ik8p)o%~Xy?;GEGejB0Yq6-6%U@W= z*Ew9^2j}OG4@voJ1EmkX4Numwfx(OEjUF?L51y9jWryjQEg0@6&qX@cEe%=Zjp)PS z9lmd^kyom+(?%XQM#^7o@1_ei?(UDMM1F-i;v?qFNfxaU2%SL=<*>qN7d<1RSrr;pQ^;o z))X@w-Nb}TJXOKAk_{6T2Ul2xr6K^w(+p2a_oHEf=$5?+?PA{i8IKA+BQoK6!L9m; zhAM{Y_1upYDYNT(`BMsTxFDe!Pj4A7V6kA|XqA$(8aT`bx`miLpYfNjX@H`CVp3+6 zIo-b~3f4Wb2CSA`Y#xRe7I=qxT8TBFz+X7-`+s+0Avh=e=h%*h1$|WEReeCt_4A+E zf-`RZ5pJV+R7ytk0;l7g3l!_+iy*Iag!5>yFO!gUiRuIgr?~pewA)#Z^ZqJxI#UBiWT9lK zDfnV8o~>KnPSrB;))!0{ImmH)4bJW7HRqt{pxwEFO_l2WhA35mfWc41DlozbmNt~; z5huDl`uIq0OH3GT7)M`4a+9a{u&}9cvW0a%+Mg3TnXn4P&9jh_$JOS(vD^&hY^ghx zqTZupb(^XP3UEQ{9);nJmG1UT4ecvc4!tAUg#vK6<4sW6W8VZ_pf~Ev!{Qfa3t`}} zoL@hJ$NTyceiPfiQ(fxmkqpb`w=2MxR?O9qG3p26dC9^82z`^2yRnZQKQ>_szAg;= z=lP!2Ym@F&W{AAdvH|3Zf(B{;y$6XZyctWq?3B*FB+lV^;+>0?ewIB+iO?Ud-Z4GT zo6+^Oev@Uu{W1>^uoA!aAuw4yES1@6h*RdQdwRg?;T`wdB@NvJJ`K;L?~kmdO#FCcQkle{9vh>KPuw56uGGo^z{J>{ zTdj13^wCqP6Fhs-*jyEO@XoPs zs@=lwjdB0eIpd=;0OP4wqQbySZ-$NcXz%<@*bckk{?8cEp- zzO^3>+ ze+sED7pM|RR?F*`pvOd7PYOMZ&%sT)zpi9W@AGkeet0l=HRVYs*WigwqE<_bDsEB0 zl=1uBY-bvpT{poJ0mo&Xc}{L4tS$2N!g}W?8pUP80&Q|aCI$*kX>6s!Y_^&Y>?`;7 zD}Q08r9FQXvtF`98`4$qBbY{P) zV?>Np;$4-OP8%UI@luxa+hdxE@s37+qAUl4?j6^!RfjyB#^Pc>;TZ|PyOiJ^_w>rT z+NPPE+q~N*)}}kAat2m$-P-#B@{j7MF7la1!@&=Y9U%Yv&{Q3q@HR%Ff2>bnQcX{JMFMmr?SiXk(@*Y)g& zPDtH_b?;n(#PvE9vWs_8&j^+?>Ui(uT$kI4Y+2@0LARNSmxcp}cPCIu-qh_G_ib)Yh7$ zG!K3*77{FTt?;yo`(rKCL{ix3<@<;jed;}a>|w_W2_4-25f;!T=A=y-DFvnB0+1Pbe=x0Ilq$ zr2?_aE1|PAhE6N4VR4}s-8iS_7O$0_X*$rP#*kNGe`U0Wv`wJjiai7G;^xDki&u)uYsmdDqE&Rux6+)(`kkW- zY&Hu5@ZQ_s#i+_ z1pg~%|vWNjs0C}+ObWXq?o4Bp{MpV?Ru=Ky|9iS;#@tlx{C)HjzZPA{PCfff58IK zySe>S!dsj^QiTK7k5yKC-2tATJF4;Y>h4}f7y-ztPkAK0YwvfW1IGHNm(QcKU5sQ9nv}$K#0faFl=qG(EnU8Rkvk?(sntr9e`C)qB zLg@U`_c7lg;^Yv<8(<=(b>UY`MoaW*p6EEH>M4NEkGp=-kH7NF!Att=^Ok8?RnQsG zKlSOdj^5RSxldGsXc?7W(JG&>%ExEYz!_;Z;c%JHBGsfjOc}YccNLyVU|+ytHO_T~ zgAN+(x$&dYb*@bP?Kk$nYc}YhP(6`i4t6-|5K8MKQb9^f8z##qowc@Hag71D1Z=(q z-ly6#WwQKhe+Licy&95mo-OrlnY|DZfYEP=-R!h<>21n5-S3G2ucATjZp7bKvi7;85$;1Dm6lPDQzca3HeRJW=zrqdyh zOAQ`muItZ*%5g(?NvPtVoB!)?+BHG{$p6I2(*CK`7Q)1{Ueo%7R1mt?=F*oh%!Muw zpsaElV4KKf8vIou@oDmWLzAN>b2`5o5BTv@k`4{&qZGn(H*mhdu;qm%Y3ZXTVh)buW-+m66Edy$uNr z_BH}_Oxi-!IP|F6|6cYX+-J|ToFi@k&rAmJEY{1k4a4@>7_^)EGa*bQ@_f}q{f~;y zZOejxlJ;wj07(fY{wu@ldb-cd6c639r^RH%LPVnXh`c(vadK$0Qp(j5W5SVm4iof|VSEy9jQ2G9aNqsm^~uwPTrn0}NqfBKPV-^I`znb_n%>B=+aX+5_p zWF^BmM~Q4Wj*ujVw=_vz;@Oob6e@K0$eDug7s*w-g0Ek)$kowNcv;>86y!v@xu3IA z{yZ{@0}N*Q4p3j-f`UA$_at}dhsNXc=#oep`-=Y2cFo0IJiRq+I(3Bx{-2hE8@-d( zvxVWKWTyQ?AJ)e~|6C*HbygdUB22=yl3%k`yYyppeASc_^=;geoZ^87JrpttMPtdE|u7#R#C3x%VBT(!h zXd8KUAYe=P&e7D>+Q{kmPDfotHsc3q;dwAa zaL(WPmeWznNczDepadb?Rp5q^6C*8wRRMJ>5TI4QE>VdE z+$T~iy5BoKF#%}QSHqGr3|Aj zkLs`shD^48Y_^s0Z_-?A*5u^lY|`~+*A!3BZj<>q3C@^f>6kEQyRAVyIO zl))W(pmqmR25e~1x_LyuG>%Fxn~~JP0?*gz62zgo8!yzXszZCbjjIOIN0ixdRPb1m5gxkoE2+i3yKiaK0?vaT+K$n*t*K-L(M!Cu!lye6J>6ueG z_jo{}8<__5`d(6ef`~KqPZHkBg%@;bRK%s76Ll5UA<5^&iOE?YlSqEG)(%Dzvofh_ z7rq1o4-ZllRLHsLl7QWbbvNmnYt~SN{8;)ygnnbjIgrh7J>1^Wcq%2=8y8tmzRII< z%hKkKb_?F~AN;%?7jt}EqR&uWHU%-RB>>`HL>+v2%9Bf>w{u;S@jl`6avZBVaEn96 z;?HGW*Pge@iHVgnXe{gH2s*tze#p(kF+?o2F7aJuuLk5T$G!7L^8|p@)V@4#c+APk z30$Z?&Ldr{v;HGfaTZffGBvV=#DO5fW^^g&DA&P*axxTmGp^`choG6V1U8$|vTaXp zyPH<}W58<8#B+Q+$y2E4#{jH_XRf`}>OY00#B0ZX7VB5~`1rYucXwL@K@rKi8iF?e z=_;eEqJIb`@@Ys&V3q|Ft8m&tYu|=eQPS* zT_l1|S*9b?(XF2?n^d5*;UNu8UGn&)T8GtSxml4;Nuh62diqxEE6I)z%Qa^nG!Y-j zjRzJ(y?Ti7+t{Il=HIv2zP$hN41E8%2Q~!k9%LNFog49naYmVTuz@=1wvXs@Q?O@O zg?u`7TNPz~GhCRzJO2v6qkHX3q*;c$8GcAiU`-g|_ksJO-9#|AaL_qHe6IUa zL0QwA1wZ*JueqIX;p9K9_e!BfjQX z#m#=gnCw|fPtAIz(DReQYQxkGu%A`8M569!84}V>Kj2ZSoX*05{=GuiZq)AjDZ$~j zcE~F^d3m4J^2UpF8z3LY(g@N7rcpjOT;|Q!jQz0l%d5 z6#GT*q4b~c5Mf=fCQO|h`r`DZ!BiLBT=LNU)yXi8G~&+?iKrSwjkmX9uqEdCRx62Z z^F-hE0k6ewVJk`m81-gF2tdb07RR-zER0s_RHxwS@G=G8dn@q>PSMyNgW6?!$8Q3w zReZTss4Rt>Ere62<6q6~bUWQGl191bWDLec&0qp3277DC+q7-3EOEI1RW66k1 zt&)FKS4gcOHC9Jqw1`^QIBfnsUP%QkCFKH4EvWN9h8Gs6JbAULdsDiKwYZ+DhF@eTztN z61oH;a3(W-=x?vNWReha15CoKk#<%g<{a#t^PO|-&+zfn^W4{-SQL9<@X&)g=xHn% zhprb)A9{a%{rNrZeR{n6=z7iv@oySAb4^?k#L1_J-i-6bhrwXATIv2v?C>zseQ&A9 zZ1A7s`_4c#8uT9TG;Lde>u@JIm!EYwkG#>6Gc9oD?4dVu#^0toImzFx zHhHU9GzzAV@4hh3S@bTut^DLhUfInaRK({_H1&dgU1EIvY%q{cu$>$xqyz(G;q|y& zu5Q$>I=@|YQM>BAc(@279yuQ0S2+qppE*IMl}AffT~9oAd|_h8x&^CCpLm>yC+>gZ z`0+U%$WFg{+@;<-jXd|b^X{}bAZ6vbd^UWRG+deV7fI5eyx1H0tnBW+fHx-or5W+X zUnfQI&E++%A(M76M7Wd@MlKH#)1~LK_=K75wmBeamNy%m8uenka|b3nRS4Lr3g3BQ zQ7-=TCRA(r51b>8=8U=bwEna;CgJEGHs*O}|Kv{KI+3fr*FJ~Oy-gk>vJlUG==~0M z2dmgUAxz-l-e@htjY)w%q{Z&o`tJnNfLTGx?ZA1gjF_-`HRnr3Rtk5XVQ%mqO+5kN1fqfU)eL?@-dkE7_9AM zxDRceNc1v@bYNQ~Q3H{Q?jhD~fYQ&@!d5lSJQbU?%LvEd$iCt094Vmn*|&x7XhfJNaWblV9v^a)D0ZKGqse)_{)6bDZQn` z$aE&Oj>8}jo-E)d-gC6|H`+`9RAQsTM0uFTd~NfsFx~y+r6BY|7%a~;P+b&6NN%Pf z5{uVV1JPDejT&82sm6DmYLxpJuIpo%$n`O7Y<$cBt@v1tE}i3JMETfvmd+cs0~wK> z&LcxN&8>7E5gp?$rSl$ExqLghd}b9Ym+xpUk0j^v2QZM-PBbiCItpS_xD%zXD2S;J z?W}g~1`7TSp@PA7U~p}Z3J5z2g!n3x8fF8_CkV-2K1pXb*2^b`{NV#Yd)Ls-W5ajex!b@ z>kh&?kP6Y=4AJIQLNsk6M4Qq>G}#K#w1p6Dst|qc5N$URqAi&rTF5tb0|>)RH0h8a za{F;v#o;#Mu=Tb=Fu0u%j3tI(dY$P)2yPzgfR3q|;@!F$F(I}R)ayny^277{AE*4@ z3l>X0Osf)wgRS78szoXtLac?-SYw%;xef!|K3{(yxE~ zq>3r&#Z+Q|IhUU!w?7Iu|X?b5ZE zcjy~h-hre6qPW!TU05Y0lqQRh20_9;Egc#O1)~G=oy&#mh~_aDfBScfWjqr=q+ORW z`JF^+lvaFTCv{Vyy3tS#Wrk|wDm)BmHn2HODiI^woR%>$^37>f{^m59Obl{+OpKN{ zr{(IT&br)Y_F*-$MMG--k!i>nCb>4nF;PiIx%DZQNoQCl(7Y1OgmwV)MCJ^WdC;6_ zf~juPwC3sv(PYX5Fe!dXix{XFCS7Hi$N|FN7+;0aH-Upc`Qg$n1H#(WV!Mx_OwPLy ze-Ev`Ktl+tmRNJfol~gidSfoLfU7=E=hSahV*N(akq6i`Y)nl+t4!LL4-P^#N}`>sdu%FjaX^ zTfABwL4))$)~P|TN_{G(J`>+t3}<2F{VOcW@sHw6;W6MBs}VU!Vvo|4#LH3gtaR3^ zj91NY@=)!q3jvoHto0}%R&uU7M99IG_%cJ80nT_z|6WpqEt6b%aU(l_~;G? zaeEdbskuWyiDED-2J?9_<&}kO@5U6=()>GWgoSL$Wg%k=Cp#1p7P5)VbO&EISjdXo z)DjEXQY_>vvXGm3*u2pI*$4yqWFvb+Ho{0g*{Eul6xm3TjgAHWW}XKtb;&7NcqgXF zy+IoH(}j0KqyiYGDZ%Z>|5z=&x4Q5y3jL-YOjQuFD~Np) iRQnfN-M>)8qU>|}ZtcgrotJMv-u(|wX0r99g8%@-r9)%@ literal 0 HcmV?d00001 diff --git a/docs/i3-sync.png b/docs/i3-sync.png new file mode 100644 index 0000000000000000000000000000000000000000..b64cce25959b1620084fc2214e35c2b7f9fb8a10 GIT binary patch literal 17308 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 literal 0 HcmV?d00001 diff --git a/docs/testsuite b/docs/testsuite new file mode 100644 index 00000000..7611499d --- /dev/null +++ b/docs/testsuite @@ -0,0 +1,446 @@ +i3 testsuite +============ +Michael Stapelberg +September 2011 + +This document explains how the i3 testsuite works, how to use it and how to +extend it. It is targeted at developers who not necessarily have been doing +testing before or have not been testing in Perl before. In general, the +testsuite is not of interest for end users. + + +== Introduction + +The i3 testsuite is a collection of files which contain testcases for various +i3 features. Some of them test if a certain workflow works correctly (moving +windows, focus behaviour, …). Others are regression tests and contain code +which previously made i3 crash or lead to unexpected behaviour. They then check +if i3 still runs (meaning it did not crash) and if it handled everything +correctly. + +The goal of having these tests is to automatically find problems and to +automatically get a feel for whether a change in the source code breaks any +existing feature. After every modification of the i3 sourcecode, the developer +should run the full testsuite. If one of the tests does not pass (but fails), +the corresponding problem should be fixed (or, in some cases, the testcase has +to be modified). For every bugreport, a testcase should be written to test the +correct behaviour. Initially, it will fail, but after fixing the bug, it will +pass. This ensures (or increases the chance) that bugs which have been fixed +once will never be found again. + +Also, when implementing a new feature, a testcase might be a good way to be +able to easily test if the feature is working correctly. Many developers will +test manually if everything works. Having a testcase not only helps you with +that, but it will also be useful for every future change. + +== Implementation + +For several reasons, the i3 testsuite has been implemented in Perl: + +1. Perl has a long tradition of testing. Every popular/bigger Perl module which + you can find on CPAN will not only come with documentation, but also with + tests. Therefore, the available infrastructure for tests is comprehensive. + See for example the excellent http://search.cpan.org/perldoc?Test::More + and the referenced http://search.cpan.org/perldoc?Test::Tutorial. + +2. Perl is widely available and has a well-working package infrastructure. +3. The author is familiar with Perl :). + +Please do not start programming language flamewars at this point. + +=== Mechanisms + +==== Script: complete-run + +The testcases are run by a script called +complete-run.pl+. It runs all +testcases by default, but you can be more specific and let it only run one or +more testcases. Also, it takes care of starting up a separate instance of i3 +with an appropriate configuration file and creates a folder for each run +containing the appropriate i3 logfile for each testcase. The latest folder can +always be found under the symlink +latest/+. It is recommended that you run the +tests on one or more separate X server instances (you can only start one window +manager per X session), for example using the provided Xdummy script. ++complete-run.pl+ takes one or more X11 display specifications and parallelizes +the testcases appropriately: + +.Example invocation of complete-run.pl+ +--------------------------------------- +$ cd ~/i3/testcases + +# start two dummy X11 instances in the background +$ ./Xdummy :1 & +$ ./Xdummy :2 & + +$ ./complete-run.pl -d :1,:2 +# output omitted because it is very long +All tests successful. +Files=78, Tests=734, 27 wallclock secs ( 0.38 usr 0.48 sys + 17.65 cusr 3.21 csys = 21.72 CPU) +Result: PASS + +$ ./complete-run.pl -d :1 t/04-floating.t +[:3] i3 startup: took 0.07s, status = 1 +[:3] Running t/04-floating.t with logfile testsuite-2011-09-24-16-06-04-4.0.2-226-g1eb011a/i3-log-for-04-floating.t +[:3] t/04-floating.t finished +[:3] killing i3 +output for t/04-floating.t: +ok 1 - use X11::XCB::Window; +ok 2 - The object isa X11::XCB::Window +ok 3 - Window is mapped +ok 4 - i3 raised the width to 75 +ok 5 - i3 raised the height to 50 +ok 6 - i3 did not map it to (0x0) +ok 7 - The object isa X11::XCB::Window +ok 8 - i3 let the width at 80 +ok 9 - i3 let the height at 90 +ok 10 - i3 mapped it to x=1 +ok 11 - i3 mapped it to y=18 +ok 12 - The object isa X11::XCB::Window +ok 13 - i3 let the width at 80 +ok 14 - i3 let the height at 90 +1..14 + +All tests successful. +Files=1, Tests=14, 0 wallclock secs ( 0.01 usr 0.00 sys + 0.19 cusr 0.03 csys = 0.23 CPU) +Result: PASS + +$ less latest/i3-log-for-04-floating.t +---------------------------------------- + +==== IPC interface + +The testsuite makes extensive use of the IPC (Inter-Process Communication) +interface which i3 provides. It is used for the startup process of i3, for +terminating it cleanly and (most importantly) for modifying and getting the +current state (layout tree). + +See [http://i3wm.org/docs/ipc.html] for documentation on the IPC interface. + +==== X11::XCB + +In order to open new windows, change attributes, get events, etc., the +testsuite uses X11::XCB, a new (and quite specific to i3 at the moment) Perl +module which uses the XCB protocol description to generate Perl bindings to +X11. They work in a very similar way to libxcb (which i3 uses) and provide +relatively high-level interfaces (objects such as +X11::XCB::Window+) aswell as +access to the low-level interface, which is very useful when testing a window +manager. + +=== Filesystem structure + +In the git root of i3, the testcases live in the folder +testcases+. This +folder contains the +complete-run.pl+ and +Xdummy+ scripts and a base +configuration file which will be used for the tests. The different testcases +themselve can be found in the conventionally named subfolder +t+: + +.Filesystem structure +-------------------------------------------- +├── testcases +│   ├── complete-run.pl +│   ├── i3-test.config +│   ├── t +│   │   ├── 00-load.t +│   │   ├── 01-tile.t +│   │   ├── 02-fullscreen.t +│   │   ├── ... +│   │   ├── omitted for brevity +│   │   ├── ... +│   │   ├── 74-regress-focus-toggle.t +│   │   └── lib +│   │   └── i3test.pm +│   └── Xdummy +-------------------------------------------- + +== Anatomy of a testcase + +Learning by example is definitely a good strategy when you are wondering how to +write a testcase. Let's take +t/11-goto.t+ as an easy example and go through it +step by step: + +.t/11-goto.t: Boilerplate +---------------------- +#!perl +# vim:ts=4:sw=4:expandtab + +use i3test; +use File::Temp; + +my $x = X11::XCB::Connection->new; +----------------------- + +This is what we call boilerplate. It exists at the top of every test file (to +some extent). The first line is the shebang, which specifies that this file is +a Perl script. The second line contains VIM specific settings on how to +edit/format this file (use spaces instead of tabs, indent using 4 spaces). +Afterwards, the +i3test+ module is used. This module contains i3 testsuite +specific functions which you are strongly encouraged to use. They make writing +testcases a lot easier and will make it easier for other people to read your +tests. + +The next line uses the +File::Temp+ module. This is specific to this testcase, +because it needs to generate a temporary name during the test. Many testcases +use only the +i3test+ module. + +The last line opens a connection to X11. You might or might not need this in +your testcase, depending on whether you are going to open windows (etc.) or +only use i3 commands. + +.t/11-goto.t: Setup +---------------------- +my $tmp = fresh_workspace; + +cmd 'split h'; +---------------------- + +The first line calls i3test's +fresh_workspace+ function which looks for a +currently unused workspace, switches to it, and returns its name. The variable ++$tmp+ will end up having a value such as +"/tmp/87kBVcHbA9"+. Note that this +is not (necessarily) a valid path, it's just a random workspace name. + +So, now that we are on a new workspace, we ensure that the workspace uses +horizontal orientation by issuing the +split h+ command (see the i3 User's +Guide for a list of commands). This is not strictly necessary, but good style. +In general, the +cmd+ function executes the specified i3 command by using the +IPC interface and returns once i3 acknowledged the command. + +.t/11-goto.t: Setup +---------------------- +##################################################################### +# Create two windows and make sure focus switching works +##################################################################### + +my $top = open_window($x); +my $mid = open_window($x); +my $bottom = open_window($x); +---------------------- + +In every major section of a testcase, you should put a comment like the one +above. This makes it immediately clear how the file is structured. + +The +open_window+ function opens a standard window, which will then be put into +tiling mode by i3. If you want a floating window, use the ++open_floating_window+ function. These functions accept the same parameters as ++X11::XCB::Window->new+, see the i3test documentation at TODO. + +.t/11-goto.t: Helper function +---------------------- +# +# Returns the input focus after sending the given command to i3 via IPC +# end sleeping for half a second to make sure i3 reacted +# +sub focus_after { + my $msg = shift; + + cmd $msg; + sync_with_i3 $x; + return $x->input_focus; +} +---------------------- + +This section defines a helper function which will be used over and over in this +testcase. If you have code which gets executed more than once or twice +(depending on the length of your test, use your best judgement), please put it +in a function. Tests should be short, concise and clear. + +The +focus_after+ function executes a command and returns the X11 focus after +the command was executed. The +sync_with_i3+ command makes sure that i3 could +push its state to X11. See <> to learn how this works exactly. + +.t/11-goto.t: Test assumptions +---------------------- +$focus = $x->input_focus; +is($focus, $bottom->id, "Latest window focused"); + +$focus = focus_after('focus left'); +is($focus, $mid->id, "Middle window focused"); +---------------------- + +Now, we run the first two real tests. They use +Test::More+'s +is+ function, +which compares two values and prints the differences if they are not the same. +After the arguments, we supply a short comment to indicate what we are testing +here. This makes it vastly more easy for the developer to spot which testcase +is the problem in case one fails. + +The first test checks that the most recently opened window is focused. +Afterwards, the command +focus left+ is issued and it is verified that the +middle window now has focus. + +Note that this is not a comprehensive test of the +focus+ command -- we would +have to test wrapping, focus when using a more complex layout, focusing the +parent/child containers, etc. But that is not the point of this testcase. +Instead, we just want to know if +$x->input_focus+ corresponds with what we are +expecting. If not, something is completely wrong with the test environment and +this trivial test will fail. + +.t/11-goto.t: Test that the feature does not work (yet) +---------------------- +##################################################################### +# Now goto a mark which does not exist +##################################################################### + +my $random_mark = mktemp('mark.XXXXXX'); + +$focus = focus_after(qq|[con_mark="$random_mark"] focus|); +is($focus, $mid->id, "focus unchanged"); +---------------------- + +In this new major section, a random mark (mark is an identifier for a window, +see "VIM-like marks" in the i3 User’s Guide) will be generated. Afterwards, we +test that trying to focus that mark will not do anything. This is important: Do +not only test that using a feature has the expected outcome, but also test that +using it without properly initializing it does no harm. This command could for +example have changed focus anyways (a bug) or crash i3 (obviously a bug). + +.t/11-goto.t: Test that the feature does work +---------------------- +cmd "mark $random_mark"; + +$focus = focus_after('focus left'); +is($focus, $top->id, "Top window focused"); + +$focus = focus_after(qq|[con_mark="$random_mark"] focus|); +is($focus, $mid->id, "goto worked"); +---------------------- + +Remember: Focus was on the middle window (we verified that earlier in "Test +assumptions"). We now mark the middle window with our randomly generated mark. +Afterwards, we switch focus away from the middle window to be able to tell if +focusing it via its mark will work. If it does work (next test), the goto +command works. + +.t/11-goto.t: Test corner case +---------------------- +# check that we can specify multiple criteria + +$focus = focus_after('focus left'); +is($focus, $top->id, "Top window focused"); + +$focus = focus_after(qq|[con_mark="$random_mark" con_mark="$random_mark"] focus|); +is($focus, $mid->id, "goto worked"); +---------------------- + +Now we test the same feature, but specifying the mark twice in the command. +This should have no effect, but let’s be sure: test it and see if things go +wrong. + +.t/11-goto.t: Test second code path +---------------------- +##################################################################### +# Check whether the focus command will switch to a different +# workspace if necessary +##################################################################### + +my $tmp2 = fresh_workspace; + +is(focused_ws(), $tmp2, 'tmp2 now focused'); + +cmd qq|[con_mark="$random_mark"] focus|; + +is(focused_ws(), $tmp, 'tmp now focused'); +---------------------- + +This part of the test checks that focusing windows by mark works across +workspaces. It uses i3test's +focused_ws+ function to get the current +workspace. + +.t/11-goto.t: Test second code path +---------------------- +done_testing; +---------------------- + +The end of every testcase has to contain the +done_testing+ line. This tells ++complete-run.pl+ that the test was finished successfully. If it does not +occur, the test might have crashed during execution -- some of the reasons why +that could happen are bugs in the used modules, bugs in the testcase itself or +an i3 crash resulting in the testcase being unable to communicate with i3 via +IPC anymore. + +[[i3_sync]] +== Appendix A: The i3 sync protocol + +Consider the following situation: You open two windows in your testcase, then +you use +focus left+ and want to verify that the X11 focus has been updated +properly. Sounds simple, right? Let’s assume you use this straight-forward +implementation: + +.Racey focus testcase +----------- +my $left = open_window($x); +my $right = open_window($x); +cmd 'focus left'; +is($x->input_focus, $left->id, 'left window focused'); +---------- + +However, the test fails. Sometimes. Apparantly, there is a race condition in +your test. If you think about it, this is because you are using two different +pieces of software: You tell i3 to update focus, i3 confirms that, and then you +ask X11 to give you the current focus. There is a certain time i3 needs to +update the X11 state. If the testcase gets CPU time before X11 processed i3's +requests, the test will fail. + +image::i3-sync.png["Diagram of the race condition", title="Diagram of the race condition"] + +One way to "solve" this would be to add +sleep 0.5;+ after the +cmd+ call. +After 0.5 seconds it should be safe to assume that focus has been updated, +right? + +In practice, this usually works. However, it has several problems: + +1. This is obviously not a clean solution, but a workaround. Ugly. +2. On very slow machines, this might not work. Unlikely, but in different + situations (a delay to wait for i3 to startup) the necessary time is much + harder to guess, even for fast machines. +3. This *wastes a lot of time*. Usually, your computer is much faster than 0.5s + to update the status. However, sometimes, it might take 0.4s, so we can’t + make it +sleep 0.1+. + +To illustrate how grave the problem with wasting time actually is: Before +removing all sleeps from the testsuite, a typical run using 4 separate X +servers took around 50 seconds on my machine. After removing all the sleeps, +we achieved times of about 25 seconds. This is very significant and influences +the way you think about tests -- the faster they are, the more likely you are +to check whether everything still works quite often (which you should). + +What I am trying to say is: This adds up quickly and makes the test suite less +robust. + +The real solution for this problem is a mechanism which I call "the i3 sync +protocol". The idea is to send a request (which does not modify state) via X11 +to i3 which will then be answered. Due to the request's position in the event +queue (*after* all previous events), you can be sure that by the time you +receive the reply, all other events have been dealt with by i3 (and, more +importantly, X11). + +image::i3-sync-working.png["Diagram of the i3 sync solution", title="Diagram of the i3 sync solution"] + +=== Implementation details + +The client which wants to sync with i3 initiates the protocol by sending a +ClientMessage to the X11 root window: + +.Send ClientMessage +------------------- +# Generate a ClientMessage, see xcb_client_message_t +my $msg = pack "CCSLLLLLLL", + CLIENT_MESSAGE, # response_type + 32, # format + 0, # sequence + $root, # destination window + $x->atom(name => 'I3_SYNC')->id, + + $_sync_window->id, # data[0]: our own window id + $myrnd, # data[1]: a random value to identify the request + 0, + 0, + 0; + +# Send it to the root window -- since i3 uses the SubstructureRedirect +# event mask, it will get the ClientMessage. +$x->send_event(0, $root, EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg); +------------------- + +i3 will then reply with the same ClientMessage, sent to the window specified in ++data[0]+. In the reply, +data[0]+ and +data[1]+ are exactly the same as in the +request. You should use a random value in +data[1]+ and check that you received +the same one when getting the reply. + +== Appendix B: Socket activation From 0e8eb5747ac87d9f03a3a9a528ba167478c09713 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 24 Sep 2011 20:11:23 +0100 Subject: [PATCH 087/333] s/This adds up quickly/Delays add up quickly (Thanks mxf) --- docs/testsuite | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/testsuite b/docs/testsuite index 7611499d..51da2041 100644 --- a/docs/testsuite +++ b/docs/testsuite @@ -400,8 +400,8 @@ we achieved times of about 25 seconds. This is very significant and influences the way you think about tests -- the faster they are, the more likely you are to check whether everything still works quite often (which you should). -What I am trying to say is: This adds up quickly and makes the test suite less -robust. +What I am trying to say is: Delays adds up quickly and make the test suite +less robust. The real solution for this problem is a mechanism which I call "the i3 sync protocol". The idea is to send a request (which does not modify state) via X11 From 1675499f1506e86e7b7339e1cc113af81c96db6b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 25 Sep 2011 14:23:47 +0100 Subject: [PATCH 088/333] docs/testsuite: a few corrections by fernandotcl --- docs/testsuite | 33 +++++++++++++++++++-------------- testcases/t/11-goto.t | 2 +- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/docs/testsuite b/docs/testsuite index 51da2041..b3b76c74 100644 --- a/docs/testsuite +++ b/docs/testsuite @@ -3,10 +3,10 @@ i3 testsuite Michael Stapelberg September 2011 -This document explains how the i3 testsuite works, how to use it and how to -extend it. It is targeted at developers who not necessarily have been doing -testing before or have not been testing in Perl before. In general, the -testsuite is not of interest for end users. +This document explains how the i3 testsuite works, how to use it and extend it. +It is targeted at developers who not necessarily have been doing testing before +or have not been testing in Perl before. In general, the testsuite is not of +interest for end users. == Introduction @@ -21,12 +21,12 @@ correctly. The goal of having these tests is to automatically find problems and to automatically get a feel for whether a change in the source code breaks any existing feature. After every modification of the i3 sourcecode, the developer -should run the full testsuite. If one of the tests does not pass (but fails), -the corresponding problem should be fixed (or, in some cases, the testcase has -to be modified). For every bugreport, a testcase should be written to test the -correct behaviour. Initially, it will fail, but after fixing the bug, it will -pass. This ensures (or increases the chance) that bugs which have been fixed -once will never be found again. +should run the full testsuite. If one of the tests fails, the corresponding +problem should be fixed (or, in some cases, the testcase has to be modified). +For every bugreport, a testcase should be written to test the correct +behaviour. Initially, it will fail, but after fixing the bug, it will pass. +This ensures (or increases the chance) that bugs which have been fixed once +will never be found again. Also, when implementing a new feature, a testcase might be a good way to be able to easily test if the feature is working correctly. Many developers will @@ -130,7 +130,8 @@ manager. In the git root of i3, the testcases live in the folder +testcases+. This folder contains the +complete-run.pl+ and +Xdummy+ scripts and a base configuration file which will be used for the tests. The different testcases -themselve can be found in the conventionally named subfolder +t+: +(their file extension is .t, not .pl) themselves can be found in the +conventionally named subfolder +t+: .Filesystem structure -------------------------------------------- @@ -225,7 +226,7 @@ tiling mode by i3. If you want a floating window, use the ---------------------- # # Returns the input focus after sending the given command to i3 via IPC -# end sleeping for half a second to make sure i3 reacted +# and syncing with i3 # sub focus_after { my $msg = shift; @@ -283,6 +284,10 @@ $focus = focus_after(qq|[con_mark="$random_mark"] focus|); is($focus, $mid->id, "focus unchanged"); ---------------------- +Syntax hint: The qq keyword is the interpolating quote operator. It lets you +chose a quote character (in this case the +|+ character, a pipe). This makes +having double quotes in our string easy. + In this new major section, a random mark (mark is an identifier for a window, see "VIM-like marks" in the i3 User’s Guide) will be generated. Afterwards, we test that trying to focus that mark will not do anything. This is important: Do @@ -304,8 +309,8 @@ is($focus, $mid->id, "goto worked"); Remember: Focus was on the middle window (we verified that earlier in "Test assumptions"). We now mark the middle window with our randomly generated mark. Afterwards, we switch focus away from the middle window to be able to tell if -focusing it via its mark will work. If it does work (next test), the goto -command works. +focusing it via its mark will work. If the test works, the goto command seems +to be working. .t/11-goto.t: Test corner case ---------------------- diff --git a/testcases/t/11-goto.t b/testcases/t/11-goto.t index 5ddef776..903fa0c4 100644 --- a/testcases/t/11-goto.t +++ b/testcases/t/11-goto.t @@ -20,7 +20,7 @@ my $bottom = open_window($x); # # Returns the input focus after sending the given command to i3 via IPC -# end sleeping for half a second to make sure i3 reacted +# and syncing with i3 # sub focus_after { my $msg = shift; From 6420b2b102bab214ad0bf1fa0f5e190f02984e82 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 25 Sep 2011 18:45:51 +0100 Subject: [PATCH 089/333] Introduce the i3-sensible-{pager,editor,terminal} scripts The former two provide fallbacks in case $PAGER or $EDITOR is not set (which might be more common than you think, because they have to be set in ~/.xsession, not in the shell configuration!) while the latter tries to launch a terminal emulator. The scripts are most prominently used in i3-nagbar, which alerts the user when the configuration is broken for some reason. Also, i3-sensible-terminal is used in the default configuration. This commit does not rely on the shell supporting ${PAGER:-less} anymore, which is not the case for 'fish'. --- Makefile | 3 +++ PACKAGE-MAINTAINER | 18 ++++++++++++------ common.mk | 1 - i3-sensible-editor | 14 ++++++++++++++ i3-sensible-pager | 15 +++++++++++++++ i3-sensible-terminal | 15 +++++++++++++++ i3.config | 2 +- i3.config.keycodes | 2 +- src/cfgparse.y | 4 ++-- 9 files changed, 63 insertions(+), 11 deletions(-) create mode 100755 i3-sensible-editor create mode 100755 i3-sensible-pager create mode 100755 i3-sensible-terminal diff --git a/Makefile b/Makefile index 15ccf0e6..bec6b64b 100644 --- a/Makefile +++ b/Makefile @@ -80,6 +80,9 @@ install: all $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/xsessions $(INSTALL) -m 0755 i3 $(DESTDIR)$(PREFIX)/bin/ $(INSTALL) -m 0755 i3-migrate-config-to-v4 $(DESTDIR)$(PREFIX)/bin/ + $(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/ test -e $(DESTDIR)$(SYSCONFDIR)/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)$(SYSCONFDIR)/i3/config test -e $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes || $(INSTALL) -m 0644 i3.config.keycodes $(DESTDIR)$(SYSCONFDIR)/i3/config.keycodes $(INSTALL) -m 0644 i3.welcome $(DESTDIR)$(SYSCONFDIR)/i3/welcome diff --git a/PACKAGE-MAINTAINER b/PACKAGE-MAINTAINER index 3d9a8e18..269ce0fd 100644 --- a/PACKAGE-MAINTAINER +++ b/PACKAGE-MAINTAINER @@ -10,15 +10,21 @@ packages for them. Please make sure the manpage for i3 will be properly created and installed in your package. -Also please provide the path to a suitable terminal emulator which is installed -as a dependency of your package (e.g. urxvt). On systems which have a special -commend to launch the best available terminal emulator, please use this one -(e.g. x-terminal-emulator on debian). +Please check the i3-sensible-{editor,pager,terminal} scripts and modify them if +necessary. The former two provide fallbacks in case $PAGER or $EDITOR is not +set (which might be more common than you think, because they have to be set in +~/.xsession, not in the shell configuration!) while the latter tries to launch +a terminal emulator. The scripts are most prominently used in i3-nagbar, which +alerts the user when the configuration is broken for some reason. Also, +i3-sensible-terminal is used in the default configuration. -On debian, this looks like this: +If your distribution has a mechanism to get the preferred terminal, such as the +x-terminal-emulator symlink in Debian, please use it in i3-sensible-terminal. + +On debian, compilation and installing the manpages looks like this: # Compilation - $(MAKE) TERM_EMU=x-terminal-emulator + $(MAKE) $(MAKE) -C man # Installation diff --git a/common.mk b/common.mk index 62eb9958..78033946 100644 --- a/common.mk +++ b/common.mk @@ -12,7 +12,6 @@ ifndef SYSCONFDIR SYSCONFDIR=$(PREFIX)/etc endif endif -TERM_EMU=xterm # The escaping is absurd, but we need to escape for shell, sed, make, define GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1), branch $(shell [ -f $(TOPDIR)/.git/HEAD ] && sed 's/ref: refs\/heads\/\(.*\)/\\\\\\"\1\\\\\\"/g' $(TOPDIR)/.git/HEAD || echo 'unknown'))" VERSION:=$(shell git describe --tags --abbrev=0) diff --git a/i3-sensible-editor b/i3-sensible-editor new file mode 100755 index 00000000..dffe00d5 --- /dev/null +++ b/i3-sensible-editor @@ -0,0 +1,14 @@ +#!/bin/sh +# This script tries to exec an editor by trying some known editors if $EDITOR is +# not set. +# +# Distributions/packagers can enhance this script with a +# distribution-specific mechanism to find the preferred pager. +which $VISUAL >/dev/null && exec $VISUAL "$@" +which $EDITOR >/dev/null && exec $EDITOR "$@" + +# Hopefully one of these is installed (no flamewars about preference please!): +which nano >/dev/null && exec nano "$@" +which vim >/dev/null && exec vim "$@" +which vi >/dev/null && exec vi "$@" +which emacs >/dev/null && exec emacs "$@" diff --git a/i3-sensible-pager b/i3-sensible-pager new file mode 100755 index 00000000..5af8d6b4 --- /dev/null +++ b/i3-sensible-pager @@ -0,0 +1,15 @@ +#!/bin/sh +# This script tries to exec a pager by trying some known pagers if $PAGER is +# not set. +# +# Distributions/packagers can enhance this script with a +# distribution-specific mechanism to find the preferred pager. +which $PAGER >/dev/null && exec $PAGER "$@" + +# Hopefully one of these is installed: +which most >/dev/null && exec most "$@" +which less >/dev/null && exec less "$@" +# we don't use 'more' because it will exit if the file is 'too short' + +# If no pager is installed, try an editor +exec i3-sensible-editor "$@" diff --git a/i3-sensible-terminal b/i3-sensible-terminal new file mode 100755 index 00000000..28e60623 --- /dev/null +++ b/i3-sensible-terminal @@ -0,0 +1,15 @@ +#!/bin/sh +# This script tries to exec a terminal emulator by trying some known terminal +# emulators. +# +# 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. +# Please don't touch the first line, though: +which $TERMINAL >/dev/null && exec $TERMINAL "$@" + +# Hopefully one of these is installed: +which xterm >/dev/null && exec xterm "$@" +which urxvt >/dev/null && exec urxvt "$@" +which rxvt >/dev/null && exec rxvt "$@" +which roxterm >/dev/null && exec roxterm "$@" diff --git a/i3.config b/i3.config index 52c4a7e1..7d7fce33 100644 --- a/i3.config +++ b/i3.config @@ -16,7 +16,7 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 floating_modifier Mod1 # start a terminal -bindsym Mod1+Return exec urxvt +bindsym Mod1+Return exec i3-sensible-terminal # kill focused window bindsym Mod1+Shift+q kill diff --git a/i3.config.keycodes b/i3.config.keycodes index 17e7483b..0f1112db 100644 --- a/i3.config.keycodes +++ b/i3.config.keycodes @@ -17,7 +17,7 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 floating_modifier $mod # start a terminal -bindcode $mod+36 exec urxvt +bindcode $mod+36 exec i3-sensible-terminal # kill focused window bindcode $mod+Shift+24 kill diff --git a/src/cfgparse.y b/src/cfgparse.y index 12a60f4d..9a417f2a 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -276,9 +276,9 @@ static void start_configerror_nagbar(const char *config_path) { if (configerror_pid == 0) { char *editaction, *pageraction; - if (asprintf(&editaction, TERM_EMU " -e sh -c \"${EDITOR:-vi} \"%s\" && i3-msg reload\"", config_path) == -1) + if (asprintf(&editaction, "i3-sensible-terminal -e sh -c \"i3-sensible-editor \\\"%s\\\" && i3-msg reload\"", config_path) == -1) exit(1); - if (asprintf(&pageraction, TERM_EMU " -e sh -c \"${PAGER:-less} \"%s\"\"", errorfilename) == -1) + if (asprintf(&pageraction, "i3-sensible-terminal -e i3-sensible-pager \"%s\"", errorfilename) == -1) exit(1); char *argv[] = { NULL, /* will be replaced by the executable path */ From 683bfa58d52ca698b286963bc4c8da71d1b37f84 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 26 Sep 2011 19:36:27 +0100 Subject: [PATCH 090/333] =?UTF-8?q?tests:=20Use=20AnyEvent::I3=E2=80=99s?= =?UTF-8?q?=20get=5Fmarks=20(requires=20AE::I3=20>=3D0.08)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We also don’t bother with timeouts anymore. It’s expected to run the tests with a sufficiently recent version of i3. The tests will just hang if it doesn’t work. --- testcases/t/73-get-marks.t | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/testcases/t/73-get-marks.t b/testcases/t/73-get-marks.t index 86481747..e74c233a 100644 --- a/testcases/t/73-get-marks.t +++ b/testcases/t/73-get-marks.t @@ -5,21 +5,8 @@ # use i3test; -# TODO: this will be available in AnyEvent::I3 soon sub get_marks { - my $i3 = i3(get_socket_path()); - $i3->connect->recv; - my $cv = AnyEvent->condvar; - my $msg = $i3->message(5); - my $t; - $msg->cb(sub { - my ($_cv) = @_; - $cv->send($_cv->recv); - }); - $t = AnyEvent->timer(after => 2, cb => sub { - $cv->croak('timeout while waiting for the marks'); - }); - return $cv->recv; + return i3(get_socket_path())->get_marks->recv; } ############################################################## From c0d198bbc9ef02fa647b42f1007a3f51c7f5d779 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 29 Sep 2011 21:04:39 +0100 Subject: [PATCH 091/333] Include i3-sensible-* in 'make dist' --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index bec6b64b..4289eb4d 100644 --- a/Makefile +++ b/Makefile @@ -96,7 +96,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 i3.config.keycodes DEPENDS GOALS LICENSE PACKAGE-MAINTAINER TODO RELEASE-NOTES-${VERSION} i3.config i3.desktop i3.welcome pseudo-doc.doxygen i3-wsbar Makefile i3-${VERSION} + cp i3-migrate-config-to-v4 i3-sensible-* i3.config.keycodes DEPENDS GOALS LICENSE PACKAGE-MAINTAINER TODO RELEASE-NOTES-${VERSION} i3.config i3.desktop i3.welcome pseudo-doc.doxygen i3-wsbar Makefile i3-${VERSION} cp -r src i3-msg i3-nagbar i3-config-wizard i3bar yajl-fallback include man i3-${VERSION} # Only copy toplevel documentation (important stuff) mkdir i3-${VERSION}/docs From 3629934b0ae2f3ed8d2453dc2ed47ed652b90eca Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 30 Sep 2011 20:31:32 +0100 Subject: [PATCH 092/333] Check for PCRE version and disable PCRE_UCP for <= 8.10 --- common.mk | 4 ++++ src/regex.c | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/common.mk b/common.mk index 78033946..b8ceea3b 100644 --- a/common.mk +++ b/common.mk @@ -53,6 +53,10 @@ CPPFLAGS += -DI3_VERSION=\"${GIT_VERSION}\" CPPFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\" CPPFLAGS += -DTERM_EMU=\"$(TERM_EMU)\" +ifeq ($(shell pkg-config --atleast-version=8.10 libpcre && echo 1),1) +CPPFLAGS += -DPCRE_HAS_UCP=1 +endif + LIBS += -lm LIBS += $(call ldflags_for_lib, xcb-event, xcb-event) LIBS += $(call ldflags_for_lib, xcb-keysyms, xcb-keysyms) diff --git a/src/regex.c b/src/regex.c index f419e4bb..64a2f3a4 100644 --- a/src/regex.c +++ b/src/regex.c @@ -25,9 +25,12 @@ struct regex *regex_new(const char *pattern) { struct regex *re = scalloc(sizeof(struct regex)); re->pattern = sstrdup(pattern); + int options = PCRE_UTF8; +#ifdef PCRE_HAS_UCP /* We use PCRE_UCP so that \B, \b, \D, \d, \S, \s, \W, \w and some POSIX * character classes play nicely with Unicode */ - int options = PCRE_UCP | PCRE_UTF8; + options |= PCRE_UCP; +#endif while (!(re->regex = pcre_compile2(pattern, options, &errorcode, &error, &offset, NULL))) { /* If the error is that PCRE was not compiled with UTF-8 support we * disable it and try again */ From 679bceccc46e97eee1dbcd850373efbfb68dab89 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 2 Oct 2011 13:18:12 +0100 Subject: [PATCH 093/333] remove obsolete comment --- src/main.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main.c b/src/main.c index cc60d69c..d51c8c65 100644 --- a/src/main.c +++ b/src/main.c @@ -172,7 +172,6 @@ static void i3_exit() { } int main(int argc, char *argv[]) { - //parse_cmd("[ foo ] attach, attach ; focus"); int screens; char *override_configpath = NULL; bool autostart = true; From e73812802cb47fa7212ba5d9423ac780941b76ed Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 2 Oct 2011 16:03:09 +0100 Subject: [PATCH 094/333] makefile: make SUBDIRS constant --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 4289eb4d..74114986 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ else UNUSED:=$(shell $(MAKE) loglevels.h) endif -SUBDIRS=i3-msg i3-input i3-nagbar i3-config-wizard i3bar +SUBDIRS:=i3-msg i3-input i3-nagbar i3-config-wizard i3bar # Depend on the specific file (.c for each .o) and on all headers src/%.o: src/%.c ${HEADERS} From 4f6e58e250f868e87d247b1559f5aef4713616c3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 2 Oct 2011 16:04:18 +0100 Subject: [PATCH 095/333] Makefiles: prefix compilation messages (for parallel builds) --- Makefile | 14 +++++++------- i3-config-wizard/Makefile | 10 +++++----- i3-input/Makefile | 6 +++--- i3-msg/Makefile | 6 +++--- i3-nagbar/Makefile | 6 +++--- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Makefile b/Makefile index 74114986..0c8227ea 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ SUBDIRS:=i3-msg i3-input i3-nagbar i3-config-wizard i3bar # Depend on the specific file (.c for each .o) and on all headers src/%.o: src/%.c ${HEADERS} - echo "CC $<" + echo "[i3] CC $<" $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="((uint64_t)1 << $(shell awk '/$(shell basename $< .c)/ { print NR; exit 0; }' loglevels.tmp))" -c -o $@ $< all: i3 subdirs @@ -39,7 +39,7 @@ subdirs: done loglevels.h: - echo "LOGLEVELS" + echo "[i3] LOGLEVELS" for file in $$(ls src/*.c src/*.y src/*.l | grep -v 'cfgparse.\(tab\|yy\).c'); \ do \ echo $$(basename $$file .c); \ @@ -51,29 +51,29 @@ loglevels.h: echo "};") > include/loglevels.h; src/cfgparse.yy.o: src/cfgparse.l src/cfgparse.y.o ${HEADERS} - echo "LEX $<" + echo "[i3] LEX $<" flex -i -o$(@:.o=.c) $< $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c) src/cmdparse.yy.o: src/cmdparse.l src/cmdparse.y.o ${HEADERS} - echo "LEX $<" + echo "[i3] LEX $<" flex -Pcmdyy -i -o$(@:.o=.c) $< $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cmdparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c) src/cfgparse.y.o: src/cfgparse.y ${HEADERS} - echo "YACC $<" + echo "[i3] YACC $<" bison --debug --verbose -b $(basename $< .y) -d $< $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c) src/cmdparse.y.o: src/cmdparse.y ${HEADERS} - echo "YACC $<" + echo "[i3] YACC $<" bison -p cmdyy --debug --verbose -b $(basename $< .y) -d $< $(CC) $(CPPFLAGS) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cmdparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c) install: all - echo "INSTALL" + echo "[i3] INSTALL" $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin $(INSTALL) -d -m 0755 $(DESTDIR)$(SYSCONFDIR)/i3 $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/include/i3 diff --git a/i3-config-wizard/Makefile b/i3-config-wizard/Makefile index 43c3a1ff..1a9638ec 100644 --- a/i3-config-wizard/Makefile +++ b/i3-config-wizard/Makefile @@ -10,28 +10,28 @@ HEADERS:=$(wildcard *.h) # Depend on the specific file (.c for each .o) and on all headers %.o: %.c ${HEADERS} - echo "CC $<" + echo "[i3-config-wizard] CC $<" $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< all: i3-config-wizard i3-config-wizard: cfgparse.y.o cfgparse.yy.o ${FILES} - echo "LINK i3-config-wizard" + echo "[i3-config-wizard] LINK i3-config-wizard" $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) cfgparse.yy.o: cfgparse.l cfgparse.y.o ${HEADERS} - echo "LEX $<" + echo "[i3-config-wizard] LEX $<" flex -i -o$(@:.o=.c) $< $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(@:.o=.c) cfgparse.y.o: cfgparse.y ${HEADERS} - echo "YACC $<" + echo "[i3-config-wizard] YACC $<" bison --debug --verbose -b $(basename $< .y) -d $< $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(<:.y=.tab.c) install: all - echo "INSTALL" + echo "[i3-config-wizard] INSTALL" $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin $(INSTALL) -m 0755 i3-config-wizard $(DESTDIR)$(PREFIX)/bin/ diff --git a/i3-input/Makefile b/i3-input/Makefile index 45653dad..5929b4c4 100644 --- a/i3-input/Makefile +++ b/i3-input/Makefile @@ -9,17 +9,17 @@ HEADERS=$(wildcard *.h) # Depend on the specific file (.c for each .o) and on all headers %.o: %.c ${HEADERS} - echo "CC $<" + echo "[i3-input] CC $<" $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< all: i3-input i3-input: ${FILES} - echo "LINK i3-input" + echo "[i3-input] LINK i3-input" $(CC) $(LDFLAGS) -o $@ ${FILES} $(LIBS) install: all - echo "INSTALL" + echo "[i3-input] INSTALL" $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin $(INSTALL) -m 0755 i3-input $(DESTDIR)$(PREFIX)/bin/ diff --git a/i3-msg/Makefile b/i3-msg/Makefile index 1b7c1c04..617df932 100644 --- a/i3-msg/Makefile +++ b/i3-msg/Makefile @@ -11,17 +11,17 @@ HEADERS=$(wildcard *.h) # Depend on the specific file (.c for each .o) and on all headers %.o: %.c ${HEADERS} - echo "CC $<" + echo "[i3-msg] CC $<" $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< all: i3-msg i3-msg: ${FILES} - echo "LINK i3-msg" + echo "[i3-msg] LINK i3-msg" $(CC) $(LDFLAGS) -o i3-msg ${FILES} $(LIBS) install: all - echo "INSTALL" + echo "[i3-msg] INSTALL" $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin $(INSTALL) -m 0755 i3-msg $(DESTDIR)$(PREFIX)/bin/ diff --git a/i3-nagbar/Makefile b/i3-nagbar/Makefile index 933ae76c..fed223db 100644 --- a/i3-nagbar/Makefile +++ b/i3-nagbar/Makefile @@ -9,17 +9,17 @@ HEADERS=$(wildcard *.h) # Depend on the specific file (.c for each .o) and on all headers %.o: %.c ${HEADERS} - echo "CC $<" + echo "[i3-nagbar] CC $<" $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< all: i3-nagbar i3-nagbar: ${FILES} - echo "LINK i3-nagbar" + echo "[i3-nagbar] LINK i3-nagbar" $(CC) $(LDFLAGS) -o $@ ${FILES} $(LIBS) install: all - echo "INSTALL" + echo "[i3-nagbar] INSTALL" $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin $(INSTALL) -m 0755 i3-nagbar $(DESTDIR)$(PREFIX)/bin/ From 094c26556e06af16aa7ef1a5d6a04930053e4154 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 2 Oct 2011 16:11:30 +0100 Subject: [PATCH 096/333] Introduce libi3, an *internal* library to eliminate code duplication --- Makefile | 10 ++++--- common.mk | 1 + libi3/Makefile | 26 ++++++++++++++++++ libi3/README | 16 +++++++++++ libi3/get_socket_path.c | 59 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 libi3/Makefile create mode 100644 libi3/README create mode 100644 libi3/get_socket_path.c diff --git a/Makefile b/Makefile index 0c8227ea..fedd6ec3 100644 --- a/Makefile +++ b/Makefile @@ -27,9 +27,12 @@ src/%.o: src/%.c ${HEADERS} all: i3 subdirs -i3: src/cfgparse.y.o src/cfgparse.yy.o src/cmdparse.y.o src/cmdparse.yy.o ${FILES} - echo "LINK i3" - $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) +i3: libi3/libi3.a src/cfgparse.y.o src/cfgparse.yy.o src/cmdparse.y.o src/cmdparse.yy.o ${FILES} + echo "[i3] LINK i3" + $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS) + +libi3/%.a: + $(MAKE) -C libi3 subdirs: for dir in $(SUBDIRS); do \ @@ -120,6 +123,7 @@ dist: distclean clean: rm -f src/*.o src/*.gcno src/cfgparse.tab.{c,h} src/cfgparse.yy.c src/cfgparse.{output,dot} src/cmdparse.tab.{c,h} src/cmdparse.yy.c src/cmdparse.{output,dot} loglevels.tmp include/loglevels.h (which lcov >/dev/null 2>&1 && lcov -d . --zerocounters) || true + $(MAKE) -C libi3 clean $(MAKE) -C docs clean $(MAKE) -C man clean for dir in $(SUBDIRS); do \ diff --git a/common.mk b/common.mk index b8ceea3b..53094fb3 100644 --- a/common.mk +++ b/common.mk @@ -58,6 +58,7 @@ CPPFLAGS += -DPCRE_HAS_UCP=1 endif LIBS += -lm +LIBS += -L $(TOPDIR)/libi3 -li3 LIBS += $(call ldflags_for_lib, xcb-event, xcb-event) LIBS += $(call ldflags_for_lib, xcb-keysyms, xcb-keysyms) ifeq ($(shell pkg-config --exists xcb-util || echo 1),1) diff --git a/libi3/Makefile b/libi3/Makefile new file mode 100644 index 00000000..e9efcf7b --- /dev/null +++ b/libi3/Makefile @@ -0,0 +1,26 @@ +# Default value so one can compile i3-msg standalone +TOPDIR=.. + +include $(TOPDIR)/common.mk + +CFLAGS += -I$(TOPDIR)/include + +# Depend on the object files of all source-files in src/*.c and on all header files +FILES=$(patsubst %.c,%.o,$(wildcard *.c)) +HEADERS=$(wildcard *.h) + +# Depend on the specific file (.c for each .o) and on all headers +%.o: %.c ${HEADERS} + echo "[libi3] CC $<" + $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< + +all: libi3.a + +libi3.a: ${FILES} + echo "[libi3] AR libi3.a" + ar rcs libi3.a ${FILES} + +clean: + rm -f *.o libi3.a + +distclean: clean diff --git a/libi3/README b/libi3/README new file mode 100644 index 00000000..740ef22e --- /dev/null +++ b/libi3/README @@ -0,0 +1,16 @@ +Introduction +============ + +libi3 is an *INTERNAL* library which contains functions that i3 and related +tools (i3-msg, i3-input, i3-nagbar, i3-config-wizard, i3bar) use. + +It is NOT to be used by other programs. + +Structure +========= + +Every function gets its own .c file, which in turn gets compiled into an .o +object file. Afterwards, all .o files are archived into one static library +(libi3.a). This library will be linked into all i3 binaries. The linker is able +to eliminate unused .o files when linking, so only the functions which you +actually use will be included in the corresponding binary. diff --git a/libi3/get_socket_path.c b/libi3/get_socket_path.c new file mode 100644 index 00000000..9e02ec51 --- /dev/null +++ b/libi3/get_socket_path.c @@ -0,0 +1,59 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009-2011 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ +#include +#include +#include +#include + +#include +#include + +/* + * Try to get the socket path from X11 and return NULL if it doesn’t work. + * + * The memory for the socket path is dynamically allocated and has to be + * free()d by the caller. + * + */ +char *socket_path_from_x11() { + xcb_connection_t *conn; + xcb_intern_atom_cookie_t atom_cookie; + xcb_intern_atom_reply_t *atom_reply; + int screen; + char *socket_path; + + if ((conn = xcb_connect(NULL, &screen)) == NULL || + xcb_connection_has_error(conn)) + return NULL; + + atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH"); + + xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen); + xcb_window_t root = root_screen->root; + + atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL); + if (atom_reply == NULL) + return NULL; + + xcb_get_property_cookie_t prop_cookie; + xcb_get_property_reply_t *prop_reply; + prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom, + XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX); + prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); + if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0) + return NULL; + if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply), + (char*)xcb_get_property_value(prop_reply)) == -1) + return NULL; + xcb_disconnect(conn); + return socket_path; +} + From 6af8b0941cff8578303cfc96b1e40b3e0ff95b68 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 2 Oct 2011 16:11:55 +0100 Subject: [PATCH 097/333] i3-msg: use socket_path_from_x11 from libi3 --- i3-msg/main.c | 37 +------------------------------------ 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/i3-msg/main.c b/i3-msg/main.c index 2d7cef0e..124663d6 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -32,46 +32,11 @@ #include #include +#include "libi3.h" #include static char *socket_path; -/* - * Try to get the socket path from X11 and return NULL if it doesn’t work. - * As i3-msg is a short-running tool, we don’t bother with cleaning up the - * connection and leave it up to the operating system on exit. - * - */ -static char *socket_path_from_x11() { - xcb_connection_t *conn; - int screen; - if ((conn = xcb_connect(NULL, &screen)) == NULL || - xcb_connection_has_error(conn)) - return NULL; - xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen); - xcb_window_t root = root_screen->root; - - xcb_intern_atom_cookie_t atom_cookie; - xcb_intern_atom_reply_t *atom_reply; - - atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH"); - atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL); - if (atom_reply == NULL) - return NULL; - - xcb_get_property_cookie_t prop_cookie; - xcb_get_property_reply_t *prop_reply; - prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom, - XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX); - prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); - if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0) - return NULL; - if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply), - (char*)xcb_get_property_value(prop_reply)) == -1) - return NULL; - return socket_path; -} - /* * Formats a message (payload) of the given size and type and sends it to i3 via * the given socket file descriptor. From 23796ea90075855ef6522e4fee8bc3e6777514c2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 2 Oct 2011 16:12:10 +0100 Subject: [PATCH 098/333] Implement i3 --get-socketpath, useful for IPC scripts In order to not depend on X11 just for getting the socket paths, scripts or other programs can now use i3 --get-socketpath. Since i3 must be present on the computer anyways, this saves one dependency :). --- include/all.h | 1 + src/main.c | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/include/all.h b/include/all.h index 9c08ebef..38cda89c 100644 --- a/include/all.h +++ b/include/all.h @@ -65,5 +65,6 @@ #include "ewmh.h" #include "assignments.h" #include "regex.h" +#include "libi3.h" #endif diff --git a/src/main.c b/src/main.c index d51c8c65..19c45681 100644 --- a/src/main.c +++ b/src/main.c @@ -189,6 +189,7 @@ int main(int argc, char *argv[]) { {"restart", required_argument, 0, 0}, {"force-xinerama", no_argument, 0, 0}, {"disable-signalhandler", no_argument, 0, 0}, + {"get-socketpath", no_argument, 0, 0}, {0, 0, 0, 0} }; int option_index = 0, opt; @@ -247,6 +248,14 @@ int main(int argc, char *argv[]) { } else if (strcmp(long_options[option_index].name, "disable-signalhandler") == 0) { disable_signalhandler = true; break; + } else if (strcmp(long_options[option_index].name, "get-socketpath") == 0) { + char *socket_path = socket_path_from_x11(); + if (socket_path) { + printf("%s\n", socket_path); + return 0; + } + + return 1; } else if (strcmp(long_options[option_index].name, "restart") == 0) { FREE(layout_path); layout_path = sstrdup(optarg); From e3ba716d42df2e52dcd1e1763ba51784053c2ad3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 2 Oct 2011 16:36:49 +0100 Subject: [PATCH 099/333] Makefile: include libi3/ in 'dist' --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index fedd6ec3..54d8464f 100644 --- a/Makefile +++ b/Makefile @@ -100,7 +100,7 @@ dist: distclean [ ! -e i3-${VERSION}.tar.bz2 ] || rm i3-${VERSION}.tar.bz2 mkdir i3-${VERSION} cp i3-migrate-config-to-v4 i3-sensible-* i3.config.keycodes DEPENDS GOALS LICENSE PACKAGE-MAINTAINER TODO RELEASE-NOTES-${VERSION} i3.config i3.desktop i3.welcome pseudo-doc.doxygen i3-wsbar Makefile i3-${VERSION} - cp -r src i3-msg i3-nagbar i3-config-wizard i3bar yajl-fallback include man i3-${VERSION} + cp -r src libi3 i3-msg i3-nagbar i3-config-wizard i3bar yajl-fallback include man i3-${VERSION} # Only copy toplevel documentation (important stuff) mkdir i3-${VERSION}/docs # Pre-generate documentation From 8f5dd749c364a38eb79d013e6b10cbd7d847f361 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 2 Oct 2011 16:41:26 +0100 Subject: [PATCH 100/333] Include libi3.h (Thanks fernandotcl) --- include/libi3.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 include/libi3.h diff --git a/include/libi3.h b/include/libi3.h new file mode 100644 index 00000000..3883ba82 --- /dev/null +++ b/include/libi3.h @@ -0,0 +1,17 @@ +/* + * vim:ts=4:sw=4:expandtab + */ + +#ifndef _LIBI3_H +#define _LIBI3_H + +/** + * Try to get the socket path from X11 and return NULL if it doesn’t work. + * + * The memory for the socket path is dynamically allocated and has to be + * free()d by the caller. + * + */ +char *socket_path_from_x11(); + +#endif From 501dc36b98c7e02cb799e0299216f8f95d45a18c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 2 Oct 2011 18:08:49 +0100 Subject: [PATCH 101/333] move sstrdup, scalloc, smalloc, srealloc to libi3, improve error messages --- include/libi3.h | 28 ++++++++++++++++++++++++++ include/util.h | 28 -------------------------- libi3/safewrappers.c | 47 ++++++++++++++++++++++++++++++++++++++++++++ src/cfgparse.l | 1 + src/cmdparse.l | 1 + src/util.c | 30 ---------------------------- 6 files changed, 77 insertions(+), 58 deletions(-) create mode 100644 libi3/safewrappers.c diff --git a/include/libi3.h b/include/libi3.h index 3883ba82..a675d11e 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -14,4 +14,32 @@ */ char *socket_path_from_x11(); +/** + * Safe-wrapper around malloc which exits if malloc returns NULL (meaning that + * there is no more memory available) + * + */ +void *smalloc(size_t size); + +/** + * Safe-wrapper around calloc which exits if malloc returns NULL (meaning that + * there is no more memory available) + * + */ +void *scalloc(size_t size); + +/** + * Safe-wrapper around realloc which exits if realloc returns NULL (meaning + * that there is no more memory available). + * + */ +void *srealloc(void *ptr, size_t size); + +/** + * Safe-wrapper around strdup which exits if malloc returns NULL (meaning that + * there is no more memory available) + * + */ +char *sstrdup(const char *str); + #endif diff --git a/include/util.h b/include/util.h index edc51d81..7c7b819a 100644 --- a/include/util.h +++ b/include/util.h @@ -66,34 +66,6 @@ Rect rect_add(Rect a, Rect b); */ bool update_if_necessary(uint32_t *destination, const uint32_t new_value); -/** - * Safe-wrapper around malloc which exits if malloc returns NULL (meaning that - * there is no more memory available) - * - */ -void *smalloc(size_t size); - -/** - * Safe-wrapper around calloc which exits if malloc returns NULL (meaning that - * there is no more memory available) - * - */ -void *scalloc(size_t size); - -/** - * Safe-wrapper around realloc which exits if realloc returns NULL (meaning - * that there is no more memory available). - * - */ -void *srealloc(void *ptr, size_t size); - -/** - * Safe-wrapper around strdup which exits if malloc returns NULL (meaning that - * there is no more memory available) - * - */ -char *sstrdup(const char *str); - /** * 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 diff --git a/libi3/safewrappers.c b/libi3/safewrappers.c new file mode 100644 index 00000000..82311fe3 --- /dev/null +++ b/libi3/safewrappers.c @@ -0,0 +1,47 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009-2011 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ +#include +#include +#include + + +/* + * The s* functions (safe) are wrappers around malloc, strdup, …, which exits if one of + * the called functions returns NULL, meaning that there is no more memory available + * + */ +void *smalloc(size_t size) { + void *result = malloc(size); + if (result == NULL) + err(EXIT_FAILURE, "malloc(%zd)", size); + return result; +} + +void *scalloc(size_t size) { + void *result = calloc(size, 1); + if (result == NULL) + err(EXIT_FAILURE, "calloc(%zd)", size); + return result; +} + +void *srealloc(void *ptr, size_t size) { + void *result = realloc(ptr, size); + if (result == NULL && size > 0) + err(EXIT_FAILURE, "realloc(%zd)", size); + return result; +} + +char *sstrdup(const char *str) { + char *result = strdup(str); + if (result == NULL) + err(EXIT_FAILURE, "strdup()"); + return result; +} diff --git a/src/cfgparse.l b/src/cfgparse.l index dad5a915..49714401 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -17,6 +17,7 @@ #include "config.h" #include "log.h" #include "util.h" +#include "libi3.h" #include "cfgparse.tab.h" diff --git a/src/cmdparse.l b/src/cmdparse.l index 968b7e52..f6b132ca 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -19,6 +19,7 @@ #include "config.h" #include "util.h" +#include "libi3.h" int cmdyycolumn = 1; diff --git a/src/util.c b/src/util.c index 2d6c3e14..30371bcd 100644 --- a/src/util.c +++ b/src/util.c @@ -58,36 +58,6 @@ bool update_if_necessary(uint32_t *destination, const uint32_t new_value) { return ((*destination = new_value) != old_value); } -/* - * The s* functions (safe) are wrappers around malloc, strdup, …, which exits if one of - * the called functions returns NULL, meaning that there is no more memory available - * - */ -void *smalloc(size_t size) { - void *result = malloc(size); - exit_if_null(result, "Error: out of memory (malloc(%zd))\n", size); - return result; -} - -void *scalloc(size_t size) { - void *result = calloc(size, 1); - exit_if_null(result, "Error: out of memory (calloc(%zd))\n", size); - return result; -} - -void *srealloc(void *ptr, size_t size) { - void *result = realloc(ptr, size); - if (result == NULL && size > 0) - die("Error: out memory (realloc(%zd))\n", size); - return result; -} - -char *sstrdup(const char *str) { - char *result = strdup(str); - exit_if_null(result, "Error: out of memory (strdup())\n"); - return result; -} - /* * 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), From ff86852453be12e90c4ff36a6952d50a4937f0a7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 2 Oct 2011 18:11:01 +0100 Subject: [PATCH 102/333] i3-msg: use smalloc, sstrdup from libi3 --- i3-msg/main.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/i3-msg/main.c b/i3-msg/main.c index 124663d6..23ffd414 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -97,9 +97,7 @@ static void ipc_recv_message(int sockfd, uint32_t message_type, errx(EXIT_FAILURE, "unexpected reply type (got %d, expected %d)", *((uint32_t*)walk), message_type); walk += sizeof(uint32_t); - *reply = malloc(*reply_length); - if ((*reply) == NULL) - err(EXIT_FAILURE, "malloc() failed"); + *reply = smalloc(*reply_length); to_read = *reply_length; read_bytes = 0; @@ -135,7 +133,7 @@ int main(int argc, char *argv[]) { if (o == 's') { if (socket_path != NULL) free(socket_path); - socket_path = strdup(optarg); + socket_path = sstrdup(optarg); } else if (o == 't') { if (strcasecmp(optarg, "command") == 0) message_type = I3_IPC_MESSAGE_TYPE_COMMAND; @@ -169,15 +167,14 @@ int main(int argc, char *argv[]) { /* Fall back to the default socket path */ if (socket_path == NULL) - socket_path = strdup("/tmp/i3-ipc.sock"); + socket_path = sstrdup("/tmp/i3-ipc.sock"); /* Use all arguments, separated by whitespace, as payload. * This way, you don’t have to do i3-msg 'mark foo', you can use * i3-msg mark foo */ while (optind < argc) { if (!payload) { - if (!(payload = strdup(argv[optind]))) - err(EXIT_FAILURE, "strdup(argv[optind])"); + payload = sstrdup(argv[optind]); } else { char *both; if (asprintf(&both, "%s %s", payload, argv[optind]) == -1) From 046cf995855c0bdaa5885d376fc77cecf607b58f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 2 Oct 2011 18:33:10 +0100 Subject: [PATCH 103/333] Move ipc_send_message and ipc_recv_message to libi3 Make i3-msg and src/ipc.c use it --- i3-msg/main.c | 89 ++++++---------------------------------- include/libi3.h | 25 +++++++++++ libi3/ipc_recv_message.c | 81 ++++++++++++++++++++++++++++++++++++ libi3/ipc_send_message.c | 53 ++++++++++++++++++++++++ src/ipc.c | 49 ++++------------------ 5 files changed, 179 insertions(+), 118 deletions(-) create mode 100644 libi3/ipc_recv_message.c create mode 100644 libi3/ipc_send_message.c diff --git a/i3-msg/main.c b/i3-msg/main.c index 23ffd414..5bc35b88 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -10,7 +10,10 @@ * i3-msg/main.c: Utility which sends messages to a running i3-instance using * IPC via UNIX domain sockets. * - * This serves as an example for how to send your own messages to i3. + * This (in combination with libi3/ipc_send_message.c and + * libi3/ipc_recv_message.c) serves as an example for how to send your own + * messages to i3. + * * Additionally, it’s even useful sometimes :-). * */ @@ -37,80 +40,6 @@ static char *socket_path; -/* - * Formats a message (payload) of the given size and type and sends it to i3 via - * the given socket file descriptor. - * - */ -static void ipc_send_message(int sockfd, uint32_t message_size, - uint32_t message_type, uint8_t *payload) { - int buffer_size = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t) + message_size; - char msg[buffer_size]; - char *walk = msg; - - strcpy(walk, I3_IPC_MAGIC); - walk += strlen(I3_IPC_MAGIC); - memcpy(walk, &message_size, sizeof(uint32_t)); - walk += sizeof(uint32_t); - memcpy(walk, &message_type, sizeof(uint32_t)); - walk += sizeof(uint32_t); - memcpy(walk, payload, message_size); - - int sent_bytes = 0; - int bytes_to_go = buffer_size; - while (sent_bytes < bytes_to_go) { - int n = write(sockfd, msg + sent_bytes, bytes_to_go); - if (n == -1) - err(EXIT_FAILURE, "write() failed"); - - sent_bytes += n; - bytes_to_go -= n; - } -} - -static void ipc_recv_message(int sockfd, uint32_t message_type, - uint32_t *reply_length, uint8_t **reply) { - /* Read the message header first */ - uint32_t to_read = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t); - char msg[to_read]; - char *walk = msg; - - uint32_t read_bytes = 0; - while (read_bytes < to_read) { - int n = read(sockfd, msg + read_bytes, to_read); - if (n == -1) - err(EXIT_FAILURE, "read() failed"); - if (n == 0) - errx(EXIT_FAILURE, "received EOF instead of reply"); - - read_bytes += n; - to_read -= n; - } - - if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) - errx(EXIT_FAILURE, "invalid magic in reply"); - - walk += strlen(I3_IPC_MAGIC); - *reply_length = *((uint32_t*)walk); - walk += sizeof(uint32_t); - if (*((uint32_t*)walk) != message_type) - errx(EXIT_FAILURE, "unexpected reply type (got %d, expected %d)", *((uint32_t*)walk), message_type); - walk += sizeof(uint32_t); - - *reply = smalloc(*reply_length); - - to_read = *reply_length; - read_bytes = 0; - while (read_bytes < to_read) { - int n = read(sockfd, *reply + read_bytes, to_read); - if (n == -1) - err(EXIT_FAILURE, "read() failed"); - - read_bytes += n; - to_read -= n; - } -} - int main(int argc, char *argv[]) { socket_path = getenv("I3SOCK"); int o, option_index = 0; @@ -199,14 +128,20 @@ int main(int argc, char *argv[]) { if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) err(EXIT_FAILURE, "Could not connect to i3"); - ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t*)payload); + if (ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t*)payload) == -1) + err(EXIT_FAILURE, "IPC: write()"); if (quiet) return 0; uint32_t reply_length; uint8_t *reply; - ipc_recv_message(sockfd, message_type, &reply_length, &reply); + int ret; + if ((ret = ipc_recv_message(sockfd, message_type, &reply_length, &reply)) != 0) { + if (ret == -1) + err(EXIT_FAILURE, "IPC: read()"); + exit(1); + } printf("%.*s\n", reply_length, reply); free(reply); diff --git a/include/libi3.h b/include/libi3.h index a675d11e..079d160b 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -42,4 +42,29 @@ void *srealloc(void *ptr, size_t size); */ char *sstrdup(const char *str); +/** + * Formats a message (payload) of the given size and type and sends it to i3 via + * the given socket file descriptor. + * + * Returns -1 when write() fails, errno will remain. + * Returns 0 on success. + * + */ +int ipc_send_message(int sockfd, uint32_t message_size, + uint32_t message_type, const uint8_t *payload); + +/** + * Reads a message from the given socket file descriptor and stores its length + * (reply_length) as well as a pointer to its contents (reply). + * + * Returns -1 when read() fails, errno will remain. + * Returns -2 when the IPC protocol is violated (invalid magic, unexpected + * message type, EOF instead of a message). Additionally, the error will be + * printed to stderr. + * Returns 0 on success. + * + */ +int ipc_recv_message(int sockfd, uint32_t message_type, + uint32_t *reply_length, uint8_t **reply); + #endif diff --git a/libi3/ipc_recv_message.c b/libi3/ipc_recv_message.c new file mode 100644 index 00000000..47d6dea3 --- /dev/null +++ b/libi3/ipc_recv_message.c @@ -0,0 +1,81 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009-2011 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ +#include +#include +#include +#include +#include + +#include + +#include "libi3.h" + +/* + * Reads a message from the given socket file descriptor and stores its length + * (reply_length) as well as a pointer to its contents (reply). + * + * Returns -1 when read() fails, errno will remain. + * Returns -2 when the IPC protocol is violated (invalid magic, unexpected + * message type, EOF instead of a message). Additionally, the error will be + * printed to stderr. + * Returns 0 on success. + * + */ +int ipc_recv_message(int sockfd, uint32_t message_type, + uint32_t *reply_length, uint8_t **reply) { + /* Read the message header first */ + uint32_t to_read = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t); + char msg[to_read]; + char *walk = msg; + + uint32_t read_bytes = 0; + while (read_bytes < to_read) { + int n = read(sockfd, msg + read_bytes, to_read); + if (n == -1) + return -1; + if (n == 0) { + fprintf(stderr, "IPC: received EOF instead of reply\n"); + return -2; + } + + read_bytes += n; + to_read -= n; + } + + if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) { + fprintf(stderr, "IPC: invalid magic in reply\n"); + return -2; + } + + walk += strlen(I3_IPC_MAGIC); + *reply_length = *((uint32_t*)walk); + walk += sizeof(uint32_t); + if (*((uint32_t*)walk) != message_type) { + fprintf(stderr, "IPC: unexpected reply type (got %d, expected %d)\n", *((uint32_t*)walk), message_type); + return -2; + } + walk += sizeof(uint32_t); + + *reply = smalloc(*reply_length); + + to_read = *reply_length; + read_bytes = 0; + while (read_bytes < to_read) { + int n = read(sockfd, *reply + read_bytes, to_read); + if (n == -1) + return -1; + + read_bytes += n; + to_read -= n; + } + + return 0; +} diff --git a/libi3/ipc_send_message.c b/libi3/ipc_send_message.c new file mode 100644 index 00000000..ff395ada --- /dev/null +++ b/libi3/ipc_send_message.c @@ -0,0 +1,53 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009-2011 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ +#include +#include +#include +#include +#include + +#include + +/* + * Formats a message (payload) of the given size and type and sends it to i3 via + * the given socket file descriptor. + * + * Returns -1 when write() fails, errno will remain. + * Returns 0 on success. + * + */ +int ipc_send_message(int sockfd, uint32_t message_size, + uint32_t message_type, const uint8_t *payload) { + int buffer_size = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t) + message_size; + char msg[buffer_size]; + char *walk = msg; + + strncpy(walk, I3_IPC_MAGIC, buffer_size - 1); + walk += strlen(I3_IPC_MAGIC); + memcpy(walk, &message_size, sizeof(uint32_t)); + walk += sizeof(uint32_t); + memcpy(walk, &message_type, sizeof(uint32_t)); + walk += sizeof(uint32_t); + memcpy(walk, payload, message_size); + + int sent_bytes = 0; + int bytes_to_go = buffer_size; + while (sent_bytes < bytes_to_go) { + int n = write(sockfd, msg + sent_bytes, bytes_to_go); + if (n == -1) + return -1; + + sent_bytes += n; + bytes_to_go -= n; + } + + return 0; +} diff --git a/src/ipc.c b/src/ipc.c index 031ee9ab..eba778cd 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -72,35 +72,6 @@ static bool mkdirp(const char *path) { return result; } -static void ipc_send_message(int fd, const unsigned char *payload, - int message_type, int message_size) { - int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) + - sizeof(uint32_t) + message_size; - char msg[buffer_size]; - char *walk = msg; - - strncpy(walk, "i3-ipc", buffer_size - 1); - walk += strlen("i3-ipc"); - memcpy(walk, &message_size, sizeof(uint32_t)); - walk += sizeof(uint32_t); - memcpy(walk, &message_type, sizeof(uint32_t)); - walk += sizeof(uint32_t); - memcpy(walk, payload, message_size); - - int sent_bytes = 0; - int bytes_to_go = buffer_size; - while (sent_bytes < bytes_to_go) { - int n = write(fd, msg + sent_bytes, bytes_to_go); - if (n == -1) { - DLOG("write() failed: %s\n", strerror(errno)); - return; - } - - sent_bytes += n; - bytes_to_go -= n; - } -} - /* * Sends the specified event to all IPC clients which are currently connected * and subscribed to this kind of event. @@ -120,8 +91,7 @@ void ipc_send_event(const char *event, uint32_t message_type, const char *payloa if (!interested) continue; - ipc_send_message(current->fd, (const unsigned char*)payload, - message_type, strlen(payload)); + ipc_send_message(current->fd, strlen(payload), message_type, (const uint8_t*)payload); } } @@ -156,8 +126,7 @@ IPC_HANDLER(command) { /* If no reply was provided, we just use the default success message */ if (reply == NULL) reply = "{\"success\":true}"; - ipc_send_message(fd, (const unsigned char*)reply, - I3_IPC_REPLY_TYPE_COMMAND, strlen(reply)); + ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_COMMAND, (const uint8_t*)reply); FREE(save_reply); } @@ -339,7 +308,7 @@ IPC_HANDLER(tree) { #endif y(get_buf, &payload, &length); - ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_TREE, length); + ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_TREE, payload); y(free); } @@ -412,7 +381,7 @@ IPC_HANDLER(get_workspaces) { #endif y(get_buf, &payload, &length); - ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_WORKSPACES, length); + ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_WORKSPACES, payload); y(free); } @@ -470,7 +439,7 @@ IPC_HANDLER(get_outputs) { #endif y(get_buf, &payload, &length); - ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_OUTPUTS, length); + ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_OUTPUTS, payload); y(free); } @@ -502,7 +471,7 @@ IPC_HANDLER(get_marks) { #endif y(get_buf, &payload, &length); - ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_MARKS, length); + ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_MARKS, payload); y(free); } @@ -580,15 +549,13 @@ IPC_HANDLER(subscribe) { yajl_free_error(p, err); const char *reply = "{\"success\":false}"; - ipc_send_message(fd, (const unsigned char*)reply, - I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply)); + ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t*)reply); yajl_free(p); return; } yajl_free(p); const char *reply = "{\"success\":true}"; - ipc_send_message(fd, (const unsigned char*)reply, - I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply)); + ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t*)reply); } /* The index of each callback function corresponds to the numeric From 4243a4053e2af66230d5df92ebec9da9f9533e65 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 2 Oct 2011 18:55:15 +0100 Subject: [PATCH 104/333] Cleanup i3 --help --- src/main.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main.c b/src/main.c index 19c45681..ac05f26e 100644 --- a/src/main.c +++ b/src/main.c @@ -266,16 +266,21 @@ int main(int argc, char *argv[]) { default: fprintf(stderr, "Usage: %s [-c configfile] [-d loglevel] [-a] [-v] [-V] [-C]\n", argv[0]); fprintf(stderr, "\n"); - fprintf(stderr, "-a: disable autostart\n"); - fprintf(stderr, "-L : load the layout from \n"); - fprintf(stderr, "-v: display version and exit\n"); - fprintf(stderr, "-V: enable verbose mode\n"); - fprintf(stderr, "-d : enable debug loglevel \n"); - fprintf(stderr, "-c : use the provided configfile instead\n"); - fprintf(stderr, "-C: check configuration file and exit\n"); - fprintf(stderr, "--force-xinerama: Use Xinerama instead of RandR. This " - "option should only be used if you are stuck with the " - "nvidia closed source driver which does not support RandR.\n"); + fprintf(stderr, "\t-a disable autostart ('exec' lines in config)\n"); + fprintf(stderr, "\t-c use the provided configfile instead\n"); + fprintf(stderr, "\t-C validate configuration file and exit\n"); + fprintf(stderr, "\t-d enable debug output with the specified loglevel\n"); + fprintf(stderr, "\t-L path to the serialized layout during restarts\n"); + fprintf(stderr, "\t-v display version and exit\n"); + fprintf(stderr, "\t-V enable verbose mode\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "\t--force-xinerama\n" + "\tUse Xinerama instead of RandR.\n" + "\tThis option should only be used if you are stuck with the\n" + "\tnvidia closed source driver which does not support RandR.\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "\t--get-socketpath\n" + "\tRetrieve the i3 IPC socket path from X11, print it, then exit.\n"); exit(EXIT_FAILURE); } } From b7553976878797fb6038308f08603028f9c1268f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 2 Oct 2011 19:20:43 +0100 Subject: [PATCH 105/333] Make i3 send arguments as command to a running i3 instance (like i3-msg) From i3 --help: If you pass plain text arguments, i3 will interpret them as a command to send to a currently running i3 (like i3-msg). This allows you to use nice and logical commands, such as: i3 border none i3 floating toggle i3 kill window --- src/main.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/src/main.c b/src/main.c index ac05f26e..3ebfb5f4 100644 --- a/src/main.c +++ b/src/main.c @@ -3,6 +3,9 @@ */ #include #include +#include +#include +#include #include "all.h" #include "sd-daemon.h" @@ -281,10 +284,77 @@ int main(int argc, char *argv[]) { fprintf(stderr, "\n"); fprintf(stderr, "\t--get-socketpath\n" "\tRetrieve the i3 IPC socket path from X11, print it, then exit.\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "If you pass plain text arguments, i3 will interpret them as a command\n" + "to send to a currently running i3 (like i3-msg). This allows you to\n" + "use nice and logical commands, such as:\n" + "\n" + "\ti3 border none\n" + "\ti3 floating toggle\n" + "\ti3 kill window\n" + "\n"); exit(EXIT_FAILURE); } } + /* If the user passes more arguments, we act like i3-msg would: Just send + * the arguments as an IPC message to i3. This allows for nice semantic + * commands such as 'i3 border none'. */ + if (optind < argc) { + /* We enable verbose mode so that the user knows what’s going on. + * This should make it easier to find mistakes when the user passes + * arguments by mistake. */ + set_verbosity(true); + + LOG("Additional arguments passed. Sending them as a command to i3.\n"); + char *payload = NULL; + while (optind < argc) { + if (!payload) { + payload = sstrdup(argv[optind]); + } else { + char *both; + if (asprintf(&both, "%s %s", payload, argv[optind]) == -1) + err(EXIT_FAILURE, "asprintf"); + free(payload); + payload = both; + } + optind++; + } + LOG("Command is: %s (%d bytes)\n", payload, strlen(payload)); + char *socket_path = socket_path_from_x11(); + if (!socket_path) { + ELOG("Could not get i3 IPC socket path\n"); + return 1; + } + + int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (sockfd == -1) + err(EXIT_FAILURE, "Could not create socket"); + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_LOCAL; + strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); + if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) + err(EXIT_FAILURE, "Could not connect to i3"); + + if (ipc_send_message(sockfd, strlen(payload), I3_IPC_MESSAGE_TYPE_COMMAND, + (uint8_t*)payload) == -1) + err(EXIT_FAILURE, "IPC: write()"); + + uint32_t reply_length; + uint8_t *reply; + int ret; + if ((ret = ipc_recv_message(sockfd, I3_IPC_MESSAGE_TYPE_COMMAND, + &reply_length, &reply)) != 0) { + if (ret == -1) + err(EXIT_FAILURE, "IPC: read()"); + return 1; + } + printf("%.*s\n", reply_length, reply); + return 0; + } + LOG("i3 (tree) version " I3_VERSION " starting\n"); conn = xcb_connect(NULL, &screens); From 92cc4494aa0a273d1c0bb170051b450b82437884 Mon Sep 17 00:00:00 2001 From: Noe Rubinstein Date: Sun, 2 Oct 2011 17:54:23 +0200 Subject: [PATCH 106/333] refactor workspace_show and friends --- include/workspace.h | 11 ++++++----- src/cmdparse.y | 8 ++++---- src/con.c | 4 ++-- src/randr.c | 6 +++--- src/tree.c | 2 +- src/workspace.c | 35 +++++++++++++++++++++-------------- 6 files changed, 37 insertions(+), 29 deletions(-) diff --git a/include/workspace.h b/include/workspace.h index aebf1365..7eee9d3d 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -47,19 +47,20 @@ void workspace_set_name(Workspace *ws, const char *name); bool workspace_is_visible(Con *ws); /** Switches to the given workspace */ -void workspace_show(const char *num); +void workspace_show(Con *ws); +void workspace_show_by_name(const char *num); /** - * Focuses the next workspace. + * Returns the next workspace. * */ -void workspace_next(); +Con* workspace_next(); /** - * Focuses the previous workspace. + * Returns the previous workspace. * */ -void workspace_prev(); +Con* workspace_prev(); #if 0 /** diff --git a/src/cmdparse.y b/src/cmdparse.y index 04e8b3ca..252f6dff 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -440,7 +440,7 @@ focus: int count = 0; TAILQ_FOREACH(current, &owindows, owindows) { Con *ws = con_get_workspace(current->con); - workspace_show(ws->name); + workspace_show(ws); LOG("focusing %p / %s\n", current->con, current->con->name); con_focus(current->con); count++; @@ -561,18 +561,18 @@ optional_kill_mode: workspace: TOK_WORKSPACE TOK_NEXT { - workspace_next(); + workspace_show(workspace_next()); tree_render(); } | TOK_WORKSPACE TOK_PREV { - workspace_prev(); + workspace_show(workspace_prev()); tree_render(); } | TOK_WORKSPACE STR { printf("should switch to workspace %s\n", $2); - workspace_show($2); + workspace_show_by_name($2); free($2); tree_render(); diff --git a/src/con.c b/src/con.c index 8fbedd3d..805d33b8 100644 --- a/src/con.c +++ b/src/con.c @@ -632,7 +632,7 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool * focused. Must do before attaching because workspace_show checks to see * if focused container is in its area. */ if (workspace_is_visible(workspace)) { - workspace_show(workspace->name); + workspace_show(workspace); /* Don’t warp if told so (when dragging floating windows with the * mouse for example) */ @@ -668,7 +668,7 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool /* Descend focus stack in case focus_next is a workspace which can * occur if we move to the same workspace. Also show current workspace * to ensure it is focused. */ - workspace_show(con_get_workspace(focus_next)->name); + workspace_show(con_get_workspace(focus_next)); con_focus(con_descend_focused(focus_next)); } diff --git a/src/randr.c b/src/randr.c index 2b8757e5..f5f5d198 100644 --- a/src/randr.c +++ b/src/randr.c @@ -363,7 +363,7 @@ void init_ws_for_output(Output *output, Con *content) { if (visible && (previous = TAILQ_NEXT(workspace, focused))) { LOG("Switching to previously used workspace \"%s\" on output \"%s\"\n", previous->name, workspace_out->name); - workspace_show(previous->name); + workspace_show(previous); } con_detach(workspace); @@ -390,7 +390,7 @@ void init_ws_for_output(Output *output, Con *content) { if (!visible) { visible = TAILQ_FIRST(&(content->nodes_head)); focused = content; - workspace_show(visible->name); + workspace_show(visible); } return; } @@ -403,7 +403,7 @@ void init_ws_for_output(Output *output, Con *content) { LOG("Initializing first assigned workspace \"%s\" for output \"%s\"\n", assignment->name, assignment->output); focused = content; - workspace_show(assignment->name); + workspace_show_by_name(assignment->name); return; } diff --git a/src/tree.c b/src/tree.c index 4baba58e..8c73b6ab 100644 --- a/src/tree.c +++ b/src/tree.c @@ -420,7 +420,7 @@ static bool _tree_next(Con *con, char way, orientation_t orientation, bool wrap) if (!workspace) return false; - workspace_show(workspace->name); + workspace_show(workspace); Con *focus = con_descend_direction(workspace, direction); if (focus) { con_focus(focus); diff --git a/src/workspace.c b/src/workspace.c index 32564459..a89029c4 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -180,11 +180,8 @@ static void workspace_reassign_sticky(Con *con) { * Switches to the given workspace * */ -void workspace_show(const char *num) { - Con *workspace, *current, *old = NULL; - - bool changed_num_workspaces; - workspace = workspace_get(num, &changed_num_workspaces); +void workspace_show_changed(Con *workspace, bool changed_num_workspaces) { + Con *current, *old = NULL; /* disable fullscreen for the other workspaces and get the workspace we are * currently on. */ @@ -238,11 +235,22 @@ void workspace_show(const char *num) { ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}"); } +void workspace_show(Con *workspace) { + workspace_show_changed(workspace, false); +} + +void workspace_show_by_name(const char *num) { + Con *workspace; + bool changed_num_workspaces; + workspace = workspace_get(num, &changed_num_workspaces); + workspace_show_changed(workspace, changed_num_workspaces); +} + /* * Focuses the next workspace. * */ -void workspace_next() { +Con* workspace_next() { Con *current = con_get_workspace(focused); Con *next = NULL; Con *output; @@ -277,7 +285,7 @@ void workspace_next() { found_current = 1; } else if (child->num == -1 && (current->num != -1 || found_current)) { next = child; - goto workspace_next_show; + goto workspace_next_end; } } } @@ -292,16 +300,15 @@ void workspace_next() { next = child; } } - -workspace_next_show: - workspace_show(next->name); +workspace_next_end: + return next; } /* * Focuses the previous workspace. * */ -void workspace_prev() { +Con* workspace_prev() { Con *current = con_get_workspace(focused); Con *prev = NULL; Con *output; @@ -336,7 +343,7 @@ void workspace_prev() { found_current = 1; } else if (child->num == -1 && (current->num != -1 || found_current)) { prev = child; - goto workspace_prev_show; + goto workspace_prev_end; } } } @@ -352,8 +359,8 @@ void workspace_prev() { } } -workspace_prev_show: - workspace_show(prev->name); +workspace_prev_end: + return prev; } static bool get_urgency_flag(Con *con) { From b2ad9a77c8ad83b9d464f190a3e8607a0daa819f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 2 Oct 2011 22:03:16 +0100 Subject: [PATCH 107/333] rename the internal function to _workspace_show, add a comment to workspace_show_by_name --- include/workspace.h | 10 +++++++++- src/workspace.c | 21 +++++++++++++-------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/include/workspace.h b/include/workspace.h index 7eee9d3d..3f0e83c2 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -46,8 +46,16 @@ void workspace_set_name(Workspace *ws, const char *name); */ bool workspace_is_visible(Con *ws); -/** Switches to the given workspace */ +/** + * Switches to the given workspace + * + */ void workspace_show(Con *ws); + +/** + * Looks up the workspace by name and switches to it. + * + */ void workspace_show_by_name(const char *num); /** diff --git a/src/workspace.c b/src/workspace.c index a89029c4..b4ed5530 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -176,11 +176,8 @@ static void workspace_reassign_sticky(Con *con) { workspace_reassign_sticky(current); } -/* - * Switches to the given workspace - * - */ -void workspace_show_changed(Con *workspace, bool changed_num_workspaces) { + +static void _workspace_show(Con *workspace, bool changed_num_workspaces) { Con *current, *old = NULL; /* disable fullscreen for the other workspaces and get the workspace we are @@ -235,15 +232,23 @@ void workspace_show_changed(Con *workspace, bool changed_num_workspaces) { ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}"); } -void workspace_show(Con *workspace) { - workspace_show_changed(workspace, false); +/* + * Switches to the given workspace + * + */ +void workspace_show(Con *workspace) { + _workspace_show(workspace, false); } +/* + * Looks up the workspace by name and switches to it. + * + */ void workspace_show_by_name(const char *num) { Con *workspace; bool changed_num_workspaces; workspace = workspace_get(num, &changed_num_workspaces); - workspace_show_changed(workspace, changed_num_workspaces); + _workspace_show(workspace, changed_num_workspaces); } /* From 1eab86b9164a0309338ca72a716437738b9b0ef8 Mon Sep 17 00:00:00 2001 From: Noe Rubinstein Date: Sun, 2 Oct 2011 17:55:19 +0200 Subject: [PATCH 108/333] add "move workspace next" and "move workspace prev" some factorization would be better here, however I don't really know my way around bison --- src/cmdparse.y | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/cmdparse.y b/src/cmdparse.y index 252f6dff..da962cf4 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -715,6 +715,38 @@ move: tree_render(); } + | TOK_MOVE TOK_WORKSPACE TOK_NEXT + { + owindow *current; + + /* get the workspace */ + Con *ws = workspace_next(); + + HANDLE_EMPTY_MATCH; + + TAILQ_FOREACH(current, &owindows, owindows) { + printf("matching: %p / %s\n", current->con, current->con->name); + con_move_to_workspace(current->con, ws, true, false); + } + + tree_render(); + } + | TOK_MOVE TOK_WORKSPACE TOK_PREV + { + owindow *current; + + /* get the workspace */ + Con *ws = workspace_prev(); + + HANDLE_EMPTY_MATCH; + + TAILQ_FOREACH(current, &owindows, owindows) { + printf("matching: %p / %s\n", current->con, current->con->name); + con_move_to_workspace(current->con, ws, true, false); + } + + tree_render(); + } | TOK_MOVE TOK_OUTPUT STR { owindow *current; From 32ea923721f840954567708b4bc8128dfdc984dc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 2 Oct 2011 22:18:21 +0100 Subject: [PATCH 109/333] add a test for the 'move workspace next/prev' command --- testcases/t/32-move-workspace.t | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/testcases/t/32-move-workspace.t b/testcases/t/32-move-workspace.t index 3b50e39b..82e59dd1 100644 --- a/testcases/t/32-move-workspace.t +++ b/testcases/t/32-move-workspace.t @@ -37,6 +37,39 @@ is($focus->[0], $second, 'same container on different ws'); ($nodes, $focus) = get_ws_content($tmp); ok($nodes->[0]->{focused}, 'first container focused on first ws'); +################################################################### +# check if 'move workspace next' and 'move workspace prev' work +################################################################### + +# Open two containers on the first workspace, one container on the second +# workspace. Because the workspaces are named, they will be sorted by order of +# creation. +$tmp = get_unused_workspace(); +$tmp2 = get_unused_workspace(); +cmd "workspace $tmp"; +ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); +$first = open_empty_con($i3); +$second = open_empty_con($i3); +ok(@{get_ws_content($tmp)} == 2, 'two containers on first ws'); + +cmd "workspace $tmp2"; +ok(@{get_ws_content($tmp2)} == 0, 'no containers yet'); +my $third = open_empty_con($i3); +ok(@{get_ws_content($tmp2)} == 1, 'one container on second ws'); + +# go back to the first workspace, move one of the containers to the next one +cmd "workspace $tmp"; +cmd 'move workspace next'; +ok(@{get_ws_content($tmp)} == 1, 'one container on first ws'); +ok(@{get_ws_content($tmp2)} == 2, 'two containers on second ws'); + +# go to the second workspace and move two containers to the first one +cmd "workspace $tmp2"; +cmd 'move workspace prev'; +cmd 'move workspace prev'; +ok(@{get_ws_content($tmp)} == 3, 'three containers on first ws'); +ok(@{get_ws_content($tmp2)} == 0, 'no containers on second ws'); + ################################################################### # check if floating cons are moved to new workspaces properly # (that is, if they are floating on the target ws, too) From b829fce813bf2d08dc5bb1573a365f91e85e84f1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 2 Oct 2011 22:21:38 +0100 Subject: [PATCH 110/333] Mention 'move workspace next/prev' in the userguide --- docs/userguide | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/userguide b/docs/userguide index 8b3a6859..27e3b9ed 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1,7 +1,7 @@ i3 User’s Guide =============== Michael Stapelberg -September 2011 +October 2011 This document contains all the information you need to configure and use the i3 window manager. If it does not, please contact us on IRC (preferred) or post your @@ -889,7 +889,8 @@ number or name of the workspace. To move containers to specific workspaces, use You can also switch to the next and previous workspace with the commands +workspace next+ and +workspace prev+, which is handy, for example, if you have workspace 1, 3, 4 and 9 and you want to cycle through them with a single key -combination. +combination. Similarily, you can use +move workspace next+ and +move workspace +prev+ to move a container to the next/previous workspace. To move a container to another xrandr output such as +LVDS1+ or +VGA1+, you can use the +move output+ command followed by the name of the target output. You From 658bf5adff869010f9c0422f79d84a7080ee400e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 3 Oct 2011 21:54:36 +0100 Subject: [PATCH 111/333] Bugfix: Warp pointer to the correct window when using the 'focus' command Comment in the code makes the fix clear, I think. Steps to reproduce the issue: 1) On the right monitor, open two terminals (h-split workspace) 2) 'i3 mark foo' on the right terminal 3) Focus the left terminal 4) Switch to the left monitor 5) i3 '[con_mark="foo"] focus' 6) the left window will contain the pointer (and is focused for a short period of time, or even permanently due to race conditions) --- src/cmdparse.y | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/cmdparse.y b/src/cmdparse.y index da962cf4..ecc4bc02 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -440,6 +440,24 @@ focus: int count = 0; TAILQ_FOREACH(current, &owindows, owindows) { Con *ws = con_get_workspace(current->con); + + /* If the container is not on the current workspace, + * workspace_show() will switch to a different workspace and (if + * enabled) trigger a mouse pointer warp to the currently focused + * container (!) on the target workspace. + * + * Therefore, before calling workspace_show(), we make sure that + * 'current' will be focused on the workspace. However, we cannot + * just con_focus(current) because then the pointer will not be + * warped at all (the code thinks we are already there). + * + * So we focus 'current' to make it the currently focused window of + * the target workspace, then revert focus. */ + Con *currently_focused = focused; + con_focus(current->con); + con_focus(currently_focused); + + /* Now switch to the workspace, then focus */ workspace_show(ws); LOG("focusing %p / %s\n", current->con, current->con->name); con_focus(current->con); From 20d6ae4110bfec1921b62a185921255ef719e042 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 4 Oct 2011 19:58:17 +0100 Subject: [PATCH 112/333] tests: make complete-run use POSIX::close(3) instead of reserving a file descriptor when starting This makes it possible to run complete-run.pl with a "tainted" environment in which fd 3 (and possibly others) are already present. --- testcases/complete-run.pl | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index 52038b33..aa32557d 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -33,15 +33,6 @@ use IO::Socket::UNIX; # core use POSIX; # core use AnyEvent::Handle; -# open a file so that we get file descriptor 3. we will later close it in the -# child and dup() the listening socket file descriptor to 3 to pass it to i3 -open(my $reserved, '<', '/dev/null'); -if (fileno($reserved) != 3) { - warn "Socket file descriptor is not 3."; - warn "Please don't start this script within a subshell of vim or something."; - exit 1; -} - # install a dummy CHLD handler to overwrite the CHLD handler of AnyEvent / EV # XXX: we could maybe also use a different loop than the default loop in EV? $SIG{CHLD} = sub { @@ -165,7 +156,7 @@ sub take_job { $ENV{DISPLAY} = $display; $^F = 3; - close($reserved); + POSIX::close(3); POSIX::dup2(fileno($socket), 3); # now execute i3 From aa65b721b889679f69fab62bfc49387b4da1678f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 4 Oct 2011 20:03:08 +0100 Subject: [PATCH 113/333] tests: eliminate dependency on the DateTime module by using POSIX::strftime --- testcases/complete-run.pl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index aa32557d..04130ec8 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -16,7 +16,6 @@ use AnyEvent; use IO::Scalar; # not in core :\ use File::Temp qw(tempfile tempdir); use v5.10; -use DateTime; use Data::Dumper; use Cwd qw(abs_path); use Proc::Background; @@ -86,7 +85,7 @@ my @testfiles = @ARGV; # 2: create an output directory for this test-run my $outdir = "testsuite-"; -$outdir .= DateTime->now->strftime("%Y-%m-%d-%H-%M-%S-"); +$outdir .= POSIX::strftime("%Y-%m-%d-%H-%M-%S-", localtime()); $outdir .= `git describe --tags`; chomp($outdir); mkdir($outdir) or die "Could not create $outdir"; From 53121c746c8fab42d5fedbca2cc536210730feae Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 4 Oct 2011 20:05:12 +0100 Subject: [PATCH 114/333] tests: remove unused dependency Proc::Background from complete-run --- testcases/complete-run.pl | 1 - 1 file changed, 1 deletion(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index 04130ec8..681119d3 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -18,7 +18,6 @@ use File::Temp qw(tempfile tempdir); use v5.10; use Data::Dumper; use Cwd qw(abs_path); -use Proc::Background; use TAP::Harness; use TAP::Parser; use TAP::Parser::Aggregator; From a94ec5ee4e0dd25c403ca854d227bd5df817071e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 4 Oct 2011 20:10:11 +0100 Subject: [PATCH 115/333] tests: re-order dependencies in complete-run, make clear which are shipped with Perl --- testcases/complete-run.pl | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index 681119d3..763a4f92 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -11,25 +11,26 @@ use strict; use warnings; -use EV; -use AnyEvent; -use IO::Scalar; # not in core :\ -use File::Temp qw(tempfile tempdir); use v5.10; -use Data::Dumper; +# the following are modules which ship with Perl (>= 5.10): use Cwd qw(abs_path); +use File::Basename qw(basename); +use File::Temp qw(tempfile tempdir); +use Getopt::Long; +use IO::Socket::UNIX; +use POSIX; +use Time::HiRes qw(sleep gettimeofday tv_interval); use TAP::Harness; use TAP::Parser; use TAP::Parser::Aggregator; -use File::Basename qw(basename); -use AnyEvent::I3 qw(:all); -use Try::Tiny; -use Getopt::Long; -use Time::HiRes qw(sleep gettimeofday tv_interval); -use X11::XCB; -use IO::Socket::UNIX; # core -use POSIX; # core +# the following modules are not shipped with Perl +use EV; +use AnyEvent; use AnyEvent::Handle; +use AnyEvent::I3 qw(:all); +use IO::Scalar; # not in core :\ +use Try::Tiny; # not in core +use X11::XCB; # install a dummy CHLD handler to overwrite the CHLD handler of AnyEvent / EV # XXX: we could maybe also use a different loop than the default loop in EV? From 6c7c4d52d09f1fd4e461f9325cafb3a69554470d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 4 Oct 2011 20:55:29 +0100 Subject: [PATCH 116/333] tests: Refactor the socket activation into lib/SocketActivation.pm --- testcases/complete-run.pl | 71 ++++------------------ testcases/lib/SocketActivation.pm | 99 +++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 59 deletions(-) create mode 100644 testcases/lib/SocketActivation.pm diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index 763a4f92..d427d70a 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -23,6 +23,9 @@ use Time::HiRes qw(sleep gettimeofday tv_interval); use TAP::Harness; use TAP::Parser; use TAP::Parser::Aggregator; +# these are shipped with the testsuite +use lib qw(lib); +use SocketActivation; # the following modules are not shipped with Perl use EV; use AnyEvent; @@ -131,73 +134,23 @@ sub take_job { my $activate_cv = AnyEvent->condvar; my $time_before_start = [gettimeofday]; - my $start_i3 = sub { - # remove the old unix socket - unlink("/tmp/nested-$display-activation"); - # pass all file descriptors up to three to the children. - # we need to set this flag before opening the socket. - open(my $fdtest, '<', '/dev/null'); - $^F = fileno($fdtest); - close($fdtest); - my $socket = IO::Socket::UNIX->new( - Listen => 1, - Local => "/tmp/nested-$display-activation", + my $pid; + if (!$dont_start) { + $pid = activate_i3( + unix_socket_path => "/tmp/nested-$display-activation", + display => $display, + configfile => $tmpfile, + logpath => $logpath, + cv => $activate_cv ); - my $pid = fork; - if (!defined($pid)) { - die "could not fork()"; - } - if ($pid == 0) { - $ENV{LISTEN_PID} = $$; - $ENV{LISTEN_FDS} = 1; - $ENV{DISPLAY} = $display; - $^F = 3; - - POSIX::close(3); - POSIX::dup2(fileno($socket), 3); - - # now execute i3 - my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler"; - my $cmd = "exec $i3cmd -c $tmpfile >$logpath 2>&1"; - exec "/bin/sh", '-c', $cmd; - - # if we are still here, i3 could not be found or exec failed. bail out. - exit 1; - } - my $child_watcher; $child_watcher = AnyEvent->child(pid => $pid, cb => sub { say "child died. pid = $pid"; undef $child_watcher; }); - - # close the socket, the child process should be the only one which keeps a file - # descriptor on the listening socket. - $socket->close; - - # We now connect (will succeed immediately) and send a request afterwards. - # As soon as the reply is there, i3 is considered ready. - my $cl = IO::Socket::UNIX->new(Peer => "/tmp/nested-$display-activation"); - my $hdl; - $hdl = AnyEvent::Handle->new(fh => $cl, on_error => sub { $activate_cv->send(0) }); - - # send a get_tree message without payload - $hdl->push_write('i3-ipc' . pack("LL", 0, 4)); - - # wait for the reply - $hdl->push_read(chunk => 1, => sub { - my ($h, $line) = @_; - $activate_cv->send(1); - undef $hdl; - }); - - return $pid; - }; - - my $pid; - $pid = $start_i3->() unless $dont_start; + } my $kill_i3 = sub { # Don’t bother killing i3 when we haven’t started it diff --git a/testcases/lib/SocketActivation.pm b/testcases/lib/SocketActivation.pm new file mode 100644 index 00000000..92e514d5 --- /dev/null +++ b/testcases/lib/SocketActivation.pm @@ -0,0 +1,99 @@ +package SocketActivation; +# vim:ts=4:sw=4:expandtab + +use IO::Socket::UNIX; # core +use Cwd qw(abs_path); # core +use POSIX; # core +use AnyEvent::Handle; # not core +use Exporter 'import'; +use v5.10; + +our @EXPORT = qw(activate_i3); + +# +# Starts i3 using socket activation. Creates a listening socket (with bind + +# listen) which is then passed to i3, who in turn calls accept and handles the +# requests. +# +# Since the kernel buffers the connect, the parent process can connect to the +# socket immediately after forking. It then sends a request and waits until it +# gets an answer. Obviously, i3 has to be initialized to actually answer the +# request. +# +# This way, we can wait *precisely* the amount of time which i3 waits to get +# ready, which is a *HUGE* speed gain (and a lot more robust) in comparison to +# using sleep() with a fixed amount of time. +# +# unix_socket_path: Location of the socket to use for the activation +# display: X11 $ENV{DISPLAY} +# configfile: path to the configuration file to use +# logpath: path to the logfile to which i3 will append +# cv: an AnyEvent->condvar which will be triggered once i3 is ready +# +sub activate_i3 { + my %args = @_; + + # remove the old unix socket + unlink($args{unix_socket_path}); + + # pass all file descriptors up to three to the children. + # we need to set this flag before opening the socket. + open(my $fdtest, '<', '/dev/null'); + $^F = fileno($fdtest); + close($fdtest); + my $socket = IO::Socket::UNIX->new( + Listen => 1, + Local => $args{unix_socket_path}, + ); + + my $pid = fork; + if (!defined($pid)) { + die "could not fork()"; + } + if ($pid == 0) { + $ENV{LISTEN_PID} = $$; + $ENV{LISTEN_FDS} = 1; + $ENV{DISPLAY} = $args{display}; + $^F = 3; + + POSIX::close(3); + POSIX::dup2(fileno($socket), 3); + + # now execute i3 + my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler"; + my $cmd = "exec $i3cmd -c $args{configfile} >$args{logpath} 2>&1"; + exec "/bin/sh", '-c', $cmd; + + # if we are still here, i3 could not be found or exec failed. bail out. + exit 1; + } + + # close the socket, the child process should be the only one which keeps a file + # descriptor on the listening socket. + $socket->close; + + # We now connect (will succeed immediately) and send a request afterwards. + # As soon as the reply is there, i3 is considered ready. + my $cl = IO::Socket::UNIX->new(Peer => $args{unix_socket_path}); + my $hdl; + $hdl = AnyEvent::Handle->new( + fh => $cl, + on_error => sub { + $hdl->destroy; + $args{cv}->send(0); + }); + + # send a get_tree message without payload + $hdl->push_write('i3-ipc' . pack("LL", 0, 4)); + + # wait for the reply + $hdl->push_read(chunk => 1, => sub { + my ($h, $line) = @_; + $args{cv}->send(1); + undef $hdl; + }); + + return $pid; +} + +1 From c8d42fbabed90f2d416500821b880a733e366705 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 4 Oct 2011 23:31:06 +0100 Subject: [PATCH 117/333] tests: SocketActivation: only close() and dup2() if fileno(socket) != 3 --- testcases/lib/SocketActivation.pm | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/testcases/lib/SocketActivation.pm b/testcases/lib/SocketActivation.pm index 92e514d5..70bbec05 100644 --- a/testcases/lib/SocketActivation.pm +++ b/testcases/lib/SocketActivation.pm @@ -56,8 +56,12 @@ sub activate_i3 { $ENV{DISPLAY} = $args{display}; $^F = 3; - POSIX::close(3); - POSIX::dup2(fileno($socket), 3); + # If the socket does not use file descriptor 3 by chance already, we + # close fd 3 and dup2() the socket to 3. + if (fileno($socket) != 3) { + POSIX::close(3); + POSIX::dup2(fileno($socket), 3); + } # now execute i3 my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler"; From bf33c8d7c938585edb98ff6ff6715ffb2fa04a58 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 4 Oct 2011 23:31:33 +0100 Subject: [PATCH 118/333] tests: SocketActivation: append to the logfile, more comments --- testcases/lib/SocketActivation.pm | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/testcases/lib/SocketActivation.pm b/testcases/lib/SocketActivation.pm index 70bbec05..ef182012 100644 --- a/testcases/lib/SocketActivation.pm +++ b/testcases/lib/SocketActivation.pm @@ -54,6 +54,8 @@ sub activate_i3 { $ENV{LISTEN_PID} = $$; $ENV{LISTEN_FDS} = 1; $ENV{DISPLAY} = $args{display}; + # Only pass file descriptors 0 (stdin), 1 (stdout), 2 (stderr) and + # 3 (socket) to the child. $^F = 3; # If the socket does not use file descriptor 3 by chance already, we @@ -63,9 +65,15 @@ sub activate_i3 { POSIX::dup2(fileno($socket), 3); } - # now execute i3 + # Construct the command to launch i3. Use maximum debug level, disable + # the interactive signalhandler to make it crash immediately instead. my $i3cmd = abs_path("../i3") . " -V -d all --disable-signalhandler"; - my $cmd = "exec $i3cmd -c $args{configfile} >$args{logpath} 2>&1"; + + # Append to $args{logpath} instead of overwriting because i3 might be + # run multiple times in one testcase. + my $cmd = "exec $i3cmd -c $args{configfile} >>$args{logpath} 2>&1"; + + # We need to use the shell due to using output redirections. exec "/bin/sh", '-c', $cmd; # if we are still here, i3 could not be found or exec failed. bail out. From 8d9b4c3c04365ffb53fe43a28022651e64dc103b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 4 Oct 2011 23:33:38 +0100 Subject: [PATCH 119/333] tests: launch_with_config: use socket activation --- testcases/complete-run.pl | 2 +- testcases/t/lib/i3test.pm | 26 +++++++++++++++++--------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index d427d70a..322c23f2 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -188,7 +188,7 @@ sub take_job { my $output; my $parser = TAP::Parser->new({ - exec => [ 'sh', '-c', qq|DISPLAY=$display LOGPATH="$logpath" /usr/bin/perl -It/lib $test| ], + exec => [ 'sh', '-c', qq|DISPLAY=$display LOGPATH="$logpath" /usr/bin/perl -It/lib -Ilib $test| ], spool => IO::Scalar->new(\$output), merge => 1, }); diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index c890693c..14ec8d32 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -14,6 +14,7 @@ use Time::HiRes qw(sleep); use Try::Tiny; use Cwd qw(abs_path); use Proc::Background; +use SocketActivation; use v5.10; @@ -414,7 +415,9 @@ sub get_socket_path { # complete-run.pl that it should not create an instance of i3 # sub launch_with_config { - my ($config) = @_; + my ($config, $dont_add_socket_path) = @_; + + $dont_add_socket_path //= 0; if (!defined($tmp_socket_path)) { $tmp_socket_path = File::Temp::tempnam('/tmp', 'i3-test-socket-'); @@ -422,20 +425,25 @@ sub launch_with_config { my ($fh, $tmpfile) = tempfile('i3-test-config-XXXXX', UNLINK => 1); say $fh $config; - say $fh "ipc-socket $tmp_socket_path"; + say $fh "ipc-socket $tmp_socket_path" unless $dont_add_socket_path; close($fh); - # Use $ENV{LOGPATH}, gets set in complete-run.pl. We append instead of - # overwrite because there might be multiple instances of i3 running during - # one test case. - my $i3cmd = "exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >>$ENV{LOGPATH} 2>&1"; - my $process = Proc::Background->new($i3cmd); - sleep 1.25; + my $cv = AnyEvent->condvar; + my $pid = activate_i3( + unix_socket_path => "$tmp_socket_path-activation", + display => $ENV{DISPLAY}, + configfile => $tmpfile, + logpath => $ENV{LOGPATH}, + cv => $cv, + ); + + # blockingly wait until i3 is ready + $cv->recv; # force update of the cached socket path in lib/i3test get_socket_path(0); - return $process; + return $pid; } 1 From 4e1d50fa3a2abd9b4ed4f295f42dfb0332bbfa72 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 4 Oct 2011 23:34:23 +0100 Subject: [PATCH 120/333] tests: Convert all testcases to use the new launch_with_config API It now returns a pid directly, not a Proc::Background object. --- testcases/t/59-socketpaths.t | 51 +++++++++++---------------- testcases/t/65-for_window.t | 41 +++++++++++---------- testcases/t/66-assign.t | 28 ++++++++------- testcases/t/67-workspace_layout.t | 8 ++--- testcases/t/70-force_focus_wrapping.t | 8 ++--- testcases/t/72-start-on-named-ws.t | 12 +++---- testcases/t/73-regress-focus-assign.t | 4 +-- testcases/t/74-border-config.t | 16 ++++----- 8 files changed, 83 insertions(+), 85 deletions(-) diff --git a/testcases/t/59-socketpaths.t b/testcases/t/59-socketpaths.t index 36c99087..7587aeab 100644 --- a/testcases/t/59-socketpaths.t +++ b/testcases/t/59-socketpaths.t @@ -18,26 +18,21 @@ my $i3_path = abs_path("../i3"); # default case: socket will be created in /tmp/i3-/ipc-socket. ##################################################################### -my ($fh, $tmpfile) = tempfile('/tmp/i3-test-config.XXXXXX', UNLINK => 1); -say $fh "# i3 config file (v4)"; -say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; -close($fh); +my $config = </dev/null 2>/dev/null"; -my $process = Proc::Background->new($i3cmd); -sleep 1; - -diag("pid = " . $process->pid); +# ensure XDG_RUNTIME_DIR is not set +delete $ENV{XDG_RUNTIME_DIR}; +my $pid = launch_with_config($config, 1); my $folder = "/tmp/i3-" . getpwuid(getuid()); ok(-d $folder, "folder $folder exists"); -my $socketpath = "$folder/ipc-socket." . $process->pid; +my $socketpath = "$folder/ipc-socket." . $pid; ok(-S $socketpath, "file $socketpath exists and is a socket"); -exit_gracefully($process->pid, $socketpath); - -sleep 0.25; +exit_gracefully($pid); ##################################################################### # XDG_RUNTIME_DIR case: socket gets created in $XDG_RUNTIME_DIR/i3/ipc-socket. @@ -47,17 +42,15 @@ my $rtdir = tempdir(CLEANUP => 1); ok(! -e "$rtdir/i3", "$rtdir/i3 does not exist yet"); -$i3cmd = "export XDG_RUNTIME_DIR=$rtdir; exec " . abs_path("../i3") . " -V -d all --disable-signalhandler -c $tmpfile >/dev/null 2>/dev/null"; -$process = Proc::Background->new($i3cmd); -sleep 1; +$ENV{XDG_RUNTIME_DIR} = $rtdir; + +$pid = launch_with_config($config, 1); ok(-d "$rtdir/i3", "$rtdir/i3 exists and is a directory"); -$socketpath = "$rtdir/i3/ipc-socket." . $process->pid; +$socketpath = "$rtdir/i3/ipc-socket." . $pid; ok(-S $socketpath, "file $socketpath exists and is a socket"); -exit_gracefully($process->pid, $socketpath); - -sleep 0.25; +exit_gracefully($pid); ##################################################################### # configuration file case: socket gets placed whereever we specify @@ -67,18 +60,16 @@ my $tmpdir = tempdir(CLEANUP => 1); $socketpath = $tmpdir . "/config.sock"; ok(! -e $socketpath, "$socketpath does not exist yet"); -($fh, $tmpfile) = tempfile('/tmp/i3-test-config.XXXXXX', UNLINK => 1); -say $fh "# i3 config file (v4)"; -say $fh "font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; -say $fh "ipc-socket $socketpath"; -close($fh); +$config = </dev/null 2>/dev/null"; -$process = Proc::Background->new($i3cmd); -sleep 1; +$pid = launch_with_config($config, 1); ok(-S $socketpath, "file $socketpath exists and is a socket"); -exit_gracefully($process->pid, $socketpath); +exit_gracefully($pid); done_testing; diff --git a/testcases/t/65-for_window.t b/testcases/t/65-for_window.t index 36a20ea9..cc100132 100644 --- a/testcases/t/65-for_window.t +++ b/testcases/t/65-for_window.t @@ -24,7 +24,7 @@ for_window [class="borderless"] border none for_window [title="special borderless title"] border none EOT -my $process = launch_with_config($config); +my $pid = launch_with_config($config); my $tmp = fresh_workspace; @@ -93,7 +93,7 @@ wait_for_unmap $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 0, 'no more nodes'); -exit_gracefully($process->pid); +exit_gracefully($pid); ############################################################## # 2: match on the title, check if for_window is really executed @@ -107,7 +107,7 @@ for_window [class="borderless"] border none for_window [title="special borderless title"] border none EOT -$process = launch_with_config($config); +$pid = launch_with_config($config); $tmp = fresh_workspace; @@ -152,7 +152,7 @@ wait_for_unmap $x; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 0, 'no more nodes'); -exit_gracefully($process->pid); +exit_gracefully($pid); ############################################################## # 3: match on the title, set border style *and* a mark @@ -167,7 +167,7 @@ for_window [title="special borderless title"] border none for_window [title="special mark title"] border none, mark bleh EOT -$process = launch_with_config($config); +$pid = launch_with_config($config); $tmp = fresh_workspace; @@ -199,7 +199,7 @@ cmd qq|[con_mark="bleh"] focus|; @content = @{get_ws_content($tmp)}; ok($content[0]->{focused}, 'first node focused'); -exit_gracefully($process->pid); +exit_gracefully($pid); ############################################################## # 4: multiple criteria for the for_window command @@ -211,7 +211,7 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 for_window [class="borderless" title="usethis"] border none EOT -$process = launch_with_config($config); +$pid = launch_with_config($config); $tmp = fresh_workspace; @@ -233,12 +233,15 @@ wait_for_map $x; cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'none', 'no border'); -$window->unmap; +cmd 'kill'; wait_for_unmap $x; +$window->destroy; @content = @{get_ws_content($tmp)}; cmp_ok(@content, '==', 0, 'no nodes on this workspace now'); +$window->_create; + set_wm_class($window->id, 'borderless', 'borderless'); $window->name('notthis'); $window->map; @@ -249,7 +252,7 @@ cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'normal', 'no border'); -exit_gracefully($process->pid); +exit_gracefully($pid); ############################################################## # 5: check that a class criterion does not match the instance @@ -261,7 +264,7 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 for_window [class="foo"] border 1pixel EOT -$process = launch_with_config($config); +$pid = launch_with_config($config); $tmp = fresh_workspace; @@ -283,7 +286,7 @@ wait_for_map $x; cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'normal', 'normal border, not matched'); -exit_gracefully($process->pid); +exit_gracefully($pid); ############################################################## # 6: check that the 'instance' criterion works @@ -296,7 +299,7 @@ for_window [class="foo"] border 1pixel for_window [instance="foo"] border none EOT -$process = launch_with_config($config); +$pid = launch_with_config($config); $tmp = fresh_workspace; @@ -318,7 +321,7 @@ wait_for_map $x; cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'none', 'no border'); -exit_gracefully($process->pid); +exit_gracefully($pid); ############################################################## # 7: check that invalid criteria don’t end up matching all windows @@ -333,7 +336,7 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 for_window [id="asdf"] border none EOT -$process = launch_with_config($config); +$pid = launch_with_config($config); $tmp = fresh_workspace; @@ -355,7 +358,7 @@ wait_for_map $x; cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'normal', 'normal border'); -exit_gracefully($process->pid); +exit_gracefully($pid); ############################################################## # 8: check that the role criterion works properly @@ -370,7 +373,7 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 for_window [window_role="i3test"] border none EOT -$process = launch_with_config($config); +$pid = launch_with_config($config); $tmp = fresh_workspace; @@ -403,7 +406,7 @@ wait_for_map $x; cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'none', 'no border (window_role)'); -exit_gracefully($process->pid); +exit_gracefully($pid); ############################################################## # 9: another test for the window_role, but this time it changes @@ -419,7 +422,7 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 for_window [window_role="i3test"] border none EOT -$process = launch_with_config($config); +$pid = launch_with_config($config); $tmp = fresh_workspace; @@ -460,7 +463,7 @@ sync_with_i3 $x; cmp_ok(@content, '==', 1, 'one node on this workspace now'); is($content[0]->{border}, 'none', 'no border (window_role 2)'); -exit_gracefully($process->pid); +exit_gracefully($pid); done_testing; diff --git a/testcases/t/66-assign.t b/testcases/t/66-assign.t index cc41b7af..4844f5b5 100644 --- a/testcases/t/66-assign.t +++ b/testcases/t/66-assign.t @@ -40,7 +40,7 @@ my $config = <pid); +exit_gracefully($pid); $window->destroy; @@ -76,7 +76,7 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 assign "special" → targetws EOT -$process = launch_with_config($config); +$pid = launch_with_config($config); $tmp = fresh_workspace; @@ -102,7 +102,7 @@ ok("targetws" ~~ @{get_workspace_names()}, 'targetws exists'); $window->destroy; -exit_gracefully($process->pid); +exit_gracefully($pid); sleep 0.25; @@ -111,7 +111,7 @@ sleep 0.25; # already, next to the existing node. ##################################################################### -$process = launch_with_config($config); +$pid = launch_with_config($config); # initialize the target workspace, then go to a fresh one ok(!("targetws" ~~ @{get_workspace_names()}), 'targetws does not exist yet'); @@ -144,7 +144,7 @@ sync_with_i3 $x; ok(@{get_ws_content($tmp)} == 0, 'still no containers'); ok(@{get_ws_content('targetws')} == 2, 'two containers on targetws'); -exit_gracefully($process->pid); +exit_gracefully($pid); ##################################################################### # start a window and see that it gets assigned to a workspace which has content @@ -157,7 +157,7 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 assign "special" → ~ EOT -$process = launch_with_config($config); +$pid = launch_with_config($config); $tmp = fresh_workspace; @@ -184,7 +184,7 @@ ok(@{$content->{floating_nodes}} == 1, 'one floating con'); $window->destroy; -exit_gracefully($process->pid); +exit_gracefully($pid); sleep 0.25; @@ -198,7 +198,7 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 assign "special" → ~ EOT -$process = launch_with_config($config); +$pid = launch_with_config($config); $tmp = fresh_workspace; @@ -225,7 +225,7 @@ ok(@{$content->{floating_nodes}} == 1, 'one floating con'); $window->destroy; -exit_gracefully($process->pid); +exit_gracefully($pid); sleep 0.25; @@ -241,7 +241,11 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 assign "special" → ~ EOT -$process = launch_with_config($config); +$pid = launch_with_config($config); + +# TODO: replace this with checking the process hierarchy +# XXX: give i3-nagbar some time to start up +sleep 1; $tmp = fresh_workspace; @@ -275,7 +279,7 @@ $window->destroy; does_i3_live; -exit_gracefully($process->pid); +exit_gracefully($pid); sleep 0.25; diff --git a/testcases/t/67-workspace_layout.t b/testcases/t/67-workspace_layout.t index 6ff3f15b..e4b18adf 100644 --- a/testcases/t/67-workspace_layout.t +++ b/testcases/t/67-workspace_layout.t @@ -21,7 +21,7 @@ my $config = <{layout}, 'stacked', 'layout not stacked'); isnt($content[1]->{layout}, 'stacked', 'layout not stacked'); -exit_gracefully($process->pid); +exit_gracefully($pid); ##################################################################### # 2: set workspace_layout stacked, check that when opening two cons, @@ -50,7 +50,7 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 workspace_layout stacked EOT -$process = launch_with_config($config); +$pid = launch_with_config($config); $tmp = fresh_workspace; @@ -122,6 +122,6 @@ is($content[1]->{layout}, 'stacked', 'layout stacked'); is(@content, 1, 'one con on target workspace'); is($content[0]->{layout}, 'stacked', 'layout stacked'); -exit_gracefully($process->pid); +exit_gracefully($pid); done_testing; diff --git a/testcases/t/70-force_focus_wrapping.t b/testcases/t/70-force_focus_wrapping.t index 2aa5407d..8a990f23 100644 --- a/testcases/t/70-force_focus_wrapping.t +++ b/testcases/t/70-force_focus_wrapping.t @@ -19,7 +19,7 @@ my $config = <input_focus, $second->id, 'second window focused'); cmd 'focus right'; is($x->input_focus, $third->id, 'third window focused'); -exit_gracefully($process->pid); +exit_gracefully($pid); ##################################################################### # 2: test the wrapping behaviour with force_focus_wrapping @@ -60,7 +60,7 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 force_focus_wrapping true EOT -$process = launch_with_config($config); +$pid = launch_with_config($config); $tmp = fresh_workspace; @@ -92,6 +92,6 @@ is($x->input_focus, $second->id, 'second window focused'); cmd 'focus right'; is($x->input_focus, $first->id, 'first window focused'); -exit_gracefully($process->pid); +exit_gracefully($pid); done_testing; diff --git a/testcases/t/72-start-on-named-ws.t b/testcases/t/72-start-on-named-ws.t index 12661867..4493bf83 100644 --- a/testcases/t/72-start-on-named-ws.t +++ b/testcases/t/72-start-on-named-ws.t @@ -19,12 +19,12 @@ my $config = <pid); +exit_gracefully($pid); ############################################################## # 2: with named workspaces, i3 should start on the first named one @@ -37,12 +37,12 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 bindsym Mod1+1 workspace foobar EOT -$process = launch_with_config($config); +$pid = launch_with_config($config); my @names = @{get_workspace_names()}; cmp_deeply(\@names, [ 'foobar' ], 'i3 starts on named workspace foobar'); -exit_gracefully($process->pid); +exit_gracefully($pid); ############################################################## # 3: the same test as 2, but with a quoted workspace name @@ -55,11 +55,11 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 bindsym Mod1+1 workspace "foobar" EOT -$process = launch_with_config($config); +$pid = launch_with_config($config); my @names = @{get_workspace_names()}; cmp_deeply(\@names, [ 'foobar' ], 'i3 starts on named workspace foobar'); -exit_gracefully($process->pid); +exit_gracefully($pid); done_testing; diff --git a/testcases/t/73-regress-focus-assign.t b/testcases/t/73-regress-focus-assign.t index bab7c9ae..70414af3 100644 --- a/testcases/t/73-regress-focus-assign.t +++ b/testcases/t/73-regress-focus-assign.t @@ -42,7 +42,7 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 assign "special" → targetws EOT -my $process = launch_with_config($config); +my $pid = launch_with_config($config); my $tmp = fresh_workspace; @@ -88,7 +88,7 @@ ok(@{get_ws_content($tmp)} == 0, 'special window not on current workspace'); ok(@{get_ws_content('targetws')} == 1, 'special window on targetws'); ok(get_ws($tmp)->{focused}, 'current workspace still focused'); -exit_gracefully($process->pid); +exit_gracefully($pid); $window->destroy; diff --git a/testcases/t/74-border-config.t b/testcases/t/74-border-config.t index 617e37b9..cc1f5c90 100644 --- a/testcases/t/74-border-config.t +++ b/testcases/t/74-border-config.t @@ -19,7 +19,7 @@ my $config = <{border}, 'normal', 'border normal by default'); -exit_gracefully($process->pid); +exit_gracefully($pid); ##################################################################### # 2: check that new tiling windows start with '1pixel' border when @@ -45,7 +45,7 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 new_window 1pixel EOT -$process = launch_with_config($config); +$pid = launch_with_config($config); $tmp = fresh_workspace; @@ -57,7 +57,7 @@ $first = open_window($x); ok(@content == 1, 'one container opened'); is($content[0]->{border}, '1pixel', 'border normal by default'); -exit_gracefully($process->pid); +exit_gracefully($pid); ##################################################################### # 3: check that new floating windows start with 'normal' border unless @@ -69,7 +69,7 @@ $config = <{nodes}->[0]->{border}, 'normal', 'border normal by default'); -exit_gracefully($process->pid); +exit_gracefully($pid); ##################################################################### # 4: check that new floating windows start with '1pixel' border when @@ -97,7 +97,7 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 new_float 1pixel EOT -$process = launch_with_config($config); +$pid = launch_with_config($config); $tmp = fresh_workspace; @@ -111,6 +111,6 @@ ok(@floating == 1, 'one floating container opened'); $floatingcon = $floating[0]; is($floatingcon->{nodes}->[0]->{border}, '1pixel', 'border normal by default'); -exit_gracefully($process->pid); +exit_gracefully($pid); done_testing; From 359717970da030c1cf9efcb00d17e2c6814f95f5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 4 Oct 2011 23:36:43 +0100 Subject: [PATCH 121/333] =?UTF-8?q?tests:=20don=E2=80=99t=20overwrite=20$t?= =?UTF-8?q?imeout,=20kill=20timer=20when=20done?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- testcases/t/lib/i3test.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 14ec8d32..9230c5ef 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -104,9 +104,10 @@ sub wait_for_event { }; # Trigger timeout after $timeout seconds (can be fractional) - my $timeout = AE::timer $timeout, 0, sub { warn "timeout"; $cv->send(0) }; + my $t = AE::timer $timeout, 0, sub { warn "timeout ($timeout secs)"; $cv->send(0) }; my $result = $cv->recv; + undef $t; return $result; } From 57484553e6bd9065443d06d85be28c16224aa501 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 4 Oct 2011 23:36:57 +0100 Subject: [PATCH 122/333] tests: use a lot higher timeouts This makes the tests more robust on a machine which is loaded. Also, it makes the tests run (significantly) longer if anything goes wrong. --- testcases/t/lib/i3test.pm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 9230c5ef..cf29d7b9 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -115,7 +115,7 @@ sub wait_for_event { # make sure to include 'structure_notify' in the window’s event_mask attribute sub wait_for_map { my ($x) = @_; - wait_for_event $x, 1, sub { $_[0]->{response_type} == MAP_NOTIFY }; + wait_for_event $x, 2, sub { $_[0]->{response_type} == MAP_NOTIFY }; } # Wrapper around wait_for_event which waits for UNMAP_NOTIFY. Also calls @@ -123,7 +123,7 @@ sub wait_for_map { # event. sub wait_for_unmap { my ($x) = @_; - wait_for_event $x, 1, sub { $_[0]->{response_type} == UNMAP_NOTIFY }; + wait_for_event $x, 2, sub { $_[0]->{response_type} == UNMAP_NOTIFY }; sync_with_i3($x); } @@ -325,7 +325,7 @@ sub sync_with_i3 { $_sync_window->map; - wait_for_event $x, 0.5, sub { $_[0]->{response_type} == MAP_NOTIFY }; + wait_for_event $x, 2, sub { $_[0]->{response_type} == MAP_NOTIFY }; } my $root = $x->get_root_window(); @@ -351,7 +351,7 @@ sub sync_with_i3 { $x->send_event(0, $root, EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg); # now wait until the reply is here - return wait_for_event $x, 1, sub { + return wait_for_event $x, 2, sub { my ($event) = @_; # TODO: const return 0 unless $event->{response_type} == 161; From bd33c09845d2069d11086f42d61efaba15d71f6d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 4 Oct 2011 23:39:36 +0100 Subject: [PATCH 123/333] tests: move i3test.pm from t/lib to lib/ --- testcases/complete-run.pl | 2 +- testcases/{t => }/lib/i3test.pm | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename testcases/{t => }/lib/i3test.pm (100%) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index 322c23f2..70bcc8c1 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -188,7 +188,7 @@ sub take_job { my $output; my $parser = TAP::Parser->new({ - exec => [ 'sh', '-c', qq|DISPLAY=$display LOGPATH="$logpath" /usr/bin/perl -It/lib -Ilib $test| ], + exec => [ 'sh', '-c', qq|DISPLAY=$display LOGPATH="$logpath" /usr/bin/perl -Ilib $test| ], spool => IO::Scalar->new(\$output), merge => 1, }); diff --git a/testcases/t/lib/i3test.pm b/testcases/lib/i3test.pm similarity index 100% rename from testcases/t/lib/i3test.pm rename to testcases/lib/i3test.pm From cdd9dc31449cdbdce22ec33f26f99faa0527665e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 5 Oct 2011 20:46:47 +0100 Subject: [PATCH 124/333] docs/testsuite: explain how socket activation works in i3 --- docs/testsuite | 96 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) diff --git a/docs/testsuite b/docs/testsuite index b3b76c74..e067d33c 100644 --- a/docs/testsuite +++ b/docs/testsuite @@ -1,7 +1,7 @@ i3 testsuite ============ Michael Stapelberg -September 2011 +October 2011 This document explains how the i3 testsuite works, how to use it and extend it. It is targeted at developers who not necessarily have been doing testing before @@ -449,3 +449,97 @@ request. You should use a random value in +data[1]+ and check that you received the same one when getting the reply. == Appendix B: Socket activation + +Socket activation is a mechanism which was made popular by systemd, an init +replacement. It basically describes creating a listening socket before starting +a program. systemd will invoke the program only when an actual connection to +the socket is made, hence the term socket activation. + +The interesting part of this (in the i3 context) is that you can very precisely +detect when the program is ready (finished its initialization). + +=== Preparing the listening socket + ++complete-run.pl+ will create a listening UNIX socket which it will then pass +to i3. This socket will be used by i3 as an additional IPC socket, just like +the one it will create on its own. Passing the socket happens implicitly +because children will inherit the parent’s sockets when fork()ing and sockets +will continue to exist after an exec() call (unless CLOEXEC is set of course). + +The only explicit things +complete-run.pl+ has to do is setting the +LISTEN_FDS+ +environment variable to the number of sockets which exist (1 in our case) and +setting the +LISTEN_PID+ environment variable to the current process ID. Both +variables are necessary so that the program (i3) knows how many sockets it +should use and if the environment variable is actually intended for it. i3 will +then start looking for sockets at file descriptor 3 (since 0, 1 and 2 are used +for stdin, stdout and stderr, respectively). + +The actual Perl code which sets up the socket, fork()s, makes sure the socket +has file descriptor 3 and sets up the environment variables follows (shortened +a bit): + + +.Setup socket and environment +----------------------------- +my $socket = IO::Socket::UNIX->new( + Listen => 1, + Local => $args{unix_socket_path}, +); + +my $pid = fork; +if ($pid == 0) { + $ENV{LISTEN_PID} = $$; + $ENV{LISTEN_FDS} = 1; + + # Only pass file descriptors 0 (stdin), 1 (stdout), + # 2 (stderr) and 3 (socket) to the child. + $^F = 3; + + # If the socket does not use file descriptor 3 by chance + # already, we close fd 3 and dup2() the socket to 3. + if (fileno($socket) != 3) { + POSIX::close(3); + POSIX::dup2(fileno($socket), 3); + } + + exec "/usr/bin/i3"; +} +----------------------------- + +=== Waiting for a reply + +In the parent process, we want to know when i3 is ready to answer our IPC +requests and handle our windows. Therefore, after forking, we immediately close +the listening socket (i3 will handle this side of the socket) and connect to it +(remember, we are talking about a named UNIX socket) as a client. This connect +call will immediately succeed because the kernel buffers it. Then, we send a +request (of type GET_TREE, but that is not really relevant). Writing data to +the socket will also succeed immediately because, again, the kernel buffers it +(only up to a certain amount of data of course). + +Afterwards, we just blockingly wait until we get an answer. In the child +process, i3 will setup the listening socket in its event loop. Immediately +after actually starting the event loop, it will notice a new client connecting +(the parent process) and handle its request. Since all initialization has been +completed successfully by the time the event loop is entered, we can now assume +that i3 is ready. + +=== Timing and conclusion + +A beautiful feature of this mechanism is that it does not depend on timing. It +does not matter when the child process gets CPU time or when the parent process +gets CPU time. On heavily loaded machines (or machines with multiple CPUs, +cores or unreliable schedulers), this makes waiting for i3 much more robust. + +Before using socket activation, we typically used a +sleep(1)+ and hoped that +i3 was initialized by that time. Of course, this breaks on some (slow) +computers and wastes a lot of time on faster computers. By using socket +activation, we decreased the total amount of time necessary to run all tests +(72 files at the time of writing) from > 100 seconds to 16 seconds. This makes +it significantly more attractive to run the test suite more often (or at all) +during development. + +An alternative approach to using socket activation is polling for the existance +of the IPC socket and connecting to it. While this might be slightly easier to +implement, it wastes CPU time and is considerably more ugly than this solution +:). After all, +lib/SocketActivation.pm+ contains only 54 SLOC. From b9cd9132d0c76234dc699fd874a84acddb802f93 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 5 Oct 2011 20:48:34 +0100 Subject: [PATCH 125/333] tests: remove unused Proc::Background --- testcases/lib/i3test.pm | 1 - testcases/t/59-socketpaths.t | 5 ----- testcases/t/71-config-migrate.t | 3 --- 3 files changed, 9 deletions(-) diff --git a/testcases/lib/i3test.pm b/testcases/lib/i3test.pm index cf29d7b9..7eea384d 100644 --- a/testcases/lib/i3test.pm +++ b/testcases/lib/i3test.pm @@ -13,7 +13,6 @@ use List::MoreUtils qw(lastval); use Time::HiRes qw(sleep); use Try::Tiny; use Cwd qw(abs_path); -use Proc::Background; use SocketActivation; use v5.10; diff --git a/testcases/t/59-socketpaths.t b/testcases/t/59-socketpaths.t index 7587aeab..eb6bd79f 100644 --- a/testcases/t/59-socketpaths.t +++ b/testcases/t/59-socketpaths.t @@ -5,15 +5,10 @@ # Tests if the various ipc_socket_path options are correctly handled # use i3test; -use Cwd qw(abs_path); -use Proc::Background; use File::Temp qw(tempfile tempdir); use POSIX qw(getuid); use v5.10; -# assuming we are run by complete-run.pl -my $i3_path = abs_path("../i3"); - ##################################################################### # default case: socket will be created in /tmp/i3-/ipc-socket. ##################################################################### diff --git a/testcases/t/71-config-migrate.t b/testcases/t/71-config-migrate.t index 561538e5..7948db6c 100644 --- a/testcases/t/71-config-migrate.t +++ b/testcases/t/71-config-migrate.t @@ -7,10 +7,7 @@ # use i3test; use Cwd qw(abs_path); -use Proc::Background; use File::Temp qw(tempfile tempdir); -use POSIX qw(getuid); -use Data::Dumper; use v5.10; # reads in a whole file From 689f3b8cf71d34d9517b7b76cdb519de9e59d258 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 5 Oct 2011 23:17:09 +0100 Subject: [PATCH 126/333] tests: Eliminate IO::Scalar --- testcases/complete-run.pl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index 70bcc8c1..db797bd2 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -31,7 +31,6 @@ use EV; use AnyEvent; use AnyEvent::Handle; use AnyEvent::I3 qw(:all); -use IO::Scalar; # not in core :\ use Try::Tiny; # not in core use X11::XCB; @@ -187,9 +186,10 @@ sub take_job { say "[$display] Running $test with logfile $logpath"; my $output; + open(my $spool, '>', \$output); my $parser = TAP::Parser->new({ exec => [ 'sh', '-c', qq|DISPLAY=$display LOGPATH="$logpath" /usr/bin/perl -Ilib $test| ], - spool => IO::Scalar->new(\$output), + spool => $spool, merge => 1, }); From b9224634dd4ed5fba9675068aba097a0d232e4e3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 5 Oct 2011 23:21:23 +0100 Subject: [PATCH 127/333] tests: eliminate Try::Tiny --- testcases/complete-run.pl | 3 +-- testcases/lib/i3test.pm | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index db797bd2..c8c67a9a 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -31,7 +31,6 @@ use EV; use AnyEvent; use AnyEvent::Handle; use AnyEvent::I3 qw(:all); -use Try::Tiny; # not in core use X11::XCB; # install a dummy CHLD handler to overwrite the CHLD handler of AnyEvent / EV @@ -159,7 +158,7 @@ sub take_job { # files are not written) and fallback to killing it if ($coverage_testing) { my $exited = 0; - try { + eval { say "Exiting i3 cleanly..."; i3("/tmp/nested-$display")->command('exit')->recv; $exited = 1; diff --git a/testcases/lib/i3test.pm b/testcases/lib/i3test.pm index 7eea384d..67a7db73 100644 --- a/testcases/lib/i3test.pm +++ b/testcases/lib/i3test.pm @@ -11,7 +11,6 @@ use EV; use List::Util qw(first); use List::MoreUtils qw(lastval); use Time::HiRes qw(sleep); -use Try::Tiny; use Cwd qw(abs_path); use SocketActivation; @@ -374,7 +373,7 @@ sub exit_gracefully { $socketpath ||= get_socket_path(); my $exited = 0; - try { + eval { say "Exiting i3 cleanly..."; i3($socketpath)->command('exit')->recv; $exited = 1; From 3136573a70d84e3641740ca171cba427b71a966d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 5 Oct 2011 23:21:36 +0100 Subject: [PATCH 128/333] tests: eliminate List::MoreUtils --- testcases/lib/i3test.pm | 4 ++-- testcases/t/15-ipc-workspaces.t | 5 ++--- testcases/t/16-nestedcons.t | 18 +++++++++++++++++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/testcases/lib/i3test.pm b/testcases/lib/i3test.pm index 67a7db73..9b7cc138 100644 --- a/testcases/lib/i3test.pm +++ b/testcases/lib/i3test.pm @@ -9,7 +9,6 @@ use X11::XCB qw(:all); use AnyEvent::I3; use EV; use List::Util qw(first); -use List::MoreUtils qw(lastval); use Time::HiRes qw(sleep); use Cwd qw(abs_path); use SocketActivation; @@ -267,7 +266,8 @@ sub get_dock_clients { my $first = first { $_->{type} == 5 } @{$output->{nodes}}; @docked = (@docked, @{$first->{nodes}}); } elsif ($which eq 'bottom') { - my $last = lastval { $_->{type} == 5 } @{$output->{nodes}}; + my @matching = grep { $_->{type} == 5 } @{$output->{nodes}}; + my $last = $matching[-1]; @docked = (@docked, @{$last->{nodes}}); } } diff --git a/testcases/t/15-ipc-workspaces.t b/testcases/t/15-ipc-workspaces.t index 085163b2..4d9a0294 100644 --- a/testcases/t/15-ipc-workspaces.t +++ b/testcases/t/15-ipc-workspaces.t @@ -2,7 +2,6 @@ # vim:ts=4:sw=4:expandtab use i3test; -use List::MoreUtils qw(all); my $i3 = i3(get_socket_path()); @@ -17,8 +16,8 @@ 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"); } diff --git a/testcases/t/16-nestedcons.t b/testcases/t/16-nestedcons.t index f9d27262..4b3958a1 100644 --- a/testcases/t/16-nestedcons.t +++ b/testcases/t/16-nestedcons.t @@ -2,9 +2,25 @@ # vim:ts=4:sw=4:expandtab use i3test; -use List::MoreUtils qw(all none); use List::Util qw(first); +# to not depend on List::MoreUtils +sub all (&@) { + my $cb = shift; + for (@_) { + return 0 unless $cb->(); + } + return 1; +} + +sub none (&@) { + my $cb = shift; + for (@_) { + return 0 if $cb->(); + } + return 1; +} + my $i3 = i3(get_socket_path()); #################### From 27a38a3917626932d908b94c603be4747490a84c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 5 Oct 2011 23:29:58 +0100 Subject: [PATCH 129/333] complete-run: explicitly state why we need to overwrite SIGCHLD --- testcases/complete-run.pl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index c8c67a9a..952f10c6 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -33,7 +33,9 @@ use AnyEvent::Handle; use AnyEvent::I3 qw(:all); use X11::XCB; -# install a dummy CHLD handler to overwrite the CHLD handler of AnyEvent / EV +# Install a dummy CHLD handler to overwrite the CHLD handler of AnyEvent / EV. +# AnyEvent’s handler wait()s for every child which conflicts with TAP (TAP +# needs to get the exit status to determine if a test is successful). # XXX: we could maybe also use a different loop than the default loop in EV? $SIG{CHLD} = sub { }; From 1056ecc88529aaca3a812c7a757928986e53392f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 5 Oct 2011 23:52:19 +0100 Subject: [PATCH 130/333] complete-run: eliminate dependency on EV --- testcases/complete-run.pl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/testcases/complete-run.pl b/testcases/complete-run.pl index 952f10c6..e7651022 100755 --- a/testcases/complete-run.pl +++ b/testcases/complete-run.pl @@ -27,16 +27,18 @@ use TAP::Parser::Aggregator; use lib qw(lib); use SocketActivation; # the following modules are not shipped with Perl -use EV; use AnyEvent; use AnyEvent::Handle; use AnyEvent::I3 qw(:all); use X11::XCB; -# Install a dummy CHLD handler to overwrite the CHLD handler of AnyEvent / EV. +# We actually use AnyEvent to make sure it loads an event loop implementation. +# Afterwards, we overwrite SIGCHLD: +my $cv = AnyEvent->condvar; + +# Install a dummy CHLD handler to overwrite the CHLD handler of AnyEvent. # AnyEvent’s handler wait()s for every child which conflicts with TAP (TAP # needs to get the exit status to determine if a test is successful). -# XXX: we could maybe also use a different loop than the default loop in EV? $SIG{CHLD} = sub { }; @@ -103,8 +105,6 @@ my $harness = TAP::Harness->new({ }); my $aggregator = TAP::Parser::Aggregator->new(); $aggregator->start(); -my $cv = AnyEvent->condvar; - # We start tests concurrently: For each display, one test gets started. Every # test starts another test after completing. take_job($_) for @wdisplays; From 30901ccf6e9183513940820aba3514cd11d9dc35 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 6 Oct 2011 07:58:39 +0100 Subject: [PATCH 131/333] docs/testsuite: s/more ugly/uglier (Thanks fernandotcl) --- docs/testsuite | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/testsuite b/docs/testsuite index e067d33c..cbe81ed4 100644 --- a/docs/testsuite +++ b/docs/testsuite @@ -541,5 +541,5 @@ during development. An alternative approach to using socket activation is polling for the existance of the IPC socket and connecting to it. While this might be slightly easier to -implement, it wastes CPU time and is considerably more ugly than this solution +implement, it wastes CPU time and is considerably uglier than this solution :). After all, +lib/SocketActivation.pm+ contains only 54 SLOC. From 8724a0babf7b4d0b226b37236481c6a0c18630c1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 8 Oct 2011 13:01:43 +0100 Subject: [PATCH 132/333] debian: add testsuite.html to i3-wm.docs --- debian/i3-wm.docs | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/i3-wm.docs b/debian/i3-wm.docs index d59c5736..baef7cdb 100644 --- a/debian/i3-wm.docs +++ b/debian/i3-wm.docs @@ -14,3 +14,4 @@ docs/wsbar.html docs/wsbar.png docs/keyboard-layer1.png docs/keyboard-layer2.png +docs/testsuite.html From c48a092e246be061b9e35440f47c60f6eefaedd9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 8 Oct 2011 14:30:31 +0100 Subject: [PATCH 133/333] testsuite: delete obsolete makefile --- testcases/Makefile | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 testcases/Makefile diff --git a/testcases/Makefile b/testcases/Makefile deleted file mode 100644 index edf5ee0d..00000000 --- a/testcases/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -test: - PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" -It/lib t/12*.t - -clean: - rm -rf testsuite-* latest - -all: test - -testfull: - PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" -It/lib t/01* t/02* t/03* t/05* t/17* t/18* t/19* t/20* From ae1ab9eb273cd2e75f834500d7386d81a925f753 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 8 Oct 2011 14:38:50 +0100 Subject: [PATCH 134/333] tests: rename files (00x-*.t is basic stuff, >=100-*.t are tests using IPC) --- testcases/t/{00-load.t => 000-load-deps.t} | 0 testcases/t/{01-tile.t => 001-tile.t} | 0 testcases/t/002-i3-sync.t | 15 +++++++++++++++ testcases/t/{05-ipc.t => 003-ipc.t} | 0 testcases/t/{03-unmanaged.t => 004-unmanaged.t} | 0 testcases/t/{04-floating.t => 005-floating.t} | 0 testcases/t/{02-fullscreen.t => 100-fullscreen.t} | 0 testcases/t/{06-focus.t => 101-focus.t} | 0 testcases/t/{10-dock.t => 102-dock.t} | 0 testcases/t/{07-move.t => 103-move.t} | 0 .../t/{08-focus-stack.t => 104-focus-stack.t} | 0 testcases/t/{09-stacking.t => 105-stacking.t} | 0 testcases/t/{11-goto.t => 111-goto.t} | 0 ...12-floating-resize.t => 112-floating-resize.t} | 0 testcases/t/{13-urgent.t => 113-urgent.t} | 0 .../t/{14-client-leader.t => 114-client-leader.t} | 0 .../{15-ipc-workspaces.t => 115-ipc-workspaces.t} | 0 testcases/t/{16-nestedcons.t => 116-nestedcons.t} | 0 testcases/t/{17-workspace.t => 117-workspace.t} | 0 testcases/t/{18-openkill.t => 118-openkill.t} | 0 testcases/t/{19-match.t => 119-match.t} | 0 .../t/{20-multiple-cmds.t => 120-multiple-cmds.t} | 0 testcases/t/{21-next-prev.t => 121-next-prev.t} | 0 testcases/t/{22-split.t => 122-split.t} | 0 testcases/t/{24-move.t => 124-move.t} | 0 .../t/{26-regress-close.t => 126-regress-close.t} | 0 ...ing-parent.t => 127-regress-floating-parent.t} | 0 testcases/t/{28-open-order.t => 128-open-order.t} | 0 ...ocus-after-close.t => 129-focus-after-close.t} | 0 ...lose-empty-split.t => 130-close-empty-split.t} | 0 .../{31-stacking-order.t => 131-stacking-order.t} | 0 .../{32-move-workspace.t => 132-move-workspace.t} | 0 testcases/t/{33-size-hints.t => 133-size-hints.t} | 0 ...34-invalid-command.t => 134-invalid-command.t} | 0 .../{35-floating-focus.t => 135-floating-focus.t} | 0 ...loating-ws-empty.t => 136-floating-ws-empty.t} | 0 .../{37-floating-unmap.t => 137-floating-unmap.t} | 0 ...38-floating-attach.t => 138-floating-attach.t} | 0 testcases/t/{39-ws-numbers.t => 139-ws-numbers.t} | 0 testcases/t/{40-focus-lost.t => 140-focus-lost.t} | 0 testcases/t/{41-resize.t => 141-resize.t} | 0 ...ove-floating.t => 142-regress-move-floating.t} | 0 ...g-restart.t => 143-regress-floating-restart.t} | 0 ...ing-resize.t => 144-regress-floating-resize.t} | 0 testcases/t/{45-flattening.t => 145-flattening.t} | 0 ...loating-reinsert.t => 146-floating-reinsert.t} | 0 ...-floatingmove.t => 147-regress-floatingmove.t} | 0 ...atingmovews.t => 148-regress-floatingmovews.t} | 0 ...-dock-restart.t => 150-regress-dock-restart.t} | 0 ...ress-float-size.t => 151-regress-float-size.t} | 0 ...-regress-level-up.t => 152-regress-level-up.t} | 0 ...originalsize.t => 153-floating-originalsize.t} | 0 ...ultiple-dock.t => 154-regress-multiple-dock.t} | 0 ...ing-split-size.t => 155-floating-split-size.t} | 0 ...-fullscreen-focus.t => 156-fullscreen-focus.t} | 0 ...vel-up.t => 157-regress-fullscreen-level-up.t} | 0 .../t/{58-wm_take_focus.t => 158-wm_take_focus.t} | 0 .../t/{59-socketpaths.t => 159-socketpaths.t} | 0 ...rs-restart.t => 161-regress-borders-restart.t} | 0 ...ss-dock-urgent.t => 162-regress-dock-urgent.t} | 0 testcases/t/{63-wm-state.t => 163-wm-state.t} | 0 ...l-win-vs-client.t => 164-kill-win-vs-client.t} | 0 testcases/t/{65-for_window.t => 165-for_window.t} | 0 testcases/t/{66-assign.t => 166-assign.t} | 0 ...-workspace_layout.t => 167-workspace_layout.t} | 0 ...restart.t => 168-regress-fullscreen-restart.t} | 0 .../t/{69-border-toggle.t => 169-border-toggle.t} | 0 ...ocus_wrapping.t => 170-force_focus_wrapping.t} | 0 .../{71-config-migrate.t => 171-config-migrate.t} | 0 ...tart-on-named-ws.t => 172-start-on-named-ws.t} | 0 testcases/t/{73-get-marks.t => 173-get-marks.t} | 0 ...-focus-assign.t => 173-regress-focus-assign.t} | 0 .../t/{74-border-config.t => 174-border-config.t} | 0 ...-focus-toggle.t => 174-regress-focus-toggle.t} | 0 74 files changed, 15 insertions(+) rename testcases/t/{00-load.t => 000-load-deps.t} (100%) rename testcases/t/{01-tile.t => 001-tile.t} (100%) create mode 100644 testcases/t/002-i3-sync.t rename testcases/t/{05-ipc.t => 003-ipc.t} (100%) rename testcases/t/{03-unmanaged.t => 004-unmanaged.t} (100%) rename testcases/t/{04-floating.t => 005-floating.t} (100%) rename testcases/t/{02-fullscreen.t => 100-fullscreen.t} (100%) rename testcases/t/{06-focus.t => 101-focus.t} (100%) rename testcases/t/{10-dock.t => 102-dock.t} (100%) rename testcases/t/{07-move.t => 103-move.t} (100%) rename testcases/t/{08-focus-stack.t => 104-focus-stack.t} (100%) rename testcases/t/{09-stacking.t => 105-stacking.t} (100%) rename testcases/t/{11-goto.t => 111-goto.t} (100%) rename testcases/t/{12-floating-resize.t => 112-floating-resize.t} (100%) rename testcases/t/{13-urgent.t => 113-urgent.t} (100%) rename testcases/t/{14-client-leader.t => 114-client-leader.t} (100%) rename testcases/t/{15-ipc-workspaces.t => 115-ipc-workspaces.t} (100%) rename testcases/t/{16-nestedcons.t => 116-nestedcons.t} (100%) rename testcases/t/{17-workspace.t => 117-workspace.t} (100%) rename testcases/t/{18-openkill.t => 118-openkill.t} (100%) rename testcases/t/{19-match.t => 119-match.t} (100%) rename testcases/t/{20-multiple-cmds.t => 120-multiple-cmds.t} (100%) rename testcases/t/{21-next-prev.t => 121-next-prev.t} (100%) rename testcases/t/{22-split.t => 122-split.t} (100%) rename testcases/t/{24-move.t => 124-move.t} (100%) rename testcases/t/{26-regress-close.t => 126-regress-close.t} (100%) rename testcases/t/{27-regress-floating-parent.t => 127-regress-floating-parent.t} (100%) rename testcases/t/{28-open-order.t => 128-open-order.t} (100%) rename testcases/t/{29-focus-after-close.t => 129-focus-after-close.t} (100%) rename testcases/t/{30-close-empty-split.t => 130-close-empty-split.t} (100%) rename testcases/t/{31-stacking-order.t => 131-stacking-order.t} (100%) rename testcases/t/{32-move-workspace.t => 132-move-workspace.t} (100%) rename testcases/t/{33-size-hints.t => 133-size-hints.t} (100%) rename testcases/t/{34-invalid-command.t => 134-invalid-command.t} (100%) rename testcases/t/{35-floating-focus.t => 135-floating-focus.t} (100%) rename testcases/t/{36-floating-ws-empty.t => 136-floating-ws-empty.t} (100%) rename testcases/t/{37-floating-unmap.t => 137-floating-unmap.t} (100%) rename testcases/t/{38-floating-attach.t => 138-floating-attach.t} (100%) rename testcases/t/{39-ws-numbers.t => 139-ws-numbers.t} (100%) rename testcases/t/{40-focus-lost.t => 140-focus-lost.t} (100%) rename testcases/t/{41-resize.t => 141-resize.t} (100%) rename testcases/t/{42-regress-move-floating.t => 142-regress-move-floating.t} (100%) rename testcases/t/{43-regress-floating-restart.t => 143-regress-floating-restart.t} (100%) rename testcases/t/{44-regress-floating-resize.t => 144-regress-floating-resize.t} (100%) rename testcases/t/{45-flattening.t => 145-flattening.t} (100%) rename testcases/t/{46-floating-reinsert.t => 146-floating-reinsert.t} (100%) rename testcases/t/{47-regress-floatingmove.t => 147-regress-floatingmove.t} (100%) rename testcases/t/{48-regress-floatingmovews.t => 148-regress-floatingmovews.t} (100%) rename testcases/t/{50-regress-dock-restart.t => 150-regress-dock-restart.t} (100%) rename testcases/t/{51-regress-float-size.t => 151-regress-float-size.t} (100%) rename testcases/t/{52-regress-level-up.t => 152-regress-level-up.t} (100%) rename testcases/t/{53-floating-originalsize.t => 153-floating-originalsize.t} (100%) rename testcases/t/{54-regress-multiple-dock.t => 154-regress-multiple-dock.t} (100%) rename testcases/t/{55-floating-split-size.t => 155-floating-split-size.t} (100%) rename testcases/t/{56-fullscreen-focus.t => 156-fullscreen-focus.t} (100%) rename testcases/t/{57-regress-fullscreen-level-up.t => 157-regress-fullscreen-level-up.t} (100%) rename testcases/t/{58-wm_take_focus.t => 158-wm_take_focus.t} (100%) rename testcases/t/{59-socketpaths.t => 159-socketpaths.t} (100%) rename testcases/t/{61-regress-borders-restart.t => 161-regress-borders-restart.t} (100%) rename testcases/t/{62-regress-dock-urgent.t => 162-regress-dock-urgent.t} (100%) rename testcases/t/{63-wm-state.t => 163-wm-state.t} (100%) rename testcases/t/{64-kill-win-vs-client.t => 164-kill-win-vs-client.t} (100%) rename testcases/t/{65-for_window.t => 165-for_window.t} (100%) rename testcases/t/{66-assign.t => 166-assign.t} (100%) rename testcases/t/{67-workspace_layout.t => 167-workspace_layout.t} (100%) rename testcases/t/{68-regress-fullscreen-restart.t => 168-regress-fullscreen-restart.t} (100%) rename testcases/t/{69-border-toggle.t => 169-border-toggle.t} (100%) rename testcases/t/{70-force_focus_wrapping.t => 170-force_focus_wrapping.t} (100%) rename testcases/t/{71-config-migrate.t => 171-config-migrate.t} (100%) rename testcases/t/{72-start-on-named-ws.t => 172-start-on-named-ws.t} (100%) rename testcases/t/{73-get-marks.t => 173-get-marks.t} (100%) rename testcases/t/{73-regress-focus-assign.t => 173-regress-focus-assign.t} (100%) rename testcases/t/{74-border-config.t => 174-border-config.t} (100%) rename testcases/t/{74-regress-focus-toggle.t => 174-regress-focus-toggle.t} (100%) diff --git a/testcases/t/00-load.t b/testcases/t/000-load-deps.t similarity index 100% rename from testcases/t/00-load.t rename to testcases/t/000-load-deps.t diff --git a/testcases/t/01-tile.t b/testcases/t/001-tile.t similarity index 100% rename from testcases/t/01-tile.t rename to testcases/t/001-tile.t diff --git a/testcases/t/002-i3-sync.t b/testcases/t/002-i3-sync.t new file mode 100644 index 00000000..7518c949 --- /dev/null +++ b/testcases/t/002-i3-sync.t @@ -0,0 +1,15 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# checks if i3 supports I3_SYNC +# +use X11::XCB qw(:all); +use X11::XCB::Connection; +use i3test; + +my $x = X11::XCB::Connection->new; + +my $result = sync_with_i3($x); +ok($result, 'syncing was successful'); + +done_testing; diff --git a/testcases/t/05-ipc.t b/testcases/t/003-ipc.t similarity index 100% rename from testcases/t/05-ipc.t rename to testcases/t/003-ipc.t diff --git a/testcases/t/03-unmanaged.t b/testcases/t/004-unmanaged.t similarity index 100% rename from testcases/t/03-unmanaged.t rename to testcases/t/004-unmanaged.t diff --git a/testcases/t/04-floating.t b/testcases/t/005-floating.t similarity index 100% rename from testcases/t/04-floating.t rename to testcases/t/005-floating.t diff --git a/testcases/t/02-fullscreen.t b/testcases/t/100-fullscreen.t similarity index 100% rename from testcases/t/02-fullscreen.t rename to testcases/t/100-fullscreen.t diff --git a/testcases/t/06-focus.t b/testcases/t/101-focus.t similarity index 100% rename from testcases/t/06-focus.t rename to testcases/t/101-focus.t diff --git a/testcases/t/10-dock.t b/testcases/t/102-dock.t similarity index 100% rename from testcases/t/10-dock.t rename to testcases/t/102-dock.t diff --git a/testcases/t/07-move.t b/testcases/t/103-move.t similarity index 100% rename from testcases/t/07-move.t rename to testcases/t/103-move.t diff --git a/testcases/t/08-focus-stack.t b/testcases/t/104-focus-stack.t similarity index 100% rename from testcases/t/08-focus-stack.t rename to testcases/t/104-focus-stack.t diff --git a/testcases/t/09-stacking.t b/testcases/t/105-stacking.t similarity index 100% rename from testcases/t/09-stacking.t rename to testcases/t/105-stacking.t diff --git a/testcases/t/11-goto.t b/testcases/t/111-goto.t similarity index 100% rename from testcases/t/11-goto.t rename to testcases/t/111-goto.t diff --git a/testcases/t/12-floating-resize.t b/testcases/t/112-floating-resize.t similarity index 100% rename from testcases/t/12-floating-resize.t rename to testcases/t/112-floating-resize.t diff --git a/testcases/t/13-urgent.t b/testcases/t/113-urgent.t similarity index 100% rename from testcases/t/13-urgent.t rename to testcases/t/113-urgent.t diff --git a/testcases/t/14-client-leader.t b/testcases/t/114-client-leader.t similarity index 100% rename from testcases/t/14-client-leader.t rename to testcases/t/114-client-leader.t diff --git a/testcases/t/15-ipc-workspaces.t b/testcases/t/115-ipc-workspaces.t similarity index 100% rename from testcases/t/15-ipc-workspaces.t rename to testcases/t/115-ipc-workspaces.t diff --git a/testcases/t/16-nestedcons.t b/testcases/t/116-nestedcons.t similarity index 100% rename from testcases/t/16-nestedcons.t rename to testcases/t/116-nestedcons.t diff --git a/testcases/t/17-workspace.t b/testcases/t/117-workspace.t similarity index 100% rename from testcases/t/17-workspace.t rename to testcases/t/117-workspace.t diff --git a/testcases/t/18-openkill.t b/testcases/t/118-openkill.t similarity index 100% rename from testcases/t/18-openkill.t rename to testcases/t/118-openkill.t diff --git a/testcases/t/19-match.t b/testcases/t/119-match.t similarity index 100% rename from testcases/t/19-match.t rename to testcases/t/119-match.t diff --git a/testcases/t/20-multiple-cmds.t b/testcases/t/120-multiple-cmds.t similarity index 100% rename from testcases/t/20-multiple-cmds.t rename to testcases/t/120-multiple-cmds.t diff --git a/testcases/t/21-next-prev.t b/testcases/t/121-next-prev.t similarity index 100% rename from testcases/t/21-next-prev.t rename to testcases/t/121-next-prev.t diff --git a/testcases/t/22-split.t b/testcases/t/122-split.t similarity index 100% rename from testcases/t/22-split.t rename to testcases/t/122-split.t diff --git a/testcases/t/24-move.t b/testcases/t/124-move.t similarity index 100% rename from testcases/t/24-move.t rename to testcases/t/124-move.t diff --git a/testcases/t/26-regress-close.t b/testcases/t/126-regress-close.t similarity index 100% rename from testcases/t/26-regress-close.t rename to testcases/t/126-regress-close.t diff --git a/testcases/t/27-regress-floating-parent.t b/testcases/t/127-regress-floating-parent.t similarity index 100% rename from testcases/t/27-regress-floating-parent.t rename to testcases/t/127-regress-floating-parent.t diff --git a/testcases/t/28-open-order.t b/testcases/t/128-open-order.t similarity index 100% rename from testcases/t/28-open-order.t rename to testcases/t/128-open-order.t diff --git a/testcases/t/29-focus-after-close.t b/testcases/t/129-focus-after-close.t similarity index 100% rename from testcases/t/29-focus-after-close.t rename to testcases/t/129-focus-after-close.t diff --git a/testcases/t/30-close-empty-split.t b/testcases/t/130-close-empty-split.t similarity index 100% rename from testcases/t/30-close-empty-split.t rename to testcases/t/130-close-empty-split.t diff --git a/testcases/t/31-stacking-order.t b/testcases/t/131-stacking-order.t similarity index 100% rename from testcases/t/31-stacking-order.t rename to testcases/t/131-stacking-order.t diff --git a/testcases/t/32-move-workspace.t b/testcases/t/132-move-workspace.t similarity index 100% rename from testcases/t/32-move-workspace.t rename to testcases/t/132-move-workspace.t diff --git a/testcases/t/33-size-hints.t b/testcases/t/133-size-hints.t similarity index 100% rename from testcases/t/33-size-hints.t rename to testcases/t/133-size-hints.t diff --git a/testcases/t/34-invalid-command.t b/testcases/t/134-invalid-command.t similarity index 100% rename from testcases/t/34-invalid-command.t rename to testcases/t/134-invalid-command.t diff --git a/testcases/t/35-floating-focus.t b/testcases/t/135-floating-focus.t similarity index 100% rename from testcases/t/35-floating-focus.t rename to testcases/t/135-floating-focus.t diff --git a/testcases/t/36-floating-ws-empty.t b/testcases/t/136-floating-ws-empty.t similarity index 100% rename from testcases/t/36-floating-ws-empty.t rename to testcases/t/136-floating-ws-empty.t diff --git a/testcases/t/37-floating-unmap.t b/testcases/t/137-floating-unmap.t similarity index 100% rename from testcases/t/37-floating-unmap.t rename to testcases/t/137-floating-unmap.t diff --git a/testcases/t/38-floating-attach.t b/testcases/t/138-floating-attach.t similarity index 100% rename from testcases/t/38-floating-attach.t rename to testcases/t/138-floating-attach.t diff --git a/testcases/t/39-ws-numbers.t b/testcases/t/139-ws-numbers.t similarity index 100% rename from testcases/t/39-ws-numbers.t rename to testcases/t/139-ws-numbers.t diff --git a/testcases/t/40-focus-lost.t b/testcases/t/140-focus-lost.t similarity index 100% rename from testcases/t/40-focus-lost.t rename to testcases/t/140-focus-lost.t diff --git a/testcases/t/41-resize.t b/testcases/t/141-resize.t similarity index 100% rename from testcases/t/41-resize.t rename to testcases/t/141-resize.t diff --git a/testcases/t/42-regress-move-floating.t b/testcases/t/142-regress-move-floating.t similarity index 100% rename from testcases/t/42-regress-move-floating.t rename to testcases/t/142-regress-move-floating.t diff --git a/testcases/t/43-regress-floating-restart.t b/testcases/t/143-regress-floating-restart.t similarity index 100% rename from testcases/t/43-regress-floating-restart.t rename to testcases/t/143-regress-floating-restart.t diff --git a/testcases/t/44-regress-floating-resize.t b/testcases/t/144-regress-floating-resize.t similarity index 100% rename from testcases/t/44-regress-floating-resize.t rename to testcases/t/144-regress-floating-resize.t diff --git a/testcases/t/45-flattening.t b/testcases/t/145-flattening.t similarity index 100% rename from testcases/t/45-flattening.t rename to testcases/t/145-flattening.t diff --git a/testcases/t/46-floating-reinsert.t b/testcases/t/146-floating-reinsert.t similarity index 100% rename from testcases/t/46-floating-reinsert.t rename to testcases/t/146-floating-reinsert.t diff --git a/testcases/t/47-regress-floatingmove.t b/testcases/t/147-regress-floatingmove.t similarity index 100% rename from testcases/t/47-regress-floatingmove.t rename to testcases/t/147-regress-floatingmove.t diff --git a/testcases/t/48-regress-floatingmovews.t b/testcases/t/148-regress-floatingmovews.t similarity index 100% rename from testcases/t/48-regress-floatingmovews.t rename to testcases/t/148-regress-floatingmovews.t diff --git a/testcases/t/50-regress-dock-restart.t b/testcases/t/150-regress-dock-restart.t similarity index 100% rename from testcases/t/50-regress-dock-restart.t rename to testcases/t/150-regress-dock-restart.t diff --git a/testcases/t/51-regress-float-size.t b/testcases/t/151-regress-float-size.t similarity index 100% rename from testcases/t/51-regress-float-size.t rename to testcases/t/151-regress-float-size.t diff --git a/testcases/t/52-regress-level-up.t b/testcases/t/152-regress-level-up.t similarity index 100% rename from testcases/t/52-regress-level-up.t rename to testcases/t/152-regress-level-up.t diff --git a/testcases/t/53-floating-originalsize.t b/testcases/t/153-floating-originalsize.t similarity index 100% rename from testcases/t/53-floating-originalsize.t rename to testcases/t/153-floating-originalsize.t diff --git a/testcases/t/54-regress-multiple-dock.t b/testcases/t/154-regress-multiple-dock.t similarity index 100% rename from testcases/t/54-regress-multiple-dock.t rename to testcases/t/154-regress-multiple-dock.t diff --git a/testcases/t/55-floating-split-size.t b/testcases/t/155-floating-split-size.t similarity index 100% rename from testcases/t/55-floating-split-size.t rename to testcases/t/155-floating-split-size.t diff --git a/testcases/t/56-fullscreen-focus.t b/testcases/t/156-fullscreen-focus.t similarity index 100% rename from testcases/t/56-fullscreen-focus.t rename to testcases/t/156-fullscreen-focus.t diff --git a/testcases/t/57-regress-fullscreen-level-up.t b/testcases/t/157-regress-fullscreen-level-up.t similarity index 100% rename from testcases/t/57-regress-fullscreen-level-up.t rename to testcases/t/157-regress-fullscreen-level-up.t diff --git a/testcases/t/58-wm_take_focus.t b/testcases/t/158-wm_take_focus.t similarity index 100% rename from testcases/t/58-wm_take_focus.t rename to testcases/t/158-wm_take_focus.t diff --git a/testcases/t/59-socketpaths.t b/testcases/t/159-socketpaths.t similarity index 100% rename from testcases/t/59-socketpaths.t rename to testcases/t/159-socketpaths.t diff --git a/testcases/t/61-regress-borders-restart.t b/testcases/t/161-regress-borders-restart.t similarity index 100% rename from testcases/t/61-regress-borders-restart.t rename to testcases/t/161-regress-borders-restart.t diff --git a/testcases/t/62-regress-dock-urgent.t b/testcases/t/162-regress-dock-urgent.t similarity index 100% rename from testcases/t/62-regress-dock-urgent.t rename to testcases/t/162-regress-dock-urgent.t diff --git a/testcases/t/63-wm-state.t b/testcases/t/163-wm-state.t similarity index 100% rename from testcases/t/63-wm-state.t rename to testcases/t/163-wm-state.t diff --git a/testcases/t/64-kill-win-vs-client.t b/testcases/t/164-kill-win-vs-client.t similarity index 100% rename from testcases/t/64-kill-win-vs-client.t rename to testcases/t/164-kill-win-vs-client.t diff --git a/testcases/t/65-for_window.t b/testcases/t/165-for_window.t similarity index 100% rename from testcases/t/65-for_window.t rename to testcases/t/165-for_window.t diff --git a/testcases/t/66-assign.t b/testcases/t/166-assign.t similarity index 100% rename from testcases/t/66-assign.t rename to testcases/t/166-assign.t diff --git a/testcases/t/67-workspace_layout.t b/testcases/t/167-workspace_layout.t similarity index 100% rename from testcases/t/67-workspace_layout.t rename to testcases/t/167-workspace_layout.t diff --git a/testcases/t/68-regress-fullscreen-restart.t b/testcases/t/168-regress-fullscreen-restart.t similarity index 100% rename from testcases/t/68-regress-fullscreen-restart.t rename to testcases/t/168-regress-fullscreen-restart.t diff --git a/testcases/t/69-border-toggle.t b/testcases/t/169-border-toggle.t similarity index 100% rename from testcases/t/69-border-toggle.t rename to testcases/t/169-border-toggle.t diff --git a/testcases/t/70-force_focus_wrapping.t b/testcases/t/170-force_focus_wrapping.t similarity index 100% rename from testcases/t/70-force_focus_wrapping.t rename to testcases/t/170-force_focus_wrapping.t diff --git a/testcases/t/71-config-migrate.t b/testcases/t/171-config-migrate.t similarity index 100% rename from testcases/t/71-config-migrate.t rename to testcases/t/171-config-migrate.t diff --git a/testcases/t/72-start-on-named-ws.t b/testcases/t/172-start-on-named-ws.t similarity index 100% rename from testcases/t/72-start-on-named-ws.t rename to testcases/t/172-start-on-named-ws.t diff --git a/testcases/t/73-get-marks.t b/testcases/t/173-get-marks.t similarity index 100% rename from testcases/t/73-get-marks.t rename to testcases/t/173-get-marks.t diff --git a/testcases/t/73-regress-focus-assign.t b/testcases/t/173-regress-focus-assign.t similarity index 100% rename from testcases/t/73-regress-focus-assign.t rename to testcases/t/173-regress-focus-assign.t diff --git a/testcases/t/74-border-config.t b/testcases/t/174-border-config.t similarity index 100% rename from testcases/t/74-border-config.t rename to testcases/t/174-border-config.t diff --git a/testcases/t/74-regress-focus-toggle.t b/testcases/t/174-regress-focus-toggle.t similarity index 100% rename from testcases/t/74-regress-focus-toggle.t rename to testcases/t/174-regress-focus-toggle.t From c67b1bc84f0fa45ad2a69d5ed6e29df20bf2aff3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 8 Oct 2011 15:18:04 +0100 Subject: [PATCH 135/333] remove obsolete CMDMODE and TODO files, update LICENSE --- CMDMODE | 47 ----------------------------------------------- LICENSE | 2 +- TODO | 7 ------- 3 files changed, 1 insertion(+), 55 deletions(-) delete mode 100644 CMDMODE delete mode 100644 TODO diff --git a/CMDMODE b/CMDMODE deleted file mode 100644 index 95cb5bc1..00000000 --- a/CMDMODE +++ /dev/null @@ -1,47 +0,0 @@ ---------------------- -- Command mode ---------------------- - -This is the grammar for the 'command mode' (your configuration file -uses these commands, too). - -left := | -right := | -up := | -down := | - -where := | -move := -snap := - -cmd := [ ] [ | ] -with := { [ ] }+ -jump := [ "[/]" | [ ] ] -focus := focus [ | floating | tiling | ft ] - (travels the focus stack backwards, number of times (by default 1). - So by specifying "focus 1" it selects the window which last had the focus - before you focused the current one window. - The following 3 special values are also valid: - 'floating' (select the next floating window). - 'tiling' (select the next tiling window). - 'ft' (toggle tiling/floating: if the current window is floating, - select the next tiling window and vice-versa) -special := [ exec | kill | exit | restart ] - -input := [ | | | | ] - -you can cancel command mode by pressing escape anytime. - -Some examples: - -Select the window on the left: -h - -Select the window two places on the left: -2h - -Move window to the right: -ml - -Move window and window on the bottom to the right: -wk ml diff --git a/LICENSE b/LICENSE index 5771597b..05f078e2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2009, Michael Stapelberg +Copyright © 2009-2011, Michael Stapelberg and contributors All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/TODO b/TODO deleted file mode 100644 index 40b56ce3..00000000 --- a/TODO +++ /dev/null @@ -1,7 +0,0 @@ - -Please see http://i3wm.org/ for our bugtracker. - -Some old notes, just to not lose them: - * was passiert, wenn zwei fenster fullscreen wollen auf dem selben workspace? - * OpenOffice - * funktioniert xinerama etc. mit --below? insbesondere das fokus-verschieben in andere screens From 0a1a32e06a18a03330a7727ebf7d538c1404bd69 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 8 Oct 2011 19:12:28 +0100 Subject: [PATCH 136/333] Fix 'make dist' --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 54d8464f..58fd0178 100644 --- a/Makefile +++ b/Makefile @@ -99,7 +99,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 i3-sensible-* i3.config.keycodes DEPENDS GOALS LICENSE PACKAGE-MAINTAINER TODO RELEASE-NOTES-${VERSION} i3.config i3.desktop i3.welcome pseudo-doc.doxygen i3-wsbar Makefile i3-${VERSION} + cp i3-migrate-config-to-v4 i3-sensible-* i3.config.keycodes DEPENDS GOALS LICENSE PACKAGE-MAINTAINER RELEASE-NOTES-${VERSION} i3.config i3.desktop i3.welcome pseudo-doc.doxygen i3-wsbar Makefile i3-${VERSION} cp -r src libi3 i3-msg i3-nagbar i3-config-wizard i3bar yajl-fallback include man i3-${VERSION} # Only copy toplevel documentation (important stuff) mkdir i3-${VERSION}/docs From 19b13a0af5264462bd339d8d64a0def7a7600a8e Mon Sep 17 00:00:00 2001 From: Maik Fischer Date: Sat, 8 Oct 2011 20:58:37 +0100 Subject: [PATCH 137/333] testcases: add Makefile.PL to automatically install deps via CPAN --- testcases/Makefile.PL | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100755 testcases/Makefile.PL diff --git a/testcases/Makefile.PL b/testcases/Makefile.PL new file mode 100755 index 00000000..4b3f1ade --- /dev/null +++ b/testcases/Makefile.PL @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +use strict; use warnings; +use ExtUtils::MakeMaker; + +WriteMakefile( + NAME => 'i3 testsuite', + MIN_PERL_VERSION => '5.010000', # 5.10.0 + PREREQ_PM => { + 'AnyEvent' => 0, + 'AnyEvent::I3' => '0.08', + 'X11::XCB' => '0.03', + 'Test::Most' => 0, + 'Test::Deep' => 0, + 'EV' => 0, + }, + # don't install any files from this directory + PM => {}, +); +# and don't run the tests while installing +sub MY::test { } From d78a2444e5fc702932dfc00ba0992679a6bcac16 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 9 Oct 2011 13:05:29 +0100 Subject: [PATCH 138/333] Bugfix: make i3bar depend on libi3 --- i3bar/Makefile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/i3bar/Makefile b/i3bar/Makefile index 643065df..a0c472bf 100644 --- a/i3bar/Makefile +++ b/i3bar/Makefile @@ -10,9 +10,12 @@ CPPFLAGS += -I$(TOPDIR)/include all: i3bar doc -i3bar: ${FILES} +i3bar: libi3/libi3.a ${FILES} echo "LINK" - $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS) + +libi3/%.a: + $(MAKE) -C $(TOPDIR)/libi3 doc: echo "" From 29c185dd0b88cb845513117461d0ca513843abaf Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 9 Oct 2011 13:40:15 +0100 Subject: [PATCH 139/333] Move fake_configure_notify to libi3 --- include/libi3.h | 11 ++++++++ include/xcb.h | 8 ------ libi3/fake_configure_notify.c | 49 +++++++++++++++++++++++++++++++++++ src/xcb.c | 34 +----------------------- 4 files changed, 61 insertions(+), 41 deletions(-) create mode 100644 libi3/fake_configure_notify.c diff --git a/include/libi3.h b/include/libi3.h index 079d160b..123e5895 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -5,6 +5,9 @@ #ifndef _LIBI3_H #define _LIBI3_H +#include +#include + /** * Try to get the socket path from X11 and return NULL if it doesn’t work. * @@ -67,4 +70,12 @@ int ipc_send_message(int sockfd, uint32_t message_size, int ipc_recv_message(int sockfd, uint32_t message_type, uint32_t *reply_length, uint8_t **reply); +/** + * Generates a configure_notify event and sends it to the given window + * Applications need this to think they’ve configured themselves correctly. + * The truth is, however, that we will manage them. + * + */ +void fake_configure_notify(xcb_connection_t *conn, xcb_rectangle_t r, xcb_window_t window, int border_width); + #endif diff --git a/include/xcb.h b/include/xcb.h index 185163b4..5bc40d2a 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -102,14 +102,6 @@ void xcb_draw_rect(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext_t gc, uint32_t colorpixel, uint32_t x, uint32_t y, uint32_t width, uint32_t height); -/** - * Generates a configure_notify event and sends it to the given window - * Applications need this to think they’ve configured themselves correctly. - * The truth is, however, that we will manage them. - * - */ -void fake_configure_notify(xcb_connection_t *conn, Rect r, xcb_window_t window, int border_width); - /** * Generates a configure_notify_event with absolute coordinates (relative to * the X root window, not to the client’s frame) for the given client. diff --git a/libi3/fake_configure_notify.c b/libi3/fake_configure_notify.c new file mode 100644 index 00000000..5f954cfa --- /dev/null +++ b/libi3/fake_configure_notify.c @@ -0,0 +1,49 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009-2011 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ +#include +#include + +#include +#include + +#include "libi3.h" + +/* + * Generates a configure_notify event and sends it to the given window + * Applications need this to think they’ve configured themselves correctly. + * The truth is, however, that we will manage them. + * + */ +void fake_configure_notify(xcb_connection_t *conn, xcb_rectangle_t r, xcb_window_t window, int border_width) { + /* Every X11 event is 32 bytes long. Therefore, XCB will copy 32 bytes. + * In order to properly initialize these bytes, we allocate 32 bytes even + * though we only need less for an xcb_configure_notify_event_t */ + void *event = scalloc(32); + xcb_configure_notify_event_t *generated_event = event; + + generated_event->event = window; + generated_event->window = window; + generated_event->response_type = XCB_CONFIGURE_NOTIFY; + + generated_event->x = r.x; + generated_event->y = r.y; + generated_event->width = r.width; + generated_event->height = r.height; + + generated_event->border_width = border_width; + generated_event->above_sibling = XCB_NONE; + generated_event->override_redirect = false; + + xcb_send_event(conn, false, window, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)generated_event); + xcb_flush(conn); + + free(event); +} diff --git a/src/xcb.c b/src/xcb.c index 09f0fb25..31d78703 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -162,45 +162,13 @@ void xcb_draw_rect(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect); } -/* - * Generates a configure_notify event and sends it to the given window - * Applications need this to think they’ve configured themselves correctly. - * The truth is, however, that we will manage them. - * - */ -void fake_configure_notify(xcb_connection_t *conn, Rect r, xcb_window_t window, int border_width) { - /* Every X11 event is 32 bytes long. Therefore, XCB will copy 32 bytes. - * In order to properly initialize these bytes, we allocate 32 bytes even - * though we only need less for an xcb_configure_notify_event_t */ - void *event = scalloc(32); - xcb_configure_notify_event_t *generated_event = event; - - generated_event->event = window; - generated_event->window = window; - generated_event->response_type = XCB_CONFIGURE_NOTIFY; - - generated_event->x = r.x; - generated_event->y = r.y; - generated_event->width = r.width; - generated_event->height = r.height; - - generated_event->border_width = border_width; - generated_event->above_sibling = XCB_NONE; - generated_event->override_redirect = false; - - xcb_send_event(conn, false, window, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)generated_event); - xcb_flush(conn); - - free(event); -} - /* * Generates a configure_notify_event with absolute coordinates (relative to the X root * window, not to the client’s frame) for the given client. * */ void fake_absolute_configure_notify(Con *con) { - Rect absolute; + xcb_rectangle_t absolute; if (con->window == NULL) return; From b561b911ee1726575a23608f12b38aa2c29899c6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 9 Oct 2011 13:50:39 +0100 Subject: [PATCH 140/333] i3bar: add modeline to src/xcb.c --- i3bar/src/xcb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 98333113..6470903e 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -1,4 +1,6 @@ /* + * vim:ts=4:sw=4:expandtab + * * i3bar - an xcb-based status- and ws-bar for i3 * * © 2010-2011 Axel Wagner and contributors From e77f08d1fc8e19af884158fd7d619077f837e261 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 9 Oct 2011 13:50:46 +0100 Subject: [PATCH 141/333] i3bar: handle ConfigureRequests for tray children (fixes gtk3 size issue) Thanks to yvesf for this simple python test script: from gi.repository import Gtk as gtk def cb(*a): print a def si_popup(*a): print a status_icon = gtk.StatusIcon() status_icon.set_from_stock(gtk.STOCK_OPEN) status_icon.connect("activate", cb) gtk.main() --- i3bar/src/xcb.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 6470903e..b38f9630 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -28,6 +28,7 @@ #include #include "common.h" +#include "libi3.h" #if defined(__APPLE__) @@ -642,6 +643,42 @@ static void handle_property_notify(xcb_property_notify_event_t *event) { } } +/* + * Handle ConfigureRequests by denying them and sending the client a + * ConfigureNotify with its actual size. + * + */ +static void handle_configure_request(xcb_configure_request_event_t *event) { + DLOG("ConfigureRequest for window = %08x\n", event->window); + + trayclient *trayclient; + i3_output *output; + SLIST_FOREACH(output, outputs, slist) { + if (!output->active) + continue; + + int clients = 0; + TAILQ_FOREACH_REVERSE(trayclient, output->trayclients, tc_head, tailq) { + clients++; + + if (trayclient->win != event->window) + continue; + + xcb_rectangle_t rect; + rect.x = output->rect.w - (clients * (font_height + 2)); + rect.y = 2; + rect.width = font_height; + rect.height = font_height; + + DLOG("This is a tray window. x = %d\n", rect.x); + fake_configure_notify(xcb_connection, rect, event->window, 0); + return; + } + } + + DLOG("WARNING: Could not find corresponding tray window.\n"); +} + /* * This function is called immediately before the main loop locks. We flush xcb * then (and only then) @@ -691,6 +728,10 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) { /* PropertyNotify */ handle_property_notify((xcb_property_notify_event_t*) event); break; + case XCB_CONFIGURE_REQUEST: + /* ConfigureRequest, sent by a tray child */ + handle_configure_request((xcb_configure_request_event_t*) event); + break; } FREE(event); } @@ -1148,8 +1189,14 @@ void reconfig_windows() { values[0] = colors.bar_bg; /* If hide_on_modifier is set, i3 is not supposed to manage our bar-windows */ values[1] = config.hide_on_modifier; - /* The events we want to receive */ - values[2] = XCB_EVENT_MASK_EXPOSURE; + /* We enable the following EventMask fields: + * EXPOSURE, to get expose events (we have to re-draw then) + * SUBSTRUCTURE_REDIRECT, to get ConfigureRequests when the tray + * child windows use ConfigureWindow + * BUTTON_PRESS, to handle clicks on the workspace buttons + * */ + values[2] = XCB_EVENT_MASK_EXPOSURE | + XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; if (!config.disable_ws) { values[2] |= XCB_EVENT_MASK_BUTTON_PRESS; } From 64a7017c32892e58cad3d504a0fee1161c75447a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 9 Oct 2011 14:28:20 +0100 Subject: [PATCH 142/333] i3bar: add modelines to all files --- i3bar/src/child.c | 2 ++ i3bar/src/ipc.c | 2 ++ i3bar/src/main.c | 2 ++ i3bar/src/outputs.c | 2 ++ i3bar/src/workspaces.c | 2 ++ 5 files changed, 10 insertions(+) diff --git a/i3bar/src/child.c b/i3bar/src/child.c index aa9d6554..ff1bc8bb 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -1,4 +1,6 @@ /* + * vim:ts=4:sw=4:expandtab + * * i3bar - an xcb-based status- and ws-bar for i3 * * © 2010-2011 Axel Wagner and contributors diff --git a/i3bar/src/ipc.c b/i3bar/src/ipc.c index 7769fdb1..e31de333 100644 --- a/i3bar/src/ipc.c +++ b/i3bar/src/ipc.c @@ -1,4 +1,6 @@ /* + * vim:ts=4:sw=4:expandtab + * * i3bar - an xcb-based status- and ws-bar for i3 * * © 2010-2011 Axel Wagner and contributors diff --git a/i3bar/src/main.c b/i3bar/src/main.c index 0def3057..bfccb8ed 100644 --- a/i3bar/src/main.c +++ b/i3bar/src/main.c @@ -1,4 +1,6 @@ /* + * vim:ts=4:sw=4:expandtab + * * i3bar - an xcb-based status- and ws-bar for i3 * * © 2010-2011 Axel Wagner and contributors diff --git a/i3bar/src/outputs.c b/i3bar/src/outputs.c index 464f24a0..11f7dc2b 100644 --- a/i3bar/src/outputs.c +++ b/i3bar/src/outputs.c @@ -1,4 +1,6 @@ /* + * vim:ts=4:sw=4:expandtab + * * i3bar - an xcb-based status- and ws-bar for i3 * * © 2010-2011 Axel Wagner and contributors diff --git a/i3bar/src/workspaces.c b/i3bar/src/workspaces.c index a84e152b..8099cd78 100644 --- a/i3bar/src/workspaces.c +++ b/i3bar/src/workspaces.c @@ -1,4 +1,6 @@ /* + * vim:ts=4:sw=4:expandtab + * * i3bar - an xcb-based status- and ws-bar for i3 * * © 2010-2011 Axel Wagner and contributors From a70e2057c827ae74f9405f11649e3a03ab7280b3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 9 Oct 2011 14:45:23 +0100 Subject: [PATCH 143/333] i3bar: set WM_CLASS and WM_NAME --- i3bar/src/xcb.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index b38f9630..9f3dd187 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -1220,6 +1221,31 @@ void reconfig_windows() { walk->rect.w, walk->rect.h); + /* Set the WM_CLASS and WM_NAME (we don't need UTF-8) atoms */ + xcb_void_cookie_t class_cookie; + class_cookie = xcb_change_property(xcb_connection, + XCB_PROP_MODE_REPLACE, + walk->bar, + XCB_ATOM_WM_CLASS, + XCB_ATOM_STRING, + 8, + (strlen("i3bar") + 1) * 2, + "i3bar\0i3bar\0"); + + char *name; + if (asprintf(&name, "i3bar for output %s", walk->name) == -1) + err(EXIT_FAILURE, "asprintf()"); + xcb_void_cookie_t name_cookie; + name_cookie = xcb_change_property(xcb_connection, + XCB_PROP_MODE_REPLACE, + walk->bar, + XCB_ATOM_WM_NAME, + XCB_ATOM_STRING, + 8, + strlen(name), + name); + free(name); + /* We want dock-windows (for now). When override_redirect is set, i3 is ignoring * this one */ xcb_void_cookie_t dock_cookie = xcb_change_property(xcb_connection, @@ -1293,6 +1319,8 @@ void reconfig_windows() { if (xcb_request_failed(win_cookie, "Could not create window") || xcb_request_failed(pm_cookie, "Could not create pixmap") || xcb_request_failed(dock_cookie, "Could not set dock mode") || + xcb_request_failed(class_cookie, "Could not set WM_CLASS") || + xcb_request_failed(name_cookie, "Could not set WM_NAME") || xcb_request_failed(strut_cookie, "Could not set strut") || xcb_request_failed(gc_cookie, "Could not create graphical context") || (!config.hide_on_modifier && xcb_request_failed(map_cookie, "Could not map window"))) { From 18991a65c769845ee62fc90701873dd5b6e0219f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 9 Oct 2011 14:45:34 +0100 Subject: [PATCH 144/333] log WM_NAME (non-utf-8 window titles) --- src/window.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/window.c b/src/window.c index 4b8b6614..bc8d28ac 100644 --- a/src/window.c +++ b/src/window.c @@ -123,6 +123,7 @@ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bo return; } + LOG("WM_NAME changed to \"%s\"\n", new_name); LOG("Using legacy window title. Note that in order to get Unicode window " "titles in i3, the application has to set _NET_WM_NAME (UTF-8)\n"); From 45a9eeb29dfdd8a17b3c6ecd9543df1d66fef856 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 9 Oct 2011 18:24:06 +0100 Subject: [PATCH 145/333] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20use=20a=20bla?= =?UTF-8?q?nk=20after=20comma=20in=20ldflags=5Ffor=5Flib=20calls=20(Thanks?= =?UTF-8?q?=20Raphael)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit '-l foo' instead of '-lfoo' is a problem on FreeBSD. --- common.mk | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/common.mk b/common.mk index 53094fb3..08d26b71 100644 --- a/common.mk +++ b/common.mk @@ -21,7 +21,11 @@ $(error "pkg-config was not found") endif # An easier way to get CFLAGS and LDFLAGS falling back in case there's -# no pkg-config support for certain libraries +# no pkg-config support for certain libraries. +# NOTE that you must not use a blank after comma when calling this: +# $(call ldflags_for_lib name, fallback) # bad +# $(call ldflags_for_lib name,fallback) # good +# Otherwise, the compiler will get -l foo instead of -lfoo cflags_for_lib = $(shell pkg-config --silence-errors --cflags $(1)) ldflags_for_lib = $(shell pkg-config --exists $(1) && pkg-config --libs $(1) || echo -l$(2)) @@ -59,23 +63,23 @@ endif LIBS += -lm LIBS += -L $(TOPDIR)/libi3 -li3 -LIBS += $(call ldflags_for_lib, xcb-event, xcb-event) -LIBS += $(call ldflags_for_lib, xcb-keysyms, xcb-keysyms) +LIBS += $(call ldflags_for_lib, xcb-event,xcb-event) +LIBS += $(call ldflags_for_lib, xcb-keysyms,xcb-keysyms) ifeq ($(shell pkg-config --exists xcb-util || echo 1),1) -LIBS += $(call ldflags_for_lib, xcb-atom, xcb-atom) -LIBS += $(call ldflags_for_lib, xcb-aux, xcb-aux) +LIBS += $(call ldflags_for_lib, xcb-atom,xcb-atom) +LIBS += $(call ldflags_for_lib, xcb-aux,xcb-aux) else LIBS += $(call ldflags_for_lib, xcb-util) endif -LIBS += $(call ldflags_for_lib, xcb-icccm, xcb-icccm) -LIBS += $(call ldflags_for_lib, xcb-xinerama, xcb-xinerama) -LIBS += $(call ldflags_for_lib, xcb-randr, xcb-randr) -LIBS += $(call ldflags_for_lib, xcb, xcb) -LIBS += $(call ldflags_for_lib, xcursor, Xcursor) -LIBS += $(call ldflags_for_lib, x11, X11) -LIBS += $(call ldflags_for_lib, yajl, yajl) -LIBS += $(call ldflags_for_lib, libev, ev) -LIBS += $(call ldflags_for_lib, libpcre, pcre) +LIBS += $(call ldflags_for_lib, xcb-icccm,xcb-icccm) +LIBS += $(call ldflags_for_lib, xcb-xinerama,xcb-xinerama) +LIBS += $(call ldflags_for_lib, xcb-randr,xcb-randr) +LIBS += $(call ldflags_for_lib, xcb,xcb) +LIBS += $(call ldflags_for_lib, xcursor,Xcursor) +LIBS += $(call ldflags_for_lib, x11,X11) +LIBS += $(call ldflags_for_lib, yajl,yajl) +LIBS += $(call ldflags_for_lib, libev,ev) +LIBS += $(call ldflags_for_lib, libpcre,pcre) # Please test if -Wl,--as-needed works on your platform and send me a patch. # it is known not to work on Darwin (Mac OS X) From 0f74f9b921ffa17a81ca3d2785410ab57fa6bc4d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 9 Oct 2011 18:25:00 +0100 Subject: [PATCH 146/333] remove left-over type definition for mode_info --- src/randr.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/randr.c b/src/randr.c index f5f5d198..96cc880a 100644 --- a/src/randr.c +++ b/src/randr.c @@ -21,7 +21,6 @@ /* While a clean namespace is usually a pretty good thing, we really need * to use shorter names than the whole xcb_randr_* default names. */ typedef xcb_randr_get_crtc_info_reply_t crtc_info; -typedef xcb_randr_mode_info_t mode_info; typedef xcb_randr_get_screen_resources_current_reply_t resources_reply; /* Pointer to the result of the query for primary output */ From 1942594ae96deef0c044a6fe4082e9eded97ddfc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 9 Oct 2011 18:35:58 +0100 Subject: [PATCH 147/333] makefile: redirect stderr to /dev/null when invoking pkg-config This silences an error about gnome-config not being installed (pkg-config seems to use gnome-config as a fall-back on some systems). --- common.mk | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/common.mk b/common.mk index 08d26b71..4237114d 100644 --- a/common.mk +++ b/common.mk @@ -22,12 +22,16 @@ endif # An easier way to get CFLAGS and LDFLAGS falling back in case there's # no pkg-config support for certain libraries. +# # NOTE that you must not use a blank after comma when calling this: # $(call ldflags_for_lib name, fallback) # bad # $(call ldflags_for_lib name,fallback) # good # Otherwise, the compiler will get -l foo instead of -lfoo -cflags_for_lib = $(shell pkg-config --silence-errors --cflags $(1)) -ldflags_for_lib = $(shell pkg-config --exists $(1) && pkg-config --libs $(1) || echo -l$(2)) +# +# We redirect stderr to /dev/null because pkg-config prints an error if support +# for gnome-config was enabled but gnome-config is not actually installed. +cflags_for_lib = $(shell pkg-config --silence-errors --cflags $(1) 2>/dev/null) +ldflags_for_lib = $(shell pkg-config --exists $(1) && pkg-config --libs $(1) 2>/dev/null || echo -l$(2)) CFLAGS += -std=c99 CFLAGS += -pipe @@ -37,7 +41,7 @@ CFLAGS += -Wall CFLAGS += -Wunused-value CFLAGS += -Iinclude CFLAGS += $(call cflags_for_lib, xcb-keysyms) -ifeq ($(shell pkg-config --exists xcb-util || echo 1),1) +ifeq ($(shell pkg-config --exists xcb-util 2>/dev/null || echo 1),1) CPPFLAGS += -DXCB_COMPAT CFLAGS += $(call cflags_for_lib, xcb-atom) CFLAGS += $(call cflags_for_lib, xcb-aux) @@ -65,7 +69,7 @@ LIBS += -lm LIBS += -L $(TOPDIR)/libi3 -li3 LIBS += $(call ldflags_for_lib, xcb-event,xcb-event) LIBS += $(call ldflags_for_lib, xcb-keysyms,xcb-keysyms) -ifeq ($(shell pkg-config --exists xcb-util || echo 1),1) +ifeq ($(shell pkg-config --exists xcb-util 2>/dev/null || echo 1),1) LIBS += $(call ldflags_for_lib, xcb-atom,xcb-atom) LIBS += $(call ldflags_for_lib, xcb-aux,xcb-aux) else From c5e9527abc63368ccfce71aae89c533a77a4b732 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 10 Oct 2011 11:37:43 +0100 Subject: [PATCH 148/333] i3bar: include xcb_compat.h for older XCB versions (Thanks motif) --- i3bar/src/xcb.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 9f3dd187..bb78d5e9 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -13,6 +13,11 @@ #include #include #include + +#ifdef XCB_COMPAT +#include "xcb_compat.h" +#endif + #include #include #include From 99825ff2684a85bb6cf53d4e0857393e40028c6f Mon Sep 17 00:00:00 2001 From: Raphael Kubo da Costa Date: Sun, 9 Oct 2011 18:35:58 -0300 Subject: [PATCH 149/333] common.mk: Silence some remaining pkg-config calls. Some pkg-config calls still didn't redirect stderr to /dev/null, causing the gnome-config error messages to be printed. --- common.mk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common.mk b/common.mk index 4237114d..bc831615 100644 --- a/common.mk +++ b/common.mk @@ -31,7 +31,7 @@ endif # We redirect stderr to /dev/null because pkg-config prints an error if support # for gnome-config was enabled but gnome-config is not actually installed. cflags_for_lib = $(shell pkg-config --silence-errors --cflags $(1) 2>/dev/null) -ldflags_for_lib = $(shell pkg-config --exists $(1) && pkg-config --libs $(1) 2>/dev/null || echo -l$(2)) +ldflags_for_lib = $(shell pkg-config --exists 2>/dev/null $(1) && pkg-config --libs $(1) 2>/dev/null || echo -l$(2)) CFLAGS += -std=c99 CFLAGS += -pipe @@ -61,7 +61,7 @@ CPPFLAGS += -DI3_VERSION=\"${GIT_VERSION}\" CPPFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\" CPPFLAGS += -DTERM_EMU=\"$(TERM_EMU)\" -ifeq ($(shell pkg-config --atleast-version=8.10 libpcre && echo 1),1) +ifeq ($(shell pkg-config --atleast-version=8.10 libpcre 2>/dev/null && echo 1),1) CPPFLAGS += -DPCRE_HAS_UCP=1 endif From 51116b46f19dad0ea95b67be369ec1e88f1e35c3 Mon Sep 17 00:00:00 2001 From: Raphael Kubo da Costa Date: Sun, 9 Oct 2011 19:30:08 -0300 Subject: [PATCH 150/333] Use $(MAKE) instead of hardcoding `make' in the Makefiles. When one is using gmake or anything other than make itself, the same command should be used in the Makefiles too. --- Makefile | 4 ++-- i3bar/Makefile | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 58fd0178..b2a39952 100644 --- a/Makefile +++ b/Makefile @@ -104,8 +104,8 @@ dist: distclean # Only copy toplevel documentation (important stuff) mkdir i3-${VERSION}/docs # Pre-generate documentation - make -C docs - make -C i3bar/doc + $(MAKE) -C docs + $(MAKE) -C i3bar/doc # Cleanup τεχ output files find docs -regex ".*\.\(aux\|out\|log\|toc\|bm\|dvi\|log\)" -exec rm '{}' \; find docs -maxdepth 1 -type f ! \( -name "*.xcf" -or -name "*.svg" \) -exec cp '{}' i3-${VERSION}/docs \; diff --git a/i3bar/Makefile b/i3bar/Makefile index a0c472bf..2a05b146 100644 --- a/i3bar/Makefile +++ b/i3bar/Makefile @@ -33,10 +33,10 @@ install: all clean: rm -f src/*.o - make -C doc clean + $(MAKE) -C doc clean distclean: clean rm -f i3bar - make -C doc distclean + $(MAKE) -C doc distclean .PHONY: install clean distclean doc From fb4b012013dab6d2d58efe41243017529a4ef33e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 10 Oct 2011 15:09:39 +0100 Subject: [PATCH 151/333] i3bar: Bugfix: Add tray icon padding when calculating text position (Thanks Bacardi55) --- i3bar/src/xcb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index bb78d5e9..4bbc8da7 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -1418,7 +1418,7 @@ void draw_bars() { /* We assume the tray icons are quadratic (we use the font * *height* as *width* of the icons) because we configured them * like this. */ - traypx += font_height; + traypx += font_height + 2; } /* Add 2px of padding if there are any tray icons */ if (traypx > 0) From 2a29d9c2c16ebd939d6729fe755747799472dd6c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 9 Oct 2011 18:19:31 +0100 Subject: [PATCH 152/333] Make conn_screen available outside of main() Will be used in other parts of the code for startup notification --- include/i3.h | 1 + src/main.c | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/include/i3.h b/include/i3.h index 22dcd476..f2b97824 100644 --- a/include/i3.h +++ b/include/i3.h @@ -20,6 +20,7 @@ #define _I3_H extern xcb_connection_t *conn; +extern int conn_screen; extern xcb_key_symbols_t *keysyms; extern char **start_argv; extern Display *xlibdpy, *xkbdpy; diff --git a/src/main.c b/src/main.c index 3ebfb5f4..6020f109 100644 --- a/src/main.c +++ b/src/main.c @@ -19,6 +19,8 @@ extern Con *focused; char **start_argv; xcb_connection_t *conn; +/* The screen (0 when you are using DISPLAY=:0) of the connection 'conn' */ +int conn_screen; xcb_screen_t *root_screen; xcb_window_t root; @@ -175,7 +177,6 @@ static void i3_exit() { } int main(int argc, char *argv[]) { - int screens; char *override_configpath = NULL; bool autostart = true; char *layout_path = NULL; @@ -357,7 +358,7 @@ int main(int argc, char *argv[]) { LOG("i3 (tree) version " I3_VERSION " starting\n"); - conn = xcb_connect(NULL, &screens); + conn = xcb_connect(NULL, &conn_screen); if (xcb_connection_has_error(conn)) errx(EXIT_FAILURE, "Cannot open display\n"); @@ -368,7 +369,7 @@ int main(int argc, char *argv[]) { if (main_loop == NULL) die("Could not initialize libev. Bad LIBEV_FLAGS?\n"); - root_screen = xcb_aux_get_screen(conn, screens); + root_screen = xcb_aux_get_screen(conn, conn_screen); root = root_screen->root; root_depth = root_screen->root_depth; xcb_get_geometry_cookie_t gcookie = xcb_get_geometry(conn, root); From b9db72dc8af70767a05fea73fb8ebacf0c5db6b8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 9 Oct 2011 18:21:59 +0100 Subject: [PATCH 153/333] Implement support for startup notifications This only sets up startup notifications for the 'exec' commands and directives. Monitoring startups follows later. --- common.mk | 2 ++ include/i3.h | 8 ++++++++ src/main.c | 10 ++++++++++ src/util.c | 23 +++++++++++++++++++++++ 4 files changed, 43 insertions(+) diff --git a/common.mk b/common.mk index bc831615..9bf427e0 100644 --- a/common.mk +++ b/common.mk @@ -57,6 +57,7 @@ CFLAGS += $(call cflags_for_lib, x11) CFLAGS += $(call cflags_for_lib, yajl) CFLAGS += $(call cflags_for_lib, libev) CFLAGS += $(call cflags_for_lib, libpcre) +CFLAGS += $(call cflags_for_lib, libstartup-notification-1.0) CPPFLAGS += -DI3_VERSION=\"${GIT_VERSION}\" CPPFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\" CPPFLAGS += -DTERM_EMU=\"$(TERM_EMU)\" @@ -84,6 +85,7 @@ LIBS += $(call ldflags_for_lib, x11,X11) LIBS += $(call ldflags_for_lib, yajl,yajl) LIBS += $(call ldflags_for_lib, libev,ev) LIBS += $(call ldflags_for_lib, libpcre,pcre) +LIBS += $(call ldflags_for_lib, libstartup-notification-1.0,startup-notification-1) # Please test if -Wl,--as-needed works on your platform and send me a patch. # it is known not to work on Darwin (Mac OS X) diff --git a/include/i3.h b/include/i3.h index f2b97824..089dfcba 100644 --- a/include/i3.h +++ b/include/i3.h @@ -12,6 +12,9 @@ #include +#define SN_API_NOT_YET_FROZEN 1 +#include + #include "queue.h" #include "data.h" #include "xcb.h" @@ -21,6 +24,11 @@ extern xcb_connection_t *conn; extern int conn_screen; +/** The last timestamp we got from X11 (timestamps are included in some events + * and are used for some things, like determining a unique ID in startup + * notification). */ +extern xcb_timestamp_t last_timestamp; +extern SnDisplay *sndisplay; extern xcb_key_symbols_t *keysyms; extern char **start_argv; extern Display *xlibdpy, *xkbdpy; diff --git a/src/main.c b/src/main.c index 6020f109..f985810e 100644 --- a/src/main.c +++ b/src/main.c @@ -22,6 +22,14 @@ xcb_connection_t *conn; /* The screen (0 when you are using DISPLAY=:0) of the connection 'conn' */ int conn_screen; +/* Display handle for libstartup-notification */ +SnDisplay *sndisplay; + +/* The last timestamp we got from X11 (timestamps are included in some events + * and are used for some things, like determining a unique ID in startup + * notification). */ +xcb_timestamp_t last_timestamp = XCB_CURRENT_TIME; + xcb_screen_t *root_screen; xcb_window_t root; uint8_t root_depth; @@ -362,6 +370,8 @@ int main(int argc, char *argv[]) { if (xcb_connection_has_error(conn)) errx(EXIT_FAILURE, "Cannot open display\n"); + sndisplay = sn_xcb_display_new(conn, NULL, NULL); + /* Initialize the libev event loop. This needs to be done before loading * the config file because the parser will install an ev_child watcher * for the nagbar when config errors are found. */ diff --git a/src/util.c b/src/util.c index 30371bcd..d0e8e947 100644 --- a/src/util.c +++ b/src/util.c @@ -21,6 +21,9 @@ #include #include +#define SN_API_NOT_YET_FROZEN 1 +#include + #include "all.h" static iconv_t conversion_descriptor = 0; @@ -69,11 +72,31 @@ bool update_if_necessary(uint32_t *destination, const uint32_t new_value) { * */ void start_application(const char *command) { + /* Create a startup notification context to monitor the progress of this + * startup. */ + SnLauncherContext *context; + context = sn_launcher_context_new(sndisplay, conn_screen); + sn_launcher_context_set_name(context, "i3"); + sn_launcher_context_set_description(context, "exec command in i3"); + /* Chop off everything starting from the first space (if there are any + * spaces in the command), since we don’t want the parameters. */ + char *first_word = sstrdup(command); + char *space = strchr(first_word, ' '); + if (space) + *space = '\0'; + sn_launcher_context_initiate(context, "i3", first_word, last_timestamp); + free(first_word); + + LOG("startup id = %s\n", sn_launcher_context_get_startup_id(context)); + LOG("executing: %s\n", command); if (fork() == 0) { /* Child process */ setsid(); if (fork() == 0) { + /* Setup the environment variable(s) */ + sn_launcher_context_setup_child_process(context); + /* Stores the path of the shell */ static const char *shell = NULL; From d1d4f39f9eae26da05da8135e2319088f89bca67 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 9 Oct 2011 20:18:38 +0100 Subject: [PATCH 154/333] save the last timestamp received by X11 in last_timestamp We need it for startup notifications (to generate a unique id) Conflicts: include/i3.h src/main.c --- src/click.c | 2 ++ src/handlers.c | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/src/click.c b/src/click.c index c270bdec..8ea182eb 100644 --- a/src/click.c +++ b/src/click.c @@ -259,6 +259,8 @@ int handle_button_press(xcb_button_press_event_t *event) { Con *con; DLOG("Button %d pressed on window 0x%08x\n", event->state, event->event); + last_timestamp = event->time; + const uint32_t mod = config.floating_modifier; bool mod_pressed = (mod != 0 && (event->state & mod) == mod); DLOG("floating_mod = %d, detail = %d\n", mod_pressed, event->detail); diff --git a/src/handlers.c b/src/handlers.c index b3cb1df7..995622b9 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -80,6 +80,9 @@ bool event_is_ignored(const int sequence, const int response_type) { * */ static int handle_key_press(xcb_key_press_event_t *event) { + + last_timestamp = event->time; + DLOG("Keypress %d, state raw = %d\n", event->detail, event->state); /* Remove the numlock bit, all other bits are modifiers we can bind to */ @@ -156,6 +159,8 @@ static void check_crossing_screen_boundary(uint32_t x, uint32_t y) { static int handle_enter_notify(xcb_enter_notify_event_t *event) { Con *con; + last_timestamp = event->time; + DLOG("enter_notify for %08x, mode = %d, detail %d, serial %d\n", event->event, event->mode, event->detail, event->sequence); DLOG("coordinates %d, %d\n", event->event_x, event->event_y); @@ -227,6 +232,9 @@ static int handle_enter_notify(xcb_enter_notify_event_t *event) { * */ static int handle_motion_notify(xcb_motion_notify_event_t *event) { + + last_timestamp = event->time; + /* Skip events where the pointer was over a child window, we are only * interested in events on the root window. */ if (event->child != 0) @@ -1084,6 +1092,7 @@ void handle_event(int type, xcb_generic_event_t *event) { case XCB_PROPERTY_NOTIFY: DLOG("Property notify\n"); xcb_property_notify_event_t *e = (xcb_property_notify_event_t*)event; + last_timestamp = e->time; property_notify(e->state, e->window, e->atom); break; From c812cdcf9a05e6799d52d6cdfffa428e3ab216c6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 9 Oct 2011 22:15:21 +0100 Subject: [PATCH 155/333] make handle_client_message not return anything The function returned an int for historical reasons. --- src/handlers.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index 995622b9..d81d7ecc 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -2,7 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE) + * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) * */ #include @@ -627,20 +627,20 @@ static int handle_expose_event(xcb_expose_event_t *event) { * Handle client messages (EWMH) * */ -static int handle_client_message(xcb_client_message_event_t *event) { +static void handle_client_message(xcb_client_message_event_t *event) { LOG("ClientMessage for window 0x%08x\n", event->window); if (event->type == A__NET_WM_STATE) { if (event->format != 32 || event->data.data32[1] != A__NET_WM_STATE_FULLSCREEN) { DLOG("atom in clientmessage is %d, fullscreen is %d\n", event->data.data32[1], A__NET_WM_STATE_FULLSCREEN); DLOG("not about fullscreen atom\n"); - return 0; + return; } Con *con = con_by_window_id(event->window); if (con == NULL) { DLOG("Could not get window for client message\n"); - return 0; + return; } /* Check if the fullscreen state should be toggled */ @@ -677,10 +677,8 @@ static int handle_client_message(xcb_client_message_event_t *event) { free(reply); } else { ELOG("unhandled clientmessage\n"); - return 0; + return; } - - return 1; } #if 0 From 198f16ece93e029600cf3e592a7769345e82fb0c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 10 Oct 2011 00:24:29 +0100 Subject: [PATCH 156/333] add testcase for the startup notification protocol --- testcases/Makefile.PL | 1 + testcases/t/175-startup-notification.t | 117 +++++++++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 testcases/t/175-startup-notification.t diff --git a/testcases/Makefile.PL b/testcases/Makefile.PL index 4b3f1ade..da0c1570 100755 --- a/testcases/Makefile.PL +++ b/testcases/Makefile.PL @@ -12,6 +12,7 @@ WriteMakefile( 'Test::Most' => 0, 'Test::Deep' => 0, 'EV' => 0, + 'Inline' => 0, }, # don't install any files from this directory PM => {}, diff --git a/testcases/t/175-startup-notification.t b/testcases/t/175-startup-notification.t new file mode 100644 index 00000000..2257b3e4 --- /dev/null +++ b/testcases/t/175-startup-notification.t @@ -0,0 +1,117 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Test for the startup notification protocol. +# + +use i3test; +use POSIX qw(mkfifo); +use File::Temp qw(:POSIX); + +my $x = X11::XCB::Connection->new; +use ExtUtils::PkgConfig; + +# setup dependency on libstartup-notification using pkg-config +my %sn_config; +BEGIN { + %sn_config = ExtUtils::PkgConfig->find('libstartup-notification-1.0'); +} + +use Inline C => Config => LIBS => $sn_config{libs}, CCFLAGS => $sn_config{cflags}; +use Inline C => <<'END_OF_C_CODE'; + +#include + +#define SN_API_NOT_YET_FROZEN 1 +#include +#include + +static SnDisplay *sndisplay; +static SnLauncheeContext *ctx; + +// TODO: this should use $x +void init_ctx() { + int screen; + xcb_connection_t *conn; + if ((conn = xcb_connect(NULL, &screen)) == NULL || + xcb_connection_has_error(conn)) + errx(1, "x11 conn failed"); + + printf("screen = %d\n", screen); + sndisplay = sn_xcb_display_new(conn, NULL, NULL); + ctx = sn_launchee_context_new_from_environment(sndisplay, screen); +} + +const char *get_startup_id() { + return sn_launchee_context_get_startup_id(ctx); +} + +void mark_window(int window) { + sn_launchee_context_setup_window(ctx, (Window)window); +} + +void complete_startup() { + /* mark the startup process complete */ + sn_launchee_context_complete(ctx); +} +END_OF_C_CODE + +my $first_ws = fresh_workspace; + +is(@{get_ws_content($first_ws)}, 0, 'no containers on this workspace yet'); + +###################################################################### +# 1) initiate startup, switch workspace, create window +# (should be placed on the original workspace) +###################################################################### + +# Start a new process via i3 (to initialize a new startup notification +# context), then steal its DESKTOP_STARTUP_ID variable. We handle the startup +# notification in the testcase from there on. +# +# This works by setting up a FIFO in which the process (started by i3) will +# echo its $DESKTOP_STARTUP_ID. We (blockingly) read the variable into +# $startup_id in the testcase. +my $tmp = tmpnam(); +mkfifo($tmp, 0600) or die "Could not create FIFO in $tmp"; + +cmd qq|exec echo \$DESKTOP_STARTUP_ID >$tmp|; + +open(my $fh, '<', $tmp); +chomp(my $startup_id = <$fh>); +close($fh); + +unlink($tmp); + +$ENV{DESKTOP_STARTUP_ID} = $startup_id; + +# Create a new libstartup-notification launchee context +init_ctx(); + +# Make sure the context was set up successfully +is(get_startup_id(), $startup_id, 'libstartup-notification returns the same id'); + +my $second_ws = fresh_workspace; + +is(@{get_ws_content($second_ws)}, 0, 'no containers on the second workspace yet'); + +my $win = open_window($x); +mark_window($win->id); + +is(@{get_ws_content($second_ws)}, 0, 'still no containers on the second workspace'); +is(@{get_ws_content($first_ws)}, 1, 'one container on the first workspace'); + +# TODO: the same thing, but in a CLIENT_LEADER situation + +###################################################################### +# 2) open another window after the startup process is completed +# (should be placed on the current workspace) +###################################################################### + +complete_startup(); +sync_with_i3($x); + +my $otherwin = open_window($x); +is(@{get_ws_content($second_ws)}, 1, 'one container on the second workspace'); + +done_testing; From f4f4d782bb590e7e03302586fa3028fe785ba95d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 10 Oct 2011 11:36:21 +0100 Subject: [PATCH 157/333] implement a startup monitor, move code to src/startup.c --- include/all.h | 1 + include/startup.h | 35 ++++++++++++++++++ include/util.h | 12 ------- src/handlers.c | 11 ++++++ src/startup.c | 91 +++++++++++++++++++++++++++++++++++++++++++++++ src/util.c | 52 --------------------------- 6 files changed, 138 insertions(+), 64 deletions(-) create mode 100644 include/startup.h create mode 100644 src/startup.c diff --git a/include/all.h b/include/all.h index 38cda89c..fd25629e 100644 --- a/include/all.h +++ b/include/all.h @@ -66,5 +66,6 @@ #include "assignments.h" #include "regex.h" #include "libi3.h" +#include "startup.h" #endif diff --git a/include/startup.h b/include/startup.h new file mode 100644 index 00000000..9e131019 --- /dev/null +++ b/include/startup.h @@ -0,0 +1,35 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009-2011 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ +#ifndef _STARTUP_H +#define _STARTUP_H + +#define SN_API_NOT_YET_FROZEN 1 +#include + +/** + * Starts the given application by passing it through a shell. We use double + * fork to avoid zombie processes. As the started application’s parent exits + * (immediately), the application is reparented to init (process-id 1), which + * correctly handles childs, so we don’t have to do it :-). + * + * The shell is determined by looking for the SHELL environment variable. If + * it does not exist, /bin/sh is used. + * + */ +void start_application(const char *command); + +/** + * Called by libstartup-notification when something happens + * + */ +void startup_monitor_event(SnMonitorEvent *event, void *userdata); + +#endif diff --git a/include/util.h b/include/util.h index 7c7b819a..efef0bd0 100644 --- a/include/util.h +++ b/include/util.h @@ -66,18 +66,6 @@ Rect rect_add(Rect a, Rect b); */ bool update_if_necessary(uint32_t *destination, const uint32_t new_value); -/** - * Starts the given application by passing it through a shell. We use double - * fork to avoid zombie processes. As the started application’s parent exits - * (immediately), the application is reparented to init (process-id 1), which - * correctly handles childs, so we don’t have to do it :-). - * - * The shell is determined by looking for the SHELL environment variable. If - * it does not exist, /bin/sh is used. - * - */ -void start_application(const char *command); - /** * exec()s an i3 utility, for example the config file migration script or * i3-nagbar. This function first searches $PATH for the given utility named, diff --git a/src/handlers.c b/src/handlers.c index d81d7ecc..340e24aa 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -11,6 +11,9 @@ #include +#define SN_API_NOT_YET_FROZEN 1 +#include + #include "all.h" int randr_base = -1; @@ -628,6 +631,11 @@ static int handle_expose_event(xcb_expose_event_t *event) { * */ static void handle_client_message(xcb_client_message_event_t *event) { + /* If this is a startup notification ClientMessage, the library will handle + * it and call our monitor_event() callback. */ + if (sn_xcb_display_process_event(sndisplay, (xcb_generic_event_t*)event)) + return; + LOG("ClientMessage for window 0x%08x\n", event->window); if (event->type == A__NET_WM_STATE) { if (event->format != 32 || event->data.data32[1] != A__NET_WM_STATE_FULLSCREEN) { @@ -984,6 +992,9 @@ static struct property_handler_t property_handlers[] = { * */ void property_handlers_init() { + + sn_monitor_context_new(sndisplay, conn_screen, startup_monitor_event, NULL, NULL); + property_handlers[0].atom = A__NET_WM_NAME; property_handlers[1].atom = XCB_ATOM_WM_HINTS; property_handlers[2].atom = XCB_ATOM_WM_NAME; diff --git a/src/startup.c b/src/startup.c new file mode 100644 index 00000000..dd327355 --- /dev/null +++ b/src/startup.c @@ -0,0 +1,91 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009-2011 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + * startup.c: Startup notification code + * + */ +#include +#include + +#define SN_API_NOT_YET_FROZEN 1 +#include + +#include "all.h" + +/* + * Starts the given application by passing it through a shell. We use double fork + * to avoid zombie processes. As the started application’s parent exits (immediately), + * the application is reparented to init (process-id 1), which correctly handles + * childs, so we don’t have to do it :-). + * + * The shell is determined by looking for the SHELL environment variable. If it + * does not exist, /bin/sh is used. + * + */ +void start_application(const char *command) { + /* Create a startup notification context to monitor the progress of this + * startup. */ + SnLauncherContext *context; + context = sn_launcher_context_new(sndisplay, conn_screen); + sn_launcher_context_set_name(context, "i3"); + sn_launcher_context_set_description(context, "exec command in i3"); + /* Chop off everything starting from the first space (if there are any + * spaces in the command), since we don’t want the parameters. */ + char *first_word = sstrdup(command); + char *space = strchr(first_word, ' '); + if (space) + *space = '\0'; + sn_launcher_context_initiate(context, "i3", first_word, last_timestamp); + free(first_word); + + LOG("startup id = %s\n", sn_launcher_context_get_startup_id(context)); + + LOG("executing: %s\n", command); + if (fork() == 0) { + /* Child process */ + setsid(); + if (fork() == 0) { + /* Setup the environment variable(s) */ + sn_launcher_context_setup_child_process(context); + + /* Stores the path of the shell */ + static const char *shell = NULL; + + if (shell == NULL) + if ((shell = getenv("SHELL")) == NULL) + shell = "/bin/sh"; + + /* This is the child */ + execl(shell, shell, "-c", command, (void*)NULL); + /* not reached */ + } + exit(0); + } + wait(0); +} + +/* + * Called by libstartup-notification when something happens + * + */ +void startup_monitor_event(SnMonitorEvent *event, void *userdata) { + SnStartupSequence *sequence; + + DLOG("something happened\n"); + sequence = sn_monitor_event_get_startup_sequence(event); + + switch (sn_monitor_event_get_type(event)) { + case SN_MONITOR_EVENT_COMPLETED: + DLOG("startup sequence %s completed\n", sn_startup_sequence_get_id(sequence)); + break; + default: + /* ignore */ + break; + } +} diff --git a/src/util.c b/src/util.c index d0e8e947..036dce9a 100644 --- a/src/util.c +++ b/src/util.c @@ -61,58 +61,6 @@ bool update_if_necessary(uint32_t *destination, const uint32_t new_value) { return ((*destination = new_value) != old_value); } -/* - * Starts the given application by passing it through a shell. We use double fork - * to avoid zombie processes. As the started application’s parent exits (immediately), - * the application is reparented to init (process-id 1), which correctly handles - * childs, so we don’t have to do it :-). - * - * The shell is determined by looking for the SHELL environment variable. If it - * does not exist, /bin/sh is used. - * - */ -void start_application(const char *command) { - /* Create a startup notification context to monitor the progress of this - * startup. */ - SnLauncherContext *context; - context = sn_launcher_context_new(sndisplay, conn_screen); - sn_launcher_context_set_name(context, "i3"); - sn_launcher_context_set_description(context, "exec command in i3"); - /* Chop off everything starting from the first space (if there are any - * spaces in the command), since we don’t want the parameters. */ - char *first_word = sstrdup(command); - char *space = strchr(first_word, ' '); - if (space) - *space = '\0'; - sn_launcher_context_initiate(context, "i3", first_word, last_timestamp); - free(first_word); - - LOG("startup id = %s\n", sn_launcher_context_get_startup_id(context)); - - LOG("executing: %s\n", command); - if (fork() == 0) { - /* Child process */ - setsid(); - if (fork() == 0) { - /* Setup the environment variable(s) */ - sn_launcher_context_setup_child_process(context); - - /* Stores the path of the shell */ - static const char *shell = NULL; - - if (shell == NULL) - if ((shell = getenv("SHELL")) == NULL) - shell = "/bin/sh"; - - /* This is the child */ - execl(shell, shell, "-c", command, (void*)NULL); - /* not reached */ - } - exit(0); - } - wait(0); -} - /* * exec()s an i3 utility, for example the config file migration script or * i3-nagbar. This function first searches $PATH for the given utility named, From 499d89bdb061f2f2f22316124f835fd79055a864 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 10 Oct 2011 12:47:56 +0100 Subject: [PATCH 158/333] Keep track of startup notifications in a TAILQ, save workspace --- include/data.h | 14 ++++++++++++++ src/startup.c | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/include/data.h b/include/data.h index f6052b9f..7a280b2e 100644 --- a/include/data.h +++ b/include/data.h @@ -138,6 +138,20 @@ struct Ignore_Event { SLIST_ENTRY(Ignore_Event) ignore_events; }; +/** + * Stores internal information about a startup sequence, like the workspace it + * was initiated on. + * + */ +struct Startup_Sequence { + /** startup ID for this sequence, generated by libstartup-notification */ + char *id; + /** workspace on which this startup was initiated */ + char *workspace; + + TAILQ_ENTRY(Startup_Sequence) sequences; +}; + /** * Regular expression wrapper. It contains the pattern itself as a string (like * ^foo[0-9]$) as well as a pointer to the compiled PCRE expression and the diff --git a/src/startup.c b/src/startup.c index dd327355..5fc875a3 100644 --- a/src/startup.c +++ b/src/startup.c @@ -18,6 +18,9 @@ #include "all.h" +static TAILQ_HEAD(startup_sequence_head, Startup_Sequence) startup_sequences = + TAILQ_HEAD_INITIALIZER(startup_sequences); + /* * 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), @@ -46,6 +49,14 @@ void start_application(const char *command) { LOG("startup id = %s\n", sn_launcher_context_get_startup_id(context)); + /* Save the ID and current workspace in our internal list of startup + * sequences */ + Con *ws = con_get_workspace(focused); + struct Startup_Sequence *sequence = scalloc(sizeof(struct Startup_Sequence)); + sequence->id = sstrdup(sn_launcher_context_get_startup_id(context)); + sequence->workspace = sstrdup(ws->name); + TAILQ_INSERT_TAIL(&startup_sequences, sequence, sequences); + LOG("executing: %s\n", command); if (fork() == 0) { /* Child process */ @@ -75,14 +86,29 @@ void start_application(const char *command) { * */ void startup_monitor_event(SnMonitorEvent *event, void *userdata) { - SnStartupSequence *sequence; + SnStartupSequence *snsequence; - DLOG("something happened\n"); - sequence = sn_monitor_event_get_startup_sequence(event); + snsequence = sn_monitor_event_get_startup_sequence(event); + + /* Get the corresponding internal startup sequence */ + const char *id = sn_startup_sequence_get_id(snsequence); + struct Startup_Sequence *current, *sequence = NULL; + TAILQ_FOREACH(current, &startup_sequences, sequences) { + if (strcmp(current->id, id) != 0) + continue; + + sequence = current; + break; + } + + if (!sequence) { + DLOG("Got event for startup sequence that we did not initiate (ID = %s). Ignoring.\n", id); + return; + } switch (sn_monitor_event_get_type(event)) { case SN_MONITOR_EVENT_COMPLETED: - DLOG("startup sequence %s completed\n", sn_startup_sequence_get_id(sequence)); + DLOG("startup sequence %s completed\n", sn_startup_sequence_get_id(snsequence)); break; default: /* ignore */ From 7750382b895bf0a2c661a7ced3c2c9aca9f0df37 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 10 Oct 2011 12:48:19 +0100 Subject: [PATCH 159/333] test: set the _NET_STARTUP_ID before mapping the window --- testcases/t/175-startup-notification.t | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/testcases/t/175-startup-notification.t b/testcases/t/175-startup-notification.t index 2257b3e4..710b3af7 100644 --- a/testcases/t/175-startup-notification.t +++ b/testcases/t/175-startup-notification.t @@ -28,11 +28,11 @@ use Inline C => <<'END_OF_C_CODE'; static SnDisplay *sndisplay; static SnLauncheeContext *ctx; +static xcb_connection_t *conn; // TODO: this should use $x void init_ctx() { int screen; - xcb_connection_t *conn; if ((conn = xcb_connect(NULL, &screen)) == NULL || xcb_connection_has_error(conn)) errx(1, "x11 conn failed"); @@ -48,6 +48,7 @@ const char *get_startup_id() { void mark_window(int window) { sn_launchee_context_setup_window(ctx, (Window)window); + xcb_flush(conn); } void complete_startup() { @@ -95,8 +96,12 @@ my $second_ws = fresh_workspace; is(@{get_ws_content($second_ws)}, 0, 'no containers on the second workspace yet'); -my $win = open_window($x); +my $win = open_window($x, { dont_map => 1 }); mark_window($win->id); +$win->map; +wait_for_map($x); +# We sync with i3 here to make sure $x->input_focus is updated. +sync_with_i3($x); is(@{get_ws_content($second_ws)}, 0, 'still no containers on the second workspace'); is(@{get_ws_content($first_ws)}, 1, 'one container on the first workspace'); From 4204b8e2b00b86916aca4718753d5f684eddfafa Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 10 Oct 2011 12:48:43 +0100 Subject: [PATCH 160/333] Get the _NET_STARTUP_ID in manage_window, get the corresponding workspace --- include/atoms.xmacro | 1 + include/startup.h | 11 ++++++++++ src/manage.c | 17 +++++++++++++++- src/startup.c | 48 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 1 deletion(-) diff --git a/include/atoms.xmacro b/include/atoms.xmacro index 309a3325..f08a90d5 100644 --- a/include/atoms.xmacro +++ b/include/atoms.xmacro @@ -15,6 +15,7 @@ xmacro(_NET_CLIENT_LIST_STACKING) xmacro(_NET_CURRENT_DESKTOP) xmacro(_NET_ACTIVE_WINDOW) xmacro(_NET_WORKAREA) +xmacro(_NET_STARTUP_ID) xmacro(WM_PROTOCOLS) xmacro(WM_DELETE_WINDOW) xmacro(UTF8_STRING) diff --git a/include/startup.h b/include/startup.h index 9e131019..555a1191 100644 --- a/include/startup.h +++ b/include/startup.h @@ -32,4 +32,15 @@ void start_application(const char *command); */ 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. + * + */ +char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply); + #endif diff --git a/src/manage.c b/src/manage.c index 35055d17..22c2814f 100644 --- a/src/manage.c +++ b/src/manage.c @@ -84,7 +84,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie, utf8_title_cookie, title_cookie, class_cookie, leader_cookie, transient_cookie, - role_cookie; + role_cookie, startup_id_cookie; geomc = xcb_get_geometry(conn, d); @@ -147,6 +147,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki title_cookie = GET_PROPERTY(XCB_ATOM_WM_NAME, 128); class_cookie = GET_PROPERTY(XCB_ATOM_WM_CLASS, 128); role_cookie = GET_PROPERTY(A_WM_WINDOW_ROLE, 128); + startup_id_cookie = GET_PROPERTY(A__NET_STARTUP_ID, 512); /* TODO: also get wm_normal_hints here. implement after we got rid of xcb-event */ DLOG("reparenting!\n"); @@ -175,6 +176,11 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki window_update_strut_partial(cwindow, xcb_get_property_reply(conn, strut_cookie, NULL)); window_update_role(cwindow, xcb_get_property_reply(conn, role_cookie, NULL), true); + xcb_get_property_reply_t *startup_id_reply; + startup_id_reply = xcb_get_property_reply(conn, startup_id_cookie, NULL); + char *startup_ws = startup_workspace_for_window(cwindow, startup_id_reply); + DLOG("startup workspace = %s\n", startup_ws); + /* check if the window needs WM_TAKE_FOCUS */ cwindow->needs_take_focus = window_supports_protocol(cwindow->id, A_WM_TAKE_FOCUS); @@ -233,6 +239,15 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki else nc = tree_open_con(nc->parent, cwindow); } /* TODO: handle assignments with type == A_TO_OUTPUT */ + } else if (startup_ws) { + /* If it’s not assigned, but was started on a specific workspace, + * we want to open it there */ + DLOG("Using workspace on which this application was started (%s)\n", startup_ws); + nc = con_descend_tiling_focused(workspace_get(startup_ws, NULL)); + DLOG("focused on ws %s: %p / %s\n", startup_ws, nc, nc->name); + if (nc->type == CT_WORKSPACE) + nc = tree_open_con(nc, cwindow); + else nc = tree_open_con(nc->parent, cwindow); } else { /* If not, insert it at the currently focused position */ if (focused->type == CT_CON && con_accepts_window(focused)) { diff --git a/src/startup.c b/src/startup.c index 5fc875a3..30932e33 100644 --- a/src/startup.c +++ b/src/startup.c @@ -115,3 +115,51 @@ void startup_monitor_event(SnMonitorEvent *event, void *userdata) { break; } } + +/* + * 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) { + /* 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) { + DLOG("No _NET_STARTUP_ID set on this window\n"); + /* TODO: check the leader, if any */ + FREE(startup_id_reply); + return NULL; + } + + char *startup_id; + if (asprintf(&startup_id, "%.*s", xcb_get_property_value_length(startup_id_reply), + (char*)xcb_get_property_value(startup_id_reply)) == -1) { + perror("asprintf()"); + DLOG("Could not get _NET_STARTUP_ID\n"); + free(startup_id_reply); + return NULL; + } + + struct Startup_Sequence *current, *sequence = NULL; + TAILQ_FOREACH(current, &startup_sequences, sequences) { + if (strcmp(current->id, startup_id) != 0) + continue; + + sequence = current; + break; + } + + free(startup_id); + free(startup_id_reply); + + if (!sequence) { + DLOG("WARNING: This sequence (ID %s) was not found\n", startup_id); + return NULL; + } + + return sequence->workspace; +} From 6ac098a45e823aaea84db9298d42e07d5d64efe8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 10 Oct 2011 13:30:52 +0100 Subject: [PATCH 161/333] support _NET_STARTUP_ID on the client leader window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (necessary for GIMP, geeqie, …) --- src/startup.c | 17 ++++++++++++++--- testcases/t/175-startup-notification.t | 12 +++++++++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/startup.c b/src/startup.c index 30932e33..dca7b999 100644 --- a/src/startup.c +++ b/src/startup.c @@ -129,10 +129,21 @@ char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t * /* 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) { - DLOG("No _NET_STARTUP_ID set on this window\n"); - /* TODO: check the leader, if any */ FREE(startup_id_reply); - return NULL; + DLOG("No _NET_STARTUP_ID set on this window\n"); + 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); + DLOG("Checking leader window 0x%08x\n", cwindow->leader); + 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"); + FREE(startup_id_reply); + return NULL; + } } char *startup_id; diff --git a/testcases/t/175-startup-notification.t b/testcases/t/175-startup-notification.t index 710b3af7..56088719 100644 --- a/testcases/t/175-startup-notification.t +++ b/testcases/t/175-startup-notification.t @@ -106,7 +106,17 @@ sync_with_i3($x); is(@{get_ws_content($second_ws)}, 0, 'still no containers on the second workspace'); is(@{get_ws_content($first_ws)}, 1, 'one container on the first workspace'); -# TODO: the same thing, but in a CLIENT_LEADER situation +###################################################################### +# same thing, but with _NET_STARTUP_ID set on the leader +###################################################################### + +my $leader = open_window($x, { dont_map => 1 }); +mark_window($leader->id); + +$win = open_window($x, { client_leader => $leader }); + +is(@{get_ws_content($second_ws)}, 0, 'still no containers on the second workspace'); +is(@{get_ws_content($first_ws)}, 2, 'two containers on the first workspace'); ###################################################################### # 2) open another window after the startup process is completed From 997a539a8a09158dd4a9c87738cd9937a471042d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 10 Oct 2011 13:50:03 +0100 Subject: [PATCH 162/333] Implement timeouts for startup notifications --- src/startup.c | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/startup.c b/src/startup.c index dca7b999..1a584cd4 100644 --- a/src/startup.c +++ b/src/startup.c @@ -21,6 +21,40 @@ static TAILQ_HEAD(startup_sequence_head, Startup_Sequence) startup_sequences = TAILQ_HEAD_INITIALIZER(startup_sequences); +/* + * After 60 seconds, a timeout will be triggered for each startup sequence. + * + * The internal startup sequence will be deleted, the libstartup-notification + * context will be completed and unref'd (therefore free'd aswell). + * + */ +static void startup_timeout(EV_P_ ev_timer *w, int revents) { + const char *id = sn_launcher_context_get_startup_id(w->data); + DLOG("Timeout for startup sequence %s\n", id); + + struct Startup_Sequence *current, *sequence = NULL; + TAILQ_FOREACH(current, &startup_sequences, sequences) { + if (strcmp(current->id, id) != 0) + continue; + + sequence = current; + break; + } + + if (!sequence) { + DLOG("Sequence already deleted, nevermind.\n"); + return; + } + + /* Delete our internal sequence */ + TAILQ_REMOVE(&startup_sequences, sequence, sequences); + + /* Complete and unref the context */ + sn_launcher_context_complete(w->data); + sn_launcher_context_unref(w->data); + free(w); +} + /* * 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), @@ -47,6 +81,12 @@ void start_application(const char *command) { sn_launcher_context_initiate(context, "i3", first_word, last_timestamp); free(first_word); + /* Trigger a timeout after 60 seconds */ + struct ev_timer *timeout = scalloc(sizeof(struct ev_timer)); + ev_timer_init(timeout, startup_timeout, 60.0, 0.); + timeout->data = context; + ev_timer_start(main_loop, timeout); + LOG("startup id = %s\n", sn_launcher_context_get_startup_id(context)); /* Save the ID and current workspace in our internal list of startup From ae7dec2774bab6bf1bfdac40bc01396003a0b6cf Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 10 Oct 2011 15:21:08 +0100 Subject: [PATCH 163/333] Move the includes after the include guard, no need to include these files more than once --- include/data.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/data.h b/include/data.h index 7a280b2e..ba836d55 100644 --- a/include/data.h +++ b/include/data.h @@ -7,13 +7,15 @@ * include/data.h: This file defines all data structures used by i3 * */ + +#ifndef _DATA_H +#define _DATA_H + #include #include #include #include -#ifndef _DATA_H -#define _DATA_H #include "queue.h" /* From 2ad4fbb34ae9110477be484f24c162bc5687253b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 10 Oct 2011 15:30:07 +0100 Subject: [PATCH 164/333] startup: delete the startup sequence upon completion, make the timeout complete it --- include/data.h | 5 +++++ src/startup.c | 25 ++++++++++++++++++------- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/include/data.h b/include/data.h index ba836d55..60e1ef26 100644 --- a/include/data.h +++ b/include/data.h @@ -11,6 +11,9 @@ #ifndef _DATA_H #define _DATA_H +#define SN_API_NOT_YET_FROZEN 1 +#include + #include #include #include @@ -150,6 +153,8 @@ struct Startup_Sequence { char *id; /** workspace on which this startup was initiated */ char *workspace; + /** libstartup-notification context for this launch */ + SnLauncherContext *context; TAILQ_ENTRY(Startup_Sequence) sequences; }; diff --git a/src/startup.c b/src/startup.c index 1a584cd4..4b6c937d 100644 --- a/src/startup.c +++ b/src/startup.c @@ -24,8 +24,8 @@ static TAILQ_HEAD(startup_sequence_head, Startup_Sequence) startup_sequences = /* * After 60 seconds, a timeout will be triggered for each startup sequence. * - * The internal startup sequence will be deleted, the libstartup-notification - * context will be completed and unref'd (therefore free'd aswell). + * The timeout will just trigger completion of the sequence, so the normal + * completion process takes place (startup_monitor_event will free it). * */ static void startup_timeout(EV_P_ ev_timer *w, int revents) { @@ -41,17 +41,16 @@ static void startup_timeout(EV_P_ ev_timer *w, int revents) { break; } + /* Unref the context (for the timeout itself, see start_application) */ + sn_launcher_context_unref(w->data); + if (!sequence) { DLOG("Sequence already deleted, nevermind.\n"); return; } - /* Delete our internal sequence */ - TAILQ_REMOVE(&startup_sequences, sequence, sequences); - - /* Complete and unref the context */ + /* Complete the startup sequence, will trigger its deletion. */ sn_launcher_context_complete(w->data); - sn_launcher_context_unref(w->data); free(w); } @@ -95,8 +94,14 @@ void start_application(const char *command) { struct Startup_Sequence *sequence = scalloc(sizeof(struct Startup_Sequence)); sequence->id = sstrdup(sn_launcher_context_get_startup_id(context)); sequence->workspace = sstrdup(ws->name); + sequence->context = context; TAILQ_INSERT_TAIL(&startup_sequences, sequence, sequences); + /* Increase the refcount once (it starts with 1, so it will be 2 now) for + * the timeout. Even if the sequence gets completed, the timeout still + * needs the context (but will unref it then) */ + sn_launcher_context_ref(context); + LOG("executing: %s\n", command); if (fork() == 0) { /* Child process */ @@ -149,6 +154,12 @@ void startup_monitor_event(SnMonitorEvent *event, void *userdata) { switch (sn_monitor_event_get_type(event)) { case SN_MONITOR_EVENT_COMPLETED: DLOG("startup sequence %s completed\n", sn_startup_sequence_get_id(snsequence)); + + /* Unref the context, will be free()d */ + sn_launcher_context_unref(sequence->context); + + /* Delete our internal sequence */ + TAILQ_REMOVE(&startup_sequences, sequence, sequences); break; default: /* ignore */ From a09d2eee749e8053f38ce3c8005fc3459798021a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 10 Oct 2011 15:34:47 +0100 Subject: [PATCH 165/333] add libstartup-notification to DEPENDS --- DEPENDS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DEPENDS b/DEPENDS index 710637e9..77034d77 100644 --- a/DEPENDS +++ b/DEPENDS @@ -21,7 +21,9 @@ │ libxcursor │ 1.1.11 │ 1.1.11 │ http://ftp.x.org/pub/current/src/lib/ │ │ Xlib │ 1.3.3 │ 1.4.3 │ http://ftp.x.org/pub/current/src/lib/ │ │ PCRE │ 8.12 │ 8.12 │ http://www.pcre.org/ │ +│ libsn¹ │ 0.12 │ 0.12 │ http://freedesktop.org/wiki/Software/startup-notification └─────────────┴────────┴────────┴────────────────────────────────────────┘ + ¹ libsn = libstartup-notification i3-msg, i3-input, i3-nagbar and i3-config-wizard do not introduce any new dependencies. From ad9ffcc91703d73cb714f6209f385c6032a2c398 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 10 Oct 2011 15:35:38 +0100 Subject: [PATCH 166/333] debian: update debian/control with new build-dep libstartup-notification0-dev --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index da13231f..d78e81d5 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Section: utils Priority: extra Maintainer: Michael Stapelberg DM-Upload-Allowed: yes -Build-Depends: debhelper (>= 6), libx11-dev, libxcb-util0-dev (>= 0.3.8), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-icccm4-dev, libxcursor-dev, asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev, perl, texlive-latex-base, texlive-latex-recommended, texlive-latex-extra, libpcre3-dev +Build-Depends: debhelper (>= 6), libx11-dev, libxcb-util0-dev (>= 0.3.8), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-icccm4-dev, libxcursor-dev, asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev, perl, texlive-latex-base, texlive-latex-recommended, texlive-latex-extra, libpcre3-dev, libstartup-notification0-dev (>= 0.12-1) Standards-Version: 3.9.2 Homepage: http://i3wm.org/ From 5f52c78aa04326c25c1f178c3612c4b5613b5f85 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 10 Oct 2011 15:53:57 +0100 Subject: [PATCH 167/333] Change the root window cursor to 'watch' during startups --- include/xcb.h | 9 +++++++++ include/xcursor.h | 1 + src/main.c | 14 +++----------- src/startup.c | 17 ++++++++++++++++- src/xcb.c | 17 +++++++++++++++++ src/xcursor.c | 8 +++++--- 6 files changed, 51 insertions(+), 15 deletions(-) diff --git a/include/xcb.h b/include/xcb.h index 5bc40d2a..65e4e6c3 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -23,6 +23,7 @@ #define XCB_CURSOR_LEFT_PTR 68 #define XCB_CURSOR_SB_H_DOUBLE_ARROW 108 #define XCB_CURSOR_SB_V_DOUBLE_ARROW 116 +#define XCB_CURSOR_WATCH 150 /* from X11/keysymdef.h */ #define XCB_NUM_LOCK 0xff7f @@ -150,4 +151,12 @@ bool xcb_reply_contains_atom(xcb_get_property_reply_t *prop, xcb_atom_t atom); */ void xcb_warp_pointer_rect(xcb_connection_t *conn, Rect *rect); +/** + * Set the cursor of the root window to the given cursor id. + * This function should only be used if xcursor_supported == false. + * Otherwise, use xcursor_set_root_cursor(). + * + */ +void xcb_set_root_cursor(int cursor); + #endif diff --git a/include/xcursor.h b/include/xcursor.h index e129a36f..f3ff4f25 100644 --- a/include/xcursor.h +++ b/include/xcursor.h @@ -10,6 +10,7 @@ enum xcursor_cursor_t { XCURSOR_CURSOR_POINTER = 0, XCURSOR_CURSOR_RESIZE_HORIZONTAL, XCURSOR_CURSOR_RESIZE_VERTICAL, + XCURSOR_CURSOR_WATCH, XCURSOR_CURSOR_MAX }; diff --git a/src/main.c b/src/main.c index f985810e..610a2c19 100644 --- a/src/main.c +++ b/src/main.c @@ -442,17 +442,9 @@ int main(int argc, char *argv[]) { /* Set a cursor for the root window (otherwise the root window will show no cursor until the first client is launched). */ - if (xcursor_supported) { - xcursor_set_root_cursor(); - } else { - xcb_cursor_t cursor_id = xcb_generate_id(conn); - i3Font cursor_font = load_font("cursor", false); - int xcb_cursor = xcursor_get_xcb_cursor(XCURSOR_CURSOR_POINTER); - xcb_create_glyph_cursor(conn, cursor_id, cursor_font.id, cursor_font.id, - xcb_cursor, xcb_cursor + 1, 0, 0, 0, 65535, 65535, 65535); - xcb_change_window_attributes(conn, root, XCB_CW_CURSOR, &cursor_id); - xcb_free_cursor(conn, cursor_id); - } + if (xcursor_supported) + xcursor_set_root_cursor(XCURSOR_CURSOR_POINTER); + else xcb_set_root_cursor(XCURSOR_CURSOR_POINTER); if (xkb_supported) { int errBase, diff --git a/src/startup.c b/src/startup.c index 4b6c937d..66cb5285 100644 --- a/src/startup.c +++ b/src/startup.c @@ -7,7 +7,9 @@ * * See file LICENSE for license information. * - * startup.c: Startup notification code + * startup.c: Startup notification code. Ensures a startup notification context + * is setup when launching applications. We store the current workspace to open + * windows in that startup notification context on the appropriate workspace. * */ #include @@ -124,6 +126,11 @@ void start_application(const char *command) { exit(0); } wait(0); + + /* Change the pointer of the root window to indicate progress */ + if (xcursor_supported) + xcursor_set_root_cursor(XCURSOR_CURSOR_WATCH); + else xcb_set_root_cursor(XCURSOR_CURSOR_WATCH); } /* @@ -160,6 +167,14 @@ void startup_monitor_event(SnMonitorEvent *event, void *userdata) { /* Delete our internal sequence */ TAILQ_REMOVE(&startup_sequences, sequence, sequences); + + if (TAILQ_EMPTY(&startup_sequences)) { + 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) + xcursor_set_root_cursor(XCURSOR_CURSOR_POINTER); + else xcb_set_root_cursor(XCURSOR_CURSOR_POINTER); + } break; default: /* ignore */ diff --git a/src/xcb.c b/src/xcb.c index 31d78703..32537388 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -341,3 +341,20 @@ void xcb_warp_pointer_rect(xcb_connection_t *conn, Rect *rect) { LOG("warp pointer to: %d %d\n", mid_x, mid_y); xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, mid_x, mid_y); } + +/* + * Set the cursor of the root window to the given cursor id. + * This function should only be used if xcursor_supported == false. + * Otherwise, use xcursor_set_root_cursor(). + * + */ +void xcb_set_root_cursor(int cursor) { + xcb_cursor_t cursor_id = xcb_generate_id(conn); + i3Font cursor_font = load_font("cursor", false); + int xcb_cursor = xcursor_get_xcb_cursor(cursor); + xcb_create_glyph_cursor(conn, cursor_id, cursor_font.id, cursor_font.id, + xcb_cursor, xcb_cursor + 1, 0, 0, 0, 65535, 65535, 65535); + xcb_change_window_attributes(conn, root, XCB_CW_CURSOR, &cursor_id); + xcb_free_cursor(conn, cursor_id); + xcb_flush(conn); +} diff --git a/src/xcursor.c b/src/xcursor.c index 69518c30..5d209b56 100644 --- a/src/xcursor.c +++ b/src/xcursor.c @@ -14,7 +14,8 @@ static Cursor cursors[XCURSOR_CURSOR_MAX]; static const int xcb_cursors[XCURSOR_CURSOR_MAX] = { XCB_CURSOR_LEFT_PTR, XCB_CURSOR_SB_H_DOUBLE_ARROW, - XCB_CURSOR_SB_V_DOUBLE_ARROW + XCB_CURSOR_SB_V_DOUBLE_ARROW, + XCB_CURSOR_WATCH }; static Cursor load_cursor(const char *name) { @@ -28,6 +29,7 @@ void xcursor_load_cursors() { 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"); } /* @@ -41,9 +43,9 @@ void xcursor_load_cursors() { * races might occur (even though we flush the Xlib connection). * */ -void xcursor_set_root_cursor() { +void xcursor_set_root_cursor(int cursor_id) { XSetWindowAttributes attributes; - attributes.cursor = xcursor_get_cursor(XCURSOR_CURSOR_POINTER); + attributes.cursor = xcursor_get_cursor(cursor_id); XChangeWindowAttributes(xlibdpy, DefaultRootWindow(xlibdpy), CWCursor, &attributes); XFlush(xlibdpy); } From 71a3da1ef30c510ae61ed0c5650e6a4f482695ca Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 10 Oct 2011 16:00:34 +0100 Subject: [PATCH 168/333] =?UTF-8?q?t/175-startup-notification:=20don?= =?UTF-8?q?=E2=80=99t=20wait=20for=20i3=20mapping=20the=20window?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is on a different workspace and will therefore not get mapped. Syncing is enough. --- testcases/t/175-startup-notification.t | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/testcases/t/175-startup-notification.t b/testcases/t/175-startup-notification.t index 56088719..58b3e0cd 100644 --- a/testcases/t/175-startup-notification.t +++ b/testcases/t/175-startup-notification.t @@ -99,7 +99,8 @@ is(@{get_ws_content($second_ws)}, 0, 'no containers on the second workspace yet' my $win = open_window($x, { dont_map => 1 }); mark_window($win->id); $win->map; -wait_for_map($x); +# We don’t use wait_for_map because the window will not get mapped -- it is on +# a different workspace. # We sync with i3 here to make sure $x->input_focus is updated. sync_with_i3($x); @@ -113,7 +114,9 @@ is(@{get_ws_content($first_ws)}, 1, 'one container on the first workspace'); my $leader = open_window($x, { dont_map => 1 }); mark_window($leader->id); -$win = open_window($x, { client_leader => $leader }); +$win = open_window($x, { dont_map => 1, client_leader => $leader }); +$win->map; +sync_with_i3($x); is(@{get_ws_content($second_ws)}, 0, 'still no containers on the second workspace'); is(@{get_ws_content($first_ws)}, 2, 'two containers on the first workspace'); From 27dadb4ad9df2015e1730366144d967aed298e95 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 10 Oct 2011 21:56:43 +0100 Subject: [PATCH 169/333] testcases/makefile: add modeline, add testsuite-* and latest to 'clean' target --- testcases/Makefile.PL | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/testcases/Makefile.PL b/testcases/Makefile.PL index da0c1570..60083667 100755 --- a/testcases/Makefile.PL +++ b/testcases/Makefile.PL @@ -1,4 +1,5 @@ #!/usr/bin/env perl +# vim:ts=4:sw=4:expandtab use strict; use warnings; use ExtUtils::MakeMaker; @@ -16,6 +17,9 @@ WriteMakefile( }, # don't install any files from this directory PM => {}, + clean => { + FILES => 'testsuite-* latest' + } ); # and don't run the tests while installing sub MY::test { } From fce422d99eca83394166eb22b3e852d1c3b3387d Mon Sep 17 00:00:00 2001 From: Maik Fischer Date: Tue, 11 Oct 2011 13:13:42 +0100 Subject: [PATCH 170/333] i3test.pm: use strict; use warnings; and fix related bugs --- testcases/lib/i3test.pm | 3 ++- testcases/t/135-floating-focus.t | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/testcases/lib/i3test.pm b/testcases/lib/i3test.pm index 9b7cc138..39fd7a94 100644 --- a/testcases/lib/i3test.pm +++ b/testcases/lib/i3test.pm @@ -1,5 +1,6 @@ package i3test; # vim:ts=4:sw=4:expandtab +use strict; use warnings; use File::Temp qw(tmpnam tempfile tempdir); use Test::Builder; @@ -243,7 +244,7 @@ sub get_focused { $lf = $focused[0]; last unless defined($con->{focus}); @focused = @{$con->{focus}}; - @cons = grep { $_->{id} == $lf } (@{$con->{nodes}}, @{$con->{'floating_nodes'}}); + my @cons = grep { $_->{id} == $lf } (@{$con->{nodes}}, @{$con->{'floating_nodes'}}); $con = $cons[0]; } diff --git a/testcases/t/135-floating-focus.t b/testcases/t/135-floating-focus.t index 4c5b562f..a1ab211e 100644 --- a/testcases/t/135-floating-focus.t +++ b/testcases/t/135-floating-focus.t @@ -62,9 +62,9 @@ is($x->input_focus, $second->id, 'second con still focused after killing third') $tmp = fresh_workspace; -$first = open_window($x, '#ff0000'); # window 5 -$second = open_window($x, '#00ff00'); # window 6 -my $third = open_window($x, '#0000ff'); # window 7 +$first = open_window($x, { background_color => '#ff0000' }); # window 5 +$second = open_window($x, { background_color => '#00ff00' }); # window 6 +my $third = open_window($x, { background_color => '#0000ff' }); # window 7 is($x->input_focus, $third->id, 'last container focused'); From 32b97745ecac6b98a1da8b65a13e46cb9c2ef9e8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 15 Oct 2011 16:56:32 +0100 Subject: [PATCH 171/333] =?UTF-8?q?Don=E2=80=99t=20call=20ev=5Fdestroy=5Fl?= =?UTF-8?q?oop=20with=20ev=20<=204=20in=20atexit=20(Thanks=20xeen)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main.c b/src/main.c index 610a2c19..ffdc1fd3 100644 --- a/src/main.c +++ b/src/main.c @@ -181,7 +181,12 @@ static void xkb_got_event(EV_P_ struct ev_io *w, int revents) { * */ static void i3_exit() { +/* We need ev >= 4 for the following code. Since it is not *that* important (it + * only makes sure that there are no i3-nagbar instances left behind) we still + * support old systems with libev 3. */ +#if EV_VERSION_MAJOR >= 4 ev_loop_destroy(main_loop); +#endif } int main(int argc, char *argv[]) { From 914ca6cfe719a5dd997f03132840cfd5600bfd09 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 15 Oct 2011 16:56:47 +0100 Subject: [PATCH 172/333] Bugfix: Use _exit in forking to avoid calling the libev cleanup handler (Thanks xeen) --- src/startup.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/startup.c b/src/startup.c index 66cb5285..388ac502 100644 --- a/src/startup.c +++ b/src/startup.c @@ -123,7 +123,7 @@ void start_application(const char *command) { execl(shell, shell, "-c", command, (void*)NULL); /* not reached */ } - exit(0); + _exit(0); } wait(0); From 88f147ebe4df9291d5deb35839eb797858b8708b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 15 Oct 2011 22:36:03 +0100 Subject: [PATCH 173/333] debian: use debian/i3-wm.manpages instead of manuall installing manpages --- debian/i3-wm.manpages | 7 +++++++ debian/rules | 9 --------- 2 files changed, 7 insertions(+), 9 deletions(-) create mode 100644 debian/i3-wm.manpages diff --git a/debian/i3-wm.manpages b/debian/i3-wm.manpages new file mode 100644 index 00000000..828a26d7 --- /dev/null +++ b/debian/i3-wm.manpages @@ -0,0 +1,7 @@ +man/i3.1 +man/i3-msg.1 +man/i3-input.1 +man/i3-nagbar.1 +man/i3-config-wizard.1 +man/i3-migrate-config-to-v4.1 +i3bar/doc/i3bar.1 diff --git a/debian/rules b/debian/rules index e62dd5ee..4ef9801b 100755 --- a/debian/rules +++ b/debian/rules @@ -46,15 +46,6 @@ install: build # Add here commands to install the package into debian/i3-wm $(MAKE) DESTDIR=$(CURDIR)/debian/i3-wm/ install - mkdir -p $(CURDIR)/debian/i3-wm/usr/share/man/man1 - cp man/i3.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1 - cp man/i3-msg.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1 - cp man/i3-input.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1 - cp man/i3-nagbar.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1 - cp man/i3-config-wizard.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1 - cp man/i3-migrate-config-to-v4.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1 - cp i3bar/doc/i3bar.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1 - # Build architecture-independent files here. binary-indep: build install From c3a18104cd6889e36a077b23cf385b98d0a4c526 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 15 Oct 2011 23:32:04 +0100 Subject: [PATCH 174/333] docs/Makefile: use $(ASCIIDOC), add asciidoc 'latest git docs' config --- docs/Makefile | 16 +- docs/asciidoc-git.conf | 662 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 671 insertions(+), 7 deletions(-) create mode 100644 docs/asciidoc-git.conf diff --git a/docs/Makefile b/docs/Makefile index 990bba87..d1e0768a 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,26 +1,28 @@ +# To pass additional parameters for asciidoc +ASCIIDOC=asciidoc all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html wsbar.html refcard.pdf testsuite.html hacking-howto.html: hacking-howto - asciidoc -a toc -n $< + $(ASCIIDOC) -a toc -n $< debugging.html: debugging - asciidoc -n $< + $(ASCIIDOC) -n $< userguide.html: userguide - asciidoc -a toc -n $< + $(ASCIIDOC) -a toc -n $< testsuite.html: testsuite - asciidoc -a toc -n $< + $(ASCIIDOC) -a toc -n $< ipc.html: ipc - asciidoc -a toc -n $< + $(ASCIIDOC) -a toc -n $< multi-monitor.html: multi-monitor - asciidoc -a toc -n $< + $(ASCIIDOC) -a toc -n $< wsbar.html: wsbar - asciidoc -a toc -n $< + $(ASCIIDOC) -a toc -n $< refcard.pdf: refcard.tex pdflatex refcard.tex && pdflatex refcard.tex diff --git a/docs/asciidoc-git.conf b/docs/asciidoc-git.conf new file mode 100644 index 00000000..24dcb596 --- /dev/null +++ b/docs/asciidoc-git.conf @@ -0,0 +1,662 @@ +# +# xhtml11.conf +# +# Asciidoc configuration file. +# xhtml11 backend, generates XHTML 1.1 conformant markup. +# + +[miscellaneous] +outfilesuffix=.html + +[attributes] +basebackend=html +basebackend-html= +basebackend-xhtml11= + +[replacements2] +# Line break. +(?m)^(.*)\s\+$=\1
+ +[replacements] +ifdef::asciidoc7compatible[] +# Superscripts. +\^(.+?)\^=\1 +# Subscripts. +~(.+?)~=\1 +endif::asciidoc7compatible[] + +[ruler-blockmacro] +
+ +[pagebreak-blockmacro] +
+ +[blockdef-pass] +asciimath-style=template="asciimathblock",subs=[] +latexmath-style=template="latexmathblock",subs=[] + +[macros] +# math macros. +# Special characters are escaped in HTML math markup. +(?su)[\\]?(?Pasciimath|latexmath):(?P\S*?)\[(?P.*?)(?asciimath|latexmath)::(?P\S*?)(\[(?P.*?)\])$=#[specialcharacters] + +[asciimath-inlinemacro] +`{passtext}` + +[asciimath-blockmacro] +
+
+
{title}
+`{passtext}` +
+ +[asciimathblock] +
+
+
{title}
+`|` +
+ +[latexmath-inlinemacro] +{passtext} + +[latexmath-blockmacro] +
+
+
{title}
+{passtext} +
+ +[latexmathblock] +
+
+
{title}
+| +
+ +[image-inlinemacro] + + +{data-uri%}{alt={target}} +{data-uri#}{alt={target}} +{link#} + + +[image-blockmacro] +
+ +
{caption={figure-caption} {counter:figure-number}. }{title}
+
+ +[unfloat-blockmacro] +
+ +[indexterm-inlinemacro] +# Index term. +{empty} + +[indexterm2-inlinemacro] +# Index term. +# Single entry index term that is visible in the primary text flow. +{1} + +[footnote-inlinemacro] +# footnote:[]. +
[{0}]
+ +[footnoteref-inlinemacro] +# footnoteref:[], create reference to footnote. +{2%}
[{1}]
+# footnoteref:[,], create footnote with ID. +{2#}
[{2}]
+ +[callout-inlinemacro] +ifndef::icons[] +<{index}> +endif::icons[] +ifdef::icons[] +ifndef::data-uri[] +{index} +endif::data-uri[] +ifdef::data-uri[] +{index} +endif::data-uri[] +endif::icons[] + +# Comment line macros. +[comment-inlinemacro] +{showcomments#}
{passtext}
+ +[comment-blockmacro] +{showcomments#}

{passtext}

+ +[literal-inlinemacro] +# Inline literal. +{passtext} + +# List tags. +[listtags-bulleted] +list=
{title?
{title}
}
    |
+item=
  • |
  • +text=

    |

    + +[listtags-numbered] +# The start attribute is not valid XHTML 1.1 but all browsers support it. +list=
    {title?
    {title}
    }
      |
    +item=
  • |
  • +text=

    |

    + +[listtags-labeled] +list=
    {title?
    {title}
    }
    |
    +entry= +label= +term=
    |
    +item=
    |
    +text=

    |

    + +[listtags-horizontal] +list=
    {title?
    {title}
    }{labelwidth?}{itemwidth?}|
    +label=| +term=|
    +entry=| +item=| +text=

    |

    + +[listtags-qanda] +list=
    {title?
    {title}
    }
      |
    +entry=
  • |
  • +label= +term=

    |

    +item= +text=

    |

    + +[listtags-callout] +ifndef::icons[] +list=
    {title?
    {title}
    }
      |
    +item=
  • |
  • +text=

    |

    +endif::icons[] +ifdef::icons[] +list=
    {title?
    {title}
    }|
    +ifndef::data-uri[] +item={listindex}| +endif::data-uri[] +ifdef::data-uri[] +item={listindex}| +endif::data-uri[] +text=| +endif::icons[] + +[listtags-glossary] +list=
    {title?
    {title}
    }
    |
    +label= +entry= +term=
    |
    +item=
    |
    +text=

    |

    + +[listtags-bibliography] +list=
    {title?
    {title}
    }
      |
    +item=
  • |
  • +text=

    |

    + +[tags] +# Quoted text. +emphasis={1?}|{1?} +strong={1?}|{1?} +monospaced={1?}|{1?} +singlequoted={lsquo}{1?}|{1?}{rsquo} +doublequoted={ldquo}{1?}|{1?}{rdquo} +unquoted={1?}|{1?} +superscript={1?}|{1?} +subscript={1?}|{1?} + +ifdef::deprecated-quotes[] +# Override with deprecated quote attributes. +emphasis={role?}|{role?} +strong={role?}|{role?} +monospaced={role?}|{role?} +singlequoted={role?}{1,2,3?}{amp}#8216;|{amp}#8217;{1,2,3?}{role?} +doublequoted={role?}{1,2,3?}{amp}#8220;|{amp}#8221;{1,2,3?}{role?} +unquoted={role?}{1,2,3?}|{1,2,3?}{role?} +superscript={role?}|{role?} +subscript={role?}|{role?} +endif::deprecated-quotes[] + +# Inline macros +[http-inlinemacro] +{0={name}:{target}} +[https-inlinemacro] +{0={name}:{target}} +[ftp-inlinemacro] +{0={name}:{target}} +[file-inlinemacro] +{0={name}:{target}} +[irc-inlinemacro] +{0={name}:{target}} +[mailto-inlinemacro] +{0={target}} +[link-inlinemacro] +{0={target}} +[callto-inlinemacro] +{0={target}} +# anchor:id[text] +[anchor-inlinemacro] + +# [[id,text]] +[anchor2-inlinemacro] + +# [[[id]]] +[anchor3-inlinemacro] +[{1}] +# xref:id[text] +[xref-inlinemacro] +{0=[{target}]} +# <> +[xref2-inlinemacro] +{2=[{1}]} + +# Special word substitution. +[emphasizedwords] +{words} +[monospacedwords] +{words} +[strongwords] +{words} + +# Paragraph substitution. +[paragraph] +
    {title?
    {title}
    }

    +| +

    + +[admonitionparagraph] +template::[admonitionblock] + +# Delimited blocks. +[listingblock] +
    +
    {caption=}{title}
    +
    +
    
    +|
    +
    +
    + +[literalblock] +
    +
    {title}
    +
    +
    
    +|
    +
    +
    + +[sidebarblock] +
    +
    +
    {title}
    +| +
    + +[openblock] +
    +
    {title}
    +
    +| +
    + +[partintroblock] +template::[openblock] + +[abstractblock] +template::[quoteblock] + +[quoteblock] +
    +
    {title}
    +
    +| +
    +
    +{citetitle}{attribution?
    } +— {attribution} +
    + +[verseblock] +
    +
    {title}
    +
    +|
    +
    +
    +{citetitle}{attribution?
    } +— {attribution} +
    + +[exampleblock] +
    +
    {caption={example-caption} {counter:example-number}. }{title}
    +
    +| +
    + +[admonitionblock] +
    + + + +
    +{data-uri%}{icons#}{caption} +{data-uri#}{icons#}{caption} +{icons%}
    {caption}
    +
    +
    {title}
    +| +
    +
    + +# Tables. +[tabletags-default] +colspec= +bodyrow=| +headdata=| +bodydata=| +paragraph=

    |

    + +[tabletags-header] +paragraph=

    |

    + +[tabletags-emphasis] +paragraph=

    |

    + +[tabletags-strong] +paragraph=

    |

    + +[tabletags-monospaced] +paragraph=

    |

    + +[tabletags-verse] +bodydata=
    |
    +paragraph= + +[tabletags-literal] +bodydata=
    |
    +paragraph= + +[tabletags-asciidoc] +bodydata=
    |
    +paragraph= + +[table] +
    + + +{colspecs} +{headrows#} +{headrows} +{headrows#} +{footrows#} +{footrows} +{footrows#} + +{bodyrows} + +
    {caption={table-caption} {counter:table-number}. }{title}
    +
    + +#-------------------------------------------------------------------- +# Deprecated old table definitions. +# + +[miscellaneous] +# Screen width in pixels. +pagewidth=800 +pageunits= + +[old_tabledef-default] +template=old_table +colspec= +bodyrow=| +headdata=| +footdata=| +bodydata=| + +[old_table] +
    + + +{colspecs} +{headrows#} +{headrows} +{headrows#} +{footrows#} +{footrows} +{footrows#} + +{bodyrows} + +
    {caption={table-caption}}{title}
    +
    + +# End of deprecated old table definitions. +#-------------------------------------------------------------------- + +[floatingtitle] +{title} + +[preamble] +# Untitled elements between header and first section title. +
    +
    +| +
    +
    + +# Document sections. +[sect0] +{title} +| + +[sect1] +
    +{numbered?{sectnum} }{title} +
    +| +
    +
    + +[sect2] +
    +{numbered?{sectnum} }{title} +| +
    + +[sect3] +
    +{numbered?{sectnum} }{title} +| +
    + +[sect4] +
    +{title} +| +
    + +[appendix] +
    +{numbered?{sectnum} }{appendix-caption} {counter:appendix-number:A}: {title} +
    +| +
    +
    + +[toc] +
    +
    {toc-title}
    + +
    + +[header] + + + + + + + + +i3: {title} +{title%}i3: {doctitle=} + +ifdef::linkcss[] + +{doctype-manpage} +ifdef::quirks[] + +endif::quirks[] + +ifdef::pygments[] +endif::linkcss[] +ifndef::linkcss[] + +endif::linkcss[] +ifndef::disable-javascript[] +ifdef::linkcss[] + + +endif::linkcss[] +ifndef::linkcss[] + +endif::linkcss[] +endif::disable-javascript[] +ifdef::asciimath[] +ifdef::linkcss[] + +endif::linkcss[] +ifndef::linkcss[] + +endif::linkcss[] +endif::asciimath[] +ifdef::latexmath[] +ifdef::linkcss[] + +endif::linkcss[] +ifndef::linkcss[] + +endif::linkcss[] +endif::latexmath[] +{docinfo1,docinfo2#}{include:{docdir}/docinfo.html} +{docinfo,docinfo2#}{include:{docdir}/{docname}-docinfo.html} + + + +
    +

    i3 - improved tiling WM

    + +
    +
    +# Article, book header. +ifndef::doctype-manpage[] + +endif::doctype-manpage[] +# Man page header. +ifdef::doctype-manpage[] + +endif::doctype-manpage[] + +[footer] +
    +{disable-javascript%

    } + + + + +ifdef::doctype-manpage[] +[synopsis] +template::[sect1] +endif::doctype-manpage[] + +ifdef::quirks[] +include::{backend}-quirks.conf[] +endif::quirks[] From 178be03fa668bf447fb7edfae3c5df1d1aef31fd Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 17 Oct 2011 23:17:56 +0100 Subject: [PATCH 175/333] Implement 'workspace back_and_forth' (Patch by Michael Walle) --- docs/userguide | 26 ++++++++++++++++++++++++++ include/config.h | 6 ++++++ include/workspace.h | 7 +++++++ src/cfgparse.l | 1 + src/cfgparse.y | 10 ++++++++++ src/cmdparse.l | 5 +++-- src/cmdparse.y | 20 ++++++++++++++++++++ src/workspace.c | 30 ++++++++++++++++++++++++++++-- 8 files changed, 101 insertions(+), 4 deletions(-) diff --git a/docs/userguide b/docs/userguide index 27e3b9ed..e12d8b6e 100644 --- a/docs/userguide +++ b/docs/userguide @@ -737,6 +737,25 @@ force_xinerama yes Also note that your output names are not descriptive (like +HDMI1+) when using Xinerama, instead they are counted up, starting at 0: +xinerama-0+, +xinerama-1+, … +=== Automatic back-and-forth when switching to the current workspace + +This configuration directive enables automatic +workspace back_and_forth+ (see +<>) when switching to the workspace that is currently focused. + +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". + +*Syntax*: +-------------------------------------- +workspace_auto_back_and_forth +-------------------------------------- + +*Example*: +--------------------------------- +workspace_auto_back_and_forth yes +--------------------------------- + == List of commands Commands are what you bind to specific keypresses. You can also issue commands @@ -892,6 +911,10 @@ workspace 1, 3, 4 and 9 and you want to cycle through them with a single key combination. Similarily, you can use +move workspace next+ and +move workspace prev+ to move a container to the next/previous workspace. +[[back_and_forth]] +To switch back to the previously focused workspace, use +workspace +back_and_forth+. + To move a container to another xrandr output such as +LVDS1+ or +VGA1+, you can use the +move output+ command followed by the name of the target output. You may also use +left+, +right+, +up+, +down+ instead of the xrandr output name to @@ -906,6 +929,9 @@ bindsym mod+2 workspace 2 bindsym mod+Shift+1 move workspace 1 bindsym mod+Shift+2 move workspace 2 ... + +# switch between the current and the previously focused one +bindsym mod+b workspace back_and_forth ------------------------- ==== Named workspaces diff --git a/include/config.h b/include/config.h index 07391a6a..337db8fb 100644 --- a/include/config.h +++ b/include/config.h @@ -133,6 +133,12 @@ struct Config { * is fetched once and never updated. */ bool force_xinerama; + /** Automatic workspace back and forth switching. If this is set, a + * switch to the currently active workspace will switch to the + * previously focused one instead, making it possible to fast toggle + * between two workspaces. */ + bool workspace_auto_back_and_forth; + /** The default border style for new windows. */ border_style_t default_border; diff --git a/include/workspace.h b/include/workspace.h index 3f0e83c2..1dce680a 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -70,6 +70,13 @@ Con* workspace_next(); */ Con* workspace_prev(); +/** + * Focuses the previously focused workspace. + * + */ +void workspace_back_and_forth(); + + #if 0 /** * Assigns the given workspace to the given screen by correctly updating its diff --git a/src/cfgparse.l b/src/cfgparse.l index 49714401..622a133b 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -127,6 +127,7 @@ none { return TOK_NONE; } focus_follows_mouse { return TOKFOCUSFOLLOWSMOUSE; } force_focus_wrapping { return TOK_FORCE_FOCUS_WRAPPING; } force_xinerama { return TOK_FORCE_XINERAMA; } +workspace_auto_back_and_forth { return TOK_WORKSPACE_AUTO_BAF; } 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 9a417f2a..6869eee7 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -623,6 +623,7 @@ void parse_file(const char *f) { %token TOKFOCUSFOLLOWSMOUSE "focus_follows_mouse" %token TOK_FORCE_FOCUS_WRAPPING "force_focus_wrapping" %token TOK_FORCE_XINERAMA "force_xinerama" +%token TOK_WORKSPACE_AUTO_BAF "workspace_auto_back_and_forth" %token TOKWORKSPACEBAR "workspace_bar" %token TOK_DEFAULT "default" %token TOK_STACKING "stacking" @@ -679,6 +680,7 @@ line: | focus_follows_mouse | force_focus_wrapping | force_xinerama + | workspace_back_and_forth | workspace_bar | workspace | assign @@ -1035,6 +1037,14 @@ force_xinerama: } ; +workspace_back_and_forth: + TOK_WORKSPACE_AUTO_BAF bool + { + DLOG("automatic workspace back-and-forth = %d\n", $2); + config.workspace_auto_back_and_forth = $2; + } + ; + workspace_bar: TOKWORKSPACEBAR bool { diff --git a/src/cmdparse.l b/src/cmdparse.l index f6b132ca..6dda34d0 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -72,10 +72,11 @@ EOL (\r?\n) cmdyycolumn = 1; } - /* the next/prev tokens are here to recognize them *before* handling - * strings ('workspace' command) */ + /* the next/prev/back_and_forth tokens are here to recognize them *before* + * handling strings ('workspace' command) */ next { return TOK_NEXT; } prev { return TOK_PREV; } +back_and_forth { return TOK_BACK_AND_FORTH; } \"[^\"]+\" { BEGIN(INITIAL); diff --git a/src/cmdparse.y b/src/cmdparse.y index ecc4bc02..a43d2be0 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -174,6 +174,7 @@ bool definitelyGreaterThan(float a, float b, float epsilon) { %token TOK_OR "or" %token TOK_PPT "ppt" %token TOK_NOP "nop" +%token TOK_BACK_AND_FORTH "back_and_forth" %token TOK_CLASS "class" %token TOK_INSTANCE "instance" @@ -587,9 +588,28 @@ workspace: workspace_show(workspace_prev()); tree_render(); } + | TOK_WORKSPACE TOK_BACK_AND_FORTH + { + workspace_back_and_forth(); + tree_render(); + } | TOK_WORKSPACE STR { printf("should switch to workspace %s\n", $2); + + Con *ws = con_get_workspace(focused); + + /* Check if the command wants to switch to the current workspace */ + if (strcmp(ws->name, $2) == 0) { + printf("This workspace is already focused.\n"); + if (config.workspace_auto_back_and_forth) { + workspace_back_and_forth(); + free($2); + tree_render(); + } + break; + } + workspace_show_by_name($2); free($2); diff --git a/src/workspace.c b/src/workspace.c index b4ed5530..e06dbde5 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -2,13 +2,17 @@ * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE) + * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) * * workspace.c: Functions for modifying workspaces * */ #include "all.h" +/* Stores a copy of the name of the last used workspace for the workspace + * back-and-forth switching. */ +static char *previous_workspace_name = NULL; + /* * Returns a pointer to the workspace with the given number (starting at 0), * creating the workspace if necessary (by allocating the necessary amount of @@ -191,11 +195,20 @@ static void _workspace_show(Con *workspace, bool changed_num_workspaces) { /* enable fullscreen for the target workspace. If it happens to be the * same one we are currently on anyways, we can stop here. */ workspace->fullscreen_mode = CF_OUTPUT; - if (workspace == con_get_workspace(focused)) { + current = con_get_workspace(focused); + if (workspace == current) { DLOG("Not switching, already there.\n"); return; } + /* Remember currently focused workspace for switching back to it later with + * the 'workspace back_and_forth' command. + * NOTE: We have to duplicate the name as the original will be freed when + * the corresponding workspace is cleaned up. */ + + FREE(previous_workspace_name); + previous_workspace_name = sstrdup(current->name); + workspace_reassign_sticky(workspace); LOG("switching to %p\n", workspace); @@ -368,6 +381,19 @@ workspace_prev_end: return prev; } +/* + * Focuses the previously focused workspace. + * + */ +void workspace_back_and_forth() { + if (!previous_workspace_name) { + DLOG("No previous workspace name set. Not switching."); + return; + } + + workspace_show_by_name(previous_workspace_name); +} + static bool get_urgency_flag(Con *con) { Con *child; TAILQ_FOREACH(child, &(con->nodes_head), nodes) From 82ec0169cb2eaea8ffc89e033bdd59b3c526a2d7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 17 Oct 2011 23:34:29 +0100 Subject: [PATCH 176/333] tests: add t/176-workspace-baf for the workspace back_and_forth feature --- testcases/t/176-workspace-baf.t | 69 +++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 testcases/t/176-workspace-baf.t diff --git a/testcases/t/176-workspace-baf.t b/testcases/t/176-workspace-baf.t new file mode 100644 index 00000000..48ea948d --- /dev/null +++ b/testcases/t/176-workspace-baf.t @@ -0,0 +1,69 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3 +# +# Checks if the 'workspace back_and_forth' command and the +# 'workspace_auto_back_and_forth' config directive work correctly. +# + +use i3test; +use X11::XCB::Connection; + +my $x = X11::XCB::Connection->new; + +my $config = <{focused}, 'first workspace focused'); + +my $second_ws = fresh_workspace; +ok(get_ws($second_ws)->{focused}, 'second workspace focused'); + +my $third_ws = fresh_workspace; +ok(get_ws($third_ws)->{focused}, 'third workspace focused'); + +cmd 'workspace back_and_forth'; +ok(get_ws($second_ws)->{focused}, 'second workspace focused'); + +##################################################################### +# test that without workspace_auto_back_and_forth switching to the same +# workspace that is currently focused is a no-op +##################################################################### + +cmd qq|workspace "$second_ws"|; +ok(get_ws($second_ws)->{focused}, 'second workspace still focused'); + +exit_gracefully($pid); + +##################################################################### +# the same test, but with workspace_auto_back_and_forth +##################################################################### + +$config = <{focused}, 'first workspace focused'); + +$second_ws = fresh_workspace; +ok(get_ws($second_ws)->{focused}, 'second workspace focused'); + +$third_ws = fresh_workspace; +ok(get_ws($third_ws)->{focused}, 'third workspace focused'); + +cmd qq|workspace "$third_ws"|; +ok(get_ws($second_ws)->{focused}, 'second workspace focused'); + +exit_gracefully($pid); + +done_testing; From f09d9a4c371ea7754eb9525c2d08ec882baab836 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 18 Oct 2011 18:32:47 +0100 Subject: [PATCH 177/333] log: use localtime_r instead of localtime localtime_r does not have the side-effect of behaving like it called tzset(), in particular it will save one stat(/etc/localtime) syscall. This is not a big deal, but it makes the strace output cleaner and thus more useful :). --- src/log.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/log.c b/src/log.c index 5e1c35eb..c258b19b 100644 --- a/src/log.c +++ b/src/log.c @@ -87,12 +87,13 @@ void add_loglevel(const char *level) { * */ void vlog(char *fmt, va_list args) { - char timebuf[64]; + static char timebuf[64]; + static struct tm result; /* Get current time */ time_t t = time(NULL); /* Convert time to local time (determined by the locale) */ - struct tm *tmp = localtime(&t); + struct tm *tmp = localtime_r(&t, &result); /* Generate time prefix */ strftime(timebuf, sizeof(timebuf), "%x %X - ", tmp); #ifdef DEBUG_TIMING From a506e59b3c53c3d6989d2bcd65a05e43481de293 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 18 Oct 2011 18:47:07 +0100 Subject: [PATCH 178/333] Bugfix: Fix segfault when starting i3 (Thanks pnutzh4x0r) --- src/workspace.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/workspace.c b/src/workspace.c index e06dbde5..2aad2a10 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -207,7 +207,8 @@ static void _workspace_show(Con *workspace, bool changed_num_workspaces) { * the corresponding workspace is cleaned up. */ FREE(previous_workspace_name); - previous_workspace_name = sstrdup(current->name); + if (current) + previous_workspace_name = sstrdup(current->name); workspace_reassign_sticky(workspace); From 9d67ae2202840bdb52766701844b9d2dd23002f5 Mon Sep 17 00:00:00 2001 From: Peter Bui Date: Fri, 14 Oct 2011 11:36:32 -0400 Subject: [PATCH 179/333] Focus new window only if it is on a visible workspace on the current focused output. --- src/manage.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/manage.c b/src/manage.c index 22c2814f..ec9e7173 100644 --- a/src/manage.c +++ b/src/manage.c @@ -283,9 +283,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki if (fs == NULL) { DLOG("Not in fullscreen mode, focusing\n"); if (!cwindow->dock) { - /* Check that the workspace is visible. If the window was assigned - * to an invisible workspace, we should not steal focus. */ - if (workspace_is_visible(ws)) { + /* Check that the workspace is visible and on the same output as + * the current focused container. If the window was assigned to an + * invisible workspace, we should not steal focus. */ + Con *current_output = con_get_output(focused); + Con *target_output = con_get_output(ws); + + if (workspace_is_visible(ws) && current_output == target_output) { con_focus(nc); } else DLOG("workspace not visible, not focusing\n"); } else DLOG("dock, not focusing\n"); From f26a344dfa00d3b53a92e374612833e31ff11705 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 12 Oct 2011 22:29:39 +0100 Subject: [PATCH 180/333] reformat include/config.h --- include/config.h | 172 +++++++++++++++++++++++------------------------ 1 file changed, 86 insertions(+), 86 deletions(-) diff --git a/include/config.h b/include/config.h index 337db8fb..24754751 100644 --- a/include/config.h +++ b/include/config.h @@ -1,5 +1,5 @@ /* - * vim:ts=8:expandtab + * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager * @@ -32,17 +32,17 @@ extern SLIST_HEAD(modes_head, Mode) modes; * */ struct context { - bool has_errors; + bool has_errors; - int line_number; - char *line_copy; - const char *filename; + int line_number; + char *line_copy; + const char *filename; - char *compact_error; + char *compact_error; - /* These are the same as in YYLTYPE */ - int first_column; - int last_column; + /* These are the same as in YYLTYPE */ + int first_column; + int last_column; }; /** @@ -51,9 +51,9 @@ struct context { * */ struct Colortriple { - uint32_t border; - uint32_t background; - uint32_t text; + uint32_t border; + uint32_t background; + uint32_t text; }; /** @@ -62,11 +62,11 @@ struct Colortriple { * */ struct Variable { - char *key; - char *value; - char *next_match; + char *key; + char *value; + char *next_match; - SLIST_ENTRY(Variable) variables; + SLIST_ENTRY(Variable) variables; }; /** @@ -76,10 +76,10 @@ struct Variable { * */ struct Mode { - char *name; - struct bindings_head *bindings; + char *name; + struct bindings_head *bindings; - SLIST_ENTRY(Mode) modes; + SLIST_ENTRY(Mode) modes; }; /** @@ -88,86 +88,86 @@ struct Mode { * */ struct Config { - const char *terminal; - i3Font font; + const char *terminal; + i3Font font; - char *ipc_socket_path; - const char *restart_state_path; + char *ipc_socket_path; + const char *restart_state_path; - int default_layout; - int container_stack_limit; - int container_stack_limit_value; + int default_layout; + int container_stack_limit; + int container_stack_limit_value; - /** Default orientation for new containers */ - int default_orientation; + /** Default orientation for new containers */ + int default_orientation; - /** By default, focus follows mouse. If the user explicitly wants to - * turn this off (and instead rely only on the keyboard for changing - * focus), we allow him to do this with this relatively special option. - * It is not planned to add any different focus models. */ - bool disable_focus_follows_mouse; + /** By default, focus follows mouse. If the user explicitly wants to + * turn this off (and instead rely only on the keyboard for changing + * focus), we allow him to do this with this relatively special option. + * It is not planned to add any different focus models. */ + bool disable_focus_follows_mouse; - /** By default, a workspace bar is drawn at the bottom of the screen. - * If you want to have a more fancy bar, it is recommended to replace - * the whole bar by dzen2, for example using the i3-wsbar script which - * comes with i3. Thus, you can turn it off entirely. */ - bool disable_workspace_bar; + /** By default, a workspace bar is drawn at the bottom of the screen. + * If you want to have a more fancy bar, it is recommended to replace + * the whole bar by dzen2, for example using the i3-wsbar script which + * comes with i3. Thus, you can turn it off entirely. */ + bool disable_workspace_bar; - /** Think of the following layout: Horizontal workspace with a tabbed - * con on the left of the screen and a terminal on the right of the - * screen. You are in the second container in the tabbed container and - * focus to the right. By default, i3 will set focus to the terminal on - * the right. If you are in the first container in the tabbed container - * however, focusing to the left will wrap. This option forces i3 to - * always wrap, which will result in you having to use "focus parent" - * more often. */ - bool force_focus_wrapping; + /** Think of the following layout: Horizontal workspace with a tabbed + * con on the left of the screen and a terminal on the right of the + * screen. You are in the second container in the tabbed container and + * focus to the right. By default, i3 will set focus to the terminal on + * the right. If you are in the first container in the tabbed container + * however, focusing to the left will wrap. This option forces i3 to + * always wrap, which will result in you having to use "focus parent" + * more often. */ + bool force_focus_wrapping; - /** By default, use the RandR API for multi-monitor setups. - * Unfortunately, the nVidia binary graphics driver doesn't support - * this API. Instead, it only support the less powerful Xinerama API, - * which can be enabled by this option. - * - * Note: this option takes only effect on the initial startup (eg. - * reconfiguration is not possible). On startup, the list of screens - * is fetched once and never updated. */ - bool force_xinerama; + /** By default, use the RandR API for multi-monitor setups. + * Unfortunately, the nVidia binary graphics driver doesn't support + * this API. Instead, it only support the less powerful Xinerama API, + * which can be enabled by this option. + * + * Note: this option takes only effect on the initial startup (eg. + * reconfiguration is not possible). On startup, the list of screens + * is fetched once and never updated. */ + bool force_xinerama; - /** Automatic workspace back and forth switching. If this is set, a - * switch to the currently active workspace will switch to the - * previously focused one instead, making it possible to fast toggle - * between two workspaces. */ - bool workspace_auto_back_and_forth; + /** Automatic workspace back and forth switching. If this is set, a + * switch to the currently active workspace will switch to the + * previously focused one instead, making it possible to fast toggle + * between two workspaces. */ + bool workspace_auto_back_and_forth; - /** The default border style for new windows. */ - border_style_t default_border; + /** The default border style for new windows. */ + border_style_t default_border; - /** The default border style for new floating windows. */ - border_style_t default_floating_border; + /** The default border style for new floating windows. */ + border_style_t default_floating_border; - /** The modifier which needs to be pressed in combination with your mouse - * buttons to do things with floating windows (move, resize) */ - uint32_t floating_modifier; + /** The modifier which needs to be pressed in combination with your mouse + * buttons to do things with floating windows (move, resize) */ + uint32_t floating_modifier; - /* Color codes are stored here */ - struct config_client { - uint32_t background; - struct Colortriple focused; - struct Colortriple focused_inactive; - struct Colortriple unfocused; - struct Colortriple urgent; - } client; - struct config_bar { - struct Colortriple focused; - struct Colortriple unfocused; - struct Colortriple urgent; - } bar; + /* Color codes are stored here */ + struct config_client { + uint32_t background; + struct Colortriple focused; + struct Colortriple focused_inactive; + struct Colortriple unfocused; + struct Colortriple urgent; + } client; + struct config_bar { + struct Colortriple focused; + struct Colortriple unfocused; + struct Colortriple urgent; + } bar; - /** What should happen when a new popup is opened during fullscreen mode */ - enum { - PDF_LEAVE_FULLSCREEN = 0, - PDF_IGNORE = 1 - } popup_during_fullscreen; + /** What should happen when a new popup is opened during fullscreen mode */ + enum { + PDF_LEAVE_FULLSCREEN = 0, + PDF_IGNORE = 1 + } popup_during_fullscreen; }; /** From 4898f78e5e3bd786e55a290ade1afe75db0bb31f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 12 Oct 2011 22:52:55 +0100 Subject: [PATCH 181/333] add a data structure for 'bar' configuration --- include/config.h | 68 ++++++++++++++++++++++++++++++++++++++++++++++++ src/config.c | 1 + 2 files changed, 69 insertions(+) diff --git a/include/config.h b/include/config.h index 24754751..200d393f 100644 --- a/include/config.h +++ b/include/config.h @@ -22,9 +22,11 @@ #include "i3.h" typedef struct Config Config; +typedef struct Barconfig Barconfig; extern char *current_configpath; extern Config config; extern SLIST_HEAD(modes_head, Mode) modes; +extern SLIST_HEAD(barconfig_head, Barconfig) barconfigs; /** * Used during the config file lexing/parsing to keep the state of the lexer @@ -170,6 +172,72 @@ struct Config { } popup_during_fullscreen; }; +/** + * Holds the status bar configuration (i3bar). One of these structures is + * created for each 'bar' block in the config. + * + */ +struct Barconfig { + /** Automatically generated ID for this bar config. Used by the bar process + * to request a specific configuration. */ + char *id; + + /** Number of outputs in the outputs array */ + int num_outputs; + /** Outputs on which this bar should show up on. We use an array for + * simplicity (since we store just strings). */ + char **outputs; + + /** Output on which the tray should be shown. The special value of 'no' + * disables the tray (it’s enabled by default). */ + char *tray_output; + + /** Path to the i3 IPC socket. This option is discouraged since programs + * can find out the path by looking for the I3_SOCKET_PATH property on the + * root window! */ + char *socket_path; + + /** Bar display mode (hide unless modifier is pressed or show in dock mode) */ + enum { M_HIDE = 0, M_DOCK = 1 } mode; + + /** Bar position (bottom by default). */ + enum { P_BOTTOM = 0, P_TOP = 1 } position; + + /** Command that should be run to get a statusline, for example 'i3status'. + * Will be passed to the shell. */ + char *status_command; + + /** Font specification for all text rendered on the bar. */ + char *font; + + /** Hide workspace buttons? Configuration option is 'workspace_buttons no' + * but we invert the bool to get the correct default when initializing with + * zero. */ + bool hide_workspace_buttons; + + /** Enable verbose mode? Useful for debugging purposes. */ + bool verbose; + + struct bar_colors { + char *background; + char *statusline; + + char *focused_workspace_text; + char *focused_workspace_bg; + + char *active_workspace_text; + char *active_workspace_bg; + + char *inactive_workspace_text; + char *inactive_workspace_bg; + + char *urgent_workspace_text; + char *urgent_workspace_bg; + } colors; + + SLIST_ENTRY(Barconfig) configs; +}; + /** * Reads the configuration from ~/.i3/config or /etc/i3/config if not found. * diff --git a/src/config.c b/src/config.c index c979d8cd..3ea18258 100644 --- a/src/config.c +++ b/src/config.c @@ -21,6 +21,7 @@ char *current_configpath = NULL; Config config; struct modes_head modes; +struct barconfig_head barconfigs; /** * Ungrabs all keys, to be called before re-grabbing the keys because of a From 24ede1c8341be5c2e50925b68107d1384cb78b4e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 12 Oct 2011 23:23:09 +0100 Subject: [PATCH 182/333] add an IPC request to get the bar configuration (by ID) --- i3-msg/main.c | 4 +- include/i3/ipc.h | 8 +++- src/ipc.c | 119 ++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 127 insertions(+), 4 deletions(-) diff --git a/i3-msg/main.c b/i3-msg/main.c index 5bc35b88..13cf0ccb 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -74,9 +74,11 @@ int main(int argc, char *argv[]) { message_type = I3_IPC_MESSAGE_TYPE_GET_TREE; else if (strcasecmp(optarg, "get_marks") == 0) message_type = I3_IPC_MESSAGE_TYPE_GET_MARKS; + else if (strcasecmp(optarg, "get_bar_config") == 0) + message_type = I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG; else { printf("Unknown message type\n"); - printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks\n"); + printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config\n"); exit(EXIT_FAILURE); } } else if (o == 'q') { diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 30b2d304..a12d5cd7 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -41,6 +41,9 @@ /** Request the current defined marks from i3 */ #define I3_IPC_MESSAGE_TYPE_GET_MARKS 5 +/** Request the configuration for a specific 'bar' */ +#define I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG 6 + /* * Messages from i3 to clients * @@ -61,9 +64,12 @@ /** Tree reply type */ #define I3_IPC_REPLY_TYPE_TREE 4 -/** Marks reply type*/ +/** Marks reply type */ #define I3_IPC_REPLY_TYPE_MARKS 5 +/** Bar config reply type */ +#define I3_IPC_REPLY_TYPE_BAR_CONFIG 6 + /* * Events from i3 to clients. Events have the first bit set high. * diff --git a/src/ipc.c b/src/ipc.c index eba778cd..60f55966 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -475,6 +475,120 @@ IPC_HANDLER(get_marks) { y(free); } +/* + * Formats the reply message for a GET_BAR_CONFIG request and sends it to the + * client. + * + */ +IPC_HANDLER(get_bar_config) { + /* To get a properly terminated buffer, we copy + * message_size bytes out of the buffer */ + char *bar_id = scalloc(message_size + 1); + strncpy(bar_id, (const char*)message, message_size); + LOG("IPC: looking for config for bar ID \"%s\"\n", bar_id); + Barconfig *current, *config = NULL; + SLIST_FOREACH(current, &barconfigs, configs) { + if (strcmp(current->id, bar_id) != 0) + continue; + + config = current; + break; + } + +#if YAJL_MAJOR >= 2 + yajl_gen gen = yajl_gen_alloc(NULL); +#else + yajl_gen gen = yajl_gen_alloc(NULL, NULL); +#endif + + y(map_open); + + if (!config) { + /* If we did not find a config for the given ID, the reply will contain + * a null 'id' field. */ + ystr("id"); + y(null); + } else { + ystr("id"); + ystr(config->id); + + if (config->num_outputs > 0) { + ystr("outputs"); + y(array_open); + for (int c = 0; c < config->num_outputs; c++) + ystr(config->outputs[c]); + y(array_close); + } + +#define YSTR_IF_SET(name) \ + do { \ + if (config->name) { \ + ystr( # name); \ + ystr(config->name); \ + } \ + } while (0) + + YSTR_IF_SET(tray_output); + YSTR_IF_SET(socket_path); + + ystr("mode"); + if (config->mode == M_HIDE) + ystr("hide"); + else ystr("dock"); + + ystr("position"); + if (config->position == P_BOTTOM) + ystr("bottom"); + else ystr("top"); + + YSTR_IF_SET(status_command); + YSTR_IF_SET(font); + + ystr("workspace_buttons"); + y(bool, !config->hide_workspace_buttons); + + ystr("verbose"); + y(bool, config->verbose); + +#undef YSTR_IF_SET +#define YSTR_IF_SET(name) \ + do { \ + if (config->colors.name) { \ + ystr( # name); \ + ystr(config->colors.name); \ + } \ + } while (0) + + ystr("colors"); + y(map_open); + YSTR_IF_SET(background); + YSTR_IF_SET(statusline); + YSTR_IF_SET(focused_workspace_text); + YSTR_IF_SET(focused_workspace_bg); + YSTR_IF_SET(active_workspace_text); + YSTR_IF_SET(active_workspace_bg); + YSTR_IF_SET(inactive_workspace_text); + YSTR_IF_SET(inactive_workspace_bg); + YSTR_IF_SET(urgent_workspace_text); + YSTR_IF_SET(urgent_workspace_bg); + +#undef YSTR_IF_SET + } + + y(map_close); + + const unsigned char *payload; +#if YAJL_MAJOR >= 2 + size_t length; +#else + unsigned int length; +#endif + y(get_buf, &payload, &length); + + ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_BAR_CONFIG, payload); + y(free); +} + /* * Callback for the YAJL parser (will be called when a string is parsed). * @@ -560,13 +674,14 @@ IPC_HANDLER(subscribe) { /* The index of each callback function corresponds to the numeric * value of the message type (see include/i3/ipc.h) */ -handler_t handlers[6] = { +handler_t handlers[7] = { handle_command, handle_get_workspaces, handle_subscribe, handle_get_outputs, handle_tree, - handle_get_marks + handle_get_marks, + handle_get_bar_config }; /* From c2c6ca25d5d14f7c9c280ad3b74577ff6b1569fa Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 18 Oct 2011 22:11:27 +0100 Subject: [PATCH 183/333] Make the barconfig list a TAILQ --- include/config.h | 4 ++-- src/config.c | 2 +- src/ipc.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/config.h b/include/config.h index 200d393f..a4a274cf 100644 --- a/include/config.h +++ b/include/config.h @@ -26,7 +26,7 @@ typedef struct Barconfig Barconfig; extern char *current_configpath; extern Config config; extern SLIST_HEAD(modes_head, Mode) modes; -extern SLIST_HEAD(barconfig_head, Barconfig) barconfigs; +extern TAILQ_HEAD(barconfig_head, Barconfig) barconfigs; /** * Used during the config file lexing/parsing to keep the state of the lexer @@ -235,7 +235,7 @@ struct Barconfig { char *urgent_workspace_bg; } colors; - SLIST_ENTRY(Barconfig) configs; + TAILQ_ENTRY(Barconfig) configs; }; /** diff --git a/src/config.c b/src/config.c index 3ea18258..d39a5761 100644 --- a/src/config.c +++ b/src/config.c @@ -21,7 +21,7 @@ char *current_configpath = NULL; Config config; struct modes_head modes; -struct barconfig_head barconfigs; +struct barconfig_head barconfigs = TAILQ_HEAD_INITIALIZER(barconfigs); /** * Ungrabs all keys, to be called before re-grabbing the keys because of a diff --git a/src/ipc.c b/src/ipc.c index 60f55966..1ae6bb0a 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -487,7 +487,7 @@ IPC_HANDLER(get_bar_config) { strncpy(bar_id, (const char*)message, message_size); LOG("IPC: looking for config for bar ID \"%s\"\n", bar_id); Barconfig *current, *config = NULL; - SLIST_FOREACH(current, &barconfigs, configs) { + TAILQ_FOREACH(current, &barconfigs, configs) { if (strcmp(current->id, bar_id) != 0) continue; From e3cbdea1fce139eb43b0149f28cb9e8cc61559f5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 18 Oct 2011 22:11:44 +0100 Subject: [PATCH 184/333] Bugfix: Correctly close the colors map in the IPC bar config reply --- src/ipc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ipc.c b/src/ipc.c index 1ae6bb0a..1660096b 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -571,6 +571,7 @@ IPC_HANDLER(get_bar_config) { YSTR_IF_SET(inactive_workspace_bg); YSTR_IF_SET(urgent_workspace_text); YSTR_IF_SET(urgent_workspace_bg); + y(map_close); #undef YSTR_IF_SET } From 149b05aacfab6d94d79081b0a2ffa8f44f5720cd Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 18 Oct 2011 22:12:46 +0100 Subject: [PATCH 185/333] ipc: when requesting the bar config without a payload, return an array of available bar IDs --- src/ipc.c | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/ipc.c b/src/ipc.c index 1660096b..97937c05 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -481,6 +481,34 @@ IPC_HANDLER(get_marks) { * */ 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 + + /* If no ID was passed, we return a JSON array with all IDs */ + if (message_size == 0) { + y(array_open); + Barconfig *current; + TAILQ_FOREACH(current, &barconfigs, configs) { + ystr(current->id); + } + y(array_close); + + const unsigned char *payload; +#if YAJL_MAJOR >= 2 + size_t length; +#else + unsigned int length; +#endif + y(get_buf, &payload, &length); + + ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_BAR_CONFIG, payload); + y(free); + return; + } + /* To get a properly terminated buffer, we copy * message_size bytes out of the buffer */ char *bar_id = scalloc(message_size + 1); @@ -495,12 +523,6 @@ IPC_HANDLER(get_bar_config) { break; } -#if YAJL_MAJOR >= 2 - yajl_gen gen = yajl_gen_alloc(NULL); -#else - yajl_gen gen = yajl_gen_alloc(NULL, NULL); -#endif - y(map_open); if (!config) { From d9f3a31cb7bc1de1c5b1d684e0a34ed4a6de00b6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 18 Oct 2011 22:15:48 +0100 Subject: [PATCH 186/333] tests: add a test to check that bar configs are parsed correctly --- testcases/t/177-bar-config.t | 130 +++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 testcases/t/177-bar-config.t diff --git a/testcases/t/177-bar-config.t b/testcases/t/177-bar-config.t new file mode 100644 index 00000000..98eb4edd --- /dev/null +++ b/testcases/t/177-bar-config.t @@ -0,0 +1,130 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# !NO_I3_INSTANCE! will prevent complete-run.pl from starting i3 +# +# Checks that the bar config is parsed correctly. +# + +use i3test; + +##################################################################### +# test a config without any bars +##################################################################### + +my $config = <get_bar_config()->recv; +is(@$bars, 0, 'no bars configured'); + +exit_gracefully($pid); + +##################################################################### +# now provide a simple bar configuration +##################################################################### + +$config = <get_bar_config()->recv; +is(@$bars, 1, 'one bar configured'); + +my $bar_id = shift @$bars; + +my $bar_config = $i3->get_bar_config($bar_id)->recv; +is($bar_config->{status_command}, 'i3status --foo', 'status_command correct'); +ok(!$bar_config->{verbose}, 'verbose off by default'); +ok($bar_config->{workspace_buttons}, 'workspace buttons enabled per default'); +is($bar_config->{mode}, 'hide', 'hide mode by default'); +is($bar_config->{position}, 'bottom', 'position bottom by default'); + +exit_gracefully($pid); + +##################################################################### +# validate a more complex configuration +##################################################################### + +$config = <get_bar_config()->recv; +is(@$bars, 1, 'one bar configured'); + +my $bar_id = shift @$bars; + +my $bar_config = $i3->get_bar_config($bar_id)->recv; +is($bar_config->{status_command}, 'i3status --bar', 'status_command correct'); +ok($bar_config->{verbose}, 'verbose on'); +ok(!$bar_config->{workspace_buttons}, 'workspace buttons disabled'); +is($bar_config->{mode}, 'dock', 'dock mode'); +is($bar_config->{position}, 'top', 'position top'); +is_deeply($bar_config->{outputs}, [ 'HDMI1', 'HDMI2' ], 'outputs ok'); +is($bar_config->{tray_output}, 'HDMI2', 'tray_output ok'); +is($bar_config->{font}, 'Terminus', 'font ok'); +is($bar_config->{socket_path}, '/tmp/foobar', 'socket_path ok'); +is_deeply($bar_config->{colors}, + { + background => 'ff0000', + statusline => '00ff00', + focused_workspace_text => 'ffffff', + focused_workspace_bg => '285577', + active_workspace_text => '888888', + active_workspace_bg => '222222', + inactive_workspace_text => '888888', + inactive_workspace_bg => '222222', + urgent_workspace_text => 'ffffff', + urgent_workspace_bg => '900000', + }, 'colors ok'); + +exit_gracefully($pid); + +done_testing; From 063b124e358705f24c1972bec7d15f52a5f56a86 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 18 Oct 2011 22:16:04 +0100 Subject: [PATCH 187/333] Implement parsing bar {} config blocks --- src/cfgparse.l | 48 ++++++++++- src/cfgparse.y | 221 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 266 insertions(+), 3 deletions(-) diff --git a/src/cfgparse.l b/src/cfgparse.l index 622a133b..2278a81e 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -37,6 +37,11 @@ int yycolumn = 1; yy_push_state(EAT_WHITESPACE); \ } while (0) +#define BAR_DOUBLE_COLOR do { \ + yy_push_state(BAR_COLOR); \ + yy_push_state(BAR_COLOR); \ +} while (0) + %} EOL (\r?\n) @@ -50,7 +55,13 @@ EOL (\r?\n) %s OUTPUT_COND %s FOR_WINDOW_COND %s EAT_WHITESPACE + %x BUFFER_LINE +%x BAR +%x BAR_MODE +%x BAR_POSITION +%x BAR_COLORS +%x BAR_COLOR %% @@ -74,6 +85,37 @@ EOL (\r?\n) yycolumn = 1; } + /* This part of the lexer handles the bar {} blocks */ +[ \t]+ { /* ignore whitespace */ ; } +"{" { return '{'; } +"}" { yy_pop_state(); return '}'; } +^[ \t]*#[^\n]* { return TOKCOMMENT; } +output { WS_STRING; return TOK_BAR_OUTPUT; } +tray_output { WS_STRING; return TOK_BAR_TRAY_OUTPUT; } +socket_path { WS_STRING; return TOK_BAR_SOCKET_PATH; } +mode { yy_push_state(BAR_MODE); return TOK_BAR_MODE; } +hide { yy_pop_state(); return TOK_BAR_HIDE; } +dock { yy_pop_state(); return TOK_BAR_DOCK; } +position { yy_push_state(BAR_POSITION); return TOK_BAR_POSITION; } +bottom { yy_pop_state(); return TOK_BAR_BOTTOM; } +top { yy_pop_state(); return TOK_BAR_TOP; } +status_command { WS_STRING; return TOK_BAR_STATUS_COMMAND; } +font { WS_STRING; return TOK_BAR_FONT; } +workspace_buttons { return TOK_BAR_WORKSPACE_BUTTONS; } +verbose { return TOK_BAR_VERBOSE; } +colors { yy_push_state(BAR_COLORS); return TOK_BAR_COLORS; } +"{" { return '{'; } +"}" { yy_pop_state(); return '}'; } +background { yy_push_state(BAR_COLOR); return TOK_BAR_COLOR_BACKGROUND; } +statusline { yy_push_state(BAR_COLOR); return TOK_BAR_COLOR_STATUSLINE; } +focused_workspace { BAR_DOUBLE_COLOR; return TOK_BAR_COLOR_FOCUSED_WORKSPACE; } +active_workspace { BAR_DOUBLE_COLOR; return TOK_BAR_COLOR_ACTIVE_WORKSPACE; } +inactive_workspace { BAR_DOUBLE_COLOR; return TOK_BAR_COLOR_INACTIVE_WORKSPACE; } +urgent_workspace { BAR_DOUBLE_COLOR; return TOK_BAR_COLOR_URGENT_WORKSPACE; } +#[0-9a-fA-F]+ { yy_pop_state(); yylval.string = sstrdup(yytext+1); return HEXCOLOR; } +[a-zA-Z]+ { yylval.string = sstrdup(yytext); return WORD; } + + "]" { yy_pop_state(); return ']'; } "[" { @@ -93,13 +135,14 @@ EOL (\r?\n) yylval.string = copy; return STR; } -[^\n]+ { BEGIN(INITIAL); yylval.string = sstrdup(yytext); return STR; } +[^\n]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return STR; } [a-zA-Z0-9_-]+ { yylval.string = sstrdup(yytext); return OUTPUT; } ^[ \t]*#[^\n]* { return TOKCOMMENT; } [0-9a-fA-F]+ { yylval.string = sstrdup(yytext); return HEX; } [ \t]*→[ \t]* { BEGIN(WANT_STRING); } [ \t]+ { BEGIN(WANT_STRING); } [0-9]+ { yylval.number = atoi(yytext); return NUMBER; } +bar { yy_push_state(BAR); return TOK_BAR; } mode { return TOKMODE; } bind { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; } bindcode { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; } @@ -179,10 +222,9 @@ con_id { yy_push_state(WANT_QSTRING); return TOK_CON_ID con_mark { yy_push_state(WANT_QSTRING); return TOK_MARK; } title { yy_push_state(WANT_QSTRING); return TOK_TITLE; } -{EOL} { +<*>{EOL} { FREE(context->line_copy); context->line_number++; - BEGIN(INITIAL); yy_push_state(BUFFER_LINE); } [ \t]+ { BEGIN(WANT_STRING); } diff --git a/src/cfgparse.y b/src/cfgparse.y index 6869eee7..6e59c87c 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -14,6 +14,7 @@ static pid_t configerror_pid = -1; static Match current_match; +static Barconfig current_bar; typedef struct yy_buffer_state *YY_BUFFER_STATE; extern int yylex(struct context *context); @@ -587,6 +588,7 @@ void parse_file(const char *f) { %token STR "" %token STR_NG "" %token HEX "" +%token HEXCOLOR "#" %token OUTPUT "" %token TOKBINDCODE %token TOKTERMINAL @@ -610,6 +612,7 @@ void parse_file(const char *f) { %token TOKCOLOR %token TOKARROW "→" %token TOKMODE "mode" +%token TOK_BAR "bar" %token TOK_ORIENTATION "default_orientation" %token TOK_HORIZ "horizontal" %token TOK_VERT "vertical" @@ -634,6 +637,27 @@ void parse_file(const char *f) { %token TOK_LEAVE_FULLSCREEN "leave_fullscreen" %token TOK_FOR_WINDOW "for_window" +%token TOK_BAR_OUTPUT "output (bar)" +%token TOK_BAR_TRAY_OUTPUT "tray_output" +%token TOK_BAR_SOCKET_PATH "socket_path" +%token TOK_BAR_MODE "mode" +%token TOK_BAR_HIDE "hide" +%token TOK_BAR_DOCK "dock" +%token TOK_BAR_POSITION "position" +%token TOK_BAR_BOTTOM "bottom" +%token TOK_BAR_TOP "top" +%token TOK_BAR_STATUS_COMMAND "status_command" +%token TOK_BAR_FONT "font" +%token TOK_BAR_WORKSPACE_BUTTONS "workspace_buttons" +%token TOK_BAR_VERBOSE "verbose" +%token TOK_BAR_COLORS "colors" +%token TOK_BAR_COLOR_BACKGROUND "background" +%token TOK_BAR_COLOR_STATUSLINE "statusline" +%token TOK_BAR_COLOR_FOCUSED_WORKSPACE "focused_workspace" +%token TOK_BAR_COLOR_ACTIVE_WORKSPACE "active_workspace" +%token TOK_BAR_COLOR_INACTIVE_WORKSPACE "inactive_workspace" +%token TOK_BAR_COLOR_URGENT_WORKSPACE "urgent_workspace" + %token TOK_MARK "mark" %token TOK_CLASS "class" %token TOK_INSTANCE "instance" @@ -655,6 +679,8 @@ void parse_file(const char *f) { %type colorpixel %type bool %type popup_setting +%type bar_position_position +%type bar_mode_mode %type command %type word_or_number %type optional_workspace_name @@ -672,6 +698,7 @@ line: bindline | for_window | mode + | bar | floating_modifier | orientation | workspace_layout @@ -902,6 +929,200 @@ modeline: } ; +bar: + TOK_BAR '{' barlines '}' + { + printf("\t new bar configuration finished, saving.\n"); + /* Generate a unique ID for this bar */ + current_bar.id = sstrdup("foo"); /* TODO */ + + /* 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)); + } + ; + +barlines: + /* empty */ + | barlines barline + ; + +barline: + comment + | bar_status_command + | bar_output + | bar_tray_output + | bar_position + | bar_mode + | bar_font + | bar_workspace_buttons + | bar_verbose + | bar_socket_path + | bar_colors + | bar_color_background + | bar_color_statusline + | bar_color_focused_workspace + | bar_color_active_workspace + | bar_color_inactive_workspace + | bar_color_urgent_workspace + ; + +bar_status_command: + TOK_BAR_STATUS_COMMAND STR + { + DLOG("should add status command %s\n", $2); + FREE(current_bar.status_command); + current_bar.status_command = $2; + } + ; + +bar_output: + TOK_BAR_OUTPUT STR + { + DLOG("bar output %s\n", $2); + 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] = $2; + current_bar.num_outputs = new_outputs; + } + ; + +bar_tray_output: + TOK_BAR_TRAY_OUTPUT STR + { + DLOG("tray %s\n", $2); + FREE(current_bar.tray_output); + current_bar.tray_output = $2; + } + ; + +bar_position: + TOK_BAR_POSITION bar_position_position + { + DLOG("position %d\n", $2); + current_bar.position = $2; + } + ; + +bar_position_position: + TOK_BAR_TOP { $$ = P_TOP; } + | TOK_BAR_BOTTOM { $$ = P_BOTTOM; } + ; + +bar_mode: + TOK_BAR_MODE bar_mode_mode + { + DLOG("mode %d\n", $2); + current_bar.mode = $2; + } + ; + +bar_mode_mode: + TOK_BAR_HIDE { $$ = M_HIDE; } + | TOK_BAR_DOCK { $$ = M_DOCK; } + ; + +bar_font: + TOK_BAR_FONT STR + { + DLOG("font %s\n", $2); + FREE(current_bar.font); + current_bar.font = $2; + } + ; + +bar_workspace_buttons: + TOK_BAR_WORKSPACE_BUTTONS bool + { + DLOG("workspace_buttons = %d\n", $2); + /* We store this inverted to make the default setting right when + * initializing the struct with zero. */ + current_bar.hide_workspace_buttons = !($2); + } + ; + +bar_verbose: + TOK_BAR_VERBOSE bool + { + DLOG("verbose = %d\n", $2); + current_bar.verbose = $2; + } + ; + +bar_socket_path: + TOK_BAR_SOCKET_PATH STR + { + DLOG("socket_path = %s\n", $2); + FREE(current_bar.socket_path); + current_bar.socket_path = $2; + } + ; + +bar_colors: + TOK_BAR_COLORS '{' barlines '}' + { + /* At the moment, the TOK_BAR_COLORS token is only to make the config + * friendlier for humans. We might change this in the future if it gets + * more complex. */ + } + ; + +bar_color_background: + TOK_BAR_COLOR_BACKGROUND HEXCOLOR + { + DLOG("background = %s\n", $2); + current_bar.colors.background = $2; + } + ; + +bar_color_statusline: + TOK_BAR_COLOR_STATUSLINE HEXCOLOR + { + DLOG("statusline = %s\n", $2); + current_bar.colors.statusline = $2; + } + ; + +bar_color_focused_workspace: + TOK_BAR_COLOR_FOCUSED_WORKSPACE HEXCOLOR HEXCOLOR + { + DLOG("focused_ws = %s and %s\n", $2, $3); + current_bar.colors.focused_workspace_text = $2; + current_bar.colors.focused_workspace_bg = $3; + } + ; + +bar_color_active_workspace: + TOK_BAR_COLOR_ACTIVE_WORKSPACE HEXCOLOR HEXCOLOR + { + DLOG("active_ws = %s and %s\n", $2, $3); + current_bar.colors.active_workspace_text = $2; + current_bar.colors.active_workspace_bg = $3; + } + ; + +bar_color_inactive_workspace: + TOK_BAR_COLOR_INACTIVE_WORKSPACE HEXCOLOR HEXCOLOR + { + DLOG("inactive_ws = %s and %s\n", $2, $3); + current_bar.colors.inactive_workspace_text = $2; + current_bar.colors.inactive_workspace_bg = $3; + } + ; + +bar_color_urgent_workspace: + TOK_BAR_COLOR_URGENT_WORKSPACE HEXCOLOR HEXCOLOR + { + DLOG("urgent_ws = %s and %s\n", $2, $3); + current_bar.colors.urgent_workspace_text = $2; + current_bar.colors.urgent_workspace_bg = $3; + } + ; + floating_modifier: TOKFLOATING_MODIFIER binding_modifiers { From 15bface10d2ebaac3f0a7b01da2c7be285a61807 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 19 Oct 2011 19:57:39 +0100 Subject: [PATCH 188/333] Create different IDs for each bar (+test) --- src/cfgparse.y | 9 ++++++++- src/main.c | 2 ++ testcases/t/177-bar-config.t | 39 ++++++++++++++++++++++++++++++++++-- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/cfgparse.y b/src/cfgparse.y index 6e59c87c..2c796af1 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -934,7 +934,14 @@ bar: { printf("\t new bar configuration finished, saving.\n"); /* Generate a unique ID for this bar */ - current_bar.id = sstrdup("foo"); /* TODO */ + 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'; + } /* Copy the current (static) structure into a dynamically allocated * one, then cleanup our static one. */ diff --git a/src/main.c b/src/main.c index ffdc1fd3..832d7f1b 100644 --- a/src/main.c +++ b/src/main.c @@ -217,6 +217,8 @@ int main(int argc, char *argv[]) { if (!isatty(fileno(stdout))) setbuf(stdout, NULL); + srand(time(NULL)); + init_logging(); start_argv = argv; diff --git a/testcases/t/177-bar-config.t b/testcases/t/177-bar-config.t index 98eb4edd..c7cf2843 100644 --- a/testcases/t/177-bar-config.t +++ b/testcases/t/177-bar-config.t @@ -99,9 +99,9 @@ $i3 = i3(get_socket_path(0)); $bars = $i3->get_bar_config()->recv; is(@$bars, 1, 'one bar configured'); -my $bar_id = shift @$bars; +$bar_id = shift @$bars; -my $bar_config = $i3->get_bar_config($bar_id)->recv; +$bar_config = $i3->get_bar_config($bar_id)->recv; is($bar_config->{status_command}, 'i3status --bar', 'status_command correct'); ok($bar_config->{verbose}, 'verbose on'); ok(!$bar_config->{workspace_buttons}, 'workspace buttons disabled'); @@ -127,4 +127,39 @@ is_deeply($bar_config->{colors}, exit_gracefully($pid); +##################################################################### +# ensure that multiple bars get different IDs +##################################################################### + +$config = <get_bar_config()->recv; +is(@$bars, 2, 'two bars configured'); +isnt($bars->[0], $bars->[1], 'bar IDs are different'); + +my $bar1_config = $i3->get_bar_config($bars->[0])->recv; +my $bar2_config = $i3->get_bar_config($bars->[1])->recv; + +isnt($bar1_config->{outputs}, $bar2_config->{outputs}, 'outputs different'); + +exit_gracefully($pid); + done_testing; From a5be27cb79f63b6303b65c70a79bddf44c1017d4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 19 Oct 2011 22:58:19 +0100 Subject: [PATCH 189/333] Make i3bar get its config from i3 via IPC In order to not duplicate configuration options and make stuff confusing, we dropped the commandline flags (except for socket_path and bar_id). This means that you *have to* specify bar_id when starting i3bar. The best way is to let i3 start i3bar, which it will do automatically for every bar {} configuration block it finds. --- i3bar/include/config.h | 17 +++- i3bar/include/xcb.h | 12 ++- i3bar/src/config.c | 214 +++++++++++++++++++++++++++++++++++++++++ i3bar/src/ipc.c | 38 +++++++- i3bar/src/main.c | 188 ++++-------------------------------- i3bar/src/xcb.c | 159 +++++++++++++++++------------- 6 files changed, 388 insertions(+), 240 deletions(-) create mode 100644 i3bar/src/config.c diff --git a/i3bar/include/config.h b/i3bar/include/config.h index b3473917..19b246dc 100644 --- a/i3bar/include/config.h +++ b/i3bar/include/config.h @@ -13,10 +13,25 @@ typedef struct config_t { int hide_on_modifier; dockpos_t dockpos; int verbose; - xcb_colors_t *colors; + struct xcb_color_strings_t colors; int disable_ws; + char *bar_id; + char *command; + char *fontname; } config_t; config_t config; +/** + * Start parsing the received bar configuration json-string + * + */ +void parse_config_json(char *json); + +/** + * free()s the color strings as soon as they are not needed anymore. + * + */ +void free_colors(struct xcb_color_strings_t *colors); + #endif diff --git a/i3bar/include/xcb.h b/i3bar/include/xcb.h index c1b7cc14..51da5d3e 100644 --- a/i3bar/include/xcb.h +++ b/i3bar/include/xcb.h @@ -40,10 +40,18 @@ struct xcb_color_strings_t { typedef struct xcb_colors_t xcb_colors_t; /* - * Initialize xcb and use the specified fontname for text-rendering + * Early initialization of the connection to X11: Everything which does not + * depend on 'config'. * */ -char *init_xcb(char *fontname); +char *init_xcb_early(); + +/** + * Initialization which depends on 'config' being usable. Called after the + * configuration has arrived. + * + */ +void init_xcb_late(char *fontname); /* * Initialize the colors diff --git a/i3bar/src/config.c b/i3bar/src/config.c new file mode 100644 index 00000000..23c5e122 --- /dev/null +++ b/i3bar/src/config.c @@ -0,0 +1,214 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3bar - an xcb-based status- and ws-bar for i3 + * + * © 2010-2011 Axel Wagner and contributors + * + * See file LICENSE for license information + * + * src/outputs.c: Maintaining the output-list + * + */ +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +static char *cur_key; + +/* + * Parse a key. + * + * Essentially we just save it in cur_key. + * + */ +#if YAJL_MAJOR >= 2 +static int config_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) { +#else +static int config_map_key_cb(void *params_, const unsigned char *keyVal, unsigned keyLen) { +#endif + FREE(cur_key); + + cur_key = malloc(sizeof(unsigned char) * (keyLen + 1)); + strncpy(cur_key, (const char*) keyVal, keyLen); + cur_key[keyLen] = '\0'; + + return 1; +} + +/* + * Parse a string + * + */ +#if YAJL_MAJOR >= 2 +static int config_string_cb(void *params_, const unsigned char *val, size_t len) { +#else +static int config_string_cb(void *params_, const unsigned char *val, unsigned int len) { +#endif + /* The id is ignored, we already have it in config.bar_id */ + if (!strcmp(cur_key, "id")) + return 1; + + if (!strcmp(cur_key, "mode")) { + DLOG("mode = %.*s, len = %d\n", len, val, len); + config.hide_on_modifier = (len == 4 && !strncmp((const char*)val, "hide", strlen("hide"))); + return 1; + } + + if (!strcmp(cur_key, "position")) { + DLOG("position = %.*s\n", len, val); + config.dockpos = (len == 3 && !strncmp((const char*)val, "top", strlen("top")) ? DOCKPOS_TOP : DOCKPOS_BOT); + return 1; + } + + if (!strcmp(cur_key, "status_command")) { + /* We cannot directly start the child here, because start_child() also + * needs to be run when no command was specified (to setup stdin). + * Therefore we save the command in 'config' and access it later in + * got_bar_config() */ + DLOG("command = %.*s\n", len, val); + asprintf(&config.command, "%.*s", len, val); + return 1; + } + + if (!strcmp(cur_key, "font")) { + DLOG("font = %.*s\n", len, val); + asprintf(&config.fontname, "%.*s", len, val); + return 1; + } + + if (!strcmp(cur_key, "outputs")) { + printf("+output %.*s\n", len, val); + /* XXX: these are not implemented yet */ + return 1; + } + + if (!strcmp(cur_key, "tray_output")) { + printf("tray_output %.*s\n", len, val); + /* XXX: these are not implemented yet */ + return 1; + } + +#define COLOR(json_name, struct_name) \ + do { \ + if (!strcmp(cur_key, #json_name)) { \ + DLOG(#json_name " = " #struct_name " = %.*s\n", len, val); \ + asprintf(&(config.colors.struct_name), "%.*s", len, val); \ + return 1; \ + } \ + } while (0) + + COLOR(statusline, bar_fg); + COLOR(background, bar_bg); + COLOR(focused_workspace_text, focus_ws_fg); + COLOR(focused_workspace_bg, focus_ws_bg); + COLOR(active_workspace_text, active_ws_fg); + COLOR(active_workspace_bg, active_ws_bg); + COLOR(inactive_workspace_text, inactive_ws_fg); + COLOR(inactive_workspace_bg, inactive_ws_bg); + COLOR(urgent_workspace_text, urgent_ws_fg); + COLOR(urgent_workspace_bg, urgent_ws_bg); + + printf("got unexpected string %.*s for cur_key = %s\n", len, val, cur_key); + + return 0; +} + +/* + * Parse a boolean value + * + */ +static int config_boolean_cb(void *params_, int val) { + if (!strcmp(cur_key, "workspace_buttons")) { + DLOG("workspace_buttons = %d\n", val); + config.disable_ws = !val; + return 1; + } + + if (!strcmp(cur_key, "verbose")) { + DLOG("verbose = %d\n", val); + config.verbose = val; + return 1; + } + + return 0; +} + +/* A datastructure to pass all these callbacks to yajl */ +static yajl_callbacks outputs_callbacks = { + NULL, + &config_boolean_cb, + NULL, + NULL, + NULL, + &config_string_cb, + NULL, + &config_map_key_cb, + NULL, + NULL, + NULL +}; + +/* + * Start parsing the received bar configuration json-string + * + */ +void parse_config_json(char *json) { + yajl_handle handle; + yajl_status state; +#if YAJL_MAJOR < 2 + yajl_parser_config parse_conf = { 0, 0 }; + + handle = yajl_alloc(&outputs_callbacks, &parse_conf, NULL, NULL); +#else + handle = yajl_alloc(&outputs_callbacks, NULL, NULL); +#endif + + state = yajl_parse(handle, (const unsigned char*) json, strlen(json)); + + /* FIXME: Proper 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 config-reply!\n"); + exit(EXIT_FAILURE); + break; + } + + yajl_free(handle); +} + +/* + * free()s the color strings as soon as they are not needed anymore. + * + */ +void free_colors(struct xcb_color_strings_t *colors) { +#define FREE_COLOR(x) \ + do { \ + if (colors->x) \ + free(colors->x); \ + } while (0) + FREE_COLOR(bar_fg); + FREE_COLOR(bar_bg); + FREE_COLOR(active_ws_fg); + FREE_COLOR(active_ws_bg); + FREE_COLOR(inactive_ws_fg); + FREE_COLOR(inactive_ws_bg); + FREE_COLOR(urgent_ws_fg); + FREE_COLOR(urgent_ws_bg); + FREE_COLOR(focus_ws_fg); + FREE_COLOR(focus_ws_bg); +#undef FREE_COLOR +} + diff --git a/i3bar/src/ipc.c b/i3bar/src/ipc.c index e31de333..5969e5d6 100644 --- a/i3bar/src/ipc.c +++ b/i3bar/src/ipc.c @@ -114,12 +114,47 @@ void got_output_reply(char *reply) { reconfig_windows(); } +/* + * Called when we get the configuration for our bar instance + * + */ +void got_bar_config(char *reply) { + DLOG("Received bar config \"%s\"\n", reply); + /* We initiate the main-function by requesting infos about the outputs and + * workspaces. Everything else (creating the bars, showing the right workspace- + * buttons and more) is taken care of by the event-drivenness of the code */ + i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL); + parse_config_json(reply); + + /* Now we can actually use 'config', so let's subscribe to the appropriate + * events and request the workspaces if necessary. */ + subscribe_events(); + if (!config.disable_ws) + i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL); + + /* Initialize the rest of XCB */ + init_xcb_late(config.fontname); + + /* Resolve color strings to colorpixels and save them, then free the strings. */ + init_colors(&(config.colors)); + free_colors(&(config.colors)); + + /* The name of this function is actually misleading. Even if no command is + * specified, this function initiates the watchers to listen on stdin and + * react accordingly */ + start_child(config.command); + FREE(config.command); +} + /* Data-structure to easily call the reply-handlers later */ handler_t reply_handlers[] = { &got_command_reply, &got_workspace_reply, &got_subscribe_reply, &got_output_reply, + NULL, + NULL, + &got_bar_config, }; /* @@ -232,7 +267,8 @@ void got_data(struct ev_loop *loop, ev_io *watcher, int events) { type ^= 1 << 31; event_handlers[type](buffer); } else { - reply_handlers[type](buffer); + if (reply_handlers[type]) + reply_handlers[type](buffer); } FREE(header); diff --git a/i3bar/src/main.c b/i3bar/src/main.c index bfccb8ed..ea489941 100644 --- a/i3bar/src/main.c +++ b/i3bar/src/main.c @@ -19,6 +19,7 @@ #include #include "common.h" +#include "libi3.h" /* * Glob path, i.e. expand ~ @@ -39,61 +40,11 @@ char *expand_path(char *path) { return result; } -static void read_color(char **color) { - int len = strlen(optarg); - if (len == 6 || (len == 7 && optarg[0] == '#')) { - int offset = len - 6; - int good = 1, i; - for (i = offset; good && i < 6 + offset; ++i) { - char c = optarg[i]; - if (!(c >= 'a' && c <= 'f') - && !(c >= 'A' && c <= 'F') - && !(c >= '0' && c <= '9')) { - good = 0; - break; - } - } - if (good) { - *color = strdup(optarg + offset); - return; - } - } - - fprintf(stderr, "Bad color value \"%s\"\n", optarg); - exit(EXIT_FAILURE); -} - -static void free_colors(struct xcb_color_strings_t *colors) { -#define FREE_COLOR(x) \ - do { \ - if (colors->x) \ - free(colors->x); \ - } while (0) - FREE_COLOR(bar_fg); - FREE_COLOR(bar_bg); - FREE_COLOR(active_ws_fg); - FREE_COLOR(active_ws_bg); - FREE_COLOR(inactive_ws_fg); - FREE_COLOR(inactive_ws_bg); - FREE_COLOR(urgent_ws_fg); - FREE_COLOR(urgent_ws_bg); - FREE_COLOR(focus_ws_fg); - FREE_COLOR(focus_ws_bg); -#undef FREE_COLOR -} - void print_usage(char *elf_name) { - printf("Usage: %s [-s sock_path] [-c command] [-m|-d[pos]] [-f font] [-V] [-h]\n", elf_name); + printf("Usage: %s [-s sock_path] [-h] [-v]\n", elf_name); printf("-s \tConnect to i3 via \n"); - printf("-c \tExecute to get stdin\n"); - printf("-m\t\tHide the bars, when mod4 is not pressed.\n"); - printf("-d[]\tEnable dockmode. is \"top\" or \"bottom\". Default is bottom\n"); - printf("\t\tIf -c is specified, the childprocess is sent a SIGSTOP on hiding,\n"); - printf("\t\tand a SIGCONT on unhiding of the bars\n"); - printf("-f \tUse X-Core-Font for display\n"); - printf("-w\t\tDisable workspace-buttons\n"); - printf("-V\t\tBe (very) verbose with the debug-output\n"); printf("-h\t\tDisplay this help-message and exit\n"); + printf("-v\t\tDisplay version number and exit\n"); } /* @@ -120,107 +71,33 @@ int main(int argc, char **argv) { int opt; int option_index = 0; char *socket_path = getenv("I3SOCK"); - char *command = NULL; - char *fontname = NULL; char *i3_default_sock_path = "/tmp/i3-ipc.sock"; - struct xcb_color_strings_t colors = { NULL, }; - /* Definition of the standard-config */ - config.hide_on_modifier = 0; - config.dockpos = DOCKPOS_NONE; - config.disable_ws = 0; + /* Initialize the standard config to use 0 as default */ + memset(&config, '\0', sizeof(config_t)); static struct option long_opt[] = { { "socket", required_argument, 0, 's' }, - { "command", required_argument, 0, 'c' }, - { "hide", no_argument, 0, 'm' }, - { "dock", optional_argument, 0, 'd' }, - { "font", required_argument, 0, 'f' }, - { "nows", no_argument, 0, 'w' }, + { "bar_id", required_argument, 0, 0 }, { "help", no_argument, 0, 'h' }, { "version", no_argument, 0, 'v' }, - { "verbose", no_argument, 0, 'V' }, - { "color-bar-fg", required_argument, 0, 'A' }, - { "color-bar-bg", required_argument, 0, 'B' }, - { "color-active-ws-fg", required_argument, 0, 'C' }, - { "color-active-ws-bg", required_argument, 0, 'D' }, - { "color-inactive-ws-fg", required_argument, 0, 'E' }, - { "color-inactive-ws-bg", required_argument, 0, 'F' }, - { "color-urgent-ws-bg", required_argument, 0, 'G' }, - { "color-urgent-ws-fg", required_argument, 0, 'H' }, - { "color-focus-ws-bg", required_argument, 0, 'I' }, - { "color-focus-ws-fg", required_argument, 0, 'J' }, { NULL, 0, 0, 0} }; - while ((opt = getopt_long(argc, argv, "s:c:d::mf:whvVA:B:C:D:E:F:G:H:I:J:", long_opt, &option_index)) != -1) { + while ((opt = getopt_long(argc, argv, "s:hv", long_opt, &option_index)) != -1) { switch (opt) { case 's': socket_path = expand_path(optarg); break; - case 'c': - command = strdup(optarg); - break; - case 'm': - config.hide_on_modifier = 1; - break; - case 'd': - config.hide_on_modifier = 0; - if (optarg == NULL) { - config.dockpos = DOCKPOS_BOT; - break; - } - if (!strcmp(optarg, "top")) { - config.dockpos = DOCKPOS_TOP; - } else if (!strcmp(optarg, "bottom")) { - config.dockpos = DOCKPOS_BOT; - } else { - print_usage(argv[0]); - exit(EXIT_FAILURE); - } - break; - case 'f': - fontname = strdup(optarg); - break; - case 'w': - config.disable_ws = 1; - break; case 'v': printf("i3bar version " I3_VERSION " © 2010-2011 Axel Wagner and contributors\n"); exit(EXIT_SUCCESS); break; - case 'V': - config.verbose = 1; - break; - case 'A': - read_color(&colors.bar_fg); - break; - case 'B': - read_color(&colors.bar_bg); - break; - case 'C': - read_color(&colors.active_ws_fg); - break; - case 'D': - read_color(&colors.active_ws_bg); - break; - case 'E': - read_color(&colors.inactive_ws_fg); - break; - case 'F': - read_color(&colors.inactive_ws_bg); - break; - case 'G': - read_color(&colors.urgent_ws_bg); - break; - case 'H': - read_color(&colors.urgent_ws_fg); - break; - case 'I': - read_color(&colors.focus_ws_bg); - break; - case 'J': - read_color(&colors.focus_ws_fg); + case 0: + if (!strcmp(long_opt[option_index].name, "bar_id")) { + FREE(config.bar_id); + config.bar_id = sstrdup(optarg); + } break; default: print_usage(argv[0]); @@ -229,26 +106,16 @@ int main(int argc, char **argv) { } } - if (fontname == NULL) { - /* This is a very restrictive default. More sensefull would be something like - * "-misc-*-*-*-*--*-*-*-*-*-*-*-*". But since that produces very ugly results - * on my machine, let's stick with this until we have a configfile */ - fontname = "-misc-fixed-medium-r-semicondensed--12-110-75-75-c-60-iso10646-1"; - } - - if (config.dockpos != DOCKPOS_NONE) { - if (config.hide_on_modifier) { - ELOG("--dock and --hide are mutually exclusive!\n"); - exit(EXIT_FAILURE); - } - } else { - config.hide_on_modifier = 1; + if (!config.bar_id) { + /* TODO: maybe we want -f which will automatically ask i3 for the first + * configured bar (and error out if there are too many)? */ + ELOG("No bar_id passed. Please let i3 start i3bar or specify --bar_id\n"); + exit(EXIT_FAILURE); } main_loop = ev_default_loop(0); - init_colors(&colors); - char *atom_sock_path = init_xcb(fontname); + char *atom_sock_path = init_xcb_early(); if (socket_path == NULL) { socket_path = atom_sock_path; @@ -259,27 +126,12 @@ int main(int argc, char **argv) { socket_path = expand_path(i3_default_sock_path); } - free_colors(&colors); - init_outputs(); if (init_connection(socket_path)) { - /* We subscribe to the i3-events we need */ - subscribe_events(); - - /* We initiate the main-function by requesting infos about the outputs and - * workspaces. Everything else (creating the bars, showing the right workspace- - * buttons and more) is taken care of by the event-driveniness of the code */ - i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL); - if (!config.disable_ws) { - i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL); - } + /* Request the bar configuration. When it arrives, we fill the config array. */ + i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG, config.bar_id); } - /* The name of this function is actually misleading. Even if no -c is specified, - * this function initiates the watchers to listen on stdin and react accordingly */ - start_child(command); - FREE(command); - /* We listen to SIGTERM/QUIT/INT and try to exit cleanly, by stopping the main-loop. * We only need those watchers on the stack, so putting them on the stack saves us * some calls to free() */ diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 4bbc8da7..471209c7 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -790,10 +790,11 @@ void xkb_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) { } /* - * Initialize xcb and use the specified fontname for text-rendering + * Early initialization of the connection to X11: Everything which does not + * depend on 'config'. * */ -char *init_xcb(char *fontname) { +char *init_xcb_early() { /* FIXME: xcb_connect leaks Memory */ xcb_connection = xcb_connect(NULL, &screen); if (xcb_connection_has_error(xcb_connection)) { @@ -809,66 +810,10 @@ char *init_xcb(char *fontname) { xcb_screen = xcb_setup_roots_iterator(xcb_get_setup(xcb_connection)).data; xcb_root = xcb_screen->root; - /* We load and allocate the font */ - xcb_font = xcb_generate_id(xcb_connection); - xcb_void_cookie_t open_font_cookie; - open_font_cookie = xcb_open_font_checked(xcb_connection, - xcb_font, - strlen(fontname), - fontname); - - /* We need to save info about the font, because we need the font's height and - * information about the width of characters */ - xcb_query_font_cookie_t query_font_cookie; - query_font_cookie = xcb_query_font(xcb_connection, - xcb_font); - - /* To grab modifiers without blocking other applications from receiving key-events - * involving that modifier, we sadly have to use xkb which is not yet fully supported - * in xcb */ - if (config.hide_on_modifier) { - int xkb_major, xkb_minor, xkb_errbase, xkb_err; - xkb_major = XkbMajorVersion; - xkb_minor = XkbMinorVersion; - - xkb_dpy = XkbOpenDisplay(NULL, - &xkb_event_base, - &xkb_errbase, - &xkb_major, - &xkb_minor, - &xkb_err); - - if (xkb_dpy == NULL) { - ELOG("No XKB!\n"); - exit(EXIT_FAILURE); - } - - if (fcntl(ConnectionNumber(xkb_dpy), F_SETFD, FD_CLOEXEC) == -1) { - ELOG("Could not set FD_CLOEXEC on xkbdpy: %s\n", strerror(errno)); - exit(EXIT_FAILURE); - } - - int i1; - if (!XkbQueryExtension(xkb_dpy, &i1, &xkb_event_base, &xkb_errbase, &xkb_major, &xkb_minor)) { - ELOG("XKB not supported by X-server!\n"); - exit(EXIT_FAILURE); - } - - if (!XkbSelectEvents(xkb_dpy, XkbUseCoreKbd, XkbStateNotifyMask, XkbStateNotifyMask)) { - ELOG("Could not grab Key!\n"); - exit(EXIT_FAILURE); - } - - xkb_io = malloc(sizeof(ev_io)); - ev_io_init(xkb_io, &xkb_io_cb, ConnectionNumber(xkb_dpy), EV_READ); - ev_io_start(main_loop, xkb_io); - XFlush(xkb_dpy); - } - /* We draw the statusline to a seperate pixmap, because it looks the same on all bars and * this way, we can choose to crop it */ uint32_t mask = XCB_GC_FOREGROUND; - uint32_t vals[3] = { colors.bar_bg, colors.bar_bg, xcb_font }; + uint32_t vals[] = { colors.bar_bg, colors.bar_bg }; statusline_clear = xcb_generate_id(xcb_connection); xcb_void_cookie_t clear_ctx_cookie = xcb_create_gc_checked(xcb_connection, @@ -877,7 +822,7 @@ char *init_xcb(char *fontname) { mask, vals); - mask |= XCB_GC_BACKGROUND | XCB_GC_FONT; + mask |= XCB_GC_BACKGROUND; vals[0] = colors.bar_fg; statusline_ctx = xcb_generate_id(xcb_connection); xcb_void_cookie_t sl_ctx_cookie = xcb_create_gc_checked(xcb_connection, @@ -931,6 +876,92 @@ char *init_xcb(char *fontname) { } } + + if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline-buffer") || + xcb_request_failed(clear_ctx_cookie, "Could not allocate statusline-buffer-clearcontext") || + xcb_request_failed(sl_ctx_cookie, "Could not allocate statusline-buffer-context")) { + exit(EXIT_FAILURE); + } + + return path; +} + +/* + * Initialization which depends on 'config' being usable. Called after the + * configuration has arrived. + * + */ +void init_xcb_late(char *fontname) { + if (fontname == NULL) { + /* This is a very restrictive default. More sensefull would be something like + * "-misc-*-*-*-*--*-*-*-*-*-*-*-*". But since that produces very ugly results + * on my machine, let's stick with this until we have a configfile */ + fontname = "-misc-fixed-medium-r-semicondensed--12-110-75-75-c-60-iso10646-1"; + } + + /* We load and allocate the font */ + xcb_font = xcb_generate_id(xcb_connection); + xcb_void_cookie_t open_font_cookie; + open_font_cookie = xcb_open_font_checked(xcb_connection, + xcb_font, + strlen(fontname), + fontname); + + /* We need to save info about the font, because we need the font's height and + * information about the width of characters */ + xcb_query_font_cookie_t query_font_cookie; + query_font_cookie = xcb_query_font(xcb_connection, + xcb_font); + + xcb_change_gc(xcb_connection, + statusline_ctx, + XCB_GC_FONT, + (uint32_t[]){ xcb_font }); + + xcb_flush(xcb_connection); + + /* To grab modifiers without blocking other applications from receiving key-events + * involving that modifier, we sadly have to use xkb which is not yet fully supported + * in xcb */ + if (config.hide_on_modifier) { + int xkb_major, xkb_minor, xkb_errbase, xkb_err; + xkb_major = XkbMajorVersion; + xkb_minor = XkbMinorVersion; + + xkb_dpy = XkbOpenDisplay(NULL, + &xkb_event_base, + &xkb_errbase, + &xkb_major, + &xkb_minor, + &xkb_err); + + if (xkb_dpy == NULL) { + ELOG("No XKB!\n"); + exit(EXIT_FAILURE); + } + + if (fcntl(ConnectionNumber(xkb_dpy), F_SETFD, FD_CLOEXEC) == -1) { + ELOG("Could not set FD_CLOEXEC on xkbdpy: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + int i1; + if (!XkbQueryExtension(xkb_dpy, &i1, &xkb_event_base, &xkb_errbase, &xkb_major, &xkb_minor)) { + ELOG("XKB not supported by X-server!\n"); + exit(EXIT_FAILURE); + } + + if (!XkbSelectEvents(xkb_dpy, XkbUseCoreKbd, XkbStateNotifyMask, XkbStateNotifyMask)) { + ELOG("Could not grab Key!\n"); + exit(EXIT_FAILURE); + } + + xkb_io = malloc(sizeof(ev_io)); + ev_io_init(xkb_io, &xkb_io_cb, ConnectionNumber(xkb_dpy), EV_READ); + ev_io_start(main_loop, xkb_io); + XFlush(xkb_dpy); + } + /* Now we save the font-infos */ font_info = xcb_query_font_reply(xcb_connection, query_font_cookie, @@ -949,14 +980,6 @@ char *init_xcb(char *fontname) { } DLOG("Calculated Font-height: %d\n", font_height); - - if (xcb_request_failed(sl_pm_cookie, "Could not allocate statusline-buffer") || - xcb_request_failed(clear_ctx_cookie, "Could not allocate statusline-buffer-clearcontext") || - xcb_request_failed(sl_ctx_cookie, "Could not allocate statusline-buffer-context")) { - exit(EXIT_FAILURE); - } - - return path; } /* From ad0f13a0a987c60094d207e8fc43bac4b525f4d1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 20 Oct 2011 18:47:09 +0100 Subject: [PATCH 190/333] Clean bars before reloading the config (+test) While the configuration gets updated in i3, the i3bar processes will not pick up these changes. We have to think about a good way to do that. --- src/config.c | 26 ++++++++++++++++++++++++++ testcases/t/177-bar-config.t | 8 ++++++++ 2 files changed, 34 insertions(+) diff --git a/src/config.c b/src/config.c index d39a5761..0830add8 100644 --- a/src/config.c +++ b/src/config.c @@ -295,6 +295,32 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, FREE(assign); } + /* Clear bar configs */ + Barconfig *barconfig; + while (!TAILQ_EMPTY(&barconfigs)) { + barconfig = TAILQ_FIRST(&barconfigs); + FREE(barconfig->id); + for (int c = 0; c < barconfig->num_outputs; c++) + free(barconfig->outputs[c]); + FREE(barconfig->outputs); + FREE(barconfig->tray_output); + FREE(barconfig->socket_path); + FREE(barconfig->status_command); + FREE(barconfig->font); + FREE(barconfig->colors.background); + FREE(barconfig->colors.statusline); + FREE(barconfig->colors.focused_workspace_text); + FREE(barconfig->colors.focused_workspace_bg); + FREE(barconfig->colors.active_workspace_text); + FREE(barconfig->colors.active_workspace_bg); + FREE(barconfig->colors.inactive_workspace_text); + FREE(barconfig->colors.inactive_workspace_bg); + FREE(barconfig->colors.urgent_workspace_text); + FREE(barconfig->colors.urgent_workspace_bg); + TAILQ_REMOVE(&barconfigs, barconfig, configs); + FREE(barconfig); + } + /* Clear workspace names */ #if 0 Workspace *ws; diff --git a/testcases/t/177-bar-config.t b/testcases/t/177-bar-config.t index c7cf2843..761dbf21 100644 --- a/testcases/t/177-bar-config.t +++ b/testcases/t/177-bar-config.t @@ -54,6 +54,14 @@ ok($bar_config->{workspace_buttons}, 'workspace buttons enabled per default'); is($bar_config->{mode}, 'hide', 'hide mode by default'); is($bar_config->{position}, 'bottom', 'position bottom by default'); +##################################################################### +# ensure that reloading cleans up the old bar configs +##################################################################### + +cmd 'reload'; +$bars = $i3->get_bar_config()->recv; +is(@$bars, 1, 'still one bar configured'); + exit_gracefully($pid); ##################################################################### From ab2d96ba48cb903c7654a679ad045da621ead31b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 20 Oct 2011 19:02:08 +0100 Subject: [PATCH 191/333] tests: modify $PATH so that it prefers the compiled versions of everything in ../ --- testcases/lib/SocketActivation.pm | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/testcases/lib/SocketActivation.pm b/testcases/lib/SocketActivation.pm index ef182012..11a672e3 100644 --- a/testcases/lib/SocketActivation.pm +++ b/testcases/lib/SocketActivation.pm @@ -54,6 +54,14 @@ sub activate_i3 { $ENV{LISTEN_PID} = $$; $ENV{LISTEN_FDS} = 1; $ENV{DISPLAY} = $args{display}; + $ENV{PATH} = join(':', + '../i3-nagbar', + '../i3-msg', + '../i3-config-wizard', + '../i3bar', + '..', + $ENV{PATH} + ); # Only pass file descriptors 0 (stdin), 1 (stdout), 2 (stderr) and # 3 (socket) to the child. $^F = 3; From 230b238870c742eb4c55de62c04d022bb6c12682 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 20 Oct 2011 19:03:40 +0100 Subject: [PATCH 192/333] Actually start i3bar instances for each configured bar --- src/main.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main.c b/src/main.c index 832d7f1b..50ebaa34 100644 --- a/src/main.c +++ b/src/main.c @@ -633,6 +633,17 @@ int main(int argc, char *argv[]) { start_application(exec_always->command); } + /* Start i3bar processes for all configured bars */ + Barconfig *barconfig; + TAILQ_FOREACH(barconfig, &barconfigs, configs) { + char *command = NULL; + asprintf(&command, "i3bar --bar_id=%s --socket=\"%s\"", + barconfig->id, current_socketpath); + LOG("Starting bar process: %s\n", command); + start_application(command); + free(command); + } + /* Make sure to destroy the event loop to invoke the cleeanup callbacks * when calling exit() */ atexit(i3_exit); From c5caa9682cac0a480e0e9c27767f467000adbf20 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 20 Oct 2011 19:08:41 +0100 Subject: [PATCH 193/333] =?UTF-8?q?i3bar:=20don=E2=80=99t=20reconnect,=20b?= =?UTF-8?q?ut=20exit(0)=20on=20EOF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since i3 starts i3bar instances as necessary, EOF is considered a signal to either shutdown because i3 exited or because it is restarting. --- i3bar/src/ipc.c | 67 +++++++------------------------------------------ 1 file changed, 9 insertions(+), 58 deletions(-) diff --git a/i3bar/src/ipc.c b/i3bar/src/ipc.c index 5969e5d6..db60a362 100644 --- a/i3bar/src/ipc.c +++ b/i3bar/src/ipc.c @@ -24,55 +24,11 @@ #include "common.h" ev_io *i3_connection; -ev_timer *reconn = NULL; const char *sock_path; typedef void(*handler_t)(char*); -/* - * Retry to connect. - * - */ -void retry_connection(struct ev_loop *loop, ev_timer *w, int events) { - static int retries = 8; - if (init_connection(sock_path) == 0) { - if (retries == 0) { - ELOG("Retried 8 times - connection failed!\n"); - exit(EXIT_FAILURE); - } - retries--; - return; - } - retries = 8; - ev_timer_stop(loop, w); - subscribe_events(); - - /* We get the current outputs and workspaces, to - * reconfigure all bars with the current configuration */ - i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL); - if (!config.disable_ws) { - i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL); - } -} - -/* - * Schedule a reconnect - * - */ -void reconnect() { - if (reconn == NULL) { - if ((reconn = malloc(sizeof(ev_timer))) == NULL) { - ELOG("malloc() failed: %s\n", strerror(errno)); - exit(EXIT_FAILURE); - } - } else { - ev_timer_stop(main_loop, reconn); - } - ev_timer_init(reconn, retry_connection, 0.25, 0.25); - ev_timer_start(main_loop, reconn); -} - /* * Called, when we get a reply to a command from i3. * Since i3 does not give us much feedback on commands, we do not much @@ -210,12 +166,10 @@ void got_data(struct ev_loop *loop, ev_io *watcher, int events) { exit(EXIT_FAILURE); } if (n == 0) { - /* EOF received. We try to recover a few times, because most likely - * i3 just restarted */ - ELOG("EOF received, try to recover...\n"); - destroy_connection(); - reconnect(); - return; + /* EOF received. Since i3 will restart i3bar instances as appropriate, + * we exit here. */ + DLOG("EOF received, exiting...\n"); + exit(EXIT_SUCCESS); } rec += n; } @@ -239,12 +193,10 @@ void got_data(struct ev_loop *loop, ev_io *watcher, int events) { * of the message */ char *buffer = malloc(size + 1); if (buffer == NULL) { - /* EOF received. We try to recover a few times, because most likely - * i3 just restarted */ - ELOG("EOF received, try to recover...\n"); - destroy_connection(); - reconnect(); - return; + /* EOF received. Since i3 will restart i3bar instances as appropriate, + * we exit here. */ + DLOG("EOF received, exiting...\n"); + exit(EXIT_SUCCESS); } rec = 0; @@ -346,8 +298,7 @@ int init_connection(const char *socket_path) { strcpy(addr.sun_path, sock_path); if (connect(sockfd, (const struct sockaddr*) &addr, sizeof(struct sockaddr_un)) < 0) { ELOG("Could not connect to i3! %s: %s\n", sock_path, strerror(errno)); - reconnect(); - return 0; + exit(EXIT_FAILURE); } i3_connection = malloc(sizeof(ev_io)); From a33c720ce86ab05d85885e4a015a2dd36b3417ba Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 20 Oct 2011 19:46:57 +0100 Subject: [PATCH 194/333] docs/ipc: document i3 --get-socketpath --- docs/ipc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/ipc b/docs/ipc index 4093ffce..12523d12 100644 --- a/docs/ipc +++ b/docs/ipc @@ -1,7 +1,7 @@ IPC interface (interprocess communication) ========================================== Michael Stapelberg -March 2010 +October 2011 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 @@ -12,7 +12,7 @@ The method of choice for IPC in our case is a unix socket because it has very little overhead on both sides and is usually available without headaches in most languages. In the default configuration file, the ipc-socket gets created in +/tmp/i3-%u/ipc-socket.%p+ where +%u+ is your UNIX username and +%p+ is the -PID of i3. +PID of i3. You can get the socketpath from i3 by 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. @@ -24,7 +24,8 @@ snippet illustrates this in Perl: ------------------------------------------------------------- use IO::Socket::UNIX; -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); +chomp(my $path = qx(i3 --get-socketpath)); +my $sock = IO::Socket::UNIX->new(Peer => $path); ------------------------------------------------------------- == Sending messages to i3 From 2bda05d1b8b442943d278d011bcd4327542f5e4a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 20 Oct 2011 19:51:01 +0100 Subject: [PATCH 195/333] docs/ipc: fix example end --- docs/ipc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/ipc b/docs/ipc index 12523d12..76643573 100644 --- a/docs/ipc +++ b/docs/ipc @@ -423,7 +423,7 @@ JSON dump: } ] } - +------------------------ === GET_MARKS reply @@ -433,7 +433,6 @@ same mark, it will be represented multiple times in the reply (the array contents are not unique). If no window has a mark the response will be the empty array []. ------------------------- == Events From 093507fc381fd5bf6c5b851a75674c4f9fec532c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 20 Oct 2011 20:16:07 +0100 Subject: [PATCH 196/333] ipc: document the GET_BAR_CONFIG request/reply --- docs/ipc | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/docs/ipc b/docs/ipc index 76643573..db1ef681 100644 --- a/docs/ipc +++ b/docs/ipc @@ -64,6 +64,10 @@ GET_MARKS (5):: Gets a list of marks (identifiers for containers to easily jump to them later). The reply will be a JSON-encoded list of window marks (see reply section). +GET_BAR_CONFIG (6):: + Gets the configuration (as JSON map) of the workspace bar with the + given ID. If no ID is provided, an array with all configured bar IDs is + returned instead. So, a typical message could look like this: -------------------------------------------------- @@ -117,6 +121,8 @@ GET_TREE (4):: Reply to the GET_TREE message. GET_MARKS (5):: Reply to the GET_MARKS message. +GET_BAR_CONFIG (6):: + Reply to the GET_BAR_CONFIG message. === COMMAND reply @@ -434,6 +440,89 @@ contents are not unique). If no window has a mark the response will be the empty array []. +=== GET_BAR_CONFIG reply + +This can be used by third-party workspace bars (especially i3bar, but others +are free to implement compatible alternatives) to get the +bar+ block +configuration from i3. + +Depending on the input, the reply is either: + +empty input:: + An array of configured bar IDs +Bar ID:: + A JSON map containing the configuration for the specified bar. + +Each bar configuration has the following properties: + +id (string):: + The ID for this bar. Included in case you request multiple + configurations and want to differentiate the different replies. +mode (string):: + Either +dock+ (the bar sets the dock window type) or +hide+ (the bar + does not show unless a specific key is pressed). +position (string):: + Either +bottom+ or +top+ at the moment. +status_command (string):: + Command which will be run to generate a statusline. Each line on stdout + of this command will be displayed in the bar. At the moment, no + formatting is supported. +font (string):: + The font to use for text on the bar. +workspace_buttons (boolean):: + Display workspace buttons or not? Defaults to true. +verbose (boolean):: + Should the bar enable verbose output for debugging? Defaults to false. +colors (map):: + Contains key/value pairs of colors. Each value is a color code in hex, + formatted rrggbb (like used in HTML). + +The following colors can be configured at the moment: + +background:: + Background color of the bar. +statusline:: + Text color to be used for the statusline. +focused_workspace_text/focused_workspace_bg:: + Text color/background color for a workspace button when the workspace + has focus. +active_workspace_text/active_workspace_bg:: + Text color/background color for a workspace button when the workspace + is active (visible) on some output, but the focus is on another one. + You can only tell this apart from the focused workspace when you are + using multiple monitors. +inactive_workspace_text/inactive_workspace_bg:: + Text color/background color for a workspace button when the workspace + does not have focus and is not active (visible) on any output. This + will be the case for most workspaces. +urgent_workspace_text/urgent_workspace_bar:: + Text color/background color for workspaces which contain at least one + window with the urgency hint set. + + +*Example of configured bars:* +-------------- +["bar-bxuqzf"] +-------------- + +*Example of bar configuration:* +-------------- +{ + "id": "bar-bxuqzf", + "mode": "dock", + "position": "bottom", + "status_command": "i3status", + "font": "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1", + "workspace_buttons": true, + "verbose": false, + "colors": { + "background": "c0c0c0", + "statusline": "00ff00", + "focused_workspace_text": "ffffff", + "focused_workspace_bg": "000000" + } +} +-------------- == Events From bf408c9a08ceff81444bbeb252014c2e774fd46a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 20 Oct 2011 20:31:44 +0100 Subject: [PATCH 197/333] tests: depend on AnyEvent::I3 0.09 (for get_bar_config) --- testcases/Makefile.PL | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcases/Makefile.PL b/testcases/Makefile.PL index 60083667..11385f76 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.08', + 'AnyEvent::I3' => '0.09', 'X11::XCB' => '0.03', 'Test::Most' => 0, 'Test::Deep' => 0, From 12d866e4f66149d426658b5e11876e739105e256 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 20 Oct 2011 22:25:59 +0100 Subject: [PATCH 198/333] =?UTF-8?q?Don=E2=80=99t=20start=20i3-nagbar=20whe?= =?UTF-8?q?n=20using=20-C=20to=20validate=20the=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/i3.h | 1 + src/cfgparse.y | 5 ++++- src/main.c | 6 +++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/include/i3.h b/include/i3.h index 089dfcba..fca983ec 100644 --- a/include/i3.h +++ b/include/i3.h @@ -44,5 +44,6 @@ extern uint8_t root_depth; extern bool xcursor_supported, xkb_supported; extern xcb_window_t root; extern struct ev_loop *main_loop; +extern bool only_check_config; #endif diff --git a/src/cfgparse.y b/src/cfgparse.y index 2c796af1..c9e1eb98 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -266,7 +266,10 @@ static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) { * */ static void start_configerror_nagbar(const char *config_path) { - fprintf(stderr, "Would start i3-nagscreen now\n"); + if (only_check_config) + return; + + fprintf(stderr, "Starting i3-nagbar due to configuration errors\n"); configerror_pid = fork(); if (configerror_pid == -1) { warn("Could not fork()"); diff --git a/src/main.c b/src/main.c index 50ebaa34..1bf6862d 100644 --- a/src/main.c +++ b/src/main.c @@ -61,6 +61,11 @@ struct ws_assignments_head ws_assignments = TAILQ_HEAD_INITIALIZER(ws_assignment bool xcursor_supported = true; bool xkb_supported = true; +/* This will be set to true when -C is used so that functions can behave + * slightly differently. We don’t want i3-nagbar to be started when validating + * the config, for example. */ +bool only_check_config = false; + /* * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb. * See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop @@ -194,7 +199,6 @@ int main(int argc, char *argv[]) { bool autostart = true; char *layout_path = NULL; bool delete_layout_path = false; - bool only_check_config = false; bool force_xinerama = false; bool disable_signalhandler = false; static struct option long_options[] = { From caee0a0fdac257ea8e4394e26f453007a3e87db5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 20 Oct 2011 22:38:46 +0100 Subject: [PATCH 199/333] cfgparse: eliminate absolute states, use yy_push_state/yy_pop_state This fixes a problem with workspace assignments. I tested i3 -C with three user configs (Thanks SardemFF7, julien, xeen) and did not notice any problems. --- src/cfgparse.l | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/cfgparse.l b/src/cfgparse.l index 2278a81e..88e6ad2c 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -136,7 +136,7 @@ EOL (\r?\n) return STR; } [^\n]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return STR; } -[a-zA-Z0-9_-]+ { yylval.string = sstrdup(yytext); return OUTPUT; } +[a-zA-Z0-9_-]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return OUTPUT; } ^[ \t]*#[^\n]* { return TOKCOMMENT; } [0-9a-fA-F]+ { yylval.string = sstrdup(yytext); return HEX; } [ \t]*→[ \t]* { BEGIN(WANT_STRING); } @@ -147,8 +147,8 @@ mode { return TOKMODE; } bind { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; } bindcode { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; } bindsym { yy_push_state(BINDSYM_COND); yy_push_state(EAT_WHITESPACE); return TOKBINDSYM; } -floating_modifier { BEGIN(INITIAL); return TOKFLOATING_MODIFIER; } -workspace { BEGIN(INITIAL); return TOKWORKSPACE; } +floating_modifier { return TOKFLOATING_MODIFIER; } +workspace { return TOKWORKSPACE; } output { yy_push_state(OUTPUT_COND); yy_push_state(EAT_WHITESPACE); return TOKOUTPUT; } terminal { WS_STRING; return TOKTERMINAL; } font { WS_STRING; return TOKFONT; } @@ -234,7 +234,6 @@ title { yy_push_state(WANT_QSTRING); return TOK_TITLE; /* if ASSIGN_COND then */ if (yy_start_stack_ptr > 0) yy_pop_state(); - else BEGIN(INITIAL); /* yylval will be the string, but without quotes */ char *copy = sstrdup(yytext+1); copy[strlen(copy)-1] = '\0'; From d970b19b597066316f8bf93c1e3bf25befc9d2fc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 20 Oct 2011 22:55:24 +0100 Subject: [PATCH 200/333] i3bar: update manpage --- i3bar/doc/i3bar.man | 80 ++++++++++++++++----------------------------- 1 file changed, 29 insertions(+), 51 deletions(-) diff --git a/i3bar/doc/i3bar.man b/i3bar/doc/i3bar.man index d28f6249..dcf3022b 100644 --- a/i3bar/doc/i3bar.man +++ b/i3bar/doc/i3bar.man @@ -1,7 +1,7 @@ i3bar(1) ======== Axel Wagner -v0.7, July 2011 +v4.1, October 2011 == NAME @@ -9,81 +9,59 @@ i3bar - xcb-based status- and workspace-bar == SYNOPSIS -*i3bar* [*-s* 'sock_path'] [*-c* 'command'] [*-m*|*-d*['pos']] [*-f* 'font'] [*-V*] [*-h*] +*i3bar* [*-s* 'sock_path'] [*-b* 'bar_id'] [*-v*] [*-h*] + +== WARNING + +i3bar will automatically be invoked by i3 for every 'bar' configuration block. + +Starting it manually is usually not what you want to do. + +You have been warned! == OPTIONS *-s, --socket* 'sock_path':: -Specifies the 'socketpath', via which *i3bar* connects to *i3*(1). If *i3bar* can not connect to *i3*, it will exit. Defaults to '/tmp/i3-ipc.sock' +Overwrites the path to the i3 IPC socket. -*-c, --command* 'command':: -Execute '' to get 'stdin'. You can also simply pipe into 'stdin', but starting the coomand for itself, *i3bar* is able to send 'SIGCONT' and 'SIGSTOP', when combined with *-m* +*-b, --bar_id* 'bar_id':: +Specifies the bar ID for which to get the configuration from i3. -*-m, --hide*:: -Hide the bar, when 'mod4' is not pressed. With this, dockmode will not be set, and the bar is out of the way most of the time so you have more room. -If *-c* is specified, the childprocess is sent a 'SIGSTOP' on hiding and a 'SIGCONT' on unhiding of the bars. -This is the default behavior of i3bar. - -*-d*['pos']*, --dock*[*=*'pos']:: -Put i3bar in dockmode. This will reserve some space for it, so it does not overlap other clients. -You can specify either *bottom* (default) or *top* as 'pos'. - -*-f, --font* 'font':: -Specifies a 'X-core-font' to use. You can choose one with *xfontsel*(1). Defaults to '+++-misc-fixed-medium-r-semicondensed--12-110-75-75-c-60-iso10646-1+++'. - -*-V, --verbose*:: -Be (very) verbose with the debug-output. If not set, only errors are reported to 'stderr' +*-v, --version*:: +Display version number and exit. *-h, --help*:: Display a short help-message and exit == DESCRIPTION -*i3bar* is an xcb- and libev-based status- and ws-bar. It is best thought of as an replacement for the *i3-wsbar*(1) + *dzen2*(1)-combination. It creates a workspace-bar for every active output ("screen") and displays a piped in statusline rightaligned on every bar. +*i3bar* displays a bar at the bottom (or top) of your monitor(s) containing +workspace switching buttons and a statusline generated by i3status(1) or +similar. It is automatically invoked (and configured through) i3. -It does not sample any status-information itself, so you still need a program like *i3status*(1) or *conky*(1) for that. - -i3bar does not support any color or other markups, so stdin should be plain utf8, one line at a time. If you use *i3status*(1), you therefore should specify 'output_format = none' in the general section of its config file. - -Also, you should disable the internal workspace bar of *i3*(1), when using *i3bar* by specifying 'workspace_bar no' in your *i3*-configfile. - -== COLORS - -*i3bar* does not yet support formatting in the displayed statusline. However it does support setting colors for the bar, the workspace-buttons and the statusline. - -For now this happens with the following command-line-options: - -*--color-bar-fg, --color-bar-bg, --color-active-ws-fg, --color-active-ws-bg, --color-inactive-ws-fg, --color-inactive-ws-bg, --color-urgent-ws-bg, --color-urgent-ws-fg, --color-focus-ws-fg, --color-focus-ws-bg* - -For each specified option you need to give a HEX-colorcode. - -Be advised that this command-line-options are only temporary and are very likely to be removed, when we finally have a config-file. +i3bar does not support any color or other markups, so stdin should be plain +utf8, one line at a time. If you use *i3status*(1), you therefore should +specify 'output_format = none' in the general section of its config file. == ENVIRONMENT === I3SOCK -If no ipc-socket is specified on the commandline, this variable is used -to determine the path, at wich the unix domain socket is expected, on which -to connect to i3. +Used as a fallback for the i3 IPC socket path if neither the commandline +contains an argument nor the I3_SOCKET_PATH property is set on the X11 root +window. == EXAMPLES -To get a docked bar with some statusinformation, you use +Nothing to see here, move along. As stated above, you should not run i3bar manually. -*i3status | i3bar --dock* - -If you rather have it displayed at the top of the screen, you use - -*i3status | i3bar --dock=top* - -If you want it to hide when not needed, you should instead simply use - -*i3bar -c i3status* +Instead, see the i3 documentation, especially the User’s Guide. == SEE ALSO -+i3(1)+, +i3-wsbar(1)+, +dzen2(1)+, +i3status(1)+ ++i3status(1)+ or +conky(1)+ for programs generating a statusline. + ++dzen2(1)+ or +xmobar(1)+ for similar programs to i3bar. == AUTHORS From 0f3d31124dc0982ce647278b83c8f705ddb829c7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 20 Oct 2011 23:38:34 +0100 Subject: [PATCH 201/333] docs/userguide: document the bar configuration --- docs/userguide | 168 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/docs/userguide b/docs/userguide index e12d8b6e..aa258f6e 100644 --- a/docs/userguide +++ b/docs/userguide @@ -756,6 +756,174 @@ workspace_auto_back_and_forth workspace_auto_back_and_forth yes --------------------------------- +== Configuring i3bar + +The bar at the bottom of your monitor is drawn by a separate process called +i3bar. Having this part of "the i3 user interface" in a separate process has +several advantages: + +1. It is a modular approach. If you don’t need a workspace bar at all, or if + you prefer a different one (dzen2, xmobar, maybe even gnome-panel?), you can + just remove the i3bar configuration and start your favorite bar instead. +2. It follows the UNIX philosophy of "Make each program do one thing well". + While i3 manages your windows well, i3bar is good at displaying a bar on + each monitor (unless you configure it otherwise). +3. It leads to two separate, clean codebases. If you want to understand i3, you + don’t need to bother with the details of i3bar and vice versa. + +That said, i3bar is configured in the same configuration file as i3. This is +because it is tightly coupled with i3 (in contrary to i3lock or i3status which +are useful for people using other window managers). Therefore, it makes no +sense to use a different configuration place when we already have a good +configuration infrastructure in place. + +Configuring your workspace bar starts with opening a +bar+ block. You can have +multiple bar blocks to use different settings for different outputs (monitors): + +*Example*: +--------------------------- +bar { + status_command i3status +} +--------------------------- + +=== Statusline command + +i3bar can run a program and display every line of its +stdout+ output on the +right hand side of the bar. This is useful to display system information like +your current IP address, battery status or date/time. + +The specified command will be passed to +sh -c+, so you can use globbing and +have to have correct quoting etc. + +*Syntax*: +---------------------- +status_command command +---------------------- + +*Example*: +------------------------------------------------- +status_command i3status --config ~/.i3status.conf +------------------------------------------------- + +=== Display mode + +You can have i3bar either be visible permanently at one edge of the screen +(+dock+ mode) or make it show up when you press your modifier key (+hide+ +mode). + +The hide mode maximizes screen space that can be used for actual windows. Also, +i3bar sends the +SIGSTOP+ and +SIGCONT+ signals to the statusline process to +save battery power. + +The default is dock mode. + +*Syntax*: +---------------- +mode +---------------- + +*Example*: +---------------- +mode hide +---------------- + +=== Position + +This option determines in which edge of the screen i3bar should show up. + +The default is bottom. + +*Syntax*: +--------------------- +position +--------------------- + +*Example*: +--------------------- +position top +--------------------- + +=== Font + +Specifies the font (again, X core font, not Xft, just like in i3) to be used in +the bar. + +*Syntax*: +--------------------- +font +--------------------- + +*Example*: +-------------------------------------------------------------- +font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 +-------------------------------------------------------------- + +=== Workspace buttons + +Specifies whether workspace buttons should be shown or not. This is useful if +you want to display a statusline-only bar containing additional information. + +The default is to show workspace buttons. + +*Syntax*: +-------------------------- +workspace_buttons +-------------------------- + +*Example*: +-------------------- +workspace_buttons no +-------------------- + +=== Colors + +As with i3, colors are in HTML hex format (#rrggbb). The following colors can +be configured at the moment: + +background:: + Background color of the bar. +statusline:: + Text color to be used for the statusline. +focused_workspace_text/focused_workspace_bg:: + Text color/background color for a workspace button when the workspace + has focus. +active_workspace_text/active_workspace_bg:: + Text color/background color for a workspace button when the workspace + is active (visible) on some output, but the focus is on another one. + You can only tell this apart from the focused workspace when you are + using multiple monitors. +inactive_workspace_text/inactive_workspace_bg:: + Text color/background color for a workspace button when the workspace + does not have focus and is not active (visible) on any output. This + will be the case for most workspaces. +urgent_workspace_text/urgent_workspace_bar:: + Text color/background color for workspaces which contain at least one + window with the urgency hint set. + +*Syntax*: +---------------------------------------- +colors { + background + statusline + + colorclass +} +---------------------------------------- + +*Example*: +-------------------------------------- +colors { + background #000000 + statusline #ffffff + + focused_workspace #ffffff #285577 + active_workspace #888888 #222222 + inactive_workspace #888888 #222222 + urgent_workspace #ffffff #900000 +} +-------------------------------------- + == List of commands Commands are what you bind to specific keypresses. You can also issue commands From 2647f47ec5533e738b57d1c4f4aff27c34fc6e25 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 20 Oct 2011 23:38:55 +0100 Subject: [PATCH 202/333] bar config: make dock mode the default --- include/config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/config.h b/include/config.h index a4a274cf..bb092091 100644 --- a/include/config.h +++ b/include/config.h @@ -198,7 +198,7 @@ struct Barconfig { char *socket_path; /** Bar display mode (hide unless modifier is pressed or show in dock mode) */ - enum { M_HIDE = 0, M_DOCK = 1 } mode; + enum { M_DOCK = 0, M_HIDE = 1 } mode; /** Bar position (bottom by default). */ enum { P_BOTTOM = 0, P_TOP = 1 } position; From cf67966fa0924ff874f90c29e688811fb2ec725a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 21 Oct 2011 18:59:59 +0100 Subject: [PATCH 203/333] i3bar: document -b in --help (Thanks mxf) --- i3bar/src/main.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/i3bar/src/main.c b/i3bar/src/main.c index ea489941..ea84ca44 100644 --- a/i3bar/src/main.c +++ b/i3bar/src/main.c @@ -41,10 +41,17 @@ char *expand_path(char *path) { } void print_usage(char *elf_name) { - printf("Usage: %s [-s sock_path] [-h] [-v]\n", elf_name); + printf("Usage: %s [-b bar_id] [-s sock_path] [-h] [-v]\n", elf_name); + printf("\n"); + printf("-b \tBar ID for which to get the configuration\n"); printf("-s \tConnect to i3 via \n"); printf("-h\t\tDisplay this help-message and exit\n"); printf("-v\t\tDisplay version number and exit\n"); + printf("\n"); + printf(" PLEASE NOTE that i3bar will be automatically started by i3\n" + " as soon as there is a 'bar' configuration block in your\n" + " config file. You should never need to start it manually.\n"); + printf("\n"); } /* From bc679b0168c89fbfe71dcb6932288389649f4c5d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 21 Oct 2011 19:01:02 +0100 Subject: [PATCH 204/333] change default config to use 'bar' blocks (Thanks mxf) --- i3.config | 4 +++- i3.config.keycodes | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/i3.config b/i3.config index 7d7fce33..9a040585 100644 --- a/i3.config +++ b/i3.config @@ -147,7 +147,9 @@ bindsym Mod1+r mode "resize" # Start i3bar to display a workspace bar (plus the system information i3status # finds out, if available) -exec i3status | i3bar -d +bar { + status_line i3status +} ####################################################################### # automatically start i3-config-wizard to offer the user to create a diff --git a/i3.config.keycodes b/i3.config.keycodes index 0f1112db..89811dbb 100644 --- a/i3.config.keycodes +++ b/i3.config.keycodes @@ -148,4 +148,6 @@ bindcode $mod+27 mode "resize" # Start i3bar to display a workspace bar (plus the system information i3status # finds out, if available) -exec i3status | i3bar -d +bar { + status_line i3status +} From 0298a32e37cc1b1ef0d92fd5c6153dddb60ddec6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 21 Oct 2011 19:03:54 +0100 Subject: [PATCH 205/333] i3bar: makefile: prefix messages with [i3bar] --- i3bar/Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/i3bar/Makefile b/i3bar/Makefile index 2a05b146..deb3ffbf 100644 --- a/i3bar/Makefile +++ b/i3bar/Makefile @@ -11,7 +11,7 @@ CPPFLAGS += -I$(TOPDIR)/include all: i3bar doc i3bar: libi3/libi3.a ${FILES} - echo "LINK" + echo "[i3bar] LINK" $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS) libi3/%.a: @@ -19,15 +19,15 @@ libi3/%.a: doc: echo "" - echo "SUBDIR doc" + echo "[i3bar] SUBDIR doc" $(MAKE) -C doc src/%.o: src/%.c ${HEADERS} - echo "CC $<" + echo "[i3bar] CC $<" $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< install: all - echo "INSTALL" + echo "[i3bar] INSTALL" $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin $(INSTALL) -m 0755 i3bar $(DESTDIR)$(PREFIX)/bin From c65d13ff9f58f557569e3e2bdadcf392be8a1a90 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 21 Oct 2011 19:06:53 +0100 Subject: [PATCH 206/333] i3bar: makefile: fix dependency on libi3 --- i3bar/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i3bar/Makefile b/i3bar/Makefile index deb3ffbf..753ee4d6 100644 --- a/i3bar/Makefile +++ b/i3bar/Makefile @@ -10,7 +10,7 @@ CPPFLAGS += -I$(TOPDIR)/include all: i3bar doc -i3bar: libi3/libi3.a ${FILES} +i3bar: $(TOPDIR)/libi3/libi3.a ${FILES} echo "[i3bar] LINK" $(CC) $(LDFLAGS) -o $@ $(filter-out libi3/libi3.a,$^) $(LIBS) From d71db710ddc59e9153ba655fb3af42e4b4fb8e29 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 21 Oct 2011 19:30:46 +0100 Subject: [PATCH 207/333] i3bar: use safewrappers from libi3 --- i3bar/include/common.h | 1 + i3bar/src/child.c | 8 ++++---- i3bar/src/config.c | 2 +- i3bar/src/ipc.c | 27 ++++----------------------- i3bar/src/main.c | 18 ++++-------------- i3bar/src/outputs.c | 14 +++++++------- i3bar/src/ucs2_to_utf8.c | 10 ++++------ i3bar/src/workspaces.c | 12 ++++-------- i3bar/src/xcb.c | 17 ++++++++--------- 9 files changed, 37 insertions(+), 72 deletions(-) diff --git a/i3bar/include/common.h b/i3bar/include/common.h index 74bd2152..4b0d4485 100644 --- a/i3bar/include/common.h +++ b/i3bar/include/common.h @@ -34,5 +34,6 @@ struct rect_t { #include "xcb.h" #include "ucs2_to_utf8.h" #include "config.h" +#include "libi3.h" #endif diff --git a/i3bar/src/child.c b/i3bar/src/child.c index ff1bc8bb..9ab6d23f 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -62,7 +62,7 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) { int n = 0; int rec = 0; int buffer_len = STDIN_CHUNK_SIZE; - char *buffer = malloc(buffer_len); + char *buffer = smalloc(buffer_len); buffer[0] = '\0'; while(1) { n = read(fd, buffer + rec, buffer_len - rec); @@ -91,7 +91,7 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) { if (rec == buffer_len) { buffer_len += STDIN_CHUNK_SIZE; - buffer = realloc(buffer, buffer_len); + buffer = srealloc(buffer, buffer_len); } } if (*buffer == '\0') { @@ -169,12 +169,12 @@ void start_child(char *command) { /* We set O_NONBLOCK because blocking is evil in event-driven software */ fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); - stdin_io = malloc(sizeof(ev_io)); + stdin_io = smalloc(sizeof(ev_io)); ev_io_init(stdin_io, &stdin_io_cb, STDIN_FILENO, EV_READ); ev_io_start(main_loop, stdin_io); /* We must cleanup, if the child unexpectedly terminates */ - child_sig = malloc(sizeof(ev_child)); + child_sig = smalloc(sizeof(ev_child)); ev_child_init(child_sig, &child_sig_cb, child_pid, 0); ev_child_start(main_loop, child_sig); diff --git a/i3bar/src/config.c b/i3bar/src/config.c index 23c5e122..ad99fb9a 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -35,7 +35,7 @@ static int config_map_key_cb(void *params_, const unsigned char *keyVal, unsigne #endif FREE(cur_key); - cur_key = malloc(sizeof(unsigned char) * (keyLen + 1)); + cur_key = smalloc(sizeof(unsigned char) * (keyLen + 1)); strncpy(cur_key, (const char*) keyVal, keyLen); cur_key[keyLen] = '\0'; diff --git a/i3bar/src/ipc.c b/i3bar/src/ipc.c index db60a362..cfbc404e 100644 --- a/i3bar/src/ipc.c +++ b/i3bar/src/ipc.c @@ -150,11 +150,7 @@ void got_data(struct ev_loop *loop, ev_io *watcher, int events) { /* First we only read the header, because we know its length */ uint32_t header_len = strlen(I3_IPC_MAGIC) + sizeof(uint32_t)*2; - char *header = malloc(header_len); - if (header == NULL) { - ELOG("Could not allocate memory: %s\n", strerror(errno)); - exit(EXIT_FAILURE); - } + char *header = smalloc(header_len); /* We first parse the fixed-length IPC-header, to know, how much data * we have to expect */ @@ -191,13 +187,7 @@ void got_data(struct ev_loop *loop, ev_io *watcher, int events) { /* Now that we know, what to expect, we can start read()ing the rest * of the message */ - char *buffer = malloc(size + 1); - if (buffer == NULL) { - /* EOF received. Since i3 will restart i3bar instances as appropriate, - * we exit here. */ - DLOG("EOF received, exiting...\n"); - exit(EXIT_SUCCESS); - } + char *buffer = smalloc(size + 1); rec = 0; while (rec < size) { @@ -243,12 +233,7 @@ int i3_send_msg(uint32_t type, const char *payload) { /* TODO: I'm not entirely sure if this buffer really has to contain more * than the pure header (why not just write() the payload from *payload?), * but we leave it for now */ - char *buffer = malloc(to_write); - if (buffer == NULL) { - ELOG("Could not allocate memory: %s\n", strerror(errno)); - exit(EXIT_FAILURE); - } - + char *buffer = smalloc(to_write); char *walk = buffer; strncpy(buffer, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)); @@ -301,11 +286,7 @@ int init_connection(const char *socket_path) { exit(EXIT_FAILURE); } - i3_connection = malloc(sizeof(ev_io)); - if (i3_connection == NULL) { - ELOG("malloc() failed: %s\n", strerror(errno)); - exit(EXIT_FAILURE); - } + i3_connection = smalloc(sizeof(ev_io)); ev_io_init(i3_connection, &got_data, sockfd, EV_READ); ev_io_start(main_loop, i3_connection); return 1; diff --git a/i3bar/src/main.c b/i3bar/src/main.c index ea84ca44..f018f2ab 100644 --- a/i3bar/src/main.c +++ b/i3bar/src/main.c @@ -19,7 +19,6 @@ #include #include "common.h" -#include "libi3.h" /* * Glob path, i.e. expand ~ @@ -31,11 +30,7 @@ char *expand_path(char *path) { ELOG("glob() failed\n"); exit(EXIT_FAILURE); } - char *result = strdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path); - if (result == NULL) { - ELOG("malloc() failed: %s\n", strerror(errno)); - exit(EXIT_FAILURE); - } + char *result = sstrdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path); globfree(&globbuf); return result; } @@ -142,14 +137,9 @@ int main(int argc, char **argv) { /* We listen to SIGTERM/QUIT/INT and try to exit cleanly, by stopping the main-loop. * We only need those watchers on the stack, so putting them on the stack saves us * some calls to free() */ - ev_signal *sig_term = malloc(sizeof(ev_signal)); - ev_signal *sig_int = malloc(sizeof(ev_signal)); - ev_signal *sig_hup = malloc(sizeof(ev_signal)); - - if (sig_term == NULL || sig_int == NULL || sig_hup == NULL) { - ELOG("malloc() failed: %s\n", strerror(errno)); - exit(EXIT_FAILURE); - } + ev_signal *sig_term = smalloc(sizeof(ev_signal)); + ev_signal *sig_int = smalloc(sizeof(ev_signal)); + ev_signal *sig_hup = smalloc(sizeof(ev_signal)); ev_signal_init(sig_term, &sig_cb, SIGTERM); ev_signal_init(sig_int, &sig_cb, SIGINT); diff --git a/i3bar/src/outputs.c b/i3bar/src/outputs.c index 11f7dc2b..98939fb4 100644 --- a/i3bar/src/outputs.c +++ b/i3bar/src/outputs.c @@ -115,7 +115,7 @@ static int outputs_string_cb(void *params_, const unsigned char *val, unsigned i struct outputs_json_params *params = (struct outputs_json_params*) params_; if (!strcmp(params->cur_key, "current_workspace")) { - char *copy = malloc(sizeof(const unsigned char) * (len + 1)); + char *copy = smalloc(sizeof(const unsigned char) * (len + 1)); strncpy(copy, (const char*) val, len); copy[len] = '\0'; @@ -134,7 +134,7 @@ static int outputs_string_cb(void *params_, const unsigned char *val, unsigned i return 0; } - char *name = malloc(sizeof(const unsigned char) * (len + 1)); + char *name = smalloc(sizeof(const unsigned char) * (len + 1)); strncpy(name, (const char*) val, len); name[len] = '\0'; @@ -154,16 +154,16 @@ static int outputs_start_map_cb(void *params_) { i3_output *new_output = NULL; if (params->cur_key == NULL) { - new_output = malloc(sizeof(i3_output)); + new_output = smalloc(sizeof(i3_output)); new_output->name = NULL; new_output->ws = 0, memset(&new_output->rect, 0, sizeof(rect)); new_output->bar = XCB_NONE; - new_output->workspaces = malloc(sizeof(struct ws_head)); + new_output->workspaces = smalloc(sizeof(struct ws_head)); TAILQ_INIT(new_output->workspaces); - new_output->trayclients = malloc(sizeof(struct tc_head)); + new_output->trayclients = smalloc(sizeof(struct tc_head)); TAILQ_INIT(new_output->trayclients); params->outputs_walk = new_output; @@ -208,7 +208,7 @@ static int outputs_map_key_cb(void *params_, const unsigned char *keyVal, unsign struct outputs_json_params *params = (struct outputs_json_params*) params_; FREE(params->cur_key); - params->cur_key = malloc(sizeof(unsigned char) * (keyLen + 1)); + params->cur_key = smalloc(sizeof(unsigned char) * (keyLen + 1)); strncpy(params->cur_key, (const char*) keyVal, keyLen); params->cur_key[keyLen] = '\0'; @@ -235,7 +235,7 @@ yajl_callbacks outputs_callbacks = { * */ void init_outputs() { - outputs = malloc(sizeof(struct outputs_head)); + outputs = smalloc(sizeof(struct outputs_head)); SLIST_INIT(outputs); } diff --git a/i3bar/src/ucs2_to_utf8.c b/i3bar/src/ucs2_to_utf8.c index 68984227..c07f63fd 100644 --- a/i3bar/src/ucs2_to_utf8.c +++ b/i3bar/src/ucs2_to_utf8.c @@ -14,6 +14,8 @@ #include #include +#include "libi3.h" + static iconv_t conversion_descriptor = 0; static iconv_t conversion_descriptor2 = 0; @@ -27,9 +29,7 @@ char *convert_ucs_to_utf8(char *input) { /* UTF-8 may consume up to 4 byte */ int buffer_size = 8; - char *buffer = calloc(buffer_size, 1); - if (buffer == NULL) - err(EXIT_FAILURE, "malloc() failed\n"); + char *buffer = scalloc(buffer_size); size_t output_size = buffer_size; /* We need to use an additional pointer, because iconv() modifies it */ char *output = buffer; @@ -68,9 +68,7 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen) { /* UCS-2 consumes exactly two bytes for each glyph */ int buffer_size = input_size * 2; - char *buffer = malloc(buffer_size); - if (buffer == NULL) - err(EXIT_FAILURE, "malloc() failed\n"); + char *buffer = smalloc(buffer_size); size_t output_size = buffer_size; /* We need to use an additional pointer, because iconv() modifies it */ char *output = buffer; diff --git a/i3bar/src/workspaces.c b/i3bar/src/workspaces.c index 8099cd78..c2bb886c 100644 --- a/i3bar/src/workspaces.c +++ b/i3bar/src/workspaces.c @@ -117,7 +117,7 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, unsigne if (!strcmp(params->cur_key, "name")) { /* Save the name */ - params->workspaces_walk->name = malloc(sizeof(const unsigned char) * (len + 1)); + params->workspaces_walk->name = smalloc(sizeof(const unsigned char) * (len + 1)); strncpy(params->workspaces_walk->name, (const char*) val, len); params->workspaces_walk->name[len] = '\0'; @@ -141,7 +141,7 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, unsigne if (!strcmp(params->cur_key, "output")) { /* We add the ws to the TAILQ of the output, it belongs to */ - output_name = malloc(sizeof(const unsigned char) * (len + 1)); + output_name = smalloc(sizeof(const unsigned char) * (len + 1)); strncpy(output_name, (const char*) val, len); output_name[len] = '\0'; params->workspaces_walk->output = get_output_by_name(output_name); @@ -167,7 +167,7 @@ static int workspaces_start_map_cb(void *params_) { i3_ws *new_workspace = NULL; if (params->cur_key == NULL) { - new_workspace = malloc(sizeof(i3_ws)); + new_workspace = smalloc(sizeof(i3_ws)); new_workspace->num = -1; new_workspace->name = NULL; new_workspace->visible = 0; @@ -197,11 +197,7 @@ static int workspaces_map_key_cb(void *params_, const unsigned char *keyVal, uns struct workspaces_json_params *params = (struct workspaces_json_params*) params_; FREE(params->cur_key); - params->cur_key = malloc(sizeof(unsigned char) * (keyLen + 1)); - if (params->cur_key == NULL) { - ELOG("Could not allocate memory: %s\n", strerror(errno)); - exit(EXIT_FAILURE); - } + params->cur_key = smalloc(sizeof(unsigned char) * (keyLen + 1)); strncpy(params->cur_key, (const char*) keyVal, keyLen); params->cur_key[keyLen] = '\0'; diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 471209c7..a3dba91e 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -51,8 +51,7 @@ char *strndup(const char *str, size_t n) { for (len = 0; len < n && str[len]; len++) continue; - if ((copy = malloc(len + 1)) == NULL) - return (NULL); + copy = smalloc(len + 1); memcpy(copy, str, len); copy[len] = '\0'; return (copy); @@ -516,7 +515,7 @@ static void handle_client_message(xcb_client_message_event_t* event) { values); /* send the XEMBED_EMBEDDED_NOTIFY message */ - void *event = calloc(32, 1); + void *event = scalloc(32); xcb_client_message_event_t *ev = event; ev->response_type = XCB_CLIENT_MESSAGE; ev->window = client; @@ -539,7 +538,7 @@ static void handle_client_message(xcb_client_message_event_t* event) { } else { DLOG("Not mapping dock client yet\n"); } - trayclient *tc = malloc(sizeof(trayclient)); + trayclient *tc = smalloc(sizeof(trayclient)); tc->win = client; tc->mapped = map_it; tc->xe_version = xe_version; @@ -841,9 +840,9 @@ char *init_xcb_early() { /* The various Watchers to communicate with xcb */ - xcb_io = malloc(sizeof(ev_io)); - xcb_prep = malloc(sizeof(ev_prepare)); - xcb_chk = malloc(sizeof(ev_check)); + xcb_io = smalloc(sizeof(ev_io)); + xcb_prep = smalloc(sizeof(ev_prepare)); + xcb_chk = smalloc(sizeof(ev_check)); ev_io_init(xcb_io, &xcb_io_cb, xcb_get_file_descriptor(xcb_connection), EV_READ); ev_prepare_init(xcb_prep, &xcb_prep_cb); @@ -956,7 +955,7 @@ void init_xcb_late(char *fontname) { exit(EXIT_FAILURE); } - xkb_io = malloc(sizeof(ev_io)); + xkb_io = smalloc(sizeof(ev_io)); ev_io_init(xkb_io, &xkb_io_cb, ConnectionNumber(xkb_dpy), EV_READ); ev_io_start(main_loop, xkb_io); XFlush(xkb_dpy); @@ -1053,7 +1052,7 @@ void init_tray() { } /* Inform clients waiting for a new _NET_SYSTEM_TRAY that we are here */ - void *event = calloc(32, 1); + void *event = scalloc(32); xcb_client_message_event_t *ev = event; ev->response_type = XCB_CLIENT_MESSAGE; ev->window = xcb_root; From 8a24be955541ff92fd5be2f90c7790009d362ea9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 21 Oct 2011 19:47:56 +0100 Subject: [PATCH 208/333] i3bar: implement the tray_output option --- i3bar/include/config.h | 1 + i3bar/src/config.c | 5 +++-- i3bar/src/xcb.c | 14 +++++++++++--- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/i3bar/include/config.h b/i3bar/include/config.h index 19b246dc..5997b7f1 100644 --- a/i3bar/include/config.h +++ b/i3bar/include/config.h @@ -18,6 +18,7 @@ typedef struct config_t { char *bar_id; char *command; char *fontname; + char *tray_output; } config_t; config_t config; diff --git a/i3bar/src/config.c b/i3bar/src/config.c index ad99fb9a..644c623e 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -90,8 +90,9 @@ static int config_string_cb(void *params_, const unsigned char *val, unsigned in } if (!strcmp(cur_key, "tray_output")) { - printf("tray_output %.*s\n", len, val); - /* XXX: these are not implemented yet */ + DLOG("tray_output %.*s\n", len, val); + FREE(config.tray_output); + asprintf(&config.tray_output, "%.*s", len, val); return 1; } diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index a3dba91e..4e347f57 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -489,6 +489,9 @@ static void handle_client_message(xcb_client_message_event_t* event) { SLIST_FOREACH(walk, outputs, slist) { if (!walk->active) continue; + if (config.tray_output && + strcasecmp(walk->name, config.tray_output) != 0) + continue; DLOG("using output %s\n", walk->name); output = walk; } @@ -988,6 +991,7 @@ void init_xcb_late(char *fontname) { * */ void init_tray() { + DLOG("Initializing system tray functionality\n"); /* request the tray manager atom for the X11 display we are running on */ char atomname[strlen("_NET_SYSTEM_TRAY_S") + 11]; snprintf(atomname, strlen("_NET_SYSTEM_TRAY_S") + 11, "_NET_SYSTEM_TRAY_S%d", screen); @@ -1194,6 +1198,7 @@ void realloc_sl_buffer() { void reconfig_windows() { uint32_t mask; uint32_t values[5]; + static bool tray_configured = false; i3_output *walk; SLIST_FOREACH(walk, outputs, slist) { @@ -1207,9 +1212,6 @@ void reconfig_windows() { if (walk->bar == XCB_NONE) { DLOG("Creating Window for output %s\n", walk->name); - /* TODO: only call init_tray() if the tray is configured for this output */ - init_tray(); - walk->bar = xcb_generate_id(xcb_connection); walk->buffer = xcb_generate_id(xcb_connection); mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; @@ -1353,6 +1355,12 @@ void reconfig_windows() { (!config.hide_on_modifier && xcb_request_failed(map_cookie, "Could not map window"))) { exit(EXIT_FAILURE); } + + if (!tray_configured && + strcasecmp("none", config.tray_output) != 0) { + init_tray(); + tray_configured = true; + } } else { /* We already have a bar, so we just reconfigure it */ mask = XCB_CONFIG_WINDOW_X | From 0f2642a9c9b68b06b7c8a758fee00f09ff2fc82e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 21 Oct 2011 19:50:40 +0100 Subject: [PATCH 209/333] docs/userguide: document the tray_output bar option --- docs/userguide | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/userguide b/docs/userguide index aa258f6e..3c0c6856 100644 --- a/docs/userguide +++ b/docs/userguide @@ -844,6 +844,28 @@ position position top --------------------- +=== Tray output + +i3bar by default provides a system tray area where programs such as +NetworkManager, VLC, Pidgin, etc. can place little icons. + +You can configure on which output (monitor) the icons should be displayed or +you can turn off the functionality entirely. + +*Syntax*: +------------------------- +tray_output +------------------------- + +*Example*: +------------------------- +# disable system tray +tray_output none + +# show tray icons on the big monitor +tray_output HDMI2 +------------------------- + === Font Specifies the font (again, X core font, not Xft, just like in i3) to be used in From a3b7ba15ed5f43c28f970666261fd02aaeefece0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 21 Oct 2011 19:59:25 +0100 Subject: [PATCH 210/333] i3bar: also use the position option when in 'hide' mode --- i3bar/include/config.h | 10 +++++----- i3bar/src/config.c | 2 +- i3bar/src/xcb.c | 12 +++++++----- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/i3bar/include/config.h b/i3bar/include/config.h index 5997b7f1..b914954c 100644 --- a/i3bar/include/config.h +++ b/i3bar/include/config.h @@ -4,14 +4,14 @@ #include "common.h" typedef enum { - DOCKPOS_NONE = 0, - DOCKPOS_TOP, - DOCKPOS_BOT -} dockpos_t; + POS_NONE = 0, + POS_TOP, + POS_BOT +} position_t; typedef struct config_t { int hide_on_modifier; - dockpos_t dockpos; + position_t position; int verbose; struct xcb_color_strings_t colors; int disable_ws; diff --git a/i3bar/src/config.c b/i3bar/src/config.c index 644c623e..caf13029 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -63,7 +63,7 @@ static int config_string_cb(void *params_, const unsigned char *val, unsigned in if (!strcmp(cur_key, "position")) { DLOG("position = %.*s\n", len, val); - config.dockpos = (len == 3 && !strncmp((const char*)val, "top", strlen("top")) ? DOCKPOS_TOP : DOCKPOS_BOT); + config.position = (len == 3 && !strncmp((const char*)val, "top", strlen("top")) ? POS_TOP : POS_BOT); return 1; } diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 4e347f57..a9ecc004 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -281,7 +281,9 @@ void unhide_bars() { XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_STACK_MODE; values[0] = walk->rect.x; - values[1] = walk->rect.y + walk->rect.h - font_height - 6; + if (config.position == POS_TOP) + values[1] = walk->rect.y; + else values[1] = walk->rect.y + walk->rect.h - font_height - 6; values[2] = walk->rect.w; values[3] = font_height + 6; values[4] = XCB_STACK_MODE_ABOVE; @@ -1305,15 +1307,15 @@ void reconfig_windows() { uint32_t bottom_start_x; uint32_t bottom_end_x; } __attribute__((__packed__)) strut_partial = {0,}; - switch (config.dockpos) { - case DOCKPOS_NONE: + switch (config.position) { + case POS_NONE: break; - case DOCKPOS_TOP: + case POS_TOP: strut_partial.top = font_height + 6; strut_partial.top_start_x = walk->rect.x; strut_partial.top_end_x = walk->rect.x + walk->rect.w; break; - case DOCKPOS_BOT: + case POS_BOT: strut_partial.bottom = font_height + 6; strut_partial.bottom_start_x = walk->rect.x; strut_partial.bottom_end_x = walk->rect.x + walk->rect.w; From 6de1590e5902bb44d9f8a84eba52139ac8a308a1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 21 Oct 2011 20:04:55 +0100 Subject: [PATCH 211/333] i3bar: spit out an error on wrong bar id --- i3bar/src/config.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/i3bar/src/config.c b/i3bar/src/config.c index caf13029..179bc464 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -42,6 +42,22 @@ static int config_map_key_cb(void *params_, const unsigned char *keyVal, unsigne return 1; } +/* + * Parse a null-value (current_workspace) + * + */ +static int config_null_cb(void *params_) { + if (!strcmp(cur_key, "id")) { + /* If 'id' is NULL, the bar config was not found. Error out. */ + ELOG("No such bar config. Use 'i3-msg -t get_bar_config' to get the available configs.\n"); + ELOG("Are you starting i3bar by hand? You should not:\n"); + ELOG("Configure a 'bar' block in your i3 config and i3 will launch i3bar automatically.\n"); + exit(EXIT_FAILURE); + } + + return 1; +} + /* * Parse a string * @@ -143,7 +159,7 @@ static int config_boolean_cb(void *params_, int val) { /* A datastructure to pass all these callbacks to yajl */ static yajl_callbacks outputs_callbacks = { - NULL, + &config_null_cb, &config_boolean_cb, NULL, NULL, From 15f021b4fc8097a2870e56008db423dac9f430a1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 21 Oct 2011 22:17:41 +0100 Subject: [PATCH 212/333] i3bar: Implement the output option --- i3bar/include/config.h | 2 ++ i3bar/src/config.c | 7 +++++-- i3bar/src/outputs.c | 35 +++++++++++++++++++++++++++++++++-- i3bar/src/workspaces.c | 11 +++++++---- 4 files changed, 47 insertions(+), 8 deletions(-) diff --git a/i3bar/include/config.h b/i3bar/include/config.h index b914954c..c5fc218c 100644 --- a/i3bar/include/config.h +++ b/i3bar/include/config.h @@ -19,6 +19,8 @@ typedef struct config_t { char *command; char *fontname; char *tray_output; + int num_outputs; + char **outputs; } config_t; config_t config; diff --git a/i3bar/src/config.c b/i3bar/src/config.c index 179bc464..9e4552ed 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -100,8 +100,11 @@ static int config_string_cb(void *params_, const unsigned char *val, unsigned in } if (!strcmp(cur_key, "outputs")) { - printf("+output %.*s\n", len, val); - /* XXX: these are not implemented yet */ + DLOG("+output %.*s\n", len, val); + int new_num_outputs = config.num_outputs + 1; + config.outputs = srealloc(config.outputs, sizeof(char*) * new_num_outputs); + asprintf(&config.outputs[config.num_outputs], "%.*s", len, val); + config.num_outputs = new_num_outputs; return 1; } diff --git a/i3bar/src/outputs.c b/i3bar/src/outputs.c index 98939fb4..6278974f 100644 --- a/i3bar/src/outputs.c +++ b/i3bar/src/outputs.c @@ -26,7 +26,7 @@ struct outputs_json_params { i3_output *outputs_walk; char *cur_key; char *json; - bool init; + bool in_rect; }; /* @@ -171,6 +171,10 @@ static int outputs_start_map_cb(void *params_) { return 1; } + if (!strcmp(params->cur_key, "rect")) { + params->in_rect = true; + } + return 1; } @@ -180,7 +184,33 @@ static int outputs_start_map_cb(void *params_) { */ static int outputs_end_map_cb(void *params_) { struct outputs_json_params *params = (struct outputs_json_params*) params_; - /* FIXME: What is at the end of a rect? */ + if (params->in_rect) { + params->in_rect = false; + /* Ignore the end of a rect */ + return 1; + } + + /* See if we actually handle that output */ + if (config.num_outputs > 0) { + bool handle_output = false; + for (int c = 0; c < config.num_outputs; c++) { + if (strcasecmp(params->outputs_walk->name, config.outputs[c]) != 0) + continue; + + handle_output = true; + break; + } + if (!handle_output) { + DLOG("Ignoring output \"%s\", not configured to handle it.\n", + params->outputs_walk->name); + FREE(params->outputs_walk->name); + FREE(params->outputs_walk->workspaces); + FREE(params->outputs_walk->trayclients); + FREE(params->outputs_walk); + FREE(params->cur_key); + return 1; + } + } i3_output *target = get_output_by_name(params->outputs_walk->name); @@ -249,6 +279,7 @@ void parse_outputs_json(char *json) { params.outputs_walk = NULL; params.cur_key = NULL; params.json = json; + params.in_rect = false; yajl_handle handle; yajl_status state; diff --git a/i3bar/src/workspaces.c b/i3bar/src/workspaces.c index c2bb886c..bb00f0ba 100644 --- a/i3bar/src/workspaces.c +++ b/i3bar/src/workspaces.c @@ -144,11 +144,14 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, unsigne output_name = smalloc(sizeof(const unsigned char) * (len + 1)); strncpy(output_name, (const char*) val, len); output_name[len] = '\0'; - params->workspaces_walk->output = get_output_by_name(output_name); + i3_output *target = get_output_by_name(output_name); + if (target) { + params->workspaces_walk->output = target; - TAILQ_INSERT_TAIL(params->workspaces_walk->output->workspaces, - params->workspaces_walk, - tailq); + TAILQ_INSERT_TAIL(params->workspaces_walk->output->workspaces, + params->workspaces_walk, + tailq); + } FREE(output_name); return 1; From 202b216c1408db58a7e6916112c6bba30f8dcc6c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 21 Oct 2011 22:22:02 +0100 Subject: [PATCH 213/333] docs/userguide: document the output bar option --- docs/userguide | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/docs/userguide b/docs/userguide index 3c0c6856..135c6bae 100644 --- a/docs/userguide +++ b/docs/userguide @@ -547,7 +547,7 @@ exec_always command *Examples*: -------------------------------- -exec i3status | i3bar -d +exec chromium exec_always ~/my_script.sh -------------------------------- @@ -844,6 +844,36 @@ position position top --------------------- +=== Output(s) + +You can restrict i3bar to one or more outputs (monitors). The default is to +handle all outputs. Restricting the outputs is useful for using different +options for different outputs by using multiple 'bar' blocks. + +*Syntax*: +--------------- +output +--------------- + +*Example*: +------------------------------- +# big monitor: everything +bar { + output HDMI2 + status_command i3status +} + +# laptop monitor: bright colors and i3status with less modules. +bar { + output LVDS1 + status_command i3status --config ~/.i3status-small.conf + colors { + background #000000 + statusline #ffffff + } +} +------------------------------- + === Tray output i3bar by default provides a system tray area where programs such as From 3baeeb1834e95de9dcf77c82fd3eedd58a569c0f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 21 Oct 2011 22:58:53 +0100 Subject: [PATCH 214/333] Bugfix: properly transition in and out of the COLOR_COND state (Thanks fernandotcl) --- src/cfgparse.l | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/cfgparse.l b/src/cfgparse.l index 88e6ad2c..50fbc6ca 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -138,7 +138,7 @@ EOL (\r?\n) [^\n]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return STR; } [a-zA-Z0-9_-]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return OUTPUT; } ^[ \t]*#[^\n]* { return TOKCOMMENT; } -[0-9a-fA-F]+ { yylval.string = sstrdup(yytext); return HEX; } +[0-9a-fA-F]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return HEX; } [ \t]*→[ \t]* { BEGIN(WANT_STRING); } [ \t]+ { BEGIN(WANT_STRING); } [0-9]+ { yylval.number = atoi(yytext); return NUMBER; } @@ -196,14 +196,14 @@ cols { /* yylval.number = STACK_LIMIT_COLS; */return rows { /* yylval.number = STACK_LIMIT_ROWS; */return TOKSTACKLIMIT; } exec { WS_STRING; return TOKEXEC; } exec_always { WS_STRING; return TOKEXEC_ALWAYS; } -client.background { BEGIN(COLOR_COND); yylval.single_color = &config.client.background; return TOKSINGLECOLOR; } -client.focused { BEGIN(COLOR_COND); yylval.color = &config.client.focused; return TOKCOLOR; } -client.focused_inactive { BEGIN(COLOR_COND); yylval.color = &config.client.focused_inactive; return TOKCOLOR; } -client.unfocused { BEGIN(COLOR_COND); yylval.color = &config.client.unfocused; return TOKCOLOR; } -client.urgent { BEGIN(COLOR_COND); yylval.color = &config.client.urgent; return TOKCOLOR; } -bar.focused { BEGIN(COLOR_COND); yylval.color = &config.bar.focused; return TOKCOLOR; } -bar.unfocused { BEGIN(COLOR_COND); yylval.color = &config.bar.unfocused; return TOKCOLOR; } -bar.urgent { BEGIN(COLOR_COND); yylval.color = &config.bar.urgent; return TOKCOLOR; } +client.background { yy_push_state(COLOR_COND); yylval.single_color = &config.client.background; return TOKSINGLECOLOR; } +client.focused { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.focused; return TOKCOLOR; } +client.focused_inactive { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.focused_inactive; return TOKCOLOR; } +client.unfocused { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.unfocused; return TOKCOLOR; } +client.urgent { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.urgent; return TOKCOLOR; } +bar.focused { yy_push_state(COLOR_COND); yylval.color = &config.bar.focused; return TOKCOLOR; } +bar.unfocused { yy_push_state(COLOR_COND); yylval.color = &config.bar.unfocused; return TOKCOLOR; } +bar.urgent { yy_push_state(COLOR_COND); yylval.color = &config.bar.urgent; return TOKCOLOR; } Mod1 { yylval.number = BIND_MOD1; return MODIFIER; } Mod2 { yylval.number = BIND_MOD2; return MODIFIER; } Mod3 { yylval.number = BIND_MOD3; return MODIFIER; } @@ -227,8 +227,8 @@ title { yy_push_state(WANT_QSTRING); return TOK_TITLE; context->line_number++; yy_push_state(BUFFER_LINE); } -[ \t]+ { BEGIN(WANT_STRING); } -[ \t]+ { BEGIN(WANT_STRING); } +[ \t]+ { yy_pop_state(); yy_push_state(WANT_STRING); } +[ \t]+ { yy_pop_state(); yy_push_state(WANT_STRING); } [ \t]+ { /* ignore whitespace */ ; } \"[^\"]+\" { /* if ASSIGN_COND then */ From 3a99c914e01720c43ffaecea13b0f8ed7c8c2928 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 21 Oct 2011 23:00:38 +0100 Subject: [PATCH 215/333] tests: default bar mode changed --- testcases/t/177-bar-config.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcases/t/177-bar-config.t b/testcases/t/177-bar-config.t index 761dbf21..371f26f5 100644 --- a/testcases/t/177-bar-config.t +++ b/testcases/t/177-bar-config.t @@ -51,7 +51,7 @@ my $bar_config = $i3->get_bar_config($bar_id)->recv; is($bar_config->{status_command}, 'i3status --foo', 'status_command correct'); ok(!$bar_config->{verbose}, 'verbose off by default'); ok($bar_config->{workspace_buttons}, 'workspace buttons enabled per default'); -is($bar_config->{mode}, 'hide', 'hide mode by default'); +is($bar_config->{mode}, 'dock', 'dock mode by default'); is($bar_config->{position}, 'bottom', 'position bottom by default'); ##################################################################### From 77961ad9c84ade490da8d79578be764a4e77053c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 21 Oct 2011 23:09:57 +0100 Subject: [PATCH 216/333] =?UTF-8?q?cfgparse:=20Don=E2=80=99t=20use=20aspri?= =?UTF-8?q?ntf=20when=20parsing=20hex=20colors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cfgparse.l | 2 +- src/cfgparse.y | 11 +++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/cfgparse.l b/src/cfgparse.l index 50fbc6ca..39e1d398 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -138,7 +138,7 @@ EOL (\r?\n) [^\n]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return STR; } [a-zA-Z0-9_-]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return OUTPUT; } ^[ \t]*#[^\n]* { return TOKCOMMENT; } -[0-9a-fA-F]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return HEX; } +#[0-9a-fA-F]+ { yy_pop_state(); yylval.string = sstrdup(yytext+1); return HEXCOLOR; } [ \t]*→[ \t]* { BEGIN(WANT_STRING); } [ \t]+ { BEGIN(WANT_STRING); } [0-9]+ { yylval.number = atoi(yytext); return NUMBER; } diff --git a/src/cfgparse.y b/src/cfgparse.y index c9e1eb98..bc4928f6 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -590,7 +590,6 @@ void parse_file(const char *f) { %token WORD "" %token STR "" %token STR_NG "" -%token HEX "" %token HEXCOLOR "#" %token OUTPUT "" %token TOKBINDCODE @@ -1506,14 +1505,10 @@ color: ; colorpixel: - '#' HEX + HEXCOLOR { - char *hex; - if (asprintf(&hex, "#%s", $2) == -1) - die("asprintf()"); - free($2); - $$ = get_colorpixel(hex); - free(hex); + $$ = get_colorpixel($1); + free($1); } ; From 014c3e4b95b574e3b290889c29bd537432553056 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 21 Oct 2011 23:15:15 +0100 Subject: [PATCH 217/333] =?UTF-8?q?i3bar:=20Bugfix:=20Don=E2=80=99t=20cras?= =?UTF-8?q?h=20when=20tray=5Foutput=20is=20not=20set=20(Thanks=20fernandot?= =?UTF-8?q?cl)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- i3bar/src/xcb.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index a9ecc004..fa3860e8 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -1359,7 +1359,8 @@ void reconfig_windows() { } if (!tray_configured && - strcasecmp("none", config.tray_output) != 0) { + (!config.tray_output || + strcasecmp("none", config.tray_output) != 0)) { init_tray(); tray_configured = true; } From 9b84348201d2720e24712683d7030e781020be09 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 21 Oct 2011 23:22:48 +0100 Subject: [PATCH 218/333] Bugfix: fix off by one when copying the hex colorcode (Thanks fernandotcl) --- src/cfgparse.l | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfgparse.l b/src/cfgparse.l index 39e1d398..cd936ac5 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -138,7 +138,7 @@ EOL (\r?\n) [^\n]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return STR; } [a-zA-Z0-9_-]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return OUTPUT; } ^[ \t]*#[^\n]* { return TOKCOMMENT; } -#[0-9a-fA-F]+ { yy_pop_state(); yylval.string = sstrdup(yytext+1); return HEXCOLOR; } +#[0-9a-fA-F]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return HEXCOLOR; } [ \t]*→[ \t]* { BEGIN(WANT_STRING); } [ \t]+ { BEGIN(WANT_STRING); } [0-9]+ { yylval.number = atoi(yytext); return NUMBER; } From 3ee8bd502d246699e764f0c6d50abbadcd4a7e93 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 22 Oct 2011 12:34:06 +0100 Subject: [PATCH 219/333] i3bar: change default colors to fit the i3 look & feel The new default looks like this (like in docs/userguide): colors { background #000000 statusline #ffffff focused_workspace #ffffff #285577 active_workspace #888888 #222222 inactive_workspace #888888 #222222 urgent_workspace #ffffff #900000 } If you want to go back to the previous colors, use: colors { background #000000 statusline #ffffff focused_workspace #ffffff #480000 active_workspace #ffffff #480000 inactive_workspace #ffffff #240000 urgent_workspace #ffffff #002400 } --- i3bar/src/xcb.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index fa3860e8..e9cc6ea1 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -311,14 +311,14 @@ void init_colors(const struct xcb_color_strings_t *new_colors) { } while (0) PARSE_COLOR(bar_fg, "FFFFFF"); PARSE_COLOR(bar_bg, "000000"); - PARSE_COLOR(active_ws_fg, "FFFFFF"); - PARSE_COLOR(active_ws_bg, "480000"); - PARSE_COLOR(inactive_ws_fg, "FFFFFF"); - PARSE_COLOR(inactive_ws_bg, "240000"); + PARSE_COLOR(active_ws_fg, "888888"); + PARSE_COLOR(active_ws_bg, "222222"); + PARSE_COLOR(inactive_ws_fg, "888888"); + PARSE_COLOR(inactive_ws_bg, "222222"); PARSE_COLOR(urgent_ws_fg, "FFFFFF"); - PARSE_COLOR(urgent_ws_bg, "002400"); + PARSE_COLOR(urgent_ws_bg, "900000"); PARSE_COLOR(focus_ws_fg, "FFFFFF"); - PARSE_COLOR(focus_ws_bg, "480000"); + PARSE_COLOR(focus_ws_bg, "285577"); #undef PARSE_COLOR } From bc2c63d4ede01b2fab626f160dd66e3beac3265f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 22 Oct 2011 15:00:31 +0100 Subject: [PATCH 220/333] i3bar: change default font to the i3 default one --- i3bar/src/xcb.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index e9cc6ea1..b5b79ae2 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -897,10 +897,8 @@ char *init_xcb_early() { */ void init_xcb_late(char *fontname) { if (fontname == NULL) { - /* This is a very restrictive default. More sensefull would be something like - * "-misc-*-*-*-*--*-*-*-*-*-*-*-*". But since that produces very ugly results - * on my machine, let's stick with this until we have a configfile */ - fontname = "-misc-fixed-medium-r-semicondensed--12-110-75-75-c-60-iso10646-1"; + /* XXX: font fallback to 'misc' like i3 does it would be good. */ + fontname = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; } /* We load and allocate the font */ From 95c2e86db962205b3960e3ca3988e1ba1090a952 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 22 Oct 2011 15:08:24 +0100 Subject: [PATCH 221/333] =?UTF-8?q?i3bar:=20delete=20include/queue.h,=20us?= =?UTF-8?q?e=20i3=E2=80=99s=20copy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- i3bar/include/queue.h | 527 ------------------------------------------ 1 file changed, 527 deletions(-) delete mode 100644 i3bar/include/queue.h diff --git a/i3bar/include/queue.h b/i3bar/include/queue.h deleted file mode 100644 index 75bb957a..00000000 --- a/i3bar/include/queue.h +++ /dev/null @@ -1,527 +0,0 @@ -/* $OpenBSD: queue.h,v 1.1 2007/10/26 03:14:08 niallo Exp $ */ -/* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */ - -/* - * Copyright (c) 1991, 1993 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * @(#)queue.h 8.5 (Berkeley) 8/20/94 - */ - -#ifndef _SYS_QUEUE_H_ -#define _SYS_QUEUE_H_ - -/* - * This file defines five types of data structures: singly-linked lists, - * lists, simple queues, tail queues, and circular queues. - * - * - * A singly-linked list is headed by a single forward pointer. The elements - * are singly linked for minimum space and pointer manipulation overhead at - * the expense of O(n) removal for arbitrary elements. New elements can be - * added to the list after an existing element or at the head of the list. - * Elements being removed from the head of the list should use the explicit - * macro for this purpose for optimum efficiency. A singly-linked list may - * only be traversed in the forward direction. Singly-linked lists are ideal - * for applications with large datasets and few or no removals or for - * implementing a LIFO queue. - * - * A list is headed by a single forward pointer (or an array of forward - * pointers for a hash table header). The elements are doubly linked - * so that an arbitrary element can be removed without a need to - * traverse the list. New elements can be added to the list before - * or after an existing element or at the head of the list. A list - * may only be traversed in the forward direction. - * - * A simple queue is headed by a pair of pointers, one the head of the - * list and the other to the tail of the list. The elements are singly - * linked to save space, so elements can only be removed from the - * head of the list. New elements can be added to the list before or after - * an existing element, at the head of the list, or at the end of the - * list. A simple queue may only be traversed in the forward direction. - * - * A tail queue is headed by a pair of pointers, one to the head of the - * list and the other to the tail of the list. The elements are doubly - * linked so that an arbitrary element can be removed without a need to - * traverse the list. New elements can be added to the list before or - * after an existing element, at the head of the list, or at the end of - * the list. A tail queue may be traversed in either direction. - * - * A circle queue is headed by a pair of pointers, one to the head of the - * list and the other to the tail of the list. The elements are doubly - * linked so that an arbitrary element can be removed without a need to - * traverse the list. New elements can be added to the list before or after - * an existing element, at the head of the list, or at the end of the list. - * A circle queue may be traversed in either direction, but has a more - * complex end of list detection. - * - * For details on the use of these macros, see the queue(3) manual page. - */ - -#if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC)) -#define _Q_INVALIDATE(a) (a) = ((void *)-1) -#else -#define _Q_INVALIDATE(a) -#endif - -/* - * Singly-linked List definitions. - */ -#define SLIST_HEAD(name, type) \ -struct name { \ - struct type *slh_first; /* first element */ \ -} - -#define SLIST_HEAD_INITIALIZER(head) \ - { NULL } - -#define SLIST_ENTRY(type) \ -struct { \ - struct type *sle_next; /* next element */ \ -} - -/* - * Singly-linked List access methods. - */ -#define SLIST_FIRST(head) ((head)->slh_first) -#define SLIST_END(head) NULL -#define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head)) -#define SLIST_NEXT(elm, field) ((elm)->field.sle_next) - -#define SLIST_FOREACH(var, head, field) \ - for((var) = SLIST_FIRST(head); \ - (var) != SLIST_END(head); \ - (var) = SLIST_NEXT(var, field)) - -#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \ - for ((varp) = &SLIST_FIRST((head)); \ - ((var) = *(varp)) != SLIST_END(head); \ - (varp) = &SLIST_NEXT((var), field)) - -/* - * Singly-linked List functions. - */ -#define SLIST_INIT(head) { \ - SLIST_FIRST(head) = SLIST_END(head); \ -} - -#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ - (elm)->field.sle_next = (slistelm)->field.sle_next; \ - (slistelm)->field.sle_next = (elm); \ -} while (0) - -#define SLIST_INSERT_HEAD(head, elm, field) do { \ - (elm)->field.sle_next = (head)->slh_first; \ - (head)->slh_first = (elm); \ -} while (0) - -#define SLIST_REMOVE_NEXT(head, elm, field) do { \ - (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \ -} while (0) - -#define SLIST_REMOVE_HEAD(head, field) do { \ - (head)->slh_first = (head)->slh_first->field.sle_next; \ -} while (0) - -#define SLIST_REMOVE(head, elm, type, field) do { \ - if ((head)->slh_first == (elm)) { \ - SLIST_REMOVE_HEAD((head), field); \ - } else { \ - struct type *curelm = (head)->slh_first; \ - \ - while (curelm->field.sle_next != (elm)) \ - curelm = curelm->field.sle_next; \ - curelm->field.sle_next = \ - curelm->field.sle_next->field.sle_next; \ - _Q_INVALIDATE((elm)->field.sle_next); \ - } \ -} while (0) - -/* - * List definitions. - */ -#define LIST_HEAD(name, type) \ -struct name { \ - struct type *lh_first; /* first element */ \ -} - -#define LIST_HEAD_INITIALIZER(head) \ - { NULL } - -#define LIST_ENTRY(type) \ -struct { \ - struct type *le_next; /* next element */ \ - struct type **le_prev; /* address of previous next element */ \ -} - -/* - * List access methods - */ -#define LIST_FIRST(head) ((head)->lh_first) -#define LIST_END(head) NULL -#define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head)) -#define LIST_NEXT(elm, field) ((elm)->field.le_next) - -#define LIST_FOREACH(var, head, field) \ - for((var) = LIST_FIRST(head); \ - (var)!= LIST_END(head); \ - (var) = LIST_NEXT(var, field)) - -/* - * List functions. - */ -#define LIST_INIT(head) do { \ - LIST_FIRST(head) = LIST_END(head); \ -} while (0) - -#define LIST_INSERT_AFTER(listelm, elm, field) do { \ - if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ - (listelm)->field.le_next->field.le_prev = \ - &(elm)->field.le_next; \ - (listelm)->field.le_next = (elm); \ - (elm)->field.le_prev = &(listelm)->field.le_next; \ -} while (0) - -#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ - (elm)->field.le_prev = (listelm)->field.le_prev; \ - (elm)->field.le_next = (listelm); \ - *(listelm)->field.le_prev = (elm); \ - (listelm)->field.le_prev = &(elm)->field.le_next; \ -} while (0) - -#define LIST_INSERT_HEAD(head, elm, field) do { \ - if (((elm)->field.le_next = (head)->lh_first) != NULL) \ - (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ - (head)->lh_first = (elm); \ - (elm)->field.le_prev = &(head)->lh_first; \ -} while (0) - -#define LIST_REMOVE(elm, field) do { \ - if ((elm)->field.le_next != NULL) \ - (elm)->field.le_next->field.le_prev = \ - (elm)->field.le_prev; \ - *(elm)->field.le_prev = (elm)->field.le_next; \ - _Q_INVALIDATE((elm)->field.le_prev); \ - _Q_INVALIDATE((elm)->field.le_next); \ -} while (0) - -#define LIST_REPLACE(elm, elm2, field) do { \ - if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ - (elm2)->field.le_next->field.le_prev = \ - &(elm2)->field.le_next; \ - (elm2)->field.le_prev = (elm)->field.le_prev; \ - *(elm2)->field.le_prev = (elm2); \ - _Q_INVALIDATE((elm)->field.le_prev); \ - _Q_INVALIDATE((elm)->field.le_next); \ -} while (0) - -/* - * Simple queue definitions. - */ -#define SIMPLEQ_HEAD(name, type) \ -struct name { \ - struct type *sqh_first; /* first element */ \ - struct type **sqh_last; /* addr of last next element */ \ -} - -#define SIMPLEQ_HEAD_INITIALIZER(head) \ - { NULL, &(head).sqh_first } - -#define SIMPLEQ_ENTRY(type) \ -struct { \ - struct type *sqe_next; /* next element */ \ -} - -/* - * Simple queue access methods. - */ -#define SIMPLEQ_FIRST(head) ((head)->sqh_first) -#define SIMPLEQ_END(head) NULL -#define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head)) -#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) - -#define SIMPLEQ_FOREACH(var, head, field) \ - for((var) = SIMPLEQ_FIRST(head); \ - (var) != SIMPLEQ_END(head); \ - (var) = SIMPLEQ_NEXT(var, field)) - -/* - * Simple queue functions. - */ -#define SIMPLEQ_INIT(head) do { \ - (head)->sqh_first = NULL; \ - (head)->sqh_last = &(head)->sqh_first; \ -} while (0) - -#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ - if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ - (head)->sqh_last = &(elm)->field.sqe_next; \ - (head)->sqh_first = (elm); \ -} while (0) - -#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ - (elm)->field.sqe_next = NULL; \ - *(head)->sqh_last = (elm); \ - (head)->sqh_last = &(elm)->field.sqe_next; \ -} while (0) - -#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ - if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ - (head)->sqh_last = &(elm)->field.sqe_next; \ - (listelm)->field.sqe_next = (elm); \ -} while (0) - -#define SIMPLEQ_REMOVE_HEAD(head, field) do { \ - if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ - (head)->sqh_last = &(head)->sqh_first; \ -} while (0) - -/* - * Tail queue definitions. - */ -#define TAILQ_HEAD(name, type) \ -struct name { \ - struct type *tqh_first; /* first element */ \ - struct type **tqh_last; /* addr of last next element */ \ -} - -#define TAILQ_HEAD_INITIALIZER(head) \ - { NULL, &(head).tqh_first } - -#define TAILQ_ENTRY(type) \ -struct { \ - struct type *tqe_next; /* next element */ \ - struct type **tqe_prev; /* address of previous next element */ \ -} - -/* - * tail queue access methods - */ -#define TAILQ_FIRST(head) ((head)->tqh_first) -#define TAILQ_END(head) NULL -#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) -#define TAILQ_LAST(head, headname) \ - (*(((struct headname *)((head)->tqh_last))->tqh_last)) -/* XXX */ -#define TAILQ_PREV(elm, headname, field) \ - (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) -#define TAILQ_EMPTY(head) \ - (TAILQ_FIRST(head) == TAILQ_END(head)) - -#define TAILQ_FOREACH(var, head, field) \ - for((var) = TAILQ_FIRST(head); \ - (var) != TAILQ_END(head); \ - (var) = TAILQ_NEXT(var, field)) - -#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ - for((var) = TAILQ_LAST(head, headname); \ - (var) != TAILQ_END(head); \ - (var) = TAILQ_PREV(var, headname, field)) - -/* - * Tail queue functions. - */ -#define TAILQ_INIT(head) do { \ - (head)->tqh_first = NULL; \ - (head)->tqh_last = &(head)->tqh_first; \ -} while (0) - -#define TAILQ_INSERT_HEAD(head, elm, field) do { \ - if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ - (head)->tqh_first->field.tqe_prev = \ - &(elm)->field.tqe_next; \ - else \ - (head)->tqh_last = &(elm)->field.tqe_next; \ - (head)->tqh_first = (elm); \ - (elm)->field.tqe_prev = &(head)->tqh_first; \ -} while (0) - -#define TAILQ_INSERT_TAIL(head, elm, field) do { \ - (elm)->field.tqe_next = NULL; \ - (elm)->field.tqe_prev = (head)->tqh_last; \ - *(head)->tqh_last = (elm); \ - (head)->tqh_last = &(elm)->field.tqe_next; \ -} while (0) - -#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ - if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ - (elm)->field.tqe_next->field.tqe_prev = \ - &(elm)->field.tqe_next; \ - else \ - (head)->tqh_last = &(elm)->field.tqe_next; \ - (listelm)->field.tqe_next = (elm); \ - (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ -} while (0) - -#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ - (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ - (elm)->field.tqe_next = (listelm); \ - *(listelm)->field.tqe_prev = (elm); \ - (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ -} while (0) - -#define TAILQ_REMOVE(head, elm, field) do { \ - if (((elm)->field.tqe_next) != NULL) \ - (elm)->field.tqe_next->field.tqe_prev = \ - (elm)->field.tqe_prev; \ - else \ - (head)->tqh_last = (elm)->field.tqe_prev; \ - *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ - _Q_INVALIDATE((elm)->field.tqe_prev); \ - _Q_INVALIDATE((elm)->field.tqe_next); \ -} while (0) - -#define TAILQ_REPLACE(head, elm, elm2, field) do { \ - if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \ - (elm2)->field.tqe_next->field.tqe_prev = \ - &(elm2)->field.tqe_next; \ - else \ - (head)->tqh_last = &(elm2)->field.tqe_next; \ - (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ - *(elm2)->field.tqe_prev = (elm2); \ - _Q_INVALIDATE((elm)->field.tqe_prev); \ - _Q_INVALIDATE((elm)->field.tqe_next); \ -} while (0) - -/* - * Circular queue definitions. - */ -#define CIRCLEQ_HEAD(name, type) \ -struct name { \ - struct type *cqh_first; /* first element */ \ - struct type *cqh_last; /* last element */ \ -} - -#define CIRCLEQ_HEAD_INITIALIZER(head) \ - { CIRCLEQ_END(&head), CIRCLEQ_END(&head) } - -#define CIRCLEQ_ENTRY(type) \ -struct { \ - struct type *cqe_next; /* next element */ \ - struct type *cqe_prev; /* previous element */ \ -} - -/* - * Circular queue access methods - */ -#define CIRCLEQ_FIRST(head) ((head)->cqh_first) -#define CIRCLEQ_LAST(head) ((head)->cqh_last) -#define CIRCLEQ_END(head) ((void *)(head)) -#define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next) -#define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev) -#define CIRCLEQ_EMPTY(head) \ - (CIRCLEQ_FIRST(head) == CIRCLEQ_END(head)) - -#define CIRCLEQ_FOREACH(var, head, field) \ - for((var) = CIRCLEQ_FIRST(head); \ - (var) != CIRCLEQ_END(head); \ - (var) = CIRCLEQ_NEXT(var, field)) - -#define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ - for((var) = CIRCLEQ_LAST(head); \ - (var) != CIRCLEQ_END(head); \ - (var) = CIRCLEQ_PREV(var, field)) - -/* - * Circular queue functions. - */ -#define CIRCLEQ_INIT(head) do { \ - (head)->cqh_first = CIRCLEQ_END(head); \ - (head)->cqh_last = CIRCLEQ_END(head); \ -} while (0) - -#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ - (elm)->field.cqe_next = (listelm)->field.cqe_next; \ - (elm)->field.cqe_prev = (listelm); \ - if ((listelm)->field.cqe_next == CIRCLEQ_END(head)) \ - (head)->cqh_last = (elm); \ - else \ - (listelm)->field.cqe_next->field.cqe_prev = (elm); \ - (listelm)->field.cqe_next = (elm); \ -} while (0) - -#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ - (elm)->field.cqe_next = (listelm); \ - (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ - if ((listelm)->field.cqe_prev == CIRCLEQ_END(head)) \ - (head)->cqh_first = (elm); \ - else \ - (listelm)->field.cqe_prev->field.cqe_next = (elm); \ - (listelm)->field.cqe_prev = (elm); \ -} while (0) - -#define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ - (elm)->field.cqe_next = (head)->cqh_first; \ - (elm)->field.cqe_prev = CIRCLEQ_END(head); \ - if ((head)->cqh_last == CIRCLEQ_END(head)) \ - (head)->cqh_last = (elm); \ - else \ - (head)->cqh_first->field.cqe_prev = (elm); \ - (head)->cqh_first = (elm); \ -} while (0) - -#define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ - (elm)->field.cqe_next = CIRCLEQ_END(head); \ - (elm)->field.cqe_prev = (head)->cqh_last; \ - if ((head)->cqh_first == CIRCLEQ_END(head)) \ - (head)->cqh_first = (elm); \ - else \ - (head)->cqh_last->field.cqe_next = (elm); \ - (head)->cqh_last = (elm); \ -} while (0) - -#define CIRCLEQ_REMOVE(head, elm, field) do { \ - if ((elm)->field.cqe_next == CIRCLEQ_END(head)) \ - (head)->cqh_last = (elm)->field.cqe_prev; \ - else \ - (elm)->field.cqe_next->field.cqe_prev = \ - (elm)->field.cqe_prev; \ - if ((elm)->field.cqe_prev == CIRCLEQ_END(head)) \ - (head)->cqh_first = (elm)->field.cqe_next; \ - else \ - (elm)->field.cqe_prev->field.cqe_next = \ - (elm)->field.cqe_next; \ - _Q_INVALIDATE((elm)->field.cqe_prev); \ - _Q_INVALIDATE((elm)->field.cqe_next); \ -} while (0) - -#define CIRCLEQ_REPLACE(head, elm, elm2, field) do { \ - if (((elm2)->field.cqe_next = (elm)->field.cqe_next) == \ - CIRCLEQ_END(head)) \ - (head)->cqh_last = (elm2); \ - else \ - (elm2)->field.cqe_next->field.cqe_prev = (elm2); \ - if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) == \ - CIRCLEQ_END(head)) \ - (head)->cqh_first = (elm2); \ - else \ - (elm2)->field.cqe_prev->field.cqe_next = (elm2); \ - _Q_INVALIDATE((elm)->field.cqe_prev); \ - _Q_INVALIDATE((elm)->field.cqe_next); \ -} while (0) - -#endif /* !_SYS_QUEUE_H_ */ From 409dcf44dbe86cce190eea1918d2036bd017d0bb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 22 Oct 2011 15:24:18 +0100 Subject: [PATCH 222/333] i3-migrate-config-to-v4: generate a bar {} block instead of exec i3bar --- i3-migrate-config-to-v4 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/i3-migrate-config-to-v4 b/i3-migrate-config-to-v4 index 4dd4418f..4f4d0134 100755 --- a/i3-migrate-config-to-v4 +++ b/i3-migrate-config-to-v4 @@ -358,6 +358,8 @@ sub convert_command { # add an i3bar invocation automatically if no 'workspace_bar no' was found if ($workspace_bar) { print "\n"; - print "# XXX: Automatically added a call to i3bar to provide a workspace bar\n"; - print "exec i3status | i3bar -d\n"; + print "# XXX: Automatically added a bar configuration\n"; + print "bar {\n"; + print " status_command i3status\n"; + print "}\n"; } From d7eba46de5c75734b07f417e9dc997d36b927fa5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 22 Oct 2011 16:16:06 +0100 Subject: [PATCH 223/333] i3-nagbar: Implement -t warning, makes colors yellow(ish) --- i3-nagbar/main.c | 57 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index d0d7e77a..a1b473ae 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -45,6 +45,14 @@ static int font_height; static char *prompt = "Please do not run this program."; static button_t *buttons; static int buttoncnt; + +/* Result of get_colorpixel() for the various colors. */ +static uint32_t color_background; /* background of the bar */ +static uint32_t color_button_background; /* background for buttons */ +static uint32_t color_border; /* color of the button border */ +static uint32_t color_border_bottom; /* color of the bottom border */ +static uint32_t color_text; /* color of the text */ + xcb_window_t root; /* @@ -118,16 +126,14 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve * */ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { - printf("expose!\n"); - /* re-draw the background */ - xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#900000")); + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, color_background); xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &rect); /* restore font color */ uint32_t values[3]; - values[0] = get_colorpixel(conn, "#FFFFFF"); - values[1] = get_colorpixel(conn, "#900000"); + values[0] = color_text; + values[1] = color_background; xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, values); xcb_image_text_8(conn, strlen(prompt), pixmap, pixmap_gc, 4 + 4/* X */, font_height + 2 + 4 /* Y = baseline of font */, prompt); @@ -136,14 +142,14 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { int line_width = 4; int w = 20; int y = rect.width; - values[0] = get_colorpixel(conn, "#680a0a"); + values[0] = color_button_background; values[1] = line_width; xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values); xcb_rectangle_t close = { y - w - (2 * line_width), 0, w + (2 * line_width), rect.height }; xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close); - xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#d92424")); + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, color_border); xcb_point_t points[] = { { y - w - (2 * line_width), line_width / 2 }, { y - (line_width / 2), line_width / 2 }, @@ -153,8 +159,8 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { }; xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points); - values[0] = get_colorpixel(conn, "#ffffff"); - values[1] = get_colorpixel(conn, "#680a0a"); + values[0] = color_text; + values[1] = color_button_background; values[2] = 1; xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_LINE_WIDTH, values); xcb_image_text_8(conn, strlen("x"), pixmap, pixmap_gc, y - w - line_width + (w / 2) - 4/* X */, @@ -169,11 +175,11 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { /* TODO: make w = text extents of the label */ w = 90; y -= 30; - xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#680a0a")); + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, color_button_background); close = (xcb_rectangle_t){ y - w - (2 * line_width), 2, w + (2 * line_width), rect.height - 6 }; xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close); - xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#d92424")); + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, color_border); buttons[c].x = y - w - (2 * line_width); buttons[c].width = w; xcb_point_t points2[] = { @@ -185,8 +191,8 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { }; xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points2); - values[0] = get_colorpixel(conn, "#ffffff"); - values[1] = get_colorpixel(conn, "#680a0a"); + values[0] = color_text; + values[1] = color_button_background; xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, values); xcb_image_text_8(conn, strlen(buttons[c].label), pixmap, pixmap_gc, y - w - line_width + 6/* X */, font_height + 2 + 3/* Y = baseline of font */, buttons[c].label); @@ -196,7 +202,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { /* border line at the bottom */ line_width = 2; - values[0] = get_colorpixel(conn, "#470909"); + values[0] = color_border_bottom; values[1] = line_width; xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values); xcb_point_t bottom[] = { @@ -216,6 +222,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { int main(int argc, char *argv[]) { char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; int o, option_index = 0; + enum { TYPE_ERROR = 0, TYPE_WARNING = 1 } bar_type = TYPE_ERROR; static struct option long_options[] = { {"version", no_argument, 0, 'v'}, @@ -223,10 +230,11 @@ int main(int argc, char *argv[]) { {"button", required_argument, 0, 'b'}, {"help", no_argument, 0, 'h'}, {"message", no_argument, 0, 'm'}, + {"type", required_argument, 0, 't'}, {0, 0, 0, 0} }; - char *options_string = "b:f:m:vh"; + char *options_string = "b:f:m:t:vh"; while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { switch (o) { @@ -240,6 +248,9 @@ int main(int argc, char *argv[]) { case 'm': prompt = strdup(optarg); break; + case 't': + bar_type = (strcasecmp(optarg, "warning") == 0 ? TYPE_WARNING : TYPE_ERROR); + break; case 'h': printf("i3-nagbar " I3_VERSION "\n"); printf("i3-nagbar [-m ] [-b