diff --git a/CMDMODE b/CMDMODE index 5e8ebbfa..6b8315e2 100644 --- a/CMDMODE +++ b/CMDMODE @@ -17,7 +17,7 @@ with := { [ ] }+ oder -special := [ exec | exit | restart ] +special := [ exec | kill | exit | restart ] an jeder Stelle kann mit escape abgebrochen werden diff --git a/i3.config b/i3.config index a40f13ad..d2cf13a0 100644 --- a/i3.config +++ b/i3.config @@ -72,6 +72,9 @@ bind Mod1+Shift+19 m10 # Mod1+Enter starts a new terminal bind Mod1+36 exec /usr/bin/urxvt +# Mod1+Shift+q kills the current client +bind Mod1+Shift+24 kill + # Mod1+v starts dmenu and launches the selected application # for now, we don’t have an own launcher bind Mod1+55 exec /usr/bin/dmenu_run diff --git a/include/i3.h b/include/i3.h index 31da700a..7a104b21 100644 --- a/include/i3.h +++ b/include/i3.h @@ -18,7 +18,7 @@ #ifndef _I3_H #define _I3_H -#define NUM_ATOMS 10 +#define NUM_ATOMS 12 extern char *application_path; extern Display *xkbdpy; diff --git a/include/util.h b/include/util.h index 827ae6d1..a156f0a3 100644 --- a/include/util.h +++ b/include/util.h @@ -46,5 +46,6 @@ void leave_stack_mode(xcb_connection_t *conn, Container *container); void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode); void warp_pointer_into(xcb_connection_t *conn, Client *client); void toggle_fullscreen(xcb_connection_t *conn, Client *client); +void kill_window(xcb_connection_t *conn, Client *window); #endif diff --git a/include/xcb.h b/include/xcb.h index 338114e5..4302fbcb 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -46,6 +46,8 @@ enum { _NET_SUPPORTED = 0, _NET_WM_WINDOW_TYPE_DOCK, _NET_WM_DESKTOP, _NET_WM_STRUT_PARTIAL, + WM_PROTOCOLS, + WM_DELETE_WINDOW, UTF8_STRING }; diff --git a/man/i3.man b/man/i3.man index c5386c5c..dc6ebc42 100644 --- a/man/i3.man +++ b/man/i3.man @@ -108,6 +108,9 @@ Enable stacking layout for the current container. Mod1+d:: Enable default layout for the current container. +Mod1+Shift+q:: +Kills the current client. + Mod1+Shift+r:: Restarts i3 in place (without losing any windows, but the layout). @@ -136,6 +139,9 @@ bind Mod1+36 exec /usr/bin/urxvt # Start dmenu (Mod1+v) bind Mod1+55 exec /usr/bin/dmenu_run +# Kill current client (Mod1+Shift+q) +bind Mod1+Shift+24 kill + # Beamer on/off bind Mod1+73 exec /home/michael/toggle_beamer.sh diff --git a/src/commands.c b/src/commands.c index 3afd6c5e..a1187c3c 100644 --- a/src/commands.c +++ b/src/commands.c @@ -560,8 +560,10 @@ void parse_command(xcb_connection_t *conn, const char *command) { } /* Is it an ? */ - if (STARTS_WITH(command, "exit")) - exit(0); + if (STARTS_WITH(command, "exit")) { + LOG("User issued exit-command, exiting without error.\n"); + exit(EXIT_SUCCESS); + } /* Is it ? Then restart in place. */ if (STARTS_WITH(command, "restart")) { @@ -570,6 +572,17 @@ void parse_command(xcb_connection_t *conn, const char *command) { /* not reached */ } + if (STARTS_WITH(command, "kill")) { + if (CUR_CELL->currently_focused == NULL) { + LOG("There is no window to kill\n"); + return; + } + + LOG("Killing current window\n"); + kill_window(conn, CUR_CELL->currently_focused); + return; + } + /* Is it 'f' for fullscreen? */ if (command[0] == 'f') { if (CUR_CELL->currently_focused == NULL) diff --git a/src/mainx.c b/src/mainx.c index 0b0e1bb1..cfb170a3 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -375,6 +375,8 @@ int main(int argc, char *argv[], char *env[]) { REQUEST_ATOM(_NET_WM_DESKTOP); REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK); REQUEST_ATOM(_NET_WM_STRUT_PARTIAL); + REQUEST_ATOM(WM_PROTOCOLS); + REQUEST_ATOM(WM_DELETE_WINDOW); REQUEST_ATOM(UTF8_STRING); /* TODO: this has to be more beautiful somewhen */ @@ -475,6 +477,8 @@ int main(int argc, char *argv[], char *env[]) { GET_ATOM(_NET_WM_DESKTOP); GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK); GET_ATOM(_NET_WM_STRUT_PARTIAL); + GET_ATOM(WM_PROTOCOLS); + GET_ATOM(WM_DELETE_WINDOW); GET_ATOM(UTF8_STRING); xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, handle_window_type, NULL); diff --git a/src/util.c b/src/util.c index 1a2cc8c5..e8440b67 100644 --- a/src/util.c +++ b/src/util.c @@ -19,6 +19,8 @@ #include #include +#include + #include "i3.h" #include "data.h" #include "table.h" @@ -391,3 +393,54 @@ void toggle_fullscreen(xcb_connection_t *conn, Client *client) { xcb_flush(conn); } + +/* + * Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW) + * + */ +static bool client_supports_protocol(xcb_connection_t *conn, Client *client, xcb_atom_t atom) { + xcb_get_property_cookie_t cookie; + xcb_get_wm_protocols_reply_t protocols; + bool result = false; + + cookie = xcb_get_wm_protocols_unchecked(conn, client->child, atoms[WM_PROTOCOLS]); + if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1) + return false; + + /* Check if the client’s protocols have the requested atom set */ + for (uint32_t i = 0; i < protocols.atoms_len; i++) + if (protocols.atoms[i] == atom) + result = true; + + xcb_get_wm_protocols_reply_wipe(&protocols); + + return result; +} + +/* + * Kills the given window using WM_DELETE_WINDOW or xcb_kill_window + * + */ +void kill_window(xcb_connection_t *conn, Client *window) { + /* If the client does not support WM_DELETE_WINDOW, we kill it the hard way */ + if (!client_supports_protocol(conn, window, atoms[WM_DELETE_WINDOW])) { + LOG("Killing window the hard way\n"); + xcb_kill_client(conn, window->child); + return; + } + + xcb_client_message_event_t ev; + + memset(&ev, 0, sizeof(xcb_client_message_event_t)); + + ev.response_type = XCB_CLIENT_MESSAGE; + ev.window = window->child; + ev.type = atoms[WM_PROTOCOLS]; + ev.format = 32; + ev.data.data32[0] = atoms[WM_DELETE_WINDOW]; + ev.data.data32[1] = XCB_CURRENT_TIME; + + LOG("Sending WM_DELETE to the client\n"); + xcb_send_event(conn, false, window->child, XCB_EVENT_MASK_NO_EVENT, (char*)&ev); + xcb_flush(conn); +}