From efbe2dfeaa8f7e5264c728d8b0004c7935147f1f Mon Sep 17 00:00:00 2001 From: Urs Ganse Date: Sun, 29 Mar 2009 14:53:48 +0200 Subject: [PATCH 001/129] Added a "jump" command to directly focus a container. Syntax is "jump ". This is quite handy for clients that you always keep in the same spot, and like to jump to quite often. The irc client would be an example. --- src/commands.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/commands.c b/src/commands.c index 09c21b34..60e5a08f 100644 --- a/src/commands.c +++ b/src/commands.c @@ -585,6 +585,39 @@ void show_workspace(xcb_connection_t *conn, int workspace) { render_layout(conn); } +/* + * Jump directly to the specified workspace, row and col. + * Great for reaching windows that you always keep in the + * same spot (hello irssi, I'm looking at you) + */ +static void jump_to_container(xcb_connection_t *conn, const char* arg_str) { + int ws,row,col; + int result; + + result = sscanf(arg_str, "%i %i %i", &ws, &row, &col); + LOG("Jump called with parameters '%s', which parses as %i numbers\n", arg_str, result); + + /* No match? (This is technically a syntax error, but who cares.) */ + if(result < 1) + return; + + /* Move to the target workspace */ + show_workspace(conn, ws); + + if(result < 3) + return; + + /* Move to row/col */ + if(row >= c_ws->rows) + row = c_ws->rows - 1; + if(col >= c_ws->cols) + col = c_ws->cols - 1; + + LOG("Jumping to row %i, col %i\n", row, col); + if (c_ws->table[col][row]->currently_focused != NULL) + set_focus(conn, c_ws->table[col][row]->currently_focused); +} + /* * Parses a command, see file CMDMODE for more information * @@ -626,6 +659,12 @@ void parse_command(xcb_connection_t *conn, const char *command) { return; } + /* Is it a jump to a specified workspae,row,col? */ + if (STARTS_WITH(command, "jump ")) { + jump_to_container(conn, command+strlen("jump ")); + return; + } + /* Is it 'f' for fullscreen? */ if (command[0] == 'f') { if (CUR_CELL->currently_focused == NULL) From 79e5d5f29d1fff50ccd5bc15e8fc124e04521933 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 30 Mar 2009 09:25:30 +0200 Subject: [PATCH 002/129] =?UTF-8?q?Some=20small=20style=20changes=20to=20m?= =?UTF-8?q?ake=20urs=E2=80=99=20code=20consistent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/commands.c | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/commands.c b/src/commands.c index 60e5a08f..9ebe7616 100644 --- a/src/commands.c +++ b/src/commands.c @@ -589,31 +589,34 @@ void show_workspace(xcb_connection_t *conn, int workspace) { * Jump directly to the specified workspace, row and col. * Great for reaching windows that you always keep in the * same spot (hello irssi, I'm looking at you) + * */ static void jump_to_container(xcb_connection_t *conn, const char* arg_str) { - int ws,row,col; + int ws, row, col; int result; - result = sscanf(arg_str, "%i %i %i", &ws, &row, &col); - LOG("Jump called with parameters '%s', which parses as %i numbers\n", arg_str, result); + result = sscanf(arg_str, "%d %d %d", &ws, &row, &col); + LOG("Jump called with %d parameters (\"%s\")\n", result, arg_str); - /* No match? (This is technically a syntax error, but who cares.) */ - if(result < 1) - return; + /* No match? Either no arguments were specified, or no numbers */ + if (result < 1) { + LOG("At least one valid argument required\n"); + return; + } /* Move to the target workspace */ show_workspace(conn, ws); - if(result < 3) - return; + if (result < 3) + return; /* Move to row/col */ - if(row >= c_ws->rows) - row = c_ws->rows - 1; - if(col >= c_ws->cols) - col = c_ws->cols - 1; + if (row >= c_ws->rows) + row = c_ws->rows - 1; + if (col >= c_ws->cols) + col = c_ws->cols - 1; - LOG("Jumping to row %i, col %i\n", row, col); + LOG("Jumping to row %d, col %d\n", row, col); if (c_ws->table[col][row]->currently_focused != NULL) set_focus(conn, c_ws->table[col][row]->currently_focused); } @@ -661,7 +664,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { /* Is it a jump to a specified workspae,row,col? */ if (STARTS_WITH(command, "jump ")) { - jump_to_container(conn, command+strlen("jump ")); + jump_to_container(conn, command + strlen("jump ")); return; } From 12fa69329add61988c868fd642e469fd53273976 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 30 Mar 2009 09:27:58 +0200 Subject: [PATCH 003/129] Change syntax of jump to col,row instead of row,col to make it consistent with the internal data structures, document it in commandmode --- CMDMODE | 4 ++++ src/commands.c | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CMDMODE b/CMDMODE index 6b8315e2..37dcb3a3 100644 --- a/CMDMODE +++ b/CMDMODE @@ -17,6 +17,10 @@ with := { [ ] }+ oder +jump := [ ] + +oder + special := [ exec | kill | exit | restart ] an jeder Stelle kann mit escape abgebrochen werden diff --git a/src/commands.c b/src/commands.c index 9ebe7616..0462438a 100644 --- a/src/commands.c +++ b/src/commands.c @@ -595,7 +595,7 @@ static void jump_to_container(xcb_connection_t *conn, const char* arg_str) { int ws, row, col; int result; - result = sscanf(arg_str, "%d %d %d", &ws, &row, &col); + result = sscanf(arg_str, "%d %d %d", &ws, &col, &row); LOG("Jump called with %d parameters (\"%s\")\n", result, arg_str); /* No match? Either no arguments were specified, or no numbers */ @@ -616,7 +616,7 @@ static void jump_to_container(xcb_connection_t *conn, const char* arg_str) { if (col >= c_ws->cols) col = c_ws->cols - 1; - LOG("Jumping to row %d, col %d\n", row, col); + LOG("Jumping to row %d, col %d\n", col, row); if (c_ws->table[col][row]->currently_focused != NULL) set_focus(conn, c_ws->table[col][row]->currently_focused); } From e295ab302b4837b0218fdfa914ea59a03cf3407d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 30 Mar 2009 09:39:35 +0200 Subject: [PATCH 004/129] Implement storing WM_CLASS of each client --- include/data.h | 3 +++ include/handlers.h | 10 +++++++++- src/handlers.c | 32 ++++++++++++++++++++++++++++++-- src/mainx.c | 4 ++++ 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/include/data.h b/include/data.h index dc7fd8ff..a54798ba 100644 --- a/include/data.h +++ b/include/data.h @@ -249,6 +249,9 @@ struct Client { legacy window names are ignored. */ bool uses_net_wm_name; + /* Holds the WM_CLASS, useful for matching the client in commands */ + char *window_class; + /* fullscreen is pretty obvious */ bool fullscreen; diff --git a/include/handlers.h b/include/handlers.h index b2c0de94..c160b600 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -85,7 +85,15 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, * */ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t state, - xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop); + xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop); + +/** + * Store the window classes for jumping to them later. + * + */ +int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state, + xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop); + /** * Expose event means we should redraw our windows (= title bar) diff --git a/src/handlers.c b/src/handlers.c index d2eec978..0525e8cd 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -744,8 +744,7 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, client->name_len = new_len; client->uses_net_wm_name = true; - if (old_name != NULL) - free(old_name); + FREE(old_name); /* If the client is a dock window, we don’t need to render anything */ if (client->dock) @@ -827,6 +826,35 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t return 1; } +/* + * Updates the client’s WM_CLASS property + * + */ +int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state, + xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) { + LOG("window class changed\n"); + if (prop == NULL || xcb_get_property_value_length(prop) == 0) { + LOG("prop == NULL\n"); + return 1; + } + Client *client = table_get(byChild, window); + if (client == NULL) + return 1; + char *new_class; + if (asprintf(&new_class, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)) == -1) { + perror("Could not get window class"); + LOG("Could not get window class\n"); + return 1; + } + + LOG("changed to %s\n", new_class); + char *old_class = client->window_class; + client->window_class = new_class; + FREE(old_class); + + return 1; +} + /* * Expose event means we should redraw our windows (= title bar) * diff --git a/src/mainx.c b/src/mainx.c index 38ae2511..e89eaab0 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -105,6 +105,7 @@ void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_ if (attr && geom) { reparent_window(conn, window, attr->visual, geom->root, geom->depth, geom->x, geom->y, geom->width, geom->height); + xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS); xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME); xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS); xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]); @@ -498,6 +499,9 @@ int main(int argc, char *argv[], char *env[]) { /* Watch WM_NAME (= title of the window in compound text) property for legacy applications */ xcb_watch_wm_name(&prophs, 128, handle_windowname_change_legacy, NULL); + /* Watch WM_CLASS (= class of the window) */ + xcb_property_set_handler(&prophs, WM_CLASS, 128, handle_windowclass_change, NULL); + /* Set up the atoms we support */ check_error(conn, xcb_change_property_checked(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED], ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED"); From f72214725ca528a47d9d41106bd297c42ab448c9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 31 Mar 2009 10:46:12 +0200 Subject: [PATCH 005/129] Implement jumping to windows by matching their class / title --- CMDMODE | 2 +- include/util.h | 4 +-- src/commands.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++---- src/util.c | 6 ++-- 4 files changed, 92 insertions(+), 12 deletions(-) diff --git a/CMDMODE b/CMDMODE index 37dcb3a3..0231c705 100644 --- a/CMDMODE +++ b/CMDMODE @@ -17,7 +17,7 @@ with := { [ ] }+ oder -jump := [ ] +jump := [ "[/]" | [ ] ] oder diff --git a/include/util.h b/include/util.h index 3ea2b3a7..fba1206f 100644 --- a/include/util.h +++ b/include/util.h @@ -22,8 +22,8 @@ #define CIRCLEQ_PREV_OR_NULL(head, elm, field) (CIRCLEQ_PREV(elm, field) != CIRCLEQ_END(head) ? \ CIRCLEQ_PREV(elm, field) : NULL) #define FOR_TABLE(workspace) \ - for (int cols = 0; cols < workspace->cols; cols++) \ - for (int rows = 0; rows < workspace->rows; rows++) + for (int cols = 0; cols < (workspace)->cols; cols++) \ + for (int rows = 0; rows < (workspace)->rows; rows++) #define FREE(pointer) do { \ if (pointer == NULL) { \ free(pointer); \ diff --git a/src/commands.c b/src/commands.c index 0462438a..95c60c8e 100644 --- a/src/commands.c +++ b/src/commands.c @@ -586,17 +586,92 @@ void show_workspace(xcb_connection_t *conn, int workspace) { } /* - * Jump directly to the specified workspace, row and col. - * Great for reaching windows that you always keep in the - * same spot (hello irssi, I'm looking at you) + * Checks if the given window class and title match the given client + * Window title is passed as "normal" string and as UCS-2 converted string for + * matching _NET_WM_NAME capable clients as well as those using legacy hints. * */ -static void jump_to_container(xcb_connection_t *conn, const char* arg_str) { +static bool client_matches_class_name(Client *client, char *to_class, char *to_title, + char *to_title_ucs, int to_title_ucs_len) { + /* Check if the given class is part of the window class */ + if (strcasestr(client->window_class, to_class) == NULL) + return false; + + /* If no title was given, we’re done */ + if (to_title == NULL) + return true; + + if (client->name_len > -1) { + /* UCS-2 converted window titles */ + if (memmem(client->name, (client->name_len * 2), to_title_ucs, (to_title_ucs_len * 2)) == NULL) + return false; + } else { + /* Legacy hints */ + if (strcasestr(client->name, to_title) == NULL) + return false; + } + + return true; +} + +/* + * Jumps to the given window class / title. + * Title is matched using strstr, that is, matches if it appears anywhere + * in the string. Regular expressions seem to be a bit overkill here. However, + * if we need them for something else somewhen, we may introduce them here, too. + * + */ +static void jump_to_window(xcb_connection_t *conn, const char *arguments) { + char *to_class, *to_title, *to_title_ucs = NULL; + int to_title_ucs_len; + + /* The first character is a quote, this was checked before */ + to_class = sstrdup(arguments+1); + /* The last character is a quote, we just set it to NULL */ + to_class[strlen(to_class)-1] = '\0'; + + /* If a title was specified, split both strings at the slash */ + if ((to_title = strstr(to_class, "/")) != NULL) { + *(to_title++) = '\0'; + /* Convert to UCS-2 */ + to_title_ucs = convert_utf8_to_ucs2(to_title, &to_title_ucs_len); + } + + LOG("Should jump to class \"%s\" / title \"%s\"\n", to_class, to_title); + for (int workspace = 0; workspace < 10; workspace++) { + if (workspaces[workspace].screen == NULL) + continue; + + FOR_TABLE(&(workspaces[workspace])) { + Container *con = workspaces[workspace].table[cols][rows]; + Client *client; + + CIRCLEQ_FOREACH(client, &(con->clients), clients) { + LOG("Checking client with class=%s, name=%s\n", client->window_class, client->name); + if (client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len)) { + set_focus(conn, client); + goto done; + } + } + } + } + +done: + free(to_class); + FREE(to_title_ucs); +} + +/* + * Jump directly to the specified workspace, row and col. + * Great for reaching windows that you always keep in the same spot (hello irssi, I'm looking at you) + * + */ +static void jump_to_container(xcb_connection_t *conn, const char *arguments) { int ws, row, col; int result; - result = sscanf(arg_str, "%d %d %d", &ws, &col, &row); - LOG("Jump called with %d parameters (\"%s\")\n", result, arg_str); + result = sscanf(arguments, "%d %d %d", &ws, &col, &row); + LOG("Jump called with %d parameters (\"%s\")\n", result, arguments); /* No match? Either no arguments were specified, or no numbers */ if (result < 1) { @@ -664,7 +739,10 @@ void parse_command(xcb_connection_t *conn, const char *command) { /* Is it a jump to a specified workspae,row,col? */ if (STARTS_WITH(command, "jump ")) { - jump_to_container(conn, command + strlen("jump ")); + const char *arguments = command + strlen("jump "); + if (arguments[0] == '"') + jump_to_window(conn, arguments); + else jump_to_container(conn, arguments); return; } diff --git a/src/util.c b/src/util.c index c3e45c51..2ac0beb5 100644 --- a/src/util.c +++ b/src/util.c @@ -213,11 +213,13 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen) { int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size); if (rc == (size_t)-1) { perror("Converting to UCS-2 failed"); - *real_strlen = 0; + if (real_strlen != NULL) + *real_strlen = 0; return NULL; } - *real_strlen = ((buffer_size - output_size) / 2) - 1; + if (real_strlen != NULL) + *real_strlen = ((buffer_size - output_size) / 2) - 1; return buffer; } From 56d637a665e8ff23b1d080a6912086963d7d722b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 5 May 2009 16:57:21 +0200 Subject: [PATCH 006/129] Add debug message for jumping --- src/commands.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/commands.c b/src/commands.c index 95c60c8e..111dce55 100644 --- a/src/commands.c +++ b/src/commands.c @@ -685,13 +685,15 @@ static void jump_to_container(xcb_connection_t *conn, const char *arguments) { if (result < 3) return; + LOG("Boundary-checking col %d, row %d... (max cols %d, max rows %d)\n", col, row, c_ws->cols, c_ws->rows); + /* Move to row/col */ if (row >= c_ws->rows) row = c_ws->rows - 1; if (col >= c_ws->cols) col = c_ws->cols - 1; - LOG("Jumping to row %d, col %d\n", col, row); + LOG("Jumping to col %d, row %d\n", col, row); if (c_ws->table[col][row]->currently_focused != NULL) set_focus(conn, c_ws->table[col][row]->currently_focused); } From 3a2b546c9ee0cbce0343b09c9acb85064c29aabf Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 5 May 2009 17:25:56 +0200 Subject: [PATCH 007/129] Implement a command to travel the focusstack. This can be used like a jumpback. However, it is a bit more flexible obviously. You can specify the offset of the window you want to go to, to implement workflows like the following: * Jump to mutt * Jump to irssi * Jump back ("focus 2" would be the command) --- CMDMODE | 51 +++++++++++++++++++++++++------------------------- src/commands.c | 50 +++++++++++++++++++++++++++++++++++++++++++------ src/handlers.c | 2 +- 3 files changed, 71 insertions(+), 32 deletions(-) diff --git a/CMDMODE b/CMDMODE index 0231c705..7428729a 100644 --- a/CMDMODE +++ b/CMDMODE @@ -1,40 +1,41 @@ -left := | +--------------------- +- Command mode +--------------------- + +This is the grammar for the command mode (your configuration file uses these commands, too). + +left := | right := | -up := | -down := | +up := | +down := | where := | -move := -snap := - -Eingabe ist entweder - -cmd := [ ] [ | ] - -oder - -with := { [ ] }+ - -oder - -jump := [ "[/]" | [ ] ] - -oder +move := +snap := +cmd := [ ] [ | ] +with := { [ ] }+ +jump := [ "[/]" | [ ] ] +focus := focus [ ] +(travels the focus stack backwards the given amount of times (by default 1), so + it selects the window which had the focus before you focused the current one when + specifying "focus 1") special := [ exec | kill | exit | restart ] -an jeder Stelle kann mit escape abgebrochen werden +input := [ | | | | ] -Beispiele: +you can cancel command mode by pressing escape anytime. -Fenster links neben dem aktuellen auswählen: +Some examples: + +Select the window on the left: h -Fenster zwei links neben dem aktuellen auswählen: +Select the window two places on the left: 2h -Fenster nach rechts verschieben: +Move window to the right: ml -Fenster und Fenster untendrunter nach rechts verschieben: +Move window and window on the bottom to the right: wk ml diff --git a/src/commands.c b/src/commands.c index 111dce55..c0f5ffdd 100644 --- a/src/commands.c +++ b/src/commands.c @@ -648,10 +648,11 @@ static void jump_to_window(xcb_connection_t *conn, const char *arguments) { CIRCLEQ_FOREACH(client, &(con->clients), clients) { LOG("Checking client with class=%s, name=%s\n", client->window_class, client->name); - if (client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len)) { - set_focus(conn, client); - goto done; - } + if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len)) + continue; + + set_focus(conn, client, true); + goto done; } } } @@ -695,7 +696,37 @@ static void jump_to_container(xcb_connection_t *conn, const char *arguments) { LOG("Jumping to col %d, row %d\n", col, row); if (c_ws->table[col][row]->currently_focused != NULL) - set_focus(conn, c_ws->table[col][row]->currently_focused); + set_focus(conn, c_ws->table[col][row]->currently_focused, true); +} + +/* + * Travels the focus stack by the given number of times (or once, if no argument + * was specified). That is, selects the window you were in before you focused + * the current window. + * + */ +static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) { + /* Start count at -1 to always skip the first element */ + int times, count = -1; + Client *current; + + if (sscanf(arguments, "%u", ×) != 1) { + LOG("No or invalid argument given (\"%s\"), using default of 1 times\n", arguments); + times = 1; + } + + Workspace *ws = CUR_CELL->workspace; + + SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) { + if (++count < times) { + LOG("Skipping\n"); + continue; + } + + LOG("Focussing\n"); + set_focus(conn, current, true); + break; + } } /* @@ -739,7 +770,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { return; } - /* Is it a jump to a specified workspae,row,col? */ + /* Is it a jump to a specified workspae, row, col? */ if (STARTS_WITH(command, "jump ")) { const char *arguments = command + strlen("jump "); if (arguments[0] == '"') @@ -748,6 +779,13 @@ void parse_command(xcb_connection_t *conn, const char *command) { return; } + /* Should we travel the focus stack? */ + if (STARTS_WITH(command, "focus")) { + const char *arguments = command + strlen("focus"); + travel_focus_stack(conn, arguments); + return; + } + /* Is it 'f' for fullscreen? */ if (command[0] == 'f') { if (CUR_CELL->currently_focused == NULL) diff --git a/src/handlers.c b/src/handlers.c index 0525e8cd..4aa3e8f5 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -837,7 +837,7 @@ int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state, LOG("prop == NULL\n"); return 1; } - Client *client = table_get(byChild, window); + Client *client = table_get(&by_child, window); if (client == NULL) return 1; char *new_class; From 4a3354da3b3de8058132b722f90a2f5e1ed63302 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 5 May 2009 17:29:46 +0200 Subject: [PATCH 008/129] Update debian changelog --- debian/changelog | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/debian/changelog b/debian/changelog index f698b32c..2c21abc0 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +i3-wm (3.b-1) unstable; urgency=low + + * Implement jumping to other windows by specifying their position or + window class/title + * Implement jumping back by using the focus stack + + -- Michael Stapelberg Tue, 05 May 2009 17:29:07 +0200 + i3-wm (3.a-bf2-1) unstable; urgency=low * Bugfix: Don't crash when setting focus From 3400f0e6bd7cd0717042b3a53be4effbe5431d83 Mon Sep 17 00:00:00 2001 From: Michael Rudolf Date: Sun, 29 Mar 2009 14:53:48 +0200 Subject: [PATCH 009/129] Implement autostart using "exec" in config Syntax is "exec ", like when creating a binding. Multiple entries are possible, applications are started in the specified order. --- include/data.h | 11 +++++++++++ include/i3.h | 1 + src/config.c | 9 +++++++++ src/mainx.c | 10 ++++++++++ 4 files changed, 31 insertions(+) diff --git a/include/data.h b/include/data.h index a54798ba..e511a7fb 100644 --- a/include/data.h +++ b/include/data.h @@ -48,6 +48,7 @@ typedef struct Font i3Font; typedef struct Container Container; typedef struct Client Client; typedef struct Binding Binding; +typedef struct Autostart Autostart; typedef struct Workspace Workspace; typedef struct Rect Rect; typedef struct Screen i3Screen; @@ -191,6 +192,16 @@ struct Binding { TAILQ_ENTRY(Binding) bindings; }; +/* + * Holds a command specified by an exec-line in the config (see src/config.c) + * + */ +struct Autostart { + /* Command, like in command mode */ + char *command; + TAILQ_ENTRY(Autostart) autostarts; +}; + /* * Data structure for cached font information: * - font id in X11 (load it once) diff --git a/include/i3.h b/include/i3.h index 02df9996..1c9e1b35 100644 --- a/include/i3.h +++ b/include/i3.h @@ -25,6 +25,7 @@ extern char **start_argv; extern Display *xkbdpy; extern TAILQ_HEAD(bindings_head, Binding) bindings; +extern TAILQ_HEAD(autostarts_head, Autostart) autostarts; extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins; extern xcb_event_handlers_t evenths; extern int num_screens; diff --git a/src/config.c b/src/config.c index 73d39021..ca84282a 100644 --- a/src/config.c +++ b/src/config.c @@ -85,6 +85,15 @@ void load_configuration(const char *override_configpath) { OPTION_STRING(terminal); OPTION_STRING(font); + /* exec-lines (autostart) */ + if (strcasecmp(key, "exec") == 0) { + Autostart *new = smalloc(sizeof(Autostart)); + new->command = sstrdup(value); + TAILQ_INSERT_TAIL(&autostarts, new, autostarts); + continue; + } + + /* key bindings */ if (strcasecmp(key, "bind") == 0) { #define CHECK_MODIFIER(name) \ if (strncasecmp(walk, #name, strlen(#name)) == 0) { \ diff --git a/src/mainx.c b/src/mainx.c index e89eaab0..da79ee3a 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -52,6 +52,9 @@ Display *xkbdpy; /* The list of key bindings */ struct bindings_head bindings = TAILQ_HEAD_INITIALIZER(bindings); +/* The list of exec-lines */ +struct autostarts_head autostarts = TAILQ_HEAD_INITIALIZER(autostarts); + /* This is a list of Stack_Windows, global, for easier/faster access on expose events */ struct stack_wins_head stack_wins = SLIST_HEAD_INITIALIZER(stack_wins); @@ -527,6 +530,13 @@ int main(int argc, char *argv[], char *env[]) { } } + /* Autostarting exec-lines */ + Autostart *exec; + TAILQ_FOREACH(exec, &autostarts, autostarts) { + LOG("auto-starting %s\n", exec->command); + start_application(exec->command); + } + /* check for Xinerama */ LOG("Checking for Xinerama...\n"); initialize_xinerama(conn); From 4b77d93c2e71527a79e0f839e8cdb4472e9072fe Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 7 May 2009 13:46:34 +0200 Subject: [PATCH 010/129] Bugfix: Resize all containers in the column instead of only the active one (Thanks Ned) This fixes ticket #33. --- src/handlers.c | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index 4aa3e8f5..61d33135 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -57,7 +57,6 @@ static bool event_is_ignored(const int sequence) { time_t now = time(NULL); for (event = SLIST_FIRST(&ignore_events); event != SLIST_END(&ignore_events);) { if ((now - event->added) > 5) { - LOG("Entry is older than five seconds, cleaning up\n"); struct Ignore_Event *save = event; event = SLIST_NEXT(event, ignore_events); SLIST_REMOVE(&ignore_events, save, Ignore_Event, ignore_events); @@ -472,23 +471,41 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ int old_unoccupied_x = get_unoccupied_x(ws, first->row); /* Convert 0 (for default width_factor) to actual numbers */ - if (first->width_factor == 0) - first->width_factor = ((float)ws->rect.width / ws->cols) / ws->rect.width; - else first->width_factor = ((first->width_factor * old_unoccupied_x) / ws->rect.width); - if (second->width_factor == 0) - second->width_factor = ((float)ws->rect.width / ws->cols) / ws->rect.width; - else second->width_factor = ((second->width_factor * old_unoccupied_x) / ws->rect.width); LOG("\n\n\n"); LOG("old_unoccupied_x = %d\n", old_unoccupied_x); - LOG("Old first->width_factor = %f\n", first->width_factor); - LOG("Old second->width_factor = %f\n", second->width_factor); + LOG("Updating first\n"); - first->width_factor *= (float)(first->width + (new_position - event->root_x)) / first->width; - second->width_factor *= (float)(second->width - (new_position - event->root_x)) / second->width; + /* Set the new width factor on all clients in the column of the first container */ + for (int row = 0; row < ws->rows; row++) { + Container *con = ws->table[first->col][row]; + + if (con->width_factor == 0) + con->width_factor = ((float)ws->rect.width / ws->cols) / ws->rect.width; + else con->width_factor = ((con->width_factor * old_unoccupied_x) / ws->rect.width); + + LOG("Old con(%d,%d)->width_factor = %f\n", first->col, row, con->width_factor); + con->width_factor *= (float)(con->width + (new_position - event->root_x)) / con->width; + LOG("New con(%d,%d)->width_factor = %f\n", first->col, row, con->width_factor); + } + LOG("Updating second\n"); + + /* Set the new width factor on all clients in the column of the second container */ + for (int row = 0; row < ws->rows; row++) { + Container *con = ws->table[second->col][row]; + + if (con->width_factor == 0) + con->width_factor = ((float)ws->rect.width / ws->cols) / ws->rect.width; + else con->width_factor = ((con->width_factor * old_unoccupied_x) / ws->rect.width); + + + LOG("Old con(%d,%d)->width_factor = %f\n", second->col, row, con->width_factor); + con->width_factor *= (float)(con->width - (new_position - event->root_x)) / con->width; + LOG("New con(%d,%d)->width_factor = %f\n", second->col, row, con->width_factor); + } LOG("new unoccupied_x = %d\n", get_unoccupied_x(ws, first->row)); LOG("old_unoccupied_x = %d\n", old_unoccupied_x); From 30b5b80b176cc2a8a0a567e3d032b09879371544 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 8 May 2009 12:09:23 +0200 Subject: [PATCH 011/129] Debian: Update build-dependencies (Thanks Ned), s/Source-Version/binary:Version for debug package --- debian/control | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/control b/debian/control index 5c35c62b..0eb8d470 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Section: utils Priority: optional Maintainer: Michael Stapelberg DM-Upload-Allowed: yes -Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms0-dev (>= 0.3.3), libxcb-xinerama0-dev (>= 1.1), libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc, xmlto, docbook-xml, pkg-config +Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc, xmlto, docbook-xml, pkg-config Standards-Version: 3.8.0 Homepage: http://i3.zekjur.net/ @@ -39,7 +39,7 @@ Package: i3-wm-dbg Architecture: any Priority: optional Section: x11 -Depends: i3-wm (=${Source-Version}) +Depends: i3-wm (=${binary:Version}) Description: Debugging symbols for the i3 window manager Debugging symbols for the i3 window manager. Please install this to produce useful backtraces before creating new tickets. From 89076ea7cf08fbc1d9dfc81b31a7da689bb9db34 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 8 May 2009 23:22:40 +0200 Subject: [PATCH 012/129] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20raise=20clien?= =?UTF-8?q?ts=20in=20fullscreen=20mode,=20send=20correct=20position=20to?= =?UTF-8?q?=20clients=20in=20fullscreen=20mode=20(Thanks=20ch3ka,=20mist)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes #38 which only happened for clients which actively tried to reconfigure themselves. --- src/handlers.c | 10 ++++++++++ src/util.c | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/handlers.c b/src/handlers.c index 61d33135..b16bb7c9 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -590,6 +590,16 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure return 1; } + if (client->fullscreen) { + LOG("Client is in fullscreen mode\n"); + + Rect child_rect = client->container->workspace->rect; + child_rect.x = child_rect.y = 0; + fake_configure_notify(conn, child_rect, client->child); + + return 1; + } + fake_absolute_configure_notify(conn, client); return 1; diff --git a/src/util.c b/src/util.c index 2ac0beb5..d4203261 100644 --- a/src/util.c +++ b/src/util.c @@ -297,7 +297,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) { Client *last_focused = get_last_focused_client(conn, client->container, NULL); /* In stacking containers, raise the client in respect to the one which was focused before */ - if (client->container->mode == MODE_STACK) { + if (client->container->mode == MODE_STACK && client->container->workspace->fullscreen_client == NULL) { /* We need to get the client again, this time excluding the current client, because * we might have just gone into stacking mode and need to raise */ Client *last_focused = get_last_focused_client(conn, client->container, client); From 18da0a301750dc18d063ffeb6f95e484d46fef0d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 May 2009 13:01:23 +0200 Subject: [PATCH 013/129] Bugfix: Force reconfiguration of all windows on workspaces which needed to be re-assigned (Thanks Mirko) When you disable a Xinerama screen (think of removing a video projector), the workspaces of that screen need to be re-assigned to another screen. Previously, the clients affected by this re-assignment did not get re- configured, which made them appear on the next screen which got configured at the position of the old one again if you did not switch to the reassigned workspace before. So, to reproduce it: xrandr --output VGA --mode 1280x1024 --right-of LVDS move windows to the new workspace xrandr --output VGA --off xrandr --output VGA --mode 1280x1024 --right-of LVDS This fixes ticket #36 --- include/layout.h | 6 ++ include/util.h | 11 ++++ src/commands.c | 27 +-------- src/layout.c | 142 ++++++++++++++++++++++++----------------------- src/util.c | 36 ++++++++++++ src/xinerama.c | 38 ++++++++++--- 6 files changed, 157 insertions(+), 103 deletions(-) diff --git a/include/layout.h b/include/layout.h index 40df0ad1..7750e75f 100644 --- a/include/layout.h +++ b/include/layout.h @@ -51,6 +51,12 @@ void render_container(xcb_connection_t *conn, Container *container); */ void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace, bool ignore_enter_notify); +/** + * Renders the given workspace on the given screen + * + */ +void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws); + /** * Renders the whole layout, that is: Go through each screen, each workspace, each container * and render each client. This also renders the bars. diff --git a/include/util.h b/include/util.h index fba1206f..889dcf12 100644 --- a/include/util.h +++ b/include/util.h @@ -137,6 +137,17 @@ void remove_client_from_container(xcb_connection_t *conn, Client *client, Contai */ Client *get_last_focused_client(xcb_connection_t *conn, Container *container, Client *exclude); +/** + * Unmaps all clients (and stack windows) of the given workspace. + * + * This needs to be called separately when temporarily rendering + * a workspace which is not the active workspace to force + * reconfiguration of all clients, like in src/xinerama.c when + * re-assigning a workspace to another screen. + * + */ +void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws); + /** * Sets the given client as focused by updating the data structures correctly, * updating the X input focus and finally re-decorating both windows (to signalize diff --git a/src/commands.c b/src/commands.c index c0f5ffdd..bb8a3a89 100644 --- a/src/commands.c +++ b/src/commands.c @@ -527,30 +527,8 @@ void show_workspace(xcb_connection_t *conn, int workspace) { t_ws->screen->current_workspace = workspace-1; - /* TODO: does grabbing the server actually bring us any (speed)advantages? */ - //xcb_grab_server(conn); - - ignore_enter_notify_forall(conn, c_ws, true); - /* Unmap all clients of the current workspace */ - int unmapped_clients = 0; - FOR_TABLE(c_ws) - CIRCLEQ_FOREACH(client, &(c_ws->table[cols][rows]->clients), clients) { - xcb_unmap_window(conn, client->frame); - unmapped_clients++; - } - - /* If we did not unmap any clients, the workspace is empty and we can destroy it */ - if (unmapped_clients == 0) - c_ws->screen = NULL; - - /* Unmap the stack windows on the current workspace, if any */ - struct Stack_Window *stack_win; - SLIST_FOREACH(stack_win, &stack_wins, stack_windows) - if (stack_win->container->workspace == c_ws) - xcb_unmap_window(conn, stack_win->window); - - ignore_enter_notify_forall(conn, c_ws, false); + unmap_workspace(conn, c_ws); c_ws = &workspaces[workspace-1]; current_row = c_ws->current_row; @@ -565,6 +543,7 @@ void show_workspace(xcb_connection_t *conn, int workspace) { xcb_map_window(conn, client->frame); /* Map all stack windows, if any */ + struct Stack_Window *stack_win; SLIST_FOREACH(stack_win, &stack_wins, stack_windows) if (stack_win->container->workspace == c_ws) xcb_map_window(conn, stack_win->window); @@ -580,8 +559,6 @@ void show_workspace(xcb_connection_t *conn, int workspace) { } } else xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME); - //xcb_ungrab_server(conn); - render_layout(conn); } diff --git a/src/layout.c b/src/layout.c index 033e2a4c..23385987 100644 --- a/src/layout.c +++ b/src/layout.c @@ -480,6 +480,78 @@ void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace, bo } } +/* + * Renders the given workspace on the given screen + * + */ +void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws) { + i3Font *font = load_font(conn, config.font); + int width = r_ws->rect.width; + int height = r_ws->rect.height; + + /* Reserve space for dock clients */ + Client *client; + SLIST_FOREACH(client, &(screen->dock_clients), dock_clients) + height -= client->desired_height; + + /* Space for the internal bar */ + height -= (font->height + 6); + + LOG("got %d rows and %d cols\n", r_ws->rows, r_ws->cols); + + int xoffset[r_ws->rows]; + int yoffset[r_ws->cols]; + /* Initialize offsets */ + for (int cols = 0; cols < r_ws->cols; cols++) + yoffset[cols] = r_ws->rect.y; + for (int rows = 0; rows < r_ws->rows; rows++) + xoffset[rows] = r_ws->rect.x; + + dump_table(conn, r_ws); + + ignore_enter_notify_forall(conn, r_ws, true); + + /* Go through the whole table and render what’s necessary */ + FOR_TABLE(r_ws) { + Container *container = r_ws->table[cols][rows]; + int single_width, single_height; + LOG("\n"); + LOG("========\n"); + LOG("container has %d colspan, %d rowspan\n", + container->colspan, container->rowspan); + LOG("container at %d, %d\n", xoffset[rows], yoffset[cols]); + /* Update position of the container */ + container->row = rows; + container->col = cols; + container->x = xoffset[rows]; + container->y = yoffset[cols]; + + if (container->width_factor == 0) + container->width = (width / r_ws->cols); + else container->width = get_unoccupied_x(r_ws, rows) * container->width_factor; + single_width = container->width; + container->width *= container->colspan; + + if (container->height_factor == 0) + container->height = (height / r_ws->rows); + else container->height = get_unoccupied_y(r_ws, cols) * container->height_factor; + single_height = container->height; + container->height *= container->rowspan; + + /* Render the container if it is not empty */ + render_container(conn, container); + + xoffset[rows] += single_width; + yoffset[cols] += single_height; + LOG("==========\n"); + } + + ignore_enter_notify_forall(conn, r_ws, false); + + render_bars(conn, r_ws, width, &height); + render_internal_bar(conn, r_ws, width, font->height + 6); +} + /* * Renders the whole layout, that is: Go through each screen, each workspace, each container * and render each client. This also renders the bars. @@ -490,78 +562,10 @@ void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace, bo */ void render_layout(xcb_connection_t *conn) { i3Screen *screen; - i3Font *font = load_font(conn, config.font); TAILQ_FOREACH(screen, virtual_screens, screens) { - /* r_ws (rendering workspace) is just a shortcut to the Workspace being currently rendered */ - Workspace *r_ws = &(workspaces[screen->current_workspace]); - LOG("Rendering screen %d\n", screen->num); - - int width = r_ws->rect.width; - int height = r_ws->rect.height; - - /* Reserve space for dock clients */ - Client *client; - SLIST_FOREACH(client, &(screen->dock_clients), dock_clients) - height -= client->desired_height; - - /* Space for the internal bar */ - height -= (font->height + 6); - - LOG("got %d rows and %d cols\n", r_ws->rows, r_ws->cols); - - int xoffset[r_ws->rows]; - int yoffset[r_ws->cols]; - /* Initialize offsets */ - for (int cols = 0; cols < r_ws->cols; cols++) - yoffset[cols] = r_ws->rect.y; - for (int rows = 0; rows < r_ws->rows; rows++) - xoffset[rows] = r_ws->rect.x; - - dump_table(conn, r_ws); - - ignore_enter_notify_forall(conn, r_ws, true); - - /* Go through the whole table and render what’s necessary */ - FOR_TABLE(r_ws) { - Container *container = r_ws->table[cols][rows]; - int single_width, single_height; - LOG("\n"); - LOG("========\n"); - LOG("container has %d colspan, %d rowspan\n", - container->colspan, container->rowspan); - LOG("container at %d, %d\n", xoffset[rows], yoffset[cols]); - /* Update position of the container */ - container->row = rows; - container->col = cols; - container->x = xoffset[rows]; - container->y = yoffset[cols]; - - if (container->width_factor == 0) - container->width = (width / r_ws->cols); - else container->width = get_unoccupied_x(r_ws, rows) * container->width_factor; - single_width = container->width; - container->width *= container->colspan; - - if (container->height_factor == 0) - container->height = (height / r_ws->rows); - else container->height = get_unoccupied_y(r_ws, cols) * container->height_factor; - single_height = container->height; - container->height *= container->rowspan; - - /* Render the container if it is not empty */ - render_container(conn, container); - - xoffset[rows] += single_width; - yoffset[cols] += single_height; - LOG("==========\n"); - } - - ignore_enter_notify_forall(conn, r_ws, false); - - render_bars(conn, r_ws, width, &height); - render_internal_bar(conn, r_ws, width, font->height + 6); + render_workspace(conn, screen, &(workspaces[screen->current_workspace])); } xcb_flush(conn); diff --git a/src/util.c b/src/util.c index d4203261..c0e74c75 100644 --- a/src/util.c +++ b/src/util.c @@ -256,6 +256,42 @@ Client *get_last_focused_client(xcb_connection_t *conn, Container *container, Cl return NULL; } +/* + * Unmaps all clients (and stack windows) of the given workspace. + * + * This needs to be called separately when temporarily rendering + * a workspace which is not the active workspace to force + * reconfiguration of all clients, like in src/xinerama.c when + * re-assigning a workspace to another screen. + * + */ +void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws) { + Client *client; + struct Stack_Window *stack_win; + + /* Ignore notify events because they would cause focus to be changed */ + ignore_enter_notify_forall(conn, u_ws, true); + + /* Unmap all clients of the current workspace */ + int unmapped_clients = 0; + FOR_TABLE(u_ws) + CIRCLEQ_FOREACH(client, &(u_ws->table[cols][rows]->clients), clients) { + xcb_unmap_window(conn, client->frame); + unmapped_clients++; + } + + /* If we did not unmap any clients, the workspace is empty and we can destroy it */ + if (unmapped_clients == 0) + u_ws->screen = NULL; + + /* Unmap the stack windows on the current workspace, if any */ + SLIST_FOREACH(stack_win, &stack_wins, stack_windows) + if (stack_win->container->workspace == u_ws) + xcb_unmap_window(conn, stack_win->window); + + ignore_enter_notify_forall(conn, u_ws, false); +} + /* * Sets the given client as focused by updating the data structures correctly, * updating the X input focus and finally re-decorating both windows (to signalize diff --git a/src/xinerama.c b/src/xinerama.c index 3a1b19a7..818df00a 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -304,16 +304,36 @@ void xinerama_requery_screens(xcb_connection_t *conn) { } /* Check for workspaces which are out of bounds */ - for (int c = 0; c < 10; c++) - if ((workspaces[c].screen != NULL) && - (workspaces[c].screen->num >= num_screens)) { - LOG("Closing bar window\n"); - xcb_destroy_window(conn, workspaces[c].screen->bar); + for (int c = 0; c < 10; c++) { + if ((workspaces[c].screen == NULL) || (workspaces[c].screen->num < num_screens)) + continue; - LOG("Workspace %d's screen out of bounds, assigning to first screen\n", c+1); - workspaces[c].screen = first; - memcpy(&(workspaces[c].rect), &(first->rect), sizeof(Rect)); - } + /* f_ws is a shortcut to the workspace to fix */ + Workspace *f_ws = &(workspaces[c]); + Client *client; + + LOG("Closing bar window\n"); + xcb_destroy_window(conn, f_ws->screen->bar); + + LOG("Workspace %d's screen out of bounds, assigning to first screen\n", c+1); + f_ws->screen = first; + memcpy(&(f_ws->rect), &(first->rect), sizeof(Rect)); + + /* Force reconfiguration for each client on that workspace */ + FOR_TABLE(f_ws) + CIRCLEQ_FOREACH(client, &(f_ws->table[cols][rows]->clients), clients) + client->force_reconfigure = true; + + /* Render the workspace to reconfigure the clients. However, they will be visible now, so… */ + render_workspace(conn, first, f_ws); + + /* …unless we want to see them at the moment, we should hide that workspace */ + if (first->current_workspace == c) + continue; + + unmap_workspace(conn, f_ws); + } + xcb_flush(conn); /* Free the old list */ while (!TAILQ_EMPTY(virtual_screens)) { From 5b4f10eacaf27db8ff9fd88f2a77134cdd0f09cf Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 May 2009 17:48:35 +0200 Subject: [PATCH 014/129] Bugfix: Store width_factor/height_factor per workspace, not per container MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a relatively big change, however all cases should be handled by now. Because the function to do graphical resizing got rather large, I’ve created a new file src/resize.c for it. This fixes ticket #35. --- docs/hacking-howto | 3 + include/commands.h | 4 +- include/data.h | 11 +-- include/layout.h | 4 +- include/resize.h | 27 ++++++ src/handlers.c | 190 +-------------------------------------- src/layout.c | 35 ++++---- src/resize.c | 220 +++++++++++++++++++++++++++++++++++++++++++++ src/table.c | 66 ++++++++++---- 9 files changed, 325 insertions(+), 235 deletions(-) create mode 100644 include/resize.h create mode 100644 src/resize.c diff --git a/docs/hacking-howto b/docs/hacking-howto index ad8b9ab4..72796b0c 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -125,6 +125,9 @@ Renders your layout (screens, workspaces, containers) src/mainx.c:: Initializes the window manager +src/resize.c:: +Contains the functions to resize columns/rows in the table. + src/table.c:: Manages the most important internal data structure, the design table. diff --git a/include/commands.h b/include/commands.h index b060cd10..88c735b9 100644 --- a/include/commands.h +++ b/include/commands.h @@ -8,11 +8,11 @@ * See file LICENSE for license information. * */ -#include - #ifndef _COMMANDS_H #define _COMMANDS_H +#include + bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction); /** Switches to the given workspace */ diff --git a/include/data.h b/include/data.h index e511a7fb..af6b3ef5 100644 --- a/include/data.h +++ b/include/data.h @@ -174,6 +174,12 @@ struct Workspace { /* This is a two-dimensional dynamic array of Container-pointers. I’ve always wanted * to be a three-star programmer :) */ Container ***table; + + /* width_factor and height_factor contain the amount of space (percentage) a column/row + has of all the space which is available for resized windows. This ensures that + non-resized windows (newly opened, for example) have the same size as always */ + float *width_factor; + float *height_factor; }; /* @@ -312,11 +318,6 @@ struct Container { /* Width/Height of the container. Changeable by the user */ int width; int height; - /* width_factor and height_factor contain the amount of space (percentage) a window - has of all the space which is available for resized windows. This ensures that - non-resized windows (newly opened, for example) have the same size as always */ - float width_factor; - float height_factor; /* When in stacking mode, we draw the titlebars of each client onto a separate window */ struct Stack_Window stack_win; diff --git a/include/layout.h b/include/layout.h index 7750e75f..19a40c5d 100644 --- a/include/layout.h +++ b/include/layout.h @@ -15,11 +15,11 @@ /** * Gets the unoccupied space (= space which is available for windows which were resized by the user) - * for the given row. This is necessary to render both, customly resized windows and never touched + * This is necessary to render both, customly resized windows and never touched * windows correctly, meaning that the aspect ratio will be maintained when opening new windows. * */ -int get_unoccupied_x(Workspace *workspace, int row); +int get_unoccupied_x(Workspace *workspace); /** * (Re-)draws window decorations for a given Client onto the given drawable/graphic context. diff --git a/include/resize.h b/include/resize.h new file mode 100644 index 00000000..aefbb005 --- /dev/null +++ b/include/resize.h @@ -0,0 +1,27 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * (c) 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ + +#ifndef _RESIZE_H +#define _RESIZE_H + +#include + +typedef enum { O_HORIZONTAL, O_VERTICAL } resize_orientation_t; + +/** + * Renders the resize window between the first/second container and resizes + * the table column/row. + * + */ +int resize_graphical_handler(xcb_connection_t *conn, Container *first, Container *second, + resize_orientation_t orientation, xcb_button_press_event_t *event); + +#endif diff --git a/src/handlers.c b/src/handlers.c index b16bb7c9..0ce1724c 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -31,6 +31,7 @@ #include "xinerama.h" #include "config.h" #include "queue.h" +#include "resize.h" /* After mapping/unmapping windows, a notify event is generated. However, we don’t want it, since it’d trigger an infinite loop of switching between the different windows when @@ -297,20 +298,15 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ return 1; } - xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; - xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; - /* Set focus in any case */ set_focus(conn, client, true); /* Let’s see if this was on the borders (= resize). If not, we’re done */ LOG("press button on x=%d, y=%d\n", event->event_x, event->event_y); - + resize_orientation_t orientation = O_VERTICAL; Container *con = client->container, *first = NULL, *second = NULL; - enum { O_HORIZONTAL, O_VERTICAL } orientation = O_VERTICAL; - int new_position; if (con == NULL) { LOG("dock. done.\n"); @@ -363,187 +359,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ second = con->workspace->table[con->col+1][con->row]; } - /* FIXME: horizontal resizing causes empty spaces to exist */ - if (orientation == O_HORIZONTAL) { - LOG("Sorry, horizontal resizing is not yet activated due to creating layout bugs." - "If you are brave, enable the code for yourself and try fixing it.\n"); - return 1; - } - - uint32_t mask = 0; - uint32_t values[2]; - - mask = XCB_CW_OVERRIDE_REDIRECT; - values[0] = 1; - - /* Open a new window, the resizebar. Grab the pointer and move the window around - as the user moves the pointer. */ - Rect grabrect = {0, 0, root_screen->width_in_pixels, root_screen->height_in_pixels}; - xcb_window_t grabwin = create_window(conn, grabrect, XCB_WINDOW_CLASS_INPUT_ONLY, -1, mask, values); - - Rect helprect; - if (orientation == O_VERTICAL) { - helprect.x = event->root_x; - helprect.y = 0; - helprect.width = 2; - helprect.height = root_screen->height_in_pixels; /* this has to be the cell’s height */ - new_position = event->root_x; - } else { - helprect.x = 0; - helprect.y = event->root_y; - helprect.width = root_screen->width_in_pixels; /* this has to be the cell’s width */ - helprect.height = 2; - new_position = event->root_y; - } - - mask = XCB_CW_BACK_PIXEL; - values[0] = get_colorpixel(conn, "#4c7899"); - - mask |= XCB_CW_OVERRIDE_REDIRECT; - values[1] = 1; - - xcb_window_t helpwin = create_window(conn, helprect, XCB_WINDOW_CLASS_INPUT_OUTPUT, - (orientation == O_VERTICAL ? - XCB_CURSOR_SB_V_DOUBLE_ARROW : - XCB_CURSOR_SB_H_DOUBLE_ARROW), mask, values); - - xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin); - - xcb_grab_pointer(conn, false, root, XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, - XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, grabwin, XCB_NONE, XCB_CURRENT_TIME); - - xcb_flush(conn); - - xcb_generic_event_t *inside_event; - /* I’ve always wanted to have my own eventhandler… */ - while ((inside_event = xcb_wait_for_event(conn))) { - /* Same as get_event_handler in xcb */ - int nr = inside_event->response_type; - if (nr == 0) { - /* An error occured */ - handle_event(NULL, conn, inside_event); - free(inside_event); - continue; - } - assert(nr < 256); - nr &= XCB_EVENT_RESPONSE_TYPE_MASK; - assert(nr >= 2); - - /* Check if we need to escape this loop */ - if (nr == XCB_BUTTON_RELEASE) - break; - - switch (nr) { - case XCB_MOTION_NOTIFY: - if (orientation == O_VERTICAL) { - values[0] = new_position = ((xcb_motion_notify_event_t*)inside_event)->root_x; - xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_X, values); - } else { - values[0] = new_position = ((xcb_motion_notify_event_t*)inside_event)->root_y; - xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_Y, values); - } - - xcb_flush(conn); - break; - default: - LOG("Passing to original handler\n"); - /* Use original handler */ - xcb_event_handle(&evenths, inside_event); - break; - } - free(inside_event); - } - - xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); - xcb_destroy_window(conn, helpwin); - xcb_destroy_window(conn, grabwin); - xcb_flush(conn); - - Workspace *ws = con->workspace; - if (orientation == O_VERTICAL) { - LOG("Resize was from X = %d to X = %d\n", event->root_x, new_position); - if (event->root_x == new_position) { - LOG("Nothing changed, not updating anything\n"); - return 1; - } - - /* Save the old unoccupied space to re-evaluate the other containers (not first or second) later */ - int old_unoccupied_x = get_unoccupied_x(ws, first->row); - - /* Convert 0 (for default width_factor) to actual numbers */ - - - LOG("\n\n\n"); - - LOG("old_unoccupied_x = %d\n", old_unoccupied_x); - - LOG("Updating first\n"); - - /* Set the new width factor on all clients in the column of the first container */ - for (int row = 0; row < ws->rows; row++) { - Container *con = ws->table[first->col][row]; - - if (con->width_factor == 0) - con->width_factor = ((float)ws->rect.width / ws->cols) / ws->rect.width; - else con->width_factor = ((con->width_factor * old_unoccupied_x) / ws->rect.width); - - LOG("Old con(%d,%d)->width_factor = %f\n", first->col, row, con->width_factor); - con->width_factor *= (float)(con->width + (new_position - event->root_x)) / con->width; - LOG("New con(%d,%d)->width_factor = %f\n", first->col, row, con->width_factor); - } - LOG("Updating second\n"); - - /* Set the new width factor on all clients in the column of the second container */ - for (int row = 0; row < ws->rows; row++) { - Container *con = ws->table[second->col][row]; - - if (con->width_factor == 0) - con->width_factor = ((float)ws->rect.width / ws->cols) / ws->rect.width; - else con->width_factor = ((con->width_factor * old_unoccupied_x) / ws->rect.width); - - - LOG("Old con(%d,%d)->width_factor = %f\n", second->col, row, con->width_factor); - con->width_factor *= (float)(con->width - (new_position - event->root_x)) / con->width; - LOG("New con(%d,%d)->width_factor = %f\n", second->col, row, con->width_factor); - } - - LOG("new unoccupied_x = %d\n", get_unoccupied_x(ws, first->row)); - LOG("old_unoccupied_x = %d\n", old_unoccupied_x); - - for (int col = 0; col < ws->cols; col++) { - Container *con = ws->table[col][first->row]; - if (con == first || con == second) - continue; - - LOG("Updating other container (current width_factor = %f)\n", con->width_factor); - con->width_factor = ((con->width_factor * old_unoccupied_x) / get_unoccupied_x(ws, first->row)); - LOG("to %f\n", con->width_factor); - } - - LOG("New first->width_factor = %f\n", first->width_factor); - LOG("New second->width_factor = %f\n", second->width_factor); - - LOG("\n\n\n"); - } else { - LOG("Resize was from Y = %d to Y = %d\n", event->root_y, new_position); - if (event->root_y == new_position) { - LOG("Nothing changed, not updating anything\n"); - return 1; - } - - /* Convert 0 (for default height_factor) to actual numbers */ - if (first->height_factor == 0) - first->height_factor = ((float)ws->rect.height / ws->rows) / ws->rect.height; - if (second->height_factor == 0) - second->height_factor = ((float)ws->rect.height / ws->rows) / ws->rect.height; - - first->height_factor *= (float)(first->height + (new_position - event->root_y)) / first->height; - second->height_factor *= (float)(second->height - (new_position - event->root_y)) / second->height; - } - - render_layout(conn); - - return 1; + return resize_graphical_handler(conn, first, second, orientation, event); } /* diff --git a/src/layout.c b/src/layout.c index 23385987..1c736c31 100644 --- a/src/layout.c +++ b/src/layout.c @@ -31,7 +31,7 @@ * */ static bool update_if_necessary(uint32_t *destination, const uint32_t new_value) { - int old_value = *destination; + uint32_t old_value = *destination; return ((*destination = new_value) != old_value); } @@ -42,20 +42,17 @@ static bool update_if_necessary(uint32_t *destination, const uint32_t new_value) * windows correctly, meaning that the aspect ratio will be maintained when opening new windows. * */ -int get_unoccupied_x(Workspace *workspace, int row) { +int get_unoccupied_x(Workspace *workspace) { int unoccupied = workspace->rect.width; float default_factor = ((float)workspace->rect.width / workspace->cols) / workspace->rect.width; LOG("get_unoccupied_x(), starting with %d, default_factor = %f\n", unoccupied, default_factor); - for (int cols = 0; cols < workspace->cols;) { - Container *con = workspace->table[cols][row]; - LOG("width_factor[%d][%d] = %f, colspan = %d\n", cols, row, con->width_factor, con->colspan); - if (con->width_factor == 0) { - LOG("- %d * %f * %d = %f\n", workspace->rect.width, default_factor, con->colspan, workspace->rect.width * default_factor * con->colspan); - unoccupied -= workspace->rect.width * default_factor * con->colspan; - } - cols += con->colspan; + for (int cols = 0; cols < workspace->cols; cols++) { + LOG("width_factor[%d] = %f\n", cols, workspace->width_factor[cols]); + + if (workspace->width_factor[cols] == 0) + unoccupied -= workspace->rect.width * default_factor; } LOG("unoccupied space: %d\n", unoccupied); @@ -69,12 +66,10 @@ int get_unoccupied_y(Workspace *workspace, int col) { LOG("get_unoccupied_y(), starting with %d, default_factor = %f\n", unoccupied, default_factor); - for (int rows = 0; rows < workspace->rows;) { - Container *con = workspace->table[col][rows]; - LOG("height_factor[%d][%d] = %f, rowspan %d\n", col, rows, con->height_factor, con->rowspan); - if (con->height_factor == 0) - unoccupied -= workspace->rect.height * default_factor * con->rowspan; - rows += con->rowspan; + for (int rows = 0; rows < workspace->rows; rows++) { + LOG("height_factor[%d] = %f\n", rows, workspace->height_factor[rows]); + if (workspace->height_factor[rows] == 0) + unoccupied -= workspace->rect.height * default_factor; } LOG("unoccupied space: %d\n", unoccupied); @@ -526,15 +521,15 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws) container->x = xoffset[rows]; container->y = yoffset[cols]; - if (container->width_factor == 0) + if (r_ws->width_factor[cols] == 0) container->width = (width / r_ws->cols); - else container->width = get_unoccupied_x(r_ws, rows) * container->width_factor; + else container->width = get_unoccupied_x(r_ws) * r_ws->width_factor[cols]; single_width = container->width; container->width *= container->colspan; - if (container->height_factor == 0) + //if (container->height_factor == 0) container->height = (height / r_ws->rows); - else container->height = get_unoccupied_y(r_ws, cols) * container->height_factor; + //else container->height = get_unoccupied_y(r_ws, cols) * container->height_factor; single_height = container->height; container->height *= container->rowspan; diff --git a/src/resize.c b/src/resize.c new file mode 100644 index 00000000..c54965d9 --- /dev/null +++ b/src/resize.c @@ -0,0 +1,220 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + * This file contains the functions for resizing table columns/rows because + * it’s actually lots of work, compared to the other handlers. + * + */ +#include +#include + +#include +#include + +#include "i3.h" +#include "data.h" +#include "resize.h" +#include "util.h" +#include "xcb.h" +#include "debug.h" +#include "layout.h" + +/* + * Renders the resize window between the first/second container and resizes + * the table column/row. + * + */ +int resize_graphical_handler(xcb_connection_t *conn, Container *first, Container *second, + resize_orientation_t orientation, xcb_button_press_event_t *event) { + int new_position; + xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; + xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; + + /* FIXME: horizontal resizing causes empty spaces to exist */ + if (orientation == O_HORIZONTAL) { + LOG("Sorry, horizontal resizing is not yet activated due to creating layout bugs." + "If you are brave, enable the code for yourself and try fixing it.\n"); + return 1; + } + + uint32_t mask = 0; + uint32_t values[2]; + + mask = XCB_CW_OVERRIDE_REDIRECT; + values[0] = 1; + + /* Open a new window, the resizebar. Grab the pointer and move the window around + as the user moves the pointer. */ + Rect grabrect = {0, 0, root_screen->width_in_pixels, root_screen->height_in_pixels}; + xcb_window_t grabwin = create_window(conn, grabrect, XCB_WINDOW_CLASS_INPUT_ONLY, -1, mask, values); + + Rect helprect; + if (orientation == O_VERTICAL) { + helprect.x = event->root_x; + helprect.y = 0; + helprect.width = 2; + helprect.height = root_screen->height_in_pixels; + new_position = event->root_x; + } else { + helprect.x = 0; + helprect.y = event->root_y; + helprect.width = root_screen->width_in_pixels; + helprect.height = 2; + new_position = event->root_y; + } + + mask = XCB_CW_BACK_PIXEL; + values[0] = get_colorpixel(conn, "#4c7899"); + + mask |= XCB_CW_OVERRIDE_REDIRECT; + values[1] = 1; + + xcb_window_t helpwin = create_window(conn, helprect, XCB_WINDOW_CLASS_INPUT_OUTPUT, + (orientation == O_VERTICAL ? + XCB_CURSOR_SB_V_DOUBLE_ARROW : + XCB_CURSOR_SB_H_DOUBLE_ARROW), mask, values); + + xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin); + + xcb_grab_pointer(conn, false, root, XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, + XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, grabwin, XCB_NONE, XCB_CURRENT_TIME); + + xcb_flush(conn); + + xcb_generic_event_t *inside_event; + /* I’ve always wanted to have my own eventhandler… */ + while ((inside_event = xcb_wait_for_event(conn))) { + /* Same as get_event_handler in xcb */ + int nr = inside_event->response_type; + if (nr == 0) { + /* An error occured */ + handle_event(NULL, conn, inside_event); + free(inside_event); + continue; + } + assert(nr < 256); + nr &= XCB_EVENT_RESPONSE_TYPE_MASK; + assert(nr >= 2); + + /* Check if we need to escape this loop */ + if (nr == XCB_BUTTON_RELEASE) + break; + + switch (nr) { + case XCB_MOTION_NOTIFY: + if (orientation == O_VERTICAL) { + values[0] = new_position = ((xcb_motion_notify_event_t*)inside_event)->root_x; + xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_X, values); + } else { + values[0] = new_position = ((xcb_motion_notify_event_t*)inside_event)->root_y; + xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_Y, values); + } + + xcb_flush(conn); + break; + default: + LOG("Passing to original handler\n"); + /* Use original handler */ + xcb_event_handle(&evenths, inside_event); + break; + } + free(inside_event); + } + + xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); + xcb_destroy_window(conn, helpwin); + xcb_destroy_window(conn, grabwin); + xcb_flush(conn); + + Workspace *ws = first->workspace; + if (orientation == O_VERTICAL) { + LOG("Resize was from X = %d to X = %d\n", event->root_x, new_position); + if (event->root_x == new_position) { + LOG("Nothing changed, not updating anything\n"); + return 1; + } + + int default_width = ws->rect.width / ws->cols; + int old_unoccupied_x = get_unoccupied_x(ws); + + /* We pre-calculate the unoccupied space to see if we need to adapt sizes before + * doing the resize */ + int new_unoccupied_x = old_unoccupied_x; + + if (old_unoccupied_x == 0) + old_unoccupied_x = ws->rect.width; + + if (ws->width_factor[first->col] == 0) + new_unoccupied_x += default_width; + + if (ws->width_factor[second->col] == 0) + new_unoccupied_x += default_width; + + LOG("\n\n\n"); + LOG("old = %d, new = %d\n", old_unoccupied_x, new_unoccupied_x); + + /* If the space used for customly resized columns has changed we need to adapt the + * other customly resized columns, if any */ + if (new_unoccupied_x != old_unoccupied_x) + for (int col = 0; col < ws->cols; col++) { + if (ws->width_factor[col] == 0) + continue; + + LOG("Updating other column (%d) (current width_factor = %f)\n", col, ws->width_factor[col]); + ws->width_factor[col] = (ws->width_factor[col] * old_unoccupied_x) / new_unoccupied_x; + LOG("to %f\n", ws->width_factor[col]); + } + + LOG("old_unoccupied_x = %d\n", old_unoccupied_x); + + LOG("Updating first (before = %f)\n", ws->width_factor[first->col]); + /* Convert 0 (for default width_factor) to actual numbers */ + if (ws->width_factor[first->col] == 0) + ws->width_factor[first->col] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x; + + LOG("middle = %f\n", ws->width_factor[first->col]); + LOG("first->width = %d, new_position = %d, event->root_x = %d\n", first->width, new_position, event->root_x); + ws->width_factor[first->col] *= (float)(first->width + (new_position - event->root_x)) / first->width; + LOG("-> %f\n", ws->width_factor[first->col]); + + + LOG("Updating second (before = %f)\n", ws->width_factor[second->col]); + if (ws->width_factor[second->col] == 0) + ws->width_factor[second->col] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x; + LOG("middle = %f\n", ws->width_factor[second->col]); + LOG("second->width = %d, new_position = %d, event->root_x = %d\n", second->width, new_position, event->root_x); + ws->width_factor[second->col] *= (float)(second->width - (new_position - event->root_x)) / second->width; + LOG("-> %f\n", ws->width_factor[second->col]); + + LOG("new unoccupied_x = %d\n", get_unoccupied_x(ws)); + + LOG("\n\n\n"); + } else { +#if 0 + LOG("Resize was from Y = %d to Y = %d\n", event->root_y, new_position); + if (event->root_y == new_position) { + LOG("Nothing changed, not updating anything\n"); + return 1; + } + + /* Convert 0 (for default height_factor) to actual numbers */ + if (first->height_factor == 0) + first->height_factor = ((float)ws->rect.height / ws->rows) / ws->rect.height; + if (second->height_factor == 0) + second->height_factor = ((float)ws->rect.height / ws->rows) / ws->rect.height; + + first->height_factor *= (float)(first->height + (new_position - event->root_y)) / first->height; + second->height_factor *= (float)(second->height - (new_position - event->root_y)) / second->height; +#endif + } + + render_layout(conn); + + return 1; +} diff --git a/src/table.c b/src/table.c index 27c4af47..20b88cec 100644 --- a/src/table.c +++ b/src/table.c @@ -24,6 +24,7 @@ #include "table.h" #include "util.h" #include "i3.h" +#include "layout.h" int current_workspace = 0; Workspace workspaces[10]; @@ -65,6 +66,9 @@ static void new_container(Workspace *workspace, Container **container, int col, void expand_table_rows(Workspace *workspace) { workspace->rows++; + workspace->height_factor = realloc(workspace->height_factor, sizeof(float) * workspace->rows); + workspace->height_factor[workspace->rows-1] = 0; + for (int c = 0; c < workspace->cols; c++) { workspace->table[c] = realloc(workspace->table[c], sizeof(Container*) * workspace->rows); new_container(workspace, &(workspace->table[c][workspace->rows-1]), c, workspace->rows-1); @@ -78,6 +82,16 @@ void expand_table_rows(Workspace *workspace) { void expand_table_rows_at_head(Workspace *workspace) { workspace->rows++; + workspace->height_factor = realloc(workspace->height_factor, sizeof(float) * workspace->rows); + + LOG("rows = %d\n", workspace->rows); + for (int rows = (workspace->rows - 1); rows >= 1; rows--) { + LOG("Moving height_factor %d (%f) to %d\n", rows-1, workspace->height_factor[rows-1], rows); + workspace->height_factor[rows] = workspace->height_factor[rows-1]; + } + + workspace->height_factor[0] = 0; + for (int cols = 0; cols < workspace->cols; cols++) workspace->table[cols] = realloc(workspace->table[cols], sizeof(Container*) * workspace->rows); @@ -88,6 +102,7 @@ void expand_table_rows_at_head(Workspace *workspace) { workspace->table[cols][rows] = workspace->table[cols][rows-1]; workspace->table[cols][rows]->row = rows; } + for (int cols = 0; cols < workspace->cols; cols++) new_container(workspace, &(workspace->table[cols][0]), cols, 0); } @@ -99,6 +114,9 @@ void expand_table_rows_at_head(Workspace *workspace) { void expand_table_cols(Workspace *workspace) { workspace->cols++; + workspace->width_factor = realloc(workspace->width_factor, sizeof(float) * workspace->cols); + workspace->width_factor[workspace->cols-1] = 0; + workspace->table = realloc(workspace->table, sizeof(Container**) * workspace->cols); workspace->table[workspace->cols-1] = calloc(sizeof(Container*) * workspace->rows, 1); for (int c = 0; c < workspace->rows; c++) @@ -112,6 +130,16 @@ void expand_table_cols(Workspace *workspace) { void expand_table_cols_at_head(Workspace *workspace) { workspace->cols++; + workspace->width_factor = realloc(workspace->width_factor, sizeof(float) * workspace->cols); + + LOG("cols = %d\n", workspace->cols); + for (int cols = (workspace->cols - 1); cols >= 1; cols--) { + LOG("Moving width_factor %d (%f) to %d\n", cols-1, workspace->width_factor[cols-1], cols); + workspace->width_factor[cols] = workspace->width_factor[cols-1]; + } + + workspace->width_factor[0] = 0; + workspace->table = realloc(workspace->table, sizeof(Container**) * workspace->cols); workspace->table[workspace->cols-1] = calloc(sizeof(Container*) * workspace->rows, 1); @@ -136,13 +164,32 @@ void expand_table_cols_at_head(Workspace *workspace) { * */ static void shrink_table_cols(Workspace *workspace) { + float free_space = workspace->width_factor[workspace->cols-1]; + workspace->cols--; + /* Shrink the width_factor array */ + workspace->width_factor = realloc(workspace->width_factor, sizeof(float) * workspace->cols); + /* Free the container-pointers */ free(workspace->table[workspace->cols]); /* Re-allocate the table */ workspace->table = realloc(workspace->table, sizeof(Container**) * workspace->cols); + + /* Distribute the free space */ + if (free_space == 0) + return; + + for (int cols = (workspace->cols-1); cols >= 0; cols--) { + if (workspace->width_factor[cols] == 0) + continue; + + LOG("Added free space (%f) to %d (had %f)\n", free_space, cols, + workspace->width_factor[cols]); + workspace->width_factor[cols] += free_space; + break; + } } /* @@ -171,25 +218,6 @@ static void free_container(xcb_connection_t *conn, Workspace *workspace, int col if (old_container->mode == MODE_STACK) leave_stack_mode(conn, old_container); - /* We need to distribute the space which will now be freed to other containers */ - if (old_container->width_factor > 0) { - Container *dest_container = NULL; - /* Check if we got a container to the left… */ - if (col > 0) - dest_container = workspace->table[col-1][row]; - /* …or to the right */ - else if ((col+1) < workspace->cols) - dest_container = workspace->table[col+1][row]; - - if (dest_container != NULL) { - if (dest_container->width_factor == 0) - dest_container->width_factor = ((float)workspace->rect.width / workspace->cols) / workspace->rect.width; - LOG("dest_container->width_factor = %f\n", dest_container->width_factor); - dest_container->width_factor += old_container->width_factor; - LOG("afterwards it's %f\n", dest_container->width_factor); - } - } - free(old_container); } From 1256730b4bd5735b14223fd82b3b6d4d882842a7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 May 2009 18:43:02 +0200 Subject: [PATCH 015/129] Bugfix: Fix display/resizing of colspanned containers --- include/resize.h | 2 +- src/handlers.c | 34 ++++++++++++++++++++-------------- src/layout.c | 10 ++++++---- src/resize.c | 37 +++++++++++++++++++------------------ 4 files changed, 46 insertions(+), 37 deletions(-) diff --git a/include/resize.h b/include/resize.h index aefbb005..520d4398 100644 --- a/include/resize.h +++ b/include/resize.h @@ -21,7 +21,7 @@ typedef enum { O_HORIZONTAL, O_VERTICAL } resize_orientation_t; * the table column/row. * */ -int resize_graphical_handler(xcb_connection_t *conn, Container *first, Container *second, +int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, int second, resize_orientation_t orientation, xcb_button_press_event_t *event); #endif diff --git a/src/handlers.c b/src/handlers.c index 0ce1724c..7652f07b 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -304,9 +304,8 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ /* Let’s see if this was on the borders (= resize). If not, we’re done */ LOG("press button on x=%d, y=%d\n", event->event_x, event->event_y); resize_orientation_t orientation = O_VERTICAL; - Container *con = client->container, - *first = NULL, - *second = NULL; + Container *con = client->container; + int first, second; if (con == NULL) { LOG("dock. done.\n"); @@ -335,31 +334,38 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ /* This was a press on the top border */ if (con->row == 0) return 1; - first = con->workspace->table[con->col][con->row-1]; - second = con; + first = con->row - 1; + second = con->row; orientation = O_HORIZONTAL; } else if (event->event_y >= (client->rect.height - 2)) { /* …bottom border */ - if (con->row == (con->workspace->rows-1)) + first = con->row + (con->rowspan - 1); + if (!cell_exists(con->col, first) || + (first == (con->workspace->rows-1))) return 1; - first = con; - second = con->workspace->table[con->col][con->row+1]; + + second = first + 1; orientation = O_HORIZONTAL; } else if (event->event_x <= 2) { /* …left border */ if (con->col == 0) return 1; - first = con->workspace->table[con->col-1][con->row]; - second = con; + + first = con->col - 1; + second = con->col; } else if (event->event_x > 2) { /* …right border */ - if (con->col == (con->workspace->cols-1)) + first = con->col + (con->colspan - 1); + LOG("column %d\n", first); + + if (!cell_exists(first, con->row) || + (first == (con->workspace->cols-1))) return 1; - first = con; - second = con->workspace->table[con->col+1][con->row]; + + second = first + 1; } - return resize_graphical_handler(conn, first, second, orientation, event); + return resize_graphical_handler(conn, con->workspace, first, second, orientation, event); } /* diff --git a/src/layout.c b/src/layout.c index 1c736c31..83cc9ff3 100644 --- a/src/layout.c +++ b/src/layout.c @@ -520,12 +520,14 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws) container->col = cols; container->x = xoffset[rows]; container->y = yoffset[cols]; + container->width = 0; - if (r_ws->width_factor[cols] == 0) - container->width = (width / r_ws->cols); - else container->width = get_unoccupied_x(r_ws) * r_ws->width_factor[cols]; + for (int c = 0; c < container->colspan; c++) { + if (r_ws->width_factor[cols+c] == 0) + container->width += (width / r_ws->cols); + else container->width += get_unoccupied_x(r_ws) * r_ws->width_factor[cols+c]; + } single_width = container->width; - container->width *= container->colspan; //if (container->height_factor == 0) container->height = (height / r_ws->rows); diff --git a/src/resize.c b/src/resize.c index c54965d9..9c6cbab4 100644 --- a/src/resize.c +++ b/src/resize.c @@ -30,7 +30,7 @@ * the table column/row. * */ -int resize_graphical_handler(xcb_connection_t *conn, Container *first, Container *second, +int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, int second, resize_orientation_t orientation, xcb_button_press_event_t *event) { int new_position; xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; @@ -132,7 +132,6 @@ int resize_graphical_handler(xcb_connection_t *conn, Container *first, Container xcb_destroy_window(conn, grabwin); xcb_flush(conn); - Workspace *ws = first->workspace; if (orientation == O_VERTICAL) { LOG("Resize was from X = %d to X = %d\n", event->root_x, new_position); if (event->root_x == new_position) { @@ -150,10 +149,10 @@ int resize_graphical_handler(xcb_connection_t *conn, Container *first, Container if (old_unoccupied_x == 0) old_unoccupied_x = ws->rect.width; - if (ws->width_factor[first->col] == 0) + if (ws->width_factor[first] == 0) new_unoccupied_x += default_width; - if (ws->width_factor[second->col] == 0) + if (ws->width_factor[second] == 0) new_unoccupied_x += default_width; LOG("\n\n\n"); @@ -173,24 +172,26 @@ int resize_graphical_handler(xcb_connection_t *conn, Container *first, Container LOG("old_unoccupied_x = %d\n", old_unoccupied_x); - LOG("Updating first (before = %f)\n", ws->width_factor[first->col]); + LOG("Updating first (before = %f)\n", ws->width_factor[first]); /* Convert 0 (for default width_factor) to actual numbers */ - if (ws->width_factor[first->col] == 0) - ws->width_factor[first->col] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x; + if (ws->width_factor[first] == 0) + ws->width_factor[first] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x; - LOG("middle = %f\n", ws->width_factor[first->col]); - LOG("first->width = %d, new_position = %d, event->root_x = %d\n", first->width, new_position, event->root_x); - ws->width_factor[first->col] *= (float)(first->width + (new_position - event->root_x)) / first->width; - LOG("-> %f\n", ws->width_factor[first->col]); + LOG("middle = %f\n", ws->width_factor[first]); + int old_width = ws->width_factor[first] * old_unoccupied_x; + LOG("first->width = %d, new_position = %d, event->root_x = %d\n", old_width, new_position, event->root_x); + ws->width_factor[first] *= (float)(old_width + (new_position - event->root_x)) / old_width; + LOG("-> %f\n", ws->width_factor[first]); - LOG("Updating second (before = %f)\n", ws->width_factor[second->col]); - if (ws->width_factor[second->col] == 0) - ws->width_factor[second->col] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x; - LOG("middle = %f\n", ws->width_factor[second->col]); - LOG("second->width = %d, new_position = %d, event->root_x = %d\n", second->width, new_position, event->root_x); - ws->width_factor[second->col] *= (float)(second->width - (new_position - event->root_x)) / second->width; - LOG("-> %f\n", ws->width_factor[second->col]); + LOG("Updating second (before = %f)\n", ws->width_factor[second]); + if (ws->width_factor[second] == 0) + ws->width_factor[second] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x; + LOG("middle = %f\n", ws->width_factor[second]); + old_width = ws->width_factor[second] * old_unoccupied_x; + LOG("second->width = %d, new_position = %d, event->root_x = %d\n", old_width, new_position, event->root_x); + ws->width_factor[second] *= (float)(old_width - (new_position - event->root_x)) / old_width; + LOG("-> %f\n", ws->width_factor[second]); LOG("new unoccupied_x = %d\n", get_unoccupied_x(ws)); From b7f94e9b5df4044f78901c5375886c251485bfda Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 May 2009 19:13:14 +0200 Subject: [PATCH 016/129] Fix a bug in window placement introduced by the last commit --- src/layout.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/layout.c b/src/layout.c index 83cc9ff3..ea9ff4f2 100644 --- a/src/layout.c +++ b/src/layout.c @@ -509,7 +509,7 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws) /* Go through the whole table and render what’s necessary */ FOR_TABLE(r_ws) { Container *container = r_ws->table[cols][rows]; - int single_width, single_height; + int single_width = -1, single_height; LOG("\n"); LOG("========\n"); LOG("container has %d colspan, %d rowspan\n", @@ -526,8 +526,10 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws) if (r_ws->width_factor[cols+c] == 0) container->width += (width / r_ws->cols); else container->width += get_unoccupied_x(r_ws) * r_ws->width_factor[cols+c]; + + if (single_width == -1) + single_width = container->width; } - single_width = container->width; //if (container->height_factor == 0) container->height = (height / r_ws->rows); From 610fabf873face907cbce61ba38981fb942d65ec Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 May 2009 19:59:36 +0200 Subject: [PATCH 017/129] Bugfix: Correctly handle col-/rowspanned containers when setting focus (Thanks Ned) This fixes ticket #34 --- src/commands.c | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/commands.c b/src/commands.c index bb8a3a89..553d347d 100644 --- a/src/commands.c +++ b/src/commands.c @@ -77,10 +77,20 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t return; if (direction == D_DOWN && cell_exists(current_col, current_row+1)) - new_row++; - else if (direction == D_UP && cell_exists(current_col, current_row-1)) + new_row = current_row + t_ws->table[current_col][current_row]->rowspan; + else if (direction == D_UP && cell_exists(current_col, current_row-1)) { + /* Set new_row as a sane default, but it may get overwritten in a second */ new_row--; - else { + + /* Search from the top to correctly handle rowspanned containers */ + for (int rows = 0; rows < current_row; rows += t_ws->table[current_col][rows]->rowspan) { + if (new_row > (rows + (t_ws->table[current_col][rows]->rowspan - 1))) + continue; + + new_row = rows; + break; + } + } else { /* Let’s see if there is a screen down/up there to which we can switch */ LOG("container is at %d with height %d\n", container->y, container->height); i3Screen *screen; @@ -95,10 +105,20 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t } } else if (direction == D_LEFT || direction == D_RIGHT) { if (direction == D_RIGHT && cell_exists(current_col+1, current_row)) - new_col++; - else if (direction == D_LEFT && cell_exists(current_col-1, current_row)) + new_col = current_col + t_ws->table[current_col][current_row]->colspan; + else if (direction == D_LEFT && cell_exists(current_col-1, current_row)) { + /* Set new_col as a sane default, but it may get overwritten in a second */ new_col--; - else { + + /* Search from the left to correctly handle colspanned containers */ + for (int cols = 0; cols < current_col; cols += t_ws->table[cols][current_row]->colspan) { + if (new_col > (cols + (t_ws->table[cols][current_row]->colspan - 1))) + continue; + + new_col = cols; + break; + } + } else { /* Let’s see if there is a screen left/right here to which we can switch */ LOG("container is at %d with width %d\n", container->x, container->width); i3Screen *screen; From 1582e5f86c69d90709fef9e25ed83ca180205c1a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 May 2009 20:14:17 +0200 Subject: [PATCH 018/129] debian: As asciidoc arrived in testing, we can require a higher version --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 0eb8d470..ff3ec52f 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Section: utils Priority: optional Maintainer: Michael Stapelberg DM-Upload-Allowed: yes -Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc, xmlto, docbook-xml, pkg-config +Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4-1), xmlto, docbook-xml, pkg-config Standards-Version: 3.8.0 Homepage: http://i3.zekjur.net/ From 6f55cf840e85dfcf62ef0c4debd708b06e91a329 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 May 2009 20:18:41 +0200 Subject: [PATCH 019/129] debian: update changelog --- debian/changelog | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 2c21abc0..e607d61d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,10 +1,15 @@ i3-wm (3.b-1) unstable; urgency=low + * Bugfix: Correctly handle col-/rowspanned containers when setting focus + * Bugfix: Force reconfiguration of all windows on workspaces which are + re-assigned because a screen was detached + * Bugfix: Several bugs in resizing table columns fixed * Implement jumping to other windows by specifying their position or window class/title * Implement jumping back by using the focus stack + * Implement autostart (exec-command in configuration file) - -- Michael Stapelberg Tue, 05 May 2009 17:29:07 +0200 + -- Michael Stapelberg Sat, 09 May 2009 20:17:58 +0200 i3-wm (3.a-bf2-1) unstable; urgency=low From 3ab4ecdb019b7d27a3e3d9f0f29c687c1b067f50 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 9 May 2009 23:16:29 +0200 Subject: [PATCH 020/129] debian: Fix section/priority/dependencies of i3-wm-dbg (Thanks chrish) --- debian/control | 6 +++--- debian/rules | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/debian/control b/debian/control index ff3ec52f..e0807987 100644 --- a/debian/control +++ b/debian/control @@ -37,9 +37,9 @@ Description: an improved dynamic tiling window manager Package: i3-wm-dbg Architecture: any -Priority: optional -Section: x11 -Depends: i3-wm (=${binary:Version}) +Priority: extra +Section: debug +Depends: i3-wm (=${binary:Version}), ${misc:Depends} Description: Debugging symbols for the i3 window manager Debugging symbols for the i3 window manager. Please install this to produce useful backtraces before creating new tickets. diff --git a/debian/rules b/debian/rules index e6e04df0..78eba656 100755 --- a/debian/rules +++ b/debian/rules @@ -38,7 +38,7 @@ install: build dh_clean -k dh_installdirs - # Add here commands to install the package into debian/wiipdf + # 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 From e79cca8f725284dfb2d15fdb7a57419029eb023e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 16 May 2009 17:32:36 +0200 Subject: [PATCH 021/129] Implement putting clients onto specific workspaces ("assign" in the configfile) This closes ticket #39 --- docs/Makefile | 4 +- docs/userguide | 59 ++++++++++++++++++++ include/client.h | 47 ++++++++++++++++ include/data.h | 12 +++- include/i3.h | 1 + include/util.h | 21 ++----- src/client.c | 133 ++++++++++++++++++++++++++++++++++++++++++++ src/commands.c | 82 ++++++--------------------- src/config.c | 40 ++++++++++++- src/handlers.c | 3 +- src/mainx.c | 142 ++++++++++++++++++++++++++++++++++------------- src/util.c | 112 ++++++++++++++----------------------- 12 files changed, 461 insertions(+), 195 deletions(-) create mode 100644 docs/userguide create mode 100644 include/client.h create mode 100644 src/client.c diff --git a/docs/Makefile b/docs/Makefile index e69aefc5..123f839f 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,5 +1,5 @@ -all: hacking-howto.html debugging.html +all: hacking-howto.html debugging.html userguide.html hacking-howto.html: hacking-howto asciidoc -a toc -n $< @@ -7,6 +7,8 @@ hacking-howto.html: hacking-howto debugging.html: debugging asciidoc -n $< +userguide.html: userguide + asciidoc -a toc -n $< clean: rm -f */*.{aux,log,toc,bm,pdf,dvi} diff --git a/docs/userguide b/docs/userguide new file mode 100644 index 00000000..36c2af4b --- /dev/null +++ b/docs/userguide @@ -0,0 +1,59 @@ +i3 User’s Guide +=============== +Michael Stapelberg +May 2009 + +This document contains all information you need to configuring and using the i3 window +manager. If it does not, please contact me on IRC, Jabber or E-Mail and I’ll help you out. + +== Configuring i3 + +TODO: document the other options, implement variables before + +terminal:: + Specifies the terminal emulator program you prefer. It will be started by default when + you press Mod1+Enter, but you can overwrite this. Refer to it as +$terminal+ to keep things + modular. +font:: + Specifies the default font you want i3 to use. Use an X core font descriptor here, like + +-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1+. You can use +xfontsel(1)+ + to pick one. + +=== Keyboard bindings + +TODO + +*Syntax*: +-------------------------------- +bind [Modifiers+]keycode command +-------------------------------- + +*Examples*: +-------------------------------- +# Fullscreen +bind Mod1+41 f + +# Restart +bind Mod1+Shift+27 restart +-------------------------------- + +=== Automatically putting clients on specific workspaces + +It is recommended that you match on window classes whereever possible because some applications +first create their window and then care about setting the correct title. Firefox with Vimperator +comes to mind, as the window starts up being named Firefox and only when Vimperator is loaded, +the title changes. As i3 will get the title as soon as the application maps the window (mapping +means actually displaying it on the screen), you’d need to have to match on Firefox in this case. + +*Syntax*: +---------------------------------------------------- +assign ["]window class[/window title]["] [→] workspace +---------------------------------------------------- + +*Examples*: +---------------------- +assign urxvt 2 +assign urxvt → 2 +assign "urxvt" → 2 +assign "urxvt/VIM" → 3 +---------------------- diff --git a/include/client.h b/include/client.h new file mode 100644 index 00000000..a88f8d0b --- /dev/null +++ b/include/client.h @@ -0,0 +1,47 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * (c) 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ +#include + +#include "data.h" + +#ifndef _CLIENT_H +#define _CLIENT_H + +/** + * Removes the given client from the container, either because it will be inserted into another + * one or because it was unmapped + * + */ +void client_remove_from_container(xcb_connection_t *conn, Client *client, Container *container); + +/** + * Warps the pointer into the given client (in the middle of it, to be specific), therefore + * selecting it + * + */ +void client_warp_pointer_into(xcb_connection_t *conn, Client *client); + +/** + * Kills the given window using WM_DELETE_WINDOW or xcb_kill_window + * + */ +void client_kill(xcb_connection_t *conn, Client *window); + +/** + * Checks if the given window class and title match the given client + * Window title is passed as "normal" string and as UCS-2 converted string for + * matching _NET_WM_NAME capable clients as well as those using legacy hints. + * + */ +bool client_matches_class_name(Client *client, char *to_class, char *to_title, + char *to_title_ucs, int to_title_ucs_len); + +#endif diff --git a/include/data.h b/include/data.h index af6b3ef5..8cb60b8f 100644 --- a/include/data.h +++ b/include/data.h @@ -48,7 +48,6 @@ typedef struct Font i3Font; typedef struct Container Container; typedef struct Client Client; typedef struct Binding Binding; -typedef struct Autostart Autostart; typedef struct Workspace Workspace; typedef struct Rect Rect; typedef struct Screen i3Screen; @@ -208,6 +207,17 @@ struct Autostart { TAILQ_ENTRY(Autostart) autostarts; }; +/* + * Holds an assignment for a given window class/title to a specific workspace + * (see src/config.c) + * + */ +struct Assignment { + char *windowclass_title; + int workspace; + TAILQ_ENTRY(Assignment) assignments; +}; + /* * Data structure for cached font information: * - font id in X11 (load it once) diff --git a/include/i3.h b/include/i3.h index 1c9e1b35..06f423a0 100644 --- a/include/i3.h +++ b/include/i3.h @@ -26,6 +26,7 @@ extern char **start_argv; extern Display *xkbdpy; extern TAILQ_HEAD(bindings_head, Binding) bindings; extern TAILQ_HEAD(autostarts_head, Autostart) autostarts; +extern TAILQ_HEAD(assignments_head, Assignment) assignments; extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins; extern xcb_event_handlers_t evenths; extern int num_screens; diff --git a/include/util.h b/include/util.h index 889dcf12..709e48a4 100644 --- a/include/util.h +++ b/include/util.h @@ -123,13 +123,6 @@ void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_mes */ char *convert_utf8_to_ucs2(char *input, int *real_strlen); -/** - * Removes the given client from the container, either because it will be inserted into another - * one or because it was unmapped - * - */ -void remove_client_from_container(xcb_connection_t *conn, Client *client, Container *container); - /** * Returns the client which comes next in focus stack (= was selected before) for * the given container, optionally excluding the given client. @@ -169,13 +162,6 @@ void leave_stack_mode(xcb_connection_t *conn, Container *container); */ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode); -/** - * Warps the pointer into the given client (in the middle of it, to be specific), therefore - * selecting it - * - */ -void warp_pointer_into(xcb_connection_t *conn, Client *client); - /** * Toggles fullscreen mode for the given client. It updates the data structures and * reconfigures (= resizes/moves) the client and its frame to the full size of the @@ -185,9 +171,12 @@ void warp_pointer_into(xcb_connection_t *conn, Client *client); void toggle_fullscreen(xcb_connection_t *conn, Client *client); /** - * Kills the given window using WM_DELETE_WINDOW or xcb_kill_window + * Gets the first matching client for the given window class/window title. + * If the paramater specific is set to a specific client, only this one + * will be checked. * */ -void kill_window(xcb_connection_t *conn, Client *window); +Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitle, + Client *specific); #endif diff --git a/src/client.c b/src/client.c new file mode 100644 index 00000000..f5409d09 --- /dev/null +++ b/src/client.c @@ -0,0 +1,133 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + * client.c: holds all client-specific functions + * + */ +#include +#include + +#include +#include + +#include "data.h" +#include "i3.h" +#include "xcb.h" +#include "util.h" +#include "queue.h" + +/* + * Removes the given client from the container, either because it will be inserted into another + * one or because it was unmapped + * + */ +void client_remove_from_container(xcb_connection_t *conn, Client *client, Container *container) { + CIRCLEQ_REMOVE(&(container->clients), client, clients); + + SLIST_REMOVE(&(container->workspace->focus_stack), client, Client, focus_clients); + + /* If the container will be empty now and is in stacking mode, we need to + unmap the stack_win */ + if (CIRCLEQ_EMPTY(&(container->clients)) && container->mode == MODE_STACK) { + struct Stack_Window *stack_win = &(container->stack_win); + stack_win->rect.height = 0; + xcb_unmap_window(conn, stack_win->window); + } +} + +/* + * Warps the pointer into the given client (in the middle of it, to be specific), therefore + * selecting it + * + */ +void client_warp_pointer_into(xcb_connection_t *conn, Client *client) { + int mid_x = client->rect.width / 2, + mid_y = client->rect.height / 2; + xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, mid_x, mid_y); +} + +/* + * Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW) + * + */ +static bool client_supports_protocol(xcb_connection_t *conn, Client *client, xcb_atom_t atom) { + xcb_get_property_cookie_t cookie; + xcb_get_wm_protocols_reply_t protocols; + bool result = false; + + cookie = xcb_get_wm_protocols_unchecked(conn, client->child, atoms[WM_PROTOCOLS]); + if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1) + return false; + + /* Check if the client’s protocols have the requested atom set */ + for (uint32_t i = 0; i < protocols.atoms_len; i++) + if (protocols.atoms[i] == atom) + result = true; + + xcb_get_wm_protocols_reply_wipe(&protocols); + + return result; +} + +/* + * Kills the given window using WM_DELETE_WINDOW or xcb_kill_window + * + */ +void client_kill(xcb_connection_t *conn, Client *window) { + /* If the client does not support WM_DELETE_WINDOW, we kill it the hard way */ + if (!client_supports_protocol(conn, window, atoms[WM_DELETE_WINDOW])) { + LOG("Killing window the hard way\n"); + xcb_kill_client(conn, window->child); + return; + } + + xcb_client_message_event_t ev; + + memset(&ev, 0, sizeof(xcb_client_message_event_t)); + + ev.response_type = XCB_CLIENT_MESSAGE; + ev.window = window->child; + ev.type = atoms[WM_PROTOCOLS]; + ev.format = 32; + ev.data.data32[0] = atoms[WM_DELETE_WINDOW]; + ev.data.data32[1] = XCB_CURRENT_TIME; + + LOG("Sending WM_DELETE to the client\n"); + xcb_send_event(conn, false, window->child, XCB_EVENT_MASK_NO_EVENT, (char*)&ev); + xcb_flush(conn); +} + +/* + * Checks if the given window class and title match the given client + * Window title is passed as "normal" string and as UCS-2 converted string for + * matching _NET_WM_NAME capable clients as well as those using legacy hints. + * + */ +bool client_matches_class_name(Client *client, char *to_class, char *to_title, + char *to_title_ucs, int to_title_ucs_len) { + /* Check if the given class is part of the window class */ + if (strcasestr(client->window_class, to_class) == NULL) + return false; + + /* If no title was given, we’re done */ + if (to_title == NULL) + return true; + + if (client->name_len > -1) { + /* UCS-2 converted window titles */ + if (memmem(client->name, (client->name_len * 2), to_title_ucs, (to_title_ucs_len * 2)) == NULL) + return false; + } else { + /* Legacy hints */ + if (strcasestr(client->name, to_title) == NULL) + return false; + } + + return true; +} diff --git a/src/commands.c b/src/commands.c index 553d347d..b84cb858 100644 --- a/src/commands.c +++ b/src/commands.c @@ -23,6 +23,7 @@ #include "layout.h" #include "i3.h" #include "xinerama.h" +#include "client.h" bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) { /* If this container is empty, we’re done */ @@ -240,7 +241,7 @@ static void move_current_window(xcb_connection_t *conn, direction_t direction) { } /* Remove it from the old container and put it into the new one */ - remove_client_from_container(conn, current_client, container); + client_remove_from_container(conn, current_client, container); if (new->currently_focused != NULL) CIRCLEQ_INSERT_AFTER(&(new->clients), new->currently_focused, current_client, clients); @@ -458,7 +459,7 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa assert(to_container != NULL); - remove_client_from_container(conn, current_client, container); + client_remove_from_container(conn, current_client, container); if (container->workspace->fullscreen_client == current_client) container->workspace->fullscreen_client = NULL; @@ -538,7 +539,7 @@ void show_workspace(xcb_connection_t *conn, int workspace) { if (CUR_CELL->currently_focused != NULL) { set_focus(conn, CUR_CELL->currently_focused, true); if (need_warp) { - warp_pointer_into(conn, CUR_CELL->currently_focused); + client_warp_pointer_into(conn, CUR_CELL->currently_focused); xcb_flush(conn); } } @@ -574,7 +575,7 @@ void show_workspace(xcb_connection_t *conn, int workspace) { if (CUR_CELL->currently_focused != NULL) { set_focus(conn, CUR_CELL->currently_focused, true); if (need_warp) { - warp_pointer_into(conn, CUR_CELL->currently_focused); + client_warp_pointer_into(conn, CUR_CELL->currently_focused); xcb_flush(conn); } } else xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME); @@ -582,35 +583,6 @@ void show_workspace(xcb_connection_t *conn, int workspace) { render_layout(conn); } -/* - * Checks if the given window class and title match the given client - * Window title is passed as "normal" string and as UCS-2 converted string for - * matching _NET_WM_NAME capable clients as well as those using legacy hints. - * - */ -static bool client_matches_class_name(Client *client, char *to_class, char *to_title, - char *to_title_ucs, int to_title_ucs_len) { - /* Check if the given class is part of the window class */ - if (strcasestr(client->window_class, to_class) == NULL) - return false; - - /* If no title was given, we’re done */ - if (to_title == NULL) - return true; - - if (client->name_len > -1) { - /* UCS-2 converted window titles */ - if (memmem(client->name, (client->name_len * 2), to_title_ucs, (to_title_ucs_len * 2)) == NULL) - return false; - } else { - /* Legacy hints */ - if (strcasestr(client->name, to_title) == NULL) - return false; - } - - return true; -} - /* * Jumps to the given window class / title. * Title is matched using strstr, that is, matches if it appears anywhere @@ -619,44 +591,22 @@ static bool client_matches_class_name(Client *client, char *to_class, char *to_t * */ static void jump_to_window(xcb_connection_t *conn, const char *arguments) { - char *to_class, *to_title, *to_title_ucs = NULL; - int to_title_ucs_len; + char *classtitle; + Client *client; /* The first character is a quote, this was checked before */ - to_class = sstrdup(arguments+1); + classtitle = sstrdup(arguments+1); /* The last character is a quote, we just set it to NULL */ - to_class[strlen(to_class)-1] = '\0'; + classtitle[strlen(classtitle)-1] = '\0'; - /* If a title was specified, split both strings at the slash */ - if ((to_title = strstr(to_class, "/")) != NULL) { - *(to_title++) = '\0'; - /* Convert to UCS-2 */ - to_title_ucs = convert_utf8_to_ucs2(to_title, &to_title_ucs_len); + if ((client = get_matching_client(conn, classtitle, NULL)) == NULL) { + free(classtitle); + LOG("No matching client found.\n"); + return; } - LOG("Should jump to class \"%s\" / title \"%s\"\n", to_class, to_title); - for (int workspace = 0; workspace < 10; workspace++) { - if (workspaces[workspace].screen == NULL) - continue; - - FOR_TABLE(&(workspaces[workspace])) { - Container *con = workspaces[workspace].table[cols][rows]; - Client *client; - - CIRCLEQ_FOREACH(client, &(con->clients), clients) { - LOG("Checking client with class=%s, name=%s\n", client->window_class, client->name); - if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len)) - continue; - - set_focus(conn, client, true); - goto done; - } - } - } - -done: - free(to_class); - FREE(to_title_ucs); + free(classtitle); + set_focus(conn, client, true); } /* @@ -763,7 +713,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { } LOG("Killing current window\n"); - kill_window(conn, CUR_CELL->currently_focused); + client_kill(conn, CUR_CELL->currently_focused); return; } diff --git a/src/config.c b/src/config.c index ca84282a..562b2e92 100644 --- a/src/config.c +++ b/src/config.c @@ -87,7 +87,7 @@ void load_configuration(const char *override_configpath) { /* exec-lines (autostart) */ if (strcasecmp(key, "exec") == 0) { - Autostart *new = smalloc(sizeof(Autostart)); + struct Autostart *new = smalloc(sizeof(struct Autostart)); new->command = sstrdup(value); TAILQ_INSERT_TAIL(&autostarts, new, autostarts); continue; @@ -133,6 +133,44 @@ void load_configuration(const char *override_configpath) { continue; } + /* assign window class[/window title] → workspace */ + if (strcasecmp(key, "assign") == 0) { + LOG("assign: \"%s\"\n", value); + char *class_title = sstrdup(value); + char *target; + + /* If the window class/title is quoted we skip quotes */ + if (class_title[0] == '"') { + class_title++; + char *end = strchr(class_title, '"'); + if (end == NULL) + die("Malformatted assignment, couldn't find finishing quote\n"); + *end = '\0'; + } else { + /* If it is not quoted, we terminate it at the first space */ + char *end = strchr(class_title, ' '); + if (end == NULL) + die("Malformed assignment, couldn't find terminating space\n"); + *end = '\0'; + } + + /* The target is the last argument separated by a space */ + if ((target = strrchr(value, ' ')) == NULL) + die("Malformed assignment, couldn't find target\n"); + target++; + + if (atoi(target) < 1 || atoi(target) > 10) + die("Malformed assignment, invalid workspace number\n"); + + LOG("assignment parsed: \"%s\" to \"%s\"\n", class_title, target); + + struct Assignment *new = smalloc(sizeof(struct Assignment)); + new->windowclass_title = class_title; + new->workspace = atoi(target); + TAILQ_INSERT_TAIL(&assignments, new, assignments); + continue; + } + fprintf(stderr, "Unknown configfile option: %s\n", key); exit(1); } diff --git a/src/handlers.c b/src/handlers.c index 7652f07b..c7c11246 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -32,6 +32,7 @@ #include "config.h" #include "queue.h" #include "resize.h" +#include "client.h" /* After mapping/unmapping windows, a notify event is generated. However, we don’t want it, since it’d trigger an infinite loop of switching between the different windows when @@ -501,7 +502,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti con->workspace->fullscreen_client = NULL; /* Remove the client from the list of clients */ - remove_client_from_container(conn, client, con); + client_remove_from_container(conn, client, con); /* Set focus to the last focused client in this container */ con->currently_focused = get_last_focused_client(conn, con, NULL); diff --git a/src/mainx.c b/src/mainx.c index da79ee3a..dbce26a1 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -55,6 +55,9 @@ struct bindings_head bindings = TAILQ_HEAD_INITIALIZER(bindings); /* The list of exec-lines */ struct autostarts_head autostarts = TAILQ_HEAD_INITIALIZER(autostarts); +/* The list of assignments */ +struct assignments_head assignments = TAILQ_HEAD_INITIALIZER(assignments); + /* This is a list of Stack_Windows, global, for easier/faster access on expose events */ struct stack_wins_head stack_wins = SLIST_HEAD_INITIALIZER(stack_wins); @@ -102,17 +105,21 @@ void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_ if (!attr) { wa.tag = TAG_COOKIE; wa.u.cookie = xcb_get_window_attributes(conn, window); - attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0); - } - geom = xcb_get_geometry_reply(conn, geomc, 0); - if (attr && geom) { - reparent_window(conn, window, attr->visual, geom->root, geom->depth, - geom->x, geom->y, geom->width, geom->height); - xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS); - xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME); - xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS); - xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]); + if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL) + return; } + if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL) + goto out; + + /* Reparent the window and add it to our list of managed windows */ + reparent_window(conn, window, attr->visual, geom->root, geom->depth, + geom->x, geom->y, geom->width, geom->height); + + /* Generate callback events for every property we watch */ + xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS); + xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME); + xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS); + xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]); free(geom); out: @@ -131,7 +138,8 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, xcb_visualid_t visual, xcb_window_t root, uint8_t depth, int16_t x, int16_t y, uint16_t width, uint16_t height) { - xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie; + xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie, + utf8_title_cookie, title_cookie, class_cookie; uint32_t mask = 0; uint32_t values[3]; @@ -147,6 +155,9 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX); strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX); state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX); + utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128); + title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128); + class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128); Client *new = table_get(&by_child, child); @@ -191,6 +202,8 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, /* Yo dawg, I heard you like windows, so I create a window around your window… */ new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values); + /* Set WM_STATE_NORMAL because GTK applications don’t want to drag & drop if we don’t. + * Also, xprop(1) needs that to work. */ long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE }; xcb_change_property(conn, XCB_PROP_MODE_REPLACE, new->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data); @@ -227,15 +240,16 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, xcb_atom_t *atom; xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL); if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) { - for (int i = 0; i < xcb_get_property_value_length(preply); i++) - if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) { - LOG("Window is a dock.\n"); - new->dock = true; - new->titlebar_position = TITLEBAR_OFF; - new->force_reconfigure = true; - new->container = NULL; - SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients); - } + for (int i = 0; i < xcb_get_property_value_length(preply); i++) { + if (atom[i] != atoms[_NET_WM_WINDOW_TYPE_DOCK]) + continue; + LOG("Window is a dock.\n"); + new->dock = true; + new->titlebar_position = TITLEBAR_OFF; + new->force_reconfigure = true; + new->container = NULL; + SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients); + } } if (new->dock) { @@ -257,18 +271,65 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", height); new->desired_height = height; } + } else { + /* If it’s not a dock, we can check on which workspace we should put it. */ + + /* Firstly, we need to get the window’s class / title. We asked for the properties at the + * top of this function, get them now and pass them to our callback function for window class / title + * changes. It is important that the client was already inserted into the by_child table, + * because the callbacks won’t work otherwise. */ + preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL); + handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply); + + preply = xcb_get_property_reply(conn, title_cookie, NULL); + handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply); + + preply = xcb_get_property_reply(conn, class_cookie, NULL); + handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply); + + LOG("DEBUG: should have all infos now\n"); + struct Assignment *assign; + TAILQ_FOREACH(assign, &assignments, assignments) { + if (get_matching_client(conn, assign->windowclass_title, new) == NULL) + continue; + + LOG("Assignment \"%s\" matches, so putting it on workspace %d\n", + assign->windowclass_title, assign->workspace); + + if (c_ws->screen->current_workspace == (assign->workspace-1)) { + LOG("We are already there, no need to do anything\n"); + break; + } + + LOG("Changin container/workspace and unmapping the client\n"); + Workspace *t_ws = &(workspaces[assign->workspace-1]); + if (t_ws->screen == NULL) { + LOG("initializing new workspace, setting num to %d\n", assign->workspace); + t_ws->screen = c_ws->screen; + /* Copy the dimensions from the virtual screen */ + memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect)); + } + + new->container = t_ws->table[t_ws->current_col][t_ws->current_row]; + new->workspace = t_ws; + xcb_unmap_window(conn, new->frame); + break; + } } - /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */ - if (CUR_CELL->workspace->fullscreen_client == NULL) { - if (!new->dock) { - CUR_CELL->currently_focused = new; - xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME); + if (CUR_CELL->workspace->fullscreen_client != NULL) { + if (new->container == CUR_CELL) { + /* If we are in fullscreen, we should lower the window to not be annoying */ + uint32_t values[] = { XCB_STACK_MODE_BELOW }; + xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values); + } + } else if (!new->dock) { + /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */ + if (new->container->workspace->fullscreen_client == NULL) { + new->container->currently_focused = new; + if (new->container == CUR_CELL) + xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME); } - } else { - /* If we are in fullscreen, we should lower the window to not be annoying */ - uint32_t values[] = { XCB_STACK_MODE_BELOW }; - xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values); } /* Insert into the currently active container, if it’s not a dock window */ @@ -276,8 +337,8 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, /* Insert after the old active client, if existing. If it does not exist, the container is empty and it does not matter, where we insert it */ if (old_focused != NULL && !old_focused->dock) - CIRCLEQ_INSERT_AFTER(&(CUR_CELL->clients), old_focused, new, clients); - else CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients); + CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients); + else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients); SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients); } @@ -287,14 +348,15 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL && (state = xcb_get_property_value(preply)) != NULL) /* Check all set _NET_WM_STATEs */ - for (int i = 0; i < xcb_get_property_value_length(preply); i++) - if (state[i] == atoms[_NET_WM_STATE_FULLSCREEN]) { - /* If the window got the fullscreen state, we just toggle fullscreen - and don’t event bother to redraw the layout – that would not change - anything anyways */ - toggle_fullscreen(conn, new); - return; - } + for (int i = 0; i < xcb_get_property_value_length(preply); i++) { + if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN]) + continue; + /* If the window got the fullscreen state, we just toggle fullscreen + and don’t event bother to redraw the layout – that would not change + anything anyways */ + toggle_fullscreen(conn, new); + return; + } render_layout(conn); } @@ -531,7 +593,7 @@ int main(int argc, char *argv[], char *env[]) { } /* Autostarting exec-lines */ - Autostart *exec; + struct Autostart *exec; TAILQ_FOREACH(exec, &autostarts, autostarts) { LOG("auto-starting %s\n", exec->command); start_application(exec->command); diff --git a/src/util.c b/src/util.c index c0e74c75..5b51bd5b 100644 --- a/src/util.c +++ b/src/util.c @@ -27,6 +27,7 @@ #include "layout.h" #include "util.h" #include "xcb.h" +#include "client.h" static iconv_t conversion_descriptor = 0; struct keyvalue_table_head by_parent = TAILQ_HEAD_INITIALIZER(by_parent); @@ -224,25 +225,6 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen) { return buffer; } -/* - * Removes the given client from the container, either because it will be inserted into another - * one or because it was unmapped - * - */ -void remove_client_from_container(xcb_connection_t *conn, Client *client, Container *container) { - CIRCLEQ_REMOVE(&(container->clients), client, clients); - - SLIST_REMOVE(&(container->workspace->focus_stack), client, Client, focus_clients); - - /* If the container will be empty now and is in stacking mode, we need to - unmap the stack_win */ - if (CIRCLEQ_EMPTY(&(container->clients)) && container->mode == MODE_STACK) { - struct Stack_Window *stack_win = &(container->stack_win); - stack_win->rect.height = 0; - xcb_unmap_window(conn, stack_win->window); - } -} - /* * Returns the client which comes next in focus stack (= was selected before) for * the given container, optionally excluding the given client. @@ -435,17 +417,6 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode) set_focus(conn, container->currently_focused, true); } -/* - * Warps the pointer into the given client (in the middle of it, to be specific), therefore - * selecting it - * - */ -void warp_pointer_into(xcb_connection_t *conn, Client *client) { - int mid_x = client->rect.width / 2, - mid_y = client->rect.height / 2; - xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, mid_x, mid_y); -} - /* * Toggles fullscreen mode for the given client. It updates the data structures and * reconfigures (= resizes/moves) the client and its frame to the full size of the @@ -508,52 +479,55 @@ void toggle_fullscreen(xcb_connection_t *conn, Client *client) { } /* - * Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW) + * Gets the first matching client for the given window class/window title. + * If the paramater specific is set to a specific client, only this one + * will be checked. * */ -static bool client_supports_protocol(xcb_connection_t *conn, Client *client, xcb_atom_t atom) { - xcb_get_property_cookie_t cookie; - xcb_get_wm_protocols_reply_t protocols; - bool result = false; +Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitle, + Client *specific) { + char *to_class, *to_title, *to_title_ucs = NULL; + int to_title_ucs_len; + Client *matching = NULL; - cookie = xcb_get_wm_protocols_unchecked(conn, client->child, atoms[WM_PROTOCOLS]); - if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1) - return false; + to_class = sstrdup(window_classtitle); - /* Check if the client’s protocols have the requested atom set */ - for (uint32_t i = 0; i < protocols.atoms_len; i++) - if (protocols.atoms[i] == atom) - result = true; - - xcb_get_wm_protocols_reply_wipe(&protocols); - - return result; -} - -/* - * Kills the given window using WM_DELETE_WINDOW or xcb_kill_window - * - */ -void kill_window(xcb_connection_t *conn, Client *window) { - /* If the client does not support WM_DELETE_WINDOW, we kill it the hard way */ - if (!client_supports_protocol(conn, window, atoms[WM_DELETE_WINDOW])) { - LOG("Killing window the hard way\n"); - xcb_kill_client(conn, window->child); - return; + /* If a title was specified, split both strings at the slash */ + if ((to_title = strstr(to_class, "/")) != NULL) { + *(to_title++) = '\0'; + /* Convert to UCS-2 */ + to_title_ucs = convert_utf8_to_ucs2(to_title, &to_title_ucs_len); } - xcb_client_message_event_t ev; + /* If we were given a specific client we only check if that one matches */ + if (specific != NULL) { + if (client_matches_class_name(specific, to_class, to_title, to_title_ucs, to_title_ucs_len)) + matching = specific; + goto done; + } - memset(&ev, 0, sizeof(xcb_client_message_event_t)); + LOG("Getting clients for class \"%s\" / title \"%s\"\n", to_class, to_title); + for (int workspace = 0; workspace < 10; workspace++) { + if (workspaces[workspace].screen == NULL) + continue; - ev.response_type = XCB_CLIENT_MESSAGE; - ev.window = window->child; - ev.type = atoms[WM_PROTOCOLS]; - ev.format = 32; - ev.data.data32[0] = atoms[WM_DELETE_WINDOW]; - ev.data.data32[1] = XCB_CURRENT_TIME; + FOR_TABLE(&(workspaces[workspace])) { + Container *con = workspaces[workspace].table[cols][rows]; + Client *client; - LOG("Sending WM_DELETE to the client\n"); - xcb_send_event(conn, false, window->child, XCB_EVENT_MASK_NO_EVENT, (char*)&ev); - xcb_flush(conn); + CIRCLEQ_FOREACH(client, &(con->clients), clients) { + LOG("Checking client with class=%s, name=%s\n", client->window_class, client->name); + if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len)) + continue; + + matching = client; + goto done; + } + } + } + +done: + free(to_class); + FREE(to_title_ucs); + return matching; } From d5d44e66a217f7c41a2968ff435c09261ca4b6d4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 16 May 2009 18:12:35 +0200 Subject: [PATCH 022/129] Bugfix: Re-assign dock windows to different workspaces when a workspace is deleted Killing a dock client and having destroyed workspace 1 before (or the workspace on which the dock client was started when it was not auto-started) crashed i3 before this bugfix. --- src/commands.c | 9 +++++---- src/handlers.c | 10 +++++++++- src/util.c | 12 +++++++++++- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/commands.c b/src/commands.c index b84cb858..e289e018 100644 --- a/src/commands.c +++ b/src/commands.c @@ -547,11 +547,12 @@ void show_workspace(xcb_connection_t *conn, int workspace) { } t_ws->screen->current_workspace = workspace-1; - - /* Unmap all clients of the current workspace */ - unmap_workspace(conn, c_ws); - + Workspace *old_workspace = c_ws; c_ws = &workspaces[workspace-1]; + + /* Unmap all clients of the old workspace */ + unmap_workspace(conn, old_workspace); + current_row = c_ws->current_row; current_col = c_ws->current_col; LOG("new current row = %d, current col = %d\n", current_row, current_col); diff --git a/src/handlers.c b/src/handlers.c index c7c11246..7dae1149 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -145,7 +145,7 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_ return 1; } /* Some events are not interesting, because they were not generated actively by the - user, but be reconfiguration of windows */ + user, but by reconfiguration of windows */ if (event_is_ignored(event->sequence)) return 1; @@ -192,6 +192,14 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_ return 1; } + if (client->container->workspace != c_ws) { + /* This can happen when a client gets assigned to a different workspace than + * the current one (see src/mainx.c:reparent_window). Shortly after it was created, + * an enter_notify will follow. */ + LOG("enter_notify for a client on a different workspace, ignoring\n"); + return 1; + } + set_focus(conn, client, false); return 1; diff --git a/src/util.c b/src/util.c index 5b51bd5b..a1146251 100644 --- a/src/util.c +++ b/src/util.c @@ -263,8 +263,18 @@ void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws) { } /* If we did not unmap any clients, the workspace is empty and we can destroy it */ - if (unmapped_clients == 0) + if (unmapped_clients == 0) { + /* Re-assign the workspace of all dock clients which use this workspace */ + Client *dock; + SLIST_FOREACH(dock, &(u_ws->screen->dock_clients), dock_clients) { + if (dock->workspace != u_ws) + continue; + + LOG("Re-assigning dock client to c_ws (%p)\n", c_ws); + dock->workspace = c_ws; + } u_ws->screen = NULL; + } /* Unmap the stack windows on the current workspace, if any */ SLIST_FOREACH(stack_win, &stack_wins, stack_windows) From b35599004aacf06405e88eda703bc76bc5b49a91 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 16 May 2009 18:25:04 +0200 Subject: [PATCH 023/129] Move the manage_*-functions into their own file as they got quite large --- include/i3.h | 5 - include/manage.h | 42 ++++++ src/handlers.c | 1 + src/mainx.c | 326 +------------------------------------------ src/manage.c | 354 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 398 insertions(+), 330 deletions(-) create mode 100644 include/manage.h create mode 100644 src/manage.c diff --git a/include/i3.h b/include/i3.h index 06f423a0..4f285b7c 100644 --- a/include/i3.h +++ b/include/i3.h @@ -32,9 +32,4 @@ extern xcb_event_handlers_t evenths; extern int num_screens; extern xcb_atom_t atoms[NUM_ATOMS]; -void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_window_t window, window_attributes_t wa); -void reparent_window(xcb_connection_t *conn, xcb_window_t child, - xcb_visualid_t visual, xcb_window_t root, uint8_t depth, - int16_t x, int16_t y, uint16_t width, uint16_t height); - #endif diff --git a/include/manage.h b/include/manage.h new file mode 100644 index 00000000..52816e37 --- /dev/null +++ b/include/manage.h @@ -0,0 +1,42 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * (c) 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ +#include + +#include "data.h" + +#ifndef _MANAGE_H +#define _MANAGE_H + +/** + * Go through all existing windows (if the window manager is restarted) and manage them + * + */ +void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root); + +/** + * Do some sanity checks and then reparent the window. + * + */ +void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, + xcb_window_t window, window_attributes_t wa); + +/** + * reparent_window() gets called when a new window was opened and becomes a child of the root + * window, or it gets called by us when we manage the already existing windows at startup. + * + * Essentially, this is the point where we take over control. + * + */ +void reparent_window(xcb_connection_t *conn, xcb_window_t child, + xcb_visualid_t visual, xcb_window_t root, uint8_t depth, + int16_t x, int16_t y, uint16_t width, uint16_t height); + +#endif diff --git a/src/handlers.c b/src/handlers.c index 7dae1149..8316fc34 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -33,6 +33,7 @@ #include "queue.h" #include "resize.h" #include "client.h" +#include "manage.h" /* After mapping/unmapping windows, a notify event is generated. However, we don’t want it, since it’d trigger an infinite loop of switching between the different windows when diff --git a/src/mainx.c b/src/mainx.c index dbce26a1..60cd6a0f 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -42,6 +42,7 @@ #include "util.h" #include "xcb.h" #include "xinerama.h" +#include "manage.h" /* This is the path to i3, copied from argv[0] when starting up */ char **start_argv; @@ -68,331 +69,6 @@ xcb_atom_t atoms[NUM_ATOMS]; int num_screens = 0; -/* - * Do some sanity checks and then reparent the window. - * - */ -void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_window_t window, window_attributes_t wa) { - LOG("managing window.\n"); - xcb_drawable_t d = { window }; - xcb_get_geometry_cookie_t geomc; - xcb_get_geometry_reply_t *geom; - xcb_get_window_attributes_reply_t *attr = 0; - - if (wa.tag == TAG_COOKIE) { - /* Check if the window is mapped (it could be not mapped when intializing and - calling manage_window() for every window) */ - if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL) - return; - - if (attr->map_state != XCB_MAP_STATE_VIEWABLE) - goto out; - - wa.tag = TAG_VALUE; - wa.u.override_redirect = attr->override_redirect; - } - - /* Don’t manage clients with the override_redirect flag */ - if (wa.u.override_redirect) - goto out; - - /* Check if the window is already managed */ - if (table_get(&by_child, window)) - goto out; - - /* Get the initial geometry (position, size, …) */ - geomc = xcb_get_geometry(conn, d); - if (!attr) { - wa.tag = TAG_COOKIE; - wa.u.cookie = xcb_get_window_attributes(conn, window); - if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL) - return; - } - if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL) - goto out; - - /* Reparent the window and add it to our list of managed windows */ - reparent_window(conn, window, attr->visual, geom->root, geom->depth, - geom->x, geom->y, geom->width, geom->height); - - /* Generate callback events for every property we watch */ - xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS); - xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME); - xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS); - xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]); - - free(geom); -out: - free(attr); - return; -} - -/* - * reparent_window() gets called when a new window was opened and becomes a child of the root - * window, or it gets called by us when we manage the already existing windows at startup. - * - * Essentially, this is the point where we take over control. - * - */ -void reparent_window(xcb_connection_t *conn, xcb_window_t child, - xcb_visualid_t visual, xcb_window_t root, uint8_t depth, - int16_t x, int16_t y, uint16_t width, uint16_t height) { - - xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie, - utf8_title_cookie, title_cookie, class_cookie; - uint32_t mask = 0; - uint32_t values[3]; - - /* We are interested in property changes */ - mask = XCB_CW_EVENT_MASK; - values[0] = CHILD_EVENT_MASK; - xcb_change_window_attributes(conn, child, mask, values); - - /* Map the window first to avoid flickering */ - xcb_map_window(conn, child); - - /* Place requests for properties ASAP */ - wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX); - strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX); - state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX); - utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128); - title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128); - class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128); - - Client *new = table_get(&by_child, child); - - /* Events for already managed windows should already be filtered in manage_window() */ - assert(new == NULL); - - LOG("reparenting new client\n"); - new = calloc(sizeof(Client), 1); - new->force_reconfigure = true; - - /* Update the data structures */ - Client *old_focused = CUR_CELL->currently_focused; - - new->container = CUR_CELL; - new->workspace = new->container->workspace; - - new->frame = xcb_generate_id(conn); - new->child = child; - new->rect.width = width; - new->rect.height = height; - - mask = 0; - - /* Don’t generate events for our new window, it should *not* be managed */ - mask |= XCB_CW_OVERRIDE_REDIRECT; - values[0] = 1; - - /* We want to know when… */ - mask |= XCB_CW_EVENT_MASK; - values[1] = FRAME_EVENT_MASK; - - LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame); - - i3Font *font = load_font(conn, config.font); - width = min(width, c_ws->rect.x + c_ws->rect.width); - height = min(height, c_ws->rect.y + c_ws->rect.height); - - Rect framerect = {x, y, - width + 2 + 2, /* 2 px border at each side */ - height + 2 + 2 + font->height}; /* 2 px border plus font’s height */ - - /* Yo dawg, I heard you like windows, so I create a window around your window… */ - new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values); - - /* Set WM_STATE_NORMAL because GTK applications don’t want to drag & drop if we don’t. - * Also, xprop(1) needs that to work. */ - long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE }; - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, new->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data); - - /* Put the client inside the save set. Upon termination (whether killed or normal exit - does not matter) of the window manager, these clients will be correctly reparented - to their most closest living ancestor (= cleanup) */ - xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child); - - /* Generate a graphics context for the titlebar */ - new->titlegc = xcb_generate_id(conn); - xcb_create_gc(conn, new->titlegc, new->frame, 0, 0); - - /* Moves the original window into the new frame we've created for it */ - new->awaiting_useless_unmap = true; - xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height); - if (xcb_request_check(conn, cookie) != NULL) { - LOG("Could not reparent the window, aborting\n"); - xcb_destroy_window(conn, new->frame); - free(new); - return; - } - - /* Put our data structure (Client) into the table */ - table_put(&by_parent, new->frame, new); - table_put(&by_child, child, new); - - /* We need to grab the mouse buttons for click to focus */ - xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS, - XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE, - 1 /* left mouse button */, - XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */); - - /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */ - xcb_atom_t *atom; - xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL); - if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) { - for (int i = 0; i < xcb_get_property_value_length(preply); i++) { - if (atom[i] != atoms[_NET_WM_WINDOW_TYPE_DOCK]) - continue; - LOG("Window is a dock.\n"); - new->dock = true; - new->titlebar_position = TITLEBAR_OFF; - new->force_reconfigure = true; - new->container = NULL; - SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients); - } - } - - if (new->dock) { - /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */ - uint32_t *strut; - preply = xcb_get_property_reply(conn, strut_cookie, NULL); - if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) { - /* We only use a subset of the provided values, namely the reserved space at the top/bottom - of the screen. This is because the only possibility for bars is at to be at the top/bottom - with maximum horizontal size. - TODO: bars at the top */ - new->desired_height = strut[3]; - if (new->desired_height == 0) { - LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", height); - new->desired_height = height; - } - LOG("the client wants to be %d pixels high\n", new->desired_height); - } else { - LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", height); - new->desired_height = height; - } - } else { - /* If it’s not a dock, we can check on which workspace we should put it. */ - - /* Firstly, we need to get the window’s class / title. We asked for the properties at the - * top of this function, get them now and pass them to our callback function for window class / title - * changes. It is important that the client was already inserted into the by_child table, - * because the callbacks won’t work otherwise. */ - preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL); - handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply); - - preply = xcb_get_property_reply(conn, title_cookie, NULL); - handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply); - - preply = xcb_get_property_reply(conn, class_cookie, NULL); - handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply); - - LOG("DEBUG: should have all infos now\n"); - struct Assignment *assign; - TAILQ_FOREACH(assign, &assignments, assignments) { - if (get_matching_client(conn, assign->windowclass_title, new) == NULL) - continue; - - LOG("Assignment \"%s\" matches, so putting it on workspace %d\n", - assign->windowclass_title, assign->workspace); - - if (c_ws->screen->current_workspace == (assign->workspace-1)) { - LOG("We are already there, no need to do anything\n"); - break; - } - - LOG("Changin container/workspace and unmapping the client\n"); - Workspace *t_ws = &(workspaces[assign->workspace-1]); - if (t_ws->screen == NULL) { - LOG("initializing new workspace, setting num to %d\n", assign->workspace); - t_ws->screen = c_ws->screen; - /* Copy the dimensions from the virtual screen */ - memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect)); - } - - new->container = t_ws->table[t_ws->current_col][t_ws->current_row]; - new->workspace = t_ws; - xcb_unmap_window(conn, new->frame); - break; - } - } - - if (CUR_CELL->workspace->fullscreen_client != NULL) { - if (new->container == CUR_CELL) { - /* If we are in fullscreen, we should lower the window to not be annoying */ - uint32_t values[] = { XCB_STACK_MODE_BELOW }; - xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values); - } - } else if (!new->dock) { - /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */ - if (new->container->workspace->fullscreen_client == NULL) { - new->container->currently_focused = new; - if (new->container == CUR_CELL) - xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME); - } - } - - /* Insert into the currently active container, if it’s not a dock window */ - if (!new->dock) { - /* Insert after the old active client, if existing. If it does not exist, the - container is empty and it does not matter, where we insert it */ - if (old_focused != NULL && !old_focused->dock) - CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients); - else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients); - - SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients); - } - - /* Check if the window already got the fullscreen hint set */ - xcb_atom_t *state; - if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL && - (state = xcb_get_property_value(preply)) != NULL) - /* Check all set _NET_WM_STATEs */ - for (int i = 0; i < xcb_get_property_value_length(preply); i++) { - if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN]) - continue; - /* If the window got the fullscreen state, we just toggle fullscreen - and don’t event bother to redraw the layout – that would not change - anything anyways */ - toggle_fullscreen(conn, new); - return; - } - - render_layout(conn); -} - -/* - * Go through all existing windows (if the window manager is restarted) and manage them - * - */ -void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) { - xcb_query_tree_reply_t *reply; - int i, len; - xcb_window_t *children; - xcb_get_window_attributes_cookie_t *cookies; - - /* Get the tree of windows whose parent is the root window (= all) */ - if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL) - return; - - len = xcb_query_tree_children_length(reply); - cookies = smalloc(len * sizeof(*cookies)); - - /* Request the window attributes for every window */ - children = xcb_query_tree_children(reply); - for(i = 0; i < len; ++i) - cookies[i] = xcb_get_window_attributes(conn, children[i]); - - /* Call manage_window with the attributes for every window */ - for(i = 0; i < len; ++i) { - window_attributes_t wa = { TAG_COOKIE, { cookies[i] } }; - manage_window(prophs, conn, children[i], wa); - } - - free(reply); - free(cookies); -} - int main(int argc, char *argv[], char *env[]) { int i, screens, opt; char *override_configpath = NULL; diff --git a/src/manage.c b/src/manage.c new file mode 100644 index 00000000..d330d88f --- /dev/null +++ b/src/manage.c @@ -0,0 +1,354 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + * src/manage.c: Contains all functions for initially managing new windows + * (or existing ones on restart). + * + */ +#include +#include +#include + +#include +#include + +#include "xcb.h" +#include "data.h" +#include "util.h" +#include "i3.h" +#include "table.h" +#include "config.h" +#include "handlers.h" +#include "layout.h" +#include "manage.h" + +/* + * Go through all existing windows (if the window manager is restarted) and manage them + * + */ +void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) { + xcb_query_tree_reply_t *reply; + int i, len; + xcb_window_t *children; + xcb_get_window_attributes_cookie_t *cookies; + + /* Get the tree of windows whose parent is the root window (= all) */ + if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL) + return; + + len = xcb_query_tree_children_length(reply); + cookies = smalloc(len * sizeof(*cookies)); + + /* Request the window attributes for every window */ + children = xcb_query_tree_children(reply); + for(i = 0; i < len; ++i) + cookies[i] = xcb_get_window_attributes(conn, children[i]); + + /* Call manage_window with the attributes for every window */ + for(i = 0; i < len; ++i) { + window_attributes_t wa = { TAG_COOKIE, { cookies[i] } }; + manage_window(prophs, conn, children[i], wa); + } + + free(reply); + free(cookies); +} + +/* + * Do some sanity checks and then reparent the window. + * + */ +void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_window_t window, window_attributes_t wa) { + LOG("managing window.\n"); + xcb_drawable_t d = { window }; + xcb_get_geometry_cookie_t geomc; + xcb_get_geometry_reply_t *geom; + xcb_get_window_attributes_reply_t *attr = 0; + + if (wa.tag == TAG_COOKIE) { + /* Check if the window is mapped (it could be not mapped when intializing and + calling manage_window() for every window) */ + if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL) + return; + + if (attr->map_state != XCB_MAP_STATE_VIEWABLE) + goto out; + + wa.tag = TAG_VALUE; + wa.u.override_redirect = attr->override_redirect; + } + + /* Don’t manage clients with the override_redirect flag */ + if (wa.u.override_redirect) + goto out; + + /* Check if the window is already managed */ + if (table_get(&by_child, window)) + goto out; + + /* Get the initial geometry (position, size, …) */ + geomc = xcb_get_geometry(conn, d); + if (!attr) { + wa.tag = TAG_COOKIE; + wa.u.cookie = xcb_get_window_attributes(conn, window); + if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL) + return; + } + if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL) + goto out; + + /* Reparent the window and add it to our list of managed windows */ + reparent_window(conn, window, attr->visual, geom->root, geom->depth, + geom->x, geom->y, geom->width, geom->height); + + /* Generate callback events for every property we watch */ + xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS); + xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME); + xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS); + xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]); + + free(geom); +out: + free(attr); + return; +} + +/* + * reparent_window() gets called when a new window was opened and becomes a child of the root + * window, or it gets called by us when we manage the already existing windows at startup. + * + * Essentially, this is the point where we take over control. + * + */ +void reparent_window(xcb_connection_t *conn, xcb_window_t child, + xcb_visualid_t visual, xcb_window_t root, uint8_t depth, + int16_t x, int16_t y, uint16_t width, uint16_t height) { + + xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie, + utf8_title_cookie, title_cookie, class_cookie; + uint32_t mask = 0; + uint32_t values[3]; + + /* We are interested in property changes */ + mask = XCB_CW_EVENT_MASK; + values[0] = CHILD_EVENT_MASK; + xcb_change_window_attributes(conn, child, mask, values); + + /* Map the window first to avoid flickering */ + xcb_map_window(conn, child); + + /* Place requests for properties ASAP */ + wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX); + strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX); + state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX); + utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128); + title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128); + class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128); + + Client *new = table_get(&by_child, child); + + /* Events for already managed windows should already be filtered in manage_window() */ + assert(new == NULL); + + LOG("reparenting new client\n"); + new = calloc(sizeof(Client), 1); + new->force_reconfigure = true; + + /* Update the data structures */ + Client *old_focused = CUR_CELL->currently_focused; + + new->container = CUR_CELL; + new->workspace = new->container->workspace; + + new->frame = xcb_generate_id(conn); + new->child = child; + new->rect.width = width; + new->rect.height = height; + + mask = 0; + + /* Don’t generate events for our new window, it should *not* be managed */ + mask |= XCB_CW_OVERRIDE_REDIRECT; + values[0] = 1; + + /* We want to know when… */ + mask |= XCB_CW_EVENT_MASK; + values[1] = FRAME_EVENT_MASK; + + LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame); + + i3Font *font = load_font(conn, config.font); + width = min(width, c_ws->rect.x + c_ws->rect.width); + height = min(height, c_ws->rect.y + c_ws->rect.height); + + Rect framerect = {x, y, + width + 2 + 2, /* 2 px border at each side */ + height + 2 + 2 + font->height}; /* 2 px border plus font’s height */ + + /* Yo dawg, I heard you like windows, so I create a window around your window… */ + new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values); + + /* Set WM_STATE_NORMAL because GTK applications don’t want to drag & drop if we don’t. + * Also, xprop(1) needs that to work. */ + long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE }; + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, new->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data); + + /* Put the client inside the save set. Upon termination (whether killed or normal exit + does not matter) of the window manager, these clients will be correctly reparented + to their most closest living ancestor (= cleanup) */ + xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child); + + /* Generate a graphics context for the titlebar */ + new->titlegc = xcb_generate_id(conn); + xcb_create_gc(conn, new->titlegc, new->frame, 0, 0); + + /* Moves the original window into the new frame we've created for it */ + new->awaiting_useless_unmap = true; + xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height); + if (xcb_request_check(conn, cookie) != NULL) { + LOG("Could not reparent the window, aborting\n"); + xcb_destroy_window(conn, new->frame); + free(new); + return; + } + + /* Put our data structure (Client) into the table */ + table_put(&by_parent, new->frame, new); + table_put(&by_child, child, new); + + /* We need to grab the mouse buttons for click to focus */ + xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS, + XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE, + 1 /* left mouse button */, + XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */); + + /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */ + xcb_atom_t *atom; + xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL); + if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) { + for (int i = 0; i < xcb_get_property_value_length(preply); i++) { + if (atom[i] != atoms[_NET_WM_WINDOW_TYPE_DOCK]) + continue; + LOG("Window is a dock.\n"); + new->dock = true; + new->titlebar_position = TITLEBAR_OFF; + new->force_reconfigure = true; + new->container = NULL; + SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients); + } + } + + if (new->dock) { + /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */ + uint32_t *strut; + preply = xcb_get_property_reply(conn, strut_cookie, NULL); + if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) { + /* We only use a subset of the provided values, namely the reserved space at the top/bottom + of the screen. This is because the only possibility for bars is at to be at the top/bottom + with maximum horizontal size. + TODO: bars at the top */ + new->desired_height = strut[3]; + if (new->desired_height == 0) { + LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", height); + new->desired_height = height; + } + LOG("the client wants to be %d pixels high\n", new->desired_height); + } else { + LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", height); + new->desired_height = height; + } + } else { + /* If it’s not a dock, we can check on which workspace we should put it. */ + + /* Firstly, we need to get the window’s class / title. We asked for the properties at the + * top of this function, get them now and pass them to our callback function for window class / title + * changes. It is important that the client was already inserted into the by_child table, + * because the callbacks won’t work otherwise. */ + preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL); + handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply); + + preply = xcb_get_property_reply(conn, title_cookie, NULL); + handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply); + + preply = xcb_get_property_reply(conn, class_cookie, NULL); + handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply); + + LOG("DEBUG: should have all infos now\n"); + struct Assignment *assign; + TAILQ_FOREACH(assign, &assignments, assignments) { + if (get_matching_client(conn, assign->windowclass_title, new) == NULL) + continue; + + LOG("Assignment \"%s\" matches, so putting it on workspace %d\n", + assign->windowclass_title, assign->workspace); + + if (c_ws->screen->current_workspace == (assign->workspace-1)) { + LOG("We are already there, no need to do anything\n"); + break; + } + + LOG("Changin container/workspace and unmapping the client\n"); + Workspace *t_ws = &(workspaces[assign->workspace-1]); + if (t_ws->screen == NULL) { + LOG("initializing new workspace, setting num to %d\n", assign->workspace); + t_ws->screen = c_ws->screen; + /* Copy the dimensions from the virtual screen */ + memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect)); + } + + new->container = t_ws->table[t_ws->current_col][t_ws->current_row]; + new->workspace = t_ws; + xcb_unmap_window(conn, new->frame); + break; + } + } + + if (CUR_CELL->workspace->fullscreen_client != NULL) { + if (new->container == CUR_CELL) { + /* If we are in fullscreen, we should lower the window to not be annoying */ + uint32_t values[] = { XCB_STACK_MODE_BELOW }; + xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values); + } + } else if (!new->dock) { + /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */ + if (new->container->workspace->fullscreen_client == NULL) { + new->container->currently_focused = new; + if (new->container == CUR_CELL) + xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME); + } + } + + /* Insert into the currently active container, if it’s not a dock window */ + if (!new->dock) { + /* Insert after the old active client, if existing. If it does not exist, the + container is empty and it does not matter, where we insert it */ + if (old_focused != NULL && !old_focused->dock) + CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients); + else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients); + + SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients); + } + + /* Check if the window already got the fullscreen hint set */ + xcb_atom_t *state; + if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL && + (state = xcb_get_property_value(preply)) != NULL) + /* Check all set _NET_WM_STATEs */ + for (int i = 0; i < xcb_get_property_value_length(preply); i++) { + if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN]) + continue; + /* If the window got the fullscreen state, we just toggle fullscreen + and don’t event bother to redraw the layout – that would not change + anything anyways */ + toggle_fullscreen(conn, new); + return; + } + + render_layout(conn); +} From 1089fb73113c307f448b707dc3eb2c4e8e462068 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 17 May 2009 10:54:12 +0200 Subject: [PATCH 024/129] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20crash=20when?= =?UTF-8?q?=20above=20dock=20windows=20(Thanks=20Mirko)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/handlers.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers.c b/src/handlers.c index 8316fc34..c45be5f4 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -193,7 +193,7 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_ return 1; } - if (client->container->workspace != c_ws) { + if (client->container != NULL && client->container->workspace != c_ws) { /* This can happen when a client gets assigned to a different workspace than * the current one (see src/mainx.c:reparent_window). Shortly after it was created, * an enter_notify will follow. */ From 5038b3c592f1748f888d8344428936f98ae38041 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 19 May 2009 13:51:03 +0200 Subject: [PATCH 025/129] Bugfix: keypress: Only use the lower 8 bits of the mask so that mouse buttons are filtered out (Thanks Mirko) Sometimes, when the mouse button gets stuck, state contains the bit for BUTTON_MASK_1 (or other buttons). We filter them out to continue processing keys correctly. --- src/handlers.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/handlers.c b/src/handlers.c index c45be5f4..bc98c86f 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -111,6 +111,10 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_ /* Remove the numlock bit, all other bits are modifiers we can bind to */ uint16_t state_filtered = event->state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK); + /* Only use the lower 8 bits of the state (modifier masks) so that mouse + * button masks are filtered out */ + state_filtered &= 0xFF; + /* Find the binding */ Binding *bind; TAILQ_FOREACH(bind, &bindings, bindings) From d98c514f8252fb73502be184481083943167e2e1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 19 May 2009 15:22:50 +0200 Subject: [PATCH 026/129] Bugfix: Correctly handle mode_switch state bit, more debugging output for states We abuse (1 << 8) as mode_switch bit, which is in the range of the filtered state bits (see previous commit). Therefore, we need to filter first and then check for mode_switch. Furthermore, we used 0x2 before, which was just wrong. So, use our bitmask, not the normal one (0x2). --- src/handlers.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index bc98c86f..da556459 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -97,23 +97,24 @@ int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_release_ev * */ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) { - LOG("Keypress %d\n", event->detail); + LOG("Keypress %d, state raw = %d\n", event->detail, event->state); + + /* Remove the numlock bit, all other bits are modifiers we can bind to */ + uint16_t state_filtered = event->state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK); + LOG("(removed numlock, state = %d)\n", state_filtered); + /* Only use the lower 8 bits of the state (modifier masks) so that mouse + * button masks are filtered out */ + state_filtered &= 0xFF; + LOG("(removed upper 8 bits, state = %d)\n", state_filtered); /* We need to get the keysym group (There are group 1 to group 4, each holding two keysyms (without shift and with shift) using Xkb because X fails to provide them reliably (it works in Xephyr, it does not in real X) */ XkbStateRec state; if (XkbGetState(xkbdpy, XkbUseCoreKbd, &state) == Success && (state.group+1) == 2) - event->state |= 0x2; + state_filtered |= BIND_MODE_SWITCH; - LOG("state %d\n", event->state); - - /* Remove the numlock bit, all other bits are modifiers we can bind to */ - uint16_t state_filtered = event->state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK); - - /* Only use the lower 8 bits of the state (modifier masks) so that mouse - * button masks are filtered out */ - state_filtered &= 0xFF; + LOG("(checked mode_switch, state %d)\n", state_filtered); /* Find the binding */ Binding *bind; @@ -130,7 +131,7 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_ } parse_command(conn, bind->command); - if (event->state & 0x2) { + if (state_filtered & BIND_MODE_SWITCH) { LOG("Mode_switch -> allow_events(SyncKeyboard)\n"); xcb_allow_events(conn, SyncKeyboard, event->time); xcb_flush(conn); From 6bb0c825885480d5c2d419a68f53697ef1bc94f7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 20 May 2009 21:51:47 +0200 Subject: [PATCH 027/129] Use UTF-8 for all locale types in the manpage. Though this does not make a difference, it is a cleaner way like this. --- man/i3.man | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/man/i3.man b/man/i3.man index 037827fc..4f0c3fe4 100644 --- a/man/i3.man +++ b/man/i3.man @@ -214,16 +214,16 @@ xset -b # Enforce correct locales from the beginning unset LC_COLLATE export LC_CTYPE=de_DE.UTF-8 -export LC_TIME=de_DE.ISO8859-15 -export LC_NUMERIC=de_DE.ISO8859-15 -export LC_MONETARY=de_DE.ISO8859-15 +export LC_TIME=de_DE.UTF-8 +export LC_NUMERIC=de_DE.UTF-8 +export LC_MONETARY=de_DE.UTF-8 export LC_MESSAGES=C -export LC_PAPER=de_DE.ISO8859-15 -export LC_NAME=de_DE.ISO8859-15 -export LC_ADDRESS=de_DE.ISO8859-15 -export LC_TELEPHONE=de_DE.ISO8859-15 -export LC_MEASUREMENT=de_DE.ISO8859-15 -export LC_IDENTIFICATION=de_DE.ISO8859-15 +export LC_PAPER=de_DE.UTF-8 +export LC_NAME=de_DE.UTF-8 +export LC_ADDRESS=de_DE.UTF-8 +export LC_TELEPHONE=de_DE.UTF-8 +export LC_MEASUREMENT=de_DE.UTF-8 +export LC_IDENTIFICATION=de_DE.UTF-8 # Enable core dumps in case something goes wrong ulimit -c unlimited From 5b8e2ecb1847743548ffa81da142f3524c866e5a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 23 May 2009 16:34:03 +0200 Subject: [PATCH 028/129] Implement floating (please test and find bugs) Details which are missing: A command to hide/show all floating clients, moving/resizing clients with your mouse holding Mod1 (click anywhere in the client, not just on its borders), resize/move by keyboard, select next/previous client by keyboard --- i3.config | 3 + include/data.h | 5 + include/floating.h | 37 +++++++ include/layout.h | 12 ++ include/xcb.h | 6 + src/commands.c | 96 ++++++++++++++-- src/floating.c | 267 +++++++++++++++++++++++++++++++++++++++++++++ src/handlers.c | 37 +++++-- src/layout.c | 23 ++-- src/manage.c | 13 +++ src/util.c | 77 +++++++------ src/xcb.c | 9 ++ 12 files changed, 525 insertions(+), 60 deletions(-) create mode 100644 include/floating.h create mode 100644 src/floating.c diff --git a/i3.config b/i3.config index d2cf13a0..c1369c73 100644 --- a/i3.config +++ b/i3.config @@ -15,6 +15,9 @@ bind Mod1+43 s # Default (Mod1+e) bind Mod1+26 d +# Toggle tiling/floating of the current window +bind Mod1+Shift+65 t + # Focus (Mod1+j/k/l/;) bind Mod1+44 h bind Mod1+45 j diff --git a/include/data.h b/include/data.h index 8cb60b8f..7308f5c1 100644 --- a/include/data.h +++ b/include/data.h @@ -253,6 +253,8 @@ struct Client { /* x, y, width, height of the frame */ Rect rect; + /* Position in floating mode and in tiling mode are saved separately */ + Rect floating_rect; /* x, y, width, height of the child (relative to its frame) */ Rect child_rect; @@ -282,6 +284,9 @@ struct Client { /* fullscreen is pretty obvious */ bool fullscreen; + /* floating? (= not in tiling layout) */ + bool floating; + /* Ensure TITLEBAR_TOP maps to 0 because we use calloc for initialization later */ enum { TITLEBAR_TOP = 0, TITLEBAR_LEFT, TITLEBAR_RIGHT, TITLEBAR_BOTTOM, TITLEBAR_OFF } titlebar_position; diff --git a/include/floating.h b/include/floating.h new file mode 100644 index 00000000..aa3c55b7 --- /dev/null +++ b/include/floating.h @@ -0,0 +1,37 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * (c) 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ +#ifndef _FLOATING_H +#define _FLOATING_H + +/** + * Enters floating mode for the given client. + * Correctly takes care of the position/size (separately stored for tiling/floating mode) + * and repositions/resizes/redecorates the client. + * + */ +void toggle_floating_mode(xcb_connection_t *conn, Client *client); + +/** + * Called whenever the user clicks on a border (not the titlebar!) of a floating window. + * Determines on which border the user clicked and launches the drag_pointer function + * with the resize_callback. + * + */ +int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event); + +/** + * Called when the user clicked on the titlebar of a floating window. + * Calls the drag_pointer function with the drag_window callback + * + */ +void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event); + +#endif diff --git a/include/layout.h b/include/layout.h index 19a40c5d..53dfbb9d 100644 --- a/include/layout.h +++ b/include/layout.h @@ -36,6 +36,18 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw */ void redecorate_window(xcb_connection_t *conn, Client *client); +/** + * Pushes the client’s x and y coordinates to X11 + * + */ +void reposition_client(xcb_connection_t *conn, Client *client); + +/** + * Pushes the client’s width/height to X11 and resizes the child window + * + */ +void resize_client(xcb_connection_t *conn, Client *client); + /** * Renders the given container. Is called by render_layout() or individually (for example * when focus changes in a stacking container) diff --git a/include/xcb.h b/include/xcb.h index c6bb70b2..bc335121 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -126,4 +126,10 @@ void fake_absolute_configure_notify(xcb_connection_t *conn, Client *client); */ void xcb_get_numlock_mask(xcb_connection_t *conn); +/** + * Raises the given window (typically client->frame) above all other windows + * + */ +void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window); + #endif diff --git a/src/commands.c b/src/commands.c index e289e018..96f390f2 100644 --- a/src/commands.c +++ b/src/commands.c @@ -24,6 +24,7 @@ #include "i3.h" #include "xinerama.h" #include "client.h" +#include "floating.h" bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) { /* If this container is empty, we’re done */ @@ -418,6 +419,49 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction render_layout(conn); } +static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *client, int workspace) { + /* t_ws (to workspace) is just a container pointer to the workspace we’re switching to */ + Workspace *t_ws = &(workspaces[workspace-1]); + + LOG("moving floating\n"); + + if (t_ws->screen == NULL) { + LOG("initializing new workspace, setting num to %d\n", workspace-1); + t_ws->screen = c_ws->screen; + /* Copy the dimensions from the virtual screen */ + memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect)); + } else { + /* Check if there is already a fullscreen client on the destination workspace and + * stop moving if so. */ + if (client->fullscreen && (t_ws->fullscreen_client != NULL)) { + LOG("Not moving: Fullscreen client already existing on destination workspace.\n"); + return; + } + } + + /* Remove from focus stack */ + SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); + + if (client->workspace->fullscreen_client == client) + client->workspace->fullscreen_client = NULL; + + /* Insert into destination focus stack */ + client->workspace = t_ws; + SLIST_INSERT_HEAD(&(t_ws->focus_stack), client, focus_clients); + if (client->fullscreen) + t_ws->fullscreen_client = client; + + /* If we’re moving it to an invisible screen, we need to unmap it */ + if (t_ws->screen->current_workspace != t_ws->num) { + LOG("This workspace is not visible, unmapping\n"); + xcb_unmap_window(conn, client->frame); + } + + LOG("done\n"); + + render_layout(conn); +} + /* * Moves the currently selected window to the given workspace * @@ -463,7 +507,9 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa if (container->workspace->fullscreen_client == current_client) container->workspace->fullscreen_client = NULL; + /* TODO: insert it to the correct position */ CIRCLEQ_INSERT_TAIL(&(to_container->clients), current_client, clients); + SLIST_INSERT_HEAD(&(to_container->workspace->focus_stack), current_client, focus_clients); if (current_client->fullscreen) t_ws->fullscreen_client = current_client; @@ -564,6 +610,14 @@ void show_workspace(xcb_connection_t *conn, int workspace) { CIRCLEQ_FOREACH(client, &(c_ws->table[cols][rows]->clients), clients) xcb_map_window(conn, client->frame); + /* Map all floating clients */ + SLIST_FOREACH(client, &(c_ws->focus_stack), focus_clients) { + if (!client->floating) + continue; + + xcb_map_window(conn, client->frame); + } + /* Map all stack windows, if any */ struct Stack_Window *stack_win; SLIST_FOREACH(stack_win, &stack_wins, stack_windows) @@ -683,6 +737,12 @@ static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) { */ void parse_command(xcb_connection_t *conn, const char *command) { LOG("--- parsing command \"%s\" ---\n", command); + /* Get the first client from focus stack because floating clients are not + * in any container, therefore CUR_CELL is not appropriate. */ + Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); + if (last_focused == SLIST_END(&(c_ws->focus_stack))) + last_focused = NULL; + /* Hmm, just to be sure */ if (command[0] == '\0') return; @@ -708,17 +768,17 @@ void parse_command(xcb_connection_t *conn, const char *command) { } if (STARTS_WITH(command, "kill")) { - if (CUR_CELL->currently_focused == NULL) { + if (last_focused == NULL) { LOG("There is no window to kill\n"); return; } LOG("Killing current window\n"); - client_kill(conn, CUR_CELL->currently_focused); + client_kill(conn, last_focused); return; } - /* Is it a jump to a specified workspae, row, col? */ + /* Is it a jump to a specified workspace, row, col? */ if (STARTS_WITH(command, "jump ")) { const char *arguments = command + strlen("jump "); if (arguments[0] == '"') @@ -736,19 +796,34 @@ void parse_command(xcb_connection_t *conn, const char *command) { /* Is it 'f' for fullscreen? */ if (command[0] == 'f') { - if (CUR_CELL->currently_focused == NULL) + if (last_focused == NULL) return; - toggle_fullscreen(conn, CUR_CELL->currently_focused); + toggle_fullscreen(conn, last_focused); return; } /* Is it just 's' for stacking or 'd' for default? */ if ((command[0] == 's' || command[0] == 'd') && (command[1] == '\0')) { + if (last_focused->floating) { + LOG("not switching, this is a floating client\n"); + return; + } LOG("Switching mode for current container\n"); switch_layout_mode(conn, CUR_CELL, (command[0] == 's' ? MODE_STACK : MODE_DEFAULT)); return; } + /* Is it 't' for toggle tiling/floating? */ + if (command[0] == 't') { + if (last_focused == NULL) { + LOG("Cannot toggle tiling/floating: workspace empty\n"); + return; + } + + toggle_floating_mode(conn, last_focused); + return; + } + enum { WITH_WINDOW, WITH_CONTAINER } with = WITH_WINDOW; /* Is it a ? */ @@ -764,7 +839,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { } } - /* It's a normal */ + /* It’s a normal */ char *rest = NULL; enum { ACTION_FOCUS, ACTION_MOVE, ACTION_SNAP } action = ACTION_FOCUS; direction_t direction; @@ -793,7 +868,14 @@ void parse_command(xcb_connection_t *conn, const char *command) { } if (*rest == '\0') { - move_current_window_to_workspace(conn, workspace); + if (last_focused->floating) + move_floating_window_to_workspace(conn, last_focused, workspace); + else move_current_window_to_workspace(conn, workspace); + return; + } + + if (last_focused->floating) { + LOG("Not performing because this is a floating window\n"); return; } diff --git a/src/floating.c b/src/floating.c new file mode 100644 index 00000000..3e8c1c07 --- /dev/null +++ b/src/floating.c @@ -0,0 +1,267 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + * src/floating.c: contains all functions for handling floating clients + * + */ +#include +#include +#include + +#include +#include + +#include "i3.h" +#include "data.h" +#include "util.h" +#include "xcb.h" +#include "debug.h" +#include "layout.h" + +/* On which border was the dragging initiated? */ +typedef enum { BORDER_LEFT, BORDER_RIGHT, BORDER_TOP, BORDER_BOTTOM} border_t; +/* Callback for dragging */ +typedef void(*callback_t)(xcb_connection_t*, Client*, border_t, Rect*, xcb_button_press_event_t*, uint32_t, uint32_t); + +/* Forward definitions */ +static void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event, + border_t border, callback_t callback); + +/* + * Toggles floating mode for the given client. + * Correctly takes care of the position/size (separately stored for tiling/floating mode) + * and repositions/resizes/redecorates the client. + * + */ +void toggle_floating_mode(xcb_connection_t *conn, Client *client) { + Container *con = client->container; + + if (con == NULL) { + LOG("This client is already in floating (container == NULL), re-inserting\n"); + Client *next_tiling; + SLIST_FOREACH(next_tiling, &(client->workspace->focus_stack), focus_clients) + if (!next_tiling->floating) + break; + /* If there are no tiling clients on this workspace, there can only be one + * container: the first one */ + if (next_tiling == SLIST_END(&(client->workspace->focus_stack))) + con = client->workspace->table[0][0]; + else con = next_tiling->container; + + LOG("destination container = %p\n", con); + Client *old_focused = con->currently_focused; + /* Preserve position/size */ + memcpy(&(client->floating_rect), &(client->rect), sizeof(Rect)); + + client->floating = false; + client->container = con; + + if (old_focused != NULL && !old_focused->dock) + CIRCLEQ_INSERT_AFTER(&(con->clients), old_focused, client, clients); + else CIRCLEQ_INSERT_TAIL(&(con->clients), client, clients); + + LOG("Re-inserted the client into the matrix.\n"); + con->currently_focused = client; + + render_container(conn, con); + xcb_flush(conn); + + return; + } + + LOG("Entering floating for client %08x\n", client->child); + + /* Remove the client of its container */ + CIRCLEQ_REMOVE(&(con->clients), client, clients); + client->container = NULL; + + if (con->currently_focused == client) { + LOG("Need to re-adjust currently_focused\n"); + /* Get the next client in the focus stack for this particular container */ + con->currently_focused = get_last_focused_client(conn, con, NULL); + } + + client->floating = true; + + /* Initialize the floating position from the position in tiling mode, if this + * client never was floating (width == 0) */ + if (client->floating_rect.width == 0) { + memcpy(&(client->floating_rect), &(client->rect), sizeof(Rect)); + LOG("(%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y, + client->floating_rect.width, client->floating_rect.height); + } else { + /* If the client was already in floating before we restore the old position / size */ + memcpy(&(client->rect), &(client->floating_rect), sizeof(Rect)); + } + + /* Raise the client */ + xcb_raise_window(conn, client->frame); + reposition_client(conn, client); + resize_client(conn, client); + /* redecorate_window flushes */ + redecorate_window(conn, client); + + /* Re-render the tiling layout of this container */ + render_container(conn, con); + xcb_flush(conn); +} + +/* + * Callback for resizing windows + * + */ +static void resize_callback(xcb_connection_t *conn, Client *client, border_t border, Rect *old_rect, + xcb_button_press_event_t *event, uint32_t new_x, uint32_t new_y) { + switch (border) { + case BORDER_RIGHT: + client->rect.width = old_rect->width + (new_x - event->root_x); + break; + + case BORDER_BOTTOM: + client->rect.height = old_rect->height + (new_y - event->root_y); + break; + + case BORDER_TOP: + client->rect.y = old_rect->y + (new_y - event->root_y); + client->rect.height = old_rect->height + (event->root_y - new_y); + break; + + case BORDER_LEFT: + client->rect.x = old_rect->x + (new_x - event->root_x); + client->rect.width = old_rect->width + (event->root_x - new_x); + break; + } + + /* Push the new position/size to X11 */ + reposition_client(conn, client); + resize_client(conn, client); + xcb_flush(conn); +} + +/* + * Called whenever the user clicks on a border (not the titlebar!) of a floating window. + * Determines on which border the user clicked and launches the drag_pointer function + * with the resize_callback. + * + */ +int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) { + + LOG("floating border click\n"); + + border_t border; + if (event->event_y < 2) + border = BORDER_TOP; + else if (event->event_y >= (client->rect.height - 2)) + border = BORDER_BOTTOM; + else if (event->event_x <= 2) + border = BORDER_LEFT; + else if (event->event_x > 2) + border = BORDER_RIGHT; + else { + LOG("Not on any border, not doing anything.\n"); + return 1; + } + + LOG("border = %d\n", border); + + drag_pointer(conn, client, event, border, resize_callback); + + return 1; +} + +static void drag_window_callback(xcb_connection_t *conn, Client *client, border_t border, Rect *old_rect, + xcb_button_press_event_t *event, uint32_t new_x, uint32_t new_y) { + /* Reposition the client correctly while moving */ + client->rect.x = old_rect->x + (new_x - event->root_x); + client->rect.y = old_rect->y + (new_y - event->root_y); + reposition_client(conn, client); + xcb_flush(conn); +} + +/* + * Called when the user clicked on the titlebar of a floating window. + * Calls the drag_pointer function with the drag_window callback + * + */ +void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) { + LOG("floating_drag_window\n"); + + drag_pointer(conn, client, event, BORDER_TOP /* irrelevant */, drag_window_callback); +} + +/* + * This function grabs your pointer and lets you drag stuff around (borders). + * Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received + * and the given callback will be called with the parameters specified (client, + * border on which the click originally was), the original rect of the client, + * the event and the new coordinates (x, y). + * + */ +static void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event, + border_t border, callback_t callback) { + xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; + uint32_t new_x, new_y; + Rect old_rect; + memcpy(&old_rect, &(client->rect), sizeof(Rect)); + + /* Grab the pointer */ + /* TODO: returncode */ + xcb_grab_pointer(conn, + false, /* get all pointer events specified by the following mask */ + root, /* grab the root window */ + XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, /* which events to let through */ + XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */ + XCB_GRAB_MODE_ASYNC, /* keyboard mode */ + XCB_NONE, /* confine_to = in which window should the cursor stay */ + XCB_NONE, /* don’t display a special cursor */ + XCB_CURRENT_TIME); + + /* Go into our own event loop */ + xcb_flush(conn); + + xcb_generic_event_t *inside_event; + /* I’ve always wanted to have my own eventhandler… */ + while ((inside_event = xcb_wait_for_event(conn))) { + /* Same as get_event_handler in xcb */ + int nr = inside_event->response_type; + if (nr == 0) { + /* An error occured */ + handle_event(NULL, conn, inside_event); + free(inside_event); + continue; + } + assert(nr < 256); + nr &= XCB_EVENT_RESPONSE_TYPE_MASK; + assert(nr >= 2); + + /* Check if we need to escape this loop */ + if (nr == XCB_BUTTON_RELEASE) + break; + + switch (nr) { + case XCB_MOTION_NOTIFY: + new_x = ((xcb_motion_notify_event_t*)inside_event)->root_x; + new_y = ((xcb_motion_notify_event_t*)inside_event)->root_y; + + callback(conn, client, border, &old_rect, event, new_x, new_y); + + break; + default: + LOG("Passing to original handler\n"); + /* Use original handler */ + xcb_event_handle(&evenths, inside_event); + break; + } + free(inside_event); + } + + xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); + xcb_flush(conn); +} + diff --git a/src/handlers.c b/src/handlers.c index da556459..10dd16fb 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -34,6 +34,7 @@ #include "resize.h" #include "client.h" #include "manage.h" +#include "floating.h" /* After mapping/unmapping windows, a notify event is generated. However, we don’t want it, since it’d trigger an infinite loop of switching between the different windows when @@ -322,7 +323,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ Container *con = client->container; int first, second; - if (con == NULL) { + if (client->dock) { LOG("dock. done.\n"); xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); xcb_flush(conn); @@ -334,6 +335,9 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ if (!border_click) { LOG("client. done.\n"); xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); + /* Floating clients should be raised on click */ + if (client->floating) + xcb_raise_window(conn, client->frame); xcb_flush(conn); return 1; } @@ -342,9 +346,22 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ i3Font *font = load_font(conn, config.font); if (event->event_y >= 2 && event->event_y <= (font->height + 2 + 2)) { LOG("click on titlebar\n"); + + /* Floating clients can be dragged by grabbing their titlebar */ + if (client->floating) { + /* Firstly, we raise it. Maybe the user just wanted to raise it without grabbing */ + uint32_t values[] = { XCB_STACK_MODE_ABOVE }; + xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values); + xcb_flush(conn); + + floating_drag_window(conn, client, event); + } return 1; } + if (client->floating) + return floating_border_click(conn, client, event); + if (event->event_y < 2) { /* This was a press on the top border */ if (con->row == 0) @@ -508,6 +525,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti if (client->name != NULL) free(client->name); + /* Clients without a container are either floating or dock windows */ if (client->container != NULL) { Container *con = client->container; @@ -524,6 +542,8 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti /* Only if this is the active container, we need to really change focus */ if ((con->currently_focused != NULL) && ((con == CUR_CELL) || client->fullscreen)) set_focus(conn, con->currently_focused, true); + } else if (client->floating) { + SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); } if (client->dock) { @@ -543,13 +563,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti } /* Let’s see how many clients there are left on the workspace to delete it if it’s empty */ - bool workspace_empty = true; - FOR_TABLE(client->workspace) - if (!CIRCLEQ_EMPTY(&(client->workspace->table[cols][rows]->clients))) { - workspace_empty = false; - break; - } + bool workspace_empty = SLIST_EMPTY(&(client->workspace->focus_stack)); + Client *to_focus = (!workspace_empty ? SLIST_FIRST(&(client->workspace->focus_stack)) : NULL); + /* If this workspace is currently active, we don’t delete it */ i3Screen *screen; TAILQ_FOREACH(screen, virtual_screens, screens) if (screen->current_workspace == client->workspace->num) { @@ -564,6 +581,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti render_layout(conn); + /* Ensure the focus is set to the next client in the focus stack */ + if (to_focus != NULL) + set_focus(conn, to_focus, true); + return 1; } @@ -755,7 +776,7 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t * return 1; } - if (client->container->mode != MODE_STACK) + if (client->container == NULL || client->container->mode != MODE_STACK) decorate_window(conn, client, client->frame, client->titlegc, 0); else { uint32_t background_color; diff --git a/src/layout.c b/src/layout.c index ea9ff4f2..1f0a6d87 100644 --- a/src/layout.c +++ b/src/layout.c @@ -83,7 +83,7 @@ int get_unoccupied_y(Workspace *workspace, int col) { * */ void redecorate_window(xcb_connection_t *conn, Client *client) { - if (client->container->mode == MODE_STACK) { + if (client->container != NULL && client->container->mode == MODE_STACK) { render_container(conn, client->container); /* We clear the frame to generate exposure events, because the color used in drawing may be different */ @@ -105,12 +105,13 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw border_color; /* Clients without a container (docks) won’t get decorated */ - if (client->container == NULL) + if (client->dock) return; - if (client->container->currently_focused == client) { + LOG("redecorating child %08x\n", client->child); + if (client->floating || client->container->currently_focused == client) { /* Distinguish if the window is currently focused… */ - if (CUR_CELL->currently_focused == client) + if (client->floating || CUR_CELL->currently_focused == client) background_color = get_colorpixel(conn, "#285577"); /* …or if it is the focused window in a not focused container */ else background_color = get_colorpixel(conn, "#555555"); @@ -133,14 +134,14 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, background_color); /* In stacking mode, we only render the rect for this specific decoration */ - if (client->container->mode == MODE_STACK) { - xcb_rectangle_t rect = {0, offset, client->container->width, offset + decoration_height }; - xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect); - } else { + if (client->container != NULL && client->container->mode == MODE_STACK) { /* We need to use the container’s width because it is the more recent value - when in stacking mode, clients get reconfigured only on demand (the not active client is not reconfigured), so the client’s rect.width would be wrong */ - xcb_rectangle_t rect = {0, 0, client->container->width, client->rect.height}; + xcb_rectangle_t rect = {0, offset, client->container->width, offset + decoration_height }; + xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect); + } else { + xcb_rectangle_t rect = {0, 0, client->rect.width, client->rect.height}; xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect); /* Draw the inner background to have a black frame around clients (such as mplayer) @@ -179,7 +180,7 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw * Pushes the client’s x and y coordinates to X11 * */ -static void reposition_client(xcb_connection_t *conn, Client *client) { +void reposition_client(xcb_connection_t *conn, Client *client) { LOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y); /* Note: We can use a pointer to client->x like an array of uint32_ts because it is followed by client->y by definition */ @@ -190,7 +191,7 @@ static void reposition_client(xcb_connection_t *conn, Client *client) { * Pushes the client’s width/height to X11 and resizes the child window * */ -static void resize_client(xcb_connection_t *conn, Client *client) { +void resize_client(xcb_connection_t *conn, Client *client) { i3Font *font = load_font(conn, config.font); LOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height); diff --git a/src/manage.c b/src/manage.c index d330d88f..e139b6c1 100644 --- a/src/manage.c +++ b/src/manage.c @@ -27,6 +27,7 @@ #include "handlers.h" #include "layout.h" #include "manage.h" +#include "floating.h" /* * Go through all existing windows (if the window manager is restarted) and manage them @@ -333,6 +334,18 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients); SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients); + + /* Ensure that it is below all floating clients */ + Client *first_floating; + SLIST_FOREACH(first_floating, &(new->container->workspace->focus_stack), focus_clients) + if (first_floating->floating) + break; + + if (first_floating != SLIST_END(&(new->container->workspace->focus_stack))) { + LOG("Setting below floating\n"); + uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW }; + xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); + } } /* Check if the window already got the fullscreen hint set */ diff --git a/src/util.c b/src/util.c index a1146251..44f25fb8 100644 --- a/src/util.c +++ b/src/util.c @@ -262,6 +262,15 @@ void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws) { unmapped_clients++; } + /* To find floating clients, we traverse the focus stack */ + SLIST_FOREACH(client, &(u_ws->focus_stack), focus_clients) { + if (!client->floating) + continue; + + xcb_unmap_window(conn, client->frame); + unmapped_clients++; + } + /* If we did not unmap any clients, the workspace is empty and we can destroy it */ if (unmapped_clients == 0) { /* Re-assign the workspace of all dock clients which use this workspace */ @@ -296,7 +305,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) { return; /* Store the old client */ - Client *old_client = CUR_CELL->currently_focused; + Client *old_client = SLIST_FIRST(&(c_ws->focus_stack)); /* Check if the focus needs to be changed at all */ if (!set_anyways && (old_client == client)) { @@ -307,47 +316,51 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) { /* Store current_row/current_col */ c_ws->current_row = current_row; c_ws->current_col = current_col; - c_ws = client->container->workspace; + c_ws = client->workspace; /* Update container */ - client->container->currently_focused = client; + if (client->container != NULL) { + client->container->currently_focused = client; - current_col = client->container->col; - current_row = client->container->row; + current_col = client->container->col; + current_row = client->container->row; + } LOG("set_focus(frame %08x, child %08x, name %s)\n", client->frame, client->child, client->name); /* Set focus to the entered window, and flush xcb buffer immediately */ xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, client->child, XCB_CURRENT_TIME); //xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, 10, 10); - /* Get the client which was last focused in this particular container, it may be a different - one than old_client */ - Client *last_focused = get_last_focused_client(conn, client->container, NULL); + if (client->container != NULL) { + /* Get the client which was last focused in this particular container, it may be a different + one than old_client */ + Client *last_focused = get_last_focused_client(conn, client->container, NULL); - /* In stacking containers, raise the client in respect to the one which was focused before */ - if (client->container->mode == MODE_STACK && client->container->workspace->fullscreen_client == NULL) { - /* We need to get the client again, this time excluding the current client, because - * we might have just gone into stacking mode and need to raise */ - Client *last_focused = get_last_focused_client(conn, client->container, client); + /* In stacking containers, raise the client in respect to the one which was focused before */ + if (client->container->mode == MODE_STACK && client->container->workspace->fullscreen_client == NULL) { + /* We need to get the client again, this time excluding the current client, because + * we might have just gone into stacking mode and need to raise */ + Client *last_focused = get_last_focused_client(conn, client->container, client); - if (last_focused != NULL) { - LOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child); - uint32_t values[] = { last_focused->frame, XCB_STACK_MODE_ABOVE }; - xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); + if (last_focused != NULL) { + LOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child); + uint32_t values[] = { last_focused->frame, XCB_STACK_MODE_ABOVE }; + xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); + } } - } - /* If it is the same one as old_client, we save us the unnecessary redecorate */ - if ((last_focused != NULL) && (last_focused != old_client)) - redecorate_window(conn, last_focused); + /* If it is the same one as old_client, we save us the unnecessary redecorate */ + if ((last_focused != NULL) && (last_focused != old_client)) + redecorate_window(conn, last_focused); + } /* If we’re in stacking mode, this renders the container to update changes in the title bars and to raise the focused client */ if ((old_client != NULL) && (old_client != client) && !old_client->dock) redecorate_window(conn, old_client); - SLIST_REMOVE(&(client->container->workspace->focus_stack), client, Client, focus_clients); - SLIST_INSERT_HEAD(&(client->container->workspace->focus_stack), client, focus_clients); + SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); + SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients); /* redecorate_window flushes, so we don’t need to */ redecorate_window(conn, client); @@ -521,18 +534,14 @@ Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitl if (workspaces[workspace].screen == NULL) continue; - FOR_TABLE(&(workspaces[workspace])) { - Container *con = workspaces[workspace].table[cols][rows]; - Client *client; + Client *client; + SLIST_FOREACH(client, &(workspaces[workspace].focus_stack), focus_clients) { + LOG("Checking client with class=%s, name=%s\n", client->window_class, client->name); + if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len)) + continue; - CIRCLEQ_FOREACH(client, &(con->clients), clients) { - LOG("Checking client with class=%s, name=%s\n", client->window_class, client->name); - if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len)) - continue; - - matching = client; - goto done; - } + matching = client; + goto done; } } diff --git a/src/xcb.c b/src/xcb.c index 10608ac4..cefb9c33 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -278,3 +278,12 @@ void xcb_get_numlock_mask(xcb_connection_t *conn) { xcb_key_symbols_free(keysyms); free(reply); } + +/* + * Raises the given window (typically client->frame) above all other windows + * + */ +void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window) { + uint32_t values[] = { XCB_STACK_MODE_ABOVE }; + xcb_configure_window(conn, window, XCB_CONFIG_WINDOW_STACK_MODE, values); +} From ac5c2fcf19934bcdff5c7caa5dab072a74e4e0c3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 26 May 2009 16:46:50 +0200 Subject: [PATCH 029/129] Bugfix: Fix fullscreen for floating clients, fix window name updates for floating clients --- src/handlers.c | 4 ++-- src/util.c | 33 +++++++++++++++++++++------------ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index 10dd16fb..bd8d0f9a 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -635,7 +635,7 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, if (client->dock) return 1; - if (client->container->mode == MODE_STACK) + if (client->container != NULL && client->container->mode == MODE_STACK) render_container(conn, client->container); else decorate_window(conn, client, client->frame, client->titlegc, 0); xcb_flush(conn); @@ -703,7 +703,7 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t if (client->dock) return 1; - if (client->container->mode == MODE_STACK) + if (client->container != NULL && client->container->mode == MODE_STACK) render_container(conn, client->container); else decorate_window(conn, client, client->frame, client->titlegc, 0); xcb_flush(conn); diff --git a/src/util.c b/src/util.c index 44f25fb8..5174c91c 100644 --- a/src/util.c +++ b/src/util.c @@ -447,10 +447,10 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode) * */ void toggle_fullscreen(xcb_connection_t *conn, Client *client) { - /* clients without a container (docks) cannot be focused */ - assert(client->container != NULL); + /* dock clients cannot enter fullscreen mode */ + assert(!client->dock); - Workspace *workspace = client->container->workspace; + Workspace *workspace = client->workspace; if (!client->fullscreen) { if (workspace->fullscreen_client != NULL) { @@ -461,10 +461,10 @@ void toggle_fullscreen(xcb_connection_t *conn, Client *client) { workspace->fullscreen_client = client; LOG("Entering fullscreen mode...\n"); /* We just entered fullscreen mode, let’s configure the window */ - uint32_t mask = XCB_CONFIG_WINDOW_X | - XCB_CONFIG_WINDOW_Y | - XCB_CONFIG_WINDOW_WIDTH | - XCB_CONFIG_WINDOW_HEIGHT; + uint32_t mask = XCB_CONFIG_WINDOW_X | + XCB_CONFIG_WINDOW_Y | + XCB_CONFIG_WINDOW_WIDTH | + XCB_CONFIG_WINDOW_HEIGHT; uint32_t values[4] = {workspace->rect.x, workspace->rect.y, workspace->rect.width, @@ -491,11 +491,20 @@ void toggle_fullscreen(xcb_connection_t *conn, Client *client) { LOG("leaving fullscreen mode\n"); client->fullscreen = false; workspace->fullscreen_client = NULL; - /* Because the coordinates of the window haven’t changed, it would not be - re-configured if we don’t set the following flag */ - client->force_reconfigure = true; - /* We left fullscreen mode, redraw the whole layout to ensure enternotify events are disabled */ - render_layout(conn); + if (client->floating) { + /* For floating clients it’s enough if we just reconfigure that window (in fact, + * re-rendering the layout will not update the client.) */ + reposition_client(conn, client); + resize_client(conn, client); + /* redecorate_window flushes */ + redecorate_window(conn, client); + } else { + /* Because the coordinates of the window haven’t changed, it would not be + re-configured if we don’t set the following flag */ + client->force_reconfigure = true; + /* We left fullscreen mode, redraw the whole layout to ensure enternotify events are disabled */ + render_layout(conn); + } } xcb_flush(conn); From b58e2fa8ed2dfc3ce498bc3ab16989a944fe339c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 26 May 2009 16:49:57 +0200 Subject: [PATCH 030/129] Move toggle_fullscreen to client.c --- include/client.h | 8 ++++++ include/util.h | 8 ------ src/client.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++ src/commands.c | 2 +- src/handlers.c | 2 +- src/manage.c | 3 +- src/util.c | 70 ---------------------------------------------- 7 files changed, 84 insertions(+), 81 deletions(-) diff --git a/include/client.h b/include/client.h index a88f8d0b..2547c52f 100644 --- a/include/client.h +++ b/include/client.h @@ -44,4 +44,12 @@ void client_kill(xcb_connection_t *conn, Client *window); bool client_matches_class_name(Client *client, char *to_class, char *to_title, char *to_title_ucs, int to_title_ucs_len); +/** + * Toggles fullscreen mode for the given client. It updates the data structures and + * reconfigures (= resizes/moves) the client and its frame to the full size of the + * screen. When leaving fullscreen, re-rendering the layout is forced. + * + */ +void client_toggle_fullscreen(xcb_connection_t *conn, Client *client); + #endif diff --git a/include/util.h b/include/util.h index 709e48a4..5658492e 100644 --- a/include/util.h +++ b/include/util.h @@ -162,14 +162,6 @@ void leave_stack_mode(xcb_connection_t *conn, Container *container); */ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode); -/** - * Toggles fullscreen mode for the given client. It updates the data structures and - * reconfigures (= resizes/moves) the client and its frame to the full size of the - * screen. When leaving fullscreen, re-rendering the layout is forced. - * - */ -void toggle_fullscreen(xcb_connection_t *conn, Client *client); - /** * Gets the first matching client for the given window class/window title. * If the paramater specific is set to a specific client, only this one diff --git a/src/client.c b/src/client.c index f5409d09..f4df06ae 100644 --- a/src/client.c +++ b/src/client.c @@ -12,6 +12,7 @@ */ #include #include +#include #include #include @@ -21,6 +22,7 @@ #include "xcb.h" #include "util.h" #include "queue.h" +#include "layout.h" /* * Removes the given client from the container, either because it will be inserted into another @@ -131,3 +133,73 @@ bool client_matches_class_name(Client *client, char *to_class, char *to_title, return true; } + +/* + * Toggles fullscreen mode for the given client. It updates the data structures and + * reconfigures (= resizes/moves) the client and its frame to the full size of the + * screen. When leaving fullscreen, re-rendering the layout is forced. + * + */ +void client_toggle_fullscreen(xcb_connection_t *conn, Client *client) { + /* dock clients cannot enter fullscreen mode */ + assert(!client->dock); + + Workspace *workspace = client->workspace; + + if (!client->fullscreen) { + if (workspace->fullscreen_client != NULL) { + LOG("Not entering fullscreen mode, there already is a fullscreen client.\n"); + return; + } + client->fullscreen = true; + workspace->fullscreen_client = client; + LOG("Entering fullscreen mode...\n"); + /* We just entered fullscreen mode, let’s configure the window */ + uint32_t mask = XCB_CONFIG_WINDOW_X | + XCB_CONFIG_WINDOW_Y | + XCB_CONFIG_WINDOW_WIDTH | + XCB_CONFIG_WINDOW_HEIGHT; + uint32_t values[4] = {workspace->rect.x, + workspace->rect.y, + workspace->rect.width, + workspace->rect.height}; + + LOG("child itself will be at %dx%d with size %dx%d\n", + values[0], values[1], values[2], values[3]); + + xcb_configure_window(conn, client->frame, mask, values); + + /* Child’s coordinates are relative to the parent (=frame) */ + values[0] = 0; + values[1] = 0; + xcb_configure_window(conn, client->child, mask, values); + + /* Raise the window */ + values[0] = XCB_STACK_MODE_ABOVE; + xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values); + + Rect child_rect = workspace->rect; + child_rect.x = child_rect.y = 0; + fake_configure_notify(conn, child_rect, client->child); + } else { + LOG("leaving fullscreen mode\n"); + client->fullscreen = false; + workspace->fullscreen_client = NULL; + if (client->floating) { + /* For floating clients it’s enough if we just reconfigure that window (in fact, + * re-rendering the layout will not update the client.) */ + reposition_client(conn, client); + resize_client(conn, client); + /* redecorate_window flushes */ + redecorate_window(conn, client); + } else { + /* Because the coordinates of the window haven’t changed, it would not be + re-configured if we don’t set the following flag */ + client->force_reconfigure = true; + /* We left fullscreen mode, redraw the whole layout to ensure enternotify events are disabled */ + render_layout(conn); + } + } + + xcb_flush(conn); +} diff --git a/src/commands.c b/src/commands.c index 96f390f2..76953abf 100644 --- a/src/commands.c +++ b/src/commands.c @@ -798,7 +798,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { if (command[0] == 'f') { if (last_focused == NULL) return; - toggle_fullscreen(conn, last_focused); + client_toggle_fullscreen(conn, last_focused); return; } diff --git a/src/handlers.c b/src/handlers.c index bd8d0f9a..81fb8655 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -830,7 +830,7 @@ int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message (!client->fullscreen && (event->data.data32[0] == _NET_WM_STATE_ADD || event->data.data32[0] == _NET_WM_STATE_TOGGLE))) - toggle_fullscreen(conn, client); + client_toggle_fullscreen(conn, client); } else { LOG("unhandled clientmessage\n"); return 0; diff --git a/src/manage.c b/src/manage.c index e139b6c1..5e6e6eed 100644 --- a/src/manage.c +++ b/src/manage.c @@ -28,6 +28,7 @@ #include "layout.h" #include "manage.h" #include "floating.h" +#include "client.h" /* * Go through all existing windows (if the window manager is restarted) and manage them @@ -359,7 +360,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, /* If the window got the fullscreen state, we just toggle fullscreen and don’t event bother to redraw the layout – that would not change anything anyways */ - toggle_fullscreen(conn, new); + client_toggle_fullscreen(conn, new); return; } diff --git a/src/util.c b/src/util.c index 5174c91c..6b931995 100644 --- a/src/util.c +++ b/src/util.c @@ -440,76 +440,6 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode) set_focus(conn, container->currently_focused, true); } -/* - * Toggles fullscreen mode for the given client. It updates the data structures and - * reconfigures (= resizes/moves) the client and its frame to the full size of the - * screen. When leaving fullscreen, re-rendering the layout is forced. - * - */ -void toggle_fullscreen(xcb_connection_t *conn, Client *client) { - /* dock clients cannot enter fullscreen mode */ - assert(!client->dock); - - Workspace *workspace = client->workspace; - - if (!client->fullscreen) { - if (workspace->fullscreen_client != NULL) { - LOG("Not entering fullscreen mode, there already is a fullscreen client.\n"); - return; - } - client->fullscreen = true; - workspace->fullscreen_client = client; - LOG("Entering fullscreen mode...\n"); - /* We just entered fullscreen mode, let’s configure the window */ - uint32_t mask = XCB_CONFIG_WINDOW_X | - XCB_CONFIG_WINDOW_Y | - XCB_CONFIG_WINDOW_WIDTH | - XCB_CONFIG_WINDOW_HEIGHT; - uint32_t values[4] = {workspace->rect.x, - workspace->rect.y, - workspace->rect.width, - workspace->rect.height}; - - LOG("child itself will be at %dx%d with size %dx%d\n", - values[0], values[1], values[2], values[3]); - - xcb_configure_window(conn, client->frame, mask, values); - - /* Child’s coordinates are relative to the parent (=frame) */ - values[0] = 0; - values[1] = 0; - xcb_configure_window(conn, client->child, mask, values); - - /* Raise the window */ - values[0] = XCB_STACK_MODE_ABOVE; - xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values); - - Rect child_rect = workspace->rect; - child_rect.x = child_rect.y = 0; - fake_configure_notify(conn, child_rect, client->child); - } else { - LOG("leaving fullscreen mode\n"); - client->fullscreen = false; - workspace->fullscreen_client = NULL; - if (client->floating) { - /* For floating clients it’s enough if we just reconfigure that window (in fact, - * re-rendering the layout will not update the client.) */ - reposition_client(conn, client); - resize_client(conn, client); - /* redecorate_window flushes */ - redecorate_window(conn, client); - } else { - /* Because the coordinates of the window haven’t changed, it would not be - re-configured if we don’t set the following flag */ - client->force_reconfigure = true; - /* We left fullscreen mode, redraw the whole layout to ensure enternotify events are disabled */ - render_layout(conn); - } - } - - xcb_flush(conn); -} - /* * Gets the first matching client for the given window class/window title. * If the paramater specific is set to a specific client, only this one From 94cead993cea3d33735a0f893f39c03385a7db90 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 26 May 2009 17:09:34 +0200 Subject: [PATCH 031/129] =?UTF-8?q?Don=E2=80=99t=20process=20autostart=20w?= =?UTF-8?q?hen=20restarting=20(new=20parameter=20-a)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- man/asciidoc.conf | 2 +- man/i3.man | 12 ++++++++++-- src/commands.c | 26 ++++++++++++++++++++++++++ src/mainx.c | 15 +++++++++++---- 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/man/asciidoc.conf b/man/asciidoc.conf index 3854debd..cd411ec7 100644 --- a/man/asciidoc.conf +++ b/man/asciidoc.conf @@ -7,7 +7,7 @@ template::[header-declarations] {mantitle} {manvolnum} i3 -alpha +beta i3 Manual diff --git a/man/i3.man b/man/i3.man index 4f0c3fe4..fbe2ac83 100644 --- a/man/i3.man +++ b/man/i3.man @@ -1,7 +1,7 @@ i3(1) ===== Michael Stapelberg -v3.alpha-bf1, May 2009 +v3.beta, May 2009 == NAME @@ -9,7 +9,15 @@ i3 - an improved dynamic, tiling window manager == SYNOPSIS -i3 [-c configfile] +i3 [-c configfile] [-a] + +== OPTIONS + +-c:: +Specifies an alternate configuration file path + +-a:: +Disables autostart. == DESCRIPTION diff --git a/src/commands.c b/src/commands.c index 76953abf..20a8bd05 100644 --- a/src/commands.c +++ b/src/commands.c @@ -731,6 +731,29 @@ static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) { } } +/* + * Goes through the list of arguments (for exec()) and checks if the given argument + * is present. If not, it copies the arguments (because we cannot realloc it) and + * appends the given argument. + * + */ +static char **append_argument(char **original, char *argument) { + int num_args; + for (num_args = 0; original[num_args] != NULL; num_args++) { + LOG("original argument: \"%s\"\n", original[num_args]); + /* If the argument is already present we return the original pointer */ + if (strcmp(original[num_args], argument) == 0) + return original; + } + /* Copy the original array */ + char **result = smalloc((num_args+2) * sizeof(char*)); + memcpy(result, original, num_args * sizeof(char*)); + result[num_args] = argument; + result[num_args+1] = NULL; + + return result; +} + /* * Parses a command, see file CMDMODE for more information * @@ -763,6 +786,9 @@ void parse_command(xcb_connection_t *conn, const char *command) { /* Is it ? Then restart in place. */ if (STARTS_WITH(command, "restart")) { LOG("restarting \"%s\"...\n", start_argv[0]); + /* make sure -a is in the argument list or append it */ + start_argv = append_argument(start_argv, "-a"); + execvp(start_argv[0], start_argv); /* not reached */ } diff --git a/src/mainx.c b/src/mainx.c index 60cd6a0f..50db96d9 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -72,6 +72,7 @@ int num_screens = 0; int main(int argc, char *argv[], char *env[]) { int i, screens, opt; char *override_configpath = NULL; + bool autostart = true; xcb_connection_t *conn; xcb_property_handlers_t prophs; xcb_window_t root; @@ -85,8 +86,12 @@ int main(int argc, char *argv[], char *env[]) { start_argv = argv; - while ((opt = getopt(argc, argv, "c:v")) != -1) { + while ((opt = getopt(argc, argv, "c:va")) != -1) { switch (opt) { + case 'a': + LOG("Autostart disabled using -a\n"); + autostart = false; + break; case 'c': override_configpath = sstrdup(optarg); break; @@ -270,9 +275,11 @@ int main(int argc, char *argv[], char *env[]) { /* Autostarting exec-lines */ struct Autostart *exec; - TAILQ_FOREACH(exec, &autostarts, autostarts) { - LOG("auto-starting %s\n", exec->command); - start_application(exec->command); + if (autostart) { + TAILQ_FOREACH(exec, &autostarts, autostarts) { + LOG("auto-starting %s\n", exec->command); + start_application(exec->command); + } } /* check for Xinerama */ From 9c3b37f2d11d10d1fa61487cf3f77098e4bfe455 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 26 May 2009 17:16:51 +0200 Subject: [PATCH 032/129] Bugfix: Cleanup the table after putting clients into floating mode --- src/commands.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/commands.c b/src/commands.c index 20a8bd05..ee2f6e61 100644 --- a/src/commands.c +++ b/src/commands.c @@ -262,7 +262,8 @@ static void move_current_window(xcb_connection_t *conn, direction_t direction) { /* Fix colspan/rowspan if it’d overlap */ fix_colrowspan(conn, workspace); - render_layout(conn); + render_workspace(conn, workspace->screen, workspace); + xcb_flush(conn); set_focus(conn, current_client, true); } @@ -847,6 +848,14 @@ void parse_command(xcb_connection_t *conn, const char *command) { } toggle_floating_mode(conn, last_focused); + /* delete all empty columns/rows */ + cleanup_table(conn, last_focused->workspace); + + /* Fix colspan/rowspan if it’d overlap */ + fix_colrowspan(conn, last_focused->workspace); + + render_workspace(conn, last_focused->workspace->screen, last_focused->workspace); + xcb_flush(conn); return; } From 2975c6a96946d7473f633821004178cbea149904 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 26 May 2009 17:25:45 +0200 Subject: [PATCH 033/129] Document new files in the hacking-howto --- docs/hacking-howto | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/docs/hacking-howto b/docs/hacking-howto index 72796b0c..69f5a167 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -107,23 +107,35 @@ Contains forward definitions for all public functions, aswell as doxygen-compati comments (so if you want to get a bit more of the big picture, either browse all header files or use doxygen if you prefer that). +src/client.c:: +Contains all functions which are specific to a certain client (make it +fullscreen, see if its class/name matches a pattern, kill it, …). + src/commands.c:: -Parsing commands +Parsing commands and actually execute them (focussing, moving, …). src/config.c:: -Parses the configuration file +Parses the configuration file. src/debug.c:: -Contains debugging functions to print unhandled X events +Contains debugging functions to print unhandled X events. + +src/floating.c:: +Contains functions for floating mode (mostly resizing/dragging). src/handlers.c:: -Contains all handlers for all kind of X events +Contains all handlers for all kind of X events (new window title, new hints, +unmapping, key presses, button presses, …). src/layout.c:: -Renders your layout (screens, workspaces, containers) +Renders your layout (screens, workspaces, containers). src/mainx.c:: -Initializes the window manager +Initializes the window manager. + +src/manage.c:: +Looks at existing or new windows and decides whether to manage them. If so, it +reparents the window and inserts it into our data structures. src/resize.c:: Contains the functions to resize columns/rows in the table. From b0cf3ec0261fe7ce00146ca92b659fef5a742ae4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 26 May 2009 17:37:56 +0200 Subject: [PATCH 034/129] Document binding on Mode_switch in userguide, be more verbose in config --- docs/userguide | 42 +++++++++++++++++++++++++++++------------- i3.config | 3 +++ src/config.c | 2 +- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/docs/userguide b/docs/userguide index 36c2af4b..3f337a19 100644 --- a/docs/userguide +++ b/docs/userguide @@ -3,21 +3,23 @@ i3 User’s Guide Michael Stapelberg May 2009 -This document contains all information you need to configuring and using the i3 window -manager. If it does not, please contact me on IRC, Jabber or E-Mail and I’ll help you out. +This document contains all information you need to configuring and using the i3 +window manager. If it does not, please contact me on IRC, Jabber or E-Mail and +I’ll help you out. == Configuring i3 TODO: document the other options, implement variables before terminal:: - Specifies the terminal emulator program you prefer. It will be started by default when - you press Mod1+Enter, but you can overwrite this. Refer to it as +$terminal+ to keep things - modular. + Specifies the terminal emulator program you prefer. It will be started + by default when you press Mod1+Enter, but you can overwrite this. Refer + to it as +$terminal+ to keep things modular. font:: - Specifies the default font you want i3 to use. Use an X core font descriptor here, like - +-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1+. You can use +xfontsel(1)+ - to pick one. + Specifies the default font you want i3 to use. Use an X core font + descriptor here, like + +-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1+. You can + use +xfontsel(1)+ to pick one. === Keyboard bindings @@ -37,13 +39,27 @@ bind Mod1+41 f bind Mod1+Shift+27 restart -------------------------------- +Available Modifiers: + +Mod1-Mod5, Shift, Control:: +Standard modifiers, see +xmodmap(1)+ + +Mode_switch:: +Unlike other window managers, i3 can use Mode_switch as a modifier. This allows +you to remap capslock (for example) to Mode_switch and use it for both: typing +umlauts or special characters 'and' having some comfortably reachable key +bindings. For example, when typing, capslock+1 or capslock+2 for switching +workspaces is totally convenient. Try it :-). + === Automatically putting clients on specific workspaces -It is recommended that you match on window classes whereever possible because some applications -first create their window and then care about setting the correct title. Firefox with Vimperator -comes to mind, as the window starts up being named Firefox and only when Vimperator is loaded, -the title changes. As i3 will get the title as soon as the application maps the window (mapping -means actually displaying it on the screen), you’d need to have to match on Firefox in this case. +It is recommended that you match on window classes whereever possible because +some applications first create their window and then care about setting the +correct title. Firefox with Vimperator comes to mind, as the window starts up +being named Firefox and only when Vimperator is loaded, the title changes. As +i3 will get the title as soon as the application maps the window (mapping means +actually displaying it on the screen), you’d need to have to match on Firefox +in this case. *Syntax*: ---------------------------------------------------- diff --git a/i3.config b/i3.config index c1369c73..7bc3fe80 100644 --- a/i3.config +++ b/i3.config @@ -1,6 +1,9 @@ # This configuration uses Mod1 and Mod3. Make sure they are mapped properly using xev(1) # and xmodmap(1). Usually, Mod1 is Alt (Alt_L) and Mod3 is Windows (Super_L) +# Tell i3 about your preferred terminal. You can refer to this as $terminal +# later. It is recommended to set this option to allow i3 to open a terminal +# containing the introduction on first start. terminal /usr/bin/urxvt # ISO 10646 = Unicode diff --git a/src/config.c b/src/config.c index 562b2e92..ab9ea9aa 100644 --- a/src/config.c +++ b/src/config.c @@ -144,7 +144,7 @@ void load_configuration(const char *override_configpath) { class_title++; char *end = strchr(class_title, '"'); if (end == NULL) - die("Malformatted assignment, couldn't find finishing quote\n"); + die("Malformed assignment, couldn't find terminating quote\n"); *end = '\0'; } else { /* If it is not quoted, we terminate it at the first space */ From 8f844fb9d5384624f8ace03b696b493f051ec6f8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 27 May 2009 00:47:00 +0200 Subject: [PATCH 035/129] Remove MAKE=make from makefile to ensure recursive invocations work (i.e. clean target) See http://www.gnu.org/software/make/manual/make.html#MAKE-Variable --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index 66277b5e..9f82b1a9 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,6 @@ UNAME=$(shell uname) DEBUG=1 INSTALL=install -MAKE=make GIT_VERSION=$(shell git describe --tags --always) VERSION=$(shell git describe --tags --abbrev=0) From bcd68d9ca1d4b64e66dc508d4c6588e82290ec9f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 27 May 2009 12:15:23 +0200 Subject: [PATCH 036/129] Bugfix: Fix crash when focusing/moving on an empty workspace (Thanks Mirko) --- src/commands.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands.c b/src/commands.c index ee2f6e61..f54787a9 100644 --- a/src/commands.c +++ b/src/commands.c @@ -831,7 +831,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { /* Is it just 's' for stacking or 'd' for default? */ if ((command[0] == 's' || command[0] == 'd') && (command[1] == '\0')) { - if (last_focused->floating) { + if (last_focused == NULL || last_focused->floating) { LOG("not switching, this is a floating client\n"); return; } @@ -903,14 +903,14 @@ void parse_command(xcb_connection_t *conn, const char *command) { } if (*rest == '\0') { - if (last_focused->floating) + if (last_focused != NULL && last_focused->floating) move_floating_window_to_workspace(conn, last_focused, workspace); else move_current_window_to_workspace(conn, workspace); return; } - if (last_focused->floating) { - LOG("Not performing because this is a floating window\n"); + if (last_focused == NULL || last_focused->floating) { + LOG("Not performing (null or floating) \n"); return; } From 94ee39d1ce8e5d3a8e2349542837b18112eea09b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 27 May 2009 12:27:29 +0200 Subject: [PATCH 037/129] =?UTF-8?q?Bugfix:=20Forgot=20to=20update=20client?= =?UTF-8?q?=E2=80=99s=20workspace=20pointer=20(Thanks=20Mirko)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/commands.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/commands.c b/src/commands.c index f54787a9..d3ef90f2 100644 --- a/src/commands.c +++ b/src/commands.c @@ -251,6 +251,7 @@ static void move_current_window(xcb_connection_t *conn, direction_t direction) { /* Update data structures */ current_client->container = new; + current_client->workspace = new->workspace; container->currently_focused = to_focus; new->currently_focused = current_client; @@ -517,6 +518,7 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa LOG("Moved.\n"); current_client->container = to_container; + current_client->workspace = to_container->workspace; container->currently_focused = to_focus; to_container->currently_focused = current_client; From d4fb34abdd549f7331933178063bb1480e33b7db Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 27 May 2009 18:19:14 +0200 Subject: [PATCH 038/129] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20ignore=20ente?= =?UTF-8?q?r=5Fnotify=20events=20for=20clients=20on=20different=20screens?= =?UTF-8?q?=20(Thanks=20Mirko)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes ticket #41. --- src/handlers.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index 81fb8655..8f164cd2 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -199,11 +199,11 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_ return 1; } - if (client->container != NULL && client->container->workspace != c_ws) { + if (client->workspace != c_ws && client->workspace->screen == c_ws->screen) { /* This can happen when a client gets assigned to a different workspace than * the current one (see src/mainx.c:reparent_window). Shortly after it was created, * an enter_notify will follow. */ - LOG("enter_notify for a client on a different workspace, ignoring\n"); + LOG("enter_notify for a client on a different workspace but the same screen, ignoring\n"); return 1; } From ac6561019b6b2a25aeb970a536123f8f262af96e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 27 May 2009 18:46:58 +0200 Subject: [PATCH 039/129] =?UTF-8?q?Don=E2=80=99t=20kill=20workspaces=20on?= =?UTF-8?q?=20which=20you=20currently=20are=20when=20changing=20screen=20c?= =?UTF-8?q?onfiguration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/util.c b/src/util.c index 6b931995..3cbb93d9 100644 --- a/src/util.c +++ b/src/util.c @@ -254,7 +254,7 @@ void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws) { /* Ignore notify events because they would cause focus to be changed */ ignore_enter_notify_forall(conn, u_ws, true); - /* Unmap all clients of the current workspace */ + /* Unmap all clients of the given workspace */ int unmapped_clients = 0; FOR_TABLE(u_ws) CIRCLEQ_FOREACH(client, &(u_ws->table[cols][rows]->clients), clients) { @@ -271,10 +271,12 @@ void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws) { unmapped_clients++; } - /* If we did not unmap any clients, the workspace is empty and we can destroy it */ - if (unmapped_clients == 0) { + /* If we did not unmap any clients, the workspace is empty and we can destroy it, at least + * if it is not the current workspace. */ + if (unmapped_clients == 0 && u_ws != c_ws) { /* Re-assign the workspace of all dock clients which use this workspace */ Client *dock; + LOG("workspace %p is empty\n", u_ws); SLIST_FOREACH(dock, &(u_ws->screen->dock_clients), dock_clients) { if (dock->workspace != u_ws) continue; @@ -285,7 +287,7 @@ void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws) { u_ws->screen = NULL; } - /* Unmap the stack windows on the current workspace, if any */ + /* Unmap the stack windows on the given workspace, if any */ SLIST_FOREACH(stack_win, &stack_wins, stack_windows) if (stack_win->container->workspace == u_ws) xcb_unmap_window(conn, stack_win->window); From b1eb93326f02b47cf9811c3ec6ed761c8402efb0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 30 May 2009 11:35:32 +0200 Subject: [PATCH 040/129] Bugfix: Correctly cleanup stack_windows when setting clients to floating This fixes ticket #44 --- include/client.h | 2 +- src/client.c | 5 +++-- src/commands.c | 4 ++-- src/floating.c | 3 ++- src/handlers.c | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/include/client.h b/include/client.h index 2547c52f..252f3619 100644 --- a/include/client.h +++ b/include/client.h @@ -20,7 +20,7 @@ * one or because it was unmapped * */ -void client_remove_from_container(xcb_connection_t *conn, Client *client, Container *container); +void client_remove_from_container(xcb_connection_t *conn, Client *client, Container *container, bool remove_from_focusstack); /** * Warps the pointer into the given client (in the middle of it, to be specific), therefore diff --git a/src/client.c b/src/client.c index f4df06ae..9eb34565 100644 --- a/src/client.c +++ b/src/client.c @@ -29,10 +29,11 @@ * one or because it was unmapped * */ -void client_remove_from_container(xcb_connection_t *conn, Client *client, Container *container) { +void client_remove_from_container(xcb_connection_t *conn, Client *client, Container *container, bool remove_from_focusstack) { CIRCLEQ_REMOVE(&(container->clients), client, clients); - SLIST_REMOVE(&(container->workspace->focus_stack), client, Client, focus_clients); + if (remove_from_focusstack) + SLIST_REMOVE(&(container->workspace->focus_stack), client, Client, focus_clients); /* If the container will be empty now and is in stacking mode, we need to unmap the stack_win */ diff --git a/src/commands.c b/src/commands.c index d3ef90f2..811f1676 100644 --- a/src/commands.c +++ b/src/commands.c @@ -242,7 +242,7 @@ static void move_current_window(xcb_connection_t *conn, direction_t direction) { } /* Remove it from the old container and put it into the new one */ - client_remove_from_container(conn, current_client, container); + client_remove_from_container(conn, current_client, container, true); if (new->currently_focused != NULL) CIRCLEQ_INSERT_AFTER(&(new->clients), new->currently_focused, current_client, clients); @@ -505,7 +505,7 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa assert(to_container != NULL); - client_remove_from_container(conn, current_client, container); + client_remove_from_container(conn, current_client, container, true); if (container->workspace->fullscreen_client == current_client) container->workspace->fullscreen_client = NULL; diff --git a/src/floating.c b/src/floating.c index 3e8c1c07..7ee53638 100644 --- a/src/floating.c +++ b/src/floating.c @@ -23,6 +23,7 @@ #include "xcb.h" #include "debug.h" #include "layout.h" +#include "client.h" /* On which border was the dragging initiated? */ typedef enum { BORDER_LEFT, BORDER_RIGHT, BORDER_TOP, BORDER_BOTTOM} border_t; @@ -78,7 +79,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client) { LOG("Entering floating for client %08x\n", client->child); /* Remove the client of its container */ - CIRCLEQ_REMOVE(&(con->clients), client, clients); + client_remove_from_container(conn, client, con, false); client->container = NULL; if (con->currently_focused == client) { diff --git a/src/handlers.c b/src/handlers.c index 8f164cd2..7682aaf0 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -534,7 +534,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti con->workspace->fullscreen_client = NULL; /* Remove the client from the list of clients */ - client_remove_from_container(conn, client, con); + client_remove_from_container(conn, client, con, true); /* Set focus to the last focused client in this container */ con->currently_focused = get_last_focused_client(conn, con, NULL); From fccbdea925d4310e7261ca2fc094bd9b8055201c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 30 May 2009 11:41:49 +0200 Subject: [PATCH 041/129] Bugfix: Correctly set focus when switching to a workspace with floating clients This fixes ticket #45 --- src/commands.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/commands.c b/src/commands.c index 811f1676..8a66f480 100644 --- a/src/commands.c +++ b/src/commands.c @@ -585,13 +585,15 @@ void show_workspace(xcb_connection_t *conn, int workspace) { /* Check if we need to change something or if we’re already there */ if (c_ws->screen->current_workspace == (workspace-1)) { - if (CUR_CELL->currently_focused != NULL) { - set_focus(conn, CUR_CELL->currently_focused, true); + Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); + if (last_focused != SLIST_END(&(c_ws->focus_stack))) { + set_focus(conn, last_focused, true); if (need_warp) { - client_warp_pointer_into(conn, CUR_CELL->currently_focused); + client_warp_pointer_into(conn, last_focused); xcb_flush(conn); } } + return; } @@ -630,10 +632,11 @@ void show_workspace(xcb_connection_t *conn, int workspace) { ignore_enter_notify_forall(conn, c_ws, false); /* Restore focus on the new workspace */ - if (CUR_CELL->currently_focused != NULL) { - set_focus(conn, CUR_CELL->currently_focused, true); + Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); + if (last_focused != SLIST_END(&(c_ws->focus_stack))) { + set_focus(conn, last_focused, true); if (need_warp) { - client_warp_pointer_into(conn, CUR_CELL->currently_focused); + client_warp_pointer_into(conn, last_focused); xcb_flush(conn); } } else xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME); From 2d5b1f0a37ab6567be0016172ce3caeb9bc5fa51 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 30 May 2009 11:49:50 +0200 Subject: [PATCH 042/129] Bugfix: Send fake configure notify events when moving clients (Thanks Volker) This fixes ticket #47 --- src/floating.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/floating.c b/src/floating.c index 7ee53638..90064c29 100644 --- a/src/floating.c +++ b/src/floating.c @@ -182,7 +182,10 @@ static void drag_window_callback(xcb_connection_t *conn, Client *client, border_ client->rect.x = old_rect->x + (new_x - event->root_x); client->rect.y = old_rect->y + (new_y - event->root_y); reposition_client(conn, client); - xcb_flush(conn); + /* Because reposition_client does not send a faked configure event (only resize does), + * we need to initiate that on our own */ + fake_absolute_configure_notify(conn, client); + /* fake_absolute_configure_notify flushes */ } /* From 65bcf170ed0ee7462824e403b26da75935c3d6b3 Mon Sep 17 00:00:00 2001 From: Atsutane Date: Fri, 29 May 2009 16:33:48 +0200 Subject: [PATCH 043/129] Introduced color setting. --- include/config.h | 15 ++++++++++++ src/config.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++- src/layout.c | 26 ++++++++++----------- 3 files changed, 86 insertions(+), 14 deletions(-) diff --git a/include/config.h b/include/config.h index 59c6866f..33aaeb49 100644 --- a/include/config.h +++ b/include/config.h @@ -7,6 +7,21 @@ extern Config config; struct Config { const char *terminal; const char *font; + + /* Color codes are stored here */ + char *client_focused_background_active; + char *client_focused_background_inactive; + char *client_focused_text; + char *client_focused_border; + char *client_unfocused_background; + char *client_unfocused_text; + char *client_unfocused_border; + char *bar_focused_background; + char *bar_focused_text; + char *bar_focused_border; + char *bar_unfocused_background; + char *bar_unfocused_text; + char *bar_unfocused_border; }; /** diff --git a/src/config.c b/src/config.c index ab9ea9aa..c4d940ae 100644 --- a/src/config.c +++ b/src/config.c @@ -52,6 +52,21 @@ void load_configuration(const char *override_configpath) { if (config.name == NULL) \ die("You did not specify required configuration option " #name "\n"); +#define OPTION_COLOR(opt, name) \ + if (strcasecmp(key, opt) == 0) { \ + config.name = sstrdup(value); \ + continue; \ + } + +#define VERIFY_COLOR(name, def) \ + if (config.name == NULL) { \ + config.name = (char *)malloc(7); \ + memset(config.name, 0, 7); \ + } \ + if ((strlen(config.name) != 7) || (config.name[0] != '#')) { \ + strncpy(config.name, def, 7); \ + } + /* Clear the old config or initialize the data structure */ memset(&config, 0, sizeof(config)); @@ -85,6 +100,34 @@ void load_configuration(const char *override_configpath) { OPTION_STRING(terminal); OPTION_STRING(font); + /* Colors */ + OPTION_COLOR("client.focused.background.active", + client_focused_background_active); + OPTION_COLOR("client.focused.background.inactive", + client_focused_background_inactive); + OPTION_COLOR("client.focused.text", + client_focused_text); + OPTION_COLOR("client.focused.border", + client_focused_border); + OPTION_COLOR("client.unfocused.background", + client_unfocused_background); + OPTION_COLOR("client.unfocused.text", + client_unfocused_text); + OPTION_COLOR("client.unfocused.border", + client_unfocused_border); + OPTION_COLOR("bar.focused.background", + bar_focused_background); + OPTION_COLOR("bar.focused.text", + bar_focused_text); + OPTION_COLOR("bar.focused.border", + bar_focused_border); + OPTION_COLOR("bar.unfocused.background", + bar_unfocused_background); + OPTION_COLOR("bar.unfocused.text", + bar_unfocused_text); + OPTION_COLOR("bar.unfocused.border", + bar_unfocused_border); + /* exec-lines (autostart) */ if (strcasecmp(key, "exec") == 0) { struct Autostart *new = smalloc(sizeof(struct Autostart)); @@ -176,8 +219,22 @@ void load_configuration(const char *override_configpath) { } fclose(handle); + VERIFY_COLOR(client_focused_background_active, "#285577"); + VERIFY_COLOR(client_focused_background_inactive, "#555555"); + VERIFY_COLOR(client_focused_text, "#ffffff"); + VERIFY_COLOR(client_focused_border, "#4c7899"); + VERIFY_COLOR(client_unfocused_background,"#222222"); + VERIFY_COLOR(client_unfocused_text, "#888888"); + VERIFY_COLOR(client_unfocused_border, "#333333"); + VERIFY_COLOR(bar_focused_background, "#285577"); + VERIFY_COLOR(bar_focused_text, "#ffffff"); + VERIFY_COLOR(bar_focused_border, "#4c7899"); + VERIFY_COLOR(bar_unfocused_background, "#222222"); + VERIFY_COLOR(bar_unfocused_text, "#888888"); + VERIFY_COLOR(bar_unfocused_border, "#333333"); + REQUIRED_OPTION(terminal); REQUIRED_OPTION(font); - + return; } diff --git a/src/layout.c b/src/layout.c index 1f0a6d87..32f52134 100644 --- a/src/layout.c +++ b/src/layout.c @@ -112,16 +112,16 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw if (client->floating || client->container->currently_focused == client) { /* Distinguish if the window is currently focused… */ if (client->floating || CUR_CELL->currently_focused == client) - background_color = get_colorpixel(conn, "#285577"); + background_color = get_colorpixel(conn, config.client_focused_background_active); /* …or if it is the focused window in a not focused container */ - else background_color = get_colorpixel(conn, "#555555"); + else background_color = get_colorpixel(conn, config.client_focused_background_inactive); - text_color = get_colorpixel(conn, "#ffffff"); - border_color = get_colorpixel(conn, "#4c7899"); + text_color = get_colorpixel(conn, config.client_focused_text); + border_color = get_colorpixel(conn, config.client_focused_border); } else { - background_color = get_colorpixel(conn, "#222222"); - text_color = get_colorpixel(conn, "#888888"); - border_color = get_colorpixel(conn, "#333333"); + background_color = get_colorpixel(conn, config.client_unfocused_background); + text_color = get_colorpixel(conn, config.client_unfocused_text); + border_color = get_colorpixel(conn, config.client_unfocused_border); } /* Our plan is the following: @@ -409,13 +409,13 @@ static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int wid black = get_colorpixel(conn, "#000000"); - background_color[SET_NORMAL] = get_colorpixel(conn, "#222222"); - text_color[SET_NORMAL] = get_colorpixel(conn, "#888888"); - border_color[SET_NORMAL] = get_colorpixel(conn, "#333333"); + background_color[SET_NORMAL] = get_colorpixel(conn, config.bar_unfocused_background); + text_color[SET_NORMAL] = get_colorpixel(conn, config.bar_unfocused_text); + border_color[SET_NORMAL] = get_colorpixel(conn, config.bar_unfocused_border); - background_color[SET_FOCUSED] = get_colorpixel(conn, "#285577"); - text_color[SET_FOCUSED] = get_colorpixel(conn, "#ffffff"); - border_color[SET_FOCUSED] = get_colorpixel(conn, "#4c7899"); + background_color[SET_FOCUSED] = get_colorpixel(conn, config.bar_focused_background); + text_color[SET_FOCUSED] = get_colorpixel(conn, config.bar_focused_text); + border_color[SET_FOCUSED] = get_colorpixel(conn, config.bar_focused_border); /* Fill the whole bar in black */ xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, black); From cbc388000766bc21fbf3715b90653719ced3beba Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 30 May 2009 22:20:32 +0200 Subject: [PATCH 044/129] Use a more efficient struct for storing colors, validate/parse them more easily --- include/config.h | 28 ++++++++------- src/config.c | 92 ++++++++++++++++++++---------------------------- src/layout.c | 26 +++++++------- 3 files changed, 67 insertions(+), 79 deletions(-) diff --git a/include/config.h b/include/config.h index 33aaeb49..6d203efa 100644 --- a/include/config.h +++ b/include/config.h @@ -4,24 +4,26 @@ typedef struct Config Config; extern Config config; +struct Colortriple { + char border[8]; + char background[8]; + char text[8]; +}; + struct Config { const char *terminal; const char *font; /* Color codes are stored here */ - char *client_focused_background_active; - char *client_focused_background_inactive; - char *client_focused_text; - char *client_focused_border; - char *client_unfocused_background; - char *client_unfocused_text; - char *client_unfocused_border; - char *bar_focused_background; - char *bar_focused_text; - char *bar_focused_border; - char *bar_unfocused_background; - char *bar_unfocused_text; - char *bar_unfocused_border; + struct config_client { + struct Colortriple focused; + struct Colortriple focused_inactive; + struct Colortriple unfocused; + } client; + struct config_bar { + struct Colortriple focused; + struct Colortriple unfocused; + } bar; }; /** diff --git a/src/config.c b/src/config.c index c4d940ae..82219793 100644 --- a/src/config.c +++ b/src/config.c @@ -52,24 +52,45 @@ void load_configuration(const char *override_configpath) { if (config.name == NULL) \ die("You did not specify required configuration option " #name "\n"); -#define OPTION_COLOR(opt, name) \ +#define OPTION_COLORTRIPLE(opt, name) \ if (strcasecmp(key, opt) == 0) { \ - config.name = sstrdup(value); \ + struct Colortriple buffer; \ + memset(&buffer, 0, sizeof(struct Colortriple)); \ + buffer.border[0] = buffer.background[0] = buffer.text[0] = '#'; \ + if (sscanf(value, "#%06[0-9a-fA-F] #%06[0-9a-fA-F] #%06[0-9a-fA-F]", \ + buffer.border + 1, buffer.background + 1, buffer.text + 1) != 3 || \ + strlen(buffer.border) != 7 || \ + strlen(buffer.background) != 7 || \ + strlen(buffer.text) != 7) \ + die("invalid color code line: %s\n", value); \ + memcpy(&config.name, &buffer, sizeof(struct Colortriple)); \ continue; \ } -#define VERIFY_COLOR(name, def) \ - if (config.name == NULL) { \ - config.name = (char *)malloc(7); \ - memset(config.name, 0, 7); \ - } \ - if ((strlen(config.name) != 7) || (config.name[0] != '#')) { \ - strncpy(config.name, def, 7); \ - } - /* Clear the old config or initialize the data structure */ memset(&config, 0, sizeof(config)); + /* Initialize default colors */ + strcpy(config.client.focused.border, "#4c7899"); + strcpy(config.client.focused.background, "#285577"); + strcpy(config.client.focused.text, "#ffffff"); + + strcpy(config.client.focused_inactive.border, "#4c7899"); + strcpy(config.client.focused_inactive.background, "#555555"); + strcpy(config.client.focused_inactive.text, "#ffffff"); + + strcpy(config.client.unfocused.border, "#333333"); + strcpy(config.client.unfocused.background, "#222222"); + strcpy(config.client.unfocused.text, "#888888"); + + strcpy(config.bar.focused.border, "#4c7899"); + strcpy(config.bar.focused.background, "#285577"); + strcpy(config.bar.focused.text, "#ffffff"); + + strcpy(config.bar.unfocused.border, "#333333"); + strcpy(config.bar.unfocused.background, "#222222"); + strcpy(config.bar.unfocused.text, "#888888"); + FILE *handle; if (override_configpath != NULL) { if ((handle = fopen(override_configpath, "r")) == NULL) @@ -101,32 +122,12 @@ void load_configuration(const char *override_configpath) { OPTION_STRING(font); /* Colors */ - OPTION_COLOR("client.focused.background.active", - client_focused_background_active); - OPTION_COLOR("client.focused.background.inactive", - client_focused_background_inactive); - OPTION_COLOR("client.focused.text", - client_focused_text); - OPTION_COLOR("client.focused.border", - client_focused_border); - OPTION_COLOR("client.unfocused.background", - client_unfocused_background); - OPTION_COLOR("client.unfocused.text", - client_unfocused_text); - OPTION_COLOR("client.unfocused.border", - client_unfocused_border); - OPTION_COLOR("bar.focused.background", - bar_focused_background); - OPTION_COLOR("bar.focused.text", - bar_focused_text); - OPTION_COLOR("bar.focused.border", - bar_focused_border); - OPTION_COLOR("bar.unfocused.background", - bar_unfocused_background); - OPTION_COLOR("bar.unfocused.text", - bar_unfocused_text); - OPTION_COLOR("bar.unfocused.border", - bar_unfocused_border); + OPTION_COLORTRIPLE("client.focused", client.focused); + OPTION_COLORTRIPLE("client.focused_inactive", client.focused_inactive); + OPTION_COLORTRIPLE("client.unfocused", client.unfocused); + OPTION_COLORTRIPLE("client.focused", client.focused); + OPTION_COLORTRIPLE("bar.focused", bar.focused); + OPTION_COLORTRIPLE("bar.unfocused", bar.unfocused); /* exec-lines (autostart) */ if (strcasecmp(key, "exec") == 0) { @@ -214,25 +215,10 @@ void load_configuration(const char *override_configpath) { continue; } - fprintf(stderr, "Unknown configfile option: %s\n", key); - exit(1); + die("Unknown configfile option: %s\n", key); } fclose(handle); - VERIFY_COLOR(client_focused_background_active, "#285577"); - VERIFY_COLOR(client_focused_background_inactive, "#555555"); - VERIFY_COLOR(client_focused_text, "#ffffff"); - VERIFY_COLOR(client_focused_border, "#4c7899"); - VERIFY_COLOR(client_unfocused_background,"#222222"); - VERIFY_COLOR(client_unfocused_text, "#888888"); - VERIFY_COLOR(client_unfocused_border, "#333333"); - VERIFY_COLOR(bar_focused_background, "#285577"); - VERIFY_COLOR(bar_focused_text, "#ffffff"); - VERIFY_COLOR(bar_focused_border, "#4c7899"); - VERIFY_COLOR(bar_unfocused_background, "#222222"); - VERIFY_COLOR(bar_unfocused_text, "#888888"); - VERIFY_COLOR(bar_unfocused_border, "#333333"); - REQUIRED_OPTION(terminal); REQUIRED_OPTION(font); diff --git a/src/layout.c b/src/layout.c index 32f52134..060625d9 100644 --- a/src/layout.c +++ b/src/layout.c @@ -112,16 +112,16 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw if (client->floating || client->container->currently_focused == client) { /* Distinguish if the window is currently focused… */ if (client->floating || CUR_CELL->currently_focused == client) - background_color = get_colorpixel(conn, config.client_focused_background_active); + background_color = get_colorpixel(conn, config.client.focused.background); /* …or if it is the focused window in a not focused container */ - else background_color = get_colorpixel(conn, config.client_focused_background_inactive); + else background_color = get_colorpixel(conn, config.client.focused_inactive.background); - text_color = get_colorpixel(conn, config.client_focused_text); - border_color = get_colorpixel(conn, config.client_focused_border); + text_color = get_colorpixel(conn, config.client.focused.text); + border_color = get_colorpixel(conn, config.client.focused.border); } else { - background_color = get_colorpixel(conn, config.client_unfocused_background); - text_color = get_colorpixel(conn, config.client_unfocused_text); - border_color = get_colorpixel(conn, config.client_unfocused_border); + background_color = get_colorpixel(conn, config.client.unfocused.background); + text_color = get_colorpixel(conn, config.client.unfocused.text); + border_color = get_colorpixel(conn, config.client.unfocused.border); } /* Our plan is the following: @@ -409,13 +409,13 @@ static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int wid black = get_colorpixel(conn, "#000000"); - background_color[SET_NORMAL] = get_colorpixel(conn, config.bar_unfocused_background); - text_color[SET_NORMAL] = get_colorpixel(conn, config.bar_unfocused_text); - border_color[SET_NORMAL] = get_colorpixel(conn, config.bar_unfocused_border); + background_color[SET_NORMAL] = get_colorpixel(conn, config.bar.unfocused.background); + text_color[SET_NORMAL] = get_colorpixel(conn, config.bar.unfocused.text); + border_color[SET_NORMAL] = get_colorpixel(conn, config.bar.unfocused.border); - background_color[SET_FOCUSED] = get_colorpixel(conn, config.bar_focused_background); - text_color[SET_FOCUSED] = get_colorpixel(conn, config.bar_focused_text); - border_color[SET_FOCUSED] = get_colorpixel(conn, config.bar_focused_border); + background_color[SET_FOCUSED] = get_colorpixel(conn, config.bar.focused.background); + text_color[SET_FOCUSED] = get_colorpixel(conn, config.bar.focused.text); + border_color[SET_FOCUSED] = get_colorpixel(conn, config.bar.focused.border); /* Fill the whole bar in black */ xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, black); From 54fb9435f8ff37796544fdf99fb877f4d176bcb3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 30 May 2009 22:22:58 +0200 Subject: [PATCH 045/129] Add header for config.h, retab! the file --- include/config.h | 44 +++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/include/config.h b/include/config.h index 6d203efa..b803c8cd 100644 --- a/include/config.h +++ b/include/config.h @@ -1,3 +1,17 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * (c) 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + * include/config.h: Contains all structs/variables for + * the configurable part of i3 + * + */ + #ifndef _CONFIG_H #define _CONFIG_H @@ -5,25 +19,25 @@ typedef struct Config Config; extern Config config; struct Colortriple { - char border[8]; - char background[8]; - char text[8]; + char border[8]; + char background[8]; + char text[8]; }; struct Config { - const char *terminal; - const char *font; + const char *terminal; + const char *font; - /* Color codes are stored here */ - struct config_client { - struct Colortriple focused; - struct Colortriple focused_inactive; - struct Colortriple unfocused; - } client; - struct config_bar { - struct Colortriple focused; - struct Colortriple unfocused; - } bar; + /* Color codes are stored here */ + struct config_client { + struct Colortriple focused; + struct Colortriple focused_inactive; + struct Colortriple unfocused; + } client; + struct config_bar { + struct Colortriple focused; + struct Colortriple unfocused; + } bar; }; /** From 706c44509e8b04ef47652bd28579574909eba367 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 30 May 2009 22:24:05 +0200 Subject: [PATCH 046/129] retab! src/config.c --- src/config.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/config.c b/src/config.c index 82219793..ec3be4ed 100644 --- a/src/config.c +++ b/src/config.c @@ -25,12 +25,12 @@ Config config; * */ static char *glob_path(const char *path) { - static glob_t globbuf; - if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0) - die("glob() failed"); - char *result = sstrdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path); - globfree(&globbuf); - return result; + static glob_t globbuf; + if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0) + die("glob() failed"); + char *result = sstrdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path); + globfree(&globbuf); + return result; } From 0cb5d7448d1db3db79f4cf374b0968dd7b5ecf06 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 31 May 2009 00:31:18 +0200 Subject: [PATCH 047/129] Implement clients going automatically into floating --- include/data.h | 11 +++++++++-- include/floating.h | 5 ++++- src/client.c | 2 +- src/commands.c | 10 +++++----- src/floating.c | 13 +++++++++---- src/handlers.c | 19 +++++++++++++++---- src/layout.c | 4 ++-- src/manage.c | 4 +++- src/util.c | 2 +- 9 files changed, 49 insertions(+), 21 deletions(-) diff --git a/include/data.h b/include/data.h index 7308f5c1..5354217b 100644 --- a/include/data.h +++ b/include/data.h @@ -242,6 +242,10 @@ struct Font { * */ struct Client { + /* initialized will be set to true if the client was fully initialized by + * manage_window() and all functions can be used normally */ + bool initialized; + /* if you set a client to floating and set it back to managed, it does remember its old position and *tries* to get back there */ Cell old_position; @@ -284,8 +288,11 @@ struct Client { /* fullscreen is pretty obvious */ bool fullscreen; - /* floating? (= not in tiling layout) */ - bool floating; + /* floating? (= not in tiling layout) This cannot be simply a bool because we want to keep track + * of whether the status was set by the application (by setting WM_CLASS to tools for example) or + * by the user. The user’s choice overwrites automatic mode, of course. The order of the values + * is important because we check with >= FLOATING_AUTO_ON if a client is floating. */ + enum { FLOATING_AUTO_OFF = 0, FLOATING_USER_OFF = 1, FLOATING_AUTO_ON = 2, FLOATING_USER_ON = 3 } floating; /* Ensure TITLEBAR_TOP maps to 0 because we use calloc for initialization later */ enum { TITLEBAR_TOP = 0, TITLEBAR_LEFT, TITLEBAR_RIGHT, TITLEBAR_BOTTOM, TITLEBAR_OFF } titlebar_position; diff --git a/include/floating.h b/include/floating.h index aa3c55b7..232e118e 100644 --- a/include/floating.h +++ b/include/floating.h @@ -16,8 +16,11 @@ * Correctly takes care of the position/size (separately stored for tiling/floating mode) * and repositions/resizes/redecorates the client. * + * If the automatic flag is set to true, this was an automatic update by a change of the + * window class from the application which can be overwritten by the user. + * */ -void toggle_floating_mode(xcb_connection_t *conn, Client *client); +void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic); /** * Called whenever the user clicks on a border (not the titlebar!) of a floating window. diff --git a/src/client.c b/src/client.c index 9eb34565..53cacbf4 100644 --- a/src/client.c +++ b/src/client.c @@ -186,7 +186,7 @@ void client_toggle_fullscreen(xcb_connection_t *conn, Client *client) { LOG("leaving fullscreen mode\n"); client->fullscreen = false; workspace->fullscreen_client = NULL; - if (client->floating) { + if (client->floating >= FLOATING_AUTO_ON) { /* For floating clients it’s enough if we just reconfigure that window (in fact, * re-rendering the layout will not update the client.) */ reposition_client(conn, client); diff --git a/src/commands.c b/src/commands.c index 8a66f480..d84adf45 100644 --- a/src/commands.c +++ b/src/commands.c @@ -617,7 +617,7 @@ void show_workspace(xcb_connection_t *conn, int workspace) { /* Map all floating clients */ SLIST_FOREACH(client, &(c_ws->focus_stack), focus_clients) { - if (!client->floating) + if (client->floating <= FLOATING_USER_OFF) continue; xcb_map_window(conn, client->frame); @@ -836,7 +836,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { /* Is it just 's' for stacking or 'd' for default? */ if ((command[0] == 's' || command[0] == 'd') && (command[1] == '\0')) { - if (last_focused == NULL || last_focused->floating) { + if (last_focused == NULL || last_focused->floating >= FLOATING_AUTO_ON) { LOG("not switching, this is a floating client\n"); return; } @@ -852,7 +852,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { return; } - toggle_floating_mode(conn, last_focused); + toggle_floating_mode(conn, last_focused, false); /* delete all empty columns/rows */ cleanup_table(conn, last_focused->workspace); @@ -908,13 +908,13 @@ void parse_command(xcb_connection_t *conn, const char *command) { } if (*rest == '\0') { - if (last_focused != NULL && last_focused->floating) + if (last_focused != NULL && last_focused->floating >= FLOATING_AUTO_ON) move_floating_window_to_workspace(conn, last_focused, workspace); else move_current_window_to_workspace(conn, workspace); return; } - if (last_focused == NULL || last_focused->floating) { + if (last_focused == NULL || last_focused->floating >= FLOATING_AUTO_ON) { LOG("Not performing (null or floating) \n"); return; } diff --git a/src/floating.c b/src/floating.c index 90064c29..9c71cf36 100644 --- a/src/floating.c +++ b/src/floating.c @@ -39,15 +39,18 @@ static void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_pres * Correctly takes care of the position/size (separately stored for tiling/floating mode) * and repositions/resizes/redecorates the client. * + * If the automatic flag is set to true, this was an automatic update by a change of the + * window class from the application which can be overwritten by the user. + * */ -void toggle_floating_mode(xcb_connection_t *conn, Client *client) { +void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic) { Container *con = client->container; if (con == NULL) { LOG("This client is already in floating (container == NULL), re-inserting\n"); Client *next_tiling; SLIST_FOREACH(next_tiling, &(client->workspace->focus_stack), focus_clients) - if (!next_tiling->floating) + if (next_tiling->floating <= FLOATING_USER_OFF) break; /* If there are no tiling clients on this workspace, there can only be one * container: the first one */ @@ -60,7 +63,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client) { /* Preserve position/size */ memcpy(&(client->floating_rect), &(client->rect), sizeof(Rect)); - client->floating = false; + client->floating = FLOATING_USER_OFF; client->container = con; if (old_focused != NULL && !old_focused->dock) @@ -88,7 +91,9 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client) { con->currently_focused = get_last_focused_client(conn, con, NULL); } - client->floating = true; + if (automatic) + client->floating = FLOATING_AUTO_ON; + else client->floating = FLOATING_USER_ON; /* Initialize the floating position from the position in tiling mode, if this * client never was floating (width == 0) */ diff --git a/src/handlers.c b/src/handlers.c index 7682aaf0..0d828d20 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -336,7 +336,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ LOG("client. done.\n"); xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); /* Floating clients should be raised on click */ - if (client->floating) + if (client->floating >= FLOATING_AUTO_ON) xcb_raise_window(conn, client->frame); xcb_flush(conn); return 1; @@ -348,7 +348,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ LOG("click on titlebar\n"); /* Floating clients can be dragged by grabbing their titlebar */ - if (client->floating) { + if (client->floating >= FLOATING_AUTO_ON) { /* Firstly, we raise it. Maybe the user just wanted to raise it without grabbing */ uint32_t values[] = { XCB_STACK_MODE_ABOVE }; xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values); @@ -359,7 +359,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ return 1; } - if (client->floating) + if (client->floating >= FLOATING_AUTO_ON) return floating_border_click(conn, client, event); if (event->event_y < 2) { @@ -542,7 +542,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti /* Only if this is the active container, we need to really change focus */ if ((con->currently_focused != NULL) && ((con == CUR_CELL) || client->fullscreen)) set_focus(conn, con->currently_focused, true); - } else if (client->floating) { + } else if (client->floating >= FLOATING_AUTO_ON) { SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); } @@ -737,6 +737,17 @@ int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state, client->window_class = new_class; FREE(old_class); + if (!client->initialized) { + LOG("Client is not yet initialized, not putting it to floating\n"); + return 1; + } + + if (strcmp(new_class, "tools") == 0) { + LOG("tool window, should we put it floating?\n"); + if (client->floating == FLOATING_AUTO_OFF) + toggle_floating_mode(conn, client, true); + } + return 1; } diff --git a/src/layout.c b/src/layout.c index 060625d9..0085cdc4 100644 --- a/src/layout.c +++ b/src/layout.c @@ -109,9 +109,9 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw return; LOG("redecorating child %08x\n", client->child); - if (client->floating || client->container->currently_focused == client) { + if (client->floating >= FLOATING_AUTO_ON || client->container->currently_focused == client) { /* Distinguish if the window is currently focused… */ - if (client->floating || CUR_CELL->currently_focused == client) + if (client->floating >= FLOATING_AUTO_ON || CUR_CELL->currently_focused == client) background_color = get_colorpixel(conn, config.client.focused.background); /* …or if it is the focused window in a not focused container */ else background_color = get_colorpixel(conn, config.client.focused_inactive.background); diff --git a/src/manage.c b/src/manage.c index 5e6e6eed..c87de575 100644 --- a/src/manage.c +++ b/src/manage.c @@ -339,7 +339,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, /* Ensure that it is below all floating clients */ Client *first_floating; SLIST_FOREACH(first_floating, &(new->container->workspace->focus_stack), focus_clients) - if (first_floating->floating) + if (first_floating->floating >= FLOATING_AUTO_ON) break; if (first_floating != SLIST_END(&(new->container->workspace->focus_stack))) { @@ -349,6 +349,8 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, } } + new->initialized = true; + /* Check if the window already got the fullscreen hint set */ xcb_atom_t *state; if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL && diff --git a/src/util.c b/src/util.c index 3cbb93d9..40e39e84 100644 --- a/src/util.c +++ b/src/util.c @@ -264,7 +264,7 @@ void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws) { /* To find floating clients, we traverse the focus stack */ SLIST_FOREACH(client, &(u_ws->focus_stack), focus_clients) { - if (!client->floating) + if (client->floating <= FLOATING_USER_OFF) continue; xcb_unmap_window(conn, client->frame); From 517c47ca95495eb332e628d9e6017189396e854d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 31 May 2009 17:18:49 +0200 Subject: [PATCH 048/129] =?UTF-8?q?Don=E2=80=99t=20check=20twice=20for=20c?= =?UTF-8?q?lient.focused,=20change=20all=20places=20were=20colorcodes=20ar?= =?UTF-8?q?e=20used?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config.c | 1 - src/handlers.c | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/config.c b/src/config.c index ec3be4ed..8e8a80bd 100644 --- a/src/config.c +++ b/src/config.c @@ -125,7 +125,6 @@ void load_configuration(const char *override_configpath) { OPTION_COLORTRIPLE("client.focused", client.focused); OPTION_COLORTRIPLE("client.focused_inactive", client.focused_inactive); OPTION_COLORTRIPLE("client.unfocused", client.unfocused); - OPTION_COLORTRIPLE("client.focused", client.focused); OPTION_COLORTRIPLE("bar.focused", bar.focused); OPTION_COLORTRIPLE("bar.unfocused", bar.unfocused); diff --git a/src/handlers.c b/src/handlers.c index 0d828d20..cf15d1e4 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -793,9 +793,9 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t * uint32_t background_color; /* Distinguish if the window is currently focused… */ if (CUR_CELL->currently_focused == client) - background_color = get_colorpixel(conn, "#285577"); + background_color = get_colorpixel(conn, config.client.focused.background); /* …or if it is the focused window in a not focused container */ - else background_color = get_colorpixel(conn, "#555555"); + else background_color = get_colorpixel(conn, config.client.focused_inactive.background); /* Set foreground color to current focused color, line width to 2 */ uint32_t values[] = {background_color, 2}; From c73bb1feaafa5a1e1dcbb434ed92d1ce44d3c53b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 31 May 2009 18:37:47 +0200 Subject: [PATCH 049/129] Draw the border lines in the window decoration correctly --- src/layout.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/layout.c b/src/layout.c index 0085cdc4..0903e2dd 100644 --- a/src/layout.c +++ b/src/layout.c @@ -153,9 +153,9 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw } /* Draw the lines */ - xcb_draw_line(conn, drawable, gc, border_color, 2, offset, client->rect.width, offset); + xcb_draw_line(conn, drawable, gc, border_color, 0, offset, client->rect.width, offset); xcb_draw_line(conn, drawable, gc, border_color, 2, offset + font->height + 3, - 2 + client->rect.width, offset + font->height + 3); + client->rect.width - 4, offset + font->height + 3); /* If the client has a title, we draw it */ if (client->name != NULL) { From 1fcad44f66926e679f4af6e4f4d6abcba700034c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 1 Jun 2009 14:59:25 +0200 Subject: [PATCH 050/129] userguide: Document most features --- docs/single_terminal.png | Bin 0 -> 3382 bytes docs/snapping.png | Bin 0 -> 4880 bytes docs/two_columns.png | Bin 0 -> 4624 bytes docs/two_terminals.png | Bin 0 -> 4901 bytes docs/userguide | 243 ++++++++++++++++++++++++++++++++++++++- 5 files changed, 240 insertions(+), 3 deletions(-) create mode 100644 docs/single_terminal.png create mode 100644 docs/snapping.png create mode 100644 docs/two_columns.png create mode 100644 docs/two_terminals.png diff --git a/docs/single_terminal.png b/docs/single_terminal.png new file mode 100644 index 0000000000000000000000000000000000000000..4fe918cd2089f16a3b72b9291f655b57b0adefbf GIT binary patch literal 3382 zcmeHK`CHQ668}n=m`l8BWu}Sdl1ti~B$U+_wx3Brvu6=0dqbTA7-; zmAL{Ewwd8lqL!dWE+j6fxDsZU?sNZ$`$NB+`8?-&=FDf#nVB<_?(6NUutjYP000W^ zZchFHAeAM-?;#+`>V6H@AQ78l9NbSpAP~+sA1(mM3*4RTPb9!+$@cL>=G&dCZQ$-i z`vddd2fP}ybl-SJnra2P`Mglx+Kbl-igMfthitaB&oa(gTC2&(^xg(ZqG2^fPPw7B z>uVcL7D@I12a8bZp07+y?5 z)b7xkUvm4R=lz#TL_~Cmsq((CW`zdIT=afI|H4A&<;O{1Loc@YrTw!IpI7)PY%tJg z@3F)x^ys8rfXYm~PhKXDz>Y6z)#&;-UFfA-iVt)3tU$@{Y|@=qIW@~C!lU_I`Q`%U z=SFZJfxyEkf_in)Ovaxzd;t^sxh72C^qS^zwa~r}&xfVV|HuCC=%;VV@iR2hEET0OsuEL@G`F|E1xyanZ%-;Radp<`%?u`#D?#_Xm+V`Z`%q;)hx_(t?>kP zigC%TB`IVTxA7q?qOkMYz3yW&GS-@exz)?9O;v4tv|_Ab4Wt4%9YYUTxPZjknDO{3uL!5+bMY0ZE2=4D|09NmyoS%}K2Y8gVGW#;Mv`_hr7Q&G`)}G0Pe{D@{Qm6I4 z&&IANm2U8*K~Sjn{Y)5JH8h9PHbOO9ej5(P$-*J?7O%K7gzgml`I+V3qn2~V-S zlf&o`v@Lepi-@)uTs@X;<`)<8mEdd^TP7^Xc=yzjq9d_>XqPTi*mKZ-uG-^gm$;-s z{A#JpK!X;yBkKC(#zp#wF1j@%{6;rD7wv-!DCSdAsY~wFiZ*&Ki%*)#WXS^}v-^NE zo{t)RTctUj4YA@S#Tt#OOXV`2-Z{)Y^zy`kwv~2F>!VzJ1HxFn$2dk0AKl{DHkPdln}Bf#Jx3&()2Gh<-ZfMy7Te2Y-N9p1y5taVF!jG^%T_ z|I1o0IwL`+zi-dC2b`|*w?uosuwfxq*oT-gi{bQ-Y|jZ;h{uRdc_nUAO%HUT2L@o@ z=PicLBI7y@g5DBh+3Va>Lg8emATGf0!s(c>$E{AEdGhZp@-(yZOa_N8!|K#M{+nVv zS25!@KnW;xx?j%=={?t$Z@gvtx~*H~R9BCmdxL z#KCChvo&QsB_tJW=@lf_GeM{&7#Hg6^X4+yND2jm5tWG=FgHHE8~9#aA9d$WZT(8s zJ~Nh)j>eG67dQcR7a1QM6!dOvwCcWMNPH^qum`dXzex4zCjT!Lg@>NmWbC%HFo54U zgH7#UW{q6!=afdr^lk3q!YfES-?Aq7F34{#^3FMG?z%SY&L2O{&sJ(s3xfNQOjaSE z`^!R=T=#dWTt$AdV%fcckjAl13Ml5Zv#RNUVW(*@sb%m)Xe;WvG-tc*M?Wf`EmWPW zyP|Ni^1Gd@L1m>PM)Xx}LtKmvZgBiTvBe1-W=F!ODhzy)-Djsrm*$Q zahDZ7{6X&XVevz;921!cv16nIxQTJ|RtGyWfYS|!3AMOR$&Bb|5?l-Boy<_OfQq1z z+I9%57Oz?;RNoGfzAG&WV)+;B8F{GeBqU84_Uw&*fu8aXXBmOt$U;wzZr0WW zc}u=3{U-i4(5M7NZOYy%r4It<-sLQ9k&2RlX59PKWMwH3vm*?hl58mj@*J`ocl<-= z&+Yt)oj;lN|4?_h-^9h3xT!q2q|Z4I8agjaN{#*9gF}-6Fx&u_s`>R-<4LxrCT5UI z5dGflR!#`=SYKYXT}+{iS4g6cP67aiMLL3#ZpAs~-@6Hbi}C>6GA*e>Q3^7Y#kh+2 zDH5md3U@U4ee8GHah&s+`}&^J5IZgpOk@~dS$^F!7eQB+mJooDb+W+Quf!1zm|6f1 zshtHLFq?Hc{aaCAQgQXA>eNBdW=$#3G^T1$76i3)Pi9!lW=%ko89yOMHdtgq<;MM6 z32n&vqp%T20A%U}lQmYFN}(ZwAa)2mkV)8;Y$-1pD|Umx1Jes2U5VmmT)V5oMti=3 loBAz@BN7r4YgEYk)(e54E9t~Dc*!6HxI240)i@w-{s)`CXZg z$~Hq}X+eY@vP*vBd7k%oy#Kt%`~Lkq-hb}M?f&zO!dfR@sp%%kX2B9Y_L_N)To@qWpRHHm?8jWOIXb;t5&QSUcm0`wCw!+ zl;g7}XYmvt?o#!=88P*8cb?w>76}4eAyFf4_qW!idLL_ds6sYtIzrslJzG?w$Punx zCv#5#;-Dy22vi^(1ctT=LTZlQ2nh}K#`yU7EFx(`ycKu>W$uS#4l!3{>IvO6SqK?Eac;7XR#eG2n>8RDx2gxd>Gcd->F%bqn483Im(Cf?p#B>s z0k{J9Tu%@GLSs96;=78JH6=P#c-VCQ$LHMQ`;y=xf;G1T6fZQOy<lLv*i-_0UgI)YJ2x|s zaw|xU!0xE2pph@&BY2bDCPfguTc(|R`Wr?LnD@>WoDU8@eL~okvoboX&$6#HPWW(_ z)GV(S8{@2#SXH$xj93f>F?JX(7v4E^x67LnC;m7&n`#Te=gG!lGp+9Eu{IqCuK@lKl6jRb)WpDa0t_mu@DD65uI>(HK3f6rD|fjlFZ?8afw9i8=@ zy4DRU(h*}xt7{#pV-H45y{-Xje9ZVe__6t;FYCw7{KR@~2JB>|Y7BLLJ$G|Y1Sz7h ztCop$Z71OyY#6RO8x4fOhTGXHc$FlNBJ3YCbyGV-g0k!KXzbYs>vdfXwJtXVsI~ZW zH|uYOqz+(w7_}m@3Kj1(FAou*Mco#mm6{i0IFM?c_s5}(l46rNP| z_4y1Xi_CVkm8m~lQV`2Aw{Z zv~+Z&HHb3&C4gnOu)7lK?m`ct8SW-yHVjDAG~l}bbGCKb>U!{dqv`Zb11eF>AiS-m zWpJfAp7MgH?e}L&&HGR9Q;roxwv^)eNBnYTSlW0GA5EJ>iYD>1&5G(rqVy2w--sbZ z@pa}_$}b$*Vkpprk>uL}|HsBc9Q;sTr+yk4C(IoqA^<4xakna|*c(+)`-(TcYkqee zbPmdC<}rKF-m~@Ow32fF&UGxJ%UbTMCS0LeV7uQqIbpH!h|E}1Z@Q&~!g%?II*)5w z_&~|RBB|~-27-gfuCj7}W|8T{Tc*gcSGIlpqiTZIy>QIomZJ%bLn!hcE|9mbX^QF8Q&#;$H%2*tWT|!tykDFz zZ#apcCeHjIYQ~Dmy+3?YrG>-G8v|$seENuK;n5AZ>}k4+Nwx^_ z00w;(9{G{7wW{H5)2cn6E)v7H*Y0}%GDHu8Iz9#H1%M-rBnV9w%0+%U`mR6HmXGC1}=T0 zVPz+8Z6QvtU}A&EjZ-GVa%;Znia|JfYi<@@rAh1#+-+W>?AI@&>1x%N)R0-Gq?I0h zB(jqfq9Jlv`$~UVEbVN`N>Rx3!D}N+{TALPxW$*nBk{V$#9BLIU?OShXuJykbw^_6 zUaydHSmbRCI(m98YnS2P+{Ep+64gZx(eOk?kN(Eifu$^mi_ihn^ay*WxZ66LugxdV(-}SaqUgu zEb?M<@-pK*?W)jLwtTeZF@;O-@iu7Zz)d99YTOp4p{P`p!BOpgYJp`{q9Kf1>GY6T zfR7Sk5(yuZ_O3Y|igeV$s?A~(b+i7d(7a!FzKli4bCv#4;rcud-RyEBl~#*qG&pPO zVdhCSX|CMa*wfO@TA2E4qEb?IMUL-C#gWWISdHk4&%QkPm1NN*gZYvb8i=JOBXpaR zJuc&&pZZFTWDpysd^VpKKfyJ4U)tUCw<9%=wvSa7kgwK@N{^XtWM^91k=;+1giuwF zD%MZ_$VUeWu-lwKP?7i*ugwKR?`s;q5oZmy?>Cl2^k;Z6hA3Mmq(_G< zBy+m6;L3fGTn(3|lST#1U~qy<<~DSY1$ z$8rh$;PJn!NfSq6>cb2EMPe0$mkbva$k&$50v3#H#jas{*YQ1MLB;! z{f0SV;{0cWU2fp2=z^n?I+fVQ|(c<)7ZQ&c*uq9zaSg@n^J zBMaZBOmXUAZoUIbzNl^(>F{m3B4g{_f1CWo%~xIS%B%n_UE8Z0y0{YgdbX4A4B)LO zJHK=w{(?8kNad8a;5SYd>y!snH?k@doy&e`QjzSsw^&Uldbj{8=Y-3lyyZ%e=GB<`?+v2uDJ*2u#sn zw*1%$;u{u$d{*0!u)mFCihHmmBpuM{@?+wL@^}7pFu#9;hq)ULM?b1&`r>o?>L=A~ zW)}R*_l+R()t}Qz!qFN6nQI#d;T)!_+qLk zGW$Q8?(@Hx-a7CfW_r`e!sC&Vk!$ZPe4$@~wMA2w6mKo-74Rv7Ztshy#((dfpaD^R zKmQA)>dh+@uo#(D`2%aIuA>?R3^hcVK@M(9&{f-Rll!tR9eW$&q~j60OHCw~n>~DXn372$xxlI+?-`kwpJO1+5D(!c7Lo5gox8t4eZ@+HL28jC=F?(l@3Bl4s zU}z>Fy3bun*xK9JJ9Fj-b=CAOkHIe#$zcCiz%wcM!NrUwtegW|PDfrYcC0Y>X!5Qa z5QQ-Nb&JAP%NG(&@=NxAMRzvp0jesDZ&-VUTPw_06$*x42jrtQ-X(5!-i~)-%2-0y z)d_VjHSWB1GB zK!9>0nWEsnld4u(+I^;*&}DKBdN6<)Nt*Gr8ymk1fGZw)$-LqMS(scnUu}$v{ud)& B=(_*_ literal 0 HcmV?d00001 diff --git a/docs/two_columns.png b/docs/two_columns.png new file mode 100644 index 0000000000000000000000000000000000000000..6dc8c40c9021a3cdf6a3728d8c48afe29db4f277 GIT binary patch literal 4624 zcmeHLXIm4>)(tTnLlG$=Du{@51yq`p07{7%q)EvMUBu9l5_*>+D4_Hjk46$O^cFe@ z5R+WT5hh$rNZ=~5cWuf1T=$P;~9ZEJ!M}J>g!9m}%oLf6} zhfap^! zpqHxuOpWiLkRND8GS)S(XohX|JWb6CkX9;};taDxYqt=Nm0`(t6s4IY`LlFht1vZ3)P5txmNyPluO@@IwCOJW z5-~KQV1f5ied$TOut{{8NT#OTR0huQbfE)&(u__yyVZrnCca<(k)q$fxYvl=z=s!l zf4;u1;WWqyy5Y(OTg$!F!|6kvyv#wENDP|Zt>qJ0ltDLg5Xh3MPAsm|lAn^G`8)N- z$*!YA4gM}>j|Y)9)4l;04ej+;T&TjhgF#%s!mTyI#=FDY!mA?wgx4u9s0QngI}F2Q zTjln;_hPxGe@+Cv3&n~TNtq3ka^j6&*_9z*x=Lzi`KHX}O+FU*r0;DpgbrWznm#vtxdc(fRWfpfB3+qyr>yy%+@#%w`TC_&fG0cqbsdc(PVL!|bDeP+b;BP*fW|_<-Gw$R(8KA}$(dYI8ZD&Ei^wx0vOnaf) zv!TT{9^?sy;o{YwN?1Gu%KvVTrP`YE^|D#$ovGfk@X>J0ysYzBeP{fJv=d^IgUzx@ znRs1d>vjyP8z3L<-sDhMsPBq?$?Q{~o7P5v@GeerEu}p_c^N-vwB*CCW!T4P?^p02 z(^4XzV0G=~T3=`}=>7FuQtbW69xf>`sB z9NbYI;=J0Ga#ijm&S*jLN=EwTnE`_L-1J0qb2FP*HD_s=UT-prdE%^#5$iYLT%D?{&y|-_eonZub>mTDEa@?u0fVG8a zBGd%130P9Y{`pem-tO_yeke{Ww5`?cY5ATfxo9}^-l9PzeSPL~=NMm|;MpXeS(%if z5~Q2?7YMBZAS-QZ_#IklVGGMPHhwm;>gw#2|1qG8GU5MFg*YKP@nU3^P4?O5RG98N zA%SYTEp2qIjbmAc`m1ax^UUy_Xk|T32j#87*k# zX34;B$a!-zF6l5ZZ`Y(Kun3Yfw-!LAub@{Z8Etr+!ZY8g0W!T#FPp~PY!p*E zc#+3HTe9n_KHRfbv!#f1qqh-l%JQrv&B}eMdeX&QgR=Kxw`jM$rb1k%f7i}X9X=f7 z;Bf*oE2lK5C2tH*UsJ+<&(^?%+v%*i>Yp?zo(wW*6-r?Tzr@SVUIV3wJIr-eMV8IC z+cUyF?l8FTvu!oUN}qql9i?pxyzb5R<9x(MW|8;Lm4ppyJ+#)j@?CMZA`T z5Siy47s2;qmaY(t*-gSE<~PzJxz!KOo!ru)W9>q}hCiDP@0Sn11j|jxM`k~1<12Rq zW#pQME#q%l`DtdOP(M@Ul0&^eg*>(QK5&v=cAM*KifdfRev4CZ zIwUvhV({-gJRb2!&OT|PE7|Pq`_JSz?-b?3R8~%2zlN z^{=R_O!Hz^;9p0ww!=uvu0mXd+S5MYnzm?)@j`nCCI|&Y{E**cPAwia?}3rfipvfU zwQf3<+o8&vO8hL6B*PT~e*)pq+F1;rLHQD`ix`=(g$lc+$dBwwaUt_xcq( zve?7O&R@@m#q`@rm$8q(*(~pfKL`zA@*E$ygf)T>kkQYZJj)G3Jh<$&q0~KuUM0_4X;^K0)cF&wjMUP%$GT9) z_T^fRDAZ?yZ-k9oMV60<1hQyoEDbkLy~igtS|nvCXYf$-{nOmM4^*}PlKJoM*6N~I zBW3J{(dlcpF^wT1BjK7Ict3Jt#sQ9)K~F>04!hrn46fRb6%EqF;HH7}?X(NYdx|Y( zw?eQ)lBZc$SPG1u5RGzZJ%5mi zva)xxTxbk41&sC)&%KFjEO|>?k{3|b+5z8H~zZ$#0Gf173B9|&$ z)@Mu#qPI}?Yg!;NOAFBwRTXqYlm!F>#ZeMLP0jwkV_0;__N19jnTEBYdul4eg;T{d zX<9(Mx;ez-n5ND$%X^zskL~l~=%#o|-$VO6-YEn|r}S!T-lf0_I+4$}vuT^f;m8lj+ibepK|9 zD14wJnC)|DbD5%Q;|Fv})sm0FuT!RyqJ>W%j@KQ_%YJuH&Z}ehO7l}XpO67K`R$j) zx=00OoE!veu#~ZXp|Wo9Tx15vDkyY%2hV+c)17okgYcU!#tPC14i)J_$pZxfucIw* zN|v_%LHsQOZ@wlzpUHRUL5nVOWaCR8J7uxw66k(|HUOvu7J(}rYV&hPMQH;*@ruj? zS->QbCkm<6hSPqbg$#X!A1?H?d3kw(w;=aMcE60?zGeEFqvZe_uNJNBN zyYgSae|`8bZT`I}|66hRIx{ynQ5-8%p7ft|0y$#R@C>k^(rqVNMg+hDW@;FBg9I>8 zE0VrDc}(A!<8#tI&Mx4-Hhf%$(OiTxbXq!qC{GfvYbA@$d5uQ@4rB*nt;l%7o>_5=S z+M@7cvhoLA9RP4e^fh?AF-uhbpX7!oh`ZF2{vsX#vU?4P*^R0ElO4!CsSo%xO-ywG zYky;96N*s69W82&Ih>_&ww?a$$24Yw#bV;_wVKJp&jY#PS39gb`L%7B5Nk}$UE7Iy xz{KSTCJj3}& literal 0 HcmV?d00001 diff --git a/docs/two_terminals.png b/docs/two_terminals.png new file mode 100644 index 0000000000000000000000000000000000000000..20b45acfa66534e8803b4cf454ba44b6d7aba9f0 GIT binary patch literal 4901 zcmeHL`8QkZ*N;fAs3NG+sG?|5TGvpm;Zk#?xP}rlRW*dDDTpbQYLS|$xs{e0>qd!+ z@v5n6o~2Q9YY1)3&Bgche%JdyylcJhFXudK@Ad4p&w2KkJp6^Rw)b~yz_v+8ET{3ndW z@*+}W+{Ko|mAt`&7!X8Mo;5l+>S%ZJOvH#dL=+S~c+A2I)dGQQVOlj0a?VC*gTO`2 z>k0?Q%&qaSUyGt9T>r8|sxHGRlHMkW=ZyO~lA)d%DC#Se(I7IQi`vBphBa_TKXZ0d zdEX+-UHPJ6WXK^|x6`$%v?7m|a1)3ez%#`#d@ikr^Bk8EzH$g7l6o9(2qL`iM_K+f zw6$i~yAnA~ZiB$Ayf?RMNOrc46T0)onHdQykI_|zX1z8z{aD2K=;MmtcT4sfh8=kH zp;~h&uzR~vaM>(<(`O4VPMi9~b23sYD|buLbU1rt+Lq+nn-bU4*YKCOAO}SBAt(6l zY==325BAz6$TIfw>`>v9Io>OXKG35v0CpHhUm1kaZLF(cSg6(u{TJIMVW$(<^{ToG zruAO!B_0&GxZHv@utZz?HYVPfj)g4_qTwXJgKd18Rc(mvAV@_dyt(ffIhn>SmcY8rBP%UTH z=+V;HqNwA;$IjZKfaNnDf6QN*?tHXI^;LOe#Qtjs724DNNSzl$G&l(YDqK|zWOPfqhO;?#69Xdq)52ZZScN^mh)78o8aNJ?xv)Y!8=}$IJEwP0yGxG}> z%kSndIBCxh5nf$ z1$9MRDUD%i8>!_QK~#^u`?IyhSnGT__E;>+Oj>%>skb<+BBPa}Sx)Dm8K5hV?aI4?84c~O-IutmVP73N3gDovCW#nHNo69`*c1v4L!z!;Q!J4r|gkJ1WKw%;4)8dOGe@pEgSdo=lx?|!5@LF*9DWA zDJXcSd2P|8=fzv&)A~O0EfS{IFT<6yE(fQo_8=#v*i}LtZ8ffnU9XMHhVKUgp8t|!A0h=s@0Ncg-PwJu*8Ve{r)1G|7yf_dCUcA>yV(?rCh&);)tL0sow zo7|_mk@cjF9J@NGv?THA1QX}^cGg)VI0^0Z+P!JjG>Fd;kL z?-WomO_>*(3D9Yb^Ab*=^{>LIq31O7II^HaW?q*vx6r&ALxae11+(Xpf7xq7{yWP~dIPntApvuY_I>X4#MZBBgPpIc39}9jF znRZttB*zDTEjiq6`#90ZDJyV~>Hp5!hY`31o1F3)nvGTdYPf6bP3r3}yXq72pmgD? zzlMWr6K+TmB^i(|cG>l(SCZoZMoYju5=#z`&#dak)CE#9ZWvg3z{6gDvPh%MV_r5t z)O#$EXk?zoU19T9WWw?8Cv{U=*gVE$@*p_6OY-Wq9K)xr=s@Xl^{VD!9XWen?OSi$ zp2Sb8Qxelg1mdou^1?e3yoYTYrWAe3=SfH2sMNkS4B=-9lUAJ)N6W|0cA4J_oA$YK z=JtjSb#;?!J4QvmGj~=; z2XtdakZngfP4tU5>whQRo->B__&Np6cHq(nq+}opq5hI*tYD}FEyJ1(-z#I^bi=*= zx;ME`6WUrRQkdf29r4@i3f)UBVvf|0aA_*SL>=$pmy7SCEt zo)RPX+%-26_fvHZPCALt_U=;xmCP7&w?*G|BpCJfKj}5OLRGc7RcrThrd~=gGEVQQ zZEx^wRwLNid{&O-`0Ljdk02HI=1Q}1dG9-_l0^`)yT&{Dv3MQPD2XwZH29X(^cPWM zY5G=pN%M2>Ztsu>-mz+&j`w9$j5qf_{$ZJAl;N48u34U!RYPfN__3@)qjRkn@6Mlz z?qpwz36kdGC)zrvT{--C!QNF4(c@TtSMH8dkBkW;DSmP!!Qs@vN>mk&ME)4LSW=K# z3&U$&N@Qh^P##`t)fwcRHrKk%%8=<(5{O+C1VAlG45L5-*5DW`Kt47G#i& zzGpc@`)*0u2_#h8QFg7}9cX@p!R5pcul4NhnI^7g7g!;pFo~gQms7QKSWfUi{;SGw z(gM7|(1{m|i#dKn?L!lJyi?2)X5I}`KcL0R0Uq1!2X(OLxu6*3-2;z{U^I_b%|xzB zMO@n$(2C2t4YpyZL{LoKM3apiq~tQ**;wO`-1s(Iz*)D_?J<&{|vQ9b-^?E-qY&JGu@kibD72jcYsq%4=+q7z(Y_ z1GU?tXDiQvE%>x*N-m1@>-^tNEjqrtySvWnlb0AM+LOJeBNrQ}#1*{@ZDM{6)~qIb zS)E~l-a*XKM4}>dNq)JHL68aYHt~6!qI)Ooc@}V#^lu&)!0S9AlMaln*6=@z-nKFk z$(y%7&hAzKp}RiJ^9I#X?Z)Qxfa{>6 zcSr+U?!8BNKMF{kRyydpP?3#Qg)h~VRZTJcXxIgyvzQ&;8#E1_kAy6$v``&{hEFF zE5dIuvZ-E1T=oE_sCsxsmU&&#HCijB&8B7}&dt}-cnL1(<5=F7H3}^zznqW*GM0hvxFm&a$;Dhz3$w2-X`wK z6x!k1=#GY>qh*OuUMBWvu`3a`uIf$>oNT41km`g#yJ4WK=hE_d7`8NezjD3MiY%m_ z2O41%l6Td-yvGNdmq1OSCpGque2lOl(r+HVz*ay@ipe&YR{$y(nB6z*=(Blh zO|?N^a{S2iEthn%v71eB`a3p0?&PrgtJu6&{gV% zkQ;m<2W!;P6XbJ;7zU|nVpk#4V)`yszT})xc8Dq(?RNKm=me_7zr$xnL4sIvzOvNc zgKR0Jl`~4WD9<>`K-SO4C?$?(*9UsOGo)o11=21{>f)-GE2IDBiiSqlI_ov5FdHU+ zu6Pqq&E#3&))N*zkZ*Qf*#t!Ht(@nstcw=@FPYm^6MdMvS<}(@=qYyt1cGjRIm8PQ zo#VS1VRW!8@DI&Q1ZD*=-G26OPVK7f6K_Q#L$(V&KwzMK67b>bQzsggw(0<5oqGrt z(TtQ0Xo)&$QL6TwW@u`@MI_wS0fBu@0D!>Za7Khb)YX+{2nR}kk4pCr$?fDXy)PIW z>e~;@-*p>&Fz}NhxwA9QB~U6`%K0_KA51E95m4+DWSeoRJN4s^#XIq46E@t1_<|+G zzvvSfd~6`#$LL(|(kM{iA651@D$~eGE&te{uN41_(!HMs2l-~oVD}B`B3@EF{Rswd zk(<{dYeahZhi}1FddmKX%`sn#L z9oHCVPrPT!=5~VN7K{L|c4!|q$v-vRd(Cn$*lA6AJ^c{r{9%-Hhl#D))Vb$*7TF)^ zSQ@%y=`K{)evqc_Fv?iIrk&)^SN^CekdXH9kvkEWrHI6+Rn{$C9?b8PS##DXS`7 z_gX6SJhyMKs|vo@qnFWm_2_(Fb>B>F(!ne9g}KH~0zK=x2GxT(V%hs;b{o?`xie`& zz!;KKVhwu89Ph`eoB=lsMFnJeUJ%E084b56MgsP2Lzu#|Yd)jR^yNl@6SWpLOzpcQ ztX5=G~eu6%P-KQ6xII#!9iZ}E2lw;uqd9LZbB8;S*8-5r!l<<^U+Y216qFW^>Aa--ie+zU%Ln;)u_qW__2?f;#rcALo) zHTg}UEYvoP!ZvA<^iw0Vntu%|Iez_gcD@a@T-5(hJInffSsAy9@A}9E@wIe9m&4oL z;RJ9MeQ+8cJ?ex$_!4#Eu-(QLjbxw7v$%&=_4iy+*(ZBtN?Q}jpK5tf#YoSs$BwLK z`)5zCy9n@b39*0v!CCL)NpPU*f2epIbdlY<^Y2Enp(_tlLrxVu!W^w^j=65-Z3ZXA zxdC!ioL8$xKmW7_u)M|s>FbQ)0mB4SfzH2R`HdY6dk7%#wR1@@eA^QMsCi1YO<{OE z64dVhmvbx#qA!7WCfwx50gBrm49!YB1!!&gO8``VC>!DfZaEO!URDsm9v&c!xJg_g zpxv@TG0XSbp)AlOLr^>1Pi6t~D#Os44!F;y9aa`71gfr~Q6|tG1Lk@-2N&FiLk;2AV0aK@HlwYh zHS*`+YtDau&c`ElzQ+Rtnwp|EO2$I6re-H!#9^oF=kun&G&~#cobf)ZOWJ8*+ya#2 zdSiGm7Yp=);R_j5>j`7NP2ttA;Z98v{yOT-cqHjQ5S{DF9MsCZ0w5-<-;E@Iq7_Ac&gSo?~ z{@#|IVE7V#e=b0)2_QtXPk?ArV?zBgBmk4*LCLYgTKRyzOAM%eMGn1n?;WJO`R_ZP|n`r*(b<=3XF#o{4U?wsXvX0E*r2I{*Lx literal 0 HcmV?d00001 diff --git a/docs/userguide b/docs/userguide index 3f337a19..b9272087 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1,15 +1,164 @@ i3 User’s Guide =============== Michael Stapelberg -May 2009 +June 2009 This document contains all information you need to configuring and using the i3 window manager. If it does not, please contact me on IRC, Jabber or E-Mail and I’ll help you out. +== Using i3 + +=== Creating terminals and moving around + +A very basic operation is to create a new terminal. By default, the keybinding +for that is Mod1+Enter, that is Alt+Enter in the default configuration. By +pressing Mod1+Enter, a new terminal will be created and it will fill the whole +space which is available on your screen. + +image:single_terminal.png[Single terminal] + +It is important to keep in mind that i3 uses a table to manage your windows. At +the moment, you have exactly one column and one row which leaves you with one +cell. In this cell, there is a container in which your newly opened terminal is. + +If you now open another terminal, you still have only one cell. However, the +container has both of your terminals. + +image:two_terminals.png[Two terminals] + +To move the focus between the two terminals, you use the direction keys which +you may know from the editor +vi+. However, in i3, your homerow is used for +these keys (in +vi+, the keys are shifted to the left by one for compatibility +with most keyboard layouts). Therefore, +Mod1+J+ is left, +Mod1+K+ is down, +Mod1+L+ +is up and `Mod1+;` is right. So, to switch between the terminals, use +Mod1+K+ or ++Mod1+L+. + +To create a new row/column, you can simply move a terminal (or any other window) +to the direction you want to expand your table. So, let’s expand the table to +the right by pressing `Mod1+Shift+;`. + +image:two_columns.png[Two columns] + +=== Changing mode of containers + +A container can be in two modes at the moment (more to be implemented later): ++default+ or +stacking+. In default mode, clients are sized so that every client +gets an equal amount of space of the container. In stacking mode, only one +focused client of the container is displayed and you get a list of windows +at the top of the container. + +To switch the mode, press +Mod1+h+ for stacking and +Mod1+e+ for default. + +=== Toggling fullscreen mode for a window + +To display a window fullscreen or to go out of fullscreen mode again, press ++Mod1+f+. + +=== Opening other applications + +Aside from opening applicatios from a terminal, you can also use the handy ++dmenu+ which is opened by pressing +Mod1+v+ by default. Just type the name +(or a part of it) of the application which you want to open. It has to be in +your +$PATH+ for that to work. + +Furthermore, if you have applications you open very frequently, you can also +create a keybinding for it. See the section "Configuring i3" for details. + +=== Closing windows + +If an application does not provide a mechanism to close (most applications +provide a menu, the escape key or a shortcut like +Control+W+ to close), you +can press +Mod1+Shift+q+ to kill a window. For applications which support +the WM_DELETE protocol, this will correctly close the application (saving +any modifications or doing other cleanup). If the application doesn’t support +it, your X server will kill the window and the behaviour depends on the +application. + +=== Using workspaces + +Workspaces are an easy way to group a set of windows. By default, you are on +the first workspace, as the bar on the bottom left indicates. To switch to +another workspace, press +Mod1+num+ where +num+ is the number of the workspace +you want to use. If the workspace does not exist yet, it will be created. + +A common paradigm is to put the web browser on one workspace, communication +applications (+mutt+, +irssi+, ...) on another one and the ones with which you +work on the third one. Of course, there is no need to follow this approach. + +If you have multiple screens, a workspace will be created on each screen. If +you open a new workspace, it will be bound to the screen you created it on. +When you switch to a workspace on another screen, i3 will set focus to this +screen. + +=== Moving windows to workspaces + +To move a window to another workspace, simply press +Mod1+Shift+num+ where ++num+ is (like when switching workspaces) the number of the target workspace. +Similarly to switching workspaces, the target workspace will be created if +it does not yet exist. + +=== Resizing columns + +To resize columns just grab the border between the two columns and move it to +the wanted size. + +A command for doing this via keyboard will be implemented soon. + +=== Restarting i3 inplace + +To restart i3 inplace (and thus get it into a clean state if it has a bug, to +reload your configuration or even to upgrade to a newer version of i3) you +can use +Mod1+Shift+r+. Be aware, though, that this kills your current layout +and all the windows you have opened will be put in a default container in only +one cell. This will be implemented in a later version. + +=== Exiting i3 + +To cleanly exit i3 without killing your X server, you can use +Mod1+Shift+e+. + +=== Snapping + +Snapping is a mechanism to increase/decrease the colspan/rowspan of a container. +Colspan/rowspan is the amount of columns/rows a specific cell of the table +consumes. This is easier explained by giving an example, so take the following +layout: + +image:snapping.png[Snapping example] + +To use the full size of your screen, you can now snap container 3 downwards +by pressing +Mod1+Control+k+. + +=== Floating + +Floating is the opposite of tiling mode. The position and size of a window +are then not managed by i3, but by you. Using this mode violates the tiling +paradigm but can be useful for some corner cases like "Save as" dialog +windows or toolbar windows (GIMP or similar). + +You can enable floating for a window by pressing +Mod1+Shift+Space+. By +dragging the window’s titlebar with your mouse, you can move the window +around. By grabbing the borders and moving them you can resize the window. + +Bindings for doing this with your keyboard will follow. + +Floating clients are always on top of tiling clients. + == Configuring i3 -TODO: document the other options, implement variables before +This is where the real fun begins ;-). Most things are very dependant on your +ideal working environment, so we can’t make reasonable defaults for them. + +While not using a programming language for the configuration, i3 stays +quite flexible regarding to the things you usually want your window manager +to do. + +For example, you can configure bindings to jump to specific windows, +you can set specific applications to start on a specific workspace, you can +automatically start applications, you can change the colors of i3 or bind +your keys to do useful stuff. + +TODO: implement variables terminal:: Specifies the terminal emulator program you prefer. It will be started @@ -23,7 +172,11 @@ font:: === Keyboard bindings -TODO +You can use each command (see below) using keyboard bindings. At the moment, +keyboard bindings require you to specify the keycode (38) of the key, not its key +symbol ("a"). This has some advantages (keybindings make sense regardless of +the layout you type) and some disadvantages (hard to remember, you have to look +them up every time). *Syntax*: -------------------------------- @@ -73,3 +226,87 @@ assign urxvt → 2 assign "urxvt" → 2 assign "urxvt/VIM" → 3 ---------------------- + +=== Automatically starting applications on startup + +By using the +exec+ keyword outside a keybinding, you can configure which +commands will be performed by i3 on the first start (not when reloading inplace +however). The commands will be run in order. + +*Syntax*: +------------ +exec command +------------ + +*Examples*: +-------------------------------- +exec sudo i3status | dzen2 -dock +-------------------------------- + +=== Jumping to specific windows + +Especially when in a multi-monitor environment, you want to quickly jump to a specific +window, for example while currently working on workspace 3 you may want to jump to +your mailclient to mail your boss that you’ve achieved some important goal. Instead +of figuring out how to navigate to your mailclient, it would be more convenient to +have a shortcut. + +*Syntax*: +---------------------------------------------------- +jump ["]window class[/window title]["] +jump workspace [ column row ] +---------------------------------------------------- + +You can either use the same matching algorithm as in the +assign+ command (see above) +or you can specify the position of the client if you always use the same layout. + +*Examples*: +-------------------------------------- +# Get me to the next open VIM instance +bind Mod1+38 jump "urxvt/VIM" +-------------------------------------- + +=== Traveling the focus stack + +This mechanism can be thought of as the opposite of the +jump+ command. It travels +the focus stack and jumps to the window you focused before. + +*Syntax*: +-------------- +focus [number] +-------------- + +Where +number+ by default is 1 meaning that the next client in the focus stack will +be selected. + +=== Changing colors + +You can change all colors which i3 uses to draw the window decorations and the +bottom bar. + +*Syntax*: +-------------------------------------------- +colorclass border background text +-------------------------------------------- + +Where colorclass can be one of: + +client.focused:: + A client which currently has the focus. +client.focused_inactive:: + A client which is the focused one of its container, but it does not have + the focus at the moment. +client.unfocused:: + A client which is not the focused one of its container. +bar.focused:: + The current workspace in the bottom bar. +bar.unfocused:: + All other workspaces in the bottom bar. + +Colors are in HTML hex format, see below. + +*Examples*: +-------------------------------------- +# class border backgr. text +client.focused #2F343A #900000 #FFFFFF +-------------------------------------- From 2a67630aa665fe184943943c197eddc60721764a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 1 Jun 2009 15:14:45 +0200 Subject: [PATCH 051/129] Optimization: Get the colorpixels when loading configuration, make use of the new config struct Instead of building arrays of colorpixels we can simply use a pointer to a struct Colortriple. Furthermore, by getting the colorpixels when loading the configuration, we save a lot of function calls in the main code. --- include/config.h | 8 +++---- src/config.c | 53 ++++++++++++++++++++++++++--------------------- src/handlers.c | 4 ++-- src/layout.c | 54 ++++++++++++++---------------------------------- src/mainx.c | 4 ++-- 5 files changed, 53 insertions(+), 70 deletions(-) diff --git a/include/config.h b/include/config.h index b803c8cd..4ed73a3d 100644 --- a/include/config.h +++ b/include/config.h @@ -19,9 +19,9 @@ typedef struct Config Config; extern Config config; struct Colortriple { - char border[8]; - char background[8]; - char text[8]; + uint32_t border; + uint32_t background; + uint32_t text; }; struct Config { @@ -47,6 +47,6 @@ struct Config { * configuration file. * */ -void load_configuration(const char *override_configfile); +void load_configuration(xcb_connection_t *conn, const char *override_configfile); #endif diff --git a/src/config.c b/src/config.c index 8e8a80bd..3dec073d 100644 --- a/src/config.c +++ b/src/config.c @@ -17,6 +17,7 @@ #include "i3.h" #include "util.h" #include "config.h" +#include "xcb.h" Config config; @@ -41,7 +42,7 @@ static char *glob_path(const char *path) { * configuration file. * */ -void load_configuration(const char *override_configpath) { +void load_configuration(xcb_connection_t *conn, const char *override_configpath) { #define OPTION_STRING(name) \ if (strcasecmp(key, #name) == 0) { \ config.name = sstrdup(value); \ @@ -54,16 +55,20 @@ void load_configuration(const char *override_configpath) { #define OPTION_COLORTRIPLE(opt, name) \ if (strcasecmp(key, opt) == 0) { \ - struct Colortriple buffer; \ - memset(&buffer, 0, sizeof(struct Colortriple)); \ - buffer.border[0] = buffer.background[0] = buffer.text[0] = '#'; \ + char border[8], background[8], text[8]; \ + memset(border, 0, sizeof(border)); \ + memset(background, 0, sizeof(background)); \ + memset(text, 0, sizeof(text)); \ + border[0] = background[0] = text[0] = '#'; \ if (sscanf(value, "#%06[0-9a-fA-F] #%06[0-9a-fA-F] #%06[0-9a-fA-F]", \ - buffer.border + 1, buffer.background + 1, buffer.text + 1) != 3 || \ - strlen(buffer.border) != 7 || \ - strlen(buffer.background) != 7 || \ - strlen(buffer.text) != 7) \ + border + 1, background + 1, text + 1) != 3 || \ + strlen(border) != 7 || \ + strlen(background) != 7 || \ + strlen(text) != 7) \ die("invalid color code line: %s\n", value); \ - memcpy(&config.name, &buffer, sizeof(struct Colortriple)); \ + config.name.border = get_colorpixel(conn, border); \ + config.name.background = get_colorpixel(conn, background); \ + config.name.text = get_colorpixel(conn, text); \ continue; \ } @@ -71,25 +76,25 @@ void load_configuration(const char *override_configpath) { memset(&config, 0, sizeof(config)); /* Initialize default colors */ - strcpy(config.client.focused.border, "#4c7899"); - strcpy(config.client.focused.background, "#285577"); - strcpy(config.client.focused.text, "#ffffff"); + config.client.focused.border = get_colorpixel(conn, "#4c7899"); + config.client.focused.background = get_colorpixel(conn, "#285577"); + config.client.focused.text = get_colorpixel(conn, "#ffffff"); - strcpy(config.client.focused_inactive.border, "#4c7899"); - strcpy(config.client.focused_inactive.background, "#555555"); - strcpy(config.client.focused_inactive.text, "#ffffff"); + config.client.focused_inactive.border = get_colorpixel(conn, "#4c7899"); + config.client.focused_inactive.background = get_colorpixel(conn, "#555555"); + config.client.focused_inactive.text = get_colorpixel(conn, "#ffffff"); - strcpy(config.client.unfocused.border, "#333333"); - strcpy(config.client.unfocused.background, "#222222"); - strcpy(config.client.unfocused.text, "#888888"); + config.client.unfocused.border = get_colorpixel(conn, "#333333"); + config.client.unfocused.background = get_colorpixel(conn, "#222222"); + config.client.unfocused.text = get_colorpixel(conn, "#888888"); - strcpy(config.bar.focused.border, "#4c7899"); - strcpy(config.bar.focused.background, "#285577"); - strcpy(config.bar.focused.text, "#ffffff"); + config.bar.focused.border = get_colorpixel(conn, "#4c7899"); + config.bar.focused.background = get_colorpixel(conn, "#285577"); + config.bar.focused.text = get_colorpixel(conn, "#ffffff"); - strcpy(config.bar.unfocused.border, "#333333"); - strcpy(config.bar.unfocused.background, "#222222"); - strcpy(config.bar.unfocused.text, "#888888"); + config.bar.unfocused.border = get_colorpixel(conn, "#333333"); + config.bar.unfocused.background = get_colorpixel(conn, "#222222"); + config.bar.unfocused.text = get_colorpixel(conn, "#888888"); FILE *handle; if (override_configpath != NULL) { diff --git a/src/handlers.c b/src/handlers.c index cf15d1e4..cdc735e4 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -793,9 +793,9 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t * uint32_t background_color; /* Distinguish if the window is currently focused… */ if (CUR_CELL->currently_focused == client) - background_color = get_colorpixel(conn, config.client.focused.background); + background_color = config.client.focused.background; /* …or if it is the focused window in a not focused container */ - else background_color = get_colorpixel(conn, config.client.focused_inactive.background); + else background_color = config.client.focused_inactive.background; /* Set foreground color to current focused color, line width to 2 */ uint32_t values[] = {background_color, 2}; diff --git a/src/layout.c b/src/layout.c index 0903e2dd..ab6b7452 100644 --- a/src/layout.c +++ b/src/layout.c @@ -100,9 +100,7 @@ void redecorate_window(xcb_connection_t *conn, Client *client) { void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t drawable, xcb_gcontext_t gc, int offset) { i3Font *font = load_font(conn, config.font); int decoration_height = font->height + 2 + 2; - uint32_t background_color, - text_color, - border_color; + struct Colortriple *color; /* Clients without a container (docks) won’t get decorated */ if (client->dock) @@ -112,26 +110,19 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw if (client->floating >= FLOATING_AUTO_ON || client->container->currently_focused == client) { /* Distinguish if the window is currently focused… */ if (client->floating >= FLOATING_AUTO_ON || CUR_CELL->currently_focused == client) - background_color = get_colorpixel(conn, config.client.focused.background); + color = &(config.client.focused); /* …or if it is the focused window in a not focused container */ - else background_color = get_colorpixel(conn, config.client.focused_inactive.background); - - text_color = get_colorpixel(conn, config.client.focused.text); - border_color = get_colorpixel(conn, config.client.focused.border); - } else { - background_color = get_colorpixel(conn, config.client.unfocused.background); - text_color = get_colorpixel(conn, config.client.unfocused.text); - border_color = get_colorpixel(conn, config.client.unfocused.border); - } + else color = &(config.client.focused_inactive); + } else color = &(config.client.unfocused); /* Our plan is the following: - - Draw a rect around the whole client in background_color + - Draw a rect around the whole client in color->background - Draw two lines in a lighter color - Draw the window’s title */ /* Draw a rectangle in background color around the window */ - xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, background_color); + xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, color->background); /* In stacking mode, we only render the rect for this specific decoration */ if (client->container != NULL && client->container->mode == MODE_STACK) { @@ -153,15 +144,15 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw } /* Draw the lines */ - xcb_draw_line(conn, drawable, gc, border_color, 0, offset, client->rect.width, offset); - xcb_draw_line(conn, drawable, gc, border_color, 2, offset + font->height + 3, + xcb_draw_line(conn, drawable, gc, color->border, 0, offset, client->rect.width, offset); + xcb_draw_line(conn, drawable, gc, color->border, 2, offset + font->height + 3, client->rect.width - 4, offset + font->height + 3); /* If the client has a title, we draw it */ if (client->name != NULL) { /* Draw the font */ uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT; - uint32_t values[] = { text_color, background_color, font->id }; + uint32_t values[] = { color->text, color->background, font->id }; xcb_change_gc(conn, gc, mask, values); /* name_len == -1 means this is a legacy application which does not specify _NET_WM_NAME, @@ -401,24 +392,10 @@ static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int wid i3Font *font = load_font(conn, config.font); i3Screen *screen = r_ws->screen; enum { SET_NORMAL = 0, SET_FOCUSED = 1 }; - uint32_t background_color[2], - text_color[2], - border_color[2], - black; char label[3]; - black = get_colorpixel(conn, "#000000"); - - background_color[SET_NORMAL] = get_colorpixel(conn, config.bar.unfocused.background); - text_color[SET_NORMAL] = get_colorpixel(conn, config.bar.unfocused.text); - border_color[SET_NORMAL] = get_colorpixel(conn, config.bar.unfocused.border); - - background_color[SET_FOCUSED] = get_colorpixel(conn, config.bar.focused.background); - text_color[SET_FOCUSED] = get_colorpixel(conn, config.bar.focused.text); - border_color[SET_FOCUSED] = get_colorpixel(conn, config.bar.focused.border); - /* Fill the whole bar in black */ - xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, black); + xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); xcb_rectangle_t rect = {0, 0, width, height}; xcb_poly_fill_rectangle(conn, screen->bar, screen->bargc, 1, &rect); @@ -430,16 +407,17 @@ static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int wid if (workspaces[c].screen != screen) continue; - int set = (screen->current_workspace == c ? SET_FOCUSED : SET_NORMAL); + struct Colortriple *color = (screen->current_workspace == c ? &(config.bar.focused) : + &(config.bar.unfocused)); - xcb_draw_rect(conn, screen->bar, screen->bargc, border_color[set], + xcb_draw_rect(conn, screen->bar, screen->bargc, color->border, drawn * height, 1, height - 2, height - 2); - xcb_draw_rect(conn, screen->bar, screen->bargc, background_color[set], + xcb_draw_rect(conn, screen->bar, screen->bargc, color->background, drawn * height + 1, 2, height - 4, height - 4); snprintf(label, sizeof(label), "%d", c+1); - xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, text_color[set]); - xcb_change_gc_single(conn, screen->bargc, XCB_GC_BACKGROUND, background_color[set]); + xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, color->text); + xcb_change_gc_single(conn, screen->bargc, XCB_GC_BACKGROUND, color->background); xcb_image_text_8(conn, strlen(label), screen->bar, screen->bargc, drawn * height + 5 /* X */, font->height + 1 /* Y = baseline of font */, label); drawn++; diff --git a/src/mainx.c b/src/mainx.c index 50db96d9..79a21e14 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -112,10 +112,10 @@ int main(int argc, char *argv[], char *env[]) { memset(&evenths, 0, sizeof(xcb_event_handlers_t)); memset(&prophs, 0, sizeof(xcb_property_handlers_t)); - load_configuration(override_configpath); - conn = xcb_connect(NULL, &screens); + load_configuration(conn, override_configpath); + /* Place requests for the atoms we need as soon as possible */ #define REQUEST_ATOM(name) atom_cookies[name] = xcb_intern_atom(conn, 0, strlen(#name), #name); From e500d3fbaf92a4c29d8e6a7ef4ee641ce549059a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 1 Jun 2009 15:21:35 +0200 Subject: [PATCH 052/129] debian: Update changelog --- debian/changelog | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/debian/changelog b/debian/changelog index e607d61d..a642bd2c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,13 +1,19 @@ i3-wm (3.b-1) unstable; urgency=low - * Bugfix: Correctly handle col-/rowspanned containers when setting focus + * Bugfix: Correctly handle col-/rowspanned containers when setting focus. * Bugfix: Force reconfiguration of all windows on workspaces which are - re-assigned because a screen was detached - * Bugfix: Several bugs in resizing table columns fixed + re-assigned because a screen was detached. + * Bugfix: Several bugs in resizing table columns fixed. + * Bugfix: Resizing should now work correctly in all cases. + * Bugfix: Correctly re-assign dock windows when workspace is destroyed. + * Bugfix: Correctly handle Mode_switch modifier. * Implement jumping to other windows by specifying their position or - window class/title - * Implement jumping back by using the focus stack - * Implement autostart (exec-command in configuration file) + window class/title. + * Implement jumping back by using the focus stack. + * Implement autostart (exec-command in configuration file). + * Implement floating. + * Implement automatically assigning clients on specific workspaces. + * Colors are now configurable. -- Michael Stapelberg Sat, 09 May 2009 20:17:58 +0200 From 170ba601912a45b772e46bf3065c598b68139c6c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 1 Jun 2009 15:35:59 +0200 Subject: [PATCH 053/129] Bugfix: Rendering of the bottom border line was still off by one --- src/layout.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layout.c b/src/layout.c index ab6b7452..8b01471a 100644 --- a/src/layout.c +++ b/src/layout.c @@ -146,7 +146,7 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw /* Draw the lines */ xcb_draw_line(conn, drawable, gc, color->border, 0, offset, client->rect.width, offset); xcb_draw_line(conn, drawable, gc, color->border, 2, offset + font->height + 3, - client->rect.width - 4, offset + font->height + 3); + client->rect.width - 3, offset + font->height + 3); /* If the client has a title, we draw it */ if (client->name != NULL) { From e689be983b69377b10097eaa9093503ef7b356b0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 1 Jun 2009 16:19:06 +0200 Subject: [PATCH 054/129] Implement variables in configfile. This implements ticket #42. Syntax is "set $key value". All further instances of $key will be replaced with value before parsing each line of the configfile. --- include/config.h | 9 ++++++++ src/config.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/include/config.h b/include/config.h index 4ed73a3d..330b194c 100644 --- a/include/config.h +++ b/include/config.h @@ -15,6 +15,8 @@ #ifndef _CONFIG_H #define _CONFIG_H +#include "queue.h" + typedef struct Config Config; extern Config config; @@ -24,6 +26,13 @@ struct Colortriple { uint32_t text; }; +struct Variable { + char *key; + char *value; + + SLIST_ENTRY(Variable) variables; +}; + struct Config { const char *terminal; const char *font; diff --git a/src/config.c b/src/config.c index 3dec073d..a9e90ab3 100644 --- a/src/config.c +++ b/src/config.c @@ -34,6 +34,28 @@ static char *glob_path(const char *path) { return result; } +/* + * This function does a very simple replacement of each instance of key with value. + * + */ +static void replace_variable(char *buffer, const char *key, const char *value) { + char *pos; + /* To prevent endless recursions when the user makes an error configuring, + * we stop after 100 replacements. That should be vastly more than enough. */ + int c = 0; + LOG("Replacing %s with %s\n", key, value); + while ((pos = strcasestr(buffer, key)) != NULL && c++ < 100) { + LOG("replacing variable %s in \"%s\" with \"%s\"\n", key, buffer, value); + char *rest = pos + strlen(key); + *pos = '\0'; + char *replaced; + asprintf(&replaced, "%s%s%s", buffer, value, rest); + /* Hm, this is a bit ugly, but sizeof(buffer) = 4, as it’s just a pointer. + * So we need to hard-code the dimensions here. */ + strncpy(buffer, replaced, 1026); + free(replaced); + } +} /* * Reads the configuration from ~/.i3/config or /etc/i3/config if not found. @@ -43,6 +65,8 @@ static char *glob_path(const char *path) { * */ void load_configuration(xcb_connection_t *conn, const char *override_configpath) { + SLIST_HEAD(variables_head, Variable) variables; + #define OPTION_STRING(name) \ if (strcasecmp(key, #name) == 0) { \ config.name = sstrdup(value); \ @@ -75,6 +99,8 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath) /* Clear the old config or initialize the data structure */ memset(&config, 0, sizeof(config)); + SLIST_INIT(&variables); + /* Initialize default colors */ config.client.focused.border = get_colorpixel(conn, "#4c7899"); config.client.focused.background = get_colorpixel(conn, "#285577"); @@ -118,6 +144,14 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath) die("Could not read configuration file\n"); } + if (config.terminal != NULL) + replace_variable(buffer, "$terminal", config.terminal); + + /* Replace all custom variables */ + struct Variable *current; + SLIST_FOREACH(current, &variables, variables) + replace_variable(buffer, current->key, current->value); + /* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */ if (sscanf(buffer, "%s %[^\n]", key, value) < 1 || key[0] == '#' || strlen(key) < 3) @@ -219,6 +253,26 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath) continue; } + /* set a custom variable */ + if (strcasecmp(key, "set") == 0) { + if (value[0] != '$') + die("Malformed variable assignment, name has to start with $\n"); + + /* get key/value for this variable */ + char *v_key = value, *v_value; + if ((v_value = strstr(value, " ")) == NULL) + die("Malformed variable assignment, need a value\n"); + + *(v_value++) = '\0'; + + struct Variable *new = scalloc(sizeof(struct Variable)); + new->key = sstrdup(v_key); + new->value = sstrdup(v_value); + SLIST_INSERT_HEAD(&variables, new, variables); + LOG("Got new variable %s = %s\n", v_key, v_value); + continue; + } + die("Unknown configfile option: %s\n", key); } fclose(handle); From 553db28664719fbbfd1cc4a2dafabf6b2da5b1da Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 1 Jun 2009 20:59:40 +0200 Subject: [PATCH 055/129] Switch to libev for the event loop to build a base for IPC stuff. Please test! --- Makefile | 1 + debian/control | 2 +- src/mainx.c | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 9f82b1a9..a42099d4 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,7 @@ LDFLAGS += -lxcb-aux LDFLAGS += -lxcb-icccm LDFLAGS += -lxcb-xinerama LDFLAGS += -lX11 +LDFLAGS += -lev LDFLAGS += -L/usr/local/lib -L/usr/pkg/lib ifeq ($(UNAME),NetBSD) diff --git a/debian/control b/debian/control index e0807987..63997e86 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Section: utils Priority: optional Maintainer: Michael Stapelberg DM-Upload-Allowed: yes -Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4-1), xmlto, docbook-xml, pkg-config +Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4-1), xmlto, docbook-xml, pkg-config, libev-dev Standards-Version: 3.8.0 Homepage: http://i3.zekjur.net/ diff --git a/src/mainx.c b/src/mainx.c index 79a21e14..5de87700 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -31,6 +31,8 @@ #include #include +#include + #include "config.h" #include "data.h" #include "debug.h" @@ -69,6 +71,34 @@ xcb_atom_t atoms[NUM_ATOMS]; int num_screens = 0; +/* + * Callback for activity on the connection to the X server + * + */ +static void xcb_got_event(EV_P_ struct ev_io *w, int revents) { + xcb_generic_event_t *event; + + /* When an event is available… */ + while ((event = xcb_poll_for_event(evenths.c)) != NULL) { + /* …we handle all events in a row: */ + do { + xcb_event_handle(&evenths, event); + xcb_aux_sync(evenths.c); + free(event); + } while ((event = xcb_poll_for_event(evenths.c))); + + /* Make sure all replies are handled/discarded */ + xcb_aux_sync(evenths.c); + + /* Afterwards, there may be new events available which would + * not trigger the select() (libev) immediately, so we check + * again (and don’t bail out of the loop). */ + } + + /* Make sure all replies are handled/discarded */ + xcb_aux_sync(evenths.c); +} + int main(int argc, char *argv[], char *env[]) { int i, screens, opt; char *override_configpath = NULL; @@ -307,8 +337,21 @@ int main(int argc, char *argv[], char *env[]) { c_ws = &workspaces[screen->current_workspace]; } - /* Enter xcb’s event handler */ - xcb_event_wait_for_event_loop(&evenths); + + /* Initialize event loop using libev */ + struct ev_loop *loop = ev_default_loop(0); + if (loop == NULL) + die("Could not initialize libev. Bad LIBEV_FLAGS?\n"); + + ev_io xcb_watcher; + ev_io_init(&xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ); + + /* Call the handler to work all events which arrived before the libev-stuff was set up */ + xcb_got_event(NULL, &xcb_watcher, 0); + + /* Enter the libev eventloop */ + ev_io_start(loop, &xcb_watcher); + ev_loop(loop, 0); /* not reached */ return 0; From 8cda574aeada1e7d3034f42e353077a74346eeda Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 1 Jun 2009 21:02:32 +0200 Subject: [PATCH 056/129] Update DEPENDS file (add libev, update URLs) --- DEPENDS | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/DEPENDS b/DEPENDS index 6d56e132..0e1de719 100644 --- a/DEPENDS +++ b/DEPENDS @@ -5,6 +5,7 @@ In that case, please try using the versions mentioned below until a fix is provi * xcb-proto-1.3 (2008-12-10) * libxcb-1.1.93 (2008-12-11) * xcb-util-0.3.3 (2009-01-31) + * libev * asciidoc >= 8.3.0 for docs/hacking-howto * asciidoc, xmlto, docbook-xml for man/i3.man * Xlib, the one that comes with your X-Server @@ -14,9 +15,10 @@ Recommendations: * dmenu for launching applications Get the libraries from: -http://xcb.freedesktop.org/dist/xcb-proto-1.3.tar.bz2 +http://xcb.freedesktop.org/dist/xcb-proto-1.5.tar.bz2 http://xcb.freedesktop.org/dist/libxcb-1.1.93.tar.bz2 -http://xcb.freedesktop.org/dist/xcb-util-0.3.3.tar.bz2 +http://xcb.freedesktop.org/dist/xcb-util-0.3.5.tar.bz2 +http://libev.schmorp.de/ http://i3.zekjur.net/i3lock/ http://tools.suckless.org/dmenu From 3f925a56d3475f623828d18ea597d7867b4fb48e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 1 Jun 2009 21:12:13 +0200 Subject: [PATCH 057/129] Fix warning about dereferencing type-punned pointers --- src/mainx.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mainx.c b/src/mainx.c index 5de87700..c0fb6581 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -343,14 +343,14 @@ int main(int argc, char *argv[], char *env[]) { if (loop == NULL) die("Could not initialize libev. Bad LIBEV_FLAGS?\n"); - ev_io xcb_watcher; - ev_io_init(&xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ); + struct ev_io *xcb_watcher = scalloc(sizeof(struct ev_io)); + ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ); /* Call the handler to work all events which arrived before the libev-stuff was set up */ - xcb_got_event(NULL, &xcb_watcher, 0); + xcb_got_event(NULL, xcb_watcher, 0); /* Enter the libev eventloop */ - ev_io_start(loop, &xcb_watcher); + ev_io_start(loop, xcb_watcher); ev_loop(loop, 0); /* not reached */ From 036728c4d78f82cabb2cdd196028018ad1a48bbc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 2 Jun 2009 14:24:37 +0200 Subject: [PATCH 058/129] Use the size when mapping the window as size for floating (correct size for tool windows) --- src/floating.c | 17 +++++++++++++---- src/handlers.c | 1 + src/manage.c | 5 +++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/floating.c b/src/floating.c index 9c71cf36..181fbe34 100644 --- a/src/floating.c +++ b/src/floating.c @@ -96,13 +96,22 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic else client->floating = FLOATING_USER_ON; /* Initialize the floating position from the position in tiling mode, if this - * client never was floating (width == 0) */ - if (client->floating_rect.width == 0) { - memcpy(&(client->floating_rect), &(client->rect), sizeof(Rect)); - LOG("(%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y, + * client never was floating (x == -1) */ + if (client->floating_rect.x == -1) { + /* Copy over the position */ + client->floating_rect.x = client->rect.x; + client->floating_rect.y = client->rect.y; + + /* Copy the size the other direction */ + client->rect.width = client->floating_rect.width; + client->rect.height = client->floating_rect.height; + + LOG("copying size from tiling (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y, client->floating_rect.width, client->floating_rect.height); } else { /* If the client was already in floating before we restore the old position / size */ + LOG("using: (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y, + client->floating_rect.width, client->floating_rect.height); memcpy(&(client->rect), &(client->floating_rect), sizeof(Rect)); } diff --git a/src/handlers.c b/src/handlers.c index cdc735e4..92ad73cb 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -874,6 +874,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w return 1; } xcb_size_hints_t size_hints; + LOG("client is %08x / child %08x\n", client->frame, client->child); /* If the hints were already in this event, use them, if not, request them */ if (reply != NULL) diff --git a/src/manage.c b/src/manage.c index c87de575..9a26f214 100644 --- a/src/manage.c +++ b/src/manage.c @@ -159,6 +159,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, assert(new == NULL); LOG("reparenting new client\n"); + LOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height); new = calloc(sizeof(Client), 1); new->force_reconfigure = true; @@ -172,6 +173,10 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, new->child = child; new->rect.width = width; new->rect.height = height; + /* Pre-initialize the values for floating */ + new->floating_rect.x = -1; + new->floating_rect.width = width; + new->floating_rect.height = height; mask = 0; From fb63c2ed5888c5a9355c58b3045396d9843fe8ea Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 3 Jun 2009 13:54:13 +0200 Subject: [PATCH 059/129] Implement moving floating clients using Mod1+Left mouse button --- src/handlers.c | 14 ++++++++++++++ src/manage.c | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/src/handlers.c b/src/handlers.c index 92ad73cb..250465d1 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -294,6 +294,7 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) { LOG("button press!\n"); + LOG("state = %d\n", event->state); /* This was either a focus for a client’s parent (= titlebar)… */ Client *client = table_get(&by_child, event->event); bool border_click = false; @@ -301,6 +302,19 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ client = table_get(&by_parent, event->event); border_click = true; } + /* See if this was a click with Mod1. If so, we need to move around + * the client if it was floating. if not, we just process as usual. */ + if ((event->state & XCB_MOD_MASK_1) != 0) { + if (client == NULL) { + LOG("Not handling, Mod1 was pressed and no client found\n"); + return 1; + } + if (client->floating) { + floating_drag_window(conn, client, event); + return 1; + } + } + if (client == NULL) { /* The client was neither on a client’s titlebar nor on a client itself, maybe on a stack_window? */ if (button_press_stackwin(conn, event)) diff --git a/src/manage.c b/src/manage.c index 9a26f214..de258c01 100644 --- a/src/manage.c +++ b/src/manage.c @@ -235,6 +235,10 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, 1 /* left mouse button */, XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */); + xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS, + XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE, + 1 /* left mouse button */, XCB_MOD_MASK_1); + /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */ xcb_atom_t *atom; xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL); From 2330c20092ddd482b4fc43bf04134366fc22c17b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 5 Jun 2009 22:56:58 +0200 Subject: [PATCH 060/129] Add new screenshot by atsutane --- website/screenshots/index.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/screenshots/index.html b/website/screenshots/index.html index fb95d45e..1651286b 100644 --- a/website/screenshots/index.html +++ b/website/screenshots/index.html @@ -72,6 +72,10 @@ li {
  • i3 v3.α-bf1, PCManFM, ROXTerm, evince, i3status + dzen2
  • +
  • + i3 v3.b (not yet released), Atsutane had too much free time and formed an i3 logo out of terminals :-) +
  • +

    Screencasts

    From 2f334c3f59f0d9c2c679e8cef8a1771c748c826c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 7 Jun 2009 19:09:39 +0200 Subject: [PATCH 061/129] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20set=20focus?= =?UTF-8?q?=20when=20unmapping=20clients=20if=20the=20workspace=20is=20not?= =?UTF-8?q?=20active=20(Thanks=20Mirko)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes ticket #53 because current workspace was set to the workspace which was deleted. --- src/commands.c | 2 +- src/handlers.c | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/commands.c b/src/commands.c index d84adf45..d1ee5c69 100644 --- a/src/commands.c +++ b/src/commands.c @@ -890,7 +890,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { } if (*rest == '\0') { - /* No rest? This was a tag number, not a times specification */ + /* No rest? This was a workspace number, not a times specification */ show_workspace(conn, times); return; } diff --git a/src/handlers.c b/src/handlers.c index 250465d1..45cb37c1 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -578,25 +578,30 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti /* Let’s see how many clients there are left on the workspace to delete it if it’s empty */ bool workspace_empty = SLIST_EMPTY(&(client->workspace->focus_stack)); + bool workspace_active = false; Client *to_focus = (!workspace_empty ? SLIST_FIRST(&(client->workspace->focus_stack)) : NULL); /* If this workspace is currently active, we don’t delete it */ i3Screen *screen; TAILQ_FOREACH(screen, virtual_screens, screens) if (screen->current_workspace == client->workspace->num) { + workspace_active = true; workspace_empty = false; break; } - if (workspace_empty) + if (workspace_empty) { + LOG("setting ws to NULL for workspace %d (%p)\n", client->workspace->num, + client->workspace); client->workspace->screen = NULL; + } free(client); render_layout(conn); /* Ensure the focus is set to the next client in the focus stack */ - if (to_focus != NULL) + if (workspace_active && to_focus != NULL) set_focus(conn, to_focus, true); return 1; From d70ea033562ac8f72d580225db1945c03359437b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 10 Jun 2009 19:59:36 +0200 Subject: [PATCH 062/129] Remove some dead code, add default paths to switch() to make static analyzers happy Check was done with scan-build from the LLVM suite. The remaining reports are false positives and have been reported to llvm: http://llvm.org/bugs/show_bug.cgi?id=4358 http://llvm.org/bugs/show_bug.cgi?id=4359 --- src/commands.c | 9 +++++++++ src/handlers.c | 11 +---------- src/layout.c | 3 --- src/util.c | 2 +- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/commands.c b/src/commands.c index d1ee5c69..80b94ab9 100644 --- a/src/commands.c +++ b/src/commands.c @@ -239,6 +239,9 @@ static void move_current_window(xcb_connection_t *conn, direction_t direction) { new = CUR_TABLE[current_col][++current_row]; break; + /* To make static analyzers happy: */ + default: + return; } /* Remove it from the old container and put it into the new one */ @@ -312,6 +315,9 @@ static void move_current_container(xcb_connection_t *conn, direction_t direction new = CUR_TABLE[current_col][++current_row]; break; + /* To make static analyzers happy: */ + default: + return; } LOG("old = %d,%d and new = %d,%d\n", container->col, container->row, new->col, new->row); @@ -416,6 +422,9 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction container->rowspan++; break; } + /* To make static analyzers happy: */ + default: + return; } render_layout(conn); diff --git a/src/handlers.c b/src/handlers.c index 45cb37c1..010dcaa5 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -911,8 +911,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w LOG("window is %08x / %s\n", client->child, client->name); - int base_width = 0, base_height = 0, - min_width = 0, min_height = 0; + int base_width = 0, base_height = 0; /* base_width/height are the desired size of the window. We check if either the program-specified size or the program-specified @@ -925,14 +924,6 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w base_height = size_hints.min_height; } - if (size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE) { - min_width = size_hints.min_width; - min_height = size_hints.min_height; - } else if (size_hints.flags & XCB_SIZE_HINT_P_SIZE) { - min_width = size_hints.base_width; - min_height = size_hints.base_height; - } - double width = client->rect.width - base_width; double height = client->rect.height - base_height; /* Convert numerator/denominator to a double */ diff --git a/src/layout.c b/src/layout.c index 8b01471a..426dd027 100644 --- a/src/layout.c +++ b/src/layout.c @@ -336,9 +336,6 @@ void render_container(xcb_connection_t *conn, Container *container) { xcb_configure_window(conn, stack_win->window, mask, values); } - /* Reconfigure the currently focused client, if necessary. It is the only visible one */ - client = container->currently_focused; - /* Render the decorations of all clients */ CIRCLEQ_FOREACH(client, &(container->clients), clients) { /* If the client is in fullscreen mode, it does not get reconfigured */ diff --git a/src/util.c b/src/util.c index 40e39e84..c0268358 100644 --- a/src/util.c +++ b/src/util.c @@ -451,7 +451,7 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode) Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitle, Client *specific) { char *to_class, *to_title, *to_title_ucs = NULL; - int to_title_ucs_len; + int to_title_ucs_len = 0; Client *matching = NULL; to_class = sstrdup(window_classtitle); From 55f0aa8f28c156562156782a3080f073f6aae5e2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 11 Jun 2009 03:30:26 +0200 Subject: [PATCH 063/129] =?UTF-8?q?Add=20a=20proper=20error=20message=20if?= =?UTF-8?q?=20connection=20to=20the=20X=20server=20can=E2=80=99t=20be=20es?= =?UTF-8?q?tablished?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainx.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mainx.c b/src/mainx.c index c0fb6581..cadd5125 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -144,6 +144,9 @@ int main(int argc, char *argv[], char *env[]) { conn = xcb_connect(NULL, &screens); + if (xcb_connection_has_error(conn)) + die("Cannot open display\n"); + load_configuration(conn, override_configpath); /* Place requests for the atoms we need as soon as possible */ From 70be57352ce77a86eafb1270d6f6e4bbf19a40e9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 11 Jun 2009 03:30:42 +0200 Subject: [PATCH 064/129] =?UTF-8?q?Fix=20memleak=20in=20config=20parsing?= =?UTF-8?q?=20(variables=20weren=E2=80=99t=20freed)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/config.c b/src/config.c index a9e90ab3..5d6312ed 100644 --- a/src/config.c +++ b/src/config.c @@ -279,6 +279,15 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath) REQUIRED_OPTION(terminal); REQUIRED_OPTION(font); + + + while (!SLIST_EMPTY(&variables)) { + struct Variable *v = SLIST_FIRST(&variables); + SLIST_REMOVE_HEAD(&variables, variables); + free(v->key); + free(v->value); + free(v); + } return; } From 6e5406167c85b86f6e4a45f3fa0c0ced85dcd0e8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 11 Jun 2009 03:30:56 +0200 Subject: [PATCH 065/129] Bugfix: Use the correct size for the modifiers (Thanks Moredread!) --- src/xcb.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/xcb.c b/src/xcb.c index cefb9c33..db38a7bb 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -267,10 +267,11 @@ void xcb_get_numlock_mask(xcb_connection_t *conn) { /* For now, we only use the first keysymbol. */ xcb_keycode_t *numlock_syms = xcb_key_symbols_get_keycode(keysyms, XCB_NUM_LOCK); xcb_keycode_t numlock = *numlock_syms; + free(numlock_syms); #endif /* Check all modifiers (Mod1-Mod5, Shift, Control, Lock) */ - for (mask = 0; mask < sizeof(masks); mask++) + for (mask = 0; mask < 8; mask++) for (i = 0; i < reply->keycodes_per_modifier; i++) if (modmap[(mask * reply->keycodes_per_modifier) + i] == numlock) xcb_numlock_mask = masks[mask]; From b13a43159fa8acd8a0221a74aed321f8b999d0a0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 11 Jun 2009 03:37:13 +0200 Subject: [PATCH 066/129] Static analyzers: Add noreturn attribute to die() --- include/util.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/util.h b/include/util.h index 5658492e..437411b6 100644 --- a/include/util.h +++ b/include/util.h @@ -54,7 +54,7 @@ void slog(char *fmt, ...); * Prints the message (see printf()) to stderr, then exits the program. * */ -void die(char *fmt, ...); +void die(char *fmt, ...) __attribute__((__noreturn__)); /** * Safe-wrapper around malloc which exits if malloc returns NULL (meaning that there From cb12e205d9cb9e87ff4095d7aa3ae2cfe8328991 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Jun 2009 22:55:56 +0200 Subject: [PATCH 067/129] =?UTF-8?q?Bugfix:=20When=20toggling=20floating,?= =?UTF-8?q?=20the=20copied=20size=20is=20the=20client=E2=80=99s=20size,=20?= =?UTF-8?q?not=20the=20total=20size?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/floating.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/floating.c b/src/floating.c index 181fbe34..6a2d8218 100644 --- a/src/floating.c +++ b/src/floating.c @@ -18,6 +18,7 @@ #include #include "i3.h" +#include "config.h" #include "data.h" #include "util.h" #include "xcb.h" @@ -45,6 +46,7 @@ static void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_pres */ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic) { Container *con = client->container; + i3Font *font = load_font(conn, config.font); if (con == NULL) { LOG("This client is already in floating (container == NULL), re-inserting\n"); @@ -103,8 +105,11 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic client->floating_rect.y = client->rect.y; /* Copy the size the other direction */ - client->rect.width = client->floating_rect.width; - client->rect.height = client->floating_rect.height; + client->child_rect.width = client->floating_rect.width; + client->child_rect.height = client->floating_rect.height; + + client->rect.width = client->child_rect.width + 2 + 2; + client->rect.height = client->child_rect.height + (font->height + 2 + 2) + 2; LOG("copying size from tiling (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y, client->floating_rect.width, client->floating_rect.height); From 052190ad05575e57716f4c4c78ee243b867b849b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Jun 2009 22:59:23 +0200 Subject: [PATCH 068/129] Handle transient hint and window class dialog to mark clients as floating --- include/handlers.h | 10 ++++++++++ include/i3.h | 2 +- include/xcb.h | 1 + src/handlers.c | 48 ++++++++++++++++++++++++++++++++++++++++++++-- src/mainx.c | 3 +++ src/manage.c | 41 ++++++++++++++++++++++++++++----------- 6 files changed, 91 insertions(+), 14 deletions(-) diff --git a/include/handlers.h b/include/handlers.h index c160b600..b4b2c99a 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -124,4 +124,14 @@ int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_wi int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t name, xcb_get_property_reply_t *reply); +/** + * Handles the transient for hints set by a window, signalizing that this window is a popup window + * for some other window. + * + * See ICCCM 4.1.2.6 for more details + * + */ +int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, + xcb_atom_t name, xcb_get_property_reply_t *reply); + #endif diff --git a/include/i3.h b/include/i3.h index 4f285b7c..7bbab415 100644 --- a/include/i3.h +++ b/include/i3.h @@ -20,7 +20,7 @@ #ifndef _I3_H #define _I3_H -#define NUM_ATOMS 13 +#define NUM_ATOMS 14 extern char **start_argv; extern Display *xkbdpy; diff --git a/include/xcb.h b/include/xcb.h index bc335121..7f89caaf 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -49,6 +49,7 @@ enum { _NET_SUPPORTED = 0, _NET_WM_STATE, _NET_WM_WINDOW_TYPE, _NET_WM_WINDOW_TYPE_DOCK, + _NET_WM_WINDOW_TYPE_DIALOG, _NET_WM_DESKTOP, _NET_WM_STRUT_PARTIAL, WM_PROTOCOLS, diff --git a/src/handlers.c b/src/handlers.c index 010dcaa5..1aaf4f5a 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -761,8 +761,8 @@ int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state, return 1; } - if (strcmp(new_class, "tools") == 0) { - LOG("tool window, should we put it floating?\n"); + if (strcmp(new_class, "tools") == 0 || strcmp(new_class, "Dialog") == 0) { + LOG("tool/dialog window, should we put it floating?\n"); if (client->floating == FLOATING_AUTO_OFF) toggle_floating_mode(conn, client, true); } @@ -901,6 +901,11 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w else xcb_get_wm_normal_hints_reply(conn, xcb_get_wm_normal_hints_unchecked(conn, client->child), &size_hints, NULL); + if ((size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE)) { + LOG("min size set\n"); + LOG("gots min_width = %d, min_height = %d\n", size_hints.min_width, size_hints.min_height); + } + /* If no aspect ratio was set or if it was invalid, we ignore the hints */ if (!(size_hints.flags & XCB_SIZE_HINT_P_ASPECT) || (size_hints.min_aspect_num <= 0) || @@ -955,3 +960,42 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w return 1; } + +/* + * Handles the transient for hints set by a window, signalizing that this window is a popup window + * for some other window. + * + * See ICCCM 4.1.2.6 for more details + * + */ +int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, + xcb_atom_t name, xcb_get_property_reply_t *reply) { + LOG("Transient hint!\n"); + Client *client = table_get(&by_child, window); + if (client == NULL) { + LOG("No such client\n"); + return 1; + } + + xcb_window_t transient_for; + + if (reply != NULL) { + if (!xcb_get_wm_transient_for_from_reply(&transient_for, reply)) { + LOG("Not transient for any window\n"); + return 1; + } + } else { + if (!xcb_get_wm_transient_for_reply(conn, xcb_get_wm_transient_for_unchecked(conn, window), + &transient_for, NULL)) { + LOG("Not transient for any window\n"); + return 1; + } + } + + if (client->floating == FLOATING_AUTO_OFF) { + LOG("This is a popup window, putting into floating\n"); + toggle_floating_mode(conn, client, true); + } + + return 1; +} diff --git a/src/mainx.c b/src/mainx.c index cadd5125..5e9bf077 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -275,6 +275,9 @@ int main(int argc, char *argv[], char *env[]) { /* Watch _NET_WM_NAME (= title of the window in UTF-8) property */ xcb_property_set_handler(&prophs, atoms[_NET_WM_NAME], 128, handle_windowname_change, NULL); + /* Watch WM_TRANSIENT_FOR property (to which client this popup window belongs) */ + xcb_property_set_handler(&prophs, WM_TRANSIENT_FOR, UINT_MAX, handle_transient_for, NULL); + /* Watch WM_NAME (= title of the window in compound text) property for legacy applications */ xcb_watch_wm_name(&prophs, 128, handle_windowname_change_legacy, NULL); diff --git a/src/manage.c b/src/manage.c index de258c01..09cf66eb 100644 --- a/src/manage.c +++ b/src/manage.c @@ -113,6 +113,7 @@ void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_ xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS); xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME); xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS); + xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_TRANSIENT_FOR); xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]); free(geom); @@ -169,6 +170,10 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, new->container = CUR_CELL; new->workspace = new->container->workspace; + /* Minimum useful size for managed windows is 75x50 (primarily affects floating) */ + width = max(width, 75); + height = max(height, 50); + new->frame = xcb_generate_id(conn); new->child = child; new->rect.width = width; @@ -243,16 +248,19 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, xcb_atom_t *atom; xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL); if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) { - for (int i = 0; i < xcb_get_property_value_length(preply); i++) { - if (atom[i] != atoms[_NET_WM_WINDOW_TYPE_DOCK]) - continue; - LOG("Window is a dock.\n"); - new->dock = true; - new->titlebar_position = TITLEBAR_OFF; - new->force_reconfigure = true; - new->container = NULL; - SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients); - } + for (int i = 0; i < xcb_get_property_value_length(preply); i++) + if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) { + LOG("Window is a dock.\n"); + new->dock = true; + new->titlebar_position = TITLEBAR_OFF; + new->force_reconfigure = true; + new->container = NULL; + SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients); + } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG]) { + /* Set the dialog window to automatically floating, will be used below */ + new->floating = FLOATING_AUTO_ON; + LOG("dialog window, automatically floating\n"); + } } if (new->dock) { @@ -336,7 +344,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, } /* Insert into the currently active container, if it’s not a dock window */ - if (!new->dock) { + if (!new->dock && new->floating <= FLOATING_USER_OFF) { /* Insert after the old active client, if existing. If it does not exist, the container is empty and it does not matter, where we insert it */ if (old_focused != NULL && !old_focused->dock) @@ -358,6 +366,17 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, } } + if (new->floating >= FLOATING_AUTO_ON) { + new->floating_rect.x = new->rect.x; + new->floating_rect.y = new->rect.y; + LOG("copying size from tiling (%d, %d) size (%d, %d)\n", + new->floating_rect.x, new->floating_rect.y, + new->floating_rect.width, new->floating_rect.height); + + /* Make sure it is on top of the other windows */ + xcb_raise_window(conn, new->frame); + } + new->initialized = true; /* Check if the window already got the fullscreen hint set */ From 13002dc04e28142d6287b94953a1f0526ef2b331 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Jun 2009 23:47:04 +0200 Subject: [PATCH 069/129] Bugfix: Actually reconfigure unmapped windows when they request it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before, we only sent a fake message. While this was sufficient for the client side most of the time, it didn’t allow us to open floating windows with the correct size. --- src/handlers.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index 1aaf4f5a..e472bde5 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -453,8 +453,27 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure Client *client = table_get(&by_child, event->window); if (client == NULL) { LOG("This client is not mapped, so we don't care and just tell the client that he will get its size\n"); - Rect rect = {event->x, event->y, event->width, event->height}; - fake_configure_notify(conn, rect, event->window); + uint32_t mask = 0; + uint32_t values[7]; + int c = 0; +#define COPY_MASK_MEMBER(mask_member, event_member) do { \ + if (event->value_mask & mask_member) { \ + mask |= mask_member; \ + values[c++] = event->event_member; \ + } \ +} while (0) + + COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_X, x); + COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_Y, y); + COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_WIDTH, width); + COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_HEIGHT, height); + COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_BORDER_WIDTH, border_width); + COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_SIBLING, sibling); + COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_STACK_MODE, stack_mode); + + xcb_configure_window(conn, event->window, mask, values); + xcb_flush(conn); + return 1; } From 9f9e21dc7fa65c1e6b35b95281f3527349fc6ea4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 13 Jun 2009 00:34:36 +0200 Subject: [PATCH 070/129] debian: Include additional documentation in the package (docs/) --- debian/i3-wm.docs | 3 +++ debian/rules | 1 + 2 files changed, 4 insertions(+) create mode 100644 debian/i3-wm.docs diff --git a/debian/i3-wm.docs b/debian/i3-wm.docs new file mode 100644 index 00000000..c909b4b8 --- /dev/null +++ b/debian/i3-wm.docs @@ -0,0 +1,3 @@ +docs/debugging.html +docs/hacking-howto.html +docs/userguide.html diff --git a/debian/rules b/debian/rules index 78eba656..1fee800a 100755 --- a/debian/rules +++ b/debian/rules @@ -19,6 +19,7 @@ build: # Add here commands to compile the package. $(MAKE) $(MAKE) -C man + $(MAKE) -C docs touch $@ From 02fa3c2cb86640628c3f04e22359ab052097785e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 13 Jun 2009 12:18:16 +0200 Subject: [PATCH 071/129] =?UTF-8?q?Bugfix:=20Correctly=20initialize=20the?= =?UTF-8?q?=20new=20atom,=20don=E2=80=99t=20float=20dock=20windows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mainx.c | 2 ++ src/manage.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/mainx.c b/src/mainx.c index 5e9bf077..67ab25fd 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -160,6 +160,7 @@ int main(int argc, char *argv[], char *env[]) { REQUEST_ATOM(_NET_WM_WINDOW_TYPE); REQUEST_ATOM(_NET_WM_DESKTOP); REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK); + REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DIALOG); REQUEST_ATOM(_NET_WM_STRUT_PARTIAL); REQUEST_ATOM(WM_PROTOCOLS); REQUEST_ATOM(WM_DELETE_WINDOW); @@ -263,6 +264,7 @@ int main(int argc, char *argv[], char *env[]) { GET_ATOM(_NET_WM_WINDOW_TYPE); GET_ATOM(_NET_WM_DESKTOP); GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK); + GET_ATOM(_NET_WM_WINDOW_TYPE_DIALOG); GET_ATOM(_NET_WM_STRUT_PARTIAL); GET_ATOM(WM_PROTOCOLS); GET_ATOM(WM_DELETE_WINDOW); diff --git a/src/manage.c b/src/manage.c index 09cf66eb..84e0e15f 100644 --- a/src/manage.c +++ b/src/manage.c @@ -256,6 +256,8 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, new->force_reconfigure = true; new->container = NULL; SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients); + /* If it’s a dock we can’t make it float, so we break */ + break; } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG]) { /* Set the dialog window to automatically floating, will be used below */ new->floating = FLOATING_AUTO_ON; From 56af58eb0bf573bf1ef3f31daaf9ec2c09346093 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 13 Jun 2009 12:20:39 +0200 Subject: [PATCH 072/129] Bugfix: Put floating clients into the focus stack (Thanks Mirko) --- src/manage.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/manage.c b/src/manage.c index 84e0e15f..e7b7c5f6 100644 --- a/src/manage.c +++ b/src/manage.c @@ -369,6 +369,8 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, } if (new->floating >= FLOATING_AUTO_ON) { + SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients); + new->floating_rect.x = new->rect.x; new->floating_rect.y = new->rect.y; LOG("copying size from tiling (%d, %d) size (%d, %d)\n", From e6d9c7bb9ebe1efc8fdd3051bba76708a9bf20ae Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 13 Jun 2009 12:30:28 +0200 Subject: [PATCH 073/129] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20use=20the=20m?= =?UTF-8?q?inimum=20amount=20of=20pixels=20for=20dock=20clients=20which=20?= =?UTF-8?q?don=E2=80=99t=20set=20size=20hints?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/manage.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/manage.c b/src/manage.c index e7b7c5f6..e84c6345 100644 --- a/src/manage.c +++ b/src/manage.c @@ -137,6 +137,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, utf8_title_cookie, title_cookie, class_cookie; uint32_t mask = 0; uint32_t values[3]; + uint16_t original_height = height; /* We are interested in property changes */ mask = XCB_CW_EVENT_MASK; @@ -276,13 +277,13 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, TODO: bars at the top */ new->desired_height = strut[3]; if (new->desired_height == 0) { - LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", height); - new->desired_height = height; + LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height); + new->desired_height = original_height; } LOG("the client wants to be %d pixels high\n", new->desired_height); } else { - LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", height); - new->desired_height = height; + LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height); + new->desired_height = original_height; } } else { /* If it’s not a dock, we can check on which workspace we should put it. */ From 33b331d444b93fee43a37553f34a4c91020e7272 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 13 Jun 2009 20:10:49 +0200 Subject: [PATCH 074/129] userguide: Document variables --- docs/userguide | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/docs/userguide b/docs/userguide index b9272087..6787363d 100644 --- a/docs/userguide +++ b/docs/userguide @@ -7,6 +7,8 @@ This document contains all information you need to configuring and using the i3 window manager. If it does not, please contact me on IRC, Jabber or E-Mail and I’ll help you out. +For a complete listing of the default keybindings, please see the manpage. + == Using i3 === Creating terminals and moving around @@ -158,8 +160,6 @@ you can set specific applications to start on a specific workspace, you can automatically start applications, you can change the colors of i3 or bind your keys to do useful stuff. -TODO: implement variables - terminal:: Specifies the terminal emulator program you prefer. It will be started by default when you press Mod1+Enter, but you can overwrite this. Refer @@ -204,6 +204,29 @@ umlauts or special characters 'and' having some comfortably reachable key bindings. For example, when typing, capslock+1 or capslock+2 for switching workspaces is totally convenient. Try it :-). +=== Variables + +As you learned in the previous section about keyboard bindings, you will have +to configure lots of bindings containing modifier keys. If you want to save +yourself some typing and have the possibility to change the modifier you want +to use later, variables can be handy. + +*Syntax*: +-------------- +set name value +-------------- + +*Examples*: +------------------------ +set $m Mod1 +bind $m+Shift+27 restart +------------------------ + +Variables are directly replaced in the file when parsing, there is no fancy +handling and there are absolutely no plans to change this. If you need a more +dynamic configuration, you should create a little script, like when configuring +wmii. + === Automatically putting clients on specific workspaces It is recommended that you match on window classes whereever possible because From 00c6bdeb0b1c575a02c5d28fbd8549875e98c1f1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 14 Jun 2009 01:04:59 +0200 Subject: [PATCH 075/129] Implement changing focus via keyboard between floating clients, fix several floating bugs --- include/data.h | 7 ++++++- include/floating.h | 9 +++++++++ src/commands.c | 17 ++++++++++++----- src/floating.c | 32 +++++++++++++++++++++++++++++++- src/handlers.c | 5 +++++ src/manage.c | 8 +++++++- src/table.c | 1 + 7 files changed, 71 insertions(+), 8 deletions(-) diff --git a/include/data.h b/include/data.h index 5354217b..1f4e1d0f 100644 --- a/include/data.h +++ b/include/data.h @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * (c) 2009 Michael Stapelberg and contributors + * © 2009 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -167,6 +167,10 @@ struct Workspace { the focus can be reverted correctly when a client is closed */ SLIST_HEAD(focus_stack_head, Client) focus_stack; + /* This tail queue contains the floating clients in order of when they were first + * set to floating (new floating clients are just appended) */ + TAILQ_HEAD(floating_clients_head, Client) floating_clients; + /* Backpointer to the screen this workspace is on */ i3Screen *screen; @@ -319,6 +323,7 @@ struct Client { CIRCLEQ_ENTRY(Client) clients; SLIST_ENTRY(Client) dock_clients; SLIST_ENTRY(Client) focus_clients; + TAILQ_ENTRY(Client) floating_clients; }; /* diff --git a/include/floating.h b/include/floating.h index 232e118e..93110bc2 100644 --- a/include/floating.h +++ b/include/floating.h @@ -37,4 +37,13 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre */ void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event); +/** + * 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); + #endif diff --git a/src/commands.c b/src/commands.c index 80b94ab9..274f0e27 100644 --- a/src/commands.c +++ b/src/commands.c @@ -923,8 +923,13 @@ void parse_command(xcb_connection_t *conn, const char *command) { return; } - if (last_focused == NULL || last_focused->floating >= FLOATING_AUTO_ON) { - LOG("Not performing (null or floating) \n"); + if (last_focused == NULL) { + LOG("Not performing (null) \n"); + return; + } + + if (last_focused->floating >= FLOATING_AUTO_ON && action != ACTION_FOCUS) { + LOG("Not performing (floating)\n"); return; } @@ -943,9 +948,11 @@ void parse_command(xcb_connection_t *conn, const char *command) { return; } - if (action == ACTION_FOCUS) - focus_thing(conn, direction, (with == WITH_WINDOW ? THING_WINDOW : THING_CONTAINER)); - else if (action == ACTION_MOVE) { + if (action == ACTION_FOCUS) { + if (last_focused->floating >= FLOATING_AUTO_ON) + floating_focus_direction(conn, last_focused, direction); + else focus_thing(conn, direction, (with == WITH_WINDOW ? THING_WINDOW : THING_CONTAINER)); + } else if (action == ACTION_MOVE) { if (with == WITH_WINDOW) move_current_window(conn, direction); else move_current_container(conn, direction); diff --git a/src/floating.c b/src/floating.c index 6a2d8218..310b6a39 100644 --- a/src/floating.c +++ b/src/floating.c @@ -56,10 +56,13 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic break; /* If there are no tiling clients on this workspace, there can only be one * container: the first one */ - if (next_tiling == SLIST_END(&(client->workspace->focus_stack))) + if (next_tiling == TAILQ_END(&(client->workspace->focus_stack))) con = client->workspace->table[0][0]; else con = next_tiling->container; + /* Remove the client from the list of floating clients */ + TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients); + LOG("destination container = %p\n", con); Client *old_focused = con->currently_focused; /* Preserve position/size */ @@ -87,6 +90,9 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic client_remove_from_container(conn, client, con, false); client->container = NULL; + /* Add the client to the list of floating clients for its workspace */ + TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients); + if (con->currently_focused == client) { LOG("Need to re-adjust currently_focused\n"); /* Get the next client in the focus stack for this particular container */ @@ -288,3 +294,27 @@ static void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_pres xcb_flush(conn); } +/* + * 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) { + LOG("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; + } + } +} diff --git a/src/handlers.c b/src/handlers.c index e472bde5..2d4afc71 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -584,6 +584,11 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti SLIST_REMOVE(&(client->workspace->screen->dock_clients), client, Client, dock_clients); } + if (client->floating) { + LOG("Removing from floating clients\n"); + TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients); + } + LOG("child of 0x%08x.\n", client->frame); xcb_reparent_window(conn, client->child, root, 0, 0); xcb_destroy_window(conn, client->frame); diff --git a/src/manage.c b/src/manage.c index e84c6345..f7769c3b 100644 --- a/src/manage.c +++ b/src/manage.c @@ -340,7 +340,8 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, } else if (!new->dock) { /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */ if (new->container->workspace->fullscreen_client == NULL) { - new->container->currently_focused = new; + if (new->floating <= FLOATING_USER_OFF) + new->container->currently_focused = new; if (new->container == CUR_CELL) xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME); } @@ -372,6 +373,11 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, if (new->floating >= FLOATING_AUTO_ON) { SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients); + /* Add the client to the list of floating clients for its workspace */ + TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients); + + new->container = NULL; + new->floating_rect.x = new->rect.x; new->floating_rect.y = new->rect.y; LOG("copying size from tiling (%d, %d) size (%d, %d)\n", diff --git a/src/table.c b/src/table.c index 20b88cec..dab4b9ac 100644 --- a/src/table.c +++ b/src/table.c @@ -43,6 +43,7 @@ void init_table() { for (int i = 0; i < 10; i++) { workspaces[i].screen = NULL; workspaces[i].num = i; + TAILQ_INIT(&(workspaces[i].floating_clients)); expand_table_cols(&(workspaces[i])); expand_table_rows(&(workspaces[i])); } From 086f6a47e3d7fbafb8832cf2b1c505eeb864697b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 14 Jun 2009 01:10:17 +0200 Subject: [PATCH 076/129] userguide: be more specific --- docs/userguide | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/userguide b/docs/userguide index 6787363d..0c7f9a07 100644 --- a/docs/userguide +++ b/docs/userguide @@ -113,7 +113,7 @@ To restart i3 inplace (and thus get it into a clean state if it has a bug, to reload your configuration or even to upgrade to a newer version of i3) you can use +Mod1+Shift+r+. Be aware, though, that this kills your current layout and all the windows you have opened will be put in a default container in only -one cell. This will be implemented in a later version. +one cell. Saving the layout will be implemented in a later version. === Exiting i3 @@ -129,7 +129,7 @@ layout: image:snapping.png[Snapping example] To use the full size of your screen, you can now snap container 3 downwards -by pressing +Mod1+Control+k+. +by pressing +Mod1+Control+k+ (or snap container 2 rightwards). === Floating From 1c02ddb4a7d85eaa470ccd9ebcf0386c004621cc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 18 Jun 2009 01:25:46 +0200 Subject: [PATCH 077/129] Bugfix: Fix assignment of clients to other workspaces (Thanks badboy) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The problem was that the old_focused pointer was pointing to an element of a different list. Using CIRCLEQ_APPEND_AFTER is not a good idea on with such an element… --- src/manage.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/manage.c b/src/manage.c index f7769c3b..72c7bce1 100644 --- a/src/manage.c +++ b/src/manage.c @@ -326,6 +326,8 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, new->container = t_ws->table[t_ws->current_col][t_ws->current_row]; new->workspace = t_ws; + old_focused = new->container->currently_focused; + xcb_unmap_window(conn, new->frame); break; } From a2a8cd85d6db9315e36c50eab52d1b8073d2aecb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 19 Jun 2009 11:05:00 +0200 Subject: [PATCH 078/129] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20crash=20when?= =?UTF-8?q?=20programs=20set=20NULL=20hints=20(xev(1)=20for=20example)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This only happened if you had some assignment configured --- src/client.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client.c b/src/client.c index 53cacbf4..a7a6e3e8 100644 --- a/src/client.c +++ b/src/client.c @@ -115,7 +115,7 @@ void client_kill(xcb_connection_t *conn, Client *window) { bool client_matches_class_name(Client *client, char *to_class, char *to_title, char *to_title_ucs, int to_title_ucs_len) { /* Check if the given class is part of the window class */ - if (strcasestr(client->window_class, to_class) == NULL) + if (client->window_class == NULL || strcasestr(client->window_class, to_class) == NULL) return false; /* If no title was given, we’re done */ @@ -124,11 +124,11 @@ bool client_matches_class_name(Client *client, char *to_class, char *to_title, if (client->name_len > -1) { /* UCS-2 converted window titles */ - if (memmem(client->name, (client->name_len * 2), to_title_ucs, (to_title_ucs_len * 2)) == NULL) + if (client->name == NULL || memmem(client->name, (client->name_len * 2), to_title_ucs, (to_title_ucs_len * 2)) == NULL) return false; } else { /* Legacy hints */ - if (strcasestr(client->name, to_title) == NULL) + if (client->name == NULL || strcasestr(client->name, to_title) == NULL) return false; } From a6d7f5451d721b26ce72a3ae14b3418fb3d6598c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 19 Jun 2009 12:05:55 +0200 Subject: [PATCH 079/129] Also set DIALOG, UTILITY and SPLASH windows floating automatically --- include/i3.h | 4 ++-- include/xcb.h | 3 +++ src/commands.c | 2 +- src/mainx.c | 6 ++++++ src/manage.c | 5 ++++- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/include/i3.h b/include/i3.h index 7bbab415..ccf7a495 100644 --- a/include/i3.h +++ b/include/i3.h @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * (c) 2009 Michael Stapelberg and contributors + * © 2009 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -20,7 +20,7 @@ #ifndef _I3_H #define _I3_H -#define NUM_ATOMS 14 +#define NUM_ATOMS 17 extern char **start_argv; extern Display *xkbdpy; diff --git a/include/xcb.h b/include/xcb.h index 7f89caaf..a8a75876 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -50,6 +50,9 @@ enum { _NET_SUPPORTED = 0, _NET_WM_WINDOW_TYPE, _NET_WM_WINDOW_TYPE_DOCK, _NET_WM_WINDOW_TYPE_DIALOG, + _NET_WM_WINDOW_TYPE_UTILITY, + _NET_WM_WINDOW_TYPE_TOOLBAR, + _NET_WM_WINDOW_TYPE_SPLASH, _NET_WM_DESKTOP, _NET_WM_STRUT_PARTIAL, WM_PROTOCOLS, diff --git a/src/commands.c b/src/commands.c index 274f0e27..10ac35f8 100644 --- a/src/commands.c +++ b/src/commands.c @@ -924,7 +924,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { } if (last_focused == NULL) { - LOG("Not performing (null) \n"); + LOG("Not performing (no window found)\n"); return; } diff --git a/src/mainx.c b/src/mainx.c index 67ab25fd..ca1bc8ad 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -161,6 +161,9 @@ int main(int argc, char *argv[], char *env[]) { REQUEST_ATOM(_NET_WM_DESKTOP); REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK); REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DIALOG); + REQUEST_ATOM(_NET_WM_WINDOW_TYPE_UTILITY); + REQUEST_ATOM(_NET_WM_WINDOW_TYPE_TOOLBAR); + REQUEST_ATOM(_NET_WM_WINDOW_TYPE_SPLASH); REQUEST_ATOM(_NET_WM_STRUT_PARTIAL); REQUEST_ATOM(WM_PROTOCOLS); REQUEST_ATOM(WM_DELETE_WINDOW); @@ -265,6 +268,9 @@ int main(int argc, char *argv[], char *env[]) { GET_ATOM(_NET_WM_DESKTOP); GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK); GET_ATOM(_NET_WM_WINDOW_TYPE_DIALOG); + GET_ATOM(_NET_WM_WINDOW_TYPE_UTILITY); + GET_ATOM(_NET_WM_WINDOW_TYPE_TOOLBAR); + GET_ATOM(_NET_WM_WINDOW_TYPE_SPLASH); GET_ATOM(_NET_WM_STRUT_PARTIAL); GET_ATOM(WM_PROTOCOLS); GET_ATOM(WM_DELETE_WINDOW); diff --git a/src/manage.c b/src/manage.c index 72c7bce1..1f79cb94 100644 --- a/src/manage.c +++ b/src/manage.c @@ -259,7 +259,10 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients); /* If it’s a dock we can’t make it float, so we break */ break; - } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG]) { + } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] || + atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] || + atom[i] == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR] || + atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) { /* Set the dialog window to automatically floating, will be used below */ new->floating = FLOATING_AUTO_ON; LOG("dialog window, automatically floating\n"); From 51402b05f5eeed44817c79f1fef989a45b69831f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 19 Jun 2009 12:34:31 +0200 Subject: [PATCH 080/129] Bugfix: Correctly initialize automatically floating clients (some rendering issues) --- src/floating.c | 2 +- src/manage.c | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/floating.c b/src/floating.c index 310b6a39..7cf4b6dc 100644 --- a/src/floating.c +++ b/src/floating.c @@ -110,7 +110,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic client->floating_rect.x = client->rect.x; client->floating_rect.y = client->rect.y; - /* Copy the size the other direction */ + /* Copy size the other direction */ client->child_rect.width = client->floating_rect.width; client->child_rect.height = client->floating_rect.height; diff --git a/src/manage.c b/src/manage.c index 1f79cb94..f4edc055 100644 --- a/src/manage.c +++ b/src/manage.c @@ -391,6 +391,10 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, /* Make sure it is on top of the other windows */ xcb_raise_window(conn, new->frame); + reposition_client(conn, new); + resize_client(conn, new); + /* redecorate_window flushes */ + redecorate_window(conn, new); } new->initialized = true; From 4135aaad7cefc6b5c29f04881a368c81f9c65013 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 19 Jun 2009 12:57:21 +0200 Subject: [PATCH 081/129] Implement moving of floating clients --- include/floating.h | 6 ++++++ src/commands.c | 27 ++++++++++++++++++++------- src/floating.c | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 7 deletions(-) diff --git a/include/floating.h b/include/floating.h index 93110bc2..020e91ff 100644 --- a/include/floating.h +++ b/include/floating.h @@ -46,4 +46,10 @@ void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_pre */ void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, direction_t direction); +/** + * Moves the client 10px to the specified direction. + * + */ +void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_t direction); + #endif diff --git a/src/commands.c b/src/commands.c index 10ac35f8..3300d1f8 100644 --- a/src/commands.c +++ b/src/commands.c @@ -928,7 +928,8 @@ void parse_command(xcb_connection_t *conn, const char *command) { return; } - if (last_focused->floating >= FLOATING_AUTO_ON && action != ACTION_FOCUS) { + if (last_focused->floating >= FLOATING_AUTO_ON && + (action != ACTION_FOCUS && action != ACTION_MOVE)) { LOG("Not performing (floating)\n"); return; } @@ -947,20 +948,32 @@ void parse_command(xcb_connection_t *conn, const char *command) { LOG("unknown direction: %c\n", *rest); return; } + rest++; if (action == ACTION_FOCUS) { - if (last_focused->floating >= FLOATING_AUTO_ON) + if (last_focused->floating >= FLOATING_AUTO_ON) { floating_focus_direction(conn, last_focused, direction); - else focus_thing(conn, direction, (with == WITH_WINDOW ? THING_WINDOW : THING_CONTAINER)); - } else if (action == ACTION_MOVE) { + continue; + } + focus_thing(conn, direction, (with == WITH_WINDOW ? THING_WINDOW : THING_CONTAINER)); + continue; + } + + if (action == ACTION_MOVE) { + if (last_focused->floating >= FLOATING_AUTO_ON) { + floating_move(conn, last_focused, direction); + continue; + } if (with == WITH_WINDOW) move_current_window(conn, direction); else move_current_container(conn, direction); + continue; } - else if (action == ACTION_SNAP) - snap_current_container(conn, direction); - rest++; + if (action == ACTION_SNAP) { + snap_current_container(conn, direction); + continue; + } } LOG("--- done ---\n"); diff --git a/src/floating.c b/src/floating.c index 7cf4b6dc..fb5c9511 100644 --- a/src/floating.c +++ b/src/floating.c @@ -318,3 +318,40 @@ void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, } } } + +/* + * Moves the client 10px to the specified direction. + * + */ +void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_t direction) { + LOG("floating move\n"); + + switch (direction) { + case D_LEFT: + if (currently_focused->rect.x < 10) + return; + currently_focused->rect.x -= 10; + break; + case D_RIGHT: + currently_focused->rect.x += 10; + break; + case D_UP: + if (currently_focused->rect.y < 10) + return; + currently_focused->rect.y -= 10; + break; + case D_DOWN: + currently_focused->rect.y += 10; + break; + /* to make static analyzers happy */ + default: + break; + } + + reposition_client(conn, currently_focused); + + /* Because reposition_client does not send a faked configure event (only resize does), + * we need to initiate that on our own */ + fake_absolute_configure_notify(conn, currently_focused); + /* fake_absolute_configure_notify flushes */ +} From 5c48444b4e290b3b604d99c2f595f6ab5f36951b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 19 Jun 2009 13:20:10 +0200 Subject: [PATCH 082/129] Implement the possibility to set a workspace open clients automatically in floating mode Use "wwt" (with workspace: toggle floating) in your configuration file --- include/data.h | 3 +++ src/commands.c | 38 +++++++++++++++++++++++--------------- src/manage.c | 7 ++++++- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/include/data.h b/include/data.h index 1f4e1d0f..a31b6982 100644 --- a/include/data.h +++ b/include/data.h @@ -161,6 +161,9 @@ struct Workspace { int current_row; int current_col; + /* Should clients on this workspace be automatically floating? */ + bool auto_float; + Client *fullscreen_client; /* The focus stack contains the clients in the correct order of focus so that diff --git a/src/commands.c b/src/commands.c index 3300d1f8..d809c81a 100644 --- a/src/commands.c +++ b/src/commands.c @@ -854,8 +854,31 @@ void parse_command(xcb_connection_t *conn, const char *command) { return; } + enum { WITH_WINDOW, WITH_CONTAINER, WITH_WORKSPACE } with = WITH_WINDOW; + + /* Is it a ? */ + if (command[0] == 'w') { + command++; + /* TODO: implement */ + if (command[0] == 'c') { + with = WITH_CONTAINER; + command++; + } else if (command[0] == 'w') { + with = WITH_WORKSPACE; + command++; + } else { + LOG("not yet implemented.\n"); + return; + } + } + /* Is it 't' for toggle tiling/floating? */ if (command[0] == 't') { + if (with == WITH_WORKSPACE) { + c_ws->auto_float = !c_ws->auto_float; + LOG("autofloat is now %d\n", c_ws->auto_float); + return; + } if (last_focused == NULL) { LOG("Cannot toggle tiling/floating: workspace empty\n"); return; @@ -873,21 +896,6 @@ void parse_command(xcb_connection_t *conn, const char *command) { return; } - enum { WITH_WINDOW, WITH_CONTAINER } with = WITH_WINDOW; - - /* Is it a ? */ - if (command[0] == 'w') { - command++; - /* TODO: implement */ - if (command[0] == 'c') { - with = WITH_CONTAINER; - command++; - } else { - LOG("not yet implemented.\n"); - return; - } - } - /* It’s a normal */ char *rest = NULL; enum { ACTION_FOCUS, ACTION_MOVE, ACTION_SNAP } action = ACTION_FOCUS; diff --git a/src/manage.c b/src/manage.c index f4edc055..aebfe546 100644 --- a/src/manage.c +++ b/src/manage.c @@ -265,10 +265,15 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) { /* Set the dialog window to automatically floating, will be used below */ new->floating = FLOATING_AUTO_ON; - LOG("dialog window, automatically floating\n"); + LOG("dialog/utility/toolbar/splash window, automatically floating\n"); } } + if (new->workspace->auto_float) { + new->floating = FLOATING_AUTO_ON; + LOG("workspace is in autofloat mode, setting floating\n"); + } + if (new->dock) { /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */ uint32_t *strut; From 89c0caaec418d0b3328cf692ffba2d4324e63a14 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 19 Jun 2009 13:59:29 +0200 Subject: [PATCH 083/129] Implement a command for hiding all floating windows (and showing them again) --- include/data.h | 2 ++ include/floating.h | 7 +++++++ src/commands.c | 15 +++++++++------ src/floating.c | 28 ++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/include/data.h b/include/data.h index a31b6982..87dd4995 100644 --- a/include/data.h +++ b/include/data.h @@ -163,6 +163,8 @@ struct Workspace { /* Should clients on this workspace be automatically floating? */ bool auto_float; + /* Are the floating clients on this workspace currently hidden? */ + bool floating_hidden; Client *fullscreen_client; diff --git a/include/floating.h b/include/floating.h index 020e91ff..edc11a95 100644 --- a/include/floating.h +++ b/include/floating.h @@ -52,4 +52,11 @@ void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, */ void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_t direction); +/** + * Hides all floating clients (or show them if they are currently hidden) on + * the specified workspace. + * + */ +void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace); + #endif diff --git a/src/commands.c b/src/commands.c index d809c81a..fb3c5422 100644 --- a/src/commands.c +++ b/src/commands.c @@ -625,12 +625,9 @@ void show_workspace(xcb_connection_t *conn, int workspace) { xcb_map_window(conn, client->frame); /* Map all floating clients */ - SLIST_FOREACH(client, &(c_ws->focus_stack), focus_clients) { - if (client->floating <= FLOATING_USER_OFF) - continue; - - xcb_map_window(conn, client->frame); - } + if (!c_ws->floating_hidden) + TAILQ_FOREACH(client, &(c_ws->floating_clients), floating_clients) + xcb_map_window(conn, client->frame); /* Map all stack windows, if any */ struct Stack_Window *stack_win; @@ -854,6 +851,12 @@ void parse_command(xcb_connection_t *conn, const char *command) { return; } + if (command[0] == 'H') { + LOG("Hiding all floating windows\n"); + floating_toggle_hide(conn, c_ws); + return; + } + enum { WITH_WINDOW, WITH_CONTAINER, WITH_WORKSPACE } with = WITH_WINDOW; /* Is it a ? */ diff --git a/src/floating.c b/src/floating.c index fb5c9511..c65f516e 100644 --- a/src/floating.c +++ b/src/floating.c @@ -355,3 +355,31 @@ void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_ fake_absolute_configure_notify(conn, currently_focused); /* fake_absolute_configure_notify flushes */ } + +/* + * Hides all floating clients (or show them if they are currently hidden) on + * the specified workspace. + * + */ +void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace) { + Client *client; + + workspace->floating_hidden = !workspace->floating_hidden; + LOG("floating_hidden is now: %d\n", workspace->floating_hidden); + TAILQ_FOREACH(client, &(workspace->floating_clients), floating_clients) { + if (workspace->floating_hidden) + xcb_unmap_window(conn, client->frame); + else xcb_map_window(conn, client->frame); + } + + /* If we just unmapped all floating windows we should ensure that the focus + * is set correctly, that ist, to the first non-floating client in stack */ + if (workspace->floating_hidden) + SLIST_FOREACH(client, &(workspace->focus_stack), focus_clients) + if (client->floating <= FLOATING_USER_OFF) { + set_focus(conn, client, true); + return; + } + + xcb_flush(conn); +} From bcd479ca92a0720e872b0cb7dc99c70a5cbaab0f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 19 Jun 2009 19:43:01 +0200 Subject: [PATCH 084/129] floating: nested functions make the callbacks a lot more easier & beautiful --- src/floating.c | 86 ++++++++++++++++++++++++-------------------------- 1 file changed, 42 insertions(+), 44 deletions(-) diff --git a/src/floating.c b/src/floating.c index c65f516e..f455739c 100644 --- a/src/floating.c +++ b/src/floating.c @@ -29,7 +29,7 @@ /* On which border was the dragging initiated? */ typedef enum { BORDER_LEFT, BORDER_RIGHT, BORDER_TOP, BORDER_BOTTOM} border_t; /* Callback for dragging */ -typedef void(*callback_t)(xcb_connection_t*, Client*, border_t, Rect*, xcb_button_press_event_t*, uint32_t, uint32_t); +typedef void(*callback_t)(Rect*, uint32_t, uint32_t); /* Forward definitions */ static void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event, @@ -138,37 +138,6 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic xcb_flush(conn); } -/* - * Callback for resizing windows - * - */ -static void resize_callback(xcb_connection_t *conn, Client *client, border_t border, Rect *old_rect, - xcb_button_press_event_t *event, uint32_t new_x, uint32_t new_y) { - switch (border) { - case BORDER_RIGHT: - client->rect.width = old_rect->width + (new_x - event->root_x); - break; - - case BORDER_BOTTOM: - client->rect.height = old_rect->height + (new_y - event->root_y); - break; - - case BORDER_TOP: - client->rect.y = old_rect->y + (new_y - event->root_y); - client->rect.height = old_rect->height + (event->root_y - new_y); - break; - - case BORDER_LEFT: - client->rect.x = old_rect->x + (new_x - event->root_x); - client->rect.width = old_rect->width + (event->root_x - new_x); - break; - } - - /* Push the new position/size to X11 */ - reposition_client(conn, client); - resize_client(conn, client); - xcb_flush(conn); -} /* * Called whenever the user clicks on a border (not the titlebar!) of a floating window. @@ -181,6 +150,34 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre LOG("floating border click\n"); border_t border; + + void resize_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) { + switch (border) { + case BORDER_RIGHT: + client->rect.width = old_rect->width + (new_x - event->root_x); + break; + + case BORDER_BOTTOM: + client->rect.height = old_rect->height + (new_y - event->root_y); + break; + + case BORDER_TOP: + client->rect.y = old_rect->y + (new_y - event->root_y); + client->rect.height = old_rect->height + (event->root_y - new_y); + break; + + case BORDER_LEFT: + client->rect.x = old_rect->x + (new_x - event->root_x); + client->rect.width = old_rect->width + (event->root_x - new_x); + break; + } + + /* Push the new position/size to X11 */ + reposition_client(conn, client); + resize_client(conn, client); + xcb_flush(conn); + } + if (event->event_y < 2) border = BORDER_TOP; else if (event->event_y >= (client->rect.height - 2)) @@ -201,17 +198,6 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre return 1; } -static void drag_window_callback(xcb_connection_t *conn, Client *client, border_t border, Rect *old_rect, - xcb_button_press_event_t *event, uint32_t new_x, uint32_t new_y) { - /* Reposition the client correctly while moving */ - client->rect.x = old_rect->x + (new_x - event->root_x); - client->rect.y = old_rect->y + (new_y - event->root_y); - reposition_client(conn, client); - /* Because reposition_client does not send a faked configure event (only resize does), - * we need to initiate that on our own */ - fake_absolute_configure_notify(conn, client); - /* fake_absolute_configure_notify flushes */ -} /* * Called when the user clicked on the titlebar of a floating window. @@ -221,6 +207,18 @@ static void drag_window_callback(xcb_connection_t *conn, Client *client, border_ void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) { LOG("floating_drag_window\n"); + void drag_window_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) { + /* Reposition the client correctly while moving */ + client->rect.x = old_rect->x + (new_x - event->root_x); + client->rect.y = old_rect->y + (new_y - event->root_y); + reposition_client(conn, client); + /* Because reposition_client does not send a faked configure event (only resize does), + * we need to initiate that on our own */ + fake_absolute_configure_notify(conn, client); + /* fake_absolute_configure_notify flushes */ + } + + drag_pointer(conn, client, event, BORDER_TOP /* irrelevant */, drag_window_callback); } @@ -278,7 +276,7 @@ static void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_pres new_x = ((xcb_motion_notify_event_t*)inside_event)->root_x; new_y = ((xcb_motion_notify_event_t*)inside_event)->root_y; - callback(conn, client, border, &old_rect, event, new_x, new_y); + callback(&old_rect, new_x, new_y); break; default: From 325d1b301f3b6b5b1a7b7bc14113d0faff159330 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 19 Jun 2009 20:20:00 +0200 Subject: [PATCH 085/129] Implement the special workspace ~ for assignments, which will set clients floating automatically --- docs/userguide | 4 ++++ include/data.h | 3 +++ src/config.c | 8 +++++--- src/manage.c | 6 ++++++ 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/docs/userguide b/docs/userguide index 0c7f9a07..4ba97ee9 100644 --- a/docs/userguide +++ b/docs/userguide @@ -237,6 +237,9 @@ 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 use the special workspace +~+ to specify that matching clients should +be put into floating mode. + *Syntax*: ---------------------------------------------------- assign ["]window class[/window title]["] [→] workspace @@ -248,6 +251,7 @@ assign urxvt 2 assign urxvt → 2 assign "urxvt" → 2 assign "urxvt/VIM" → 3 +assign "gecko" → ~ ---------------------- === Automatically starting applications on startup diff --git a/include/data.h b/include/data.h index 87dd4995..b468c1f7 100644 --- a/include/data.h +++ b/include/data.h @@ -223,6 +223,9 @@ struct Autostart { */ struct Assignment { char *windowclass_title; + /* floating is true if this was an assignment to the special workspace "~". + * Matching clients will be put into floating mode automatically. */ + bool floating; int workspace; TAILQ_ENTRY(Assignment) assignments; }; diff --git a/src/config.c b/src/config.c index 5d6312ed..197fd4e1 100644 --- a/src/config.c +++ b/src/config.c @@ -241,14 +241,16 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath) die("Malformed assignment, couldn't find target\n"); target++; - if (atoi(target) < 1 || atoi(target) > 10) + if (*target != '~' && (atoi(target) < 1 || atoi(target) > 10)) die("Malformed assignment, invalid workspace number\n"); LOG("assignment parsed: \"%s\" to \"%s\"\n", class_title, target); - struct Assignment *new = smalloc(sizeof(struct Assignment)); + struct Assignment *new = scalloc(sizeof(struct Assignment)); new->windowclass_title = class_title; - new->workspace = atoi(target); + if (*target == '~') + new->floating = true; + else new->workspace = atoi(target); TAILQ_INSERT_TAIL(&assignments, new, assignments); continue; } diff --git a/src/manage.c b/src/manage.c index aebfe546..dfbe12cc 100644 --- a/src/manage.c +++ b/src/manage.c @@ -315,6 +315,12 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, if (get_matching_client(conn, assign->windowclass_title, new) == NULL) continue; + if (assign->floating) { + new->floating = FLOATING_AUTO_ON; + LOG("Assignment matches, putting client into floating mode\n"); + break; + } + LOG("Assignment \"%s\" matches, so putting it on workspace %d\n", assign->windowclass_title, assign->workspace); From 93ff4159c1d41e538bb8a47a960e30fe0ac0e342 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 19 Jun 2009 22:39:03 +0200 Subject: [PATCH 086/129] Bugfix: Obey colspan/rowspan when checking if containers can be snapped to the right/bottom (Thanks Mirko) This fixes ticket #54. --- src/commands.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/commands.c b/src/commands.c index fb3c5422..89e19615 100644 --- a/src/commands.c +++ b/src/commands.c @@ -361,7 +361,7 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction case D_LEFT: /* Snap to the left is actually a move to the left and then a snap right */ if (!cell_exists(container->col - 1, container->row) || - CUR_TABLE[container->col-1][container->row]->currently_focused != NULL) { + CUR_TABLE[container->col-1][container->row]->currently_focused != NULL) { LOG("cannot snap to left - the cell is already used\n"); return; } @@ -372,11 +372,12 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction case D_RIGHT: { /* Check if the cell is used */ int new_col = container->col + container->colspan; - if (!cell_exists(new_col, container->row) || - CUR_TABLE[new_col][container->row]->currently_focused != NULL) { - LOG("cannot snap to right - the cell is already used\n"); - return; - } + for (int i = 0; i < container->rowspan; i++) + if (!cell_exists(new_col, container->row + i) || + CUR_TABLE[new_col][container->row + i]->currently_focused != NULL) { + LOG("cannot snap to right - the cell is already used\n"); + return; + } /* Check if there are other cells with rowspan, which are in our way. * If so, reduce their rowspan. */ @@ -393,7 +394,7 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction } case D_UP: if (!cell_exists(container->col, container->row - 1) || - CUR_TABLE[container->col][container->row-1]->currently_focused != NULL) { + CUR_TABLE[container->col][container->row-1]->currently_focused != NULL) { LOG("cannot snap to top - the cell is already used\n"); return; } @@ -404,11 +405,12 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction case D_DOWN: { LOG("snapping down\n"); int new_row = container->row + container->rowspan; - if (!cell_exists(container->col, new_row) || - CUR_TABLE[container->col][new_row]->currently_focused != NULL) { - LOG("cannot snap down - the cell is already used\n"); - return; - } + for (int i = 0; i < container->colspan; i++) + if (!cell_exists(container->col + i, new_row) || + CUR_TABLE[container->col + i][new_row]->currently_focused != NULL) { + LOG("cannot snap down - the cell is already used\n"); + return; + } for (int i = container->col-1; i >= 0; i--) { LOG("we got cell %d, %d with colspan %d\n", From 589a73c8ea302b223e722c75ee165bb29e5a9847 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 19 Jun 2009 22:48:18 +0200 Subject: [PATCH 087/129] Bugfix: Correctly check for floating mode in the buttonpress handler (Thanks Mirko) --- src/handlers.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers.c b/src/handlers.c index 2d4afc71..803c6f3b 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -309,7 +309,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ LOG("Not handling, Mod1 was pressed and no client found\n"); return 1; } - if (client->floating) { + if (client->floating >= FLOATING_AUTO_ON) { floating_drag_window(conn, client, event); return 1; } From 5c0496a3efe0afc7e1908cd2807d0561e7e1342c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 19 Jun 2009 22:57:19 +0200 Subject: [PATCH 088/129] Bugfix: Make sure floating clients are alwalys above tiling clients (Thanks Mirko) --- src/floating.c | 8 ++++++++ src/manage.c | 8 ++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/floating.c b/src/floating.c index f455739c..f416160a 100644 --- a/src/floating.c +++ b/src/floating.c @@ -78,6 +78,14 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic LOG("Re-inserted the client into the matrix.\n"); con->currently_focused = client; + /* Ensure that it is below all floating clients */ + Client *first_floating = TAILQ_FIRST(&(client->workspace->floating_clients)); + if (first_floating != TAILQ_END(&(client->workspace->floating_clients))) { + LOG("Setting below floating\n"); + uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW }; + xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); + } + render_container(conn, con); xcb_flush(conn); diff --git a/src/manage.c b/src/manage.c index dfbe12cc..827c05ca 100644 --- a/src/manage.c +++ b/src/manage.c @@ -374,12 +374,8 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients); /* Ensure that it is below all floating clients */ - Client *first_floating; - SLIST_FOREACH(first_floating, &(new->container->workspace->focus_stack), focus_clients) - if (first_floating->floating >= FLOATING_AUTO_ON) - break; - - if (first_floating != SLIST_END(&(new->container->workspace->focus_stack))) { + Client *first_floating = TAILQ_FIRST(&(new->workspace->floating_clients)); + if (first_floating != TAILQ_END(&(new->workspace->floating_clients))) { LOG("Setting below floating\n"); uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW }; xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); From 8e19f8dabfea60fcf34a6f5e5966278747121948 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 19 Jun 2009 23:18:43 +0200 Subject: [PATCH 089/129] =?UTF-8?q?floating:=20Don=E2=80=99t=20let=20clien?= =?UTF-8?q?ts=20become=20hidden=20under=20stack=20windows=20or=20fulscreen?= =?UTF-8?q?=20clients=20(Thanks=20Mirko)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/client.h | 8 ++++++++ src/client.c | 19 +++++++++++++++++++ src/floating.c | 8 +------- src/layout.c | 9 +++++++-- src/manage.c | 8 +------- 5 files changed, 36 insertions(+), 16 deletions(-) diff --git a/include/client.h b/include/client.h index 252f3619..068a364a 100644 --- a/include/client.h +++ b/include/client.h @@ -52,4 +52,12 @@ bool client_matches_class_name(Client *client, char *to_class, char *to_title, */ void client_toggle_fullscreen(xcb_connection_t *conn, Client *client); +/** + * Sets the position of the given client in the X stack to the highest (tiling layer is always + * on the same position, so this doesn’t matter) below the first floating client, so that + * floating windows are always on top. + * + */ +void client_set_below_floating(xcb_connection_t *conn, Client *client); + #endif diff --git a/src/client.c b/src/client.c index a7a6e3e8..18126a1a 100644 --- a/src/client.c +++ b/src/client.c @@ -23,6 +23,7 @@ #include "util.h" #include "queue.h" #include "layout.h" +#include "client.h" /* * Removes the given client from the container, either because it will be inserted into another @@ -194,6 +195,8 @@ void client_toggle_fullscreen(xcb_connection_t *conn, Client *client) { /* redecorate_window flushes */ redecorate_window(conn, client); } else { + client_set_below_floating(conn, client); + /* Because the coordinates of the window haven’t changed, it would not be re-configured if we don’t set the following flag */ client->force_reconfigure = true; @@ -204,3 +207,19 @@ void client_toggle_fullscreen(xcb_connection_t *conn, Client *client) { xcb_flush(conn); } + +/* + * Sets the position of the given client in the X stack to the highest (tiling layer is always + * on the same position, so this doesn’t matter) below the first floating client, so that + * floating windows are always on top. + * + */ +void client_set_below_floating(xcb_connection_t *conn, Client *client) { + /* Ensure that it is below all floating clients */ + Client *first_floating = TAILQ_FIRST(&(client->workspace->floating_clients)); + if (first_floating != TAILQ_END(&(client->workspace->floating_clients))) { + LOG("Setting below floating\n"); + uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW }; + xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); + } +} diff --git a/src/floating.c b/src/floating.c index f416160a..f4b22823 100644 --- a/src/floating.c +++ b/src/floating.c @@ -78,13 +78,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic LOG("Re-inserted the client into the matrix.\n"); con->currently_focused = client; - /* Ensure that it is below all floating clients */ - Client *first_floating = TAILQ_FIRST(&(client->workspace->floating_clients)); - if (first_floating != TAILQ_END(&(client->workspace->floating_clients))) { - LOG("Setting below floating\n"); - uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW }; - xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); - } + client_set_below_floating(conn, client); render_container(conn, con); xcb_flush(conn); diff --git a/src/layout.c b/src/layout.c index 426dd027..952f3d60 100644 --- a/src/layout.c +++ b/src/layout.c @@ -327,8 +327,13 @@ void render_container(xcb_connection_t *conn, Container *container) { XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_STACK_MODE; - /* If there is no fullscreen client, we raise the stack window */ - if (container->workspace->fullscreen_client != NULL) { + /* Raise the stack window, but keep it below the first floating client + * and below the fullscreen client (if any) */ + Client *first_floating = TAILQ_FIRST(&(container->workspace->floating_clients)); + if (first_floating != TAILQ_END(&(container->workspace->floating_clients))) { + mask |= XCB_CONFIG_WINDOW_SIBLING; + values[4] = first_floating->frame; + } else if (container->workspace->fullscreen_client != NULL) { mask |= XCB_CONFIG_WINDOW_SIBLING; values[4] = container->workspace->fullscreen_client->frame; } diff --git a/src/manage.c b/src/manage.c index 827c05ca..948e9aba 100644 --- a/src/manage.c +++ b/src/manage.c @@ -373,13 +373,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients); - /* Ensure that it is below all floating clients */ - Client *first_floating = TAILQ_FIRST(&(new->workspace->floating_clients)); - if (first_floating != TAILQ_END(&(new->workspace->floating_clients))) { - LOG("Setting below floating\n"); - uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW }; - xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); - } + client_set_below_floating(conn, new); } if (new->floating >= FLOATING_AUTO_ON) { From 83d3146b65a486da744bd70474181729b0da7270 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 20 Jun 2009 00:31:57 +0200 Subject: [PATCH 090/129] Bugfix: Correctly raise the currently focused client when going into stack mode --- src/handlers.c | 3 +-- src/util.c | 27 +++++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index 803c6f3b..3a422171 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -364,8 +364,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ /* Floating clients can be dragged by grabbing their titlebar */ if (client->floating >= FLOATING_AUTO_ON) { /* Firstly, we raise it. Maybe the user just wanted to raise it without grabbing */ - uint32_t values[] = { XCB_STACK_MODE_ABOVE }; - xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values); + xcb_raise_window(conn, client->frame); xcb_flush(conn); floating_drag_window(conn, client, event); diff --git a/src/util.c b/src/util.c index c0268358..762b9481 100644 --- a/src/util.c +++ b/src/util.c @@ -399,7 +399,7 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode) /* When entering stacking mode, we need to open a window on which we can draw the title bars of the clients, it has height 1 because we don’t bother here with calculating the correct height - it will be adjusted when rendering anyways. */ - Rect rect = {container->x, container->y, container->width, 1 }; + Rect rect = {container->x, container->y, container->width, 1}; uint32_t mask = 0; uint32_t values[2]; @@ -438,8 +438,31 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode) render_layout(conn); - if (container->currently_focused != NULL) + if (container->currently_focused != NULL) { + /* We need to make sure that this client is above *each* of the + * other clients in this container */ + Client *last_focused = get_last_focused_client(conn, container, container->currently_focused); + + CIRCLEQ_FOREACH(client, &(container->clients), clients) { + if (client == container->currently_focused || client == last_focused) + continue; + + LOG("setting %08x below %08x / %08x\n", client->frame, container->currently_focused->frame); + uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW }; + xcb_configure_window(conn, client->frame, + XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); + } + + if (last_focused != NULL) { + LOG("Putting last_focused directly underneath the currently focused\n"); + uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW }; + xcb_configure_window(conn, last_focused->frame, + XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); + } + + set_focus(conn, container->currently_focused, true); + } } /* From ce97e23913b2fbd278b2079821e7c1e6d7b83d43 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 20 Jun 2009 18:05:24 +0200 Subject: [PATCH 091/129] Remove getting colorpixels from the X server, saves code and round-trips. Requires truecolor displays. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If anyone has a serious (!) use for non-truecolor displays and problems with i3’s colors after this commit, please send a mail. --- src/xcb.c | 37 ++++--------------------------------- 1 file changed, 4 insertions(+), 33 deletions(-) diff --git a/src/xcb.c b/src/xcb.c index db38a7bb..4062f648 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -22,7 +22,6 @@ #include "xcb.h" TAILQ_HEAD(cached_fonts_head, Font) cached_fonts = TAILQ_HEAD_INITIALIZER(cached_fonts); -SLIST_HEAD(colorpixel_head, Colorpixel) colorpixels; unsigned int xcb_numlock_mask; /* @@ -74,42 +73,14 @@ i3Font *load_font(xcb_connection_t *conn, const char *pattern) { * */ uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) { - /* Lookup this colorpixel in the cache */ - struct Colorpixel *colorpixel; - SLIST_FOREACH(colorpixel, &(colorpixels), colorpixels) - if (strcmp(colorpixel->hex, hex) == 0) - return colorpixel->pixel; - - #define RGB_8_TO_16(i) (65535 * ((i) & 0xFF) / 255) char strgroups[3][3] = {{hex[1], hex[2], '\0'}, {hex[3], hex[4], '\0'}, {hex[5], hex[6], '\0'}}; - int rgb16[3] = {RGB_8_TO_16(strtol(strgroups[0], NULL, 16)), - RGB_8_TO_16(strtol(strgroups[1], NULL, 16)), - RGB_8_TO_16(strtol(strgroups[2], NULL, 16))}; + uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)), + (strtol(strgroups[1], NULL, 16)), + (strtol(strgroups[2], NULL, 16))}; - xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; - xcb_alloc_color_reply_t *reply; - - reply = xcb_alloc_color_reply(conn, xcb_alloc_color(conn, root_screen->default_colormap, - rgb16[0], rgb16[1], rgb16[2]), NULL); - - if (!reply) { - LOG("Could not allocate color\n"); - exit(1); - } - - uint32_t pixel = reply->pixel; - free(reply); - - /* Store the result in the cache */ - struct Colorpixel *cache_pixel = scalloc(sizeof(struct Colorpixel)); - cache_pixel->hex = sstrdup(hex); - cache_pixel->pixel = pixel; - - SLIST_INSERT_HEAD(&(colorpixels), cache_pixel, colorpixels); - - return pixel; + return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2]; } /* From 6ca9210335f6a35000e3212b8eb613c756b28833 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 21 Jun 2009 13:05:54 +0200 Subject: [PATCH 092/129] =?UTF-8?q?Bugfix:=20Properly=20integrate=20libxcb?= =?UTF-8?q?=E2=80=99s=20event=20loop=20into=20libev.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes a race condition with GIMP (where it configured its window, sent the map request and waited for the window to get mapped, but i3 didn’t get the event until another one was sent (key binding for example)). The new solution is much better as it properly hands off all the work to xcb_poll_for_event. Inspired by awesome, which uses the same mechanism. Thanks. --- src/mainx.c | 76 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/src/mainx.c b/src/mainx.c index ca1bc8ad..a832f098 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -72,31 +72,34 @@ xcb_atom_t atoms[NUM_ATOMS]; int num_screens = 0; /* - * Callback for activity on the connection to the X server + * 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 * */ static void xcb_got_event(EV_P_ struct ev_io *w, int revents) { + /* empty, because xcb_prepare_cb and xcb_check_cb are used */ +} + +/* + * Flush before blocking (and waiting for new events) + * + */ +static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) { + xcb_flush(evenths.c); +} + +/* + * Instead of polling the X connection socket we leave this to + * xcb_poll_for_event() which knows better than we can ever know. + * + */ +static void xcb_check_cb(EV_P_ ev_check *w, int revents) { xcb_generic_event_t *event; - /* When an event is available… */ while ((event = xcb_poll_for_event(evenths.c)) != NULL) { - /* …we handle all events in a row: */ - do { - xcb_event_handle(&evenths, event); - xcb_aux_sync(evenths.c); - free(event); - } while ((event = xcb_poll_for_event(evenths.c))); - - /* Make sure all replies are handled/discarded */ - xcb_aux_sync(evenths.c); - - /* Afterwards, there may be new events available which would - * not trigger the select() (libev) immediately, so we check - * again (and don’t bail out of the loop). */ + xcb_event_handle(&evenths, event); + free(event); } - - /* Make sure all replies are handled/discarded */ - xcb_aux_sync(evenths.c); } int main(int argc, char *argv[], char *env[]) { @@ -190,6 +193,27 @@ int main(int argc, char *argv[], char *env[]) { } /* end of ugliness */ + /* Initialize event loop using libev */ + struct ev_loop *loop = ev_default_loop(0); + if (loop == NULL) + die("Could not initialize libev. Bad LIBEV_FLAGS?\n"); + + struct ev_io *xcb_watcher = scalloc(sizeof(struct ev_io)); + struct ev_check *xcb_check = scalloc(sizeof(struct ev_check)); + struct ev_prepare *xcb_prepare = scalloc(sizeof(struct ev_prepare)); + + ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ); + ev_io_start(loop, xcb_watcher); + + ev_check_init(xcb_check, xcb_check_cb); + ev_check_start(loop, xcb_check); + + ev_prepare_init(xcb_prepare, xcb_prepare_cb); + ev_prepare_start(loop, xcb_prepare); + + /* Grab the server to delay any events until we enter the eventloop */ + xcb_grab_server(conn); + xcb_event_handlers_init(conn, &evenths); /* DEBUG: Trap all events and print them */ @@ -351,20 +375,8 @@ int main(int argc, char *argv[], char *env[]) { c_ws = &workspaces[screen->current_workspace]; } - - /* Initialize event loop using libev */ - struct ev_loop *loop = ev_default_loop(0); - if (loop == NULL) - die("Could not initialize libev. Bad LIBEV_FLAGS?\n"); - - struct ev_io *xcb_watcher = scalloc(sizeof(struct ev_io)); - ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ); - - /* Call the handler to work all events which arrived before the libev-stuff was set up */ - xcb_got_event(NULL, xcb_watcher, 0); - - /* Enter the libev eventloop */ - ev_io_start(loop, xcb_watcher); + /* Ungrab the server to receive events and enter libev’s eventloop */ + xcb_ungrab_server(conn); ev_loop(loop, 0); /* not reached */ From 2751eedae5f378415338f9a7ee989ce12bd7dcb8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 21 Jun 2009 13:12:42 +0200 Subject: [PATCH 093/129] Bugfix: Floating: open windows at their requested position --- src/manage.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/manage.c b/src/manage.c index 948e9aba..bfdd9b4f 100644 --- a/src/manage.c +++ b/src/manage.c @@ -384,8 +384,8 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, new->container = NULL; - new->floating_rect.x = new->rect.x; - new->floating_rect.y = new->rect.y; + new->floating_rect.x = new->rect.x = x; + new->floating_rect.y = new->rect.y = y; LOG("copying size from tiling (%d, %d) size (%d, %d)\n", new->floating_rect.x, new->floating_rect.y, new->floating_rect.width, new->floating_rect.height); From 0e8b3c3401e398f81164cc593cd2cd1717978c7a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 21 Jun 2009 13:18:54 +0200 Subject: [PATCH 094/129] Bugfix: floating: open windows with correct width/height --- src/manage.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/manage.c b/src/manage.c index bfdd9b4f..8cacd2a0 100644 --- a/src/manage.c +++ b/src/manage.c @@ -386,9 +386,13 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, new->floating_rect.x = new->rect.x = x; new->floating_rect.y = new->rect.y = y; - LOG("copying size from tiling (%d, %d) size (%d, %d)\n", + new->rect.width = new->floating_rect.width + 2 + 2; + new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2; + LOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n", new->floating_rect.x, new->floating_rect.y, new->floating_rect.width, new->floating_rect.height); + LOG("outer rect (%d, %d) size (%d, %d)\n", + new->rect.x, new->rect.y, new->rect.width, new->rect.height); /* Make sure it is on top of the other windows */ xcb_raise_window(conn, new->frame); From 8140619d5bfc5ce8324fe4e72c224b39cd7d39fc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 21 Jun 2009 13:29:48 +0200 Subject: [PATCH 095/129] Implement reconfiguration of floating clients --- src/handlers.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/handlers.c b/src/handlers.c index 3a422171..129eca95 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -476,6 +476,30 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure return 1; } + /* Floating clients can be reconfigured */ + if (client->floating >= FLOATING_AUTO_ON) { + i3Font *font = load_font(conn, config.font); + + if (event->value_mask & XCB_CONFIG_WINDOW_X) + client->rect.x = event->x; + if (event->value_mask & XCB_CONFIG_WINDOW_Y) + client->rect.y = event->y; + if (event->value_mask & XCB_CONFIG_WINDOW_WIDTH) + client->rect.width = event->width + 2 + 2; + if (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT) + client->rect.height = event->height + (font->height + 2 + 2) + 2; + + LOG("Accepted new position/size for floating client: (%d, %d) size %d x %d\n", + client->rect.x, client->rect.y, client->rect.width, client->rect.height); + + /* Push the new position/size to X11 */ + reposition_client(conn, client); + resize_client(conn, client); + xcb_flush(conn); + + return 1; + } + if (client->fullscreen) { LOG("Client is in fullscreen mode\n"); From 626e6b2b6f9c37e2daa96ba4542293810399e5cf Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 21 Jun 2009 13:44:14 +0200 Subject: [PATCH 096/129] Bugfix: yuck! FREE() used a wrong check, effectively never free()ing memory --- include/util.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/util.h b/include/util.h index 437411b6..c7209061 100644 --- a/include/util.h +++ b/include/util.h @@ -25,7 +25,7 @@ for (int cols = 0; cols < (workspace)->cols; cols++) \ for (int rows = 0; rows < (workspace)->rows; rows++) #define FREE(pointer) do { \ - if (pointer == NULL) { \ + if (pointer != NULL) { \ free(pointer); \ pointer = NULL; \ } \ From a5489d6546f7ab24fcc33399c62c3388a4c336ea Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 21 Jun 2009 13:44:44 +0200 Subject: [PATCH 097/129] Use a nested event loop which polls and saves motion notify events for later This should speed up resizing/dragging quite a bit, thus fixing ticket #51 --- src/floating.c | 71 +++++++++++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/src/floating.c b/src/floating.c index f4b22823..09b33ba1 100644 --- a/src/floating.c +++ b/src/floating.c @@ -254,42 +254,53 @@ static void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_pres /* Go into our own event loop */ xcb_flush(conn); - xcb_generic_event_t *inside_event; + xcb_generic_event_t *inside_event, *last_motion_notify = NULL; /* I’ve always wanted to have my own eventhandler… */ while ((inside_event = xcb_wait_for_event(conn))) { - /* Same as get_event_handler in xcb */ - int nr = inside_event->response_type; - if (nr == 0) { - /* An error occured */ - handle_event(NULL, conn, inside_event); - free(inside_event); + /* We now handle all events we can get using xcb_poll_for_event */ + do { + /* Same as get_event_handler in xcb */ + int nr = inside_event->response_type; + if (nr == 0) { + /* An error occured */ + handle_event(NULL, conn, inside_event); + free(inside_event); + continue; + } + assert(nr < 256); + nr &= XCB_EVENT_RESPONSE_TYPE_MASK; + assert(nr >= 2); + + switch (nr) { + case XCB_BUTTON_RELEASE: + goto done; + + case XCB_MOTION_NOTIFY: + /* motion_notify events are saved for later */ + FREE(last_motion_notify); + last_motion_notify = inside_event; + + break; + default: + LOG("Passing to original handler\n"); + /* Use original handler */ + xcb_event_handle(&evenths, inside_event); + break; + } + if (last_motion_notify != inside_event) + free(inside_event); + } while ((inside_event = xcb_poll_for_event(conn)) != NULL); + + if (last_motion_notify == NULL) continue; - } - assert(nr < 256); - nr &= XCB_EVENT_RESPONSE_TYPE_MASK; - assert(nr >= 2); - /* Check if we need to escape this loop */ - if (nr == XCB_BUTTON_RELEASE) - break; + new_x = ((xcb_motion_notify_event_t*)last_motion_notify)->root_x; + new_y = ((xcb_motion_notify_event_t*)last_motion_notify)->root_y; - switch (nr) { - case XCB_MOTION_NOTIFY: - new_x = ((xcb_motion_notify_event_t*)inside_event)->root_x; - new_y = ((xcb_motion_notify_event_t*)inside_event)->root_y; - - callback(&old_rect, new_x, new_y); - - break; - default: - LOG("Passing to original handler\n"); - /* Use original handler */ - xcb_event_handle(&evenths, inside_event); - break; - } - free(inside_event); + callback(&old_rect, new_x, new_y); + FREE(last_motion_notify); } - +done: xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); xcb_flush(conn); } From cba36914a8371c9f2b8bdad66195de909989ad78 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 21 Jun 2009 16:14:15 +0200 Subject: [PATCH 098/129] Implement selecting the next tiling/floating window (using "focus") Also update documentation (manpage, userguide). To make the code easier to read/write when checking if a client is floating, introduce client_is_floating(). --- CMDMODE | 7 +++-- docs/userguide | 12 ++++++++- i3.config | 6 ++++- include/client.h | 8 ++++++ man/i3.man | 13 +++++++++ src/client.c | 12 ++++++++- src/commands.c | 69 +++++++++++++++++++++++++++++++++--------------- src/floating.c | 13 ++++----- src/handlers.c | 12 ++++----- src/layout.c | 5 ++-- src/mainx.c | 3 +++ src/manage.c | 6 ++--- src/util.c | 2 +- 13 files changed, 124 insertions(+), 44 deletions(-) diff --git a/CMDMODE b/CMDMODE index 7428729a..7d8f6f23 100644 --- a/CMDMODE +++ b/CMDMODE @@ -16,10 +16,13 @@ snap := cmd := [ ] [ | ] with := { [ ] }+ jump := [ "[/]" | [ ] ] -focus := focus [ ] +focus := focus [ | floating | tiling | ft ] (travels the focus stack backwards the given amount of times (by default 1), so it selects the window which had the focus before you focused the current one when - specifying "focus 1") + specifying "focus 1". + The special values 'floating' (select the next floating window), 'tiling' + (select the next tiling window), 'ft' (if the current window is floating, + select the next tiling window and vice-versa) are also valid) special := [ exec | kill | exit | restart ] input := [ | | | | ] diff --git a/docs/userguide b/docs/userguide index 4ba97ee9..aa7575bc 100644 --- a/docs/userguide +++ b/docs/userguide @@ -300,12 +300,22 @@ the focus stack and jumps to the window you focused before. *Syntax*: -------------- -focus [number] +focus [number] | floating | tilling | ft -------------- Where +number+ by default is 1 meaning that the next client in the focus stack will be selected. +The special values have the following meaning: + +floating:: + The next floating window is selected. +tiling:: + The next tiling window is selected. +ft:: + If the current window is floating, the next tiling window will be selected + and vice-versa. + === Changing colors You can change all colors which i3 uses to draw the window decorations and the diff --git a/i3.config b/i3.config index 7bc3fe80..19f3caee 100644 --- a/i3.config +++ b/i3.config @@ -18,9 +18,13 @@ bind Mod1+43 s # Default (Mod1+e) bind Mod1+26 d -# Toggle tiling/floating of the current window +# Toggle tiling/floating of the current window (Mod1+Shift+Space) bind Mod1+Shift+65 t +# Go into the tiling layer / floating layer, depending on whether +# the current window is tiling / floating (Mod1+t) +bind Mod1+28 focus ft + # Focus (Mod1+j/k/l/;) bind Mod1+44 h bind Mod1+45 j diff --git a/include/client.h b/include/client.h index 068a364a..964dd2ea 100644 --- a/include/client.h +++ b/include/client.h @@ -60,4 +60,12 @@ void client_toggle_fullscreen(xcb_connection_t *conn, Client *client); */ void client_set_below_floating(xcb_connection_t *conn, Client *client); +/** + * Returns true if the client is floating. Makes the code more beatiful, as floating + * is not simply a boolean, but also saves whether the user selected the current state + * or whether it was automatically set. + * + */ +bool client_is_floating(Client *client); + #endif diff --git a/man/i3.man b/man/i3.man index fbe2ac83..c1513ea4 100644 --- a/man/i3.man +++ b/man/i3.man @@ -106,6 +106,12 @@ Enable stacking layout for the current container. Mod1+e:: Enable default layout for the current container. +Mod1+Shift+Space:: +Toggle tiling/floating for the current window. + +Mod1+t:: +Select the first tiling window if the current window is floating and vice-versa. + Mod1+Shift+q:: Kills the current client. @@ -165,6 +171,13 @@ bind Mod1+43 s # Default (Mod1+e) bind Mod1+26 d +# Toggle tiling/floating of the current window (Mod1+Shift+Space) +bind Mod1+Shift+65 t + +# Go into the tiling layer / floating layer, depending on whether +# the current window is tiling / floating (Mod1+t) +bind Mod1+28 focus ft + # Focus (Mod1+j/k/l/;) bind Mod1+44 h bind Mod1+45 j diff --git a/src/client.c b/src/client.c index 18126a1a..5d78d38f 100644 --- a/src/client.c +++ b/src/client.c @@ -187,7 +187,7 @@ void client_toggle_fullscreen(xcb_connection_t *conn, Client *client) { LOG("leaving fullscreen mode\n"); client->fullscreen = false; workspace->fullscreen_client = NULL; - if (client->floating >= FLOATING_AUTO_ON) { + if (client_is_floating(client)) { /* For floating clients it’s enough if we just reconfigure that window (in fact, * re-rendering the layout will not update the client.) */ reposition_client(conn, client); @@ -223,3 +223,13 @@ void client_set_below_floating(xcb_connection_t *conn, Client *client) { xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); } } + +/* + * Returns true if the client is floating. Makes the code more beatiful, as floating + * is not simply a boolean, but also saves whether the user selected the current state + * or whether it was automatically set. + * + */ +bool client_is_floating(Client *client) { + return (client->floating >= FLOATING_AUTO_ON); +} diff --git a/src/commands.c b/src/commands.c index 89e19615..084a388e 100644 --- a/src/commands.c +++ b/src/commands.c @@ -720,29 +720,56 @@ static void jump_to_container(xcb_connection_t *conn, const char *arguments) { * was specified). That is, selects the window you were in before you focused * the current window. * + * The special values 'floating' (select the next floating window), 'tiling' + * (select the next tiling window), 'ft' (if the current window is floating, + * select the next tiling window and vice-versa) are also valid + * */ static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) { /* Start count at -1 to always skip the first element */ int times, count = -1; Client *current; + bool floating_criteria; - if (sscanf(arguments, "%u", ×) != 1) { - LOG("No or invalid argument given (\"%s\"), using default of 1 times\n", arguments); - times = 1; - } - - Workspace *ws = CUR_CELL->workspace; - - SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) { - if (++count < times) { - LOG("Skipping\n"); - continue; + /* Either it’s one of the special values… */ + if (strcasecmp(arguments, "floating") == 0) { + floating_criteria = true; + } else if (strcasecmp(arguments, "tiling") == 0) { + floating_criteria = false; + } else if (strcasecmp(arguments, "ft") == 0) { + Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); + if (last_focused == SLIST_END(&(c_ws->focus_stack))) { + LOG("Cannot select the next floating/tiling client because there is no client at all\n"); + return; } - LOG("Focussing\n"); - set_focus(conn, current, true); - break; + floating_criteria = !client_is_floating(last_focused); + } else { + /* …or a number was specified */ + if (sscanf(arguments, "%u", ×) != 1) { + LOG("No or invalid argument given (\"%s\"), using default of 1 times\n", arguments); + times = 1; + } + + SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients) { + if (++count < times) { + LOG("Skipping\n"); + continue; + } + + LOG("Focussing\n"); + set_focus(conn, current, true); + break; + } + return; } + + /* Select the next client matching the criteria parsed above */ + SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients) + if (client_is_floating(current) == floating_criteria) { + set_focus(conn, current, true); + break; + } } /* @@ -829,7 +856,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { /* Should we travel the focus stack? */ if (STARTS_WITH(command, "focus")) { - const char *arguments = command + strlen("focus"); + const char *arguments = command + strlen("focus "); travel_focus_stack(conn, arguments); return; } @@ -844,7 +871,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { /* Is it just 's' for stacking or 'd' for default? */ if ((command[0] == 's' || command[0] == 'd') && (command[1] == '\0')) { - if (last_focused == NULL || last_focused->floating >= FLOATING_AUTO_ON) { + if (last_focused == NULL || client_is_floating(last_focused)) { LOG("not switching, this is a floating client\n"); return; } @@ -930,7 +957,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { } if (*rest == '\0') { - if (last_focused != NULL && last_focused->floating >= FLOATING_AUTO_ON) + if (last_focused != NULL && client_is_floating(last_focused)) move_floating_window_to_workspace(conn, last_focused, workspace); else move_current_window_to_workspace(conn, workspace); return; @@ -941,8 +968,8 @@ void parse_command(xcb_connection_t *conn, const char *command) { return; } - if (last_focused->floating >= FLOATING_AUTO_ON && - (action != ACTION_FOCUS && action != ACTION_MOVE)) { + if (client_is_floating(last_focused) && + (action != ACTION_FOCUS && action != ACTION_MOVE)) { LOG("Not performing (floating)\n"); return; } @@ -964,7 +991,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { rest++; if (action == ACTION_FOCUS) { - if (last_focused->floating >= FLOATING_AUTO_ON) { + if (client_is_floating(last_focused)) { floating_focus_direction(conn, last_focused, direction); continue; } @@ -973,7 +1000,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { } if (action == ACTION_MOVE) { - if (last_focused->floating >= FLOATING_AUTO_ON) { + if (client_is_floating(last_focused)) { floating_move(conn, last_focused, direction); continue; } diff --git a/src/floating.c b/src/floating.c index 09b33ba1..1664d602 100644 --- a/src/floating.c +++ b/src/floating.c @@ -52,7 +52,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic LOG("This client is already in floating (container == NULL), re-inserting\n"); Client *next_tiling; SLIST_FOREACH(next_tiling, &(client->workspace->focus_stack), focus_clients) - if (next_tiling->floating <= FLOATING_USER_OFF) + if (!client_is_floating(next_tiling)) break; /* If there are no tiling clients on this workspace, there can only be one * container: the first one */ @@ -386,11 +386,12 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace) { /* If we just unmapped all floating windows we should ensure that the focus * is set correctly, that ist, to the first non-floating client in stack */ if (workspace->floating_hidden) - SLIST_FOREACH(client, &(workspace->focus_stack), focus_clients) - if (client->floating <= FLOATING_USER_OFF) { - set_focus(conn, client, true); - return; - } + SLIST_FOREACH(client, &(workspace->focus_stack), focus_clients) { + if (client_is_floating(client)) + continue; + set_focus(conn, client, true); + return; + } xcb_flush(conn); } diff --git a/src/handlers.c b/src/handlers.c index 129eca95..3dbef90d 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -309,7 +309,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ LOG("Not handling, Mod1 was pressed and no client found\n"); return 1; } - if (client->floating >= FLOATING_AUTO_ON) { + if (client_is_floating(client)) { floating_drag_window(conn, client, event); return 1; } @@ -350,7 +350,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ LOG("client. done.\n"); xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); /* Floating clients should be raised on click */ - if (client->floating >= FLOATING_AUTO_ON) + if (client_is_floating(client)) xcb_raise_window(conn, client->frame); xcb_flush(conn); return 1; @@ -362,7 +362,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ LOG("click on titlebar\n"); /* Floating clients can be dragged by grabbing their titlebar */ - if (client->floating >= FLOATING_AUTO_ON) { + if (client_is_floating(client)) { /* Firstly, we raise it. Maybe the user just wanted to raise it without grabbing */ xcb_raise_window(conn, client->frame); xcb_flush(conn); @@ -372,7 +372,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ return 1; } - if (client->floating >= FLOATING_AUTO_ON) + if (client_is_floating(client)) return floating_border_click(conn, client, event); if (event->event_y < 2) { @@ -477,7 +477,7 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure } /* Floating clients can be reconfigured */ - if (client->floating >= FLOATING_AUTO_ON) { + if (client_is_floating(client)) { i3Font *font = load_font(conn, config.font); if (event->value_mask & XCB_CONFIG_WINDOW_X) @@ -598,7 +598,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti /* Only if this is the active container, we need to really change focus */ if ((con->currently_focused != NULL) && ((con == CUR_CELL) || client->fullscreen)) set_focus(conn, con->currently_focused, true); - } else if (client->floating >= FLOATING_AUTO_ON) { + } else if (client_is_floating(client)) { SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); } diff --git a/src/layout.c b/src/layout.c index 952f3d60..2b01f90c 100644 --- a/src/layout.c +++ b/src/layout.c @@ -24,6 +24,7 @@ #include "util.h" #include "xinerama.h" #include "layout.h" +#include "client.h" /* * Updates *destination with new_value and returns true if it was changed or false @@ -107,9 +108,9 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw return; LOG("redecorating child %08x\n", client->child); - if (client->floating >= FLOATING_AUTO_ON || client->container->currently_focused == client) { + if (client_is_floating(client) || client->container->currently_focused == client) { /* Distinguish if the window is currently focused… */ - if (client->floating >= FLOATING_AUTO_ON || CUR_CELL->currently_focused == client) + if (client_is_floating(client) || CUR_CELL->currently_focused == client) color = &(config.client.focused); /* …or if it is the focused window in a not focused container */ else color = &(config.client.focused_inactive); diff --git a/src/mainx.c b/src/mainx.c index a832f098..d95ca465 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -375,6 +375,9 @@ int main(int argc, char *argv[], char *env[]) { c_ws = &workspaces[screen->current_workspace]; } + /* Handle the events which arrived until now */ + xcb_check_cb(NULL, NULL, 0); + /* Ungrab the server to receive events and enter libev’s eventloop */ xcb_ungrab_server(conn); ev_loop(loop, 0); diff --git a/src/manage.c b/src/manage.c index 8cacd2a0..203a66d9 100644 --- a/src/manage.c +++ b/src/manage.c @@ -356,7 +356,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, } else if (!new->dock) { /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */ if (new->container->workspace->fullscreen_client == NULL) { - if (new->floating <= FLOATING_USER_OFF) + if (!client_is_floating(new)) new->container->currently_focused = new; if (new->container == CUR_CELL) xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME); @@ -364,7 +364,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, } /* Insert into the currently active container, if it’s not a dock window */ - if (!new->dock && new->floating <= FLOATING_USER_OFF) { + if (!new->dock && !client_is_floating(new)) { /* Insert after the old active client, if existing. If it does not exist, the container is empty and it does not matter, where we insert it */ if (old_focused != NULL && !old_focused->dock) @@ -376,7 +376,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, client_set_below_floating(conn, new); } - if (new->floating >= FLOATING_AUTO_ON) { + if (client_is_floating(new)) { SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients); /* Add the client to the list of floating clients for its workspace */ diff --git a/src/util.c b/src/util.c index 762b9481..ed9dc49a 100644 --- a/src/util.c +++ b/src/util.c @@ -264,7 +264,7 @@ void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws) { /* To find floating clients, we traverse the focus stack */ SLIST_FOREACH(client, &(u_ws->focus_stack), focus_clients) { - if (client->floating <= FLOATING_USER_OFF) + if (!client_is_floating(client)) continue; xcb_unmap_window(conn, client->frame); From 13b3543ab15923353e4dcbb70a8b3c3a57cf8655 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 21 Jun 2009 16:40:23 +0200 Subject: [PATCH 099/129] Enter feature freeze. Update debian/changelog, add RELEASE-NOTES-3.b (not yet released!) --- RELEASE-NOTES-3.b | 49 +++++++++++++++++++++++++++++++++++++++++++++++ debian/changelog | 8 +++++++- 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 RELEASE-NOTES-3.b diff --git a/RELEASE-NOTES-3.b b/RELEASE-NOTES-3.b new file mode 100644 index 00000000..da1d5fa7 --- /dev/null +++ b/RELEASE-NOTES-3.b @@ -0,0 +1,49 @@ +Release notes for i3 v3.β +----------------------------- + +This is the second version (3.β, transcribed 3.b) of i3. It is considered stable. + +The most important change probably is the implementation of floating clients, +primarily useful for dialog/toolbar/popup/splash windows. When using i3 for +managing floating windows other than the ones mentioned beforehand, please +keep in mind that i3 is a tiling window manager in the first place and thus +you might better use a "traditional" window manager when having to deal a +lot with floating windows. + +Now that you’re warned, let’s have a quick glance at the other new features: + * jumping to other windows by specifying their position or window class/title + * assigning clients to specific workspaces by window class/title + * automatically starting programs (such as i3status + dzen2) + * configurable colors + * variables in configfile + +Furthermore, we now have a user’s guide which should be the first document +you read when new to i3 (apart from the manpage). + +Thanks for this release go out to mist, Atsutane, ch3ka, urs, Moredread, +badboy and all other people who reported bugs/made suggestions. + +A list of changes follows: + + * Bugfix: Correctly handle col-/rowspanned containers when setting focus. + * Bugfix: Correctly handle col-/rowspanned containers when snapping. + * Bugfix: Force reconfiguration of all windows on workspaces which are + re-assigned because a screen was detached. + * Bugfix: Several bugs in resizing table columns fixed. + * Bugfix: Resizing should now work correctly in all cases. + * Bugfix: Correctly re-assign dock windows when workspace is destroyed. + * Bugfix: Correctly handle Mode_switch modifier. + * Bugfix: Don't raise clients in fullscreen mode. + * Bugfix: Re-assign dock windows to different workspaces when a workspace + is detached. + * Bugfix: Fix crash because of workspace-pointer which did not get updated + * Implement jumping to other windows by specifying their position or + window class/title. + * Implement jumping back by using the focus stack. + * Implement autostart (exec-command in configuration file). + * Implement floating. + * Implement automatically assigning clients on specific workspaces. + * Implement variables in configfile. + * Colors are now configurable. + +-- Michael Stapelberg, 2009-06-21 diff --git a/debian/changelog b/debian/changelog index a642bd2c..30477b6d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,21 +1,27 @@ i3-wm (3.b-1) unstable; urgency=low * Bugfix: Correctly handle col-/rowspanned containers when setting focus. + * Bugfix: Correctly handle col-/rowspanned containers when snapping. * Bugfix: Force reconfiguration of all windows on workspaces which are re-assigned because a screen was detached. * Bugfix: Several bugs in resizing table columns fixed. * Bugfix: Resizing should now work correctly in all cases. * Bugfix: Correctly re-assign dock windows when workspace is destroyed. * Bugfix: Correctly handle Mode_switch modifier. + * Bugfix: Don't raise clients in fullscreen mode. + * Bugfix: Re-assign dock windows to different workspaces when a workspace + is detached. + * Bugfix: Fix crash because of workspace-pointer which did not get updated * Implement jumping to other windows by specifying their position or window class/title. * Implement jumping back by using the focus stack. * Implement autostart (exec-command in configuration file). * Implement floating. * Implement automatically assigning clients on specific workspaces. + * Implement variables in configfile. * Colors are now configurable. - -- Michael Stapelberg Sat, 09 May 2009 20:17:58 +0200 + -- Michael Stapelberg Sun, 21 Jun 2009 16:39:35 +0200 i3-wm (3.a-bf2-1) unstable; urgency=low From 183b6d8942dfc90d8d9c9e5dd24923b5af45a017 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 21 Jun 2009 17:05:23 +0200 Subject: [PATCH 100/129] debian: change Priority to extra (makes more sense, plus necessary because xcb is in extra) --- debian/control | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/debian/control b/debian/control index 63997e86..2347df16 100644 --- a/debian/control +++ b/debian/control @@ -1,6 +1,6 @@ Source: i3-wm Section: utils -Priority: optional +Priority: extra Maintainer: Michael Stapelberg DM-Upload-Allowed: yes Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4-1), xmlto, docbook-xml, pkg-config, libev-dev @@ -9,7 +9,7 @@ Homepage: http://i3.zekjur.net/ Package: i3 Architecture: any -Priority: optional +Priority: extra Section: x11 Depends: i3-wm, ${misc:Depends} Recommends: i3lock, dwm-tools @@ -20,7 +20,7 @@ Description: metapackage (i3 window manager, i3lock (screen locker), dwm-tools) Package: i3-wm Architecture: any -Priority: optional +Priority: extra Section: x11 Depends: ${shlibs:Depends}, ${misc:Depends} Provides: x-window-manager From 4f31709b1d339294e77a8fc9aadc565ea24c7106 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 23 Jun 2009 22:42:54 +0200 Subject: [PATCH 101/129] floating: enforce minimum size of 50x20 when resizing (Thanks Mirko) --- src/floating.c | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/src/floating.c b/src/floating.c index 1664d602..fb280dc9 100644 --- a/src/floating.c +++ b/src/floating.c @@ -155,23 +155,44 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre void resize_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) { switch (border) { - case BORDER_RIGHT: - client->rect.width = old_rect->width + (new_x - event->root_x); + case BORDER_RIGHT: { + int new_width = old_rect->width + (new_x - event->root_x); + if ((new_width < 0) || + (new_width < 50 && client->rect.width >= new_width)) + return; + client->rect.width = new_width; break; + } - case BORDER_BOTTOM: + case BORDER_BOTTOM: { + int new_height = old_rect->height + (new_y - event->root_y); + if ((new_height < 0) || + (new_height < 20 && client->rect.height >= new_height)) + return; client->rect.height = old_rect->height + (new_y - event->root_y); break; + } + + case BORDER_TOP: { + int new_height = old_rect->height + (event->root_y - new_y); + if ((new_height < 0) || + (new_height < 20 && client->rect.height >= new_height)) + return; - case BORDER_TOP: client->rect.y = old_rect->y + (new_y - event->root_y); - client->rect.height = old_rect->height + (event->root_y - new_y); + client->rect.height = new_height; break; + } - case BORDER_LEFT: + case BORDER_LEFT: { + int new_width = old_rect->width + (event->root_x - new_x); + if ((new_width < 0) || + (new_width < 50 && client->rect.width >= new_width)) + return; client->rect.x = old_rect->x + (new_x - event->root_x); - client->rect.width = old_rect->width + (event->root_x - new_x); + client->rect.width = new_width; break; + } } /* Push the new position/size to X11 */ From 81e9d8282a7a58d164673b9675d67353a61456f6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 23 Jun 2009 23:10:04 +0200 Subject: [PATCH 102/129] Bugfix: load current_col/current_row from workspace when setting focus (Thanks Mirko) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the case of floating clients which have no container, the values were still the ones from your old workspace, which was a problem if your dimensions didn’t match… --- src/util.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/util.c b/src/util.c index ed9dc49a..9bb42872 100644 --- a/src/util.c +++ b/src/util.c @@ -319,6 +319,9 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) { c_ws->current_row = current_row; c_ws->current_col = current_col; c_ws = client->workspace; + /* Load current_col/current_row if we switch to a client without a container */ + current_col = c_ws->current_col; + current_row = c_ws->current_row; /* Update container */ if (client->container != NULL) { From 0aed552bae7a468ecff2972add7100bd13189df1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 23 Jun 2009 23:17:06 +0200 Subject: [PATCH 103/129] Bugfix: Remove/add floating clients to workspace->floating_clients when moving to other workspaces --- src/commands.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/commands.c b/src/commands.c index 084a388e..8b088793 100644 --- a/src/commands.c +++ b/src/commands.c @@ -452,15 +452,17 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl } } - /* Remove from focus stack */ + /* Remove from focus stack and list of floating clients */ SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); + TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients); if (client->workspace->fullscreen_client == client) client->workspace->fullscreen_client = NULL; - /* Insert into destination focus stack */ + /* Insert into destination focus stack and list of floating clients */ client->workspace = t_ws; SLIST_INSERT_HEAD(&(t_ws->focus_stack), client, focus_clients); + TAILQ_INSERT_TAIL(&(t_ws->floating_clients), client, floating_clients); if (client->fullscreen) t_ws->fullscreen_client = client; From 125faef1f475351352d162feba5058c1af0f8d57 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Jun 2009 00:34:03 +0200 Subject: [PATCH 104/129] Correctly move floating clients to other workspaces on visible screens --- src/commands.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/commands.c b/src/commands.c index 8b088793..b8a28573 100644 --- a/src/commands.c +++ b/src/commands.c @@ -434,7 +434,8 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *client, int workspace) { /* t_ws (to workspace) is just a container pointer to the workspace we’re switching to */ - Workspace *t_ws = &(workspaces[workspace-1]); + Workspace *t_ws = &(workspaces[workspace-1]), + *old_ws = client->workspace; LOG("moving floating\n"); @@ -470,6 +471,19 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl if (t_ws->screen->current_workspace != t_ws->num) { LOG("This workspace is not visible, unmapping\n"); xcb_unmap_window(conn, client->frame); + } else { + /* If this is not the case, we move the window to a workspace + * which is on another screen, so we also need to adjust its + * coordinates. */ + LOG("before x = %d, y = %d\n", client->rect.x, client->rect.y); + uint32_t relative_x = client->rect.x - old_ws->rect.x, + relative_y = client->rect.y - old_ws->rect.y; + LOG("rel_x = %d, rel_y = %d\n", relative_x, relative_y); + client->rect.x = t_ws->rect.x + relative_x; + client->rect.y = t_ws->rect.y + relative_y; + LOG("after x = %d, y = %d\n", client->rect.x, client->rect.y); + reposition_client(conn, client); + xcb_flush(conn); } LOG("done\n"); From 7ed967c96fb392847394d675b3e4052d334a4cea Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Jun 2009 00:35:05 +0200 Subject: [PATCH 105/129] Ensure that the focus is set correctly when moving floating clients --- src/commands.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/commands.c b/src/commands.c index b8a28573..ff682bd5 100644 --- a/src/commands.c +++ b/src/commands.c @@ -489,6 +489,8 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl LOG("done\n"); render_layout(conn); + + set_focus(conn, client, true); } /* From 07bebdf84169621732456c23f07f40421b12e781 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Jun 2009 17:12:12 +0200 Subject: [PATCH 106/129] Correctly re-assign floating clients to the destination workspace when moving --- include/floating.h | 10 +++++++++- src/commands.c | 14 +------------- src/floating.c | 22 ++++++++++++++++++++++ src/layout.c | 15 +++++++++++++++ 4 files changed, 47 insertions(+), 14 deletions(-) diff --git a/include/floating.h b/include/floating.h index edc11a95..a4f8619a 100644 --- a/include/floating.h +++ b/include/floating.h @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * (c) 2009 Michael Stapelberg and contributors + * © 2009 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -22,6 +22,14 @@ */ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic); +/** + * Removes the floating client from its workspace and attaches it to the new workspace. + * This is centralized here because it may happen if you move it via keyboard and + * if you move it using your mouse. + * + */ +void floating_assign_to_workspace(Client *client, Workspace *new_workspace); + /** * Called whenever the user clicks on a border (not the titlebar!) of a floating window. * Determines on which border the user clicked and launches the drag_pointer function diff --git a/src/commands.c b/src/commands.c index ff682bd5..2b4b163e 100644 --- a/src/commands.c +++ b/src/commands.c @@ -453,19 +453,7 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl } } - /* Remove from focus stack and list of floating clients */ - SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); - TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients); - - if (client->workspace->fullscreen_client == client) - client->workspace->fullscreen_client = NULL; - - /* Insert into destination focus stack and list of floating clients */ - client->workspace = t_ws; - SLIST_INSERT_HEAD(&(t_ws->focus_stack), client, focus_clients); - TAILQ_INSERT_TAIL(&(t_ws->floating_clients), client, floating_clients); - if (client->fullscreen) - t_ws->fullscreen_client = client; + floating_assign_to_workspace(client, t_ws); /* If we’re moving it to an invisible screen, we need to unmap it */ if (t_ws->screen->current_workspace != t_ws->num) { diff --git a/src/floating.c b/src/floating.c index fb280dc9..6e5bec52 100644 --- a/src/floating.c +++ b/src/floating.c @@ -140,6 +140,28 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic xcb_flush(conn); } +/* + * Removes the floating client from its workspace and attaches it to the new workspace. + * This is centralized here because it may happen if you move it via keyboard and + * if you move it using your mouse. + * + */ +void floating_assign_to_workspace(Client *client, Workspace *new_workspace) { + /* Remove from focus stack and list of floating clients */ + SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); + TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients); + + if (client->workspace->fullscreen_client == client) + client->workspace->fullscreen_client = NULL; + + /* Insert into destination focus stack and list of floating clients */ + client->workspace = new_workspace; + SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients); + TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients); + if (client->fullscreen) + client->workspace->fullscreen_client = client; + +} /* * Called whenever the user clicks on a border (not the titlebar!) of a floating window. diff --git a/src/layout.c b/src/layout.c index 2b01f90c..e70d0f49 100644 --- a/src/layout.c +++ b/src/layout.c @@ -25,6 +25,7 @@ #include "xinerama.h" #include "layout.h" #include "client.h" +#include "floating.h" /* * Updates *destination with new_value and returns true if it was changed or false @@ -173,10 +174,24 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw * */ void reposition_client(xcb_connection_t *conn, Client *client) { + i3Screen *screen; + LOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y); /* Note: We can use a pointer to client->x like an array of uint32_ts because it is followed by client->y by definition */ xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x)); + + if (!client_is_floating(client)) + return; + + /* If the client is floating, we need to check if we moved it to a different workspace */ + if (client->workspace->screen == (screen = get_screen_containing(client->rect.x, client->rect.y))) + return; + + LOG("Client is on workspace %p with screen %p\n", client->workspace, client->workspace->screen); + LOG("but screen at %d, %d is %p\n", client->rect.x, client->rect.y, screen); + floating_assign_to_workspace(client, &workspaces[screen->current_workspace]); + LOG("fixed that\n"); } /* From ee217523f1a71cfb4c977c734cb64368629a3327 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Jun 2009 17:24:09 +0200 Subject: [PATCH 107/129] Bugfix: resizing: boundary checking (Thanks Mirko) --- src/resize.c | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/resize.c b/src/resize.c index 9c6cbab4..7a20061c 100644 --- a/src/resize.c +++ b/src/resize.c @@ -24,6 +24,8 @@ #include "xcb.h" #include "debug.h" #include "layout.h" +#include "xinerama.h" +#include "config.h" /* * Renders the resize window between the first/second container and resizes @@ -35,6 +37,13 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i int new_position; xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; + i3Screen *screen = get_screen_containing(event->root_x, event->root_y); + if (screen == NULL) { + LOG("BUG: No screen found at this position (%d, %d)\n", event->root_x, event->root_y); + return 1; + } + + LOG("Screen dimensions: (%d, %d) %d x %d\n", screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height); /* FIXME: horizontal resizing causes empty spaces to exist */ if (orientation == O_HORIZONTAL) { @@ -57,9 +66,9 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i Rect helprect; if (orientation == O_VERTICAL) { helprect.x = event->root_x; - helprect.y = 0; + helprect.y = screen->rect.y; helprect.width = 2; - helprect.height = root_screen->height_in_pixels; + helprect.height = screen->rect.height; new_position = event->root_x; } else { helprect.x = 0; @@ -70,7 +79,7 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i } mask = XCB_CW_BACK_PIXEL; - values[0] = get_colorpixel(conn, "#4c7899"); + values[0] = config.client.focused.border; mask |= XCB_CW_OVERRIDE_REDIRECT; values[1] = 1; @@ -107,10 +116,16 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i break; switch (nr) { - case XCB_MOTION_NOTIFY: + case XCB_MOTION_NOTIFY: { + xcb_motion_notify_event_t *motion_event = (xcb_motion_notify_event_t*)inside_event; if (orientation == O_VERTICAL) { - values[0] = new_position = ((xcb_motion_notify_event_t*)inside_event)->root_x; - xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_X, values); + if (motion_event->root_x < (screen->rect.x + screen->rect.width) && + motion_event->root_x > screen->rect.x) { + values[0] = new_position = ((xcb_motion_notify_event_t*)inside_event)->root_x; + xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_X, values); + } else { + LOG("Ignoring new position\n"); + } } else { values[0] = new_position = ((xcb_motion_notify_event_t*)inside_event)->root_y; xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_Y, values); @@ -118,6 +133,7 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i xcb_flush(conn); break; + } default: LOG("Passing to original handler\n"); /* Use original handler */ From 607b1d071e7b9900a6e5d8618f60881b8b0d2744 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Jun 2009 17:40:34 +0200 Subject: [PATCH 108/129] Use drag_pointer from floating.c for the resize handler --- include/floating.h | 17 ++++++++++++ src/floating.c | 23 ++++++---------- src/resize.c | 69 ++++++++++++++-------------------------------- 3 files changed, 46 insertions(+), 63 deletions(-) diff --git a/include/floating.h b/include/floating.h index a4f8619a..b0c0b7cc 100644 --- a/include/floating.h +++ b/include/floating.h @@ -11,6 +11,12 @@ #ifndef _FLOATING_H #define _FLOATING_H +/** Callback for dragging */ +typedef void(*callback_t)(Rect*, uint32_t, uint32_t); + +/** On which border was the dragging initiated? */ +typedef enum { BORDER_LEFT, BORDER_RIGHT, BORDER_TOP, BORDER_BOTTOM} border_t; + /** * Enters floating mode for the given client. * Correctly takes care of the position/size (separately stored for tiling/floating mode) @@ -67,4 +73,15 @@ void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_ */ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace); +/** + * This function grabs your pointer and lets you drag stuff around (borders). + * Every time you move your mouse, an XCB_MOTION_NOTIFY event will be received + * and the given callback will be called with the parameters specified (client, + * border on which the click originally was), the original rect of the client, + * the event and the new coordinates (x, y). + * + */ +void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event, + xcb_window_t confine_to, border_t border, callback_t callback); + #endif diff --git a/src/floating.c b/src/floating.c index 6e5bec52..5deda47a 100644 --- a/src/floating.c +++ b/src/floating.c @@ -25,15 +25,7 @@ #include "debug.h" #include "layout.h" #include "client.h" - -/* On which border was the dragging initiated? */ -typedef enum { BORDER_LEFT, BORDER_RIGHT, BORDER_TOP, BORDER_BOTTOM} border_t; -/* Callback for dragging */ -typedef void(*callback_t)(Rect*, uint32_t, uint32_t); - -/* Forward definitions */ -static void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event, - border_t border, callback_t callback); +#include "floating.h" /* * Toggles floating mode for the given client. @@ -238,7 +230,7 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre LOG("border = %d\n", border); - drag_pointer(conn, client, event, border, resize_callback); + drag_pointer(conn, client, event, XCB_NONE, border, resize_callback); return 1; } @@ -264,7 +256,7 @@ void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_pre } - drag_pointer(conn, client, event, BORDER_TOP /* irrelevant */, drag_window_callback); + drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback); } /* @@ -275,12 +267,13 @@ void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_pre * the event and the new coordinates (x, y). * */ -static void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event, - border_t border, callback_t callback) { +void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event, + xcb_window_t confine_to, border_t border, callback_t callback) { xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; uint32_t new_x, new_y; Rect old_rect; - memcpy(&old_rect, &(client->rect), sizeof(Rect)); + if (client != NULL) + memcpy(&old_rect, &(client->rect), sizeof(Rect)); /* Grab the pointer */ /* TODO: returncode */ @@ -290,7 +283,7 @@ static void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_pres XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, /* which events to let through */ XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */ XCB_GRAB_MODE_ASYNC, /* keyboard mode */ - XCB_NONE, /* confine_to = in which window should the cursor stay */ + confine_to, /* confine_to = in which window should the cursor stay */ XCB_NONE, /* don’t display a special cursor */ XCB_CURRENT_TIME); diff --git a/src/resize.c b/src/resize.c index 7a20061c..3a639e2e 100644 --- a/src/resize.c +++ b/src/resize.c @@ -26,6 +26,7 @@ #include "layout.h" #include "xinerama.h" #include "config.h" +#include "floating.h" /* * Renders the resize window between the first/second container and resizes @@ -35,7 +36,6 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, int second, resize_orientation_t orientation, xcb_button_press_event_t *event) { int new_position; - xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; i3Screen *screen = get_screen_containing(event->root_x, event->root_y); if (screen == NULL) { @@ -91,59 +91,32 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin); - xcb_grab_pointer(conn, false, root, XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, - XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, grabwin, XCB_NONE, XCB_CURRENT_TIME); - xcb_flush(conn); - xcb_generic_event_t *inside_event; - /* I’ve always wanted to have my own eventhandler… */ - while ((inside_event = xcb_wait_for_event(conn))) { - /* Same as get_event_handler in xcb */ - int nr = inside_event->response_type; - if (nr == 0) { - /* An error occured */ - handle_event(NULL, conn, inside_event); - free(inside_event); - continue; + void resize_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) { + LOG("new x = %d, y = %d\n", new_x, new_y); + if (orientation == O_VERTICAL) { + /* Check if the new coordinates are within screen boundaries */ + if (new_x > (screen->rect.x + screen->rect.width) || + new_x < screen->rect.x) + return; + + values[0] = new_position = new_x; + xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_X, values); + } else { + if (new_y > (screen->rect.y + screen->rect.height) || + new_y < screen->rect.y) + return; + + values[0] = new_position = new_y; + xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_Y, values); } - assert(nr < 256); - nr &= XCB_EVENT_RESPONSE_TYPE_MASK; - assert(nr >= 2); - /* Check if we need to escape this loop */ - if (nr == XCB_BUTTON_RELEASE) - break; - - switch (nr) { - case XCB_MOTION_NOTIFY: { - xcb_motion_notify_event_t *motion_event = (xcb_motion_notify_event_t*)inside_event; - if (orientation == O_VERTICAL) { - if (motion_event->root_x < (screen->rect.x + screen->rect.width) && - motion_event->root_x > screen->rect.x) { - values[0] = new_position = ((xcb_motion_notify_event_t*)inside_event)->root_x; - xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_X, values); - } else { - LOG("Ignoring new position\n"); - } - } else { - values[0] = new_position = ((xcb_motion_notify_event_t*)inside_event)->root_y; - xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_Y, values); - } - - xcb_flush(conn); - break; - } - default: - LOG("Passing to original handler\n"); - /* Use original handler */ - xcb_event_handle(&evenths, inside_event); - break; - } - free(inside_event); + xcb_flush(conn); } - xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); + drag_pointer(conn, NULL, event, grabwin, BORDER_TOP, resize_callback); + xcb_destroy_window(conn, helpwin); xcb_destroy_window(conn, grabwin); xcb_flush(conn); From 7773a3e5dd2a39ef60aaeeb5a254b4701bc0a268 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Jun 2009 18:31:43 +0200 Subject: [PATCH 109/129] Ensure a minimum size of 25px when resizing windows --- src/resize.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/resize.c b/src/resize.c index 3a639e2e..3d634420 100644 --- a/src/resize.c +++ b/src/resize.c @@ -97,15 +97,15 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i LOG("new x = %d, y = %d\n", new_x, new_y); if (orientation == O_VERTICAL) { /* Check if the new coordinates are within screen boundaries */ - if (new_x > (screen->rect.x + screen->rect.width) || - new_x < screen->rect.x) + if (new_x > (screen->rect.x + screen->rect.width - 25) || + new_x < (screen->rect.x + 25)) return; values[0] = new_position = new_x; xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_X, values); } else { - if (new_y > (screen->rect.y + screen->rect.height) || - new_y < screen->rect.y) + if (new_y > (screen->rect.y + screen->rect.height - 25) || + new_y < (screen->rect.y + 25)) return; values[0] = new_position = new_y; From 61b1279f67c4cc37b131e36e6c8927ed88354747 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Jun 2009 19:05:33 +0200 Subject: [PATCH 110/129] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20invalidate=20?= =?UTF-8?q?container=20pointer=20and=20access=20it=20when=20unmapping?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/handlers.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index 3dbef90d..ae1a5d7f 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -619,8 +619,9 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti table_remove(&by_parent, client->frame); if (client->container != NULL) { - cleanup_table(conn, client->container->workspace); - fix_colrowspan(conn, client->container->workspace); + Workspace *workspace = client->container->workspace; + cleanup_table(conn, workspace); + fix_colrowspan(conn, workspace); } /* Let’s see how many clients there are left on the workspace to delete it if it’s empty */ From 675d28dd545aff901a11fcfabe448369f408a4ce Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Jun 2009 19:22:09 +0200 Subject: [PATCH 111/129] Bugfix: Check screen for NULL, free client memory correctly (Thanks dirkson) --- src/handlers.c | 5 ++--- src/layout.c | 5 +++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index ae1a5d7f..3930e1e7 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -578,9 +578,6 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti client = table_remove(&by_child, event->window); - if (client->name != NULL) - free(client->name); - /* Clients without a container are either floating or dock windows */ if (client->container != NULL) { Container *con = client->container; @@ -644,6 +641,8 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti client->workspace->screen = NULL; } + FREE(client->window_class); + FREE(client->name); free(client); render_layout(conn); diff --git a/src/layout.c b/src/layout.c index e70d0f49..4f132aea 100644 --- a/src/layout.c +++ b/src/layout.c @@ -188,6 +188,11 @@ void reposition_client(xcb_connection_t *conn, Client *client) { if (client->workspace->screen == (screen = get_screen_containing(client->rect.x, client->rect.y))) return; + if (screen == NULL) { + LOG("Boundary checking disabled, no screen found for (%d, %d)\n", client->rect.x, client->rect.y); + return; + } + LOG("Client is on workspace %p with screen %p\n", client->workspace, client->workspace->screen); LOG("but screen at %d, %d is %p\n", client->rect.x, client->rect.y, screen); floating_assign_to_workspace(client, &workspaces[screen->current_workspace]); From 44fe2e9cf277ea450776c19111de12c9154e69f4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Jun 2009 19:49:07 +0200 Subject: [PATCH 112/129] userguide: Explain containers (Thanks dirkson) --- docs/userguide | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/userguide b/docs/userguide index aa7575bc..8cde2939 100644 --- a/docs/userguide +++ b/docs/userguide @@ -25,7 +25,9 @@ the moment, you have exactly one column and one row which leaves you with one cell. In this cell, there is a container in which your newly opened terminal is. If you now open another terminal, you still have only one cell. However, the -container has both of your terminals. +container has both of your terminals. So, a container is just a group of clients +with a specific layout. You can resize containers as they directly resemble +columns/rows of the layout table. image:two_terminals.png[Two terminals] From 9a931079fdc94fa54b5d1f50d59f0a409dc0a0fd Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Jun 2009 20:14:41 +0200 Subject: [PATCH 113/129] debugging: Recommend "backtrace full" --- docs/debugging | 32 +++----------------------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/docs/debugging b/docs/debugging index d33f6467..d32329d4 100644 --- a/docs/debugging +++ b/docs/debugging @@ -84,35 +84,9 @@ gdb $(which i3) core.i3.3849 Then, generate a backtrace using: ---------- -backtrace ---------- - -Also, getting an overview of the local variables might help: ------------ -info locals ------------ - -If your backtrace looks like this: ---------------------------------------------------------------------------------------------------- -(gdb) backtrace -#0 0x041b1a01 in vfprintf () from /lib/libc.so.6 -#1 0x041b2f80 in vprintf () from /lib/libc.so.6 -#2 0x080555de in slog (fmt=0x8059ba0 "%s:%s:%d - Name should change to \"%s\"\n") at src/util.c:60 -#3 0x0804fa73 in handle_windowname_change_legacy (data=0x0, conn=0x42da908, - state=0 '\0', window=8389918, atom=39, prop=0x4303f90) at src/handlers.c:752 -#4 0x0406cace in ?? () from /usr/lib/libxcb-property.so.1 -#5 0x00000000 in ?? () ---------------------------------------------------------------------------------------------------- - -you need to find the first frame which actually belongs to i3 code. You can easily spot them, as -their filename starts with src/ and has a line number. In this case, frame 2 would be the correct -frame, so before getting the local variables, switch to frame 2: - ------------ -frame 2 -info locals ------------ +-------------- +backtrace full +-------------- == Sending bugreports/debugging on IRC From 62c8d58d828c98806b1a95fa10a23c7cf19fd4d8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Jun 2009 20:31:00 +0200 Subject: [PATCH 114/129] Implement the configuration option floating_modifier and document it --- docs/userguide | 20 ++++++++++++++++++++ i3.config | 3 +++ include/config.h | 6 +++++- src/config.c | 24 ++++++++++++++++++++++++ src/handlers.c | 8 +++++--- 5 files changed, 57 insertions(+), 4 deletions(-) diff --git a/docs/userguide b/docs/userguide index 8cde2939..36c30d62 100644 --- a/docs/userguide +++ b/docs/userguide @@ -206,6 +206,26 @@ umlauts or special characters 'and' having some comfortably reachable key bindings. For example, when typing, capslock+1 or capslock+2 for switching workspaces is totally convenient. Try it :-). +=== The floating modifier + +To move floating windows with your mouse, you can either grab their titlebar +or configure the so called floating modifier which you can then press and +click anywhere in the window itself. The most common setup is to configure +it as the same one you use for managing windows (Mod1 for example). Afterwards, +you can press Mod1, click into a window using your left mouse button and drag +it to the position you want it at. + +*Syntax*: +-------------------------------- +floating_modifier +-------------------------------- + +*Examples*: +-------------------------------- +floating_modifier Mod1 +-------------------------------- + + === Variables As you learned in the previous section about keyboard bindings, you will have diff --git a/i3.config b/i3.config index 19f3caee..816b364c 100644 --- a/i3.config +++ b/i3.config @@ -9,6 +9,9 @@ terminal /usr/bin/urxvt # ISO 10646 = Unicode font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 +# Use Mouse+Mod1 to drag floating windows to their wanted position +floating_modifier Mod1 + # Fullscreen (Mod1+f) bind Mod1+41 f diff --git a/include/config.h b/include/config.h index 330b194c..1e85d471 100644 --- a/include/config.h +++ b/include/config.h @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * (c) 2009 Michael Stapelberg and contributors + * © 2009 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -37,6 +37,10 @@ struct Config { const char *terminal; const char *font; + /** 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 { struct Colortriple focused; diff --git a/src/config.c b/src/config.c index 197fd4e1..d55c2a75 100644 --- a/src/config.c +++ b/src/config.c @@ -215,6 +215,30 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath) continue; } + if (strcasecmp(key, "floating_modifier") == 0) { + char *walk = value; + uint32_t modifiers = 0; + + while (*walk != '\0') { + /* Need to check for Mod1-5, Ctrl, Shift, Mode_switch */ + CHECK_MODIFIER(SHIFT); + CHECK_MODIFIER(CONTROL); + CHECK_MODIFIER(MODE_SWITCH); + CHECK_MODIFIER(MOD1); + CHECK_MODIFIER(MOD2); + CHECK_MODIFIER(MOD3); + CHECK_MODIFIER(MOD4); + CHECK_MODIFIER(MOD5); + + /* No modifier found? Then we’re done with this step */ + break; + } + + LOG("Floating modifiers = %d\n", modifiers); + config.floating_modifier = modifiers; + continue; + } + /* assign window class[/window title] → workspace */ if (strcasecmp(key, "assign") == 0) { LOG("assign: \"%s\"\n", value); diff --git a/src/handlers.c b/src/handlers.c index 3930e1e7..11c8e576 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -302,9 +302,11 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ client = table_get(&by_parent, event->event); border_click = true; } - /* See if this was a click with Mod1. If so, we need to move around - * the client if it was floating. if not, we just process as usual. */ - if ((event->state & XCB_MOD_MASK_1) != 0) { + /* See if this was a click with the configured modifier. If so, we need + * to move around the client if it was floating. if not, we just process + * as usual. */ + if (config.floating_modifier != 0 && + (event->state & config.floating_modifier) != 0) { if (client == NULL) { LOG("Not handling, Mod1 was pressed and no client found\n"); return 1; From 1ac3ef431c1a6ec8f50354896626d74da0db9cde Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Jun 2009 20:50:21 +0200 Subject: [PATCH 115/129] hacking-howto: document resizing, add links to git documentation --- docs/hacking-howto | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/docs/hacking-howto b/docs/hacking-howto index 69f5a167..ceff2a84 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -225,7 +225,7 @@ chosen for those: * ``conn'' is the xcb_connection_t * ``event'' is the event of the particular type * ``container'' names a container - * ``client'' names a client, for example when using a `CIRCLEQ_FOREACH` + * ``client'' names a client, for example when using a +CIRCLEQ_FOREACH+ == Startup (src/mainx.c, main()) @@ -373,9 +373,26 @@ when rendering. === Resizing containers -By clicking and dragging the border of a container, you can resize it freely. +By clicking and dragging the border of a container, you can resize the whole column +(respectively row) which this container is in. This is necessary to keep the table +layout working and consistent. -TODO +Currently, only vertical resizing is implemented. + +The resizing works similarly to the resizing of floating windows or movement of floating +windows: + +* A new, invisible window with the size of the root window is created (+grabwin+) +* Another window, 2px width and as high as your screen (or vice versa for horizontal + resizing) is created. Its background color is the border color and it is only + there to signalize the user how big the container will be (it creates the impression + of dragging the border out of the container). +* The +drag_pointer+ function of +src/floating.c+ is called to grab the pointer and + enter an own event loop which will pass all events (expose events) but motion notify + events. This function then calls the specified callback (+resize_callback+) which + does some boundary checking and moves the helper window. As soon as the mouse + button is released, this loop will be terminated. +* The new width_factor for each involved column (respectively row) will be calculated. == User commands / commandmode (src/commands.c) @@ -408,7 +425,8 @@ direction to move a window respectively or snap. == Using git / sending patches -For a short introduction into using git, see TODO. +For a short introduction into using git, see http://www.spheredev.org/wiki/Git_for_the_lazy +or, for more documentation, see http://git-scm.com/documentation When you want to send a patch because you fixed a bug or implemented a cool feature (please talk to us before working on features to see whether they are maybe already implemented, not From 777eedf0f4cc5a1524021aaf0164d1830d939c45 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Jun 2009 21:10:12 +0200 Subject: [PATCH 116/129] Bugfix: Fix selecting col-/rowspanned containers --- src/commands.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/commands.c b/src/commands.c index 2b4b163e..f15921b4 100644 --- a/src/commands.c +++ b/src/commands.c @@ -105,6 +105,19 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t t_ws = &(workspaces[screen->current_workspace]); new_row = (direction == D_UP ? (t_ws->rows - 1) : 0); } + + LOG("new_col = %d, new_row = %d\n", new_col, new_row); + if (t_ws->table[new_col][new_row]->currently_focused == NULL) { + LOG("Cell empty, checking for colspanned client above...\n"); + for (int cols = 0; cols < new_col; cols += t_ws->table[cols][new_row]->colspan) { + if (new_col > (cols + (t_ws->table[cols][new_row]->colspan - 1))) + continue; + + new_col = cols; + break; + } + LOG("Fixed it to new col %d\n", new_col); + } } else if (direction == D_LEFT || direction == D_RIGHT) { if (direction == D_RIGHT && cell_exists(current_col+1, current_row)) new_col = current_col + t_ws->table[current_col][current_row]->colspan; @@ -132,6 +145,19 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t t_ws = &(workspaces[screen->current_workspace]); new_col = (direction == D_LEFT ? (t_ws->cols - 1) : 0); } + + LOG("new_col = %d, new_row = %d\n", new_col, new_row); + if (t_ws->table[new_col][new_row]->currently_focused == NULL) { + LOG("Cell empty, checking for rowspanned client above...\n"); + for (int rows = 0; rows < new_row; rows += t_ws->table[new_col][rows]->rowspan) { + if (new_row > (rows + (t_ws->table[new_col][rows]->rowspan - 1))) + continue; + + new_row = rows; + break; + } + LOG("Fixed it to new row %d\n", new_row); + } } else { LOG("direction unhandled\n"); return; From d1592a06a7624b3b113671c8a2a70d842a69f548 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Jun 2009 21:21:35 +0200 Subject: [PATCH 117/129] debian: include docs/*.png in /usr/share/docs/i3-wm --- debian/i3-wm.docs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/debian/i3-wm.docs b/debian/i3-wm.docs index c909b4b8..6372ffe9 100644 --- a/debian/i3-wm.docs +++ b/debian/i3-wm.docs @@ -1,3 +1,8 @@ docs/debugging.html docs/hacking-howto.html docs/userguide.html +docs/bigpicture.png +docs/single_terminal.png +docs/snapping.png +docs/two_columns.png +docs/two_terminals.png From c662b33e47729feca90e56d929338d5fb9abd7a0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Jun 2009 21:25:53 +0200 Subject: [PATCH 118/129] debian: add recommendation for i3status --- debian/control | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/debian/control b/debian/control index 2347df16..bd37b892 100644 --- a/debian/control +++ b/debian/control @@ -12,11 +12,13 @@ Architecture: any Priority: extra Section: x11 Depends: i3-wm, ${misc:Depends} -Recommends: i3lock, dwm-tools -Description: metapackage (i3 window manager, i3lock (screen locker), dwm-tools) +Recommends: i3lock, dwm-tools, i3status +Description: metapackage (i3 window manager, screen locker, menu, statusbar) This metapackage installs the i3 window manager (i3-wm), the i3lock screen - locker (slightly improved version of slock) and dwm-tools which contains dmenu. - These are all the tools you need to use the i3 window manager efficiently. + locker (slightly improved version of slock), dwm-tools which contains dmenu + and i3status, which displays useful information about your system in + combination with dzen2. These are all the tools you need to use the i3 window + manager efficiently. Package: i3-wm Architecture: any From b16ab02cc7014359940f4248775f82a06f08b0a5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Jun 2009 23:50:04 +0200 Subject: [PATCH 119/129] Bugfix: Correctly initialize screens when Xinerama is disabled (Thanks Moredread) --- src/xinerama.c | 54 +++++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/src/xinerama.c b/src/xinerama.c index 818df00a..f1becf8c 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -100,6 +100,31 @@ i3Screen *get_screen_most(direction_t direction) { return candidate; } +static void initialize_screen(xcb_connection_t *conn, i3Screen *screen, Workspace *workspace) { + i3Font *font = load_font(conn, config.font); + + workspace->screen = screen; + screen->current_workspace = workspace->num; + + /* Create a bar for each screen */ + Rect bar_rect = {screen->rect.x, + screen->rect.height - (font->height + 6), + screen->rect.x + screen->rect.width, + font->height + 6}; + uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; + uint32_t values[] = {1, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS}; + screen->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values); + screen->bargc = xcb_generate_id(conn); + xcb_create_gc(conn, screen->bargc, screen->bar, 0, 0); + + SLIST_INIT(&(screen->dock_clients)); + + /* Copy dimensions */ + memcpy(&(workspace->rect), &(screen->rect), sizeof(Rect)); + LOG("that is virtual screen at %d x %d with %d x %d\n", + screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height); +} + /* * Fills virtual_screens with exactly one screen with width/height of the whole X server. * @@ -114,6 +139,10 @@ static void disable_xinerama(xcb_connection_t *conn) { s->rect.width = root_screen->width_in_pixels; s->rect.height = root_screen->height_in_pixels; + num_screens = 1; + s->num = 0; + initialize_screen(conn, s, &(workspaces[0])); + TAILQ_INSERT_TAIL(virtual_screens, s, screens); xinerama_enabled = false; @@ -170,31 +199,6 @@ static void query_screens(xcb_connection_t *conn, struct screens_head *screenlis } } -static void initialize_screen(xcb_connection_t *conn, i3Screen *screen, Workspace *workspace) { - i3Font *font = load_font(conn, config.font); - - workspace->screen = screen; - screen->current_workspace = workspace->num; - - /* Create a bar for each screen */ - Rect bar_rect = {screen->rect.x, - screen->rect.height - (font->height + 6), - screen->rect.x + screen->rect.width, - font->height + 6}; - uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; - uint32_t values[] = {1, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS}; - screen->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values); - screen->bargc = xcb_generate_id(conn); - xcb_create_gc(conn, screen->bargc, screen->bar, 0, 0); - - SLIST_INIT(&(screen->dock_clients)); - - /* Copy dimensions */ - memcpy(&(workspace->rect), &(screen->rect), sizeof(Rect)); - LOG("that is virtual screen at %d x %d with %d x %d\n", - screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height); -} - /* * We have just established a connection to the X server and need the initial Xinerama * information to setup workspaces for each screen. From aa2f20ce26e929bb78bb8804e4b7f50753cce3d4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 25 Jun 2009 13:31:58 +0200 Subject: [PATCH 120/129] Bugfix: Correctly handle floating windows when unmapping, fix focus bug when moving --- src/commands.c | 7 +++++-- src/handlers.c | 7 ++----- src/util.c | 3 +++ 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/commands.c b/src/commands.c index f15921b4..507fdf6d 100644 --- a/src/commands.c +++ b/src/commands.c @@ -481,8 +481,10 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl floating_assign_to_workspace(client, t_ws); + bool target_invisible = t_ws->screen->current_workspace != t_ws->num; + /* If we’re moving it to an invisible screen, we need to unmap it */ - if (t_ws->screen->current_workspace != t_ws->num) { + if (target_invisible) { LOG("This workspace is not visible, unmapping\n"); xcb_unmap_window(conn, client->frame); } else { @@ -504,7 +506,8 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl render_layout(conn); - set_focus(conn, client, true); + if (!target_invisible) + set_focus(conn, client, true); } /* diff --git a/src/handlers.c b/src/handlers.c index 11c8e576..383207f8 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -598,6 +598,8 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti if ((con->currently_focused != NULL) && ((con == CUR_CELL) || client->fullscreen)) set_focus(conn, con->currently_focused, true); } else if (client_is_floating(client)) { + LOG("Removing from floating clients\n"); + TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients); SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); } @@ -606,11 +608,6 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti SLIST_REMOVE(&(client->workspace->screen->dock_clients), client, Client, dock_clients); } - if (client->floating) { - LOG("Removing from floating clients\n"); - TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients); - } - LOG("child of 0x%08x.\n", client->frame); xcb_reparent_window(conn, client->child, root, 0, 0); xcb_destroy_window(conn, client->frame); diff --git a/src/util.c b/src/util.c index 9bb42872..bccb9941 100644 --- a/src/util.c +++ b/src/util.c @@ -258,6 +258,7 @@ void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws) { int unmapped_clients = 0; FOR_TABLE(u_ws) CIRCLEQ_FOREACH(client, &(u_ws->table[cols][rows]->clients), clients) { + LOG("unmapping normal client %p / %p / %p\n", client, client->frame, client->child); xcb_unmap_window(conn, client->frame); unmapped_clients++; } @@ -267,6 +268,8 @@ void unmap_workspace(xcb_connection_t *conn, Workspace *u_ws) { if (!client_is_floating(client)) continue; + LOG("unmapping floating client %p / %p / %p\n", client, client->frame, client->child); + xcb_unmap_window(conn, client->frame); unmapped_clients++; } From 8659419ef66fe64fd45dcf086262b7020fa438b4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 25 Jun 2009 13:46:47 +0200 Subject: [PATCH 121/129] Bugfix: Correctly handle moving fullscreen client onto another screen (Thanks dirkson) --- include/client.h | 7 +++ src/client.c | 117 ++++++++++++++++++++++++++--------------------- src/commands.c | 15 ++++-- 3 files changed, 85 insertions(+), 54 deletions(-) diff --git a/include/client.h b/include/client.h index 964dd2ea..85a41531 100644 --- a/include/client.h +++ b/include/client.h @@ -44,6 +44,13 @@ void client_kill(xcb_connection_t *conn, Client *window); bool client_matches_class_name(Client *client, char *to_class, char *to_title, char *to_title_ucs, int to_title_ucs_len); +/** + * Enters fullscreen mode for the given client. This is called by toggle_fullscreen + * and when moving a fullscreen client to another screen. + * + */ +void client_enter_fullscreen(xcb_connection_t *conn, Client *client); + /** * Toggles fullscreen mode for the given client. It updates the data structures and * reconfigures (= resizes/moves) the client and its frame to the full size of the diff --git a/src/client.c b/src/client.c index 5d78d38f..c3a80c36 100644 --- a/src/client.c +++ b/src/client.c @@ -136,6 +136,53 @@ bool client_matches_class_name(Client *client, char *to_class, char *to_title, return true; } +/* + * Enters fullscreen mode for the given client. This is called by toggle_fullscreen + * and when moving a fullscreen client to another screen. + * + */ +void client_enter_fullscreen(xcb_connection_t *conn, Client *client) { + Workspace *workspace = client->workspace; + + if (workspace->fullscreen_client != NULL) { + LOG("Not entering fullscreen mode, there already is a fullscreen client.\n"); + return; + } + + client->fullscreen = true; + workspace->fullscreen_client = client; + LOG("Entering fullscreen mode...\n"); + /* We just entered fullscreen mode, let’s configure the window */ + uint32_t mask = XCB_CONFIG_WINDOW_X | + XCB_CONFIG_WINDOW_Y | + XCB_CONFIG_WINDOW_WIDTH | + XCB_CONFIG_WINDOW_HEIGHT; + uint32_t values[4] = {workspace->rect.x, + workspace->rect.y, + workspace->rect.width, + workspace->rect.height}; + + LOG("child itself will be at %dx%d with size %dx%d\n", + values[0], values[1], values[2], values[3]); + + xcb_configure_window(conn, client->frame, mask, values); + + /* Child’s coordinates are relative to the parent (=frame) */ + values[0] = 0; + values[1] = 0; + xcb_configure_window(conn, client->child, mask, values); + + /* Raise the window */ + values[0] = XCB_STACK_MODE_ABOVE; + xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values); + + Rect child_rect = workspace->rect; + child_rect.x = child_rect.y = 0; + fake_configure_notify(conn, child_rect, client->child); + + xcb_flush(conn); +} + /* * Toggles fullscreen mode for the given client. It updates the data structures and * reconfigures (= resizes/moves) the client and its frame to the full size of the @@ -149,60 +196,28 @@ void client_toggle_fullscreen(xcb_connection_t *conn, Client *client) { Workspace *workspace = client->workspace; if (!client->fullscreen) { - if (workspace->fullscreen_client != NULL) { - LOG("Not entering fullscreen mode, there already is a fullscreen client.\n"); - return; - } - client->fullscreen = true; - workspace->fullscreen_client = client; - LOG("Entering fullscreen mode...\n"); - /* We just entered fullscreen mode, let’s configure the window */ - uint32_t mask = XCB_CONFIG_WINDOW_X | - XCB_CONFIG_WINDOW_Y | - XCB_CONFIG_WINDOW_WIDTH | - XCB_CONFIG_WINDOW_HEIGHT; - uint32_t values[4] = {workspace->rect.x, - workspace->rect.y, - workspace->rect.width, - workspace->rect.height}; + client_enter_fullscreen(conn, client); + return; + } - LOG("child itself will be at %dx%d with size %dx%d\n", - values[0], values[1], values[2], values[3]); - - xcb_configure_window(conn, client->frame, mask, values); - - /* Child’s coordinates are relative to the parent (=frame) */ - values[0] = 0; - values[1] = 0; - xcb_configure_window(conn, client->child, mask, values); - - /* Raise the window */ - values[0] = XCB_STACK_MODE_ABOVE; - xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values); - - Rect child_rect = workspace->rect; - child_rect.x = child_rect.y = 0; - fake_configure_notify(conn, child_rect, client->child); + LOG("leaving fullscreen mode\n"); + client->fullscreen = false; + workspace->fullscreen_client = NULL; + if (client_is_floating(client)) { + /* For floating clients it’s enough if we just reconfigure that window (in fact, + * re-rendering the layout will not update the client.) */ + reposition_client(conn, client); + resize_client(conn, client); + /* redecorate_window flushes */ + redecorate_window(conn, client); } else { - LOG("leaving fullscreen mode\n"); - client->fullscreen = false; - workspace->fullscreen_client = NULL; - if (client_is_floating(client)) { - /* For floating clients it’s enough if we just reconfigure that window (in fact, - * re-rendering the layout will not update the client.) */ - reposition_client(conn, client); - resize_client(conn, client); - /* redecorate_window flushes */ - redecorate_window(conn, client); - } else { - client_set_below_floating(conn, client); + client_set_below_floating(conn, client); - /* Because the coordinates of the window haven’t changed, it would not be - re-configured if we don’t set the following flag */ - client->force_reconfigure = true; - /* We left fullscreen mode, redraw the whole layout to ensure enternotify events are disabled */ - render_layout(conn); - } + /* Because the coordinates of the window haven’t changed, it would not be + re-configured if we don’t set the following flag */ + client->force_reconfigure = true; + /* We left fullscreen mode, redraw the whole layout to ensure enternotify events are disabled */ + render_layout(conn); } xcb_flush(conn); diff --git a/src/commands.c b/src/commands.c index 507fdf6d..e0935525 100644 --- a/src/commands.c +++ b/src/commands.c @@ -25,6 +25,7 @@ #include "xinerama.h" #include "client.h" #include "floating.h" +#include "xcb.h" bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) { /* If this container is empty, we’re done */ @@ -559,8 +560,6 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa CIRCLEQ_INSERT_TAIL(&(to_container->clients), current_client, clients); SLIST_INSERT_HEAD(&(to_container->workspace->focus_stack), current_client, focus_clients); - if (current_client->fullscreen) - t_ws->fullscreen_client = current_client; LOG("Moved.\n"); current_client->container = to_container; @@ -568,16 +567,26 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa container->currently_focused = to_focus; to_container->currently_focused = current_client; + bool target_invisible = (to_container->workspace->screen->current_workspace != to_container->workspace->num); + /* If we’re moving it to an invisible screen, we need to unmap it */ - if (to_container->workspace->screen->current_workspace != to_container->workspace->num) { + if (target_invisible) { LOG("This workspace is not visible, unmapping\n"); xcb_unmap_window(conn, current_client->frame); + } else { + if (current_client->fullscreen) { + LOG("Calling client_enter_fullscreen again\n"); + client_enter_fullscreen(conn, current_client); + } } /* delete all empty columns/rows */ cleanup_table(conn, container->workspace); render_layout(conn); + + if (!target_invisible) + set_focus(conn, current_client, true); } /* From c2f01fdd84ccb4a86e9596329663853f2635db0b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 25 Jun 2009 22:40:13 +0200 Subject: [PATCH 122/129] manpage: Document setting background color (Thanks Moredread) --- man/i3.man | 3 +++ 1 file changed, 3 insertions(+) diff --git a/man/i3.man b/man/i3.man index c1513ea4..43102164 100644 --- a/man/i3.man +++ b/man/i3.man @@ -246,6 +246,9 @@ export LC_TELEPHONE=de_DE.UTF-8 export LC_MEASUREMENT=de_DE.UTF-8 export LC_IDENTIFICATION=de_DE.UTF-8 +# Set background color +xsetroot -solid "#333333" + # Enable core dumps in case something goes wrong ulimit -c unlimited From 27a418f4544b667400f8ecafaf6a33c33bda4007 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Jun 2009 04:15:21 +0200 Subject: [PATCH 123/129] Bugfix: Correctly remove fullscreen floating clients when unmapping (Thanks Volker) --- src/handlers.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index 383207f8..209422d4 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -580,14 +580,14 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti client = table_remove(&by_child, event->window); + /* If this was the fullscreen client, we need to unset it */ + if (client->fullscreen) + client->workspace->fullscreen_client = NULL; + /* Clients without a container are either floating or dock windows */ if (client->container != NULL) { Container *con = client->container; - /* If this was the fullscreen client, we need to unset it */ - if (client->fullscreen) - con->workspace->fullscreen_client = NULL; - /* Remove the client from the list of clients */ client_remove_from_container(conn, client, con, true); From 8f87c212be86d7f88e215a3796a04e51c592bb7e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Jun 2009 04:21:30 +0200 Subject: [PATCH 124/129] Bugfix: When moving, first check boundaries, then check for col-/rowspan (Thanks Mirko) --- src/commands.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/commands.c b/src/commands.c index e0935525..509e3e26 100644 --- a/src/commands.c +++ b/src/commands.c @@ -60,10 +60,17 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t int new_row = current_row, new_col = current_col; - Container *container = CUR_CELL; Workspace *t_ws = c_ws; + /* Makes sure new_col and new_row are within bounds of the new workspace */ + void check_colrow_boundaries() { + if (new_col >= t_ws->cols) + new_col = (t_ws->cols - 1); + if (new_row >= t_ws->rows) + new_row = (t_ws->rows - 1); + } + /* There always is a container. If not, current_col or current_row is wrong */ assert(container != NULL); @@ -107,6 +114,8 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t new_row = (direction == D_UP ? (t_ws->rows - 1) : 0); } + check_colrow_boundaries(); + LOG("new_col = %d, new_row = %d\n", new_col, new_row); if (t_ws->table[new_col][new_row]->currently_focused == NULL) { LOG("Cell empty, checking for colspanned client above...\n"); @@ -147,6 +156,8 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t new_col = (direction == D_LEFT ? (t_ws->cols - 1) : 0); } + check_colrow_boundaries(); + LOG("new_col = %d, new_row = %d\n", new_col, new_row); if (t_ws->table[new_col][new_row]->currently_focused == NULL) { LOG("Cell empty, checking for rowspanned client above...\n"); @@ -164,11 +175,7 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t return; } - /* Bounds checking */ - if (new_col >= t_ws->cols) - new_col = (t_ws->cols - 1); - if (new_row >= t_ws->rows) - new_row = (t_ws->rows - 1); + check_colrow_boundaries(); if (t_ws->table[new_col][new_row]->currently_focused != NULL) set_focus(conn, t_ws->table[new_col][new_row]->currently_focused, true); From 319f6d669f062dc4aa9d84b770dd248179a82e29 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Jun 2009 04:25:51 +0200 Subject: [PATCH 125/129] manpage: document killing of windows better (Thanks Moredread) --- man/i3.man | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/man/i3.man b/man/i3.man index 43102164..cf592c62 100644 --- a/man/i3.man +++ b/man/i3.man @@ -113,7 +113,10 @@ Mod1+t:: Select the first tiling window if the current window is floating and vice-versa. Mod1+Shift+q:: -Kills the current client. +Kills the current window. This is equivalent to "clicking on the close button", meaning a polite +request to the application to close this window. For example, Firefox will save its session +upon such a request. If the application does not support that, the window will be killed and +it depends on the application what happens. Mod1+Shift+r:: Restarts i3 in place (without losing any windows, but the layout). @@ -262,6 +265,14 @@ exec /usr/bin/i3 >> ~/.i3/logfile There is still lot of work to do. Please check our bugtracker for up-to-date information about tasks which are still not finished. +== SEE ALSO + +You should have a copy of the userguide (featuring nice screenshots/graphics which is why this +is not integrated into this manpage), the debugging guide and the "how to hack" guide. If you +are building from source, run +make -C docs+. + +You can also access these documents online at http://i3.zekjur.net/ + == AUTHOR Michael Stapelberg and contributors From ab48d714cf013f92c2cbac7a89d0c061f00cc86e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Jun 2009 04:34:36 +0200 Subject: [PATCH 126/129] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20lose=20focus?= =?UTF-8?q?=20when=20putting=20windows=20into=20floating?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/commands.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/commands.c b/src/commands.c index 509e3e26..275f8f96 100644 --- a/src/commands.c +++ b/src/commands.c @@ -975,7 +975,11 @@ void parse_command(xcb_connection_t *conn, const char *command) { fix_colrowspan(conn, last_focused->workspace); render_workspace(conn, last_focused->workspace->screen, last_focused->workspace); - xcb_flush(conn); + + /* Re-focus the client because cleanup_table sets the focus to the last + * focused client inside a container only. */ + set_focus(conn, last_focused, true); + return; } From dd053c254b7767e39f60855e51deb811a6f4f8c3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Jun 2009 04:39:59 +0200 Subject: [PATCH 127/129] Bugfix: When a window is fullscreen, put new windows after that one in focus stack (Thanks Volker) --- src/manage.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/manage.c b/src/manage.c index 203a66d9..d4641dbc 100644 --- a/src/manage.c +++ b/src/manage.c @@ -371,7 +371,9 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients); else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients); - SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients); + if (new->container->workspace->fullscreen_client != NULL) + SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients); + else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients); client_set_below_floating(conn, new); } From a1c26fa72fb5896d5b47345750cd35fa84a63290 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Jun 2009 04:42:58 +0200 Subject: [PATCH 128/129] debian: update changelog --- RELEASE-NOTES-3.b | 2 ++ debian/changelog | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES-3.b b/RELEASE-NOTES-3.b index da1d5fa7..43220a4d 100644 --- a/RELEASE-NOTES-3.b +++ b/RELEASE-NOTES-3.b @@ -37,6 +37,8 @@ A list of changes follows: * Bugfix: Re-assign dock windows to different workspaces when a workspace is detached. * Bugfix: Fix crash because of workspace-pointer which did not get updated + * Bugfix: Correctly initialize screen when Xinerama is disabled. + * Bugfix: Fullscreen window movement and focus problems fixed * Implement jumping to other windows by specifying their position or window class/title. * Implement jumping back by using the focus stack. diff --git a/debian/changelog b/debian/changelog index 30477b6d..17ece2ba 100644 --- a/debian/changelog +++ b/debian/changelog @@ -12,6 +12,8 @@ i3-wm (3.b-1) unstable; urgency=low * Bugfix: Re-assign dock windows to different workspaces when a workspace is detached. * Bugfix: Fix crash because of workspace-pointer which did not get updated + * Bugfix: Correctly initialize screen when Xinerama is disabled. + * Bugfix: Fullscreen window movement and focus problems fixed * Implement jumping to other windows by specifying their position or window class/title. * Implement jumping back by using the focus stack. @@ -21,7 +23,7 @@ i3-wm (3.b-1) unstable; urgency=low * Implement variables in configfile. * Colors are now configurable. - -- Michael Stapelberg Sun, 21 Jun 2009 16:39:35 +0200 + -- Michael Stapelberg Fri, 26 Jun 2009 04:42:23 +0200 i3-wm (3.a-bf2-1) unstable; urgency=low From aaccc0e62c696e59fa3e6f9e649f439086a01094 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Jun 2009 12:14:20 +0200 Subject: [PATCH 129/129] Bugfix: Correctly check coordinates for resizing floating windows (Thanks Mirko) --- src/floating.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/floating.c b/src/floating.c index 5deda47a..70112b57 100644 --- a/src/floating.c +++ b/src/floating.c @@ -221,7 +221,7 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre border = BORDER_BOTTOM; else if (event->event_x <= 2) border = BORDER_LEFT; - else if (event->event_x > 2) + else if (event->event_x >= (client->rect.width - 2)) border = BORDER_RIGHT; else { LOG("Not on any border, not doing anything.\n");