From 47ebb9e1ddccae90002749e3bdd71f5ac44f4626 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 10 Nov 2009 00:07:40 +0100 Subject: [PATCH 001/247] Fix compilation warning on OpenBSD (Thanks bapt) --- src/util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util.c b/src/util.c index 149af2da..f17bd6e1 100644 --- a/src/util.c +++ b/src/util.c @@ -148,7 +148,7 @@ void start_application(const char *command) { shell = "/bin/sh"; /* This is the child */ - execl(shell, shell, "-c", command, NULL); + execl(shell, shell, "-c", command, (void*)NULL); /* not reached */ } exit(0); From 6779a0e27fb724a16fc676f9f9171d6ea7f6d723 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 10 Nov 2009 14:37:51 +0100 Subject: [PATCH 002/247] Makefile: Use POSIX-compliant method to delete multiple files (for-loop) --- man/Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/man/Makefile b/man/Makefile index 2bf15fc5..151b9abc 100644 --- a/man/Makefile +++ b/man/Makefile @@ -3,4 +3,7 @@ all: a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3-msg.man a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3-input.man clean: - rm -f i3.{1,html,xml} i3-msg.{1,html,xml} i3-input.{1,html,xml} + for file in "i3 i3-msg i3-input"; \ + do \ + rm -f $${file}.1 $${file}.html $${file}.xml; \ + done From 4b1bb7d19a16147f015308f47c4a050ffecefefb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 12 Nov 2009 20:28:09 +0100 Subject: [PATCH 003/247] Add ctrl as synonym for control --- src/cfgparse.l | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cfgparse.l b/src/cfgparse.l index a80329fd..4b706d66 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -65,6 +65,7 @@ Mod4 { yylval.number = BIND_MOD4; return MODIFIER; } Mod5 { yylval.number = BIND_MOD5; return MODIFIER; } Mode_switch { yylval.number = BIND_MODE_SWITCH; return MODIFIER; } control { return TOKCONTROL; } +ctrl { return TOKCONTROL; } shift { return TOKSHIFT; } → { return TOKARROW; } \n /* ignore end of line */; From c0c4dd2978a4bbcb0af72e3b59b141281498d912 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 13 Nov 2009 00:30:42 +0100 Subject: [PATCH 004/247] Disable XKB instead of quitting with an error (Thanks sur5r) This is necessary for running i3 in Xvnc for example. --- include/i3.h | 1 + src/handlers.c | 14 ++++++++------ src/mainx.c | 45 ++++++++++++++++++++++++++------------------- 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/include/i3.h b/include/i3.h index e34c5da3..fda6cfd8 100644 --- a/include/i3.h +++ b/include/i3.h @@ -34,6 +34,7 @@ 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 bool xkb_supported; extern xcb_atom_t atoms[NUM_ATOMS]; extern xcb_window_t root; diff --git a/src/handlers.c b/src/handlers.c index 5b9ab8bc..34c4aaba 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -105,12 +105,14 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_ state_filtered &= 0xFF; LOG("(removed upper 8 bits, state = %d)\n", state_filtered); - /* We need to get the keysym group (There are group 1 to group 4, each holding - two keysyms (without shift and with shift) using Xkb because X fails to - provide them reliably (it works in Xephyr, it does not in real X) */ - XkbStateRec state; - if (XkbGetState(xkbdpy, XkbUseCoreKbd, &state) == Success && (state.group+1) == 2) - state_filtered |= BIND_MODE_SWITCH; + if (xkb_supported) { + /* We need to get the keysym group (There are group 1 to group 4, each holding + two keysyms (without shift and with shift) using Xkb because X fails to + provide them reliably (it works in Xephyr, it does not in real X) */ + XkbStateRec state; + if (XkbGetState(xkbdpy, XkbUseCoreKbd, &state) == Success && (state.group+1) == 2) + state_filtered |= BIND_MODE_SWITCH; + } LOG("(checked mode_switch, state %d)\n", state_filtered); diff --git a/src/mainx.c b/src/mainx.c index 322e51e2..9d209208 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -83,6 +83,9 @@ int num_screens = 0; /* The depth of the root screen (used e.g. for creating new pixmaps later) */ uint8_t root_depth; +/* We hope that XKB is supported and set this to false */ +bool xkb_supported = true; + /* * 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 @@ -245,25 +248,27 @@ int main(int argc, char *argv[], char *env[]) { int evBase, errBase; if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) { - fprintf(stderr, "XkbOpenDisplay() failed\n"); - return 1; + LOG("ERROR: XkbOpenDisplay() failed, disabling XKB support\n"); + xkb_supported = false; } - if (fcntl(ConnectionNumber(xkbdpy), F_SETFD, FD_CLOEXEC) == -1) { - fprintf(stderr, "Could not set FD_CLOEXEC on xkbdpy\n"); - return 1; - } + if (xkb_supported) { + if (fcntl(ConnectionNumber(xkbdpy), F_SETFD, FD_CLOEXEC) == -1) { + fprintf(stderr, "Could not set FD_CLOEXEC on xkbdpy\n"); + return 1; + } - int i1; - if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) { - fprintf(stderr, "XKB not supported by X-server\n"); - return 1; - } - /* end of ugliness */ + int i1; + if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) { + fprintf(stderr, "XKB not supported by X-server\n"); + return 1; + } + /* end of ugliness */ - if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd, XkbMapNotifyMask, XkbMapNotifyMask)) { - fprintf(stderr, "Could not set XKB event mask\n"); - return 1; + if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd, XkbMapNotifyMask, XkbMapNotifyMask)) { + fprintf(stderr, "Could not set XKB event mask\n"); + return 1; + } } /* Initialize event loop using libev */ @@ -279,11 +284,13 @@ int main(int argc, char *argv[], char *env[]) { ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ); ev_io_start(loop, xcb_watcher); - ev_io_init(xkb, xkb_got_event, ConnectionNumber(xkbdpy), EV_READ); - ev_io_start(loop, xkb); + if (xkb_supported) { + ev_io_init(xkb, xkb_got_event, ConnectionNumber(xkbdpy), EV_READ); + ev_io_start(loop, xkb); - /* Flush the buffer so that libev can properly get new events */ - XFlush(xkbdpy); + /* Flush the buffer so that libev can properly get new events */ + XFlush(xkbdpy); + } ev_check_init(xcb_check, xcb_check_cb); ev_check_start(loop, xcb_check); From 51b937741d147ed6f78bc8e27d6293cb4a08542f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 13 Nov 2009 19:46:07 +0100 Subject: [PATCH 005/247] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20draw=20window?= =?UTF-8?q?=20title=20when=20titlebar=20is=20disabled=20(Thanks=20msi)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layout.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layout.c b/src/layout.c index 24cafb72..4e06c44b 100644 --- a/src/layout.c +++ b/src/layout.c @@ -199,7 +199,7 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw } /* If the client has a title, we draw it */ - if (client->name != NULL) { + if (client->name != NULL && client->titlebar_position != TITLEBAR_OFF) { /* Draw the font */ uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT; uint32_t values[] = { color->text, color->background, font->id }; From d48a5157521ec015e7423639c8ddc6deec5a4c54 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 13 Nov 2009 19:55:34 +0100 Subject: [PATCH 006/247] Bugfix: Correctly switch border types for floating windows (Thanks msi) --- src/client.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/client.c b/src/client.c index 215140c2..c0031d71 100644 --- a/src/client.c +++ b/src/client.c @@ -297,11 +297,16 @@ void client_change_border(xcb_connection_t *conn, Client *client, char border_ty /* 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 */ + /* For clients inside a container, we can simply render the container */ if (client->container != NULL) render_container(conn, client->container); - else render_layout(conn); + else { + /* If the client is floating, directly push its size */ + if (client_is_floating(client)) + resize_client(conn, client); + /* Otherwise, it may be a dock client, thus render the whole layout */ + else render_layout(conn); + } redecorate_window(conn, client); } From aa2e0d7d1404e9a185cd1e60fe466f058b7e46f4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 13 Nov 2009 20:22:23 +0100 Subject: [PATCH 007/247] Bugfix: Correctly replay pointer if the click handler does not trigger (Thanks merovius) --- src/click.c | 146 +++++++++++++++++++++++++++++----------------------- 1 file changed, 83 insertions(+), 63 deletions(-) diff --git a/src/click.c b/src/click.c index 43452816..efb26ad3 100644 --- a/src/click.c +++ b/src/click.c @@ -174,6 +174,82 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e return false; } +/* + * Called when the user clicks using the floating_modifier, but the client is in + * tiling layout. + * + * Returns false if it does not do anything (that is, the click should be sent + * to the client). + * + */ +static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client, + xcb_button_press_event_t *event) { + /* Only the right mouse button is interesting for us at the moment */ + if (event->detail != 3) + return false; + + /* The client is in tiling layout. We can still + * initiate a resize with the right mouse button, + * by chosing the border which is the most near one + * to the position of the mouse pointer */ + int to_right = client->rect.width - event->event_x, + to_left = event->event_x, + to_top = event->event_y, + to_bottom = client->rect.height - event->event_y; + resize_orientation_t orientation = O_VERTICAL; + Container *con = client->container; + Workspace *ws = con->workspace; + int first = 0, second = 0; + + LOG("click was %d px to the right, %d px to the left, %d px to top, %d px to bottom\n", + to_right, to_left, to_top, to_bottom); + + if (to_right < to_left && + to_right < to_top && + to_right < to_bottom) { + /* …right border */ + first = con->col + (con->colspan - 1); + LOG("column %d\n", first); + + if (!cell_exists(first, con->row) || + (first == (ws->cols-1))) + return false; + + second = first + 1; + } else if (to_left < to_right && + to_left < to_top && + to_left < to_bottom) { + /* …left border */ + if (con->col == 0) + return false; + + first = con->col - 1; + second = con->col; + } else if (to_top < to_right && + to_top < to_left && + to_top < to_bottom) { + /* This was a press on the top border */ + if (con->row == 0) + return false; + first = con->row - 1; + second = con->row; + orientation = O_HORIZONTAL; + } else if (to_bottom < to_right && + to_bottom < to_left && + to_bottom < to_top) { + /* …bottom border */ + first = con->row + (con->rowspan - 1); + if (!cell_exists(con->col, first) || + (first == (ws->rows-1))) + return false; + + second = first + 1; + orientation = O_HORIZONTAL; + } + + return resize_graphical_handler(conn, ws, first, second, orientation, event); +} + int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) { LOG("Button %d pressed\n", event->state); /* This was either a focus for a client’s parent (= titlebar)… */ @@ -206,70 +282,14 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ floating_resize_window(conn, client, event); } return 1; - } else { - /* The client is in tiling layout. We can still - * initiate a resize with the right mouse button, - * by chosing the border which is the most near one - * to the position of the mouse pointer */ - if (event->detail == 3) { - int to_right = client->rect.width - event->event_x, - to_left = event->event_x, - to_top = event->event_y, - to_bottom = client->rect.height - event->event_y; - resize_orientation_t orientation = O_VERTICAL; - Container *con = client->container; - Workspace *ws = con->workspace; - int first = 0, second = 0; - - LOG("click was %d px to the right, %d px to the left, %d px to top, %d px to bottom\n", - to_right, to_left, to_top, to_bottom); - - if (to_right < to_left && - to_right < to_top && - to_right < to_bottom) { - /* …right border */ - first = con->col + (con->colspan - 1); - LOG("column %d\n", first); - - if (!cell_exists(first, con->row) || - (first == (ws->cols-1))) - return 1; - - second = first + 1; - } else if (to_left < to_right && - to_left < to_top && - to_left < to_bottom) { - /* …left border */ - if (con->col == 0) - return 1; - - first = con->col - 1; - second = con->col; - } else if (to_top < to_right && - to_top < to_left && - to_top < to_bottom) { - /* This was a press on the top border */ - if (con->row == 0) - return 1; - first = con->row - 1; - second = con->row; - orientation = O_HORIZONTAL; - } else if (to_bottom < to_right && - to_bottom < to_left && - to_bottom < to_top) { - /* …bottom border */ - first = con->row + (con->rowspan - 1); - if (!cell_exists(con->col, first) || - (first == (ws->rows-1))) - return 1; - - second = first + 1; - orientation = O_HORIZONTAL; - } - - return resize_graphical_handler(conn, ws, first, second, orientation, event); - } } + + if (!floating_mod_on_tiled_client(conn, client, event)) { + xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); + xcb_flush(conn); + } + + return 1; } if (client == NULL) { From d266474f972a13798866551cdb9b700d16101bd2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 13 Nov 2009 20:36:59 +0100 Subject: [PATCH 008/247] Remove the terminal option from config. The welcome message is displayed using xmessage(1), not using your terminal. Thus, it makes no sense to have this option anymore. Also, the new lex/yacc parser cannot correctly handle the situation: normal variables are expanded before parsing the file. As a replacement, you can use: set $terminal /usr/bin/urxvt --- docs/userguide | 4 ---- i3.config | 5 ----- src/cfgparse.y | 4 ++-- src/config.c | 1 - 4 files changed, 2 insertions(+), 12 deletions(-) diff --git a/docs/userguide b/docs/userguide index f7eb8ab1..03d4452c 100644 --- a/docs/userguide +++ b/docs/userguide @@ -178,10 +178,6 @@ and edit it with a text editor. === General configuration -terminal:: - Specifies the terminal emulator program you prefer. It will be started - by default when you press Mod1+Enter, but you can overwrite this. Refer - to it as +$terminal+ to keep things modular. font:: Specifies the default font you want i3 to use. Use an X core font descriptor here, like diff --git a/i3.config b/i3.config index 6b429632..24110046 100644 --- a/i3.config +++ b/i3.config @@ -1,11 +1,6 @@ # This configuration uses Mod1 and Mod3. Make sure they are mapped properly using xev(1) # and xmodmap(1). Usually, Mod1 is Alt (Alt_L) and Mod3 is Windows (Super_L) -# Tell i3 about your preferred terminal. You can refer to this as $terminal -# later. It is recommended to set this option to allow i3 to open a terminal -# containing the introduction on first start. -terminal /usr/bin/urxvt - # ISO 10646 = Unicode font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 diff --git a/src/cfgparse.y b/src/cfgparse.y index efed14e0..ff96bb5b 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -486,8 +486,8 @@ exec: terminal: TOKTERMINAL WHITESPACE STR { - config.terminal = sstrdup($3); - printf("terminal %s\n", config.terminal); + LOG("The terminal option is DEPRECATED and has no effect. " + "Please remove it from your configuration file."); } ; diff --git a/src/config.c b/src/config.c index 06953b0d..c2622730 100644 --- a/src/config.c +++ b/src/config.c @@ -597,7 +597,6 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, } } - REQUIRED_OPTION(terminal); REQUIRED_OPTION(font); /* Set an empty name for every workspace which got no name */ From fb04388289c4c1848cc06c0ea81d8e585b86e179 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 15 Nov 2009 17:35:15 +0100 Subject: [PATCH 009/247] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20hide=20window?= =?UTF-8?q?=20titles=20in=20tabbing=20mode=20(Thanks=20badboy)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layout.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/layout.c b/src/layout.c index 4e06c44b..37efdd04 100644 --- a/src/layout.c +++ b/src/layout.c @@ -199,7 +199,10 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw } /* If the client has a title, we draw it */ - if (client->name != NULL && client->titlebar_position != TITLEBAR_OFF) { + if (client->name != NULL && + ((client->container != NULL && + client->container->mode != MODE_DEFAULT) || + client->titlebar_position != TITLEBAR_OFF)) { /* Draw the font */ uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT; uint32_t values[] = { color->text, color->background, font->id }; From 6a5bdf6f8e855ff840ead6c043ffd847fa9f1430 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 18 Nov 2009 19:53:57 +0100 Subject: [PATCH 010/247] Bugfix: Also allow WORDs as workspace names (Thanks Grauwolf) --- src/cfgparse.y | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cfgparse.y b/src/cfgparse.y index ff96bb5b..0623ddde 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -412,6 +412,7 @@ optional_workspace_name: workspace_name: QUOTEDSTRING { $$ = $1; } | STR { $$ = $1; } + | WORD { $$ = $1; } ; screen: From 82c32616e1c704ddf0dfb20c9af1a345ee3f1b2e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 18 Nov 2009 20:20:54 +0100 Subject: [PATCH 011/247] Bugfix: Correctly clear the urgency hint if a window gets unmapped without clearing the hint --- src/handlers.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/handlers.c b/src/handlers.c index 34c4aaba..af541e6c 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -513,6 +513,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti if (workspace_empty) client->workspace->screen = NULL; + /* Remove the urgency flag if set */ + client->urgent = false; + workspace_update_urgent_flag(client->workspace); + FREE(client->window_class); FREE(client->name); free(client); From 0610c028d6bba1b61c688df6aa38aab8b5f7a2a8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 18 Nov 2009 20:39:53 +0100 Subject: [PATCH 012/247] Add testcase for the urgency hint Needs latest X11::XCB from git --- testcases/t/13-urgent.t | 55 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 testcases/t/13-urgent.t diff --git a/testcases/t/13-urgent.t b/testcases/t/13-urgent.t new file mode 100644 index 00000000..c23ee060 --- /dev/null +++ b/testcases/t/13-urgent.t @@ -0,0 +1,55 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# Beware that this test uses workspace 9 to perform some tests (it expects +# the workspace to be empty). +# TODO: skip it by default? + +use Test::More tests => 9; +use Test::Deep; +use X11::XCB qw(:all); +use Data::Dumper; +use Time::HiRes qw(sleep); +use FindBin; +use Digest::SHA1 qw(sha1_base64); +use lib "$FindBin::Bin/lib"; +use i3test; + +BEGIN { + use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); + use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); +} + +my $x = X11::XCB::Connection->new; + +my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); +isa_ok($sock, 'IO::Socket::UNIX'); + +# Switch to the nineth workspace +$sock->write(i3test::format_ipc_command("9")); + +sleep 0.25; + +##################################################################### +# Create two windows and put them in stacking mode +##################################################################### + +my $top = i3test::open_standard_window($x); +sleep 0.25; +my $bottom = i3test::open_standard_window($x); +sleep 0.25; + +$sock->write(i3test::format_ipc_command("s")); +sleep 0.25; + +##################################################################### +# Add the urgency hint, switch to a different workspace and back again +##################################################################### +$top->add_hint('urgency'); +sleep 1; + +$sock->write(i3test::format_ipc_command("1")); +sleep 0.25; +$sock->write(i3test::format_ipc_command("9")); +sleep 0.25; +$sock->write(i3test::format_ipc_command("1")); +sleep 1; From ff3809f388a92170964961a943b9ae51c4abbc35 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 18 Nov 2009 22:52:34 +0100 Subject: [PATCH 013/247] Add testcase for resizing of floating windows --- testcases/t/12-floating-resize.t | 83 ++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 testcases/t/12-floating-resize.t diff --git a/testcases/t/12-floating-resize.t b/testcases/t/12-floating-resize.t new file mode 100644 index 00000000..034205ec --- /dev/null +++ b/testcases/t/12-floating-resize.t @@ -0,0 +1,83 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# Beware that this test uses workspace 9 to perform some tests (it expects +# the workspace to be empty). +# TODO: skip it by default? + +use Test::More tests => 16; +use Test::Deep; +use X11::XCB qw(:all); +use Data::Dumper; +use Time::HiRes qw(sleep); +use FindBin; +use Digest::SHA1 qw(sha1_base64); +use lib "$FindBin::Bin/lib"; +use i3test; + +BEGIN { + use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); + use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); +} + +my $x = X11::XCB::Connection->new; + +my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); +isa_ok($sock, 'IO::Socket::UNIX'); + +# Switch to the nineth workspace +$sock->write(i3test::format_ipc_command("9")); + +sleep 0.25; + +##################################################################### +# Create a floating window and see if resizing works +##################################################################### + +# Create a floating window +my $window = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30], + background_color => '#C0C0C0', + # replace the type with 'utility' as soon as the coercion works again in X11::XCB + type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'), +); + +isa_ok($window, 'X11::XCB::Window'); + +$window->map; +sleep 0.25; + + +sub test_resize { + $window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 100, height => 100)); + + my ($absolute, $top) = $window->rect; + + # Make sure the width/height are different from what we’re gonna test, so + # that the test will work. + isnt($absolute->width, 300, 'width != 300'); + isnt($absolute->height, 500, 'height != 500'); + + $window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 300, height => 500)); + sleep 0.25; + + ($absolute, $top) = $window->rect; + + is($absolute->width, 300, 'width = 300'); + is($absolute->height, 500, 'height = 500'); +} + +# Test with default border +test_resize; + +# Test borderless +$sock->write(i3test::format_ipc_command("bb")); +sleep 0.25; + +test_resize; + +# Test with 1-px-border +$sock->write(i3test::format_ipc_command("bp")); +sleep 0.25; + +test_resize; From c5da7bd266391bb0be5500ddfc6f5b154852869c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 18 Nov 2009 22:53:17 +0100 Subject: [PATCH 014/247] Bugfix: Fix resizing of floating windows in borderless/1-px-border mode (Thanks Grauwolf) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Calculations were wrong (they simply didn’t take into account that there is more than one border style, the code was from before we implemented that…). We cannot directly set child_rect to the coordinates as resize_client takes rect and calculates the child_rect, so we need the new lines of code for this bugfix in any case (rect needs to be updated). --- src/handlers.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index af541e6c..56be49ce 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -342,15 +342,34 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure /* Floating clients can be reconfigured */ if (client_is_floating(client)) { i3Font *font = load_font(conn, config.font); + int mode = (client->container != NULL ? client->container->mode : MODE_DEFAULT); if (event->value_mask & XCB_CONFIG_WINDOW_X) client->rect.x = event->x; if (event->value_mask & XCB_CONFIG_WINDOW_Y) client->rect.y = event->y; - if (event->value_mask & XCB_CONFIG_WINDOW_WIDTH) - client->rect.width = event->width + 2 + 2; - if (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT) - client->rect.height = event->height + (font->height + 2 + 2) + 2; + if (event->value_mask & XCB_CONFIG_WINDOW_WIDTH) { + if (mode == MODE_STACK || mode == MODE_TABBED) { + client->rect.width = event->width + 2 + 2; + } else { + if (client->titlebar_position == TITLEBAR_OFF && client->borderless) + client->rect.width = event->width; + else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) + client->rect.width = event->width + (1 + 1); + else client->rect.width = event->width + (2 + 2); + } + } + if (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT) { + if (mode == MODE_STACK || mode == MODE_TABBED) { + client->rect.height = event->height + 2; + } else { + if (client->titlebar_position == TITLEBAR_OFF && client->borderless) + client->rect.height = event->height; + else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) + client->rect.height = event->height + (1 + 1); + else client->rect.height = event->height + (font->height + 2 + 2) + 2; + } + } LOG("Accepted new position/size for floating client: (%d, %d) size %d x %d\n", client->rect.x, client->rect.y, client->rect.width, client->rect.height); From c0256edd2ef557feaa4bb8fc156b05d2b522d20e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 20 Nov 2009 15:33:38 +0100 Subject: [PATCH 015/247] Bugfix: Accept underscores in bindsym (Thanks jace) --- src/cfgparse.l | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfgparse.l b/src/cfgparse.l index 4b706d66..0787f193 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -87,7 +87,7 @@ shift { return TOKSHIFT; } return QUOTEDSTRING; } [^ \t]+ { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR_NG; } -[a-zA-Z0-9]+ { yylval.string = strdup(yytext); return WORD; } +[a-zA-Z0-9_]+ { yylval.string = strdup(yytext); return WORD; } [a-zA-Z]+ { yylval.string = strdup(yytext); return WORD; } . { return (int)yytext[0]; } %% From 29464dc79184bce92388486fe78190de07d52d47 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 20 Nov 2009 15:55:54 +0100 Subject: [PATCH 016/247] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20set=20the=20u?= =?UTF-8?q?rgency=20flag=20if=20the=20window=20is=20currently=20active?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/handlers.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/handlers.c b/src/handlers.c index 56be49ce..6765be8a 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -952,6 +952,12 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t return 1; } + Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); + if (client == last_focused) { + LOG("Ignoring urgency flag for current client\n"); + return 1; + } + /* Update the flag on the client directly */ client->urgent = (xcb_wm_hints_get_urgency(&hints) != 0); CLIENT_LOG(client); From 245e29ef95ab8bd9b7637bf6d135e4c6eddc195f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 20 Nov 2009 15:56:18 +0100 Subject: [PATCH 017/247] Expand testcase for urgency hint --- testcases/t/13-urgent.t | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/testcases/t/13-urgent.t b/testcases/t/13-urgent.t index c23ee060..7dee21c6 100644 --- a/testcases/t/13-urgent.t +++ b/testcases/t/13-urgent.t @@ -52,4 +52,9 @@ sleep 0.25; $sock->write(i3test::format_ipc_command("9")); sleep 0.25; $sock->write(i3test::format_ipc_command("1")); +sleep 0.25; + +my $std = i3test::open_standard_window($x); +sleep 0.25; +$std->add_hint('urgency'); sleep 1; From 2c8b041500081bbbafc3e459b343f716cf11f832 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 22 Nov 2009 14:05:35 +0100 Subject: [PATCH 018/247] Bugfix: Correctly calculate width when resizing (Thanks Merovius) --- src/resize.c | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/resize.c b/src/resize.c index 9243b610..0ef775dc 100644 --- a/src/resize.c +++ b/src/resize.c @@ -165,6 +165,27 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco LOG("\n\n\n"); LOG("old = %d, new = %d\n", old_unoccupied_x, new_unoccupied_x); + int cols_without_wf = 0; + int old_width, old_second_width; + for (int col = 0; col < ws->cols; col++) + if (ws->width_factor[col] == 0) + cols_without_wf++; + + LOG("old_unoccupied_x = %d\n", old_unoccupied_x); + + LOG("Updating first (before = %f)\n", ws->width_factor[first]); + /* Convert 0 (for default width_factor) to actual numbers */ + if (ws->width_factor[first] == 0) + old_width = (old_unoccupied_x / max(cols_without_wf, 1)); + else old_width = ws->width_factor[first] * old_unoccupied_x; + + LOG("second (before = %f)\n", ws->width_factor[second]); + if (ws->width_factor[second] == 0) + old_second_width = (old_unoccupied_x / max(cols_without_wf, 1)); + else old_second_width = ws->width_factor[second] * old_unoccupied_x; + + LOG("middle = %f\n", ws->width_factor[first]); + /* If the space used for customly resized columns has changed we need to adapt the * other customly resized columns, if any */ if (new_unoccupied_x != old_unoccupied_x) @@ -177,16 +198,12 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco LOG("to %f\n", ws->width_factor[col]); } - LOG("old_unoccupied_x = %d\n", old_unoccupied_x); - LOG("Updating first (before = %f)\n", ws->width_factor[first]); /* Convert 0 (for default width_factor) to actual numbers */ if (ws->width_factor[first] == 0) ws->width_factor[first] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x; - LOG("middle = %f\n", ws->width_factor[first]); - int old_width = ws->width_factor[first] * old_unoccupied_x; - LOG("first->width = %d, pixels = %d\n", pixels); + LOG("first->width = %d, pixels = %d\n", old_width, pixels); ws->width_factor[first] *= (float)(old_width + pixels) / old_width; LOG("-> %f\n", ws->width_factor[first]); @@ -194,10 +211,10 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco LOG("Updating second (before = %f)\n", ws->width_factor[second]); if (ws->width_factor[second] == 0) ws->width_factor[second] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x; + LOG("middle = %f\n", ws->width_factor[second]); - old_width = ws->width_factor[second] * old_unoccupied_x; - LOG("second->width = %d, pixels = %d\n", pixels); - ws->width_factor[second] *= (float)(old_width - pixels) / old_width; + LOG("second->width = %d, pixels = %d\n", old_second_width, pixels); + ws->width_factor[second] *= (float)(old_second_width - pixels) / old_second_width; LOG("-> %f\n", ws->width_factor[second]); LOG("new unoccupied_x = %d\n", get_unoccupied_x(ws)); From 52945486fd60ef6bae11a7743c94e913e446ca24 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 22 Nov 2009 14:32:40 +0100 Subject: [PATCH 019/247] Bugfix: Also fix horizontal resizing The same problem as in the commit before this one was present. Additionally, the dock_clients and internal bar were not taken into account everywhere. --- include/workspace.h | 13 +++++++++++++ src/layout.c | 12 +----------- src/resize.c | 42 +++++++++++++++++++++++++++++++----------- src/workspace.c | 28 ++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 22 deletions(-) diff --git a/include/workspace.h b/include/workspace.h index 9904627d..f3bdd565 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -83,4 +83,17 @@ void workspace_map_clients(xcb_connection_t *conn, Workspace *ws); */ void workspace_update_urgent_flag(Workspace *ws); +/* + * Returns the width of the workspace. + * + */ +int workspace_width(Workspace *ws); + +/* + * Returns the effective height of the workspace (without the internal bar and + * without dock clients). + * + */ +int workspace_height(Workspace *ws); + #endif diff --git a/src/layout.c b/src/layout.c index 37efdd04..34e9b03d 100644 --- a/src/layout.c +++ b/src/layout.c @@ -65,17 +65,7 @@ int get_unoccupied_x(Workspace *workspace) { /* See get_unoccupied_x() */ int get_unoccupied_y(Workspace *workspace) { - int height = workspace->rect.height; - i3Font *font = load_font(global_conn, config.font); - - /* Reserve space for dock clients */ - Client *client; - SLIST_FOREACH(client, &(workspace->screen->dock_clients), dock_clients) - height -= client->desired_height; - - /* Space for the internal bar */ - height -= (font->height + 6); - + int height = workspace_height(workspace); int unoccupied = height; float default_factor = ((float)height / workspace->rows) / height; diff --git a/src/resize.c b/src/resize.c index 0ef775dc..b9127f5b 100644 --- a/src/resize.c +++ b/src/resize.c @@ -27,6 +27,7 @@ #include "xinerama.h" #include "config.h" #include "floating.h" +#include "workspace.h" /* * Renders the resize window between the first/second container and resizes @@ -221,7 +222,8 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco LOG("\n\n\n"); } else { - int default_height = ws->rect.height / ws->rows; + int ws_height = workspace_height(ws); + int default_height = ws_height / ws->rows; int old_unoccupied_y = get_unoccupied_y(ws); /* We pre-calculate the unoccupied space to see if we need to adapt sizes before @@ -229,7 +231,7 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco int new_unoccupied_y = old_unoccupied_y; if (old_unoccupied_y == 0) - old_unoccupied_y = ws->rect.height; + old_unoccupied_y = ws_height; if (ws->height_factor[first] == 0) new_unoccupied_y += default_height; @@ -237,6 +239,28 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco if (ws->height_factor[second] == 0) new_unoccupied_y += default_height; + int cols_without_hf = 0; + int old_height, old_second_height; + for (int row = 0; row < ws->rows; row++) + if (ws->height_factor[row] == 0) + cols_without_hf++; + + LOG("old_unoccupied_y = %d\n", old_unoccupied_y); + + LOG("Updating first (before = %f)\n", ws->height_factor[first]); + /* Convert 0 (for default width_factor) to actual numbers */ + if (ws->height_factor[first] == 0) + old_height = (old_unoccupied_y / max(cols_without_hf, 1)); + else old_height = ws->height_factor[first] * old_unoccupied_y; + + LOG("second (before = %f)\n", ws->height_factor[second]); + if (ws->height_factor[second] == 0) + old_second_height = (old_unoccupied_y / max(cols_without_hf, 1)); + else old_second_height = ws->height_factor[second] * old_unoccupied_y; + + LOG("middle = %f\n", ws->height_factor[first]); + + LOG("\n\n\n"); LOG("old = %d, new = %d\n", old_unoccupied_y, new_unoccupied_y); @@ -252,27 +276,23 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco LOG("to %f\n", ws->height_factor[row]); } - LOG("old_unoccupied_y = %d\n", old_unoccupied_y); LOG("Updating first (before = %f)\n", ws->height_factor[first]); /* Convert 0 (for default width_factor) to actual numbers */ if (ws->height_factor[first] == 0) - ws->height_factor[first] = ((float)ws->rect.height / ws->rows) / new_unoccupied_y; + ws->height_factor[first] = ((float)ws_height / ws->rows) / new_unoccupied_y; - LOG("middle = %f\n", ws->height_factor[first]); - int old_height = ws->height_factor[first] * old_unoccupied_y; - LOG("first->width = %d, pixels = %d\n", pixels); + LOG("first->width = %d, pixels = %d\n", old_height, pixels); ws->height_factor[first] *= (float)(old_height + pixels) / old_height; LOG("-> %f\n", ws->height_factor[first]); LOG("Updating second (before = %f)\n", ws->height_factor[second]); if (ws->height_factor[second] == 0) - ws->height_factor[second] = ((float)ws->rect.height / ws->rows) / new_unoccupied_y; + ws->height_factor[second] = ((float)ws_height / ws->rows) / new_unoccupied_y; LOG("middle = %f\n", ws->height_factor[second]); - old_height = ws->height_factor[second] * old_unoccupied_y; - LOG("second->width = %d, pixels = %d\n", pixels); - ws->height_factor[second] *= (float)(old_height - pixels) / old_height; + LOG("second->width = %d, pixels = %d\n", old_second_height, pixels); + ws->height_factor[second] *= (float)(old_second_height - pixels) / old_second_height; LOG("-> %f\n", ws->height_factor[second]); LOG("new unoccupied_y = %d\n", get_unoccupied_y(ws)); diff --git a/src/workspace.c b/src/workspace.c index c5e9ecdb..774b7c41 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -417,3 +417,31 @@ void workspace_update_urgent_flag(Workspace *ws) { ws->urgent = false; } + +/* + * Returns the width of the workspace. + * + */ +int workspace_width(Workspace *ws) { + return ws->rect.width; +} + +/* + * Returns the effective height of the workspace (without the internal bar and + * without dock clients). + * + */ +int workspace_height(Workspace *ws) { + int height = ws->rect.height; + i3Font *font = load_font(global_conn, config.font); + + /* Reserve space for dock clients */ + Client *client; + SLIST_FOREACH(client, &(ws->screen->dock_clients), dock_clients) + height -= client->desired_height; + + /* Space for the internal bar */ + height -= (font->height + 6); + + return height; +} From 4ace0d2138c607f2b26f431a7e141a9ee943bb15 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 22 Nov 2009 20:25:33 +0100 Subject: [PATCH 020/247] config: delete old parser, new lexer/parser is the default by now --- include/config.h | 10 +- src/cfgparse.y | 1 - src/config.c | 439 +++++------------------------------------------ src/mainx.c | 2 +- 4 files changed, 52 insertions(+), 400 deletions(-) diff --git a/include/config.h b/include/config.h index 32e29cf1..3080c5c3 100644 --- a/include/config.h +++ b/include/config.h @@ -7,8 +7,10 @@ * * See file LICENSE for license information. * - * include/config.h: Contains all structs/variables for - * the configurable part of i3 + * include/config.h: Contains all structs/variables for the configurable + * part of i3 as well as functions handling the configuration file (calling + * the parser (src/cfgparse.y) with the correct path, switching key bindings + * mode). * */ @@ -21,7 +23,6 @@ typedef struct Config Config; extern Config config; -extern bool config_use_lexer; extern SLIST_HEAD(modes_head, Mode) modes; /** @@ -124,4 +125,7 @@ void grab_all_keys(xcb_connection_t *conn); */ void switch_mode(xcb_connection_t *conn, const char *new_mode); +/* prototype for src/cfgparse.y */ +void parse_file(const char *f); + #endif diff --git a/src/cfgparse.y b/src/cfgparse.y index 0623ddde..878d2e17 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -22,7 +22,6 @@ #include "workspace.h" #include "xcb.h" - typedef struct yy_buffer_state *YY_BUFFER_STATE; extern int yylex(void); extern int yyparse(void); diff --git a/src/config.c b/src/config.c index c2622730..8d129bf4 100644 --- a/src/config.c +++ b/src/config.c @@ -7,6 +7,10 @@ * * See file LICENSE for license information. * + * src/config.c: Contains all functions handling the configuration file (calling + * the parser (src/cfgparse.y) with the correct path, switching key bindings + * mode). + * */ #include #include @@ -26,15 +30,9 @@ #include "table.h" #include "workspace.h" -/* prototype for src/cfgparse.y, will be cleaned up as soon as we completely - * switched to the new scanner/parser. */ -void parse_file(const char *f); - Config config; struct modes_head modes; -bool config_use_lexer = false; - /* * This function resolves ~ in pathnames. * @@ -48,27 +46,6 @@ static char *glob_path(const char *path) { return result; } -/* - * This function does a very simple replacement of each instance of key with value. - * - */ -static void replace_variable(char *buffer, const char *key, const char *value) { - char *pos; - /* To prevent endless recursions when the user makes an error configuring, - * we stop after 100 replacements. That should be vastly more than enough. */ - int c = 0; - while ((pos = strcasestr(buffer, key)) != NULL && c++ < 100) { - char *rest = pos + strlen(key); - *pos = '\0'; - char *replaced; - asprintf(&replaced, "%s%s%s", buffer, value, rest); - /* Hm, this is a bit ugly, but sizeof(buffer) = 4, as it’s just a pointer. - * So we need to hard-code the dimensions here. */ - strncpy(buffer, replaced, 1026); - free(replaced); - } -} - /** * Ungrabs all keys, to be called before re-grabbing the keys because of a * mapping_notify event or a configuration file reload @@ -171,10 +148,33 @@ void switch_mode(xcb_connection_t *conn, const char *new_mode) { } /* - * Reads the configuration from ~/.i3/config or /etc/i3/config if not found. + * Finds the configuration file to use (either the one specified by + * override_configpath), the user’s one or the system default) and calls + * parse_file(). * - * If you specify override_configpath, only this path is used to look for a - * configuration file. + */ +static void parse_configuration(const char *override_configpath) { + if (override_configpath != NULL) { + parse_file(override_configpath); + return; + } + + FILE *handle; + char *globbed = glob_path("~/.i3/config"); + if ((handle = fopen(globbed, "r")) == NULL) { + if ((handle = fopen("/etc/i3/config", "r")) == NULL) + die("Neither \"%s\" nor /etc/i3/config could be opened\n", globbed); + + parse_file("/etc/i3/config"); + return; + } + + parse_file(globbed); + fclose(handle); +} + +/* + * (Re-)loads the configuration file (sets useful defaults before). * */ void load_configuration(xcb_connection_t *conn, const char *override_configpath, bool reload) { @@ -220,382 +220,33 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, bindings = default_mode->bindings; - SLIST_HEAD(variables_head, Variable) variables; - -#define OPTION_STRING(name) \ - if (strcasecmp(key, #name) == 0) { \ - config.name = sstrdup(value); \ - continue; \ - } - #define REQUIRED_OPTION(name) \ if (config.name == NULL) \ die("You did not specify required configuration option " #name "\n"); -#define OPTION_COLORTRIPLE(opt, name) \ - if (strcasecmp(key, opt) == 0) { \ - char border[8], background[8], text[8]; \ - memset(border, 0, sizeof(border)); \ - memset(background, 0, sizeof(background)); \ - memset(text, 0, sizeof(text)); \ - border[0] = background[0] = text[0] = '#'; \ - if (sscanf(value, "#%06[0-9a-fA-F] #%06[0-9a-fA-F] #%06[0-9a-fA-F]", \ - border + 1, background + 1, text + 1) != 3 || \ - strlen(border) != 7 || \ - strlen(background) != 7 || \ - strlen(text) != 7) \ - die("invalid color code line: %s\n", value); \ - config.name.border = get_colorpixel(conn, border); \ - config.name.background = get_colorpixel(conn, background); \ - config.name.text = get_colorpixel(conn, text); \ - continue; \ - } - /* Clear the old config or initialize the data structure */ memset(&config, 0, sizeof(config)); - SLIST_INIT(&variables); - /* Initialize default colors */ - config.client.focused.border = get_colorpixel(conn, "#4c7899"); - config.client.focused.background = get_colorpixel(conn, "#285577"); - config.client.focused.text = get_colorpixel(conn, "#ffffff"); +#define INIT_COLOR(x, cborder, cbackground, ctext) \ + do { \ + x.border = get_colorpixel(conn, cborder); \ + x.background = get_colorpixel(conn, cbackground); \ + x.text = get_colorpixel(conn, ctext); \ + } while (0) - config.client.focused_inactive.border = get_colorpixel(conn, "#333333"); - config.client.focused_inactive.background = get_colorpixel(conn, "#5f676a"); - config.client.focused_inactive.text = get_colorpixel(conn, "#ffffff"); + INIT_COLOR(config.client.focused, "#4c7899", "#285577", "#ffffff"); + INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff"); + INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888"); + INIT_COLOR(config.client.urgent, "#2f343a", "#900000", "#ffffff"); + INIT_COLOR(config.bar.focused, "#4c7899", "#285577", "#ffffff"); + INIT_COLOR(config.bar.unfocused, "#333333", "#222222", "#888888"); + INIT_COLOR(config.bar.urgent, "#2f343a", "#900000", "#ffffff"); - config.client.unfocused.border = get_colorpixel(conn, "#333333"); - config.client.unfocused.background = get_colorpixel(conn, "#222222"); - config.client.unfocused.text = get_colorpixel(conn, "#888888"); + parse_configuration(override_configpath); - config.client.urgent.border = get_colorpixel(conn, "#2f343a"); - config.client.urgent.background = get_colorpixel(conn, "#900000"); - config.client.urgent.text = get_colorpixel(conn, "#ffffff"); - - config.bar.focused.border = get_colorpixel(conn, "#4c7899"); - config.bar.focused.background = get_colorpixel(conn, "#285577"); - config.bar.focused.text = get_colorpixel(conn, "#ffffff"); - - config.bar.unfocused.border = get_colorpixel(conn, "#333333"); - config.bar.unfocused.background = get_colorpixel(conn, "#222222"); - config.bar.unfocused.text = get_colorpixel(conn, "#888888"); - - config.bar.urgent.border = get_colorpixel(conn, "#2f343a"); - config.bar.urgent.background = get_colorpixel(conn, "#900000"); - config.bar.urgent.text = get_colorpixel(conn, "#ffffff"); - - if (config_use_lexer) { - /* Yes, this will be cleaned up soon. */ - if (override_configpath != NULL) { - parse_file(override_configpath); - } else { - FILE *handle; - char *globbed = glob_path("~/.i3/config"); - if ((handle = fopen(globbed, "r")) == NULL) { - if ((handle = fopen("/etc/i3/config", "r")) == NULL) { - die("Neither \"%s\" nor /etc/i3/config could be opened\n", globbed); - } else { - parse_file("/etc/i3/config"); - } - } else { - parse_file(globbed); - } - } - if (reload) - grab_all_keys(conn); - } else { - - FILE *handle; - if (override_configpath != NULL) { - if ((handle = fopen(override_configpath, "r")) == NULL) - die("Could not open configfile \"%s\".\n", override_configpath); - } else { - /* We first check for ~/.i3/config, then for /etc/i3/config */ - char *globbed = glob_path("~/.i3/config"); - if ((handle = fopen(globbed, "r")) == NULL) - if ((handle = fopen("/etc/i3/config", "r")) == NULL) - die("Neither \"%s\" nor /etc/i3/config could be opened\n", globbed); - free(globbed); - } - char key[512], value[512], buffer[1026]; - - while (!feof(handle)) { - if (fgets(buffer, 1024, handle) == NULL) { - /* fgets returns NULL on EOF and on error, so see which one it is. */ - if (feof(handle)) - break; - die("Could not read configuration file\n"); - } - - if (config.terminal != NULL) - replace_variable(buffer, "$terminal", config.terminal); - - /* Replace all custom variables */ - struct Variable *current; - SLIST_FOREACH(current, &variables, variables) - replace_variable(buffer, current->key, current->value); - - /* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */ - if (sscanf(buffer, "%s %[^\n]", key, value) < 1 || - key[0] == '#' || strlen(key) < 3) - continue; - - OPTION_STRING(terminal); - OPTION_STRING(font); - - /* Colors */ - OPTION_COLORTRIPLE("client.focused", client.focused); - OPTION_COLORTRIPLE("client.focused_inactive", client.focused_inactive); - OPTION_COLORTRIPLE("client.unfocused", client.unfocused); - OPTION_COLORTRIPLE("client.urgent", client.urgent); - OPTION_COLORTRIPLE("bar.focused", bar.focused); - OPTION_COLORTRIPLE("bar.unfocused", bar.unfocused); - OPTION_COLORTRIPLE("bar.urgent", bar.urgent); - - /* exec-lines (autostart) */ - if (strcasecmp(key, "exec") == 0) { - struct Autostart *new = smalloc(sizeof(struct Autostart)); - new->command = sstrdup(value); - TAILQ_INSERT_TAIL(&autostarts, new, autostarts); - continue; - } - - /* key bindings */ - if (strcasecmp(key, "bind") == 0 || strcasecmp(key, "bindsym") == 0) { - #define CHECK_MODIFIER(name) \ - if (strncasecmp(walk, #name, strlen(#name)) == 0) { \ - modifiers |= BIND_##name; \ - walk += strlen(#name) + 1; \ - continue; \ - } - char *walk = value, *rest; - uint32_t modifiers = 0; - - while (*walk != '\0') { - /* Need to check for Mod1-5, Ctrl, Shift, Mode_switch */ - CHECK_MODIFIER(SHIFT); - CHECK_MODIFIER(CONTROL); - CHECK_MODIFIER(MODE_SWITCH); - CHECK_MODIFIER(MOD1); - CHECK_MODIFIER(MOD2); - CHECK_MODIFIER(MOD3); - CHECK_MODIFIER(MOD4); - CHECK_MODIFIER(MOD5); - - /* No modifier found? Then we’re done with this step */ - break; - } - - Binding *new = scalloc(sizeof(Binding)); - - /* Now check for the keycode or copy the symbol */ - if (strcasecmp(key, "bind") == 0) { - int keycode = strtol(walk, &rest, 10); - if (!rest || *rest != ' ') - die("Invalid binding (keycode)\n"); - new->keycode = keycode; - } else { - rest = walk; - char *sym = rest; - while (*rest != '\0' && *rest != ' ') - rest++; - if (*rest != ' ') - die("Invalid binding (keysym)\n"); -#if defined(__OpenBSD__) - size_t len = strlen(sym); - if (len > (rest - sym)) - len = (rest - sym); - new->symbol = smalloc(len + 1); - memcpy(new->symbol, sym, len+1); - new->symbol[len]='\0'; -#else - new->symbol = strndup(sym, (rest - sym)); -#endif - } - rest++; - LOG("keycode = %d, symbol = %s, modifiers = %d, command = *%s*\n", new->keycode, new->symbol, modifiers, rest); - new->mods = modifiers; - new->command = sstrdup(rest); - TAILQ_INSERT_TAIL(bindings, new, bindings); - continue; - } - - if (strcasecmp(key, "floating_modifier") == 0) { - char *walk = value; - uint32_t modifiers = 0; - - while (*walk != '\0') { - /* Need to check for Mod1-5, Ctrl, Shift, Mode_switch */ - CHECK_MODIFIER(SHIFT); - CHECK_MODIFIER(CONTROL); - CHECK_MODIFIER(MODE_SWITCH); - CHECK_MODIFIER(MOD1); - CHECK_MODIFIER(MOD2); - CHECK_MODIFIER(MOD3); - CHECK_MODIFIER(MOD4); - CHECK_MODIFIER(MOD5); - - /* No modifier found? Then we’re done with this step */ - break; - } - - LOG("Floating modifiers = %d\n", modifiers); - config.floating_modifier = modifiers; - continue; - } - - /* workspace "workspace number" [screen ] ["name of the workspace"] - * with screen := | , e.g. screen 1280 or screen 1 */ - if (strcasecmp(key, "name") == 0 || strcasecmp(key, "workspace") == 0) { - LOG("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 (strncasecmp(name, "screen ", strlen("screen ")) == 0) { - char *screen = strdup(name + strlen("screen ")); - if ((end = strchr(screen, ' ')) != NULL) - *end = '\0'; - LOG("Setting preferred screen for workspace %d to \"%s\"\n", ws_num, screen); - workspace_get(ws_num-1)->preferred_screen = screen; - - name += strlen("screen ") + strlen(screen); - } - - /* Strip leading whitespace */ - while (*name != '\0' && *name == ' ') - name++; - - LOG("rest to parse = %s\n", name); - - if (name == '\0') { - free(ws_str); - continue; - } - - LOG("setting name to \"%s\"\n", name); - - if (*name != '\0') - workspace_set_name(workspace_get(ws_num - 1), name); - free(ws_str); - continue; - } - - /* assign window class[/window title] → workspace */ - if (strcasecmp(key, "assign") == 0) { - LOG("assign: \"%s\"\n", value); - char *class_title; - char *target; - char *end; - - /* If the window class/title is quoted we skip quotes */ - 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 */ - 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] == ' ') - 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 (\"%s\")\n", value); - target++; - - 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 (strchr(target, '~') != NULL) - new->floating = ASSIGN_FLOATING_ONLY; - - while (*target == '~') - target++; - - if (atoi(target) >= 1) { - 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; - } - - /* set a custom variable */ - if (strcasecmp(key, "set") == 0) { - if (value[0] != '$') - die("Malformed variable assignment, name has to start with $\n"); - - /* get key/value for this variable */ - char *v_key = value, *v_value; - if ((v_value = strstr(value, " ")) == NULL) - die("Malformed variable assignment, need a value\n"); - - *(v_value++) = '\0'; - - struct Variable *new = scalloc(sizeof(struct Variable)); - new->key = sstrdup(v_key); - new->value = sstrdup(v_value); - SLIST_INSERT_HEAD(&variables, new, variables); - LOG("Got new variable %s = %s\n", v_key, v_value); - continue; - } - - 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 */ if (reload) grab_all_keys(conn); - fclose(handle); - - while (!SLIST_EMPTY(&variables)) { - struct Variable *v = SLIST_FIRST(&variables); - SLIST_REMOVE_HEAD(&variables, variables); - free(v->key); - free(v->value); - free(v); - } - } REQUIRED_OPTION(font); @@ -613,6 +264,4 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, workspace_set_name(ws, NULL); } - - return; } diff --git a/src/mainx.c b/src/mainx.c index 9d209208..4e33aa01 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -182,7 +182,7 @@ int main(int argc, char *argv[], char *env[]) { printf("i3 version " I3_VERSION " © 2009 Michael Stapelberg and contributors\n"); exit(EXIT_SUCCESS); case 'l': - config_use_lexer = true; + /* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */ break; default: fprintf(stderr, "Usage: %s [-c configfile] [-a] [-v]\n", argv[0]); From 4ba26659fdf42f5d3f455209bdfde2e35a41bf41 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 22 Nov 2009 22:48:08 +0100 Subject: [PATCH 021/247] Bugfix: Fix stack-limit cols, handle stack-limit cols on tabbed containers (Thanks jace) --- src/layout.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/layout.c b/src/layout.c index 34e9b03d..1e7c7a03 100644 --- a/src/layout.c +++ b/src/layout.c @@ -463,7 +463,7 @@ void render_container(xcb_connection_t *conn, Container *container) { if (container->stack_limit == STACK_LIMIT_COLS) { /* wrap stores the number of rows after which we will * wrap to a new column. */ - wrap = ceil((float)num_clients / container->stack_limit_value); + wrap = container->stack_limit_value; } else if (container->stack_limit == STACK_LIMIT_ROWS) { /* When limiting rows, the wrap variable serves a * slightly different purpose: it holds the number of @@ -494,14 +494,16 @@ void render_container(xcb_connection_t *conn, Container *container) { int offset_x = 0; int offset_y = 0; - if (container->mode == MODE_STACK) { + if (container->mode == MODE_STACK || + (container->mode == MODE_TABBED && + container->stack_limit == STACK_LIMIT_COLS)) { if (container->stack_limit == STACK_LIMIT_COLS) { offset_x = current_col * (stack_win->rect.width / container->stack_limit_value); offset_y = current_row * decoration_height; - current_row++; - if ((current_row % wrap) == 0) { - current_col++; - current_row = 0; + current_col++; + if ((current_col % wrap) == 0) { + current_row++; + current_col = 0; } } else if (container->stack_limit == STACK_LIMIT_ROWS) { offset_x = current_col * wrap; @@ -515,8 +517,13 @@ void render_container(xcb_connection_t *conn, Container *container) { offset_y = current_client * decoration_height; } current_client++; - } else if (container->mode == MODE_TABBED) + } else if (container->mode == MODE_TABBED) { + if (container->stack_limit == STACK_LIMIT_ROWS) { + LOG("You limited this container in its rows. " + "This makes no sense in tabbing mode.\n"); + } offset_x = current_client++ * size_each; + } decorate_window(conn, client, stack_win->pixmap.id, stack_win->pixmap.gc, offset_x, offset_y); } From d2a88f7089bfddad42bf1fbfd19aaa4f61d18922 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 23 Nov 2009 08:35:40 +0100 Subject: [PATCH 022/247] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20ignore=20urge?= =?UTF-8?q?ncy=20flag=20when=20the=20client=20wants=20to=20clean=20it=20(T?= =?UTF-8?q?hanks=20Syntropy)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/handlers.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers.c b/src/handlers.c index 6765be8a..b9a6169c 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -953,7 +953,7 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t } Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); - if (client == last_focused) { + if (!client->urgent && client == last_focused) { LOG("Ignoring urgency flag for current client\n"); return 1; } From 5329ed01580bfe63e3311dd0e43b8f696d996795 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 23 Nov 2009 09:42:38 +0100 Subject: [PATCH 023/247] Bugfix: Resize client after updating base_height/base_width (Thanks Merovius) This fixes the problem that urxvt/xterm "lost" a line of space before being resized the first time. --- src/handlers.c | 9 +++++++-- src/layout.c | 8 ++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index b9a6169c..e9c44da6 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -889,8 +889,13 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w base_height = size_hints.min_height; } - client->base_width = base_width; - client->base_height = base_height; + if (base_width != client->base_width || + base_height != client->base_height) { + client->base_width = base_width; + client->base_height = base_height; + LOG("client's base_height changed to %d\n", base_height); + resize_client(conn, client); + } /* If no aspect ratio was set or if it was invalid, we ignore the hints */ if (!(size_hints.flags & XCB_SIZE_HINT_P_ASPECT) || diff --git a/src/layout.c b/src/layout.c index 1e7c7a03..101b1ddb 100644 --- a/src/layout.c +++ b/src/layout.c @@ -322,15 +322,15 @@ void resize_client(xcb_connection_t *conn, Client *client) { if (client->height_increment > 1) { int old_height = rect->height; rect->height -= (rect->height - client->base_height) % client->height_increment; - LOG("Lost %d pixel due to client's height_increment (%d px)\n", - old_height - rect->height, client->height_increment); + LOG("Lost %d pixel due to client's height_increment (%d px, base_height = %d)\n", + old_height - rect->height, client->height_increment, client->base_height); } if (client->width_increment > 1) { int old_width = rect->width; rect->width -= (rect->width - client->base_width) % client->width_increment; - LOG("Lost %d pixel due to client's width_increment (%d px)\n", - old_width - rect->width, client->width_increment); + LOG("Lost %d pixel due to client's width_increment (%d px, base_width = %d)\n", + old_width - rect->width, client->width_increment, client->base_width); } LOG("child will be at %dx%d with size %dx%d\n", rect->x, rect->y, rect->width, rect->height); From 36989b8b5df538807f149075c00ced9fd9f1a897 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 23 Nov 2009 21:34:21 +0100 Subject: [PATCH 024/247] debian: set priority only for the source package, binaries will inherit --- debian/control | 3 --- 1 file changed, 3 deletions(-) diff --git a/debian/control b/debian/control index b10114fb..84e57e8a 100644 --- a/debian/control +++ b/debian/control @@ -9,7 +9,6 @@ Homepage: http://i3.zekjur.net/ Package: i3 Architecture: any -Priority: extra Section: x11 Depends: i3-wm, ${misc:Depends} Recommends: i3lock, dwm-tools, i3status @@ -22,7 +21,6 @@ Description: metapackage (i3 window manager, screen locker, menu, statusbar) Package: i3-wm Architecture: any -Priority: extra Section: x11 Depends: ${shlibs:Depends}, ${misc:Depends} Provides: x-window-manager @@ -40,7 +38,6 @@ Description: an improved dynamic tiling window manager Package: i3-wm-dbg Architecture: any -Priority: extra Section: debug Depends: i3-wm (=${binary:Version}), ${misc:Depends} Description: Debugging symbols for the i3 window manager From c4d453c21a9b8cf112c97d7ae1c3c17bafe7f599 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 23 Nov 2009 21:44:23 +0100 Subject: [PATCH 025/247] debian: add missing files to docs/ --- debian/i3-wm.docs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/debian/i3-wm.docs b/debian/i3-wm.docs index 6372ffe9..587c93a3 100644 --- a/debian/i3-wm.docs +++ b/debian/i3-wm.docs @@ -6,3 +6,5 @@ docs/single_terminal.png docs/snapping.png docs/two_columns.png docs/two_terminals.png +docs/modes.png +docs/stacklimit.png From c3080432698ee57ab63c1028d90d4cef024bad63 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 23 Nov 2009 21:50:01 +0100 Subject: [PATCH 026/247] debian: Add i3-wm.doc-base --- debian/i3-wm.doc-base | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 debian/i3-wm.doc-base diff --git a/debian/i3-wm.doc-base b/debian/i3-wm.doc-base new file mode 100644 index 00000000..8e24720a --- /dev/null +++ b/debian/i3-wm.doc-base @@ -0,0 +1,10 @@ +Document: i3-wm +Title: i3 documentation +Author: Michael Stapelberg +Abstract: The documentation explains how to use and modify the i3 window + manager. +Section: Window Managers + +Format: HTML +Files: /usr/share/doc/i3-wm/*.html +Index: /usr/share/doc/i3-wm/userguide.html From abba7714ac5c10e366d54519f02040e540b2c692 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 23 Nov 2009 21:58:32 +0100 Subject: [PATCH 027/247] debian: update changelog --- debian/changelog | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/debian/changelog b/debian/changelog index 9b95d50d..4399644b 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +i3-wm (3.d-2) unstable; urgency=low + + * debian: register in doc-base + * debian: add watchfile + * debian: remove unnecessary priority-field from binary packages + * debian: add missing images to documentation + + -- Michael Stapelberg Mon, 23 Nov 2009 21:56:04 +0100 + i3-wm (3.d-1) unstable; urgency=low * Implement tabbing (command "T") From 5a1668db36d31ee7984c90c33ef5868b4f0d0c29 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 26 Nov 2009 21:32:53 +0100 Subject: [PATCH 028/247] Bugfix: Render containers after setting the client active (Thanks Mirko) This lead to race conditions when the window did not change its title after mapping and was displayed in a tabbed container. --- src/manage.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/manage.c b/src/manage.c index 98d01073..e754d9b5 100644 --- a/src/manage.c +++ b/src/manage.c @@ -444,8 +444,10 @@ map: if ((CUR_CELL->workspace->fullscreen_client == NULL || new->fullscreen) && !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) || new->fullscreen) { - if (!client_is_floating(new)) + if (!client_is_floating(new)) { new->container->currently_focused = new; + render_container(conn, new->container); + } 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 ec2e5e83645b2c9bb6ca654946094bfa8d1491b1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 26 Nov 2009 22:17:38 +0100 Subject: [PATCH 029/247] Bugfix: Fix two problems in resizing floating windows with right mouse button (Thanks Mirko) Minimum width/height was not consistent with the limit for grabbing and resizing a window at its border. If one of both was violated (width < min_width for example), none of them were updated. --- src/floating.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/floating.c b/src/floating.c index b79c0756..4177b6e5 100644 --- a/src/floating.c +++ b/src/floating.c @@ -271,13 +271,13 @@ void floating_resize_window(xcb_connection_t *conn, Client *client, xcb_button_p void resize_window_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) { int32_t new_width = old_rect->width + (new_x - event->root_x); int32_t new_height = old_rect->height + (new_y - event->root_y); - /* Obey minimum window size */ - if (new_width < 75 || new_height < 50) - return; - /* Reposition the client correctly while moving */ - client->rect.width = new_width; - client->rect.height = new_height; + /* Obey minimum window size and reposition the client */ + if (new_width >= 50) + client->rect.width = new_width; + + if (new_height >= 20) + client->rect.height = new_height; /* resize_client flushes */ resize_client(conn, client); From 13231e13abc975842e09aed04ce0ccd990d20aee Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 7 Dec 2009 10:25:12 +0100 Subject: [PATCH 030/247] Add new chapters to userguide: multi-monitor and software environment --- docs/userguide | 178 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) diff --git a/docs/userguide b/docs/userguide index 03d4452c..700f8359 100644 --- a/docs/userguide +++ b/docs/userguide @@ -310,6 +310,8 @@ wmii. === Automatically putting clients on specific workspaces +[[assign_workspace]] + It is recommended that you match on window classes whereever possible because some applications first create their window and then care about setting the correct title. Firefox with Vimperator comes to mind, as the window starts up @@ -355,6 +357,8 @@ exec sudo i3status | dzen2 -dock === Automatically putting workspaces on specific screens +[[workspace_screen]] + If you use the assigning of clients to workspaces and start some clients automatically, it might be handy to put the workspaces on specific screens. Also, the assignment of workspaces to screens will determine the workspace @@ -591,6 +595,8 @@ bindsym Mod1+a jump "urxvt/VIM" === VIM-like marks (mark/goto) +[[vim_like_marks]] + This feature is like the jump feature: It allows you to directly jump to a specific window (this means switching to the appropriate workspace and setting focus to the windows). However, you can directly mark a specific window with @@ -702,3 +708,175 @@ bindsym Mod1+Shift+r restart bindsym Mod1+Shift+w reload bindsym Mod1+Shift+e exit ---------------------------- + +== Multiple monitors + +[[multi_monitor]] + +As you can read in the goal list on its website, i3 was specifically developed +with Xinerama (support for multiple monitors) in mind. This section will +explain how to handle multiple monitors. + +When you have only one monitor, things are simple. You usually start with +workspace 1 on your monitor and open new ones as you need them. + +When you have more than one monitor, each monitor will get an initial +workspace, say the first gets 1, the second gets 2 and a possible third would +get 3. When you switch to a workspace on a different screen, i3 will switch +to that screen and then switch to the workspace. This way, you don’t need +shortcuts to switch to a specific screen and remember where you put which +workspace. New workspaces will be opened on the screen you currently are on. +There is no possiblity to have a screen without workspaces. + +The idea to make workspaces global is due to the observation that most users +have a very limited set of workspaces on their additional monitors, often +using them for a specific task (browser, shell) or for monitoring several +things (mail, IRC, syslog, …). Thus, using one workspace on one monitor and +"the rest" on the other monitors often makes sense. However, as you can +create unlimited workspaces in i3 and tie them to specific screens, you can +have the "traditional" approach of having X workspaces per screen by +changing your configuration (using modes, for example). + +=== Configuring your monitors + +To help you get going if you never did multiple monitors before, here comes a +short overview of the xrandr options which are probably of interest for you. +It is always useful to get an overview of the current screen configuration, so +just run "xrandr" and you will get an output like the following: +-------------------------------------------------------------------------------------- +$ xrandr +Screen 0: minimum 320 x 200, current 1280 x 800, maximum 8192 x 8192 +VGA1 disconnected (normal left inverted right x axis y axis) +LVDS1 connected 1280x800+0+0 (normal left inverted right x axis y axis) 261mm x 163mm + 1280x800 60.0*+ 50.0 + 1024x768 85.0 75.0 70.1 60.0 + 832x624 74.6 + 800x600 85.1 72.2 75.0 60.3 56.2 + 640x480 85.0 72.8 75.0 59.9 + 720x400 85.0 + 640x400 85.1 + 640x350 85.1 +-------------------------------------------------------------------------------------- + +Several things are important here: You can see that +LVDS1+ is connected (of +course, it is the internal flat panel) but +VGA1+ is not. If you have connected +a monitor to one of the ports but xrandr still says "disconnected", you should +check your cable, monitor or graphics driver. + +Furthermore, the maximum resolution you can see at the end of the first line +is the maximum combined resolution of your monitors. By default, it is usually +too low and has to be increased by editing +/etc/X11/xorg.conf+. + +So, say you connected VGA1 and want to use it as an additional screen: +------------------------------------------- +xrandr --output VGA1 --auto --left-of LVDS1 +------------------------------------------- +This command lets xrandr try to find out the native resolution of the device +connected to +VGA1+ and configures it to the left of your internal flat panel. +When running "xrandr" again, the output looks like this: +----------------------------------------------------------------------------------------- +$ xrandr +Screen 0: minimum 320 x 200, current 2560 x 1024, maximum 8192 x 8192 +VGA1 connected 1280x1024+0+0 (normal left inverted right x axis y axis) 338mm x 270mm + 1280x1024 60.0*+ 75.0 + 1280x960 60.0 + 1152x864 75.0 + 1024x768 75.1 70.1 60.0 + 832x624 74.6 + 800x600 72.2 75.0 60.3 56.2 + 640x480 72.8 75.0 66.7 60.0 + 720x400 70.1 +LVDS1 connected 1280x800+1280+0 (normal left inverted right x axis y axis) 261mm x 163mm + 1280x800 60.0*+ 50.0 + 1024x768 85.0 75.0 70.1 60.0 + 832x624 74.6 + 800x600 85.1 72.2 75.0 60.3 56.2 + 640x480 85.0 72.8 75.0 59.9 + 720x400 85.0 + 640x400 85.1 + 640x350 85.1 +----------------------------------------------------------------------------------------- +Please note that i3 uses exactly the same API as xrandr does, so it will see +only what you can see in xrandr. + +See also <> for more examples of multi-monitor setups. + +=== Interesting configuration for multi-monitor environments + +There are several things to configure in i3 which may be interesting if you +have more than one monitor: + +1. You can specify which workspace should be put on which screen. This will + allow you to have a different set of workspaces when starting than just + 1 for the first monitor, 2 for the second and so on. See + <>. +2. If you want some applications to generally open on the bigger screen + (MPlayer, Firefox, …), you can assign them to a specific workspace, see + <>. +3. If you have many workspaces on many monitors, it might get hard to keep + track of which window you put where. Thus, you can use vim-like marks to + quickly switch between windows. See <>. + +== i3 and the rest of your software world + +=== Displaying a status line + +A very common thing amongst users of exotic window managers is a status line at +some corner of the screen. It is an often superior replacement of the widget +approach you have in the task bar of a traditional desktop environment. + +If you don’t already have your favorite way of generating such a status line +(self-written scripts, conky, …), then i3status is the recommended tool for +this task. It was written in C with the goal to have as little syscalls as +possible to reduce the time your CPU is waken up from sleep states. + +Regardless of which application you use to generate the status line, you +want to make sure that the application does one of the following things: + +1. Register as a dock window using EWMH hints. This will make i3 position the + window above the workspace bar but below every other client. This is the + recommended way, but for example in case of dzen2 you need to check out + the source of dzen2 from subversion, because the -dock option is not present + in the released versions. +2. Overlay the internal workspace bar. This method will not waste any space + in the workspace bar. However, it is a rather hackish way. Just configure + the output window to be over your workspace bar (say -x 200 and -y 780 if + your screen is 800 px height). + +The planned solution for this problem is to make the workspace bar optional +and switch to dzen2 (for example) completely (it will contain the workspaces +then). + +=== Giving presentations (multi-monitor) + +When giving a presentation, you typically want the audience to see what you see +on your screen and then go through a series of slides (if the presentation is +simple). For more complex presentations, you might want to have some notes +which only you can see on your screen, while the audience can only see the +slides. + +[[presentations]] +==== Case 1: everybody gets the same output +This is the rather easy case. You connect your computer to the video projector, +turn on both (computer and video projector) and configure your X server to +clone the internal flat panel of your computer to the video output: +----------------------------------------------------- +xrandr --output VGA1 --mode 1024x768 --same-as LVDS1 +----------------------------------------------------- +i3 will then use the lowest common subset of screen resolutions, the rest of +your screen will be left untouched (so it will show the X background). So, in +our example, this would be 1024x768 (my notebook has 1280x800). + +==== Case 2: you can see more than your audience +This case is a bit harder. First of all, you should configure the VGA output +somewhere near your internal flat panel, say right of it: +----------------------------------------------------- +xrandr --output VGA1 --mode 1024x768 --right-of LVDS1 +----------------------------------------------------- +Now, i3 will put a new workspace (depending on your settings) on the new screen +and you are in multi-monitor mode (see <>). + +Because i3 is not a compositing window manager, there is no possibility to +display a window on two screens at the same time. Instead, you presentation +software needs to do this job (that is, open a window on each screen with the +same contents). From 1a53cc067b1087815fb99297d65e0b058c187d18 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 7 Dec 2009 16:31:49 +0100 Subject: [PATCH 031/247] =?UTF-8?q?When=20no=20screens=20are=20found,=20do?= =?UTF-8?q?n=E2=80=99t=20hog=20the=20CPU.=20Also,=20wait=20longer=20for=20?= =?UTF-8?q?screens=20(10=20seconds).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of just going on, i3 will exit cleanly now, putting an appropriate message into the logfile. --- src/xinerama.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/xinerama.c b/src/xinerama.c index 6c87e775..06a861f3 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -188,7 +188,7 @@ static void query_screens(xcb_connection_t *conn, struct screens_head *screenlis * 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) { + while ((time(NULL) - before_trying) < 10) { reply = xcb_xinerama_query_screens_reply(conn, xcb_xinerama_query_screens_unchecked(conn), NULL); if (!reply) { LOG("Couldn't get Xinerama screens\n"); @@ -227,11 +227,19 @@ static void query_screens(xcb_connection_t *conn, struct screens_head *screenlis if (num_screens == 0) { LOG("No screens found. This is weird. Trying again...\n"); + /* Give the scheduler a chance to do something else + * and don’t hog the CPU */ + usleep(250); continue; } break; } + + if (num_screens == 0) { + LOG("No screens found for 10 seconds. Please fix your setup. i3 will exit now.\n"); + exit(0); + } } /* From 6abf70895d3e898aaa61a40ee3a7c43847fff51c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 7 Dec 2009 16:58:46 +0100 Subject: [PATCH 032/247] Update and reformat the hacking howto --- docs/hacking-howto | 457 +++++++++++++++++++++++++-------------------- 1 file changed, 258 insertions(+), 199 deletions(-) diff --git a/docs/hacking-howto b/docs/hacking-howto index 82f0a941..a774b903 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -1,24 +1,25 @@ Hacking i3: How To ================== Michael Stapelberg -May 2009 +December 2009 -This document is intended to be the first thing you read before looking and/or touching -i3’s source code. It should contain all important information to help you understand -why things are like they are. If it does not mention something you find necessary, please -do not hesitate to contact me. +This document is intended to be the first thing you read before looking and/or +touching i3’s source code. It should contain all important information to help +you understand why things are like they are. If it does not mention something +you find necessary, please do not hesitate to contact me. == Window Managers -A window manager is not necessarily needed to run X, but it is usually used in combination -to facilitate some things. The window manager's job is to take care of the placement of -windows, to provide the user some mechanisms to change the position/size of windows and -to communicate with clients to a certain extent (for example handle fullscreen requests -of clients such as MPlayer). +A window manager is not necessarily needed to run X, but it is usually used in +combination with X to facilitate some things. The window manager's job is to +take care of the placement of windows, to provide the user with some mechanisms +to change the position/size of windows and to communicate with clients to a +certain extent (for example handle fullscreen requests of clients such as +MPlayer). -There are no different contexts in which X11 clients run, so a window manager is just another -client, like all other X11 applications. However, it handles some events which normal clients -usually don’t handle. +There are no different contexts in which X11 clients run, so a window manager +is just another client, like all other X11 applications. However, it handles +some events which normal clients usually don’t handle. In the case of i3, the tasks (and order of them) are the following: @@ -29,6 +30,7 @@ In the case of i3, the tasks (and order of them) are the following: . Handle the client’s `_WM_STATE` property, but only the `_WM_STATE_FULLSCREEN` . Handle the client’s `WM_NAME` property . Handle the client’s size hints to display them proportionally +. Handle the client’s urgency hint . Handle enter notifications (focus follows mouse) . Handle button (as in mouse buttons) presses for focus/raise on click . Handle expose events to re-draw own windows such as decorations @@ -36,37 +38,43 @@ In the case of i3, the tasks (and order of them) are the following: Change the layout mode of a container (default/stacking), Start a new application, Restart the window manager -In the following chapters, each of these tasks and their implementation details will be discussed. +In the following chapters, each of these tasks and their implementation details +will be discussed. === Tiling window managers -Traditionally, there are two approaches to managing windows: The most common one nowadays is -floating, which means the user can freely move/resize the windows. The other approach is called -tiling, which means that your window manager distributing windows to use as much space as -possible while not overlapping. +Traditionally, there are two approaches to managing windows: The most common +one nowadays is floating, which means the user can freely move/resize the +windows. The other approach is called tiling, which means that your window +manager distributing windows to use as much space as possible while not +overlapping. -The idea behind tiling is that you should not need to waste your time moving/resizing windows -while you usually want to get some work done. After all, most users sooner or later tend to -lay out their windows in a way which corresponds to tiling or stacking mode in i3. Therefore, -why not let i3 do this for you? Certainly, it’s faster than you could ever do it. +The idea behind tiling is that you should not need to waste your time +moving/resizing windows while you usually want to get some work done. After +all, most users sooner or later tend to lay out their windows in a way which +corresponds to tiling or stacking mode in i3. Therefore, why not let i3 do this +for you? Certainly, it’s faster than you could ever do it. -The problem with most tiling window managers is that they are too unflexible. In my opinion, a -window manager is just another tool, and similar to vim which can edit all kinds of text files -(like source code, HTML, …) and is not limited to a specific file type, a window manager should -not limit itself to a certain layout (like dwm, awesome, …) but provide mechanisms for you to -easily create the layout you need at the moment. +The problem with most tiling window managers is that they are too unflexible. +In my opinion, a window manager is just another tool, and similar to vim which +can edit all kinds of text files (like source code, HTML, …) and is not limited +to a specific file type, a window manager should not limit itself to a certain +layout (like dwm, awesome, …) but provide mechanisms for you to easily create +the layout you need at the moment. === The layout table -To accomplish flexible layouts, we decided to simply use a table. The table grows and shrinks -as you need it. Each cell holds a container which then holds windows (see picture below). You -can use different layouts for each container (default layout and stacking layout). +To accomplish flexible layouts, we decided to simply use a table. The table +grows and shrinks as you need it. Each cell holds a container which then holds +windows (see picture below). You can use different layouts for each container +(default layout and stacking layout). -So, when you open a terminal and immediately open another one, they reside in the same container, -in default layout. The layout table has exactly one column, one row and therefore one cell. -When you move one of the terminals to the right, the table needs to grow. It will be expanded -to two columns and one row. This enables you to have different layouts for each container. -The table then looks like this: +So, when you open a terminal and immediately open another one, they reside in +the same container, in default layout. The layout table has exactly one column, +one row and therefore one cell. When you move one of the terminals to the +right, the table needs to grow. It will be expanded to two columns and one row. +This enables you to have different layouts for each container. The table then +looks like this: [width="15%",cols="^,^"] |======== @@ -81,9 +89,9 @@ When moving terminal 2 to the bottom, the table will be expanded again. | | T2 |======== -You can really think of the layout table like a traditional HTML table, if you’ve ever -designed one. Especially col- and rowspan work equally. Below you see an example of -colspan=2 for the first container (which has T1 as window). +You can really think of the layout table like a traditional HTML table, if +you’ve ever designed one. Especially col- and rowspan work equally. Below you +see an example of colspan=2 for the first container (which has T1 as window). [width="15%",cols="^asciidoc"] |======== @@ -100,12 +108,23 @@ Furthermore, you can freely resize table cells. == Files include/data.h:: -Contains data definitions used by nearly all files. You really need to read this first. +Contains data definitions used by nearly all files. You really need to read +this first. include/*.h:: -Contains forward definitions for all public functions, aswell as doxygen-compatible -comments (so if you want to get a bit more of the big picture, either browse all -header files or use doxygen if you prefer that). +Contains forward definitions for all public functions, aswell as +doxygen-compatible comments (so if you want to get a bit more of the big +picture, either browse all header files or use doxygen if you prefer that). + +src/cfgparse.l:: +Contains the lexer for i3’s configuration file, written for +flex(1)+. + +src/cfgparse.y:: +Contains the parser for i3’s configuration file, written for +bison(1)+. + +src/click.c:: +Contains all functions which handle mouse button clicks (right mouse button +clicks initiate resizing and thus are relatively complex). src/client.c:: Contains all functions which are specific to a certain client (make it @@ -159,12 +178,13 @@ src/xcb.c:: Contains wrappers to use xcb more easily. src/xinerama.c:: -(Re-)initializes the available screens and converts them to virtual screens (see below). +(Re-)initializes the available screens and converts them to virtual screens +(see below). == Data structures -See include/data.h for documented data structures. The most important ones are explained -right here. +See include/data.h for documented data structures. The most important ones are +explained right here. image:bigpicture.png[The Big Picture] @@ -178,37 +198,40 @@ So, the hierarchy is: === Virtual screens -A virtual screen (type `i3Screen`) is generated from the connected screens obtained -through Xinerama. The difference to the raw Xinerama monitors as seen when using +xrandr(1)+ -is that it falls back to the lowest common resolution of the logical screens. +A virtual screen (type `i3Screen`) is generated from the connected screens +obtained through Xinerama. The difference to the raw Xinerama monitors as seen +when using +xrandr(1)+ is that it falls back to the lowest common resolution of +the logical screens. -For example, if your notebook has 1280x800 and you connect a video projector with -1024x768, set up in clone mode (+xrandr \--output VGA \--mode 1024x768 \--same-as LVDS+), -i3 will have one virtual screen. +For example, if your notebook has 1280x800 and you connect a video projector +with 1024x768, set up in clone mode (+xrandr \--output VGA \--mode 1024x768 +\--same-as LVDS+), i3 will have one virtual screen. -However, if you configure it using +xrandr \--output VGA \--mode 1024x768 \--right-of LVDS+, -i3 will generate two virtual screens. For each virtual screen, a new workspace will be -assigned. New workspaces are created on the screen you are currently on. +However, if you configure it using +xrandr \--output VGA \--mode 1024x768 +\--right-of LVDS+, i3 will generate two virtual screens. For each virtual +screen, a new workspace will be assigned. New workspaces are created on the +screen you are currently on. === Workspace -A workspace is identified by its number. Basically, you could think of workspaces -as different desks in your bureau, if you like the desktop methaphor. They just contain -different sets of windows and are completely separate of each other. Other window -managers also call this ``Virtual desktops''. +A workspace is identified by its number. Basically, you could think of +workspaces as different desks in your bureau, if you like the desktop +methaphor. They just contain different sets of windows and are completely +separate of each other. Other window managers also call this ``Virtual +desktops''. === The layout table -Each workspace has a table, which is just a two-dimensional dynamic array containing -Containers (see below). This table grows and shrinks as you need it (by moving windows -to the right you can create a new column in the table, by moving them to the bottom -you create a new row). +Each workspace has a table, which is just a two-dimensional dynamic array +containing Containers (see below). This table grows and shrinks as you need it +(by moving windows to the right you can create a new column in the table, by +moving them to the bottom you create a new row). === Container -A container is the content of a table’s cell. It holds an arbitrary amount of windows -and has a specific layout (default layout or stack layout). Containers can consume -multiple table cells by modifying their colspan/rowspan attribute. +A container is the content of a table’s cell. It holds an arbitrary amount of +windows and has a specific layout (default layout or stack layout). Containers +can consume multiple table cells by modifying their colspan/rowspan attribute. === Client @@ -216,20 +239,22 @@ A client is x11-speak for a window. == List/queue macros -i3 makes heavy use of the list macros defined in BSD operating systems. To ensure -that the operating system on which i3 is compiled has all the awaited features, -i3 comes with `include/queue.h`. On BSD systems, you can use man `queue(3)`. On Linux, -you have to use google. +i3 makes heavy use of the list macros defined in BSD operating systems. To +ensure that the operating system on which i3 is compiled has all the expected +features, i3 comes with `include/queue.h`. On BSD systems, you can use man +`queue(3)`. On Linux, you have to use google (or read the source). -The lists used are `SLIST` (single linked lists) and `CIRCLEQ` (circular queues). -Usually, only forward traversal is necessary, so an `SLIST` works fine. However, -for the windows inside a container, a `CIRCLEQ` is necessary to go from the currently +The lists used are `SLIST` (single linked lists), `CIRCLEQ` (circular +queues) and TAILQ (tail queues). Usually, only forward traversal is necessary, +so an `SLIST` works fine. If inserting elements at arbitrary positions or at +the end of a list is necessary, a `TAILQ` is used instead. However, for the +windows inside a container, a `CIRCLEQ` is necessary to go from the currently selected window to the window above/below. == Naming conventions -There is a row of standard variables used in many events. The following names should be -chosen for those: +There is a row of standard variables used in many events. The following names +should be chosen for those: * ``conn'' is the xcb_connection_t * ``event'' is the event of the particular type @@ -249,116 +274,138 @@ chosen for those: === Grabbing the bindings -Grabbing the bindings is quite straight-forward. You pass X your combination of modifiers and -the keycode you want to grab and whether you want to grab them actively or passively. Most -bindings (everything except for bindings using Mode_switch) are grabbed passively, that is, -just the window manager gets the event and cannot replay it. +Grabbing the bindings is quite straight-forward. You pass X your combination of +modifiers and the keycode you want to grab and whether you want to grab them +actively or passively. Most bindings (everything except for bindings using +Mode_switch) are grabbed passively, that is, just the window manager gets the +event and cannot replay it. -We need to grab bindings that use Mode_switch actively because of a bug in X. When the window -manager receives the keypress/keyrelease event for an actively grabbed keycode, it has to decide -what to do with this event: It can either replay it so that other applications get it or it -can prevent other applications from receiving it. +We need to grab bindings that use Mode_switch actively because of a bug in X. +When the window manager receives the keypress/keyrelease event for an actively +grabbed keycode, it has to decide what to do with this event: It can either +replay it so that other applications get it or it can prevent other +applications from receiving it. -So, why do we need to grab keycodes actively? Because X does not set the state-property of -keypress/keyrelease events properly. The Mode_switch bit is not set and we need to get it -using XkbGetState. This means we cannot pass X our combination of modifiers containing Mode_switch -when grabbing the key and therefore need to grab the keycode itself without any modiffiers. -This means, if you bind Mode_switch + keycode 38 ("a"), i3 will grab keycode 38 ("a") and -check on each press of "a" if the Mode_switch bit is set using XKB. If yes, it will handle -the event, if not, it will replay the event. +So, why do we need to grab keycodes actively? Because X does not set the +state-property of keypress/keyrelease events properly. The Mode_switch bit is +not set and we need to get it using XkbGetState. This means we cannot pass X +our combination of modifiers containing Mode_switch when grabbing the key and +therefore need to grab the keycode itself without any modiffiers. This means, +if you bind Mode_switch + keycode 38 ("a"), i3 will grab keycode 38 ("a") and +check on each press of "a" if the Mode_switch bit is set using XKB. If yes, it +will handle the event, if not, it will replay the event. === Handling a keypress -As mentioned in "Grabbing the bindings", upon a keypress event, i3 first gets the correct state. +As mentioned in "Grabbing the bindings", upon a keypress event, i3 first gets +the correct state. -Then, it looks through all bindings and gets the one which matches the received event. +Then, it looks through all bindings and gets the one which matches the received +event. The bound command is parsed directly in command mode. == Manage windows (src/mainx.c, manage_window() and reparent_window()) -`manage_window()` does some checks to decide whether the window should be managed at all: +`manage_window()` does some checks to decide whether the window should be +managed at all: * Windows have to be mapped, that is, visible on screen - * The override_redirect must not be set. Windows with override_redirect shall not be - managed by a window manager + * The override_redirect must not be set. Windows with override_redirect shall + not be managed by a window manager -Afterwards, i3 gets the intial geometry and reparents the window if it wasn’t already -managed. +Afterwards, i3 gets the intial geometry and reparents the window (see +`reparent_window()`) if it wasn’t already managed. -Reparenting means that for each window which is reparented, a new window, slightly larger -than the original one, is created. The original window is then reparented to the bigger one -(called "frame"). +Reparenting means that for each window which is reparented, a new window, +slightly larger than the original one, is created. The original window is then +reparented to the bigger one (called "frame"). -After reparenting, the window type (`_NET_WM_WINDOW_TYPE`) is checked to see whether this -window is a dock (`_NET_WM_WINDOW_TYPE_DOCK`), like dzen2 for example. Docks are handled -differently, they don’t have decorations and are not assigned to a specific container. -Instead, they are positioned at the bottom of the screen. To get the height which needsd -to be reserved for the window, the `_NET_WM_STRUT_PARTIAL` property is used. +After reparenting, the window type (`_NET_WM_WINDOW_TYPE`) is checked to see +whether this window is a dock (`_NET_WM_WINDOW_TYPE_DOCK`), like dzen2 for +example. Docks are handled differently, they don’t have decorations and are not +assigned to a specific container. Instead, they are positioned at the bottom +of the screen. To get the height which needsd to be reserved for the window, +the `_NET_WM_STRUT_PARTIAL` property is used. + +Furthermore, the list of assignments (to other workspaces, which may be on +other screens) is checked. If the window matches one of the user’s criteria, +it may either be put in floating mode or moved to a different workspace. If the +target workspace is not visible, the window will not be mapped. == What happens when an application is started? -i3 does not care for applications. All it notices is when new windows are mapped (see -`src/handlers.c`, `handle_map_request()`). The window is then reparented (see section -"Manage windows"). +i3 does not care for applications. All it notices is when new windows are +mapped (see `src/handlers.c`, `handle_map_request()`). The window is then +reparented (see section "Manage windows"). -After reparenting the window, `render_layout()` is called which renders the internal -layout table. The window was placed in the currently focused container and -therefore the new window and the old windows (if any) need to be moved/resized -so that the currently active layout (default mode/stacking mode) is rendered -correctly. To move/resize windows, a window is ``configured'' in X11-speak. +After reparenting the window, `render_layout()` is called which renders the +internal layout table. The new window has been placed in the currently focused +container and therefore the new window and the old windows (if any) need to be +moved/resized so that the currently active layout (default mode/stacking mode) +is rendered correctly. To move/resize windows, a window is ``configured'' in +X11-speak. -Some applications, such as MPlayer obivously assume the window manager is stupid -and try to configure their windows by themselves. This generates an event called -configurerequest. i3 handles these events and tells the window the size it had -before the configurerequest (with the exception of not yet mapped windows, which -get configured like they want to, and floating windows, which can reconfigure -themselves). +Some applications, such as MPlayer obivously assume the window manager is +stupid and try to configure their windows by themselves. This generates an +event called configurerequest. i3 handles these events and tells the window the +size it had before the configurerequest (with the exception of not yet mapped +windows, which get configured like they want to, and floating windows, which +can reconfigure themselves). == _NET_WM_STATE -Only the _NET_WM_STATE_FULLSCREEN atom is handled. It calls ``toggle_fullscreen()'' for the -specific client which just configures the client to use the whole screen on which it -currently is. Also, it is set as fullscreen_client for the i3Screen. +Only the _NET_WM_STATE_FULLSCREEN atom is handled. It calls +``toggle_fullscreen()'' for the specific client which just configures the +client to use the whole screen on which it currently is. Also, it is set as +fullscreen_client for the i3Screen. == WM_NAME -When the WM_NAME property of a window changes, its decoration (containing the title) -is re-rendered. +When the WM_NAME property of a window changes, its decoration (containing the +title) is re-rendered. Note that WM_NAME is in COMPOUND_TEXT encoding which is +totally uncommon and cumbersome. Therefore, the _NET_WM_NAME atom will be used +if present. + +== _NET_WM_NAME + +Like WM_NAME, this atom contains the title of a window. However, _NET_WM_NAME +is encoded in UTF-8. i3 will recode it to UCS-2 in order to be able to pass it +to X. Using an appropriate font (ISO-10646), you can see most special +characters (every special character contained in your font). == Size hints -Size hints specify the minimum/maximum size for a given window aswell as its aspect ratio. -At the moment, as i3 does not have a floating mode yet, only the aspect ratio is parsed. -This is important for clients like mplayer, who only set the aspect ratio and resize their -window to be as small as possible (but only with some video outputs, for example in Xv, -while when using x11, mplayer does the necessary centering for itself). +Size hints specify the minimum/maximum size for a given window aswell as its +aspect ratio. This is important for clients like mplayer, who only set the +aspect ratio and resize their window to be as small as possible (but only with +some video outputs, for example in Xv, while when using x11, mplayer does the +necessary centering for itself). -So, when an aspect ratio was specified, i3 adjusts the height of the window until the -size maintains the correct aspect ratio. For the code to do this, see src/layout.c, -function resize_client(). +So, when an aspect ratio was specified, i3 adjusts the height of the window +until the size maintains the correct aspect ratio. For the code to do this, see +src/layout.c, function resize_client(). == Rendering (src/layout.c, render_layout() and render_container()) -There are two entry points to rendering: render_layout() and render_container(). The -former one renders all virtual screens, the currently active workspace of each virtual -screen and all containers (inside the table cells) of these workspaces using -render_container(). Therefore, if you need to render only a single container, for -example because a window was removed, added or changed its title, you should directly -call render_container(). +There are several entry points to rendering: `render_layout()`, +`render_workspace()` and `render_container()`. The former one calls +`render_workspace()` for every screen, which in turn will call +`render_container()` for every container inside its layout table. Therefore, if +you need to render only a single container, for example because a window was +removed, added or changed its title, you should directly call +render_container(). -Rendering consists of two steps: In the first one, in render_layout(), each container -gets its position (screen offset + offset in the table) and size (container's width -times colspan/rowspan). Then, render_container() is called: - -render_container() then takes different approaches, depending on the mode the container -is in. +Rendering consists of two steps: In the first one, in `render_workspace()`, each +container gets its position (screen offset + offset in the table) and size +(container's width times colspan/rowspan). Then, `render_container()` is called, +which takes different approaches, depending on the mode the container is in: === Common parts -On the frame (the window which was created around the client’s window for the decorations), -a black rectangle is drawn as a background for windows like MPlayer, which don’t completely -fit into the frame. +On the frame (the window which was created around the client’s window for the +decorations), a black rectangle is drawn as a background for windows like +MPlayer, which do not completely fit into the frame. === Default mode @@ -366,97 +413,109 @@ Each clients gets the container’s width and an equal amount of height. === Stack mode -In stack mode, a window containing the decorations of all windows inside the container -is placed at the top. The currently focused window is then given the whole remaining -space. +In stack mode, a window containing the decorations of all windows inside the +container is placed at the top. The currently focused window is then given the +whole remaining space. + +=== Tabbed mode + +Tabbed mode is like stack mode, except that the window decorations are drawn +in one single line at the top of the container. === Window decorations -The window decorations consist of a rectangle in the appropriate color (depends on whether -this window is the currently focused one or the last focused one in a not focused container -or not focused at all) forming the background. Afterwards, two lighter lines are drawn -and the last step is drawing the window’s title (see WM_NAME) onto it. +The window decorations consist of a rectangle in the appropriate color (depends +on whether this window is the currently focused one, the last focused one in a +not focused container or not focused at all) forming the background. +Afterwards, two lighter lines are drawn and the last step is drawing the +window’s title (see WM_NAME) onto it. === Fullscreen windows -For fullscreen windows, the `rect` (x, y, width, height) is not changed to allow the client -to easily go back to its previous position. Instead, fullscreen windows are skipped -when rendering. +For fullscreen windows, the `rect` (x, y, width, height) is not changed to +allow the client to easily go back to its previous position. Instead, +fullscreen windows are skipped when rendering. === Resizing containers -By clicking and dragging the border of a container, you can resize the whole column -(respectively row) which this container is in. This is necessary to keep the table -layout working and consistent. +By clicking and dragging the border of a container, you can resize the whole +column (respectively row) which this container is in. This is necessary to keep +the table layout working and consistent. -Currently, only vertical resizing is implemented. +The resizing works similarly to the resizing of floating windows or movement of +floating windows: -The resizing works similarly to the resizing of floating windows or movement of floating -windows: - -* A new, invisible window with the size of the root window is created (+grabwin+) -* Another window, 2px width and as high as your screen (or vice versa for horizontal - resizing) is created. Its background color is the border color and it is only - there to signalize the user how big the container will be (it creates the impression - of dragging the border out of the container). -* The +drag_pointer+ function of +src/floating.c+ is called to grab the pointer and - enter an own event loop which will pass all events (expose events) but motion notify - events. This function then calls the specified callback (+resize_callback+) which - does some boundary checking and moves the helper window. As soon as the mouse - button is released, this loop will be terminated. -* The new width_factor for each involved column (respectively row) will be calculated. +* A new, invisible window with the size of the root window is created + (+grabwin+) +* Another window, 2px width and as high as your screen (or vice versa for + horizontal resizing) is created. Its background color is the border color and + it is only there to signalize the user how big the container will be (it + creates the impression of dragging the border out of the container). +* The +drag_pointer+ function of +src/floating.c+ is called to grab the pointer + and enter an own event loop which will pass all events (expose events) but + motion notify events. This function then calls the specified callback + (+resize_callback+) which does some boundary checking and moves the helper + window. As soon as the mouse button is released, this loop will be + terminated. +* The new width_factor for each involved column (respectively row) will be + calculated. == User commands / commandmode (src/commands.c) -Like in vim, you can control i3 using commands. They are intended to be a powerful -alternative to lots of shortcuts, because they can be combined. There are a few special -commands, which are the following: +Like in vim, you can control i3 using commands. They are intended to be a +powerful alternative to lots of shortcuts, because they can be combined. There +are a few special commands, which are the following: exec :: Starts the given command by passing it to `/bin/sh`. restart:: -Restarts i3 by executing `argv[0]` (the path with which you started i3) without forking. +Restarts i3 by executing `argv[0]` (the path with which you started i3) without +forking. w:: -"With". This is used to select a bunch of windows. Currently, only selecting the whole -container in which the window is in, is supported by specifying "w". +"With". This is used to select a bunch of windows. Currently, only selecting +the whole container in which the window is in, is supported by specifying "w". f, s, d:: Toggle fullscreen, stacking, default mode for the current window/container. -The other commands are to be combined with a direction. The directions are h, j, k and l, -like in vim (h = left, j = down, k = up, l = right). When you just specify the direction -keys, i3 will move the focus in that direction. You can provide "m" or "s" before the -direction to move a window respectively or snap. +The other commands are to be combined with a direction. The directions are h, +j, k and l, like in vim (h = left, j = down, k = up, l = right). When you just +specify the direction keys, i3 will move the focus in that direction. You can +provide "m" or "s" before the direction to move a window respectively or snap. == Gotchas -* Forgetting to call `xcb_flush(conn);` after sending a request. This usually leads to - code which looks like it works fine but which does not work under certain conditions. +* Forgetting to call `xcb_flush(conn);` after sending a request. This usually + leads to code which looks like it works fine but which does not work under + certain conditions. == Using git / sending patches -For a short introduction into using git, see http://www.spheredev.org/wiki/Git_for_the_lazy -or, for more documentation, see http://git-scm.com/documentation +For a short introduction into using git, see +http://www.spheredev.org/wiki/Git_for_the_lazy or, for more documentation, see +http://git-scm.com/documentation -When you want to send a patch because you fixed a bug or implemented a cool feature (please -talk to us before working on features to see whether they are maybe already implemented, not -possible because of some reason or don’t fit into the concept), please use git to create -a patchfile. +When you want to send a patch because you fixed a bug or implemented a cool +feature (please talk to us before working on features to see whether they are +maybe already implemented, not possible because of some reason or don’t fit +into the concept), please use git to create a patchfile. -First of all, update your working copy to the latest version of the master branch: +First of all, update your working copy to the latest version of the master +branch: -------- -git pull +rit pull -------- -Afterwards, make the necessary changes for your bugfix/feature. Then, review the changes -using +git diff+ (you might want to enable colors in the diff using +git config diff.color auto+). -When you are definitely done, use +git commit -a+ to commit all changes you’ve made. +Afterwards, make the necessary changes for your bugfix/feature. Then, review +the changes using +git diff+ (you might want to enable colors in the diff using ++git config diff.color auto+). When you are definitely done, use +git commit +-a+ to commit all changes you’ve made. -Then, use the following command to generate a patchfile which we can directly apply to -the branch, preserving your commit message and name: +Then, use the following command to generate a patchfile which we can directly +apply to the branch, preserving your commit message and name: ----------------------- git format-patch origin From 99c7a14285ad3c2007e4d888c71ba5c35d27e575 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 7 Dec 2009 19:06:57 +0100 Subject: [PATCH 033/247] Correct typo --- docs/hacking-howto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hacking-howto b/docs/hacking-howto index a774b903..b66f7d62 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -506,7 +506,7 @@ First of all, update your working copy to the latest version of the master branch: -------- -rit pull +git pull -------- Afterwards, make the necessary changes for your bugfix/feature. Then, review From befd7f6f22f3399eee50b15896a6186534e47018 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 8 Dec 2009 11:48:25 +0100 Subject: [PATCH 034/247] Bugfix: Use more precise floating point arithmetic (Thanks helgiks) This prevents errors in rounding leading to an unoccupied space of -1 which in turn leads to infinity when calculating the new size of a container after resizing. --- src/layout.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/layout.c b/src/layout.c index 101b1ddb..80707319 100644 --- a/src/layout.c +++ b/src/layout.c @@ -47,37 +47,37 @@ static bool update_if_necessary(uint32_t *destination, const uint32_t new_value) * */ int get_unoccupied_x(Workspace *workspace) { - int unoccupied = workspace->rect.width; - float default_factor = ((float)workspace->rect.width / workspace->cols) / workspace->rect.width; + double unoccupied = workspace->rect.width; + double default_factor = ((float)workspace->rect.width / workspace->cols) / workspace->rect.width; - LOG("get_unoccupied_x(), starting with %d, default_factor = %f\n", unoccupied, default_factor); + LOG("get_unoccupied_x(), starting with %f, default_factor = %f\n", unoccupied, default_factor); for (int cols = 0; cols < workspace->cols; cols++) { - LOG("width_factor[%d] = %f\n", cols, workspace->width_factor[cols]); + LOG("width_factor[%d] = %f, unoccupied = %f\n", cols, workspace->width_factor[cols], unoccupied); if (workspace->width_factor[cols] == 0) unoccupied -= workspace->rect.width * default_factor; } - LOG("unoccupied space: %d\n", unoccupied); + LOG("unoccupied space: %f\n", unoccupied); return unoccupied; } /* See get_unoccupied_x() */ int get_unoccupied_y(Workspace *workspace) { int height = workspace_height(workspace); - int unoccupied = height; - float default_factor = ((float)height / workspace->rows) / height; + double unoccupied = height; + double default_factor = ((float)height / workspace->rows) / height; - LOG("get_unoccupied_y(), starting with %d, default_factor = %f\n", unoccupied, default_factor); + LOG("get_unoccupied_y(), starting with %f, default_factor = %f\n", unoccupied, default_factor); for (int rows = 0; rows < workspace->rows; rows++) { - LOG("height_factor[%d] = %f\n", rows, workspace->height_factor[rows]); + LOG("height_factor[%d] = %f, unoccupied = %f\n", rows, workspace->height_factor[rows], unoccupied); if (workspace->height_factor[rows] == 0) unoccupied -= height * default_factor; } - LOG("unoccupied space: %d\n", unoccupied); + LOG("unoccupied space: %f\n", unoccupied); return unoccupied; } From ddf311955222a9d864ea4b524304a4cc6beb2135 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 8 Dec 2009 20:52:19 +0100 Subject: [PATCH 035/247] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20resize=20clie?= =?UTF-8?q?nt=20after=20base=5Fheight=20changes=20if=20client=20is=20in=20?= =?UTF-8?q?fullscreen=20mode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/handlers.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/handlers.c b/src/handlers.c index e9c44da6..560fad25 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -894,7 +894,10 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w client->base_width = base_width; client->base_height = base_height; LOG("client's base_height changed to %d\n", base_height); - resize_client(conn, client); + if (client->fullscreen) + LOG("Not resizing client, it is in fullscreen mode\n"); + else + resize_client(conn, client); } /* If no aspect ratio was set or if it was invalid, we ignore the hints */ From e5c4fa6c1c854a00051fe8177060c39f6aab42f4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 8 Dec 2009 20:55:17 +0100 Subject: [PATCH 036/247] Include unistd.h for usleep() --- src/xinerama.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/xinerama.c b/src/xinerama.c index 06a861f3..fcc3a44b 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include From 17082ef5b797cc5cf5bb98e2cf2ca931d7f006c7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 11 Dec 2009 17:57:42 +0100 Subject: [PATCH 037/247] Bugfix: close file handle after parsing --- src/config.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config.c b/src/config.c index 8d129bf4..69506578 100644 --- a/src/config.c +++ b/src/config.c @@ -166,6 +166,7 @@ static void parse_configuration(const char *override_configpath) { die("Neither \"%s\" nor /etc/i3/config could be opened\n", globbed); parse_file("/etc/i3/config"); + fclose(handle); return; } From ffe925f733d2840bba0083b433aa9dd7586dcd99 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 12 Dec 2009 18:43:30 +0100 Subject: [PATCH 038/247] Bugfix: Fix assignments to not yet visible workspaces This was broken by commit 5a1668db3 --- src/manage.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/manage.c b/src/manage.c index e754d9b5..030a3883 100644 --- a/src/manage.c +++ b/src/manage.c @@ -446,7 +446,8 @@ map: if ((new->workspace->fullscreen_client == NULL) || new->fullscreen) { if (!client_is_floating(new)) { new->container->currently_focused = new; - render_container(conn, new->container); + if (map_frame) + render_container(conn, new->container); } 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 4afe65eea2b3a347ddbdca8fb52515e096970b3d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 12 Dec 2009 21:29:07 +0100 Subject: [PATCH 039/247] Bugfix: Correctly place new windows below fullscreen windows (Thanks Moredread) This bug could happen if you have floating and tiling windows (for example Firefox in tiling mode and its Open dialog in autmatically floating mode) and you opened a new tiling window while in fullscreen. i3 would then place the window below the floating windows, but floating clients are above fullscreen windows. Thus, the client would be placed above the fullscreen window. --- src/client.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/client.c b/src/client.c index c0031d71..b686a1ae 100644 --- a/src/client.c +++ b/src/client.c @@ -239,11 +239,20 @@ void client_set_below_floating(xcb_connection_t *conn, Client *client) { /* Ensure that it is below all floating clients */ Workspace *ws = client->workspace; Client *first_floating = TAILQ_FIRST(&(ws->floating_clients)); - if (first_floating != TAILQ_END(&(ws->floating_clients))) { - LOG("Setting below floating\n"); - uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW }; - xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); - } + if (first_floating == TAILQ_END(&(ws->floating_clients))) + return; + + LOG("Setting below floating\n"); + uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW }; + xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); + + if (client->workspace->fullscreen_client == NULL) + return; + + LOG("(and below fullscreen)\n"); + /* Ensure that the window is still below the fullscreen window */ + values[0] = client->workspace->fullscreen_client->frame; + xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); } /* From c6c0862e24d244b38c46559ad401daae0547e7da Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 12 Dec 2009 21:31:41 +0100 Subject: [PATCH 040/247] Bugfix: Correctly check for fullscreen windows when mapping new clients CUR_CELL only works if you currently are in that container (not for windows which are assigned to invisible workspaces, for example). --- src/manage.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/manage.c b/src/manage.c index 030a3883..da5ec175 100644 --- a/src/manage.c +++ b/src/manage.c @@ -350,12 +350,12 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, } } - if (CUR_CELL->workspace->fullscreen_client != NULL) { - if (new->container == CUR_CELL) { - /* If we are in fullscreen, we should lower the window to not be annoying */ - uint32_t values[] = { XCB_STACK_MODE_BELOW }; - xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values); - } + if (new->workspace->fullscreen_client != NULL) { + LOG("Setting below fullscreen window\n"); + + /* If we are in fullscreen, we should lower the window to not be annoying */ + uint32_t values[] = { XCB_STACK_MODE_BELOW }; + xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values); } /* Insert into the currently active container, if it’s not a dock window */ From 47e54241ce121ddc7e6f4ac1ece7b3b5e4aaf7ef Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 12 Dec 2009 21:34:53 +0100 Subject: [PATCH 041/247] Add new entry in debian/changelog to prevent apt from overwriting self-built packages --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 4399644b..f42cd35f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +i3-wm (3.d-bf1-1) unstable; urgency=low + + * NOT YET RELEASED + + -- Michael Stapelberg Sat, 12 Dec 2009 21:34:37 +0100 + i3-wm (3.d-2) unstable; urgency=low * debian: register in doc-base From 8d8804221ba2c2d854fc1dd12e8bdd5e4e4d9074 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 12 Dec 2009 21:42:56 +0100 Subject: [PATCH 042/247] Partly revert 4ba26659, it was just wrong. --- src/layout.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/layout.c b/src/layout.c index 80707319..98c270b7 100644 --- a/src/layout.c +++ b/src/layout.c @@ -463,7 +463,7 @@ void render_container(xcb_connection_t *conn, Container *container) { if (container->stack_limit == STACK_LIMIT_COLS) { /* wrap stores the number of rows after which we will * wrap to a new column. */ - wrap = container->stack_limit_value; + wrap = ceil((float)num_clients / container->stack_limit_value); } else if (container->stack_limit == STACK_LIMIT_ROWS) { /* When limiting rows, the wrap variable serves a * slightly different purpose: it holds the number of @@ -500,10 +500,10 @@ void render_container(xcb_connection_t *conn, Container *container) { if (container->stack_limit == STACK_LIMIT_COLS) { offset_x = current_col * (stack_win->rect.width / container->stack_limit_value); offset_y = current_row * decoration_height; - current_col++; - if ((current_col % wrap) == 0) { - current_row++; - current_col = 0; + current_row++; + if ((current_row % wrap) == 0) { + current_col++; + current_row = 0; } } else if (container->stack_limit == STACK_LIMIT_ROWS) { offset_x = current_col * wrap; From f87b98e0a7bf385fd60c06281c96cc2d9c768c44 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 12 Dec 2009 22:27:57 +0100 Subject: [PATCH 043/247] =?UTF-8?q?Take=20into=20account=20the=20window?= =?UTF-8?q?=E2=80=99s=20base=5F{width,height}=20when=20resizing=20(Thanks?= =?UTF-8?q?=20Mirko)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/client.h | 14 ++++++++++++++ src/client.c | 36 ++++++++++++++++++++++++++++++++++++ src/floating.c | 12 ++++++------ 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/include/client.h b/include/client.h index 9da3a5ec..dced48ec 100644 --- a/include/client.h +++ b/include/client.h @@ -112,6 +112,20 @@ void client_map(xcb_connection_t *conn, Client *client); */ void client_mark(xcb_connection_t *conn, Client *client, const char *mark); +/** + * Returns the minimum height of a specific window. The height is calculated + * by using 2 pixels (for the client window itself), possibly padding this to + * comply with the client’s base_height and then adding the decoration height. + * + */ +uint32_t client_min_height(Client *client); + +/** + * See client_min_height. + * + */ +uint32_t client_min_width(Client *client); + /** * Pretty-prints the client’s information into the logfile. * diff --git a/src/client.c b/src/client.c index b686a1ae..2de501ea 100644 --- a/src/client.c +++ b/src/client.c @@ -26,6 +26,7 @@ #include "client.h" #include "table.h" #include "workspace.h" +#include "config.h" /* * Removes the given client from the container, either because it will be inserted into another @@ -372,3 +373,38 @@ void client_mark(xcb_connection_t *conn, Client *client, const char *mark) { break; } } + +/* + * Returns the minimum height of a specific window. The height is calculated + * by using 2 pixels (for the client window itself), possibly padding this to + * comply with the client’s base_height and then adding the decoration height. + * + */ +uint32_t client_min_height(Client *client) { + uint32_t height = max(2, client->base_height); + i3Font *font = load_font(global_conn, config.font); + + if (client->titlebar_position == TITLEBAR_OFF && client->borderless) + return height; + + if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) + return height + 2; + + return height + font->height + 2 + 2; +} + +/* + * See client_min_height. + * + */ +uint32_t client_min_width(Client *client) { + uint32_t width = max(2, client->base_width); + + if (client->titlebar_position == TITLEBAR_OFF && client->borderless) + return width; + + if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) + return width + 2; + + return width + 2 + 2; +} diff --git a/src/floating.c b/src/floating.c index 4177b6e5..a8557416 100644 --- a/src/floating.c +++ b/src/floating.c @@ -173,7 +173,7 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre case BORDER_RIGHT: { int new_width = old_rect->width + (new_x - event->root_x); if ((new_width < 0) || - (new_width < 50 && client->rect.width >= new_width)) + (new_width < client_min_width(client) && client->rect.width >= new_width)) return; client->rect.width = new_width; break; @@ -182,7 +182,7 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre case BORDER_BOTTOM: { int new_height = old_rect->height + (new_y - event->root_y); if ((new_height < 0) || - (new_height < 20 && client->rect.height >= new_height)) + (new_height < client_min_height(client) && client->rect.height >= new_height)) return; client->rect.height = old_rect->height + (new_y - event->root_y); break; @@ -191,7 +191,7 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre case BORDER_TOP: { int new_height = old_rect->height + (event->root_y - new_y); if ((new_height < 0) || - (new_height < 20 && client->rect.height >= new_height)) + (new_height < client_min_height(client) && client->rect.height >= new_height)) return; client->rect.y = old_rect->y + (new_y - event->root_y); @@ -202,7 +202,7 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre case BORDER_LEFT: { int new_width = old_rect->width + (event->root_x - new_x); if ((new_width < 0) || - (new_width < 50 && client->rect.width >= new_width)) + (new_width < client_min_width(client) && client->rect.width >= new_width)) return; client->rect.x = old_rect->x + (new_x - event->root_x); client->rect.width = new_width; @@ -273,10 +273,10 @@ void floating_resize_window(xcb_connection_t *conn, Client *client, xcb_button_p int32_t new_height = old_rect->height + (new_y - event->root_y); /* Obey minimum window size and reposition the client */ - if (new_width >= 50) + if (new_width > 0 && new_width >= client_min_width(client)) client->rect.width = new_width; - if (new_height >= 20) + if (new_height > 0 && new_height >= client_min_height(client)) client->rect.height = new_height; /* resize_client flushes */ From 937048d47bfe6b3cb9d6a7eac2f76171b3568238 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 13 Dec 2009 17:02:22 +0100 Subject: [PATCH 044/247] Distribute rest space between windows as long as possible. (Thanks msi) When having 8 windows in a container which has 766 px available, you ended up losing 0,75 px per window which would quickly sum up. Now, the rest space (6 px in this example) is distributed in units of one pixel to as many windows as possible. --- src/layout.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/layout.c b/src/layout.c index 98c270b7..c626edbd 100644 --- a/src/layout.c +++ b/src/layout.c @@ -363,6 +363,10 @@ void render_container(xcb_connection_t *conn, Container *container) { num_clients++; if (container->mode == MODE_DEFAULT) { + int height = (container->height / max(1, num_clients)); + int rest_pixels = (container->height % max(1, num_clients)); + LOG("height per client = %d, rest = %d\n", height, rest_pixels); + CIRCLEQ_FOREACH(client, &(container->clients), clients) { /* If the client is in fullscreen mode, it does not get reconfigured */ if (container->workspace->fullscreen_client == client) { @@ -370,6 +374,13 @@ void render_container(xcb_connection_t *conn, Container *container) { continue; } + /* If we have some pixels left to distribute, add one + * pixel to each client as long as possible. */ + int this_height = height; + if (rest_pixels > 0) { + height++; + rest_pixels--; + } /* Check if we changed client->x or client->y by updating it. * Note the bitwise OR instead of logical OR to force evaluation of both statements */ if (client->force_reconfigure | @@ -377,7 +388,7 @@ void render_container(xcb_connection_t *conn, Container *container) { update_if_necessary(&(client->rect.y), container->y + (container->height / num_clients) * current_client) | update_if_necessary(&(client->rect.width), container->width) | - update_if_necessary(&(client->rect.height), container->height / num_clients)) + update_if_necessary(&(client->rect.height), this_height)) resize_client(conn, client); /* TODO: vertical default layout */ From 57e4972fd1b2f60e7862951bf219a871088e7c9f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 15 Dec 2009 19:11:01 +0100 Subject: [PATCH 045/247] Add clarification about border color in userguide (Thanks xeen) --- docs/userguide | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/userguide b/docs/userguide index 700f8359..529478ba 100644 --- a/docs/userguide +++ b/docs/userguide @@ -444,6 +444,10 @@ Colors are in HTML hex format, see below. client.focused #2F343A #900000 #FFFFFF -------------------------------------- +Note that for the window decorations the color around the child window is the +background color and the border color is only the two thin lines at the top of +the window. + === Interprocess communication i3 uses unix sockets to provide an IPC interface. At the moment, this interface From a61e34d2771fffdf2d94ee69d887585087c76b98 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 16 Dec 2009 22:59:25 +0100 Subject: [PATCH 046/247] Add x11-utils as dependency to have xmessage(1) for the welcome message --- DEPENDS | 2 ++ debian/control | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/DEPENDS b/DEPENDS index 48df53aa..47258068 100644 --- a/DEPENDS +++ b/DEPENDS @@ -10,6 +10,8 @@ In that case, please try using the versions mentioned below until a fix is provi * asciidoc >= 8.3.0 for docs/hacking-howto * asciidoc, xmlto, docbook-xml for man/i3.man * Xlib, the one that comes with your X-Server + * x11-utils for xmessage (only for displaying the welcome message, so this is + mainly interesting for distributors) Recommendations: * i3lock for locking your screen diff --git a/debian/control b/debian/control index 84e57e8a..7ea7ad66 100644 --- a/debian/control +++ b/debian/control @@ -22,7 +22,7 @@ Description: metapackage (i3 window manager, screen locker, menu, statusbar) Package: i3-wm Architecture: any Section: x11 -Depends: ${shlibs:Depends}, ${misc:Depends} +Depends: ${shlibs:Depends}, ${misc:Depends}, x11-utils Provides: x-window-manager Suggests: rxvt-unicode | x-terminal-emulator Recommends: xfonts-base From 5397e893b955eb1359cc306b014ef06cac71b8e8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 19 Dec 2009 22:33:50 +0100 Subject: [PATCH 047/247] logging: new makefile target to generate loglevels at compile time Using shell commands, a bitmask is generated for each file. Additionally, a C header containing an array of loglevels and their files is created in include/loglevels.h. --- Makefile | 25 ++++++++++++++++++++----- common.mk | 2 +- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 84f54cba..bfaff1ce 100644 --- a/Makefile +++ b/Makefile @@ -6,14 +6,14 @@ include $(TOPDIR)/common.mk AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c FILES:=$(filter-out $(AUTOGENERATED),$(wildcard src/*.c)) FILES:=$(FILES:.c=.o) -HEADERS=$(wildcard include/*.h) +HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h)) # Depend on the specific file (.c for each .o) and on all headers src/%.o: src/%.c ${HEADERS} echo "CC $<" - $(CC) $(CFLAGS) -c -o $@ $< + $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/$(shell basename $< .c)/ { print NR }' loglevels.tmp))" -c -o $@ $< -all: src/cfgparse.y.o src/cfgparse.yy.o ${FILES} +all: loglevels.h src/cfgparse.y.o src/cfgparse.yy.o ${FILES} echo "LINK i3" $(CC) -o i3 ${FILES} src/cfgparse.y.o src/cfgparse.yy.o $(LDFLAGS) echo "" @@ -22,15 +22,30 @@ all: src/cfgparse.y.o src/cfgparse.yy.o ${FILES} echo "SUBDIR i3-input" $(MAKE) TOPDIR=$(TOPDIR) -C i3-input +rm_loglevels: + rm -f loglevels.h + +loglevels.h: rm_loglevels + echo "LOGLEVELS" + for file in $$(ls src/*.c src/*.y src/*.l | grep -v 'cfgparse.\(tab\|yy\).c'); \ + do \ + echo $$(basename $$file .c); \ + done > loglevels.tmp + (echo "char *loglevels[] = {"; for file in $$(cat loglevels.tmp); \ + do \ + echo -e "\t\"$$file\", "; \ + done; \ + echo "};") > include/loglevels.h + src/cfgparse.yy.o: src/cfgparse.l echo "LEX $<" flex -i -o$(@:.o=.c) $< - $(CC) $(CFLAGS) -c -o $@ $(@:.o=.c) + $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c) src/cfgparse.y.o: src/cfgparse.y echo "YACC $<" bison --debug --verbose -b $(basename $< .y) -d $< - $(CC) $(CFLAGS) -c -o $@ $(<:.y=.tab.c) + $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c) install: all echo "INSTALL" diff --git a/common.mk b/common.mk index 70305148..856f9a7b 100644 --- a/common.mk +++ b/common.mk @@ -74,5 +74,5 @@ endif .SILENT: # Always remake the following targets -.PHONY: install clean dist distclean +.PHONY: install clean dist distclean rm_loglevels From 4226cc61de6d02dfc6c4ce34864b225fa89cbcc2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 19 Dec 2009 22:37:15 +0100 Subject: [PATCH 048/247] add log.c/log.h which contain all the log related macros and functions --- include/log.h | 65 +++++++++++++++++++++++++++ src/log.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 include/log.h create mode 100644 src/log.c diff --git a/include/log.h b/include/log.h new file mode 100644 index 00000000..def99fc1 --- /dev/null +++ b/include/log.h @@ -0,0 +1,65 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ +#ifndef _LOG_H +#define _LOG_H + +#include + +/** ##__VA_ARGS__ means: leave out __VA_ARGS__ completely if it is empty, that + is, delete the preceding comma */ +#define LOG(fmt, ...) verboselog(fmt, ##__VA_ARGS__) +#define ELOG(fmt, ...) errorlog("ERROR: " fmt, ##__VA_ARGS__) +#define DLOG(fmt, ...) debuglog(LOGLEVEL, "%s:%s:%d - " fmt, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__) + +extern char *loglevels[]; + +/** + * Enables the given loglevel. + * + */ +void add_loglevel(const char *level); + +/** + * Set verbosity of i3. If verbose is set to true, informative messages will + * be printed to stdout. If verbose is set to false, only errors will be + * printed. + * + */ +void set_verbosity(bool _verbose); + +/** + * Logs the given message to stdout while prefixing the current time to it, + * but only if the corresponding debug loglevel was activated. + * + */ +void debuglog(int lev, char *fmt, ...); + +/** + * Logs the given message to stdout while prefixing the current time to it. + * + */ +void errorlog(char *fmt, ...); + +/** + * Logs the given message to stdout while prefixing the current time to it, + * but only if verbose mode is activated. + * + */ +void verboselog(char *fmt, ...); + +/** + * Logs the given message to stdout while prefixing the current time to it. + * This is to be called by LOG() which includes filename/linenumber + * + */ +void slog(char *fmt, va_list args); + +#endif diff --git a/src/log.c b/src/log.c new file mode 100644 index 00000000..1fcf70cb --- /dev/null +++ b/src/log.c @@ -0,0 +1,121 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + * src/log.c: handles the setting of loglevels, contains the logging functions. + * + */ +#include +#include +#include +#include + +#include "util.h" +#include "log.h" + +/* loglevels.h is autogenerated at make time */ +#include "loglevels.h" + +static uint32_t loglevel = 0; +static bool verbose = false; + +/** + * Set verbosity of i3. If verbose is set to true, informative messages will + * be printed to stdout. If verbose is set to false, only errors will be + * printed. + * + */ +void set_verbosity(bool _verbose) { + verbose = _verbose; +} + +/** + * Enables the given loglevel. + * + */ +void add_loglevel(const char *level) { + /* Handle the special loglevel "all" */ + if (strcasecmp(level, "all") == 0) { + loglevel = UINT32_MAX; + return; + } + + for (int i = 0; i < sizeof(loglevels) / sizeof(char*); i++) { + if (strcasecmp(loglevels[i], level) != 0) + continue; + + /* The position in the array (plus one) is the amount of times + * which we need to shift 1 to the left to get our bitmask for + * the specific loglevel. */ + loglevel |= (1 << (i+1)); + break; + } +} + +/* + * Logs the given message to stdout while prefixing the current time to it. + * This is to be called by *LOG() which includes filename/linenumber/function. + * + */ +void vlog(char *fmt, va_list args) { + char timebuf[64]; + + /* Get current time */ + time_t t = time(NULL); + /* Convert time to local time (determined by the locale) */ + struct tm *tmp = localtime(&t); + /* Generate time prefix */ + strftime(timebuf, sizeof(timebuf), "%x %X - ", tmp); + printf("%s", timebuf); + vprintf(fmt, args); +} + +/** + * Logs the given message to stdout while prefixing the current time to it, + * but only if verbose mode is activated. + * + */ +void verboselog(char *fmt, ...) { + va_list args; + + if (!verbose) + return; + + va_start(args, fmt); + vlog(fmt, args); + va_end(args); +} + +/** + * Logs the given message to stdout while prefixing the current time to it. + * + */ +void errorlog(char *fmt, ...) { + va_list args; + + va_start(args, fmt); + vlog(fmt, args); + va_end(args); +} + +/* + * Logs the given message to stdout while prefixing the current time to it, + * but only if the corresponding debug loglevel was activated. + * This is to be called by DLOG() which includes filename/linenumber + * + */ +void debuglog(int lev, char *fmt, ...) { + va_list args; + + if ((loglevel & lev) == 0) + return; + + va_start(args, fmt); + vlog(fmt, args); + va_end(args); +} From 37d795c81d996a528a3446d822aabc92984895c0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 19 Dec 2009 22:38:32 +0100 Subject: [PATCH 049/247] Add new options -V for verbose mode and -d for debug log levels --- src/mainx.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/mainx.c b/src/mainx.c index 4e33aa01..0090d587 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -49,6 +49,7 @@ #include "xinerama.h" #include "manage.h" #include "ipc.h" +#include "log.h" xcb_connection_t *global_conn; @@ -169,7 +170,7 @@ int main(int argc, char *argv[], char *env[]) { start_argv = argv; - while ((opt = getopt_long(argc, argv, "c:vahl", long_options, &option_index)) != -1) { + while ((opt = getopt_long(argc, argv, "c:vahld:V", long_options, &option_index)) != -1) { switch (opt) { case 'a': LOG("Autostart disabled using -a\n"); @@ -181,14 +182,23 @@ int main(int argc, char *argv[], char *env[]) { case 'v': printf("i3 version " I3_VERSION " © 2009 Michael Stapelberg and contributors\n"); exit(EXIT_SUCCESS); + case 'V': + set_verbosity(true); + break; + case 'd': + LOG("Enabling debug loglevel %s\n", optarg); + add_loglevel(optarg); + break; case 'l': /* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */ break; default: - fprintf(stderr, "Usage: %s [-c configfile] [-a] [-v]\n", argv[0]); + fprintf(stderr, "Usage: %s [-c configfile] [-d loglevel] [-a] [-v] [-V]\n", argv[0]); fprintf(stderr, "\n"); fprintf(stderr, "-a: disable autostart\n"); fprintf(stderr, "-v: display version and exit\n"); + fprintf(stderr, "-V: enable verbose mode\n"); + fprintf(stderr, "-d : enable debug loglevel \n"); fprintf(stderr, "-c : use the provided configfile instead\n"); exit(EXIT_FAILURE); } From 6ef0d1fa79776383f2618151e07a20355173826a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 19 Dec 2009 22:39:00 +0100 Subject: [PATCH 050/247] Touch each log message and classify it as DLOG (debug), ELOG (error) or LOG (verbose) --- include/client.h | 2 +- include/util.h | 13 +---- src/cfgparse.y | 17 +++--- src/click.c | 43 +++++++-------- src/client.c | 9 ++-- src/commands.c | 133 ++++++++++++++++++++++++----------------------- src/config.c | 15 +++--- src/floating.c | 36 ++++++------- src/handlers.c | 97 +++++++++++++++++----------------- src/ipc.c | 23 ++++---- src/layout.c | 51 +++++++++--------- src/mainx.c | 20 +++---- src/manage.c | 37 ++++++------- src/resize.c | 77 +++++++++++++-------------- src/table.c | 57 ++++++++++---------- src/util.c | 36 +++---------- src/workspace.c | 33 ++++++------ src/xcb.c | 7 +-- src/xinerama.c | 39 +++++++------- 19 files changed, 364 insertions(+), 381 deletions(-) diff --git a/include/client.h b/include/client.h index dced48ec..54035f6e 100644 --- a/include/client.h +++ b/include/client.h @@ -131,7 +131,7 @@ uint32_t client_min_width(Client *client); * */ #define CLIENT_LOG(client) do { \ - LOG("Window: frame 0x%08x, child 0x%08x\n", client->frame, client->child); \ + DLOG("Window: frame 0x%08x, child 0x%08x\n", client->frame, client->child); \ } while (0) #endif diff --git a/include/util.h b/include/util.h index ed85d539..f4e95604 100644 --- a/include/util.h +++ b/include/util.h @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * (c) 2009 Michael Stapelberg and contributors + * © 2009 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -34,10 +34,6 @@ } \ while (0) -/** ##__VA_ARGS__ means: leave out __VA_ARGS__ completely if it is empty, that - is, delete the preceding comma */ -#define LOG(fmt, ...) slog("%s:%s:%d - " fmt, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__) - TAILQ_HEAD(keyvalue_table_head, keyvalue_element); extern struct keyvalue_table_head by_parent; extern struct keyvalue_table_head by_child; @@ -45,13 +41,6 @@ extern struct keyvalue_table_head by_child; int min(int a, int b); int max(int a, int b); -/** - * Logs the given message to stdout while prefixing the current time to it. - * This is to be called by LOG() which includes filename/linenumber - * - */ -void slog(char *fmt, ...); - /** * Safe-wrapper around malloc which exits if malloc returns NULL (meaning that * there is no more memory available) diff --git a/src/cfgparse.y b/src/cfgparse.y index 878d2e17..030c51af 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -21,6 +21,7 @@ #include "table.h" #include "workspace.h" #include "xcb.h" +#include "log.h" typedef struct yy_buffer_state *YY_BUFFER_STATE; extern int yylex(void); @@ -94,7 +95,7 @@ void parse_file(const char *f) { new->key = sstrdup(v_key); new->value = sstrdup(v_value); SLIST_INSERT_HEAD(&variables, new, variables); - LOG("Got new variable %s = %s\n", v_key, v_value); + DLOG("Got new variable %s = %s\n", v_key, v_value); continue; } } @@ -324,7 +325,7 @@ modeline: floating_modifier: TOKFLOATING_MODIFIER WHITESPACE binding_modifiers { - LOG("floating modifier = %d\n", $3); + DLOG("floating modifier = %d\n", $3); config.floating_modifier = $3; } ; @@ -332,7 +333,7 @@ floating_modifier: new_container: TOKNEWCONTAINER WHITESPACE TOKCONTAINERMODE { - LOG("new containers will be in mode %d\n", $3); + DLOG("new containers will be in mode %d\n", $3); config.container_mode = $3; /* We also need to change the layout of the already existing @@ -354,7 +355,7 @@ new_container: } | TOKNEWCONTAINER WHITESPACE TOKSTACKLIMIT WHITESPACE TOKSTACKLIMIT WHITESPACE NUMBER { - LOG("stack-limit %d with val %d\n", $5, $7); + DLOG("stack-limit %d with val %d\n", $5, $7); config.container_stack_limit = $5; config.container_stack_limit_value = $7; @@ -373,7 +374,7 @@ new_container: new_window: TOKNEWWINDOW WHITESPACE WORD { - LOG("new windows should start in mode %s\n", $3); + DLOG("new windows should start in mode %s\n", $3); config.default_border = strdup($3); } ; @@ -383,7 +384,7 @@ workspace: { int ws_num = $3; if (ws_num < 1) { - LOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); + DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); } else { Workspace *ws = workspace_get(ws_num - 1); ws->preferred_screen = sstrdup($7); @@ -395,7 +396,7 @@ workspace: { int ws_num = $3; if (ws_num < 1) { - LOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); + DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); } else { if ($5 != NULL) workspace_set_name(workspace_get(ws_num - 1), $5); @@ -486,7 +487,7 @@ exec: terminal: TOKTERMINAL WHITESPACE STR { - LOG("The terminal option is DEPRECATED and has no effect. " + DLOG("The terminal option is DEPRECATED and has no effect. " "Please remove it from your configuration file."); } ; diff --git a/src/click.c b/src/click.c index efb26ad3..dccd5b4f 100644 --- a/src/click.c +++ b/src/click.c @@ -36,6 +36,7 @@ #include "commands.h" #include "floating.h" #include "resize.h" +#include "log.h" static struct Stack_Window *get_stack_window(xcb_window_t window_id) { struct Stack_Window *current; @@ -97,18 +98,18 @@ static bool button_press_stackwin(xcb_connection_t *conn, xcb_button_press_event int wrap = ceil((float)num_clients / container->stack_limit_value); int clicked_column = (event->event_x / (stack_win->rect.width / container->stack_limit_value)); int clicked_row = (event->event_y / decoration_height); - LOG("clicked on column %d, row %d\n", clicked_column, clicked_row); + DLOG("clicked on column %d, row %d\n", clicked_column, clicked_row); destination = (wrap * clicked_column) + clicked_row; } else { int width = (stack_win->rect.width / ceil((float)num_clients / container->stack_limit_value)); int clicked_column = (event->event_x / width); int clicked_row = (event->event_y / decoration_height); - LOG("clicked on column %d, row %d\n", clicked_column, clicked_row); + DLOG("clicked on column %d, row %d\n", clicked_column, clicked_row); destination = (container->stack_limit_value * clicked_column) + clicked_row; } } - LOG("Click on stack_win for client %d\n", destination); + DLOG("Click on stack_win for client %d\n", destination); CIRCLEQ_FOREACH(client, &(stack_win->container->clients), clients) if (c++ == destination) { set_focus(conn, client, true); @@ -129,7 +130,7 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e if (screen->bar != event->event) continue; - LOG("Click on a bar\n"); + DLOG("Click on a bar\n"); /* Check if the button was one of button4 or button5 (scroll up / scroll down) */ if (event->detail == XCB_BUTTON_INDEX_4 || event->detail == XCB_BUTTON_INDEX_5) { @@ -158,7 +159,7 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e TAILQ_FOREACH(ws, workspaces, workspaces) { if (ws->screen != screen) continue; - LOG("Checking if click was on workspace %d with drawn = %d, tw = %d\n", + DLOG("Checking if click was on workspace %d with drawn = %d, tw = %d\n", ws->num, drawn, ws->text_width); if (event->event_x > (drawn + 1) && event->event_x <= (drawn + 1 + ws->text_width + 5 + 5)) { @@ -201,7 +202,7 @@ static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client, Workspace *ws = con->workspace; int first = 0, second = 0; - LOG("click was %d px to the right, %d px to the left, %d px to top, %d px to bottom\n", + DLOG("click was %d px to the right, %d px to the left, %d px to top, %d px to bottom\n", to_right, to_left, to_top, to_bottom); if (to_right < to_left && @@ -209,7 +210,7 @@ static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client, to_right < to_bottom) { /* …right border */ first = con->col + (con->colspan - 1); - LOG("column %d\n", first); + DLOG("column %d\n", first); if (!cell_exists(first, con->row) || (first == (ws->cols-1))) @@ -251,7 +252,7 @@ static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client, } int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) { - LOG("Button %d pressed\n", event->state); + DLOG("Button %d pressed\n", event->state); /* This was either a focus for a client’s parent (= titlebar)… */ Client *client = table_get(&by_child, event->event); bool border_click = false; @@ -265,20 +266,20 @@ 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, floating_modifier was pressed and no client found\n"); + DLOG("Not handling, floating_modifier was pressed and no client found\n"); return 1; } if (client->fullscreen) { - LOG("Not handling, client is in fullscreen mode\n"); + DLOG("Not handling, client is in fullscreen mode\n"); return 1; } if (client_is_floating(client)) { - LOG("button %d pressed\n", event->detail); + DLOG("button %d pressed\n", event->detail); if (event->detail == 1) { - LOG("left mouse button, dragging\n"); + DLOG("left mouse button, dragging\n"); floating_drag_window(conn, client, event); } else if (event->detail == 3) { - LOG("right mouse button\n"); + DLOG("right mouse button\n"); floating_resize_window(conn, client, event); } return 1; @@ -301,7 +302,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ if (button_press_bar(conn, event)) return 1; - LOG("Could not handle this button press\n"); + DLOG("Could not handle this button press\n"); return 1; } @@ -309,19 +310,19 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ set_focus(conn, client, true); /* Let’s see if this was on the borders (= resize). If not, we’re done */ - LOG("press button on x=%d, y=%d\n", event->event_x, event->event_y); + DLOG("press button on x=%d, y=%d\n", event->event_x, event->event_y); resize_orientation_t orientation = O_VERTICAL; Container *con = client->container; int first, second; if (client->dock) { - LOG("dock. done.\n"); + DLOG("dock. done.\n"); xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); xcb_flush(conn); return 1; } - LOG("event->event_x = %d, client->rect.width = %d\n", event->event_x, client->rect.width); + DLOG("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 @@ -331,12 +332,12 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ 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"); + DLOG("Fixing border_click = false because of click in child\n"); border_click = false; } if (!border_click) { - LOG("client. done.\n"); + DLOG("client. done.\n"); xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); /* Floating clients should be raised on click */ if (client_is_floating(client)) @@ -348,7 +349,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ /* Don’t handle events inside the titlebar, only borders are interesting */ i3Font *font = load_font(conn, config.font); if (event->event_y >= 2 && event->event_y <= (font->height + 2 + 2)) { - LOG("click on titlebar\n"); + DLOG("click on titlebar\n"); /* Floating clients can be dragged by grabbing their titlebar */ if (client_is_floating(client)) { @@ -392,7 +393,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ } else if (event->event_x > 2) { /* …right border */ first = con->col + (con->colspan - 1); - LOG("column %d\n", first); + DLOG("column %d\n", first); if (!cell_exists(first, con->row) || (first == (ws->cols-1))) diff --git a/src/client.c b/src/client.c index 2de501ea..c3113391 100644 --- a/src/client.c +++ b/src/client.c @@ -27,6 +27,7 @@ #include "table.h" #include "workspace.h" #include "config.h" +#include "log.h" /* * Removes the given client from the container, either because it will be inserted into another @@ -44,7 +45,7 @@ void client_remove_from_container(xcb_connection_t *conn, Client *client, Contai if (CIRCLEQ_EMPTY(&(container->clients)) && (container->mode == MODE_STACK || container->mode == MODE_TABBED)) { - LOG("Unmapping stack window\n"); + DLOG("Unmapping stack window\n"); struct Stack_Window *stack_win = &(container->stack_win); stack_win->rect.height = 0; xcb_unmap_window(conn, stack_win->window); @@ -169,7 +170,7 @@ void client_enter_fullscreen(xcb_connection_t *conn, Client *client) { workspace->rect.width, workspace->rect.height}; - LOG("child itself will be at %dx%d with size %dx%d\n", + DLOG("child itself will be at %dx%d with size %dx%d\n", values[0], values[1], values[2], values[3]); xcb_configure_window(conn, client->frame, mask, values); @@ -243,14 +244,14 @@ void client_set_below_floating(xcb_connection_t *conn, Client *client) { if (first_floating == TAILQ_END(&(ws->floating_clients))) return; - LOG("Setting below floating\n"); + DLOG("Setting below floating\n"); uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW }; xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); if (client->workspace->fullscreen_client == NULL) return; - LOG("(and below fullscreen)\n"); + DLOG("(and below fullscreen)\n"); /* Ensure that the window is still below the fullscreen window */ values[0] = client->workspace->fullscreen_client->frame; xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); diff --git a/src/commands.c b/src/commands.c index 4ef03d9c..7cb0bdca 100644 --- a/src/commands.c +++ b/src/commands.c @@ -30,6 +30,7 @@ #include "workspace.h" #include "commands.h" #include "resize.h" +#include "log.h" bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) { /* If this container is empty, we’re done */ @@ -45,7 +46,7 @@ bool focus_window_in_container(xcb_connection_t *conn, Container *container, dir else if (direction == D_DOWN) { if ((candidate = CIRCLEQ_NEXT_OR_NULL(&(container->clients), container->currently_focused, clients)) == NULL) candidate = CIRCLEQ_FIRST(&(container->clients)); - } else LOG("Direction not implemented!\n"); + } else ELOG("Direction not implemented!\n"); /* If we could not switch, the container contains exactly one client. We return false */ if (candidate == container->currently_focused) @@ -74,11 +75,11 @@ static void jump_to_mark(xcb_connection_t *conn, const char *mark) { return; } - LOG("No window with this mark found\n"); + ELOG("No window with this mark found\n"); } static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t thing) { - LOG("focusing direction %d\n", direction); + DLOG("focusing direction %d\n", direction); int new_row = current_row, new_col = current_col; @@ -120,7 +121,7 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t i3Screen *target = get_screen_containing(bounds.x, bounds.y); if (target == NULL) { - LOG("Target screen NULL\n"); + DLOG("Target screen NULL\n"); /* Wrap around if the target screen is out of bounds */ if (direction == D_RIGHT) target = get_screen_most(D_LEFT, cs); @@ -131,7 +132,7 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t else target = get_screen_most(D_UP, cs); } - LOG("Switching to ws %d\n", target->current_workspace + 1); + DLOG("Switching to ws %d\n", target->current_workspace + 1); workspace_show(conn, target->current_workspace->num + 1); return; } @@ -159,11 +160,11 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t } } else { /* Let’s see if there is a screen down/up there to which we can switch */ - LOG("container is at %d with height %d\n", container->y, container->height); + DLOG("container is at %d with height %d\n", container->y, container->height); i3Screen *screen; int destination_y = (direction == D_UP ? (container->y - 1) : (container->y + container->height + 1)); if ((screen = get_screen_containing(container->x, destination_y)) == NULL) { - LOG("Wrapping screen around vertically\n"); + DLOG("Wrapping screen around vertically\n"); /* No screen found? Then wrap */ screen = get_screen_most((direction == D_UP ? D_DOWN : D_UP), container->workspace->screen); } @@ -173,9 +174,9 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t check_colrow_boundaries(); - LOG("new_col = %d, new_row = %d\n", new_col, new_row); + DLOG("new_col = %d, new_row = %d\n", new_col, new_row); if (t_ws->table[new_col][new_row]->currently_focused == NULL) { - LOG("Cell empty, checking for colspanned client above...\n"); + DLOG("Cell empty, checking for colspanned client above...\n"); for (int cols = 0; cols < new_col; cols += t_ws->table[cols][new_row]->colspan) { if (new_col > (cols + (t_ws->table[cols][new_row]->colspan - 1))) continue; @@ -183,7 +184,7 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t new_col = cols; break; } - LOG("Fixed it to new col %d\n", new_col); + DLOG("Fixed it to new col %d\n", new_col); } } else if (direction == D_LEFT || direction == D_RIGHT) { if (direction == D_RIGHT && cell_exists(current_col+1, current_row)) @@ -202,11 +203,11 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t } } else { /* Let’s see if there is a screen left/right here to which we can switch */ - LOG("container is at %d with width %d\n", container->x, container->width); + DLOG("container is at %d with width %d\n", container->x, container->width); i3Screen *screen; int destination_x = (direction == D_LEFT ? (container->x - 1) : (container->x + container->width + 1)); if ((screen = get_screen_containing(destination_x, container->y)) == NULL) { - LOG("Wrapping screen around horizontally\n"); + DLOG("Wrapping screen around horizontally\n"); screen = get_screen_most((direction == D_LEFT ? D_RIGHT : D_LEFT), container->workspace->screen); } t_ws = screen->current_workspace; @@ -215,9 +216,9 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t check_colrow_boundaries(); - LOG("new_col = %d, new_row = %d\n", new_col, new_row); + DLOG("new_col = %d, new_row = %d\n", new_col, new_row); if (t_ws->table[new_col][new_row]->currently_focused == NULL) { - LOG("Cell empty, checking for rowspanned client above...\n"); + DLOG("Cell empty, checking for rowspanned client above...\n"); for (int rows = 0; rows < new_row; rows += t_ws->table[new_col][rows]->rowspan) { if (new_row > (rows + (t_ws->table[new_col][rows]->rowspan - 1))) continue; @@ -225,10 +226,10 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t new_row = rows; break; } - LOG("Fixed it to new row %d\n", new_row); + DLOG("Fixed it to new row %d\n", new_row); } } else { - LOG("direction unhandled\n"); + ELOG("direction unhandled\n"); return; } @@ -254,7 +255,7 @@ static bool move_current_window_in_container(xcb_connection_t *conn, Client *cli if (other == CIRCLEQ_END(&(client->container->clients))) return false; - LOG("i can do that\n"); + DLOG("i can do that\n"); /* We can move the client inside its current container */ CIRCLEQ_REMOVE(&(client->container->clients), client, clients); if (direction == D_UP) @@ -411,7 +412,7 @@ static void move_current_container(xcb_connection_t *conn, direction_t direction return; } - LOG("old = %d,%d and new = %d,%d\n", container->col, container->row, new->col, new->row); + DLOG("old = %d,%d and new = %d,%d\n", container->col, container->row, new->col, new->row); /* Swap the containers */ int col = new->col; @@ -453,7 +454,7 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction /* Snap to the left is actually a move to the left and then a snap right */ if (!cell_exists(container->col - 1, container->row) || CUR_TABLE[container->col-1][container->row]->currently_focused != NULL) { - LOG("cannot snap to left - the cell is already used\n"); + ELOG("cannot snap to left - the cell is already used\n"); return; } @@ -466,18 +467,18 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction for (int i = 0; i < container->rowspan; i++) if (!cell_exists(new_col, container->row + i) || CUR_TABLE[new_col][container->row + i]->currently_focused != NULL) { - LOG("cannot snap to right - the cell is already used\n"); + ELOG("cannot snap to right - the cell is already used\n"); return; } /* Check if there are other cells with rowspan, which are in our way. * If so, reduce their rowspan. */ for (int i = container->row-1; i >= 0; i--) { - LOG("we got cell %d, %d with rowspan %d\n", + DLOG("we got cell %d, %d with rowspan %d\n", new_col, i, CUR_TABLE[new_col][i]->rowspan); while ((CUR_TABLE[new_col][i]->rowspan-1) >= (container->row - i)) CUR_TABLE[new_col][i]->rowspan--; - LOG("new rowspan = %d\n", CUR_TABLE[new_col][i]->rowspan); + DLOG("new rowspan = %d\n", CUR_TABLE[new_col][i]->rowspan); } container->colspan++; @@ -486,7 +487,7 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction case D_UP: if (!cell_exists(container->col, container->row - 1) || CUR_TABLE[container->col][container->row-1]->currently_focused != NULL) { - LOG("cannot snap to top - the cell is already used\n"); + ELOG("cannot snap to top - the cell is already used\n"); return; } @@ -494,21 +495,21 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction snap_current_container(conn, D_DOWN); return; case D_DOWN: { - LOG("snapping down\n"); + DLOG("snapping down\n"); int new_row = container->row + container->rowspan; for (int i = 0; i < container->colspan; i++) if (!cell_exists(container->col + i, new_row) || CUR_TABLE[container->col + i][new_row]->currently_focused != NULL) { - LOG("cannot snap down - the cell is already used\n"); + ELOG("cannot snap down - the cell is already used\n"); return; } for (int i = container->col-1; i >= 0; i--) { - LOG("we got cell %d, %d with colspan %d\n", + DLOG("we got cell %d, %d with colspan %d\n", i, new_row, CUR_TABLE[i][new_row]->colspan); while ((CUR_TABLE[i][new_row]->colspan-1) >= (container->col - i)) CUR_TABLE[i][new_row]->colspan--; - LOG("new colspan = %d\n", CUR_TABLE[i][new_row]->colspan); + DLOG("new colspan = %d\n", CUR_TABLE[i][new_row]->colspan); } @@ -535,7 +536,7 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl /* Check if there is already a fullscreen client on the destination workspace and * stop moving if so. */ if (client->fullscreen && (t_ws->fullscreen_client != NULL)) { - LOG("Not moving: Fullscreen client already existing on destination workspace.\n"); + ELOG("Not moving: Fullscreen client already existing on destination workspace.\n"); return; } @@ -543,24 +544,24 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl /* If we’re moving it to an invisible screen, we need to unmap it */ if (!workspace_is_visible(t_ws)) { - LOG("This workspace is not visible, unmapping\n"); + DLOG("This workspace is not visible, unmapping\n"); client_unmap(conn, client); } else { /* If this is not the case, we move the window to a workspace * which is on another screen, so we also need to adjust its * coordinates. */ - LOG("before x = %d, y = %d\n", client->rect.x, client->rect.y); + DLOG("before x = %d, y = %d\n", client->rect.x, client->rect.y); uint32_t relative_x = client->rect.x - old_ws->rect.x, relative_y = client->rect.y - old_ws->rect.y; - LOG("rel_x = %d, rel_y = %d\n", relative_x, relative_y); + DLOG("rel_x = %d, rel_y = %d\n", relative_x, relative_y); client->rect.x = t_ws->rect.x + relative_x; client->rect.y = t_ws->rect.y + relative_y; - LOG("after x = %d, y = %d\n", client->rect.x, client->rect.y); + DLOG("after x = %d, y = %d\n", client->rect.x, client->rect.y); reposition_client(conn, client); xcb_flush(conn); } - LOG("done\n"); + DLOG("done\n"); render_layout(conn); @@ -584,7 +585,7 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa Client *current_client = container->currently_focused; if (current_client == NULL) { - LOG("No currently focused client in current container.\n"); + ELOG("No currently focused client in current container.\n"); return; } Client *to_focus = CIRCLEQ_NEXT_OR_NULL(&(container->clients), current_client, clients); @@ -595,7 +596,7 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa /* Check if there is already a fullscreen client on the destination workspace and * stop moving if so. */ if (current_client->fullscreen && (t_ws->fullscreen_client != NULL)) { - LOG("Not moving: Fullscreen client already existing on destination workspace.\n"); + ELOG("Not moving: Fullscreen client already existing on destination workspace.\n"); return; } @@ -611,7 +612,7 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa CIRCLEQ_INSERT_TAIL(&(to_container->clients), current_client, clients); SLIST_INSERT_HEAD(&(to_container->workspace->focus_stack), current_client, focus_clients); - LOG("Moved.\n"); + DLOG("Moved.\n"); current_client->container = to_container; current_client->workspace = to_container->workspace; @@ -620,11 +621,11 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa /* If we’re moving it to an invisible screen, we need to unmap it */ if (!workspace_is_visible(to_container->workspace)) { - LOG("This workspace is not visible, unmapping\n"); + DLOG("This workspace is not visible, unmapping\n"); client_unmap(conn, current_client); } else { if (current_client->fullscreen) { - LOG("Calling client_enter_fullscreen again\n"); + DLOG("Calling client_enter_fullscreen again\n"); client_enter_fullscreen(conn, current_client); } } @@ -656,7 +657,7 @@ static void jump_to_window(xcb_connection_t *conn, const char *arguments) { if ((client = get_matching_client(conn, classtitle, NULL)) == NULL) { free(classtitle); - LOG("No matching client found.\n"); + ELOG("No matching client found.\n"); return; } @@ -678,7 +679,7 @@ static void jump_to_container(xcb_connection_t *conn, const char *arguments) { /* No match? Either no arguments were specified, or no numbers */ if (result < 1) { - LOG("At least one valid argument required\n"); + ELOG("At least one valid argument required\n"); return; } @@ -688,7 +689,7 @@ static void jump_to_container(xcb_connection_t *conn, const char *arguments) { if (result < 3) return; - LOG("Boundary-checking col %d, row %d... (max cols %d, max rows %d)\n", col, row, c_ws->cols, c_ws->rows); + DLOG("Boundary-checking col %d, row %d... (max cols %d, max rows %d)\n", col, row, c_ws->cols, c_ws->rows); /* Move to row/col */ if (row >= c_ws->rows) @@ -696,7 +697,7 @@ static void jump_to_container(xcb_connection_t *conn, const char *arguments) { if (col >= c_ws->cols) col = c_ws->cols - 1; - LOG("Jumping to col %d, row %d\n", col, row); + DLOG("Jumping to col %d, row %d\n", col, row); if (c_ws->table[col][row]->currently_focused != NULL) set_focus(conn, c_ws->table[col][row]->currently_focused, true); } @@ -725,7 +726,7 @@ static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) { } else if (strcasecmp(arguments, "ft") == 0) { Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); if (last_focused == SLIST_END(&(c_ws->focus_stack))) { - LOG("Cannot select the next floating/tiling client because there is no client at all\n"); + ELOG("Cannot select the next floating/tiling client because there is no client at all\n"); return; } @@ -733,17 +734,17 @@ static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) { } else { /* …or a number was specified */ if (sscanf(arguments, "%u", ×) != 1) { - LOG("No or invalid argument given (\"%s\"), using default of 1 times\n", arguments); + ELOG("No or invalid argument given (\"%s\"), using default of 1 times\n", arguments); times = 1; } SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients) { if (++count < times) { - LOG("Skipping\n"); + DLOG("Skipping\n"); continue; } - LOG("Focussing\n"); + DLOG("Focussing\n"); set_focus(conn, current, true); break; } @@ -767,7 +768,7 @@ static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) { static char **append_argument(char **original, char *argument) { int num_args; for (num_args = 0; original[num_args] != NULL; num_args++) { - LOG("original argument: \"%s\"\n", original[num_args]); + DLOG("original argument: \"%s\"\n", original[num_args]); /* If the argument is already present we return the original pointer */ if (strcmp(original[num_args], argument) == 0) return original; @@ -821,7 +822,7 @@ static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, c command += strlen("left"); } else if (STARTS_WITH(command, "right")) { first = con->col + (con->colspan - 1); - LOG("column %d\n", first); + DLOG("column %d\n", first); if (!cell_exists(first, con->row) || (first == (ws->cols-1))) @@ -846,7 +847,7 @@ static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, c orientation = O_HORIZONTAL; command += strlen("bottom"); } else { - LOG("Syntax: resize [+|-]\n"); + ELOG("Syntax: resize [+|-]\n"); return; } @@ -882,14 +883,14 @@ void parse_command(xcb_connection_t *conn, const char *command) { if (STARTS_WITH(command, "mark")) { if (last_focused == NULL) { - LOG("There is no window to mark\n"); + ELOG("There is no window to mark\n"); return; } const char *rest = command + strlen("mark"); while (*rest == ' ') rest++; if (*rest == '\0') { - LOG("interactive mark starting\n"); + DLOG("interactive mark starting\n"); start_application("i3-input -p 'mark ' -l 1 -P 'Mark: '"); } else { LOG("mark with \"%s\"\n", rest); @@ -903,7 +904,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { while (*rest == ' ') rest++; if (*rest == '\0') { - LOG("interactive go to mark starting\n"); + DLOG("interactive go to mark starting\n"); start_application("i3-input -p 'goto ' -l 1 -P 'Goto: '"); } else { LOG("go to \"%s\"\n", rest); @@ -914,7 +915,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { if (STARTS_WITH(command, "stack-limit ")) { if (last_focused == NULL || client_is_floating(last_focused)) { - LOG("No container focused\n"); + ELOG("No container focused\n"); return; } const char *rest = command + strlen("stack-limit "); @@ -925,7 +926,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { last_focused->container->stack_limit = STACK_LIMIT_COLS; rest += strlen("cols "); } else { - LOG("Syntax: stack-limit \n"); + ELOG("Syntax: stack-limit \n"); return; } @@ -974,7 +975,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { if (STARTS_WITH(command, "kill")) { if (last_focused == NULL) { - LOG("There is no window to kill\n"); + ELOG("There is no window to kill\n"); return; } @@ -1010,7 +1011,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { /* Is it just 's' for stacking or 'd' for default? */ if ((command[0] == 's' || command[0] == 'd' || command[0] == 'T') && (command[1] == '\0')) { if (last_focused != NULL && client_is_floating(last_focused)) { - LOG("not switching, this is a floating client\n"); + ELOG("not switching, this is a floating client\n"); return; } LOG("Switching mode for current container\n"); @@ -1027,7 +1028,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { /* or even 'bt' (toggle border: 'bp' -> 'bb' -> 'bn' ) */ if (command[0] == 'b') { if (last_focused == NULL) { - LOG("No window focused, cannot change border type\n"); + ELOG("No window focused, cannot change border type\n"); return; } @@ -1068,7 +1069,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { with = WITH_SCREEN; command++; } else { - LOG("not yet implemented.\n"); + ELOG("not yet implemented.\n"); return; } } @@ -1081,7 +1082,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { return; } if (last_focused == NULL) { - LOG("Cannot toggle tiling/floating: workspace empty\n"); + ELOG("Cannot toggle tiling/floating: workspace empty\n"); return; } @@ -1115,7 +1116,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { direction_t direction; int times = strtol(command, &rest, 10); if (rest == NULL) { - LOG("Invalid command (\"%s\")\n", command); + ELOG("Invalid command (\"%s\")\n", command); return; } @@ -1133,7 +1134,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { int workspace = strtol(rest, &rest, 10); if (rest == NULL) { - LOG("Invalid command (\"%s\")\n", command); + ELOG("Invalid command (\"%s\")\n", command); return; } @@ -1145,13 +1146,13 @@ void parse_command(xcb_connection_t *conn, const char *command) { } if (last_focused == NULL) { - LOG("Not performing (no window found)\n"); + ELOG("Not performing (no window found)\n"); return; } if (client_is_floating(last_focused) && (action != ACTION_FOCUS && action != ACTION_MOVE)) { - LOG("Not performing (floating)\n"); + ELOG("Not performing (floating)\n"); return; } @@ -1166,7 +1167,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { else if (*rest == 'l') direction = D_RIGHT; else { - LOG("unknown direction: %c\n", *rest); + ELOG("unknown direction: %c\n", *rest); return; } rest++; @@ -1189,7 +1190,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { /* TODO: this should swap the screen’s contents * (e.g. all workspaces) with the next/previous/… * screen */ - LOG("Not yet implemented\n"); + ELOG("Not yet implemented\n"); continue; } if (client_is_floating(last_focused)) { @@ -1204,7 +1205,7 @@ 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"); + ELOG("You cannot snap a screen (it makes no sense).\n"); continue; } snap_current_container(conn, direction); diff --git a/src/config.c b/src/config.c index 69506578..930b09c5 100644 --- a/src/config.c +++ b/src/config.c @@ -29,6 +29,7 @@ #include "xcb.h" #include "table.h" #include "workspace.h" +#include "log.h" Config config; struct modes_head modes; @@ -52,12 +53,12 @@ static char *glob_path(const char *path) { * */ void ungrab_all_keys(xcb_connection_t *conn) { - LOG("Ungrabbing all keys\n"); + DLOG("Ungrabbing all keys\n"); xcb_ungrab_key(conn, XCB_GRAB_ANY, root, XCB_BUTTON_MASK_ANY); } static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint32_t keycode) { - LOG("Grabbing %d\n", keycode); + DLOG("Grabbing %d\n", keycode); if ((bind->mods & BIND_MODE_SWITCH) != 0) xcb_grab_key(conn, 0, root, 0, keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC); @@ -87,14 +88,14 @@ void grab_all_keys(xcb_connection_t *conn) { /* We need to translate the symbol to a keycode */ xcb_keysym_t keysym = XStringToKeysym(bind->symbol); if (keysym == NoSymbol) { - LOG("Could not translate string to key symbol: \"%s\"\n", bind->symbol); + ELOG("Could not translate string to key symbol: \"%s\"\n", bind->symbol); continue; } #ifdef OLD_XCB_KEYSYMS_API bind->number_keycodes = 1; xcb_keycode_t code = xcb_key_symbols_get_keycode(keysyms, keysym); - LOG("Translated symbol \"%s\" to 1 keycode (%d)\n", bind->symbol, code); + DLOG("Translated symbol \"%s\" to 1 keycode (%d)\n", bind->symbol, code); grab_keycode_for_binding(conn, bind, code); bind->translated_to = smalloc(sizeof(xcb_keycode_t)); memcpy(bind->translated_to, &code, sizeof(xcb_keycode_t)); @@ -102,7 +103,7 @@ void grab_all_keys(xcb_connection_t *conn) { uint32_t last_keycode = 0; xcb_keycode_t *keycodes = xcb_key_symbols_get_keycode(keysyms, keysym); if (keycodes == NULL) { - LOG("Could not translate symbol \"%s\"\n", bind->symbol); + DLOG("Could not translate symbol \"%s\"\n", bind->symbol); continue; } @@ -117,7 +118,7 @@ void grab_all_keys(xcb_connection_t *conn) { last_keycode = *walk; bind->number_keycodes++; } - LOG("Translated symbol \"%s\" to %d keycode\n", bind->symbol, bind->number_keycodes); + DLOG("Translated symbol \"%s\" to %d keycode\n", bind->symbol, bind->number_keycodes); bind->translated_to = smalloc(bind->number_keycodes * sizeof(xcb_keycode_t)); memcpy(bind->translated_to, keycodes, bind->number_keycodes * sizeof(xcb_keycode_t)); free(keycodes); @@ -144,7 +145,7 @@ void switch_mode(xcb_connection_t *conn, const char *new_mode) { return; } - LOG("ERROR: Mode not found\n"); + ELOG("ERROR: Mode not found\n"); } /* diff --git a/src/floating.c b/src/floating.c index a8557416..2facc6cf 100644 --- a/src/floating.c +++ b/src/floating.c @@ -27,6 +27,7 @@ #include "client.h" #include "floating.h" #include "workspace.h" +#include "log.h" /* * Toggles floating mode for the given client. @@ -42,7 +43,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic i3Font *font = load_font(conn, config.font); if (con == NULL) { - LOG("This client is already in floating (container == NULL), re-inserting\n"); + DLOG("This client is already in floating (container == NULL), re-inserting\n"); Client *next_tiling; Workspace *ws = client->workspace; SLIST_FOREACH(next_tiling, &(ws->focus_stack), focus_clients) @@ -57,7 +58,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic /* Remove the client from the list of floating clients */ TAILQ_REMOVE(&(ws->floating_clients), client, floating_clients); - LOG("destination container = %p\n", con); + DLOG("destination container = %p\n", con); Client *old_focused = con->currently_focused; /* Preserve position/size */ memcpy(&(client->floating_rect), &(client->rect), sizeof(Rect)); @@ -69,7 +70,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic CIRCLEQ_INSERT_AFTER(&(con->clients), old_focused, client, clients); else CIRCLEQ_INSERT_TAIL(&(con->clients), client, clients); - LOG("Re-inserted the client into the matrix.\n"); + DLOG("Re-inserted the window.\n"); con->currently_focused = client; client_set_below_floating(conn, client); @@ -80,7 +81,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic return; } - LOG("Entering floating for client %08x\n", client->child); + DLOG("Entering floating for client %08x\n", client->child); /* Remove the client of its container */ client_remove_from_container(conn, client, con, false); @@ -90,7 +91,7 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic TAILQ_INSERT_TAIL(&(client->workspace->floating_clients), client, floating_clients); if (con->currently_focused == client) { - LOG("Need to re-adjust currently_focused\n"); + DLOG("Need to re-adjust currently_focused\n"); /* Get the next client in the focus stack for this particular container */ con->currently_focused = get_last_focused_client(conn, con, NULL); } @@ -113,11 +114,11 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic client->rect.width = client->child_rect.width + 2 + 2; client->rect.height = client->child_rect.height + (font->height + 2 + 2) + 2; - LOG("copying size from tiling (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y, + DLOG("copying size from tiling (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y, client->floating_rect.width, client->floating_rect.height); } else { /* If the client was already in floating before we restore the old position / size */ - LOG("using: (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y, + DLOG("using: (%d, %d) size (%d, %d)\n", client->floating_rect.x, client->floating_rect.y, client->floating_rect.width, client->floating_rect.height); memcpy(&(client->rect), &(client->floating_rect), sizeof(Rect)); } @@ -163,8 +164,7 @@ void floating_assign_to_workspace(Client *client, Workspace *new_workspace) { * */ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) { - - LOG("floating border click\n"); + DLOG("floating border click\n"); border_t border; @@ -225,11 +225,11 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre else if (event->event_x >= (client->rect.width - 2)) border = BORDER_RIGHT; else { - LOG("Not on any border, not doing anything.\n"); + DLOG("Not on any border, not doing anything.\n"); return 1; } - LOG("border = %d\n", border); + DLOG("border = %d\n", border); drag_pointer(conn, client, event, XCB_NONE, border, resize_callback); @@ -243,7 +243,7 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre * */ void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) { - LOG("floating_drag_window\n"); + DLOG("floating_drag_window\n"); void drag_window_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) { /* Reposition the client correctly while moving */ @@ -266,7 +266,7 @@ void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_pre * */ void floating_resize_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) { - LOG("floating_resize_window\n"); + DLOG("floating_resize_window\n"); void resize_window_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) { int32_t new_width = old_rect->width + (new_x - event->root_x); @@ -346,12 +346,12 @@ void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event break; case XCB_UNMAP_NOTIFY: - LOG("Unmap-notify, aborting\n"); + DLOG("Unmap-notify, aborting\n"); xcb_event_handle(&evenths, inside_event); goto done; default: - LOG("Passing to original handler\n"); + DLOG("Passing to original handler\n"); /* Use original handler */ xcb_event_handle(&evenths, inside_event); break; @@ -382,7 +382,7 @@ done: * */ void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, direction_t direction) { - LOG("floating focus\n"); + DLOG("floating focus\n"); if (direction == D_LEFT || direction == D_RIGHT) { /* Go to the next/previous floating client */ @@ -404,7 +404,7 @@ void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, * */ void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_t direction) { - LOG("floating move\n"); + DLOG("floating move\n"); switch (direction) { case D_LEFT: @@ -445,7 +445,7 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace) { Client *client; workspace->floating_hidden = !workspace->floating_hidden; - LOG("floating_hidden is now: %d\n", workspace->floating_hidden); + DLOG("floating_hidden is now: %d\n", workspace->floating_hidden); TAILQ_FOREACH(client, &(workspace->floating_clients), floating_clients) { if (workspace->floating_hidden) client_unmap(conn, client); diff --git a/src/handlers.c b/src/handlers.c index 560fad25..823ed8b8 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -36,6 +36,7 @@ #include "manage.h" #include "floating.h" #include "workspace.h" +#include "log.h" /* After mapping/unmapping windows, a notify event is generated. However, we don’t want it, since it’d trigger an infinite loop of switching between the different windows when @@ -95,15 +96,15 @@ int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_release_ev * */ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) { - LOG("Keypress %d, state raw = %d\n", event->detail, event->state); + DLOG("Keypress %d, state raw = %d\n", event->detail, event->state); /* Remove the numlock bit, all other bits are modifiers we can bind to */ uint16_t state_filtered = event->state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK); - LOG("(removed numlock, state = %d)\n", state_filtered); + DLOG("(removed numlock, state = %d)\n", state_filtered); /* Only use the lower 8 bits of the state (modifier masks) so that mouse * button masks are filtered out */ state_filtered &= 0xFF; - LOG("(removed upper 8 bits, state = %d)\n", state_filtered); + DLOG("(removed upper 8 bits, state = %d)\n", state_filtered); if (xkb_supported) { /* We need to get the keysym group (There are group 1 to group 4, each holding @@ -114,7 +115,7 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_ state_filtered |= BIND_MODE_SWITCH; } - LOG("(checked mode_switch, state %d)\n", state_filtered); + DLOG("(checked mode_switch, state %d)\n", state_filtered); /* Find the binding */ Binding *bind; @@ -147,7 +148,7 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_ parse_command(conn, bind->command); if (state_filtered & BIND_MODE_SWITCH) { - LOG("Mode_switch -> allow_events(SyncKeyboard)\n"); + DLOG("Mode_switch -> allow_events(SyncKeyboard)\n"); xcb_allow_events(conn, SyncKeyboard, event->time); xcb_flush(conn); } @@ -164,7 +165,7 @@ static void check_crossing_screen_boundary(uint32_t x, uint32_t y) { i3Screen *screen; if ((screen = get_screen_containing(x, y)) == NULL) { - LOG("ERROR: No such screen\n"); + ELOG("ERROR: No such screen\n"); return; } if (screen == c_ws->screen) @@ -175,7 +176,7 @@ static void check_crossing_screen_boundary(uint32_t x, uint32_t y) { c_ws = screen->current_workspace; current_row = c_ws->current_row; current_col = c_ws->current_col; - LOG("We're now on virtual screen number %d\n", screen->num); + DLOG("We're now on virtual screen number %d\n", screen->num); } /* @@ -183,9 +184,9 @@ static void check_crossing_screen_boundary(uint32_t x, uint32_t y) { * */ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_event_t *event) { - LOG("enter_notify for %08x, mode = %d, detail %d, serial %d\n", event->event, event->mode, event->detail, event->sequence); + DLOG("enter_notify for %08x, mode = %d, detail %d, serial %d\n", event->event, event->mode, event->detail, event->sequence); if (event->mode != XCB_NOTIFY_MODE_NORMAL) { - LOG("This was not a normal notify, ignoring\n"); + DLOG("This was not a normal notify, ignoring\n"); return 1; } /* Some events are not interesting, because they were not generated actively by the @@ -212,7 +213,7 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_ /* If not, then the user moved his cursor to the root window. In that case, we adjust c_ws */ if (client == NULL) { - LOG("Getting screen at %d x %d\n", event->root_x, event->root_y); + DLOG("Getting screen at %d x %d\n", event->root_x, event->root_y); check_crossing_screen_boundary(event->root_x, event->root_y); return 1; } @@ -222,7 +223,7 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_ if (client->container != NULL && client->container->mode == MODE_STACK && client->container->currently_focused != client) { - LOG("Plausibility check says: no\n"); + DLOG("Plausibility check says: no\n"); return 1; } @@ -230,7 +231,7 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_ /* This can happen when a client gets assigned to a different workspace than * the current one (see src/mainx.c:reparent_window). Shortly after it was created, * an enter_notify will follow. */ - LOG("enter_notify for a client on a different workspace but the same screen, ignoring\n"); + DLOG("enter_notify for a client on a different workspace but the same screen, ignoring\n"); return 1; } @@ -266,7 +267,7 @@ int handle_mapping_notify(void *ignored, xcb_connection_t *conn, xcb_mapping_not event->request != XCB_MAPPING_MODIFIER) return 0; - LOG("Received mapping_notify for keyboard or modifier mapping, re-grabbing keys\n"); + DLOG("Received mapping_notify for keyboard or modifier mapping, re-grabbing keys\n"); xcb_refresh_keyboard_mapping(keysyms, event); xcb_get_numlock_mask(conn); @@ -286,7 +287,7 @@ int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_eve cookie = xcb_get_window_attributes_unchecked(conn, event->window); - LOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence); + DLOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence); add_ignore_event(event->sequence); manage_window(prophs, conn, event->window, cookie, false); @@ -300,7 +301,7 @@ int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_eve * */ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure_request_event_t *event) { - LOG("window 0x%08x wants to be at %dx%d with %dx%d\n", + DLOG("window 0x%08x wants to be at %dx%d with %dx%d\n", event->window, event->x, event->y, event->width, event->height); Client *client = table_get(&by_child, event->window); @@ -330,7 +331,7 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure } if (client->fullscreen) { - LOG("Client is in fullscreen mode\n"); + DLOG("Client is in fullscreen mode\n"); Rect child_rect = client->workspace->rect; child_rect.x = child_rect.y = 0; @@ -371,7 +372,7 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure } } - LOG("Accepted new position/size for floating client: (%d, %d) size %d x %d\n", + DLOG("Accepted new position/size for floating client: (%d, %d) size %d x %d\n", client->rect.x, client->rect.y, client->rect.width, client->rect.height); /* Push the new position/size to X11 */ @@ -384,10 +385,10 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure /* Dock clients can be reconfigured in their height */ if (client->dock) { - LOG("Reconfiguring height of this dock client\n"); + DLOG("Reconfiguring height of this dock client\n"); if (!(event->value_mask & XCB_CONFIG_WINDOW_HEIGHT)) { - LOG("Ignoring configure request, no height given\n"); + DLOG("Ignoring configure request, no height given\n"); return 1; } @@ -399,7 +400,7 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure } if (client->fullscreen) { - LOG("Client is in fullscreen mode\n"); + DLOG("Client is in fullscreen mode\n"); Rect child_rect = client->container->workspace->rect; child_rect.x = child_rect.y = 0; @@ -426,8 +427,8 @@ int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_n add_ignore_event(event->sequence); if (event->event == root) { - LOG("event->x = %d, ->y = %d, ->width = %d, ->height = %d\n", event->x, event->y, event->width, event->height); - LOG("reconfigure of the root window, need to xinerama\n"); + DLOG("event->x = %d, ->y = %d, ->width = %d, ->height = %d\n", event->x, event->y, event->width, event->height); + DLOG("reconfigure of the root window, need to xinerama\n"); /* FIXME: Somehow, this is occuring too often. Therefore, we check for 0/0, but is there a better way? */ if (event->x == 0 && event->y == 0) @@ -456,10 +457,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti return 1; } - LOG("event->window = %08x, event->event = %08x\n", event->window, event->event); - LOG("UnmapNotify for 0x%08x (received from 0x%08x)\n", event->window, event->event); + DLOG("event->window = %08x, event->event = %08x\n", event->window, event->event); + DLOG("UnmapNotify for 0x%08x (received from 0x%08x)\n", event->window, event->event); if (client == NULL) { - LOG("not a managed window. Ignoring.\n"); + DLOG("not a managed window. Ignoring.\n"); /* This was most likely the destroyed frame of a client which is * currently being unmapped, so we add this sequence (again!) to @@ -490,17 +491,17 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti if ((con->currently_focused != NULL) && ((con == CUR_CELL) || client->fullscreen)) set_focus(conn, con->currently_focused, true); } else if (client_is_floating(client)) { - LOG("Removing from floating clients\n"); + DLOG("Removing from floating clients\n"); TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients); SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); } if (client->dock) { - LOG("Removing from dock clients\n"); + DLOG("Removing from dock clients\n"); SLIST_REMOVE(&(client->workspace->screen->dock_clients), client, Client, dock_clients); } - LOG("child of 0x%08x.\n", client->frame); + DLOG("child of 0x%08x.\n", client->frame); xcb_reparent_window(conn, client->child, root, 0, 0); client_unmap(conn, client); @@ -550,7 +551,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti if (to_focus != NULL) set_focus(conn, to_focus, true); else { - LOG("Restoring focus to root screen\n"); + DLOG("Restoring focus to root screen\n"); xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME); xcb_flush(conn); } @@ -566,7 +567,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) { if (prop == NULL || xcb_get_property_value_length(prop) == 0) { - LOG("_NET_WM_NAME not specified, not changing\n"); + DLOG("_NET_WM_NAME not specified, not changing\n"); return 1; } Client *client = table_get(&by_child, window); @@ -628,7 +629,7 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) { if (prop == NULL || xcb_get_property_value_length(prop) == 0) { - LOG("prop == NULL\n"); + DLOG("prop == NULL\n"); return 1; } Client *client = table_get(&by_child, window); @@ -643,7 +644,7 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t char *new_name; if (asprintf(&new_name, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)) == -1) { perror("Could not get old name"); - LOG("Could not get old name\n"); + DLOG("Could not get old name\n"); return 1; } /* Convert it to UCS-2 here for not having to convert it later every time we want to pass it to X */ @@ -688,7 +689,7 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) { if (prop == NULL || xcb_get_property_value_length(prop) == 0) { - LOG("prop == NULL\n"); + DLOG("prop == NULL\n"); return 1; } Client *client = table_get(&by_child, window); @@ -697,7 +698,7 @@ int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state, char *new_class; if (asprintf(&new_class, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)) == -1) { perror("Could not get window class"); - LOG("Could not get window class\n"); + DLOG("Could not get window class\n"); return 1; } @@ -710,7 +711,7 @@ int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state, return 1; if (strcmp(new_class, "tools") == 0 || strcmp(new_class, "Dialog") == 0) { - LOG("tool/dialog window, should we put it floating?\n"); + DLOG("tool/dialog window, should we put it floating?\n"); if (client->floating == FLOATING_AUTO_OFF) toggle_floating_mode(conn, client, true); } @@ -727,7 +728,7 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t * skip all events but the last one */ if (event->count != 0) return 1; - LOG("window = %08x\n", event->window); + DLOG("window = %08x\n", event->window); Client *client = table_get(&by_parent, event->window); if (client == NULL) { @@ -812,7 +813,7 @@ int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message event->data.data32[0] == _NET_WM_STATE_TOGGLE))) client_toggle_fullscreen(conn, client); } else { - LOG("unhandled clientmessage\n"); + ELOG("unhandled clientmessage\n"); return 0; } @@ -823,7 +824,7 @@ int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_wi xcb_atom_t atom, xcb_get_property_reply_t *property) { /* TODO: Implement this one. To do this, implement a little test program which sleep(1)s before changing this property. */ - LOG("_NET_WM_WINDOW_TYPE changed, this is not yet implemented.\n"); + ELOG("_NET_WM_WINDOW_TYPE changed, this is not yet implemented.\n"); return 0; } @@ -838,7 +839,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w xcb_atom_t name, xcb_get_property_reply_t *reply) { Client *client = table_get(&by_child, window); if (client == NULL) { - LOG("Received WM_SIZE_HINTS for unknown client\n"); + DLOG("Received WM_SIZE_HINTS for unknown client\n"); return 1; } xcb_size_hints_t size_hints; @@ -893,9 +894,9 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w base_height != client->base_height) { client->base_width = base_width; client->base_height = base_height; - LOG("client's base_height changed to %d\n", base_height); + DLOG("client's base_height changed to %d\n", base_height); if (client->fullscreen) - LOG("Not resizing client, it is in fullscreen mode\n"); + DLOG("Not resizing client, it is in fullscreen mode\n"); else resize_client(conn, client); } @@ -913,8 +914,8 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w double min_aspect = (double)size_hints.min_aspect_num / size_hints.min_aspect_den; double max_aspect = (double)size_hints.max_aspect_num / size_hints.min_aspect_den; - LOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect); - LOG("width = %f, height = %f\n", width, height); + DLOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect); + DLOG("width = %f, height = %f\n", width, height); /* Sanity checks, this is user-input, in a way */ if (max_aspect <= 0 || min_aspect <= 0 || height == 0 || (width / height) <= 0) @@ -947,7 +948,7 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t xcb_atom_t name, xcb_get_property_reply_t *reply) { Client *client = table_get(&by_child, window); if (client == NULL) { - LOG("Received WM_HINTS for unknown client\n"); + DLOG("Received WM_HINTS for unknown client\n"); return 1; } xcb_wm_hints_t hints; @@ -962,7 +963,7 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); if (!client->urgent && client == last_focused) { - LOG("Ignoring urgency flag for current client\n"); + DLOG("Ignoring urgency flag for current client\n"); return 1; } @@ -996,7 +997,7 @@ int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_ xcb_atom_t name, xcb_get_property_reply_t *reply) { Client *client = table_get(&by_child, window); if (client == NULL) { - LOG("No such client\n"); + DLOG("No such client\n"); return 1; } @@ -1012,7 +1013,7 @@ int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_ } if (client->floating == FLOATING_AUTO_OFF) { - LOG("This is a popup window, putting into floating\n"); + DLOG("This is a popup window, putting into floating\n"); toggle_floating_mode(conn, client, true); } @@ -1041,7 +1042,7 @@ int handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state if (leader == NULL || *leader == 0) return 1; - LOG("Client leader changed to %08x\n", *leader); + DLOG("Client leader changed to %08x\n", *leader); client->leader = *leader; diff --git a/src/ipc.c b/src/ipc.c index 0bef2ea2..9e56fd0e 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -28,6 +28,7 @@ #include "i3.h" #include "util.h" #include "commands.h" +#include "log.h" typedef struct ipc_client { int fd; @@ -71,10 +72,10 @@ void broadcast(EV_P_ struct ev_timer *t, int revents) { */ 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); + DLOG("handling message of size %d\n", size); + DLOG("sender specified size %d\n", message_size); + DLOG("sender specified type %d\n", message_type); + DLOG("payload as a string = %s\n", message); switch (message_type) { case I3_IPC_MESSAGE_TYPE_COMMAND: { @@ -88,7 +89,7 @@ static void ipc_handle_message(uint8_t *message, int size, break; } default: - LOG("unhandled ipc message\n"); + DLOG("unhandled ipc message\n"); break; } } @@ -135,7 +136,7 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) { ev_io_stop(EV_A_ w); - LOG("IPC: client disconnected\n"); + DLOG("IPC: client disconnected\n"); return; } @@ -144,18 +145,18 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) { /* Check if the message starts with the i3 IPC magic code */ if (n < strlen(I3_IPC_MAGIC)) { - LOG("IPC: message too short, ignoring\n"); + DLOG("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"); + DLOG("IPC: message does not start with the IPC magic\n"); return; } uint8_t *message = (uint8_t*)buf; while (n > 0) { - LOG("IPC: n = %d\n", n); + DLOG("IPC: n = %d\n", n); message += strlen(I3_IPC_MAGIC); n -= strlen(I3_IPC_MAGIC); @@ -165,7 +166,7 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) { n -= sizeof(uint32_t); if (message_size > n) { - LOG("IPC: Either the message size was wrong or the message was not read completely, dropping\n"); + DLOG("IPC: Either the message size was wrong or the message was not read completely, dropping\n"); return; } @@ -204,7 +205,7 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) { ev_io_init(package, ipc_receive_message, client, EV_READ); ev_io_start(EV_A_ package); - LOG("IPC: new client connected\n"); + DLOG("IPC: new client connected\n"); struct ipc_client *new = calloc(sizeof(struct ipc_client), 1); new->fd = client; diff --git a/src/layout.c b/src/layout.c index c626edbd..fe9c1cfd 100644 --- a/src/layout.c +++ b/src/layout.c @@ -28,6 +28,7 @@ #include "floating.h" #include "handlers.h" #include "workspace.h" +#include "log.h" /* * Updates *destination with new_value and returns true if it was changed or false @@ -50,16 +51,16 @@ int get_unoccupied_x(Workspace *workspace) { double unoccupied = workspace->rect.width; double default_factor = ((float)workspace->rect.width / workspace->cols) / workspace->rect.width; - LOG("get_unoccupied_x(), starting with %f, default_factor = %f\n", unoccupied, default_factor); + DLOG("get_unoccupied_x(), starting with %f, default_factor = %f\n", unoccupied, default_factor); for (int cols = 0; cols < workspace->cols; cols++) { - LOG("width_factor[%d] = %f, unoccupied = %f\n", cols, workspace->width_factor[cols], unoccupied); + DLOG("width_factor[%d] = %f, unoccupied = %f\n", cols, workspace->width_factor[cols], unoccupied); if (workspace->width_factor[cols] == 0) unoccupied -= workspace->rect.width * default_factor; } - LOG("unoccupied space: %f\n", unoccupied); + DLOG("unoccupied space: %f\n", unoccupied); return unoccupied; } @@ -69,15 +70,15 @@ int get_unoccupied_y(Workspace *workspace) { double unoccupied = height; double default_factor = ((float)height / workspace->rows) / height; - LOG("get_unoccupied_y(), starting with %f, default_factor = %f\n", unoccupied, default_factor); + DLOG("get_unoccupied_y(), starting with %f, default_factor = %f\n", unoccupied, default_factor); for (int rows = 0; rows < workspace->rows; rows++) { - LOG("height_factor[%d] = %f, unoccupied = %f\n", rows, workspace->height_factor[rows], unoccupied); + DLOG("height_factor[%d] = %f, unoccupied = %f\n", rows, workspace->height_factor[rows], unoccupied); if (workspace->height_factor[rows] == 0) unoccupied -= height * default_factor; } - LOG("unoccupied space: %f\n", unoccupied); + DLOG("unoccupied space: %f\n", unoccupied); return unoccupied; } @@ -217,7 +218,7 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw void reposition_client(xcb_connection_t *conn, Client *client) { i3Screen *screen; - LOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y); + DLOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y); /* Note: We can use a pointer to client->x like an array of uint32_ts because it is followed by client->y by definition */ xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x)); @@ -230,12 +231,12 @@ void reposition_client(xcb_connection_t *conn, Client *client) { return; if (screen == NULL) { - LOG("Boundary checking disabled, no screen found for (%d, %d)\n", client->rect.x, client->rect.y); + DLOG("Boundary checking disabled, no screen found for (%d, %d)\n", client->rect.x, client->rect.y); return; } - LOG("Client is on workspace %p with screen %p\n", client->workspace, client->workspace->screen); - LOG("but screen at %d, %d is %p\n", client->rect.x, client->rect.y, screen); + DLOG("Client is on workspace %p with screen %p\n", client->workspace, client->workspace->screen); + DLOG("but screen at %d, %d is %p\n", client->rect.x, client->rect.y, screen); floating_assign_to_workspace(client, screen->current_workspace); } @@ -249,8 +250,8 @@ void reposition_client(xcb_connection_t *conn, Client *client) { void resize_client(xcb_connection_t *conn, Client *client) { i3Font *font = load_font(conn, config.font); - LOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y); - LOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height); + DLOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y); + DLOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height); xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | @@ -300,7 +301,7 @@ void resize_client(xcb_connection_t *conn, Client *client) { /* Obey the ratio, if any */ if (client->proportional_height != 0 && client->proportional_width != 0) { - LOG("proportional height = %d, width = %d\n", client->proportional_height, client->proportional_width); + DLOG("proportional height = %d, width = %d\n", client->proportional_height, client->proportional_width); double new_height = rect->height + 1; int new_width = rect->width; @@ -316,24 +317,24 @@ void resize_client(xcb_connection_t *conn, Client *client) { rect->height = new_height; rect->width = new_width; - LOG("new_height = %f, new_width = %d\n", new_height, new_width); + DLOG("new_height = %f, new_width = %d\n", new_height, new_width); } if (client->height_increment > 1) { int old_height = rect->height; rect->height -= (rect->height - client->base_height) % client->height_increment; - LOG("Lost %d pixel due to client's height_increment (%d px, base_height = %d)\n", + DLOG("Lost %d pixel due to client's height_increment (%d px, base_height = %d)\n", old_height - rect->height, client->height_increment, client->base_height); } if (client->width_increment > 1) { int old_width = rect->width; rect->width -= (rect->width - client->base_width) % client->width_increment; - LOG("Lost %d pixel due to client's width_increment (%d px, base_width = %d)\n", + DLOG("Lost %d pixel due to client's width_increment (%d px, base_width = %d)\n", old_width - rect->width, client->width_increment, client->base_width); } - LOG("child will be at %dx%d with size %dx%d\n", rect->x, rect->y, rect->width, rect->height); + DLOG("child will be at %dx%d with size %dx%d\n", rect->x, rect->y, rect->width, rect->height); xcb_configure_window(conn, client->child, mask, &(rect->x)); @@ -365,7 +366,7 @@ void render_container(xcb_connection_t *conn, Container *container) { if (container->mode == MODE_DEFAULT) { int height = (container->height / max(1, num_clients)); int rest_pixels = (container->height % max(1, num_clients)); - LOG("height per client = %d, rest = %d\n", height, rest_pixels); + DLOG("height per client = %d, rest = %d\n", height, rest_pixels); CIRCLEQ_FOREACH(client, &(container->clients), clients) { /* If the client is in fullscreen mode, it does not get reconfigured */ @@ -409,15 +410,15 @@ void render_container(xcb_connection_t *conn, Container *container) { /* Check if we need to remap our stack title window, it gets unmapped when the container is empty in src/handlers.c:unmap_notify() */ if (stack_win->rect.height == 0 && num_clients > 0) { - LOG("remapping stack win\n"); + DLOG("remapping stack win\n"); xcb_map_window(conn, stack_win->window); - } else LOG("not remapping stackwin, height = %d, num_clients = %d\n", + } else DLOG("not remapping stackwin, height = %d, num_clients = %d\n", stack_win->rect.height, num_clients); if (container->mode == MODE_TABBED) { /* By setting num_clients to 1 we force that the stack window will be only one line * high. The rest of the code is useful in both cases. */ - LOG("tabbed mode, setting num_clients = 1\n"); + DLOG("tabbed mode, setting num_clients = 1\n"); if (stack_lines > 1) stack_lines = 1; } @@ -530,7 +531,7 @@ void render_container(xcb_connection_t *conn, Container *container) { current_client++; } else if (container->mode == MODE_TABBED) { if (container->stack_limit == STACK_LIMIT_ROWS) { - LOG("You limited this container in its rows. " + LOG("You limited a tabbed container in its rows. " "This makes no sense in tabbing mode.\n"); } offset_x = current_client++ * size_each; @@ -571,7 +572,7 @@ void render_container(xcb_connection_t *conn, Container *container) { static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int *height) { Client *client; SLIST_FOREACH(client, &(r_ws->screen->dock_clients), dock_clients) { - LOG("client is at %d, should be at %d\n", client->rect.y, *height); + DLOG("client is at %d, should be at %d\n", client->rect.y, *height); if (client->force_reconfigure | update_if_necessary(&(client->rect.x), r_ws->rect.x) | update_if_necessary(&(client->rect.y), *height)) @@ -583,7 +584,7 @@ static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int resize_client(conn, client); client->force_reconfigure = false; - LOG("desired_height = %d\n", client->desired_height); + DLOG("desired_height = %d\n", client->desired_height); *height += client->desired_height; } } @@ -717,7 +718,7 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws) single_width = container->width; } - LOG("height is %d\n", height); + DLOG("height is %d\n", height); container->height = 0; diff --git a/src/mainx.c b/src/mainx.c index 0090d587..1706294c 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -124,7 +124,7 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) { * */ static void xkb_got_event(EV_P_ struct ev_io *w, int revents) { - LOG("got xkb event, yay\n"); + DLOG("Handling XKB event\n"); XEvent ev; /* When using xmodmap, every change (!) gets an own event. * Therefore, we just read all events and only handle the @@ -139,9 +139,9 @@ static void xkb_got_event(EV_P_ struct ev_io *w, int revents) { xcb_get_numlock_mask(global_conn); ungrab_all_keys(global_conn); - LOG("Re-grabbing...\n"); + DLOG("Re-grabbing...\n"); grab_all_keys(global_conn); - LOG("Done\n"); + DLOG("Done\n"); } @@ -258,7 +258,7 @@ int main(int argc, char *argv[], char *env[]) { int evBase, errBase; if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) { - LOG("ERROR: XkbOpenDisplay() failed, disabling XKB support\n"); + ELOG("ERROR: XkbOpenDisplay() failed, disabling XKB support\n"); xkb_supported = false; } @@ -383,7 +383,7 @@ int main(int argc, char *argv[], char *env[]) { #define GET_ATOM(name) { \ xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, atom_cookies[name], NULL); \ if (!reply) { \ - LOG("Could not get atom " #name "\n"); \ + ELOG("Could not get atom " #name "\n"); \ exit(-1); \ } \ atoms[name] = reply->atom; \ @@ -453,7 +453,7 @@ int main(int argc, char *argv[], char *env[]) { } /* check for Xinerama */ - LOG("Checking for Xinerama...\n"); + DLOG("Checking for Xinerama...\n"); initialize_xinerama(conn); xcb_flush(conn); @@ -461,18 +461,18 @@ int main(int argc, char *argv[], char *env[]) { /* Get pointer position to see on which screen we’re starting */ xcb_query_pointer_reply_t *reply; if ((reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL)) == NULL) { - LOG("Could not get pointer position\n"); + ELOG("Could not get pointer position\n"); return 1; } i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y); if (screen == NULL) { - LOG("ERROR: No screen at %d x %d, starting on the first screen\n", + ELOG("ERROR: No screen at %d x %d, starting on the first screen\n", reply->root_x, reply->root_y); screen = TAILQ_FIRST(virtual_screens); } - LOG("Starting on %d\n", screen->current_workspace); + DLOG("Starting on %d\n", screen->current_workspace); c_ws = screen->current_workspace; manage_existing_windows(conn, &prophs, root); @@ -481,7 +481,7 @@ int main(int argc, char *argv[], char *env[]) { 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"); + ELOG("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); diff --git a/src/manage.c b/src/manage.c index da5ec175..54e02fe4 100644 --- a/src/manage.c +++ b/src/manage.c @@ -30,6 +30,7 @@ #include "floating.h" #include "client.h" #include "workspace.h" +#include "log.h" /* * Go through all existing windows (if the window manager is restarted) and manage them @@ -78,7 +79,7 @@ void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, /* Check if the window is mapped (it could be not mapped when intializing and calling manage_window() for every window) */ if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) { - LOG("Could not get attributes\n"); + ELOG("Could not get attributes\n"); return; } @@ -156,8 +157,8 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, /* Events for already managed windows should already be filtered in manage_window() */ assert(new == NULL); - LOG("Reparenting window 0x%08x\n", child); - LOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height); + LOG("Managing window 0x%08x\n", child); + DLOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height); new = calloc(sizeof(Client), 1); new->force_reconfigure = true; @@ -220,7 +221,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, new->awaiting_useless_unmap = true; xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height); if (xcb_request_check(conn, cookie) != NULL) { - LOG("Could not reparent the window, aborting\n"); + DLOG("Could not reparent the window, aborting\n"); xcb_destroy_window(conn, new->frame); free(new); return; @@ -247,7 +248,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) { for (int i = 0; i < xcb_get_property_value_length(preply); i++) if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) { - LOG("Window is a dock.\n"); + DLOG("Window is a dock.\n"); new->dock = true; new->borderless = true; new->titlebar_position = TITLEBAR_OFF; @@ -263,19 +264,19 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) { /* Set the dialog window to automatically floating, will be used below */ new->floating = FLOATING_AUTO_ON; - LOG("dialog/utility/toolbar/splash window, automatically floating\n"); + DLOG("dialog/utility/toolbar/splash window, automatically floating\n"); } } /* 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"); + DLOG("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"); + DLOG("workspace is in autofloat mode, setting floating\n"); } if (new->dock) { @@ -289,12 +290,12 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, TODO: bars at the top */ new->desired_height = strut[3]; if (new->desired_height == 0) { - LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height); + DLOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height); new->desired_height = original_height; } - LOG("the client wants to be %d pixels high\n", new->desired_height); + DLOG("the client wants to be %d pixels high\n", new->desired_height); } else { - LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height); + DLOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height); new->desired_height = original_height; } } else { @@ -333,11 +334,11 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, assign->windowclass_title, assign->workspace); if (c_ws->screen->current_workspace->num == (assign->workspace-1)) { - LOG("We are already there, no need to do anything\n"); + DLOG("We are already there, no need to do anything\n"); break; } - LOG("Changing container/workspace and unmapping the client\n"); + DLOG("Changing container/workspace and unmapping the client\n"); Workspace *t_ws = workspace_get(assign->workspace-1); workspace_initialize(t_ws, c_ws->screen); @@ -351,7 +352,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, } if (new->workspace->fullscreen_client != NULL) { - LOG("Setting below fullscreen window\n"); + DLOG("Setting below fullscreen window\n"); /* If we are in fullscreen, we should lower the window to not be annoying */ uint32_t values[] = { XCB_STACK_MODE_BELOW }; @@ -388,10 +389,10 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, * 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"); + DLOG("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"); + DLOG("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); @@ -402,10 +403,10 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, } new->floating_rect.x = new->rect.x = x; new->floating_rect.y = new->rect.y = y; - LOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n", + DLOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n", new->floating_rect.x, new->floating_rect.y, new->floating_rect.width, new->floating_rect.height); - LOG("outer rect (%d, %d) size (%d, %d)\n", + DLOG("outer rect (%d, %d) size (%d, %d)\n", new->rect.x, new->rect.y, new->rect.width, new->rect.height); /* Make sure it is on top of the other windows */ diff --git a/src/resize.c b/src/resize.c index b9127f5b..db8e7442 100644 --- a/src/resize.c +++ b/src/resize.c @@ -28,6 +28,7 @@ #include "config.h" #include "floating.h" #include "workspace.h" +#include "log.h" /* * Renders the resize window between the first/second container and resizes @@ -39,7 +40,7 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i int new_position; i3Screen *screen = get_screen_containing(event->root_x, event->root_y); if (screen == NULL) { - LOG("BUG: No screen found at this position (%d, %d)\n", event->root_x, event->root_y); + ELOG("BUG: No screen found at this position (%d, %d)\n", event->root_x, event->root_y); return 1; } @@ -51,9 +52,9 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i i3Screen *most_right = get_screen_most(D_RIGHT, screen), *most_bottom = get_screen_most(D_DOWN, screen); - LOG("event->event_x = %d, event->root_x = %d\n", event->event_x, event->root_x); + DLOG("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); + DLOG("Screen dimensions: (%d, %d) %d x %d\n", screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height); uint32_t mask = 0; uint32_t values[2]; @@ -100,7 +101,7 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i xcb_flush(conn); void resize_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) { - LOG("new x = %d, y = %d\n", new_x, new_y); + DLOG("new x = %d, y = %d\n", new_x, new_y); if (orientation == O_VERTICAL) { /* Check if the new coordinates are within screen boundaries */ if (new_x > (screen->rect.x + screen->rect.width - 25) || @@ -163,8 +164,8 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco if (ws->width_factor[second] == 0) new_unoccupied_x += default_width; - LOG("\n\n\n"); - LOG("old = %d, new = %d\n", old_unoccupied_x, new_unoccupied_x); + DLOG("\n\n\n"); + DLOG("old = %d, new = %d\n", old_unoccupied_x, new_unoccupied_x); int cols_without_wf = 0; int old_width, old_second_width; @@ -172,20 +173,20 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco if (ws->width_factor[col] == 0) cols_without_wf++; - LOG("old_unoccupied_x = %d\n", old_unoccupied_x); + DLOG("old_unoccupied_x = %d\n", old_unoccupied_x); - LOG("Updating first (before = %f)\n", ws->width_factor[first]); + DLOG("Updating first (before = %f)\n", ws->width_factor[first]); /* Convert 0 (for default width_factor) to actual numbers */ if (ws->width_factor[first] == 0) old_width = (old_unoccupied_x / max(cols_without_wf, 1)); else old_width = ws->width_factor[first] * old_unoccupied_x; - LOG("second (before = %f)\n", ws->width_factor[second]); + DLOG("second (before = %f)\n", ws->width_factor[second]); if (ws->width_factor[second] == 0) old_second_width = (old_unoccupied_x / max(cols_without_wf, 1)); else old_second_width = ws->width_factor[second] * old_unoccupied_x; - LOG("middle = %f\n", ws->width_factor[first]); + DLOG("middle = %f\n", ws->width_factor[first]); /* If the space used for customly resized columns has changed we need to adapt the * other customly resized columns, if any */ @@ -194,33 +195,33 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco if (ws->width_factor[col] == 0) continue; - LOG("Updating other column (%d) (current width_factor = %f)\n", col, ws->width_factor[col]); + DLOG("Updating other column (%d) (current width_factor = %f)\n", col, ws->width_factor[col]); ws->width_factor[col] = (ws->width_factor[col] * old_unoccupied_x) / new_unoccupied_x; - LOG("to %f\n", ws->width_factor[col]); + DLOG("to %f\n", ws->width_factor[col]); } - LOG("Updating first (before = %f)\n", ws->width_factor[first]); + DLOG("Updating first (before = %f)\n", ws->width_factor[first]); /* Convert 0 (for default width_factor) to actual numbers */ if (ws->width_factor[first] == 0) ws->width_factor[first] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x; - LOG("first->width = %d, pixels = %d\n", old_width, pixels); + DLOG("first->width = %d, pixels = %d\n", old_width, pixels); ws->width_factor[first] *= (float)(old_width + pixels) / old_width; - LOG("-> %f\n", ws->width_factor[first]); + DLOG("-> %f\n", ws->width_factor[first]); - LOG("Updating second (before = %f)\n", ws->width_factor[second]); + DLOG("Updating second (before = %f)\n", ws->width_factor[second]); if (ws->width_factor[second] == 0) ws->width_factor[second] = ((float)ws->rect.width / ws->cols) / new_unoccupied_x; - LOG("middle = %f\n", ws->width_factor[second]); - LOG("second->width = %d, pixels = %d\n", old_second_width, pixels); + DLOG("middle = %f\n", ws->width_factor[second]); + DLOG("second->width = %d, pixels = %d\n", old_second_width, pixels); ws->width_factor[second] *= (float)(old_second_width - pixels) / old_second_width; - LOG("-> %f\n", ws->width_factor[second]); + DLOG("-> %f\n", ws->width_factor[second]); - LOG("new unoccupied_x = %d\n", get_unoccupied_x(ws)); + DLOG("new unoccupied_x = %d\n", get_unoccupied_x(ws)); - LOG("\n\n\n"); + DLOG("\n\n\n"); } else { int ws_height = workspace_height(ws); int default_height = ws_height / ws->rows; @@ -245,24 +246,24 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco if (ws->height_factor[row] == 0) cols_without_hf++; - LOG("old_unoccupied_y = %d\n", old_unoccupied_y); + DLOG("old_unoccupied_y = %d\n", old_unoccupied_y); - LOG("Updating first (before = %f)\n", ws->height_factor[first]); + DLOG("Updating first (before = %f)\n", ws->height_factor[first]); /* Convert 0 (for default width_factor) to actual numbers */ if (ws->height_factor[first] == 0) old_height = (old_unoccupied_y / max(cols_without_hf, 1)); else old_height = ws->height_factor[first] * old_unoccupied_y; - LOG("second (before = %f)\n", ws->height_factor[second]); + DLOG("second (before = %f)\n", ws->height_factor[second]); if (ws->height_factor[second] == 0) old_second_height = (old_unoccupied_y / max(cols_without_hf, 1)); else old_second_height = ws->height_factor[second] * old_unoccupied_y; - LOG("middle = %f\n", ws->height_factor[first]); + DLOG("middle = %f\n", ws->height_factor[first]); - LOG("\n\n\n"); - LOG("old = %d, new = %d\n", old_unoccupied_y, new_unoccupied_y); + DLOG("\n\n\n"); + DLOG("old = %d, new = %d\n", old_unoccupied_y, new_unoccupied_y); /* If the space used for customly resized columns has changed we need to adapt the * other customly resized columns, if any */ @@ -271,33 +272,33 @@ void resize_container(xcb_connection_t *conn, Workspace *ws, int first, int seco if (ws->height_factor[row] == 0) continue; - LOG("Updating other column (%d) (current width_factor = %f)\n", row, ws->height_factor[row]); + DLOG("Updating other column (%d) (current width_factor = %f)\n", row, ws->height_factor[row]); ws->height_factor[row] = (ws->height_factor[row] * old_unoccupied_y) / new_unoccupied_y; - LOG("to %f\n", ws->height_factor[row]); + DLOG("to %f\n", ws->height_factor[row]); } - LOG("Updating first (before = %f)\n", ws->height_factor[first]); + DLOG("Updating first (before = %f)\n", ws->height_factor[first]); /* Convert 0 (for default width_factor) to actual numbers */ if (ws->height_factor[first] == 0) ws->height_factor[first] = ((float)ws_height / ws->rows) / new_unoccupied_y; - LOG("first->width = %d, pixels = %d\n", old_height, pixels); + DLOG("first->width = %d, pixels = %d\n", old_height, pixels); ws->height_factor[first] *= (float)(old_height + pixels) / old_height; - LOG("-> %f\n", ws->height_factor[first]); + DLOG("-> %f\n", ws->height_factor[first]); - LOG("Updating second (before = %f)\n", ws->height_factor[second]); + DLOG("Updating second (before = %f)\n", ws->height_factor[second]); if (ws->height_factor[second] == 0) ws->height_factor[second] = ((float)ws_height / ws->rows) / new_unoccupied_y; - LOG("middle = %f\n", ws->height_factor[second]); - LOG("second->width = %d, pixels = %d\n", old_second_height, pixels); + DLOG("middle = %f\n", ws->height_factor[second]); + DLOG("second->width = %d, pixels = %d\n", old_second_height, pixels); ws->height_factor[second] *= (float)(old_second_height - pixels) / old_second_height; - LOG("-> %f\n", ws->height_factor[second]); + DLOG("-> %f\n", ws->height_factor[second]); - LOG("new unoccupied_y = %d\n", get_unoccupied_y(ws)); + DLOG("new unoccupied_y = %d\n", get_unoccupied_y(ws)); - LOG("\n\n\n"); + DLOG("\n\n\n"); } render_layout(conn); diff --git a/src/table.c b/src/table.c index eebe8de0..8aa02fba 100644 --- a/src/table.c +++ b/src/table.c @@ -27,6 +27,7 @@ #include "layout.h" #include "config.h" #include "workspace.h" +#include "log.h" int current_workspace = 0; int num_workspaces = 1; @@ -96,9 +97,9 @@ void expand_table_rows_at_head(Workspace *workspace) { workspace->height_factor = realloc(workspace->height_factor, sizeof(float) * workspace->rows); - LOG("rows = %d\n", workspace->rows); + DLOG("rows = %d\n", workspace->rows); for (int rows = (workspace->rows - 1); rows >= 1; rows--) { - LOG("Moving height_factor %d (%f) to %d\n", rows-1, workspace->height_factor[rows-1], rows); + DLOG("Moving height_factor %d (%f) to %d\n", rows-1, workspace->height_factor[rows-1], rows); workspace->height_factor[rows] = workspace->height_factor[rows-1]; } @@ -110,7 +111,7 @@ void expand_table_rows_at_head(Workspace *workspace) { /* Move the other rows */ for (int cols = 0; cols < workspace->cols; cols++) for (int rows = workspace->rows - 1; rows > 0; rows--) { - LOG("Moving row %d to %d\n", rows-1, rows); + DLOG("Moving row %d to %d\n", rows-1, rows); workspace->table[cols][rows] = workspace->table[cols][rows-1]; workspace->table[cols][rows]->row = rows; } @@ -148,9 +149,9 @@ void expand_table_cols_at_head(Workspace *workspace) { workspace->width_factor = realloc(workspace->width_factor, sizeof(float) * workspace->cols); - LOG("cols = %d\n", workspace->cols); + DLOG("cols = %d\n", workspace->cols); for (int cols = (workspace->cols - 1); cols >= 1; cols--) { - LOG("Moving width_factor %d (%f) to %d\n", cols-1, workspace->width_factor[cols-1], cols); + DLOG("Moving width_factor %d (%f) to %d\n", cols-1, workspace->width_factor[cols-1], cols); workspace->width_factor[cols] = workspace->width_factor[cols-1]; } @@ -162,7 +163,7 @@ void expand_table_cols_at_head(Workspace *workspace) { /* Move the other columns */ for (int rows = 0; rows < workspace->rows; rows++) for (int cols = workspace->cols - 1; cols > 0; cols--) { - LOG("Moving col %d to %d\n", cols-1, cols); + DLOG("Moving col %d to %d\n", cols-1, cols); workspace->table[cols][rows] = workspace->table[cols-1][rows]; workspace->table[cols][rows]->col = cols; } @@ -201,7 +202,7 @@ static void shrink_table_cols(Workspace *workspace) { if (workspace->width_factor[cols] == 0) continue; - LOG("Added free space (%f) to %d (had %f)\n", free_space, cols, + DLOG("Added free space (%f) to %d (had %f)\n", free_space, cols, workspace->width_factor[cols]); workspace->width_factor[cols] += free_space; break; @@ -230,7 +231,7 @@ static void shrink_table_rows(Workspace *workspace) { if (workspace->height_factor[rows] == 0) continue; - LOG("Added free space (%f) to %d (had %f)\n", free_space, rows, + DLOG("Added free space (%f) to %d (had %f)\n", free_space, rows, workspace->height_factor[rows]); workspace->height_factor[rows] += free_space; break; @@ -256,7 +257,7 @@ static void free_container(xcb_connection_t *conn, Workspace *workspace, int col } static void move_columns_from(xcb_connection_t *conn, Workspace *workspace, int cols) { - LOG("firstly freeing \n"); + DLOG("firstly freeing \n"); /* Free the columns which are cleaned up */ for (int rows = 0; rows < workspace->rows; rows++) @@ -264,10 +265,10 @@ static void move_columns_from(xcb_connection_t *conn, Workspace *workspace, int for (; cols < workspace->cols; cols++) for (int rows = 0; rows < workspace->rows; rows++) { - LOG("at col = %d, row = %d\n", cols, rows); + DLOG("at col = %d, row = %d\n", cols, rows); Container *new_container = workspace->table[cols][rows]; - LOG("moving cols = %d to cols -1 = %d\n", cols, cols-1); + DLOG("moving cols = %d to cols -1 = %d\n", cols, cols-1); workspace->table[cols-1][rows] = new_container; new_container->row = rows; @@ -283,7 +284,7 @@ static void move_rows_from(xcb_connection_t *conn, Workspace *workspace, int row for (int cols = 0; cols < workspace->cols; cols++) { Container *new_container = workspace->table[cols][rows]; - LOG("moving rows = %d to rows -1 = %d\n", rows, rows - 1); + DLOG("moving rows = %d to rows -1 = %d\n", rows, rows - 1); workspace->table[cols][rows-1] = new_container; new_container->row = rows-1; @@ -296,19 +297,19 @@ static void move_rows_from(xcb_connection_t *conn, Workspace *workspace, int row * */ void dump_table(xcb_connection_t *conn, Workspace *workspace) { - LOG("dump_table()\n"); + DLOG("dump_table()\n"); FOR_TABLE(workspace) { Container *con = workspace->table[cols][rows]; - LOG("----\n"); - LOG("at col=%d, row=%d\n", cols, rows); - LOG("currently_focused = %p\n", con->currently_focused); + DLOG("----\n"); + DLOG("at col=%d, row=%d\n", cols, rows); + DLOG("currently_focused = %p\n", con->currently_focused); Client *loop; CIRCLEQ_FOREACH(loop, &(con->clients), clients) { - LOG("got client %08x / %s\n", loop->child, loop->name); + DLOG("got client %08x / %s\n", loop->child, loop->name); } - LOG("----\n"); + DLOG("----\n"); } - LOG("done\n"); + DLOG("done\n"); } /* @@ -316,7 +317,7 @@ void dump_table(xcb_connection_t *conn, Workspace *workspace) { * */ void cleanup_table(xcb_connection_t *conn, Workspace *workspace) { - LOG("cleanup_table()\n"); + DLOG("cleanup_table()\n"); /* Check for empty columns if we got more than one column */ for (int cols = 0; (workspace->cols > 1) && (cols < workspace->cols);) { @@ -327,7 +328,7 @@ void cleanup_table(xcb_connection_t *conn, Workspace *workspace) { break; } if (completely_empty) { - LOG("Removing completely empty column %d\n", cols); + DLOG("Removing completely empty column %d\n", cols); if (cols < (workspace->cols - 1)) move_columns_from(conn, workspace, cols+1); else { @@ -344,14 +345,14 @@ void cleanup_table(xcb_connection_t *conn, Workspace *workspace) { /* Check for empty rows if we got more than one row */ for (int rows = 0; (workspace->rows > 1) && (rows < workspace->rows);) { bool completely_empty = true; - LOG("Checking row %d\n", rows); + DLOG("Checking row %d\n", rows); for (int cols = 0; cols < workspace->cols; cols++) if (workspace->table[cols][rows]->currently_focused != NULL) { completely_empty = false; break; } if (completely_empty) { - LOG("Removing completely empty row %d\n", rows); + DLOG("Removing completely empty row %d\n", rows); if (rows < (workspace->rows - 1)) move_rows_from(conn, workspace, rows+1); else { @@ -381,25 +382,25 @@ void cleanup_table(xcb_connection_t *conn, Workspace *workspace) { * */ void fix_colrowspan(xcb_connection_t *conn, Workspace *workspace) { - LOG("Fixing col/rowspan\n"); + DLOG("Fixing col/rowspan\n"); FOR_TABLE(workspace) { Container *con = workspace->table[cols][rows]; if (con->colspan > 1) { - LOG("gots one with colspan %d (at %d c, %d r)\n", con->colspan, cols, rows); + DLOG("gots one with colspan %d (at %d c, %d r)\n", con->colspan, cols, rows); while (con->colspan > 1 && (!cell_exists(cols + (con->colspan-1), rows) || workspace->table[cols + (con->colspan - 1)][rows]->currently_focused != NULL)) con->colspan--; - LOG("fixed it to %d\n", con->colspan); + DLOG("fixed it to %d\n", con->colspan); } if (con->rowspan > 1) { - LOG("gots one with rowspan %d (at %d c, %d r)\n", con->rowspan, cols, rows); + DLOG("gots one with rowspan %d (at %d c, %d r)\n", con->rowspan, cols, rows); while (con->rowspan > 1 && (!cell_exists(cols, rows + (con->rowspan - 1)) || workspace->table[cols][rows + (con->rowspan - 1)]->currently_focused != NULL)) con->rowspan--; - LOG("fixed it to %d\n", con->rowspan); + DLOG("fixed it to %d\n", con->rowspan); } } } diff --git a/src/util.c b/src/util.c index f17bd6e1..47bcc8fd 100644 --- a/src/util.c +++ b/src/util.c @@ -31,6 +31,7 @@ #include "util.h" #include "xcb.h" #include "client.h" +#include "log.h" static iconv_t conversion_descriptor = 0; struct keyvalue_table_head by_parent = TAILQ_HEAD_INITIALIZER(by_parent); @@ -44,27 +45,6 @@ int max(int a, int b) { return (a > b ? a : b); } -/* - * Logs the given message to stdout while prefixing the current time to it. - * This is to be called by LOG() which includes filename/linenumber - * - */ -void slog(char *fmt, ...) { - va_list args; - char timebuf[64]; - - va_start(args, fmt); - /* Get current time */ - time_t t = time(NULL); - /* Convert time to local time (determined by the locale) */ - struct tm *tmp = localtime(&t); - /* Generate time prefix */ - strftime(timebuf, sizeof(timebuf), "%x %X - ", tmp); - printf("%s", timebuf); - vprintf(fmt, args); - va_end(args); -} - /* * 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 @@ -280,7 +260,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) { Client *last_focused = get_last_focused_client(conn, client->container, client); if (last_focused != NULL) { - LOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child); + DLOG("raising above frame %p / child %p\n", last_focused->frame, last_focused->child); uint32_t values[] = { last_focused->frame, XCB_STACK_MODE_ABOVE }; xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); } @@ -294,13 +274,13 @@ 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 (old_client != NULL && client_is_floating(old_client)) { - LOG("Coming from floating client, searching next tiling...\n"); + DLOG("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); + DLOG("Found window: %p / child %p\n", current->frame, current->child); redecorate_window(conn, current); break; } @@ -411,14 +391,14 @@ after_stackwin: if (client == container->currently_focused || client == last_focused) continue; - LOG("setting %08x below %08x / %08x\n", client->frame, container->currently_focused->frame); + DLOG("setting %08x below %08x / %08x\n", client->frame, container->currently_focused->frame); uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW }; xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); } if (last_focused != NULL) { - LOG("Putting last_focused directly underneath the currently focused\n"); + DLOG("Putting last_focused directly underneath the currently focused\n"); uint32_t values[] = { container->currently_focused->frame, XCB_STACK_MODE_BELOW }; xcb_configure_window(conn, last_focused->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); @@ -457,7 +437,7 @@ Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitl goto done; } - LOG("Getting clients for class \"%s\" / title \"%s\"\n", to_class, to_title); + DLOG("Getting clients for class \"%s\" / title \"%s\"\n", to_class, to_title); Workspace *ws; TAILQ_FOREACH(ws, workspaces, workspaces) { if (ws->screen == NULL) @@ -465,7 +445,7 @@ Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitl Client *client; SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) { - LOG("Checking client with class=%s, name=%s\n", client->window_class, client->name); + DLOG("Checking client with class=%s, name=%s\n", client->window_class, client->name); if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len)) continue; diff --git a/src/workspace.c b/src/workspace.c index 774b7c41..bca0544c 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -26,6 +26,7 @@ #include "layout.h" #include "workspace.h" #include "client.h" +#include "log.h" /* * Returns a pointer to the workspace with the given number (starting at 0), @@ -42,10 +43,10 @@ Workspace *workspace_get(int number) { /* If we are still there, we could not find the requested workspace. */ int last_ws = TAILQ_LAST(workspaces, workspaces_head)->num; - LOG("We need to initialize that one, last ws = %d\n", last_ws); + DLOG("We need to initialize that one, last ws = %d\n", last_ws); for (int c = last_ws; c < number; c++) { - LOG("Creating new ws\n"); + DLOG("Creating new ws\n"); ws = scalloc(sizeof(Workspace)); ws->num = c+1; @@ -56,7 +57,7 @@ Workspace *workspace_get(int number) { TAILQ_INSERT_TAIL(workspaces, ws, workspaces); } - LOG("done\n"); + DLOG("done\n"); return ws; } @@ -109,7 +110,7 @@ void workspace_show(xcb_connection_t *conn, int workspace) { /* t_ws (to workspace) is just a convenience pointer to the workspace we’re switching to */ Workspace *t_ws = workspace_get(workspace-1); - LOG("show_workspace(%d)\n", workspace); + DLOG("show_workspace(%d)\n", workspace); /* Store current_row/current_col */ c_ws->current_row = current_row; @@ -120,7 +121,7 @@ void workspace_show(xcb_connection_t *conn, int workspace) { if (c_ws->screen != t_ws->screen) { /* We need to switch to the other screen first */ - LOG("moving over to other screen.\n"); + DLOG("moving over to other screen.\n"); /* Store the old client */ Client *old_client = CUR_CELL->currently_focused; @@ -163,7 +164,7 @@ void workspace_show(xcb_connection_t *conn, int workspace) { current_row = c_ws->current_row; current_col = c_ws->current_col; - LOG("new current row = %d, current col = %d\n", current_row, current_col); + DLOG("new current row = %d, current col = %d\n", current_row, current_col); workspace_map_clients(conn, c_ws); @@ -206,7 +207,7 @@ static i3Screen *get_screen_from_preference(struct screens_head *slist, char *pr char *rest; int preferred_screen = strtol(preference, &rest, 10); - LOG("Getting screen for preference \"%s\" (%d)\n", preference, preferred_screen); + DLOG("Getting screen for preference \"%s\" (%d)\n", preference, preferred_screen); if ((rest == preference) || (preferred_screen >= num_screens)) { int x = INT_MAX, y = INT_MAX; @@ -222,16 +223,16 @@ static i3Screen *get_screen_from_preference(struct screens_head *slist, char *pr x = atoi(preference); } - LOG("Looking for screen at %d x %d\n", x, y); + DLOG("Looking for screen at %d x %d\n", x, y); TAILQ_FOREACH(screen, slist, screens) if ((x == INT_MAX || screen->rect.x == x) && (y == INT_MAX || screen->rect.y == y)) { - LOG("found %p\n", screen); + DLOG("found %p\n", screen); return screen; } - LOG("none found\n"); + DLOG("none found\n"); return NULL; } else { int c = 0; @@ -252,7 +253,7 @@ static i3Screen *get_screen_from_preference(struct screens_head *slist, char *pr */ void workspace_initialize(Workspace *ws, i3Screen *screen) { if (ws->screen != NULL) { - LOG("Workspace already initialized\n"); + DLOG("Workspace already initialized\n"); return; } @@ -298,7 +299,7 @@ Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen * } if (result == NULL) { - LOG("No existing free workspace found to assign, creating a new one\n"); + DLOG("No existing free workspace found to assign, creating a new one\n"); Workspace *ws; int last_ws = 0; @@ -359,7 +360,7 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) { int unmapped_clients = 0; FOR_TABLE(u_ws) CIRCLEQ_FOREACH(client, &(u_ws->table[cols][rows]->clients), clients) { - LOG("unmapping normal client %p / %p / %p\n", client, client->frame, client->child); + DLOG("unmapping normal client %p / %p / %p\n", client, client->frame, client->child); client_unmap(conn, client); unmapped_clients++; } @@ -369,7 +370,7 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) { if (!client_is_floating(client)) continue; - LOG("unmapping floating client %p / %p / %p\n", client, client->frame, client->child); + DLOG("unmapping floating client %p / %p / %p\n", client, client->frame, client->child); client_unmap(conn, client); unmapped_clients++; @@ -380,12 +381,12 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) { if (unmapped_clients == 0 && u_ws != c_ws) { /* Re-assign the workspace of all dock clients which use this workspace */ Client *dock; - LOG("workspace %p is empty\n", u_ws); + DLOG("workspace %p is empty\n", u_ws); SLIST_FOREACH(dock, &(u_ws->screen->dock_clients), dock_clients) { if (dock->workspace != u_ws) continue; - LOG("Re-assigning dock client to c_ws (%p)\n", c_ws); + DLOG("Re-assigning dock client to c_ws (%p)\n", c_ws); dock->workspace = c_ws; } u_ws->screen = NULL; diff --git a/src/xcb.c b/src/xcb.c index a2f813b0..07286d0f 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -21,6 +21,7 @@ #include "i3.h" #include "util.h" #include "xcb.h" +#include "log.h" TAILQ_HEAD(cached_fonts_head, Font) cached_fonts = TAILQ_HEAD_INITIALIZER(cached_fonts); unsigned int xcb_numlock_mask; @@ -270,7 +271,7 @@ void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window) { * */ void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap) { - LOG("preparing pixmap\n"); + DLOG("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) @@ -279,11 +280,11 @@ void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap) memcpy(&(pixmap->rect), pixmap->referred_rect, sizeof(Rect)); if (pixmap->id == 0 || pixmap->gc == 0) { - LOG("Creating new pixmap...\n"); + DLOG("Creating new pixmap...\n"); pixmap->id = xcb_generate_id(conn); pixmap->gc = xcb_generate_id(conn); } else { - LOG("Re-creating this pixmap...\n"); + DLOG("Re-creating this pixmap...\n"); xcb_free_gc(conn, pixmap->gc); xcb_free_pixmap(conn, pixmap->id); } diff --git a/src/xinerama.c b/src/xinerama.c index fcc3a44b..79483c66 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -29,6 +29,7 @@ #include "xcb.h" #include "config.h" #include "workspace.h" +#include "log.h" /* This TAILQ of i3Screens stores the virtual screens, used for handling overlapping screens * (xrandr --same-as) */ @@ -75,7 +76,7 @@ i3Screen *get_screen_at(int x, int y, struct screens_head *screenlist) { i3Screen *get_screen_containing(int x, int y) { i3Screen *screen; TAILQ_FOREACH(screen, virtual_screens, screens) { - LOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n", + DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n", x, y, screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height); if (x >= screen->rect.x && x < (screen->rect.x + screen->rect.width) && y >= screen->rect.y && y < (screen->rect.y + screen->rect.height)) @@ -149,7 +150,7 @@ static void initialize_screen(xcb_connection_t *conn, i3Screen *screen, Workspac SLIST_INIT(&(screen->dock_clients)); - LOG("that is virtual screen at %d x %d with %d x %d\n", + DLOG("that is virtual screen at %d x %d with %d x %d\n", screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height); } @@ -192,7 +193,7 @@ static void query_screens(xcb_connection_t *conn, struct screens_head *screenlis while ((time(NULL) - before_trying) < 10) { reply = xcb_xinerama_query_screens_reply(conn, xcb_xinerama_query_screens_unchecked(conn), NULL); if (!reply) { - LOG("Couldn't get Xinerama screens\n"); + DLOG("Couldn't get Xinerama screens\n"); return; } screen_info = xcb_xinerama_query_screens_screen_info(reply); @@ -219,7 +220,7 @@ static void query_screens(xcb_connection_t *conn, struct screens_head *screenlis num_screens++; } - LOG("found Xinerama screen: %d x %d at %d x %d\n", + DLOG("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); } @@ -227,7 +228,7 @@ static void query_screens(xcb_connection_t *conn, struct screens_head *screenlis free(reply); if (num_screens == 0) { - LOG("No screens found. This is weird. Trying again...\n"); + DLOG("No screens found. This is weird. Trying again...\n"); /* Give the scheduler a chance to do something else * and don’t hog the CPU */ usleep(250); @@ -238,7 +239,7 @@ static void query_screens(xcb_connection_t *conn, struct screens_head *screenlis } if (num_screens == 0) { - LOG("No screens found for 10 seconds. Please fix your setup. i3 will exit now.\n"); + DLOG("No screens found for 10 seconds. Please fix your setup. i3 will exit now.\n"); exit(0); } } @@ -253,14 +254,14 @@ void initialize_xinerama(xcb_connection_t *conn) { TAILQ_INIT(virtual_screens); if (!xcb_get_extension_data(conn, &xcb_xinerama_id)->present) { - LOG("Xinerama extension not found, disabling.\n"); + DLOG("Xinerama extension not found, disabling.\n"); disable_xinerama(conn); } else { xcb_xinerama_is_active_reply_t *reply; reply = xcb_xinerama_is_active_reply(conn, xcb_xinerama_is_active(conn), NULL); if (reply == NULL || !reply->state) { - LOG("Xinerama is not active (in your X-Server), disabling.\n"); + DLOG("Xinerama is not active (in your X-Server), disabling.\n"); disable_xinerama(conn); } else query_screens(conn, virtual_screens); @@ -291,7 +292,7 @@ void xinerama_requery_screens(xcb_connection_t *conn) { it change when I move the --right-of video projector to --left-of? */ if (!xinerama_enabled) { - LOG("Xinerama is disabled\n"); + DLOG("Xinerama is disabled\n"); return; } @@ -319,21 +320,21 @@ void xinerama_requery_screens(xcb_connection_t *conn) { if (old_screen->num != screen_count) continue; - LOG("Found a matching screen\n"); + DLOG("Found a matching screen\n"); /* Use the same workspace */ screen->current_workspace = old_screen->current_workspace; /* Re-use the old bar window */ screen->bar = old_screen->bar; screen->bargc = old_screen->bargc; - LOG("old_screen->bar = %p\n", old_screen->bar); + DLOG("old_screen->bar = %p\n", old_screen->bar); Rect bar_rect = {screen->rect.x, screen->rect.y + screen->rect.height - (font->height + 6), screen->rect.x + screen->rect.width, font->height + 6}; - LOG("configuring bar to be at %d x %d with %d x %d\n", + DLOG("configuring bar to be at %d x %d with %d x %d\n", bar_rect.x, bar_rect.y, bar_rect.height, bar_rect.width); xcb_configure_window(conn, screen->bar, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | @@ -350,7 +351,7 @@ void xinerama_requery_screens(xcb_connection_t *conn) { if (ws->screen != old_screen) continue; - LOG("re-assigning ws %d\n", ws->num); + DLOG("re-assigning ws %d\n", ws->num); memcpy(&(ws->rect), &(screen->rect), sizeof(Rect)); ws->screen = screen; ws->reassigned = true; @@ -362,7 +363,7 @@ void xinerama_requery_screens(xcb_connection_t *conn) { /* Find the first unused workspace, preferring the ones * which are assigned to this screen and initialize * the screen with it. */ - LOG("getting first ws for screen %p\n", screen); + DLOG("getting first ws for screen %p\n", screen); Workspace *ws = get_first_workspace_for_screen(new_screens, screen); initialize_screen(conn, screen, ws); ws->reassigned = true; @@ -379,7 +380,7 @@ void xinerama_requery_screens(xcb_connection_t *conn) { if (SLIST_EMPTY(&(old_screen->dock_clients))) continue; - LOG("dock_clients out of bounds at screen %p, reassigning\n", old_screen); + DLOG("dock_clients out of bounds at screen %p, reassigning\n", old_screen); if (SLIST_EMPTY(&(first->dock_clients))) { first->dock_clients = old_screen->dock_clients; continue; @@ -402,10 +403,10 @@ void xinerama_requery_screens(xcb_connection_t *conn) { Client *client; - LOG("Closing bar window (%p)\n", ws->screen->bar); + DLOG("Closing bar window (%p)\n", ws->screen->bar); xcb_destroy_window(conn, ws->screen->bar); - LOG("Workspace %d's screen out of bounds, assigning to first screen\n", ws->num + 1); + DLOG("Workspace %d's screen out of bounds, assigning to first screen\n", ws->num + 1); ws->screen = first; memcpy(&(ws->rect), &(first->rect), sizeof(Rect)); @@ -424,7 +425,7 @@ void xinerama_requery_screens(xcb_connection_t *conn) { workspace_unmap_clients(conn, ws); if (c_ws == ws) { - LOG("Need to adjust c_ws...\n"); + DLOG("Need to adjust c_ws...\n"); c_ws = first->current_workspace; } } @@ -440,7 +441,7 @@ void xinerama_requery_screens(xcb_connection_t *conn) { virtual_screens = new_screens; - LOG("Current workspace is now: %d\n", first->current_workspace); + DLOG("Current workspace is now: %d\n", first->current_workspace); render_layout(conn); } From e498b90e7a96134bb66460d5cd900468fec0efc7 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 19 Dec 2009 22:42:24 +0100 Subject: [PATCH 051/247] makefile: clean temporary files --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index bfaff1ce..ffae7baf 100644 --- a/Makefile +++ b/Makefile @@ -79,7 +79,7 @@ dist: distclean rm -rf i3-${VERSION} clean: - rm -f src/*.o src/cfgparse.tab.{c,h} src/cfgparse.yy.c + rm -f src/*.o src/cfgparse.tab.{c,h} src/cfgparse.yy.c loglevels.tmp include/loglevels.h $(MAKE) -C docs clean $(MAKE) -C man clean $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg clean From 1776c12c0b03c6e737650f9e63f33bd774475cd6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 20 Dec 2009 12:58:45 +0100 Subject: [PATCH 052/247] makefile: properly document dependencies on each target to fix parallel make (Thanks Atsutane) --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index ffae7baf..c83fe094 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ FILES:=$(FILES:.c=.o) HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h)) # Depend on the specific file (.c for each .o) and on all headers -src/%.o: src/%.c ${HEADERS} +src/%.o: src/%.c ${HEADERS} loglevels.h echo "CC $<" $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/$(shell basename $< .c)/ { print NR }' loglevels.tmp))" -c -o $@ $< @@ -37,12 +37,12 @@ loglevels.h: rm_loglevels done; \ echo "};") > include/loglevels.h -src/cfgparse.yy.o: src/cfgparse.l +src/cfgparse.yy.o: src/cfgparse.l loglevels.h echo "LEX $<" flex -i -o$(@:.o=.c) $< $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c) -src/cfgparse.y.o: src/cfgparse.y +src/cfgparse.y.o: src/cfgparse.y loglevels.h echo "YACC $<" bison --debug --verbose -b $(basename $< .y) -d $< $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c) From 4b3ff1795c81c444635f2720dae85d271caceb1d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 20 Dec 2009 23:43:49 +0100 Subject: [PATCH 053/247] Remove -e and -t from loglevels.h target to make it POSIX compliant (Thanks Mirko) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c83fe094..4f921730 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ loglevels.h: rm_loglevels done > loglevels.tmp (echo "char *loglevels[] = {"; for file in $$(cat loglevels.tmp); \ do \ - echo -e "\t\"$$file\", "; \ + echo "\"$$file\", "; \ done; \ echo "};") > include/loglevels.h From 0b5554c762732f1c79e3e587d867922b65878a78 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 21 Dec 2009 20:41:02 +0100 Subject: [PATCH 054/247] xinerama: change some log messages to errors --- src/xinerama.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/xinerama.c b/src/xinerama.c index 79483c66..68c90ac4 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -193,7 +193,7 @@ static void query_screens(xcb_connection_t *conn, struct screens_head *screenlis while ((time(NULL) - before_trying) < 10) { reply = xcb_xinerama_query_screens_reply(conn, xcb_xinerama_query_screens_unchecked(conn), NULL); if (!reply) { - DLOG("Couldn't get Xinerama screens\n"); + ELOG("Couldn't get Xinerama screens\n"); return; } screen_info = xcb_xinerama_query_screens_screen_info(reply); @@ -228,7 +228,7 @@ static void query_screens(xcb_connection_t *conn, struct screens_head *screenlis free(reply); if (num_screens == 0) { - DLOG("No screens found. This is weird. Trying again...\n"); + ELOG("No screens found. This is weird. Trying again...\n"); /* Give the scheduler a chance to do something else * and don’t hog the CPU */ usleep(250); @@ -239,7 +239,7 @@ static void query_screens(xcb_connection_t *conn, struct screens_head *screenlis } if (num_screens == 0) { - DLOG("No screens found for 10 seconds. Please fix your setup. i3 will exit now.\n"); + ELOG("No screens found for 10 seconds. Please fix your setup. i3 will exit now.\n"); exit(0); } } From e900a8d23db1c10a36b8c45327d68038b0607a9b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 21 Dec 2009 22:30:08 +0100 Subject: [PATCH 055/247] xinerama: correctly put windows which are assigned to a specific screen on that screen when it becomes available (Thanks badboy) --- include/workspace.h | 13 +++++++++- src/commands.c | 4 +-- src/manage.c | 2 +- src/workspace.c | 63 ++++++++++++++++++++++++++++++++++++++++----- src/xinerama.c | 38 ++++++++++----------------- 5 files changed, 86 insertions(+), 34 deletions(-) diff --git a/include/workspace.h b/include/workspace.h index f3bdd565..01e1b6fa 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -44,6 +44,17 @@ bool workspace_is_visible(Workspace *ws); /** Switches to the given workspace */ void workspace_show(xcb_connection_t *conn, int workspace); +/** + * Assigns the given workspace to the given screen by correctly updating its + * state and reconfiguring all the clients on this workspace. + * + * This is called when initializing a screen and when re-assigning it to a + * different screen which just got available (if you configured it to be on + * screen 1 and you just plugged in screen 1). + * + */ +void workspace_assign_to(Workspace *ws, i3Screen *screen); + /** * Initializes the given workspace if it is not already initialized. The given * screen is to be understood as a fallback, if the workspace itself either @@ -51,7 +62,7 @@ void workspace_show(xcb_connection_t *conn, int workspace); * the screen is not attached at the moment. * */ -void workspace_initialize(Workspace *ws, i3Screen *screen); +void workspace_initialize(Workspace *ws, i3Screen *screen, bool recheck); /** * Gets the first unused workspace for the given screen, taking into account diff --git a/src/commands.c b/src/commands.c index 7cb0bdca..32b7a398 100644 --- a/src/commands.c +++ b/src/commands.c @@ -531,7 +531,7 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl LOG("moving floating\n"); - workspace_initialize(t_ws, c_ws->screen); + workspace_initialize(t_ws, c_ws->screen, false); /* Check if there is already a fullscreen client on the destination workspace and * stop moving if so. */ @@ -592,7 +592,7 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa if (to_focus == NULL) to_focus = CIRCLEQ_PREV_OR_NULL(&(container->clients), current_client, clients); - workspace_initialize(t_ws, container->workspace->screen); + workspace_initialize(t_ws, container->workspace->screen, false); /* Check if there is already a fullscreen client on the destination workspace and * stop moving if so. */ if (current_client->fullscreen && (t_ws->fullscreen_client != NULL)) { diff --git a/src/manage.c b/src/manage.c index 54e02fe4..40a6f64a 100644 --- a/src/manage.c +++ b/src/manage.c @@ -340,7 +340,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, DLOG("Changing container/workspace and unmapping the client\n"); Workspace *t_ws = workspace_get(assign->workspace-1); - workspace_initialize(t_ws, c_ws->screen); + workspace_initialize(t_ws, c_ws->screen, false); new->container = t_ws->table[t_ws->current_col][t_ws->current_row]; new->workspace = t_ws; diff --git a/src/workspace.c b/src/workspace.c index bca0544c..f8bfcd76 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -117,7 +117,7 @@ void workspace_show(xcb_connection_t *conn, int workspace) { c_ws->current_col = current_col; /* Check if the workspace has not been used yet */ - workspace_initialize(t_ws, c_ws->screen); + workspace_initialize(t_ws, c_ws->screen, false); if (c_ws->screen != t_ws->screen) { /* We need to switch to the other screen first */ @@ -244,6 +244,49 @@ static i3Screen *get_screen_from_preference(struct screens_head *slist, char *pr return NULL; } +/* + * Assigns the given workspace to the given screen by correctly updating its + * state and reconfiguring all the clients on this workspace. + * + * This is called when initializing a screen and when re-assigning it to a + * different screen which just got available (if you configured it to be on + * screen 1 and you just plugged in screen 1). + * + */ +void workspace_assign_to(Workspace *ws, i3Screen *screen) { + Client *client; + bool empty = true; + + ws->screen = screen; + + /* Copy the dimensions from the virtual screen */ + memcpy(&(ws->rect), &(ws->screen->rect), sizeof(Rect)); + + /* Force reconfiguration for each client on that workspace */ + FOR_TABLE(ws) + CIRCLEQ_FOREACH(client, &(ws->table[cols][rows]->clients), clients) { + client->force_reconfigure = true; + empty = false; + } + + if (empty) + return; + + /* Render the workspace to reconfigure the clients. However, they will be visible now, so… */ + render_workspace(global_conn, screen, ws); + + /* …unless we want to see them at the moment, we should hide that workspace */ + if (workspace_is_visible(ws)) + return; + + workspace_unmap_clients(global_conn, ws); + + if (c_ws == ws) { + DLOG("Need to adjust c_ws...\n"); + c_ws = screen->current_workspace; + } +} + /* * Initializes the given workspace if it is not already initialized. The given * screen is to be understood as a fallback, if the workspace itself either @@ -251,12 +294,16 @@ static i3Screen *get_screen_from_preference(struct screens_head *slist, char *pr * the screen is not attached at the moment. * */ -void workspace_initialize(Workspace *ws, i3Screen *screen) { - if (ws->screen != NULL) { +void workspace_initialize(Workspace *ws, i3Screen *screen, bool recheck) { + i3Screen *old_screen; + + if (ws->screen != NULL && !recheck) { DLOG("Workspace already initialized\n"); return; } + old_screen = ws->screen; + /* If this workspace has no preferred screen or if the screen it wants * to be on is not available at the moment, we initialize it with * the screen which was given */ @@ -264,8 +311,12 @@ void workspace_initialize(Workspace *ws, i3Screen *screen) { (ws->screen = get_screen_from_preference(virtual_screens, ws->preferred_screen)) == NULL) ws->screen = screen; - /* Copy the dimensions from the virtual screen */ - memcpy(&(ws->rect), &(ws->screen->rect), sizeof(Rect)); + DLOG("old_screen = %p, ws->screen = %p\n", old_screen, ws->screen); + /* If the assignment did not change, we do not need to update anything */ + if (old_screen != NULL && ws->screen == old_screen) + return; + + workspace_assign_to(ws, ws->screen); } /* @@ -308,7 +359,7 @@ Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen * result = workspace_get(last_ws + 1); } - workspace_initialize(result, screen); + workspace_initialize(result, screen, false); return result; } diff --git a/src/xinerama.c b/src/xinerama.c index 68c90ac4..81359ec2 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -203,12 +203,14 @@ static void query_screens(xcb_connection_t *conn, struct screens_head *screenlis 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) { + DLOG("Re-used old Xinerama screen %p\n", s); /* 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); + DLOG("Created new Xinerama screen %p\n", s); s->rect.x = screen_info[screen].x_org; s->rect.y = screen_info[screen].y_org; s->rect.width = screen_info[screen].width; @@ -331,7 +333,7 @@ void xinerama_requery_screens(xcb_connection_t *conn) { Rect bar_rect = {screen->rect.x, screen->rect.y + screen->rect.height - (font->height + 6), - screen->rect.x + screen->rect.width, + screen->rect.width, font->height + 6}; DLOG("configuring bar to be at %d x %d with %d x %d\n", @@ -401,34 +403,13 @@ void xinerama_requery_screens(xcb_connection_t *conn) { if (ws->reassigned) continue; - Client *client; - DLOG("Closing bar window (%p)\n", ws->screen->bar); xcb_destroy_window(conn, ws->screen->bar); DLOG("Workspace %d's screen out of bounds, assigning to first screen\n", ws->num + 1); - ws->screen = first; - memcpy(&(ws->rect), &(first->rect), sizeof(Rect)); - - /* Force reconfiguration for each client on that workspace */ - FOR_TABLE(ws) - CIRCLEQ_FOREACH(client, &(ws->table[cols][rows]->clients), clients) - client->force_reconfigure = true; - - /* Render the workspace to reconfigure the clients. However, they will be visible now, so… */ - render_workspace(conn, first, ws); - - /* …unless we want to see them at the moment, we should hide that workspace */ - if (workspace_is_visible(ws)) - continue; - - workspace_unmap_clients(conn, ws); - - if (c_ws == ws) { - DLOG("Need to adjust c_ws...\n"); - c_ws = first->current_workspace; - } + workspace_assign_to(ws, first); } + xcb_flush(conn); /* Free the old list */ @@ -441,6 +422,15 @@ void xinerama_requery_screens(xcb_connection_t *conn) { virtual_screens = new_screens; + /* Check for workspaces which need to be assigned to specific screens + * which may now be available */ + TAILQ_FOREACH(ws, workspaces, workspaces) { + if (ws->preferred_screen == NULL) + continue; + + workspace_initialize(ws, ws->screen, true); + } + DLOG("Current workspace is now: %d\n", first->current_workspace); render_layout(conn); From 500127705eb82a1002ed4b7b88dcc0c620bf5c92 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 21 Dec 2009 23:08:08 +0100 Subject: [PATCH 056/247] debian: update changelog --- debian/changelog | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index f42cd35f..3776e56e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,28 @@ -i3-wm (3.d-bf1-1) unstable; urgency=low +i3-wm (3.e-0) unstable; urgency=low * NOT YET RELEASED - -- Michael Stapelberg Sat, 12 Dec 2009 21:34:37 +0100 + -- Michael Stapelberg Mon, 21 Dec 2009 23:08:01 +0100 + +i3-wm (3.d-bf1-1) unstable; urgency=low + + * Bugfix: Don’t draw window title when titlebar is disabled + * Bugfix: Correctly switch border types for floating windows + * Bugfix: Correctly replay pointer if the click handler does not trigger + * Bugfix: Also allow WORDs as workspace names + * Bugfix: Correctly clear the urgency hint if a window gets unmapped without + clearing it + * Bugfix: Fix resizing of floating windows in borderless/1-px-border mode + * Bugfix: Accept underscores in bindsym + * Bugfix: Don’t set the urgency flag if the window is focused + * Bugfix: Handle stack-limit cols on tabbed containers + * Bugfix: Resize client after updating base_width/base_height + * Bugfix: Force render containers after setting the client active + * Bugfix: Fix two problems in resizing floating windows with right mouse + * Bugfix: Use more precise floating point arithmetics + * Bugfix: Correctly place new windows below fullscreen windows + + -- Michael Stapelberg Mon, 21 Dec 2009 22:33:02 +0100 i3-wm (3.d-2) unstable; urgency=low From 9df64a8d0ae1e622363b0483e1b7220c178be773 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 22 Dec 2009 11:29:24 +0100 Subject: [PATCH 057/247] Correctly exit when another window manager is already running This is implemented by checking if setting the redirect mask returned an error or not. --- src/mainx.c | 4 +++- src/util.c | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/mainx.c b/src/mainx.c index 1706294c..9c38c4ac 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -377,7 +377,9 @@ int main(int argc, char *argv[], char *env[]) { XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_ENTER_WINDOW }; - xcb_change_window_attributes(conn, root, mask, values); + xcb_void_cookie_t cookie; + cookie = xcb_change_window_attributes_checked(conn, root, mask, values); + check_error(conn, cookie, "Another window manager seems to be running"); /* Setup NetWM atoms */ #define GET_ATOM(name) { \ diff --git a/src/util.c b/src/util.c index 47bcc8fd..915f8cde 100644 --- a/src/util.c +++ b/src/util.c @@ -144,7 +144,7 @@ void start_application(const char *command) { void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_message) { xcb_generic_error_t *error = xcb_request_check(conn, cookie); if (error != NULL) { - fprintf(stderr, "ERROR: %s : %d\n", err_message , error->error_code); + fprintf(stderr, "ERROR: %s (X error %d)\n", err_message , error->error_code); xcb_disconnect(conn); exit(-1); } From 75ac464c0dc7758a0db7723da4bb9dcf061466eb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 22 Dec 2009 12:14:09 +0100 Subject: [PATCH 058/247] makefile: rather than a dependency for each source file, generate loglevels.h by recursion This little hack runs make recursively to generate include/loglevels.h before running any other target but skip an explicit dependency on loglevels.h in each rule. Therefore, you do not need to rebuild every source file when compiling. --- Makefile | 25 ++++++++++++++++--------- common.mk | 2 +- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 4f921730..7245c5c4 100644 --- a/Makefile +++ b/Makefile @@ -8,12 +8,22 @@ FILES:=$(filter-out $(AUTOGENERATED),$(wildcard src/*.c)) FILES:=$(FILES:.c=.o) HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h)) +# Recursively generate loglevels.h by explicitly calling make +# We need this step because we need to ensure that loglevels.h will be +# updated if necessary, but we also want to save rebuilds of the object +# files, so we cannot let the object files depend on loglevels.h. +ifeq ($(MAKECMDGOALS),loglevels.h) +UNUSED:=$(warning Generating loglevels.h) +else +UNUSED:=$(shell $(MAKE) loglevels.h) +endif + # Depend on the specific file (.c for each .o) and on all headers -src/%.o: src/%.c ${HEADERS} loglevels.h +src/%.o: src/%.c ${HEADERS} echo "CC $<" $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/$(shell basename $< .c)/ { print NR }' loglevels.tmp))" -c -o $@ $< -all: loglevels.h src/cfgparse.y.o src/cfgparse.yy.o ${FILES} +all: src/cfgparse.y.o src/cfgparse.yy.o ${FILES} echo "LINK i3" $(CC) -o i3 ${FILES} src/cfgparse.y.o src/cfgparse.yy.o $(LDFLAGS) echo "" @@ -22,10 +32,7 @@ all: loglevels.h src/cfgparse.y.o src/cfgparse.yy.o ${FILES} echo "SUBDIR i3-input" $(MAKE) TOPDIR=$(TOPDIR) -C i3-input -rm_loglevels: - rm -f loglevels.h - -loglevels.h: rm_loglevels +loglevels.h: echo "LOGLEVELS" for file in $$(ls src/*.c src/*.y src/*.l | grep -v 'cfgparse.\(tab\|yy\).c'); \ do \ @@ -35,14 +42,14 @@ loglevels.h: rm_loglevels do \ echo "\"$$file\", "; \ done; \ - echo "};") > include/loglevels.h + echo "};") > include/loglevels.h; -src/cfgparse.yy.o: src/cfgparse.l loglevels.h +src/cfgparse.yy.o: src/cfgparse.l echo "LEX $<" flex -i -o$(@:.o=.c) $< $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c) -src/cfgparse.y.o: src/cfgparse.y loglevels.h +src/cfgparse.y.o: src/cfgparse.y echo "YACC $<" bison --debug --verbose -b $(basename $< .y) -d $< $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c) diff --git a/common.mk b/common.mk index 856f9a7b..70305148 100644 --- a/common.mk +++ b/common.mk @@ -74,5 +74,5 @@ endif .SILENT: # Always remake the following targets -.PHONY: install clean dist distclean rm_loglevels +.PHONY: install clean dist distclean From 848d9c1b01367f943057449ae374025423aa755d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 22 Dec 2009 23:40:06 +0100 Subject: [PATCH 059/247] Make containers containing exactly one window behave like default containers Starting from this commit, a borderless window will always be borderless if it is the only window in a container. For example, you can have Firefox borderless in a tabbed container and as soon as the download manager or a viewer gets opened, the container will be rendered like a normal tabbed container. This solves the user-interface dilemma of borderless/1-px-border windows inside stacked/tabbed containers, at least for this special case. Thanks to Merovius for this suggestion. --- include/client.h | 2 +- include/container.h | 26 +++++++++++++++++++++++ src/container.c | 43 ++++++++++++++++++++++++++++++++++++++ src/handlers.c | 12 +++++------ src/layout.c | 51 +++++++++++++++++++++++++++------------------ 5 files changed, 106 insertions(+), 28 deletions(-) create mode 100644 include/container.h create mode 100644 src/container.c diff --git a/include/client.h b/include/client.h index 54035f6e..4eac127e 100644 --- a/include/client.h +++ b/include/client.h @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * (c) 2009 Michael Stapelberg and contributors + * © 2009 Michael Stapelberg and contributors * * See file LICENSE for license information. * diff --git a/include/container.h b/include/container.h new file mode 100644 index 00000000..78938508 --- /dev/null +++ b/include/container.h @@ -0,0 +1,26 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ +#include "data.h" + +#ifndef _CONTAINER_H +#define _CONTAINER_H + +/** + * Returns the mode of the given container (or MODE_DEFAULT if a NULL pointer + * was passed in order to save a few explicit checks in other places). If + * for_frame was set to true, the special case of having exactly one client + * in a container is handled so that MODE_DEFAULT is returned. For some parts + * of the rendering, this is interesting, other parts need the real mode. + * + */ +int container_mode(Container *con, bool for_frame); + +#endif diff --git a/src/container.c b/src/container.c new file mode 100644 index 00000000..a51c7235 --- /dev/null +++ b/src/container.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. + * + */ + +#include "data.h" +#include "log.h" + +/* + * Returns the mode of the given container (or MODE_DEFAULT if a NULL pointer + * was passed in order to save a few explicit checks in other places). If + * for_frame was set to true, the special case of having exactly one client + * in a container is handled so that MODE_DEFAULT is returned. For some parts + * of the rendering, this is interesting, other parts need the real mode. + * + */ +int container_mode(Container *con, bool for_frame) { + int num_clients = 0; + Client *client; + + if (con == NULL || con->mode == MODE_DEFAULT) + return MODE_DEFAULT; + + if (!for_frame) + return con->mode; + + CIRCLEQ_FOREACH(client, &(con->clients), clients) + num_clients++; + + /* If the container contains only one client, mode is irrelevant */ + if (num_clients == 1) { + DLOG("mode to default\n"); + return MODE_DEFAULT; + } + + return con->mode; +} diff --git a/src/handlers.c b/src/handlers.c index 823ed8b8..c9f95345 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -37,6 +37,7 @@ #include "floating.h" #include "workspace.h" #include "log.h" +#include "container.h" /* After mapping/unmapping windows, a notify event is generated. However, we don’t want it, since it’d trigger an infinite loop of switching between the different windows when @@ -605,9 +606,8 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, if (client->dock) return 1; - if (client->container != NULL && - (client->container->mode == MODE_STACK || - client->container->mode == MODE_TABBED)) + int mode = container_mode(client->container, true); + if (mode == MODE_STACK || mode == MODE_TABBED) render_container(conn, client->container); else decorate_window(conn, client, client->frame, client->titlegc, 0, 0); xcb_flush(conn); @@ -752,9 +752,7 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t * if (client->dock) return 1; - if (client->container == NULL || - (client->container->mode != MODE_STACK && - client->container->mode != MODE_TABBED)) + if (container_mode(client->container, true) == MODE_DEFAULT) decorate_window(conn, client, client->frame, client->titlegc, 0, 0); else { uint32_t background_color; @@ -779,7 +777,7 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t * /* Draw a black background */ xcb_change_gc_single(conn, client->titlegc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); - if (client->titlebar_position == TITLEBAR_OFF) { + if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) { xcb_rectangle_t crect = {1, 0, client->rect.width - (1 + 1), client->rect.height - 1}; xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect); } else { diff --git a/src/layout.c b/src/layout.c index fe9c1cfd..4b55c0ac 100644 --- a/src/layout.c +++ b/src/layout.c @@ -29,6 +29,7 @@ #include "handlers.h" #include "workspace.h" #include "log.h" +#include "container.h" /* * Updates *destination with new_value and returns true if it was changed or false @@ -141,16 +142,15 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw - Draw two lines in a lighter color - Draw the window’s title */ + int mode = container_mode(client->container, true); /* Draw a rectangle in background color around the window */ - if (client->borderless && (client->container == NULL || - (client->container->mode != MODE_STACK && - client->container->mode != MODE_TABBED))) + if (client->borderless && mode == MODE_DEFAULT) xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); else xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, color->background); /* In stacking mode, we only render the rect for this specific decoration */ - if (client->container != NULL && (client->container->mode == MODE_STACK || client->container->mode == MODE_TABBED)) { + if (mode == MODE_STACK || mode == MODE_TABBED) { /* We need to use the container’s width because it is the more recent value - when in stacking mode, clients get reconfigured only on demand (the not active client is not reconfigured), so the client’s rect.width would be wrong */ @@ -165,7 +165,10 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw /* Draw the inner background to have a black frame around clients (such as mplayer) which cannot be resized exactly in our frames and therefore are centered */ xcb_change_gc_single(conn, client->titlegc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); - if (client->titlebar_position == TITLEBAR_OFF) { + if (client->titlebar_position == TITLEBAR_OFF && client->borderless) { + xcb_rectangle_t crect = {0, 0, client->rect.width, client->rect.height}; + xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect); + } else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) { xcb_rectangle_t crect = {1, 1, client->rect.width - (1 + 1), client->rect.height - (1 + 1)}; xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect); } else { @@ -175,13 +178,13 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw } } + mode = container_mode(client->container, false); + if (client->titlebar_position != TITLEBAR_OFF) { /* Draw the lines */ xcb_draw_line(conn, drawable, gc, color->border, offset_x, offset_y, offset_x + client->rect.width, offset_y); - if ((client->container == NULL || - (client->container->mode != MODE_STACK && - client->container->mode != MODE_TABBED) || - CIRCLEQ_NEXT_OR_NULL(&(client->container->clients), client, clients) == NULL)) + if (mode == MODE_DEFAULT || + CIRCLEQ_NEXT_OR_NULL(&(client->container->clients), client, clients) == NULL) xcb_draw_line(conn, drawable, gc, color->border, offset_x + 2, /* x */ offset_y + font->height + 3, /* y */ @@ -191,9 +194,7 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw /* If the client has a title, we draw it */ if (client->name != NULL && - ((client->container != NULL && - client->container->mode != MODE_DEFAULT) || - client->titlebar_position != TITLEBAR_OFF)) { + (mode != MODE_DEFAULT || client->titlebar_position != TITLEBAR_OFF)) { /* Draw the font */ uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT; uint32_t values[] = { color->text, color->background, font->id }; @@ -267,7 +268,7 @@ void resize_client(xcb_connection_t *conn, Client *client) { XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; Rect *rect = &(client->child_rect); - switch ((client->container != NULL ? client->container->mode : MODE_DEFAULT)) { + switch (container_mode(client->container, true)) { case MODE_STACK: case MODE_TABBED: rect->x = 2; @@ -409,7 +410,7 @@ void render_container(xcb_connection_t *conn, Container *container) { /* Check if we need to remap our stack title window, it gets unmapped when the container is empty in src/handlers.c:unmap_notify() */ - if (stack_win->rect.height == 0 && num_clients > 0) { + if (stack_win->rect.height == 0 && num_clients > 1) { DLOG("remapping stack win\n"); xcb_map_window(conn, stack_win->window); } else DLOG("not remapping stackwin, height = %d, num_clients = %d\n", @@ -429,11 +430,21 @@ void render_container(xcb_connection_t *conn, Container *container) { stack_lines = min(num_clients, container->stack_limit_value); } + int height = decoration_height * stack_lines; + if (num_clients == 1) { + height = 0; + stack_win->rect.height = 0; + xcb_unmap_window(conn, stack_win->window); + + DLOG("Just one client, setting height to %d\n", height); + } + /* Check if we need to reconfigure our stack title window */ - if (update_if_necessary(&(stack_win->rect.x), container->x) | - update_if_necessary(&(stack_win->rect.y), container->y) | - update_if_necessary(&(stack_win->rect.width), container->width) | - update_if_necessary(&(stack_win->rect.height), decoration_height * stack_lines)) { + if (height > 0 && ( + update_if_necessary(&(stack_win->rect.x), container->x) | + update_if_necessary(&(stack_win->rect.y), container->y) | + update_if_necessary(&(stack_win->rect.width), container->width) | + update_if_necessary(&(stack_win->rect.height), height))) { /* Configuration can happen in two slightly different ways: @@ -497,9 +508,9 @@ void render_container(xcb_connection_t *conn, Container *container) { * Note the bitwise OR instead of logical OR to force evaluation of all statements */ if (client->force_reconfigure | update_if_necessary(&(client->rect.x), container->x) | - update_if_necessary(&(client->rect.y), container->y + (decoration_height * stack_lines)) | + update_if_necessary(&(client->rect.y), container->y + height) | update_if_necessary(&(client->rect.width), container->width) | - update_if_necessary(&(client->rect.height), container->height - (decoration_height * stack_lines))) + update_if_necessary(&(client->rect.height), container->height - height)) resize_client(conn, client); client->force_reconfigure = false; From 3e53ecf69e265f58e162d80f542db6fe75869cec Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 22 Dec 2009 23:43:05 +0100 Subject: [PATCH 060/247] retab! src/container.c --- src/container.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/container.c b/src/container.c index a51c7235..8533fd49 100644 --- a/src/container.c +++ b/src/container.c @@ -21,23 +21,23 @@ * */ int container_mode(Container *con, bool for_frame) { - int num_clients = 0; - Client *client; + int num_clients = 0; + Client *client; - if (con == NULL || con->mode == MODE_DEFAULT) - return MODE_DEFAULT; + if (con == NULL || con->mode == MODE_DEFAULT) + return MODE_DEFAULT; if (!for_frame) return con->mode; - CIRCLEQ_FOREACH(client, &(con->clients), clients) - num_clients++; + CIRCLEQ_FOREACH(client, &(con->clients), clients) + num_clients++; - /* If the container contains only one client, mode is irrelevant */ - if (num_clients == 1) { - DLOG("mode to default\n"); - return MODE_DEFAULT; - } + /* If the container contains only one client, mode is irrelevant */ + if (num_clients == 1) { + DLOG("mode to default\n"); + return MODE_DEFAULT; + } - return con->mode; + return con->mode; } From ba82a3e63bfefcccef560155fbafc1f2d8a66bc2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 23 Dec 2009 00:39:03 +0100 Subject: [PATCH 061/247] Bugfix: Fix NULL pointer dereference in workspaces which have preferred screens but were not used yet (Thanks badboy) --- src/xinerama.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xinerama.c b/src/xinerama.c index 81359ec2..15e8b0a3 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -425,7 +425,7 @@ void xinerama_requery_screens(xcb_connection_t *conn) { /* Check for workspaces which need to be assigned to specific screens * which may now be available */ TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->preferred_screen == NULL) + if (ws->preferred_screen == NULL || ws->screen == NULL) continue; workspace_initialize(ws, ws->screen, true); From 0641e6a1a386aa9568eb6ffa5f38694d05539851 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 25 Dec 2009 15:05:17 +0100 Subject: [PATCH 062/247] ewmh: correctly set _NET_CURRENT_DESKTOP to the number of the active workspace --- include/ewmh.h | 23 +++++++++++++++++++++++ include/i3.h | 2 +- include/xcb.h | 3 ++- src/ewmh.c | 32 ++++++++++++++++++++++++++++++++ src/mainx.c | 2 ++ src/util.c | 2 ++ 6 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 include/ewmh.h create mode 100644 src/ewmh.c diff --git a/include/ewmh.h b/include/ewmh.h new file mode 100644 index 00000000..a54a79c3 --- /dev/null +++ b/include/ewmh.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 _EWMH_C +#define _EWMH_C + +/** + * Updates _NET_CURRENT_DESKTOP with the current desktop number. + * + * EWMH: The index of the current desktop. This is always an integer between 0 + * and _NET_NUMBER_OF_DESKTOPS - 1. + * + */ +void ewmh_update_current_desktop(); + +#endif diff --git a/include/i3.h b/include/i3.h index fda6cfd8..ddcbc3b7 100644 --- a/include/i3.h +++ b/include/i3.h @@ -21,7 +21,7 @@ #ifndef _I3_H #define _I3_H -#define NUM_ATOMS 18 +#define NUM_ATOMS 19 extern xcb_connection_t *global_conn; extern xcb_key_symbols_t *keysyms; diff --git a/include/xcb.h b/include/xcb.h index d01f6da1..aa9a4ec5 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -61,7 +61,8 @@ enum { _NET_SUPPORTED = 0, WM_DELETE_WINDOW, UTF8_STRING, WM_STATE, - WM_CLIENT_LEADER + WM_CLIENT_LEADER, + _NET_CURRENT_DESKTOP }; extern unsigned int xcb_numlock_mask; diff --git a/src/ewmh.c b/src/ewmh.c new file mode 100644 index 00000000..b222e289 --- /dev/null +++ b/src/ewmh.c @@ -0,0 +1,32 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + * ewmh.c: Functions to get/set certain EWMH properties easily. + * + */ +#include + +#include "data.h" +#include "table.h" +#include "i3.h" +#include "xcb.h" + +/* + * Updates _NET_CURRENT_DESKTOP with the current desktop number. + * + * EWMH: The index of the current desktop. This is always an integer between 0 + * and _NET_NUMBER_OF_DESKTOPS - 1. + * + */ +void ewmh_update_current_desktop() { + uint32_t current_desktop = c_ws->num; + xcb_change_property(global_conn, XCB_PROP_MODE_REPLACE, root, + atoms[_NET_CURRENT_DESKTOP], CARDINAL, 32, 1, + ¤t_desktop); +} diff --git a/src/mainx.c b/src/mainx.c index 9c38c4ac..9298fb3d 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -248,6 +248,7 @@ int main(int argc, char *argv[], char *env[]) { REQUEST_ATOM(UTF8_STRING); REQUEST_ATOM(WM_STATE); REQUEST_ATOM(WM_CLIENT_LEADER); + REQUEST_ATOM(_NET_CURRENT_DESKTOP); /* TODO: this has to be more beautiful somewhen */ int major, minor, error; @@ -410,6 +411,7 @@ int main(int argc, char *argv[], char *env[]) { GET_ATOM(UTF8_STRING); GET_ATOM(WM_STATE); GET_ATOM(WM_CLIENT_LEADER); + GET_ATOM(_NET_CURRENT_DESKTOP); 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 */ diff --git a/src/util.c b/src/util.c index 915f8cde..8a202299 100644 --- a/src/util.c +++ b/src/util.c @@ -32,6 +32,7 @@ #include "xcb.h" #include "client.h" #include "log.h" +#include "ewmh.h" static iconv_t conversion_descriptor = 0; struct keyvalue_table_head by_parent = TAILQ_HEAD_INITIALIZER(by_parent); @@ -230,6 +231,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) { c_ws->current_row = current_row; c_ws->current_col = current_col; c_ws = client->workspace; + ewmh_update_current_desktop(); /* Load current_col/current_row if we switch to a client without a container */ current_col = c_ws->current_col; current_row = c_ws->current_row; From e7e9e8e49d3df01c3f534a17e810242b9621058c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 25 Dec 2009 15:19:39 +0100 Subject: [PATCH 063/247] ewmh: correctly set _NET_ACTIVE_WINDOW --- include/ewmh.h | 9 +++++++++ include/i3.h | 2 +- include/xcb.h | 3 ++- src/ewmh.c | 12 ++++++++++++ src/mainx.c | 2 ++ src/manage.c | 5 ++++- src/util.c | 1 + 7 files changed, 31 insertions(+), 3 deletions(-) diff --git a/include/ewmh.h b/include/ewmh.h index a54a79c3..9ed85275 100644 --- a/include/ewmh.h +++ b/include/ewmh.h @@ -20,4 +20,13 @@ */ void ewmh_update_current_desktop(); +/** + * Updates _NET_ACTIVE_WINDOW with the currently focused window. + * + * EWMH: The window ID of the currently active window or None if no window has + * the focus. + * + */ +void ewmh_update_active_window(xcb_window_t window); + #endif diff --git a/include/i3.h b/include/i3.h index ddcbc3b7..abd503f4 100644 --- a/include/i3.h +++ b/include/i3.h @@ -21,7 +21,7 @@ #ifndef _I3_H #define _I3_H -#define NUM_ATOMS 19 +#define NUM_ATOMS 20 extern xcb_connection_t *global_conn; extern xcb_key_symbols_t *keysyms; diff --git a/include/xcb.h b/include/xcb.h index aa9a4ec5..883824dc 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -62,7 +62,8 @@ enum { _NET_SUPPORTED = 0, UTF8_STRING, WM_STATE, WM_CLIENT_LEADER, - _NET_CURRENT_DESKTOP + _NET_CURRENT_DESKTOP, + _NET_ACTIVE_WINDOW }; extern unsigned int xcb_numlock_mask; diff --git a/src/ewmh.c b/src/ewmh.c index b222e289..2e6e121b 100644 --- a/src/ewmh.c +++ b/src/ewmh.c @@ -30,3 +30,15 @@ void ewmh_update_current_desktop() { atoms[_NET_CURRENT_DESKTOP], CARDINAL, 32, 1, ¤t_desktop); } + +/* + * Updates _NET_ACTIVE_WINDOW with the currently focused window. + * + * EWMH: The window ID of the currently active window or None if no window has + * the focus. + * + */ +void ewmh_update_active_window(xcb_window_t window) { + xcb_change_property(global_conn, XCB_PROP_MODE_REPLACE, root, + atoms[_NET_ACTIVE_WINDOW], WINDOW, 32, 1, &window); +} diff --git a/src/mainx.c b/src/mainx.c index 9298fb3d..2a5f0037 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -249,6 +249,7 @@ int main(int argc, char *argv[], char *env[]) { REQUEST_ATOM(WM_STATE); REQUEST_ATOM(WM_CLIENT_LEADER); REQUEST_ATOM(_NET_CURRENT_DESKTOP); + REQUEST_ATOM(_NET_ACTIVE_WINDOW); /* TODO: this has to be more beautiful somewhen */ int major, minor, error; @@ -412,6 +413,7 @@ int main(int argc, char *argv[], char *env[]) { GET_ATOM(WM_STATE); GET_ATOM(WM_CLIENT_LEADER); GET_ATOM(_NET_CURRENT_DESKTOP); + GET_ATOM(_NET_ACTIVE_WINDOW); 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 */ diff --git a/src/manage.c b/src/manage.c index 40a6f64a..b80c94e0 100644 --- a/src/manage.c +++ b/src/manage.c @@ -31,6 +31,7 @@ #include "client.h" #include "workspace.h" #include "log.h" +#include "ewmh.h" /* * Go through all existing windows (if the window manager is restarted) and manage them @@ -450,8 +451,10 @@ map: if (map_frame) render_container(conn, new->container); } - if (new->container == CUR_CELL || client_is_floating(new)) + if (new->container == CUR_CELL || client_is_floating(new)) { xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME); + ewmh_update_active_window(new->child); + } } } diff --git a/src/util.c b/src/util.c index 8a202299..bc54caa5 100644 --- a/src/util.c +++ b/src/util.c @@ -247,6 +247,7 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) { CLIENT_LOG(client); /* Set focus to the entered window, and flush xcb buffer immediately */ xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, client->child, XCB_CURRENT_TIME); + ewmh_update_active_window(client->child); //xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, 10, 10); if (client->container != NULL) { From c50bde458b30672d195736d7dbe7615d6fa626e6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 29 Dec 2009 17:48:16 +0100 Subject: [PATCH 064/247] Fix bindings using the cursor keys in default config --- i3.config | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/i3.config b/i3.config index 24110046..7b838512 100644 --- a/i3.config +++ b/i3.config @@ -43,10 +43,10 @@ bind Mod3+45 wcj bind Mod3+46 wck bind Mod3+47 wcl # (alternatively, you can use the cursor keys:) -bindsym Mod3+Left h -bindsym Mod3+Down j -bindsym Mod3+Up k -bindsym Mod3+Right l +bindsym Mod3+Left wch +bindsym Mod3+Down wcj +bindsym Mod3+Up wck +bindsym Mod3+Right wcl # Snap (Mod1+Control+j/k/l/;) bind Mod1+Control+44 sh @@ -54,10 +54,10 @@ bind Mod1+Control+45 sj bind Mod1+Control+46 sk bind Mod1+Control+47 sl # (alternatively, you can use the cursor keys:) -bindsym Mod1+Control+Left h -bindsym Mod1+Control+Down j -bindsym Mod1+Control+Up k -bindsym Mod1+Control+Right l +bindsym Mod1+Control+Left sh +bindsym Mod1+Control+Down sj +bindsym Mod1+Control+Up sk +bindsym Mod1+Control+Right sl # Move (Mod1+Shift+j/k/l/;) bind Mod1+Shift+44 mh @@ -65,10 +65,10 @@ bind Mod1+Shift+45 mj bind Mod1+Shift+46 mk bind Mod1+Shift+47 ml # (alternatively, you can use the cursor keys:) -bindsym Mod1+Shift+Left h -bindsym Mod1+Shift+Down j -bindsym Mod1+Shift+Up k -bindsym Mod1+Shift+Right l +bindsym Mod1+Shift+Left mh +bindsym Mod1+Shift+Down mj +bindsym Mod1+Shift+Up mk +bindsym Mod1+Shift+Right ml # Move Container (Mod3+Shift+j/k/l/;) bind Mod3+Shift+44 wcmh From 75aac5bc02f07f1b1b1814e488926d22173e3594 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 31 Dec 2009 17:48:41 +0100 Subject: [PATCH 065/247] ewmh: implement support for _NET_WORKAREA (rdesktop can use that) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Please note that rdesktop’s -g workarea option will not work on 64-bit systems at the moment because of a bug in rdesktop (see the rdesktop-devel mailing list). --- include/data.h | 10 +++++++--- include/ewmh.h | 10 ++++++++++ include/i3.h | 2 +- include/xcb.h | 3 ++- src/ewmh.c | 32 ++++++++++++++++++++++++++++++++ src/mainx.c | 2 ++ src/workspace.c | 5 +++++ 7 files changed, 59 insertions(+), 5 deletions(-) diff --git a/include/data.h b/include/data.h index 12c23554..8c846537 100644 --- a/include/data.h +++ b/include/data.h @@ -75,12 +75,16 @@ enum { /** * Stores a rectangle, for example the size of a window, the child window etc. + * It needs to be packed so that the compiler will not add any padding bytes. + * (it is used in src/ewmh.c for example) * */ struct Rect { - uint32_t x, y; - uint32_t width, height; -}; + uint32_t x; + uint32_t y; + uint32_t width; + uint32_t height; +} __attribute__((packed)); /** * Defines a position in the table diff --git a/include/ewmh.h b/include/ewmh.h index 9ed85275..c73c4a45 100644 --- a/include/ewmh.h +++ b/include/ewmh.h @@ -29,4 +29,14 @@ void ewmh_update_current_desktop(); */ void ewmh_update_active_window(xcb_window_t window); +/** + * Updates the workarea for each desktop. + * + * EWMH: Contains a geometry for each desktop. These geometries specify an area + * that is completely contained within the viewport. Work area SHOULD be used by + * desktop applications to place desktop icons appropriately. + * + */ +void ewmh_update_workarea(); + #endif diff --git a/include/i3.h b/include/i3.h index abd503f4..ce86e924 100644 --- a/include/i3.h +++ b/include/i3.h @@ -21,7 +21,7 @@ #ifndef _I3_H #define _I3_H -#define NUM_ATOMS 20 +#define NUM_ATOMS 21 extern xcb_connection_t *global_conn; extern xcb_key_symbols_t *keysyms; diff --git a/include/xcb.h b/include/xcb.h index 883824dc..d4ac33a3 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -63,7 +63,8 @@ enum { _NET_SUPPORTED = 0, WM_STATE, WM_CLIENT_LEADER, _NET_CURRENT_DESKTOP, - _NET_ACTIVE_WINDOW + _NET_ACTIVE_WINDOW, + _NET_WORKAREA }; extern unsigned int xcb_numlock_mask; diff --git a/src/ewmh.c b/src/ewmh.c index 2e6e121b..0d1e8a1b 100644 --- a/src/ewmh.c +++ b/src/ewmh.c @@ -11,11 +11,15 @@ * */ #include +#include +#include #include "data.h" #include "table.h" #include "i3.h" #include "xcb.h" +#include "util.h" +#include "log.h" /* * Updates _NET_CURRENT_DESKTOP with the current desktop number. @@ -42,3 +46,31 @@ void ewmh_update_active_window(xcb_window_t window) { xcb_change_property(global_conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_ACTIVE_WINDOW], WINDOW, 32, 1, &window); } + +/* + * Updates the workarea for each desktop. + * + * EWMH: Contains a geometry for each desktop. These geometries specify an area + * that is completely contained within the viewport. Work area SHOULD be used by + * desktop applications to place desktop icons appropriately. + * + */ +void ewmh_update_workarea() { + Workspace *ws; + int num_workspaces = 0, count = 0; + /* Get the number of workspaces */ + TAILQ_FOREACH(ws, workspaces, workspaces) + num_workspaces++; + DLOG("Got %d workspaces\n", num_workspaces); + uint8_t *workarea = smalloc(sizeof(Rect) * num_workspaces); + TAILQ_FOREACH(ws, workspaces, workspaces) { + DLOG("storing %d: %dx%d with %d x %d\n", count, ws->rect.x, ws->rect.y, ws->rect.width, ws->rect.height); + memcpy(workarea + (sizeof(Rect) * count++), &(ws->rect), sizeof(Rect)); + } + xcb_change_property(global_conn, XCB_PROP_MODE_REPLACE, root, + atoms[_NET_WORKAREA], CARDINAL, 32, + num_workspaces * (sizeof(Rect) / sizeof(uint32_t)), + workarea); + free(workarea); + xcb_flush(global_conn); +} diff --git a/src/mainx.c b/src/mainx.c index 2a5f0037..1e174644 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -250,6 +250,7 @@ int main(int argc, char *argv[], char *env[]) { REQUEST_ATOM(WM_CLIENT_LEADER); REQUEST_ATOM(_NET_CURRENT_DESKTOP); REQUEST_ATOM(_NET_ACTIVE_WINDOW); + REQUEST_ATOM(_NET_WORKAREA); /* TODO: this has to be more beautiful somewhen */ int major, minor, error; @@ -414,6 +415,7 @@ int main(int argc, char *argv[], char *env[]) { GET_ATOM(WM_CLIENT_LEADER); GET_ATOM(_NET_CURRENT_DESKTOP); GET_ATOM(_NET_ACTIVE_WINDOW); + GET_ATOM(_NET_WORKAREA); 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 */ diff --git a/src/workspace.c b/src/workspace.c index f8bfcd76..7c29e6f7 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -27,6 +27,7 @@ #include "workspace.h" #include "client.h" #include "log.h" +#include "ewmh.h" /* * Returns a pointer to the workspace with the given number (starting at 0), @@ -59,6 +60,8 @@ Workspace *workspace_get(int number) { } DLOG("done\n"); + ewmh_update_workarea(); + return ws; } @@ -262,6 +265,8 @@ void workspace_assign_to(Workspace *ws, i3Screen *screen) { /* Copy the dimensions from the virtual screen */ memcpy(&(ws->rect), &(ws->screen->rect), sizeof(Rect)); + ewmh_update_workarea(); + /* Force reconfiguration for each client on that workspace */ FOR_TABLE(ws) CIRCLEQ_FOREACH(client, &(ws->table[cols][rows]->clients), clients) { From 65cae2cad1e12530c48bca710ef4e886745d2786 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 1 Jan 2010 16:19:42 +0100 Subject: [PATCH 066/247] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20put=20dock=20?= =?UTF-8?q?clients=20into=20floating=20mode=20(Thanks=20xeen)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/floating.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/floating.c b/src/floating.c index 2facc6cf..1e7a4fac 100644 --- a/src/floating.c +++ b/src/floating.c @@ -42,6 +42,11 @@ void toggle_floating_mode(xcb_connection_t *conn, Client *client, bool automatic Container *con = client->container; i3Font *font = load_font(conn, config.font); + if (client->dock) { + DLOG("Not putting dock client into floating mode\n"); + return; + } + if (con == NULL) { DLOG("This client is already in floating (container == NULL), re-inserting\n"); Client *next_tiling; From 8a9b57c874976e81c576fec64a601c7c336b4264 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 1 Jan 2010 16:19:55 +0100 Subject: [PATCH 067/247] Extend testcase for dock clients with wm_transient_for hint --- testcases/t/10-dock.t | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/testcases/t/10-dock.t b/testcases/t/10-dock.t index 97ac5f41..b1b7bfcb 100644 --- a/testcases/t/10-dock.t +++ b/testcases/t/10-dock.t @@ -43,4 +43,17 @@ sleep 0.25; my $rect = $window->rect; is($rect->width, $primary->rect->width, 'dock client is as wide as the screen'); +my $fwindow = $x->root->create_child( + class => WINDOW_CLASS_INPUT_OUTPUT, + rect => [ 0, 0, 30, 30], + background_color => '#FF0000', + type => $x->atom(name => '_NET_WM_WINDOW_TYPE_DOCK'), +); + +$fwindow->transient_for($window); +$fwindow->map; + +sleep 0.25; + + diag( "Testing i3, Perl $], $^X" ); From 092f3139d87f5b57311aee577ba55d8725896e00 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 1 Jan 2010 17:30:27 +0100 Subject: [PATCH 068/247] Remove superfluous #include (Thanks badboy) --- src/mainx.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mainx.c b/src/mainx.c index 1e174644..3ff8c022 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -9,7 +9,6 @@ * */ #include -#include #include #include #include From ba2dd3a3ebef790b0f342027476238ef8bfc9d8e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 1 Jan 2010 22:40:50 +0100 Subject: [PATCH 069/247] Bugfix: Containers could lose their snap state (Thanks Atsutane) When being on a different workspace than the one where the snapped container is, the function to cleanup cols/rows would clean up too much. --- include/table.h | 2 +- src/click.c | 8 ++++---- src/commands.c | 20 ++++++++++---------- src/table.c | 10 +++++----- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/include/table.h b/include/table.h index 18561474..6236bef5 100644 --- a/include/table.h +++ b/include/table.h @@ -48,7 +48,7 @@ void expand_table_cols_at_head(Workspace *workspace); * Performs simple bounds checking for the given column/row * */ -bool cell_exists(int col, int row); +bool cell_exists(Workspace *ws, int col, int row); /** * Shrinks the table by "compacting" it, that is, removing completely empty diff --git a/src/click.c b/src/click.c index dccd5b4f..2ec071c4 100644 --- a/src/click.c +++ b/src/click.c @@ -212,7 +212,7 @@ static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client, first = con->col + (con->colspan - 1); DLOG("column %d\n", first); - if (!cell_exists(first, con->row) || + if (!cell_exists(ws, first, con->row) || (first == (ws->cols-1))) return false; @@ -240,7 +240,7 @@ static bool floating_mod_on_tiled_client(xcb_connection_t *conn, Client *client, to_bottom < to_top) { /* …bottom border */ first = con->row + (con->rowspan - 1); - if (!cell_exists(con->col, first) || + if (!cell_exists(ws, con->col, first) || (first == (ws->rows-1))) return false; @@ -377,7 +377,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ } else if (event->event_y >= (client->rect.height - 2)) { /* …bottom border */ first = con->row + (con->rowspan - 1); - if (!cell_exists(con->col, first) || + if (!cell_exists(ws, con->col, first) || (first == (ws->rows-1))) return 1; @@ -395,7 +395,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ first = con->col + (con->colspan - 1); DLOG("column %d\n", first); - if (!cell_exists(first, con->row) || + if (!cell_exists(ws, first, con->row) || (first == (ws->cols-1))) return 1; diff --git a/src/commands.c b/src/commands.c index 32b7a398..31564bb9 100644 --- a/src/commands.c +++ b/src/commands.c @@ -144,9 +144,9 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t if (focus_window_in_container(conn, container, direction)) return; - if (direction == D_DOWN && cell_exists(current_col, current_row+1)) + if (direction == D_DOWN && cell_exists(t_ws, current_col, current_row+1)) new_row = current_row + t_ws->table[current_col][current_row]->rowspan; - else if (direction == D_UP && cell_exists(current_col, current_row-1)) { + else if (direction == D_UP && cell_exists(c_ws, current_col, current_row-1)) { /* Set new_row as a sane default, but it may get overwritten in a second */ new_row--; @@ -187,9 +187,9 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t DLOG("Fixed it to new col %d\n", new_col); } } else if (direction == D_LEFT || direction == D_RIGHT) { - if (direction == D_RIGHT && cell_exists(current_col+1, current_row)) + if (direction == D_RIGHT && cell_exists(t_ws, current_col+1, current_row)) new_col = current_col + t_ws->table[current_col][current_row]->colspan; - else if (direction == D_LEFT && cell_exists(current_col-1, current_row)) { + else if (direction == D_LEFT && cell_exists(t_ws, current_col-1, current_row)) { /* Set new_col as a sane default, but it may get overwritten in a second */ new_col--; @@ -452,7 +452,7 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction switch (direction) { case D_LEFT: /* Snap to the left is actually a move to the left and then a snap right */ - if (!cell_exists(container->col - 1, container->row) || + if (!cell_exists(container->workspace, container->col - 1, container->row) || CUR_TABLE[container->col-1][container->row]->currently_focused != NULL) { ELOG("cannot snap to left - the cell is already used\n"); return; @@ -465,7 +465,7 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction /* Check if the cell is used */ int new_col = container->col + container->colspan; for (int i = 0; i < container->rowspan; i++) - if (!cell_exists(new_col, container->row + i) || + if (!cell_exists(container->workspace, new_col, container->row + i) || CUR_TABLE[new_col][container->row + i]->currently_focused != NULL) { ELOG("cannot snap to right - the cell is already used\n"); return; @@ -485,7 +485,7 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction break; } case D_UP: - if (!cell_exists(container->col, container->row - 1) || + if (!cell_exists(container->workspace, container->col, container->row - 1) || CUR_TABLE[container->col][container->row-1]->currently_focused != NULL) { ELOG("cannot snap to top - the cell is already used\n"); return; @@ -498,7 +498,7 @@ static void snap_current_container(xcb_connection_t *conn, direction_t direction DLOG("snapping down\n"); int new_row = container->row + container->rowspan; for (int i = 0; i < container->colspan; i++) - if (!cell_exists(container->col + i, new_row) || + if (!cell_exists(container->workspace, container->col + i, new_row) || CUR_TABLE[container->col + i][new_row]->currently_focused != NULL) { ELOG("cannot snap down - the cell is already used\n"); return; @@ -824,7 +824,7 @@ static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, c first = con->col + (con->colspan - 1); DLOG("column %d\n", first); - if (!cell_exists(first, con->row) || + if (!cell_exists(ws, first, con->row) || (first == (ws->cols-1))) return; @@ -839,7 +839,7 @@ static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, c command += strlen("top"); } else if (STARTS_WITH(command, "bottom")) { first = con->row + (con->rowspan - 1); - if (!cell_exists(con->col, first) || + if (!cell_exists(ws, con->col, first) || (first == (ws->rows-1))) return; diff --git a/src/table.c b/src/table.c index 8aa02fba..1660d308 100644 --- a/src/table.c +++ b/src/table.c @@ -242,9 +242,9 @@ static void shrink_table_rows(Workspace *workspace) { * Performs simple bounds checking for the given column/row * */ -bool cell_exists(int col, int row) { - return (col >= 0 && col < c_ws->cols) && - (row >= 0 && row < c_ws->rows); +bool cell_exists(Workspace *ws, int col, int row) { + return (col >= 0 && col < ws->cols) && + (row >= 0 && row < ws->rows); } static void free_container(xcb_connection_t *conn, Workspace *workspace, int col, int row) { @@ -389,7 +389,7 @@ void fix_colrowspan(xcb_connection_t *conn, Workspace *workspace) { if (con->colspan > 1) { DLOG("gots one with colspan %d (at %d c, %d r)\n", con->colspan, cols, rows); while (con->colspan > 1 && - (!cell_exists(cols + (con->colspan-1), rows) || + (!cell_exists(workspace, cols + (con->colspan-1), rows) && workspace->table[cols + (con->colspan - 1)][rows]->currently_focused != NULL)) con->colspan--; DLOG("fixed it to %d\n", con->colspan); @@ -397,7 +397,7 @@ void fix_colrowspan(xcb_connection_t *conn, Workspace *workspace) { if (con->rowspan > 1) { DLOG("gots one with rowspan %d (at %d c, %d r)\n", con->rowspan, cols, rows); while (con->rowspan > 1 && - (!cell_exists(cols, rows + (con->rowspan - 1)) || + (!cell_exists(workspace, cols, rows + (con->rowspan - 1)) && workspace->table[cols][rows + (con->rowspan - 1)]->currently_focused != NULL)) con->rowspan--; DLOG("fixed it to %d\n", con->rowspan); From 7c130fb540da378c4ba3744d2ff39983df3ad705 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 3 Jan 2010 16:58:41 +0100 Subject: [PATCH 070/247] Obey the XDG Base Directory Specification for config file paths This means you can now put your i3 config into $XDG_CONFIG_HOME/i3/config, which probably is ~/.config/i3/config if not set otherwise. --- src/config.c | 83 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 13 deletions(-) diff --git a/src/config.c b/src/config.c index 930b09c5..4babce1b 100644 --- a/src/config.c +++ b/src/config.c @@ -15,8 +15,10 @@ #include #include #include +#include #include #include +#include /* We need Xlib for XStringToKeysym */ #include @@ -47,6 +49,15 @@ static char *glob_path(const char *path) { return result; } +/* + * Checks if the given path exists by calling stat(). + * + */ +static bool path_exists(const char *path) { + struct stat buf; + return (stat(path, &buf) == 0); +} + /** * Ungrabs all keys, to be called before re-grabbing the keys because of a * mapping_notify event or a configuration file reload @@ -148,6 +159,61 @@ void switch_mode(xcb_connection_t *conn, const char *new_mode) { ELOG("ERROR: Mode not found\n"); } +/* + * Get the path of the first configuration file found. Checks the XDG folders + * first ($XDG_CONFIG_HOME, $XDG_CONFIG_DIRS), then the traditional paths. + * + */ +static char *get_config_path() { + /* 1: check for $XDG_CONFIG_HOME/i3/config */ + char *xdg_config_home, *xdg_config_dirs, *config_path; + + if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) + xdg_config_home = "~/.config"; + + xdg_config_home = glob_path(xdg_config_home); + if (asprintf(&config_path, "%s/i3/config", xdg_config_home) == -1) + die("asprintf() failed"); + free(xdg_config_home); + + if (path_exists(config_path)) + return config_path; + free(config_path); + + /* 2: check for $XDG_CONFIG_DIRS/i3/config */ + if ((xdg_config_dirs = getenv("XDG_CONFIG_DIRS")) == NULL) + xdg_config_dirs = "/etc/xdg"; + + char *buf = strdup(xdg_config_dirs); + char *tok = strtok(buf, ":"); + while (tok != NULL) { + tok = glob_path(tok); + if (asprintf(&config_path, "%s/i3/config", tok) == -1) + die("asprintf() failed"); + free(tok); + if (path_exists(config_path)) { + free(buf); + return config_path; + } + free(config_path); + tok = strtok(NULL, ":"); + } + free(buf); + + /* 3: check traditional paths */ + config_path = glob_path("~/.i3/config"); + if (path_exists(config_path)) + return config_path; + + config_path = strdup("/etc/i3/config"); + if (!path_exists(config_path)) + die("Neither $XDG_CONFIG_HOME/i3/config, nor " + "$XDG_CONFIG_DIRS/i3/config, nor ~/.i3/config nor " + "/etc/i3/config exist."); + + return config_path; +} + /* * Finds the configuration file to use (either the one specified by * override_configpath), the user’s one or the system default) and calls @@ -160,19 +226,10 @@ static void parse_configuration(const char *override_configpath) { return; } - FILE *handle; - char *globbed = glob_path("~/.i3/config"); - if ((handle = fopen(globbed, "r")) == NULL) { - if ((handle = fopen("/etc/i3/config", "r")) == NULL) - die("Neither \"%s\" nor /etc/i3/config could be opened\n", globbed); - - parse_file("/etc/i3/config"); - fclose(handle); - return; - } - - parse_file(globbed); - fclose(handle); + char *path = get_config_path(); + DLOG("Parsing configfile %s\n", path); + parse_file(path); + free(path); } /* From 715983024d0dbbbd8a4755441c75ff8b7436b8a9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 3 Jan 2010 18:36:50 +0100 Subject: [PATCH 071/247] =?UTF-8?q?ewmh:=20Don=E2=80=99t=20push=20workspac?= =?UTF-8?q?es=20with=20width=3D0=20and=20height=3D0=20(Thanks=20Atsutane,?= =?UTF-8?q?=20badboy)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Even though i3 cannot know the width/height of some workspaces as long as they are not initialized (say you used workspace 1 and 3, but not workspace 2), some applications require this information. In this case, it was Firefox which intersects the available workareas (see mozilla/gfx/src/gtk/nsScreenGtk.cpp) and did not position some windows correctly when being confronted with zero-width/height workspaces. --- src/ewmh.c | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/ewmh.c b/src/ewmh.c index 0d1e8a1b..6bfa3096 100644 --- a/src/ewmh.c +++ b/src/ewmh.c @@ -58,14 +58,41 @@ void ewmh_update_active_window(xcb_window_t window) { void ewmh_update_workarea() { Workspace *ws; int num_workspaces = 0, count = 0; + Rect last_rect = {0, 0, 0, 0}; + /* Get the number of workspaces */ - TAILQ_FOREACH(ws, workspaces, workspaces) + TAILQ_FOREACH(ws, workspaces, workspaces) { + /* Check if we need to initialize last_rect. The case that the + * first workspace is all-zero may happen when the user + * assigned workspace 2 for his first screen, for example. Thus + * we need an initialized last_rect in the very first run of + * the following loop. */ + if (last_rect.width == 0 && last_rect.height == 0 && + ws->rect.width != 0 && ws->rect.height != 0) { + memcpy(&last_rect, &(ws->rect), sizeof(Rect)); + } num_workspaces++; + } + DLOG("Got %d workspaces\n", num_workspaces); uint8_t *workarea = smalloc(sizeof(Rect) * num_workspaces); TAILQ_FOREACH(ws, workspaces, workspaces) { - DLOG("storing %d: %dx%d with %d x %d\n", count, ws->rect.x, ws->rect.y, ws->rect.width, ws->rect.height); + DLOG("storing %d: %dx%d with %d x %d\n", count, ws->rect.x, + ws->rect.y, ws->rect.width, ws->rect.height); + /* If a workspace is not yet initialized and thus its + * dimensions are zero, we will instead put the dimensions + * of the last workspace in the list. For example firefox + * intersects all workspaces and does not cope so well with + * an all-zero workspace. */ + if (ws->rect.width == 0 || ws->rect.height == 0) { + DLOG("re-using last_rect (%dx%d, %d, %d)\n", + last_rect.x, last_rect.y, last_rect.width, + last_rect.height); + memcpy(workarea + (sizeof(Rect) * count++), &last_rect, sizeof(Rect)); + continue; + } memcpy(workarea + (sizeof(Rect) * count++), &(ws->rect), sizeof(Rect)); + memcpy(&last_rect, &(ws->rect), sizeof(Rect)); } xcb_change_property(global_conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WORKAREA], CARDINAL, 32, From 614b360bd4e49cb9f8639e35f01534c619c89304 Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Sun, 3 Jan 2010 14:52:38 +0100 Subject: [PATCH 072/247] added popup for handling SIGSEGV or SIGFPE the popup is placed on each of the virtual screens the user can decide to restart or quit i3 in case of an exit a core-dump is generated --- include/sighandler.h | 21 +++++ include/util.h | 7 ++ src/commands.c | 31 +------ src/mainx.c | 2 + src/sighandler.c | 215 +++++++++++++++++++++++++++++++++++++++++++ src/util.c | 69 ++++++++++---- 6 files changed, 300 insertions(+), 45 deletions(-) create mode 100644 include/sighandler.h create mode 100644 src/sighandler.c diff --git a/include/sighandler.h b/include/sighandler.h new file mode 100644 index 00000000..cbb72cdd --- /dev/null +++ b/include/sighandler.h @@ -0,0 +1,21 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * © 2009 Jan-Erik Rediger + * + * See file LICENSE for license information. + * + */ +#ifndef _SIGHANDLER_H +#define _SIGHANDLER_H + +/* + * Setup signal handlers to safely handle SIGSEGV and SIGFPE + * + */ +void setup_signal_handler(); + +#endif diff --git a/include/util.h b/include/util.h index f4e95604..e45fc75a 100644 --- a/include/util.h +++ b/include/util.h @@ -150,6 +150,13 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode); Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitle, Client *specific); +/* + * Restart i3 in-place + * appends -a to argument list to disable autostart + * + */ +void i3_restart(); + #if defined(__OpenBSD__) /* OpenBSD does not provide memmem(), so we provide FreeBSD’s implementation */ void *memmem(const void *l, size_t l_len, const void *s, size_t s_len); diff --git a/src/commands.c b/src/commands.c index 31564bb9..92b4aa8c 100644 --- a/src/commands.c +++ b/src/commands.c @@ -31,6 +31,7 @@ #include "commands.h" #include "resize.h" #include "log.h" +#include "sighandler.h" bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) { /* If this container is empty, we’re done */ @@ -759,29 +760,6 @@ static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) { } } -/* - * Goes through the list of arguments (for exec()) and checks if the given argument - * is present. If not, it copies the arguments (because we cannot realloc it) and - * appends the given argument. - * - */ -static char **append_argument(char **original, char *argument) { - int num_args; - for (num_args = 0; original[num_args] != NULL; num_args++) { - DLOG("original argument: \"%s\"\n", original[num_args]); - /* If the argument is already present we return the original pointer */ - if (strcmp(original[num_args], argument) == 0) - return original; - } - /* Copy the original array */ - char **result = smalloc((num_args+2) * sizeof(char*)); - memcpy(result, original, num_args * sizeof(char*)); - result[num_args] = argument; - result[num_args+1] = NULL; - - return result; -} - /* * Switch to next or previous existing workspace * @@ -965,12 +943,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { /* Is it ? Then restart in place. */ if (STARTS_WITH(command, "restart")) { - LOG("restarting \"%s\"...\n", start_argv[0]); - /* make sure -a is in the argument list or append it */ - start_argv = append_argument(start_argv, "-a"); - - execvp(start_argv[0], start_argv); - /* not reached */ + i3_restart(); } if (STARTS_WITH(command, "kill")) { diff --git a/src/mainx.c b/src/mainx.c index 3ff8c022..61e71613 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -49,6 +49,7 @@ #include "manage.h" #include "ipc.h" #include "log.h" +#include "sighandler.h" xcb_connection_t *global_conn; @@ -499,6 +500,7 @@ int main(int argc, char *argv[], char *env[]) { /* Handle the events which arrived until now */ xcb_check_cb(NULL, NULL, 0); + setup_signal_handler(); /* Ungrab the server to receive events and enter libev’s eventloop */ xcb_ungrab_server(conn); ev_loop(loop, 0); diff --git a/src/sighandler.c b/src/sighandler.c new file mode 100644 index 00000000..9eeb2398 --- /dev/null +++ b/src/sighandler.c @@ -0,0 +1,215 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009 Michael Stapelberg and contributors + * © 2009 Jan-Erik Rediger + * + * See file LICENSE for license information. + * + * sighandler.c: contains all functions for signal handling + * + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "i3.h" +#include "util.h" +#include "xcb.h" +#include "log.h" +#include "config.h" +#include "xinerama.h" + +static xcb_gcontext_t pixmap_gc; +static xcb_pixmap_t pixmap; +static int raised_signal; + +static char *crash_text[] = { + "i3 just crashed.", + "To debug this problem, either attach gdb now", + "or press 'e' to exit and get a core-dump.", + "If you want to keep your session,", + "press 'r' to restart i3 in-place." +}; +static int crash_text_longest = 1; + +/* + * Draw the window containing the info text + * + */ +static int sig_draw_window(xcb_connection_t *conn, xcb_window_t win, int width, int height, int font_height) { + /* re-draw the background */ + xcb_rectangle_t border = {0, 0, width, height}, inner = {2, 2, width-4, height-4}; + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000")); + xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border); + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); + xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner); + + /* restore font color */ + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF")); + + char *full_text; + int text_len; + int i; + int crash_text_num = sizeof(crash_text) / sizeof(char*); + for (i = 0; i < crash_text_num; i++) { + text_len = strlen(crash_text[i]); + full_text = convert_utf8_to_ucs2(crash_text[i], &text_len); + xcb_image_text_16(conn, text_len, pixmap, pixmap_gc, 8 /* X */, + 3 + (i+1)*font_height /* Y = baseline of font */, (xcb_char2b_t*)full_text); + free(full_text); + } + + /* Copy the contents of the pixmap to the real window */ + xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, width, height); + xcb_flush(conn); + + return 1; +} + + +/* + * Handles keypresses of 'e' or 'r' to exit or restart i3 + * + */ +static int sig_handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) { + xcb_keysym_t sym = xcb_key_press_lookup_keysym(keysyms, event, event->state); + + if (sym == 'e') { + LOG("User issued exit-command, raising error again.\n"); + raise(raised_signal); + exit(1); + } + if (sym == 'r') + i3_restart(); + + return 1; +} + +/* + * Opens the window we use for input/output and maps it + * + */ +static xcb_window_t open_input_window(xcb_connection_t *conn, Rect screen_rect, uint32_t width, uint32_t height) { + xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; + xcb_window_t win = xcb_generate_id(conn); + + uint32_t mask = 0; + uint32_t values[3]; + + mask |= XCB_CW_BACK_PIXEL; + values[0] = 0; + + mask |= XCB_CW_OVERRIDE_REDIRECT; + values[1] = 1; + + mask |= XCB_CW_EVENT_MASK; + values[2] = XCB_EVENT_MASK_EXPOSURE; + + /* center each popup on the specified screen */ + uint32_t x = screen_rect.x + ((screen_rect.width / 2) - (width/2)), + y = screen_rect.y + ((screen_rect.height / 2) - (height/2)); + + xcb_create_window(conn, + XCB_COPY_FROM_PARENT, + win, /* the window id */ + root, /* parent == root */ + x, y, width, height, /* dimensions */ + 0, /* border = 0, we draw our own */ + XCB_WINDOW_CLASS_INPUT_OUTPUT, + XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */ + mask, + values); + + /* Map the window (= make it visible) */ + xcb_map_window(conn, win); + + return win; +} + +/* + * Handle signals + * It creates a window asking the user to restart in-place + * or exit to generate a core dump + * + */ +void handle_signal(int sig, siginfo_t *info, void *data) { + LOG("i3 crashed. SIG: %d\n", sig); + + struct sigaction action; + action.sa_handler = SIG_DFL; + sigaction(sig, &action, NULL); + raised_signal = sig; + + xcb_connection_t *conn = global_conn; + + /* Set up event handlers for key press and key release */ + xcb_event_handlers_t sig_evenths; + memset(&sig_evenths, 0, sizeof(xcb_event_handlers_t)); + xcb_event_handlers_init(conn, &sig_evenths); + xcb_event_set_key_press_handler(&sig_evenths, sig_handle_key_press, NULL); + + i3Font *font = load_font(conn, config.font); + + /* width and height of the popup window, so that the text fits in */ + int crash_text_num = sizeof(crash_text) / sizeof(char*); + int height = 13 + (crash_text_num * font->height); + + /* calculate width for longest text */ + int text_len = strlen(crash_text[crash_text_longest]); + char *longest_text = convert_utf8_to_ucs2(crash_text[crash_text_longest], &text_len); + int font_width = predict_text_width(conn, config.font, longest_text, text_len); + int width = font_width + 20; + + /* Open an popup window on each virtual screen */ + i3Screen *screen; + xcb_window_t win; + TAILQ_FOREACH(screen, virtual_screens, screens) { + LOG("%d, %d, %d, %d\n", screen->rect.width, screen->rect.height, screen->rect.x, screen->rect.y); + + win = open_input_window(conn, screen->rect, width, height); + + /* Create pixmap */ + pixmap = xcb_generate_id(conn); + pixmap_gc = xcb_generate_id(conn); + xcb_create_pixmap(conn, root_depth, pixmap, win, width, height); + xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); + + /* Create graphics context */ + xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font->id); + + /* Grab the keyboard to get all input */ + xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); + + sig_draw_window(conn, win, width, height, font->height); + xcb_flush(conn); + } + + xcb_event_wait_for_event_loop(&sig_evenths); +} + +/* + * Setup signal handlers to safely handle SIGSEGV and SIGFPE + * + */ +void setup_signal_handler() { + struct sigaction action; + action.sa_sigaction = handle_signal; + action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO; + sigemptyset(&action.sa_mask); + sigaction(SIGSEGV, &action, NULL); + sigaction(SIGFPE, &action, NULL); +} diff --git a/src/util.c b/src/util.c index bc54caa5..ba4bd6dd 100644 --- a/src/util.c +++ b/src/util.c @@ -159,16 +159,16 @@ void check_error(xcb_connection_t *conn, xcb_void_cookie_t cookie, char *err_mes * */ char *convert_utf8_to_ucs2(char *input, int *real_strlen) { - size_t input_size = strlen(input) + 1; - /* UCS-2 consumes exactly two bytes for each glyph */ - int buffer_size = input_size * 2; + size_t input_size = strlen(input) + 1; + /* UCS-2 consumes exactly two bytes for each glyph */ + int buffer_size = input_size * 2; - char *buffer = smalloc(buffer_size); - size_t output_size = buffer_size; - /* We need to use an additional pointer, because iconv() modifies it */ - char *output = buffer; + char *buffer = smalloc(buffer_size); + size_t output_size = buffer_size; + /* We need to use an additional pointer, because iconv() modifies it */ + char *output = buffer; - /* We convert the input into UCS-2 big endian */ + /* We convert the input into UCS-2 big endian */ if (conversion_descriptor == 0) { conversion_descriptor = iconv_open("UCS-2BE", "UTF-8"); if (conversion_descriptor == 0) { @@ -177,22 +177,22 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen) { } } - /* Get the conversion descriptor back to original state */ - iconv(conversion_descriptor, NULL, NULL, NULL, NULL); + /* Get the conversion descriptor back to original state */ + iconv(conversion_descriptor, NULL, NULL, NULL, NULL); - /* Convert our text */ - int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size); + /* Convert our text */ + int rc = iconv(conversion_descriptor, (void*)&input, &input_size, &output, &output_size); if (rc == (size_t)-1) { perror("Converting to UCS-2 failed"); if (real_strlen != NULL) - *real_strlen = 0; + *real_strlen = 0; return NULL; - } + } if (real_strlen != NULL) - *real_strlen = ((buffer_size - output_size) / 2) - 1; + *real_strlen = ((buffer_size - output_size) / 2) - 1; - return buffer; + return buffer; } /* @@ -463,6 +463,43 @@ done: return matching; } +/* + * Goes through the list of arguments (for exec()) and checks if the given argument + * is present. If not, it copies the arguments (because we cannot realloc it) and + * appends the given argument. + * + */ +static char **append_argument(char **original, char *argument) { + int num_args; + for (num_args = 0; original[num_args] != NULL; num_args++) { + DLOG("original argument: \"%s\"\n", original[num_args]); + /* If the argument is already present we return the original pointer */ + if (strcmp(original[num_args], argument) == 0) + return original; + } + /* Copy the original array */ + char **result = smalloc((num_args+2) * sizeof(char*)); + memcpy(result, original, num_args * sizeof(char*)); + result[num_args] = argument; + result[num_args+1] = NULL; + + return result; +} + +/* + * Restart i3 in-place + * appends -a to argument list to disable autostart + * + */ +void i3_restart() { + LOG("restarting \"%s\"...\n", start_argv[0]); + /* make sure -a is in the argument list or append it */ + start_argv = append_argument(start_argv, "-a"); + + execvp(start_argv[0], start_argv); + /* not reached */ +} + #if defined(__OpenBSD__) /* From 8afdf540781fbe54c90abe7cc583857738fc7f43 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 3 Jan 2010 21:53:30 +0100 Subject: [PATCH 073/247] Update copyright to 2010 --- include/sighandler.h | 4 ++-- src/sighandler.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/sighandler.h b/include/sighandler.h index cbb72cdd..08e89599 100644 --- a/include/sighandler.h +++ b/include/sighandler.h @@ -3,8 +3,8 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors - * © 2009 Jan-Erik Rediger + * © 2009-2010 Michael Stapelberg and contributors + * © 2009-2010 Jan-Erik Rediger * * See file LICENSE for license information. * diff --git a/src/sighandler.c b/src/sighandler.c index 9eeb2398..0221cfa6 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -3,8 +3,8 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors - * © 2009 Jan-Erik Rediger + * © 2009-2010 Michael Stapelberg and contributors + * © 2009-2010 Jan-Erik Rediger * * See file LICENSE for license information. * From fc4ce84d3685754c76538391519f4a93f2cd9906 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 3 Jan 2010 21:53:42 +0100 Subject: [PATCH 074/247] Use doxygen compatible comments --- include/sighandler.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/sighandler.h b/include/sighandler.h index 08e89599..49317438 100644 --- a/include/sighandler.h +++ b/include/sighandler.h @@ -12,7 +12,7 @@ #ifndef _SIGHANDLER_H #define _SIGHANDLER_H -/* +/** * Setup signal handlers to safely handle SIGSEGV and SIGFPE * */ From 0c35573e51f35b67506ea59ad67dabd6e6b5b407 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 3 Jan 2010 21:54:26 +0100 Subject: [PATCH 075/247] some little style changes --- src/sighandler.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/sighandler.c b/src/sighandler.c index 0221cfa6..4b784041 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -52,7 +52,8 @@ static int crash_text_longest = 1; */ static int sig_draw_window(xcb_connection_t *conn, xcb_window_t win, int width, int height, int font_height) { /* re-draw the background */ - xcb_rectangle_t border = {0, 0, width, height}, inner = {2, 2, width-4, height-4}; + xcb_rectangle_t border = { 0, 0, width, height}, + inner = { 2, 2, width - 4, height - 4}; xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000")); xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border); xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); @@ -61,15 +62,12 @@ static int sig_draw_window(xcb_connection_t *conn, xcb_window_t win, int width, /* restore font color */ xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF")); - char *full_text; - int text_len; - int i; - int crash_text_num = sizeof(crash_text) / sizeof(char*); - for (i = 0; i < crash_text_num; i++) { - text_len = strlen(crash_text[i]); - full_text = convert_utf8_to_ucs2(crash_text[i], &text_len); + for (int i = 0; i < sizeof(crash_text) / sizeof(char*); i++) { + int text_len = strlen(crash_text[i]); + char *full_text = convert_utf8_to_ucs2(crash_text[i], &text_len); xcb_image_text_16(conn, text_len, pixmap, pixmap_gc, 8 /* X */, - 3 + (i+1)*font_height /* Y = baseline of font */, (xcb_char2b_t*)full_text); + 3 + (i + 1) * font_height /* Y = baseline of font */, + (xcb_char2b_t*)full_text); free(full_text); } @@ -93,6 +91,7 @@ static int sig_handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_p raise(raised_signal); exit(1); } + if (sym == 'r') i3_restart(); @@ -156,7 +155,7 @@ void handle_signal(int sig, siginfo_t *info, void *data) { xcb_connection_t *conn = global_conn; - /* Set up event handlers for key press and key release */ + /* setup event handler for key presses */ xcb_event_handlers_t sig_evenths; memset(&sig_evenths, 0, sizeof(xcb_event_handlers_t)); xcb_event_handlers_init(conn, &sig_evenths); @@ -174,7 +173,7 @@ void handle_signal(int sig, siginfo_t *info, void *data) { int font_width = predict_text_width(conn, config.font, longest_text, text_len); int width = font_width + 20; - /* Open an popup window on each virtual screen */ + /* Open a popup window on each virtual screen */ i3Screen *screen; xcb_window_t win; TAILQ_FOREACH(screen, virtual_screens, screens) { @@ -207,6 +206,7 @@ void handle_signal(int sig, siginfo_t *info, void *data) { */ void setup_signal_handler() { struct sigaction action; + action.sa_sigaction = handle_signal; action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO; sigemptyset(&action.sa_mask); From 7b2363776ae6edad0e2a0294c5cc5318c72c7376 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 3 Jan 2010 21:54:47 +0100 Subject: [PATCH 076/247] Use DLOG instead of LOG, remove unnecessary debug statement --- src/sighandler.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/sighandler.c b/src/sighandler.c index 4b784041..5518daf1 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -87,7 +87,7 @@ static int sig_handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_p xcb_keysym_t sym = xcb_key_press_lookup_keysym(keysyms, event, event->state); if (sym == 'e') { - LOG("User issued exit-command, raising error again.\n"); + DLOG("User issued exit-command, raising error again.\n"); raise(raised_signal); exit(1); } @@ -146,7 +146,7 @@ static xcb_window_t open_input_window(xcb_connection_t *conn, Rect screen_rect, * */ void handle_signal(int sig, siginfo_t *info, void *data) { - LOG("i3 crashed. SIG: %d\n", sig); + DLOG("i3 crashed. SIG: %d\n", sig); struct sigaction action; action.sa_handler = SIG_DFL; @@ -177,8 +177,6 @@ void handle_signal(int sig, siginfo_t *info, void *data) { i3Screen *screen; xcb_window_t win; TAILQ_FOREACH(screen, virtual_screens, screens) { - LOG("%d, %d, %d, %d\n", screen->rect.width, screen->rect.height, screen->rect.x, screen->rect.y); - win = open_input_window(conn, screen->rect, width, height); /* Create pixmap */ From a69dfb3f0613d460051c60c8c003eb67c929baa3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 3 Jan 2010 21:55:02 +0100 Subject: [PATCH 077/247] Remove expose event mask, expose events will not be generated in any case --- src/sighandler.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/sighandler.c b/src/sighandler.c index 5518daf1..8bfec493 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -107,7 +107,7 @@ static xcb_window_t open_input_window(xcb_connection_t *conn, Rect screen_rect, xcb_window_t win = xcb_generate_id(conn); uint32_t mask = 0; - uint32_t values[3]; + uint32_t values[2]; mask |= XCB_CW_BACK_PIXEL; values[0] = 0; @@ -115,9 +115,6 @@ static xcb_window_t open_input_window(xcb_connection_t *conn, Rect screen_rect, mask |= XCB_CW_OVERRIDE_REDIRECT; values[1] = 1; - mask |= XCB_CW_EVENT_MASK; - values[2] = XCB_EVENT_MASK_EXPOSURE; - /* center each popup on the specified screen */ uint32_t x = screen_rect.x + ((screen_rect.width / 2) - (width/2)), y = screen_rect.y + ((screen_rect.height / 2) - (height/2)); From be33c8e599cab989be9b2041106625aa7eacef96 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 3 Jan 2010 21:55:22 +0100 Subject: [PATCH 078/247] Check return code of sigaction() --- src/sighandler.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sighandler.c b/src/sighandler.c index 8bfec493..dc551ffc 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -205,6 +205,8 @@ void setup_signal_handler() { action.sa_sigaction = handle_signal; action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO; sigemptyset(&action.sa_mask); - sigaction(SIGSEGV, &action, NULL); - sigaction(SIGFPE, &action, NULL); + + if (sigaction(SIGSEGV, &action, NULL) == -1 || + sigaction(SIGFPE, &action, NULL) == -1) + ELOG("Could not setup signal handler"); } From 2b1a132c39d0ed16ce52fc683289377ee86ef8c4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 3 Jan 2010 22:02:07 +0100 Subject: [PATCH 079/247] grab the pointer inside the signal handler popup --- src/sighandler.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sighandler.c b/src/sighandler.c index dc551ffc..61a3e129 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -188,6 +188,10 @@ void handle_signal(int sig, siginfo_t *info, void *data) { /* Grab the keyboard to get all input */ xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); + /* Grab the cursor inside the popup */ + xcb_grab_pointer(conn, false, win, XCB_NONE, XCB_GRAB_MODE_ASYNC, + XCB_GRAB_MODE_ASYNC, win, XCB_NONE, XCB_CURRENT_TIME); + sig_draw_window(conn, win, width, height, font->height); xcb_flush(conn); } From 44191d5ffca96f1b6a5c4234c2b03785d9b2ee06 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 3 Jan 2010 22:07:39 +0100 Subject: [PATCH 080/247] sighandler: last little style fixes --- src/sighandler.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/sighandler.c b/src/sighandler.c index 61a3e129..b013c60d 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -78,7 +78,6 @@ static int sig_draw_window(xcb_connection_t *conn, xcb_window_t win, int width, return 1; } - /* * Handles keypresses of 'e' or 'r' to exit or restart i3 * @@ -116,8 +115,8 @@ static xcb_window_t open_input_window(xcb_connection_t *conn, Rect screen_rect, values[1] = 1; /* center each popup on the specified screen */ - uint32_t x = screen_rect.x + ((screen_rect.width / 2) - (width/2)), - y = screen_rect.y + ((screen_rect.height / 2) - (height/2)); + uint32_t x = screen_rect.x + ((screen_rect.width / 2) - (width / 2)), + y = screen_rect.y + ((screen_rect.height / 2) - (height / 2)); xcb_create_window(conn, XCB_COPY_FROM_PARENT, From b6de5654454621e0d94e8f66b609b43b2ae7758f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 7 Jan 2010 13:36:52 +0100 Subject: [PATCH 081/247] Bugfix: Use ev_loop_new instead of ev_default_loop because the latter one blocks SIGCHLD (Thanks Ciprian) SIGCHLD was inherited to child processes started by i3 and not all of them unblocked it (shells generally did, though), leading to zombie processes. --- src/mainx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mainx.c b/src/mainx.c index 61e71613..389fc76c 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -285,7 +285,7 @@ int main(int argc, char *argv[], char *env[]) { } /* Initialize event loop using libev */ - struct ev_loop *loop = ev_default_loop(0); + struct ev_loop *loop = ev_loop_new(0); if (loop == NULL) die("Could not initialize libev. Bad LIBEV_FLAGS?\n"); From af0b33bcc377a35274ccb4ab4aef90bb68486a34 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 7 Jan 2010 14:38:00 +0100 Subject: [PATCH 082/247] Bugfix: if a font provides no per-char info for width, fall back to the default (Thanks Ciprian) --- src/xcb.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/xcb.c b/src/xcb.c index 07286d0f..5f3b22ab 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -345,10 +345,25 @@ static xcb_charinfo_t *get_charinfo(int col, int row, xcb_query_font_reply_t *fo 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; + xcb_generic_error_t *error; 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); + font_info = xcb_query_font_reply(conn, xcb_query_font(conn, font->id), &error); + if (error != NULL) { + fprintf(stderr, "ERROR: query font (X error code %d)\n", error->error_code); + /* We return the rather safe guess of 7 pixels, because a + * rendering error is better than a crash. Plus, the user will + * see the error on his stderr. */ + return 7; + } + + /* If no per-char info is available for this font, we use the default */ + if (xcb_query_font_char_infos_length(font_info) == 0) { + DLOG("Falling back on default char_width of %d pixels\n", font_info->max_bounds.character_width); + return (font_info->max_bounds.character_width * length); + } + table = xcb_query_font_char_infos(font_info); for (i = 0; i < 2 * length; i += 2) { From d949a335e6cefdebc2c91f250d3a7d235889d2ee Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 8 Jan 2010 18:56:01 +0100 Subject: [PATCH 083/247] bugfix: lexer: return to INITIAL state (Thanks dirkson) 'floating_modifier' after 'workspace' did not work because of wrong state --- src/cfgparse.l | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfgparse.l b/src/cfgparse.l index 0787f193..ac862a2e 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -33,7 +33,7 @@ mode { return TOKMODE; } bind { BEGIN(BIND_COND); return TOKBIND; } bindsym { BEGIN(BINDSYM_COND); return TOKBINDSYM; } -floating_modifier { return TOKFLOATING_MODIFIER; } +floating_modifier { BEGIN(INITIAL); return TOKFLOATING_MODIFIER; } workspace { BEGIN(INITIAL); return TOKWORKSPACE; } screen { BEGIN(SCREEN_COND); return TOKSCREEN; } terminal { BEGIN(BIND_AWS_COND); return TOKTERMINAL; } From c606d93630c46a030a7f0046319dd649e7cffe57 Mon Sep 17 00:00:00 2001 From: Cedric Staub Date: Fri, 15 Jan 2010 16:30:28 +0100 Subject: [PATCH 084/247] Feature: Cycle through workspaces On command pw/nw, cycle through all workspaces (starting from previous/next one) until we reach the current one again. --- src/commands.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/commands.c b/src/commands.c index 92b4aa8c..8205d224 100644 --- a/src/commands.c +++ b/src/commands.c @@ -768,7 +768,15 @@ static void next_previous_workspace(xcb_connection_t *conn, int direction) { Workspace *ws = c_ws; if (direction == 'n') { - while ((ws = TAILQ_NEXT(ws, workspaces)) != TAILQ_END(workspaces_head)) { + while (1) { + ws = TAILQ_NEXT(ws, workspaces); + + if (ws == TAILQ_END(workspaces)) + ws = TAILQ_FIRST(workspaces); + + if (ws == c_ws) + return; + if (ws->screen == NULL) continue; @@ -776,7 +784,15 @@ static void next_previous_workspace(xcb_connection_t *conn, int direction) { return; } } else if (direction == 'p') { - while ((ws = TAILQ_PREV(ws, workspaces_head, workspaces)) != TAILQ_END(workspaces)) { + while (1) { + ws = TAILQ_PREV(ws, workspaces_head, workspaces); + + if (ws == TAILQ_END(workspaces)) + ws = TAILQ_LAST(workspaces, workspaces_head); + + if (ws == c_ws) + return; + if (ws->screen == NULL) continue; From fbe8dafe63268f21130da06d605101e5fd3fc5b3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 20 Jan 2010 22:34:25 +0100 Subject: [PATCH 085/247] manpage: mention i3-msg and i3-input in SEE ALSO (Thanks artoj) --- man/i3.man | 2 ++ 1 file changed, 2 insertions(+) diff --git a/man/i3.man b/man/i3.man index 70333805..0b114da3 100644 --- a/man/i3.man +++ b/man/i3.man @@ -281,6 +281,8 @@ are building from source, run +make -C docs+. You can also access these documents online at http://i3.zekjur.net/ +i3-input(1), i3-msg(1) + == AUTHOR Michael Stapelberg and contributors From fde11f9a5c715c3deacd58f2263d39004ad771a9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 23 Jan 2010 00:34:29 +0100 Subject: [PATCH 086/247] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20leak=20IPC=20?= =?UTF-8?q?socket=20to=20launched=20processes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ipc.c b/src/ipc.c index 9e56fd0e..b4356fda 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -229,6 +229,8 @@ int ipc_create_socket(const char *filename) { return -1; } + (void)fcntl(sockfd, F_SETFD, FD_CLOEXEC); + struct sockaddr_un addr; memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_LOCAL; From 7270b747325e16d35ccd8bc2dacba82dc25ebb99 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 23 Jan 2010 18:57:29 +0100 Subject: [PATCH 087/247] Implement resize command for floating clients --- src/commands.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/commands.c b/src/commands.c index 8205d224..1bec7a84 100644 --- a/src/commands.c +++ b/src/commands.c @@ -806,7 +806,34 @@ static void parse_resize_command(xcb_connection_t *conn, Client *last_focused, c int first, second; resize_orientation_t orientation = O_VERTICAL; Container *con = last_focused->container; - Workspace *ws = con->workspace; + Workspace *ws = last_focused->workspace; + + if (client_is_floating(last_focused)) { + DLOG("Resizing a floating client\n"); + if (STARTS_WITH(command, "left")) { + command += strlen("left"); + last_focused->rect.width += atoi(command); + last_focused->rect.x -= atoi(command); + } else if (STARTS_WITH(command, "right")) { + command += strlen("right"); + last_focused->rect.width += atoi(command); + } else if (STARTS_WITH(command, "top")) { + command += strlen("top"); + last_focused->rect.height += atoi(command); + last_focused->rect.y -= atoi(command); + } else if (STARTS_WITH(command, "bottom")) { + command += strlen("bottom"); + last_focused->rect.height += atoi(command); + } else { + ELOG("Syntax: resize [+|-]\n"); + return; + } + + /* resize_client flushes */ + resize_client(conn, last_focused); + + return; + } if (STARTS_WITH(command, "left")) { if (con->col == 0) From 0abf9cc570d8a751566fbec8cd0ebb4f6f224138 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 23 Jan 2010 22:35:10 +0100 Subject: [PATCH 088/247] Include date of the last commit in version number (Thanks Scytale) --- common.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.mk b/common.mk index 70305148..775b592d 100644 --- a/common.mk +++ b/common.mk @@ -1,7 +1,7 @@ UNAME=$(shell uname) DEBUG=1 INSTALL=install -GIT_VERSION:=$(shell git describe --tags --always) +GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1))" VERSION:=$(shell git describe --tags --abbrev=0) CFLAGS += -std=c99 From 148547c765ff38caef25d8513ed507021dc9bfb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kim=20Silkeb=C3=A6kken=20=28lokaltog=29?= Date: Tue, 26 Jan 2010 11:05:59 +0100 Subject: [PATCH 089/247] Fixed cursor orientation when resizing --- src/resize.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/resize.c b/src/resize.c index db8e7442..db1ac073 100644 --- a/src/resize.c +++ b/src/resize.c @@ -93,8 +93,8 @@ 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), true, mask, values); + XCB_CURSOR_SB_H_DOUBLE_ARROW : + XCB_CURSOR_SB_V_DOUBLE_ARROW), true, mask, values); xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin); From 36909c6e3bb4a00d673075869768d8300b2d0617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kim=20Silkeb=C3=A6kken=20=28lokaltog=29?= Date: Tue, 26 Jan 2010 11:06:10 +0100 Subject: [PATCH 090/247] Fixed cursor code in create_window(), allowing the cursor to be changed --- src/xcb.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/xcb.c b/src/xcb.c index 5f3b22ab..f7835d3a 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -99,14 +99,6 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_cl /* If the window class is XCB_WINDOW_CLASS_INPUT_ONLY, depth has to be 0 */ uint16_t depth = (window_class == XCB_WINDOW_CLASS_INPUT_ONLY ? 0 : XCB_COPY_FROM_PARENT); - /* Use the default cursor (left pointer) */ - if (cursor > -1) { - i3Font *cursor_font = load_font(conn, "cursor"); - xcb_create_glyph_cursor(conn, cursor_id, cursor_font->id, cursor_font->id, - XCB_CURSOR_LEFT_PTR, XCB_CURSOR_LEFT_PTR + 1, - 0, 0, 0, 65535, 65535, 65535); - } - xcb_create_window(conn, depth, result, /* the window id */ @@ -118,8 +110,14 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, uint16_t window_cl mask, values); - if (cursor > -1) - xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id); + /* Set the cursor */ + i3Font *cursor_font = load_font(conn, "cursor"); + xcb_create_glyph_cursor(conn, cursor_id, cursor_font->id, cursor_font->id, + (cursor == -1 ? XCB_CURSOR_LEFT_PTR : cursor), + (cursor == -1 ? XCB_CURSOR_LEFT_PTR : cursor) + 1, + 0, 0, 0, 65535, 65535, 65535); + xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, &cursor_id); + xcb_free_cursor(conn, cursor_id); /* Map the window (= make it visible) */ if (map) From 7c7756d75f5c8c1739bba138be37b98f64681ca9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 26 Jan 2010 22:48:12 +0100 Subject: [PATCH 091/247] docs: improve documentation on modes (Thanks xeen) --- docs/userguide | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/userguide b/docs/userguide index 529478ba..10806030 100644 --- a/docs/userguide +++ b/docs/userguide @@ -546,8 +546,8 @@ bindsym Mod1+p pw === Resizing columns/rows If you want to resize columns/rows using your keyboard, you can use the -+resize+ command, I recommend using it a +mode+ (you need to use the new -lexer/parser for that, so pass +-l+ to i3 when starting): ++resize+ command, I recommend using it inside a so called +mode+ (you need to +use the new lexer/parser for that, so pass +-l+ to i3 when starting): .Example: Configuration file, defining a mode for resizing ---------------------------------------------------------------------- @@ -572,6 +572,9 @@ mode "resize" { bind 36 mode default } + +# Enter resize mode +bindsym Mod1+r mode resize ---------------------------------------------------------------------- === Jumping to specific windows From 7f10970fc74b5a428789ad376df6d6055bf43e7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kim=20Silkeb=C3=A6kken=20=28lokaltog=29?= Date: Tue, 26 Jan 2010 12:10:48 +0100 Subject: [PATCH 092/247] Added focus_follows_mouse config option --- include/config.h | 2 ++ src/cfgparse.l | 1 + src/cfgparse.y | 9 +++++++++ src/handlers.c | 3 ++- 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/include/config.h b/include/config.h index 3080c5c3..b81c3492 100644 --- a/include/config.h +++ b/include/config.h @@ -77,6 +77,8 @@ struct Config { int container_stack_limit; int container_stack_limit_value; + bool focus_follows_mouse; + const char *default_border; /** The modifier which needs to be pressed in combination with your mouse diff --git a/src/cfgparse.l b/src/cfgparse.l index ac862a2e..ce912549 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -44,6 +44,7 @@ ipc-socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; } ipc_socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; } new_container { return TOKNEWCONTAINER; } new_window { return TOKNEWWINDOW; } +focus_follows_mouse { return TOKFOCUSFOLLOWSMOUSE; } default { yylval.number = MODE_DEFAULT; return TOKCONTAINERMODE; } stacking { yylval.number = MODE_STACK; return TOKCONTAINERMODE; } tabbed { yylval.number = MODE_TABBED; return TOKCONTAINERMODE; } diff --git a/src/cfgparse.y b/src/cfgparse.y index 030c51af..cc1b2d16 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -197,6 +197,7 @@ void parse_file(const char *f) { %token TOKMODE %token TOKNEWCONTAINER %token TOKNEWWINDOW +%token TOKFOCUSFOLLOWSMOUSE %token TOKCONTAINERMODE %token TOKSTACKLIMIT @@ -213,6 +214,7 @@ line: | floating_modifier | new_container | new_window + | focus_follows_mouse | workspace | assign | ipcsocket @@ -379,6 +381,13 @@ new_window: } ; +focus_follows_mouse: + TOKFOCUSFOLLOWSMOUSE WHITESPACE NUMBER + { + config.focus_follows_mouse = ($3 == 0 ? 0 : 1); + } + ; + workspace: TOKWORKSPACE WHITESPACE NUMBER WHITESPACE TOKSCREEN WHITESPACE screen optional_workspace_name { diff --git a/src/handlers.c b/src/handlers.c index c9f95345..a0097bcd 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -236,7 +236,8 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_ return 1; } - set_focus(conn, client, false); + if (config.focus_follows_mouse) + set_focus(conn, client, false); return 1; } From 88b9700cdbf6029f90dd86d4d441a7e9c186e4bb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 29 Jan 2010 21:51:38 +0100 Subject: [PATCH 093/247] Invert logic for the last commit This makes it more clear that the option is meant to be a special case (it *disables* part of the focus handling). Also, when initializing the config data structure with zeros, it will get initialized with the right value. Furthermore, the config file parser now also accepts various values which represent "true", not only numbers. --- include/config.h | 6 +++++- src/cfgparse.y | 23 ++++++++++++++++++++--- src/handlers.c | 2 +- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/include/config.h b/include/config.h index b81c3492..fe5405bc 100644 --- a/include/config.h +++ b/include/config.h @@ -77,7 +77,11 @@ struct Config { int container_stack_limit; int container_stack_limit_value; - bool focus_follows_mouse; + /** By default, focus follows mouse. If the user explicitly wants to + * turn this off (and instead rely only on the keyboard for changing + * focus), we allow him to do this with this relatively special option. + * It is not planned to add any different focus models. */ + bool disable_focus_follows_mouse; const char *default_border; diff --git a/src/cfgparse.y b/src/cfgparse.y index cc1b2d16..6b458e2c 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -381,10 +381,27 @@ new_window: } ; -focus_follows_mouse: - TOKFOCUSFOLLOWSMOUSE WHITESPACE NUMBER +bool: + NUMBER { - config.focus_follows_mouse = ($3 == 0 ? 0 : 1); + $$ = ($1 == 1); + } + | WORD + { + DLOG("checking word \"%s\"\n", $1); + $$ = (strcasecmp($1, "yes") == 0 || + strcasecmp($1, "true") == 0 || + strcasecmp($1, "on") == 0 || + strcasecmp($1, "enable") == 0 || + strcasecmp($1, "active") == 0); + } + ; + +focus_follows_mouse: + TOKFOCUSFOLLOWSMOUSE WHITESPACE bool + { + DLOG("focus follows mouse = %d\n", $3); + config.disable_focus_follows_mouse = !($3); } ; diff --git a/src/handlers.c b/src/handlers.c index a0097bcd..6e54b04b 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -236,7 +236,7 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_ return 1; } - if (config.focus_follows_mouse) + if (!config.disable_focus_follows_mouse) set_focus(conn, client, false); return 1; From 69279d45679c4d217849ab067be7f2111f813aa5 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 29 Jan 2010 21:58:28 +0100 Subject: [PATCH 094/247] Add documentation for the new focus follows mouse-option --- docs/userguide | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/userguide b/docs/userguide index 10806030..63f8e8ac 100644 --- a/docs/userguide +++ b/docs/userguide @@ -462,6 +462,24 @@ ipc-socket /tmp/i3-ipc.sock You can then use the i3-msg command to perform any command listed in the next section. +=== Disable focus follows mouse + +If you have a setup where your mouse usually is in your way (like a touchpad +on your laptop which you do not want to disable completely), you might want +to disable focus follows mouse and control focus only by using your keyboard. +The mouse will still be useful inside the currently active window (for example +to click on links in your browser window). + +*Syntax*: +---------------------------- +focus_follows_mouse +---------------------------- + +*Examples*: +---------------------- +focus_follows_mouse no +---------------------- + == List of commands === Manipulating layout From 9568836e12ee69cedc9ab341a5a326875d995439 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 5 Feb 2010 16:11:15 +0100 Subject: [PATCH 095/247] Remove duplicate entry (Thanks Merovius) --- docs/hacking-howto | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/hacking-howto b/docs/hacking-howto index b66f7d62..c9da4e43 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -162,9 +162,6 @@ reparents the window and inserts it into our data structures. src/resize.c:: Contains the functions to resize columns/rows in the table. -src/resize.c:: -Contains the functions to resize columns/rows in the table. - src/table.c:: Manages the most important internal data structure, the design table. From 0b6b8e8380b8026ba834436f4ae4de47ff07feda Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Feb 2010 12:12:25 +0100 Subject: [PATCH 096/247] Bugfix: Use both parts of WM_CLASS (it contains instance and class) (Thanks fallen) Actually, WM_CLASS contains two null-terminated strings, so we cannot use asprintf() to get its value but rather use strdup() to get both of them. Both values are compared when a client is matched against a wm_class/title combination (for assignments for example). --- include/data.h | 6 ++++-- src/client.c | 5 ++++- src/handlers.c | 34 +++++++++++++++------------------- src/util.c | 3 ++- 4 files changed, 25 insertions(+), 23 deletions(-) diff --git a/include/data.h b/include/data.h index 8c846537..47343d19 100644 --- a/include/data.h +++ b/include/data.h @@ -382,8 +382,10 @@ struct Client { * in. If set to true, legacy window names are ignored. */ bool uses_net_wm_name; - /** Holds the WM_CLASS, useful for matching the client in commands */ - char *window_class; + /** Holds the WM_CLASS (which consists of two strings, the instance + * and the class), useful for matching the client in commands */ + char *window_class_instance; + char *window_class_class; /** Holds the client’s mark, for vim-like jumping */ char *mark; diff --git a/src/client.c b/src/client.c index c3113391..3b747f54 100644 --- a/src/client.c +++ b/src/client.c @@ -124,7 +124,10 @@ void client_kill(xcb_connection_t *conn, Client *window) { bool client_matches_class_name(Client *client, char *to_class, char *to_title, char *to_title_ucs, int to_title_ucs_len) { /* Check if the given class is part of the window class */ - if (client->window_class == NULL || strcasestr(client->window_class, to_class) == NULL) + if ((client->window_class_instance == NULL || + strcasestr(client->window_class_instance, to_class) == NULL) && + (client->window_class_class == NULL || + strcasestr(client->window_class_class, to_class) == NULL)) return false; /* If no title was given, we’re done */ diff --git a/src/handlers.c b/src/handlers.c index 6e54b04b..0f550835 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -539,7 +539,8 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti client->urgent = false; workspace_update_urgent_flag(client->workspace); - FREE(client->window_class); + FREE(client->window_class_instance); + FREE(client->window_class_class); FREE(client->name); free(client); @@ -696,28 +697,23 @@ int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state, Client *client = table_get(&by_child, window); if (client == NULL) return 1; - char *new_class; - if (asprintf(&new_class, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)) == -1) { - perror("Could not get window class"); - DLOG("Could not get window class\n"); - return 1; - } - LOG("WM_CLASS changed to %s\n", new_class); - char *old_class = client->window_class; - client->window_class = new_class; - FREE(old_class); + /* We cannot use asprintf here since this property contains two + * null-terminated strings (for compatibility reasons). Instead, we + * use strdup() on both strings */ + char *new_class = xcb_get_property_value(prop); - if (!client->initialized) - return 1; + FREE(client->window_class_instance); + FREE(client->window_class_class); - if (strcmp(new_class, "tools") == 0 || strcmp(new_class, "Dialog") == 0) { - DLOG("tool/dialog window, should we put it floating?\n"); - if (client->floating == FLOATING_AUTO_OFF) - toggle_floating_mode(conn, client, true); - } + client->window_class_instance = strdup(new_class); + if ((strlen(new_class) + 1) < xcb_get_property_value_length(prop)) + client->window_class_class = strdup(new_class + strlen(new_class) + 1); + else client->window_class_class = NULL; + LOG("WM_CLASS changed to %s (instance), %s (class)\n", + client->window_class_instance, client->window_class_class); - return 1; + return 0; } /* diff --git a/src/util.c b/src/util.c index ba4bd6dd..1cdc758a 100644 --- a/src/util.c +++ b/src/util.c @@ -448,7 +448,8 @@ Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitl Client *client; SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) { - DLOG("Checking client with class=%s, name=%s\n", client->window_class, client->name); + DLOG("Checking client with class=%s / %s, name=%s\n", client->window_class_instance, + client->window_class_class, client->name); if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len)) continue; From 87f8c501da5c32648b2ff48b6201a03d667d17a1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Feb 2010 13:06:59 +0100 Subject: [PATCH 097/247] Bugfix: Correctly do boundary checking/moving to other workspaces when moving floating clients via keyboard (Thanks sasha) --- include/data.h | 6 ++++++ src/floating.c | 25 +++++++++++++++++-------- src/layout.c | 6 +++++- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/include/data.h b/include/data.h index 47343d19..31cef493 100644 --- a/include/data.h +++ b/include/data.h @@ -78,6 +78,12 @@ enum { * It needs to be packed so that the compiler will not add any padding bytes. * (it is used in src/ewmh.c for example) * + * Note that x and y can contain signed values in some cases (for example when + * used for the coordinates of a window, which can be set outside of the + * visible area, but not when specifying the position of a workspace for the + * _NET_WM_WORKAREA hint). Not declaring x/y as int32_t saves us a lot of + * typecasts. + * */ struct Rect { uint32_t x; diff --git a/src/floating.c b/src/floating.c index 1e7a4fac..ca294cc6 100644 --- a/src/floating.c +++ b/src/floating.c @@ -411,28 +411,37 @@ void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_t direction) { DLOG("floating move\n"); + Rect destination = currently_focused->rect; + Rect *screen = &(currently_focused->workspace->screen->rect); + switch (direction) { case D_LEFT: - if (currently_focused->rect.x < 10) - return; - currently_focused->rect.x -= 10; + destination.x -= 10; break; case D_RIGHT: - currently_focused->rect.x += 10; + destination.x += 10; break; case D_UP: - if (currently_focused->rect.y < 10) - return; - currently_focused->rect.y -= 10; + destination.y -= 10; break; case D_DOWN: - currently_focused->rect.y += 10; + destination.y += 10; break; /* to make static analyzers happy */ default: break; } + /* Prevent windows from vanishing completely */ + if ((int32_t)(destination.x + destination.width - 5) <= (int32_t)screen->x || + (int32_t)(destination.x + 5) >= (int32_t)(screen->x + screen->width) || + (int32_t)(destination.y + destination.height - 5) <= (int32_t)screen->y || + (int32_t)(destination.y + 5) >= (int32_t)(screen->y + screen->height)) { + DLOG("boundary check failed, not moving\n"); + return; + } + + currently_focused->rect = destination; reposition_client(conn, currently_focused); /* Because reposition_client does not send a faked configure event (only resize does), diff --git a/src/layout.c b/src/layout.c index 4b55c0ac..1017c3dd 100644 --- a/src/layout.c +++ b/src/layout.c @@ -228,7 +228,9 @@ void reposition_client(xcb_connection_t *conn, Client *client) { return; /* If the client is floating, we need to check if we moved it to a different workspace */ - if (client->workspace->screen == (screen = get_screen_containing(client->rect.x, client->rect.y))) + screen = get_screen_containing(client->rect.x + (client->rect.width / 2), + client->rect.y + (client->rect.height / 2)); + if (client->workspace->screen == screen) return; if (screen == NULL) { @@ -239,6 +241,8 @@ void reposition_client(xcb_connection_t *conn, Client *client) { DLOG("Client is on workspace %p with screen %p\n", client->workspace, client->workspace->screen); DLOG("but screen at %d, %d is %p\n", client->rect.x, client->rect.y, screen); floating_assign_to_workspace(client, screen->current_workspace); + + set_focus(conn, client, true); } /* From e446747812e6bdc4efff87e57a26ff12335691d2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Feb 2010 16:19:58 +0100 Subject: [PATCH 098/247] Also warp the pointer when moving a window to a another visible workspace (Thanks Thomas) --- src/commands.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/commands.c b/src/commands.c index 1bec7a84..2a2399d7 100644 --- a/src/commands.c +++ b/src/commands.c @@ -636,8 +636,10 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa render_layout(conn); - if (workspace_is_visible(to_container->workspace)) + if (workspace_is_visible(to_container->workspace)) { + client_warp_pointer_into(conn, current_client); set_focus(conn, current_client, true); + } } /* From 79a4e304888f2ddaf84be34ff138cc571d057e38 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 13 Feb 2010 15:27:43 +0100 Subject: [PATCH 099/247] parser: enable verbose error messages, add aliases to tokens --- src/cfgparse.y | 53 +++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/src/cfgparse.y b/src/cfgparse.y index 6b458e2c..1ce75724 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -161,6 +161,7 @@ void parse_file(const char *f) { %} %expect 1 +%error-verbose %union { int number; @@ -170,36 +171,36 @@ void parse_file(const char *f) { struct Binding *binding; } -%token NUMBER -%token WORD -%token STR -%token STR_NG -%token HEX +%token NUMBER "" +%token WORD "" +%token STR "" +%token STR_NG "" +%token HEX "" %token TOKBIND %token TOKTERMINAL -%token TOKCOMMENT -%token TOKFONT -%token TOKBINDSYM -%token MODIFIER -%token TOKCONTROL -%token TOKSHIFT -%token WHITESPACE -%token TOKFLOATING_MODIFIER -%token QUOTEDSTRING -%token TOKWORKSPACE -%token TOKSCREEN -%token TOKASSIGN +%token TOKCOMMENT "" +%token TOKFONT "font" +%token TOKBINDSYM "bindsym" +%token MODIFIER "" +%token TOKCONTROL "control" +%token TOKSHIFT "shift" +%token WHITESPACE "" +%token TOKFLOATING_MODIFIER "floating_modifier" +%token QUOTEDSTRING "" +%token TOKWORKSPACE "workspace" +%token TOKSCREEN "screen" +%token TOKASSIGN "assign" %token TOKSET -%token TOKIPCSOCKET -%token TOKEXEC +%token TOKIPCSOCKET "ipc_socket" +%token TOKEXEC "exec" %token TOKCOLOR -%token TOKARROW -%token TOKMODE -%token TOKNEWCONTAINER -%token TOKNEWWINDOW -%token TOKFOCUSFOLLOWSMOUSE -%token TOKCONTAINERMODE -%token TOKSTACKLIMIT +%token TOKARROW "→" +%token TOKMODE "mode" +%token TOKNEWCONTAINER "new_container" +%token TOKNEWWINDOW "new_window" +%token TOKFOCUSFOLLOWSMOUSE "focus_follows_mouse" +%token TOKCONTAINERMODE "default/stacking/tabbed" +%token TOKSTACKLIMIT "stack-limit" %% From 01297af20ac4cf3f7743067fb19ebf1075d83de3 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 13 Feb 2010 19:27:28 +0100 Subject: [PATCH 100/247] makefile: parser/lexer also depend on header files --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 7245c5c4..6e07d080 100644 --- a/Makefile +++ b/Makefile @@ -44,12 +44,12 @@ loglevels.h: done; \ echo "};") > include/loglevels.h; -src/cfgparse.yy.o: src/cfgparse.l +src/cfgparse.yy.o: src/cfgparse.l ${HEADERS} echo "LEX $<" flex -i -o$(@:.o=.c) $< $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c) -src/cfgparse.y.o: src/cfgparse.y +src/cfgparse.y.o: src/cfgparse.y ${HEADERS} echo "YACC $<" bison --debug --verbose -b $(basename $< .y) -d $< $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c) From 64cf88403d6dd6e563a85055230d240e32704953 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 13 Feb 2010 19:42:54 +0100 Subject: [PATCH 101/247] lexer/parser: proper error messages Error messages now look like this: 13.02.2010 19:42:30 - ERROR: 13.02.2010 19:42:30 - ERROR: CONFIG: syntax error, unexpected , expecting default/stacking/tabbed or stack-limit 13.02.2010 19:42:30 - ERROR: CONFIG: in file "inv", line 15: 13.02.2010 19:42:30 - ERROR: CONFIG: new_container foobar 13.02.2010 19:42:30 - ERROR: CONFIG: ^^^^^^ 13.02.2010 19:42:30 - ERROR: --- include/config.h | 17 +++++++++++- src/cfgparse.l | 71 +++++++++++++++++++++++++++++++++++++++++------- src/cfgparse.y | 29 +++++++++++++++++--- src/mainx.c | 11 +++++++- 4 files changed, 112 insertions(+), 16 deletions(-) diff --git a/include/config.h b/include/config.h index fe5405bc..627fe525 100644 --- a/include/config.h +++ b/include/config.h @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -25,6 +25,21 @@ typedef struct Config Config; extern Config config; extern SLIST_HEAD(modes_head, Mode) modes; +/** + * Used during the config file lexing/parsing to keep the state of the lexer + * in order to provide useful error messages in yyerror(). + * + */ +struct context { + int line_number; + char *line_copy; + const char *filename; + + /* These are the same as in YYLTYPE */ + int first_column; + int last_column; +}; + /** * Part of the struct Config. It makes sense to group colors for background, * border and text as every element in i3 has them (window decorations, bar). diff --git a/src/cfgparse.l b/src/cfgparse.l index ce912549..1982452d 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -1,5 +1,7 @@ %option nounput %option noinput +%option noyy_top_state +%option stack %{ /* @@ -13,19 +15,57 @@ #include "data.h" #include "config.h" +#include "log.h" +#include "util.h" + +int yycolumn = 1; + +#define YY_DECL int yylex (struct context *context) + +#define YY_USER_ACTION { \ + context->first_column = yycolumn; \ + context->last_column = yycolumn+yyleng-1; \ + yycolumn += yyleng; \ +} + %} -%Start BIND_COND -%Start BINDSYM_COND -%Start BIND_AWS_COND -%Start BINDSYM_AWS_COND -%Start BIND_A2WS_COND -%Start ASSIGN_COND -%Start COLOR_COND -%Start SCREEN_COND -%Start SCREEN_AWS_COND +EOL (\r?\n) + +%s BIND_COND +%s BINDSYM_COND +%s BIND_AWS_COND +%s BINDSYM_AWS_COND +%s BIND_A2WS_COND +%s ASSIGN_COND +%s COLOR_COND +%s SCREEN_COND +%s SCREEN_AWS_COND +%x BUFFER_LINE %% + + { + /* This is called when a new line is lexed. We only want the + * first line to match to go into state BUFFER_LINE */ + if (context->line_number == 0) { + context->line_number = 1; + BEGIN(INITIAL); + yy_push_state(BUFFER_LINE); + } + } + +^[^\r\n]*/{EOL}? { + /* save whole line */ + context->line_copy = strdup(yytext); + + yyless(0); + yy_pop_state(); + yy_set_bol(true); + yycolumn = 1; +} + + [^\n]+ { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR; } ^[ \t]*#[^\n]* { return TOKCOMMENT; } [0-9a-fA-F]+ { yylval.string = strdup(yytext); return HEX; } @@ -69,7 +109,11 @@ control { return TOKCONTROL; } ctrl { return TOKCONTROL; } shift { return TOKSHIFT; } → { return TOKARROW; } -\n /* ignore end of line */; +{EOL} { + FREE(context->line_copy); + context->line_number++; + yy_push_state(BUFFER_LINE); + } x { return (int)yytext[0]; } [ \t]+ { BEGIN(BIND_AWS_COND); return WHITESPACE; } [ \t]+ { BEGIN(BINDSYM_AWS_COND); return WHITESPACE; } @@ -91,4 +135,11 @@ shift { return TOKSHIFT; } [a-zA-Z0-9_]+ { yylval.string = strdup(yytext); return WORD; } [a-zA-Z]+ { yylval.string = strdup(yytext); return WORD; } . { return (int)yytext[0]; } + +<> { + while (yy_start_stack_ptr > 0) + yy_pop_state(); + yyterminate(); +} + %% diff --git a/src/cfgparse.y b/src/cfgparse.y index 1ce75724..3879cf97 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -24,17 +24,32 @@ #include "log.h" typedef struct yy_buffer_state *YY_BUFFER_STATE; -extern int yylex(void); +extern int yylex(struct context *context); extern int yyparse(void); extern FILE *yyin; YY_BUFFER_STATE yy_scan_string(const char *); static struct bindings_head *current_bindings; +static struct context *context; -int yydebug = 1; +/* We don’t need yydebug for now, as we got decent error messages using + * yyerror(). Should you ever want to extend the parser, it might be handy + * to just comment it in again, so it stays here. */ +//int yydebug = 1; -void yyerror(const char *str) { - fprintf(stderr,"error: %s\n",str); +void yyerror(const char *error_message) { + ELOG("\n"); + ELOG("CONFIG: %s\n", error_message); + ELOG("CONFIG: in file \"%s\", line %d:\n", + context->filename, context->line_number); + ELOG("CONFIG: %s\n", context->line_copy); + ELOG("CONFIG: "); + for (int c = 1; c <= context->last_column; c++) + if (c >= context->first_column) + printf("^"); + else printf(" "); + printf("\n"); + ELOG("\n"); } int yywrap() { @@ -149,11 +164,16 @@ void parse_file(const char *f) { yy_scan_string(new); + context = scalloc(sizeof(struct context)); + context->filename = f; + if (yyparse() != 0) { fprintf(stderr, "Could not parse configfile\n"); exit(1); } + FREE(context->line_copy); + free(context); free(new); free(buf); } @@ -162,6 +182,7 @@ void parse_file(const char *f) { %expect 1 %error-verbose +%lex-param { struct context *context } %union { int number; diff --git a/src/mainx.c b/src/mainx.c index 389fc76c..6dd72a17 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -150,6 +150,7 @@ int main(int argc, char *argv[], char *env[]) { int i, screens, opt; char *override_configpath = NULL; bool autostart = true; + bool only_check_config = false; xcb_connection_t *conn; xcb_property_handlers_t prophs; xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS]; @@ -170,7 +171,7 @@ int main(int argc, char *argv[], char *env[]) { start_argv = argv; - while ((opt = getopt_long(argc, argv, "c:vahld:V", long_options, &option_index)) != -1) { + while ((opt = getopt_long(argc, argv, "c:Cvahld:V", long_options, &option_index)) != -1) { switch (opt) { case 'a': LOG("Autostart disabled using -a\n"); @@ -179,6 +180,10 @@ int main(int argc, char *argv[], char *env[]) { case 'c': override_configpath = sstrdup(optarg); break; + case 'C': + LOG("Checking configuration file only (-C)\n"); + only_check_config = true; + break; case 'v': printf("i3 version " I3_VERSION " © 2009 Michael Stapelberg and contributors\n"); exit(EXIT_SUCCESS); @@ -218,6 +223,10 @@ int main(int argc, char *argv[], char *env[]) { die("Cannot open display\n"); load_configuration(conn, override_configpath, false); + if (only_check_config) { + LOG("Done checking configuration file. Exiting.\n"); + exit(0); + } /* Create the initial container on the first workspace. This used to * be part of init_table, but since it possibly requires an X From 2af2bc36c9de1a7d9e8960726e1573f582195f39 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 13 Feb 2010 19:58:43 +0100 Subject: [PATCH 102/247] Document the new options --- man/i3.man | 20 ++++++++++++++++---- src/mainx.c | 3 ++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/man/i3.man b/man/i3.man index 0b114da3..92b951ff 100644 --- a/man/i3.man +++ b/man/i3.man @@ -9,16 +9,28 @@ i3 - an improved dynamic, tiling window manager == SYNOPSIS -i3 [-c configfile] [-a] +i3 [-a] [-c configfile] [-C] [-d ] [-v] [-V] == OPTIONS --c:: -Specifies an alternate configuration file path - -a:: Disables autostart. +-c:: +Specifies an alternate configuration file path. + +-C:: +Check the configuration file for validity and exit. + +-d:: +Specifies the debug loglevel. To see the most output, use -d all. + +-v:: +Display version number (and date of the last commit). + +-V:: +Be verbose. + == DESCRIPTION === INTRODUCTION diff --git a/src/mainx.c b/src/mainx.c index 6dd72a17..f79beae2 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -198,13 +198,14 @@ int main(int argc, char *argv[], char *env[]) { /* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */ break; default: - fprintf(stderr, "Usage: %s [-c configfile] [-d loglevel] [-a] [-v] [-V]\n", argv[0]); + fprintf(stderr, "Usage: %s [-c configfile] [-d loglevel] [-a] [-v] [-V] [-C]\n", argv[0]); fprintf(stderr, "\n"); fprintf(stderr, "-a: disable autostart\n"); fprintf(stderr, "-v: display version and exit\n"); fprintf(stderr, "-V: enable verbose mode\n"); fprintf(stderr, "-d : enable debug loglevel \n"); fprintf(stderr, "-c : use the provided configfile instead\n"); + fprintf(stderr, "-C: check configuration file and exit\n"); exit(EXIT_FAILURE); } } From e78bb020823490bac5b7645591faf1c1e73af9d0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 14 Feb 2010 16:59:22 +0100 Subject: [PATCH 103/247] parser: ignore errors --- src/cfgparse.y | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cfgparse.y b/src/cfgparse.y index 3879cf97..8d97401b 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -227,6 +227,7 @@ void parse_file(const char *f) { lines: /* empty */ | lines WHITESPACE line + | lines error | lines line ; From e2257424157690321e2c5863af8987a4506f4f2c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 16 Feb 2010 19:25:07 +0100 Subject: [PATCH 104/247] Make the warning about $terminal being deprecated an error (Thanks fallen) --- src/cfgparse.y | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfgparse.y b/src/cfgparse.y index 8d97401b..2633f4d2 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -536,7 +536,7 @@ exec: terminal: TOKTERMINAL WHITESPACE STR { - DLOG("The terminal option is DEPRECATED and has no effect. " + ELOG("The terminal option is DEPRECATED and has no effect. " "Please remove it from your configuration file."); } ; From 432f06a21e4fb62f5e4378df5a0ef83e81bb77e4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 16 Feb 2010 19:55:04 +0100 Subject: [PATCH 105/247] Add missing carriage return (Thanks fallen) --- src/cfgparse.y | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfgparse.y b/src/cfgparse.y index 2633f4d2..cc0e77cb 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -537,7 +537,7 @@ terminal: TOKTERMINAL WHITESPACE STR { ELOG("The terminal option is DEPRECATED and has no effect. " - "Please remove it from your configuration file."); + "Please remove it from your configuration file.\n"); } ; From 4fcbb7980e92b51df3e36f261ec664c895ef3531 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 20 Feb 2010 20:32:43 +0100 Subject: [PATCH 106/247] work around clients setting 0xFFFF as resize increments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I am not sure if behaviour is specified for this case, but as the workaround is not really a big deal, why bother… --- src/handlers.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index 0f550835..627744f4 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -855,12 +855,12 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w if ((size_hints.flags & XCB_SIZE_HINT_P_RESIZE_INC)) { bool changed = false; - if (size_hints.width_inc > 0) + if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF) if (client->width_increment != size_hints.width_inc) { client->width_increment = size_hints.width_inc; changed = true; } - if (size_hints.height_inc > 0) + if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF) if (client->height_increment != size_hints.height_inc) { client->height_increment = size_hints.height_inc; changed = true; From e209fd7d3a91654a14672cdcf88e271a9bc6d5c2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 22 Feb 2010 07:09:17 +0100 Subject: [PATCH 107/247] docs: the comparison is size and position, not only size (Thanks Merovius) --- src/xinerama.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/xinerama.c b/src/xinerama.c index 15e8b0a3..f6af933e 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -51,8 +51,8 @@ bool screens_are_equal(i3Screen *screen1, i3Screen *screen2) { if (screen1 == screen2) return true; - /* Compare their size - other properties are not relevant to determine - * if a screen is equal to another one */ + /* Compare their size and position - other properties are not relevant + * to determine if a screen is equal to another one */ return (memcmp(&(screen1->rect), &(screen2->rect), sizeof(Rect)) == 0); } From 7caf98dd189a6cc2c238d8d8db731d1d5846bd63 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Feb 2010 18:39:11 +0100 Subject: [PATCH 108/247] Move update_if_necessary to util.c, will be necessary later --- include/util.h | 7 +++++++ src/layout.c | 11 ----------- src/util.c | 11 +++++++++++ 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/include/util.h b/include/util.h index e45fc75a..d1384962 100644 --- a/include/util.h +++ b/include/util.h @@ -41,6 +41,13 @@ extern struct keyvalue_table_head by_child; int min(int a, int b); int max(int a, int b); +/** + * Updates *destination with new_value and returns true if it was changed or false + * if it was the same + * + */ +bool update_if_necessary(uint32_t *destination, const uint32_t new_value); + /** * Safe-wrapper around malloc which exits if malloc returns NULL (meaning that * there is no more memory available) diff --git a/src/layout.c b/src/layout.c index 1017c3dd..182870df 100644 --- a/src/layout.c +++ b/src/layout.c @@ -31,17 +31,6 @@ #include "log.h" #include "container.h" -/* - * Updates *destination with new_value and returns true if it was changed or false - * if it was the same - * - */ -static bool update_if_necessary(uint32_t *destination, const uint32_t new_value) { - uint32_t old_value = *destination; - - return ((*destination = new_value) != old_value); -} - /* * Gets the unoccupied space (= space which is available for windows which were resized by the user) * for the given row. This is necessary to render both, customly resized windows and never touched diff --git a/src/util.c b/src/util.c index 1cdc758a..f14052b0 100644 --- a/src/util.c +++ b/src/util.c @@ -46,6 +46,17 @@ int max(int a, int b) { return (a > b ? a : b); } +/* + * Updates *destination with new_value and returns true if it was changed or false + * if it was the same + * + */ +bool update_if_necessary(uint32_t *destination, const uint32_t new_value) { + uint32_t old_value = *destination; + + return ((*destination = new_value) != old_value); +} + /* * The s* functions (safe) are wrappers around malloc, strdup, …, which exits if one of * the called functions returns NULL, meaning that there is no more memory available From 6f72970ece75a12913d2009e6f02946073772ab1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 28 Feb 2010 20:35:30 +0100 Subject: [PATCH 109/247] add xcb_set_window_rect which configures a window according to a Rect --- include/xcb.h | 6 ++++++ src/client.c | 25 ++++++++----------------- src/layout.c | 13 ++----------- src/xcb.c | 15 ++++++++++++++- 4 files changed, 30 insertions(+), 29 deletions(-) diff --git a/include/xcb.h b/include/xcb.h index d4ac33a3..78e1373a 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -164,4 +164,10 @@ void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap) int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *text, int length); +/** + * Configures the given window to have the size/position specified by given rect + * + */ +void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r); + #endif diff --git a/src/client.c b/src/client.c index 3b747f54..1ca35dc7 100644 --- a/src/client.c +++ b/src/client.c @@ -164,32 +164,23 @@ void client_enter_fullscreen(xcb_connection_t *conn, Client *client) { workspace->fullscreen_client = client; LOG("Entering fullscreen mode...\n"); /* We just entered fullscreen mode, let’s configure the window */ - uint32_t mask = XCB_CONFIG_WINDOW_X | - XCB_CONFIG_WINDOW_Y | - XCB_CONFIG_WINDOW_WIDTH | - XCB_CONFIG_WINDOW_HEIGHT; - uint32_t values[4] = {workspace->rect.x, - workspace->rect.y, - workspace->rect.width, - workspace->rect.height}; + Rect r = workspace->rect; DLOG("child itself will be at %dx%d with size %dx%d\n", - values[0], values[1], values[2], values[3]); + r.x, r.y, r.width, r.height); - xcb_configure_window(conn, client->frame, mask, values); + xcb_set_window_rect(conn, client->frame, r); /* Child’s coordinates are relative to the parent (=frame) */ - values[0] = 0; - values[1] = 0; - xcb_configure_window(conn, client->child, mask, values); + r.x = 0; + r.y = 0; + xcb_set_window_rect(conn, client->child, r); /* Raise the window */ - values[0] = XCB_STACK_MODE_ABOVE; + uint32_t values[] = { XCB_STACK_MODE_ABOVE }; xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values); - Rect child_rect = workspace->rect; - child_rect.x = child_rect.y = 0; - fake_configure_notify(conn, child_rect, client->child); + fake_configure_notify(conn, r, client->child); xcb_flush(conn); } diff --git a/src/layout.c b/src/layout.c index 182870df..19babe2f 100644 --- a/src/layout.c +++ b/src/layout.c @@ -246,20 +246,11 @@ void resize_client(xcb_connection_t *conn, Client *client) { DLOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y); DLOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height); - xcb_configure_window(conn, client->frame, - XCB_CONFIG_WINDOW_X | - XCB_CONFIG_WINDOW_Y | - XCB_CONFIG_WINDOW_WIDTH | - XCB_CONFIG_WINDOW_HEIGHT, - &(client->rect.x)); + xcb_set_window_rect(conn, client->frame, client->rect); /* Adjust the position of the child inside its frame. * The coordinates of the child are relative to its frame, we * add a border of 2 pixel to each value */ - uint32_t mask = XCB_CONFIG_WINDOW_X | - XCB_CONFIG_WINDOW_Y | - XCB_CONFIG_WINDOW_WIDTH | - XCB_CONFIG_WINDOW_HEIGHT; Rect *rect = &(client->child_rect); switch (container_mode(client->container, true)) { case MODE_STACK: @@ -330,7 +321,7 @@ void resize_client(xcb_connection_t *conn, Client *client) { DLOG("child will be at %dx%d with size %dx%d\n", rect->x, rect->y, rect->width, rect->height); - xcb_configure_window(conn, client->child, mask, &(rect->x)); + xcb_set_window_rect(conn, client->child, *rect); /* After configuring a child window we need to fake a configure_notify_event (see ICCCM 4.2.3). * This is necessary to inform the client of its position relative to the root window, diff --git a/src/xcb.c b/src/xcb.c index f7835d3a..fd01391e 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -375,3 +375,16 @@ int predict_text_width(xcb_connection_t *conn, const char *font_pattern, char *t return width; } + +/* + * Configures the given window to have the size/position specified by given rect + * + */ +void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r) { + xcb_configure_window(conn, window, + XCB_CONFIG_WINDOW_X | + XCB_CONFIG_WINDOW_Y | + XCB_CONFIG_WINDOW_WIDTH | + XCB_CONFIG_WINDOW_HEIGHT, + &(r.x)); +} From 818e02ef3545b000e104b79cf52610ac0f1765cd Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 2 Mar 2010 12:47:21 +0100 Subject: [PATCH 110/247] huge change: implement RandR instead of Xinerama Thanks to Merovius for doing a proof of concept on this one and being a driving force behind the idea. Using RandR instead of Xinerama means that we are now able to use the full potential of the modern way of configuring screens. That means, i3 now has an idea of the outputs your graphic driver provides, which allowed us to get rid of the ugly way of detecting changes in the screen configuration which we used before. Now, your workspaces should not be confused when changing output modes anymore. Also, instead of having ugly heuristics to assign your workspaces to (the screen at position X or the second screen in the list of screens) you will be able to just specify an output name. As this change basically touches everything, you should be prepared for bugs. Please test and report them! --- common.mk | 2 +- include/data.h | 39 ++-- include/handlers.h | 12 +- include/layout.h | 2 +- include/randr.h | 62 ++++++ include/workspace.h | 10 +- include/xinerama.h | 62 ------ src/click.c | 19 +- src/commands.c | 38 ++-- src/debug.c | 2 + src/floating.c | 2 +- src/handlers.c | 65 ++++--- src/layout.c | 57 +++--- src/mainx.c | 22 ++- src/manage.c | 6 +- src/randr.c | 455 ++++++++++++++++++++++++++++++++++++++++++++ src/resize.c | 6 +- src/sighandler.c | 6 +- src/util.c | 2 +- src/workspace.c | 99 +++++----- src/xinerama.c | 437 ------------------------------------------ 21 files changed, 724 insertions(+), 681 deletions(-) create mode 100644 include/randr.h delete mode 100644 include/xinerama.h create mode 100644 src/randr.c delete mode 100644 src/xinerama.c diff --git a/common.mk b/common.mk index 775b592d..3b88b6fe 100644 --- a/common.mk +++ b/common.mk @@ -35,7 +35,7 @@ LDFLAGS += -lxcb-keysyms LDFLAGS += -lxcb-atom LDFLAGS += -lxcb-aux LDFLAGS += -lxcb-icccm -LDFLAGS += -lxcb-xinerama +LDFLAGS += -lxcb-randr LDFLAGS += -lxcb LDFLAGS += -lX11 LDFLAGS += -lev diff --git a/include/data.h b/include/data.h index 31cef493..10242f20 100644 --- a/include/data.h +++ b/include/data.h @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -11,6 +11,7 @@ * */ #include +#include #include #include @@ -25,11 +26,12 @@ * * Let’s start from the biggest to the smallest: * - * - An i3Screen is a virtual screen (Xinerama). This can be a single one, - * though two monitors might be connected, if you’re running clone - * mode. There can also be multiple of them. + * - An Output is a physical output on your graphics driver. Outputs which + * are currently in use have (output->active == true). Each output has a + * position and a mode. An output usually corresponds to one connected + * screen (except if you are running multiple screens in clone mode). * - * - Each i3Screen contains Workspaces. The concept is known from various + * - Each Output contains Workspaces. The concept is known from various * other window managers. Basically, a workspace is a specific set of * windows, usually grouped thematically (irc, www, work, …). You can switch * between these. @@ -54,7 +56,7 @@ typedef struct Client Client; typedef struct Binding Binding; typedef struct Workspace Workspace; typedef struct Rect Rect; -typedef struct Screen i3Screen; +typedef struct xoutput Output; /****************************************************************************** * Helper types @@ -228,8 +230,8 @@ struct Workspace { * appended) */ TAILQ_HEAD(floating_clients_head, Client) floating_clients; - /** Backpointer to the screen this workspace is on */ - i3Screen *screen; + /** Backpointer to the output this workspace is on */ + Output *output; /** This is a two-dimensional dynamic array of * Container-pointers. I’ve always wanted to be a three-star @@ -496,14 +498,21 @@ struct Container { }; /** - * This is a virtual screen (Xinerama). This can be a single one, though two - * monitors might be connected, if you’re running clone mode. There can also - * be multiple of them. + * An Output is a physical output on your graphics driver. Outputs which + * are currently in use have (output->active == true). Each output has a + * position and a mode. An output usually corresponds to one connected + * screen (except if you are running multiple screens in clone mode). * */ -struct Screen { - /** Virtual screen number */ - int num; +struct xoutput { + /** Output id, so that we can requery the output directly later */ + xcb_randr_output_t id; + /** Name of the output */ + char *name; + + /** Whether the output is currently (has a CRTC attached with a valid + * mode) */ + bool active; /** Current workspace selected on this virtual screen */ Workspace *current_workspace; @@ -519,7 +528,7 @@ struct Screen { * _NET_WM_WINDOW_TYPE_DOCK */ SLIST_HEAD(dock_clients_head, Client) dock_clients; - TAILQ_ENTRY(Screen) screens; + TAILQ_ENTRY(xoutput) outputs; }; #endif diff --git a/include/handlers.h b/include/handlers.h index 95194c14..5f0586f8 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * (c) 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -11,6 +11,8 @@ #ifndef _HANDLERS_H #define _HANDLERS_H +#include + /** * Due to bindings like Mode_switch + , we need to bind some keys in * XCB_GRAB_MODE_SYNC. Therefore, we just replay all key presses. @@ -74,6 +76,14 @@ int handle_map_request(void *prophs, xcb_connection_t *conn, */ int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_notify_event_t *event); +/** + * Gets triggered upon a RandR screen change event, that is when the user + * changes the screen configuration in any way (mode, position, …) + * + */ +int handle_screen_change(void *prophs, xcb_connection_t *conn, + xcb_generic_event_t *e); + /** * Configure requests are received when the application wants to resize * windows on their own. diff --git a/include/layout.h b/include/layout.h index a96aabc3..1cbb7837 100644 --- a/include/layout.h +++ b/include/layout.h @@ -79,7 +79,7 @@ void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace, * Renders the given workspace on the given screen * */ -void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws); +void render_workspace(xcb_connection_t *conn, Output *output, Workspace *r_ws); /** * Renders the whole layout, that is: Go through each screen, each workspace, diff --git a/include/randr.h b/include/randr.h new file mode 100644 index 00000000..50e19938 --- /dev/null +++ b/include/randr.h @@ -0,0 +1,62 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009-2010 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ +#include "data.h" +#include + +#ifndef _RANDR_H +#define _RANDR_H + +TAILQ_HEAD(outputs_head, xoutput); +extern struct outputs_head outputs; + +/** + * Returns true if both screen objects describe the same screen (checks their + * size and position). + * + */ +bool screens_are_equal(Output *screen1, Output *screen2); + +/** + * We have just established a connection to the X server and need the initial + * XRandR information to setup workspaces for each screen. + * + */ +void initialize_randr(xcb_connection_t *conn, int *event_base); + +/** + * (Re-)queries the outputs via RandR and stores them in the list of outputs. + * + */ +void randr_query_screens(xcb_connection_t *conn); + +/** + * Returns the first output which is active. + * + */ +Output *get_first_output(); + +/** + * Looks in virtual_screens for the i3Screen which contains coordinates x, y + * + */ +Output *get_screen_containing(int x, int y); + +/** + * Gets the screen which is the last one in the given direction, for example + * the screen on the most bottom when direction == D_DOWN, the screen most + * right when direction == D_RIGHT and so on. + * + * This function always returns a screen. + * + */ +Output *get_screen_most(direction_t direction, Output *current); + +#endif diff --git a/include/workspace.h b/include/workspace.h index 01e1b6fa..f22ca8e4 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -11,7 +11,7 @@ #include #include "data.h" -#include "xinerama.h" +#include "randr.h" #ifndef _WORKSPACE_H #define _WORKSPACE_H @@ -53,7 +53,7 @@ void workspace_show(xcb_connection_t *conn, int workspace); * screen 1 and you just plugged in screen 1). * */ -void workspace_assign_to(Workspace *ws, i3Screen *screen); +void workspace_assign_to(Workspace *ws, Output *screen); /** * Initializes the given workspace if it is not already initialized. The given @@ -62,14 +62,14 @@ void workspace_assign_to(Workspace *ws, i3Screen *screen); * the screen is not attached at the moment. * */ -void workspace_initialize(Workspace *ws, i3Screen *screen, bool recheck); +void workspace_initialize(Workspace *ws, Output *screen, bool recheck); /** * Gets the first unused workspace for the given screen, taking into account * the preferred_screen setting of every workspace (workspace assignments). * */ -Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen *screen); +Workspace *get_first_workspace_for_screen(Output *screen); /** * Unmaps all clients (and stack windows) of the given workspace. diff --git a/include/xinerama.h b/include/xinerama.h deleted file mode 100644 index 135ab1ab..00000000 --- a/include/xinerama.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * vim:ts=8:expandtab - * - * i3 - an improved dynamic tiling window manager - * - * (c) 2009 Michael Stapelberg and contributors - * - * See file LICENSE for license information. - * - */ -#include "data.h" - -#ifndef _XINERAMA_H -#define _XINERAMA_H - -TAILQ_HEAD(screens_head, Screen); -extern struct screens_head *virtual_screens; - -/** - * Returns true if both screen objects describe the same screen (checks their - * size and position). - * - */ -bool screens_are_equal(i3Screen *screen1, i3Screen *screen2); - -/** - * We have just established a connection to the X server and need the initial - * Xinerama information to setup workspaces for each screen. - * - */ -void initialize_xinerama(xcb_connection_t *conn); - -/** - * This is called when the rootwindow receives a configure_notify event and - * therefore the number/position of the Xinerama screens could have changed. - * - */ -void xinerama_requery_screens(xcb_connection_t *conn); - -/** - * Looks in virtual_screens for the i3Screen whose start coordinates are x, y - * - */ -i3Screen *get_screen_at(int x, int y, struct screens_head *screenlist); - -/** - * Looks in virtual_screens for the i3Screen which contains coordinates x, y - * - */ -i3Screen *get_screen_containing(int x, int y); - -/** - * Gets the screen which is the last one in the given direction, for example - * the screen on the most bottom when direction == D_DOWN, the screen most - * right when direction == D_RIGHT and so on. - * - * This function always returns a screen. - * - */ -i3Screen *get_screen_most(direction_t direction, i3Screen *current); - -#endif diff --git a/src/click.c b/src/click.c index 2ec071c4..d792ce23 100644 --- a/src/click.c +++ b/src/click.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -37,6 +37,7 @@ #include "floating.h" #include "resize.h" #include "log.h" +#include "randr.h" static struct Stack_Window *get_stack_window(xcb_window_t window_id) { struct Stack_Window *current; @@ -125,9 +126,9 @@ static bool button_press_stackwin(xcb_connection_t *conn, xcb_button_press_event * */ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *event) { - i3Screen *screen; - TAILQ_FOREACH(screen, virtual_screens, screens) { - if (screen->bar != event->event) + Output *output; + TAILQ_FOREACH(output, &outputs, outputs) { + if (output->bar != event->event) continue; DLOG("Click on a bar\n"); @@ -137,14 +138,14 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e Workspace *ws = c_ws; if (event->detail == XCB_BUTTON_INDEX_5) { while ((ws = TAILQ_NEXT(ws, workspaces)) != TAILQ_END(workspaces_head)) { - if (ws->screen == screen) { + if (ws->output == output) { workspace_show(conn, ws->num + 1); return true; } } } else { while ((ws = TAILQ_PREV(ws, workspaces_head, workspaces)) != TAILQ_END(workspaces)) { - if (ws->screen == screen) { + if (ws->output == output) { workspace_show(conn, ws->num + 1); return true; } @@ -153,11 +154,11 @@ static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *e return true; } 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 */ + /* Because workspaces can be on different outputs, we need to loop + through all of them and decide to count it based on its ->output */ Workspace *ws; TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->screen != screen) + if (ws->output != output) continue; DLOG("Checking if click was on workspace %d with drawn = %d, tw = %d\n", ws->num, drawn, ws->text_width); diff --git a/src/commands.c b/src/commands.c index 2a2399d7..cdd82513 100644 --- a/src/commands.c +++ b/src/commands.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -22,7 +22,7 @@ #include "table.h" #include "layout.h" #include "i3.h" -#include "xinerama.h" +#include "randr.h" #include "client.h" #include "floating.h" #include "xcb.h" @@ -108,7 +108,7 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t * right/left/bottom/top and just switch to the workspace on * the target screen. */ if (thing == THING_SCREEN) { - i3Screen *cs = c_ws->screen; + Output *cs = c_ws->output; assert(cs != NULL); Rect bounds = cs->rect; @@ -120,9 +120,9 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t bounds.y -= bounds.height; else bounds.y += bounds.height; - i3Screen *target = get_screen_containing(bounds.x, bounds.y); + Output *target = get_screen_containing(bounds.x, bounds.y); if (target == NULL) { - DLOG("Target screen NULL\n"); + DLOG("Target output NULL\n"); /* Wrap around if the target screen is out of bounds */ if (direction == D_RIGHT) target = get_screen_most(D_LEFT, cs); @@ -162,14 +162,14 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t } else { /* Let’s see if there is a screen down/up there to which we can switch */ DLOG("container is at %d with height %d\n", container->y, container->height); - i3Screen *screen; + Output *output; int destination_y = (direction == D_UP ? (container->y - 1) : (container->y + container->height + 1)); - if ((screen = get_screen_containing(container->x, destination_y)) == NULL) { + if ((output = get_screen_containing(container->x, destination_y)) == NULL) { DLOG("Wrapping screen around vertically\n"); /* No screen found? Then wrap */ - screen = get_screen_most((direction == D_UP ? D_DOWN : D_UP), container->workspace->screen); + output = get_screen_most((direction == D_UP ? D_DOWN : D_UP), container->workspace->output); } - t_ws = screen->current_workspace; + t_ws = output->current_workspace; new_row = (direction == D_UP ? (t_ws->rows - 1) : 0); } @@ -205,13 +205,13 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t } else { /* Let’s see if there is a screen left/right here to which we can switch */ DLOG("container is at %d with width %d\n", container->x, container->width); - i3Screen *screen; + Output *output; int destination_x = (direction == D_LEFT ? (container->x - 1) : (container->x + container->width + 1)); - if ((screen = get_screen_containing(destination_x, container->y)) == NULL) { + if ((output = get_screen_containing(destination_x, container->y)) == NULL) { DLOG("Wrapping screen around horizontally\n"); - screen = get_screen_most((direction == D_LEFT ? D_RIGHT : D_LEFT), container->workspace->screen); + output = get_screen_most((direction == D_LEFT ? D_RIGHT : D_LEFT), container->workspace->output); } - t_ws = screen->current_workspace; + t_ws = output->current_workspace; new_col = (direction == D_LEFT ? (t_ws->cols - 1) : 0); } @@ -359,7 +359,7 @@ static void move_current_window(xcb_connection_t *conn, direction_t direction) { /* Fix colspan/rowspan if it’d overlap */ fix_colrowspan(conn, workspace); - render_workspace(conn, workspace->screen, workspace); + render_workspace(conn, workspace->output, workspace); xcb_flush(conn); set_focus(conn, current_client, true); @@ -532,7 +532,7 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl LOG("moving floating\n"); - workspace_initialize(t_ws, c_ws->screen, false); + workspace_initialize(t_ws, c_ws->output, false); /* Check if there is already a fullscreen client on the destination workspace and * stop moving if so. */ @@ -593,7 +593,7 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa if (to_focus == NULL) to_focus = CIRCLEQ_PREV_OR_NULL(&(container->clients), current_client, clients); - workspace_initialize(t_ws, container->workspace->screen, false); + workspace_initialize(t_ws, container->workspace->output, false); /* Check if there is already a fullscreen client on the destination workspace and * stop moving if so. */ if (current_client->fullscreen && (t_ws->fullscreen_client != NULL)) { @@ -779,7 +779,7 @@ static void next_previous_workspace(xcb_connection_t *conn, int direction) { if (ws == c_ws) return; - if (ws->screen == NULL) + if (ws->output == NULL) continue; workspace_show(conn, ws->num + 1); @@ -795,7 +795,7 @@ static void next_previous_workspace(xcb_connection_t *conn, int direction) { if (ws == c_ws) return; - if (ws->screen == NULL) + if (ws->output == NULL) continue; workspace_show(conn, ws->num + 1); @@ -1113,7 +1113,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { /* Fix colspan/rowspan if it’d overlap */ fix_colrowspan(conn, ws); - render_workspace(conn, ws->screen, ws); + render_workspace(conn, ws->output, ws); /* Re-focus the client because cleanup_table sets the focus to the last * focused client inside a container only. */ diff --git a/src/debug.c b/src/debug.c index 1be47269..cd89b296 100644 --- a/src/debug.c +++ b/src/debug.c @@ -225,6 +225,8 @@ int format_event(xcb_generic_event_t *e) { labelRequest[*((uint8_t *) e + 10)]); break; default: + if (e->response_type > sizeof(labelEvent) / sizeof(char*)) + break; printf("Event %s following seqnum %d%s.\n", labelEvent[e->response_type], seqnum, diff --git a/src/floating.c b/src/floating.c index ca294cc6..02397bfc 100644 --- a/src/floating.c +++ b/src/floating.c @@ -412,7 +412,7 @@ void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_ DLOG("floating move\n"); Rect destination = currently_focused->rect; - Rect *screen = &(currently_focused->workspace->screen->rect); + Rect *screen = &(currently_focused->workspace->output->rect); switch (direction) { case D_LEFT: diff --git a/src/handlers.c b/src/handlers.c index 627744f4..bc484c92 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -17,6 +17,7 @@ #include #include #include +#include #include @@ -28,7 +29,7 @@ #include "data.h" #include "xcb.h" #include "util.h" -#include "xinerama.h" +#include "randr.h" #include "config.h" #include "queue.h" #include "resize.h" @@ -163,21 +164,21 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_ * */ static void check_crossing_screen_boundary(uint32_t x, uint32_t y) { - i3Screen *screen; + Output *output; - if ((screen = get_screen_containing(x, y)) == NULL) { + if ((output = get_screen_containing(x, y)) == NULL) { ELOG("ERROR: No such screen\n"); return; } - if (screen == c_ws->screen) + if (output == c_ws->output) return; c_ws->current_row = current_row; c_ws->current_col = current_col; - c_ws = screen->current_workspace; + c_ws = output->current_workspace; current_row = c_ws->current_row; current_col = c_ws->current_col; - DLOG("We're now on virtual screen number %d\n", screen->num); + DLOG("We're now on output %p\n", output); } /* @@ -228,7 +229,7 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_ return 1; } - if (client->workspace != c_ws && client->workspace->screen == c_ws->screen) { + if (client->workspace != c_ws && client->workspace->output == c_ws->output) { /* This can happen when a client gets assigned to a different workspace than * the current one (see src/mainx.c:reparent_window). Shortly after it was created, * an enter_notify will follow. */ @@ -395,7 +396,7 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure } client->desired_height = event->height; - render_workspace(conn, c_ws->screen, c_ws); + render_workspace(conn, c_ws->output, c_ws); xcb_flush(conn); return 1; @@ -417,26 +418,28 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure } /* - * Configuration notifies are only handled because we need to set up ignore for the following - * enter notify events + * Configuration notifies are only handled because we need to set up ignore for + * the following enter notify events. * */ int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_notify_event_t *event) { - xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; - /* We ignore this sequence twice because events for child and frame should be ignored */ add_ignore_event(event->sequence); add_ignore_event(event->sequence); - if (event->event == root) { - DLOG("event->x = %d, ->y = %d, ->width = %d, ->height = %d\n", event->x, event->y, event->width, event->height); - DLOG("reconfigure of the root window, need to xinerama\n"); - /* FIXME: Somehow, this is occuring too often. Therefore, we check for 0/0, - but is there a better way? */ - if (event->x == 0 && event->y == 0) - xinerama_requery_screens(conn); - return 1; - } + return 1; +} + +/* + * Gets triggered upon a RandR screen change event, that is when the user + * changes the screen configuration in any way (mode, position, …) + * + */ +int handle_screen_change(void *prophs, xcb_connection_t *conn, + xcb_generic_event_t *e) { + DLOG("RandR screen change\n"); + + randr_query_screens(conn); return 1; } @@ -500,7 +503,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti if (client->dock) { DLOG("Removing from dock clients\n"); - SLIST_REMOVE(&(client->workspace->screen->dock_clients), client, Client, dock_clients); + SLIST_REMOVE(&(client->workspace->output->dock_clients), client, Client, dock_clients); } DLOG("child of 0x%08x.\n", client->frame); @@ -524,8 +527,8 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti Client *to_focus = (!workspace_empty ? SLIST_FIRST(&(client->workspace->focus_stack)) : NULL); /* If this workspace is currently active, we don’t delete it */ - i3Screen *screen; - TAILQ_FOREACH(screen, virtual_screens, screens) + Output *screen; + TAILQ_FOREACH(screen, &outputs, outputs) if (screen->current_workspace == client->workspace) { workspace_active = true; workspace_empty = false; @@ -533,7 +536,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti } if (workspace_empty) - client->workspace->screen = NULL; + client->workspace->output = NULL; /* Remove the urgency flag if set */ client->urgent = false; @@ -739,9 +742,9 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t * } /* …or one of the bars? */ - i3Screen *screen; - TAILQ_FOREACH(screen, virtual_screens, screens) - if (screen->bar == event->window) + Output *output; + TAILQ_FOREACH(output, &outputs, outputs) + if (output->bar == event->window) render_layout(conn); return 1; } @@ -973,8 +976,8 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t /* If the workspace this client is on is not visible, we need to redraw * the workspace bar */ if (!workspace_is_visible(client->workspace)) { - i3Screen *screen = client->workspace->screen; - render_workspace(conn, screen, screen->current_workspace); + Output *output = client->workspace->output; + render_workspace(conn, output, output->current_workspace); xcb_flush(conn); } diff --git a/src/layout.c b/src/layout.c index 19babe2f..c189a950 100644 --- a/src/layout.c +++ b/src/layout.c @@ -22,7 +22,7 @@ #include "xcb.h" #include "table.h" #include "util.h" -#include "xinerama.h" +#include "randr.h" #include "layout.h" #include "client.h" #include "floating.h" @@ -206,7 +206,7 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw * */ void reposition_client(xcb_connection_t *conn, Client *client) { - i3Screen *screen; + Output *output; DLOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y); /* Note: We can use a pointer to client->x like an array of uint32_ts @@ -217,19 +217,19 @@ void reposition_client(xcb_connection_t *conn, Client *client) { return; /* If the client is floating, we need to check if we moved it to a different workspace */ - screen = get_screen_containing(client->rect.x + (client->rect.width / 2), + output = get_screen_containing(client->rect.x + (client->rect.width / 2), client->rect.y + (client->rect.height / 2)); - if (client->workspace->screen == screen) + if (client->workspace->output == output) return; - if (screen == NULL) { - DLOG("Boundary checking disabled, no screen found for (%d, %d)\n", client->rect.x, client->rect.y); + if (output == NULL) { + DLOG("Boundary checking disabled, no output found for (%d, %d)\n", client->rect.x, client->rect.y); return; } - DLOG("Client is on workspace %p with screen %p\n", client->workspace, client->workspace->screen); - DLOG("but screen at %d, %d is %p\n", client->rect.x, client->rect.y, screen); - floating_assign_to_workspace(client, screen->current_workspace); + DLOG("Client is on workspace %p with output %p\n", client->workspace, client->workspace->output); + DLOG("but output at %d, %d is %p\n", client->rect.x, client->rect.y, output); + floating_assign_to_workspace(client, output->current_workspace); set_focus(conn, client, true); } @@ -566,7 +566,7 @@ void render_container(xcb_connection_t *conn, Container *container) { static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int *height) { Client *client; - SLIST_FOREACH(client, &(r_ws->screen->dock_clients), dock_clients) { + SLIST_FOREACH(client, &(r_ws->output->dock_clients), dock_clients) { DLOG("client is at %d, should be at %d\n", client->rect.y, *height); if (client->force_reconfigure | update_if_necessary(&(client->rect.x), r_ws->rect.x) | @@ -586,48 +586,48 @@ static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int width, int height) { i3Font *font = load_font(conn, config.font); - i3Screen *screen = r_ws->screen; + Output *output = r_ws->output; enum { SET_NORMAL = 0, SET_FOCUSED = 1 }; /* Fill the whole bar in black */ - xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); + xcb_change_gc_single(conn, output->bargc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); xcb_rectangle_t rect = {0, 0, width, height}; - xcb_poly_fill_rectangle(conn, screen->bar, screen->bargc, 1, &rect); + xcb_poly_fill_rectangle(conn, output->bar, output->bargc, 1, &rect); /* Set font */ - xcb_change_gc_single(conn, screen->bargc, XCB_GC_FONT, font->id); + xcb_change_gc_single(conn, output->bargc, XCB_GC_FONT, font->id); int drawn = 0; Workspace *ws; TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->screen != screen) + if (ws->output != output) continue; struct Colortriple *color; - if (screen->current_workspace == ws) + if (output->current_workspace == ws) color = &(config.bar.focused); else if (ws->urgent) color = &(config.bar.urgent); else color = &(config.bar.unfocused); /* Draw the outer rect */ - xcb_draw_rect(conn, screen->bar, screen->bargc, color->border, + xcb_draw_rect(conn, output->bar, output->bargc, color->border, drawn, /* x */ 1, /* y */ 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, + xcb_draw_rect(conn, output->bar, output->bargc, color->background, drawn + 1, 2, ws->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_16(conn, ws->name_len, screen->bar, screen->bargc, drawn + 5 /* X */, + xcb_change_gc_single(conn, output->bargc, XCB_GC_FOREGROUND, color->text); + xcb_change_gc_single(conn, output->bargc, XCB_GC_BACKGROUND, color->background); + xcb_image_text_16(conn, ws->name_len, output->bar, output->bargc, drawn + 5 /* X */, font->height + 1 /* Y = baseline of font */, (xcb_char2b_t*)ws->name); drawn += ws->text_width + 12; @@ -668,14 +668,14 @@ void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace, bo * Renders the given workspace on the given screen * */ -void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws) { +void render_workspace(xcb_connection_t *conn, Output *output, Workspace *r_ws) { i3Font *font = load_font(conn, config.font); int width = r_ws->rect.width; int height = r_ws->rect.height; /* Reserve space for dock clients */ Client *client; - SLIST_FOREACH(client, &(screen->dock_clients), dock_clients) + SLIST_FOREACH(client, &(output->dock_clients), dock_clients) height -= client->desired_height; /* Space for the internal bar */ @@ -748,14 +748,11 @@ void render_workspace(xcb_connection_t *conn, i3Screen *screen, Workspace *r_ws) * */ void render_layout(xcb_connection_t *conn) { - i3Screen *screen; + Output *output; - if (virtual_screens == NULL) - return; - - TAILQ_FOREACH(screen, virtual_screens, screens) - if (screen->current_workspace != NULL) - render_workspace(conn, screen, screen->current_workspace); + TAILQ_FOREACH(output, &outputs, outputs) + if (output->current_workspace != NULL) + render_workspace(conn, output, output->current_workspace); xcb_flush(conn); } diff --git a/src/mainx.c b/src/mainx.c index f79beae2..8130c074 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -30,7 +30,6 @@ #include #include #include -#include #include @@ -45,7 +44,7 @@ #include "table.h" #include "util.h" #include "xcb.h" -#include "xinerama.h" +#include "randr.h" #include "manage.h" #include "ipc.h" #include "log.h" @@ -470,9 +469,14 @@ int main(int argc, char *argv[], char *env[]) { } } - /* check for Xinerama */ - DLOG("Checking for Xinerama...\n"); - initialize_xinerama(conn); + DLOG("Checking for XRandR...\n"); + int randr_base; + initialize_randr(conn, &randr_base); + + xcb_event_set_handler(&evenths, + randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY, + handle_screen_change, + NULL); xcb_flush(conn); @@ -483,14 +487,14 @@ int main(int argc, char *argv[], char *env[]) { return 1; } - i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y); + Output *screen = get_screen_containing(reply->root_x, reply->root_y); if (screen == NULL) { ELOG("ERROR: No screen at %d x %d, starting on the first screen\n", reply->root_x, reply->root_y); - screen = TAILQ_FIRST(virtual_screens); + screen = get_first_output(); } - DLOG("Starting on %d\n", screen->current_workspace); + DLOG("Starting on %p\n", screen->current_workspace); c_ws = screen->current_workspace; manage_existing_windows(conn, &prophs, root); diff --git a/src/manage.c b/src/manage.c index b80c94e0..d685a8d2 100644 --- a/src/manage.c +++ b/src/manage.c @@ -255,7 +255,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, new->titlebar_position = TITLEBAR_OFF; new->force_reconfigure = true; new->container = NULL; - SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients); + SLIST_INSERT_HEAD(&(c_ws->output->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; @@ -334,14 +334,14 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, LOG("Assignment \"%s\" matches, so putting it on workspace %d\n", assign->windowclass_title, assign->workspace); - if (c_ws->screen->current_workspace->num == (assign->workspace-1)) { + if (c_ws->output->current_workspace->num == (assign->workspace-1)) { DLOG("We are already there, no need to do anything\n"); break; } DLOG("Changing container/workspace and unmapping the client\n"); Workspace *t_ws = workspace_get(assign->workspace-1); - workspace_initialize(t_ws, c_ws->screen, false); + workspace_initialize(t_ws, c_ws->output, false); new->container = t_ws->table[t_ws->current_col][t_ws->current_row]; new->workspace = t_ws; diff --git a/src/randr.c b/src/randr.c new file mode 100644 index 00000000..f63b2f0c --- /dev/null +++ b/src/randr.c @@ -0,0 +1,455 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009-2010 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + * For more information on RandR, please see the X.org RandR specification at + * http://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt + * (take your time to read it completely, it answers all questions). + * + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "queue.h" +#include "i3.h" +#include "data.h" +#include "table.h" +#include "util.h" +#include "layout.h" +#include "xcb.h" +#include "config.h" +#include "workspace.h" +#include "log.h" +#include "ewmh.h" + +/* While a clean namespace is usually a pretty good thing, we really need + * to use shorter names than the whole xcb_randr_* default names. */ +typedef xcb_randr_get_crtc_info_reply_t crtc_info; +typedef xcb_randr_mode_info_t mode_info; +typedef xcb_randr_get_screen_resources_current_reply_t resources_reply; + +/* Stores all outputs available in your current session. */ +struct outputs_head outputs = TAILQ_HEAD_INITIALIZER(outputs); + +/* + * Returns true if both screen objects describe the same screen (checks their + * size and position). + * + */ +bool screens_are_equal(Output *screen1, Output *screen2) { + /* If one of both objects (or both) are NULL, we cannot compare them */ + if (screen1 == NULL || screen2 == NULL) + return false; + + /* If the pointers are equal, take the short-circuit */ + if (screen1 == screen2) + return true; + + /* Compare their size and position - other properties are not relevant + * to determine if a screen is equal to another one */ + return (memcmp(&(screen1->rect), &(screen2->rect), sizeof(Rect)) == 0); +} + +/* + * Get a specific output by its internal X11 id. Used by randr_query_screens + * to check if the output is new (only in the first scan) or if we are + * re-scanning. + * + */ +static Output *get_output_by_id(xcb_randr_output_t id) { + Output *screen; + TAILQ_FOREACH(screen, &outputs, outputs) + if (screen->id == id) + return screen; + + return NULL; +} + +/* + * Returns the first output which is active. + * + */ +Output *get_first_output() { + Output *screen; + + TAILQ_FOREACH(screen, &outputs, outputs) { + if (screen->active) + return screen; + } + + return NULL; +} + +/* + * Looks in virtual_screens for the Output which contains coordinates x, y + * + */ +Output *get_screen_containing(int x, int y) { + Output *screen; + TAILQ_FOREACH(screen, &outputs, outputs) { + if (!screen->active) + continue; + DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n", + x, y, screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height); + if (x >= screen->rect.x && x < (screen->rect.x + screen->rect.width) && + y >= screen->rect.y && y < (screen->rect.y + screen->rect.height)) + return screen; + } + + return NULL; +} + +/* + * Gets the screen which is the last one in the given direction, for example the screen + * on the most bottom when direction == D_DOWN, the screen most right when direction == D_RIGHT + * and so on. + * + * This function always returns a screen. + * + */ +Output *get_screen_most(direction_t direction, Output *current) { + Output *screen, *candidate = NULL; + int position = 0; + TAILQ_FOREACH(screen, &outputs, outputs) { + /* Repeated calls of WIN determine the winner of the comparison */ + #define WIN(variable, condition) \ + if (variable condition) { \ + candidate = screen; \ + position = variable; \ + } \ + break; + + if (((direction == D_UP) || (direction == D_DOWN)) && + (current->rect.x != screen->rect.x)) + continue; + + if (((direction == D_LEFT) || (direction == D_RIGHT)) && + (current->rect.y != screen->rect.y)) + continue; + + switch (direction) { + case D_UP: + WIN(screen->rect.y, <= position); + case D_DOWN: + WIN(screen->rect.y, >= position); + case D_LEFT: + WIN(screen->rect.x, <= position); + case D_RIGHT: + WIN(screen->rect.x, >= position); + } + } + + assert(candidate != NULL); + + return candidate; +} + +static void initialize_output(xcb_connection_t *conn, Output *output, + Workspace *workspace) { + i3Font *font = load_font(conn, config.font); + + workspace->output = output; + output->current_workspace = workspace; + + /* Create a xoutput for each output */ + Rect bar_rect = {output->rect.x, + output->rect.y + output->rect.height - (font->height + 6), + output->rect.x + output->rect.width, + font->height + 6}; + uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; + uint32_t values[] = {1, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS}; + output->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, true, mask, values); + output->bargc = xcb_generate_id(conn); + xcb_create_gc(conn, output->bargc, output->bar, 0, 0); + + SLIST_INIT(&(output->dock_clients)); + + DLOG("initialized output at (%d, %d) with %d x %d\n", + output->rect.x, output->rect.y, output->rect.width, output->rect.height); +} + +/* + * Fills virtual_screens with exactly one screen with width/height of the + * whole X screen. + * + */ +static void disable_randr(xcb_connection_t *conn) { + xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; + + DLOG("RandR extension not found, disabling.\n"); + + Output *s = scalloc(sizeof(Output)); + + s->active = true; + s->rect.x = 0; + s->rect.y = 0; + s->rect.width = root_screen->width_in_pixels; + s->rect.height = root_screen->height_in_pixels; + + TAILQ_INSERT_TAIL(&outputs, s, outputs); +} + +/* + * Searches for a mode in the current RandR configuration by the mode id. + * Returns NULL if no such mode could be found (should never happen). + * + */ +static mode_info *get_mode_by_id(resources_reply *reply, xcb_randr_mode_t mode) { + xcb_randr_mode_info_iterator_t it; + + for (it = xcb_randr_get_screen_resources_current_modes_iterator(reply); + it.rem > 0; + xcb_randr_mode_info_next(&it)) { + if (it.data->id == mode) + return it.data; + } + + return NULL; +} + +/* + * This function needs to be called when changing the mode of an output when + * it already has some workspaces (or a bar window) assigned. + * + * It reconfigures the bar window for the new mode, copies the new rect into + * each workspace on this output and forces all windows on the affected + * workspaces to be reconfigured. + * + * It is necessary to call render_layout() afterwards. + * + */ +static void output_change_mode(xcb_connection_t *conn, Output *output) { + i3Font *font = load_font(conn, config.font); + Workspace *ws; + Client *client; + + DLOG("Output mode changed, reconfiguring bar, updating workspaces\n"); + Rect bar_rect = {output->rect.x, + output->rect.y + output->rect.height - (font->height + 6), + output->rect.x + output->rect.width, + font->height + 6}; + + xcb_set_window_rect(conn, output->bar, bar_rect); + + /* go through all workspaces and set force_reconfigure */ + TAILQ_FOREACH(ws, workspaces, workspaces) { + if (ws->output != output) + continue; + + /* Update dimensions from output */ + memcpy(&(ws->rect), &(ws->output->rect), sizeof(Rect)); + + SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) + client->force_reconfigure = true; + } +} + +/* + * Gets called by randr_query_screens() for each output. The function adds new + * outputs to the list of outputs, checks if the mode of existing outputs has + * been changed or if an existing output has been disabled. + * + */ +static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, + xcb_randr_get_output_info_reply_t *output, + xcb_timestamp_t cts, resources_reply *res) { + Workspace *ws; + + /* each CRT controller has a position in which we are interested in */ + crtc_info *crtc; + + /* the CRTC runs in a specific mode, while the position is stored in + * the output itself */ + mode_info *mode; + + Output *new = get_output_by_id(id); + bool existing = (new != NULL); + if (!existing) + new = scalloc(sizeof(Output)); + new->id = id; + asprintf(&new->name, "%.*s", + xcb_randr_get_output_info_name_length(output), + xcb_randr_get_output_info_name(output)); + + DLOG("found output with name %s\n", new->name); + + /* Even if no CRTC is used at the moment, we store the output so that + * we do not need to change the list ever again (we only update the + * position/size) */ + if (output->crtc == XCB_NONE) { + if (!existing) + TAILQ_INSERT_TAIL(&outputs, new, outputs); + else if (new->active) { + new->active = false; + new->current_workspace = NULL; + DLOG("Output %s disabled (no CRTC)\n", new->name); + TAILQ_FOREACH(ws, workspaces, workspaces) { + if (ws->output != new) + continue; + + workspace_assign_to(ws, get_first_output()); + } + + } + free(output); + return; + } + + xcb_randr_get_crtc_info_cookie_t icookie; + icookie = xcb_randr_get_crtc_info(conn, output->crtc, cts); + if ((crtc = xcb_randr_get_crtc_info_reply(conn, icookie, NULL)) == NULL || + (mode = get_mode_by_id(res, crtc->mode)) == NULL) { + DLOG("Skipping output %s: could not get CRTC/mode (%p/%p)\n", + new->name, crtc, mode); + free(new); + free(output); + return; + } + + new->active = true; + bool updated = update_if_necessary(&(new->rect.x), crtc->x) | + update_if_necessary(&(new->rect.y), crtc->y) | + update_if_necessary(&(new->rect.width), mode->width) | + update_if_necessary(&(new->rect.height), mode->height); + + DLOG("mode: %dx%d+%d+%d\n", new->rect.width, new->rect.height, + new->rect.x, new->rect.y); + + /* If we don’t need to change an existing output or if the output + * does not exist in the first place, the case is simple: we either + * need to insert the new output or we are done. */ + if (!updated || !existing) { + if (!existing) + TAILQ_INSERT_TAIL(&outputs, new, outputs); + free(output); + return; + } + + output_change_mode(conn, new); +} + +/* + * (Re-)queries the outputs via RandR and stores them in the list of outputs. + * + */ +void randr_query_screens(xcb_connection_t *conn) { + xcb_randr_get_screen_resources_current_cookie_t rcookie; + resources_reply *res; + /* timestamp of the configuration so that we get consistent replies to all + * requests (if the configuration changes between our different calls) */ + xcb_timestamp_t cts; + + /* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */ + xcb_randr_output_t *randr_outputs; + + /* Get screen resources (crtcs, outputs, modes) */ + rcookie = xcb_randr_get_screen_resources_current(conn, root); + if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) + die("Could not get RandR screen resources\n"); + cts = res->config_timestamp; + + int len = xcb_randr_get_screen_resources_current_outputs_length(res); + randr_outputs = xcb_randr_get_screen_resources_current_outputs(res); + + /* Request information for each output */ + xcb_randr_get_output_info_cookie_t ocookie[len]; + for (int i = 0; i < len; i++) + ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts); + + /* Loop through all outputs available for this X11 screen */ + xcb_randr_get_output_info_reply_t *output; + for (int i = 0; i < len; i++) { + if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL) + continue; + + handle_output(conn, randr_outputs[i], output, cts, res); + } + + free(res); + Output *screen, *oscreen; + /* Check for clones and reduce the mode to the lowest common mode */ + TAILQ_FOREACH(screen, &outputs, outputs) { + if (!screen->active) + continue; + DLOG("screen %p, position (%d, %d), checking for clones\n", + screen, screen->rect.x, screen->rect.y); + + TAILQ_FOREACH(oscreen, &outputs, outputs) { + if (oscreen == screen || !oscreen->active) + continue; + + if (oscreen->rect.x != screen->rect.x || + oscreen->rect.y != screen->rect.y) + continue; + + DLOG("screen %p has the same position, his mode = %d x %d\n", + oscreen, oscreen->rect.width, oscreen->rect.height); + uint32_t width = min(oscreen->rect.width, screen->rect.width); + uint32_t height = min(oscreen->rect.height, screen->rect.height); + + if (update_if_necessary(&(screen->rect.width), width) | + update_if_necessary(&(screen->rect.height), height)) + output_change_mode(conn, screen); + + if (update_if_necessary(&(oscreen->rect.width), width) | + update_if_necessary(&(oscreen->rect.height), height)) + output_change_mode(conn, oscreen); + + + DLOG("new screen mode %d x %d, oscreen mode %d x %d\n", + screen->rect.width, screen->rect.height, + oscreen->rect.width, oscreen->rect.height); + } + } + + ewmh_update_workarea(); + + /* Just go through each workspace and associate as many screens as we can. */ + TAILQ_FOREACH(screen, &outputs, outputs) { + if (!screen->active || screen->current_workspace != NULL) + continue; + Workspace *ws = get_first_workspace_for_screen(screen); + initialize_output(conn, screen, ws); + } + + /* render_layout flushes */ + render_layout(conn); +} + +/* + * We have just established a connection to the X server and need the initial + * XRandR information to setup workspaces for each screen. + * + */ +void initialize_randr(xcb_connection_t *conn, int *event_base) { + const xcb_query_extension_reply_t *extreply; + + extreply = xcb_get_extension_data(conn, &xcb_randr_id); + if (!extreply->present) + disable_randr(conn); + else randr_query_screens(conn); + + if (event_base != NULL) + *event_base = extreply->first_event; + + xcb_randr_select_input(conn, root, + XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE | + XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE | + XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE | + XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY); + + xcb_flush(conn); +} diff --git a/src/resize.c b/src/resize.c index db1ac073..7b1f7bd8 100644 --- a/src/resize.c +++ b/src/resize.c @@ -24,7 +24,7 @@ #include "xcb.h" #include "debug.h" #include "layout.h" -#include "xinerama.h" +#include "randr.h" #include "config.h" #include "floating.h" #include "workspace.h" @@ -38,7 +38,7 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, int second, resize_orientation_t orientation, xcb_button_press_event_t *event) { int new_position; - i3Screen *screen = get_screen_containing(event->root_x, event->root_y); + struct xoutput *screen = get_screen_containing(event->root_x, event->root_y); if (screen == NULL) { ELOG("BUG: No screen found at this position (%d, %d)\n", event->root_x, event->root_y); return 1; @@ -49,7 +49,7 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i * screens during runtime. Instead, we just use the most right and most * bottom Xinerama screen and use their position + width/height to get * the area of pixels currently in use */ - i3Screen *most_right = get_screen_most(D_RIGHT, screen), + struct xoutput *most_right = get_screen_most(D_RIGHT, screen), *most_bottom = get_screen_most(D_DOWN, screen); DLOG("event->event_x = %d, event->root_x = %d\n", event->event_x, event->root_x); diff --git a/src/sighandler.c b/src/sighandler.c index b013c60d..8330232a 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -31,7 +31,7 @@ #include "xcb.h" #include "log.h" #include "config.h" -#include "xinerama.h" +#include "randr.h" static xcb_gcontext_t pixmap_gc; static xcb_pixmap_t pixmap; @@ -170,9 +170,9 @@ void handle_signal(int sig, siginfo_t *info, void *data) { int width = font_width + 20; /* Open a popup window on each virtual screen */ - i3Screen *screen; + Output *screen; xcb_window_t win; - TAILQ_FOREACH(screen, virtual_screens, screens) { + TAILQ_FOREACH(screen, &outputs, outputs) { win = open_input_window(conn, screen->rect, width, height); /* Create pixmap */ diff --git a/src/util.c b/src/util.c index f14052b0..2f8225c0 100644 --- a/src/util.c +++ b/src/util.c @@ -454,7 +454,7 @@ Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitl DLOG("Getting clients for class \"%s\" / title \"%s\"\n", to_class, to_title); Workspace *ws; TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->screen == NULL) + if (ws->output == NULL) continue; Client *client; diff --git a/src/workspace.c b/src/workspace.c index 7c29e6f7..5a4902ca 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -22,7 +22,7 @@ #include "config.h" #include "xcb.h" #include "table.h" -#include "xinerama.h" +#include "randr.h" #include "layout.h" #include "workspace.h" #include "client.h" @@ -100,7 +100,7 @@ void workspace_set_name(Workspace *ws, const char *name) { * */ bool workspace_is_visible(Workspace *ws) { - return (ws->screen->current_workspace == ws); + return (ws->output->current_workspace == ws); } /* @@ -120,22 +120,22 @@ void workspace_show(xcb_connection_t *conn, int workspace) { c_ws->current_col = current_col; /* Check if the workspace has not been used yet */ - workspace_initialize(t_ws, c_ws->screen, false); + workspace_initialize(t_ws, c_ws->output, false); - if (c_ws->screen != t_ws->screen) { - /* We need to switch to the other screen first */ - DLOG("moving over to other screen.\n"); + if (c_ws->output != t_ws->output) { + /* We need to switch to the other output first */ + DLOG("moving over to other output.\n"); /* Store the old client */ Client *old_client = CUR_CELL->currently_focused; - c_ws = t_ws->screen->current_workspace; + c_ws = t_ws->output->current_workspace; current_col = c_ws->current_col; current_row = c_ws->current_row; if (CUR_CELL->currently_focused != NULL) need_warp = true; else { - Rect *dims = &(c_ws->screen->rect); + Rect *dims = &(c_ws->output->rect); xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, dims->x + (dims->width / 2), dims->y + (dims->height / 2)); } @@ -147,7 +147,7 @@ void workspace_show(xcb_connection_t *conn, int workspace) { } /* Check if we need to change something or if we’re already there */ - if (c_ws->screen->current_workspace->num == (workspace-1)) { + if (c_ws->output->current_workspace->num == (workspace-1)) { Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); if (last_focused != SLIST_END(&(c_ws->focus_stack))) set_focus(conn, last_focused, true); @@ -160,7 +160,7 @@ void workspace_show(xcb_connection_t *conn, int workspace) { } Workspace *old_workspace = c_ws; - c_ws = t_ws->screen->current_workspace = workspace_get(workspace-1); + c_ws = t_ws->output->current_workspace = workspace_get(workspace-1); /* Unmap all clients of the old workspace */ workspace_unmap_clients(conn, old_workspace); @@ -173,7 +173,7 @@ void workspace_show(xcb_connection_t *conn, int workspace) { /* POTENTIAL TO IMPROVE HERE: due to the call to _map_clients first and * render_layout afterwards, there is a short flickering on the source - * workspace (assign ws 3 to screen 0, ws 4 to screen 1, create single + * workspace (assign ws 3 to output 0, ws 4 to output 1, create single * client on ws 4, move it to ws 3, switch to ws 3, you’ll see the * flickering). */ @@ -188,7 +188,7 @@ void workspace_show(xcb_connection_t *conn, int workspace) { /* We can warp the pointer only after the window has been * reconfigured in render_layout, otherwise the pointer will * be warped to the old position, which will not work when we - * moved it to another screen. */ + * moved it to another output. */ if (last_focused != SLIST_END(&(c_ws->focus_stack)) && need_warp) { client_warp_pointer_into(conn, last_focused); xcb_flush(conn); @@ -205,8 +205,8 @@ void workspace_show(xcb_connection_t *conn, int workspace) { * ("1280x800"). * */ -static i3Screen *get_screen_from_preference(struct screens_head *slist, char *preference) { - i3Screen *screen; +static Output *get_screen_from_preference(char *preference) { + Output *screen; char *rest; int preferred_screen = strtol(preference, &rest, 10); @@ -228,7 +228,7 @@ static i3Screen *get_screen_from_preference(struct screens_head *slist, char *pr DLOG("Looking for screen at %d x %d\n", x, y); - TAILQ_FOREACH(screen, slist, screens) + TAILQ_FOREACH(screen, &outputs, outputs) if ((x == INT_MAX || screen->rect.x == x) && (y == INT_MAX || screen->rect.y == y)) { DLOG("found %p\n", screen); @@ -239,7 +239,7 @@ static i3Screen *get_screen_from_preference(struct screens_head *slist, char *pr return NULL; } else { int c = 0; - TAILQ_FOREACH(screen, slist, screens) + TAILQ_FOREACH(screen, &outputs, outputs) if (c++ == preferred_screen) return screen; } @@ -248,37 +248,36 @@ static i3Screen *get_screen_from_preference(struct screens_head *slist, char *pr } /* - * Assigns the given workspace to the given screen by correctly updating its + * Assigns the given workspace to the given output by correctly updating its * state and reconfiguring all the clients on this workspace. * - * This is called when initializing a screen and when re-assigning it to a - * different screen which just got available (if you configured it to be on - * screen 1 and you just plugged in screen 1). + * This is called when initializing a output and when re-assigning it to a + * different output which just got available (if you configured it to be on + * output 1 and you just plugged in output 1). * */ -void workspace_assign_to(Workspace *ws, i3Screen *screen) { +void workspace_assign_to(Workspace *ws, Output *output) { Client *client; bool empty = true; - ws->screen = screen; + ws->output = output; - /* Copy the dimensions from the virtual screen */ - memcpy(&(ws->rect), &(ws->screen->rect), sizeof(Rect)); + /* Copy the dimensions from the virtual output */ + memcpy(&(ws->rect), &(ws->output->rect), sizeof(Rect)); ewmh_update_workarea(); /* Force reconfiguration for each client on that workspace */ - FOR_TABLE(ws) - CIRCLEQ_FOREACH(client, &(ws->table[cols][rows]->clients), clients) { - client->force_reconfigure = true; - empty = false; - } + SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) { + client->force_reconfigure = true; + empty = false; + } if (empty) return; /* Render the workspace to reconfigure the clients. However, they will be visible now, so… */ - render_workspace(global_conn, screen, ws); + render_workspace(global_conn, output, ws); /* …unless we want to see them at the moment, we should hide that workspace */ if (workspace_is_visible(ws)) @@ -288,7 +287,7 @@ void workspace_assign_to(Workspace *ws, i3Screen *screen) { if (c_ws == ws) { DLOG("Need to adjust c_ws...\n"); - c_ws = screen->current_workspace; + c_ws = output->current_workspace; } } @@ -299,29 +298,29 @@ void workspace_assign_to(Workspace *ws, i3Screen *screen) { * the screen is not attached at the moment. * */ -void workspace_initialize(Workspace *ws, i3Screen *screen, bool recheck) { - i3Screen *old_screen; +void workspace_initialize(Workspace *ws, Output *output, bool recheck) { + Output *old_output; - if (ws->screen != NULL && !recheck) { + if (ws->output != NULL && !recheck) { DLOG("Workspace already initialized\n"); return; } - old_screen = ws->screen; + old_output = ws->output; - /* If this workspace has no preferred screen or if the screen it wants + /* If this workspace has no preferred output or if the output it wants * to be on is not available at the moment, we initialize it with - * the screen which was given */ + * the output which was given */ if (ws->preferred_screen == NULL || - (ws->screen = get_screen_from_preference(virtual_screens, ws->preferred_screen)) == NULL) - ws->screen = screen; + (ws->output = get_screen_from_preference(ws->preferred_screen)) == NULL) + ws->output = output; - DLOG("old_screen = %p, ws->screen = %p\n", old_screen, ws->screen); + DLOG("old_output = %p, ws->output = %p\n", old_output, ws->output); /* If the assignment did not change, we do not need to update anything */ - if (old_screen != NULL && ws->screen == old_screen) + if (old_output != NULL && ws->output == old_output) return; - workspace_assign_to(ws, ws->screen); + workspace_assign_to(ws, ws->output); } /* @@ -329,13 +328,13 @@ void workspace_initialize(Workspace *ws, i3Screen *screen, bool recheck) { * the preferred_screen setting of every workspace (workspace assignments). * */ -Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen *screen) { +Workspace *get_first_workspace_for_screen(Output *output) { Workspace *result = NULL; Workspace *ws; TAILQ_FOREACH(ws, workspaces, workspaces) { if (ws->preferred_screen == NULL || - !screens_are_equal(get_screen_from_preference(slist, ws->preferred_screen), screen)) + !screens_are_equal(get_screen_from_preference(ws->preferred_screen), output)) continue; result = ws; @@ -346,7 +345,7 @@ Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen * /* No assignment found, returning first unused workspace */ Workspace *ws; TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->screen != NULL) + if (ws->output != NULL) continue; result = ws; @@ -364,7 +363,7 @@ Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen * result = workspace_get(last_ws + 1); } - workspace_initialize(result, screen, false); + workspace_initialize(result, output, false); return result; } @@ -438,14 +437,14 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) { /* Re-assign the workspace of all dock clients which use this workspace */ Client *dock; DLOG("workspace %p is empty\n", u_ws); - SLIST_FOREACH(dock, &(u_ws->screen->dock_clients), dock_clients) { + SLIST_FOREACH(dock, &(u_ws->output->dock_clients), dock_clients) { if (dock->workspace != u_ws) continue; DLOG("Re-assigning dock client to c_ws (%p)\n", c_ws); dock->workspace = c_ws; } - u_ws->screen = NULL; + u_ws->output = NULL; } /* Unmap the stack windows on the given workspace, if any */ @@ -494,7 +493,7 @@ int workspace_height(Workspace *ws) { /* Reserve space for dock clients */ Client *client; - SLIST_FOREACH(client, &(ws->screen->dock_clients), dock_clients) + SLIST_FOREACH(client, &(ws->output->dock_clients), dock_clients) height -= client->desired_height; /* Space for the internal bar */ diff --git a/src/xinerama.c b/src/xinerama.c deleted file mode 100644 index f6af933e..00000000 --- a/src/xinerama.c +++ /dev/null @@ -1,437 +0,0 @@ -/* - * vim:ts=8:expandtab - * - * i3 - an improved dynamic tiling window manager - * - * © 2009 Michael Stapelberg and contributors - * - * See file LICENSE for license information. - * - */ -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "queue.h" -#include "i3.h" -#include "data.h" -#include "table.h" -#include "util.h" -#include "xinerama.h" -#include "layout.h" -#include "xcb.h" -#include "config.h" -#include "workspace.h" -#include "log.h" - -/* This TAILQ of i3Screens stores the virtual screens, used for handling overlapping screens - * (xrandr --same-as) */ -struct screens_head *virtual_screens; - -static bool xinerama_enabled = true; - -/* - * Returns true if both screen objects describe the same screen (checks their - * size and position). - * - */ -bool screens_are_equal(i3Screen *screen1, i3Screen *screen2) { - /* If one of both objects (or both) are NULL, we cannot compare them */ - if (screen1 == NULL || screen2 == NULL) - return false; - - /* If the pointers are equal, take the short-circuit */ - if (screen1 == screen2) - return true; - - /* Compare their size and position - other properties are not relevant - * to determine if a screen is equal to another one */ - return (memcmp(&(screen1->rect), &(screen2->rect), sizeof(Rect)) == 0); -} - -/* - * Looks in virtual_screens for the i3Screen whose start coordinates are x, y - * - */ -i3Screen *get_screen_at(int x, int y, struct screens_head *screenlist) { - i3Screen *screen; - TAILQ_FOREACH(screen, screenlist, screens) - if (screen->rect.x == x && screen->rect.y == y) - return screen; - - return NULL; -} - -/* - * Looks in virtual_screens for the i3Screen which contains coordinates x, y - * - */ -i3Screen *get_screen_containing(int x, int y) { - i3Screen *screen; - TAILQ_FOREACH(screen, virtual_screens, screens) { - DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n", - x, y, screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height); - if (x >= screen->rect.x && x < (screen->rect.x + screen->rect.width) && - y >= screen->rect.y && y < (screen->rect.y + screen->rect.height)) - return screen; - } - - return NULL; -} - -/* - * Gets the screen which is the last one in the given direction, for example the screen - * on the most bottom when direction == D_DOWN, the screen most right when direction == D_RIGHT - * and so on. - * - * This function always returns a screen. - * - */ -i3Screen *get_screen_most(direction_t direction, i3Screen *current) { - i3Screen *screen, *candidate = NULL; - int position = 0; - TAILQ_FOREACH(screen, virtual_screens, screens) { - /* Repeated calls of WIN determine the winner of the comparison */ - #define WIN(variable, condition) \ - if (variable condition) { \ - candidate = screen; \ - position = variable; \ - } \ - break; - - if (((direction == D_UP) || (direction == D_DOWN)) && - (current->rect.x != screen->rect.x)) - continue; - - if (((direction == D_LEFT) || (direction == D_RIGHT)) && - (current->rect.y != screen->rect.y)) - continue; - - switch (direction) { - case D_UP: - WIN(screen->rect.y, <= position); - case D_DOWN: - WIN(screen->rect.y, >= position); - case D_LEFT: - WIN(screen->rect.x, <= position); - case D_RIGHT: - WIN(screen->rect.x, >= position); - } - } - - assert(candidate != NULL); - - return candidate; -} - -static void initialize_screen(xcb_connection_t *conn, i3Screen *screen, Workspace *workspace) { - i3Font *font = load_font(conn, config.font); - - workspace->screen = screen; - screen->current_workspace = workspace; - - /* Create a bar for each screen */ - Rect bar_rect = {screen->rect.x, - screen->rect.y + screen->rect.height - (font->height + 6), - screen->rect.x + screen->rect.width, - font->height + 6}; - uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; - uint32_t values[] = {1, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS}; - screen->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, true, mask, values); - screen->bargc = xcb_generate_id(conn); - xcb_create_gc(conn, screen->bargc, screen->bar, 0, 0); - - SLIST_INIT(&(screen->dock_clients)); - - DLOG("that is virtual screen at %d x %d with %d x %d\n", - screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height); -} - -/* - * Fills virtual_screens with exactly one screen with width/height of the whole X server. - * - */ -static void disable_xinerama(xcb_connection_t *conn) { - xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; - - i3Screen *s = calloc(sizeof(i3Screen), 1); - - s->rect.x = 0; - s->rect.y = 0; - s->rect.width = root_screen->width_in_pixels; - s->rect.height = root_screen->height_in_pixels; - - num_screens = 1; - s->num = 0; - - TAILQ_INSERT_TAIL(virtual_screens, s, screens); - - xinerama_enabled = false; -} - -/* - * Gets the Xinerama screens and converts them to virtual i3Screens (only one screen for two - * Xinerama screen which are configured in clone mode) in the given screenlist - * - */ -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); - - /* 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) < 10) { - reply = xcb_xinerama_query_screens_reply(conn, xcb_xinerama_query_screens_unchecked(conn), NULL); - if (!reply) { - ELOG("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) { - DLOG("Re-used old Xinerama screen %p\n", s); - /* 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); - DLOG("Created new Xinerama screen %p\n", s); - 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++; - } - - DLOG("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); - - if (num_screens == 0) { - ELOG("No screens found. This is weird. Trying again...\n"); - /* Give the scheduler a chance to do something else - * and don’t hog the CPU */ - usleep(250); - continue; - } - - break; - } - - if (num_screens == 0) { - ELOG("No screens found for 10 seconds. Please fix your setup. i3 will exit now.\n"); - exit(0); - } -} - -/* - * We have just established a connection to the X server and need the initial Xinerama - * information to setup workspaces for each screen. - * - */ -void initialize_xinerama(xcb_connection_t *conn) { - virtual_screens = scalloc(sizeof(struct screens_head)); - TAILQ_INIT(virtual_screens); - - if (!xcb_get_extension_data(conn, &xcb_xinerama_id)->present) { - DLOG("Xinerama extension not found, disabling.\n"); - disable_xinerama(conn); - } else { - xcb_xinerama_is_active_reply_t *reply; - reply = xcb_xinerama_is_active_reply(conn, xcb_xinerama_is_active(conn), NULL); - - if (reply == NULL || !reply->state) { - DLOG("Xinerama is not active (in your X-Server), disabling.\n"); - disable_xinerama(conn); - } else - query_screens(conn, virtual_screens); - - FREE(reply); - } - - i3Screen *screen; - num_screens = 0; - /* Just go through each workspace and associate as many screens as we can. */ - TAILQ_FOREACH(screen, virtual_screens, screens) { - screen->num = num_screens; - num_screens++; - Workspace *ws = get_first_workspace_for_screen(virtual_screens, screen); - initialize_screen(conn, screen, ws); - } -} - -/* - * This is called when the rootwindow receives a configure_notify event and therefore the - * number/position of the Xinerama screens could have changed. - * - */ -void xinerama_requery_screens(xcb_connection_t *conn) { - i3Font *font = load_font(conn, config.font); - - /* POSSIBLE PROBLEM: Is the order of the Xinerama screens always constant? That is, can - it change when I move the --right-of video projector to --left-of? */ - - if (!xinerama_enabled) { - DLOG("Xinerama is disabled\n"); - return; - } - - /* We use a separate copy to diff with the previous set of screens */ - struct screens_head *new_screens = scalloc(sizeof(struct screens_head)); - TAILQ_INIT(new_screens); - - query_screens(conn, new_screens); - - i3Screen *first = TAILQ_FIRST(new_screens), - *screen, - *old_screen; - int screen_count = 0; - /* Mark each workspace which currently is assigned to a screen, so we - * can garbage-collect afterwards */ - Workspace *ws; - TAILQ_FOREACH(ws, workspaces, workspaces) - ws->reassigned = (ws->screen == NULL); - - TAILQ_FOREACH(screen, new_screens, screens) { - screen->num = screen_count; - screen->current_workspace = NULL; - - TAILQ_FOREACH(old_screen, virtual_screens, screens) { - if (old_screen->num != screen_count) - continue; - - DLOG("Found a matching screen\n"); - /* Use the same workspace */ - screen->current_workspace = old_screen->current_workspace; - - /* Re-use the old bar window */ - screen->bar = old_screen->bar; - screen->bargc = old_screen->bargc; - DLOG("old_screen->bar = %p\n", old_screen->bar); - - Rect bar_rect = {screen->rect.x, - screen->rect.y + screen->rect.height - (font->height + 6), - screen->rect.width, - font->height + 6}; - - DLOG("configuring bar to be at %d x %d with %d x %d\n", - bar_rect.x, bar_rect.y, bar_rect.height, bar_rect.width); - xcb_configure_window(conn, screen->bar, XCB_CONFIG_WINDOW_X | - XCB_CONFIG_WINDOW_Y | - XCB_CONFIG_WINDOW_WIDTH | - XCB_CONFIG_WINDOW_HEIGHT, &(bar_rect.x)); - - /* Copy the list head for the dock clients */ - screen->dock_clients = old_screen->dock_clients; - SLIST_INIT(&(old_screen->dock_clients)); - - /* Update the dimensions */ - Workspace *ws; - TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->screen != old_screen) - continue; - - DLOG("re-assigning ws %d\n", ws->num); - memcpy(&(ws->rect), &(screen->rect), sizeof(Rect)); - ws->screen = screen; - ws->reassigned = true; - } - - break; - } - if (screen->current_workspace == NULL) { - /* Find the first unused workspace, preferring the ones - * which are assigned to this screen and initialize - * the screen with it. */ - DLOG("getting first ws for screen %p\n", screen); - Workspace *ws = get_first_workspace_for_screen(new_screens, screen); - initialize_screen(conn, screen, ws); - ws->reassigned = true; - - /* As this workspace just got visible (we got a new screen - * without workspace), we need to map its clients */ - workspace_map_clients(conn, ws); - } - screen_count++; - } - - /* check for dock_clients which are out of bounds */ - TAILQ_FOREACH(old_screen, virtual_screens, screens) { - if (SLIST_EMPTY(&(old_screen->dock_clients))) - continue; - - DLOG("dock_clients out of bounds at screen %p, reassigning\n", old_screen); - if (SLIST_EMPTY(&(first->dock_clients))) { - first->dock_clients = old_screen->dock_clients; - continue; - } - - /* We need to merge the lists */ - Client *dock_client; - - while (!SLIST_EMPTY(&(old_screen->dock_clients))) { - dock_client = SLIST_FIRST(&(old_screen->dock_clients)); - SLIST_INSERT_HEAD(&(first->dock_clients), dock_client, dock_clients); - SLIST_REMOVE_HEAD(&(old_screen->dock_clients), dock_clients); - } - } - - /* Check for workspaces which are out of bounds */ - TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->reassigned) - continue; - - DLOG("Closing bar window (%p)\n", ws->screen->bar); - xcb_destroy_window(conn, ws->screen->bar); - - DLOG("Workspace %d's screen out of bounds, assigning to first screen\n", ws->num + 1); - workspace_assign_to(ws, first); - } - - xcb_flush(conn); - - /* Free the old list */ - while (!TAILQ_EMPTY(virtual_screens)) { - screen = TAILQ_FIRST(virtual_screens); - TAILQ_REMOVE(virtual_screens, screen, screens); - free(screen); - } - free(virtual_screens); - - virtual_screens = new_screens; - - /* Check for workspaces which need to be assigned to specific screens - * which may now be available */ - TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->preferred_screen == NULL || ws->screen == NULL) - continue; - - workspace_initialize(ws, ws->screen, true); - } - - DLOG("Current workspace is now: %d\n", first->current_workspace); - - render_layout(conn); -} From c9c068c36c124cd206edd25d963895b4eb9a9941 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 2 Mar 2010 12:56:45 +0100 Subject: [PATCH 111/247] Fix parallel compilation (sometimes it failed because of wrong order in lex/yacc) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6e07d080..73a33d3e 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,7 @@ loglevels.h: done; \ echo "};") > include/loglevels.h; -src/cfgparse.yy.o: src/cfgparse.l ${HEADERS} +src/cfgparse.yy.o: src/cfgparse.l src/cfgparse.y.o ${HEADERS} echo "LEX $<" flex -i -o$(@:.o=.c) $< $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c) From aae824b1f3a17644ea73998856d5699c689fdeb4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 2 Mar 2010 13:35:43 +0100 Subject: [PATCH 112/247] Change workspace assignments to use the RandR output name instead of --- docs/userguide | 26 +++++++-------------- include/data.h | 5 ++-- include/randr.h | 13 +++++------ src/cfgparse.l | 20 +++++++++++----- src/cfgparse.y | 15 ++++-------- src/randr.c | 41 ++++++++++++++------------------ src/workspace.c | 62 ++++--------------------------------------------- 7 files changed, 59 insertions(+), 123 deletions(-) diff --git a/docs/userguide b/docs/userguide index 63f8e8ac..7f47acce 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1,7 +1,7 @@ i3 User’s Guide =============== Michael Stapelberg -August 2009 +March 2010 This document contains all information you need to configuring and using the i3 window manager. If it does not, please contact me on IRC, Jabber or E-Mail and @@ -367,25 +367,17 @@ default it will use 1 for the first screen, 2 for the second screen and so on). *Syntax*: ---------------------------------- -workspace screen +workspace output ---------------------------------- -Screen can be either a number (starting at 0 for the first screen) or a -position. When using numbers, it is not guaranteed that your screens always -get the same number. Though, unless you upgrade your X server or drivers, the -order usually stays the same. When using positions, you have to specify the -exact pixel where the screen *starts*, not a pixel which is contained by the -screen. Thus, if your first screen has the dimensions 1280x800, you can match -the second screen right of it by specifying 1280. You cannot use 1281. +The output is the name of the RandR output you attach your screen to. On a +laptop, you might have VGA1 and LVDS1 as output names. You can see the +available outputs by running +xrandr --current+. *Examples*: --------------------------- -workspace 1 screen 0 -workspace 5 screen 1 - -workspace 1 screen 1280 -workspace 2 screen x800 -workspace 3 screen 1280x800 +workspace 1 output LVDS1 +workspace 5 output VGA1 --------------------------- === Named workspaces @@ -396,10 +388,10 @@ them names (of course UTF-8 is supported): *Syntax*: --------------------------------------- workspace -workspace screen name +workspace output name --------------------------------------- -For more details about the screen-part of this command, see above. +For more details about the output-part of this command, see above. *Examples*: -------------------------- diff --git a/include/data.h b/include/data.h index 10242f20..84ea1cd2 100644 --- a/include/data.h +++ b/include/data.h @@ -206,9 +206,8 @@ struct Workspace { /** Are the floating clients on this workspace currently hidden? */ bool floating_hidden; - /** A specifier on which this workspace would like to be (if - * the screen is available). screen := | */ - char *preferred_screen; + /** The name of the RandR output this screen should be on */ + char *preferred_output; /** Temporary flag needed for re-querying xinerama screens */ bool reassigned; diff --git a/include/randr.h b/include/randr.h index 50e19938..627afdbe 100644 --- a/include/randr.h +++ b/include/randr.h @@ -17,13 +17,6 @@ TAILQ_HEAD(outputs_head, xoutput); extern struct outputs_head outputs; -/** - * Returns true if both screen objects describe the same screen (checks their - * size and position). - * - */ -bool screens_are_equal(Output *screen1, Output *screen2); - /** * We have just established a connection to the X server and need the initial * XRandR information to setup workspaces for each screen. @@ -43,6 +36,12 @@ void randr_query_screens(xcb_connection_t *conn); */ Output *get_first_output(); +/** + * Returns the output with the given name if it is active (!) or NULL. + * + */ +Output *get_output_by_name(const char *name); + /** * Looks in virtual_screens for the i3Screen which contains coordinates x, y * diff --git a/src/cfgparse.l b/src/cfgparse.l index 1982452d..67a41300 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -39,8 +39,8 @@ EOL (\r?\n) %s BIND_A2WS_COND %s ASSIGN_COND %s COLOR_COND -%s SCREEN_COND -%s SCREEN_AWS_COND +%s OUTPUT_COND +%s OUTPUT_AWS_COND %x BUFFER_LINE %% @@ -67,6 +67,7 @@ EOL (\r?\n) [^\n]+ { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR; } +[a-zA-Z0-9_-]+ { BEGIN(BIND_A2WS_COND); yylval.string = strdup(yytext); return OUTPUT; } ^[ \t]*#[^\n]* { return TOKCOMMENT; } [0-9a-fA-F]+ { yylval.string = strdup(yytext); return HEX; } [0-9]+ { yylval.number = atoi(yytext); return NUMBER; } @@ -75,7 +76,14 @@ bind { BEGIN(BIND_COND); return TOKBIND; } bindsym { BEGIN(BINDSYM_COND); return TOKBINDSYM; } floating_modifier { BEGIN(INITIAL); return TOKFLOATING_MODIFIER; } workspace { BEGIN(INITIAL); return TOKWORKSPACE; } -screen { BEGIN(SCREEN_COND); return TOKSCREEN; } +output { BEGIN(OUTPUT_COND); return TOKOUTPUT; } +screen { + /* for compatibility until v3.φ */ + ELOG("Assignments to screens are DEPRECATED and will not work. " \ + "Please replace them with assignments to outputs.\n"); + BEGIN(OUTPUT_COND); + return TOKOUTPUT; + } terminal { BEGIN(BIND_AWS_COND); return TOKTERMINAL; } font { BEGIN(BIND_AWS_COND); return TOKFONT; } assign { BEGIN(ASSIGN_COND); return TOKASSIGN; } @@ -112,15 +120,15 @@ shift { return TOKSHIFT; } {EOL} { FREE(context->line_copy); context->line_number++; + BEGIN(INITIAL); yy_push_state(BUFFER_LINE); } -x { return (int)yytext[0]; } [ \t]+ { BEGIN(BIND_AWS_COND); return WHITESPACE; } [ \t]+ { BEGIN(BINDSYM_AWS_COND); return WHITESPACE; } [ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; } [ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; } -[ \t]+ { BEGIN(SCREEN_AWS_COND); return WHITESPACE; } -[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; } +[ \t]+ { BEGIN(OUTPUT_AWS_COND); return WHITESPACE; } +[ \t]+ { BEGIN(BIND_A2WS_COND); return WHITESPACE; } [ \t]+ { return WHITESPACE; } \"[^\"]+\" { /* if ASSIGN_COND then */ diff --git a/src/cfgparse.y b/src/cfgparse.y index cc0e77cb..9ed17ea1 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -197,6 +197,7 @@ void parse_file(const char *f) { %token STR "" %token STR_NG "" %token HEX "" +%token OUTPUT "" %token TOKBIND %token TOKTERMINAL %token TOKCOMMENT "" @@ -209,7 +210,7 @@ void parse_file(const char *f) { %token TOKFLOATING_MODIFIER "floating_modifier" %token QUOTEDSTRING "" %token TOKWORKSPACE "workspace" -%token TOKSCREEN "screen" +%token TOKOUTPUT "output" %token TOKASSIGN "assign" %token TOKSET %token TOKIPCSOCKET "ipc_socket" @@ -429,14 +430,14 @@ focus_follows_mouse: ; workspace: - TOKWORKSPACE WHITESPACE NUMBER WHITESPACE TOKSCREEN WHITESPACE screen optional_workspace_name + TOKWORKSPACE WHITESPACE NUMBER WHITESPACE TOKOUTPUT WHITESPACE OUTPUT optional_workspace_name { int ws_num = $3; if (ws_num < 1) { DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); } else { Workspace *ws = workspace_get(ws_num - 1); - ws->preferred_screen = sstrdup($7); + ws->preferred_output = sstrdup($7); if ($8 != NULL) workspace_set_name(ws, $8); } @@ -447,6 +448,7 @@ workspace: if (ws_num < 1) { DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); } else { + DLOG("workspace name to: %s\n", $5); if ($5 != NULL) workspace_set_name(workspace_get(ws_num - 1), $5); } @@ -464,13 +466,6 @@ workspace_name: | WORD { $$ = $1; } ; -screen: - NUMBER { asprintf(&$$, "%d", $1); } - | NUMBER 'x' { asprintf(&$$, "%d", $1); } - | NUMBER 'x' NUMBER { asprintf(&$$, "%dx%d", $1, $3); } - | 'x' NUMBER { asprintf(&$$, "x%d", $2); } - ; - assign: TOKASSIGN WHITESPACE window_class WHITESPACE optional_arrow assign_target { diff --git a/src/randr.c b/src/randr.c index f63b2f0c..d56f01f0 100644 --- a/src/randr.c +++ b/src/randr.c @@ -44,25 +44,6 @@ typedef xcb_randr_get_screen_resources_current_reply_t resources_reply; /* Stores all outputs available in your current session. */ struct outputs_head outputs = TAILQ_HEAD_INITIALIZER(outputs); -/* - * Returns true if both screen objects describe the same screen (checks their - * size and position). - * - */ -bool screens_are_equal(Output *screen1, Output *screen2) { - /* If one of both objects (or both) are NULL, we cannot compare them */ - if (screen1 == NULL || screen2 == NULL) - return false; - - /* If the pointers are equal, take the short-circuit */ - if (screen1 == screen2) - return true; - - /* Compare their size and position - other properties are not relevant - * to determine if a screen is equal to another one */ - return (memcmp(&(screen1->rect), &(screen2->rect), sizeof(Rect)) == 0); -} - /* * Get a specific output by its internal X11 id. Used by randr_query_screens * to check if the output is new (only in the first scan) or if we are @@ -70,10 +51,24 @@ bool screens_are_equal(Output *screen1, Output *screen2) { * */ static Output *get_output_by_id(xcb_randr_output_t id) { - Output *screen; - TAILQ_FOREACH(screen, &outputs, outputs) - if (screen->id == id) - return screen; + Output *output; + TAILQ_FOREACH(output, &outputs, outputs) + if (output->id == id) + return output; + + return NULL; +} + +/* + * Returns the output with the given name if it is active (!) or NULL. + * + */ +Output *get_output_by_name(const char *name) { + Output *output; + TAILQ_FOREACH(output, &outputs, outputs) + if (output->active && + strcasecmp(output->name, name) == 0) + return output; return NULL; } diff --git a/src/workspace.c b/src/workspace.c index 5a4902ca..a579bf1d 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -195,58 +195,6 @@ void workspace_show(xcb_connection_t *conn, int workspace) { } } - -/* - * Parses the preferred_screen property of a workspace. You can either specify - * the screen number (it is not given that the screen numbering always stays - * the same) or the screen coordinates (exact coordinates, e.g. 1280 will match - * the screen starting at x=1280, but 1281 will not). For coordinates, you can - * either specify an x coordinate ("1280") or an y coordinate ("x800") or both - * ("1280x800"). - * - */ -static Output *get_screen_from_preference(char *preference) { - Output *screen; - char *rest; - int preferred_screen = strtol(preference, &rest, 10); - - DLOG("Getting screen for preference \"%s\" (%d)\n", preference, preferred_screen); - - if ((rest == preference) || (preferred_screen >= num_screens)) { - int x = INT_MAX, y = INT_MAX; - if (strchr(preference, 'x') != NULL) { - /* Check if only the y coordinate was specified */ - if (*preference == 'x') - y = atoi(preference+1); - else { - x = atoi(preference); - y = atoi(strchr(preference, 'x') + 1); - } - } else { - x = atoi(preference); - } - - DLOG("Looking for screen at %d x %d\n", x, y); - - TAILQ_FOREACH(screen, &outputs, outputs) - if ((x == INT_MAX || screen->rect.x == x) && - (y == INT_MAX || screen->rect.y == y)) { - DLOG("found %p\n", screen); - return screen; - } - - DLOG("none found\n"); - return NULL; - } else { - int c = 0; - TAILQ_FOREACH(screen, &outputs, outputs) - if (c++ == preferred_screen) - return screen; - } - - return NULL; -} - /* * Assigns the given workspace to the given output by correctly updating its * state and reconfiguring all the clients on this workspace. @@ -311,8 +259,8 @@ void workspace_initialize(Workspace *ws, Output *output, bool recheck) { /* If this workspace has no preferred output or if the output it wants * to be on is not available at the moment, we initialize it with * the output which was given */ - if (ws->preferred_screen == NULL || - (ws->output = get_screen_from_preference(ws->preferred_screen)) == NULL) + if (ws->preferred_output == NULL || + (ws->output = get_output_by_name(ws->preferred_output)) == NULL) ws->output = output; DLOG("old_output = %p, ws->output = %p\n", old_output, ws->output); @@ -325,7 +273,7 @@ void workspace_initialize(Workspace *ws, Output *output, bool recheck) { /* * Gets the first unused workspace for the given screen, taking into account - * the preferred_screen setting of every workspace (workspace assignments). + * the preferred_output setting of every workspace (workspace assignments). * */ Workspace *get_first_workspace_for_screen(Output *output) { @@ -333,8 +281,8 @@ Workspace *get_first_workspace_for_screen(Output *output) { Workspace *ws; TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->preferred_screen == NULL || - !screens_are_equal(get_screen_from_preference(ws->preferred_screen), output)) + if (ws->preferred_output == NULL || + get_output_by_name(ws->preferred_output) != output) continue; result = ws; From d08ec003294481f5b2299099779fef623240bcfa Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 2 Mar 2010 14:42:24 +0100 Subject: [PATCH 113/247] use scalloc instead of some places where calloc was still used --- src/ipc.c | 4 ++-- src/manage.c | 2 +- src/table.c | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ipc.c b/src/ipc.c index b4356fda..f7aeccf4 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -201,13 +201,13 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) { set_nonblock(client); - struct ev_io *package = calloc(sizeof(struct ev_io), 1); + struct ev_io *package = scalloc(sizeof(struct ev_io)); ev_io_init(package, ipc_receive_message, client, EV_READ); ev_io_start(EV_A_ package); DLOG("IPC: new client connected\n"); - struct ipc_client *new = calloc(sizeof(struct ipc_client), 1); + struct ipc_client *new = scalloc(sizeof(struct ipc_client)); new->fd = client; TAILQ_INSERT_TAIL(&all_clients, new, clients); diff --git a/src/manage.c b/src/manage.c index d685a8d2..93ae4865 100644 --- a/src/manage.c +++ b/src/manage.c @@ -160,7 +160,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, LOG("Managing window 0x%08x\n", child); DLOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height); - new = calloc(sizeof(Client), 1); + new = scalloc(sizeof(Client)); new->force_reconfigure = true; /* Update the data structures */ diff --git a/src/table.c b/src/table.c index 1660d308..71081013 100644 --- a/src/table.c +++ b/src/table.c @@ -53,7 +53,7 @@ void init_table() { static void new_container(Workspace *workspace, Container **container, int col, int row, bool skip_layout_switch) { Container *new; - new = *container = calloc(sizeof(Container), 1); + new = *container = scalloc(sizeof(Container)); CIRCLEQ_INIT(&(new->clients)); new->colspan = 1; new->rowspan = 1; @@ -131,7 +131,7 @@ void expand_table_cols(Workspace *workspace) { workspace->width_factor[workspace->cols-1] = 0; workspace->table = realloc(workspace->table, sizeof(Container**) * workspace->cols); - workspace->table[workspace->cols-1] = calloc(sizeof(Container*) * workspace->rows, 1); + workspace->table[workspace->cols-1] = scalloc(sizeof(Container*) * workspace->rows); for (int c = 0; c < workspace->rows; c++) new_container(workspace, &(workspace->table[workspace->cols-1][c]), workspace->cols-1, c, true); @@ -158,7 +158,7 @@ void expand_table_cols_at_head(Workspace *workspace) { workspace->width_factor[0] = 0; workspace->table = realloc(workspace->table, sizeof(Container**) * workspace->cols); - workspace->table[workspace->cols-1] = calloc(sizeof(Container*) * workspace->rows, 1); + workspace->table[workspace->cols-1] = scalloc(sizeof(Container*) * workspace->rows); /* Move the other columns */ for (int rows = 0; rows < workspace->rows; rows++) From b53c5861a235851b53eb3a3126d9f9f0f97d234c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 2 Mar 2010 15:25:08 +0100 Subject: [PATCH 114/247] Restore geometry of all windows before exiting/restarting (Thanks Sasha) This fixes ticket #185 --- include/manage.h | 10 ++++++++++ src/commands.c | 2 ++ src/manage.c | 22 ++++++++++++++++++++++ src/util.c | 3 +++ 4 files changed, 37 insertions(+) diff --git a/include/manage.h b/include/manage.h index 65542d91..9c87a08e 100644 --- a/include/manage.h +++ b/include/manage.h @@ -23,6 +23,16 @@ void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root); +/** + * Restores the geometry of each window by reparenting it to the root window + * at the position of its frame. + * + * This is to be called *only* before exiting/restarting i3 because of evil + * side-effects which are to be expected when continuing to run i3. + * + */ +void restore_geometry(xcb_connection_t *conn); + /** * Do some sanity checks and then reparent the window. * diff --git a/src/commands.c b/src/commands.c index cdd82513..3aab9b82 100644 --- a/src/commands.c +++ b/src/commands.c @@ -32,6 +32,7 @@ #include "resize.h" #include "log.h" #include "sighandler.h" +#include "manage.h" bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) { /* If this container is empty, we’re done */ @@ -977,6 +978,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { /* Is it an ? */ if (STARTS_WITH(command, "exit")) { LOG("User issued exit-command, exiting without error.\n"); + restore_geometry(global_conn); exit(EXIT_SUCCESS); } diff --git a/src/manage.c b/src/manage.c index 93ae4865..c4118641 100644 --- a/src/manage.c +++ b/src/manage.c @@ -63,6 +63,28 @@ void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *pr free(cookies); } +/* + * Restores the geometry of each window by reparenting it to the root window + * at the position of its frame. + * + * This is to be called *only* before exiting/restarting i3 because of evil + * side-effects which are to be expected when continuing to run i3. + * + */ +void restore_geometry(xcb_connection_t *conn) { + Workspace *ws; + Client *client; + DLOG("Restoring geometry\n"); + + TAILQ_FOREACH(ws, workspaces, workspaces) + SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) + xcb_reparent_window(conn, client->child, root, + client->rect.x, client->rect.y); + + /* Make sure our changes reach the X server, we restart/exit now */ + xcb_flush(conn); +} + /* * Do some sanity checks and then reparent the window. * diff --git a/src/util.c b/src/util.c index 2f8225c0..a25a9dcf 100644 --- a/src/util.c +++ b/src/util.c @@ -33,6 +33,7 @@ #include "client.h" #include "log.h" #include "ewmh.h" +#include "manage.h" static iconv_t conversion_descriptor = 0; struct keyvalue_table_head by_parent = TAILQ_HEAD_INITIALIZER(by_parent); @@ -504,6 +505,8 @@ static char **append_argument(char **original, char *argument) { * */ void i3_restart() { + restore_geometry(global_conn); + LOG("restarting \"%s\"...\n", start_argv[0]); /* make sure -a is in the argument list or append it */ start_argv = append_argument(start_argv, "-a"); From 4e69bd65c03d0f8787d9d6731cd1a97da42270f8 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 2 Mar 2010 15:30:23 +0100 Subject: [PATCH 115/247] Move autostart after creating the IPC socket in start process (Thanks Sasha) This fixes ticket #179. --- src/mainx.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/mainx.c b/src/mainx.c index 8130c074..00ecd432 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -460,15 +460,6 @@ int main(int argc, char *argv[], char *env[]) { grab_all_keys(conn); - /* Autostarting exec-lines */ - struct Autostart *exec; - if (autostart) { - TAILQ_FOREACH(exec, &autostarts, autostarts) { - LOG("auto-starting %s\n", exec->command); - start_application(exec->command); - } - } - DLOG("Checking for XRandR...\n"); int randr_base; initialize_randr(conn, &randr_base); @@ -517,6 +508,16 @@ int main(int argc, char *argv[], char *env[]) { setup_signal_handler(); /* Ungrab the server to receive events and enter libev’s eventloop */ xcb_ungrab_server(conn); + + /* Autostarting exec-lines */ + struct Autostart *exec; + if (autostart) { + TAILQ_FOREACH(exec, &autostarts, autostarts) { + LOG("auto-starting %s\n", exec->command); + start_application(exec->command); + } + } + ev_loop(loop, 0); /* not reached */ From e3e7ebe23ae5db9ab5c4959e08ecdac16448a7bd Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 2 Mar 2010 15:45:03 +0100 Subject: [PATCH 116/247] Bugfix: only return active screens in get_screen_most --- src/randr.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/randr.c b/src/randr.c index d56f01f0..cc9efb25 100644 --- a/src/randr.c +++ b/src/randr.c @@ -119,6 +119,9 @@ Output *get_screen_most(direction_t direction, Output *current) { Output *screen, *candidate = NULL; int position = 0; TAILQ_FOREACH(screen, &outputs, outputs) { + if (!screen->active) + continue; + /* Repeated calls of WIN determine the winner of the comparison */ #define WIN(variable, condition) \ if (variable condition) { \ From 5dbcb0158f24191ecc07990902433cacc68659bf Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 2 Mar 2010 15:45:48 +0100 Subject: [PATCH 117/247] When in fullscreen mode, focus whole screens instead of denying to focus (Thanks dothebart) This fixes ticket #169. --- src/commands.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands.c b/src/commands.c index 3aab9b82..f4bc04be 100644 --- a/src/commands.c +++ b/src/commands.c @@ -100,8 +100,8 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t assert(container != NULL); if (container->workspace->fullscreen_client != NULL) { - LOG("You're in fullscreen mode. Won't switch focus\n"); - return; + LOG("You're in fullscreen mode. Forcing focus to operate on whole screens\n"); + thing = THING_SCREEN; } /* For focusing screens, situation is different: we get the rect From 296a0078adb4b6c24b24faefc3dde72fbc827fba Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 2 Mar 2010 18:56:57 +0100 Subject: [PATCH 118/247] debian: require libxcb-randr0-dev instead of libxcb-xinerama0-dev --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 7ea7ad66..5fb494b4 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Section: utils Priority: extra Maintainer: Michael Stapelberg DM-Upload-Allowed: yes -Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison +Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-randr0-dev, libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison Standards-Version: 3.8.3 Homepage: http://i3.zekjur.net/ From 7839b7a4fe8a24d2fc7ccc3a620a793bb87b2608 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 3 Mar 2010 01:20:56 +0100 Subject: [PATCH 119/247] Bugfix: checked for wrong flag in size hints --- src/handlers.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers.c b/src/handlers.c index bc484c92..b7b10a74 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -880,7 +880,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w /* base_width/height are the desired size of the window. We check if either the program-specified size or the program-specified min-size is available */ - if (size_hints.flags & XCB_SIZE_HINT_P_SIZE) { + if (size_hints.flags & XCB_SIZE_HINT_BASE_SIZE) { base_width = size_hints.base_width; base_height = size_hints.base_height; } else if (size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE) { From e5f222e03ec4922c345b9b58909e0c0a2894baf1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 3 Mar 2010 01:22:39 +0100 Subject: [PATCH 120/247] Save a resize_client() when handling the size hints --- src/handlers.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index b7b10a74..66deceff 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -852,12 +852,11 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w if ((size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE)) { // TODO: Minimum size is not yet implemented - //LOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height); + DLOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height); } + bool changed = false; if ((size_hints.flags & XCB_SIZE_HINT_P_RESIZE_INC)) { - bool changed = false; - if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF) if (client->width_increment != size_hints.width_inc) { client->width_increment = size_hints.width_inc; @@ -869,10 +868,8 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w changed = true; } - if (changed) { - resize_client(conn, client); - xcb_flush(conn); - } + if (changed) + DLOG("resize increments changed\n"); } int base_width = 0, base_height = 0; @@ -884,6 +881,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w base_width = size_hints.base_width; base_height = size_hints.base_height; } else if (size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE) { + /* TODO: is this right? icccm says not */ base_width = size_hints.min_width; base_height = size_hints.min_height; } @@ -893,10 +891,17 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w client->base_width = base_width; client->base_height = base_height; DLOG("client's base_height changed to %d\n", base_height); + DLOG("client's base_width changed to %d\n", base_width); + changed = true; + } + + if (changed) { if (client->fullscreen) DLOG("Not resizing client, it is in fullscreen mode\n"); - else + else { resize_client(conn, client); + xcb_flush(conn); + } } /* If no aspect ratio was set or if it was invalid, we ignore the hints */ From 187c72fa96c90c90dee1d19da07b1725bc897df9 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 3 Mar 2010 08:48:57 +0100 Subject: [PATCH 121/247] sighandler: only display on active outputs --- src/sighandler.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sighandler.c b/src/sighandler.c index 8330232a..590608b6 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -173,6 +173,8 @@ void handle_signal(int sig, siginfo_t *info, void *data) { Output *screen; xcb_window_t win; TAILQ_FOREACH(screen, &outputs, outputs) { + if (!screen->active) + continue; win = open_input_window(conn, screen->rect, width, height); /* Create pixmap */ From 0ec3481c697e5fb6c9392efee630f7ce47905c93 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 3 Mar 2010 08:49:07 +0100 Subject: [PATCH 122/247] draw consistent borders for each frame in a tabbed/stacked container The condition was a relict from older rendering code, I think. This fixes #172. --- src/layout.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/layout.c b/src/layout.c index c189a950..0e1eecb1 100644 --- a/src/layout.c +++ b/src/layout.c @@ -172,13 +172,11 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw if (client->titlebar_position != TITLEBAR_OFF) { /* Draw the lines */ xcb_draw_line(conn, drawable, gc, color->border, offset_x, offset_y, offset_x + client->rect.width, offset_y); - if (mode == MODE_DEFAULT || - CIRCLEQ_NEXT_OR_NULL(&(client->container->clients), client, clients) == NULL) - xcb_draw_line(conn, drawable, gc, color->border, - offset_x + 2, /* x */ - offset_y + font->height + 3, /* y */ - offset_x + client->rect.width - 3, /* to_x */ - offset_y + font->height + 3 /* to_y */); + xcb_draw_line(conn, drawable, gc, color->border, + offset_x + 2, /* x */ + offset_y + font->height + 3, /* y */ + offset_x + client->rect.width - 3, /* to_x */ + offset_y + font->height + 3 /* to_y */); } /* If the client has a title, we draw it */ From 3cfe1b35e0b4ac3a0a83eb631bd0243c42081684 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 3 Mar 2010 09:32:31 +0100 Subject: [PATCH 123/247] Update fullscreen client position/size when an output changes (Thanks Merovius) This fixes #187. --- src/randr.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/randr.c b/src/randr.c index cc9efb25..500ce5d5 100644 --- a/src/randr.c +++ b/src/randr.c @@ -252,6 +252,18 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) { SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) client->force_reconfigure = true; + + /* Update the dimensions of a fullscreen client, if any */ + if (ws->fullscreen_client != NULL) { + DLOG("Updating fullscreen client size\n"); + client = ws->fullscreen_client; + Rect r = ws->rect; + xcb_set_window_rect(conn, client->frame, r); + + r.x = 0; + r.y = 0; + xcb_set_window_rect(conn, client->child, r); + } } } From 5dd59562414f5ad1a63365371cf9ba62efcf4f52 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 3 Mar 2010 10:08:17 +0100 Subject: [PATCH 124/247] i3-input: Bugfix: repeatedly grab the keyboard if it does not succeed This should fix ticket #175 --- i3-input/main.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/i3-input/main.c b/i3-input/main.c index 8cfc1e84..97976396 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -306,11 +306,33 @@ int main(int argc, char *argv[]) { xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font_height + 8); xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); + /* Set input focus (we have override_redirect=1, so the wm will not do + * this for us) */ + xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME); + /* Create graphics context */ xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id); /* Grab the keyboard to get all input */ - xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); + xcb_flush(conn); + + /* Try (repeatedly, if necessary) to grab the keyboard. We might not + * get the keyboard at the first attempt because of the keybinding + * still being active when started via a wm’s keybinding. */ + xcb_grab_keyboard_cookie_t cookie; + xcb_grab_keyboard_reply_t *reply = NULL; + + int count = 0; + while ((reply == NULL || reply->status != XCB_GRAB_STATUS_SUCCESS) && (count++ < 500)) { + cookie = xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); + reply = xcb_grab_keyboard_reply(conn, cookie, NULL); + usleep(1000); + } + + if (reply->status != XCB_GRAB_STATUS_SUCCESS) { + fprintf(stderr, "Could not grab keyboard, status = %d\n", reply->status); + exit(-1); + } xcb_flush(conn); From 85437877e80b8b93820dd0b8c13d866428390319 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 5 Mar 2010 00:09:52 +0100 Subject: [PATCH 125/247] fix comment (Thanks Merovius) --- src/randr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/randr.c b/src/randr.c index 500ce5d5..eb7dce90 100644 --- a/src/randr.c +++ b/src/randr.c @@ -427,7 +427,7 @@ void randr_query_screens(xcb_connection_t *conn) { ewmh_update_workarea(); - /* Just go through each workspace and associate as many screens as we can. */ + /* Just go through each active output and associate one workspace */ TAILQ_FOREACH(screen, &outputs, outputs) { if (!screen->active || screen->current_workspace != NULL) continue; From d73f1748dbd0d237d5817af93f5152a875cc03c1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 5 Mar 2010 01:59:52 +0100 Subject: [PATCH 126/247] Remove superfluous definitions (Thanks Merovius) --- src/workspace.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/workspace.c b/src/workspace.c index a579bf1d..70b034ab 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -291,7 +291,6 @@ Workspace *get_first_workspace_for_screen(Output *output) { if (result == NULL) { /* No assignment found, returning first unused workspace */ - Workspace *ws; TAILQ_FOREACH(ws, workspaces, workspaces) { if (ws->output != NULL) continue; @@ -304,7 +303,6 @@ Workspace *get_first_workspace_for_screen(Output *output) { if (result == NULL) { DLOG("No existing free workspace found to assign, creating a new one\n"); - Workspace *ws; int last_ws = 0; TAILQ_FOREACH(ws, workspaces, workspaces) last_ws = ws->num; From 718d62a3cd04795752391e19315a886848d2e302 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 5 Mar 2010 14:32:48 +0100 Subject: [PATCH 127/247] Bugfix: Fix clone mode with new RandR code (Thanks Merovius) --- include/data.h | 5 ++++ src/randr.c | 79 +++++++++++++++++++++++++++++++++++-------------- src/workspace.c | 7 +++-- 3 files changed, 65 insertions(+), 26 deletions(-) diff --git a/include/data.h b/include/data.h index 84ea1cd2..2cc8362f 100644 --- a/include/data.h +++ b/include/data.h @@ -513,6 +513,11 @@ struct xoutput { * mode) */ bool active; + /** Internal flags, necessary for querying RandR screens (happens in + * two stages) */ + bool changed; + bool to_be_disabled; + /** Current workspace selected on this virtual screen */ Workspace *current_workspace; diff --git a/src/randr.c b/src/randr.c index eb7dce90..ac41e34f 100644 --- a/src/randr.c +++ b/src/randr.c @@ -270,14 +270,14 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) { /* * Gets called by randr_query_screens() for each output. The function adds new * outputs to the list of outputs, checks if the mode of existing outputs has - * been changed or if an existing output has been disabled. + * been changed or if an existing output has been disabled. It will then change + * either the "changed" or the "to_be_deleted" flag of the output, if + * appropriate. * */ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, xcb_randr_get_output_info_reply_t *output, xcb_timestamp_t cts, resources_reply *res) { - Workspace *ws; - /* each CRT controller has a position in which we are interested in */ crtc_info *crtc; @@ -303,15 +303,7 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, if (!existing) TAILQ_INSERT_TAIL(&outputs, new, outputs); else if (new->active) { - new->active = false; - new->current_workspace = NULL; - DLOG("Output %s disabled (no CRTC)\n", new->name); - TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->output != new) - continue; - - workspace_assign_to(ws, get_first_output()); - } + new->to_be_disabled = true; } free(output); @@ -348,7 +340,7 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, return; } - output_change_mode(conn, new); + new->changed = true; } /* @@ -356,6 +348,7 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, * */ void randr_query_screens(xcb_connection_t *conn) { + Workspace *ws; xcb_randr_get_screen_resources_current_cookie_t rcookie; resources_reply *res; /* timestamp of the configuration so that we get consistent replies to all @@ -380,8 +373,9 @@ void randr_query_screens(xcb_connection_t *conn) { ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts); /* Loop through all outputs available for this X11 screen */ - xcb_randr_get_output_info_reply_t *output; for (int i = 0; i < len; i++) { + xcb_randr_get_output_info_reply_t *output; + if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL) continue; @@ -390,15 +384,17 @@ void randr_query_screens(xcb_connection_t *conn) { free(res); Output *screen, *oscreen; - /* Check for clones and reduce the mode to the lowest common mode */ + /* Check for clones and reduce the mode to the lowest common mode */ TAILQ_FOREACH(screen, &outputs, outputs) { - if (!screen->active) + if (!screen->active || screen->to_be_disabled) continue; DLOG("screen %p, position (%d, %d), checking for clones\n", screen, screen->rect.x, screen->rect.y); - TAILQ_FOREACH(oscreen, &outputs, outputs) { - if (oscreen == screen || !oscreen->active) + for (oscreen = screen; + oscreen != TAILQ_END(&outputs); + oscreen = TAILQ_NEXT(oscreen, outputs)) { + if (oscreen == screen || !oscreen->active || oscreen->to_be_disabled) continue; if (oscreen->rect.x != screen->rect.x || @@ -412,12 +408,13 @@ void randr_query_screens(xcb_connection_t *conn) { if (update_if_necessary(&(screen->rect.width), width) | update_if_necessary(&(screen->rect.height), height)) - output_change_mode(conn, screen); + screen->changed = true; - if (update_if_necessary(&(oscreen->rect.width), width) | - update_if_necessary(&(oscreen->rect.height), height)) - output_change_mode(conn, oscreen); + update_if_necessary(&(oscreen->rect.width), width); + update_if_necessary(&(oscreen->rect.height), height); + DLOG("disabling screen %p (%s)\n", oscreen, oscreen->name); + oscreen->to_be_disabled = true; DLOG("new screen mode %d x %d, oscreen mode %d x %d\n", screen->rect.width, screen->rect.height, @@ -425,13 +422,49 @@ void randr_query_screens(xcb_connection_t *conn) { } } + Output *output, *first; + TAILQ_FOREACH(output, &outputs, outputs) { + if (output->to_be_disabled) { + output->active = false; + DLOG("Output %s disabled, re-assigning workspaces/docks\n", output->name); + + if ((first = get_first_output()) == NULL) + die("No usable outputs available\n"); + + bool needs_init = (first->current_workspace == NULL); + + TAILQ_FOREACH(ws, workspaces, workspaces) { + if (ws->output != output) + continue; + + workspace_assign_to(ws, first); + if (!needs_init) + continue; + initialize_output(conn, first, ws); + needs_init = false; + } + + Client *dock; + while (!SLIST_EMPTY(&(output->dock_clients))) { + dock = SLIST_FIRST(&(output->dock_clients)); + SLIST_INSERT_HEAD(&(first->dock_clients), dock, dock_clients); + SLIST_REMOVE_HEAD(&(output->dock_clients), dock_clients); + } + output->current_workspace = NULL; + output->to_be_disabled = false; + } else if (output->changed) { + output_change_mode(conn, output); + output->changed = false; + } + } + ewmh_update_workarea(); /* Just go through each active output and associate one workspace */ TAILQ_FOREACH(screen, &outputs, outputs) { if (!screen->active || screen->current_workspace != NULL) continue; - Workspace *ws = get_first_workspace_for_screen(screen); + ws = get_first_workspace_for_screen(screen); initialize_output(conn, screen, ws); } diff --git a/src/workspace.c b/src/workspace.c index 70b034ab..ea1134b6 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -207,6 +207,7 @@ void workspace_show(xcb_connection_t *conn, int workspace) { void workspace_assign_to(Workspace *ws, Output *output) { Client *client; bool empty = true; + bool visible = workspace_is_visible(ws); ws->output = output; @@ -228,14 +229,14 @@ void workspace_assign_to(Workspace *ws, Output *output) { render_workspace(global_conn, output, ws); /* …unless we want to see them at the moment, we should hide that workspace */ - if (workspace_is_visible(ws)) + if (visible) return; workspace_unmap_clients(global_conn, ws); if (c_ws == ws) { - DLOG("Need to adjust c_ws...\n"); - c_ws = output->current_workspace; + DLOG("Need to adjust output->current_workspace...\n"); + output->current_workspace = c_ws; } } From 8b192ac7ed1bab2f0da1099fae24b627f9597bf0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 5 Mar 2010 15:22:12 +0100 Subject: [PATCH 128/247] Bugfix: Correctly hide/show workspaces when enabling new outputs, correctly handle focus (Thanks Merovius) --- include/workspace.h | 2 +- src/handlers.c | 18 ++++++++++++++++++ src/randr.c | 8 +++++++- src/workspace.c | 6 +++--- 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/include/workspace.h b/include/workspace.h index f22ca8e4..1d280983 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -53,7 +53,7 @@ void workspace_show(xcb_connection_t *conn, int workspace); * screen 1 and you just plugged in screen 1). * */ -void workspace_assign_to(Workspace *ws, Output *screen); +void workspace_assign_to(Workspace *ws, Output *screen, bool hide_it); /** * Initializes the given workspace if it is not already initialized. The given diff --git a/src/handlers.c b/src/handlers.c index 66deceff..da5697c1 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -179,6 +179,24 @@ static void check_crossing_screen_boundary(uint32_t x, uint32_t y) { current_row = c_ws->current_row; current_col = c_ws->current_col; DLOG("We're now on output %p\n", output); + + /* While usually this function is only called when the user switches + * to a different output using his mouse (and thus the output is + * empty), it may be that the following race condition occurs: + * 1) the user actives a new output (say VGA1). + * 2) the cursor is sent to the first pixel of the new VGA1, thus + * generating an enter_notify for the screen (the enter_notify + * is not yet received by i3). + * 3) i3 requeries screen configuration and maps a workspace onto the + * new output. + * 4) the enter_notify event arrives and c_ws is set to the new + * workspace but the existing windows on the new workspace are not + * focused. + * + * Therefore, we re-set the focus here to be sure it’s correct. */ + Client *first_client = SLIST_FIRST(&(c_ws->focus_stack)); + if (first_client != NULL) + set_focus(global_conn, first_client, true); } /* diff --git a/src/randr.c b/src/randr.c index ac41e34f..2d890efc 100644 --- a/src/randr.c +++ b/src/randr.c @@ -162,6 +162,12 @@ static void initialize_output(xcb_connection_t *conn, Output *output, workspace->output = output; output->current_workspace = workspace; + /* Copy rect for the workspace */ + memcpy(&(workspace->rect), &(output->rect), sizeof(Rect)); + + /* Map clients on the workspace, if any */ + workspace_map_clients(conn, workspace); + /* Create a xoutput for each output */ Rect bar_rect = {output->rect.x, output->rect.y + output->rect.height - (font->height + 6), @@ -437,7 +443,7 @@ void randr_query_screens(xcb_connection_t *conn) { if (ws->output != output) continue; - workspace_assign_to(ws, first); + workspace_assign_to(ws, first, true); if (!needs_init) continue; initialize_output(conn, first, ws); diff --git a/src/workspace.c b/src/workspace.c index ea1134b6..ef963a76 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -204,7 +204,7 @@ void workspace_show(xcb_connection_t *conn, int workspace) { * output 1 and you just plugged in output 1). * */ -void workspace_assign_to(Workspace *ws, Output *output) { +void workspace_assign_to(Workspace *ws, Output *output, bool hide_it) { Client *client; bool empty = true; bool visible = workspace_is_visible(ws); @@ -229,7 +229,7 @@ void workspace_assign_to(Workspace *ws, Output *output) { render_workspace(global_conn, output, ws); /* …unless we want to see them at the moment, we should hide that workspace */ - if (visible) + if (visible && !hide_it) return; workspace_unmap_clients(global_conn, ws); @@ -269,7 +269,7 @@ void workspace_initialize(Workspace *ws, Output *output, bool recheck) { if (old_output != NULL && ws->output == old_output) return; - workspace_assign_to(ws, ws->output); + workspace_assign_to(ws, ws->output, false); } /* From 8d648b4e371cd7959a332d80aa288a3fa390cc7e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 5 Mar 2010 16:18:41 +0100 Subject: [PATCH 129/247] Update function names, variable names and documentation for the RandR changes --- include/randr.h | 15 ++--- include/workspace.h | 2 +- src/commands.c | 18 +++--- src/handlers.c | 4 +- src/layout.c | 2 +- src/mainx.c | 2 +- src/randr.c | 149 ++++++++++++++++++++++++-------------------- src/resize.c | 8 +-- src/workspace.c | 2 +- 9 files changed, 107 insertions(+), 95 deletions(-) diff --git a/include/randr.h b/include/randr.h index 627afdbe..35e5f79b 100644 --- a/include/randr.h +++ b/include/randr.h @@ -28,7 +28,7 @@ void initialize_randr(xcb_connection_t *conn, int *event_base); * (Re-)queries the outputs via RandR and stores them in the list of outputs. * */ -void randr_query_screens(xcb_connection_t *conn); +void randr_query_outputs(xcb_connection_t *conn); /** * Returns the first output which is active. @@ -43,19 +43,20 @@ Output *get_first_output(); Output *get_output_by_name(const char *name); /** - * Looks in virtual_screens for the i3Screen which contains coordinates x, y + * Returns the active (!) output which contains the coordinates x, y or NULL + * if there is no output which contains these coordinates. * */ -Output *get_screen_containing(int x, int y); +Output *get_output_containing(int x, int y); /** - * Gets the screen which is the last one in the given direction, for example - * the screen on the most bottom when direction == D_DOWN, the screen most + * Gets the output which is the last one in the given direction, for example + * the output on the most bottom when direction == D_DOWN, the output most * right when direction == D_RIGHT and so on. * - * This function always returns a screen. + * This function always returns a output. * */ -Output *get_screen_most(direction_t direction, Output *current); +Output *get_output_most(direction_t direction, Output *current); #endif diff --git a/include/workspace.h b/include/workspace.h index 1d280983..dae245ce 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -69,7 +69,7 @@ void workspace_initialize(Workspace *ws, Output *screen, bool recheck); * the preferred_screen setting of every workspace (workspace assignments). * */ -Workspace *get_first_workspace_for_screen(Output *screen); +Workspace *get_first_workspace_for_output(Output *screen); /** * Unmaps all clients (and stack windows) of the given workspace. diff --git a/src/commands.c b/src/commands.c index f4bc04be..4317b36f 100644 --- a/src/commands.c +++ b/src/commands.c @@ -121,17 +121,17 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t bounds.y -= bounds.height; else bounds.y += bounds.height; - Output *target = get_screen_containing(bounds.x, bounds.y); + Output *target = get_output_containing(bounds.x, bounds.y); if (target == NULL) { DLOG("Target output NULL\n"); /* Wrap around if the target screen is out of bounds */ if (direction == D_RIGHT) - target = get_screen_most(D_LEFT, cs); + target = get_output_most(D_LEFT, cs); else if (direction == D_LEFT) - target = get_screen_most(D_RIGHT, cs); + target = get_output_most(D_RIGHT, cs); else if (direction == D_UP) - target = get_screen_most(D_DOWN, cs); - else target = get_screen_most(D_UP, cs); + target = get_output_most(D_DOWN, cs); + else target = get_output_most(D_UP, cs); } DLOG("Switching to ws %d\n", target->current_workspace + 1); @@ -165,10 +165,10 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t DLOG("container is at %d with height %d\n", container->y, container->height); Output *output; int destination_y = (direction == D_UP ? (container->y - 1) : (container->y + container->height + 1)); - if ((output = get_screen_containing(container->x, destination_y)) == NULL) { + if ((output = get_output_containing(container->x, destination_y)) == NULL) { DLOG("Wrapping screen around vertically\n"); /* No screen found? Then wrap */ - output = get_screen_most((direction == D_UP ? D_DOWN : D_UP), container->workspace->output); + output = get_output_most((direction == D_UP ? D_DOWN : D_UP), container->workspace->output); } t_ws = output->current_workspace; new_row = (direction == D_UP ? (t_ws->rows - 1) : 0); @@ -208,9 +208,9 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t DLOG("container is at %d with width %d\n", container->x, container->width); Output *output; int destination_x = (direction == D_LEFT ? (container->x - 1) : (container->x + container->width + 1)); - if ((output = get_screen_containing(destination_x, container->y)) == NULL) { + if ((output = get_output_containing(destination_x, container->y)) == NULL) { DLOG("Wrapping screen around horizontally\n"); - output = get_screen_most((direction == D_LEFT ? D_RIGHT : D_LEFT), container->workspace->output); + output = get_output_most((direction == D_LEFT ? D_RIGHT : D_LEFT), container->workspace->output); } t_ws = output->current_workspace; new_col = (direction == D_LEFT ? (t_ws->cols - 1) : 0); diff --git a/src/handlers.c b/src/handlers.c index da5697c1..9c9f16f0 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -166,7 +166,7 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_ static void check_crossing_screen_boundary(uint32_t x, uint32_t y) { Output *output; - if ((output = get_screen_containing(x, y)) == NULL) { + if ((output = get_output_containing(x, y)) == NULL) { ELOG("ERROR: No such screen\n"); return; } @@ -457,7 +457,7 @@ int handle_screen_change(void *prophs, xcb_connection_t *conn, xcb_generic_event_t *e) { DLOG("RandR screen change\n"); - randr_query_screens(conn); + randr_query_outputs(conn); return 1; } diff --git a/src/layout.c b/src/layout.c index 0e1eecb1..31ee404e 100644 --- a/src/layout.c +++ b/src/layout.c @@ -215,7 +215,7 @@ void reposition_client(xcb_connection_t *conn, Client *client) { return; /* If the client is floating, we need to check if we moved it to a different workspace */ - output = get_screen_containing(client->rect.x + (client->rect.width / 2), + output = get_output_containing(client->rect.x + (client->rect.width / 2), client->rect.y + (client->rect.height / 2)); if (client->workspace->output == output) return; diff --git a/src/mainx.c b/src/mainx.c index 00ecd432..23fd757f 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -478,7 +478,7 @@ int main(int argc, char *argv[], char *env[]) { return 1; } - Output *screen = get_screen_containing(reply->root_x, reply->root_y); + Output *screen = get_output_containing(reply->root_x, reply->root_y); if (screen == NULL) { ELOG("ERROR: No screen at %d x %d, starting on the first screen\n", reply->root_x, reply->root_y); diff --git a/src/randr.c b/src/randr.c index 2d890efc..1d440c2a 100644 --- a/src/randr.c +++ b/src/randr.c @@ -44,8 +44,10 @@ typedef xcb_randr_get_screen_resources_current_reply_t resources_reply; /* Stores all outputs available in your current session. */ struct outputs_head outputs = TAILQ_HEAD_INITIALIZER(outputs); +static bool randr_disabled = false; + /* - * Get a specific output by its internal X11 id. Used by randr_query_screens + * Get a specific output by its internal X11 id. Used by randr_query_outputs * to check if the output is new (only in the first scan) or if we are * re-scanning. * @@ -78,75 +80,75 @@ Output *get_output_by_name(const char *name) { * */ Output *get_first_output() { - Output *screen; + Output *output; - TAILQ_FOREACH(screen, &outputs, outputs) { - if (screen->active) - return screen; - } + TAILQ_FOREACH(output, &outputs, outputs) + if (output->active) + return output; return NULL; } /* - * Looks in virtual_screens for the Output which contains coordinates x, y + * Returns the active (!) output which contains the coordinates x, y or NULL + * if there is no output which contains these coordinates. * */ -Output *get_screen_containing(int x, int y) { - Output *screen; - TAILQ_FOREACH(screen, &outputs, outputs) { - if (!screen->active) +Output *get_output_containing(int x, int y) { + Output *output; + TAILQ_FOREACH(output, &outputs, outputs) { + if (!output->active) continue; DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n", - x, y, screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height); - if (x >= screen->rect.x && x < (screen->rect.x + screen->rect.width) && - y >= screen->rect.y && y < (screen->rect.y + screen->rect.height)) - return screen; + x, y, output->rect.x, output->rect.y, output->rect.width, output->rect.height); + if (x >= output->rect.x && x < (output->rect.x + output->rect.width) && + y >= output->rect.y && y < (output->rect.y + output->rect.height)) + return output; } return NULL; } /* - * Gets the screen which is the last one in the given direction, for example the screen - * on the most bottom when direction == D_DOWN, the screen most right when direction == D_RIGHT - * and so on. + * Gets the output which is the last one in the given direction, for example + * the output on the most bottom when direction == D_DOWN, the output most + * right when direction == D_RIGHT and so on. * - * This function always returns a screen. + * This function always returns a output. * */ -Output *get_screen_most(direction_t direction, Output *current) { - Output *screen, *candidate = NULL; +Output *get_output_most(direction_t direction, Output *current) { + Output *output, *candidate = NULL; int position = 0; - TAILQ_FOREACH(screen, &outputs, outputs) { - if (!screen->active) + TAILQ_FOREACH(output, &outputs, outputs) { + if (!output->active) continue; /* Repeated calls of WIN determine the winner of the comparison */ #define WIN(variable, condition) \ if (variable condition) { \ - candidate = screen; \ + candidate = output; \ position = variable; \ } \ break; if (((direction == D_UP) || (direction == D_DOWN)) && - (current->rect.x != screen->rect.x)) + (current->rect.x != output->rect.x)) continue; if (((direction == D_LEFT) || (direction == D_RIGHT)) && - (current->rect.y != screen->rect.y)) + (current->rect.y != output->rect.y)) continue; switch (direction) { case D_UP: - WIN(screen->rect.y, <= position); + WIN(output->rect.y, <= position); case D_DOWN: - WIN(screen->rect.y, >= position); + WIN(output->rect.y, >= position); case D_LEFT: - WIN(screen->rect.x, <= position); + WIN(output->rect.x, <= position); case D_RIGHT: - WIN(screen->rect.x, >= position); + WIN(output->rect.x, >= position); } } @@ -186,14 +188,14 @@ static void initialize_output(xcb_connection_t *conn, Output *output, } /* - * Fills virtual_screens with exactly one screen with width/height of the - * whole X screen. + * Disables RandR support by creating exactly one output with the size of the + * X11 screen. * */ static void disable_randr(xcb_connection_t *conn) { xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; - DLOG("RandR extension not found, disabling.\n"); + DLOG("RandR extension unusable, disabling.\n"); Output *s = scalloc(sizeof(Output)); @@ -204,6 +206,8 @@ static void disable_randr(xcb_connection_t *conn) { s->rect.height = root_screen->height_in_pixels; TAILQ_INSERT_TAIL(&outputs, s, outputs); + + randr_disabled = true; } /* @@ -274,7 +278,7 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) { } /* - * Gets called by randr_query_screens() for each output. The function adds new + * Gets called by randr_query_outputs() for each output. The function adds new * outputs to the list of outputs, checks if the mode of existing outputs has * been changed or if an existing output has been disabled. It will then change * either the "changed" or the "to_be_deleted" flag of the output, if @@ -353,8 +357,9 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, * (Re-)queries the outputs via RandR and stores them in the list of outputs. * */ -void randr_query_screens(xcb_connection_t *conn) { +void randr_query_outputs(xcb_connection_t *conn) { Workspace *ws; + Output *output, *other, *first; xcb_randr_get_screen_resources_current_cookie_t rcookie; resources_reply *res; /* timestamp of the configuration so that we get consistent replies to all @@ -364,10 +369,15 @@ void randr_query_screens(xcb_connection_t *conn) { /* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */ xcb_randr_output_t *randr_outputs; + if (randr_disabled) + return; + /* Get screen resources (crtcs, outputs, modes) */ rcookie = xcb_randr_get_screen_resources_current(conn, root); - if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) - die("Could not get RandR screen resources\n"); + if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) { + disable_randr(conn); + return; + } cts = res->config_timestamp; int len = xcb_randr_get_screen_resources_current_outputs_length(res); @@ -389,46 +399,47 @@ void randr_query_screens(xcb_connection_t *conn) { } free(res); - Output *screen, *oscreen; - /* Check for clones and reduce the mode to the lowest common mode */ - TAILQ_FOREACH(screen, &outputs, outputs) { - if (!screen->active || screen->to_be_disabled) + /* Check for clones, disable the clones and reduce the mode to the + * lowest common mode */ + TAILQ_FOREACH(output, &outputs, outputs) { + if (!output->active || output->to_be_disabled) continue; - DLOG("screen %p, position (%d, %d), checking for clones\n", - screen, screen->rect.x, screen->rect.y); + DLOG("output %p, position (%d, %d), checking for clones\n", + output, output->rect.x, output->rect.y); - for (oscreen = screen; - oscreen != TAILQ_END(&outputs); - oscreen = TAILQ_NEXT(oscreen, outputs)) { - if (oscreen == screen || !oscreen->active || oscreen->to_be_disabled) + for (other = output; + other != TAILQ_END(&outputs); + other = TAILQ_NEXT(other, outputs)) { + if (other == output || !other->active || other->to_be_disabled) continue; - if (oscreen->rect.x != screen->rect.x || - oscreen->rect.y != screen->rect.y) + if (other->rect.x != output->rect.x || + other->rect.y != output->rect.y) continue; - DLOG("screen %p has the same position, his mode = %d x %d\n", - oscreen, oscreen->rect.width, oscreen->rect.height); - uint32_t width = min(oscreen->rect.width, screen->rect.width); - uint32_t height = min(oscreen->rect.height, screen->rect.height); + DLOG("output %p has the same position, his mode = %d x %d\n", + other, other->rect.width, other->rect.height); + uint32_t width = min(other->rect.width, output->rect.width); + uint32_t height = min(other->rect.height, output->rect.height); - if (update_if_necessary(&(screen->rect.width), width) | - update_if_necessary(&(screen->rect.height), height)) - screen->changed = true; + if (update_if_necessary(&(output->rect.width), width) | + update_if_necessary(&(output->rect.height), height)) + output->changed = true; - update_if_necessary(&(oscreen->rect.width), width); - update_if_necessary(&(oscreen->rect.height), height); + update_if_necessary(&(other->rect.width), width); + update_if_necessary(&(other->rect.height), height); - DLOG("disabling screen %p (%s)\n", oscreen, oscreen->name); - oscreen->to_be_disabled = true; + DLOG("disabling output %p (%s)\n", other, other->name); + other->to_be_disabled = true; - DLOG("new screen mode %d x %d, oscreen mode %d x %d\n", - screen->rect.width, screen->rect.height, - oscreen->rect.width, oscreen->rect.height); + DLOG("new output mode %d x %d, other mode %d x %d\n", + output->rect.width, output->rect.height, + other->rect.width, other->rect.height); } } - Output *output, *first; + /* Handle outputs which have a new mode or are disabled now (either + * because the user disabled them or because they are clones) */ TAILQ_FOREACH(output, &outputs, outputs) { if (output->to_be_disabled) { output->active = false; @@ -467,11 +478,11 @@ void randr_query_screens(xcb_connection_t *conn) { ewmh_update_workarea(); /* Just go through each active output and associate one workspace */ - TAILQ_FOREACH(screen, &outputs, outputs) { - if (!screen->active || screen->current_workspace != NULL) + TAILQ_FOREACH(output, &outputs, outputs) { + if (!output->active || output->current_workspace != NULL) continue; - ws = get_first_workspace_for_screen(screen); - initialize_output(conn, screen, ws); + ws = get_first_workspace_for_output(output); + initialize_output(conn, output, ws); } /* render_layout flushes */ @@ -489,7 +500,7 @@ void initialize_randr(xcb_connection_t *conn, int *event_base) { extreply = xcb_get_extension_data(conn, &xcb_randr_id); if (!extreply->present) disable_randr(conn); - else randr_query_screens(conn); + else randr_query_outputs(conn); if (event_base != NULL) *event_base = extreply->first_event; diff --git a/src/resize.c b/src/resize.c index 7b1f7bd8..4dd03523 100644 --- a/src/resize.c +++ b/src/resize.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -38,7 +38,7 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, int second, resize_orientation_t orientation, xcb_button_press_event_t *event) { int new_position; - struct xoutput *screen = get_screen_containing(event->root_x, event->root_y); + struct xoutput *screen = get_output_containing(event->root_x, event->root_y); if (screen == NULL) { ELOG("BUG: No screen found at this position (%d, %d)\n", event->root_x, event->root_y); return 1; @@ -49,8 +49,8 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i * screens during runtime. Instead, we just use the most right and most * bottom Xinerama screen and use their position + width/height to get * the area of pixels currently in use */ - struct xoutput *most_right = get_screen_most(D_RIGHT, screen), - *most_bottom = get_screen_most(D_DOWN, screen); + struct xoutput *most_right = get_output_most(D_RIGHT, screen), + *most_bottom = get_output_most(D_DOWN, screen); DLOG("event->event_x = %d, event->root_x = %d\n", event->event_x, event->root_x); diff --git a/src/workspace.c b/src/workspace.c index ef963a76..a17560ed 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -277,7 +277,7 @@ void workspace_initialize(Workspace *ws, Output *output, bool recheck) { * the preferred_output setting of every workspace (workspace assignments). * */ -Workspace *get_first_workspace_for_screen(Output *output) { +Workspace *get_first_workspace_for_output(Output *output) { Workspace *result = NULL; Workspace *ws; From 40475b250f40ce9c390211545a58b12d1c37d4c1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 5 Mar 2010 17:42:50 +0100 Subject: [PATCH 130/247] Allow unsetting WM_CLIENT_LEADER (XCB_NONE is not an invalid value) --- src/handlers.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers.c b/src/handlers.c index 9c9f16f0..37ae0e54 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -1060,7 +1060,7 @@ int handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state return 1; xcb_window_t *leader = xcb_get_property_value(prop); - if (leader == NULL || *leader == 0) + if (leader == NULL) return 1; DLOG("Client leader changed to %08x\n", *leader); From c367eaa369c3fbea1964d858298e7aba35facc39 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 5 Mar 2010 18:01:32 +0100 Subject: [PATCH 131/247] put windows with WM_CLIENT_LEADER on the workspace of their leader This fixes ticket #163 --- src/manage.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/manage.c b/src/manage.c index c4118641..b14f73e8 100644 --- a/src/manage.c +++ b/src/manage.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -340,6 +340,29 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, preply = xcb_get_property_reply(conn, leader_cookie, NULL); handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply); + /* if WM_CLIENT_LEADER is set, we put the new window on the + * same window as its leader. This might be overwritten by + * assignments afterwards. */ + if (new->leader != XCB_NONE) { + DLOG("client->leader is set (to 0x%08x)\n", new->leader); + Client *parent = table_get(&by_child, new->leader); + if (parent != NULL && parent->container != NULL) { + Workspace *t_ws = parent->workspace; + new->container = t_ws->table[parent->container->col][parent->container->row]; + new->workspace = t_ws; + old_focused = new->container->currently_focused; + map_frame = workspace_is_visible(t_ws); + new->urgent = true; + /* This is a little tricky: we cannot use + * workspace_update_urgent_flag() because the + * new window was not yet inserted into the + * focus stack on t_ws. */ + t_ws->urgent = true; + } else { + DLOG("parent is not usable\n"); + } + } + struct Assignment *assign; TAILQ_FOREACH(assign, &assignments, assignments) { if (get_matching_client(conn, assign->windowclass_title, new) == NULL) From 7a9755ad91d58af277eec1b5a25f2acc55991b5d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 5 Mar 2010 19:53:33 +0100 Subject: [PATCH 132/247] Add testcase for the last commit --- testcases/t/14-client-leader.t | 72 ++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 testcases/t/14-client-leader.t diff --git a/testcases/t/14-client-leader.t b/testcases/t/14-client-leader.t new file mode 100644 index 00000000..ead52764 --- /dev/null +++ b/testcases/t/14-client-leader.t @@ -0,0 +1,72 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# Beware that this test uses workspace 9 and 10 to perform some tests (it expects +# the workspace to be empty). +# TODO: skip it by default? + +use Test::More tests => 5; +use Test::Deep; +use X11::XCB qw(:all); +use Data::Dumper; +use Time::HiRes qw(sleep); +use FindBin; +use Digest::SHA1 qw(sha1_base64); +use lib "$FindBin::Bin/lib"; +use i3test; + +BEGIN { + use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); + use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); +} + +my $x = X11::XCB::Connection->new; + +my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); +isa_ok($sock, 'IO::Socket::UNIX'); + +# Switch to the nineth workspace +$sock->write(i3test::format_ipc_command("9")); + +sleep 0.25; + +##################################################################### +# Create a parent window +##################################################################### + +my $window = $x->root->create_child( +class => WINDOW_CLASS_INPUT_OUTPUT, +rect => [ 0, 0, 30, 30 ], +background_color => '#C0C0C0', +); + +$window->name('Parent window'); +$window->map; + +sleep 0.25; + +######################################################################### +# Switch workspace to 10 and open a child window. It should be positioned +# on workspace 9. +######################################################################### +$sock->write(i3test::format_ipc_command("10")); +sleep 0.25; + +my $child = $x->root->create_child( +class => WINDOW_CLASS_INPUT_OUTPUT, +rect => [ 0, 0, 30, 30 ], +background_color => '#C0C0C0', +); + +$child->name('Child window'); +$child->client_leader($window); +$child->map; + +sleep 0.25; + +isnt($x->input_focus, $child->id, "Child window focused"); + +# Switch back +$sock->write(i3test::format_ipc_command("9")); +sleep 0.25; + +is($x->input_focus, $child->id, "Child window focused"); From f1a011c014bfb14946840fb9f1528e3940c9916c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 5 Mar 2010 20:03:38 +0100 Subject: [PATCH 133/247] Clear urgency hints set by i3 (see HEAD~2) --- src/util.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/util.c b/src/util.c index a25a9dcf..dab3199b 100644 --- a/src/util.c +++ b/src/util.c @@ -34,6 +34,7 @@ #include "log.h" #include "ewmh.h" #include "manage.h" +#include "workspace.h" static iconv_t conversion_descriptor = 0; struct keyvalue_table_head by_parent = TAILQ_HEAD_INITIALIZER(by_parent); @@ -299,12 +300,17 @@ void set_focus(xcb_connection_t *conn, Client *client, bool set_anyways) { 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); + /* Clear the urgency flag if set (necessary when i3 sets the flag, for + * example when automatically putting windows on the workspace of their + * leader) */ + client->urgent = false; + workspace_update_urgent_flag(client->workspace); + /* 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) From 4e9c6515f3788577a284ad126569f54b8832545d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorsten=20T=C3=B6pper?= Date: Fri, 5 Mar 2010 23:09:51 +0100 Subject: [PATCH 134/247] Changing to stacking/tabbing toggles mode to default if already in stacking/tiling. --- src/commands.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands.c b/src/commands.c index 4317b36f..e4e944b7 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1036,9 +1036,9 @@ void parse_command(xcb_connection_t *conn, const char *command) { } LOG("Switching mode for current container\n"); int new_mode = MODE_DEFAULT; - if (command[0] == 's') + if (command[0] == 's' && CUR_CELL->mode != MODE_STACK) new_mode = MODE_STACK; - if (command[0] == 'T') + if (command[0] == 'T' && CUR_CELL->mode != MODE_TABBED) new_mode = MODE_TABBED; switch_layout_mode(conn, CUR_CELL, new_mode); return; From 6be76e4c564ef09aa878919d6f4d46ef97bc2e0f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 5 Mar 2010 23:26:45 +0100 Subject: [PATCH 135/247] Remove trailing whitespace --- src/commands.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands.c b/src/commands.c index e4e944b7..a2d9ea4e 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1036,7 +1036,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { } LOG("Switching mode for current container\n"); int new_mode = MODE_DEFAULT; - if (command[0] == 's' && CUR_CELL->mode != MODE_STACK) + if (command[0] == 's' && CUR_CELL->mode != MODE_STACK) new_mode = MODE_STACK; if (command[0] == 'T' && CUR_CELL->mode != MODE_TABBED) new_mode = MODE_TABBED; From e9ef672c3634a6d4cbd82f7ecd99860f030a9224 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 6 Mar 2010 16:34:07 +0100 Subject: [PATCH 136/247] =?UTF-8?q?Don=E2=80=99t=20render=20if=20no=20stac?= =?UTF-8?q?k=5Fwindow=20has=20been=20opened?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This could happen when you have only one client in a stacking/tabbed container. While not being a critical bug, this avoids X errors. --- src/layout.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/layout.c b/src/layout.c index 31ee404e..ddf79f8a 100644 --- a/src/layout.c +++ b/src/layout.c @@ -529,8 +529,10 @@ void render_container(xcb_connection_t *conn, Container *container) { } offset_x = current_client++ * size_each; } - decorate_window(conn, client, stack_win->pixmap.id, stack_win->pixmap.gc, - offset_x, offset_y); + if (stack_win->pixmap.id == XCB_NONE) + continue; + decorate_window(conn, client, stack_win->pixmap.id, + stack_win->pixmap.gc, offset_x, offset_y); } /* Check if we need to fill one column because of an uneven @@ -557,6 +559,8 @@ void render_container(xcb_connection_t *conn, Container *container) { } } + if (stack_win->pixmap.id == XCB_NONE) + return; 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); } From afc922ef57fd347e79f8c713057fe01c2fbd09ed Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 6 Mar 2010 16:51:17 +0100 Subject: [PATCH 137/247] Avoid more errors by not trying to re-create a stack win with height == 0 --- src/layout.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/layout.c b/src/layout.c index ddf79f8a..267cfec9 100644 --- a/src/layout.c +++ b/src/layout.c @@ -460,7 +460,8 @@ void render_container(xcb_connection_t *conn, Container *container) { } /* Prepare the pixmap for usage */ - cached_pixmap_prepare(conn, &(stack_win->pixmap)); + if (num_clients != 1) + cached_pixmap_prepare(conn, &(stack_win->pixmap)); int current_row = 0, current_col = 0; int wrap = 0; From 46642d0e455034c90e4783a2855296441931abd0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 6 Mar 2010 16:58:20 +0100 Subject: [PATCH 138/247] For num_clients == 0, the last fix is also valid --- src/layout.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layout.c b/src/layout.c index 267cfec9..04f9cf60 100644 --- a/src/layout.c +++ b/src/layout.c @@ -460,7 +460,7 @@ void render_container(xcb_connection_t *conn, Container *container) { } /* Prepare the pixmap for usage */ - if (num_clients != 1) + if (num_clients > 1) cached_pixmap_prepare(conn, &(stack_win->pixmap)); int current_row = 0, current_col = 0; From f66d018e0252853e919bee52819ad62aaa5eb37f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 7 Mar 2010 00:00:04 +0100 Subject: [PATCH 139/247] Update userguide, explain comments and normalize the paragraph about fonts (Thanks kraM) --- docs/userguide | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/docs/userguide b/docs/userguide index 7f47acce..2212e792 100644 --- a/docs/userguide +++ b/docs/userguide @@ -173,16 +173,35 @@ you can set specific applications to start on a specific workspace, you can automatically start applications, you can change the colors of i3 or bind your keys to do useful stuff. -To change the configuration of i3, copy +/etc/i3/config+ to +~/.i3/config+ -and edit it with a text editor. +To change the configuration of i3, copy +/etc/i3/config+ to +\~/.i3/config+ +(or +~/.config/i3/config+ if you like the XDG directory scheme) and edit it +with a text editor. -=== General configuration +=== Comments -font:: - Specifies the default font you want i3 to use. Use an X core font - descriptor here, like - +-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1+. You can - use +xfontsel(1)+ to pick one. +It is possible and recommended to use comments in your configuration file to +properly document your setup for later reference. Comments are started with +a # and can only be used at the beginning of a line, like this: + +*Examples*: +------------------- +# This is a comment +------------------- + +=== Fonts + +i3 uses X core fonts (not Xft) for rendering window titles and the internal +workspace bar. You can use +xfontsel(1)+ to generate such a font description. + +*Syntax*: +------------------------------ +font +------------------------------ + +*Examples*: +-------------------------------------------------------------- +font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 +-------------------------------------------------------------- === Keyboard bindings From 85308715ea3eb40d3af59a81527d049fe560f977 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 7 Mar 2010 19:00:34 +0100 Subject: [PATCH 140/247] Turn nested functions into real functions or macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This enables compilation with llvm-clang and thus closes ticket #101. While it makes the code more ugly, I don’t see a beautiful solution which would enable us to stay with the more elegant solution of nested functions and still allow compilation with any other compiler than gcc. --- common.mk | 4 +- include/floating.h | 13 +++- src/commands.c | 19 ++--- src/floating.c | 177 +++++++++++++++++++++++++-------------------- src/resize.c | 70 +++++++++++------- 5 files changed, 165 insertions(+), 118 deletions(-) diff --git a/common.mk b/common.mk index 3b88b6fe..ab9c050a 100644 --- a/common.mk +++ b/common.mk @@ -7,7 +7,9 @@ VERSION:=$(shell git describe --tags --abbrev=0) CFLAGS += -std=c99 CFLAGS += -pipe CFLAGS += -Wall -CFLAGS += -Wunused +# unused-function, unused-label, unused-variable are turned on by -Wall +# We don’t want unused-parameter because of the use of many callbacks +CFLAGS += -Wunused-value CFLAGS += -Iinclude CFLAGS += -I/usr/local/include CFLAGS += -DI3_VERSION=\"${GIT_VERSION}\" diff --git a/include/floating.h b/include/floating.h index 5cf3dedd..03da9e08 100644 --- a/include/floating.h +++ b/include/floating.h @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -12,7 +12,13 @@ #define _FLOATING_H /** Callback for dragging */ -typedef void(*callback_t)(Rect*, uint32_t, uint32_t); +typedef void(*callback_t)(xcb_connection_t*, Client*, Rect*, uint32_t, uint32_t, void*); + +/** Macro to create a callback function for dragging */ +#define DRAGGING_CB(name) \ + static void name(xcb_connection_t *conn, Client *client, \ + Rect *old_rect, uint32_t new_x, uint32_t new_y, \ + void *extra) /** On which border was the dragging initiated? */ typedef enum { BORDER_LEFT, BORDER_RIGHT, BORDER_TOP, BORDER_BOTTOM} border_t; @@ -97,6 +103,7 @@ void floating_toggle_hide(xcb_connection_t *conn, Workspace *workspace); * */ void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event, - xcb_window_t confine_to, border_t border, callback_t callback); + xcb_window_t confine_to, border_t border, callback_t callback, + void *extra); #endif diff --git a/src/commands.c b/src/commands.c index a2d9ea4e..184394b4 100644 --- a/src/commands.c +++ b/src/commands.c @@ -89,12 +89,13 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t Workspace *t_ws = c_ws; /* Makes sure new_col and new_row are within bounds of the new workspace */ - void check_colrow_boundaries() { - if (new_col >= t_ws->cols) - new_col = (t_ws->cols - 1); - if (new_row >= t_ws->rows) - new_row = (t_ws->rows - 1); - } +#define CHECK_COLROW_BOUNDARIES \ + do { \ + if (new_col >= t_ws->cols) \ + new_col = (t_ws->cols - 1); \ + if (new_row >= t_ws->rows) \ + new_row = (t_ws->rows - 1); \ + } while (0) /* There always is a container. If not, current_col or current_row is wrong */ assert(container != NULL); @@ -174,7 +175,7 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t new_row = (direction == D_UP ? (t_ws->rows - 1) : 0); } - check_colrow_boundaries(); + CHECK_COLROW_BOUNDARIES; DLOG("new_col = %d, new_row = %d\n", new_col, new_row); if (t_ws->table[new_col][new_row]->currently_focused == NULL) { @@ -216,7 +217,7 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t new_col = (direction == D_LEFT ? (t_ws->cols - 1) : 0); } - check_colrow_boundaries(); + CHECK_COLROW_BOUNDARIES; DLOG("new_col = %d, new_row = %d\n", new_col, new_row); if (t_ws->table[new_col][new_row]->currently_focused == NULL) { @@ -235,7 +236,7 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t return; } - check_colrow_boundaries(); + CHECK_COLROW_BOUNDARIES; if (t_ws->table[new_col][new_row]->currently_focused != NULL) set_focus(conn, t_ws->table[new_col][new_row]->currently_focused, true); diff --git a/src/floating.c b/src/floating.c index 02397bfc..e5e80235 100644 --- a/src/floating.c +++ b/src/floating.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -162,6 +162,69 @@ void floating_assign_to_workspace(Client *client, Workspace *new_workspace) { client->workspace->fullscreen_client = client; } +/* + * This is an ugly data structure which we need because there is no standard + * way of having nested functions (only available as a gcc extension at the + * moment, clang doesn’t support it) or blocks (only available as a clang + * extension and only on Mac OS X systems at the moment). + * + */ +struct callback_params { + border_t border; + xcb_button_press_event_t *event; +}; + +DRAGGING_CB(resize_callback) { + struct callback_params *params = extra; + xcb_button_press_event_t *event = params->event; + switch (params->border) { + case BORDER_RIGHT: { + int new_width = old_rect->width + (new_x - event->root_x); + if ((new_width < 0) || + (new_width < client_min_width(client) && client->rect.width >= new_width)) + return; + client->rect.width = new_width; + break; + } + + case BORDER_BOTTOM: { + int new_height = old_rect->height + (new_y - event->root_y); + if ((new_height < 0) || + (new_height < client_min_height(client) && client->rect.height >= new_height)) + return; + client->rect.height = old_rect->height + (new_y - event->root_y); + break; + } + + case BORDER_TOP: { + int new_height = old_rect->height + (event->root_y - new_y); + if ((new_height < 0) || + (new_height < client_min_height(client) && client->rect.height >= new_height)) + return; + + client->rect.y = old_rect->y + (new_y - event->root_y); + client->rect.height = new_height; + break; + } + + case BORDER_LEFT: { + int new_width = old_rect->width + (event->root_x - new_x); + if ((new_width < 0) || + (new_width < client_min_width(client) && client->rect.width >= new_width)) + return; + client->rect.x = old_rect->x + (new_x - event->root_x); + client->rect.width = new_width; + break; + } + } + + /* Push the new position/size to X11 */ + reposition_client(conn, client); + resize_client(conn, client); + xcb_flush(conn); +} + + /* * Called whenever the user clicks on a border (not the titlebar!) of a floating window. * Determines on which border the user clicked and launches the drag_pointer function @@ -173,54 +236,6 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre border_t border; - void resize_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) { - switch (border) { - case BORDER_RIGHT: { - int new_width = old_rect->width + (new_x - event->root_x); - if ((new_width < 0) || - (new_width < client_min_width(client) && client->rect.width >= new_width)) - return; - client->rect.width = new_width; - break; - } - - case BORDER_BOTTOM: { - int new_height = old_rect->height + (new_y - event->root_y); - if ((new_height < 0) || - (new_height < client_min_height(client) && client->rect.height >= new_height)) - return; - client->rect.height = old_rect->height + (new_y - event->root_y); - break; - } - - case BORDER_TOP: { - int new_height = old_rect->height + (event->root_y - new_y); - if ((new_height < 0) || - (new_height < client_min_height(client) && client->rect.height >= new_height)) - return; - - client->rect.y = old_rect->y + (new_y - event->root_y); - client->rect.height = new_height; - break; - } - - case BORDER_LEFT: { - int new_width = old_rect->width + (event->root_x - new_x); - if ((new_width < 0) || - (new_width < client_min_width(client) && client->rect.width >= new_width)) - return; - client->rect.x = old_rect->x + (new_x - event->root_x); - client->rect.width = new_width; - break; - } - } - - /* Push the new position/size to X11 */ - reposition_client(conn, client); - resize_client(conn, client); - xcb_flush(conn); - } - if (event->event_y < 2) border = BORDER_TOP; else if (event->event_y >= (client->rect.height - 2)) @@ -236,11 +251,25 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre DLOG("border = %d\n", border); - drag_pointer(conn, client, event, XCB_NONE, border, resize_callback); + struct callback_params params = { border, event }; + + drag_pointer(conn, client, event, XCB_NONE, border, resize_callback, ¶ms); return 1; } +DRAGGING_CB(drag_window_callback) { + struct xcb_button_press_event_t *event = extra; + + /* Reposition the client correctly while moving */ + client->rect.x = old_rect->x + (new_x - event->root_x); + client->rect.y = old_rect->y + (new_y - event->root_y); + reposition_client(conn, client); + /* Because reposition_client does not send a faked configure event (only resize does), + * we need to initiate that on our own */ + fake_absolute_configure_notify(conn, client); + /* fake_absolute_configure_notify flushes */ +} /* * Called when the user clicked on the titlebar of a floating window. @@ -250,18 +279,23 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) { DLOG("floating_drag_window\n"); - void drag_window_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) { - /* Reposition the client correctly while moving */ - client->rect.x = old_rect->x + (new_x - event->root_x); - client->rect.y = old_rect->y + (new_y - event->root_y); - reposition_client(conn, client); - /* Because reposition_client does not send a faked configure event (only resize does), - * we need to initiate that on our own */ - fake_absolute_configure_notify(conn, client); - /* fake_absolute_configure_notify flushes */ - } + drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback, event); +} - drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback); +DRAGGING_CB(resize_window_callback) { + xcb_button_press_event_t *event = extra; + int32_t new_width = old_rect->width + (new_x - event->root_x); + int32_t new_height = old_rect->height + (new_y - event->root_y); + + /* Obey minimum window size and reposition the client */ + if (new_width > 0 && new_width >= client_min_width(client)) + client->rect.width = new_width; + + if (new_height > 0 && new_height >= client_min_height(client)) + client->rect.height = new_height; + + /* resize_client flushes */ + resize_client(conn, client); } /* @@ -273,22 +307,7 @@ void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_pre void floating_resize_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) { DLOG("floating_resize_window\n"); - void resize_window_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) { - int32_t new_width = old_rect->width + (new_x - event->root_x); - int32_t new_height = old_rect->height + (new_y - event->root_y); - - /* Obey minimum window size and reposition the client */ - if (new_width > 0 && new_width >= client_min_width(client)) - client->rect.width = new_width; - - if (new_height > 0 && new_height >= client_min_height(client)) - client->rect.height = new_height; - - /* resize_client flushes */ - resize_client(conn, client); - } - - drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback); + drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback, event); } @@ -301,7 +320,7 @@ void floating_resize_window(xcb_connection_t *conn, Client *client, xcb_button_p * */ void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event, - xcb_window_t confine_to, border_t border, callback_t callback) { + xcb_window_t confine_to, border_t border, callback_t callback, void *extra) { xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; uint32_t new_x, new_y; Rect old_rect; @@ -371,7 +390,7 @@ void drag_pointer(xcb_connection_t *conn, Client *client, xcb_button_press_event new_x = ((xcb_motion_notify_event_t*)last_motion_notify)->root_x; new_y = ((xcb_motion_notify_event_t*)last_motion_notify)->root_y; - callback(&old_rect, new_x, new_y); + callback(conn, client, &old_rect, new_x, new_y, extra); FREE(last_motion_notify); } done: diff --git a/src/resize.c b/src/resize.c index 4dd03523..a32e5755 100644 --- a/src/resize.c +++ b/src/resize.c @@ -30,6 +30,44 @@ #include "workspace.h" #include "log.h" +/* + * This is an ugly data structure which we need because there is no standard + * way of having nested functions (only available as a gcc extension at the + * moment, clang doesn’t support it) or blocks (only available as a clang + * extension and only on Mac OS X systems at the moment). + * + */ +struct callback_params { + resize_orientation_t orientation; + Output *screen; + xcb_window_t helpwin; + uint32_t *new_position; +}; + +DRAGGING_CB(resize_callback) { + struct callback_params *params = extra; + Output *screen = params->screen; + DLOG("new x = %d, y = %d\n", new_x, new_y); + if (params->orientation == O_VERTICAL) { + /* Check if the new coordinates are within screen boundaries */ + if (new_x > (screen->rect.x + screen->rect.width - 25) || + new_x < (screen->rect.x + 25)) + return; + + *(params->new_position) = new_x; + xcb_configure_window(conn, params->helpwin, XCB_CONFIG_WINDOW_X, params->new_position); + } else { + if (new_y > (screen->rect.y + screen->rect.height - 25) || + new_y < (screen->rect.y + 25)) + return; + + *(params->new_position) = new_y; + xcb_configure_window(conn, params->helpwin, XCB_CONFIG_WINDOW_Y, params->new_position); + } + + xcb_flush(conn); +} + /* * Renders the resize window between the first/second container and resizes * the table column/row. @@ -37,8 +75,8 @@ */ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, int second, resize_orientation_t orientation, xcb_button_press_event_t *event) { - int new_position; - struct xoutput *screen = get_output_containing(event->root_x, event->root_y); + uint32_t new_position; + Output *screen = get_output_containing(event->root_x, event->root_y); if (screen == NULL) { ELOG("BUG: No screen found at this position (%d, %d)\n", event->root_x, event->root_y); return 1; @@ -49,8 +87,8 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i * screens during runtime. Instead, we just use the most right and most * bottom Xinerama screen and use their position + width/height to get * the area of pixels currently in use */ - struct xoutput *most_right = get_output_most(D_RIGHT, screen), - *most_bottom = get_output_most(D_DOWN, screen); + Output *most_right = get_output_most(D_RIGHT, screen), + *most_bottom = get_output_most(D_DOWN, screen); DLOG("event->event_x = %d, event->root_x = %d\n", event->event_x, event->root_x); @@ -100,29 +138,9 @@ int resize_graphical_handler(xcb_connection_t *conn, Workspace *ws, int first, i xcb_flush(conn); - void resize_callback(Rect *old_rect, uint32_t new_x, uint32_t new_y) { - DLOG("new x = %d, y = %d\n", new_x, new_y); - if (orientation == O_VERTICAL) { - /* Check if the new coordinates are within screen boundaries */ - if (new_x > (screen->rect.x + screen->rect.width - 25) || - new_x < (screen->rect.x + 25)) - return; + struct callback_params params = { orientation, screen, helpwin, &new_position }; - values[0] = new_position = new_x; - xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_X, values); - } else { - if (new_y > (screen->rect.y + screen->rect.height - 25) || - new_y < (screen->rect.y + 25)) - return; - - values[0] = new_position = new_y; - xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_Y, values); - } - - xcb_flush(conn); - } - - drag_pointer(conn, NULL, event, grabwin, BORDER_TOP, resize_callback); + drag_pointer(conn, NULL, event, grabwin, BORDER_TOP, resize_callback, ¶ms); xcb_destroy_window(conn, helpwin); xcb_destroy_window(conn, grabwin); From 496106cd44014450091f9aa7bcd88bd1ea518366 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 7 Mar 2010 21:12:59 +0100 Subject: [PATCH 141/247] =?UTF-8?q?Include=20graphic=20of=20the=20default?= =?UTF-8?q?=20keyboard=20layout=20in=20user=E2=80=99s=20guide?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This closes ticket #166 --- docs/keyboard-layer1.png | Bin 0 -> 43543 bytes docs/keyboard-layer1.svg | 915 +++++++++++++++++++++++++++++++++++++++ docs/keyboard-layer2.png | Bin 0 -> 42056 bytes docs/keyboard-layer2.svg | 896 ++++++++++++++++++++++++++++++++++++++ docs/userguide | 20 +- 5 files changed, 1830 insertions(+), 1 deletion(-) create mode 100644 docs/keyboard-layer1.png create mode 100644 docs/keyboard-layer1.svg create mode 100644 docs/keyboard-layer2.png create mode 100644 docs/keyboard-layer2.svg diff --git a/docs/keyboard-layer1.png b/docs/keyboard-layer1.png new file mode 100644 index 0000000000000000000000000000000000000000..88268f7b45974b6f0a07af641c1b028180a733ce GIT binary patch literal 43543 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ℜydEyG6E>khdAn%e4DFZ^tvn-Fa7 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 aWtz0xFJgss1lTF~N9CC2(PV{-xBo9yFUpz# literal 0 HcmV?d00001 diff --git a/docs/keyboard-layer1.svg b/docs/keyboard-layer1.svg new file mode 100644 index 00000000..18e9ae4b --- /dev/null +++ b/docs/keyboard-layer1.svg @@ -0,0 +1,915 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Caps Lock + + + + + + + + + + + + + + + + + + Shift + + + + Shift + + + + + + + + + + + + fullscreen + tabbedlayout + defaultlayout + dmenu + focusfloating/tiling + left + down + up + right + stackinglayout + Mod1 + + diff --git a/docs/keyboard-layer2.png b/docs/keyboard-layer2.png new file mode 100644 index 0000000000000000000000000000000000000000..85a7d2163579430f9008e663f7c09dcdb8c86bd6 GIT binary patch literal 42056 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 @f}f;lpwmO1|wxgJ#;YC3Pu(dRUjCg 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 nSye<>*p8U^^|na + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Caps Lock + + + + + + + + + + + + + + + + + + + + + Shift + + + + Shift + + + + + + + + + + + + toggle tiling/floating + killwindow + exiti3 + restarti3 + moveleft + movedown + moveup + moveright + Mod1 + + diff --git a/docs/userguide b/docs/userguide index 2212e792..fb537a90 100644 --- a/docs/userguide +++ b/docs/userguide @@ -7,7 +7,25 @@ This document contains all information you need to configuring and using the i3 window manager. If it does not, please contact me on IRC, Jabber or E-Mail and I’ll help you out. -For a complete listing of the default keybindings, please see the manpage. +== Default keybindings + +For the "too long; didn’t read" people, here comes an overview of the default +keybindings (click to see the full size image): + +*Keys to use with Mod1 (alt):* + +image:keyboard-layer1.png["Keys to use with Mod1 (alt)",width=600,link="keyboard-layer1.png"] + +*Keys to use with Shift+Mod1:* + +image:keyboard-layer2.png["Keys to use with Shift+Mod1",width=600,link="keyboard-layer2.png"] + +As i3 uses keycodes in the default configuration, it does not mapper which +layout you actually use. The key positions are what matters (of course you can +also use keysymbols, see below). + +The red keys are the modifiers you need to press (by default, you may have +changed which keys are which modifier), the blue keys are your homerow. == Using i3 From 9c77b0f9a15c239022af2965d6908f56a416a1ac Mon Sep 17 00:00:00 2001 From: Axel Wagner Date: Mon, 8 Mar 2010 02:02:35 +0100 Subject: [PATCH 142/247] Implement screen-spanning fullscreen-mode (command: 'fg') This closes ticket #188 --- docs/userguide | 9 +++- include/client.h | 14 +++++- src/client.c | 115 +++++++++++++++++++++++++++++++++++++---------- src/commands.c | 9 ++-- src/workspace.c | 6 +++ 5 files changed, 124 insertions(+), 29 deletions(-) diff --git a/docs/userguide b/docs/userguide index fb537a90..b7c84b21 100644 --- a/docs/userguide +++ b/docs/userguide @@ -86,6 +86,9 @@ image:modes.png[Container modes] To display a window fullscreen or to go out of fullscreen mode again, press +Mod1+f+. +There is also a global fullscreen mode in i3 in which the client will use all +available outputs. To use it, or to get out of it again, press +Mod1+Shift+f+. + === Opening other applications Aside from opening applicatios from a terminal, you can also use the handy @@ -515,7 +518,8 @@ focus_follows_mouse no To change the layout of the current container to stacking, use +s+, for default use +d+ and for tabbed, use +T+. To make the current client (!) fullscreen, -use +f+, to make it floating (or tiling again) use +t+: +use +f+, to make it spanning all outputs, use +fg+, to make it floating (or +tiling again) use +t+: *Examples*: -------------- @@ -526,6 +530,9 @@ bindsym Mod1+w T # Toggle fullscreen bindsym Mod1+f f +# Toggle global fullscreen +bindsym Mod1+Shift+f fg + # Toggle floating/tiling bindsym Mod1+t t -------------- diff --git a/include/client.h b/include/client.h index 4eac127e..8f97eb44 100644 --- a/include/client.h +++ b/include/client.h @@ -51,7 +51,13 @@ bool client_matches_class_name(Client *client, char *to_class, char *to_title, * and when moving a fullscreen client to another screen. * */ -void client_enter_fullscreen(xcb_connection_t *conn, Client *client); +void client_enter_fullscreen(xcb_connection_t *conn, Client *client, bool global); + +/** + * Leaves fullscreen mode for the given client. This is called by toggle_fullscreen. + * + */ +void client_leave_fullscreen(xcb_connection_t *conn, Client *client); /** * Toggles fullscreen mode for the given client. It updates the data @@ -62,6 +68,12 @@ void client_enter_fullscreen(xcb_connection_t *conn, Client *client); */ void client_toggle_fullscreen(xcb_connection_t *conn, Client *client); +/** + * Like client_toggle_fullscreen(), but putting it in global fullscreen-mode. + * + */ +void client_toggle_fullscreen_global(xcb_connection_t *conn, Client *client); + /** * Sets the position of the given client in the X stack to the highest (tiling * layer is always on the same position, so this doesn’t matter) below the diff --git a/src/client.c b/src/client.c index 1ca35dc7..f5e7718e 100644 --- a/src/client.c +++ b/src/client.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -152,20 +153,62 @@ bool client_matches_class_name(Client *client, char *to_class, char *to_title, * and when moving a fullscreen client to another screen. * */ -void client_enter_fullscreen(xcb_connection_t *conn, Client *client) { - Workspace *workspace = client->workspace; +void client_enter_fullscreen(xcb_connection_t *conn, Client *client, bool global) { + Workspace *workspace; + Rect r; - if (workspace->fullscreen_client != NULL) { - LOG("Not entering fullscreen mode, there already is a fullscreen client.\n"); - return; + if (global) { + TAILQ_FOREACH(workspace, workspaces, workspaces) { + if (workspace->fullscreen_client == NULL && workspace->fullscreen_client != client) + continue; + + LOG("Not entering global fullscreen mode, there already is a fullscreen client.\n"); + return; + } + + r = (Rect) { UINT_MAX, UINT_MAX, 0,0 }; + Output *output; + + /* Set fullscreen_client for each active workspace. + * Expand the rectangle to contain all outputs. */ + TAILQ_FOREACH(output, &outputs, outputs) { + if(!output->active) + continue; + + output->current_workspace->fullscreen_client = client; + + /* Temporarily abuse width/heigth as coordinates of the lower right corner */ + if(r.x > output->rect.x) + r.x = output->rect.x; + if(r.y > output->rect.y) + r.y = output->rect.y; + if(r.x + r.width < output->rect.x + output->rect.width) + r.width = output->rect.x + output->rect.width; + if(r.y + r.height < output->rect.y + output->rect.height) + r.height = output->rect.y + output->rect.height; + } + + /* Putting them back to their original meaning */ + r.height -= r.x; + r.width -= r.y; + + LOG("Entering global fullscreen mode...\n"); + } else { + workspace = client->workspace; + if (workspace->fullscreen_client != NULL && workspace->fullscreen_client != client) { + LOG("Not entering fullscreen mode, there already is a fullscreen client.\n"); + return; + } + + workspace->fullscreen_client = client; + r = workspace->rect; + + LOG("Entering fullscreen mode...\n"); } client->fullscreen = true; - workspace->fullscreen_client = client; - LOG("Entering fullscreen mode...\n"); - /* We just entered fullscreen mode, let’s configure the window */ - Rect r = workspace->rect; + /* We just entered fullscreen mode, let’s configure the window */ DLOG("child itself will be at %dx%d with size %dx%d\n", r.x, r.y, r.width, r.height); @@ -186,25 +229,17 @@ void client_enter_fullscreen(xcb_connection_t *conn, Client *client) { } /* - * Toggles fullscreen mode for the given client. It updates the data structures and - * reconfigures (= resizes/moves) the client and its frame to the full size of the - * screen. When leaving fullscreen, re-rendering the layout is forced. + * Leaves fullscreen mode for the current client. This is called by toggle_fullscreen. * */ -void client_toggle_fullscreen(xcb_connection_t *conn, Client *client) { - /* dock clients cannot enter fullscreen mode */ - assert(!client->dock); - - Workspace *workspace = client->workspace; - - if (!client->fullscreen) { - client_enter_fullscreen(conn, client); - return; - } - +void client_leave_fullscreen(xcb_connection_t *conn, Client *client) { LOG("leaving fullscreen mode\n"); client->fullscreen = false; - workspace->fullscreen_client = NULL; + Workspace *ws; + TAILQ_FOREACH(ws, workspaces, workspaces) + if (ws->fullscreen_client == client) + ws->fullscreen_client = NULL; + if (client_is_floating(client)) { /* For floating clients it’s enough if we just reconfigure that window (in fact, * re-rendering the layout will not update the client.) */ @@ -225,6 +260,38 @@ void client_toggle_fullscreen(xcb_connection_t *conn, Client *client) { xcb_flush(conn); } +/* + * Toggles fullscreen mode for the given client. It updates the data structures and + * reconfigures (= resizes/moves) the client and its frame to the full size of the + * screen. When leaving fullscreen, re-rendering the layout is forced. + * + */ +void client_toggle_fullscreen(xcb_connection_t *conn, Client *client) { + /* dock clients cannot enter fullscreen mode */ + assert(!client->dock); + + if (!client->fullscreen) { + client_enter_fullscreen(conn, client, false); + } else { + client_leave_fullscreen(conn, client); + } +} + +/* + * Like client_toggle_fullscreen(), but putting it in global fullscreen-mode. + * + */ +void client_toggle_fullscreen_global(xcb_connection_t *conn, Client *client) { + /* dock clients cannot enter fullscreen mode */ + assert(!client->dock); + + if (!client->fullscreen) { + client_enter_fullscreen(conn, client, true); + } else { + client_leave_fullscreen(conn, client); + } +} + /* * Sets the position of the given client in the X stack to the highest (tiling layer is always * on the same position, so this doesn’t matter) below the first floating client, so that diff --git a/src/commands.c b/src/commands.c index 184394b4..fd81e65c 100644 --- a/src/commands.c +++ b/src/commands.c @@ -629,7 +629,7 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa } else { if (current_client->fullscreen) { DLOG("Calling client_enter_fullscreen again\n"); - client_enter_fullscreen(conn, current_client); + client_enter_fullscreen(conn, current_client, false); } } @@ -1021,11 +1021,14 @@ void parse_command(xcb_connection_t *conn, const char *command) { return; } - /* Is it 'f' for fullscreen? */ + /* Is it 'f' for fullscreen, or 'fg' for fullscreen_global? */ if (command[0] == 'f') { if (last_focused == NULL) return; - client_toggle_fullscreen(conn, last_focused); + if (command[1] == 'g') + client_toggle_fullscreen_global(conn, last_focused); + else + client_toggle_fullscreen(conn, last_focused); return; } diff --git a/src/workspace.c b/src/workspace.c index a17560ed..f3cc24c8 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -144,6 +144,12 @@ void workspace_show(xcb_connection_t *conn, int workspace) { if ((old_client != NULL) && !old_client->dock) redecorate_window(conn, old_client); else xcb_flush(conn); + + /* We need to check, if a global fullscreen-client is blocking the t_ws and if + * necessary switch that to local fullscreen */ + Client* client = c_ws->fullscreen_client; + if (client != NULL && client->workspace != c_ws) + client_enter_fullscreen(conn, client, false); } /* Check if we need to change something or if we’re already there */ From 4dfe61c2d4fbe022dfaed18b4e54f3341d20011b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 8 Mar 2010 11:12:02 +0100 Subject: [PATCH 143/247] Fixes for the last commit: check outputs instead of workspaces, correctly reset state when switching workspaces, little style fixes --- src/client.c | 23 ++++++++++++++--------- src/workspace.c | 9 ++++++--- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/client.c b/src/client.c index f5e7718e..9c136ca6 100644 --- a/src/client.c +++ b/src/client.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -155,14 +155,19 @@ bool client_matches_class_name(Client *client, char *to_class, char *to_title, */ void client_enter_fullscreen(xcb_connection_t *conn, Client *client, bool global) { Workspace *workspace; + Output *output; Rect r; if (global) { - TAILQ_FOREACH(workspace, workspaces, workspaces) { - if (workspace->fullscreen_client == NULL && workspace->fullscreen_client != client) + TAILQ_FOREACH(output, &outputs, outputs) { + if (!output->active) continue; - LOG("Not entering global fullscreen mode, there already is a fullscreen client.\n"); + if (output->current_workspace->fullscreen_client == NULL) + continue; + + LOG("Not entering global fullscreen mode, there already " + "is a fullscreen client on output %s.\n", output->name); return; } @@ -172,19 +177,19 @@ void client_enter_fullscreen(xcb_connection_t *conn, Client *client, bool global /* Set fullscreen_client for each active workspace. * Expand the rectangle to contain all outputs. */ TAILQ_FOREACH(output, &outputs, outputs) { - if(!output->active) + if (!output->active) continue; output->current_workspace->fullscreen_client = client; /* Temporarily abuse width/heigth as coordinates of the lower right corner */ - if(r.x > output->rect.x) + if (r.x > output->rect.x) r.x = output->rect.x; - if(r.y > output->rect.y) + if (r.y > output->rect.y) r.y = output->rect.y; - if(r.x + r.width < output->rect.x + output->rect.width) + if (r.x + r.width < output->rect.x + output->rect.width) r.width = output->rect.x + output->rect.width; - if(r.y + r.height < output->rect.y + output->rect.height) + if (r.y + r.height < output->rect.y + output->rect.height) r.height = output->rect.y + output->rect.height; } diff --git a/src/workspace.c b/src/workspace.c index f3cc24c8..94d6a068 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -145,11 +145,14 @@ void workspace_show(xcb_connection_t *conn, int workspace) { redecorate_window(conn, old_client); else xcb_flush(conn); - /* We need to check, if a global fullscreen-client is blocking the t_ws and if - * necessary switch that to local fullscreen */ + /* We need to check if a global fullscreen-client is blocking + * the t_ws and if necessary switch that to local fullscreen */ Client* client = c_ws->fullscreen_client; - if (client != NULL && client->workspace != c_ws) + if (client != NULL && client->workspace != c_ws) { + if (c_ws->fullscreen_client->workspace != c_ws) + c_ws->fullscreen_client = NULL; client_enter_fullscreen(conn, client, false); + } } /* Check if we need to change something or if we’re already there */ From bd76e994b8b0c21defc0fecdff510277a970acd2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 9 Mar 2010 20:00:56 +0100 Subject: [PATCH 144/247] Re-add old Xinerama code for the poor nvidia users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add --force-xinerama when starting i3 to use Xinerama instead of RandR. This should *ONLY* be done if you have no other choice (nvidia’s binary driver uses twinview and does not expose the monitor information through RandR). --- common.mk | 1 + include/i3.h | 1 - include/randr.h | 13 +++++ include/xinerama.h | 23 ++++++++ src/mainx.c | 33 +++++++++--- src/randr.c | 9 ++-- src/xinerama.c | 128 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 198 insertions(+), 10 deletions(-) create mode 100644 include/xinerama.h create mode 100644 src/xinerama.c diff --git a/common.mk b/common.mk index ab9c050a..39aac5f6 100644 --- a/common.mk +++ b/common.mk @@ -37,6 +37,7 @@ LDFLAGS += -lxcb-keysyms LDFLAGS += -lxcb-atom LDFLAGS += -lxcb-aux LDFLAGS += -lxcb-icccm +LDFLAGS += -lxcb-xinerama LDFLAGS += -lxcb-randr LDFLAGS += -lxcb LDFLAGS += -lX11 diff --git a/include/i3.h b/include/i3.h index ce86e924..77c792a7 100644 --- a/include/i3.h +++ b/include/i3.h @@ -32,7 +32,6 @@ extern TAILQ_HEAD(autostarts_head, Autostart) autostarts; extern TAILQ_HEAD(assignments_head, Assignment) assignments; extern SLIST_HEAD(stack_wins_head, Stack_Window) stack_wins; extern xcb_event_handlers_t evenths; -extern int num_screens; extern uint8_t root_depth; extern bool xkb_supported; extern xcb_atom_t atoms[NUM_ATOMS]; diff --git a/include/randr.h b/include/randr.h index 35e5f79b..4832efe5 100644 --- a/include/randr.h +++ b/include/randr.h @@ -24,6 +24,19 @@ extern struct outputs_head outputs; */ void initialize_randr(xcb_connection_t *conn, int *event_base); +/** + * Disables RandR support by creating exactly one output with the size of the + * X11 screen. + * + */ +void disable_randr(xcb_connection_t *conn); + +/** + * Initializes the specified output, assigning the specified workspace to it. + * + */ +void initialize_output(xcb_connection_t *conn, Output *output, Workspace *workspace); + /** * (Re-)queries the outputs via RandR and stores them in the list of outputs. * diff --git a/include/xinerama.h b/include/xinerama.h new file mode 100644 index 00000000..f1182349 --- /dev/null +++ b/include/xinerama.h @@ -0,0 +1,23 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009-2010 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + */ +#include "data.h" + +#ifndef _XINERAMA_H +#define _XINERAMA_H + +/** + * We have just established a connection to the X server and need the initial + * Xinerama information to setup workspaces for each screen. + * + */ +void initialize_xinerama(xcb_connection_t *conn); + +#endif diff --git a/src/mainx.c b/src/mainx.c index 23fd757f..8542ab22 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -45,6 +45,7 @@ #include "util.h" #include "xcb.h" #include "randr.h" +#include "xinerama.h" #include "manage.h" #include "ipc.h" #include "log.h" @@ -150,6 +151,7 @@ int main(int argc, char *argv[], char *env[]) { char *override_configpath = NULL; bool autostart = true; bool only_check_config = false; + bool force_xinerama = false; xcb_connection_t *conn; xcb_property_handlers_t prophs; xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS]; @@ -158,6 +160,7 @@ int main(int argc, char *argv[], char *env[]) { {"config", required_argument, 0, 'c'}, {"version", no_argument, 0, 'v'}, {"help", no_argument, 0, 'h'}, + {"force-xinerama", no_argument, 0, 0}, {0, 0, 0, 0} }; int option_index = 0; @@ -196,6 +199,17 @@ int main(int argc, char *argv[], char *env[]) { case 'l': /* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */ break; + case 0: + if (strcmp(long_options[option_index].name, "force-xinerama") == 0) { + force_xinerama = true; + ELOG("Using Xinerama instead of RandR. This option should be " + "avoided at all cost because it does not refresh the list " + "of screens, so you cannot configure displays at runtime. " + "Please check if your driver really does not support RandR " + "and disable this option as soon as you can.\n"); + break; + } + /* fall-through */ default: fprintf(stderr, "Usage: %s [-c configfile] [-d loglevel] [-a] [-v] [-V] [-C]\n", argv[0]); fprintf(stderr, "\n"); @@ -205,6 +219,9 @@ int main(int argc, char *argv[], char *env[]) { fprintf(stderr, "-d : enable debug loglevel \n"); fprintf(stderr, "-c : use the provided configfile instead\n"); fprintf(stderr, "-C: check configuration file and exit\n"); + fprintf(stderr, "--force-xinerama: Use Xinerama instead of RandR. This " + "option should only be used if you are stuck with the " + "nvidia closed source driver which does not support RandR.\n"); exit(EXIT_FAILURE); } } @@ -460,14 +477,18 @@ int main(int argc, char *argv[], char *env[]) { grab_all_keys(conn); - DLOG("Checking for XRandR...\n"); int randr_base; - initialize_randr(conn, &randr_base); + if (force_xinerama) { + initialize_xinerama(conn); + } else { + DLOG("Checking for XRandR...\n"); + initialize_randr(conn, &randr_base); - xcb_event_set_handler(&evenths, - randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY, - handle_screen_change, - NULL); + xcb_event_set_handler(&evenths, + randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY, + handle_screen_change, + NULL); + } xcb_flush(conn); diff --git a/src/randr.c b/src/randr.c index 1d440c2a..7cf9d514 100644 --- a/src/randr.c +++ b/src/randr.c @@ -157,8 +157,11 @@ Output *get_output_most(direction_t direction, Output *current) { return candidate; } -static void initialize_output(xcb_connection_t *conn, Output *output, - Workspace *workspace) { +/* + * Initializes the specified output, assigning the specified workspace to it. + * + */ +void initialize_output(xcb_connection_t *conn, Output *output, Workspace *workspace) { i3Font *font = load_font(conn, config.font); workspace->output = output; @@ -192,7 +195,7 @@ static void initialize_output(xcb_connection_t *conn, Output *output, * X11 screen. * */ -static void disable_randr(xcb_connection_t *conn) { +void disable_randr(xcb_connection_t *conn) { xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; DLOG("RandR extension unusable, disabling.\n"); diff --git a/src/xinerama.c b/src/xinerama.c new file mode 100644 index 00000000..d7efff0d --- /dev/null +++ b/src/xinerama.c @@ -0,0 +1,128 @@ +/* + * vim:ts=8:expandtab + * + * i3 - an improved dynamic tiling window manager + * + * © 2009-2010 Michael Stapelberg and contributors + * + * See file LICENSE for license information. + * + * This is LEGACY code (we support RandR, which can do much more than + * Xinerama), but necessary for the poor users of the nVidia binary + * driver which does not support RandR in 2010 *sigh*. + * + */ +#include +#include +#include + +#include +#include + +#include "queue.h" +#include "data.h" +#include "util.h" +#include "xinerama.h" +#include "workspace.h" +#include "log.h" +#include "randr.h" + +static int num_screens; + +/* + * Looks in outputs for the Output whose start coordinates are x, y + * + */ +static Output *get_screen_at(int x, int y) { + Output *output; + TAILQ_FOREACH(output, &outputs, outputs) + if (output->rect.x == x && output->rect.y == y) + return output; + + return NULL; +} + +/* + * Gets the Xinerama screens and converts them to virtual Outputs (only one screen for two + * Xinerama screen which are configured in clone mode) in the given screenlist + * + */ +static void query_screens(xcb_connection_t *conn) { + xcb_xinerama_query_screens_reply_t *reply; + xcb_xinerama_screen_info_t *screen_info; + + reply = xcb_xinerama_query_screens_reply(conn, xcb_xinerama_query_screens_unchecked(conn), NULL); + if (!reply) { + ELOG("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); + + for (int screen = 0; screen < screens; screen++) { + Output *s = get_screen_at(screen_info[screen].x_org, screen_info[screen].y_org); + if (s != NULL) { + DLOG("Re-used old Xinerama screen %p\n", s); + /* 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 = scalloc(sizeof(Output)); + asprintf(&(s->name), "xinerama-%d", num_screens); + DLOG("Created new Xinerama screen %s (%p)\n", s->name, s); + s->active = true; + 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(&outputs, s, outputs); + else TAILQ_INSERT_TAIL(&outputs, s, outputs); + num_screens++; + } + + DLOG("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); + + if (num_screens == 0) { + ELOG("No screens found. Please fix your setup. i3 will exit now.\n"); + exit(0); + } +} + +/* + * We have just established a connection to the X server and need the initial Xinerama + * information to setup workspaces for each screen. + * + */ +void initialize_xinerama(xcb_connection_t *conn) { + if (!xcb_get_extension_data(conn, &xcb_xinerama_id)->present) { + DLOG("Xinerama extension not found, disabling.\n"); + disable_randr(conn); + } else { + xcb_xinerama_is_active_reply_t *reply; + reply = xcb_xinerama_is_active_reply(conn, xcb_xinerama_is_active(conn), NULL); + + if (reply == NULL || !reply->state) { + DLOG("Xinerama is not active (in your X-Server), disabling.\n"); + disable_randr(conn); + } else + query_screens(conn); + + FREE(reply); + } + + Output *output; + Workspace *ws; + /* Just go through each active output and associate one workspace */ + TAILQ_FOREACH(output, &outputs, outputs) { + ws = get_first_workspace_for_output(output); + initialize_output(conn, output, ws); + } +} From 5d4d9681a03cd673cd726c3ac412b303049deab2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 10 Mar 2010 12:36:22 +0100 Subject: [PATCH 145/247] debian: re-require libxcb-xinerama0-dev --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 5fb494b4..48af208c 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Section: utils Priority: extra Maintainer: Michael Stapelberg DM-Upload-Allowed: yes -Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-randr0-dev, libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison +Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison Standards-Version: 3.8.3 Homepage: http://i3.zekjur.net/ From 593bff5ffdff63fdb6be72bd6321c2bb183df0e1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 10 Mar 2010 13:01:27 +0100 Subject: [PATCH 146/247] reformat docs/debugging --- docs/debugging | 84 +++++++++++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/docs/debugging b/docs/debugging index d32329d4..f2a92739 100644 --- a/docs/debugging +++ b/docs/debugging @@ -3,19 +3,20 @@ Debugging i3: How To Michael Stapelberg April 2009 -This document describes how to debug i3 suitably for sending us useful bug reports, even -if you have no clue of C programming. +This document describes how to debug i3 suitably for sending us useful bug +reports, even if you have no clue of C programming. -First of all: Thank you for being interested in debugging i3. It really means something -to us to get your bug fixed. If you have any questions about the debugging and/or need -further help, do not hesitate to contact us! +First of all: Thank you for being interested in debugging i3. It really means +something to us to get your bug fixed. If you have any questions about the +debugging and/or need further help, do not hesitate to contact us! == Enabling logging -i3 spits out much information onto stdout. To have a clearly defined place where logfiles -will be saved, you should redirect stdout and stderr in xsession. While you’re at it, -putting each run of i3 in a separate logfile with date/time in it is a good idea to not -get confused about the different logfiles later on. +i3 spits out much information onto stdout. To have a clearly defined place +where logfiles will be saved, you should redirect stdout and stderr in +xsession. While you’re at it, putting each run of i3 in a separate logfile with +date/time in it is a good idea to not get confused about the different logfiles +later on. -------------------------------------------------------------------- exec /usr/bin/i3 >/home/michael/i3/i3log-$(date +'%F-%k-%M-%S') 2>&1 @@ -23,32 +24,35 @@ exec /usr/bin/i3 >/home/michael/i3/i3log-$(date +'%F-%k-%M-%S') 2>&1 == Enabling coredumps -When i3 crashes, often you have the chance of getting a coredump (an image of the memory -of the i3 process which can be loaded into a debugger). To get a core-dump, you have to -make sure that the user limit for core dump files is set high enough. Many systems ship -with a default value which even forbids core dumps completely. To disable the limit -completely and thus enable coredumps, use the following command (in your .xsession, before -starting i3): +When i3 crashes, often you have the chance of getting a coredump (an image of +the memory of the i3 process which can be loaded into a debugger). To get a +core-dump, you have to make sure that the user limit for core dump files is set +high enough. Many systems ship with a default value which even forbids core +dumps completely. To disable the limit completely and thus enable coredumps, +use the following command (in your .xsession, before starting i3): ------------------- ulimit -c unlimited ------------------- -Furthermore, to easily recognize core dumps and allow multiple of them, you should set -a custom core dump filename pattern, using a command like the following: +Furthermore, to easily recognize core dumps and allow multiple of them, you +should set a custom core dump filename pattern, using a command like the +following: --------------------------------------------- sudo sysctl -w kernel.core_pattern=core.%e.%p --------------------------------------------- -This will generate files which have the executable’s file name (%e) and the process id -(%p) in it. You can save this setting across reboots using +/etc/sysctl.conf+. +This will generate files which have the executable’s file name (%e) and the +process id (%p) in it. You can save this setting across reboots using ++/etc/sysctl.conf+. == Compiling with debug symbols -To actually get useful coredumps, you should make sure that your version of i3 is compiled -with debug symbols, that is, that they are not stripped during the build process. You -can check whether your executable contains symbols by issuing the following command: +To actually get useful coredumps, you should make sure that your version of i3 +is compiled with debug symbols, that is, that they are not stripped during the +build process. You can check whether your executable contains symbols by +issuing the following command: ---------------- file $(which i3) @@ -60,23 +64,23 @@ You should get an output like this: linked (uses shared libs), for GNU/Linux 2.6.18, not stripped ------------------------------------------------------------------------------ -Notice the +not stripped+, which is the important part. If you have a version which is -stripped, please have a look if your distribution provides debug symbols (package +i3-wm-dbg+ -on Debian for example) or if you can turn off stripping. If nothing helps, please build -i3 from source. +Notice the +not stripped+, which is the important part. If you have a version +which is stripped, please have a look if your distribution provides debug +symbols (package +i3-wm-dbg+ on Debian for example) or if you can turn off +stripping. If nothing helps, please build i3 from source. == Generating a backtrace -Once you have made sure that your i3 is compiled with debug symbols and that coredumps -are enabled, you can start getting some sense out of the coredumps. +Once you have made sure that your i3 is compiled with debug symbols and that +coredumps are enabled, you can start getting some sense out of the coredumps. -Because the coredump depends on the original executable (and its debug symbols), please -do this as soon as you encounter the problem. If you re-compile i3, your coredump might -be useless afterwards. +Because the coredump depends on the original executable (and its debug +symbols), please do this as soon as you encounter the problem. If you +re-compile i3, your coredump might be useless afterwards. -Please install +gdb+, a debugger for C. No worries, you don’t need to learn it now. -Start gdb using the following command (replacing the actual name of the coredump of -course): +Please install +gdb+, a debugger for C. No worries, you don’t need to learn it +now. Start gdb using the following command (replacing the actual name of the +coredump of course): ---------------------------- gdb $(which i3) core.i3.3849 @@ -90,9 +94,11 @@ backtrace full == Sending bugreports/debugging on IRC -When sending bugreports, please paste the relevant part of the log (if in doubt, please send us rather -too much information than too less) and the whole backtrace (if there was a coredump). +When sending bugreports, please paste the relevant part of the log (if in +doubt, please send us rather too much information than too less) and the whole +backtrace (if there was a coredump). -When debugging with us in IRC, be prepared to use a so called nopaste service such as http://nopaste.info -because pasting large amounts of text in IRC sometimes leads to incomplete lines (servers have line -length limitations) or flood kicks. +When debugging with us in IRC, be prepared to use a so called nopaste service +such as http://nopaste.info because pasting large amounts of text in IRC +sometimes leads to incomplete lines (servers have line length limitations) or +flood kicks. From a70f4353b79f437116e6accae2557f3f729039ce Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 10 Mar 2010 23:27:24 +0100 Subject: [PATCH 147/247] Fix predict_text_width by using xcb_query_text_extents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes ticket #173, at least for the rendering errors. I don’t really know why I implemented predict_text_width like it was before (querying the whole table and pulling out information one by one). Maybe I have overlooked xcb_query_text_extents. In any case, it works better now. --- src/xcb.c | 83 ++++++++++--------------------------------------------- 1 file changed, 14 insertions(+), 69 deletions(-) diff --git a/src/xcb.c b/src/xcb.c index fd01391e..ee3148ed 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -294,85 +294,30 @@ void cached_pixmap_prepare(xcb_connection_t *conn, struct Cached_Pixmap *pixmap) } /* - * 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. + * Query 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, const char *font_pattern, char *text, int length) { - xcb_query_font_reply_t *font_info; - xcb_charinfo_t *table; - xcb_generic_error_t *error; - int i, width = 0; i3Font *font = load_font(conn, font_pattern); - font_info = xcb_query_font_reply(conn, xcb_query_font(conn, font->id), &error); - if (error != NULL) { - fprintf(stderr, "ERROR: query font (X error code %d)\n", error->error_code); + xcb_query_text_extents_cookie_t cookie; + xcb_query_text_extents_reply_t *reply; + xcb_generic_error_t *error; + int width; + + cookie = xcb_query_text_extents(conn, font->id, length, (xcb_char2b_t*)text); + if ((reply = xcb_query_text_extents_reply(conn, cookie, &error)) == NULL) { + ELOG("Could not get text extents (X error code %d)\n", + error->error_code); /* We return the rather safe guess of 7 pixels, because a * rendering error is better than a crash. Plus, the user will - * see the error on his stderr. */ + * see the error in his log. */ return 7; } - /* If no per-char info is available for this font, we use the default */ - if (xcb_query_font_char_infos_length(font_info) == 0) { - DLOG("Falling back on default char_width of %d pixels\n", font_info->max_bounds.character_width); - return (font_info->max_bounds.character_width * length); - } - - 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; - } - - free(font_info); - + width = reply->overall_width; + free(reply); return width; } From f7c8e767822b209ab4ae995a128e217768d51a4d Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 11 Mar 2010 00:15:34 +0100 Subject: [PATCH 148/247] Select containers above or near the whole snapped width/height This fixes ticket #100, and is best explained using a little example. Consider the following layout: +---+---+ | | X | +---+---+ | X | +---+---+ Where X marks a window, so you have an empty container in the upper left, the container on the bottom is snapped to the right. Before this commit, nothing would happen when focusing "above". After this commit, the upper window gets focused. --- src/commands.c | 39 ++++++++++++++++++++++++++++++++-- testcases/t/06-focus.t | 48 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/src/commands.c b/src/commands.c index fd81e65c..bef0989f 100644 --- a/src/commands.c +++ b/src/commands.c @@ -185,9 +185,26 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t continue; new_col = cols; + DLOG("Fixed it to new col %d\n", new_col); + break; + } + } + + if (t_ws->table[new_col][new_row]->currently_focused == NULL) { + DLOG("Cell still empty, checking for full cols above spanned width...\n"); + DLOG("new_col = %d\n", new_col); + DLOG("colspan = %d\n", container->colspan); + for (int cols = new_col; + cols < container->col + container->colspan; + cols += t_ws->table[cols][new_row]->colspan) { + DLOG("candidate: new_row = %d, cols = %d\n", new_row, cols); + if (t_ws->table[cols][new_row]->currently_focused == NULL) + continue; + + new_col = cols; + DLOG("Fixed it to new col %d\n", new_col); break; } - DLOG("Fixed it to new col %d\n", new_col); } } else if (direction == D_LEFT || direction == D_RIGHT) { if (direction == D_RIGHT && cell_exists(t_ws, current_col+1, current_row)) @@ -227,10 +244,28 @@ static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t t continue; new_row = rows; + DLOG("Fixed it to new row %d\n", new_row); break; } - DLOG("Fixed it to new row %d\n", new_row); } + + if (t_ws->table[new_col][new_row]->currently_focused == NULL) { + DLOG("Cell still empty, checking for full cols near full spanned height...\n"); + DLOG("new_row = %d\n", new_row); + DLOG("rowspan = %d\n", container->rowspan); + for (int rows = new_row; + rows < container->row + container->rowspan; + rows += t_ws->table[new_col][rows]->rowspan) { + DLOG("candidate: new_col = %d, rows = %d\n", new_col, rows); + if (t_ws->table[new_col][rows]->currently_focused == NULL) + continue; + + new_row = rows; + DLOG("Fixed it to new col %d\n", new_row); + break; + } + } + } else { ELOG("direction unhandled\n"); return; diff --git a/testcases/t/06-focus.t b/testcases/t/06-focus.t index efdf5749..5ca3e062 100644 --- a/testcases/t/06-focus.t +++ b/testcases/t/06-focus.t @@ -4,7 +4,7 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 8; +use Test::More tests => 14; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -76,4 +76,50 @@ is($focus, $bottom->id, "Bottom window focused (wrapping to the top works)"); $focus = focus_after("j"); is($focus, $top->id, "Top window focused (wrapping to the bottom works)"); +############################################### +# Test focus with empty containers and colspan +############################################### + +# Switch to the 10. workspace +$sock->write(i3test::format_ipc_command("10")); +sleep 0.25; + +$top = i3test::open_standard_window($x); +$bottom = i3test::open_standard_window($x); +sleep 0.25; + +$focus = focus_after("mj"); +$focus = focus_after("mh"); +$focus = focus_after("k"); +is($focus, $bottom->id, "Selecting top window without snapping doesn't work"); + +$focus = focus_after("sl"); +is($focus, $bottom->id, "Bottom window focused"); + +$focus = focus_after("k"); +is($focus, $top->id, "Top window focused"); + +# Same thing, but left/right instead of top/bottom + +# Switch to the 11. workspace +$sock->write(i3test::format_ipc_command("11")); +sleep 0.25; + +my $left = i3test::open_standard_window($x); +my $right = i3test::open_standard_window($x); +sleep 0.25; + +$focus = focus_after("ml"); +$focus = focus_after("h"); +$focus = focus_after("mk"); +$focus = focus_after("l"); +is($focus, $left->id, "Selecting right window without snapping doesn't work"); + +$focus = focus_after("sj"); +is($focus, $left->id, "left window focused"); + +$focus = focus_after("l"); +is($focus, $right->id, "right window focused"); + + diag( "Testing i3, Perl $], $^X" ); From 952914c3c5c8956435900549e71d36bc17a04c2f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 11 Mar 2010 15:36:54 +0100 Subject: [PATCH 149/247] =?UTF-8?q?Remove=20-ftrampolines=20as=20we=20don?= =?UTF-8?q?=E2=80=99t=20have=20nested=20functions=20anymore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common.mk | 1 - 1 file changed, 1 deletion(-) diff --git a/common.mk b/common.mk index 39aac5f6..cca747be 100644 --- a/common.mk +++ b/common.mk @@ -51,7 +51,6 @@ LDFLAGS += -Wl,-rpath,/usr/local/lib -Wl,-rpath,/usr/pkg/lib endif ifeq ($(UNAME),OpenBSD) -CFLAGS += -ftrampolines CFLAGS += -I${X11BASE}/include LDFLAGS += -liconv LDFLAGS += -L${X11BASE}/lib From 9a9ba1b859388f8cf74a08073c1fad49975832d1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 11 Mar 2010 15:58:39 +0100 Subject: [PATCH 150/247] ipc: implement GET_WORKSPACES message type This is the foundation to use dzen2 or similar as a complete replacement for the internal workspaces bar. A testcase is included, more documentation about the IPC interface will follow. --- DEPENDS | 2 + common.mk | 1 + debian/control | 2 +- include/data.h | 3 ++ include/i3/ipc.h | 20 ++++++- src/ipc.c | 94 +++++++++++++++++++++++++++++++-- src/workspace.c | 4 +- testcases/Makefile | 2 +- testcases/t/15-ipc-workspaces.t | 56 ++++++++++++++++++++ testcases/t/lib/i3test.pm | 18 +++++++ 10 files changed, 192 insertions(+), 10 deletions(-) create mode 100644 testcases/t/15-ipc-workspaces.t diff --git a/DEPENDS b/DEPENDS index 47258068..31947bf9 100644 --- a/DEPENDS +++ b/DEPENDS @@ -7,6 +7,7 @@ In that case, please try using the versions mentioned below until a fix is provi * xcb-util-0.3.3 (2009-01-31) * libev * flex and bison + * yajl (the IPC interface uses JSON to serialize data) * asciidoc >= 8.3.0 for docs/hacking-howto * asciidoc, xmlto, docbook-xml for man/i3.man * Xlib, the one that comes with your X-Server @@ -24,6 +25,7 @@ http://xcb.freedesktop.org/dist/xcb-util-0.3.5.tar.bz2 http://libev.schmorp.de/ http://flex.sourceforge.net/ http://www.gnu.org/software/bison/ +http://lloyd.github.com/yajl/ http://i3.zekjur.net/i3lock/ http://tools.suckless.org/dmenu diff --git a/common.mk b/common.mk index cca747be..b1940140 100644 --- a/common.mk +++ b/common.mk @@ -40,6 +40,7 @@ LDFLAGS += -lxcb-icccm LDFLAGS += -lxcb-xinerama LDFLAGS += -lxcb-randr LDFLAGS += -lxcb +LDFLAGS += -lyajl LDFLAGS += -lX11 LDFLAGS += -lev LDFLAGS += -L/usr/local/lib -L/usr/pkg/lib diff --git a/debian/control b/debian/control index 48af208c..8dde5b80 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Section: utils Priority: extra Maintainer: Michael Stapelberg DM-Upload-Allowed: yes -Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison +Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev Standards-Version: 3.8.3 Homepage: http://i3.zekjur.net/ diff --git a/include/data.h b/include/data.h index 2cc8362f..6819ca95 100644 --- a/include/data.h +++ b/include/data.h @@ -177,6 +177,9 @@ struct Workspace { /** Number of this workspace, starting from 0 */ int num; + /** Name of the workspace (in UTF-8) */ + char *utf8_name; + /** Name of the workspace (in UCS-2) */ char *name; diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 40e01158..131c4b16 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -15,10 +15,26 @@ #ifndef _I3_IPC_H #define _I3_IPC_H +/* + * Messages from clients to i3 + * + */ + /** 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 +#define I3_IPC_MESSAGE_TYPE_COMMAND 0 + +/** Requests the current workspaces from i3 */ +#define I3_IPC_MESSAGE_TYPE_GET_WORKSPACES 1 + +/* + * Messages from i3 to clients + * + */ + +/** Workspaces reply type */ +#define I3_IPC_REPLY_TYPE_WORKSPACES 1 #endif diff --git a/src/ipc.c b/src/ipc.c index f7aeccf4..15676e4e 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -22,6 +22,7 @@ #include #include #include +#include #include "queue.h" #include "i3/ipc.h" @@ -29,6 +30,11 @@ #include "util.h" #include "commands.h" #include "log.h" +#include "table.h" + +/* Shorter names for all those yajl_gen_* functions */ +#define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__) +#define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str)) typedef struct ipc_client { int fd; @@ -60,6 +66,83 @@ void broadcast(EV_P_ struct ev_timer *t, int revents) { } #endif +static void ipc_send_message(int fd, const unsigned char *payload, + int message_type, int message_size) { + int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) + + sizeof(uint32_t) + message_size; + char msg[buffer_size]; + char *walk = msg; + + 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(fd, msg + sent_bytes, bytes_to_go); + if (n == -1) + err(EXIT_FAILURE, "write() failed"); + + sent_bytes += n; + bytes_to_go -= n; + } +} + +/* + * Formats the reply message for a GET_WORKSPACES request and sends it to the + * client + * + */ +static void ipc_send_workspaces(int fd) { + Workspace *ws; + + yajl_gen gen = yajl_gen_alloc(NULL, NULL); + y(array_open); + + TAILQ_FOREACH(ws, workspaces, workspaces) { + if (ws->output == NULL) + continue; + + y(map_open); + ystr("num"); + y(integer, ws->num); + + ystr("name"); + ystr(ws->utf8_name); + + ystr("rect"); + y(map_open); + ystr("x"); + y(integer, ws->rect.x); + ystr("y"); + y(integer, ws->rect.y); + ystr("width"); + y(integer, ws->rect.width); + ystr("height"); + y(integer, ws->rect.height); + y(map_close); + + ystr("output"); + ystr(ws->output->name); + + y(map_close); + } + + y(array_close); + + const unsigned char *payload; + unsigned int length; + y(get_buf, &payload, &length); + + ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_WORKSPACES, length); + y(free); +} + /* * Decides what to do with the received message. * @@ -70,8 +153,8 @@ void broadcast(EV_P_ struct ev_timer *t, int revents) { * 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) { +static void ipc_handle_message(int fd, uint8_t *message, int size, + uint32_t message_size, uint32_t message_type) { DLOG("handling message of size %d\n", size); DLOG("sender specified size %d\n", message_size); DLOG("sender specified type %d\n", message_type); @@ -88,6 +171,9 @@ static void ipc_handle_message(uint8_t *message, int size, break; } + case I3_IPC_MESSAGE_TYPE_GET_WORKSPACES: + ipc_send_workspaces(fd); + break; default: DLOG("unhandled ipc message\n"); break; @@ -175,7 +261,7 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) { message += sizeof(uint32_t); n -= sizeof(uint32_t); - ipc_handle_message(message, n, message_size, message_type); + ipc_handle_message(w->fd, message, n, message_size, message_type); n -= message_size; message += message_size; } diff --git a/src/workspace.c b/src/workspace.c index 94d6a068..59040132 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -84,13 +84,13 @@ void workspace_set_name(Workspace *ws, const char *name) { errx(1, "asprintf() failed"); FREE(ws->name); + FREE(ws->utf8_name); ws->name = convert_utf8_to_ucs2(label, &(ws->name_len)); if (config.font != NULL) ws->text_width = predict_text_width(global_conn, config.font, ws->name, ws->name_len); else ws->text_width = 0; - - free(label); + ws->utf8_name = label; } /* diff --git a/testcases/Makefile b/testcases/Makefile index d37450ab..010b595f 100644 --- a/testcases/Makefile +++ b/testcases/Makefile @@ -1,4 +1,4 @@ test: - PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" t/*.t + PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" t/15*.t all: test diff --git a/testcases/t/15-ipc-workspaces.t b/testcases/t/15-ipc-workspaces.t new file mode 100644 index 00000000..01947094 --- /dev/null +++ b/testcases/t/15-ipc-workspaces.t @@ -0,0 +1,56 @@ +#!perl +# vim:ts=4:sw=4:expandtab + +use Test::More tests => 8; +use Test::Exception; +use Data::Dumper; +use JSON::XS; +use List::MoreUtils qw(all); +use FindBin; +use lib "$FindBin::Bin/lib"; +use i3test; + +BEGIN { + use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); + use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); +} + +my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); +isa_ok($sock, 'IO::Socket::UNIX'); + +#################### +# Request workspaces +#################### + +# message type 1 is GET_WORKSPACES +my $message = "i3-ipc" . pack("LL", 0, 1); +$sock->write($message); + +####################################### +# Test the reply format for correctness +####################################### + +# The following lines duplicate functionality from recv_ipc_command +# to have it included in the test-suite. +my $buffer; +$sock->read($buffer, length($message)); +is(substr($buffer, 0, length("i3-ipc")), "i3-ipc", "ipc message received"); +my ($len, $type) = unpack("LL", substr($buffer, 6)); +is($type, 1, "correct reply type"); + +# read the payload +$sock->read($buffer, $len); +my $workspaces; + +######################### +# Actually test the reply +######################### + +lives_ok { $workspaces = decode_json($buffer) } 'JSON could be decoded'; + +ok(@{$workspaces} > 0, "More than zero workspaces found"); + +my $name_exists = all { defined($_->{name}) } @{$workspaces}; +ok($name_exists, "All workspaces have a name"); + +diag( "Testing i3, Perl $], $^X" ); diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index a60fd6d2..540551b0 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -40,4 +40,22 @@ sub format_ipc_command { return $message; } +sub recv_ipc_command { + my ($sock, $expected) = @_; + + my $buffer; + # header is 14 bytes ("i3-ipc" + 32 bit + 32 bit) + $sock->read($buffer, 14); + return undef unless substr($buffer, 0, length("i3-ipc")) eq "i3-ipc"; + + my ($len, $type) = unpack("LL", substr($buffer, 6)); + + return undef unless $type == $expected; + + # read the payload + $sock->read($buffer, $len); + + decode_json($buffer) +} + 1 From e11ca7540703f2251eac00cd1ed60a050ca53c2a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 11 Mar 2010 16:48:48 +0100 Subject: [PATCH 151/247] ipc: add active flag --- src/ipc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ipc.c b/src/ipc.c index 15676e4e..0df379b4 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -115,6 +115,9 @@ static void ipc_send_workspaces(int fd) { ystr("name"); ystr(ws->utf8_name); + ystr("active"); + y(bool, ws->output->current_workspace == ws); + ystr("rect"); y(map_open); ystr("x"); From 93a9f3c2445101bb697ea04ae02d245604fcf905 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 11 Mar 2010 23:34:29 +0100 Subject: [PATCH 152/247] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20mess=20up=20x?= =?UTF-8?q?/y=20coordinates=20in=20configurerequests=20for=20floating=20wi?= =?UTF-8?q?ndows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This was the cause for ticket #93, which actually has a false conclusion for the reason of this bug. This code needs to be refactored. --- src/handlers.c | 28 ++++++++++++++++++++++++---- testcases/t/12-floating-resize.t | 9 ++++++++- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index 37ae0e54..b43cc4f3 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -365,11 +365,31 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure if (client_is_floating(client)) { i3Font *font = load_font(conn, config.font); int mode = (client->container != NULL ? client->container->mode : MODE_DEFAULT); + /* TODO: refactor this code. we need a function to translate + * coordinates of child_rect/rect. */ - if (event->value_mask & XCB_CONFIG_WINDOW_X) - client->rect.x = event->x; - if (event->value_mask & XCB_CONFIG_WINDOW_Y) - client->rect.y = event->y; + if (event->value_mask & XCB_CONFIG_WINDOW_X) { + if (mode == MODE_STACK || mode == MODE_TABBED) { + client->rect.x = event->x - 2; + } else { + if (client->titlebar_position == TITLEBAR_OFF && client->borderless) + client->rect.x = event->x; + else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) + client->rect.x = event->x - 1; + else client->rect.x = event->x - 2; + } + } + if (event->value_mask & XCB_CONFIG_WINDOW_Y) { + if (mode == MODE_STACK || mode == MODE_TABBED) { + client->rect.y = event->y - 2; + } else { + if (client->titlebar_position == TITLEBAR_OFF && client->borderless) + client->rect.y = event->y; + else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) + client->rect.y = event->y - 1; + else client->rect.y = event->y - font->height - 2 - 2; + } + } if (event->value_mask & XCB_CONFIG_WINDOW_WIDTH) { if (mode == MODE_STACK || mode == MODE_TABBED) { client->rect.width = event->width + 2 + 2; diff --git a/testcases/t/12-floating-resize.t b/testcases/t/12-floating-resize.t index 034205ec..d908d345 100644 --- a/testcases/t/12-floating-resize.t +++ b/testcases/t/12-floating-resize.t @@ -4,7 +4,7 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 16; +use Test::More tests => 17; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -47,6 +47,13 @@ isa_ok($window, 'X11::XCB::Window'); $window->map; sleep 0.25; +# See if configurerequests cause window movements (they should not) +my ($a, $t) = $window->rect; +$window->rect(X11::XCB::Rect->new(x => $a->x, y => $a->y, width => $a->width, height => $a->height)); + +sleep 0.25; +my ($na, $nt) = $window->rect; +is_deeply($na, $a, 'Rects are equal after configurerequest'); sub test_resize { $window->rect(X11::XCB::Rect->new(x => 0, y => 0, width => 100, height => 100)); From 5f370b494cab934201691a770137c0452dd15221 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Mar 2010 00:41:40 +0100 Subject: [PATCH 153/247] =?UTF-8?q?bugfix:=20don=E2=80=99t=20remap=20stack?= =?UTF-8?q?=20windows=20errnously=20when=20changing=20workspaces?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes ticket #193 (long-standing rendering bug). --- src/workspace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/workspace.c b/src/workspace.c index 59040132..7a9a959c 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -345,7 +345,7 @@ void workspace_map_clients(xcb_connection_t *conn, Workspace *ws) { /* Map all stack windows, if any */ struct Stack_Window *stack_win; SLIST_FOREACH(stack_win, &stack_wins, stack_windows) - if (stack_win->container->workspace == ws) + if (stack_win->container->workspace == ws && stack_win->rect.height > 0) xcb_map_window(conn, stack_win->window); ignore_enter_notify_forall(conn, ws, false); From 7c1be8369235ca947e2ec56e8466d3e8220f92c4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Mar 2010 02:59:16 +0100 Subject: [PATCH 154/247] Update comment (Thanks Merovius) --- include/floating.h | 4 ++-- src/floating.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/floating.h b/include/floating.h index 03da9e08..d4942e56 100644 --- a/include/floating.h +++ b/include/floating.h @@ -62,8 +62,8 @@ void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event); /** - * Called when the user right-clicked on the titlebar of a floating window to - * resize it. + * Called when the user clicked on a floating window while holding the + * floating_modifier and the right mouse button. * Calls the drag_pointer function with the resize_window callback * */ diff --git a/src/floating.c b/src/floating.c index e5e80235..9a94e409 100644 --- a/src/floating.c +++ b/src/floating.c @@ -299,8 +299,8 @@ DRAGGING_CB(resize_window_callback) { } /* - * Called when the user right-clicked on the titlebar of a floating window to - * resize it. + * Called when the user clicked on a floating window while holding the + * floating_modifier and the right mouse button. * Calls the drag_pointer function with the resize_window callback * */ From 9ed5e00107f97c59d7b4c6461ab9de32b7815b7a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Mar 2010 03:01:34 +0100 Subject: [PATCH 155/247] ipc: change active to visible, introduce focused visible == currently visible on the output it is on (multiple workspaces can be visible at the same time) focused == has the focus (only one workspace can be focused) --- src/ipc.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ipc.c b/src/ipc.c index 0df379b4..20e41219 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -101,6 +101,10 @@ static void ipc_send_message(int fd, const unsigned char *payload, static void ipc_send_workspaces(int fd) { Workspace *ws; + Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); + if (last_focused == SLIST_END(&(c_ws->focus_stack))) + last_focused = NULL; + yajl_gen gen = yajl_gen_alloc(NULL, NULL); y(array_open); @@ -115,9 +119,12 @@ static void ipc_send_workspaces(int fd) { ystr("name"); ystr(ws->utf8_name); - ystr("active"); + ystr("visible"); y(bool, ws->output->current_workspace == ws); + ystr("focused"); + y(bool, (last_focused != NULL && last_focused->workspace == ws)); + ystr("rect"); y(map_open); ystr("x"); From 520f4d15d29aba4402f26f5349666fc06376df5a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Mar 2010 03:05:42 +0100 Subject: [PATCH 156/247] Add documentation for the IPC interface --- docs/Makefile | 5 +- docs/ipc.html | 766 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 770 insertions(+), 1 deletion(-) create mode 100644 docs/ipc.html diff --git a/docs/Makefile b/docs/Makefile index 123f839f..ffac9ab5 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,5 +1,5 @@ -all: hacking-howto.html debugging.html userguide.html +all: hacking-howto.html debugging.html userguide.html ipc.html hacking-howto.html: hacking-howto asciidoc -a toc -n $< @@ -10,6 +10,9 @@ debugging.html: debugging userguide.html: userguide asciidoc -a toc -n $< +ipc.html: ipc + asciidoc -a toc -n $< + clean: rm -f */*.{aux,log,toc,bm,pdf,dvi} rm -f *.log *.html diff --git a/docs/ipc.html b/docs/ipc.html new file mode 100644 index 00000000..298f9ef1 --- /dev/null +++ b/docs/ipc.html @@ -0,0 +1,766 @@ + + + + + +IPC interface (interprocess communication) + + + + +

+
+
+
+

This document describes how to interface with i3 from a separate process. This +is useful for example to remote-control i3 (to write test cases for example) or +to get various information like the current workspaces to implement an external +workspace bar.

+

The method of choice for IPC in our case is a unix socket because it has very +little overhead on both sides and is usually available without headaches in +most languages. In the default configuration file, no ipc-socket path is +specified and thus no socket is created. The standard path (which i3-msg and +i3-input use) is /tmp/i3-ipc.sock.

+
+
+

1. Establishing a connection

+
+

To establish a connection, simply open the IPC socket. The following code +snippet illustrates this in Perl:

+
+
+
use IO::Socket::UNIX;
+my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
+
+
+

2. Sending messages to i3

+
+

To send a message to i3, you have to format in the binary message format which +i3 expects. This format specifies a magic string in the beginning to ensure +the integrity of messages (to prevent follow-up errors). Afterwards follows +the length of the payload of the message as 32-bit integer and the type of +the message as 32-bit integer (the integers are not converted, so they are +in native byte order).

+

The magic string currently is "i3-ipc" and will only be changed when a change +in the IPC API is done which breaks compatibility (we hope that we don’t need +to do that).

+

Currently implemented message types are the following:

+
+
+0 (COMMAND) +
+
+

+ The payload of the message is a command for i3 (like the commands you + can bind to keys in the configuration file) and will be executed + directly after receiving it. There is no reply to this message. +

+
+
+1 (GET_WORKSPACES) +
+
+

+ Gets the current workspaces. The reply will be a JSON-encoded list of + workspaces (see the reply section). +

+
+
+

So, a typical message could look like this:

+
+
+
"i3-ipc" <message length> <message type> <payload>
+
+

Or, as a hexdump:

+
+
+
00000000  69 33 2d 69 70 63 04 00  00 00 00 00 00 00 65 78  |i3-ipc........ex|
+00000010  69 74 0a                                          |it.|
+
+

To generate and send such a message, you could use the following code in Perl:

+
+
+
sub format_ipc_command {
+    my ($msg) = @_;
+    my $len;
+    # Get the real byte count (vs. amount of characters)
+    { use bytes; $len = length($msg); }
+    return "i3-ipc" . pack("LL", $len, 0) . $msg;
+}
+
+$sock->write(format_ipc_command("exit"));
+
+
+

3. Receiving replies from i3

+
+

Replies of i3 usually consist of a simple string (the length of the string +is the message_length, so you can consider them length-prefixed) which in turn +contain the JSON serialization of a data structure. For example, the +GET_WORKSPACES message returns an array of workspaces (each workspace is a map +with certain attributes).

+

3.1. Reply format

+

The reply format is identical to the normal message format. There also is +the magic string, then the message length, then the message type and the +payload.

+

The following reply types are implemented:

+
+
+1 (GET_WORKSPACES) +
+
+

+ Reply to the GET_WORKSPACES message. +

+
+
+

3.2. GET_WORKSPACES reply

+

The reply consists of a serialized list of workspaces. Each workspace has the +following properties:

+
+
+num (integer) +
+
+

+ The internal number of the workspace. Corresponds to the command + to switch to this workspace. +

+
+
+name (string) +
+
+

+ The name of this workspace (by default num+1), as changed by the + user. Encoded in UTF-8. +

+
+
+visible (boolean) +
+
+

+ Whether this workspace is currently visible on an output (multiple + workspaces can be visible at the same time). +

+
+
+focused (boolean) +
+
+

+ Whether this workspace currently has the focus (only one workspace + can have the focus at the same time). +

+
+
+rect (map) +
+
+

+ The rectangle of this workspace (equals the rect of the output it + is on), consists of x, y, width, height. +

+
+
+output (string) +
+
+

+ The video output this workspace is on (LVDS1, VGA1, …). +

+
+
+

Example:

+
+
+
[
+ {
+  "num": 0,
+  "name": "1",
+  "visible": true,
+  "focused": true,
+  "rect": {
+   "x": 0,
+   "y": 0,
+   "width": 1280,
+   "height": 800
+  },
+  "output": "LVDS1"
+ },
+ {
+  "num": 1,
+  "name": "2",
+  "visible": false,
+  "focused": false,
+  "rect": {
+   "x": 0,
+   "y": 0,
+   "width": 1280,
+   "height": 800
+  },
+  "output": "LVDS1"
+ }
+]
+
+
+
+

+ + + From 4cbece46c28ad5b28285ec8a15388694a4c7130c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Mar 2010 03:06:40 +0100 Subject: [PATCH 157/247] committed the wrong file --- docs/ipc | 146 ++++++++++ docs/ipc.html | 766 -------------------------------------------------- 2 files changed, 146 insertions(+), 766 deletions(-) create mode 100644 docs/ipc delete mode 100644 docs/ipc.html diff --git a/docs/ipc b/docs/ipc new file mode 100644 index 00000000..d1783b25 --- /dev/null +++ b/docs/ipc @@ -0,0 +1,146 @@ +IPC interface (interprocess communication) +========================================== +Michael Stapelberg +March 2010 + +This document describes how to interface with i3 from a separate process. This +is useful for example to remote-control i3 (to write test cases for example) or +to get various information like the current workspaces to implement an external +workspace bar. + +The method of choice for IPC in our case is a unix socket because it has very +little overhead on both sides and is usually available without headaches in +most languages. In the default configuration file, no ipc-socket path is +specified and thus no socket is created. The standard path (which +i3-msg+ and ++i3-input+ use) is +/tmp/i3-ipc.sock+. + +== Establishing a connection + +To establish a connection, simply open the IPC socket. The following code +snippet illustrates this in Perl: + +------------------------------------------------------------- +use IO::Socket::UNIX; +my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); +------------------------------------------------------------- + +== Sending messages to i3 + +To send a message to i3, you have to format in the binary message format which +i3 expects. This format specifies a magic string in the beginning to ensure +the integrity of messages (to prevent follow-up errors). Afterwards follows +the length of the payload of the message as 32-bit integer and the type of +the message as 32-bit integer (the integers are not converted, so they are +in native byte order). + +The magic string currently is "i3-ipc" and will only be changed when a change +in the IPC API is done which breaks compatibility (we hope that we don’t need +to do that). + +Currently implemented message types are the following: + +0 (COMMAND):: + The payload of the message is a command for i3 (like the commands you + can bind to keys in the configuration file) and will be executed + directly after receiving it. There is no reply to this message. +1 (GET_WORKSPACES):: + Gets the current workspaces. The reply will be a JSON-encoded list of + workspaces (see the reply section). + +So, a typical message could look like this: +-------------------------------------------------- +"i3-ipc" +-------------------------------------------------- + +Or, as a hexdump: +------------------------------------------------------------------------------ +00000000 69 33 2d 69 70 63 04 00 00 00 00 00 00 00 65 78 |i3-ipc........ex| +00000010 69 74 0a |it.| +------------------------------------------------------------------------------ + +To generate and send such a message, you could use the following code in Perl: +------------------------------------------------------------ +sub format_ipc_command { + my ($msg) = @_; + my $len; + # Get the real byte count (vs. amount of characters) + { use bytes; $len = length($msg); } + return "i3-ipc" . pack("LL", $len, 0) . $msg; +} + +$sock->write(format_ipc_command("exit")); +------------------------------------------------------------ + +== Receiving replies from i3 + +Replies of i3 usually consist of a simple string (the length of the string +is the message_length, so you can consider them length-prefixed) which in turn +contain the JSON serialization of a data structure. For example, the +GET_WORKSPACES message returns an array of workspaces (each workspace is a map +with certain attributes). + +=== Reply format + +The reply format is identical to the normal message format. There also is +the magic string, then the message length, then the message type and the +payload. + +The following reply types are implemented: + +1 (GET_WORKSPACES):: + Reply to the GET_WORKSPACES message. + +=== GET_WORKSPACES reply + +The reply consists of a serialized list of workspaces. Each workspace has the +following properties: + +num (integer):: + The internal number of the workspace. Corresponds to the command + to switch to this workspace. +name (string):: + The name of this workspace (by default num+1), as changed by the + user. Encoded in UTF-8. +visible (boolean):: + Whether this workspace is currently visible on an output (multiple + workspaces can be visible at the same time). +focused (boolean):: + Whether this workspace currently has the focus (only one workspace + can have the focus at the same time). +rect (map):: + The rectangle of this workspace (equals the rect of the output it + is on), consists of x, y, width, height. +output (string):: + The video output this workspace is on (LVDS1, VGA1, …). + +*Example:* +------------------- +[ + { + "num": 0, + "name": "1", + "visible": true, + "focused": true, + "rect": { + "x": 0, + "y": 0, + "width": 1280, + "height": 800 + }, + "output": "LVDS1" + }, + { + "num": 1, + "name": "2", + "visible": false, + "focused": false, + "rect": { + "x": 0, + "y": 0, + "width": 1280, + "height": 800 + }, + "output": "LVDS1" + } +] +------------------- diff --git a/docs/ipc.html b/docs/ipc.html deleted file mode 100644 index 298f9ef1..00000000 --- a/docs/ipc.html +++ /dev/null @@ -1,766 +0,0 @@ - - - - - -IPC interface (interprocess communication) - - - - - -
-
-
-

This document describes how to interface with i3 from a separate process. This -is useful for example to remote-control i3 (to write test cases for example) or -to get various information like the current workspaces to implement an external -workspace bar.

-

The method of choice for IPC in our case is a unix socket because it has very -little overhead on both sides and is usually available without headaches in -most languages. In the default configuration file, no ipc-socket path is -specified and thus no socket is created. The standard path (which i3-msg and -i3-input use) is /tmp/i3-ipc.sock.

-
-
-

1. Establishing a connection

-
-

To establish a connection, simply open the IPC socket. The following code -snippet illustrates this in Perl:

-
-
-
use IO::Socket::UNIX;
-my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock');
-
-
-

2. Sending messages to i3

-
-

To send a message to i3, you have to format in the binary message format which -i3 expects. This format specifies a magic string in the beginning to ensure -the integrity of messages (to prevent follow-up errors). Afterwards follows -the length of the payload of the message as 32-bit integer and the type of -the message as 32-bit integer (the integers are not converted, so they are -in native byte order).

-

The magic string currently is "i3-ipc" and will only be changed when a change -in the IPC API is done which breaks compatibility (we hope that we don’t need -to do that).

-

Currently implemented message types are the following:

-
-
-0 (COMMAND) -
-
-

- The payload of the message is a command for i3 (like the commands you - can bind to keys in the configuration file) and will be executed - directly after receiving it. There is no reply to this message. -

-
-
-1 (GET_WORKSPACES) -
-
-

- Gets the current workspaces. The reply will be a JSON-encoded list of - workspaces (see the reply section). -

-
-
-

So, a typical message could look like this:

-
-
-
"i3-ipc" <message length> <message type> <payload>
-
-

Or, as a hexdump:

-
-
-
00000000  69 33 2d 69 70 63 04 00  00 00 00 00 00 00 65 78  |i3-ipc........ex|
-00000010  69 74 0a                                          |it.|
-
-

To generate and send such a message, you could use the following code in Perl:

-
-
-
sub format_ipc_command {
-    my ($msg) = @_;
-    my $len;
-    # Get the real byte count (vs. amount of characters)
-    { use bytes; $len = length($msg); }
-    return "i3-ipc" . pack("LL", $len, 0) . $msg;
-}
-
-$sock->write(format_ipc_command("exit"));
-
-
-

3. Receiving replies from i3

-
-

Replies of i3 usually consist of a simple string (the length of the string -is the message_length, so you can consider them length-prefixed) which in turn -contain the JSON serialization of a data structure. For example, the -GET_WORKSPACES message returns an array of workspaces (each workspace is a map -with certain attributes).

-

3.1. Reply format

-

The reply format is identical to the normal message format. There also is -the magic string, then the message length, then the message type and the -payload.

-

The following reply types are implemented:

-
-
-1 (GET_WORKSPACES) -
-
-

- Reply to the GET_WORKSPACES message. -

-
-
-

3.2. GET_WORKSPACES reply

-

The reply consists of a serialized list of workspaces. Each workspace has the -following properties:

-
-
-num (integer) -
-
-

- The internal number of the workspace. Corresponds to the command - to switch to this workspace. -

-
-
-name (string) -
-
-

- The name of this workspace (by default num+1), as changed by the - user. Encoded in UTF-8. -

-
-
-visible (boolean) -
-
-

- Whether this workspace is currently visible on an output (multiple - workspaces can be visible at the same time). -

-
-
-focused (boolean) -
-
-

- Whether this workspace currently has the focus (only one workspace - can have the focus at the same time). -

-
-
-rect (map) -
-
-

- The rectangle of this workspace (equals the rect of the output it - is on), consists of x, y, width, height. -

-
-
-output (string) -
-
-

- The video output this workspace is on (LVDS1, VGA1, …). -

-
-
-

Example:

-
-
-
[
- {
-  "num": 0,
-  "name": "1",
-  "visible": true,
-  "focused": true,
-  "rect": {
-   "x": 0,
-   "y": 0,
-   "width": 1280,
-   "height": 800
-  },
-  "output": "LVDS1"
- },
- {
-  "num": 1,
-  "name": "2",
-  "visible": false,
-  "focused": false,
-  "rect": {
-   "x": 0,
-   "y": 0,
-   "width": 1280,
-   "height": 800
-  },
-  "output": "LVDS1"
- }
-]
-
-
-
-

- - - From 0f5256dc72728f61054a18d1f47224d393cc95af Mon Sep 17 00:00:00 2001 From: Axel Wagner Date: Fri, 12 Mar 2010 01:31:48 +0100 Subject: [PATCH 158/247] Floating resize uses arbitrary corners This closes ticket #121 --- include/floating.h | 5 +++- src/floating.c | 68 ++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/include/floating.h b/include/floating.h index d4942e56..d368048b 100644 --- a/include/floating.h +++ b/include/floating.h @@ -21,7 +21,10 @@ typedef void(*callback_t)(xcb_connection_t*, Client*, Rect*, uint32_t, uint32_t, void *extra) /** On which border was the dragging initiated? */ -typedef enum { BORDER_LEFT, BORDER_RIGHT, BORDER_TOP, BORDER_BOTTOM} border_t; +typedef enum { BORDER_LEFT = (1 << 0), + BORDER_RIGHT = (1 << 1), + BORDER_TOP = (1 << 2), + BORDER_BOTTOM = (1 << 3)} border_t; /** * Enters floating mode for the given client. Correctly takes care of the diff --git a/src/floating.c b/src/floating.c index 9a94e409..0d97c27a 100644 --- a/src/floating.c +++ b/src/floating.c @@ -169,13 +169,13 @@ void floating_assign_to_workspace(Client *client, Workspace *new_workspace) { * extension and only on Mac OS X systems at the moment). * */ -struct callback_params { +struct resize_callback_params { border_t border; xcb_button_press_event_t *event; }; DRAGGING_CB(resize_callback) { - struct callback_params *params = extra; + struct resize_callback_params *params = extra; xcb_button_press_event_t *event = params->event; switch (params->border) { case BORDER_RIGHT: { @@ -251,7 +251,7 @@ int floating_border_click(xcb_connection_t *conn, Client *client, xcb_button_pre DLOG("border = %d\n", border); - struct callback_params params = { border, event }; + struct resize_callback_params params = { border, event }; drag_pointer(conn, client, event, XCB_NONE, border, resize_callback, ¶ms); @@ -282,17 +282,49 @@ void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_pre drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, drag_window_callback, event); } +/* + * This is an ugly data structure which we need because there is no standard + * way of having nested functions (only available as a gcc extension at the + * moment, clang doesn’t support it) or blocks (only available as a clang + * extension and only on Mac OS X systems at the moment). + * + */ +struct resize_window_callback_params { + border_t corner; + xcb_button_press_event_t *event; +}; + DRAGGING_CB(resize_window_callback) { - xcb_button_press_event_t *event = extra; - int32_t new_width = old_rect->width + (new_x - event->root_x); - int32_t new_height = old_rect->height + (new_y - event->root_y); + struct resize_window_callback_params *params = extra; + xcb_button_press_event_t *event = params->event; + border_t corner = params->corner; + + int32_t dest_x = client->rect.x; + int32_t dest_y = client->rect.y; + uint32_t dest_width; + uint32_t dest_height; + + if (corner & BORDER_LEFT) { + dest_x = old_rect->x + (new_x - event->root_x); + dest_width = old_rect->width - (new_x - event->root_x); + } else dest_width = old_rect->width + (new_x - event->root_x); + + if (corner & BORDER_TOP) { + dest_y = old_rect->y + (new_y - event->root_y); + dest_height = old_rect->height - (new_y - event->root_y); + } else dest_height = old_rect->height + (new_y - event->root_y); + /* Obey minimum window size and reposition the client */ - if (new_width > 0 && new_width >= client_min_width(client)) - client->rect.width = new_width; + if (dest_width > 0 && dest_width >= client_min_width(client)) { + client->rect.x = dest_x; + client->rect.width = dest_width; + } - if (new_height > 0 && new_height >= client_min_height(client)) - client->rect.height = new_height; + if (dest_height > 0 && dest_height >= client_min_height(client)) { + client->rect.y = dest_y; + client->rect.height = dest_height; + } /* resize_client flushes */ resize_client(conn, client); @@ -307,7 +339,21 @@ DRAGGING_CB(resize_window_callback) { void floating_resize_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) { DLOG("floating_resize_window\n"); - drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback, event); + /* corner saves the nearest corner to the original click. It contains + * a bitmask of the nearest borders (BORDER_LEFT, BORDER_RIGHT, …) */ + border_t corner = 0; + + if (event->event_x <= (client->rect.width / 2)) + corner |= BORDER_LEFT; + else corner |= BORDER_RIGHT; + + if (event->event_y <= (client->rect.height / 2)) + corner |= BORDER_TOP; + else corner |= BORDER_RIGHT; + + struct resize_window_callback_params params = { corner, event }; + + drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback, ¶ms); } From 5a3d1b38e8e67c11a20ff7ce7d40d0d464b8f702 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Mar 2010 15:02:00 +0100 Subject: [PATCH 159/247] ipc: Correctly deal with SIGPIPE/failing write()s If a client disconnects while i3 still wants to write the reply, this could lead to exits of i3 before. --- src/ipc.c | 6 ++++-- src/mainx.c | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/ipc.c b/src/ipc.c index 20e41219..629ec89d 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -85,8 +85,10 @@ static void ipc_send_message(int fd, const unsigned char *payload, int bytes_to_go = buffer_size; while (sent_bytes < bytes_to_go) { int n = write(fd, msg + sent_bytes, bytes_to_go); - if (n == -1) - err(EXIT_FAILURE, "write() failed"); + if (n == -1) { + DLOG("write() failed: %s\n", strerror(errno)); + return; + } sent_bytes += n; bytes_to_go -= n; diff --git a/src/mainx.c b/src/mainx.c index 8542ab22..84df8b2e 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -527,6 +527,11 @@ int main(int argc, char *argv[], char *env[]) { xcb_check_cb(NULL, NULL, 0); setup_signal_handler(); + + /* Ignore SIGPIPE to survive errors when an IPC client disconnects + * while we are sending him a message */ + signal(SIGPIPE, SIG_IGN); + /* Ungrab the server to receive events and enter libev’s eventloop */ xcb_ungrab_server(conn); From d6f726283ca2445a9c24065db2b57767cba37371 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Mar 2010 15:29:44 +0100 Subject: [PATCH 160/247] ipc: also send a reply for COMMAND messages --- include/i3/ipc.h | 3 +++ src/ipc.c | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 131c4b16..19d93298 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -34,6 +34,9 @@ * */ +/** Command reply type */ +#define I3_IPC_REPLY_TYPE_COMMAND 0 + /** Workspaces reply type */ #define I3_IPC_REPLY_TYPE_WORKSPACES 1 diff --git a/src/ipc.c b/src/ipc.c index 629ec89d..8bae4f07 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -181,6 +181,12 @@ static void ipc_handle_message(int fd, uint8_t *message, int size, parse_command(global_conn, (const char*)command); free(command); + /* For now, every command gets a positive acknowledge + * (will change with the new command parser) */ + const char *reply = "{\"success\":true}"; + ipc_send_message(fd, (const unsigned char*)reply, + I3_IPC_REPLY_TYPE_COMMAND, strlen(reply)); + break; } case I3_IPC_MESSAGE_TYPE_GET_WORKSPACES: From 77fe1f29b0cff10e84bec8e4c1682b67b95dc263 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Mar 2010 15:30:09 +0100 Subject: [PATCH 161/247] i3-msg: read replies, implement get_workspaces command --- i3-msg/Makefile | 2 ++ i3-msg/main.c | 90 ++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 80 insertions(+), 12 deletions(-) diff --git a/i3-msg/Makefile b/i3-msg/Makefile index a5e15b6e..ec4ba6e6 100644 --- a/i3-msg/Makefile +++ b/i3-msg/Makefile @@ -3,6 +3,8 @@ TOPDIR=.. include $(TOPDIR)/common.mk +CFLAGS += -I$(TOPDIR)/include + # Depend on the object files of all source-files in src/*.c and on all header files FILES=$(patsubst %.c,%.o,$(wildcard *.c)) HEADERS=$(wildcard *.h) diff --git a/i3-msg/main.c b/i3-msg/main.c index 197cceb6..4c0efbaa 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -16,6 +16,7 @@ */ #include #include +#include #include #include #include @@ -27,6 +28,8 @@ #include #include +#include + /* * Formats a message (payload) of the given size and type and sends it to i3 via * the given socket file descriptor. @@ -34,12 +37,12 @@ */ 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; + int buffer_size = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t) + message_size; char msg[buffer_size]; char *walk = msg; - strcpy(walk, "i3-ipc"); - walk += strlen("i3-ipc"); + strcpy(walk, I3_IPC_MAGIC); + walk += strlen(I3_IPC_MAGIC); memcpy(walk, &message_size, sizeof(uint32_t)); walk += sizeof(uint32_t); memcpy(walk, &message_type, sizeof(uint32_t)); @@ -58,26 +61,83 @@ static void ipc_send_message(int sockfd, uint32_t message_size, } } +static void ipc_recv_message(int sockfd, uint32_t message_type, + uint32_t *reply_length, uint8_t **reply) { + /* Read the message header first */ + uint32_t to_read = strlen(I3_IPC_MAGIC) + sizeof(uint32_t) + sizeof(uint32_t); + char msg[to_read]; + char *walk = msg; + + uint32_t read_bytes = 0; + while (read_bytes < to_read) { + int n = read(sockfd, msg + read_bytes, to_read); + if (n == -1) + err(EXIT_FAILURE, "read() failed"); + + read_bytes += n; + to_read -= n; + } + + if (memcmp(walk, I3_IPC_MAGIC, strlen(I3_IPC_MAGIC)) != 0) + errx(EXIT_FAILURE, "invalid magic in reply"); + + walk += strlen(I3_IPC_MAGIC); + *reply_length = *((uint32_t*)walk); + walk += sizeof(uint32_t); + if (*((uint32_t*)walk) != message_type) + errx(EXIT_FAILURE, "unexpected reply type (got %d, expected %d)", *((uint32_t*)walk), message_type); + walk += sizeof(uint32_t); + + *reply = malloc(*reply_length); + if ((*reply) == NULL) + err(EXIT_FAILURE, "malloc() failed"); + + to_read = *reply_length; + read_bytes = 0; + while (read_bytes < to_read) { + int n = read(sockfd, *reply + read_bytes, to_read); + if (n == -1) + err(EXIT_FAILURE, "read() failed"); + + read_bytes += n; + to_read -= n; + } +} + int main(int argc, char *argv[]) { char *socket_path = "/tmp/i3-ipc.sock"; int o, option_index = 0; + int message_type; + char *payload = ""; + bool quiet = false; static struct option long_options[] = { {"socket", required_argument, 0, 's'}, {"type", required_argument, 0, 't'}, {"version", no_argument, 0, 'v'}, + {"quiet", no_argument, 0, 'q'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; - char *options_string = "s:t:vh"; + char *options_string = "s:t:vhq"; 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"); + if (strcasecmp(optarg, "command") == 0) + message_type = I3_IPC_MESSAGE_TYPE_COMMAND; + else if (strcasecmp(optarg, "get_workspaces") == 0) + message_type = I3_IPC_MESSAGE_TYPE_GET_WORKSPACES; + else { + printf("Unknown message type\n"); + printf("Known types: command, get_workspaces\n"); + exit(EXIT_FAILURE); + } + } else if (o == 'q') { + quiet = true; } else if (o == 'v') { printf("i3-msg " I3_VERSION); return 0; @@ -88,11 +148,8 @@ int main(int argc, char *argv[]) { } } - if (optind >= argc) { - fprintf(stderr, "Error: missing message\n"); - fprintf(stderr, "i3-msg [-s ] [-t ] \n"); - return 1; - } + if (optind < argc) + payload = argv[optind]; int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); if (sockfd == -1) @@ -105,7 +162,16 @@ int main(int argc, char *argv[]) { if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) err(EXIT_FAILURE, "Could not connect to i3"); - ipc_send_message(sockfd, strlen(argv[optind]), 0, (uint8_t*)argv[optind]); + ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t*)payload); + + if (quiet) + return 0; + + uint32_t reply_length; + uint8_t *reply; + ipc_recv_message(sockfd, message_type, &reply_length, &reply); + printf("%.*s", reply_length, reply); + free(reply); close(sockfd); From 95666d007e0899a61e3a614d92aee0aa7d53cb0c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Mar 2010 15:45:36 +0100 Subject: [PATCH 162/247] i3-msg: Initialize message_type (Thanks badboy) --- 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 4c0efbaa..193d04e9 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -107,7 +107,7 @@ static void ipc_recv_message(int sockfd, uint32_t message_type, int main(int argc, char *argv[]) { char *socket_path = "/tmp/i3-ipc.sock"; int o, option_index = 0; - int message_type; + int message_type = I3_IPC_MESSAGE_TYPE_COMMAND; char *payload = ""; bool quiet = false; From d86531b958babece00593affcd4f1adf0352c819 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Mar 2010 15:59:38 +0100 Subject: [PATCH 163/247] ipc: return logical workspace numbers, not internal ones --- src/ipc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipc.c b/src/ipc.c index 8bae4f07..0d1d5b5c 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -116,7 +116,7 @@ static void ipc_send_workspaces(int fd) { y(map_open); ystr("num"); - y(integer, ws->num); + y(integer, ws->num + 1); ystr("name"); ystr(ws->utf8_name); From 5d4982e27a5be5ab8967b5e00ad1354b603bbd4f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Mar 2010 17:51:53 +0100 Subject: [PATCH 164/247] hacking-howto: add tabbed layout (Thanks msi) --- docs/hacking-howto | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/hacking-howto b/docs/hacking-howto index c9da4e43..ce97d2c8 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -227,8 +227,9 @@ moving them to the bottom you create a new row). === Container A container is the content of a table’s cell. It holds an arbitrary amount of -windows and has a specific layout (default layout or stack layout). Containers -can consume multiple table cells by modifying their colspan/rowspan attribute. +windows and has a specific layout (default layout, stack layout or tabbed +layout). Containers can consume multiple table cells by modifying their +colspan/rowspan attribute. === Client From 69ed5734221deaff4320f8c4f342d42b8cae3a63 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Mar 2010 18:17:27 +0100 Subject: [PATCH 165/247] hacking-howto: add tabbed layout (Thanks msi) --- docs/hacking-howto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hacking-howto b/docs/hacking-howto index ce97d2c8..017e0d0b 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -340,7 +340,7 @@ reparented (see section "Manage windows"). After reparenting the window, `render_layout()` is called which renders the internal layout table. The new window has been placed in the currently focused container and therefore the new window and the old windows (if any) need to be -moved/resized so that the currently active layout (default mode/stacking mode) +moved/resized so that the currently active layout (default/stacking/tabbed mode) is rendered correctly. To move/resize windows, a window is ``configured'' in X11-speak. From 3db4890683e8783b7fbf85099a517046ac64d613 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 12 Mar 2010 21:05:05 +0100 Subject: [PATCH 166/247] ipc: implement events, cleanup the code a bit --- include/i3/ipc.h | 13 ++++ include/ipc.h | 38 +++++++++- src/handlers.c | 5 +- src/ipc.c | 186 +++++++++++++++++++++++++++++++++-------------- src/workspace.c | 7 ++ 5 files changed, 192 insertions(+), 57 deletions(-) diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 19d93298..aad34976 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -29,6 +29,9 @@ /** Requests the current workspaces from i3 */ #define I3_IPC_MESSAGE_TYPE_GET_WORKSPACES 1 +/** Subscribe to the specified events */ +#define I3_IPC_MESSAGE_TYPE_SUBSCRIBE 2 + /* * Messages from i3 to clients * @@ -40,4 +43,14 @@ /** Workspaces reply type */ #define I3_IPC_REPLY_TYPE_WORKSPACES 1 +/** Subscription reply type */ +#define I3_IPC_REPLY_TYPE_SUBSCRIBE 2 + +/* + * Events from i3 to clients + * + */ + +#define I3_IPC_EVENT_WORKSPACE 0 + #endif diff --git a/include/ipc.h b/include/ipc.h index de4e2264..b798b5ff 100644 --- a/include/ipc.h +++ b/include/ipc.h @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -16,6 +16,34 @@ #include "i3/ipc.h" +typedef struct ipc_client { + int fd; + + /* The events which this client wants to receive */ + int num_events; + char **events; + + TAILQ_ENTRY(ipc_client) clients; +} ipc_client; + +/* + * Callback type for the different message types. + * + * 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. + * + */ +typedef void(*handler_t)(int, uint8_t*, int, uint32_t, uint32_t); + +/* Macro to declare a callback */ +#define IPC_HANDLER(name) \ + static void handle_ ## name (int fd, uint8_t *message, \ + int size, uint32_t message_size, \ + uint32_t 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 @@ -32,4 +60,12 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents); */ int ipc_create_socket(const char *filename); +/** + * Sends the specified event to all IPC clients which are currently connected + * and subscribed to this kind of event. + * + */ +void ipc_send_event(const char *event, uint32_t message_type, const char *payload); + + #endif diff --git a/src/handlers.c b/src/handlers.c index b43cc4f3..109281a1 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -39,6 +39,7 @@ #include "workspace.h" #include "log.h" #include "container.h" +#include "ipc.h" /* After mapping/unmapping windows, a notify event is generated. However, we don’t want it, since it’d trigger an infinite loop of switching between the different windows when @@ -573,8 +574,10 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti break; } - if (workspace_empty) + if (workspace_empty) { client->workspace->output = NULL; + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}"); + } /* Remove the urgency flag if set */ client->urgent = false; diff --git a/src/ipc.c b/src/ipc.c index 0d1d5b5c..f396c43b 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -23,9 +23,10 @@ #include #include #include +#include #include "queue.h" -#include "i3/ipc.h" +#include "ipc.h" #include "i3.h" #include "util.h" #include "commands.h" @@ -36,12 +37,6 @@ #define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__) #define ystr(str) yajl_gen_string(gen, (unsigned char*)str, strlen(str)) -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); /* @@ -57,15 +52,6 @@ static void set_nonblock(int sockfd) { 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 - static void ipc_send_message(int fd, const unsigned char *payload, int message_type, int message_size) { int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) + @@ -95,12 +81,56 @@ static void ipc_send_message(int fd, const unsigned char *payload, } } +/* + * Sends the specified event to all IPC clients which are currently connected + * and subscribed to this kind of event. + * + */ +void ipc_send_event(const char *event, uint32_t message_type, const char *payload) { + ipc_client *current; + TAILQ_FOREACH(current, &all_clients, clients) { + /* see if this client is interested in this event */ + bool interested = false; + for (int i = 0; i < current->num_events; i++) { + if (strcasecmp(current->events[i], event) != 0) + continue; + interested = true; + break; + } + if (!interested) + continue; + + ipc_send_message(current->fd, (const unsigned char*)payload, + message_type, strlen(payload)); + } +} + +/* + * Executes the command and returns whether it could be successfully parsed + * or not (at the moment, always returns true). + * + */ +IPC_HANDLER(command) { + /* To get a properly terminated buffer, we copy + * message_size bytes out of the buffer */ + char *command = scalloc(message_size); + strncpy(command, (const char*)message, message_size); + parse_command(global_conn, (const char*)command); + free(command); + + /* For now, every command gets a positive acknowledge + * (will change with the new command parser) */ + const char *reply = "{\"success\":true}"; + ipc_send_message(fd, (const unsigned char*)reply, + I3_IPC_REPLY_TYPE_COMMAND, strlen(reply)); +} + /* * Formats the reply message for a GET_WORKSPACES request and sends it to the * client * */ -static void ipc_send_workspaces(int fd) { +IPC_HANDLER(get_workspaces) { Workspace *ws; Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack)); @@ -156,48 +186,87 @@ static void ipc_send_workspaces(int fd) { } /* - * 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. + * Callback for the YAJL parser (will be called when a string is parsed). * */ -static void ipc_handle_message(int fd, uint8_t *message, int size, - uint32_t message_size, uint32_t message_type) { - DLOG("handling message of size %d\n", size); - DLOG("sender specified size %d\n", message_size); - DLOG("sender specified type %d\n", message_type); - DLOG("payload as a string = %s\n", message); +static int add_subscription(void *extra, const unsigned char *s, + unsigned int len) { + ipc_client *client = extra; - switch (message_type) { - case I3_IPC_MESSAGE_TYPE_COMMAND: { - /* To get a properly terminated buffer, we copy - * message_size bytes out of the buffer */ - char *command = scalloc(message_size); - strncpy(command, (const char*)message, message_size); - parse_command(global_conn, (const char*)command); - free(command); + DLOG("should add subscription to extra %p, sub %.*s\n", client, len, s); + int event = client->num_events; - /* For now, every command gets a positive acknowledge - * (will change with the new command parser) */ - const char *reply = "{\"success\":true}"; - ipc_send_message(fd, (const unsigned char*)reply, - I3_IPC_REPLY_TYPE_COMMAND, strlen(reply)); + client->num_events++; + client->events = realloc(client->events, client->num_events * sizeof(char*)); + /* We copy the string because it is not null-terminated and strndup() + * is missing on some BSD systems */ + client->events[event] = scalloc(len+1); + memcpy(client->events[event], s, len); - break; - } - case I3_IPC_MESSAGE_TYPE_GET_WORKSPACES: - ipc_send_workspaces(fd); - break; - default: - DLOG("unhandled ipc message\n"); - break; - } + DLOG("client is now subscribed to:\n"); + for (int i = 0; i < client->num_events; i++) + DLOG("event %s\n", client->events[i]); + DLOG("(done)\n"); + + return 1; } +/* + * Subscribes this connection to the event types which were given as a JSON + * serialized array in the payload field of the message. + * + */ +IPC_HANDLER(subscribe) { + yajl_handle p; + yajl_callbacks callbacks; + yajl_status stat; + ipc_client *current, *client = NULL; + + /* Search the ipc_client structure for this connection */ + TAILQ_FOREACH(current, &all_clients, clients) { + if (current->fd != fd) + continue; + + client = current; + break; + } + + if (client == NULL) { + ELOG("Could not find ipc_client data structure for fd %d\n", fd); + return; + } + + /* Setup the JSON parser */ + memset(&callbacks, 0, sizeof(yajl_callbacks)); + callbacks.yajl_string = add_subscription; + + p = yajl_alloc(&callbacks, NULL, NULL, (void*)client); + stat = yajl_parse(p, (const unsigned char*)message, message_size); + if (stat != yajl_status_ok) { + unsigned char *err; + err = yajl_get_error(p, true, (const unsigned char*)message, + message_size); + ELOG("YAJL parse error: %s\n", err); + yajl_free_error(p, err); + + const char *reply = "{\"success\":false}"; + ipc_send_message(fd, (const unsigned char*)reply, + I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply)); + yajl_free(p); + return; + } + yajl_free(p); + const char *reply = "{\"success\":true}"; + ipc_send_message(fd, (const unsigned char*)reply, + I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply)); +} + +handler_t handlers[3] = { + handle_command, + handle_get_workspaces, + handle_subscribe +}; + /* * Handler for activity on a client connection, receives a message from a * client. @@ -227,11 +296,13 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) { close(w->fd); /* Delete the client from the list of clients */ - struct ipc_client *current; + ipc_client *current; TAILQ_FOREACH(current, &all_clients, clients) { if (current->fd != w->fd) continue; + for (int i = 0; i < current->num_events; i++) + free(current->events[i]); /* We can call TAILQ_REMOVE because we break out of the * TAILQ_FOREACH afterwards */ TAILQ_REMOVE(&all_clients, current, clients); @@ -279,7 +350,12 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) { message += sizeof(uint32_t); n -= sizeof(uint32_t); - ipc_handle_message(w->fd, message, n, message_size, message_type); + if (message_type >= (sizeof(handlers) / sizeof(handler_t))) + DLOG("Unhandled message type: %d\n", message_type); + else { + handler_t h = handlers[message_type]; + h(w->fd, message, n, message_size, message_type); + } n -= message_size; message += message_size; } @@ -311,7 +387,7 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) { DLOG("IPC: new client connected\n"); - struct ipc_client *new = scalloc(sizeof(struct ipc_client)); + ipc_client *new = scalloc(sizeof(ipc_client)); new->fd = client; TAILQ_INSERT_TAIL(&all_clients, new, clients); diff --git a/src/workspace.c b/src/workspace.c index 7a9a959c..3b8c6ba4 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -28,6 +28,7 @@ #include "client.h" #include "log.h" #include "ewmh.h" +#include "ipc.h" /* * Returns a pointer to the workspace with the given number (starting at 0), @@ -57,6 +58,8 @@ Workspace *workspace_get(int number) { workspace_set_name(ws, NULL); TAILQ_INSERT_TAIL(workspaces, ws, workspaces); + + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); } DLOG("done\n"); @@ -165,6 +168,8 @@ void workspace_show(xcb_connection_t *conn, int workspace) { xcb_flush(conn); } + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}"); + return; } @@ -178,6 +183,8 @@ void workspace_show(xcb_connection_t *conn, int workspace) { current_col = c_ws->current_col; DLOG("new current row = %d, current col = %d\n", current_row, current_col); + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}"); + workspace_map_clients(conn, c_ws); /* POTENTIAL TO IMPROVE HERE: due to the call to _map_clients first and From 50d06612037d0f7f38f6e90d0250ed30f8dcb1a3 Mon Sep 17 00:00:00 2001 From: Axel Wagner Date: Sat, 13 Mar 2010 00:59:16 +0100 Subject: [PATCH 167/247] Add documentation for floating resize --- docs/userguide | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/userguide b/docs/userguide index b7c84b21..2910e29b 100644 --- a/docs/userguide +++ b/docs/userguide @@ -283,6 +283,9 @@ it as the same one you use for managing windows (Mod1 for example). Afterwards, you can press Mod1, click into a window using your left mouse button and drag it to the position you want it at. +When holding the floating modifier, you can resize a floating window by pressing +the right mousebutton on it and moving around holding it. + *Syntax*: -------------------------------- floating_modifier From 9cb35383a8a942524949899e18b1fea057fea68a Mon Sep 17 00:00:00 2001 From: Axel Wagner Date: Sat, 13 Mar 2010 10:38:22 +0100 Subject: [PATCH 168/247] Implement proportional floating-resize. --- include/floating.h | 2 +- src/click.c | 3 ++- src/floating.c | 44 ++++++++++++++++++++++++++++---------------- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/include/floating.h b/include/floating.h index d368048b..aa9d9d5f 100644 --- a/include/floating.h +++ b/include/floating.h @@ -71,7 +71,7 @@ void floating_drag_window(xcb_connection_t *conn, Client *client, * */ void floating_resize_window(xcb_connection_t *conn, Client *client, - xcb_button_press_event_t *event); + bool proportional, xcb_button_press_event_t *event); /** * Changes focus in the given direction for floating clients. diff --git a/src/click.c b/src/click.c index d792ce23..bec86788 100644 --- a/src/click.c +++ b/src/click.c @@ -280,8 +280,9 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ DLOG("left mouse button, dragging\n"); floating_drag_window(conn, client, event); } else if (event->detail == 3) { + bool proportional = (event->state & BIND_SHIFT); DLOG("right mouse button\n"); - floating_resize_window(conn, client, event); + floating_resize_window(conn, client, proportional, event); } return 1; } diff --git a/src/floating.c b/src/floating.c index 0d97c27a..61e95599 100644 --- a/src/floating.c +++ b/src/floating.c @@ -291,6 +291,7 @@ void floating_drag_window(xcb_connection_t *conn, Client *client, xcb_button_pre */ struct resize_window_callback_params { border_t corner; + bool proportional; xcb_button_press_event_t *event; }; @@ -304,27 +305,37 @@ DRAGGING_CB(resize_window_callback) { uint32_t dest_width; uint32_t dest_height; - if (corner & BORDER_LEFT) { - dest_x = old_rect->x + (new_x - event->root_x); + double ratio = (double) old_rect->width / old_rect->height; + + /* First guess: We resize by exactly the amount the mouse moved, + * taking into account in which corner the client was grabbed */ + if (corner & BORDER_LEFT) dest_width = old_rect->width - (new_x - event->root_x); - } else dest_width = old_rect->width + (new_x - event->root_x); + else dest_width = old_rect->width + (new_x - event->root_x); - if (corner & BORDER_TOP) { - dest_y = old_rect->y + (new_y - event->root_y); + if (corner & BORDER_TOP) dest_height = old_rect->height - (new_y - event->root_y); - } else dest_height = old_rect->height + (new_y - event->root_y); + else dest_height = old_rect->height + (new_y - event->root_y); + /* Obey minimum window size */ + dest_width = max(dest_width, client_min_width(client)); + dest_height = max(dest_height, client_min_height(client)); - /* Obey minimum window size and reposition the client */ - if (dest_width > 0 && dest_width >= client_min_width(client)) { - client->rect.x = dest_x; - client->rect.width = dest_width; + /* User wants to keep proportions, so we may have to adjust our values */ + if (params->proportional) { + dest_width = max(dest_width, (int) (dest_height * ratio)); + dest_height = max(dest_height, (int) (dest_width / ratio)); } - if (dest_height > 0 && dest_height >= client_min_height(client)) { - client->rect.y = dest_y; - client->rect.height = dest_height; - } + /* If not the lower right corner is grabbed, we must also reposition + * the client by exactly the amount we resized it */ + if (corner & BORDER_LEFT) + dest_x = old_rect->x + (old_rect->width - dest_width); + + if (corner & BORDER_TOP) + dest_y = old_rect->y + (old_rect->height - dest_height); + + client->rect = (Rect) { dest_x, dest_y, dest_width, dest_height }; /* resize_client flushes */ resize_client(conn, client); @@ -336,7 +347,8 @@ DRAGGING_CB(resize_window_callback) { * Calls the drag_pointer function with the resize_window callback * */ -void floating_resize_window(xcb_connection_t *conn, Client *client, xcb_button_press_event_t *event) { +void floating_resize_window(xcb_connection_t *conn, Client *client, + bool proportional, xcb_button_press_event_t *event) { DLOG("floating_resize_window\n"); /* corner saves the nearest corner to the original click. It contains @@ -351,7 +363,7 @@ void floating_resize_window(xcb_connection_t *conn, Client *client, xcb_button_p corner |= BORDER_TOP; else corner |= BORDER_RIGHT; - struct resize_window_callback_params params = { corner, event }; + struct resize_window_callback_params params = { corner, proportional, event }; drag_pointer(conn, client, event, XCB_NONE, BORDER_TOP /* irrelevant */, resize_window_callback, ¶ms); } From 6580f89560b97e49686aa39d452f6d56ca39ffaa Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 13 Mar 2010 13:24:26 +0100 Subject: [PATCH 169/247] Mention proportional resizing in userguide --- docs/userguide | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/userguide b/docs/userguide index 2910e29b..75179664 100644 --- a/docs/userguide +++ b/docs/userguide @@ -284,7 +284,8 @@ you can press Mod1, click into a window using your left mouse button and drag it to the position you want it at. When holding the floating modifier, you can resize a floating window by pressing -the right mousebutton on it and moving around holding it. +the right mousebutton on it and moving around holding it. If you hold the shift +button aswell, the resize will be proportional. *Syntax*: -------------------------------- From b7da973d09fa9508bfc996fed66524847481d690 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 13 Mar 2010 15:04:23 +0100 Subject: [PATCH 170/247] ipc: change message type of events (first bit set high) --- include/i3/ipc.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/i3/ipc.h b/include/i3/ipc.h index aad34976..5adec378 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -47,10 +47,11 @@ #define I3_IPC_REPLY_TYPE_SUBSCRIBE 2 /* - * Events from i3 to clients + * Events from i3 to clients. Events have the first bit set high. * */ +#define I3_IPC_EVENT_MASK (1 << 31) -#define I3_IPC_EVENT_WORKSPACE 0 +#define I3_IPC_EVENT_WORKSPACE (I3_IPC_EVENT_MASK | 0) #endif From b6088b803e4ccd35ba712bac20b691d6f990a671 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 13 Mar 2010 16:27:03 +0100 Subject: [PATCH 171/247] ipc: correctly set "focused" for workspaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Didn’t work on empty workspaces before --- src/ipc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipc.c b/src/ipc.c index f396c43b..f74f437e 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -155,7 +155,7 @@ IPC_HANDLER(get_workspaces) { y(bool, ws->output->current_workspace == ws); ystr("focused"); - y(bool, (last_focused != NULL && last_focused->workspace == ws)); + y(bool, c_ws == ws); ystr("rect"); y(map_open); From a0465a9a4fe6edee10b784ae3dd2c18f42663b07 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 13 Mar 2010 19:09:49 +0100 Subject: [PATCH 172/247] Update IPC documentation --- docs/ipc | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 89 insertions(+), 4 deletions(-) diff --git a/docs/ipc b/docs/ipc index d1783b25..91ab5fa8 100644 --- a/docs/ipc +++ b/docs/ipc @@ -39,13 +39,16 @@ to do that). Currently implemented message types are the following: -0 (COMMAND):: +COMMAND (0):: The payload of the message is a command for i3 (like the commands you can bind to keys in the configuration file) and will be executed directly after receiving it. There is no reply to this message. -1 (GET_WORKSPACES):: +GET_WORKSPACES (1):: Gets the current workspaces. The reply will be a JSON-encoded list of workspaces (see the reply section). +SUBSCRIBE (2):: + Subscribes your connection to certain events. See <> for a + description of this message and the concept of events. So, a typical message could look like this: -------------------------------------------------- @@ -87,8 +90,22 @@ payload. The following reply types are implemented: -1 (GET_WORKSPACES):: +COMMAND (0):: + Confirmation/Error code for the COMMAND message. +GET_WORKSPACES (1):: Reply to the GET_WORKSPACES message. +SUBSCRIBE (2):: + Confirmation/Error code for the SUBSCRIBE message. + +=== COMMAND reply + +The reply consists of a single serialized map. At the moment, the only +property is +success (bool)+, but this will be expanded in future versions. + +*Example:* +------------------- +{ "success": true } +------------------- === GET_WORKSPACES reply @@ -96,7 +113,7 @@ The reply consists of a serialized list of workspaces. Each workspace has the following properties: num (integer):: - The internal number of the workspace. Corresponds to the command + The logical number of the workspace. Corresponds to the command to switch to this workspace. name (string):: The name of this workspace (by default num+1), as changed by the @@ -144,3 +161,71 @@ output (string):: } ] ------------------- + +=== SUBSCRIBE reply + +The reply consists of a single serialized map. The only property is ++success (bool)+, indicating whether the subscription was successful (the +default) or whether a JSON parse error occurred. + +*Example:* +------------------- +{ "success": true } +------------------- + +== Events + +[[events]] + +To get informed when certain things happen in i3, clients can subscribe to +events. Events consist of a name (like "workspace") and an event reply type +(like I3_IPC_EVENT_WORKSPACE). The events sent by i3 are in the same format +as replies to specific commands. + +Caveat: As soon as you subscribe to an event, it is not guaranteed any longer +that the requests to i3 are processed in order. This means, the following +situation can happen: You send a GET_WORKSPACES request but you receive a +"workspace" event before receiving the reply to GET_WORKSPACES. If your +program does not want to cope which such kinds of race conditions (an +event based library may not have a problem here), I advise to create a separate +connection to receive events. + +=== Subscribing to events + +By sending a message of type SUBSCRIBE with a JSON-encoded array as payload +you can register to an event. + +*Example:* +--------------------------------- +type: SUBSCRIBE +payload: [ "workspace", "focus" ] +--------------------------------- + +=== Available events + +workspace:: + Sent when the user switches to a different workspace, when a new + workspace is initialized or when a workspace is removed (because the + last client vanished). + +=== workspace event + +This event consists of a single serialized map containing a property ++change (string)+ which indicates the type of the change ("focus", "init", +"empty"). + +*Example:* +--------------------- +{ "change": "focus" } +--------------------- + +== See also + +For some languages, libraries are available (so you don’t have to implement +all this on your own). This list names some (if you wrote one, please let me +know): + +Ruby:: + http://github.com/badboy/i3-ipc +Perl:: + http://search.cpan.org/search?query=AnyEvent::I3 From 2df374ca4cf770f32cdda2ff4e1b8be092131270 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 13 Mar 2010 19:15:28 +0100 Subject: [PATCH 173/247] Add configuration option to turn off workspace bar --- include/config.h | 6 ++++++ src/cfgparse.l | 1 + src/cfgparse.y | 10 ++++++++++ 3 files changed, 17 insertions(+) diff --git a/include/config.h b/include/config.h index 627fe525..d80fbb96 100644 --- a/include/config.h +++ b/include/config.h @@ -98,6 +98,12 @@ struct Config { * It is not planned to add any different focus models. */ bool disable_focus_follows_mouse; + /** By default, a workspace bar is drawn at the bottom of the screen. + * If you want to have a more fancy bar, it is recommended to replace + * the whole bar by dzen2, for example using the i3-wsbar script which + * comes with i3. Thus, you can turn it off entirely. */ + bool disable_workspace_bar; + const char *default_border; /** The modifier which needs to be pressed in combination with your mouse diff --git a/src/cfgparse.l b/src/cfgparse.l index 67a41300..755065dd 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -93,6 +93,7 @@ ipc_socket { BEGIN(BIND_AWS_COND); return TOKIPCSOCKET; } new_container { return TOKNEWCONTAINER; } new_window { return TOKNEWWINDOW; } focus_follows_mouse { return TOKFOCUSFOLLOWSMOUSE; } +workspace_bar { return TOKWORKSPACEBAR; } default { yylval.number = MODE_DEFAULT; return TOKCONTAINERMODE; } stacking { yylval.number = MODE_STACK; return TOKCONTAINERMODE; } tabbed { yylval.number = MODE_TABBED; return TOKCONTAINERMODE; } diff --git a/src/cfgparse.y b/src/cfgparse.y index 9ed17ea1..b9fae726 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -221,6 +221,7 @@ void parse_file(const char *f) { %token TOKNEWCONTAINER "new_container" %token TOKNEWWINDOW "new_window" %token TOKFOCUSFOLLOWSMOUSE "focus_follows_mouse" +%token TOKWORKSPACEBAR "workspace_bar" %token TOKCONTAINERMODE "default/stacking/tabbed" %token TOKSTACKLIMIT "stack-limit" @@ -239,6 +240,7 @@ line: | new_container | new_window | focus_follows_mouse + | workspace_bar | workspace | assign | ipcsocket @@ -429,6 +431,14 @@ focus_follows_mouse: } ; +workspace_bar: + TOKWORKSPACEBAR WHITESPACE bool + { + DLOG("workspace bar = %d\n", $3); + config.disable_workspace_bar = !($3); + } + ; + workspace: TOKWORKSPACE WHITESPACE NUMBER WHITESPACE TOKOUTPUT WHITESPACE OUTPUT optional_workspace_name { From 2df1fb8ac825c77b2744812d12dfcdc762bc7d75 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 14 Mar 2010 12:59:45 +0100 Subject: [PATCH 174/247] Implement disabling the internal workspace bar --- src/layout.c | 8 +++++--- src/randr.c | 22 ++++++++++++---------- src/workspace.c | 3 ++- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/layout.c b/src/layout.c index 04f9cf60..202f5601 100644 --- a/src/layout.c +++ b/src/layout.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -682,7 +682,8 @@ void render_workspace(xcb_connection_t *conn, Output *output, Workspace *r_ws) { height -= client->desired_height; /* Space for the internal bar */ - height -= (font->height + 6); + if (!config.disable_workspace_bar) + height -= (font->height + 6); int xoffset[r_ws->rows]; int yoffset[r_ws->cols]; @@ -739,7 +740,8 @@ void render_workspace(xcb_connection_t *conn, Output *output, Workspace *r_ws) { ignore_enter_notify_forall(conn, r_ws, false); render_bars(conn, r_ws, width, &height); - render_internal_bar(conn, r_ws, width, font->height + 6); + if (!config.disable_workspace_bar) + render_internal_bar(conn, r_ws, width, font->height + 6); } /* diff --git a/src/randr.c b/src/randr.c index 7cf9d514..9ef45fd0 100644 --- a/src/randr.c +++ b/src/randr.c @@ -173,16 +173,18 @@ void initialize_output(xcb_connection_t *conn, Output *output, Workspace *worksp /* Map clients on the workspace, if any */ workspace_map_clients(conn, workspace); - /* Create a xoutput for each output */ - Rect bar_rect = {output->rect.x, - output->rect.y + output->rect.height - (font->height + 6), - output->rect.x + output->rect.width, - font->height + 6}; - uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; - uint32_t values[] = {1, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS}; - output->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, true, mask, values); - output->bargc = xcb_generate_id(conn); - xcb_create_gc(conn, output->bargc, output->bar, 0, 0); + /* Create a bar window on each output */ + if (!config.disable_workspace_bar) { + Rect bar_rect = {output->rect.x, + output->rect.y + output->rect.height - (font->height + 6), + output->rect.x + output->rect.width, + font->height + 6}; + uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; + uint32_t values[] = {1, XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS}; + output->bar = create_window(conn, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, true, mask, values); + output->bargc = xcb_generate_id(conn); + xcb_create_gc(conn, output->bargc, output->bar, 0, 0); + } SLIST_INIT(&(output->dock_clients)); diff --git a/src/workspace.c b/src/workspace.c index 3b8c6ba4..8d762729 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -460,7 +460,8 @@ int workspace_height(Workspace *ws) { height -= client->desired_height; /* Space for the internal bar */ - height -= (font->height + 6); + if (!config.disable_workspace_bar) + height -= (font->height + 6); return height; } From c738b2e454bb8b096dd99d44e9e51030f8355b90 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 14 Mar 2010 22:35:51 +0100 Subject: [PATCH 175/247] =?UTF-8?q?Don=E2=80=99t=20use=20SYNC=20key=20bind?= =?UTF-8?q?ings=20for=20Mode=5Fswitch=20but=20re-grab=20keys?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this commit, i3 used key bindings in SYNC mode for bindings like Mode_switch + and replayed the key if the current state did not include Mode_switch. This had some problems: 1) The WM needed to acknowledge much more key presses than you actually had bindings for, thus making the system a bit laggy sometimes. 2) Users of layouts who constantly type in the third level (like russian layouts) did not get their cyrillic symbols correctly (they were not replayed right), neither did the keybindings work in both modes. So, the current implementation uses the following approach: XKB provides an event which contains the current state (including the current level). i3 signs up for this event and upon receival, it re-maps the bindings using Mode_switch (enables them when the level goes to the third level and disables them as soon as the level goes back to normal). This fixes both problems. --- include/config.h | 15 +++++++- include/handlers.h | 8 ---- include/i3.h | 1 + src/config.c | 95 ++++++++++++++++++++++++++++++++++++---------- src/handlers.c | 65 ++++++++----------------------- src/mainx.c | 74 +++++++++++++++++++++++++++++------- 6 files changed, 166 insertions(+), 92 deletions(-) diff --git a/include/config.h b/include/config.h index d80fbb96..0b671b7a 100644 --- a/include/config.h +++ b/include/config.h @@ -133,6 +133,12 @@ struct Config { */ void load_configuration(xcb_connection_t *conn, const char *override_configfile, bool reload); +/** + * Translates keysymbols to keycodes for all bindings which use keysyms. + * + */ +void translate_keysyms(); + /** * Ungrabs all keys, to be called before re-grabbing the keys because of a * mapping_notify event or a configuration file reload @@ -144,7 +150,7 @@ void ungrab_all_keys(xcb_connection_t *conn); * Grab the bound keys (tell X to send us keypress events for those keycodes) * */ -void grab_all_keys(xcb_connection_t *conn); +void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch); /** * Switches the key bindings to the given mode, if the mode exists @@ -152,6 +158,13 @@ void grab_all_keys(xcb_connection_t *conn); */ void switch_mode(xcb_connection_t *conn, const char *new_mode); +/** + * Returns a pointer to the Binding with the specified modifiers and keycode + * or NULL if no such binding exists. + * + */ +Binding *get_binding(uint16_t modifiers, xcb_keycode_t keycode); + /* prototype for src/cfgparse.y */ void parse_file(const char *f); diff --git a/include/handlers.h b/include/handlers.h index 5f0586f8..03be5281 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -13,14 +13,6 @@ #include -/** - * Due to bindings like Mode_switch + , we need to bind some keys in - * XCB_GRAB_MODE_SYNC. Therefore, we just replay all key presses. - * - */ -int handle_key_release(void *ignored, xcb_connection_t *conn, - xcb_key_release_event_t *event); - /** * There was a key press. We compare this key code with our bindings table and * pass the bound action to parse_command(). diff --git a/include/i3.h b/include/i3.h index 77c792a7..bf9d4b81 100644 --- a/include/i3.h +++ b/include/i3.h @@ -27,6 +27,7 @@ extern xcb_connection_t *global_conn; extern xcb_key_symbols_t *keysyms; extern char **start_argv; extern Display *xkbdpy; +extern int xkb_current_group; extern TAILQ_HEAD(bindings_head, Binding) *bindings; extern TAILQ_HEAD(autostarts_head, Autostart) autostarts; extern TAILQ_HEAD(assignments_head, Assignment) assignments; diff --git a/src/config.c b/src/config.c index 4babce1b..8d32ad4a 100644 --- a/src/config.c +++ b/src/config.c @@ -70,31 +70,62 @@ void ungrab_all_keys(xcb_connection_t *conn) { static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint32_t keycode) { DLOG("Grabbing %d\n", keycode); - if ((bind->mods & BIND_MODE_SWITCH) != 0) - xcb_grab_key(conn, 0, root, 0, 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, 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 the key in all combinations */ + #define GRAB_KEY(modifier) \ + do { \ + xcb_grab_key(conn, 0, root, modifier, keycode, \ + XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); \ + } while (0) + int mods = bind->mods; + if ((bind->mods & BIND_MODE_SWITCH) != 0) { + mods &= ~BIND_MODE_SWITCH; + if (mods == 0) + mods = XCB_MOD_MASK_ANY; } + GRAB_KEY(mods); + GRAB_KEY(mods | xcb_numlock_mask); + GRAB_KEY(mods | xcb_numlock_mask | XCB_MOD_MASK_LOCK); } /* - * Grab the bound keys (tell X to send us keypress events for those keycodes) + * Returns a pointer to the Binding with the specified modifiers and keycode + * or NULL if no such binding exists. * */ -void grab_all_keys(xcb_connection_t *conn) { +Binding *get_binding(uint16_t modifiers, xcb_keycode_t keycode) { + Binding *bind; + + TAILQ_FOREACH(bind, bindings, bindings) { + /* First compare the modifiers */ + if (bind->mods != modifiers) + continue; + + /* If a symbol was specified by the user, we need to look in + * the array of translated keycodes for the event’s keycode */ + if (bind->symbol != NULL) { + if (memmem(bind->translated_to, + bind->number_keycodes * sizeof(xcb_keycode_t), + &keycode, sizeof(xcb_keycode_t)) != NULL) + break; + } else { + /* This case is easier: The user specified a keycode */ + if (bind->keycode == keycode) + break; + } + } + + return (bind == TAILQ_END(bindings) ? NULL : bind); +} + +/* + * Translates keysymbols to keycodes for all bindings which use keysyms. + * + */ +void translate_keysyms() { Binding *bind; TAILQ_FOREACH(bind, bindings, bindings) { - /* The easy case: the user specified a keycode directly. */ - if (bind->keycode > 0) { - grab_keycode_for_binding(conn, bind, bind->keycode); + if (bind->keycode > 0) continue; - } /* We need to translate the symbol to a keycode */ xcb_keysym_t keysym = XStringToKeysym(bind->symbol); @@ -125,7 +156,6 @@ void grab_all_keys(xcb_connection_t *conn) { * and skip them */ if (last_keycode == *walk) continue; - grab_keycode_for_binding(conn, bind, *walk); last_keycode = *walk; bind->number_keycodes++; } @@ -137,6 +167,29 @@ void grab_all_keys(xcb_connection_t *conn) { } } +/* + * Grab the bound keys (tell X to send us keypress events for those keycodes) + * + */ +void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch) { + Binding *bind; + TAILQ_FOREACH(bind, bindings, bindings) { + if ((bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) == 0) || + (!bind_mode_switch && (bind->mods & BIND_MODE_SWITCH) != 0)) + continue; + + /* The easy case: the user specified a keycode directly. */ + if (bind->keycode > 0) { + grab_keycode_for_binding(conn, bind, bind->keycode); + continue; + } + + xcb_keycode_t *walk = bind->translated_to; + for (int i = 0; i < bind->number_keycodes; i++) + grab_keycode_for_binding(conn, bind, *walk); + } +} + /* * Switches the key bindings to the given mode, if the mode exists * @@ -152,7 +205,7 @@ void switch_mode(xcb_connection_t *conn, const char *new_mode) { ungrab_all_keys(conn); bindings = mode->bindings; - grab_all_keys(conn); + grab_all_keys(conn, false); return; } @@ -304,8 +357,10 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, parse_configuration(override_configpath); - if (reload) - grab_all_keys(conn); + if (reload) { + translate_keysyms(); + grab_all_keys(conn, false); + } REQUIRED_OPTION(font); diff --git a/src/handlers.c b/src/handlers.c index 109281a1..b5c1cf1b 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -82,17 +82,6 @@ static bool event_is_ignored(const int sequence) { return false; } -/* - * Due to bindings like Mode_switch + , we need to bind some keys in XCB_GRAB_MODE_SYNC. - * Therefore, we just replay all key presses. - * - */ -int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_release_event_t *event) { - xcb_allow_events(conn, XCB_ALLOW_REPLAY_KEYBOARD, event->time); - xcb_flush(conn); - return 1; -} - /* * There was a key press. We compare this key code with our bindings table and pass * the bound action to parse_command(). @@ -109,52 +98,29 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_ state_filtered &= 0xFF; DLOG("(removed upper 8 bits, state = %d)\n", state_filtered); - if (xkb_supported) { - /* We need to get the keysym group (There are group 1 to group 4, each holding - two keysyms (without shift and with shift) using Xkb because X fails to - provide them reliably (it works in Xephyr, it does not in real X) */ - XkbStateRec state; - if (XkbGetState(xkbdpy, XkbUseCoreKbd, &state) == Success && (state.group+1) == 2) - state_filtered |= BIND_MODE_SWITCH; - } + if (xkb_current_group == XkbGroup2Index) + state_filtered |= BIND_MODE_SWITCH; DLOG("(checked mode_switch, state %d)\n", state_filtered); /* Find the binding */ - Binding *bind; - TAILQ_FOREACH(bind, bindings, bindings) { - /* First compare the modifiers */ - if (bind->mods != state_filtered) - continue; + Binding *bind = get_binding(state_filtered, event->detail); - /* If a symbol was specified by the user, we need to look in - * the array of translated keycodes for the event’s keycode */ - if (bind->symbol != NULL) { - if (memmem(bind->translated_to, - bind->number_keycodes * sizeof(xcb_keycode_t), - &(event->detail), sizeof(xcb_keycode_t)) != NULL) - break; - } else { - /* This case is easier: The user specified a keycode */ - if (bind->keycode == event->detail) - break; + /* No match? Then the user has Mode_switch enabled but does not have a + * specific keybinding. Fall back to the default keybindings (without + * Mode_switch). Makes it much more convenient for users of a hybrid + * layout (like us, ru). */ + if (bind == NULL) { + state_filtered &= ~(BIND_MODE_SWITCH); + DLOG("no match, new state_filtered = %d\n", state_filtered); + if ((bind = get_binding(state_filtered, event->detail)) == NULL) { + ELOG("Could not lookup key binding (modifiers %d, keycode %d)\n", + state_filtered, event->detail); + return 1; } } - /* No match? Then it was an actively grabbed key, that is with Mode_switch, and - the user did not press Mode_switch, so just pass it… */ - if (bind == TAILQ_END(bindings)) { - xcb_allow_events(conn, ReplayKeyboard, event->time); - xcb_flush(conn); - return 1; - } - parse_command(conn, bind->command); - if (state_filtered & BIND_MODE_SWITCH) { - DLOG("Mode_switch -> allow_events(SyncKeyboard)\n"); - xcb_allow_events(conn, SyncKeyboard, event->time); - xcb_flush(conn); - } return 1; } @@ -295,7 +261,8 @@ int handle_mapping_notify(void *ignored, xcb_connection_t *conn, xcb_mapping_not xcb_get_numlock_mask(conn); ungrab_all_keys(conn); - grab_all_keys(conn); + translate_keysyms(); + grab_all_keys(conn, false); return 0; } diff --git a/src/mainx.c b/src/mainx.c index 84df8b2e..c80b8168 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -51,6 +51,10 @@ #include "log.h" #include "sighandler.h" +static int xkb_event_base; + +int xkb_current_group; + xcb_connection_t *global_conn; /* This is the path to i3, copied from argv[0] when starting up */ @@ -125,14 +129,54 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) { */ static void xkb_got_event(EV_P_ struct ev_io *w, int revents) { DLOG("Handling XKB event\n"); - XEvent ev; + XkbEvent ev; + /* When using xmodmap, every change (!) gets an own event. * Therefore, we just read all events and only handle the - * mapping_notify once (we do not receive any other XKB - * events anyway). */ - while (XPending(xkbdpy)) - XNextEvent(xkbdpy, &ev); + * mapping_notify once. */ + bool mapping_changed = false; + while (XPending(xkbdpy)) { + XNextEvent(xkbdpy, (XEvent*)&ev); + /* While we should never receive a non-XKB event, + * better do sanity checking */ + if (ev.type != xkb_event_base) + continue; + if (ev.any.xkb_type == XkbMapNotify) { + mapping_changed = true; + continue; + } + + if (ev.any.xkb_type != XkbStateNotify) { + ELOG("Unknown XKB event received (type %d)\n", ev.any.xkb_type); + continue; + } + + /* See The XKB Extension: Library Specification, section 14.1 */ + /* We check if the current group (each group contains + * two levels) has been changed. Mode_switch activates + * group XkbGroup2Index */ + if (xkb_current_group == ev.state.group) + continue; + + xkb_current_group = ev.state.group; + + if (ev.state.group == XkbGroup2Index) { + DLOG("Mode_switch enabled\n"); + grab_all_keys(global_conn, true); + } + + if (ev.state.group == XkbGroup1Index) { + DLOG("Mode_switch disabled\n"); + ungrab_all_keys(global_conn); + grab_all_keys(global_conn, false); + } + } + + if (!mapping_changed) + return; + + DLOG("Keyboard mapping changed, updating keybindings\n"); xcb_key_symbols_free(keysyms); keysyms = xcb_key_symbols_alloc(global_conn); @@ -140,9 +184,9 @@ static void xkb_got_event(EV_P_ struct ev_io *w, int revents) { ungrab_all_keys(global_conn); DLOG("Re-grabbing...\n"); - grab_all_keys(global_conn); + translate_keysyms(); + grab_all_keys(global_conn, (xkb_current_group == XkbGroup2Index)); DLOG("Done\n"); - } @@ -284,9 +328,9 @@ int main(int argc, char *argv[], char *env[]) { major = XkbMajorVersion; minor = XkbMinorVersion; - int evBase, errBase; + int errBase; - if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) { + if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &xkb_event_base, &errBase, &major, &minor, &error)) == NULL) { ELOG("ERROR: XkbOpenDisplay() failed, disabling XKB support\n"); xkb_supported = false; } @@ -298,13 +342,15 @@ int main(int argc, char *argv[], char *env[]) { } int i1; - if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) { + if (!XkbQueryExtension(xkbdpy,&i1,&xkb_event_base,&errBase,&major,&minor)) { fprintf(stderr, "XKB not supported by X-server\n"); return 1; } /* end of ugliness */ - if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd, XkbMapNotifyMask, XkbMapNotifyMask)) { + if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd, + XkbMapNotifyMask | XkbStateNotifyMask, + XkbMapNotifyMask | XkbStateNotifyMask)) { fprintf(stderr, "Could not set XKB event mask\n"); return 1; } @@ -352,9 +398,8 @@ int main(int argc, char *argv[], char *env[]) { /* Expose = an Application should redraw itself, in this case it’s our titlebars. */ xcb_event_set_expose_handler(&evenths, handle_expose_event, NULL); - /* Key presses/releases are pretty obvious, I think */ + /* Key presses are pretty obvious, I think */ xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL); - xcb_event_set_key_release_handler(&evenths, handle_key_release, NULL); /* Enter window = user moved his mouse over the window */ xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, NULL); @@ -475,7 +520,8 @@ int main(int argc, char *argv[], char *env[]) { xcb_get_numlock_mask(conn); - grab_all_keys(conn); + translate_keysyms(); + grab_all_keys(conn, false); int randr_base; if (force_xinerama) { From c58c7b9c63f497013420c2d6f7eeeb9be594f752 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 15 Mar 2010 18:23:12 +0100 Subject: [PATCH 176/247] =?UTF-8?q?Fix=20spelling=20errors/strange=20sente?= =?UTF-8?q?nces=20in=20the=20user=E2=80=99s=20guide?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Proof-reading is very appreciated. --- docs/userguide | 106 ++++++++++++++++++++++++++----------------------- 1 file changed, 56 insertions(+), 50 deletions(-) diff --git a/docs/userguide b/docs/userguide index 75179664..7f1bef50 100644 --- a/docs/userguide +++ b/docs/userguide @@ -3,7 +3,7 @@ i3 User’s Guide Michael Stapelberg March 2010 -This document contains all information you need to configuring and using the i3 +This document contains all information you need for configuring and using the i3 window manager. If it does not, please contact me on IRC, Jabber or E-Mail and I’ll help you out. @@ -20,20 +20,20 @@ image:keyboard-layer1.png["Keys to use with Mod1 (alt)",width=600,link="keyboard image:keyboard-layer2.png["Keys to use with Shift+Mod1",width=600,link="keyboard-layer2.png"] -As i3 uses keycodes in the default configuration, it does not mapper which +As i3 uses keycodes in the default configuration, it does not matter which layout you actually use. The key positions are what matters (of course you can also use keysymbols, see below). -The red keys are the modifiers you need to press (by default, you may have -changed which keys are which modifier), the blue keys are your homerow. +The red keys are the modifiers you need to press (by default), the blue keys +are your homerow. == Using i3 -=== Creating terminals and moving around +=== Opening terminals and moving around -A very basic operation is to create a new terminal. By default, the keybinding +A very basic operation is to open a new terminal. By default, the keybinding for that is Mod1+Enter, that is Alt+Enter in the default configuration. By -pressing Mod1+Enter, a new terminal will be created and it will fill the whole +pressing Mod1+Enter, a new terminal will be opened and it will fill the whole space which is available on your screen. image:single_terminal.png[Single terminal] @@ -64,7 +64,7 @@ image:two_columns.png[Two columns] === Changing mode of containers -A container can be in different modes: +A container can be in the following modes: default:: Windows are sized so that every window gets an equal amount of space of the @@ -86,7 +86,7 @@ image:modes.png[Container modes] To display a window fullscreen or to go out of fullscreen mode again, press +Mod1+f+. -There is also a global fullscreen mode in i3 in which the client will use all +There also is a global fullscreen mode in i3 in which the client will use all available outputs. To use it, or to get out of it again, press +Mod1+Shift+f+. === Opening other applications @@ -97,7 +97,8 @@ Aside from opening applicatios from a terminal, you can also use the handy your +$PATH+ for that to work. Furthermore, if you have applications you open very frequently, you can also -create a keybinding for it. See the section "Configuring i3" for details. +create a keybinding for starting the application directly. See the section +"Configuring i3" for details. === Closing windows @@ -143,11 +144,11 @@ columns/rows with your keyboard. === Restarting i3 inplace -To restart i3 inplace (and thus get it into a clean state if it has a bug, to -reload your configuration or even to upgrade to a newer version of i3) you -can use +Mod1+Shift+r+. Be aware, though, that this kills your current layout -and all the windows you have opened will be put in a default container in only -one cell. Saving the layout will be implemented in a later version. +To restart i3 inplace (and thus get it into a clean state if it has a bug or +to upgrade to a newer version of i3) you can use +Mod1+Shift+r+. Be aware, +though, that this kills your current layout and all the windows you have opened +will be put in a default container in only one cell. Saving the layout will be +implemented in a later version. === Exiting i3 @@ -167,12 +168,12 @@ by pressing +Mod1+Control+k+ (or snap container 2 rightwards). === Floating -Floating is the opposite of tiling mode. The position and size of a window +Floating mode is the opposite of tiling mode. The position and size of a window are then not managed by i3, but by you. Using this mode violates the tiling paradigm but can be useful for some corner cases like "Save as" dialog windows or toolbar windows (GIMP or similar). -You can enable floating for a window by pressing +Mod1+Shift+Space+. By +You can enable floating mode for a window by pressing +Mod1+Shift+Space+. By dragging the window’s titlebar with your mouse, you can move the window around. By grabbing the borders and moving them you can resize the window. @@ -213,6 +214,8 @@ a # and can only be used at the beginning of a line, like this: i3 uses X core fonts (not Xft) for rendering window titles and the internal workspace bar. You can use +xfontsel(1)+ to generate such a font description. +To see special characters (Unicode), you need to use a font which supports +the ISO-10646 encoding. *Syntax*: ------------------------------ @@ -237,7 +240,7 @@ also mix your bindings, though i3 will not protect you from overlapping ones). * Keycodes however do not need to have a symbol assigned (handy for some hotkeys on some notebooks) and they will not change their meaning as you switch to a - different keyboard layout. + different keyboard layout (when using +xmodmap+). My recommendation is: If you often switch keyboard layouts because you try to learn a different one, but you want to keep your bindings at the same place, @@ -253,10 +256,10 @@ bind [Modifiers+]keycode command *Examples*: -------------------------------- # Fullscreen -bind Mod1+f f +bindsym Mod1+f f # Restart -bind Mod1+Shift+r restart +bindsym Mod1+Shift+r restart # Notebook-specific hotkeys bind 214 exec /home/michael/toggle_beamer.sh @@ -284,7 +287,7 @@ you can press Mod1, click into a window using your left mouse button and drag it to the position you want it at. When holding the floating modifier, you can resize a floating window by pressing -the right mousebutton on it and moving around holding it. If you hold the shift +the right mouse button on it and moving around holding it. If you hold the shift button aswell, the resize will be proportional. *Syntax*: @@ -299,8 +302,7 @@ floating_modifier Mod1 === Layout mode for new containers -This option is only available when using the new lexer/parser (pass +-l+ to i3 -when starting). It determines in which mode new containers will start. See also +This option determines in which mode new containers will start. See also <>. *Syntax*: @@ -316,8 +318,7 @@ new_container tabbed === Border style for new windows -This option is only available when using the new lexer/parser (pass +-l+ to i3 -when starting). It determines which border new windows will have. +This option determines which border style new windows will have. *Syntax*: --------------------------------------------- @@ -349,8 +350,9 @@ bindsym $m+Shift+r restart Variables are directly replaced in the file when parsing, there is no fancy handling and there are absolutely no plans to change this. If you need a more -dynamic configuration, you should create a little script, like when configuring -wmii. +dynamic configuration, you should create a little script which generates a +configuration file and run it before starting i3 (for example in your ++.xsession+ file). === Automatically putting clients on specific workspaces @@ -383,10 +385,13 @@ assign "gecko" → ~4 assign "xv/MPlayer" → ~ ---------------------- +Note that the arrow is not required, it just looks good :-). If you decide to +use it, it has to be a UTF-8 encoded arrow, not "->" or something like that. + === Automatically starting applications on startup By using the +exec+ keyword outside a keybinding, you can configure which -commands will be performed by i3 on the first start (not when reloading inplace +commands will be performed by i3 on the first start (not when restarting inplace however). The commands will be run in order. *Syntax*: @@ -403,11 +408,11 @@ exec sudo i3status | dzen2 -dock [[workspace_screen]] -If you use the assigning of clients to workspaces and start some clients -automatically, it might be handy to put the workspaces on specific screens. -Also, the assignment of workspaces to screens will determine the workspace -which i3 uses for a new screen when adding screens or when starting (e.g., by -default it will use 1 for the first screen, 2 for the second screen and so on). +If you use assignments of clients to workspaces, it might be handy to put the +workspaces on specific screens. Also, the assignment of workspaces to screens +will determine the workspace which i3 uses for a new screen when adding screens +or when starting (e.g., by default it will use 1 for the first screen, 2 for +the second screen and so on). *Syntax*: ---------------------------------- @@ -472,7 +477,7 @@ bar.unfocused:: bar.urgent:: A workspace which has at least one client with an activated urgency hint. -Colors are in HTML hex format, see below. +Colors are in HTML hex format (#rrggbb), see the following example: *Examples*: -------------------------------------- @@ -486,16 +491,19 @@ the window. === Interprocess communication -i3 uses unix sockets to provide an IPC interface. At the moment, this interface -is only useful for sending commands. To enable it, you have to configure a path -where the unix socket will be stored. The default path is +/tmp/i3-ipc.sock+. +i3 uses unix sockets to provide an IPC interface. This allows third-party +programs to get information like the current workspaces to display a workspace +bar and to control i3. + +To enable it, you have to configure a path where the unix socket will be +stored. The default path is +/tmp/i3-ipc.sock+. *Examples*: ---------------------------- ipc-socket /tmp/i3-ipc.sock ---------------------------- -You can then use the i3-msg command to perform any command listed in the next +You can then use the +i3-msg+ command to perform any command listed in the next section. === Disable focus follows mouse @@ -522,7 +530,7 @@ focus_follows_mouse no To change the layout of the current container to stacking, use +s+, for default use +d+ and for tabbed, use +T+. To make the current client (!) fullscreen, -use +f+, to make it spanning all outputs, use +fg+, to make it floating (or +use +f+, to make it span all outputs, use +fg+, to make it floating (or tiling again) use +t+: *Examples*: @@ -544,7 +552,7 @@ bindsym Mod1+t t === Focussing/Moving/Snapping clients/containers/screens To change the focus, use one of the +h+, +j+, +k+ and +l+ commands, meaning -respectively left, down, up, right. To focus a container, prefix it with +wc+, +left, down, up, right (respectively). To focus a container, prefix it with +wc+, to focus a screen, prefix it with +ws+. The same principle applies for moving and snapping, just prefix the command @@ -604,8 +612,7 @@ bindsym Mod1+p pw === Resizing columns/rows If you want to resize columns/rows using your keyboard, you can use the -+resize+ command, I recommend using it inside a so called +mode+ (you need to -use the new lexer/parser for that, so pass +-l+ to i3 when starting): ++resize+ command, I recommend using it inside a so called +mode+: .Example: Configuration file, defining a mode for resizing ---------------------------------------------------------------------- @@ -698,7 +705,7 @@ the focus stack and jumps to the window you focused before. *Syntax*: -------------- -focus [number] | floating | tilling | ft +focus [number] | floating | tiling | ft -------------- Where +number+ by default is 1 meaning that the next client in the focus stack will @@ -779,8 +786,8 @@ bindsym Mod1+Shift+e exit [[multi_monitor]] As you can read in the goal list on its website, i3 was specifically developed -with Xinerama (support for multiple monitors) in mind. This section will -explain how to handle multiple monitors. +with support for multiple monitors in mind. This section will explain how to +handle multiple monitors. When you have only one monitor, things are simple. You usually start with workspace 1 on your monitor and open new ones as you need them. @@ -804,7 +811,7 @@ changing your configuration (using modes, for example). === Configuring your monitors -To help you get going if you never did multiple monitors before, here comes a +To help you get going if you never used multiple monitors before, here comes a short overview of the xrandr options which are probably of interest for you. It is always useful to get an overview of the current screen configuration, so just run "xrandr" and you will get an output like the following: @@ -836,7 +843,7 @@ So, say you connected VGA1 and want to use it as an additional screen: ------------------------------------------- xrandr --output VGA1 --auto --left-of LVDS1 ------------------------------------------- -This command lets xrandr try to find out the native resolution of the device +This command makes xrandr try to find out the native resolution of the device connected to +VGA1+ and configures it to the left of your internal flat panel. When running "xrandr" again, the output looks like this: ----------------------------------------------------------------------------------------- @@ -892,7 +899,7 @@ approach you have in the task bar of a traditional desktop environment. If you don’t already have your favorite way of generating such a status line (self-written scripts, conky, …), then i3status is the recommended tool for -this task. It was written in C with the goal to have as little syscalls as +this task. It was written in C with the goal to use as little syscalls as possible to reduce the time your CPU is waken up from sleep states. Regardless of which application you use to generate the status line, you @@ -943,5 +950,4 @@ and you are in multi-monitor mode (see <>). Because i3 is not a compositing window manager, there is no possibility to display a window on two screens at the same time. Instead, you presentation -software needs to do this job (that is, open a window on each screen with the -same contents). +software needs to do this job (that is, open a window on each screen). From f9e6f8ba4b4649db4d46d016af678e82520b0993 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 15 Mar 2010 22:17:00 +0100 Subject: [PATCH 177/247] Bugfix: Correctly stack windows when new windows are opened while in fullscreen mode This fixes ticket #195. --- src/manage.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/manage.c b/src/manage.c index b14f73e8..40ef6cca 100644 --- a/src/manage.c +++ b/src/manage.c @@ -400,9 +400,15 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, if (new->workspace->fullscreen_client != NULL) { DLOG("Setting below fullscreen window\n"); - /* If we are in fullscreen, we should lower the window to not be annoying */ - uint32_t values[] = { XCB_STACK_MODE_BELOW }; - xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values); + /* If we are in fullscreen, we should place the window below + * the fullscreen window to not be annoying */ + uint32_t values[] = { + new->workspace->fullscreen_client->frame, + XCB_STACK_MODE_BELOW + }; + xcb_configure_window(conn, new->frame, + XCB_CONFIG_WINDOW_SIBLING | + XCB_CONFIG_WINDOW_STACK_MODE, values); } /* Insert into the currently active container, if it’s not a dock window */ From 67d80ee1d2aca4bc38f739143f95f84df49c8a2a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 15 Mar 2010 22:41:39 +0100 Subject: [PATCH 178/247] Update i3.man --- man/i3.man | 66 +++++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/man/i3.man b/man/i3.man index 92b951ff..2f97dd04 100644 --- a/man/i3.man +++ b/man/i3.man @@ -1,7 +1,7 @@ i3(1) ===== Michael Stapelberg -v3.delta, November 2009 +v3.epsilon, March 2010 == NAME @@ -36,8 +36,8 @@ Be verbose. === INTRODUCTION i3 was created because wmii, our favorite window manager at the time, didn’t -provide some features we wanted (Xinerama done right, for example), had some -bugs, didn’t progress since quite some time and wasn’t easy to hack at all +provide some features we wanted (multi-monitor done right, for example), had +some bugs, didn’t progress since quite some time and wasn’t easy to hack at all (source code comments/documentation completely lacking). Still, we think the wmii developers and contributors did a great job. Thank you for inspiring us to create i3. @@ -50,36 +50,35 @@ Client:: A client is X11-speak for a window. Table:: -Your workspace is managed using a table. You can move windows around and create new columns -(move a client to the right) or rows (move it to the bottom) implicitly. +Your workspace is managed using a table. You can move windows around and create +new columns (move a client to the right) or rows (move it to the bottom) +implicitly. + By "snapping" a client in a specific direction, you increase its colspan/rowspan. Container:: -A container contains a variable number of clients. Each cell of the table is a container. +A container contains a variable number of clients. Each cell of the table is a +container. + -Containers can be used in various modes. The default mode is called "default" and just -resizes each client equally so that it fits. +Containers can be used in various modes. The default mode is called "default" +and just resizes each client equally so that it fits. Workspace:: -A workspace is a set of clients (technically speaking, it’s just a table). Other window -managers call this "Virtual Desktops". +A workspace is a set of clients (technically speaking, it’s just a table). +Other window managers call this "Virtual Desktops". + -In i3, each workspace is assigned to a specific virtual screen. By default, screen 1 -has workspace 1, screen 2 has workspace 2 and so on… However, when you create a new -workspace (by simply switching to it), it’ll be assigned the screen you are currently -on. +In i3, each workspace is assigned to a specific virtual screen. By default, +screen 1 has workspace 1, screen 2 has workspace 2 and so on… However, when you +create a new workspace (by simply switching to it), it’ll be assigned the +screen you are currently on. -Virtual Screen:: -Using Xinerama, you can have an X11 screen spanning multiple real monitors. Furthermore, -you can set them up in cloning mode or with positions (monitor 1 is left of monitor 2). +Output:: +Using XRandR, you can have an X11 screen spanning multiple real monitors. +Furthermore, you can set them up in cloning mode or with positions (monitor 1 +is left of monitor 2). + -A virtual screen is the result of your Xinerama setup. For example, if you have attached -two real monitors (let’s say your laptop screen and a video projector) and enabled cloning, i3 -will use one virtual screen with the size of the smallest screen you have attached (so -that you can see all your windows on each screen all the time). -If you have two monitors attached, one configured to be left of the other, i3 will use -two virtual screens. +i3 uses the RandR API to query which outputs are available and which screens +are connected to these outputs. == KEYBINDINGS @@ -126,10 +125,11 @@ Mod1+t:: Select the first tiling window if the current window is floating and vice-versa. Mod1+Shift+q:: -Kills the current window. This is equivalent to "clicking on the close button", meaning a polite -request to the application to close this window. For example, Firefox will save its session -upon such a request. If the application does not support that, the window will be killed and -it depends on the application what happens. +Kills the current window. This is equivalent to "clicking on the close button", +meaning a polite request to the application to close this window. For example, +Firefox will save its session upon such a request. If the application does not +support that, the window will be killed and it depends on the application what +happens. Mod1+Shift+r:: Restarts i3 in place (without losing any windows, but the layout). @@ -139,18 +139,18 @@ Exits i3. == FILES -=== ~/.i3/config +=== \~/.i3/config (or ~/.config/i3/config) -When starting, i3 looks for ~/.i3/config and loads the configuration. If ~/.i3/config is not found, -i3 tries /etc/i3/config. You can specify a custom path using the -c option. +When starting, i3 looks for configuration files in the following order: -At the moment, you can specify only the path to your favorite terminal emulator, the font and keybindings. +1. ~/.config/i3/config (according to the XDG specification) +2. ~/.i3/config +3. /etc/i3/config -At the moment, you have to bind to keycodes (find them out via xev(1)). +You can specify a custom path using the -c option. .Sample configuration ------------------------------------------------------------- -terminal /usr/bin/urxvt font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 # Start terminal (Mod1+Enter) From 0b1eed490697e2fd916be1b5ab0731105703a5ec Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 15 Mar 2010 23:04:32 +0100 Subject: [PATCH 179/247] Add document about the current multi-monitor situation with RandR --- docs/Makefile | 5 +++- docs/multi-monitor | 58 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 docs/multi-monitor diff --git a/docs/Makefile b/docs/Makefile index ffac9ab5..b17413ca 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,5 +1,5 @@ -all: hacking-howto.html debugging.html userguide.html ipc.html +all: hacking-howto.html debugging.html userguide.html ipc.html multi-monitor.html hacking-howto.html: hacking-howto asciidoc -a toc -n $< @@ -13,6 +13,9 @@ userguide.html: userguide ipc.html: ipc asciidoc -a toc -n $< +multi-monitor.html: multi-monitor + asciidoc -a toc -n $< + clean: rm -f */*.{aux,log,toc,bm,pdf,dvi} rm -f *.log *.html diff --git a/docs/multi-monitor b/docs/multi-monitor new file mode 100644 index 00000000..9affb0cc --- /dev/null +++ b/docs/multi-monitor @@ -0,0 +1,58 @@ +The multi-monitor situation +=========================== +Michael Stapelberg +March 2010 + +…or: oh no, I have an nvidia graphics card! + +== The quick fix + +If you are using the nvidia binary graphics driver, you need to use the ++--force-xinerama+ flag when starting i3, like so (in your xsession): + +.Example: +---------------------------------------------- +exec i3 --force-xinerama -V >>~/.i3/i3log 2>&1 +---------------------------------------------- + +== The explanation + +Starting with version 3.ε, i3 uses the RandR (Rotate and Resize) API instead +of Xinerama. This is due to the reason that RandR provides more information +about your outputs and connected screens than Xinerama does. To be specific, +the code which handled on-the-fly screen reconfiguration (meaning without +restarting the X server) was a very messy heuristic and most of the time did +not work correctly -- that is just not possible with the little information +Xinerama offers (just a list of screen resolutions, no identifiers for the +screens or any additional information). Xinerama simply was not designed +for dynamic configuration. + +So, RandR came up as a more powerful alternative (RandR 1.2 to be specific). +It offers all of Xinerama’s possibilities and lots more. Using the RandR API +made our code much more robust and clean. Also, you can now reliably assign +workspaces to output names instead of some rather unreliable screen identifier +(position inside the list of screens, which could change, and so on…). + +As RandR is around for about three years, it seemed like a very good idea to +us and it still is a very good one. What we did not expect, however, was the +nVidia binary driver. It still does not support RandR (as of March 2010), even +though nVidia announced that it will support RandR eventually. What does this +mean for you, if you are stuck with the binary driver for some reason (say +the free drivers don’t work with your card)? First of all, you are stuck with +TwinView and cannot use +xrandr+. While this ruins the user experience, the +more grave problem is that the nVidia driver not only does not support dynamic +configuration using RandR, it also does not even expose correct multi-monitor +information via the RandR API. So, in some setups, i3 will not find any +screens, in others it will find one large screen which actually contains both +of your physical screens (but it will not know that these are two screens). + +For this very reason, we decided to implement the following workaround: As +long as the nVidia driver does not support RandR, an option called ++--force-xinerama+ is available in i3. This option gets the list of screens +*once* when starting and never updates it. As the nVidia driver cannot do +dynamic configuration anyways, this is not a big deal. + +== See also + +For more information on how to use multi-monitor setups, see the i3 User’s +Guide. From 29c4ac93951bf7c378f179f3158df6f9a868bc1b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 16 Mar 2010 00:00:27 +0100 Subject: [PATCH 180/247] makefile: install header file i3/ipc.h --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 73a33d3e..8f44771e 100644 --- a/Makefile +++ b/Makefile @@ -58,11 +58,13 @@ install: all echo "INSTALL" $(INSTALL) -d -m 0755 $(DESTDIR)/usr/bin $(INSTALL) -d -m 0755 $(DESTDIR)/etc/i3 + $(INSTALL) -d -m 0755 $(DESTDIR)/usr/include/i3 $(INSTALL) -d -m 0755 $(DESTDIR)/usr/share/xsessions $(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.welcome $(DESTDIR)/etc/i3/welcome $(INSTALL) -m 0644 i3.desktop $(DESTDIR)/usr/share/xsessions/ + $(INSTALL) -m 0644 include/i3/ipc.h $(DESTDIR)/usr/include/i3/ $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg install $(MAKE) TOPDIR=$(TOPDIR) -C i3-input install From 1bce8f210428019f0e4165145c678bc2590ac729 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 16 Mar 2010 00:08:54 +0100 Subject: [PATCH 181/247] makefiles: support PREFIX and SYSCONFDIR --- Makefile | 18 +++++++++--------- common.mk | 6 ++++++ i3-input/Makefile | 4 ++-- i3-msg/Makefile | 4 ++-- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 8f44771e..9a4a52ea 100644 --- a/Makefile +++ b/Makefile @@ -56,15 +56,15 @@ src/cfgparse.y.o: src/cfgparse.y ${HEADERS} install: all echo "INSTALL" - $(INSTALL) -d -m 0755 $(DESTDIR)/usr/bin - $(INSTALL) -d -m 0755 $(DESTDIR)/etc/i3 - $(INSTALL) -d -m 0755 $(DESTDIR)/usr/include/i3 - $(INSTALL) -d -m 0755 $(DESTDIR)/usr/share/xsessions - $(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.welcome $(DESTDIR)/etc/i3/welcome - $(INSTALL) -m 0644 i3.desktop $(DESTDIR)/usr/share/xsessions/ - $(INSTALL) -m 0644 include/i3/ipc.h $(DESTDIR)/usr/include/i3/ + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -d -m 0755 $(DESTDIR)$(SYSCONFDIR)/i3 + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/include/i3 + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/xsessions + $(INSTALL) -m 0755 i3 $(DESTDIR)$(PREFIX)/bin/ + test -e $(DESTDIR)$(SYSCONFDIR)/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)$(SYSCONFDIR)/i3/config + $(INSTALL) -m 0644 i3.welcome $(DESTDIR)$(SYSCONFDIR)/i3/welcome + $(INSTALL) -m 0644 i3.desktop $(DESTDIR)$(PREFIX)/share/xsessions/ + $(INSTALL) -m 0644 include/i3/ipc.h $(DESTDIR)$(PREFIX)/include/i3/ $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg install $(MAKE) TOPDIR=$(TOPDIR) -C i3-input install diff --git a/common.mk b/common.mk index b1940140..0334ac61 100644 --- a/common.mk +++ b/common.mk @@ -1,6 +1,12 @@ UNAME=$(shell uname) DEBUG=1 INSTALL=install +PREFIX=/usr +ifeq ($(PREFIX),/usr) +SYSCONFDIR=/etc +else +SYSCONFDIR=$(PREFIX)/etc +endif GIT_VERSION:="$(shell git describe --tags --always) ($(shell git log --pretty=format:%cd --date=short -n1))" VERSION:=$(shell git describe --tags --abbrev=0) diff --git a/i3-input/Makefile b/i3-input/Makefile index c8881654..74f3f8da 100644 --- a/i3-input/Makefile +++ b/i3-input/Makefile @@ -18,8 +18,8 @@ all: ${FILES} install: all echo "INSTALL" - $(INSTALL) -d -m 0755 $(DESTDIR)/usr/bin - $(INSTALL) -m 0755 i3-input $(DESTDIR)/usr/bin/ + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -m 0755 i3-input $(DESTDIR)$(PREFIX)/bin/ clean: rm -f *.o diff --git a/i3-msg/Makefile b/i3-msg/Makefile index ec4ba6e6..d75d807c 100644 --- a/i3-msg/Makefile +++ b/i3-msg/Makefile @@ -20,8 +20,8 @@ all: ${FILES} install: all echo "INSTALL" - $(INSTALL) -d -m 0755 $(DESTDIR)/usr/bin - $(INSTALL) -m 0755 i3-msg $(DESTDIR)/usr/bin/ + $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -m 0755 i3-msg $(DESTDIR)$(PREFIX)/bin/ clean: rm -f *.o From be6a64e43ffc3ac7d49f3ce8c9c8c94d57d37d4c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 16 Mar 2010 00:12:01 +0100 Subject: [PATCH 182/247] manpage: correctly document the search order for XDG --- man/i3.man | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/man/i3.man b/man/i3.man index 2f97dd04..dd3f6333 100644 --- a/man/i3.man +++ b/man/i3.man @@ -143,9 +143,10 @@ Exits i3. When starting, i3 looks for configuration files in the following order: -1. ~/.config/i3/config (according to the XDG specification) -2. ~/.i3/config -3. /etc/i3/config +1. ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set) +2. /etc/xdg/i3/config (or $XDG_CONFIG_DIRS/i3/config if set) +3. ~/.i3/config +4. /etc/i3/config You can specify a custom path using the -c option. From fca826a6f911780d6e1ea7c303969f6433627565 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 16 Mar 2010 00:13:40 +0100 Subject: [PATCH 183/247] docs/ipc: document C header file --- docs/ipc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/ipc b/docs/ipc index 91ab5fa8..fc3f1939 100644 --- a/docs/ipc +++ b/docs/ipc @@ -225,6 +225,9 @@ For some languages, libraries are available (so you don’t have to implement all this on your own). This list names some (if you wrote one, please let me know): +C:: + i3 includes a headerfile +i3/ipc.h+ which provides you all constants. + However, there is no library yet. Ruby:: http://github.com/badboy/i3-ipc Perl:: From f7a1a9fb20b793a32dacdc8ca319952be31ace60 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 16 Mar 2010 02:44:47 +0100 Subject: [PATCH 184/247] ipc: correctly shutdown IPC sockets when exiting/restarting --- i3-msg/main.c | 2 ++ include/ipc.h | 6 ++++++ src/commands.c | 2 ++ src/ipc.c | 13 +++++++++++++ src/util.c | 3 +++ 5 files changed, 26 insertions(+) diff --git a/i3-msg/main.c b/i3-msg/main.c index 193d04e9..a1bdd72e 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -73,6 +73,8 @@ static void ipc_recv_message(int sockfd, uint32_t message_type, int n = read(sockfd, msg + read_bytes, to_read); if (n == -1) err(EXIT_FAILURE, "read() failed"); + if (n == 0) + errx(EXIT_FAILURE, "received EOF instead of reply"); read_bytes += n; to_read -= n; diff --git a/include/ipc.h b/include/ipc.h index b798b5ff..63d59141 100644 --- a/include/ipc.h +++ b/include/ipc.h @@ -67,5 +67,11 @@ int ipc_create_socket(const char *filename); */ void ipc_send_event(const char *event, uint32_t message_type, const char *payload); +/** + * Calls shutdown() on each socket and closes it. This function to be called + * when exiting or restarting only! + * + */ +void ipc_shutdown(); #endif diff --git a/src/commands.c b/src/commands.c index bef0989f..273f9d39 100644 --- a/src/commands.c +++ b/src/commands.c @@ -33,6 +33,7 @@ #include "log.h" #include "sighandler.h" #include "manage.h" +#include "ipc.h" bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) { /* If this container is empty, we’re done */ @@ -1015,6 +1016,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { if (STARTS_WITH(command, "exit")) { LOG("User issued exit-command, exiting without error.\n"); restore_geometry(global_conn); + ipc_shutdown(); exit(EXIT_SUCCESS); } diff --git a/src/ipc.c b/src/ipc.c index f74f437e..f2dfd1f6 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -105,6 +105,19 @@ void ipc_send_event(const char *event, uint32_t message_type, const char *payloa } } +/* + * Calls shutdown() on each socket and closes it. This function to be called + * when exiting or restarting only! + * + */ +void ipc_shutdown() { + ipc_client *current; + TAILQ_FOREACH(current, &all_clients, clients) { + shutdown(current->fd, SHUT_RDWR); + close(current->fd); + } +} + /* * Executes the command and returns whether it could be successfully parsed * or not (at the moment, always returns true). diff --git a/src/util.c b/src/util.c index dab3199b..cb37d30a 100644 --- a/src/util.c +++ b/src/util.c @@ -35,6 +35,7 @@ #include "ewmh.h" #include "manage.h" #include "workspace.h" +#include "ipc.h" static iconv_t conversion_descriptor = 0; struct keyvalue_table_head by_parent = TAILQ_HEAD_INITIALIZER(by_parent); @@ -513,6 +514,8 @@ static char **append_argument(char **original, char *argument) { void i3_restart() { restore_geometry(global_conn); + ipc_shutdown(); + LOG("restarting \"%s\"...\n", start_argv[0]); /* make sure -a is in the argument list or append it */ start_argv = append_argument(start_argv, "-a"); From b6a003afdf364170d28e58afc68b46f19d3f8d6b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 16 Mar 2010 20:28:43 +0100 Subject: [PATCH 185/247] docs/userguide: merge little corrections (Thanks fallen) --- docs/userguide | 252 +++++++++++++++++++++++++------------------------ 1 file changed, 127 insertions(+), 125 deletions(-) diff --git a/docs/userguide b/docs/userguide index 7f1bef50..5a313c92 100644 --- a/docs/userguide +++ b/docs/userguide @@ -3,13 +3,13 @@ i3 User’s Guide Michael Stapelberg March 2010 -This document contains all information you need for configuring and using the i3 +This document contains all the information you need to configure and use the i3 window manager. If it does not, please contact me on IRC, Jabber or E-Mail and I’ll help you out. == Default keybindings -For the "too long; didn’t read" people, here comes an overview of the default +For the "too long; didn’t read" people, here is an overview of the default keybindings (click to see the full size image): *Keys to use with Mod1 (alt):* @@ -21,7 +21,7 @@ image:keyboard-layer1.png["Keys to use with Mod1 (alt)",width=600,link="keyboard image:keyboard-layer2.png["Keys to use with Shift+Mod1",width=600,link="keyboard-layer2.png"] As i3 uses keycodes in the default configuration, it does not matter which -layout you actually use. The key positions are what matters (of course you can +keyboard layout you actually use. The key positions are what matters (of course you can also use keysymbols, see below). The red keys are the modifiers you need to press (by default), the blue keys @@ -31,21 +31,21 @@ are your homerow. === Opening terminals and moving around -A very basic operation is to open a new terminal. By default, the keybinding -for that is Mod1+Enter, that is Alt+Enter in the default configuration. By -pressing Mod1+Enter, a new terminal will be opened and it will fill the whole -space which is available on your screen. +One very basic operation is opening a new terminal. By default, the keybinding +for this is Mod1+Enter, that is Alt+Enter in the default configuration. By +pressing Mod1+Enter, a new terminal will be opened. It will fill the whole +space available on your screen. image:single_terminal.png[Single terminal] It is important to keep in mind that i3 uses a table to manage your windows. At the moment, you have exactly one column and one row which leaves you with one -cell. In this cell, there is a container in which your newly opened terminal is. +cell. In this cell there is a container which is where your new terminal is opened. If you now open another terminal, you still have only one cell. However, the -container has both of your terminals. So, a container is just a group of clients -with a specific layout. You can resize containers as they directly resemble -columns/rows of the layout table. +container in that cell holds both of your terminals. So, a container is just a +group of clients with a specific layout. Containers can be resized by adjusting +the size of the cell that holds them. image:two_terminals.png[Two terminals] @@ -56,27 +56,27 @@ with most keyboard layouts). Therefore, +Mod1+J+ is left, +Mod1+K+ is down, +Mod is up and `Mod1+;` is right. So, to switch between the terminals, use +Mod1+K+ or +Mod1+L+. -To create a new row/column, you can simply move a terminal (or any other window) -to the direction you want to expand your table. So, let’s expand the table to -the right by pressing `Mod1+Shift+;`. +To create a new row/column (and a new cell), you can simply move a terminal (or +any other window) to the direction you want to expand your table. So, let’s +expand the table to the right by pressing `Mod1+Shift+;`. image:two_columns.png[Two columns] -=== Changing mode of containers +=== Changing container modes -A container can be in the following modes: +A container can have the following modes: default:: -Windows are sized so that every window gets an equal amount of space of the +Windows are sized so that every window gets an equal amount of space in the container. stacking:: -Only the focused client of the container is displayed and you get a list of +Only the focused window in the container is displayed. You get a list of windows at the top of the container. tabbed:: The same principle as +stacking+, but the list of windows at the top is only -a single line which will be vertically split. +a single line which is vertically split. -To switch the mode, press +Mod1+e+ for default, +Mod1+h+ for stacking and +To switch modes, press +Mod1+e+ for default, +Mod1+h+ for stacking and +Mod1+w+ for tabbed. image:modes.png[Container modes] @@ -86,29 +86,29 @@ image:modes.png[Container modes] To display a window fullscreen or to go out of fullscreen mode again, press +Mod1+f+. -There also is a global fullscreen mode in i3 in which the client will use all +There is also a global fullscreen mode in i3 in which the client will use all available outputs. To use it, or to get out of it again, press +Mod1+Shift+f+. === Opening other applications -Aside from opening applicatios from a terminal, you can also use the handy +Aside from opening applications from a terminal, you can also use the handy +dmenu+ which is opened by pressing +Mod1+v+ by default. Just type the name -(or a part of it) of the application which you want to open. It has to be in -your +$PATH+ for that to work. +(or a part of it) of the application which you want to open. The application +typed has to be in your +$PATH+ for this to work. -Furthermore, if you have applications you open very frequently, you can also +Additionally, if you have applications you open very frequently, you can create a keybinding for starting the application directly. See the section "Configuring i3" for details. === Closing windows -If an application does not provide a mechanism to close (most applications +If an application does not provide a mechanism for closing (most applications provide a menu, the escape key or a shortcut like +Control+W+ to close), you can press +Mod1+Shift+q+ to kill a window. For applications which support the WM_DELETE protocol, this will correctly close the application (saving any modifications or doing other cleanup). If the application doesn’t support -it, your X server will kill the window and the behaviour depends on the -application. +the WM_DELETE protocol your X server will kill the window and the behaviour +depends on the application. === Using workspaces @@ -121,10 +121,10 @@ A common paradigm is to put the web browser on one workspace, communication applications (+mutt+, +irssi+, ...) on another one and the ones with which you work on the third one. Of course, there is no need to follow this approach. -If you have multiple screens, a workspace will be created on each screen. If -you open a new workspace, it will be bound to the screen you created it on. -When you switch to a workspace on another screen, i3 will set focus to this -screen. +If you have multiple screens, a workspace will be created on each screen at +startup. If you open a new workspace, it will be bound to the screen you +created it on. When you switch to a workspace on another screen, i3 will set +focus to that screen. === Moving windows to workspaces @@ -137,17 +137,19 @@ it does not yet exist. To resize columns or rows just grab the border between the two columns/rows and move it to the wanted size. Please keep in mind that each cell of the table -holds a +container+ and thus you cannot horizontally resize single windows. +holds a +container+ and thus you cannot horizontally resize single windows. If +you need applications with different horizontal sizes place them in seperate +cells one above the other. See <> for how to configure i3 to be able to resize columns/rows with your keyboard. === Restarting i3 inplace -To restart i3 inplace (and thus get it into a clean state if it has a bug or +To restart i3 inplace (and thus get into a clean state if there is a bug or to upgrade to a newer version of i3) you can use +Mod1+Shift+r+. Be aware, though, that this kills your current layout and all the windows you have opened -will be put in a default container in only one cell. Saving the layout will be +will be put in a default container in only one cell. Saving layouts will be implemented in a later version. === Exiting i3 @@ -157,7 +159,7 @@ To cleanly exit i3 without killing your X server, you can use +Mod1+Shift+e+. === Snapping Snapping is a mechanism to increase/decrease the colspan/rowspan of a container. -Colspan/rowspan is the amount of columns/rows a specific cell of the table +Colspan/rowspan is the number of columns/rows a specific cell of the table consumes. This is easier explained by giving an example, so take the following layout: @@ -169,31 +171,31 @@ by pressing +Mod1+Control+k+ (or snap container 2 rightwards). === Floating Floating mode is the opposite of tiling mode. The position and size of a window -are then not managed by i3, but by you. Using this mode violates the tiling +are not managed by i3, but by you. Using this mode violates the tiling paradigm but can be useful for some corner cases like "Save as" dialog windows or toolbar windows (GIMP or similar). You can enable floating mode for a window by pressing +Mod1+Shift+Space+. By -dragging the window’s titlebar with your mouse, you can move the window +dragging the window’s titlebar with your mouse you can move the window around. By grabbing the borders and moving them you can resize the window. Bindings for doing this with your keyboard will follow. -Floating clients are always on top of tiling clients. +Floating windows are always on top of tiling windows. == Configuring i3 This is where the real fun begins ;-). Most things are very dependant on your -ideal working environment, so we can’t make reasonable defaults for them. +ideal working environment so we can’t make reasonable defaults for them. While not using a programming language for the configuration, i3 stays -quite flexible regarding to the things you usually want your window manager +quite flexible in regards to the things you usually want your window manager to do. For example, you can configure bindings to jump to specific windows, -you can set specific applications to start on a specific workspace, you can -automatically start applications, you can change the colors of i3 or bind -your keys to do useful stuff. +you can set specific applications to start on specific workspaces, you can +automatically start applications, you can change the colors of i3, and you +can bind your keys to do useful things. To change the configuration of i3, copy +/etc/i3/config+ to +\~/.i3/config+ (or +~/.config/i3/config+ if you like the XDG directory scheme) and edit it @@ -203,7 +205,7 @@ with a text editor. It is possible and recommended to use comments in your configuration file to properly document your setup for later reference. Comments are started with -a # and can only be used at the beginning of a line, like this: +a # and can only be used at the beginning of a line: *Examples*: ------------------- @@ -235,17 +237,16 @@ also mix your bindings, though i3 will not protect you from overlapping ones). * A keysym (key symbol) is a description for a specific symbol, like "a" or "b", but also more strange ones like "underscore" instead of "_". These are the ones - you also use in Xmodmap to remap your keys. To get the current mapping of your + you use in Xmodmap to remap your keys. To get the current mapping of your keys, use +xmodmap -pke+. -* Keycodes however do not need to have a symbol assigned (handy for some hotkeys +* Keycodes do not need to have a symbol assigned (handy for some hotkeys on some notebooks) and they will not change their meaning as you switch to a different keyboard layout (when using +xmodmap+). -My recommendation is: If you often switch keyboard layouts because you try to -learn a different one, but you want to keep your bindings at the same place, -use keycodes. If you don’t switch layouts and like a clean and simple config -file, use keysyms. +My recommendation is: If you often switch keyboard layouts but you want to keep +your bindings in the same physical location on the keyboard use keycodes. If you +don’t switch layouts and want a clean and simple config file, use keysyms. *Syntax*: ---------------------------------- @@ -281,14 +282,14 @@ workspaces is totally convenient. Try it :-). To move floating windows with your mouse, you can either grab their titlebar or configure the so called floating modifier which you can then press and -click anywhere in the window itself. The most common setup is to configure -it as the same one you use for managing windows (Mod1 for example). Afterwards, -you can press Mod1, click into a window using your left mouse button and drag -it to the position you want it at. +click anywhere in the window itself to move it. The most common setup is to +use the same key you use for managing windows (Mod1 for example). Then +you can press Mod1, click into a window using your left mouse button, and drag +it to the position you want. When holding the floating modifier, you can resize a floating window by pressing -the right mouse button on it and moving around holding it. If you hold the shift -button aswell, the resize will be proportional. +the right mouse button on it and moving around while holding it. If you hold the +shift button as well, the resize will be proportional. *Syntax*: -------------------------------- @@ -332,10 +333,10 @@ new_window bp === Variables -As you learned in the previous section about keyboard bindings, you will have +As you learned in the section about keyboard bindings, you will have to configure lots of bindings containing modifier keys. If you want to save -yourself some typing and have the possibility to change the modifier you want -to use later, variables can be handy. +yourself some typing and be able to change the modifier you use later, +variables can be handy. *Syntax*: -------------- @@ -348,9 +349,9 @@ set $m Mod1 bindsym $m+Shift+r restart ------------------------ -Variables are directly replaced in the file when parsing, there is no fancy +Variables are directly replaced in the file when parsing. There is no fancy handling and there are absolutely no plans to change this. If you need a more -dynamic configuration, you should create a little script which generates a +dynamic configuration you should create a little script which generates a configuration file and run it before starting i3 (for example in your +.xsession+ file). @@ -359,9 +360,9 @@ configuration file and run it before starting i3 (for example in your [[assign_workspace]] It is recommended that you match on window classes whereever possible because -some applications first create their window and then care about setting the -correct title. Firefox with Vimperator comes to mind, as the window starts up -being named Firefox and only when Vimperator is loaded, the title changes. As +some applications first create their window and then worry about setting the +correct title. Firefox with Vimperator comes to mind. The window starts up +being named Firefox and only when Vimperator is loaded the title changes. As i3 will get the title as soon as the application maps the window (mapping means actually displaying it on the screen), you’d need to have to match on Firefox in this case. @@ -391,8 +392,8 @@ use it, it has to be a UTF-8 encoded arrow, not "->" or something like that. === Automatically starting applications on startup By using the +exec+ keyword outside a keybinding, you can configure which -commands will be performed by i3 on the first start (not when restarting inplace -however). The commands will be run in order. +commands will be performed by i3 on initial startup (not when restarting inplace +however). These commands will be run in order. *Syntax*: ------------ @@ -408,9 +409,9 @@ exec sudo i3status | dzen2 -dock [[workspace_screen]] -If you use assignments of clients to workspaces, it might be handy to put the +If you assign clients to workspaces, it might be handy to put the workspaces on specific screens. Also, the assignment of workspaces to screens -will determine the workspace which i3 uses for a new screen when adding screens +will determine which workspace i3 uses for a new screen when adding screens or when starting (e.g., by default it will use 1 for the first screen, 2 for the second screen and so on). @@ -493,7 +494,7 @@ the window. i3 uses unix sockets to provide an IPC interface. This allows third-party programs to get information like the current workspaces to display a workspace -bar and to control i3. +bar, and to control i3. To enable it, you have to configure a path where the unix socket will be stored. The default path is +/tmp/i3-ipc.sock+. @@ -503,7 +504,7 @@ stored. The default path is +/tmp/i3-ipc.sock+. ipc-socket /tmp/i3-ipc.sock ---------------------------- -You can then use the +i3-msg+ command to perform any command listed in the next +You can then use the +i3-msg+ application to perform any command listed in the next section. === Disable focus follows mouse @@ -589,8 +590,8 @@ To change to a specific workspace, the command is just the number of the workspace, e.g. +1+ or +3+. To move the current client to a specific workspace, prefix the number with an +m+. -Furthermore, you can switch to the next and previous workspace with the -commands +nw+ and +pw+, which is handy for example if you have workspace +You can also switch to the next and previous workspace with the +commands +nw+ and +pw+, which is handy, for example, if you have workspace 1, 3, 4 and 9 and you want to cycle through them with a single key combination. *Examples*: @@ -644,8 +645,8 @@ bindsym Mod1+r mode resize === Jumping to specific windows -Especially when in a multi-monitor environment, you want to quickly jump to a specific -window, for example while currently working on workspace 3 you may want to jump to +Often when in a multi-monitor environment, you want to quickly jump to a specific +window. For example while working on workspace 3 you may want to jump to your mailclient to mail your boss that you’ve achieved some important goal. Instead of figuring out how to navigate to your mailclient, it would be more convenient to have a shortcut. @@ -672,16 +673,14 @@ bindsym Mod1+a jump "urxvt/VIM" This feature is like the jump feature: It allows you to directly jump to a specific window (this means switching to the appropriate workspace and setting focus to the windows). However, you can directly mark a specific window with -an arbitrary label and use it afterwards, that is, you do not need to ensure -that your windows have unique classes or titles and you do not need to change +an arbitrary label and use it afterwards. You do not need to ensure +that your windows have unique classes or titles, and you do not need to change your configuration file. As the command needs to include the label with which you want to mark the -window, you cannot simply bind it to a key (or, you could bind it to a key and -only use the set of labels for which you created bindings). +i3-input+ is a -tool created for this purpose: It lets you input a command and sends the -command to i3. It can also prefix this command and display a custom prompt for -the input dialog. +window, you cannot simply bind it to a key. +i3-input+ is a tool created +for this purpose: It lets you input a command and sends the command to i3. It +can also prefix this command and display a custom prompt for the input dialog. *Syntax*: ----------------- @@ -698,10 +697,13 @@ bindsym Mod1+m exec i3-input -p 'mark ' -l 1 -P 'Mark: ' bindsym Mod1+g exec i3-input -p 'goto ' -l 1 -P 'Goto: ' --------------------------------------- +Alternatively, if you do not want to mess with +i3-input+, you could create +seperate bindings for a specific set of labels and then only use those labels. + === Traveling the focus stack This mechanism can be thought of as the opposite of the +jump+ command. It travels -the focus stack and jumps to the window you focused before. +the focus stack and jumps to the window which had focus previously. *Syntax*: -------------- @@ -725,7 +727,7 @@ ft:: To change the border of the current client, you can use +bn+ to use the normal border (including window title), +bp+ to use a 1-pixel border (no window title) -and +bb+ to make the client borderless. There also is +bt+ which will toggle +and +bb+ to make the client borderless. There is also +bt+ which will toggle the different border styles. *Examples*: @@ -739,12 +741,12 @@ bindsym Mod1+u bb === Changing the stack-limit of a container -If you have a single container with a lot of windows inside (say, more than +If you have a single container with a lot of windows inside it (say, more than 10), the default layout of a stacking container can get a little unhandy. -Depending on your screen’s size, you might end up only using half of the -titlebars of each window in the container. +Depending on your screen’s size, you might end up seeing only half of the +titlebars for each window in the container. -Using the +stack-limit+ command, you can limit the amount of rows or columns +Using the +stack-limit+ command, you can limit the number of rows or columns in a stacking container. i3 will create columns or rows (depending on what you limited) automatically as needed. @@ -772,7 +774,7 @@ restart i3 inplace with the +restart+ command to get it out of some weird state your X session. However, your layout is not preserved at the moment, meaning that all open windows will be in a single container in default layout. To exit i3 properly, you can use the +exit+ command, however you don’t need to (e.g., -simply killing your X session is fine aswell). +simply killing your X session is fine as well). *Examples*: ---------------------------- @@ -785,36 +787,36 @@ bindsym Mod1+Shift+e exit [[multi_monitor]] -As you can read in the goal list on its website, i3 was specifically developed +As you can see in the goal list on the website, i3 was specifically developed with support for multiple monitors in mind. This section will explain how to handle multiple monitors. -When you have only one monitor, things are simple. You usually start with +When you have only one monitor things are simple. You usually start with workspace 1 on your monitor and open new ones as you need them. When you have more than one monitor, each monitor will get an initial -workspace, say the first gets 1, the second gets 2 and a possible third would -get 3. When you switch to a workspace on a different screen, i3 will switch -to that screen and then switch to the workspace. This way, you don’t need -shortcuts to switch to a specific screen and remember where you put which -workspace. New workspaces will be opened on the screen you currently are on. -There is no possiblity to have a screen without workspaces. +workspace. The first monitor gets 1, the second gets 2 and a possible third would +get 3. When you switch to a workspace on a different monitor, i3 will switch +to that monitor and then switch to the workspace. This way, you don’t need +shortcuts to switch to a specific monitor, and you don’t need to remember where +you put which workspace. New workspaces will be opened on the currently active +monitor. It is not possible to have a monitor without a workspace. -The idea to make workspaces global is due to the observation that most users -have a very limited set of workspaces on their additional monitors, often -using them for a specific task (browser, shell) or for monitoring several +The idea of making workspaces global is based on the observation that most users +have a very limited set of workspaces on their additional monitors. They are +often used for a specific task (browser, shell) or for monitoring several things (mail, IRC, syslog, …). Thus, using one workspace on one monitor and "the rest" on the other monitors often makes sense. However, as you can -create unlimited workspaces in i3 and tie them to specific screens, you can -have the "traditional" approach of having X workspaces per screen by +create an unlimited number of workspaces in i3 and tie them to specific screens, +you can have the "traditional" approach of having X workspaces per screen by changing your configuration (using modes, for example). === Configuring your monitors -To help you get going if you never used multiple monitors before, here comes a -short overview of the xrandr options which are probably of interest for you. -It is always useful to get an overview of the current screen configuration, so -just run "xrandr" and you will get an output like the following: +To help you get going if you have never used multiple monitors before, here is a +short overview of the xrandr options which will probably be of interest to you. +It is always useful to get an overview of the current screen configuration. +Just run "xrandr" and you will get an output like the following: -------------------------------------------------------------------------------------- $ xrandr Screen 0: minimum 320 x 200, current 1280 x 800, maximum 8192 x 8192 @@ -831,11 +833,11 @@ LVDS1 connected 1280x800+0+0 (normal left inverted right x axis y axis) 261mm x -------------------------------------------------------------------------------------- Several things are important here: You can see that +LVDS1+ is connected (of -course, it is the internal flat panel) but +VGA1+ is not. If you have connected -a monitor to one of the ports but xrandr still says "disconnected", you should +course, it is the internal flat panel) but +VGA1+ is not. If you have a monitor +connected to one of the ports but xrandr still says "disconnected", you should check your cable, monitor or graphics driver. -Furthermore, the maximum resolution you can see at the end of the first line +The maximum resolution you can see at the end of the first line is the maximum combined resolution of your monitors. By default, it is usually too low and has to be increased by editing +/etc/X11/xorg.conf+. @@ -843,7 +845,7 @@ So, say you connected VGA1 and want to use it as an additional screen: ------------------------------------------- xrandr --output VGA1 --auto --left-of LVDS1 ------------------------------------------- -This command makes xrandr try to find out the native resolution of the device +This command makes xrandr try to find the native resolution of the device connected to +VGA1+ and configures it to the left of your internal flat panel. When running "xrandr" again, the output looks like this: ----------------------------------------------------------------------------------------- @@ -878,8 +880,8 @@ See also <> for more examples of multi-monitor setups. There are several things to configure in i3 which may be interesting if you have more than one monitor: -1. You can specify which workspace should be put on which screen. This will - allow you to have a different set of workspaces when starting than just +1. You can specify which workspace should be put on which screen. This + allows you to have a different set of workspaces when starting than just 1 for the first monitor, 2 for the second and so on. See <>. 2. If you want some applications to generally open on the bigger screen @@ -894,30 +896,30 @@ have more than one monitor: === Displaying a status line A very common thing amongst users of exotic window managers is a status line at -some corner of the screen. It is an often superior replacement of the widget +some corner of the screen. It is an often superior replacement to the widget approach you have in the task bar of a traditional desktop environment. If you don’t already have your favorite way of generating such a status line (self-written scripts, conky, …), then i3status is the recommended tool for -this task. It was written in C with the goal to use as little syscalls as -possible to reduce the time your CPU is waken up from sleep states. +this task. It was written in C with the goal of using as few syscalls as +possible to reduce the time your CPU is woken up from sleep states. Regardless of which application you use to generate the status line, you want to make sure that the application does one of the following things: 1. Register as a dock window using EWMH hints. This will make i3 position the window above the workspace bar but below every other client. This is the - recommended way, but for example in case of dzen2 you need to check out - the source of dzen2 from subversion, because the -dock option is not present + recommended way, but in case of dzen2, for example, you need to check out + the source of dzen2 from subversion, as the -dock option is not present in the released versions. 2. Overlay the internal workspace bar. This method will not waste any space - in the workspace bar. However, it is a rather hackish way. Just configure - the output window to be over your workspace bar (say -x 200 and -y 780 if + on the workspace bar, however, it is rather hackish. Just configure + the output window to be over the workspace bar (say -x 200 and -y 780 if your screen is 800 px height). The planned solution for this problem is to make the workspace bar optional -and switch to dzen2 (for example) completely (it will contain the workspaces -then). +and switch to a third party application completely (dzen2 for example) +which will then contain the workspace bar. === Giving presentations (multi-monitor) @@ -929,14 +931,14 @@ slides. [[presentations]] ==== Case 1: everybody gets the same output -This is the rather easy case. You connect your computer to the video projector, +This is the simple case. You connect your computer to the video projector, turn on both (computer and video projector) and configure your X server to clone the internal flat panel of your computer to the video output: ----------------------------------------------------- xrandr --output VGA1 --mode 1024x768 --same-as LVDS1 ----------------------------------------------------- i3 will then use the lowest common subset of screen resolutions, the rest of -your screen will be left untouched (so it will show the X background). So, in +your screen will be left untouched (it will show the X background). So, in our example, this would be 1024x768 (my notebook has 1280x800). ==== Case 2: you can see more than your audience @@ -948,6 +950,6 @@ xrandr --output VGA1 --mode 1024x768 --right-of LVDS1 Now, i3 will put a new workspace (depending on your settings) on the new screen and you are in multi-monitor mode (see <>). -Because i3 is not a compositing window manager, there is no possibility to -display a window on two screens at the same time. Instead, you presentation +Because i3 is not a compositing window manager, there is no ability to +display a window on two screens at the same time. Instead, your presentation software needs to do this job (that is, open a window on each screen). From b47d0a89327da6c06c38b0c7252e140b8c5f0013 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 17 Mar 2010 00:36:08 +0100 Subject: [PATCH 186/247] Bugfix: configure floating windows above tiling windows when moving them to another workspaces (Thanks Sasha) --- src/commands.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/commands.c b/src/commands.c index 273f9d39..6276ac19 100644 --- a/src/commands.c +++ b/src/commands.c @@ -600,6 +600,22 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl xcb_flush(conn); } + /* Configure the window above all tiling windows (or below a fullscreen + * window, if any) */ + if (t_ws->fullscreen_client != NULL) { + uint32_t values[] = { t_ws->fullscreen_client->frame, XCB_STACK_MODE_BELOW }; + xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); + } else { + Client *last_tiling; + SLIST_FOREACH(last_tiling, &(t_ws->focus_stack), focus_clients) + if (!client_is_floating(last_tiling)) + break; + if (last_tiling != SLIST_END(&(t_ws->focus_stack))) { + uint32_t values[] = { last_tiling->frame, XCB_STACK_MODE_ABOVE }; + xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values); + } + } + DLOG("done\n"); render_layout(conn); From a604af6340c41220e7055abec555ecbb95f46f50 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 17 Mar 2010 03:18:13 +0100 Subject: [PATCH 187/247] make pointer follow the focus when moving to a different screen also for floating windows --- src/commands.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/commands.c b/src/commands.c index 6276ac19..28bf7dc1 100644 --- a/src/commands.c +++ b/src/commands.c @@ -620,8 +620,10 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl render_layout(conn); - if (workspace_is_visible(t_ws)) + if (workspace_is_visible(t_ws)) { + client_warp_pointer_into(conn, client); set_focus(conn, client, true); + } } /* From 1680071555af560b0eb370087ab7a0a333481d88 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 17 Mar 2010 15:56:26 +0100 Subject: [PATCH 188/247] randr: use effective CRTC width/height, not its mode (Thanks moemoe) This fixes setups which use panning and cloning. --- src/randr.c | 33 +++++---------------------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/src/randr.c b/src/randr.c index 9ef45fd0..d88fb9c2 100644 --- a/src/randr.c +++ b/src/randr.c @@ -215,24 +215,6 @@ void disable_randr(xcb_connection_t *conn) { randr_disabled = true; } -/* - * Searches for a mode in the current RandR configuration by the mode id. - * Returns NULL if no such mode could be found (should never happen). - * - */ -static mode_info *get_mode_by_id(resources_reply *reply, xcb_randr_mode_t mode) { - xcb_randr_mode_info_iterator_t it; - - for (it = xcb_randr_get_screen_resources_current_modes_iterator(reply); - it.rem > 0; - xcb_randr_mode_info_next(&it)) { - if (it.data->id == mode) - return it.data; - } - - return NULL; -} - /* * This function needs to be called when changing the mode of an output when * it already has some workspaces (or a bar window) assigned. @@ -296,10 +278,6 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, /* each CRT controller has a position in which we are interested in */ crtc_info *crtc; - /* the CRTC runs in a specific mode, while the position is stored in - * the output itself */ - mode_info *mode; - Output *new = get_output_by_id(id); bool existing = (new != NULL); if (!existing) @@ -327,10 +305,9 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, xcb_randr_get_crtc_info_cookie_t icookie; icookie = xcb_randr_get_crtc_info(conn, output->crtc, cts); - if ((crtc = xcb_randr_get_crtc_info_reply(conn, icookie, NULL)) == NULL || - (mode = get_mode_by_id(res, crtc->mode)) == NULL) { - DLOG("Skipping output %s: could not get CRTC/mode (%p/%p)\n", - new->name, crtc, mode); + if ((crtc = xcb_randr_get_crtc_info_reply(conn, icookie, NULL)) == NULL) { + DLOG("Skipping output %s: could not get CRTC (%p)\n", + new->name, crtc); free(new); free(output); return; @@ -339,8 +316,8 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, new->active = true; bool updated = update_if_necessary(&(new->rect.x), crtc->x) | update_if_necessary(&(new->rect.y), crtc->y) | - update_if_necessary(&(new->rect.width), mode->width) | - update_if_necessary(&(new->rect.height), mode->height); + update_if_necessary(&(new->rect.width), crtc->width) | + update_if_necessary(&(new->rect.height), crtc->height); DLOG("mode: %dx%d+%d+%d\n", new->rect.width, new->rect.height, new->rect.x, new->rect.y); From 62c45323290ede9c3be3e213b6e91a6a4d111045 Mon Sep 17 00:00:00 2001 From: Helgi Kristvin Sigurbjarnarson Date: Fri, 19 Mar 2010 00:18:25 +0000 Subject: [PATCH 189/247] Bugfix: Take window out of fullscreen before entering floating mode. --- src/commands.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/commands.c b/src/commands.c index 28bf7dc1..aefb3e5b 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1167,6 +1167,9 @@ void parse_command(xcb_connection_t *conn, const char *command) { Workspace *ws = last_focused->workspace; + if(last_focused->fullscreen) + client_leave_fullscreen(conn, last_focused); + toggle_floating_mode(conn, last_focused, false); /* delete all empty columns/rows */ cleanup_table(conn, ws); From 91b6c69eaea391b3bd8e2c124662fda9c043e89c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 19 Mar 2010 01:43:11 +0100 Subject: [PATCH 190/247] little style fix for the last commit --- src/commands.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands.c b/src/commands.c index aefb3e5b..e7ebb946 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1167,7 +1167,7 @@ void parse_command(xcb_connection_t *conn, const char *command) { Workspace *ws = last_focused->workspace; - if(last_focused->fullscreen) + if (last_focused->fullscreen) client_leave_fullscreen(conn, last_focused); toggle_floating_mode(conn, last_focused, false); From 0bb1b718d1576e9ee5c7f6859560af1300e41c11 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 19 Mar 2010 16:02:12 +0100 Subject: [PATCH 191/247] Fix compilation with the old xcb keysyms api --- src/config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.c b/src/config.c index 8d32ad4a..f42583fa 100644 --- a/src/config.c +++ b/src/config.c @@ -138,7 +138,7 @@ void translate_keysyms() { bind->number_keycodes = 1; xcb_keycode_t code = xcb_key_symbols_get_keycode(keysyms, keysym); DLOG("Translated symbol \"%s\" to 1 keycode (%d)\n", bind->symbol, code); - grab_keycode_for_binding(conn, bind, code); + grab_keycode_for_binding(global_conn, bind, code); bind->translated_to = smalloc(sizeof(xcb_keycode_t)); memcpy(bind->translated_to, &code, sizeof(xcb_keycode_t)); #else From af00df9321927bfc587140fbdaac0da61341ccc6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 19 Mar 2010 18:48:36 +0100 Subject: [PATCH 192/247] Use DLOG for debug messages instead of printf (Thanks kruM) --- include/log.h | 3 ++- src/debug.c | 10 ++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/include/log.h b/include/log.h index def99fc1..6d529a00 100644 --- a/include/log.h +++ b/include/log.h @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -12,6 +12,7 @@ #define _LOG_H #include +#include /** ##__VA_ARGS__ means: leave out __VA_ARGS__ completely if it is empty, that is, delete the preceding comma */ diff --git a/src/debug.c b/src/debug.c index cd89b296..de47fca2 100644 --- a/src/debug.c +++ b/src/debug.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -14,6 +14,8 @@ #include #include +#include "log.h" + static const char *labelError[] = { "Success", "BadRequest", @@ -219,7 +221,7 @@ int format_event(xcb_generic_event_t *e) { switch(e->response_type) { case 0: - printf("Error %s on seqnum %d (%s).\n", + DLOG("Error %s on seqnum %d (%s).\n", labelError[*((uint8_t *) e + 1)], seqnum, labelRequest[*((uint8_t *) e + 10)]); @@ -227,13 +229,13 @@ int format_event(xcb_generic_event_t *e) { default: if (e->response_type > sizeof(labelEvent) / sizeof(char*)) break; - printf("Event %s following seqnum %d%s.\n", + DLOG("Event %s following seqnum %d%s.\n", labelEvent[e->response_type], seqnum, labelSendEvent[sendEvent]); break; case XCB_KEYMAP_NOTIFY: - printf("Event %s%s.\n", + DLOG("Event %s%s.\n", labelEvent[e->response_type], labelSendEvent[sendEvent]); break; From ee76b2ebf619ed9c7c44d00ed790ae818f879a4b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 19 Mar 2010 18:51:01 +0100 Subject: [PATCH 193/247] Bugfix: When disabling RandR/Xinerama, give a name to the pseudo-output (Thanks fallen) --- src/randr.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/randr.c b/src/randr.c index d88fb9c2..7531ecf2 100644 --- a/src/randr.c +++ b/src/randr.c @@ -209,6 +209,7 @@ void disable_randr(xcb_connection_t *conn) { s->rect.y = 0; s->rect.width = root_screen->width_in_pixels; s->rect.height = root_screen->height_in_pixels; + s->name = "xroot-0"; TAILQ_INSERT_TAIL(&outputs, s, outputs); From 3c8c426011c482de9c0d84ba4feb2e2de4d5fc16 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 19 Mar 2010 18:51:19 +0100 Subject: [PATCH 194/247] Disable RandR if no outputs are found (Thanks fallen) --- src/randr.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/randr.c b/src/randr.c index 7531ecf2..3be654a7 100644 --- a/src/randr.c +++ b/src/randr.c @@ -458,6 +458,11 @@ void randr_query_outputs(xcb_connection_t *conn) { } } + if (TAILQ_EMPTY(&outputs)) { + ELOG("No outputs found via RandR, disabling\n"); + disable_randr(conn); + } + ewmh_update_workarea(); /* Just go through each active output and associate one workspace */ From fdcbec248a7f139fab45993a61c96073084696f4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 19 Mar 2010 21:44:36 +0100 Subject: [PATCH 195/247] Start dock clients on the output they request to be started on according to their geometry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use dzen2’s -xs option to use this, or specify the coordinates using -x manually. --- src/manage.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/manage.c b/src/manage.c index 40ef6cca..10cf74c7 100644 --- a/src/manage.c +++ b/src/manage.c @@ -272,12 +272,18 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, for (int i = 0; i < xcb_get_property_value_length(preply); i++) if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) { DLOG("Window is a dock.\n"); + Output *t_out = get_output_containing(x, y); + if (t_out != c_ws->output) { + DLOG("Dock client requested to be on output %s by geometry (%d, %d)\n", + t_out->name, x, y); + new->workspace = t_out->current_workspace; + } new->dock = true; new->borderless = true; new->titlebar_position = TITLEBAR_OFF; new->force_reconfigure = true; new->container = NULL; - SLIST_INSERT_HEAD(&(c_ws->output->dock_clients), new, dock_clients); + SLIST_INSERT_HEAD(&(t_out->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; From a607eae53a3b31ede9cd59b487b96c487c63e126 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 19 Mar 2010 22:01:21 +0100 Subject: [PATCH 196/247] ipc: include the urgent flag in the workspaces reply --- docs/ipc | 4 ++++ src/ipc.c | 3 +++ 2 files changed, 7 insertions(+) diff --git a/docs/ipc b/docs/ipc index fc3f1939..383bde50 100644 --- a/docs/ipc +++ b/docs/ipc @@ -124,6 +124,8 @@ visible (boolean):: focused (boolean):: Whether this workspace currently has the focus (only one workspace can have the focus at the same time). +urgent (boolean):: + Whether a window on this workspace has the "urgent" flag set. rect (map):: The rectangle of this workspace (equals the rect of the output it is on), consists of x, y, width, height. @@ -138,6 +140,7 @@ output (string):: "name": "1", "visible": true, "focused": true, + "urgent": false, "rect": { "x": 0, "y": 0, @@ -151,6 +154,7 @@ output (string):: "name": "2", "visible": false, "focused": false, + "urgent": false, "rect": { "x": 0, "y": 0, diff --git a/src/ipc.c b/src/ipc.c index f2dfd1f6..1b9bf6e0 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -185,6 +185,9 @@ IPC_HANDLER(get_workspaces) { ystr("output"); ystr(ws->output->name); + ystr("urgent"); + y(bool, ws->urgent); + y(map_close); } From 4ce0d6f014fb89c17af8493357b61f0cf75e7946 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 19 Mar 2010 22:24:52 +0100 Subject: [PATCH 197/247] ipc: implement GET_OUTPUTS --- docs/ipc | 49 ++++++++++++++++++++++++++++++++++++++++ include/data.h | 4 ++-- include/i3/ipc.h | 6 +++++ src/ipc.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 113 insertions(+), 4 deletions(-) diff --git a/docs/ipc b/docs/ipc index 383bde50..35a5fa03 100644 --- a/docs/ipc +++ b/docs/ipc @@ -49,6 +49,9 @@ GET_WORKSPACES (1):: SUBSCRIBE (2):: Subscribes your connection to certain events. See <> for a description of this message and the concept of events. +GET_OUTPUTS (3):: + Gets the current outputs. The reply will be a JSON-encoded list of outputs + (see the reply section). So, a typical message could look like this: -------------------------------------------------- @@ -96,6 +99,8 @@ GET_WORKSPACES (1):: Reply to the GET_WORKSPACES message. SUBSCRIBE (2):: Confirmation/Error code for the SUBSCRIBE message. +GET_OUTPUTS (3):: + Reply to the GET_OUTPUTS message. === COMMAND reply @@ -177,6 +182,50 @@ default) or whether a JSON parse error occurred. { "success": true } ------------------- +=== GET_OUTPUTS reply + +The reply consists of a serialized list of outputs. Each output has the +following properties: + +name (string):: + The name of this output (as seen in +xrandr(1)+). Encoded in UTF-8. +active (boolean):: + Whether this output is currently active (has a valid mode). +current_workspace (integer):: + The current workspace which is visible on this output. +null+ if the + output is not active. +rect (map):: + The rectangle of this output (equals the rect of the output it + is on), consists of x, y, width, height. + +*Example:* +------------------- +[ + { + "name": "LVDS1", + "active": true, + "current_workspace": 4, + "rect": { + "x": 0, + "y": 0, + "width": 1280, + "height": 800 + } + }, + { + "name": "VGA1", + "active": true, + "current_workspace": 1, + "rect": { + "x": 1280, + "y": 0, + "width": 1280, + "height": 1024 + }, + } +] +------------------- + == Events [[events]] diff --git a/include/data.h b/include/data.h index 6819ca95..2d8c7b1a 100644 --- a/include/data.h +++ b/include/data.h @@ -512,8 +512,8 @@ struct xoutput { /** Name of the output */ char *name; - /** Whether the output is currently (has a CRTC attached with a valid - * mode) */ + /** Whether the output is currently active (has a CRTC attached with a + * valid mode) */ bool active; /** Internal flags, necessary for querying RandR screens (happens in diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 5adec378..56f22344 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -32,6 +32,9 @@ /** Subscribe to the specified events */ #define I3_IPC_MESSAGE_TYPE_SUBSCRIBE 2 +/** Requests the current outputs from i3 */ +#define I3_IPC_MESSAGE_TYPE_GET_OUTPUTS 3 + /* * Messages from i3 to clients * @@ -46,6 +49,9 @@ /** Subscription reply type */ #define I3_IPC_REPLY_TYPE_SUBSCRIBE 2 +/** Outputs reply type */ +#define I3_IPC_REPLY_TYPE_OUTPUTS 3 + /* * Events from i3 to clients. Events have the first bit set high. * diff --git a/src/ipc.c b/src/ipc.c index 1b9bf6e0..8ed455dd 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -32,6 +32,7 @@ #include "commands.h" #include "log.h" #include "table.h" +#include "randr.h" /* Shorter names for all those yajl_gen_* functions */ #define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__) @@ -201,6 +202,56 @@ IPC_HANDLER(get_workspaces) { y(free); } +/* + * Formats the reply message for a GET_OUTPUTS request and sends it to the + * client + * + */ +IPC_HANDLER(get_outputs) { + Output *output; + + yajl_gen gen = yajl_gen_alloc(NULL, NULL); + y(array_open); + + TAILQ_FOREACH(output, &outputs, outputs) { + y(map_open); + + ystr("name"); + ystr(output->name); + + ystr("active"); + y(bool, output->active); + + ystr("rect"); + y(map_open); + ystr("x"); + y(integer, output->rect.x); + ystr("y"); + y(integer, output->rect.y); + ystr("width"); + y(integer, output->rect.width); + ystr("height"); + y(integer, output->rect.height); + y(map_close); + + ystr("current_workspace"); + if (output->current_workspace == NULL) + y(null); + else y(integer, output->current_workspace->num + 1); + + y(map_close); + } + + y(array_close); + + const unsigned char *payload; + unsigned int length; + y(get_buf, &payload, &length); + + ipc_send_message(fd, payload, I3_IPC_REPLY_TYPE_OUTPUTS, length); + y(free); +} + /* * Callback for the YAJL parser (will be called when a string is parsed). * @@ -277,10 +328,13 @@ IPC_HANDLER(subscribe) { I3_IPC_REPLY_TYPE_SUBSCRIBE, strlen(reply)); } -handler_t handlers[3] = { +/* The index of each callback function corresponds to the numeric + * value of the message type (see include/i3/ipc.h) */ +handler_t handlers[4] = { handle_command, handle_get_workspaces, - handle_subscribe + handle_subscribe, + handle_get_outputs }; /* From aec40126b4335fbc39d0e1df220f11c21fd88f59 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 19 Mar 2010 22:40:43 +0100 Subject: [PATCH 198/247] ipc: implement output event --- docs/ipc | 14 ++++++++++++++ include/i3/ipc.h | 4 ++++ src/handlers.c | 2 ++ 3 files changed, 20 insertions(+) diff --git a/docs/ipc b/docs/ipc index 35a5fa03..5d70d7fe 100644 --- a/docs/ipc +++ b/docs/ipc @@ -260,6 +260,9 @@ workspace:: Sent when the user switches to a different workspace, when a new workspace is initialized or when a workspace is removed (because the last client vanished). +output:: + Sent when RandR issues a change notification (of either screens, + outputs, CRTCs or output properties). === workspace event @@ -272,6 +275,17 @@ This event consists of a single serialized map containing a property { "change": "focus" } --------------------- +=== output event + +This event consists of a single serialized map containing a property ++change (string)+ which indicates the type of the change (currently only +"unspecified"). + +*Example:* +--------------------------- +{ "change": "unspecified" } +--------------------------- + == See also For some languages, libraries are available (so you don’t have to implement diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 56f22344..1ea39182 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -58,6 +58,10 @@ */ #define I3_IPC_EVENT_MASK (1 << 31) +/* The workspace event will be triggered upon changes in the workspace list */ #define I3_IPC_EVENT_WORKSPACE (I3_IPC_EVENT_MASK | 0) +/* The output event will be triggered upon changes in the output list */ +#define I3_IPC_EVENT_OUTPUT (I3_IPC_EVENT_MASK | 1) + #endif diff --git a/src/handlers.c b/src/handlers.c index b5c1cf1b..c2911942 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -447,6 +447,8 @@ int handle_screen_change(void *prophs, xcb_connection_t *conn, randr_query_outputs(conn); + ipc_send_event("output", I3_IPC_EVENT_OUTPUT, "{\"change\":\"unspecified\"}"); + return 1; } From 46bd9ac58a9b2a096cf0ed443d49e8224900ae28 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 20 Mar 2010 02:52:06 +0100 Subject: [PATCH 199/247] Bugfix: correctly re-assign dock clients when output goes inactive --- src/randr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/randr.c b/src/randr.c index 3be654a7..ab79ea5b 100644 --- a/src/randr.c +++ b/src/randr.c @@ -447,8 +447,8 @@ void randr_query_outputs(xcb_connection_t *conn) { Client *dock; while (!SLIST_EMPTY(&(output->dock_clients))) { dock = SLIST_FIRST(&(output->dock_clients)); - SLIST_INSERT_HEAD(&(first->dock_clients), dock, dock_clients); SLIST_REMOVE_HEAD(&(output->dock_clients), dock_clients); + SLIST_INSERT_HEAD(&(first->dock_clients), dock, dock_clients); } output->current_workspace = NULL; output->to_be_disabled = false; From 35a791f7e6c111c84ac1e949c300fba70ad802fb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 20 Mar 2010 02:56:23 +0100 Subject: [PATCH 200/247] ipc: also send workspace event when initializing a workspace for an output --- src/randr.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/randr.c b/src/randr.c index ab79ea5b..dac3c010 100644 --- a/src/randr.c +++ b/src/randr.c @@ -34,6 +34,7 @@ #include "workspace.h" #include "log.h" #include "ewmh.h" +#include "ipc.h" /* While a clean namespace is usually a pretty good thing, we really need * to use shorter names than the whole xcb_randr_* default names. */ @@ -188,6 +189,7 @@ void initialize_output(xcb_connection_t *conn, Output *output, Workspace *worksp SLIST_INIT(&(output->dock_clients)); + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); DLOG("initialized output at (%d, %d) with %d x %d\n", output->rect.x, output->rect.y, output->rect.width, output->rect.height); } From 77efb29d9fff8ed1dbb3bf213910ab4118c32b5b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 20 Mar 2010 03:09:42 +0100 Subject: [PATCH 201/247] ipc: send a workspace event when the urgency flag changes --- docs/ipc | 2 +- src/workspace.c | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/ipc b/docs/ipc index 5d70d7fe..117050a3 100644 --- a/docs/ipc +++ b/docs/ipc @@ -268,7 +268,7 @@ output:: This event consists of a single serialized map containing a property +change (string)+ which indicates the type of the change ("focus", "init", -"empty"). +"empty", "urgent"). *Example:* --------------------- diff --git a/src/workspace.c b/src/workspace.c index 8d762729..67874f29 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -425,16 +425,21 @@ void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) { */ void workspace_update_urgent_flag(Workspace *ws) { Client *current; + bool old_flag = ws->urgent; + bool urgent = false; SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) { if (!current->urgent) continue; - ws->urgent = true; - return; + urgent = true; + break; } - ws->urgent = false; + ws->urgent = urgent; + + if (old_flag != urgent) + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"urgent\"}"); } /* From 234ed6c99b03eca5a95e686cda298eae33d40c2f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 21 Mar 2010 01:50:10 +0100 Subject: [PATCH 202/247] docs: merge spelling and grammar fixes by sasha (Thanks!) --- CMDMODE | 19 +++-- DEPENDS | 7 +- PACKAGE-MAINTAINER | 21 ++++-- docs/debugging | 42 +++++------ docs/hacking-howto | 40 +++++----- docs/ipc | 16 ++-- docs/multi-monitor | 40 +++++----- docs/userguide | 183 +++++++++++++++++++++++---------------------- i3.config | 2 +- i3.welcome | 13 ++-- man/i3-input.man | 8 +- man/i3-msg.man | 3 +- man/i3.man | 14 ++-- 13 files changed, 215 insertions(+), 193 deletions(-) diff --git a/CMDMODE b/CMDMODE index 7d8f6f23..95cb5bc1 100644 --- a/CMDMODE +++ b/CMDMODE @@ -2,7 +2,8 @@ - Command mode --------------------- -This is the grammar for the command mode (your configuration file uses these commands, too). +This is the grammar for the 'command mode' (your configuration file +uses these commands, too). left := | right := | @@ -17,15 +18,17 @@ cmd := [ ] [ | ] with := { [ ] }+ jump := [ "[/]" | [ ] ] focus := focus [ | floating | tiling | ft ] -(travels the focus stack backwards the given amount of times (by default 1), so - it selects the window which had the focus before you focused the current one when - specifying "focus 1". - The special values 'floating' (select the next floating window), 'tiling' - (select the next tiling window), 'ft' (if the current window is floating, - select the next tiling window and vice-versa) are also valid) + (travels the focus stack backwards, number of times (by default 1). + So by specifying "focus 1" it selects the window which last had the focus + before you focused the current one window. + The following 3 special values are also valid: + 'floating' (select the next floating window). + 'tiling' (select the next tiling window). + 'ft' (toggle tiling/floating: if the current window is floating, + select the next tiling window and vice-versa) special := [ exec | kill | exit | restart ] -input := [ | | | | ] +input := [ | | | | ] you can cancel command mode by pressing escape anytime. diff --git a/DEPENDS b/DEPENDS index 31947bf9..b7a6fefb 100644 --- a/DEPENDS +++ b/DEPENDS @@ -1,6 +1,7 @@ -You need the following libraries. The version given is to be understand as the minimum -version. However, if any of these libraries changes the API, i3 may not compile anymore. -In that case, please try using the versions mentioned below until a fix is provided. +You need the following libraries. The version given is to be understood as the +minimum version required. However, if any of these libraries changes the API, +i3 may not compile anymore. In that case, please try using the versions +mentioned below until a fix is provided. * xcb-proto-1.3 (2008-12-10) * libxcb-1.1.93 (2008-12-11) diff --git a/PACKAGE-MAINTAINER b/PACKAGE-MAINTAINER index c5c10038..40222803 100644 --- a/PACKAGE-MAINTAINER +++ b/PACKAGE-MAINTAINER @@ -1,11 +1,15 @@ Dear package maintainer, -thanks for packaging i3. By doing so, you are improving your distribution and i3 in general. +thanks for packaging i3. By doing so, you are improving your distribution +and i3 in general. -Please read the file DEPENDS now, so you know which libraries are necessary and where to -get them from if your distribution does not already have packages for them. +Please read the file DEPENDS now, so you know which libraries are necessary +and where to get them from if your distribution does not already have +packages for them. + +Please make sure the manpage for i3 will be properly created and installed +in your package. -Please make sure the manpage for i3 will be properly created and installed in your package. On debian, this looks like this: # Compilation @@ -17,10 +21,11 @@ On debian, this looks like this: mkdir -p $(CURDIR)/debian/i3-wm/usr/share/man/man1 cp man/i3.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1 -If you got any questions, ideas, hints, problems or whatever, please do not -hesitate to contact me. I will help you out. Just drop me an E-Mail (find the address at -http://michael.stapelberg.de/Kontakt, scroll down to bottom), contact me using the same -address in jabber or ask on our IRC channel (#i3 on irc.twice-irc.de). +If you have any questions, ideas, hints, problems or whatever, please do not +hesitate to contact me. I will help you out. Just drop me an E-Mail (find the +address at http://michael.stapelberg.de/Kontakt, scroll down to bottom), +contact me using the same address in jabber or ask on our IRC channel: +(#i3 on irc.twice-irc.de). Thanks again for your efforts, Michael diff --git a/docs/debugging b/docs/debugging index f2a92739..ca680f24 100644 --- a/docs/debugging +++ b/docs/debugging @@ -1,5 +1,5 @@ Debugging i3: How To -================== +==================== Michael Stapelberg April 2009 @@ -13,22 +13,22 @@ debugging and/or need further help, do not hesitate to contact us! == Enabling logging i3 spits out much information onto stdout. To have a clearly defined place -where logfiles will be saved, you should redirect stdout and stderr in -xsession. While you’re at it, putting each run of i3 in a separate logfile with -date/time in it is a good idea to not get confused about the different logfiles -later on. +where log files will be saved, you should redirect stdout and stderr in +xsession. While you’re at it, putting each run of i3 in a separate log file +with date/time in it is a good idea to not get confused about the different +log files later on. -------------------------------------------------------------------- exec /usr/bin/i3 >/home/michael/i3/i3log-$(date +'%F-%k-%M-%S') 2>&1 -------------------------------------------------------------------- -== Enabling coredumps +== Enabling core dumps -When i3 crashes, often you have the chance of getting a coredump (an image of -the memory of the i3 process which can be loaded into a debugger). To get a -core-dump, you have to make sure that the user limit for core dump files is set +When i3 crashes, often you have the chance of getting a 'core dump' (an image +of the memory of the i3 process which can be loaded into a debugger). To get a +core dump, you have to make sure that the user limit for core dump files is set high enough. Many systems ship with a default value which even forbids core -dumps completely. To disable the limit completely and thus enable coredumps, +dumps completely. To disable the limit completely and thus enable core dumps, use the following command (in your .xsession, before starting i3): ------------------- @@ -49,7 +49,7 @@ process id (%p) in it. You can save this setting across reboots using == Compiling with debug symbols -To actually get useful coredumps, you should make sure that your version of i3 +To actually get useful core dumps, you should make sure that your version of i3 is compiled with debug symbols, that is, that they are not stripped during the build process. You can check whether your executable contains symbols by issuing the following command: @@ -72,15 +72,15 @@ stripping. If nothing helps, please build i3 from source. == Generating a backtrace Once you have made sure that your i3 is compiled with debug symbols and that -coredumps are enabled, you can start getting some sense out of the coredumps. +core dumps are enabled, you can start making sense out of the core dumps. -Because the coredump depends on the original executable (and its debug +Because the core dump depends on the original executable (and its debug symbols), please do this as soon as you encounter the problem. If you -re-compile i3, your coredump might be useless afterwards. +re-compile i3, your core dump might be useless afterwards. Please install +gdb+, a debugger for C. No worries, you don’t need to learn it now. Start gdb using the following command (replacing the actual name of the -coredump of course): +core dump of course): ---------------------------- gdb $(which i3) core.i3.3849 @@ -92,13 +92,13 @@ Then, generate a backtrace using: backtrace full -------------- -== Sending bugreports/debugging on IRC +== Sending bug reports/debugging on IRC -When sending bugreports, please paste the relevant part of the log (if in +When sending bug reports, please paste the relevant part of the log (if in doubt, please send us rather too much information than too less) and the whole -backtrace (if there was a coredump). +backtrace (if there was a core dump). When debugging with us in IRC, be prepared to use a so called nopaste service -such as http://nopaste.info because pasting large amounts of text in IRC -sometimes leads to incomplete lines (servers have line length limitations) or -flood kicks. +such as http://nopaste.info or http://pastebin.com because pasting large +amounts of text in IRC sometimes leads to incomplete lines (servers have line +length limitations) or flood kicks. diff --git a/docs/hacking-howto b/docs/hacking-howto index 017e0d0b..dff074cb 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -24,8 +24,9 @@ some events which normal clients usually don’t handle. In the case of i3, the tasks (and order of them) are the following: . Grab the key bindings (events will be sent upon keypress/keyrelease) -. Iterate through all existing windows (if the window manager is not started as the first - client of X) and manage them (= reparent them, create window decorations) +. Iterate through all existing windows (if the window manager is not started as + the first client of X) and manage them (reparent them, create window + decorations, etc.) . When new windows are created, manage them . Handle the client’s `_WM_STATE` property, but only the `_WM_STATE_FULLSCREEN` . Handle the client’s `WM_NAME` property @@ -35,8 +36,8 @@ In the case of i3, the tasks (and order of them) are the following: . Handle button (as in mouse buttons) presses for focus/raise on click . Handle expose events to re-draw own windows such as decorations . React to the user’s commands: Change focus, Move windows, Switch workspaces, -Change the layout mode of a container (default/stacking), Start a new application, -Restart the window manager + Change the layout mode of a container (default/stacking/tabbed), start a new + application, restart the window manager In the following chapters, each of these tasks and their implementation details will be discussed. @@ -46,8 +47,8 @@ will be discussed. Traditionally, there are two approaches to managing windows: The most common one nowadays is floating, which means the user can freely move/resize the windows. The other approach is called tiling, which means that your window -manager distributing windows to use as much space as possible while not -overlapping. +manager distributes windows to use as much space as possible while not +overlapping each other. The idea behind tiling is that you should not need to waste your time moving/resizing windows while you usually want to get some work done. After @@ -90,8 +91,9 @@ When moving terminal 2 to the bottom, the table will be expanded again. |======== You can really think of the layout table like a traditional HTML table, if -you’ve ever designed one. Especially col- and rowspan work equally. Below you -see an example of colspan=2 for the first container (which has T1 as window). +you’ve ever designed one. Especially col- and rowspan work similarly. Below, +you see an example of colspan=2 for the first container (which has T1 as +window). [width="15%",cols="^asciidoc"] |======== @@ -112,7 +114,7 @@ Contains data definitions used by nearly all files. You really need to read this first. include/*.h:: -Contains forward definitions for all public functions, aswell as +Contains forward definitions for all public functions, as well as doxygen-compatible comments (so if you want to get a bit more of the big picture, either browse all header files or use doxygen if you prefer that). @@ -131,7 +133,7 @@ Contains all functions which are specific to a certain client (make it fullscreen, see if its class/name matches a pattern, kill it, …). src/commands.c:: -Parsing commands and actually execute them (focussing, moving, …). +Parsing commands and actually executing them (focusing, moving, …). src/config.c:: Parses the configuration file. @@ -143,7 +145,7 @@ src/floating.c:: Contains functions for floating mode (mostly resizing/dragging). src/handlers.c:: -Contains all handlers for all kind of X events (new window title, new hints, +Contains all handlers for all kinds of X events (new window title, new hints, unmapping, key presses, button presses, …). src/ipc.c:: @@ -212,7 +214,7 @@ screen you are currently on. === Workspace A workspace is identified by its number. Basically, you could think of -workspaces as different desks in your bureau, if you like the desktop +workspaces as different desks in your office, if you like the desktop methaphor. They just contain different sets of windows and are completely separate of each other. Other window managers also call this ``Virtual desktops''. @@ -288,7 +290,7 @@ So, why do we need to grab keycodes actively? Because X does not set the state-property of keypress/keyrelease events properly. The Mode_switch bit is not set and we need to get it using XkbGetState. This means we cannot pass X our combination of modifiers containing Mode_switch when grabbing the key and -therefore need to grab the keycode itself without any modiffiers. This means, +therefore need to grab the keycode itself without any modifiers. This means, if you bind Mode_switch + keycode 38 ("a"), i3 will grab keycode 38 ("a") and check on each press of "a" if the Mode_switch bit is set using XKB. If yes, it will handle the event, if not, it will replay the event. @@ -344,7 +346,7 @@ moved/resized so that the currently active layout (default/stacking/tabbed mode) is rendered correctly. To move/resize windows, a window is ``configured'' in X11-speak. -Some applications, such as MPlayer obivously assume the window manager is +Some applications, such as MPlayer obviously assume the window manager is stupid and try to configure their windows by themselves. This generates an event called configurerequest. i3 handles these events and tells the window the size it had before the configurerequest (with the exception of not yet mapped @@ -374,7 +376,7 @@ characters (every special character contained in your font). == Size hints -Size hints specify the minimum/maximum size for a given window aswell as its +Size hints specify the minimum/maximum size for a given window as well as its aspect ratio. This is important for clients like mplayer, who only set the aspect ratio and resize their window to be as small as possible (but only with some video outputs, for example in Xv, while when using x11, mplayer does the @@ -447,10 +449,10 @@ floating windows: (+grabwin+) * Another window, 2px width and as high as your screen (or vice versa for horizontal resizing) is created. Its background color is the border color and - it is only there to signalize the user how big the container will be (it + it is only there to inform the user how big the container will be (it creates the impression of dragging the border out of the container). * The +drag_pointer+ function of +src/floating.c+ is called to grab the pointer - and enter an own event loop which will pass all events (expose events) but + and enter its own event loop which will pass all events (expose events) but motion notify events. This function then calls the specified callback (+resize_callback+) which does some boundary checking and moves the helper window. As soon as the mouse button is released, this loop will be @@ -497,7 +499,7 @@ http://git-scm.com/documentation When you want to send a patch because you fixed a bug or implemented a cool feature (please talk to us before working on features to see whether they are -maybe already implemented, not possible because of some reason or don’t fit +maybe already implemented, not possible for some some reason, or don’t fit into the concept), please use git to create a patchfile. First of all, update your working copy to the latest version of the master @@ -519,4 +521,4 @@ apply to the branch, preserving your commit message and name: git format-patch origin ----------------------- -Just send us the generated file via mail. +Just send us the generated file via email. diff --git a/docs/ipc b/docs/ipc index 117050a3..4e46bc9e 100644 --- a/docs/ipc +++ b/docs/ipc @@ -28,10 +28,10 @@ my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); To send a message to i3, you have to format in the binary message format which i3 expects. This format specifies a magic string in the beginning to ensure -the integrity of messages (to prevent follow-up errors). Afterwards follows -the length of the payload of the message as 32-bit integer and the type of -the message as 32-bit integer (the integers are not converted, so they are -in native byte order). +the integrity of messages (to prevent follow-up errors). Following the magic +string comes the length of the payload of the message as 32-bit integer, and +the type of the message as 32-bit integer (the integers are not converted, so +they are in native byte order). The magic string currently is "i3-ipc" and will only be changed when a change in the IPC API is done which breaks compatibility (we hope that we don’t need @@ -75,11 +75,11 @@ sub format_ipc_command { } $sock->write(format_ipc_command("exit")); ------------------------------------------------------------- +------------------------------------------------------------------------------ == Receiving replies from i3 -Replies of i3 usually consist of a simple string (the length of the string +Replies from i3 usually consist of a simple string (the length of the string is the message_length, so you can consider them length-prefixed) which in turn contain the JSON serialization of a data structure. For example, the GET_WORKSPACES message returns an array of workspaces (each workspace is a map @@ -240,8 +240,8 @@ that the requests to i3 are processed in order. This means, the following situation can happen: You send a GET_WORKSPACES request but you receive a "workspace" event before receiving the reply to GET_WORKSPACES. If your program does not want to cope which such kinds of race conditions (an -event based library may not have a problem here), I advise to create a separate -connection to receive events. +event based library may not have a problem here), I suggest you create a +separate connection to receive events. === Subscribing to events diff --git a/docs/multi-monitor b/docs/multi-monitor index 9affb0cc..ec0256c0 100644 --- a/docs/multi-monitor +++ b/docs/multi-monitor @@ -3,22 +3,23 @@ The multi-monitor situation Michael Stapelberg March 2010 -…or: oh no, I have an nvidia graphics card! +…or: oh no, I have an nVidia graphics card! == The quick fix -If you are using the nvidia binary graphics driver, you need to use the -+--force-xinerama+ flag when starting i3, like so (in your xsession): +If you are using the nVidia binary graphics driver (also known as 'blob') +you need to use the +--force-xinerama+ flag (in your .xsession) when starting +i3, like so: .Example: ---------------------------------------------- -exec i3 --force-xinerama -V >>~/.i3/i3log 2>&1 +exec i3 --force-xinerama -V >>~/.i3/i3log 2>&1 ---------------------------------------------- == The explanation Starting with version 3.ε, i3 uses the RandR (Rotate and Resize) API instead -of Xinerama. This is due to the reason that RandR provides more information +of Xinerama. The reason for this, is that RandR provides more information about your outputs and connected screens than Xinerama does. To be specific, the code which handled on-the-fly screen reconfiguration (meaning without restarting the X server) was a very messy heuristic and most of the time did @@ -27,29 +28,30 @@ Xinerama offers (just a list of screen resolutions, no identifiers for the screens or any additional information). Xinerama simply was not designed for dynamic configuration. -So, RandR came up as a more powerful alternative (RandR 1.2 to be specific). +So RandR came along, as a more powerful alternative (RandR 1.2 to be specific). It offers all of Xinerama’s possibilities and lots more. Using the RandR API made our code much more robust and clean. Also, you can now reliably assign workspaces to output names instead of some rather unreliable screen identifier (position inside the list of screens, which could change, and so on…). -As RandR is around for about three years, it seemed like a very good idea to -us and it still is a very good one. What we did not expect, however, was the -nVidia binary driver. It still does not support RandR (as of March 2010), even -though nVidia announced that it will support RandR eventually. What does this -mean for you, if you are stuck with the binary driver for some reason (say -the free drivers don’t work with your card)? First of all, you are stuck with -TwinView and cannot use +xrandr+. While this ruins the user experience, the -more grave problem is that the nVidia driver not only does not support dynamic -configuration using RandR, it also does not even expose correct multi-monitor -information via the RandR API. So, in some setups, i3 will not find any -screens, in others it will find one large screen which actually contains both -of your physical screens (but it will not know that these are two screens). +As RandR has been around for about three years as of this writing, it seemed +like a very good idea to us, and it still is a very good one. What we did not +expect, however, was the nVidia binary driver. It still does not support RandR +(as of March 2010), even though nVidia has announced that it will support RandR +eventually. What does this mean for you, if you are stuck with the binary +driver for some reason (say the free drivers don’t work with your card)? First +of all, you are stuck with TwinView and cannot use +xrandr+. While this ruins +the user experience, the more grave problem is that the nVidia driver not only +does not support dynamic configuration using RandR, it also does not expose +correct multi-monitor information via the RandR API. So, in some setups, i3 +will not find any screens; in others, it will find one large screen which +actually contains both of your physical screens (but it will not know that +these are two screens). For this very reason, we decided to implement the following workaround: As long as the nVidia driver does not support RandR, an option called +--force-xinerama+ is available in i3. This option gets the list of screens -*once* when starting and never updates it. As the nVidia driver cannot do +*once* when starting, and never updates it. As the nVidia driver cannot do dynamic configuration anyways, this is not a big deal. == See also diff --git a/docs/userguide b/docs/userguide index 5a313c92..f4a27743 100644 --- a/docs/userguide +++ b/docs/userguide @@ -21,8 +21,8 @@ image:keyboard-layer1.png["Keys to use with Mod1 (alt)",width=600,link="keyboard image:keyboard-layer2.png["Keys to use with Shift+Mod1",width=600,link="keyboard-layer2.png"] As i3 uses keycodes in the default configuration, it does not matter which -keyboard layout you actually use. The key positions are what matters (of course you can -also use keysymbols, see below). +keyboard layout you actually use. The key positions are what matters (of course +you can also use keysymbols, see below). The red keys are the modifiers you need to press (by default), the blue keys are your homerow. @@ -40,7 +40,8 @@ image:single_terminal.png[Single terminal] It is important to keep in mind that i3 uses a table to manage your windows. At the moment, you have exactly one column and one row which leaves you with one -cell. In this cell there is a container which is where your new terminal is opened. +cell. In this cell there is a container, which is where your new terminal is +opened. If you now open another terminal, you still have only one cell. However, the container in that cell holds both of your terminals. So, a container is just a @@ -52,12 +53,12 @@ image:two_terminals.png[Two terminals] To move the focus between the two terminals, you use the direction keys which you may know from the editor +vi+. However, in i3, your homerow is used for these keys (in +vi+, the keys are shifted to the left by one for compatibility -with most keyboard layouts). Therefore, +Mod1+J+ is left, +Mod1+K+ is down, +Mod1+L+ -is up and `Mod1+;` is right. So, to switch between the terminals, use +Mod1+K+ or -+Mod1+L+. +with most keyboard layouts). Therefore, +Mod1+J+ is left, +Mod1+K+ is down, ++Mod1+L+ is up and `Mod1+;` is right. So, to switch between the terminals, +use +Mod1+K+ or +Mod1+L+. To create a new row/column (and a new cell), you can simply move a terminal (or -any other window) to the direction you want to expand your table. So, let’s +any other window) in the direction you want to expand your table. So, let’s expand the table to the right by pressing `Mod1+Shift+;`. image:two_columns.png[Two columns] @@ -118,8 +119,8 @@ another workspace, press +Mod1+num+ where +num+ is the number of the workspace you want to use. If the workspace does not exist yet, it will be created. A common paradigm is to put the web browser on one workspace, communication -applications (+mutt+, +irssi+, ...) on another one and the ones with which you -work on the third one. Of course, there is no need to follow this approach. +applications (+mutt+, +irssi+, ...) on another one, and the ones with which you +work, on the third one. Of course, there is no need to follow this approach. If you have multiple screens, a workspace will be created on each screen at startup. If you open a new workspace, it will be bound to the screen you @@ -135,10 +136,10 @@ it does not yet exist. === Resizing columns/rows -To resize columns or rows just grab the border between the two columns/rows +To resize columns or rows, just grab the border between the two columns/rows and move it to the wanted size. Please keep in mind that each cell of the table holds a +container+ and thus you cannot horizontally resize single windows. If -you need applications with different horizontal sizes place them in seperate +you need applications with different horizontal sizes, place them in seperate cells one above the other. See <> for how to configure i3 to be able to resize @@ -146,7 +147,7 @@ columns/rows with your keyboard. === Restarting i3 inplace -To restart i3 inplace (and thus get into a clean state if there is a bug or +To restart i3 inplace (and thus get into a clean state if there is a bug, or to upgrade to a newer version of i3) you can use +Mod1+Shift+r+. Be aware, though, that this kills your current layout and all the windows you have opened will be put in a default container in only one cell. Saving layouts will be @@ -173,7 +174,7 @@ by pressing +Mod1+Control+k+ (or snap container 2 rightwards). Floating mode is the opposite of tiling mode. The position and size of a window are not managed by i3, but by you. Using this mode violates the tiling paradigm but can be useful for some corner cases like "Save as" dialog -windows or toolbar windows (GIMP or similar). +windows, or toolbar windows (GIMP or similar). You can enable floating mode for a window by pressing +Mod1+Shift+Space+. By dragging the window’s titlebar with your mouse you can move the window @@ -235,18 +236,19 @@ A keyboard binding makes i3 execute a command (see below) upon pressing a specific key. i3 allows you to bind either on keycodes or on keysyms (you can also mix your bindings, though i3 will not protect you from overlapping ones). -* A keysym (key symbol) is a description for a specific symbol, like "a" or "b", - but also more strange ones like "underscore" instead of "_". These are the ones - you use in Xmodmap to remap your keys. To get the current mapping of your - keys, use +xmodmap -pke+. +* A keysym (key symbol) is a description for a specific symbol, like "a" + or "b", but also more strange ones like "underscore" instead of "_". These + are the ones you use in Xmodmap to remap your keys. To get the current + mapping of your keys, use +xmodmap -pke+. * Keycodes do not need to have a symbol assigned (handy for some hotkeys on some notebooks) and they will not change their meaning as you switch to a different keyboard layout (when using +xmodmap+). My recommendation is: If you often switch keyboard layouts but you want to keep -your bindings in the same physical location on the keyboard use keycodes. If you -don’t switch layouts and want a clean and simple config file, use keysyms. +your bindings in the same physical location on the keyboard, use keycodes. +If you don’t switch layouts, and want a clean and simple config file, use +keysyms. *Syntax*: ---------------------------------- @@ -287,9 +289,9 @@ use the same key you use for managing windows (Mod1 for example). Then you can press Mod1, click into a window using your left mouse button, and drag it to the position you want. -When holding the floating modifier, you can resize a floating window by pressing -the right mouse button on it and moving around while holding it. If you hold the -shift button as well, the resize will be proportional. +When holding the floating modifier, you can resize a floating window by +pressing the right mouse button on it and moving around while holding it. If +you hold the shift button as well, the resize will be proportional. *Syntax*: -------------------------------- @@ -359,13 +361,13 @@ configuration file and run it before starting i3 (for example in your [[assign_workspace]] -It is recommended that you match on window classes whereever possible because -some applications first create their window and then worry about setting the +It is recommended that you match on window classes wherever possible because +some applications first create their window, and then worry about setting the correct title. Firefox with Vimperator comes to mind. The window starts up -being named Firefox and only when Vimperator is loaded the title changes. As -i3 will get the title as soon as the application maps the window (mapping means -actually displaying it on the screen), you’d need to have to match on Firefox -in this case. +being named Firefox, and only when Vimperator is loaded does the title change. +As i3 will get the title as soon as the application maps the window (mapping +means actually displaying it on the screen), you’d need to have to match on +'Firefox' in this case. You can prefix or suffix workspaces with a `~` to specify that matching clients should be put into floating mode. If you specify only a `~`, the client will @@ -389,11 +391,11 @@ assign "xv/MPlayer" → ~ Note that the arrow is not required, it just looks good :-). If you decide to use it, it has to be a UTF-8 encoded arrow, not "->" or something like that. -=== Automatically starting applications on startup +=== Automatically starting applications on i3 startup By using the +exec+ keyword outside a keybinding, you can configure which -commands will be performed by i3 on initial startup (not when restarting inplace -however). These commands will be run in order. +commands will be performed by i3 on initial startup (not when restarting i3 +in-place however). These commands will be run in order. *Syntax*: ------------ @@ -420,7 +422,7 @@ the second screen and so on). workspace output ---------------------------------- -The output is the name of the RandR output you attach your screen to. On a +The 'output' is the name of the RandR output you attach your screen to. On a laptop, you might have VGA1 and LVDS1 as output names. You can see the available outputs by running +xrandr --current+. @@ -441,7 +443,7 @@ workspace workspace output name --------------------------------------- -For more details about the output-part of this command, see above. +For more details about the 'output' part of this command, see above. *Examples*: -------------------------- @@ -486,15 +488,15 @@ Colors are in HTML hex format (#rrggbb), see the following example: client.focused #2F343A #900000 #FFFFFF -------------------------------------- -Note that for the window decorations the color around the child window is the -background color and the border color is only the two thin lines at the top of +Note that for the window decorations, the color around the child window is the +background color, and the border color is only the two thin lines at the top of the window. === Interprocess communication i3 uses unix sockets to provide an IPC interface. This allows third-party -programs to get information like the current workspaces to display a workspace -bar, and to control i3. +programs to get information from i3, such as the current workspaces +(to display a workspace bar), and to control i3. To enable it, you have to configure a path where the unix socket will be stored. The default path is +/tmp/i3-ipc.sock+. @@ -504,14 +506,14 @@ stored. The default path is +/tmp/i3-ipc.sock+. ipc-socket /tmp/i3-ipc.sock ---------------------------- -You can then use the +i3-msg+ application to perform any command listed in the next -section. +You can then use the +i3-msg+ application to perform any command listed in +the next section. === Disable focus follows mouse If you have a setup where your mouse usually is in your way (like a touchpad on your laptop which you do not want to disable completely), you might want -to disable focus follows mouse and control focus only by using your keyboard. +to disable 'focus follows mouse' and control focus only by using your keyboard. The mouse will still be useful inside the currently active window (for example to click on links in your browser window). @@ -550,13 +552,13 @@ bindsym Mod1+Shift+f fg bindsym Mod1+t t -------------- -=== Focussing/Moving/Snapping clients/containers/screens +=== Focusing/Moving/Snapping clients/containers/screens To change the focus, use one of the +h+, +j+, +k+ and +l+ commands, meaning -left, down, up, right (respectively). To focus a container, prefix it with +wc+, -to focus a screen, prefix it with +ws+. +left, down, up, right (respectively). To focus a container, prefix it with ++wc+. To focus a screen, prefix it with +ws+. -The same principle applies for moving and snapping, just prefix the command +The same principle applies for moving and snapping: just prefix the command with +m+ when moving and with +s+ when snapping: *Examples*: @@ -590,9 +592,9 @@ To change to a specific workspace, the command is just the number of the workspace, e.g. +1+ or +3+. To move the current client to a specific workspace, prefix the number with an +m+. -You can also switch to the next and previous workspace with the -commands +nw+ and +pw+, which is handy, for example, if you have workspace -1, 3, 4 and 9 and you want to cycle through them with a single key combination. +You can also switch to the next and previous workspace with the commands +nw+ +and +pw+, which is handy, for example, if you have workspace 1, 3, 4 and 9 and +you want to cycle through them with a single key combination. *Examples*: ------------------------- @@ -645,11 +647,11 @@ bindsym Mod1+r mode resize === Jumping to specific windows -Often when in a multi-monitor environment, you want to quickly jump to a specific -window. For example while working on workspace 3 you may want to jump to -your mailclient to mail your boss that you’ve achieved some important goal. Instead -of figuring out how to navigate to your mailclient, it would be more convenient to -have a shortcut. +Often when in a multi-monitor environment, you want to quickly jump to a +specific window. For example, while working on workspace 3 you may want to +jump to your mail client to email your boss that you’ve achieved some +important goal. Instead of figuring out how to navigate to your mailclient, +it would be more convenient to have a shortcut. *Syntax*: ---------------------------------------------------- @@ -657,8 +659,9 @@ jump ["]window class[/window title]["] jump workspace [ column row ] ---------------------------------------------------- -You can either use the same matching algorithm as in the +assign+ command (see above) -or you can specify the position of the client if you always use the same layout. +You can either use the same matching algorithm as in the +assign+ command +(see above) or you can specify the position of the client if you always use +the same layout. *Examples*: -------------------------------------- @@ -673,9 +676,9 @@ bindsym Mod1+a jump "urxvt/VIM" This feature is like the jump feature: It allows you to directly jump to a specific window (this means switching to the appropriate workspace and setting focus to the windows). However, you can directly mark a specific window with -an arbitrary label and use it afterwards. You do not need to ensure -that your windows have unique classes or titles, and you do not need to change -your configuration file. +an arbitrary label and use it afterwards. You do not need to ensure that your +windows have unique classes or titles, and you do not need to change your +configuration file. As the command needs to include the label with which you want to mark the window, you cannot simply bind it to a key. +i3-input+ is a tool created @@ -702,16 +705,16 @@ seperate bindings for a specific set of labels and then only use those labels. === Traveling the focus stack -This mechanism can be thought of as the opposite of the +jump+ command. It travels -the focus stack and jumps to the window which had focus previously. +This mechanism can be thought of as the opposite of the +jump+ command. +It travels the focus stack and jumps to the window which had focus previously. *Syntax*: -------------- focus [number] | floating | tiling | ft -------------- -Where +number+ by default is 1 meaning that the next client in the focus stack will -be selected. +Where +number+ by default is 1 meaning that the next client in the focus stack +will be selected. The special values have the following meaning: @@ -720,8 +723,8 @@ floating:: tiling:: The next tiling window is selected. ft:: - If the current window is floating, the next tiling window will be selected - and vice-versa. + If the current window is floating, the next tiling window will be + selected; and vice-versa. === Changing border style @@ -772,9 +775,9 @@ You can make i3 reload its configuration file with +reload+. You can also restart i3 inplace with the +restart+ command to get it out of some weird state (if that should ever happen) or to perform an upgrade without having to restart your X session. However, your layout is not preserved at the moment, meaning -that all open windows will be in a single container in default layout. To exit -i3 properly, you can use the +exit+ command, however you don’t need to (e.g., -simply killing your X session is fine as well). +that all open windows will end up in a single container in default layout +after the restart. To exit i3 properly, you can use the +exit+ command, +however you don’t need to (simply killing your X session is fine as well). *Examples*: ---------------------------- @@ -791,33 +794,33 @@ As you can see in the goal list on the website, i3 was specifically developed with support for multiple monitors in mind. This section will explain how to handle multiple monitors. -When you have only one monitor things are simple. You usually start with +When you have only one monitor, things are simple. You usually start with workspace 1 on your monitor and open new ones as you need them. When you have more than one monitor, each monitor will get an initial -workspace. The first monitor gets 1, the second gets 2 and a possible third would -get 3. When you switch to a workspace on a different monitor, i3 will switch -to that monitor and then switch to the workspace. This way, you don’t need -shortcuts to switch to a specific monitor, and you don’t need to remember where -you put which workspace. New workspaces will be opened on the currently active -monitor. It is not possible to have a monitor without a workspace. +workspace. The first monitor gets 1, the second gets 2 and a possible third +would get 3. When you switch to a workspace on a different monitor, i3 will +switch to that monitor and then switch to the workspace. This way, you don’t +need shortcuts to switch to a specific monitor, and you don’t need to remember +where you put which workspace. New workspaces will be opened on the currently +active monitor. It is not possible to have a monitor without a workspace. -The idea of making workspaces global is based on the observation that most users -have a very limited set of workspaces on their additional monitors. They are -often used for a specific task (browser, shell) or for monitoring several -things (mail, IRC, syslog, …). Thus, using one workspace on one monitor and -"the rest" on the other monitors often makes sense. However, as you can -create an unlimited number of workspaces in i3 and tie them to specific screens, -you can have the "traditional" approach of having X workspaces per screen by -changing your configuration (using modes, for example). +The idea of making workspaces global is based on the observation that most +users have a very limited set of workspaces on their additional monitors. +They are often used for a specific task (browser, shell) or for monitoring +several things (mail, IRC, syslog, …). Thus, using one workspace on one monitor +and "the rest" on the other monitors often makes sense. However, as you can +create an unlimited number of workspaces in i3 and tie them to specific +screens, you can have the "traditional" approach of having X workspaces per +screen by changing your configuration (using modes, for example). === Configuring your monitors -To help you get going if you have never used multiple monitors before, here is a -short overview of the xrandr options which will probably be of interest to you. -It is always useful to get an overview of the current screen configuration. +To help you get going if you have never used multiple monitors before, here is +a short overview of the xrandr options which will probably be of interest to +you. It is always useful to get an overview of the current screen configuration. Just run "xrandr" and you will get an output like the following: --------------------------------------------------------------------------------------- +------------------------------------------------------------------------------- $ xrandr Screen 0: minimum 320 x 200, current 1280 x 800, maximum 8192 x 8192 VGA1 disconnected (normal left inverted right x axis y axis) @@ -837,9 +840,9 @@ course, it is the internal flat panel) but +VGA1+ is not. If you have a monitor connected to one of the ports but xrandr still says "disconnected", you should check your cable, monitor or graphics driver. -The maximum resolution you can see at the end of the first line -is the maximum combined resolution of your monitors. By default, it is usually -too low and has to be increased by editing +/etc/X11/xorg.conf+. +The maximum resolution you can see at the end of the first line is the maximum +combined resolution of your monitors. By default, it is usually too low and has +to be increased by editing +/etc/X11/xorg.conf+. So, say you connected VGA1 and want to use it as an additional screen: ------------------------------------------- @@ -848,7 +851,7 @@ xrandr --output VGA1 --auto --left-of LVDS1 This command makes xrandr try to find the native resolution of the device connected to +VGA1+ and configures it to the left of your internal flat panel. When running "xrandr" again, the output looks like this: ------------------------------------------------------------------------------------------ +------------------------------------------------------------------------------- $ xrandr Screen 0: minimum 320 x 200, current 2560 x 1024, maximum 8192 x 8192 VGA1 connected 1280x1024+0+0 (normal left inverted right x axis y axis) 338mm x 270mm @@ -869,7 +872,7 @@ LVDS1 connected 1280x800+1280+0 (normal left inverted right x axis y axis) 261mm 720x400 85.0 640x400 85.1 640x350 85.1 ------------------------------------------------------------------------------------------ +------------------------------------------------------------------------------- Please note that i3 uses exactly the same API as xrandr does, so it will see only what you can see in xrandr. diff --git a/i3.config b/i3.config index 7b838512..56cb2704 100644 --- a/i3.config +++ b/i3.config @@ -107,7 +107,7 @@ bind Mod1+36 exec /usr/bin/urxvt bind Mod1+Shift+24 kill # Mod1+v starts dmenu and launches the selected application -# for now, we don’t have an own launcher +# for now, we don’t have a launcher of our own. bind Mod1+55 exec /usr/bin/dmenu_run # Mod1+Shift+e exits i3 diff --git a/i3.welcome b/i3.welcome index 9c9861e9..72717ee7 100644 --- a/i3.welcome +++ b/i3.welcome @@ -1,6 +1,6 @@ 1.) Welcome to i3! -This message provides you with an overview of the default keybindings to use i3. +This message provides an overview of the default keybindings to use i3. Please also make sure to have a look at the man page and the user's guide: http://i3.zekjur.net/docs/userguide.html @@ -8,7 +8,8 @@ http://i3.zekjur.net/docs/userguide.html 2.) Configuration Files /etc/i3/config is the default configuration. It is recommended to copy it and -afterwards edit it to suit your needs (you can especially disable this message): +afterwards edit it to suit your needs (in particular, you may want to disable +this message): cp /etc/i3/config ~/.i3/config @@ -17,7 +18,7 @@ afterwards edit it to suit your needs (you can especially disable this message): The following explanation is related to the QWERTY layout, but as the default configuration uses keycodes instead of keysymbols for binding, you still have -to press the same keys, regardless of your keyboard layout. +to press the same physical keys, regardless of your keyboard layout. The Mod1 key is usually bound to the "Alt" key on your keyboard. @@ -28,15 +29,15 @@ The directional keys are j(left), k(down), l(up) and ;(right). You can also use the arrow keys on your keyboard, if you prefer them. Mod1+ moves the focus to the window in the given direction -Mod1+Shift+ moves the window to the given direction, +Mod1+Shift+ moves the window to the given direction Mod1+ opens the corresponding workspace -Mod1+Shift+ moves a window to the wished workspace +Mod1+Shift+ moves a window to the selected workspace Mod1+h sets the mode of a container to stacking Mod1+e sets the mode back to default Mod1+f toggles fullscreen mode for the current window Mod1+Shift+Space toggles floating mode for the current window Mod1+Shift+q closes a window -Mod1+Shift+r restarts i3 in-place (you will lose your layout, though) +Mod1+Shift+r restarts i3 in-place (at this time, you will lose your layout) Mod1+Shift+e exits i3 If you have any questions, please don't hesitate to ask! diff --git a/man/i3-input.man b/man/i3-input.man index b53406ff..29668027 100644 --- a/man/i3-input.man +++ b/man/i3-input.man @@ -1,11 +1,12 @@ i3-input(1) ========= + Michael Stapelberg v3.delta, November 2009 == NAME -i3-input - interactively take a command for i3 +i3-input - interactively take a command for i3 window manager == SYNOPSIS @@ -13,8 +14,9 @@ i3-input [-s ] [-p ] [-l ] [-P ] [-v] == DESCRIPTION -i3-input is a tool to take commands (or parts of a command) and then send it -to i3. This is useful for example for the mark/goto command. +i3-input is a tool to take commands (or parts of a command) composed by +the user, and send it/them to i3. This is useful, for example, for the +mark/goto command. == EXAMPLE diff --git a/man/i3-msg.man b/man/i3-msg.man index a2b17683..dedf7dc4 100644 --- a/man/i3-msg.man +++ b/man/i3-msg.man @@ -1,11 +1,12 @@ i3-msg(1) ========= + Michael Stapelberg v3.delta, November 2009 == NAME -i3-msg - send messages to i3 +i3-msg - send messages to i3 window manager == SYNOPSIS diff --git a/man/i3.man b/man/i3.man index dd3f6333..5877f143 100644 --- a/man/i3.man +++ b/man/i3.man @@ -132,7 +132,8 @@ support that, the window will be killed and it depends on the application what happens. Mod1+Shift+r:: -Restarts i3 in place (without losing any windows, but the layout). +Restarts i3 in place (without losing any windows, but at this time, the layout +and placement of windows is not retained). Mod1+Shift+e:: Exits i3. @@ -283,14 +284,15 @@ exec /usr/bin/i3 >> ~/.i3/logfile == TODO -There is still lot of work to do. Please check our bugtracker for up-to-date information -about tasks which are still not finished. +There is still lot of work to do. Please check our bugtracker for up-to-date +information about tasks which are still not finished. == SEE ALSO -You should have a copy of the userguide (featuring nice screenshots/graphics which is why this -is not integrated into this manpage), the debugging guide and the "how to hack" guide. If you -are building from source, run +make -C docs+. +You should have a copy of the userguide (featuring nice screenshots/graphics +which is why this is not integrated into this manpage), the debugging guide, +and the "how to hack" guide. If you are building from source, run: + +make -C docs+ You can also access these documents online at http://i3.zekjur.net/ From 960a2014fc85933f2a85afdc49f2b7a47811c8f6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 21 Mar 2010 01:55:02 +0100 Subject: [PATCH 203/247] remove superflous newlines (breaks asciidoc) --- man/i3-input.man | 1 - man/i3-msg.man | 1 - 2 files changed, 2 deletions(-) diff --git a/man/i3-input.man b/man/i3-input.man index 29668027..5b7ce6d9 100644 --- a/man/i3-input.man +++ b/man/i3-input.man @@ -1,6 +1,5 @@ i3-input(1) ========= - Michael Stapelberg v3.delta, November 2009 diff --git a/man/i3-msg.man b/man/i3-msg.man index dedf7dc4..c723bd1e 100644 --- a/man/i3-msg.man +++ b/man/i3-msg.man @@ -1,6 +1,5 @@ i3-msg(1) ========= - Michael Stapelberg v3.delta, November 2009 From 42b638eac00f72cf96c91b1b8aa94f82187960c0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 22 Mar 2010 15:10:26 +0100 Subject: [PATCH 204/247] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20enter=20BIND?= =?UTF-8?q?=5FA2WS=5FCOND=20state=20too=20early=20(Thanks=20fallen)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cfgparse.l | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfgparse.l b/src/cfgparse.l index 755065dd..10a13076 100644 --- a/src/cfgparse.l +++ b/src/cfgparse.l @@ -67,7 +67,7 @@ EOL (\r?\n) [^\n]+ { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR; } -[a-zA-Z0-9_-]+ { BEGIN(BIND_A2WS_COND); yylval.string = strdup(yytext); return OUTPUT; } +[a-zA-Z0-9_-]+ { yylval.string = strdup(yytext); return OUTPUT; } ^[ \t]*#[^\n]* { return TOKCOMMENT; } [0-9a-fA-F]+ { yylval.string = strdup(yytext); return HEX; } [0-9]+ { yylval.number = atoi(yytext); return NUMBER; } From 56139f3656a3c4da195fc7189441443e5dd094cf Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 23 Mar 2010 14:43:35 +0100 Subject: [PATCH 205/247] Bugfix: only restore focus if the workspace is focused, not if it is visible --- src/handlers.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index c2911942..53a5cf38 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -531,17 +531,12 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti /* Let’s see how many clients there are left on the workspace to delete it if it’s empty */ bool workspace_empty = SLIST_EMPTY(&(client->workspace->focus_stack)); - bool workspace_active = false; + bool workspace_focused = (c_ws == client->workspace); Client *to_focus = (!workspace_empty ? SLIST_FIRST(&(client->workspace->focus_stack)) : NULL); - /* If this workspace is currently active, we don’t delete it */ - Output *screen; - TAILQ_FOREACH(screen, &outputs, outputs) - if (screen->current_workspace == client->workspace) { - workspace_active = true; - workspace_empty = false; - break; - } + /* If this workspace is currently visible, we don’t delete it */ + if (workspace_is_visible(client->workspace)) + workspace_empty = false; if (workspace_empty) { client->workspace->output = NULL; @@ -563,7 +558,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti * the screen itself (if we do not focus the screen, it can happen that * the focus is "nowhere" and thus keypress events will not be received * by i3, thus the user cannot use any hotkeys). */ - if (workspace_active) { + if (workspace_focused) { if (to_focus != NULL) set_focus(conn, to_focus, true); else { From 85730d6892aa6dff925a032788878e723e3540d4 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Mar 2010 03:28:38 +0100 Subject: [PATCH 206/247] Fix numlock state Apparantly, after activating numlock once, the numlock modifier stays turned on (use xev(1) to verify). So, to resolve useful keysyms, we remove the numlock flag from the event state. What currently does not work is actually using your keypad. --- i3-input/i3-input.h | 2 +- i3-input/main.c | 12 +++++++++++- i3-input/xcb.c | 6 +++--- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/i3-input/i3-input.h b/i3-input/i3-input.h index 6c982bc5..8d8b467f 100644 --- a/i3-input/i3-input.h +++ b/i3-input/i3-input.h @@ -8,7 +8,7 @@ char *convert_ucs_to_utf8(char *input); char *convert_utf8_to_ucs2(char *input, int *real_strlen); uint32_t get_colorpixel(xcb_connection_t *conn, char *hex); -uint32_t get_mode_switch_mask(xcb_connection_t *conn); +uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode); int connect_ipc(char *socket_path); void ipc_send_message(int sockfd, uint32_t message_size, uint32_t message_type, uint8_t *payload); diff --git a/i3-input/main.c b/i3-input/main.c index 97976396..f5229c08 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -37,6 +37,7 @@ static int sockfd; static xcb_key_symbols_t *symbols; static int modeswitchmask; +static int numlockmask; static bool modeswitch_active = false; static xcb_window_t win; static xcb_pixmap_t pixmap; @@ -119,6 +120,9 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t static int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_release_event_t *event) { printf("releasing %d, state raw = %d\n", event->detail, event->state); + /* fix state */ + event->state &= ~numlockmask; + xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state); if (sym == XK_Mode_switch) { printf("Mode switch disabled\n"); @@ -163,6 +167,11 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press if (modeswitch_active) event->state |= modeswitchmask; + /* Apparantly, after activating numlock once, the numlock modifier + * stays turned on (use xev(1) to verify). So, to resolve useful + * keysyms, we remove the numlock flag from the event state */ + event->state &= ~numlockmask; + xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state); if (sym == XK_Mode_switch) { printf("Mode switch enabled\n"); @@ -290,7 +299,8 @@ int main(int argc, char *argv[]) { xcb_event_set_key_release_handler(&evenths, handle_key_release, NULL); xcb_event_set_expose_handler(&evenths, handle_expose, NULL); - modeswitchmask = get_mode_switch_mask(conn); + modeswitchmask = get_mod_mask(conn, XK_Mode_switch); + numlockmask = get_mod_mask(conn, XK_Num_Lock); symbols = xcb_key_symbols_alloc(conn); uint32_t font_id = get_font_id(conn, pattern, &font_height); diff --git a/i3-input/xcb.c b/i3-input/xcb.c index 71189a97..661d4863 100644 --- a/i3-input/xcb.c +++ b/i3-input/xcb.c @@ -53,12 +53,12 @@ uint32_t get_colorpixel(xcb_connection_t *conn, char *hex) { * keycode). * */ -uint32_t get_mode_switch_mask(xcb_connection_t *conn) { +uint32_t get_mod_mask(xcb_connection_t *conn, uint32_t keycode) { xcb_key_symbols_t *symbols = xcb_key_symbols_alloc(conn); xcb_get_modifier_mapping_reply_t *modmap_r; xcb_keycode_t *modmap, kc; - xcb_keycode_t *modeswitchcodes = xcb_key_symbols_get_keycode(symbols, XK_Mode_switch); + xcb_keycode_t *modeswitchcodes = xcb_key_symbols_get_keycode(symbols, keycode); if (modeswitchcodes == NULL) return 0; @@ -66,7 +66,7 @@ uint32_t get_mode_switch_mask(xcb_connection_t *conn) { modmap = xcb_get_modifier_mapping_keycodes(modmap_r); for (int i = 0; i < 8; i++) - for(int j = 0; j < modmap_r->keycodes_per_modifier; j++) { + for (int j = 0; j < modmap_r->keycodes_per_modifier; j++) { kc = modmap[i * modmap_r->keycodes_per_modifier + j]; for (xcb_keycode_t *ktest = modeswitchcodes; *ktest; ktest++) { if (*ktest != kc) From 0f7ac09c7b0e5c03ac6c90cc589abc7b8bbb2577 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Mar 2010 16:06:21 +0100 Subject: [PATCH 207/247] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20unmap=20windo?= =?UTF-8?q?ws=20when=20current=20workspace=20gets=20reassigned?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Steps to reproduce were: 1) xrandr --output VGA1 --auto --left-of LVDS1 2) open a terminal on VGA1 3) xrandr --output VGA1 --off --- src/workspace.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/workspace.c b/src/workspace.c index 67874f29..3fe4751b 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -248,12 +248,17 @@ void workspace_assign_to(Workspace *ws, Output *output, bool hide_it) { if (visible && !hide_it) return; - workspace_unmap_clients(global_conn, ws); - + /* however, if this is the current workspace, we only need to adjust + * the output’s current_workspace pointer (and must not unmap the + * windows) */ if (c_ws == ws) { DLOG("Need to adjust output->current_workspace...\n"); output->current_workspace = c_ws; + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}"); + return; } + + workspace_unmap_clients(global_conn, ws); } /* From 7eea1067f89822d87d42e61204f5f43f4f187b2e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Mar 2010 16:09:43 +0100 Subject: [PATCH 208/247] Bugfix: correctly translate coordinates for floating windows when outputs change --- src/layout.c | 16 ++++++++++++++++ src/randr.c | 18 +++++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/layout.c b/src/layout.c index 202f5601..b1338040 100644 --- a/src/layout.c +++ b/src/layout.c @@ -225,6 +225,12 @@ void reposition_client(xcb_connection_t *conn, Client *client) { return; } + if (output->current_workspace == NULL) { + DLOG("Boundary checking deferred, no current workspace on output\n"); + client->force_reconfigure = true; + return; + } + DLOG("Client is on workspace %p with output %p\n", client->workspace, client->workspace->output); DLOG("but output at %d, %d is %p\n", client->rect.x, client->rect.y, output); floating_assign_to_workspace(client, output->current_workspace); @@ -737,6 +743,16 @@ void render_workspace(xcb_connection_t *conn, Output *output, Workspace *r_ws) { yoffset[cols] += single_height; } + /* Reposition all floating clients with force_reconfigure == true */ + TAILQ_FOREACH(client, &(r_ws->floating_clients), floating_clients) { + if (!client->force_reconfigure) + continue; + + client->force_reconfigure = false; + reposition_client(conn, client); + resize_client(conn, client); + } + ignore_enter_notify_forall(conn, r_ws, false); render_bars(conn, r_ws, width, &height); diff --git a/src/randr.c b/src/randr.c index dac3c010..f9e2a416 100644 --- a/src/randr.c +++ b/src/randr.c @@ -35,6 +35,7 @@ #include "log.h" #include "ewmh.h" #include "ipc.h" +#include "client.h" /* While a clean namespace is usually a pretty good thing, we really need * to use shorter names than the whole xcb_randr_* default names. */ @@ -247,12 +248,23 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) { if (ws->output != output) continue; + SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) { + client->force_reconfigure = true; + if (!client_is_floating(client)) + continue; + /* For floating clients we need to translate the + * coordinates (old workspace to new workspace) */ + DLOG("old: (%x, %x)\n", client->rect.x, client->rect.y); + client->rect.x -= ws->rect.x; + client->rect.y -= ws->rect.y; + client->rect.x += ws->output->rect.x; + client->rect.y += ws->output->rect.y; + DLOG("new: (%x, %x)\n", client->rect.x, client->rect.y); + } + /* Update dimensions from output */ memcpy(&(ws->rect), &(ws->output->rect), sizeof(Rect)); - SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) - client->force_reconfigure = true; - /* Update the dimensions of a fullscreen client, if any */ if (ws->fullscreen_client != NULL) { DLOG("Updating fullscreen client size\n"); From e90e80c87db036f07348126345b09fadeaa705b6 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Mar 2010 16:10:47 +0100 Subject: [PATCH 209/247] Bugfix: fix state of keypresses in sighandler (like in i3-input) --- src/sighandler.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/sighandler.c b/src/sighandler.c index 590608b6..92cbc5cb 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -83,7 +83,14 @@ static int sig_draw_window(xcb_connection_t *conn, xcb_window_t win, int width, * */ static int sig_handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) { - xcb_keysym_t sym = xcb_key_press_lookup_keysym(keysyms, event, event->state); + uint16_t state = event->state; + + /* Apparantly, after activating numlock once, the numlock modifier + * stays turned on (use xev(1) to verify). So, to resolve useful + * keysyms, we remove the numlock flag from the event state */ + state &= ~xcb_numlock_mask; + + xcb_keysym_t sym = xcb_key_press_lookup_keysym(keysyms, event, state); if (sym == 'e') { DLOG("User issued exit-command, raising error again.\n"); From 46e7cf5fe13f17870db66819544baf737dc2d648 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Mar 2010 16:52:16 +0100 Subject: [PATCH 210/247] Handle destroy notify events like unmap notify events This helps for windows which are immediately destroyed instead of unmapped, like when starting i3status | ./foobar | dzen2 -dock and foobar does not exist (i3status and dzen2 will get a SIGPIPE). --- include/handlers.h | 12 ++++++++++++ src/handlers.c | 20 ++++++++++++++++++++ src/mainx.c | 3 +++ 3 files changed, 35 insertions(+) diff --git a/include/handlers.h b/include/handlers.h index 03be5281..c7cbb322 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -94,6 +94,18 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, */ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_notify_event_t *event); +/** + * A destroy notify event is sent when the window is not unmapped, but + * immediately destroyed (for example when starting a window and immediately + * killing the program which started it). + * + * We just pass on the event to the unmap notify handler (by copying the + * important fields in the event data structure). + * + */ +int handle_destroy_notify_event(void *data, xcb_connection_t *conn, + xcb_destroy_notify_event_t *event); + /** * Called when a window changes its title * diff --git a/src/handlers.c b/src/handlers.c index 53a5cf38..624c3430 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -571,6 +571,26 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti return 1; } +/* + * A destroy notify event is sent when the window is not unmapped, but + * immediately destroyed (for example when starting a window and immediately + * killing the program which started it). + * + * We just pass on the event to the unmap notify handler (by copying the + * important fields in the event data structure). + * + */ +int handle_destroy_notify_event(void *data, xcb_connection_t *conn, xcb_destroy_notify_event_t *event) { + DLOG("destroy notify for 0x%08x, 0x%08x\n", event->event, event->window); + + xcb_unmap_notify_event_t unmap; + unmap.sequence = event->sequence; + unmap.event = event->event; + unmap.window = event->window; + + return handle_unmap_notify_event(NULL, conn, &unmap); +} + /* * Called when a window changes its title * diff --git a/src/mainx.c b/src/mainx.c index c80b8168..e779361b 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -414,6 +414,9 @@ int main(int argc, char *argv[], char *env[]) { it any longer. Usually, the client destroys the window shortly afterwards. */ xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, NULL); + /* Destroy notify is handled the same as unmap notify */ + xcb_event_set_destroy_notify_handler(&evenths, handle_destroy_notify_event, NULL); + /* Configure notify = window’s configuration (geometry, stacking, …). We only need it to set up ignore the following enter_notify events */ xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, NULL); From f2e04b30cc4f4bb9c43625cb80f8a53141a35b94 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 24 Mar 2010 19:13:19 +0100 Subject: [PATCH 211/247] Add initial version of i3-wsbar --- i3-wsbar | 244 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100755 i3-wsbar diff --git a/i3-wsbar b/i3-wsbar new file mode 100755 index 00000000..b1a50c40 --- /dev/null +++ b/i3-wsbar @@ -0,0 +1,244 @@ +#!/usr/bin/env perl +# vim:ts=4:sw=4:expandtab:ft=perl +# © 2010 Michael Stapelberg, see LICENSE for license information + +use strict; +use warnings; +use Getopt::Long; +use Pod::Usage; +use IPC::Run qw(start pump); +use AnyEvent::I3; +use AnyEvent; +use v5.10; + +my $stdin; +my $i3 = i3; +my ($workspaces, $outputs) = ([], {}); +my $last_line = ""; + +my $command = ""; +my $input_on = ""; +my $output_on = ""; +my $show_all = 0; + +my $result = GetOptions( + 'command=s' => \$command, + 'input-on=s' => \$input_on, + 'output-on=s' => \$output_on, + 'show-all' => \$show_all, + 'help' => sub { pod2usage(1); exit 0 }, +); + +if ($command eq '') { + say "i3-wsbar is only useful in combination with dzen2."; + say "Please specify -c (command)"; + exit 1; +} + +my @input_on = split(/,/, $input_on); +my @output_on = split(/,/, $output_on); + +# Disable buffering +$| = 1; + +# Wait a short amount of time and try to connect to i3 again +sub reconnect { + my $timer; + my $c = sub { + $timer = AnyEvent->timer( + after => 0.01, + cb => sub { $i3->connect->cb(\&connected) } + ); + }; + $c->(); +} + +# Connection attempt succeeded or failed +sub connected { + my ($cv) = @_; + + if (!$cv->recv) { + reconnect(); + return; + } + + $i3->subscribe({ + workspace => \&ws_change, + output => \&output_change, + _error => sub { reconnect() } + }); + ws_change(); + output_change(); +} + +# Called when a ws changes +sub ws_change { + # Request the current workspaces and update the output afterwards + $i3->get_workspaces->cb( + sub { + my ($cv) = @_; + $workspaces = $cv->recv; + update_output(); + }); +} + +# Called when the reply to the GET_OUTPUTS message arrives +# Compares old outputs with new outputs and starts/kills +# $command for each output (if specified) +sub got_outputs { + my $reply = shift->recv; + my %old = %{$outputs}; + my %new = map { ($_->{name}, $_) } grep { $_->{active} } @{$reply}; + + # If no command was given, we do not need to compare outputs + if ($command eq '') { + update_output(); + return; + } + + # Handle new outputs + for my $name (keys %new) { + next if @output_on and !($name ~~ @output_on); + + if (defined($old{$name})) { + # Check if the mode changed (by reversing the hashes so + # that we can check for equality using the smartmatch op) + my %oldrect = reverse %{$old{$name}->{rect}}; + my %newrect = reverse %{$new{$name}->{rect}}; + next if (%oldrect ~~ %newrect); + + # On mode changes, we re-start the command + $outputs->{$name}->{cmd}->finish; + delete $outputs->{$name}; + } + + my $x = $new{$name}->{rect}->{x}; + my $launch = $command; + $launch =~ s/([^%])%x/$1$x/g; + $launch =~ s/%%x/%x/g; + + $new{$name}->{cmd_input} = ''; + my @cmd = ('/bin/sh', '-c', $launch); + $new{$name}->{cmd} = start \@cmd, \$new{$name}->{cmd_input}; + $outputs->{$name} = $new{$name}; + } + + # Handle old outputs + for my $name (keys %old) { + next if defined($new{$name}); + + $outputs->{$name}->{cmd}->finish; + delete $outputs->{$name}; + } + + update_output(); +} + +sub output_change { + $i3->get_outputs->cb(\&got_outputs) +} + +sub update_output { + my $dzen_bg = "#111111"; + my $out; + + for my $name (keys %{$outputs}) { + my $width = $outputs->{$name}->{rect}->{width}; + + $out = qq|^pa(;2)|; + for my $ws (@{$workspaces}) { + next if $ws->{output} ne $name and !$show_all; + + my ($bg, $fg) = qw(333333 888888); + ($bg, $fg) = qw(4c7899 ffffff) if $ws->{visible}; + ($bg, $fg) = qw(900000 ffffff) if $ws->{urgent}; + + my $cmd = q|i3-msg "| . $ws->{num} . q|"|; + my $name = $ws->{name}; + + # Begin the clickable area + $out .= qq|^ca(1,$cmd)|; + + # Draw the rest of the bar in the background color, but + # don’t move the "cursor" + $out .= qq|^p(_LOCK_X)^fg(#$bg)^r(${width}x17)^p(_UNLOCK_X)|; + # Draw the name of the workspace without overwriting the + # background color + $out .= qq|^p(+3)^fg(#$fg)^ib(1)$name^ib(0)^p(+5)|; + # Draw the rest of the bar in the normal background color + # without moving the "cursor" + $out .= qq|^p(_LOCK_X)^fg($dzen_bg)^r(${width}x17)^p(_UNLOCK_X)|; + + # End the clickable area + $out .= qq|^ca()|; + + # Move to the next rect, reset Y coordinate + $out .= qq|^p(2)^pa(;2)|; + } + + $out .= qq|^p(_LOCK_X)^fg($dzen_bg)^r(${width}x17)^p(_UNLOCK_X)^fg(white)|; + $out .= qq|^p(+5)|; + $out .= $last_line if (!@input_on or $name ~~ @input_on); + $out .= "\n"; + + $outputs->{$name}->{cmd_input} = $out; + pump $outputs->{$name}->{cmd} while length $outputs->{$name}->{cmd_input}; + } +} + +$i3->connect->cb(\&connected); + +$stdin = AnyEvent->io( + fh => \*STDIN, + poll => 'r', + cb => sub { + chomp (my $line = ); + $last_line = $line; + update_output(); + }); + +# let AnyEvent do the rest ("endless loop") +AnyEvent->condvar->recv + +__END__ + +=head1 NAME + +i3-wsbar - sample implementation of a standalone workspace bar + +=head1 SYNOPSIS + +i3-wsbar -c [options] + +=head1 OPTIONS + +=over 4 + +=item B<--command> + +This command (at the moment only dzen2 is supported) will be started for each +output. C<%x> will be replaced with the X coordinate of the output. + +Example: + --command "dzen2 -dock -x %x" + +=item B<--input-on> + +Specifies on which outputs the contents of stdin should be appended to the +workspace bar. + +Example: + --input-on "LVDS1" + +=item B<--output-on> + +Specifies for which outputs i3-wsbar should start C. + +=item B<--show-all> + +If enabled, all workspaces are shown (not only those of the current output). +Handy to use with C<--output-on>. + +=back + +=cut From 239dbbb4f5b87110a1b992bbc14c1b1878092d33 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 25 Mar 2010 02:47:01 +0100 Subject: [PATCH 212/247] Add documentation for the workspace_bar option --- docs/userguide | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/userguide b/docs/userguide index f4a27743..c8821961 100644 --- a/docs/userguide +++ b/docs/userguide @@ -527,6 +527,26 @@ focus_follows_mouse focus_follows_mouse no ---------------------- +=== Internal workspace bar + +The internal workspace bar (the thing at the bottom of your screen) is very +simple -- it does not provide a way to display custom text and it does not +offer advanced customization features. This is intended because we do not +want to duplicate functionality of tools like +dzen2+, +xmobar+ and so on +(they render bars, we manage windows). Instead, there is an option which will +turn off the internal bar completely, so that you can use a separate program to +display it (see +i3-wsbar+, a sample implementation of such a program): + +*Syntax*: +---------------------- +workspace_bar +---------------------- + +*Examples*: +---------------- +workspace_bar no +---------------- + == List of commands === Manipulating layout From 2c42c0c760efa95f052c46bbb0f5b81725848e5b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 25 Mar 2010 02:53:53 +0100 Subject: [PATCH 213/247] Bugfix: Correctly check bitmask for floating_modifier --- src/click.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/click.c b/src/click.c index bec86788..c8b9d23b 100644 --- a/src/click.c +++ b/src/click.c @@ -265,7 +265,7 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ * to move around the client if it was floating. if not, we just process * as usual. */ if (config.floating_modifier != 0 && - (event->state & config.floating_modifier) != 0) { + (event->state & config.floating_modifier) == config.floating_modifier) { if (client == NULL) { DLOG("Not handling, floating_modifier was pressed and no client found\n"); return 1; From 538d1b3c0f31ff9971c41a1cce7d54347bdbe9eb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 25 Mar 2010 03:11:31 +0100 Subject: [PATCH 214/247] makefile: install i3-wsbar --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 9a4a52ea..c73723ad 100644 --- a/Makefile +++ b/Makefile @@ -61,6 +61,7 @@ install: all $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/include/i3 $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/share/xsessions $(INSTALL) -m 0755 i3 $(DESTDIR)$(PREFIX)/bin/ + $(INSTALL) -m 0755 i3-wsbar $(DESTDIR)$(PREFIX)/bin/ test -e $(DESTDIR)$(SYSCONFDIR)/i3/config || $(INSTALL) -m 0644 i3.config $(DESTDIR)$(SYSCONFDIR)/i3/config $(INSTALL) -m 0644 i3.welcome $(DESTDIR)$(SYSCONFDIR)/i3/welcome $(INSTALL) -m 0644 i3.desktop $(DESTDIR)$(PREFIX)/share/xsessions/ From 3bbcfadd227d596131d56b45999635e4897a0007 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 25 Mar 2010 03:26:59 +0100 Subject: [PATCH 215/247] docs: add/cleanup references --- docs/userguide | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/docs/userguide b/docs/userguide index c8821961..5cb1fe58 100644 --- a/docs/userguide +++ b/docs/userguide @@ -22,7 +22,7 @@ image:keyboard-layer2.png["Keys to use with Shift+Mod1",width=600,link="keyboard As i3 uses keycodes in the default configuration, it does not matter which keyboard layout you actually use. The key positions are what matters (of course -you can also use keysymbols, see below). +you can also use keysymbols, see <>). The red keys are the modifiers you need to press (by default), the blue keys are your homerow. @@ -178,9 +178,10 @@ windows, or toolbar windows (GIMP or similar). You can enable floating mode for a window by pressing +Mod1+Shift+Space+. By dragging the window’s titlebar with your mouse you can move the window -around. By grabbing the borders and moving them you can resize the window. +around. By grabbing the borders and moving them you can resize the window. You +can also do that by using the <>. -Bindings for doing this with your keyboard will follow. +For resizing floating windows with your keyboard, see <>. Floating windows are always on top of tiling windows. @@ -230,6 +231,8 @@ font font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 -------------------------------------------------------------- +[[keybindings]] + === Keyboard bindings A keyboard binding makes i3 execute a command (see below) upon pressing a @@ -280,6 +283,8 @@ umlauts or special characters 'and' having some comfortably reachable key bindings. For example, when typing, capslock+1 or capslock+2 for switching workspaces is totally convenient. Try it :-). +[[floating_modifier]] + === The floating modifier To move floating windows with your mouse, you can either grab their titlebar @@ -407,10 +412,10 @@ exec command exec sudo i3status | dzen2 -dock -------------------------------- -=== Automatically putting workspaces on specific screens - [[workspace_screen]] +=== Automatically putting workspaces on specific screens + If you assign clients to workspaces, it might be handy to put the workspaces on specific screens. Also, the assignment of workspaces to screens will determine which workspace i3 uses for a new screen when adding screens @@ -806,10 +811,10 @@ bindsym Mod1+Shift+w reload bindsym Mod1+Shift+e exit ---------------------------- -== Multiple monitors - [[multi_monitor]] +== Multiple monitors + As you can see in the goal list on the website, i3 was specifically developed with support for multiple monitors in mind. This section will explain how to handle multiple monitors. From 469f22caeb3f146c268cd165f3537431ca14aa48 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 25 Mar 2010 18:07:40 +0100 Subject: [PATCH 216/247] Bugfix: Correctly switch workspace when using the "jump" command (Thanks fallen) --- src/commands.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/commands.c b/src/commands.c index e7ebb946..b601c1c0 100644 --- a/src/commands.c +++ b/src/commands.c @@ -721,6 +721,7 @@ static void jump_to_window(xcb_connection_t *conn, const char *arguments) { } free(classtitle); + workspace_show(conn, client->workspace->num + 1); set_focus(conn, client, true); } From 6699d546404b36729968dbd3fa7020d5f93b0967 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 25 Mar 2010 19:08:37 +0100 Subject: [PATCH 217/247] Fix rendering of workspace names after "reload" (Thanks fallen) --- src/commands.c | 3 +++ src/config.c | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/commands.c b/src/commands.c index b601c1c0..2d8155f2 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1042,6 +1042,9 @@ void parse_command(xcb_connection_t *conn, const char *command) { /* Is it a ? */ if (STARTS_WITH(command, "reload")) { load_configuration(conn, NULL, true); + render_layout(conn); + /* Send an IPC event just in case the ws names have changed */ + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"reload\"}"); return; } diff --git a/src/config.c b/src/config.c index f42583fa..d89af991 100644 --- a/src/config.c +++ b/src/config.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -320,6 +320,11 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, TAILQ_REMOVE(&assignments, assign, assignments); FREE(assign); } + + /* Clear workspace names */ + Workspace *ws; + TAILQ_FOREACH(ws, workspaces, workspaces) + workspace_set_name(ws, NULL); } SLIST_INIT(&modes); From 1b8299002ecf75f183f43e8aa52aa63ddfcf2087 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Thu, 25 Mar 2010 21:18:46 +0100 Subject: [PATCH 218/247] Bugfix: Translate keysyms to keycodes before entering mode (Thanks fallen) --- src/config.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config.c b/src/config.c index d89af991..c7ef0af9 100644 --- a/src/config.c +++ b/src/config.c @@ -205,6 +205,7 @@ void switch_mode(xcb_connection_t *conn, const char *new_mode) { ungrab_all_keys(conn); bindings = mode->bindings; + translate_keysyms(); grab_all_keys(conn, false); return; } From 6fd56757f063c3d89a94c87ed35dca3ddf66e5da Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Mar 2010 00:13:28 +0100 Subject: [PATCH 219/247] Bugfix: Assign all workspace to new outputs as new outputs get available (Thanks badboy) --- src/randr.c | 13 +++++++++++++ src/workspace.c | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/randr.c b/src/randr.c index f9e2a416..6d4bbef9 100644 --- a/src/randr.c +++ b/src/randr.c @@ -193,6 +193,19 @@ void initialize_output(xcb_connection_t *conn, Output *output, Workspace *worksp ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}"); DLOG("initialized output at (%d, %d) with %d x %d\n", output->rect.x, output->rect.y, output->rect.width, output->rect.height); + + DLOG("assigning configured workspaces to this output...\n"); + Workspace *ws; + TAILQ_FOREACH(ws, workspaces, workspaces) { + if (ws == workspace) + continue; + if (ws->preferred_output == NULL || + get_output_by_name(ws->preferred_output) != output) + continue; + + DLOG("assigning ws %d\n", ws->num + 1); + workspace_assign_to(ws, output, true); + } } /* diff --git a/src/workspace.c b/src/workspace.c index 3fe4751b..c950df8f 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -103,7 +103,7 @@ void workspace_set_name(Workspace *ws, const char *name) { * */ bool workspace_is_visible(Workspace *ws) { - return (ws->output->current_workspace == ws); + return (ws->output != NULL && ws->output->current_workspace == ws); } /* From a151fd95a841481b9c5334f2dcec7bb884e800ca Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Mar 2010 00:28:30 +0100 Subject: [PATCH 220/247] remove newlines --- src/randr.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/randr.c b/src/randr.c index 6d4bbef9..42dd8844 100644 --- a/src/randr.c +++ b/src/randr.c @@ -323,10 +323,8 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, if (output->crtc == XCB_NONE) { if (!existing) TAILQ_INSERT_TAIL(&outputs, new, outputs); - else if (new->active) { + else if (new->active) new->to_be_disabled = true; - - } free(output); return; } From 7bd4ea3699cb83740998bf4be6bc960d9cfdc909 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Mar 2010 01:52:08 +0100 Subject: [PATCH 221/247] =?UTF-8?q?randr:=20Don=E2=80=99t=20enable=20outpu?= =?UTF-8?q?ts=20with=20mode=200x0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Just a sanity check for some possibly broken drivers. --- src/randr.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/randr.c b/src/randr.c index 42dd8844..d46ffbc4 100644 --- a/src/randr.c +++ b/src/randr.c @@ -339,11 +339,16 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, return; } - new->active = true; bool updated = update_if_necessary(&(new->rect.x), crtc->x) | update_if_necessary(&(new->rect.y), crtc->y) | update_if_necessary(&(new->rect.width), crtc->width) | update_if_necessary(&(new->rect.height), crtc->height); + free(crtc); + new->active = (new->rect.width != 0 && new->rect.height != 0); + if (!new->active) { + DLOG("width/height 0/0, disabling output\n"); + return; + } DLOG("mode: %dx%d+%d+%d\n", new->rect.width, new->rect.height, new->rect.x, new->rect.y); From a542515f9efa4c0211c24baf5f17edc0badd98eb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Mar 2010 01:52:39 +0100 Subject: [PATCH 222/247] Fix memory leaks --- include/data.h | 3 --- src/cfgparse.y | 36 ++++++++++++++++++++++++------------ src/randr.c | 5 ++--- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/include/data.h b/include/data.h index 2d8c7b1a..3618dfb8 100644 --- a/include/data.h +++ b/include/data.h @@ -212,9 +212,6 @@ struct Workspace { /** The name of the RandR output this screen should be on */ char *preferred_output; - /** Temporary flag needed for re-querying xinerama screens */ - bool reassigned; - /** True if any client on this workspace has its urgent flag set */ bool urgent; diff --git a/src/cfgparse.y b/src/cfgparse.y index b9fae726..e2b03412 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -176,6 +176,14 @@ void parse_file(const char *f) { free(context); free(new); free(buf); + + while (!SLIST_EMPTY(&variables)) { + current = SLIST_FIRST(&variables); + FREE(current->key); + FREE(current->value); + SLIST_REMOVE_HEAD(&variables, variables); + FREE(current); + } } %} @@ -279,7 +287,7 @@ bind: new->keycode = $2; new->mods = $1; - new->command = sstrdup($4); + new->command = $4; $$ = new; } @@ -291,9 +299,9 @@ bindsym: printf("\tFound symbolic mod%d with key %s and command %s\n", $1, $2, $4); Binding *new = scalloc(sizeof(Binding)); - new->symbol = sstrdup($2); + new->symbol = $2; new->mods = $1; - new->command = sstrdup($4); + new->command = $4; $$ = new; } @@ -323,7 +331,7 @@ mode: } struct Mode *mode = scalloc(sizeof(struct Mode)); - mode->name = strdup($3); + mode->name = $3; mode->bindings = current_bindings; current_bindings = NULL; SLIST_INSERT_HEAD(&modes, mode, modes); @@ -403,7 +411,7 @@ new_window: TOKNEWWINDOW WHITESPACE WORD { DLOG("new windows should start in mode %s\n", $3); - config.default_border = strdup($3); + config.default_border = sstrdup($3); } ; @@ -447,9 +455,11 @@ workspace: DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); } else { Workspace *ws = workspace_get(ws_num - 1); - ws->preferred_output = sstrdup($7); - if ($8 != NULL) + ws->preferred_output = $7; + if ($8 != NULL) { workspace_set_name(ws, $8); + free($8); + } } } | TOKWORKSPACE WHITESPACE NUMBER WHITESPACE workspace_name @@ -459,8 +469,10 @@ workspace: DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); } else { DLOG("workspace name to: %s\n", $5); - if ($5 != NULL) + if ($5 != NULL) { workspace_set_name(workspace_get(ws_num - 1), $5); + free($5); + } } } ; @@ -484,7 +496,7 @@ assign: struct Assignment *new = $6; printf(" to %d\n", new->workspace); printf(" floating = %d\n", new->floating); - new->windowclass_title = strdup($3); + new->windowclass_title = $3; TAILQ_INSERT_TAIL(&assignments, new, assignments); } ; @@ -525,7 +537,7 @@ optional_arrow: ipcsocket: TOKIPCSOCKET WHITESPACE STR { - config.ipc_socket_path = sstrdup($3); + config.ipc_socket_path = $3; } ; @@ -533,7 +545,7 @@ exec: TOKEXEC WHITESPACE STR { struct Autostart *new = smalloc(sizeof(struct Autostart)); - new->command = sstrdup($3); + new->command = $3; TAILQ_INSERT_TAIL(&autostarts, new, autostarts); } ; @@ -549,7 +561,7 @@ terminal: font: TOKFONT WHITESPACE STR { - config.font = sstrdup($3); + config.font = $3; printf("font %s\n", config.font); } ; diff --git a/src/randr.c b/src/randr.c index d46ffbc4..e61fd9b2 100644 --- a/src/randr.c +++ b/src/randr.c @@ -311,6 +311,7 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, if (!existing) new = scalloc(sizeof(Output)); new->id = id; + FREE(new->name); asprintf(&new->name, "%.*s", xcb_randr_get_output_info_name_length(output), xcb_randr_get_output_info_name(output)); @@ -325,7 +326,6 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, TAILQ_INSERT_TAIL(&outputs, new, outputs); else if (new->active) new->to_be_disabled = true; - free(output); return; } @@ -335,7 +335,6 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, DLOG("Skipping output %s: could not get CRTC (%p)\n", new->name, crtc); free(new); - free(output); return; } @@ -359,7 +358,6 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, if (!updated || !existing) { if (!existing) TAILQ_INSERT_TAIL(&outputs, new, outputs); - free(output); return; } @@ -409,6 +407,7 @@ void randr_query_outputs(xcb_connection_t *conn) { continue; handle_output(conn, randr_outputs[i], output, cts, res); + free(output); } free(res); From 41b6631f684558afc95f8b4288f6736cd71a758b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Mar 2010 03:04:54 +0100 Subject: [PATCH 223/247] Bugfix: null-terminate buffer --- src/cfgparse.y | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfgparse.y b/src/cfgparse.y index e2b03412..2774f05c 100644 --- a/src/cfgparse.y +++ b/src/cfgparse.y @@ -70,7 +70,7 @@ void parse_file(const char *f) { if (fstat(fd, &stbuf) == -1) die("Could not fstat file: %s\n", strerror(errno)); - buf = smalloc(stbuf.st_size * sizeof(char)); + buf = scalloc((stbuf.st_size + 1) * sizeof(char)); while (read_bytes < stbuf.st_size) { if ((ret = read(fd, buf + read_bytes, (stbuf.st_size - read_bytes))) < 0) die("Could not read(): %s\n", strerror(errno)); From 64b5985d5d0b421afeec2b4f32602750530083dd Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Mar 2010 20:07:03 +0100 Subject: [PATCH 224/247] =?UTF-8?q?i3-msg:=20don=E2=80=99t=20stop=20proces?= =?UTF-8?q?sing=20options=20after=20-s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- i3-msg/main.c | 1 - 1 file changed, 1 deletion(-) diff --git a/i3-msg/main.c b/i3-msg/main.c index a1bdd72e..2a9caec8 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -127,7 +127,6 @@ int main(int argc, char *argv[]) { 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') { if (strcasecmp(optarg, "command") == 0) message_type = I3_IPC_MESSAGE_TYPE_COMMAND; From fc555fa16031c83c0e23cdab19f7f08664dab770 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 26 Mar 2010 21:14:23 +0100 Subject: [PATCH 225/247] re-enable all testcases --- testcases/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcases/Makefile b/testcases/Makefile index 010b595f..d37450ab 100644 --- a/testcases/Makefile +++ b/testcases/Makefile @@ -1,4 +1,4 @@ test: - PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" t/15*.t + PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(1)" t/*.t all: test From 112f17c6908905c46558cbb707eb0a27cd7e1998 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Mar 2010 03:45:48 +0100 Subject: [PATCH 226/247] Bugfix: Correctly ignore clicks when client is in fullscreen mode (Thanks Sasha) --- src/click.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/click.c b/src/click.c index c8b9d23b..9f2a47ff 100644 --- a/src/click.c +++ b/src/click.c @@ -268,10 +268,14 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ (event->state & config.floating_modifier) == config.floating_modifier) { if (client == NULL) { DLOG("Not handling, floating_modifier was pressed and no client found\n"); + xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); + xcb_flush(conn); return 1; } if (client->fullscreen) { DLOG("Not handling, client is in fullscreen mode\n"); + xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); + xcb_flush(conn); return 1; } if (client_is_floating(client)) { From ffa388f1946ad2555b62cea5bc2460c546e36fd0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Mar 2010 04:02:55 +0100 Subject: [PATCH 227/247] Bugfix: When moving fullscreen floating windows to a different workspace, correctly reposition/resize (Thanks Sasha) --- src/commands.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/commands.c b/src/commands.c index 2d8155f2..c0cb8782 100644 --- a/src/commands.c +++ b/src/commands.c @@ -593,11 +593,16 @@ static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *cl uint32_t relative_x = client->rect.x - old_ws->rect.x, relative_y = client->rect.y - old_ws->rect.y; DLOG("rel_x = %d, rel_y = %d\n", relative_x, relative_y); - client->rect.x = t_ws->rect.x + relative_x; - client->rect.y = t_ws->rect.y + relative_y; - DLOG("after x = %d, y = %d\n", client->rect.x, client->rect.y); - reposition_client(conn, client); - xcb_flush(conn); + if (client->fullscreen) { + client_enter_fullscreen(conn, client, false); + memcpy(&(client->rect), &(t_ws->rect), sizeof(Rect)); + } else { + client->rect.x = t_ws->rect.x + relative_x; + client->rect.y = t_ws->rect.y + relative_y; + DLOG("after x = %d, y = %d\n", client->rect.x, client->rect.y); + reposition_client(conn, client); + xcb_flush(conn); + } } /* Configure the window above all tiling windows (or below a fullscreen From 2ac12eca170184b53f4fc9caa87012392285089e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Mar 2010 04:08:50 +0100 Subject: [PATCH 228/247] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20allow=20fulls?= =?UTF-8?q?creen=20floating=20windows=20to=20be=20moved=20(Thanks=20Sasha)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/floating.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/floating.c b/src/floating.c index 61e95599..7e62eea5 100644 --- a/src/floating.c +++ b/src/floating.c @@ -488,6 +488,11 @@ void floating_focus_direction(xcb_connection_t *conn, Client *currently_focused, void floating_move(xcb_connection_t *conn, Client *currently_focused, direction_t direction) { DLOG("floating move\n"); + if (currently_focused->fullscreen) { + DLOG("Cannot move fullscreen windows\n"); + return; + } + Rect destination = currently_focused->rect; Rect *screen = &(currently_focused->workspace->output->rect); From 34f79416d554bce023358dd29e7572e9300e5f83 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Mar 2010 14:43:36 +0100 Subject: [PATCH 229/247] ipc: change default socket path to ~/.i3/ipc.sock, enable in default config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Having the IPC socket is handy, so enable it by default. Also, if you have > 1 user running i3 on the same machine, the old path of /tmp/i3-ipc.sock is not so useful. On the other hand, we needed quite a bit of changes to make tilde expansion and creation of directories (mkdir -p) work… --- i3.config | 4 ++++ include/config.h | 12 ++++++++++++ src/config.c | 18 ++++++++++++++++-- src/ipc.c | 44 +++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 75 insertions(+), 3 deletions(-) diff --git a/i3.config b/i3.config index 56cb2704..11bd9066 100644 --- a/i3.config +++ b/i3.config @@ -116,6 +116,10 @@ bind Mod1+Shift+26 exit # Mod1+Shift+r restarts i3 inplace bind Mod1+Shift+27 restart +# The IPC interface allows programs like an external workspace bar +# (i3-wsbar) or i3-msg (can be used to "remote-control" i3) to work. +ipc-socket ~/.i3/ipc.sock + ############################################################# # DELETE THE FOLLOWING LINES TO DISABLE THE WELCOME MESSAGE # ############################################################# diff --git a/include/config.h b/include/config.h index 0b671b7a..0c790bf2 100644 --- a/include/config.h +++ b/include/config.h @@ -124,6 +124,18 @@ struct Config { } bar; }; +/** + * This function resolves ~ in pathnames. + * + */ +char *glob_path(const char *path); + +/** + * Checks if the given path exists by calling stat(). + * + */ +bool path_exists(const char *path); + /** * Reads the configuration from ~/.i3/config or /etc/i3/config if not found. * diff --git a/src/config.c b/src/config.c index c7ef0af9..972e376c 100644 --- a/src/config.c +++ b/src/config.c @@ -18,6 +18,7 @@ #include #include #include +#include #include /* We need Xlib for XStringToKeysym */ @@ -40,12 +41,25 @@ struct modes_head modes; * This function resolves ~ in pathnames. * */ -static char *glob_path(const char *path) { +char *glob_path(const char *path) { static glob_t globbuf; if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0) die("glob() failed"); char *result = sstrdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path); globfree(&globbuf); + + /* If the file does not exist yet, we still may need to resolve tilde, + * so call wordexp */ + if (strcmp(result, path) == 0) { + wordexp_t we; + wordexp(path, &we, WRDE_NOCMD); + if (we.we_wordc > 0) { + free(result); + result = sstrdup(we.we_wordv[0]); + } + wordfree(&we); + } + return result; } @@ -53,7 +67,7 @@ static char *glob_path(const char *path) { * Checks if the given path exists by calling stat(). * */ -static bool path_exists(const char *path) { +bool path_exists(const char *path) { struct stat buf; return (stat(path, &buf) == 0); } diff --git a/src/ipc.c b/src/ipc.c index 8ed455dd..dbd0a697 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -12,6 +12,7 @@ */ #include #include +#include #include #include #include @@ -21,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +35,7 @@ #include "log.h" #include "table.h" #include "randr.h" +#include "config.h" /* Shorter names for all those yajl_gen_* functions */ #define y(x, ...) yajl_gen_ ## x (gen, ##__VA_ARGS__) @@ -53,6 +56,34 @@ static void set_nonblock(int sockfd) { err(-1, "Could not set O_NONBLOCK"); } +/* + * Emulates mkdir -p (creates any missing folders) + * + */ +static bool mkdirp(const char *path) { + if (mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == 0) + return true; + if (errno != ENOENT) { + ELOG("mkdir(%s) failed: %s\n", path, strerror(errno)); + return false; + } + char *copy = strdup(path); + /* strip trailing slashes, if any */ + while (copy[strlen(copy)-1] == '/') + copy[strlen(copy)-1] = '\0'; + + char *sep = strrchr(copy, '/'); + if (sep == NULL) + return false; + *sep = '\0'; + bool result = false; + if (mkdirp(copy)) + result = mkdirp(path); + free(copy); + + return result; +} + static void ipc_send_message(int fd, const unsigned char *payload, int message_type, int message_size) { int buffer_size = strlen("i3-ipc") + sizeof(uint32_t) + @@ -471,11 +502,20 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents) { int ipc_create_socket(const char *filename) { int sockfd; + char *globbed = glob_path(filename); + DLOG("Creating IPC-socket at %s\n", globbed); + char *copy = sstrdup(globbed); + const char *dir = dirname(copy); + if (!path_exists(dir)) + mkdirp(dir); + free(copy); + /* Unlink the unix domain socket before */ unlink(filename); if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) { perror("socket()"); + free(globbed); return -1; } @@ -484,12 +524,14 @@ int ipc_create_socket(const char *filename) { struct sockaddr_un addr; memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_LOCAL; - strcpy(addr.sun_path, filename); + strcpy(addr.sun_path, globbed); if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) { perror("bind()"); + free(globbed); return -1; } + free(globbed); set_nonblock(sockfd); if (listen(sockfd, 5) < 0) { From dc9bc89172681cf5b4f6feed6c23e5bcef65e593 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Mar 2010 14:50:12 +0100 Subject: [PATCH 230/247] make i3-msg and i3-input use the new default path --- i3-input/main.c | 22 +++++++++++++++++++--- i3-msg/main.c | 20 ++++++++++++++++++-- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/i3-input/main.c b/i3-input/main.c index f5229c08..2a3f02fd 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -3,7 +3,7 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -51,6 +52,21 @@ static char *prompt; static int prompt_len; static int limit; +/* + * This function resolves ~ in pathnames (and more, see glob(3)). + * + */ +static char *glob_path(const char *path) { + static glob_t globbuf; + if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0) + errx(EXIT_FAILURE, "glob() failed"); + char *result = strdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path); + if (result == NULL) + err(EXIT_FAILURE, "malloc() failed"); + globfree(&globbuf); + return result; +} + /* * Concats the glyphs (either UCS-2 or UTF-8) to a single string, suitable for * rendering it (UCS-2) or sending it to i3 (UTF-8). @@ -241,7 +257,7 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press } int main(int argc, char *argv[]) { - char *socket_path = "/tmp/i3-ipc.sock"; + char *socket_path = glob_path("~/.i3/ipc.sock"); char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; int o, option_index = 0; @@ -260,7 +276,7 @@ int main(int argc, char *argv[]) { while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { switch (o) { case 's': - socket_path = strdup(optarg); + socket_path = glob_path(optarg); break; case 'v': printf("i3-input " I3_VERSION); diff --git a/i3-msg/main.c b/i3-msg/main.c index 2a9caec8..b22d550e 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -27,9 +27,25 @@ #include #include #include +#include #include +/* + * This function resolves ~ in pathnames (and more, see glob(3)). + * + */ +static char *glob_path(const char *path) { + static glob_t globbuf; + if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0) + errx(EXIT_FAILURE, "glob() failed"); + char *result = strdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path); + if (result == NULL) + err(EXIT_FAILURE, "malloc() failed"); + globfree(&globbuf); + return result; +} + /* * Formats a message (payload) of the given size and type and sends it to i3 via * the given socket file descriptor. @@ -107,7 +123,7 @@ static void ipc_recv_message(int sockfd, uint32_t message_type, } int main(int argc, char *argv[]) { - char *socket_path = "/tmp/i3-ipc.sock"; + char *socket_path = glob_path("~/.i3/ipc.sock"); int o, option_index = 0; int message_type = I3_IPC_MESSAGE_TYPE_COMMAND; char *payload = ""; @@ -126,7 +142,7 @@ int main(int argc, char *argv[]) { while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { if (o == 's') { - socket_path = strdup(optarg); + socket_path = glob_path(optarg); } else if (o == 't') { if (strcasecmp(optarg, "command") == 0) message_type = I3_IPC_MESSAGE_TYPE_COMMAND; From 5e2ab5be655da22d91f2f320e02d8a549270f35e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Mar 2010 15:05:16 +0100 Subject: [PATCH 231/247] bugfix: use globbed instead of filename when unlinking ipc socket --- src/ipc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipc.c b/src/ipc.c index dbd0a697..1937d55d 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -511,7 +511,7 @@ int ipc_create_socket(const char *filename) { free(copy); /* Unlink the unix domain socket before */ - unlink(filename); + unlink(globbed); if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) { perror("socket()"); From 8fb641cca47eda94e9f4f9c2347210350ae5ff7a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Mar 2010 15:20:38 +0100 Subject: [PATCH 232/247] make testcases use AnyEvent::I3 --- testcases/t/05-ipc.t | 16 ++++--------- testcases/t/06-focus.t | 23 +++++++------------ testcases/t/07-move.t | 22 +++++++----------- testcases/t/08-focus-stack.t | 13 ++++------- testcases/t/09-stacking.t | 14 ++++-------- testcases/t/10-dock.t | 1 - testcases/t/11-goto.t | 16 +++++-------- testcases/t/12-floating-resize.t | 17 +++++--------- testcases/t/13-urgent.t | 23 +++++++------------ testcases/t/14-client-leader.t | 18 +++++---------- testcases/t/15-ipc-workspaces.t | 39 ++++---------------------------- testcases/t/lib/i3test.pm | 29 ------------------------ 12 files changed, 61 insertions(+), 170 deletions(-) diff --git a/testcases/t/05-ipc.t b/testcases/t/05-ipc.t index ac52911a..8f427938 100644 --- a/testcases/t/05-ipc.t +++ b/testcases/t/05-ipc.t @@ -1,7 +1,7 @@ #!perl # vim:ts=4:sw=4:expandtab -use Test::More tests => 4; +use Test::More tests => 3; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -9,6 +9,7 @@ use Time::HiRes qw(sleep); use FindBin; use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; BEGIN { use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); @@ -17,19 +18,14 @@ BEGIN { my $x = X11::XCB::Connection->new; -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); - -isa_ok($sock, 'IO::Socket::UNIX'); - +my $i3 = i3; ##################################################################### # Ensure IPC works by switching workspaces ##################################################################### # Switch to the first workspace to get a clean testing environment -$sock->write(i3test::format_ipc_command("1")); - -sleep(0.25); +$i3->command('1')->recv; # Create a window so we can get a focus different from NULL my $window = i3test::open_standard_window($x); @@ -41,9 +37,7 @@ my $focus = $x->input_focus; diag("old focus = $focus"); # Switch to the nineth workspace -$sock->write(i3test::format_ipc_command("9")); - -sleep(0.25); +$i3->command('9')->recv; my $new_focus = $x->input_focus; isnt($focus, $new_focus, "Focus changed"); diff --git a/testcases/t/06-focus.t b/testcases/t/06-focus.t index 5ca3e062..a95e0e40 100644 --- a/testcases/t/06-focus.t +++ b/testcases/t/06-focus.t @@ -4,7 +4,7 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 14; +use Test::More tests => 13; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -12,29 +12,25 @@ use Time::HiRes qw(sleep); use FindBin; use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; BEGIN { - use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } my $x = X11::XCB::Connection->new; -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); -isa_ok($sock, 'IO::Socket::UNIX'); +my $i3 = i3; # Switch to the nineth workspace -$sock->write(i3test::format_ipc_command("9")); - -sleep(0.25); +$i3->command('9')->recv; ##################################################################### # Create two windows and make sure focus switching works ##################################################################### # Change mode of the container to "default" for following tests -$sock->write(i3test::format_ipc_command("d")); -sleep(0.25); +$i3->command('d')->recv; my $top = i3test::open_standard_window($x); my $mid = i3test::open_standard_window($x); @@ -52,8 +48,7 @@ diag("bottom id = " . $bottom->id); sub focus_after { my $msg = shift; - $sock->write(i3test::format_ipc_command($msg)); - sleep(0.5); + $i3->command($msg)->recv; return $x->input_focus; } @@ -81,8 +76,7 @@ is($focus, $top->id, "Top window focused (wrapping to the bottom works)"); ############################################### # Switch to the 10. workspace -$sock->write(i3test::format_ipc_command("10")); -sleep 0.25; +$i3->command('10')->recv; $top = i3test::open_standard_window($x); $bottom = i3test::open_standard_window($x); @@ -102,8 +96,7 @@ is($focus, $top->id, "Top window focused"); # Same thing, but left/right instead of top/bottom # Switch to the 11. workspace -$sock->write(i3test::format_ipc_command("11")); -sleep 0.25; +$i3->command('11')->recv; my $left = i3test::open_standard_window($x); my $right = i3test::open_standard_window($x); diff --git a/testcases/t/07-move.t b/testcases/t/07-move.t index 10cb9830..efd3df15 100644 --- a/testcases/t/07-move.t +++ b/testcases/t/07-move.t @@ -4,7 +4,7 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 10; +use Test::More tests => 8; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -12,21 +12,18 @@ use Time::HiRes qw(sleep); use FindBin; use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; BEGIN { - use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } my $x = X11::XCB::Connection->new; -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); -isa_ok($sock, 'IO::Socket::UNIX'); +my $i3 = i3; # Switch to the nineth workspace -$sock->write(i3test::format_ipc_command("9")); - -sleep(0.25); +$i3->command('9')->recv; ##################################################################### # Create two windows and make sure focus switching works @@ -50,8 +47,7 @@ diag("bottom id = " . $bottom->id); sub focus_after { my $msg = shift; - $sock->write(i3test::format_ipc_command($msg)); - sleep(0.5); + $i3->command($msg)->recv; return $x->input_focus; } @@ -82,9 +78,7 @@ is($focus, $top->id, "Top window focused"); # Move window cross-workspace ##################################################################### -$sock->write(i3test::format_ipc_command("m12")); -$sock->write(i3test::format_ipc_command("t")); -$sock->write(i3test::format_ipc_command("m13")); -$sock->write(i3test::format_ipc_command("12")); -$sock->write(i3test::format_ipc_command("13")); +for my $cmd (qw(m12 t m13 12 13)) { + $i3->command($cmd)->recv; +} ok(1, "Still living"); diff --git a/testcases/t/08-focus-stack.t b/testcases/t/08-focus-stack.t index 370369d8..4ae92407 100644 --- a/testcases/t/08-focus-stack.t +++ b/testcases/t/08-focus-stack.t @@ -3,7 +3,7 @@ # Checks if the focus is correctly restored, when creating a floating client # over an unfocused tiling client and destroying the floating one again. -use Test::More tests => 6; +use Test::More tests => 4; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -11,28 +11,25 @@ use Time::HiRes qw(sleep); use FindBin; use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; BEGIN { - use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Window') or BAIL_OUT('Could not load X11::XCB::Window'); } my $x = X11::XCB::Connection->new; -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); -isa_ok($sock, 'IO::Socket::UNIX'); +my $i3 = i3; # Switch to the nineth workspace -$sock->write(i3test::format_ipc_command("9")); - -sleep(0.25); +$i3->command('9')->recv; my $tiled_left = i3test::open_standard_window($x); my $tiled_right = i3test::open_standard_window($x); sleep(0.25); -$sock->write(i3test::format_ipc_command("ml")); +$i3->command('ml')->recv; # Get input focus before creating the floating window my $focus = $x->input_focus; diff --git a/testcases/t/09-stacking.t b/testcases/t/09-stacking.t index 8f40047e..59d2e6f4 100644 --- a/testcases/t/09-stacking.t +++ b/testcases/t/09-stacking.t @@ -4,7 +4,7 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 24; +use Test::More tests => 22; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -12,21 +12,18 @@ use Time::HiRes qw(sleep); use FindBin; use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; BEGIN { - use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } my $x = X11::XCB::Connection->new; -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); -isa_ok($sock, 'IO::Socket::UNIX'); +my $i3 = i3; # Switch to the nineth workspace -$sock->write(i3test::format_ipc_command("9")); - -sleep(0.25); +$i3->command('9')->recv; ##################################################################### # Create two windows and make sure focus switching works @@ -50,8 +47,7 @@ diag("bottom id = " . $bottom->id); sub focus_after { my $msg = shift; - $sock->write(i3test::format_ipc_command($msg)); - sleep(0.25); + $i3->command($msg)->recv; return $x->input_focus; } diff --git a/testcases/t/10-dock.t b/testcases/t/10-dock.t index b1b7bfcb..52063131 100644 --- a/testcases/t/10-dock.t +++ b/testcases/t/10-dock.t @@ -12,7 +12,6 @@ use i3test; use List::Util qw(first); BEGIN { - #use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } diff --git a/testcases/t/11-goto.t b/testcases/t/11-goto.t index 47675903..9b06112b 100644 --- a/testcases/t/11-goto.t +++ b/testcases/t/11-goto.t @@ -4,7 +4,7 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 9; +use Test::More tests => 7; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -13,21 +13,18 @@ use FindBin; use Digest::SHA1 qw(sha1_base64); use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; BEGIN { - use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } my $x = X11::XCB::Connection->new; -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); -isa_ok($sock, 'IO::Socket::UNIX'); +my $i3 = i3; # Switch to the nineth workspace -$sock->write(i3test::format_ipc_command("9")); - -sleep(0.25); +$i3->command('9')->recv; ##################################################################### # Create two windows and make sure focus switching works @@ -51,8 +48,7 @@ diag("bottom id = " . $bottom->id); sub focus_after { my $msg = shift; - $sock->write(i3test::format_ipc_command($msg)); - sleep(0.5); + $i3->command($msg)->recv; return $x->input_focus; } @@ -74,7 +70,7 @@ my $random_mark = sha1_base64(rand()); $focus = focus_after("goto $random_mark"); is($focus, $mid->id, "focus unchanged"); -$sock->write(i3test::format_ipc_command("mark $random_mark")); +$i3->command("mark $random_mark")->recv; $focus = focus_after("k"); is($focus, $top->id, "Top window focused"); diff --git a/testcases/t/12-floating-resize.t b/testcases/t/12-floating-resize.t index d908d345..74f66535 100644 --- a/testcases/t/12-floating-resize.t +++ b/testcases/t/12-floating-resize.t @@ -4,7 +4,7 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 17; +use Test::More tests => 15; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -13,21 +13,18 @@ use FindBin; use Digest::SHA1 qw(sha1_base64); use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; BEGIN { - use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } my $x = X11::XCB::Connection->new; -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); -isa_ok($sock, 'IO::Socket::UNIX'); +my $i3 = i3; # Switch to the nineth workspace -$sock->write(i3test::format_ipc_command("9")); - -sleep 0.25; +$i3->command('9')->recv; ##################################################################### # Create a floating window and see if resizing works @@ -78,13 +75,11 @@ sub test_resize { test_resize; # Test borderless -$sock->write(i3test::format_ipc_command("bb")); -sleep 0.25; +$i3->command('bb')->recv; test_resize; # Test with 1-px-border -$sock->write(i3test::format_ipc_command("bp")); -sleep 0.25; +$i3->command('bp')->recv; test_resize; diff --git a/testcases/t/13-urgent.t b/testcases/t/13-urgent.t index 7dee21c6..5fce6aee 100644 --- a/testcases/t/13-urgent.t +++ b/testcases/t/13-urgent.t @@ -4,7 +4,7 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 9; +use Test::More tests => 7; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -13,21 +13,18 @@ use FindBin; use Digest::SHA1 qw(sha1_base64); use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; BEGIN { - use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } my $x = X11::XCB::Connection->new; -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); -isa_ok($sock, 'IO::Socket::UNIX'); +my $i3 = i3; # Switch to the nineth workspace -$sock->write(i3test::format_ipc_command("9")); - -sleep 0.25; +$i3->command('9')->recv; ##################################################################### # Create two windows and put them in stacking mode @@ -38,8 +35,7 @@ sleep 0.25; my $bottom = i3test::open_standard_window($x); sleep 0.25; -$sock->write(i3test::format_ipc_command("s")); -sleep 0.25; +$i3->command('s')->recv; ##################################################################### # Add the urgency hint, switch to a different workspace and back again @@ -47,12 +43,9 @@ sleep 0.25; $top->add_hint('urgency'); sleep 1; -$sock->write(i3test::format_ipc_command("1")); -sleep 0.25; -$sock->write(i3test::format_ipc_command("9")); -sleep 0.25; -$sock->write(i3test::format_ipc_command("1")); -sleep 0.25; +$i3->command('1')->recv; +$i3->command('9')->recv; +$i3->command('1')->recv; my $std = i3test::open_standard_window($x); sleep 0.25; diff --git a/testcases/t/14-client-leader.t b/testcases/t/14-client-leader.t index ead52764..b9160131 100644 --- a/testcases/t/14-client-leader.t +++ b/testcases/t/14-client-leader.t @@ -4,7 +4,7 @@ # the workspace to be empty). # TODO: skip it by default? -use Test::More tests => 5; +use Test::More tests => 3; use Test::Deep; use X11::XCB qw(:all); use Data::Dumper; @@ -13,21 +13,17 @@ use FindBin; use Digest::SHA1 qw(sha1_base64); use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; BEGIN { - use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); } my $x = X11::XCB::Connection->new; - -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); -isa_ok($sock, 'IO::Socket::UNIX'); +my $i3 = i3; # Switch to the nineth workspace -$sock->write(i3test::format_ipc_command("9")); - -sleep 0.25; +$i3->command('9')->recv; ##################################################################### # Create a parent window @@ -48,8 +44,7 @@ sleep 0.25; # Switch workspace to 10 and open a child window. It should be positioned # on workspace 9. ######################################################################### -$sock->write(i3test::format_ipc_command("10")); -sleep 0.25; +$i3->command('10')->recv; my $child = $x->root->create_child( class => WINDOW_CLASS_INPUT_OUTPUT, @@ -66,7 +61,6 @@ sleep 0.25; isnt($x->input_focus, $child->id, "Child window focused"); # Switch back -$sock->write(i3test::format_ipc_command("9")); -sleep 0.25; +$i3->command('9')->recv; is($x->input_focus, $child->id, "Child window focused"); diff --git a/testcases/t/15-ipc-workspaces.t b/testcases/t/15-ipc-workspaces.t index 01947094..4e2c0e8d 100644 --- a/testcases/t/15-ipc-workspaces.t +++ b/testcases/t/15-ipc-workspaces.t @@ -1,52 +1,21 @@ #!perl # vim:ts=4:sw=4:expandtab -use Test::More tests => 8; +use Test::More tests => 3; use Test::Exception; -use Data::Dumper; -use JSON::XS; use List::MoreUtils qw(all); use FindBin; use lib "$FindBin::Bin/lib"; use i3test; +use AnyEvent::I3; -BEGIN { - use_ok('IO::Socket::UNIX') or BAIL_OUT('Cannot load IO::Socket::UNIX'); - use_ok('X11::XCB::Connection') or BAIL_OUT('Cannot load X11::XCB::Connection'); -} - -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); -isa_ok($sock, 'IO::Socket::UNIX'); +my $i3 = i3; #################### # Request workspaces #################### -# message type 1 is GET_WORKSPACES -my $message = "i3-ipc" . pack("LL", 0, 1); -$sock->write($message); - -####################################### -# Test the reply format for correctness -####################################### - -# The following lines duplicate functionality from recv_ipc_command -# to have it included in the test-suite. -my $buffer; -$sock->read($buffer, length($message)); -is(substr($buffer, 0, length("i3-ipc")), "i3-ipc", "ipc message received"); -my ($len, $type) = unpack("LL", substr($buffer, 6)); -is($type, 1, "correct reply type"); - -# read the payload -$sock->read($buffer, $len); -my $workspaces; - -######################### -# Actually test the reply -######################### - -lives_ok { $workspaces = decode_json($buffer) } 'JSON could be decoded'; +my $workspaces = $i3->get_workspaces->recv; ok(@{$workspaces} > 0, "More than zero workspaces found"); diff --git a/testcases/t/lib/i3test.pm b/testcases/t/lib/i3test.pm index 540551b0..eb4167a8 100644 --- a/testcases/t/lib/i3test.pm +++ b/testcases/t/lib/i3test.pm @@ -29,33 +29,4 @@ sub open_standard_window { return $window; } -sub format_ipc_command { - my $msg = shift; - my $len; - - { use bytes; $len = length($msg); } - - my $message = "i3-ipc" . pack("LL", $len, 0) . $msg; - - return $message; -} - -sub recv_ipc_command { - my ($sock, $expected) = @_; - - my $buffer; - # header is 14 bytes ("i3-ipc" + 32 bit + 32 bit) - $sock->read($buffer, 14); - return undef unless substr($buffer, 0, length("i3-ipc")) eq "i3-ipc"; - - my ($len, $type) = unpack("LL", substr($buffer, 6)); - - return undef unless $type == $expected; - - # read the payload - $sock->read($buffer, $len); - - decode_json($buffer) -} - 1 From e7b354e0dcf031b331c89f095fc81fcd1ce9fd9f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Mar 2010 15:22:28 +0100 Subject: [PATCH 233/247] docs: update for new ipc socket path --- docs/ipc | 4 ++-- docs/userguide | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/ipc b/docs/ipc index 4e46bc9e..f65ae484 100644 --- a/docs/ipc +++ b/docs/ipc @@ -12,7 +12,7 @@ The method of choice for IPC in our case is a unix socket because it has very little overhead on both sides and is usually available without headaches in most languages. In the default configuration file, no ipc-socket path is specified and thus no socket is created. The standard path (which +i3-msg+ and -+i3-input+ use) is +/tmp/i3-ipc.sock+. ++i3-input+ use) is +~/.i3/ipc.sock+. == Establishing a connection @@ -21,7 +21,7 @@ snippet illustrates this in Perl: ------------------------------------------------------------- use IO::Socket::UNIX; -my $sock = IO::Socket::UNIX->new(Peer => '/tmp/i3-ipc.sock'); +my $sock = IO::Socket::UNIX->new(Peer => '~/.i3/ipc.sock'); ------------------------------------------------------------- == Sending messages to i3 diff --git a/docs/userguide b/docs/userguide index 5cb1fe58..c706c631 100644 --- a/docs/userguide +++ b/docs/userguide @@ -504,11 +504,11 @@ programs to get information from i3, such as the current workspaces (to display a workspace bar), and to control i3. To enable it, you have to configure a path where the unix socket will be -stored. The default path is +/tmp/i3-ipc.sock+. +stored. The default path is +~/.i3/ipc.sock+. *Examples*: ---------------------------- -ipc-socket /tmp/i3-ipc.sock +ipc-socket ~/.i3/ipc.sock ---------------------------- You can then use the +i3-msg+ application to perform any command listed in From 996884db180576e553836b613164f689dc1e0536 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Mar 2010 15:30:09 +0100 Subject: [PATCH 234/247] mention -V -d all in docs/debugging --- docs/debugging | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/debugging b/docs/debugging index ca680f24..d52edea9 100644 --- a/docs/debugging +++ b/docs/debugging @@ -1,7 +1,7 @@ Debugging i3: How To ==================== Michael Stapelberg -April 2009 +March 2010 This document describes how to debug i3 suitably for sending us useful bug reports, even if you have no clue of C programming. @@ -12,14 +12,14 @@ debugging and/or need further help, do not hesitate to contact us! == Enabling logging -i3 spits out much information onto stdout. To have a clearly defined place -where log files will be saved, you should redirect stdout and stderr in -xsession. While you’re at it, putting each run of i3 in a separate log file -with date/time in it is a good idea to not get confused about the different -log files later on. +i3 spits out much information onto stdout, if told so. To have a clearly +defined place where log files will be saved, you should redirect stdout and +stderr in xsession. While you’re at it, putting each run of i3 in a separate +log file with date/time in it is a good idea to not get confused about the +different log files later on. -------------------------------------------------------------------- -exec /usr/bin/i3 >/home/michael/i3/i3log-$(date +'%F-%k-%M-%S') 2>&1 +exec /usr/bin/i3 -V -d all >/home/michael/i3/i3log-$(date +'%F-%k-%M-%S') 2>&1 -------------------------------------------------------------------- == Enabling core dumps From 170bb7857f9be291e9ace3e94ad3c2e730733b1c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Mar 2010 15:43:43 +0100 Subject: [PATCH 235/247] manpages: extract manpage for i3-wsbar using pod2man(1) --- man/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/man/Makefile b/man/Makefile index 151b9abc..8b82c40d 100644 --- a/man/Makefile +++ b/man/Makefile @@ -2,6 +2,8 @@ all: a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3.man a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3-msg.man a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3-input.man + pod2man ../i3-wsbar > i3-wsbar.1 + clean: for file in "i3 i3-msg i3-input"; \ do \ From 7063036d67465849a91c729f1efdbdb4d2e4138b Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Mar 2010 15:53:03 +0100 Subject: [PATCH 236/247] makefile: include i3-wsbar in dist --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c73723ad..62c94bfb 100644 --- a/Makefile +++ b/Makefile @@ -73,7 +73,7 @@ dist: distclean [ ! -d i3-${VERSION} ] || rm -rf i3-${VERSION} [ ! -e i3-${VERSION}.tar.bz2 ] || rm i3-${VERSION}.tar.bz2 mkdir i3-${VERSION} - cp DEPENDS GOALS LICENSE PACKAGE-MAINTAINER TODO RELEASE-NOTES-${VERSION} i3.config i3.desktop i3.welcome pseudo-doc.doxygen Makefile i3-${VERSION} + cp DEPENDS GOALS LICENSE PACKAGE-MAINTAINER TODO RELEASE-NOTES-${VERSION} i3.config i3.desktop i3.welcome i3-wsbar pseudo-doc.doxygen Makefile i3-${VERSION} cp -r src i3-msg include man i3-${VERSION} # Only copy toplevel documentation (important stuff) mkdir i3-${VERSION}/docs From 704c60096901b45a0f37d8833b620a6c3b92b047 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Mar 2010 15:55:11 +0100 Subject: [PATCH 237/247] makefile: also clean src/cfgparse.output --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 62c94bfb..dfdc7a74 100644 --- a/Makefile +++ b/Makefile @@ -89,7 +89,7 @@ dist: distclean rm -rf i3-${VERSION} clean: - rm -f src/*.o src/cfgparse.tab.{c,h} src/cfgparse.yy.c loglevels.tmp include/loglevels.h + rm -f src/*.o src/cfgparse.tab.{c,h} src/cfgparse.output src/cfgparse.yy.c loglevels.tmp include/loglevels.h $(MAKE) -C docs clean $(MAKE) -C man clean $(MAKE) TOPDIR=$(TOPDIR) -C i3-msg clean From 70369da37ae6ede84afe21b5dbac5666fa7d944e Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Mar 2010 15:56:27 +0100 Subject: [PATCH 238/247] debian: update packaging --- debian/control | 12 ++++++------ debian/i3-wm.docs | 2 ++ debian/rules | 1 + 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/debian/control b/debian/control index 8dde5b80..446f8a22 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Section: utils Priority: extra Maintainer: Michael Stapelberg DM-Upload-Allowed: yes -Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev +Build-Depends: debhelper (>= 5), libx11-dev, libxcb-aux0-dev (>= 0.3.3), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-event1-dev (>= 0.3.3), libxcb-property1-dev (>= 0.3.3), libxcb-atom1-dev (>= 0.3.3), libxcb-icccm1-dev (>= 0.3.3), asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev, perl Standards-Version: 3.8.3 Homepage: http://i3.zekjur.net/ @@ -27,12 +27,12 @@ Provides: x-window-manager Suggests: rxvt-unicode | x-terminal-emulator Recommends: xfonts-base Description: an improved dynamic tiling window manager - Key features of i3 are correct implementation of Xinerama (workspaces are + Key features of i3 are good support of multi-monitor setups (workspaces are assigned to virtual screens, i3 does the right thing when attaching new - monitors), XrandR support (not done yet), horizontal and vertical columns - (think of a table) in tiling. Also, special focus is on writing clean, - readable and well documented code. i3 uses xcb for asynchronous - communication with X11, and has several measures to be very fast. + monitors), XRandR support, horizontal and vertical columns (think of a table) + in tiling. Also, special focus is on writing clean, readable and well + documented code. i3 uses XCB for asynchronous communication with X11, and has + several measures to be very fast. . Please be aware i3 is primarily targeted at advanced users and developers. diff --git a/debian/i3-wm.docs b/debian/i3-wm.docs index 587c93a3..a8747817 100644 --- a/debian/i3-wm.docs +++ b/debian/i3-wm.docs @@ -8,3 +8,5 @@ docs/two_columns.png docs/two_terminals.png docs/modes.png docs/stacklimit.png +docs/ipc.html +docs/multi-monitor.html diff --git a/debian/rules b/debian/rules index 43545a01..981da126 100755 --- a/debian/rules +++ b/debian/rules @@ -45,6 +45,7 @@ install: build cp man/i3.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1 cp man/i3-msg.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1 cp man/i3-input.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1 + cp man/i3-wsbar.1 $(CURDIR)/debian/i3-wm/usr/share/man/man1 # Build architecture-independent files here. From 200cd71b4d7712b1fea021a444d50254ce2cf720 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Mar 2010 16:05:26 +0100 Subject: [PATCH 239/247] =?UTF-8?q?manpages:=20don=E2=80=99t=20regenerate?= =?UTF-8?q?=20manpages=20on=20every=20'make'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- man/Makefile | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/man/Makefile b/man/Makefile index 8b82c40d..4d7836ec 100644 --- a/man/Makefile +++ b/man/Makefile @@ -1,11 +1,18 @@ -all: - a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3.man - a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3-msg.man - a2x -f manpage --asciidoc-opts="-f asciidoc.conf" i3-input.man - pod2man ../i3-wsbar > i3-wsbar.1 +A2M:=a2x -f manpage --asciidoc-opts="-f asciidoc.conf" + +all: i3.1 i3-msg.1 i3-input.1 i3-wsbar.1 + +%.1: %.man asciidoc.conf + ${A2M} $< + +i3-wsbar.1: ../i3-wsbar + pod2man $^ > $@ clean: for file in "i3 i3-msg i3-input"; \ do \ rm -f $${file}.1 $${file}.html $${file}.xml; \ done + +distclean: clean + rm -f *.1 From 569ac46b2b54ef3e9a43fa3cec743b55e10be1d0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Mar 2010 16:05:46 +0100 Subject: [PATCH 240/247] manpage: update i3(1) --- man/i3.man | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/man/i3.man b/man/i3.man index 5877f143..109248e5 100644 --- a/man/i3.man +++ b/man/i3.man @@ -44,6 +44,18 @@ create i3. Please be aware that i3 is primarily targeted at advanced users and developers. +=== IMPORTANT NOTE TO nVidia BINARY DRIVER USERS + +If you are using the nVidia binary graphics driver (also known as 'blob') +you need to use the +--force-xinerama+ flag (in your .xsession) when starting +i3, like so: + +---------------------------------------------- +exec i3 --force-xinerama -V >>~/.i3/i3log 2>&1 +---------------------------------------------- + +See also docs/multi-monitor for the full explanation. + === TERMINOLOGY Client:: @@ -279,7 +291,7 @@ ulimit -c unlimited # Start i3 and log to ~/.i3/logfile echo "Starting at $(date)" >> ~/.i3/logfile -exec /usr/bin/i3 >> ~/.i3/logfile +exec /usr/bin/i3 -V -d all >> ~/.i3/logfile ------------------------------------------------------------- == TODO @@ -296,7 +308,7 @@ and the "how to hack" guide. If you are building from source, run: You can also access these documents online at http://i3.zekjur.net/ -i3-input(1), i3-msg(1) +i3-input(1), i3-msg(1), i3-wsbar(1) == AUTHOR From 2a3d44a79460afb78bea0d6f899857b604132502 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Mar 2010 16:05:56 +0100 Subject: [PATCH 241/247] manpage: update version --- man/asciidoc.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/asciidoc.conf b/man/asciidoc.conf index d3d5cce8..e15695d2 100644 --- a/man/asciidoc.conf +++ b/man/asciidoc.conf @@ -7,7 +7,7 @@ template::[header-declarations] {mantitle} {manvolnum} i3 -delta +epsilon i3 Manual From 7d6e80b5ef8656bf35a8dc7471d36ad64ef279fb Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Mar 2010 17:04:56 +0100 Subject: [PATCH 242/247] Bugfix: Ignore enter_notify when warping pointer (makes "goto" work correctly) --- include/handlers.h | 8 ++++++++ src/client.c | 8 +++++++- src/handlers.c | 8 +++++++- src/workspace.c | 5 ++++- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/include/handlers.h b/include/handlers.h index c7cbb322..7d0662e1 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -13,6 +13,14 @@ #include +/** + * Adds the sequence number of an event to the ignored events. + * Useful to ignore for example the enter notify caused by a pointer warp of + * i3. + * + */ +void add_ignore_event(const int sequence); + /** * There was a key press. We compare this key code with our bindings table and * pass the bound action to parse_command(). diff --git a/src/client.c b/src/client.c index 9c136ca6..ba3babe0 100644 --- a/src/client.c +++ b/src/client.c @@ -29,6 +29,7 @@ #include "workspace.h" #include "config.h" #include "log.h" +#include "handlers.h" /* * Removes the given client from the container, either because it will be inserted into another @@ -62,7 +63,12 @@ void client_remove_from_container(xcb_connection_t *conn, Client *client, Contai void client_warp_pointer_into(xcb_connection_t *conn, Client *client) { int mid_x = client->rect.width / 2, mid_y = client->rect.height / 2; - xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, mid_x, mid_y); + xcb_void_cookie_t cookie; + cookie = xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, mid_x, mid_y); + /* We need to add this event twice because we get one enter_notify for + * the child and one for the frame */ + add_ignore_event(cookie.sequence); + add_ignore_event(cookie.sequence); } /* diff --git a/src/handlers.c b/src/handlers.c index 624c3430..5eddf244 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -46,7 +46,13 @@ changing workspaces */ static SLIST_HEAD(ignore_head, Ignore_Event) ignore_events; -static void add_ignore_event(const int sequence) { +/* + * Adds the sequence number of an event to the ignored events. + * Useful to ignore for example the enter notify caused by a pointer warp of + * i3. + * + */ +void add_ignore_event(const int sequence) { struct Ignore_Event *event = smalloc(sizeof(struct Ignore_Event)); event->sequence = sequence; diff --git a/src/workspace.c b/src/workspace.c index c950df8f..2798687d 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -29,6 +29,7 @@ #include "log.h" #include "ewmh.h" #include "ipc.h" +#include "handlers.h" /* * Returns a pointer to the workspace with the given number (starting at 0), @@ -139,8 +140,10 @@ void workspace_show(xcb_connection_t *conn, int workspace) { need_warp = true; else { Rect *dims = &(c_ws->output->rect); - xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, + xcb_void_cookie_t cookie; + cookie = xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, dims->x + (dims->width / 2), dims->y + (dims->height / 2)); + add_ignore_event(cookie.sequence); } /* Re-decorate the old client, it’s not focused anymore */ From 78d4d18477226dc29933b361142e8f1adb14a1dc Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Mar 2010 17:06:25 +0100 Subject: [PATCH 243/247] bump copyright year --- src/mainx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mainx.c b/src/mainx.c index e779361b..7e1b394b 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -231,7 +231,7 @@ int main(int argc, char *argv[], char *env[]) { only_check_config = true; break; case 'v': - printf("i3 version " I3_VERSION " © 2009 Michael Stapelberg and contributors\n"); + printf("i3 version " I3_VERSION " © 2009-2010 Michael Stapelberg and contributors\n"); exit(EXIT_SUCCESS); case 'V': set_verbosity(true); From a6d22f005dd29a3391c52425ec316bfaf0177c73 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 27 Mar 2010 17:51:28 +0100 Subject: [PATCH 244/247] Revert "Bugfix: Ignore enter_notify when warping pointer (makes "goto" work correctly)" This reverts commit 7d6e80b5ef8656bf35a8dc7471d36ad64ef279fb. Instead fix it by setting focus correctly before causig the warp. --- include/handlers.h | 8 -------- src/client.c | 8 +------- src/commands.c | 2 +- src/handlers.c | 8 +------- src/workspace.c | 5 +---- 5 files changed, 4 insertions(+), 27 deletions(-) diff --git a/include/handlers.h b/include/handlers.h index 7d0662e1..c7cbb322 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -13,14 +13,6 @@ #include -/** - * Adds the sequence number of an event to the ignored events. - * Useful to ignore for example the enter notify caused by a pointer warp of - * i3. - * - */ -void add_ignore_event(const int sequence); - /** * There was a key press. We compare this key code with our bindings table and * pass the bound action to parse_command(). diff --git a/src/client.c b/src/client.c index ba3babe0..9c136ca6 100644 --- a/src/client.c +++ b/src/client.c @@ -29,7 +29,6 @@ #include "workspace.h" #include "config.h" #include "log.h" -#include "handlers.h" /* * Removes the given client from the container, either because it will be inserted into another @@ -63,12 +62,7 @@ void client_remove_from_container(xcb_connection_t *conn, Client *client, Contai void client_warp_pointer_into(xcb_connection_t *conn, Client *client) { int mid_x = client->rect.width / 2, mid_y = client->rect.height / 2; - xcb_void_cookie_t cookie; - cookie = xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, mid_x, mid_y); - /* We need to add this event twice because we get one enter_notify for - * the child and one for the frame */ - add_ignore_event(cookie.sequence); - add_ignore_event(cookie.sequence); + xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, mid_x, mid_y); } /* diff --git a/src/commands.c b/src/commands.c index c0cb8782..b2649bee 100644 --- a/src/commands.c +++ b/src/commands.c @@ -73,8 +73,8 @@ static void jump_to_mark(xcb_connection_t *conn, const char *mark) { if (current->mark == NULL || strcmp(current->mark, mark) != 0) continue; - workspace_show(conn, current->workspace->num + 1); set_focus(conn, current, true); + workspace_show(conn, current->workspace->num + 1); return; } diff --git a/src/handlers.c b/src/handlers.c index 5eddf244..624c3430 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -46,13 +46,7 @@ changing workspaces */ static SLIST_HEAD(ignore_head, Ignore_Event) ignore_events; -/* - * Adds the sequence number of an event to the ignored events. - * Useful to ignore for example the enter notify caused by a pointer warp of - * i3. - * - */ -void add_ignore_event(const int sequence) { +static void add_ignore_event(const int sequence) { struct Ignore_Event *event = smalloc(sizeof(struct Ignore_Event)); event->sequence = sequence; diff --git a/src/workspace.c b/src/workspace.c index 2798687d..c950df8f 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -29,7 +29,6 @@ #include "log.h" #include "ewmh.h" #include "ipc.h" -#include "handlers.h" /* * Returns a pointer to the workspace with the given number (starting at 0), @@ -140,10 +139,8 @@ void workspace_show(xcb_connection_t *conn, int workspace) { need_warp = true; else { Rect *dims = &(c_ws->output->rect); - xcb_void_cookie_t cookie; - cookie = xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, + xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, dims->x + (dims->width / 2), dims->y + (dims->height / 2)); - add_ignore_event(cookie.sequence); } /* Re-decorate the old client, it’s not focused anymore */ From 6016b6333f3413c9dcb550f8f9d51749bebf8eb1 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 29 Mar 2010 19:18:45 +0200 Subject: [PATCH 245/247] =?UTF-8?q?Bugfix:=20Don=E2=80=99t=20render=20cont?= =?UTF-8?q?ainers=20which=20are=20not=20visible=20upon=20hint=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/handlers.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers.c b/src/handlers.c index 624c3430..67b377f8 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -960,7 +960,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w client->force_reconfigure = true; - if (client->container != NULL) { + if (client->container != NULL && workspace_is_visible(client->workspace)) { render_container(conn, client->container); xcb_flush(conn); } From 6a7b0f92779405df1ef0ea9b7c950899367cd88f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 30 Mar 2010 08:12:06 +0200 Subject: [PATCH 246/247] Bugfix: The last commit also needs to be applied to some other pieces of code (Thanks ffMeta) --- src/handlers.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/handlers.c b/src/handlers.c index 67b377f8..d9fa0d2d 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -636,6 +636,9 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, if (client->dock) return 1; + if (!workspace_is_visible(client->workspace)) + return 1; + int mode = container_mode(client->container, true); if (mode == MODE_STACK || mode == MODE_TABBED) render_container(conn, client->container); @@ -702,6 +705,9 @@ int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t if (client->dock) return 1; + if (!workspace_is_visible(client->workspace)) + return 1; + if (client->container != NULL && (client->container->mode == MODE_STACK || client->container->mode == MODE_TABBED)) @@ -1001,7 +1007,6 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t LOG("Urgency flag changed to %d\n", client->urgent); workspace_update_urgent_flag(client->workspace); - redecorate_window(conn, client); /* If the workspace this client is on is not visible, we need to redraw * the workspace bar */ @@ -1009,6 +1014,8 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t Output *output = client->workspace->output; render_workspace(conn, output, output->current_workspace); xcb_flush(conn); + } else { + redecorate_window(conn, client); } return 1; From 86e196c57bf6780292a6143a9d294785194fe93a Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Tue, 30 Mar 2010 11:56:39 +0200 Subject: [PATCH 247/247] Bugfix: Unset global fullscreen clients from all workspaces (Thanks Sasha) --- src/handlers.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/handlers.c b/src/handlers.c index d9fa0d2d..a173777c 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -486,9 +486,14 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti client = table_remove(&by_child, event->window); - /* If this was the fullscreen client, we need to unset it */ - if (client->fullscreen) - client->workspace->fullscreen_client = NULL; + /* If this was the fullscreen client, we need to unset it from all + * workspaces it was on (global fullscreen) */ + if (client->fullscreen) { + Workspace *ws; + TAILQ_FOREACH(ws, workspaces, workspaces) + if (ws->fullscreen_client == client) + ws->fullscreen_client = NULL; + } /* Clients without a container are either floating or dock windows */ if (client->container != NULL) {