From 5b51c8c6f0735d93949c4faa4be3632c0a66c67e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 17 Jul 2009 18:32:40 +0200 Subject: [PATCH 01/35] optimization: Render on pixmaps and copy the result on Stack_Wins This should speed up the rendering of Stack_Wins with many window decorations and it should considerably reduce flicker. --- include/data.h | 18 +++++++++++++++++- include/i3.h | 1 + include/xcb.h | 9 +++++++++ src/layout.c | 8 +++++++- src/mainx.c | 7 ++++++- src/util.c | 11 +++++++---- src/xcb.c | 33 +++++++++++++++++++++++++++++++++ 7 files changed, 80 insertions(+), 7 deletions(-) diff --git a/include/data.h b/include/data.h index 2776d506..3288cb8d 100644 --- a/include/data.h +++ b/include/data.h @@ -101,6 +101,22 @@ struct Colorpixel { SLIST_ENTRY(Colorpixel) colorpixels; }; +struct Cached_Pixmap { + xcb_pixmap_t id; + + /* We’re going to paint on it, so a graphics context will be needed */ + xcb_gcontext_t gc; + + /* The rect with which the pixmap was created */ + Rect rect; + + /* The rect of the object to which this pixmap belongs. Necessary to + * find out when we need to re-create the pixmap. */ + Rect *referred_rect; + + xcb_drawable_t referred_drawable; +}; + /** * Contains data for the windows needed to draw the titlebars on in stacking * mode @@ -108,7 +124,7 @@ struct Colorpixel { */ struct Stack_Window { xcb_window_t window; - xcb_gcontext_t gc; + struct Cached_Pixmap pixmap; Rect rect; /** Backpointer to the container this stack window is in */ diff --git a/include/i3.h b/include/i3.h index ccf7a495..a489b42f 100644 --- a/include/i3.h +++ b/include/i3.h @@ -30,6 +30,7 @@ extern TAILQ_HEAD(assignments_head, Assignment) assignments; extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins; extern xcb_event_handlers_t evenths; extern int num_screens; +extern uint8_t root_depth; extern xcb_atom_t atoms[NUM_ATOMS]; #endif diff --git a/include/xcb.h b/include/xcb.h index de74bab6..b540a5cc 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -143,4 +143,13 @@ void xcb_get_numlock_mask(xcb_connection_t *conn); */ void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window); +/** + * + * Prepares the given Cached_Pixmap for usage (checks whether the size of the + * object this pixmap is related to (e.g. a window) has changed and re-creates + * the pixmap if so). + * + */ +void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap); + #endif diff --git a/src/layout.c b/src/layout.c index 4f132aea..89c561ea 100644 --- a/src/layout.c +++ b/src/layout.c @@ -362,6 +362,9 @@ void render_container(xcb_connection_t *conn, Container *container) { xcb_configure_window(conn, stack_win->window, mask, values); } + /* Prepare the pixmap for usage */ + cached_pixmap_prepare(conn, &(stack_win->pixmap)); + /* Render the decorations of all clients */ CIRCLEQ_FOREACH(client, &(container->clients), clients) { /* If the client is in fullscreen mode, it does not get reconfigured */ @@ -384,9 +387,12 @@ void render_container(xcb_connection_t *conn, Container *container) { client->force_reconfigure = false; - decorate_window(conn, client, stack_win->window, stack_win->gc, + decorate_window(conn, client, stack_win->pixmap.id, stack_win->pixmap.gc, current_client++ * decoration_height); } + + xcb_copy_area(conn, stack_win->pixmap.id, stack_win->window, stack_win->pixmap.gc, + 0, 0, 0, 0, stack_win->rect.width, stack_win->rect.height); } } diff --git a/src/mainx.c b/src/mainx.c index d95ca465..6061fd8d 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -71,6 +71,9 @@ xcb_atom_t atoms[NUM_ATOMS]; int num_screens = 0; +/* The depth of the root screen (used e.g. for creating new pixmaps later) */ +uint8_t root_depth; + /* * 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 @@ -261,7 +264,9 @@ int main(int argc, char *argv[], char *env[]) { xcb_property_set_handler(&prophs, WM_NORMAL_HINTS, UINT_MAX, handle_normal_hints, NULL); /* Get the root window and set the event mask */ - root = xcb_aux_get_screen(conn, screens)->root; + xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens); + root = root_screen->root; + root_depth = root_screen->root_depth; uint32_t mask = XCB_CW_EVENT_MASK; uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | diff --git a/src/util.c b/src/util.c index 3a3890f1..02ce5496 100644 --- a/src/util.c +++ b/src/util.c @@ -385,7 +385,8 @@ void leave_stack_mode(xcb_connection_t *conn, Container *container) { SLIST_REMOVE(&stack_wins, stack_win, Stack_Window, stack_windows); - xcb_free_gc(conn, stack_win->gc); + xcb_free_gc(conn, stack_win->pixmap.gc); + xcb_free_pixmap(conn, stack_win->pixmap.id); xcb_destroy_window(conn, stack_win->window); stack_win->rect.width = -1; @@ -423,9 +424,11 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode) struct Stack_Window *stack_win = &(container->stack_win); stack_win->window = create_window(conn, rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values); - /* Generate a graphics context for the titlebar */ - stack_win->gc = xcb_generate_id(conn); - xcb_create_gc(conn, stack_win->gc, stack_win->window, 0, 0); + /* Initialize the entry for our cached pixmap. It will be + * created as soon as it’s needed (see cached_pixmap_prepare). */ + memset(&(stack_win->pixmap), 0, sizeof(struct Cached_Pixmap)); + stack_win->pixmap.referred_rect = &stack_win->rect; + stack_win->pixmap.referred_drawable = stack_win->window; stack_win->container = container; diff --git a/src/xcb.c b/src/xcb.c index 4062f648..4ad69db1 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -18,6 +18,7 @@ #include #include +#include "i3.h" #include "util.h" #include "xcb.h" @@ -259,3 +260,35 @@ 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); } + +/* + * + * Prepares the given Cached_Pixmap for usage (checks whether the size of the + * object this pixmap is related to (e.g. a window) has changed and re-creates + * the pixmap if so). + * + */ +void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap) { + LOG("preparing pixmap\n"); + + /* If the Rect did not change, the pixmap does not need to be recreated */ + if (memcmp(&(pixmap->rect), pixmap->referred_rect, sizeof(Rect)) == 0) + return; + + memcpy(&(pixmap->rect), pixmap->referred_rect, sizeof(Rect)); + + if (pixmap->id == 0 || pixmap->gc == 0) { + LOG("Creating new pixmap...\n"); + pixmap->id = xcb_generate_id(conn); + pixmap->gc = xcb_generate_id(conn); + } else { + LOG("Re-creating this pixmap...\n"); + xcb_free_gc(conn, pixmap->gc); + xcb_free_pixmap(conn, pixmap->id); + } + + xcb_create_pixmap(conn, root_depth, pixmap->id, + pixmap->referred_drawable, pixmap->rect.width, pixmap->rect.height); + + xcb_create_gc(conn, pixmap->gc, pixmap->id, 0, 0); +} From 87494107b33aecd9529fa8bb3cbb16637e7819bc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 21 Jul 2009 15:49:08 +0200 Subject: [PATCH 02/35] Bugfix: Correctly redecorate clients when changing focus (Thanks msi) When moving your cursor from one tiling window to another tiling window via a floating client, the old tiling window was not re- decorated correctly --- src/util.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/util.c b/src/util.c index 02ce5496..26b339a9 100644 --- a/src/util.c +++ b/src/util.c @@ -367,6 +367,22 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) { if ((old_client != NULL) && (old_client != client) && !old_client->dock) redecorate_window(conn, old_client); + /* If the last client was a floating client, we need to go to the next + * tiling client in stack and re-decorate it. */ + if (client_is_floating(old_client)) { + LOG("Coming from floating client, searching next tiling...\n"); + Client *current; + SLIST_FOREACH(current, &(client->workspace->focus_stack), focus_clients) { + if (client_is_floating(current)) + continue; + + LOG("Found window: %p / child %p\n", current->frame, current->child); + redecorate_window(conn, current); + break; + } + + } + SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients); From b893ec9987b26d4854e451466b1e504a5e88c8e7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 21 Jul 2009 15:59:11 +0200 Subject: [PATCH 03/35] Bugfix: Repeatedly try to find screens if none are available (Thanks mxf) When rotating your screens (xrandr --output LVDS1 --rotate right), sometimes the X server returned no screens which lead to an exit(1) of i3. Now, i3 tries to find screens for up to 5 seconds and only quits afterwards. --- src/xinerama.c | 78 ++++++++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/src/xinerama.c b/src/xinerama.c index f1becf8c..2bb8b298 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -156,46 +157,55 @@ static void disable_xinerama(xcb_connection_t *conn) { static void query_screens(xcb_connection_t *conn, struct screens_head *screenlist) { xcb_xinerama_query_screens_reply_t *reply; xcb_xinerama_screen_info_t *screen_info; + time_t before_trying = time(NULL); - reply = xcb_xinerama_query_screens_reply(conn, xcb_xinerama_query_screens_unchecked(conn), NULL); - if (!reply) { - LOG("Couldn't get Xinerama screens\n"); - return; - } - screen_info = xcb_xinerama_query_screens_screen_info(reply); - int screens = xcb_xinerama_query_screens_screen_info_length(reply); - num_screens = 0; + /* Try repeatedly to find screens (there might be short timeframes in + * which the X server does not return any screens, such as when rotating + * screens), but not longer than 5 seconds (strictly speaking, only four + * seconds of trying are guaranteed due to the 1-second-resolution) */ + while ((time(NULL) - before_trying) < 5) { + reply = xcb_xinerama_query_screens_reply(conn, xcb_xinerama_query_screens_unchecked(conn), NULL); + if (!reply) { + LOG("Couldn't get Xinerama screens\n"); + return; + } + screen_info = xcb_xinerama_query_screens_screen_info(reply); + int screens = xcb_xinerama_query_screens_screen_info_length(reply); + num_screens = 0; - for (int screen = 0; screen < screens; screen++) { - i3Screen *s = get_screen_at(screen_info[screen].x_org, screen_info[screen].y_org, screenlist); - if (s!= NULL) { - /* This screen already exists. We use the littlest screen so that the user - can always see the complete workspace */ - s->rect.width = min(s->rect.width, screen_info[screen].width); - s->rect.height = min(s->rect.height, screen_info[screen].height); - } else { - s = calloc(sizeof(i3Screen), 1); - s->rect.x = screen_info[screen].x_org; - s->rect.y = screen_info[screen].y_org; - s->rect.width = screen_info[screen].width; - s->rect.height = screen_info[screen].height; - /* We always treat the screen at 0x0 as the primary screen */ - if (s->rect.x == 0 && s->rect.y == 0) - TAILQ_INSERT_HEAD(screenlist, s, screens); - else TAILQ_INSERT_TAIL(screenlist, s, screens); - num_screens++; + for (int screen = 0; screen < screens; screen++) { + i3Screen *s = get_screen_at(screen_info[screen].x_org, screen_info[screen].y_org, screenlist); + if (s!= NULL) { + /* This screen already exists. We use the littlest screen so that the user + can always see the complete workspace */ + s->rect.width = min(s->rect.width, screen_info[screen].width); + s->rect.height = min(s->rect.height, screen_info[screen].height); + } else { + s = calloc(sizeof(i3Screen), 1); + s->rect.x = screen_info[screen].x_org; + s->rect.y = screen_info[screen].y_org; + s->rect.width = screen_info[screen].width; + s->rect.height = screen_info[screen].height; + /* We always treat the screen at 0x0 as the primary screen */ + if (s->rect.x == 0 && s->rect.y == 0) + TAILQ_INSERT_HEAD(screenlist, s, screens); + else TAILQ_INSERT_TAIL(screenlist, s, screens); + num_screens++; + } + + LOG("found Xinerama screen: %d x %d at %d x %d\n", + screen_info[screen].width, screen_info[screen].height, + screen_info[screen].x_org, screen_info[screen].y_org); } - LOG("found Xinerama screen: %d x %d at %d x %d\n", - screen_info[screen].width, screen_info[screen].height, - screen_info[screen].x_org, screen_info[screen].y_org); - } + free(reply); - free(reply); + if (num_screens == 0) { + LOG("No screens found. This is weird. Trying again...\n"); + continue; + } - if (num_screens == 0) { - LOG("No screens found. This is weird.\n"); - exit(1); + break; } } From 008a2665c19ca5cbd5f4cd54b2cdc6c4b8ee719d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 21 Jul 2009 16:05:43 +0200 Subject: [PATCH 04/35] Bugfix: Strip trailing whitespace when parsing assignments (Thanks bapt) --- src/config.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/config.c b/src/config.c index d55c2a75..2c2ba045 100644 --- a/src/config.c +++ b/src/config.c @@ -260,6 +260,10 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath) *end = '\0'; } + /* Strip trailing whitespace */ + while (strlen(value) > 0 && value[strlen(value)-1] == ' ') + value[strlen(value)-1] = '\0'; + /* The target is the last argument separated by a space */ if ((target = strrchr(value, ' ')) == NULL) die("Malformed assignment, couldn't find target\n"); From ffcc8bbc3ad5ad2a9cce23a06ee8409a77e99134 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 21 Jul 2009 16:43:20 +0200 Subject: [PATCH 05/35] Implement putting clients into floating mode at a specific workspace This changes syntax of the assign command a bit. Old configurations will continue to work. See the userguide. --- docs/userguide | 14 ++++++++------ include/data.h | 7 ++++++- src/config.c | 24 ++++++++++++++++++++---- src/manage.c | 6 ++++-- 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/docs/userguide b/docs/userguide index 36c30d62..05593cd5 100644 --- a/docs/userguide +++ b/docs/userguide @@ -259,13 +259,14 @@ 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. +You can prefix or suffix workspaces with a +~+ to specify that matching clients +should be put into floating mode. If you specify only a +~+, the client will +not be put onto any workspace, but will be set floating on the current one. *Syntax*: ----------------------------------------------------- -assign ["]window class[/window title]["] [→] workspace ----------------------------------------------------- +------------------------------------------------------------ +assign ["]window class[/window title]["] [→] [~ | workspace] +------------------------------------------------------------ *Examples*: ---------------------- @@ -273,7 +274,8 @@ assign urxvt 2 assign urxvt → 2 assign "urxvt" → 2 assign "urxvt/VIM" → 3 -assign "gecko" → ~ +assign "gecko" → ~4 +assign "xv/MPlayer" → ~ ---------------------- === Automatically starting applications on startup diff --git a/include/data.h b/include/data.h index 3288cb8d..3ed8d65b 100644 --- a/include/data.h +++ b/include/data.h @@ -250,7 +250,12 @@ struct Assignment { /** floating is true if this was an assignment to the special * workspace "~". Matching clients will be put into floating mode * automatically. */ - bool floating; + enum { + ASSIGN_FLOATING_NO, /* don’t float, but put on a workspace */ + ASSIGN_FLOATING_ONLY, /* float, but don’t assign on a workspace */ + ASSIGN_FLOATING /* float and put on a workspace */ + } floating; + /** The number of the workspace to assign to. */ int workspace; TAILQ_ENTRY(Assignment) assignments; diff --git a/src/config.c b/src/config.c index 2c2ba045..3fe6d067 100644 --- a/src/config.c +++ b/src/config.c @@ -269,17 +269,33 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath) die("Malformed assignment, couldn't find target\n"); target++; - if (*target != '~' && (atoi(target) < 1 || atoi(target) > 10)) + if (strchr(target, '~') == NULL && (atoi(target) < 1 || atoi(target) > 10)) die("Malformed assignment, invalid workspace number\n"); LOG("assignment parsed: \"%s\" to \"%s\"\n", class_title, target); struct Assignment *new = scalloc(sizeof(struct Assignment)); new->windowclass_title = class_title; - if (*target == '~') - new->floating = true; - else new->workspace = atoi(target); + if (strchr(target, '~') != NULL) + new->floating = ASSIGN_FLOATING_ONLY; + + while (*target == '~') + target++; + + if (atoi(target) >= 1 && atoi(target) <= 10) { + if (new->floating == ASSIGN_FLOATING_ONLY) + new->floating = ASSIGN_FLOATING; + new->workspace = atoi(target); + } TAILQ_INSERT_TAIL(&assignments, new, assignments); + + LOG("Assignment loaded: \"%s\":\n", class_title); + if (new->floating != ASSIGN_FLOATING_ONLY) + LOG(" to workspace %d\n", new->workspace); + + if (new->floating != ASSIGN_FLOATING_NO) + LOG(" will be floating\n"); + continue; } diff --git a/src/manage.c b/src/manage.c index c1a79e7f..f7630eb0 100644 --- a/src/manage.c +++ b/src/manage.c @@ -311,10 +311,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) { + if (assign->floating == ASSIGN_FLOATING_ONLY || + assign->floating == ASSIGN_FLOATING) { new->floating = FLOATING_AUTO_ON; LOG("Assignment matches, putting client into floating mode\n"); - break; + if (assign->floating == ASSIGN_FLOATING_ONLY) + break; } LOG("Assignment \"%s\" matches, so putting it on workspace %d\n", From 3e262913e83b3813b5cf95c562d6dd2b78bd3d9a Mon Sep 17 00:00:00 2001 From: Bapt Date: Tue, 21 Jul 2009 21:32:29 +0200 Subject: [PATCH 06/35] Implements next-previous workspace --- src/commands.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/commands.c b/src/commands.c index 275f8f96..b57b3ac5 100644 --- a/src/commands.c +++ b/src/commands.c @@ -845,6 +845,31 @@ static char **append_argument(char **original, char *argument) { return result; } +/* + * switch to next or previous existing workspace + */ +static void next_previous_workspace(xcb_connection_t *conn, int direction) { + Workspace *t_ws; + int i; + if (direction == 'n') { + if (c_ws->num == 9) + return; + for ( i = c_ws->num + 1; i <= 9; i++) { + t_ws = &(workspaces[i]); + if (t_ws->screen != NULL) break; + } + } else if (direction == 'p' ) { + if (c_ws->num == 0) + return; + for (i = c_ws->num - 1; i >= 0 ; i--) { + t_ws = &(workspaces[i]); + if (t_ws->screen != NULL) break; + } + } + if (t_ws->screen != NULL) + show_workspace(conn,i+1); +} + /* * Parses a command, see file CMDMODE for more information @@ -982,7 +1007,14 @@ void parse_command(xcb_connection_t *conn, const char *command) { return; } + /* Is it 'n' for next workspace (nw) */ + if (command[0] == 'n' && command[1] == 'w') { + next_previous_workspace(conn, command[0]); + } + if (command[0] == 'p' && command[1] == 'w') { + next_previous_workspace(conn, command[0]); + } /* It’s a normal */ char *rest = NULL; enum { ACTION_FOCUS, ACTION_MOVE, ACTION_SNAP } action = ACTION_FOCUS; From 9db8535b4cd55702bbdf0fd005882b469a989e4f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 22 Jul 2009 00:16:16 +0200 Subject: [PATCH 07/35] =?UTF-8?q?Some=20little=20fixes=20(mostly=20formatt?= =?UTF-8?q?ing)=20for=20bapt=E2=80=99s=20patch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/commands.c | 61 +++++++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/src/commands.c b/src/commands.c index b57b3ac5..0322a51d 100644 --- a/src/commands.c +++ b/src/commands.c @@ -845,31 +845,38 @@ static char **append_argument(char **original, char *argument) { return result; } + /* - * switch to next or previous existing workspace + * Switch to next or previous existing workspace + * */ static void next_previous_workspace(xcb_connection_t *conn, int direction) { - Workspace *t_ws; - int i; - if (direction == 'n') { - if (c_ws->num == 9) - return; - for ( i = c_ws->num + 1; i <= 9; i++) { - t_ws = &(workspaces[i]); - if (t_ws->screen != NULL) break; - } - } else if (direction == 'p' ) { - if (c_ws->num == 0) - return; - for (i = c_ws->num - 1; i >= 0 ; i--) { - t_ws = &(workspaces[i]); - if (t_ws->screen != NULL) break; - } - } - if (t_ws->screen != NULL) - show_workspace(conn,i+1); -} + Workspace *t_ws; + int i; + if (direction == 'n') { + /* If we are on the last workspace, we cannot go any further */ + if (c_ws->num == 9) + return; + + for (i = c_ws->num + 1; i <= 9; i++) { + t_ws = &(workspaces[i]); + if (t_ws->screen != NULL) + break; + } + } else if (direction == 'p') { + if (c_ws->num == 0) + return; + for (i = c_ws->num - 1; i >= 0 ; i--) { + t_ws = &(workspaces[i]); + if (t_ws->screen != NULL) + break; + } + } + + if (t_ws->screen != NULL) + show_workspace(conn, i+1); +} /* * Parses a command, see file CMDMODE for more information @@ -1007,14 +1014,18 @@ void parse_command(xcb_connection_t *conn, const char *command) { return; } - /* Is it 'n' for next workspace (nw) */ - if (command[0] == 'n' && command[1] == 'w') { + + /* Is it 'n' or 'p' for next/previous workspace? (nw) */ + if (command[0] == 'n' && command[1] == 'w') { next_previous_workspace(conn, command[0]); - } + return; + } if (command[0] == 'p' && command[1] == 'w') { next_previous_workspace(conn, command[0]); - } + return; + } + /* It’s a normal */ char *rest = NULL; enum { ACTION_FOCUS, ACTION_MOVE, ACTION_SNAP } action = ACTION_FOCUS; From 40750e227dc15116c8af0a8723eafa0469b019dc Mon Sep 17 00:00:00 2001 From: bapt Date: Thu, 23 Jul 2009 16:14:24 +0000 Subject: [PATCH 08/35] Implements a reload command --- include/config.h | 4 +++- include/i3.h | 1 + src/commands.c | 7 ++++++ src/config.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++- src/mainx.c | 20 +++-------------- 5 files changed, 69 insertions(+), 19 deletions(-) diff --git a/include/config.h b/include/config.h index 140db5ab..80fa2f02 100644 --- a/include/config.h +++ b/include/config.h @@ -15,6 +15,7 @@ #ifndef _CONFIG_H #define _CONFIG_H +#include #include "queue.h" typedef struct Config Config; @@ -75,6 +76,7 @@ struct Config { * configuration file. * */ -void load_configuration(xcb_connection_t *conn, const char *override_configfile); +void load_configuration(xcb_connection_t *conn, const char *override_configfile, bool reload); +void grab_all_keys(xcb_connection_t *conn); #endif diff --git a/include/i3.h b/include/i3.h index a489b42f..2a31b93b 100644 --- a/include/i3.h +++ b/include/i3.h @@ -32,5 +32,6 @@ extern xcb_event_handlers_t evenths; extern int num_screens; extern uint8_t root_depth; extern xcb_atom_t atoms[NUM_ATOMS]; +extern xcb_window_t root; #endif diff --git a/src/commands.c b/src/commands.c index 0322a51d..d1b77635 100644 --- a/src/commands.c +++ b/src/commands.c @@ -26,6 +26,7 @@ #include "client.h" #include "floating.h" #include "xcb.h" +#include "config.h" bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) { /* If this container is empty, we’re done */ @@ -907,6 +908,12 @@ void parse_command(xcb_connection_t *conn, const char *command) { exit(EXIT_SUCCESS); } + /* Is it ? Then restart in place. */ if (STARTS_WITH(command, "restart")) { LOG("restarting \"%s\"...\n", start_argv[0]); diff --git a/src/config.c b/src/config.c index 3fe6d067..1530622f 100644 --- a/src/config.c +++ b/src/config.c @@ -57,6 +57,36 @@ static void replace_variable(char *buffer, const char *key, const char *value) { } } +/* UnGrab the bound keys */ + +void ungrab_all_keys(xcb_connection_t *conn) { + Binding *bind; + TAILQ_FOREACH(bind, &bindings, bindings) { + LOG("UnGrabbing %d\n", bind->keycode); + #define UNGRAB_KEY(modifier) xcb_ungrab_key(conn,bind->keycode,root,modifier); + UNGRAB_KEY(bind->keycode); + } +} + +/* Grab the bound keys */ +void grab_all_keys(xcb_connection_t *conn) { + Binding *bind; + TAILQ_FOREACH(bind, &bindings, bindings) { + LOG("Grabbing %d\n", bind->keycode); + if ( bind->mods & BIND_MODE_SWITCH ) + xcb_grab_key(conn, 0, root, 0, bind->keycode, + XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC); + else { + /* Grab the key in all combinations */ + #define GRAB_KEY(modifier) xcb_grab_key(conn, 0, root, modifier, bind->keycode, \ + XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC) + GRAB_KEY(bind->mods); + GRAB_KEY(bind->mods | xcb_numlock_mask); + GRAB_KEY(bind->mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK); + } + } +} + /* * Reads the configuration from ~/.i3/config or /etc/i3/config if not found. * @@ -64,7 +94,28 @@ static void replace_variable(char *buffer, const char *key, const char *value) { * configuration file. * */ -void load_configuration(xcb_connection_t *conn, const char *override_configpath) { +void load_configuration(xcb_connection_t *conn, const char *override_configpath,bool reload) { + + if(reload) { + /* First ungrab the keys */ + ungrab_all_keys(conn); + /* clean up lists */ + Binding *bind; + TAILQ_FOREACH(bind,&bindings,bindings) { + TAILQ_REMOVE(&bindings,bind,bindings); + free(bind->command); + free(bind); + } + + struct Assignment *assign; + TAILQ_FOREACH(assign,&assignments,assignments) { + TAILQ_REMOVE(&assignments,assign,assignments); + free(assign->windowclass_title); + free(assign) + } + } + + SLIST_HEAD(variables_head, Variable) variables; #define OPTION_STRING(name) \ @@ -321,6 +372,9 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath) die("Unknown configfile option: %s\n", key); } + /* now grab all keys again */ + if(reload) + grab_all_keys(conn); fclose(handle); REQUIRED_OPTION(terminal); diff --git a/src/mainx.c b/src/mainx.c index 6061fd8d..56b864e3 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -69,6 +69,7 @@ struct stack_wins_head stack_wins = SLIST_HEAD_INITIALIZER(stack_wins); xcb_event_handlers_t evenths; xcb_atom_t atoms[NUM_ATOMS]; +xcb_window_t root; int num_screens = 0; /* The depth of the root screen (used e.g. for creating new pixmaps later) */ @@ -111,7 +112,6 @@ int main(int argc, char *argv[], char *env[]) { bool autostart = true; xcb_connection_t *conn; xcb_property_handlers_t prophs; - xcb_window_t root; xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS]; setlocale(LC_ALL, ""); @@ -153,7 +153,7 @@ int main(int argc, char *argv[], char *env[]) { if (xcb_connection_has_error(conn)) die("Cannot open display\n"); - load_configuration(conn, override_configpath); + load_configuration(conn, override_configpath,false); /* 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); @@ -330,21 +330,7 @@ int main(int argc, char *argv[], char *env[]) { xcb_get_numlock_mask(conn); - /* Grab the bound keys */ - Binding *bind; - TAILQ_FOREACH(bind, &bindings, bindings) { - LOG("Grabbing %d\n", bind->keycode); - if (bind->mods & BIND_MODE_SWITCH) - xcb_grab_key(conn, 0, root, 0, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC); - else { - /* Grab the key in all combinations */ - #define GRAB_KEY(modifier) xcb_grab_key(conn, 0, root, modifier, bind->keycode, \ - XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC) - GRAB_KEY(bind->mods); - GRAB_KEY(bind->mods | xcb_numlock_mask); - GRAB_KEY(bind->mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK); - } - } + grab_all_keys(conn); /* Autostarting exec-lines */ struct Autostart *exec; From ce501c9de9954da29a7afa3c11f53d5bdf98c0df Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 23 Jul 2009 20:36:48 +0200 Subject: [PATCH 09/35] =?UTF-8?q?Some=20fixes/reformatting=20for=20bapt?= =?UTF-8?q?=E2=80=99s=20patch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/commands.c | 6 ++-- src/config.c | 75 ++++++++++++++++++++++++++------------------------ src/mainx.c | 2 +- 3 files changed, 43 insertions(+), 40 deletions(-) diff --git a/src/commands.c b/src/commands.c index d1b77635..74f8f2a6 100644 --- a/src/commands.c +++ b/src/commands.c @@ -908,10 +908,10 @@ void parse_command(xcb_connection_t *conn, const char *command) { exit(EXIT_SUCCESS); } - /* Is it ? */ if (STARTS_WITH(command, "reload")) { - load_configuration(conn,NULL,true); - return; + load_configuration(conn, NULL, true); + return; } /* Is it ? Then restart in place. */ diff --git a/src/config.c b/src/config.c index 1530622f..ee188537 100644 --- a/src/config.c +++ b/src/config.c @@ -57,24 +57,28 @@ static void replace_variable(char *buffer, const char *key, const char *value) { } } -/* UnGrab the bound keys */ - +/* + * Ungrab the bound keys + * + */ void ungrab_all_keys(xcb_connection_t *conn) { Binding *bind; TAILQ_FOREACH(bind, &bindings, bindings) { - LOG("UnGrabbing %d\n", bind->keycode); - #define UNGRAB_KEY(modifier) xcb_ungrab_key(conn,bind->keycode,root,modifier); - UNGRAB_KEY(bind->keycode); + LOG("Ungrabbing %d\n", bind->keycode); + xcb_ungrab_key(conn, bind->keycode, root, bind->keycode); } } -/* Grab the bound keys */ +/* + * Grab the bound keys (tell X to send us keypress events for those keycodes) + * + */ void grab_all_keys(xcb_connection_t *conn) { Binding *bind; TAILQ_FOREACH(bind, &bindings, bindings) { LOG("Grabbing %d\n", bind->keycode); - if ( bind->mods & BIND_MODE_SWITCH ) - xcb_grab_key(conn, 0, root, 0, bind->keycode, + if ((bind->mods & BIND_MODE_SWITCH) != 0) + xcb_grab_key(conn, 0, root, 0, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC); else { /* Grab the key in all combinations */ @@ -94,28 +98,29 @@ void grab_all_keys(xcb_connection_t *conn) { * configuration file. * */ -void load_configuration(xcb_connection_t *conn, const char *override_configpath,bool reload) { - - if(reload) { +void load_configuration(xcb_connection_t *conn, const char *override_configpath, bool reload) { + if (reload) { /* First ungrab the keys */ ungrab_all_keys(conn); - /* clean up lists */ + + /* Clear the old binding and assignment lists */ Binding *bind; - TAILQ_FOREACH(bind,&bindings,bindings) { - TAILQ_REMOVE(&bindings,bind,bindings); - free(bind->command); - free(bind); + while (!TAILQ_EMPTY(&bindings)) { + bind = TAILQ_FIRST(&bindings); + TAILQ_REMOVE(&bindings, bind, bindings); + FREE(bind->command); + FREE(bind); } struct Assignment *assign; - TAILQ_FOREACH(assign,&assignments,assignments) { - TAILQ_REMOVE(&assignments,assign,assignments); - free(assign->windowclass_title); - free(assign) + while (!TAILQ_EMPTY(&assignments)) { + assign = TAILQ_FIRST(&assignments); + FREE(assign->windowclass_title); + TAILQ_REMOVE(&assignments, assign, assignments); + FREE(assign); } } - SLIST_HEAD(variables_head, Variable) variables; #define OPTION_STRING(name) \ @@ -293,23 +298,22 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, /* assign window class[/window title] → workspace */ if (strcasecmp(key, "assign") == 0) { LOG("assign: \"%s\"\n", value); - char *class_title = sstrdup(value); + char *class_title; char *target; + char *end; /* 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("Malformed assignment, couldn't find terminating quote\n"); - *end = '\0'; + if (value[0] == '"') { + class_title = sstrdup(value+1); + end = strchr(class_title, '"'); } else { + class_title = sstrdup(value); /* If it is not quoted, we terminate it at the first space */ - char *end = strchr(class_title, ' '); - if (end == NULL) - die("Malformed assignment, couldn't find terminating space\n"); - *end = '\0'; + end = strchr(class_title, ' '); } + if (end == NULL) + die("Malformed assignment, couldn't find terminating quote\n"); + *end = '\0'; /* Strip trailing whitespace */ while (strlen(value) > 0 && value[strlen(value)-1] == ' ') @@ -317,7 +321,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, /* The target is the last argument separated by a space */ if ((target = strrchr(value, ' ')) == NULL) - die("Malformed assignment, couldn't find target\n"); + die("Malformed assignment, couldn't find target (\"%s\")\n", value); target++; if (strchr(target, '~') == NULL && (atoi(target) < 1 || atoi(target) > 10)) @@ -373,14 +377,13 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, die("Unknown configfile option: %s\n", key); } /* now grab all keys again */ - if(reload) - grab_all_keys(conn); + if (reload) + grab_all_keys(conn); fclose(handle); REQUIRED_OPTION(terminal); REQUIRED_OPTION(font); - while (!SLIST_EMPTY(&variables)) { struct Variable *v = SLIST_FIRST(&variables); SLIST_REMOVE_HEAD(&variables, variables); diff --git a/src/mainx.c b/src/mainx.c index 56b864e3..06330eb0 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -153,7 +153,7 @@ int main(int argc, char *argv[], char *env[]) { if (xcb_connection_has_error(conn)) die("Cannot open display\n"); - load_configuration(conn, override_configpath,false); + load_configuration(conn, override_configpath, false); /* 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 ed60b31fd0bb21c03c6b7addca376afa08c13aee Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 24 Jul 2009 19:49:06 +0200 Subject: [PATCH 10/35] Implement predict_text_width, which will be needed for named workspaces --- include/xcb.h | 8 +++++++ src/xcb.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/include/xcb.h b/include/xcb.h index b540a5cc..334760ff 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -152,4 +152,12 @@ void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window); */ void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap); +/** + * Calculate the width of the given text (16-bit characters, UCS) with given + * real length (amount of glyphs) using the given font. + * + */ +int predict_text_width(xcb_connection_t *conn, char *font_pattern, char *text, + int length); + #endif diff --git a/src/xcb.c b/src/xcb.c index 4ad69db1..661fdb7c 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -292,3 +292,69 @@ void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap) xcb_create_gc(conn, pixmap->gc, pixmap->id, 0, 0); } + +/* + * Returns the xcb_charinfo_t for the given character (specified by row and + * column in the lookup table) if existing, otherwise the minimum bounds. + * + */ +static xcb_charinfo_t *get_charinfo(int col, int row, xcb_query_font_reply_t *font_info, + xcb_charinfo_t *table, bool dont_fallback) { + xcb_charinfo_t *result; + + /* Bounds checking */ + if (row < font_info->min_byte1 || row > font_info->max_byte1 || + col < font_info->min_char_or_byte2 || col > font_info->max_char_or_byte2) + return NULL; + + /* If we don’t have a table to lookup the infos per character, return the + * minimum bounds */ + if (table == NULL) + return &font_info->min_bounds; + + result = &table[((row - font_info->min_byte1) * + (font_info->max_char_or_byte2 - font_info->min_char_or_byte2 + 1)) + + (col - font_info->min_char_or_byte2)]; + + /* If the character has an entry in the table, return it */ + if (result->character_width != 0 || + (result->right_side_bearing | + result->left_side_bearing | + result->ascent | + result->descent) != 0) + return result; + + /* Otherwise, get the default character and return its charinfo */ + if (dont_fallback) + return NULL; + + return get_charinfo((font_info->default_char >> 8), + (font_info->default_char & 0xFF), + font_info, + table, + true); +} + +/* + * Calculate the width of the given text (16-bit characters, UCS) with given + * real length (amount of glyphs) using the given font. + * + */ +int predict_text_width(xcb_connection_t *conn, char *font_pattern, char *text, int length) { + xcb_query_font_reply_t *font_info; + xcb_charinfo_t *table; + int i, width; + i3Font *font = load_font(conn, font_pattern); + + font_info = xcb_query_font_reply(conn, xcb_query_font_unchecked(conn, font->id), NULL); + table = xcb_query_font_char_infos(font_info); + + for (i = 0; i < 2 * length; i += 2) { + xcb_charinfo_t *info = get_charinfo(text[i+1], text[i], font_info, table, false); + if (info == NULL) + continue; + width += info->character_width; + } + + return width; +} From 163c9ad7dbd82905088ba6b6cc1c4ee9ed210258 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 25 Jul 2009 22:29:28 +0200 Subject: [PATCH 11/35] Map window/its decoration *after* calling render_layout() Thus, no more flickering because the window was first mapped and then moved. Especially users of multiple monitors should be happy now ;-). Rather radical change, though, so be prepared for problems. --- include/xcb.h | 2 +- src/layout.c | 3 --- src/manage.c | 28 ++++++++++++++++------------ src/resize.c | 4 ++-- src/util.c | 2 +- src/xcb.c | 5 +++-- src/xinerama.c | 2 +- 7 files changed, 24 insertions(+), 22 deletions(-) diff --git a/include/xcb.h b/include/xcb.h index b540a5cc..16ce3ce8 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -89,7 +89,7 @@ uint32_t get_colorpixel(xcb_connection_t *conn, char *hex); * */ xcb_window_t create_window(xcb_connection_t *conn, Rect r, uint16_t window_class, - int cursor, uint32_t mask, uint32_t *values); + int cursor, bool map, uint32_t mask, uint32_t *values); /** * Changes a single value in the graphic context (so one doesn’t have to diff --git a/src/layout.c b/src/layout.c index 89c561ea..ce1541ab 100644 --- a/src/layout.c +++ b/src/layout.c @@ -282,9 +282,6 @@ void render_container(xcb_connection_t *conn, Container *container) { Client *client; int num_clients = 0, current_client = 0; - if (container->currently_focused == NULL) - return; - CIRCLEQ_FOREACH(client, &(container->clients), clients) num_clients++; diff --git a/src/manage.c b/src/manage.c index f7630eb0..9ddcab42 100644 --- a/src/manage.c +++ b/src/manage.c @@ -140,9 +140,6 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, 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); @@ -201,7 +198,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, 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); + new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, false, 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. */ @@ -351,14 +348,6 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, 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) { - 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); - } } /* Insert into the currently active container, if it’s not a dock window */ @@ -420,4 +409,19 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, } render_layout(conn); + + /* Map the window first to avoid flickering */ + xcb_map_window(conn, new->frame); + xcb_map_window(conn, child); + if (CUR_CELL->workspace->fullscreen_client == NULL && !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 (!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); + } + } + + xcb_flush(conn); } diff --git a/src/resize.c b/src/resize.c index 3d634420..52b064b4 100644 --- a/src/resize.c +++ b/src/resize.c @@ -61,7 +61,7 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i /* 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); + xcb_window_t grabwin = create_window(conn, grabrect, XCB_WINDOW_CLASS_INPUT_ONLY, -1, true, mask, values); Rect helprect; if (orientation == O_VERTICAL) { @@ -87,7 +87,7 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i xcb_window_t helpwin = create_window(conn, helprect, XCB_WINDOW_CLASS_INPUT_OUTPUT, (orientation == O_VERTICAL ? XCB_CURSOR_SB_V_DOUBLE_ARROW : - XCB_CURSOR_SB_H_DOUBLE_ARROW), mask, values); + XCB_CURSOR_SB_H_DOUBLE_ARROW), true, mask, values); xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin); diff --git a/src/util.c b/src/util.c index 26b339a9..108bf4a1 100644 --- a/src/util.c +++ b/src/util.c @@ -438,7 +438,7 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode) XCB_EVENT_MASK_EXPOSURE; /* …our window needs to be redrawn */ struct Stack_Window *stack_win = &(container->stack_win); - stack_win->window = create_window(conn, rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values); + stack_win->window = create_window(conn, rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, true, mask, values); /* Initialize the entry for our cached pixmap. It will be * created as soon as it’s needed (see cached_pixmap_prepare). */ diff --git a/src/xcb.c b/src/xcb.c index 4ad69db1..96a3ce15 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -90,7 +90,7 @@ uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) { * */ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_class, int cursor, - uint32_t mask, uint32_t *values) { + bool map, uint32_t mask, uint32_t *values) { xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; xcb_window_t result = xcb_generate_id(conn); xcb_cursor_t cursor_id = xcb_generate_id(conn); @@ -121,7 +121,8 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_cl xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id); /* Map the window (= make it visible) */ - xcb_map_window(conn, result); + if (map) + xcb_map_window(conn, result); return result; } diff --git a/src/xinerama.c b/src/xinerama.c index 2bb8b298..1b410e03 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -114,7 +114,7 @@ static void initialize_screen(xcb_connection_t *conn, i3Screen *screen, Workspac 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->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, true, mask, values); screen->bargc = xcb_generate_id(conn); xcb_create_gc(conn, screen->bargc, screen->bar, 0, 0); From 270922bf61c0c610917fb07e49b47721c8afc35d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 25 Jul 2009 22:53:33 +0200 Subject: [PATCH 12/35] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20crash=20on=20fl?= =?UTF-8?q?oating=20windows,=20set=20focus=20correctly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/manage.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/manage.c b/src/manage.c index 9ddcab42..83c27f99 100644 --- a/src/manage.c +++ b/src/manage.c @@ -415,10 +415,10 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, xcb_map_window(conn, child); if (CUR_CELL->workspace->fullscreen_client == NULL && !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->workspace->fullscreen_client == NULL) { if (!client_is_floating(new)) new->container->currently_focused = new; - if (new->container == CUR_CELL) + if (new->container == CUR_CELL || client_is_floating(new)) xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME); } } From 76664df3df5e553281cfdd6f0fe5c7b3a0f5a6a0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 26 Jul 2009 02:12:45 +0200 Subject: [PATCH 13/35] Bugfix: Make assignments work again (Thanks badboy) --- src/manage.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/manage.c b/src/manage.c index 83c27f99..5d246833 100644 --- a/src/manage.c +++ b/src/manage.c @@ -134,6 +134,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, uint32_t mask = 0; uint32_t values[3]; uint16_t original_height = height; + bool map_frame = true; /* We are interested in property changes */ mask = XCB_CW_EVENT_MASK; @@ -337,7 +338,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, new->workspace = t_ws; old_focused = new->container->currently_focused; - xcb_unmap_window(conn, new->frame); + map_frame = false; break; } } @@ -411,8 +412,9 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, render_layout(conn); /* Map the window first to avoid flickering */ - xcb_map_window(conn, new->frame); xcb_map_window(conn, child); + if (map_frame) + xcb_map_window(conn, new->frame); if (CUR_CELL->workspace->fullscreen_client == NULL && !new->dock) { /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */ if (new->workspace->fullscreen_client == NULL) { From 33e536113d1cde99b8396907a9b8b62a60e978a1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 28 Jul 2009 00:43:52 +0200 Subject: [PATCH 14/35] Bugfix: Fix NULL-pointer dereferencing introduced by commit 874941 (Thanks tsdh) --- src/util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util.c b/src/util.c index 108bf4a1..48bf43a0 100644 --- a/src/util.c +++ b/src/util.c @@ -369,7 +369,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) { /* If the last client was a floating client, we need to go to the next * tiling client in stack and re-decorate it. */ - if (client_is_floating(old_client)) { + if (old_client != NULL && client_is_floating(old_client)) { LOG("Coming from floating client, searching next tiling...\n"); Client *current; SLIST_FOREACH(current, &(client->workspace->focus_stack), focus_clients) { From ddcb11babae761e9fcaa1b243cba3b27eb6d83e8 Mon Sep 17 00:00:00 2001 From: Bapt Date: Fri, 24 Jul 2009 17:30:27 +0000 Subject: [PATCH 15/35] Implements configurable named workspaces --- include/data.h | 3 +++ src/config.c | 32 ++++++++++++++++++++++++++++++++ src/layout.c | 42 ++++++++++++++++++++++++++++++++++-------- 3 files changed, 69 insertions(+), 8 deletions(-) diff --git a/include/data.h b/include/data.h index 3ed8d65b..eb4c2fd6 100644 --- a/include/data.h +++ b/include/data.h @@ -165,6 +165,9 @@ struct Workspace { /** Number of this workspace, starting from 0 */ int num; + /** Name of the workspave */ + char *name; + /** x, y, width, height */ Rect rect; diff --git a/src/config.c b/src/config.c index ee188537..49c10b10 100644 --- a/src/config.c +++ b/src/config.c @@ -18,6 +18,7 @@ #include "util.h" #include "config.h" #include "xcb.h" +#include "table.h" Config config; @@ -295,6 +296,37 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, continue; } + /* name "workspace number" "name of the workspace" */ + if (strcasecmp(key, "name") == 0) { + LOG("name workspace: %s\n",value); + char *ws_str = sstrdup(value); + char *end = strchr(ws_str, ' '); + if (end == NULL) + die("Malformed name, couln't find terminating space\n"); + *end='\0'; + + /* Strip trailing whitespace */ + while (strlen(value) > 0 && value[strlen(value)-1] == ' ') + value[strlen(value)-1] = '\0'; + + int ws_num=atoi(ws_str); + + if ( ws_num < 1 || ws_num > 10 ) + die("Malformed name, invalid workspace Number\n"); + + /* find the name */ + char *name= value; + name += strlen(ws_str) + 1; + + /* if no name reinitialize the name to NULL for reload */ + if (name == '\0') { + workspaces[ws_num - 1].name=NULL; + continue; + } + workspaces[ws_num - 1].name=sstrdup(name); + continue; + } + /* assign window class[/window title] → workspace */ if (strcasecmp(key, "assign") == 0) { LOG("assign: \"%s\"\n", value); diff --git a/src/layout.c b/src/layout.c index ce1541ab..7bc22f1b 100644 --- a/src/layout.c +++ b/src/layout.c @@ -418,7 +418,6 @@ 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 }; - char label[3]; /* Fill the whole bar in black */ xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); @@ -436,17 +435,44 @@ static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int wid struct Colortriple *color = (screen->current_workspace == c ? &(config.bar.focused) : &(config.bar.unfocused)); - 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, color->background, - drawn * height + 1, 2, height - 4, height - 4); + char *label=NULL; + if ( workspaces[c].name != NULL ) + asprintf(&label, "%d: %s",c+1, workspaces[c].name); + else + asprintf(&label, "%d", c+1); + /* Calculate the length of a string in a given font */ + + xcb_query_text_extents_cookie_t extents_cookie; + xcb_query_text_extents_reply_t *extents_reply; + xcb_char2b_t *chars; + int str_width; + int i; + chars = malloc(strlen(label) * sizeof(xcb_char2b_t)); + for (i=0; iid, + strlen(label), + chars); + extents_reply = xcb_query_text_extents_reply(conn, + extents_cookie, + NULL); + free(chars); + str_width = extents_reply->overall_width; + free(extents_reply); + xcb_draw_rect(conn, screen->bar, screen->bargc, color->border, + drawn, 1, str_width + 8, height - 2); + xcb_draw_rect(conn, screen->bar, screen->bargc, color->background, + drawn + 1, 2, str_width + 6, height - 4); - snprintf(label, sizeof(label), "%d", c+1); 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 */, + xcb_image_text_8(conn, strlen(label), screen->bar, screen->bargc, drawn + 5 /* X */, font->height + 1 /* Y = baseline of font */, label); - drawn++; + drawn+=str_width+8; + free(label); } LOG("done rendering internal\n"); From e6198ad6c8474e8c1caf0ab20072a21f48029468 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 28 Jul 2009 13:55:09 +0200 Subject: [PATCH 16/35] =?UTF-8?q?Some=20little=20fixes=20for=20bapt?= =?UTF-8?q?=E2=80=99s=20patch,=20use=20predict=5Ftext=5Fwidth,=20support?= =?UTF-8?q?=20UTF8,=20pre-render=20workspace=20names?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/data.h | 5 ++++- include/workspace.h | 27 +++++++++++++++++++++++++ include/xcb.h | 2 +- src/config.c | 32 +++++++++++++++++++----------- src/layout.c | 48 ++++++++++++++++----------------------------- src/workspace.c | 43 ++++++++++++++++++++++++++++++++++++++++ src/xcb.c | 4 ++-- 7 files changed, 115 insertions(+), 46 deletions(-) create mode 100644 include/workspace.h create mode 100644 src/workspace.c diff --git a/include/data.h b/include/data.h index eb4c2fd6..5bca3ae7 100644 --- a/include/data.h +++ b/include/data.h @@ -165,9 +165,12 @@ struct Workspace { /** Number of this workspace, starting from 0 */ int num; - /** Name of the workspave */ + /** Name of the workspace (in UCS-2) */ char *name; + /** Length of the workspace’s name (in glyphs) */ + int name_len; + /** x, y, width, height */ Rect rect; diff --git a/include/workspace.h b/include/workspace.h new file mode 100644 index 00000000..2b41a527 --- /dev/null +++ b/include/workspace.h @@ -0,0 +1,27 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ +#include + +#include "data.h" + +#ifndef _WORKSPACE_H +#define _WORKSPACE_H + +/** + * Sets the name (or just its number) for the given workspace. This has to + * be called for every workspace as the rendering function + * (render_internal_bar) relies on workspace->name and workspace->name_len + * being ready-to-use. + * + */ +void workspace_set_name(Workspace *ws, const char *name); + +#endif diff --git a/include/xcb.h b/include/xcb.h index 694c6c6c..6ae49107 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -157,7 +157,7 @@ void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap) * real length (amount of glyphs) using the given font. * */ -int predict_text_width(xcb_connection_t *conn, char *font_pattern, char *text, +int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *text, int length); #endif diff --git a/src/config.c b/src/config.c index 49c10b10..fc5fa647 100644 --- a/src/config.c +++ b/src/config.c @@ -19,6 +19,7 @@ #include "config.h" #include "xcb.h" #include "table.h" +#include "workspace.h" Config config; @@ -302,28 +303,29 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, char *ws_str = sstrdup(value); char *end = strchr(ws_str, ' '); if (end == NULL) - die("Malformed name, couln't find terminating space\n"); - *end='\0'; + die("Malformed name, couln't find terminating space\n"); + *end = '\0'; /* Strip trailing whitespace */ while (strlen(value) > 0 && value[strlen(value)-1] == ' ') - value[strlen(value)-1] = '\0'; + value[strlen(value)-1] = '\0'; - int ws_num=atoi(ws_str); + int ws_num = atoi(ws_str); - if ( ws_num < 1 || ws_num > 10 ) - die("Malformed name, invalid workspace Number\n"); + if (ws_num < 1 || ws_num > 10) + die("Malformed name, invalid workspace number\n"); /* find the name */ - char *name= value; + char *name = value; name += strlen(ws_str) + 1; - /* if no name reinitialize the name to NULL for reload */ if (name == '\0') { - workspaces[ws_num - 1].name=NULL; - continue; + free(ws_str); + continue; } - workspaces[ws_num - 1].name=sstrdup(name); + + workspace_set_name(&(workspaces[ws_num - 1]), name); + free(ws_str); continue; } @@ -423,6 +425,14 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, free(v->value); free(v); } + + /* Set an empty name for every workspace which got no name */ + for (int i = 0; i < 10; i++) { + if (workspaces[i].name != NULL) + continue; + + workspace_set_name(&(workspaces[i]), NULL); + } return; } diff --git a/src/layout.c b/src/layout.c index 7bc22f1b..2dbcf71c 100644 --- a/src/layout.c +++ b/src/layout.c @@ -434,45 +434,31 @@ static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int wid struct Colortriple *color = (screen->current_workspace == c ? &(config.bar.focused) : &(config.bar.unfocused)); + Workspace *ws = &workspaces[c]; - char *label=NULL; - if ( workspaces[c].name != NULL ) - asprintf(&label, "%d: %s",c+1, workspaces[c].name); - else - asprintf(&label, "%d", c+1); /* Calculate the length of a string in a given font */ + int text_width = predict_text_width(conn, config.font, ws->name, ws->name_len); - xcb_query_text_extents_cookie_t extents_cookie; - xcb_query_text_extents_reply_t *extents_reply; - xcb_char2b_t *chars; - int str_width; - int i; - chars = malloc(strlen(label) * sizeof(xcb_char2b_t)); - for (i=0; iid, - strlen(label), - chars); - extents_reply = xcb_query_text_extents_reply(conn, - extents_cookie, - NULL); - free(chars); - str_width = extents_reply->overall_width; - free(extents_reply); + /* Draw the outer rect */ xcb_draw_rect(conn, screen->bar, screen->bargc, color->border, - drawn, 1, str_width + 8, height - 2); + drawn, /* x */ + 1, /* y */ + text_width + 5 + 5, /* width = text width + 5 px left + 5px right */ + height - 2 /* height = max. height - 1 px upper and 1 px bottom border */); + + /* Draw the background of this rect */ xcb_draw_rect(conn, screen->bar, screen->bargc, color->background, - drawn + 1, 2, str_width + 6, height - 4); + drawn + 1, + 2, + text_width + 4 + 4, + height - 4); xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, color->text); xcb_change_gc_single(conn, screen->bargc, XCB_GC_BACKGROUND, color->background); - xcb_image_text_8(conn, strlen(label), screen->bar, screen->bargc, drawn + 5 /* X */, - font->height + 1 /* Y = baseline of font */, label); - drawn+=str_width+8; - free(label); + xcb_image_text_16(conn, ws->name_len, screen->bar, screen->bargc, drawn + 5 /* X */, + font->height + 1 /* Y = baseline of font */, + (xcb_char2b_t*)ws->name); + drawn += text_width + 12; } LOG("done rendering internal\n"); diff --git a/src/workspace.c b/src/workspace.c new file mode 100644 index 00000000..40a5692a --- /dev/null +++ b/src/workspace.c @@ -0,0 +1,43 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + * workspace.c: Functions for modifying workspaces + * + */ +#include +#include +#include + +#include "util.h" +#include "data.h" + +/* + * Sets the name (or just its number) for the given workspace. This has to + * be called for every workspace as the rendering function + * (render_internal_bar) relies on workspace->name and workspace->name_len + * being ready-to-use. + * + */ +void workspace_set_name(Workspace *ws, const char *name) { + char *label; + int ret; + + if (name != NULL) + ret = asprintf(&label, "%d: %s", ws->num + 1, name); + else ret = asprintf(&label, "%d", ws->num + 1); + + if (ret == -1) + errx(1, "asprintf() failed"); + + FREE(ws->name); + + ws->name = convert_utf8_to_ucs2(label, &(ws->name_len)); + + free(label); +} diff --git a/src/xcb.c b/src/xcb.c index 62927961..ff0c32d0 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -341,10 +341,10 @@ static xcb_charinfo_t *get_charinfo(int col, int row, xcb_query_font_reply_t *fo * real length (amount of glyphs) using the given font. * */ -int predict_text_width(xcb_connection_t *conn, char *font_pattern, char *text, int length) { +int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *text, int length) { xcb_query_font_reply_t *font_info; xcb_charinfo_t *table; - int i, width; + int i, width = 0; i3Font *font = load_font(conn, font_pattern); font_info = xcb_query_font_reply(conn, xcb_query_font_unchecked(conn, font->id), NULL); From 1befbb2a504388c016d4484f1a96f5c87bc95e3b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 28 Jul 2009 14:03:50 +0200 Subject: [PATCH 17/35] Use errx() instead of an own die() function --- include/util.h | 8 ++------ src/util.c | 18 ++---------------- 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/include/util.h b/include/util.h index bc2dc5e0..f4c9d539 100644 --- a/include/util.h +++ b/include/util.h @@ -9,12 +9,14 @@ * */ #include +#include #include "data.h" #ifndef _UTIL_H #define _UTIL_H +#define die(...) errx(EXIT_FAILURE, __VA_ARGS__); #define exit_if_null(pointer, ...) { if (pointer == NULL) die(__VA_ARGS__); } #define STARTS_WITH(string, needle) (strncasecmp(string, needle, strlen(needle)) == 0) #define CIRCLEQ_NEXT_OR_NULL(head, elm, field) (CIRCLEQ_NEXT(elm, field) != CIRCLEQ_END(head) ? \ @@ -50,12 +52,6 @@ int max(int a, int b); */ void slog(char *fmt, ...); -/** - * Prints the message (see printf()) to stderr, then exits the program. - * - */ -void die(char *fmt, ...) __attribute__((__noreturn__)); - /** * Safe-wrapper around malloc which exits if malloc returns NULL (meaning that * there is no more memory available) diff --git a/src/util.c b/src/util.c index 48bf43a0..ff98bb31 100644 --- a/src/util.c +++ b/src/util.c @@ -62,20 +62,6 @@ void slog(char *fmt, ...) { va_end(args); } -/* - * Prints the message (see printf()) to stderr, then exits the program. - * - */ -void die(char *fmt, ...) { - va_list args; - - va_start(args, fmt); - vfprintf(stderr, fmt, args); - va_end(args); - - exit(EXIT_FAILURE); -} - /* * The s* functions (safe) are wrappers around malloc, strdup, …, which exits if one of * the called functions returns NULL, meaning that there is no more memory available @@ -83,13 +69,13 @@ void die(char *fmt, ...) { */ void *smalloc(size_t size) { void *result = malloc(size); - exit_if_null(result, "Error: out of memory (malloc(%d))\n", size); + exit_if_null(result, "Error: out of memory (malloc(%zd))\n", size); return result; } void *scalloc(size_t size) { void *result = calloc(size, 1); - exit_if_null(result, "Error: out of memory (calloc(%d))\n", size); + exit_if_null(result, "Error: out of memory (calloc(%zd))\n", size); return result; } From a43846ea275bbd398ce0e2d009ed89c7e800cb95 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 28 Jul 2009 20:51:29 +0200 Subject: [PATCH 18/35] Initial implementation of IPC via UNIX domain sockets --- include/i3.h | 1 + include/i3/ipc.h | 24 +++++ include/ipc.h | 23 +++++ src/ipc.c | 232 +++++++++++++++++++++++++++++++++++++++++++++++ src/mainx.c | 15 ++- 5 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 include/i3/ipc.h create mode 100644 include/ipc.h create mode 100644 src/ipc.c diff --git a/include/i3.h b/include/i3.h index 2a31b93b..18844e09 100644 --- a/include/i3.h +++ b/include/i3.h @@ -22,6 +22,7 @@ #define NUM_ATOMS 17 +extern xcb_connection_t *global_conn; extern char **start_argv; extern Display *xkbdpy; extern TAILQ_HEAD(bindings_head, Binding) bindings; diff --git a/include/i3/ipc.h b/include/i3/ipc.h new file mode 100644 index 00000000..40e01158 --- /dev/null +++ b/include/i3/ipc.h @@ -0,0 +1,24 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + * This public header defines the different constants and message types to use + * for the IPC interface to i3 (see docs/ipc for more information). + * + */ + +#ifndef _I3_IPC_H +#define _I3_IPC_H + +/** Never change this, only on major IPC breakage (don’t do that) */ +#define I3_IPC_MAGIC "i3-ipc" + +/** The payload of the message will be interpreted as a command */ +#define I3_IPC_MESSAGE_TYPE_COMMAND 0 + +#endif diff --git a/include/ipc.h b/include/ipc.h new file mode 100644 index 00000000..478354b5 --- /dev/null +++ b/include/ipc.h @@ -0,0 +1,23 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ + +#ifndef _IPC_H +#define _IPC_H + +#include + +#include "i3/ipc.h" + +void ipc_new_client(EV_P_ struct ev_io *w, int revents); + +int ipc_create_socket(const char *filename); + +#endif diff --git a/src/ipc.c b/src/ipc.c new file mode 100644 index 00000000..34675943 --- /dev/null +++ b/src/ipc.c @@ -0,0 +1,232 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + * ipc.c: Everything about the UNIX domain sockets for IPC + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "queue.h" +#include "i3/ipc.h" +#include "i3.h" +#include "util.h" +#include "commands.h" + +typedef struct ipc_client { + int fd; + + TAILQ_ENTRY(ipc_client) clients; +} ipc_client; + +TAILQ_HEAD(ipc_client_head, ipc_client) all_clients = TAILQ_HEAD_INITIALIZER(all_clients); + +/* + * Puts the given socket file descriptor into non-blocking mode or dies if + * setting O_NONBLOCK failed. Non-blocking sockets are a good idea for our + * IPC model because we should by no means block the window manager. + * + */ +static void set_nonblock(int sockfd) { + int flags = fcntl(sockfd, F_GETFL, 0); + flags |= O_NONBLOCK; + if (fcntl(sockfd, F_SETFL, flags) < 0) + err(-1, "Could not set O_NONBLOCK"); +} + +#if 0 +void broadcast(EV_P_ struct ev_timer *t, int revents) { + ipc_client *current; + TAILQ_FOREACH(current, &all_clients, clients) { + write(current->fd, "hi there!\n", strlen("hi there!\n")); + } +} +#endif + +/* + * Decides what to do with the received message. + * + * message is the raw packet, as received from the UNIX domain socket. size + * is the remaining size of bytes for this packet. + * + * message_size is the size of the message as the sender specified it. + * message_type is the type of the message as the sender specified it. + * + */ +static void ipc_handle_message(uint8_t *message, int size, + uint32_t message_size, uint32_t message_type) { + LOG("handling message of size %d\n", size); + LOG("sender specified size %d\n", message_size); + LOG("sender specified type %d\n", message_type); + LOG("payload as a string = %s\n", message); + + switch (message_type) { + case I3_IPC_MESSAGE_TYPE_COMMAND: + parse_command(global_conn, (const char*)message); + + break; + default: + LOG("unhandled ipc message\n"); + break; + } +} + +/* + * Handler for activity on a client connection, receives a message from a + * client. + * + * For now, the maximum message size is 2048. I’m not sure for what the + * IPC interface will be used in the future, thus I’m not implementing a + * mechanism for arbitrarily long messages, as it seems like overkill + * at the moment. + * + */ +static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) { + char buf[2048]; + int n = read(w->fd, buf, sizeof(buf)); + + /* On error or an empty message, we close the connection */ + if (n <= 0) { +#if 0 + /* FIXME: I get these when closing a client socket, + * therefore we just treat them as an error. Is this + * correct? */ + if (errno == EAGAIN || errno == EWOULDBLOCK) + return; +#endif + + /* If not, there was some kind of error. We don’t bother + * and close the connection */ + close(w->fd); + + /* Delete the client from the list of clients */ + struct ipc_client *current; + TAILQ_FOREACH(current, &all_clients, clients) { + if (current->fd != w->fd) + continue; + + /* We can call TAILQ_REMOVE because we break out of the + * TAILQ_FOREACH afterwards */ + TAILQ_REMOVE(&all_clients, current, clients); + break; + } + + ev_io_stop(EV_A_ w); + + LOG("IPC: client disconnected\n"); + return; + } + + /* Terminate the message correctly */ + buf[n] = '\0'; + + /* Check if the message starts with the i3 IPC magic code */ + if (n < strlen(I3_IPC_MAGIC)) { + LOG("IPC: message too short, ignoring\n"); + return; + } + + if (strncmp(buf, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) { + LOG("IPC: message does not start with the IPC magic\n"); + return; + } + + uint8_t *message = (uint8_t*)buf; + message += strlen(I3_IPC_MAGIC); + n -= strlen(I3_IPC_MAGIC); + + /* The next 32 bit after the magic are the message size */ + uint32_t message_size = *((uint32_t*)message); + message += sizeof(uint32_t); + n -= sizeof(uint32_t); + + /* The last 32 bits of the header are the message type */ + uint32_t message_type = *((uint32_t*)message); + message += sizeof(uint32_t); + n -= sizeof(uint32_t); + + ipc_handle_message(message, n, message_size, message_type); +} + +/* + * Handler for activity on the listening socket, meaning that a new client + * has just connected and we should accept() him. Sets up the event handler + * for activity on the new connection and inserts the file descriptor into + * the list of clients. + * + */ +void ipc_new_client(EV_P_ struct ev_io *w, int revents) { + struct sockaddr_un peer; + socklen_t len = sizeof(struct sockaddr_un); + int client; + if ((client = accept(w->fd, (struct sockaddr*)&peer, &len)) < 0) { + if (errno == EINTR) + return; + else perror("accept()"); + return; + } + + set_nonblock(client); + + struct ev_io *package = calloc(sizeof(struct ev_io), 1); + ev_io_init(package, ipc_receive_message, client, EV_READ); + ev_io_start(EV_A_ package); + + LOG("IPC: new client connected\n"); + + struct ipc_client *new = calloc(sizeof(struct ipc_client), 1); + new->fd = client; + + TAILQ_INSERT_TAIL(&all_clients, new, clients); +} + +/* + * Creates the UNIX domain socket at the given path, sets it to non-blocking + * mode, bind()s and listen()s on it. + * + */ +int ipc_create_socket(const char *filename) { + int sockfd; + + /* Unlink the unix domain socket before */ + unlink(filename); + + if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) { + perror("socket()"); + return -1; + } + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_LOCAL; + strcpy(addr.sun_path, filename); + if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) { + perror("bind()"); + return -1; + } + + set_nonblock(sockfd); + + if (listen(sockfd, 5) < 0) { + perror("listen()"); + return -1; + } + + return sockfd; +} diff --git a/src/mainx.c b/src/mainx.c index 06330eb0..684526c6 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -45,6 +45,9 @@ #include "xcb.h" #include "xinerama.h" #include "manage.h" +#include "ipc.h" + +xcb_connection_t *global_conn; /* This is the path to i3, copied from argv[0] when starting up */ char **start_argv; @@ -148,7 +151,7 @@ int main(int argc, char *argv[], char *env[]) { memset(&evenths, 0, sizeof(xcb_event_handlers_t)); memset(&prophs, 0, sizeof(xcb_property_handlers_t)); - conn = xcb_connect(NULL, &screens); + conn = global_conn = xcb_connect(NULL, &screens); if (xcb_connection_has_error(conn)) die("Cannot open display\n"); @@ -366,6 +369,16 @@ int main(int argc, char *argv[], char *env[]) { c_ws = &workspaces[screen->current_workspace]; } + /* Create the UNIX domain socket for IPC */ + int ipc_socket = ipc_create_socket("/tmp/i3.s"); + if (ipc_socket == -1) { + LOG("Could not create the IPC socket, IPC disabled\n"); + } else { + struct ev_io *ipc_io = scalloc(sizeof(struct ev_io)); + ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ); + ev_io_start(loop, ipc_io); + } + /* Handle the events which arrived until now */ xcb_check_cb(NULL, NULL, 0); From 45c3341e09da6558182ad7b8db2812756f0d85e5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 28 Jul 2009 20:58:56 +0200 Subject: [PATCH 19/35] Add docs to include/ipc.h --- include/ipc.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/include/ipc.h b/include/ipc.h index 478354b5..de4e2264 100644 --- a/include/ipc.h +++ b/include/ipc.h @@ -16,8 +16,20 @@ #include "i3/ipc.h" +/** + * Handler for activity on the listening socket, meaning that a new client + * has just connected and we should accept() him. Sets up the event handler + * for activity on the new connection and inserts the file descriptor into + * the list of clients. + * + */ void ipc_new_client(EV_P_ struct ev_io *w, int revents); +/** + * Creates the UNIX domain socket at the given path, sets it to non-blocking + * mode, bind()s and listen()s on it. + * + */ int ipc_create_socket(const char *filename); #endif From ec9b58ada9738ca4f5cdcb0b25e4e9d822a6546a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 28 Jul 2009 21:26:36 +0200 Subject: [PATCH 20/35] Add i3-msg, a sample implementation and hopefully useful utility --- Makefile | 75 ++++------------------------------ common.mk | 70 ++++++++++++++++++++++++++++++++ i3-msg/Makefile | 28 +++++++++++++ i3-msg/main.c | 105 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 210 insertions(+), 68 deletions(-) create mode 100644 common.mk create mode 100644 i3-msg/Makefile create mode 100644 i3-msg/main.c diff --git a/Makefile b/Makefile index a42099d4..8666d9ef 100644 --- a/Makefile +++ b/Makefile @@ -1,72 +1,6 @@ -UNAME=$(shell uname) -DEBUG=1 -INSTALL=install -GIT_VERSION=$(shell git describe --tags --always) -VERSION=$(shell git describe --tags --abbrev=0) +TOPDIR=$(shell pwd) -CFLAGS += -std=c99 -CFLAGS += -pipe -CFLAGS += -Wall -CFLAGS += -Wunused -CFLAGS += -Iinclude -CFLAGS += -I/usr/local/include -CFLAGS += -DI3_VERSION=\"${GIT_VERSION}\" - -# Check if pkg-config is installed, because without pkg-config, the following -# check for the version of libxcb cannot be done. -ifeq ($(shell which pkg-config 2>/dev/null 1>/dev/null || echo 1),1) -$(error "pkg-config was not found") -endif - -ifeq ($(shell pkg-config --exists xcb-keysyms || echo 1),1) -$(error "pkg-config could not find xcb-keysyms.pc") -endif - -ifeq ($(shell pkg-config --exact-version=0.3.3 xcb-keysyms && echo 1),1) -# xcb-keysyms fixed API from 0.3.3 to 0.3.4, so for some months, we will -# have this here. Distributions should upgrade their libxcb in the meantime. -CFLAGS += -DOLD_XCB_KEYSYMS_API -endif - -LDFLAGS += -lm -LDFLAGS += -lxcb-event -LDFLAGS += -lxcb-property -LDFLAGS += -lxcb-keysyms -LDFLAGS += -lxcb-atom -LDFLAGS += -lxcb-aux -LDFLAGS += -lxcb-icccm -LDFLAGS += -lxcb-xinerama -LDFLAGS += -lX11 -LDFLAGS += -lev -LDFLAGS += -L/usr/local/lib -L/usr/pkg/lib - -ifeq ($(UNAME),NetBSD) -# We need -idirafter instead of -I to prefer the system’s iconv over GNU libiconv -CFLAGS += -idirafter /usr/pkg/include -LDFLAGS += -Wl,-rpath,/usr/local/lib -Wl,-rpath,/usr/pkg/lib -endif - -ifeq ($(UNAME),FreeBSD) -LDFLAGS += -liconv -endif - -ifeq ($(UNAME),Linux) -CFLAGS += -D_GNU_SOURCE -endif - -ifeq ($(DEBUG),1) -# Extended debugging flags, macros shall be available in gcc -CFLAGS += -gdwarf-2 -CFLAGS += -g3 -else -CFLAGS += -O2 -endif - -# Don’t print command lines which are run -.SILENT: - -# Always remake the following targets -.PHONY: install clean dist distclean +include $(TOPDIR)/common.mk # Depend on the object files of all source-files in src/*.c and on all header files FILES=$(patsubst %.c,%.o,$(wildcard src/*.c)) @@ -80,6 +14,9 @@ src/%.o: src/%.c ${HEADERS} all: ${FILES} echo "LINK i3" $(CC) -o i3 ${FILES} $(LDFLAGS) + echo "" + echo "SUBDIR i3-msg" + $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg install: all echo "INSTALL" @@ -89,6 +26,7 @@ install: all $(INSTALL) -m 0755 i3 $(DESTDIR)/usr/bin/ test -e $(DESTDIR)/etc/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)/etc/i3/config $(INSTALL) -m 0644 i3.desktop $(DESTDIR)/usr/share/xsessions/ + $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg dist: clean [ ! -d i3-${VERSION} ] || rm -rf i3-${VERSION} @@ -108,6 +46,7 @@ clean: rm -f src/*.o $(MAKE) -C docs clean $(MAKE) -C man clean + $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg clean distclean: clean rm -f i3 diff --git a/common.mk b/common.mk new file mode 100644 index 00000000..7944196c --- /dev/null +++ b/common.mk @@ -0,0 +1,70 @@ +UNAME=$(shell uname) +DEBUG=1 +INSTALL=install +GIT_VERSION=$(shell git describe --tags --always) +VERSION=$(shell git describe --tags --abbrev=0) + +CFLAGS += -std=c99 +CFLAGS += -pipe +CFLAGS += -Wall +CFLAGS += -Wunused +CFLAGS += -Iinclude +CFLAGS += -I/usr/local/include +CFLAGS += -DI3_VERSION=\"${GIT_VERSION}\" + +# Check if pkg-config is installed, because without pkg-config, the following +# check for the version of libxcb cannot be done. +ifeq ($(shell which pkg-config 2>/dev/null 1>/dev/null || echo 1),1) +$(error "pkg-config was not found") +endif + +ifeq ($(shell pkg-config --exists xcb-keysyms || echo 1),1) +$(error "pkg-config could not find xcb-keysyms.pc") +endif + +ifeq ($(shell pkg-config --exact-version=0.3.3 xcb-keysyms && echo 1),1) +# xcb-keysyms fixed API from 0.3.3 to 0.3.4, so for some months, we will +# have this here. Distributions should upgrade their libxcb in the meantime. +CFLAGS += -DOLD_XCB_KEYSYMS_API +endif + +LDFLAGS += -lm +LDFLAGS += -lxcb-event +LDFLAGS += -lxcb-property +LDFLAGS += -lxcb-keysyms +LDFLAGS += -lxcb-atom +LDFLAGS += -lxcb-aux +LDFLAGS += -lxcb-icccm +LDFLAGS += -lxcb-xinerama +LDFLAGS += -lX11 +LDFLAGS += -lev +LDFLAGS += -L/usr/local/lib -L/usr/pkg/lib + +ifeq ($(UNAME),NetBSD) +# We need -idirafter instead of -I to prefer the system’s iconv over GNU libiconv +CFLAGS += -idirafter /usr/pkg/include +LDFLAGS += -Wl,-rpath,/usr/local/lib -Wl,-rpath,/usr/pkg/lib +endif + +ifeq ($(UNAME),FreeBSD) +LDFLAGS += -liconv +endif + +ifeq ($(UNAME),Linux) +CFLAGS += -D_GNU_SOURCE +endif + +ifeq ($(DEBUG),1) +# Extended debugging flags, macros shall be available in gcc +CFLAGS += -gdwarf-2 +CFLAGS += -g3 +else +CFLAGS += -O2 +endif + +# Don’t print command lines which are run +.SILENT: + +# Always remake the following targets +.PHONY: install clean dist distclean + diff --git a/i3-msg/Makefile b/i3-msg/Makefile new file mode 100644 index 00000000..a5e15b6e --- /dev/null +++ b/i3-msg/Makefile @@ -0,0 +1,28 @@ +# Default value so one can compile i3-msg standalone +TOPDIR=.. + +include $(TOPDIR)/common.mk + +# Depend on the object files of all source-files in src/*.c and on all header files +FILES=$(patsubst %.c,%.o,$(wildcard *.c)) +HEADERS=$(wildcard *.h) + +# Depend on the specific file (.c for each .o) and on all headers +%.o: %.c ${HEADERS} + echo "CC $<" + $(CC) $(CFLAGS) -c -o $@ $< + +all: ${FILES} + echo "LINK i3-msg" + $(CC) -o i3-msg ${FILES} $(LDFLAGS) + +install: all + echo "INSTALL" + $(INSTALL) -d -m 0755 $(DESTDIR)/usr/bin + $(INSTALL) -m 0755 i3-msg $(DESTDIR)/usr/bin/ + +clean: + rm -f *.o + +distclean: clean + rm -f i3-msg diff --git a/i3-msg/main.c b/i3-msg/main.c new file mode 100644 index 00000000..8404be3e --- /dev/null +++ b/i3-msg/main.c @@ -0,0 +1,105 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + * i3-msg/main.c: Utility which sends messages to a running i3-instance using + * IPC via UNIX domain sockets. + * + * This serves as an example for how to send your own messages to i3. + * Additionally, it’s even useful sometimes :-). + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void ipc_send_message(int sockfd, uint32_t message_size, + uint32_t message_type, uint8_t *payload) { + int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) + sizeof(uint32_t) + message_size; + char msg[buffer_size]; + char *walk = msg; + + strcpy(walk, "i3-ipc"); + walk += strlen("i3-ipc"); + memcpy(walk, &message_size, sizeof(uint32_t)); + walk += sizeof(uint32_t); + memcpy(walk, &message_type, sizeof(uint32_t)); + walk += sizeof(uint32_t); + memcpy(walk, payload, message_size); + + int sent_bytes = 0; + int bytes_to_go = buffer_size; + while (sent_bytes < bytes_to_go) { + int n = write(sockfd, msg + sent_bytes, bytes_to_go); + if (n == -1) + err(EXIT_FAILURE, "write() failed"); + + sent_bytes += n; + bytes_to_go -= n; + } +} + +int main(int argc, char *argv[]) { + char *socket_path = "/tmp/i3-ipc.sock"; + int o, option_index = 0; + + static struct option long_options[] = { + {"socket", required_argument, 0, 's'}, + {"type", required_argument, 0, 't'}, + {"version", no_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + char *options_string = "s:t:vh"; + + while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { + if (o == 's') { + socket_path = strdup(optarg); + break; + } else if (o == 't') { + printf("currently only commands are implemented\n"); + } else if (o == 'v') { + printf("i3-msg " I3_VERSION); + return 0; + } else if (o == 'h') { + printf("i3-msg " I3_VERSION); + printf("i3-msg [-s ] [-t ] \n"); + return 0; + } + } + + if (optind >= argc) { + fprintf(stderr, "Error: missing message\n"); + fprintf(stderr, "i3-msg [-s ] [-t ] \n"); + return 1; + } + + int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); + struct sockaddr_un addr; + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_LOCAL; + strcpy(addr.sun_path, socket_path); + if (connect(sockfd, &addr, sizeof(struct sockaddr_un)) < 0) + err(-1, "Could not connect to i3"); + + ipc_send_message(sockfd, strlen(argv[optind]), 0, (uint8_t*)argv[optind]); + + close(sockfd); + + return 0; +} From c56dc0f966624aa3279b298134585b791c043ab4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 28 Jul 2009 21:29:23 +0200 Subject: [PATCH 21/35] i3-msg: more error handling, more comments --- i3-msg/main.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/i3-msg/main.c b/i3-msg/main.c index 8404be3e..ebeca7d5 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -27,6 +27,11 @@ #include #include +/* + * Formats a message (payload) of the given size and type and sends it to i3 via + * the given socket file descriptor. + * + */ static void ipc_send_message(int sockfd, uint32_t message_size, uint32_t message_type, uint8_t *payload) { int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) + sizeof(uint32_t) + message_size; @@ -90,12 +95,15 @@ int main(int argc, char *argv[]) { } int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (sockfd == -1) + err(EXIT_FAILURE, "Could not create socket"); + struct sockaddr_un addr; memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_LOCAL; strcpy(addr.sun_path, socket_path); if (connect(sockfd, &addr, sizeof(struct sockaddr_un)) < 0) - err(-1, "Could not connect to i3"); + err(EXIT_FAILURE, "Could not connect to i3"); ipc_send_message(sockfd, strlen(argv[optind]), 0, (uint8_t*)argv[optind]); From e7bf93163d2356393e3b969d9f6e77631bd9179d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 28 Jul 2009 22:09:53 +0200 Subject: [PATCH 22/35] Make path configurable --- Makefile | 2 +- include/config.h | 2 ++ src/config.c | 5 +++++ src/mainx.c | 16 +++++++++------- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 8666d9ef..850693e3 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ install: all $(INSTALL) -m 0755 i3 $(DESTDIR)/usr/bin/ test -e $(DESTDIR)/etc/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)/etc/i3/config $(INSTALL) -m 0644 i3.desktop $(DESTDIR)/usr/share/xsessions/ - $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg + $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg install dist: clean [ ! -d i3-${VERSION} ] || rm -rf i3-${VERSION} diff --git a/include/config.h b/include/config.h index 80fa2f02..cfedfdb8 100644 --- a/include/config.h +++ b/include/config.h @@ -53,6 +53,8 @@ struct Config { const char *terminal; const char *font; + const char *ipc_socket_path; + /** The modifier which needs to be pressed in combination with your mouse * buttons to do things with floating windows (move, resize) */ uint32_t floating_modifier; diff --git a/src/config.c b/src/config.c index fc5fa647..2f78dec9 100644 --- a/src/config.c +++ b/src/config.c @@ -408,6 +408,11 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, continue; } + if (strcasecmp(key, "ipc-socket") == 0) { + config.ipc_socket_path = sstrdup(value); + continue; + } + die("Unknown configfile option: %s\n", key); } /* now grab all keys again */ diff --git a/src/mainx.c b/src/mainx.c index 684526c6..03cb9d1b 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -370,13 +370,15 @@ int main(int argc, char *argv[], char *env[]) { } /* Create the UNIX domain socket for IPC */ - int ipc_socket = ipc_create_socket("/tmp/i3.s"); - if (ipc_socket == -1) { - LOG("Could not create the IPC socket, IPC disabled\n"); - } else { - struct ev_io *ipc_io = scalloc(sizeof(struct ev_io)); - ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ); - ev_io_start(loop, ipc_io); + if (config.ipc_socket_path != NULL) { + int ipc_socket = ipc_create_socket(config.ipc_socket_path); + if (ipc_socket == -1) { + LOG("Could not create the IPC socket, IPC disabled\n"); + } else { + struct ev_io *ipc_io = scalloc(sizeof(struct ev_io)); + ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ); + ev_io_start(loop, ipc_io); + } } /* Handle the events which arrived until now */ From 188629ddcd39d63b4708d1768065c4d8c9890ae7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 2 Aug 2009 20:06:09 +0200 Subject: [PATCH 23/35] update debian changelog --- debian/changelog | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/debian/changelog b/debian/changelog index 17ece2ba..8bb2c846 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,15 @@ +i3-wm (3.c-0) unstable; urgency=low + + * Implement a reload command + * Implement IPC via unix sockets + * Optimization: Render stack windows on pixmaps to reduce flickering + * Optimization: Directly position new windows to their final position + * Bugfix: Repeatedly try to find screens if none are available + * Bugfix: Correctly redecorate clients when changing focus + * Bugfix: Don’t crash when clients reconfigure themselves + + -- Michael Stapelberg Sun, 02 Aug 2009 20:05:58 +0200 + i3-wm (3.b-1) unstable; urgency=low * Bugfix: Correctly handle col-/rowspanned containers when setting focus. From a753684ac5cfe413324e707fe2aa623ea4135cc7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 2 Aug 2009 21:32:35 +0200 Subject: [PATCH 24/35] cache text_width for named workspaces, fix memory leak --- include/data.h | 3 +++ src/layout.c | 9 +++------ src/workspace.c | 4 ++++ src/xcb.c | 2 ++ 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/include/data.h b/include/data.h index 5bca3ae7..4ced21f8 100644 --- a/include/data.h +++ b/include/data.h @@ -171,6 +171,9 @@ struct Workspace { /** Length of the workspace’s name (in glyphs) */ int name_len; + /** Width of the workspace’s name (in pixels) rendered in config.font */ + int text_width; + /** x, y, width, height */ Rect rect; diff --git a/src/layout.c b/src/layout.c index 2dbcf71c..d094ca42 100644 --- a/src/layout.c +++ b/src/layout.c @@ -436,21 +436,18 @@ static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int wid &(config.bar.unfocused)); Workspace *ws = &workspaces[c]; - /* Calculate the length of a string in a given font */ - int text_width = predict_text_width(conn, config.font, ws->name, ws->name_len); - /* Draw the outer rect */ xcb_draw_rect(conn, screen->bar, screen->bargc, color->border, drawn, /* x */ 1, /* y */ - text_width + 5 + 5, /* width = text width + 5 px left + 5px right */ + ws->text_width + 5 + 5, /* width = text width + 5 px left + 5px right */ height - 2 /* height = max. height - 1 px upper and 1 px bottom border */); /* Draw the background of this rect */ xcb_draw_rect(conn, screen->bar, screen->bargc, color->background, drawn + 1, 2, - text_width + 4 + 4, + ws->text_width + 4 + 4, height - 4); xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, color->text); @@ -458,7 +455,7 @@ static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int wid xcb_image_text_16(conn, ws->name_len, screen->bar, screen->bargc, drawn + 5 /* X */, font->height + 1 /* Y = baseline of font */, (xcb_char2b_t*)ws->name); - drawn += text_width + 12; + drawn += ws->text_width + 12; } LOG("done rendering internal\n"); diff --git a/src/workspace.c b/src/workspace.c index 40a5692a..d3de73e4 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -16,6 +16,9 @@ #include "util.h" #include "data.h" +#include "i3.h" +#include "config.h" +#include "xcb.h" /* * Sets the name (or just its number) for the given workspace. This has to @@ -38,6 +41,7 @@ void workspace_set_name(Workspace *ws, const char *name) { FREE(ws->name); ws->name = convert_utf8_to_ucs2(label, &(ws->name_len)); + ws->text_width = predict_text_width(global_conn, config.font, ws->name, ws->name_len); free(label); } diff --git a/src/xcb.c b/src/xcb.c index ff0c32d0..3f5d4280 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -357,5 +357,7 @@ int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *t width += info->character_width; } + free(font_info); + return width; } From 7cfe5207553feefd6fc4597a73c2af15b61bffb7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 2 Aug 2009 22:31:52 +0200 Subject: [PATCH 25/35] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20hide=20assigned?= =?UTF-8?q?=20clients=20to=20inactive=20but=20visible=20workspaces=20(Than?= =?UTF-8?q?ks=20xeen)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/workspace.h | 8 ++++++++ src/commands.c | 13 +++++-------- src/manage.c | 5 +++-- src/workspace.c | 10 ++++++++++ 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/include/workspace.h b/include/workspace.h index 2b41a527..9c69e55b 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -24,4 +24,12 @@ */ void workspace_set_name(Workspace *ws, const char *name); +/** + * Returns true if the workspace is currently visible. Especially important for + * multi-monitor environments, as they can have multiple currenlty active + * workspaces. + * + */ +bool workspace_is_visible(Workspace *ws); + #endif diff --git a/src/commands.c b/src/commands.c index 74f8f2a6..16b2152a 100644 --- a/src/commands.c +++ b/src/commands.c @@ -27,6 +27,7 @@ #include "floating.h" #include "xcb.h" #include "config.h" +#include "workspace.h" bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) { /* If this container is empty, we’re done */ @@ -490,10 +491,8 @@ 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 (target_invisible) { + if (!workspace_is_visible(t_ws)) { LOG("This workspace is not visible, unmapping\n"); xcb_unmap_window(conn, client->frame); } else { @@ -515,7 +514,7 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl render_layout(conn); - if (!target_invisible) + if (workspace_is_visible(t_ws)) set_focus(conn, client, true); } @@ -575,10 +574,8 @@ 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 (target_invisible) { + if (!workspace_is_visible(to_container->workspace)) { LOG("This workspace is not visible, unmapping\n"); xcb_unmap_window(conn, current_client->frame); } else { @@ -593,7 +590,7 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa render_layout(conn); - if (!target_invisible) + if (workspace_is_visible(to_container->workspace)) set_focus(conn, current_client, true); } diff --git a/src/manage.c b/src/manage.c index 5d246833..12d96c8c 100644 --- a/src/manage.c +++ b/src/manage.c @@ -29,6 +29,7 @@ #include "manage.h" #include "floating.h" #include "client.h" +#include "workspace.h" /* * Go through all existing windows (if the window manager is restarted) and manage them @@ -325,7 +326,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, break; } - LOG("Changin container/workspace and unmapping the client\n"); + LOG("Changing 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); @@ -338,7 +339,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, new->workspace = t_ws; old_focused = new->container->currently_focused; - map_frame = false; + map_frame = workspace_is_visible(t_ws); break; } } diff --git a/src/workspace.c b/src/workspace.c index d3de73e4..1e2aaf4f 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -45,3 +45,13 @@ void workspace_set_name(Workspace *ws, const char *name) { free(label); } + +/* + * Returns true if the workspace is currently visible. Especially important for + * multi-monitor environments, as they can have multiple currenlty active + * workspaces. + * + */ +bool workspace_is_visible(Workspace *ws) { + return (ws->screen->current_workspace == ws->num); +} From 19abb63393ff2a87dbde5aa4cd3554edf7204bde Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 2 Aug 2009 22:58:23 +0200 Subject: [PATCH 26/35] s/Mod1/floating_modifier (Thanks badboy) --- src/handlers.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers.c b/src/handlers.c index ec02180d..cf56ab6d 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -308,7 +308,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ 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"); + LOG("Not handling, floating_modifier was pressed and no client found\n"); return 1; } if (client_is_floating(client)) { From 1aeaa153e5ebb277d75020fbdef590425e047950 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 4 Aug 2009 01:40:05 +0200 Subject: [PATCH 27/35] Add logo to git. Thanks to steckdenis, yellowiscool and farvardin! --- logo.svg | 555 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 555 insertions(+) create mode 100644 logo.svg diff --git a/logo.svg b/logo.svg new file mode 100644 index 00000000..11ec4648 --- /dev/null +++ b/logo.svg @@ -0,0 +1,555 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Logo I3 + + + yellowiscool, farvardin + + + + + steckdenis + + + Logo for I3, an improved dynamic tiling window manager: http://i3.zekjur.net/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 4c373c2128cbb60a6d6623f5b6a7deac498bcabc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 4 Aug 2009 22:27:01 +0200 Subject: [PATCH 28/35] Fix clicking on workspaces in internal bar (with named workspaces) (Thanks bapt) --- src/handlers.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index cf56ab6d..a667628c 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -276,16 +276,22 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e } return true; } - i3Font *font = load_font(conn, config.font); - int workspace = event->event_x / (font->height + 6), - c = 0; + int drawn = 0; /* Because workspaces can be on different screens, we need to loop through all of them and decide to count it based on its ->screen */ - for (int i = 0; i < 10; i++) - if ((workspaces[i].screen == screen) && (c++ == workspace)) { + for (int i = 0; i < 10; i++) { + if (workspaces[i].screen != screen) + continue; + LOG("Checking if click was on workspace %d with drawn = %d, tw = %d\n", + i, drawn, workspaces[i].text_width); + if (event->event_x > (drawn + 1) && + event->event_x <= (drawn + 1 + workspaces[i].text_width + 5 + 5)) { show_workspace(conn, i+1); return true; } + + drawn += workspaces[i].text_width + 5 + 5 + 2; + } return true; } From af3972aa9fd691a9723bb816a954704d50794ad6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 4 Aug 2009 22:47:42 +0200 Subject: [PATCH 29/35] Bugfix: Recognize clicks as client clicks (opposed to border_clicks) when clients send them for their parent window See comment, happened for example with xfontsel. You normally got to see the resize bar (when having >1 column, of course). --- src/handlers.c | 12 ++++++++++++ src/resize.c | 2 ++ 2 files changed, 14 insertions(+) diff --git a/src/handlers.c b/src/handlers.c index a667628c..ee47141a 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -354,6 +354,18 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ LOG("event->event_x = %d, client->rect.width = %d\n", event->event_x, client->rect.width); + /* Some clients (xfontsel for example) seem to pass clicks on their + * window to the parent window, thus we receive an event here which in + * reality is a border_click. Check for the position and fix state. */ + if (border_click && + event->event_x >= client->child_rect.x && + event->event_x <= (client->child_rect.x + client->child_rect.width) && + event->event_y >= client->child_rect.y && + event->event_y <= (client->child_rect.y + client->child_rect.height)) { + LOG("Fixing border_click = false because of click in child\n"); + border_click = false; + } + if (!border_click) { LOG("client. done.\n"); xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); diff --git a/src/resize.c b/src/resize.c index 52b064b4..09a8d083 100644 --- a/src/resize.c +++ b/src/resize.c @@ -43,6 +43,8 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i return 1; } + LOG("event->event_x = %d, event->root_x = %d\n", event->event_x, event->root_x); + LOG("Screen dimensions: (%d, %d) %d x %d\n", screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height); /* FIXME: horizontal resizing causes empty spaces to exist */ From 3114d6821dd9679f7702b90732f2648482d8cc06 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 5 Aug 2009 00:39:55 +0200 Subject: [PATCH 30/35] Add support for WM_CLIENT_LEADER, put floating windows mapping to (0x0) to center of leader/workspace --- include/data.h | 4 ++++ include/handlers.h | 9 +++++++++ include/i3.h | 2 +- include/xcb.h | 3 ++- src/handlers.c | 28 ++++++++++++++++++++++++++++ src/mainx.c | 5 +++++ src/manage.c | 38 ++++++++++++++++++++++++++++++++++---- 7 files changed, 83 insertions(+), 6 deletions(-) diff --git a/include/data.h b/include/data.h index 4ced21f8..99e79ab2 100644 --- a/include/data.h +++ b/include/data.h @@ -341,6 +341,10 @@ struct Client { /** Holds the WM_CLASS, useful for matching the client in commands */ char *window_class; + /** Holds the xcb_window_t (just an ID) for the leader window (logical + * parent for toolwindows and similar floating windows) */ + xcb_window_t leader; + /** fullscreen is pretty obvious */ bool fullscreen; diff --git a/include/handlers.h b/include/handlers.h index 74e29e4b..83b70894 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -154,4 +154,13 @@ 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); +/** + * Handles changes of the WM_CLIENT_LEADER atom which specifies if this is a + * toolwindow (or similar) and to which window it belongs (logical parent). + * + */ +int handle_clientleader_change(void *data, xcb_connection_t *conn, + uint8_t state, xcb_window_t window, + xcb_atom_t name, xcb_get_property_reply_t *prop); + #endif diff --git a/include/i3.h b/include/i3.h index 18844e09..46caa415 100644 --- a/include/i3.h +++ b/include/i3.h @@ -20,7 +20,7 @@ #ifndef _I3_H #define _I3_H -#define NUM_ATOMS 17 +#define NUM_ATOMS 18 extern xcb_connection_t *global_conn; extern char **start_argv; diff --git a/include/xcb.h b/include/xcb.h index 6ae49107..d01f6da1 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -60,7 +60,8 @@ enum { _NET_SUPPORTED = 0, WM_PROTOCOLS, WM_DELETE_WINDOW, UTF8_STRING, - WM_STATE + WM_STATE, + WM_CLIENT_LEADER }; extern unsigned int xcb_numlock_mask; diff --git a/src/handlers.c b/src/handlers.c index ee47141a..9a402629 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -1064,3 +1064,31 @@ int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_ return 1; } + +/* + * Handles changes of the WM_CLIENT_LEADER atom which specifies if this is a + * toolwindow (or similar) and to which window it belongs (logical parent). + * + */ +int handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, + xcb_atom_t name, xcb_get_property_reply_t *prop) { + LOG("client leader changed\n"); + if (prop == NULL) { + prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn, + false, window, WM_CLIENT_LEADER, WINDOW, 0, 32), NULL); + } + + Client *client = table_get(&by_child, window); + if (client == NULL) + return 1; + + xcb_window_t *leader = xcb_get_property_value(prop); + if (leader == NULL) + return 1; + + LOG("changed to %08x\n", *leader); + + client->leader = *leader; + + return 1; +} diff --git a/src/mainx.c b/src/mainx.c index 03cb9d1b..27e026b5 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -178,6 +178,7 @@ int main(int argc, char *argv[], char *env[]) { REQUEST_ATOM(WM_DELETE_WINDOW); REQUEST_ATOM(UTF8_STRING); REQUEST_ATOM(WM_STATE); + REQUEST_ATOM(WM_CLIENT_LEADER); /* TODO: this has to be more beautiful somewhen */ int major, minor, error; @@ -308,6 +309,7 @@ int main(int argc, char *argv[], char *env[]) { GET_ATOM(WM_DELETE_WINDOW); GET_ATOM(UTF8_STRING); GET_ATOM(WM_STATE); + GET_ATOM(WM_CLIENT_LEADER); xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, handle_window_type, NULL); /* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */ @@ -324,6 +326,9 @@ int main(int argc, char *argv[], char *env[]) { /* Watch WM_CLASS (= class of the window) */ xcb_property_set_handler(&prophs, WM_CLASS, 128, handle_windowclass_change, NULL); + /* Watch WM_CLIENT_LEADER (= logical parent window for toolbars etc.) */ + xcb_property_set_handler(&prophs, atoms[WM_CLIENT_LEADER], UINT_MAX, handle_clientleader_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"); diff --git a/src/manage.c b/src/manage.c index 12d96c8c..ba1e6cb1 100644 --- a/src/manage.c +++ b/src/manage.c @@ -111,6 +111,7 @@ void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, 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[WM_CLIENT_LEADER]); xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]); free(geom); @@ -131,7 +132,8 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, 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; + utf8_title_cookie, title_cookie, + class_cookie, leader_cookie; uint32_t mask = 0; uint32_t values[3]; uint16_t original_height = height; @@ -147,8 +149,9 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, 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); + leader_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[WM_CLIENT_LEADER], UINT32_MAX); 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); + class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128); Client *new = table_get(&by_child, child); @@ -253,6 +256,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, 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 */ + new->floating = FLOATING_AUTO_OFF; break; } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] || atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] || @@ -264,6 +268,12 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, } } + /* All clients which have a leader should be floating */ + if (!new->dock && !client_is_floating(new) && new->leader != 0) { + LOG("Client has WM_CLIENT_LEADER hint set, setting floating\n"); + new->floating = FLOATING_AUTO_ON; + } + if (new->workspace->auto_float) { new->floating = FLOATING_AUTO_ON; LOG("workspace is in autofloat mode, setting floating\n"); @@ -304,6 +314,9 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, preply = xcb_get_property_reply(conn, class_cookie, NULL); handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply); + preply = xcb_get_property_reply(conn, leader_cookie, NULL); + handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply); + LOG("DEBUG: should have all infos now\n"); struct Assignment *assign; TAILQ_FOREACH(assign, &assignments, assignments) { @@ -375,10 +388,27 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, new->container = NULL; - new->floating_rect.x = new->rect.x = x; - new->floating_rect.y = new->rect.y = y; new->rect.width = new->floating_rect.width + 2 + 2; new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2; + + /* Some clients (like GIMP’s color picker window) get mapped + * to (0, 0), so we push them to a reasonable position + * (centered over their leader) */ + if (new->leader != 0 && x == 0 && y == 0) { + LOG("Floating client wants to (0x0), moving it over its leader instead\n"); + Client *leader = table_get(&by_child, new->leader); + if (leader == NULL) { + LOG("leader is NULL, centering it over current workspace\n"); + + x = c_ws->rect.x + (c_ws->rect.width / 2) - (new->rect.width / 2); + y = c_ws->rect.y + (c_ws->rect.height / 2) - (new->rect.height / 2); + } else { + x = leader->rect.x + (leader->rect.width / 2) - (new->rect.width / 2); + y = leader->rect.y + (leader->rect.height / 2) - (new->rect.height / 2); + } + } + new->floating_rect.x = new->rect.x = x; + new->floating_rect.y = new->rect.y = y; LOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n", new->floating_rect.x, new->floating_rect.y, new->floating_rect.width, new->floating_rect.height); From 13c481c9f5a0a40aaf5a387139a853cdb962ad62 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 5 Aug 2009 14:38:55 +0200 Subject: [PATCH 31/35] i3-msg: Fix compilation warning (Thanks mxf) --- i3-msg/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i3-msg/main.c b/i3-msg/main.c index ebeca7d5..197cceb6 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -102,7 +102,7 @@ int main(int argc, char *argv[]) { memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_LOCAL; strcpy(addr.sun_path, socket_path); - if (connect(sockfd, &addr, sizeof(struct sockaddr_un)) < 0) + if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) err(EXIT_FAILURE, "Could not connect to i3"); ipc_send_message(sockfd, strlen(argv[optind]), 0, (uint8_t*)argv[optind]); From 9222bea3b222ff425077cef3cee56e2d6278ebc4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 5 Aug 2009 18:33:44 +0200 Subject: [PATCH 32/35] Implement borderless / 1-px-bordered windows Use bn (normal), bp (1-px), bb (borderless) as commands to change the border style of the currently focused window. Feel free to use i3-msg to do this. --- include/client.h | 7 +++++++ include/data.h | 4 ++++ src/client.c | 39 +++++++++++++++++++++++++++++++++++++++ src/commands.c | 10 ++++++++++ src/layout.c | 17 ++++++++++++----- 5 files changed, 72 insertions(+), 5 deletions(-) diff --git a/include/client.h b/include/client.h index 8fbd4434..5d87b2ee 100644 --- a/include/client.h +++ b/include/client.h @@ -78,4 +78,11 @@ void client_set_below_floating(xcb_connection_t *conn, Client *client); */ bool client_is_floating(Client *client); +/** + * Change the border type for the given client to normal (n), 1px border (p) or + * completely borderless (b). + * + */ +void client_change_border(xcb_connection_t *conn, Client *client, char border_type); + #endif diff --git a/include/data.h b/include/data.h index 99e79ab2..c2d83783 100644 --- a/include/data.h +++ b/include/data.h @@ -360,6 +360,10 @@ struct Client { * initialization later */ enum { TITLEBAR_TOP = 0, TITLEBAR_LEFT, TITLEBAR_RIGHT, TITLEBAR_BOTTOM, TITLEBAR_OFF } titlebar_position; + /** Contains a bool specifying whether this window should not be drawn + * with the usual decorations */ + bool borderless; + /** If a client is set as a dock, it is placed at the very bottom of * the screen and its requested size is used */ bool dock; diff --git a/src/client.c b/src/client.c index c3a80c36..81430b2f 100644 --- a/src/client.c +++ b/src/client.c @@ -248,3 +248,42 @@ void client_set_below_floating(xcb_connection_t *conn, Client *client) { bool client_is_floating(Client *client) { return (client->floating >= FLOATING_AUTO_ON); } + +/* + * Change the border type for the given client to normal (n), 1px border (p) or + * completely borderless (b). + * + */ +void client_change_border(xcb_connection_t *conn, Client *client, char border_type) { + switch (border_type) { + case 'n': + LOG("Changing to normal border\n"); + client->titlebar_position = TITLEBAR_TOP; + client->borderless = false; + break; + case 'p': + LOG("Changing to 1px border\n"); + client->titlebar_position = TITLEBAR_OFF; + client->borderless = false; + break; + case 'b': + LOG("Changing to borderless\n"); + client->titlebar_position = TITLEBAR_OFF; + client->borderless = true; + break; + default: + LOG("Unknown border mode\n"); + return; + } + + /* Ensure that the child’s position inside our window gets updated */ + client->force_reconfigure = true; + + /* For clients inside a container, we can simply render the container. + * If the client is floating, we need to render the whole layout */ + if (client->container != NULL) + render_container(conn, client->container); + else render_layout(conn); + + redecorate_window(conn, client); +} diff --git a/src/commands.c b/src/commands.c index 16b2152a..69a6de52 100644 --- a/src/commands.c +++ b/src/commands.c @@ -967,6 +967,16 @@ void parse_command(xcb_connection_t *conn, const char *command) { return; } + /* Is it 'bn' (border normal), 'bp' (border 1pixel) or 'bb' (border borderless)? */ + if (command[0] == 'b') { + if (last_focused == NULL) { + LOG("No window focused, cannot change border type\n"); + return; + } + client_change_border(conn, last_focused, command[1]); + return; + } + if (command[0] == 'H') { LOG("Hiding all floating windows\n"); floating_toggle_hide(conn, c_ws); diff --git a/src/layout.c b/src/layout.c index d094ca42..44ce1b1d 100644 --- a/src/layout.c +++ b/src/layout.c @@ -145,10 +145,12 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect); } - /* 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 - 3, offset + font->height + 3); + if (client->titlebar_position != TITLEBAR_OFF) { + /* 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 - 3, offset + font->height + 3); + } /* If the client has a title, we draw it */ if (client->name != NULL) { @@ -227,11 +229,16 @@ void resize_client(xcb_connection_t *conn, Client *client) { rect->height = client->rect.height - 2; break; default: - if (client->titlebar_position == TITLEBAR_OFF) { + if (client->titlebar_position == TITLEBAR_OFF && client->borderless) { rect->x = 0; rect->y = 0; rect->width = client->rect.width; rect->height = client->rect.height; + } else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) { + rect->x = 1; + rect->y = 1; + rect->width = client->rect.width - 1 - 1; + rect->height = client->rect.height - 1 - 1; } else { rect->x = 2; rect->y = font->height + 2 + 2; From 22e4f0355370bf0ba37cbb3140a096ca085d4c99 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 5 Aug 2009 19:24:21 +0200 Subject: [PATCH 33/35] Implement ws (with screen) to focus the next screen (wsl for example) --- src/commands.c | 65 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/src/commands.c b/src/commands.c index 69a6de52..fce3ae65 100644 --- a/src/commands.c +++ b/src/commands.c @@ -28,6 +28,7 @@ #include "xcb.h" #include "config.h" #include "workspace.h" +#include "commands.h" bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) { /* If this container is empty, we’re done */ @@ -55,7 +56,7 @@ bool focus_window_in_container(xcb_connection_t *conn, Container *container, dir return true; } -typedef enum { THING_WINDOW, THING_CONTAINER } thing_t; +typedef enum { THING_WINDOW, THING_CONTAINER, THING_SCREEN } thing_t; static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t thing) { LOG("focusing direction %d\n", direction); @@ -81,6 +82,41 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t return; } + /* For focusing screens, situation is different: we get the rect + * of the current screen, then get the screen which is on its + * right/left/bottom/top and just switch to the workspace on + * the target screen. */ + if (thing == THING_SCREEN) { + i3Screen *cs = c_ws->screen; + assert(cs != NULL); + Rect bounds = cs->rect; + + if (direction == D_RIGHT) + bounds.x += bounds.width; + else if (direction == D_LEFT) + bounds.x -= bounds.width; + else if (direction == D_UP) + bounds.y -= bounds.height; + else bounds.y += bounds.height; + + i3Screen *target = get_screen_containing(bounds.x, bounds.y); + if (target == NULL) { + LOG("Target screen NULL\n"); + /* Wrap around if the target screen is out of bounds */ + if (direction == D_RIGHT) + target = get_screen_most(D_LEFT); + else if (direction == D_LEFT) + target = get_screen_most(D_RIGHT); + else if (direction == D_UP) + target = get_screen_most(D_DOWN); + else target = get_screen_most(D_UP); + } + + LOG("Switching to ws %d\n", target->current_workspace + 1); + show_workspace(conn, target->current_workspace + 1); + return; + } + /* TODO: for horizontal default layout, this has to be expanded to LEFT/RIGHT */ if (direction == D_UP || direction == D_DOWN) { if (thing == THING_WINDOW) @@ -983,7 +1019,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { return; } - enum { WITH_WINDOW, WITH_CONTAINER, WITH_WORKSPACE } with = WITH_WINDOW; + enum { WITH_WINDOW, WITH_CONTAINER, WITH_WORKSPACE, WITH_SCREEN } with = WITH_WINDOW; /* Is it a ? */ if (command[0] == 'w') { @@ -995,6 +1031,9 @@ void parse_command(xcb_connection_t *conn, const char *command) { } else if (command[0] == 'w') { with = WITH_WORKSPACE; command++; + } else if (command[0] == 's') { + with = WITH_SCREEN; + command++; } else { LOG("not yet implemented.\n"); return; @@ -1030,12 +1069,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { } /* Is it 'n' or 'p' for next/previous workspace? (nw) */ - if (command[0] == 'n' && command[1] == 'w') { - next_previous_workspace(conn, command[0]); - return; - } - - if (command[0] == 'p' && command[1] == 'w') { + if ((command[0] == 'n' || command[0] == 'p') && command[1] == 'w') { next_previous_workspace(conn, command[0]); return; } @@ -1103,6 +1137,10 @@ void parse_command(xcb_connection_t *conn, const char *command) { rest++; if (action == ACTION_FOCUS) { + if (with == WITH_SCREEN) { + focus_thing(conn, direction, THING_SCREEN); + continue; + } if (client_is_floating(last_focused)) { floating_focus_direction(conn, last_focused, direction); continue; @@ -1112,6 +1150,13 @@ void parse_command(xcb_connection_t *conn, const char *command) { } if (action == ACTION_MOVE) { + if (with == WITH_SCREEN) { + /* TODO: this should swap the screen’s contents + * (e.g. all workspaces) with the next/previous/… + * screen */ + LOG("Not yet implemented\n"); + continue; + } if (client_is_floating(last_focused)) { floating_move(conn, last_focused, direction); continue; @@ -1123,6 +1168,10 @@ void parse_command(xcb_connection_t *conn, const char *command) { } if (action == ACTION_SNAP) { + if (with == WITH_SCREEN) { + LOG("You cannot snap a screen (it makes no sense).\n"); + continue; + } snap_current_container(conn, direction); continue; } From 78b9e7f5ce9ecf3b4f5ef9f3b30245e1fe8271b8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 5 Aug 2009 19:28:21 +0200 Subject: [PATCH 34/35] Bugfix: dock clients need to have borderless = true --- src/manage.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/manage.c b/src/manage.c index ba1e6cb1..70d12488 100644 --- a/src/manage.c +++ b/src/manage.c @@ -251,6 +251,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) { LOG("Window is a dock.\n"); new->dock = true; + new->borderless = true; new->titlebar_position = TITLEBAR_OFF; new->force_reconfigure = true; new->container = NULL; From 5d14dca41d4b1a4a494f7481957ee7d1b6108254 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 5 Aug 2009 19:59:58 +0200 Subject: [PATCH 35/35] Implement correct rendering of floating windows (decoration color) --- src/layout.c | 20 ++++++++++++++------ src/util.c | 10 +++++----- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/layout.c b/src/layout.c index 44ce1b1d..9cc41a0c 100644 --- a/src/layout.c +++ b/src/layout.c @@ -103,19 +103,27 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw i3Font *font = load_font(conn, config.font); int decoration_height = font->height + 2 + 2; struct Colortriple *color; + Client *last_focused; /* Clients without a container (docks) won’t get decorated */ if (client->dock) return; LOG("redecorating child %08x\n", client->child); - if (client_is_floating(client) || client->container->currently_focused == client) { - /* Distinguish if the window is currently focused… */ - if (client_is_floating(client) || CUR_CELL->currently_focused == client) + last_focused = SLIST_FIRST(&(client->workspace->focus_stack)); + if (client_is_floating(client)) { + if (last_focused == client) color = &(config.client.focused); - /* …or if it is the focused window in a not focused container */ - else color = &(config.client.focused_inactive); - } else color = &(config.client.unfocused); + else color = &(config.client.unfocused); + } else { + if (client->container->currently_focused == client) { + /* Distinguish if the window is currently focused… */ + if (last_focused == client) + color = &(config.client.focused); + /* …or if it is the focused window in a not focused container */ + else color = &(config.client.focused_inactive); + } else color = &(config.client.unfocused); + } /* Our plan is the following: - Draw a rect around the whole client in color->background diff --git a/src/util.c b/src/util.c index ff98bb31..d09dafe2 100644 --- a/src/util.c +++ b/src/util.c @@ -348,11 +348,6 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) { 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); - /* If the last client was a floating client, we need to go to the next * tiling client in stack and re-decorate it. */ if (old_client != NULL && client_is_floating(old_client)) { @@ -372,6 +367,11 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) { SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); SLIST_INSERT_HEAD(&(client->workspace->focus_stack), client, focus_clients); + /* 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); + /* redecorate_window flushes, so we don’t need to */ redecorate_window(conn, client); }