From 167bdd26b71789b9b1ad28883fbd2d3998944080 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 13 May 2011 20:41:03 +0200 Subject: [PATCH] Argument for 'kill' for killing a specific window (now default) or the whole client (+test) Use 'kill window' to kill a specific window (for example only one specific popup), use 'kill client' to kill the whole application (or X11 connection to be specific). --- include/data.h | 4 ++ include/tree.h | 4 +- include/x.h | 2 +- src/cmdparse.l | 4 +- src/cmdparse.y | 17 ++++++-- src/con.c | 2 +- src/floating.c | 4 +- src/handlers.c | 2 +- src/randr.c | 2 +- src/tree.c | 16 +++---- src/workspace.c | 2 +- src/x.c | 11 +++-- testcases/t/64-kill-win-vs-client.t | 66 +++++++++++++++++++++++++++++ 13 files changed, 111 insertions(+), 25 deletions(-) create mode 100644 testcases/t/64-kill-win-vs-client.t diff --git a/include/data.h b/include/data.h index d4836dfe..1165da99 100644 --- a/include/data.h +++ b/include/data.h @@ -43,6 +43,10 @@ typedef enum { D_LEFT, D_RIGHT, D_UP, D_DOWN } direction_t; typedef enum { NO_ORIENTATION = 0, HORIZ, VERT } orientation_t; typedef enum { BS_NORMAL = 0, BS_NONE = 1, BS_1PIXEL = 2 } border_style_t; +/** parameter to specify whether tree_close() and x_window_kill() should kill + * only this specific window or the whole X11 client */ +typedef enum { DONT_KILL_WINDOW = 0, KILL_WINDOW = 1, KILL_CLIENT = 2 } kill_window_t; + enum { BIND_NONE = 0, BIND_SHIFT = XCB_MOD_MASK_SHIFT, /* (1 << 0) */ diff --git a/include/tree.h b/include/tree.h index 8ffeca0d..98358edd 100644 --- a/include/tree.h +++ b/include/tree.h @@ -56,7 +56,7 @@ void tree_render(); * Closes the current container using tree_close(). * */ -void tree_close_con(); +void tree_close_con(kill_window_t kill_window); /** * Changes focus in the given way (next/previous) and given orientation @@ -71,7 +71,7 @@ void tree_next(char way, orientation_t orientation); * and the window is expected to kill itself. * */ -bool tree_close(Con *con, bool kill_window, bool dont_kill_parent); +bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent); /** * Loads tree from ~/.i3/_restart.json (used for in-place restarts). diff --git a/include/x.h b/include/x.h index d110d92c..b8ead5ee 100644 --- a/include/x.h +++ b/include/x.h @@ -52,7 +52,7 @@ bool window_supports_protocol(xcb_window_t window, xcb_atom_t atom); * Kills the given X11 window using WM_DELETE_WINDOW (if supported). * */ -void x_window_kill(xcb_window_t window); +void x_window_kill(xcb_window_t window, kill_window_t kill_window); /** * Draws the decoration of the given container onto its parent. diff --git a/src/cmdparse.l b/src/cmdparse.l index 3e5dc889..6e33c950 100644 --- a/src/cmdparse.l +++ b/src/cmdparse.l @@ -2,7 +2,7 @@ * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE) + * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) * * cmdparse.l: the lexer for commands you send to i3 (or bind on keys) * @@ -92,6 +92,8 @@ exit { return TOK_EXIT; } reload { return TOK_RELOAD; } restart { return TOK_RESTART; } kill { return TOK_KILL; } +window { return TOK_WINDOW; } +client { return TOK_CLIENT; } fullscreen { return TOK_FULLSCREEN; } global { return TOK_GLOBAL; } layout { return TOK_LAYOUT; } diff --git a/src/cmdparse.y b/src/cmdparse.y index 5dacc64f..73658eb2 100644 --- a/src/cmdparse.y +++ b/src/cmdparse.y @@ -91,7 +91,7 @@ char *parse_cmd(const char *new) { %} -%expect 4 +%expect 5 %error-verbose %lex-param { struct context *context } @@ -107,6 +107,8 @@ char *parse_cmd(const char *new) { %token TOK_RELOAD "reload" %token TOK_RESTART "restart" %token TOK_KILL "kill" +%token TOK_WINDOW "window" +%token TOK_CLIENT "client" %token TOK_FULLSCREEN "fullscreen" %token TOK_GLOBAL "global" %token TOK_LAYOUT "layout" @@ -161,6 +163,7 @@ char *parse_cmd(const char *new) { %type resize_px %type resize_way %type resize_tiling +%type optional_kill_mode %% @@ -398,24 +401,30 @@ focus: ; kill: - TOK_KILL + TOK_KILL optional_kill_mode { owindow *current; printf("killing!\n"); /* check if the match is empty, not if the result is empty */ if (match_is_empty(¤t_match)) - tree_close_con(); + tree_close_con($2); else { TAILQ_FOREACH(current, &owindows, owindows) { printf("matching: %p / %s\n", current->con, current->con->name); - tree_close(current->con, true, false); + tree_close(current->con, $2, false); } } } ; +optional_kill_mode: + /* empty */ { $$ = KILL_WINDOW; } + | WHITESPACE TOK_WINDOW { $$ = KILL_WINDOW; } + | WHITESPACE TOK_CLIENT { $$ = KILL_CLIENT; } + ; + workspace: TOK_WORKSPACE WHITESPACE STR { diff --git a/src/con.c b/src/con.c index 4bfd5b07..45021f5f 100644 --- a/src/con.c +++ b/src/con.c @@ -885,7 +885,7 @@ static void con_on_remove_child(Con *con) { int children = con_num_children(con); if (children == 0) { DLOG("Container empty, closing\n"); - tree_close(con, false, false); + tree_close(con, DONT_KILL_WINDOW, false); return; } } diff --git a/src/floating.c b/src/floating.c index 2ac4afda..2f3f9bac 100644 --- a/src/floating.c +++ b/src/floating.c @@ -85,7 +85,7 @@ void floating_enable(Con *con, bool automatic) { /* check if the parent container is empty and close it if so */ if ((con->parent->type == CT_CON || con->parent->type == CT_FLOATING_CON) && con_num_children(con->parent) == 0) { DLOG("Old container empty after setting this child to floating, closing\n"); - tree_close(con->parent, false, false); + tree_close(con->parent, DONT_KILL_WINDOW, false); } char *name; @@ -186,7 +186,7 @@ void floating_disable(Con *con, bool automatic) { /* 2: kill parent container */ TAILQ_REMOVE(&(con->parent->parent->floating_head), con->parent, floating_windows); TAILQ_REMOVE(&(con->parent->parent->focus_head), con->parent, focused); - tree_close(con->parent, false, false); + tree_close(con->parent, DONT_KILL_WINDOW, false); /* 3: re-attach to the parent of the currently focused con on the workspace * this floating con was on */ diff --git a/src/handlers.c b/src/handlers.c index c63f236c..f1e1c4b0 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -466,7 +466,7 @@ static int handle_unmap_notify_event(xcb_unmap_notify_event_t *event) { return 1; } - tree_close(con, false, false); + tree_close(con, DONT_KILL_WINDOW, false); tree_render(); x_push_changes(croot); return 1; diff --git a/src/randr.c b/src/randr.c index 0c43670d..b61e3090 100644 --- a/src/randr.c +++ b/src/randr.c @@ -709,7 +709,7 @@ void randr_query_outputs() { } DLOG("destroying disappearing con %p\n", output->con); - tree_close(output->con, false, true); + tree_close(output->con, DONT_KILL_WINDOW, true); DLOG("Done. Should be fine now\n"); output->con = NULL; } diff --git a/src/tree.c b/src/tree.c index 411e5251..8d55c14b 100644 --- a/src/tree.c +++ b/src/tree.c @@ -99,7 +99,7 @@ static bool _is_con_mapped(Con *con) { * and the window is expected to kill itself. * */ -bool tree_close(Con *con, bool kill_window, bool dont_kill_parent) { +bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent) { bool was_mapped = con->mapped; Con *parent = con->parent; @@ -133,8 +133,8 @@ bool tree_close(Con *con, bool kill_window, bool dont_kill_parent) { } if (con->window != NULL) { - if (kill_window) { - x_window_kill(con->window->id); + if (kill_window != DONT_KILL_WINDOW) { + x_window_kill(con->window->id, kill_window); return false; } else { /* un-parent the window */ @@ -165,7 +165,7 @@ bool tree_close(Con *con, bool kill_window, bool dont_kill_parent) { if (con_is_floating(con)) { Con *ws = con_get_workspace(con); DLOG("Container was floating, killing floating container\n"); - tree_close(parent, false, false); + tree_close(parent, DONT_KILL_WINDOW, false); DLOG("parent container killed\n"); if (con == focused) { DLOG("This is the focused container, i need to find another one to focus. I start looking at ws = %p\n", ws); @@ -192,7 +192,7 @@ bool tree_close(Con *con, bool kill_window, bool dont_kill_parent) { } if (was_mapped || con == focused) { - if (kill_window || !dont_kill_parent || con == focused) { + if ((kill_window != DONT_KILL_WINDOW) || !dont_kill_parent || con == focused) { DLOG("focusing %p / %s\n", next, next->name); /* TODO: check if the container (or one of its children) was focused */ if (next->type == CT_DOCKAREA) { @@ -220,7 +220,7 @@ bool tree_close(Con *con, bool kill_window, bool dont_kill_parent) { * Closes the current container using tree_close(). * */ -void tree_close_con() { +void tree_close_con(kill_window_t kill_window) { assert(focused != NULL); if (focused->type == CT_WORKSPACE) { LOG("Cannot close workspace\n"); @@ -232,7 +232,7 @@ void tree_close_con() { assert(focused->type != CT_ROOT); /* Kill con */ - tree_close(focused, true, false); + tree_close(focused, kill_window, false); } /* @@ -463,7 +463,7 @@ void tree_flatten(Con *con) { /* 4: close the redundant cons */ DLOG("closing redundant cons\n"); - tree_close(con, false, true); + tree_close(con, DONT_KILL_WINDOW, true); /* Well, we got to abort the recursion here because we destroyed the * container. However, if tree_flatten() is called sufficiently often, diff --git a/src/workspace.c b/src/workspace.c index 8104aa89..7ff31157 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -244,7 +244,7 @@ void workspace_show(const char *num) { /* check if this workspace is currently visible */ if (!workspace_is_visible(old)) { LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name); - tree_close(old, false, false); + tree_close(old, DONT_KILL_WINDOW, false); ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}"); changed_num_workspaces = true; } diff --git a/src/x.c b/src/x.c index 76add0ff..4fd8a8ba 100644 --- a/src/x.c +++ b/src/x.c @@ -199,11 +199,16 @@ bool window_supports_protocol(xcb_window_t window, xcb_atom_t atom) { * Kills the given X11 window using WM_DELETE_WINDOW (if supported). * */ -void x_window_kill(xcb_window_t window) { +void x_window_kill(xcb_window_t window, kill_window_t kill_window) { /* if this window does not support WM_DELETE_WINDOW, we kill it the hard way */ if (!window_supports_protocol(window, A_WM_DELETE_WINDOW)) { - LOG("Killing window the hard way\n"); - xcb_kill_client(conn, window); + if (kill_window == KILL_WINDOW) { + LOG("Killing specific window 0x%08x\n", window); + xcb_destroy_window(conn, window); + } else { + LOG("Killing the X11 client which owns window 0x%08x\n", window); + xcb_kill_client(conn, window); + } return; } diff --git a/testcases/t/64-kill-win-vs-client.t b/testcases/t/64-kill-win-vs-client.t new file mode 100644 index 00000000..2e0669ba --- /dev/null +++ b/testcases/t/64-kill-win-vs-client.t @@ -0,0 +1,66 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Tests if WM_STATE is WM_STATE_NORMAL when mapped and WM_STATE_WITHDRAWN when +# unmapped. +# +use X11::XCB qw(:all); +use X11::XCB::Connection; +use i3test; + +my $x = X11::XCB::Connection->new; + +sub two_windows { + my $tmp = fresh_workspace; + + ok(@{get_ws_content($tmp)} == 0, 'no containers yet'); + + my $first = open_standard_window($x); + my $second = open_standard_window($x); + + is($x->input_focus, $second->id, 'second window focused'); + ok(@{get_ws_content($tmp)} == 2, 'two containers opened'); + + return $tmp; +} + +############################################################## +# 1: open two windows (in the same client), kill one and see if +# the other one is still there +############################################################## + +my $tmp = two_windows; + +cmd 'kill'; + +sleep 0.25; + +ok(@{get_ws_content($tmp)} == 1, 'one container left after killing'); + +############################################################## +# 2: same test case as test 1, but with the explicit variant +# 'kill window' +############################################################## + +my $tmp = two_windows; + +cmd 'kill window'; + +sleep 0.25; + +ok(@{get_ws_content($tmp)} == 1, 'one container left after killing'); + +############################################################## +# 3: open two windows (in the same client), use 'kill client' +# and check if both are gone +############################################################## + +my $tmp = two_windows; + +cmd 'kill client'; + +sleep 0.25; + +ok(@{get_ws_content($tmp)} == 0, 'no containers left after killing'); + +done_testing;