Implement putting clients onto specific workspaces ("assign" in the configfile)

This closes ticket #39
This commit is contained in:
Michael Stapelberg
2009-05-16 17:32:36 +02:00
parent 3ab4ecdb01
commit e79cca8f72
12 changed files with 461 additions and 195 deletions

133
src/client.c Normal file
View File

@ -0,0 +1,133 @@
/*
* vim:ts=8:expandtab
*
* i3 - an improved dynamic tiling window manager
*
* © 2009 Michael Stapelberg and contributors
*
* See file LICENSE for license information.
*
* client.c: holds all client-specific functions
*
*/
#include <stdlib.h>
#include <string.h>
#include <xcb/xcb.h>
#include <xcb/xcb_icccm.h>
#include "data.h"
#include "i3.h"
#include "xcb.h"
#include "util.h"
#include "queue.h"
/*
* Removes the given client from the container, either because it will be inserted into another
* one or because it was unmapped
*
*/
void client_remove_from_container(xcb_connection_t *conn, Client *client, Container *container) {
CIRCLEQ_REMOVE(&(container->clients), client, clients);
SLIST_REMOVE(&(container->workspace->focus_stack), client, Client, focus_clients);
/* If the container will be empty now and is in stacking mode, we need to
unmap the stack_win */
if (CIRCLEQ_EMPTY(&(container->clients)) && container->mode == MODE_STACK) {
struct Stack_Window *stack_win = &(container->stack_win);
stack_win->rect.height = 0;
xcb_unmap_window(conn, stack_win->window);
}
}
/*
* Warps the pointer into the given client (in the middle of it, to be specific), therefore
* selecting it
*
*/
void client_warp_pointer_into(xcb_connection_t *conn, Client *client) {
int mid_x = client->rect.width / 2,
mid_y = client->rect.height / 2;
xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, mid_x, mid_y);
}
/*
* Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
*
*/
static bool client_supports_protocol(xcb_connection_t *conn, Client *client, xcb_atom_t atom) {
xcb_get_property_cookie_t cookie;
xcb_get_wm_protocols_reply_t protocols;
bool result = false;
cookie = xcb_get_wm_protocols_unchecked(conn, client->child, atoms[WM_PROTOCOLS]);
if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1)
return false;
/* Check if the clients protocols have the requested atom set */
for (uint32_t i = 0; i < protocols.atoms_len; i++)
if (protocols.atoms[i] == atom)
result = true;
xcb_get_wm_protocols_reply_wipe(&protocols);
return result;
}
/*
* Kills the given window using WM_DELETE_WINDOW or xcb_kill_window
*
*/
void client_kill(xcb_connection_t *conn, Client *window) {
/* If the client does not support WM_DELETE_WINDOW, we kill it the hard way */
if (!client_supports_protocol(conn, window, atoms[WM_DELETE_WINDOW])) {
LOG("Killing window the hard way\n");
xcb_kill_client(conn, window->child);
return;
}
xcb_client_message_event_t ev;
memset(&ev, 0, sizeof(xcb_client_message_event_t));
ev.response_type = XCB_CLIENT_MESSAGE;
ev.window = window->child;
ev.type = atoms[WM_PROTOCOLS];
ev.format = 32;
ev.data.data32[0] = atoms[WM_DELETE_WINDOW];
ev.data.data32[1] = XCB_CURRENT_TIME;
LOG("Sending WM_DELETE to the client\n");
xcb_send_event(conn, false, window->child, XCB_EVENT_MASK_NO_EVENT, (char*)&ev);
xcb_flush(conn);
}
/*
* Checks if the given window class and title match the given client
* Window title is passed as "normal" string and as UCS-2 converted string for
* matching _NET_WM_NAME capable clients as well as those using legacy hints.
*
*/
bool client_matches_class_name(Client *client, char *to_class, char *to_title,
char *to_title_ucs, int to_title_ucs_len) {
/* Check if the given class is part of the window class */
if (strcasestr(client->window_class, to_class) == NULL)
return false;
/* If no title was given, were done */
if (to_title == NULL)
return true;
if (client->name_len > -1) {
/* UCS-2 converted window titles */
if (memmem(client->name, (client->name_len * 2), to_title_ucs, (to_title_ucs_len * 2)) == NULL)
return false;
} else {
/* Legacy hints */
if (strcasestr(client->name, to_title) == NULL)
return false;
}
return true;
}

View File

@ -23,6 +23,7 @@
#include "layout.h"
#include "i3.h"
#include "xinerama.h"
#include "client.h"
bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) {
/* If this container is empty, were done */
@ -240,7 +241,7 @@ static void move_current_window(xcb_connection_t *conn, direction_t direction) {
}
/* Remove it from the old container and put it into the new one */
remove_client_from_container(conn, current_client, container);
client_remove_from_container(conn, current_client, container);
if (new->currently_focused != NULL)
CIRCLEQ_INSERT_AFTER(&(new->clients), new->currently_focused, current_client, clients);
@ -458,7 +459,7 @@ static void move_current_window_to_workspace(xcb_connection_t *conn, int workspa
assert(to_container != NULL);
remove_client_from_container(conn, current_client, container);
client_remove_from_container(conn, current_client, container);
if (container->workspace->fullscreen_client == current_client)
container->workspace->fullscreen_client = NULL;
@ -538,7 +539,7 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
if (CUR_CELL->currently_focused != NULL) {
set_focus(conn, CUR_CELL->currently_focused, true);
if (need_warp) {
warp_pointer_into(conn, CUR_CELL->currently_focused);
client_warp_pointer_into(conn, CUR_CELL->currently_focused);
xcb_flush(conn);
}
}
@ -574,7 +575,7 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
if (CUR_CELL->currently_focused != NULL) {
set_focus(conn, CUR_CELL->currently_focused, true);
if (need_warp) {
warp_pointer_into(conn, CUR_CELL->currently_focused);
client_warp_pointer_into(conn, CUR_CELL->currently_focused);
xcb_flush(conn);
}
} else xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
@ -582,35 +583,6 @@ void show_workspace(xcb_connection_t *conn, int workspace) {
render_layout(conn);
}
/*
* Checks if the given window class and title match the given client
* Window title is passed as "normal" string and as UCS-2 converted string for
* matching _NET_WM_NAME capable clients as well as those using legacy hints.
*
*/
static bool client_matches_class_name(Client *client, char *to_class, char *to_title,
char *to_title_ucs, int to_title_ucs_len) {
/* Check if the given class is part of the window class */
if (strcasestr(client->window_class, to_class) == NULL)
return false;
/* If no title was given, were done */
if (to_title == NULL)
return true;
if (client->name_len > -1) {
/* UCS-2 converted window titles */
if (memmem(client->name, (client->name_len * 2), to_title_ucs, (to_title_ucs_len * 2)) == NULL)
return false;
} else {
/* Legacy hints */
if (strcasestr(client->name, to_title) == NULL)
return false;
}
return true;
}
/*
* Jumps to the given window class / title.
* Title is matched using strstr, that is, matches if it appears anywhere
@ -619,44 +591,22 @@ static bool client_matches_class_name(Client *client, char *to_class, char *to_t
*
*/
static void jump_to_window(xcb_connection_t *conn, const char *arguments) {
char *to_class, *to_title, *to_title_ucs = NULL;
int to_title_ucs_len;
char *classtitle;
Client *client;
/* The first character is a quote, this was checked before */
to_class = sstrdup(arguments+1);
classtitle = sstrdup(arguments+1);
/* The last character is a quote, we just set it to NULL */
to_class[strlen(to_class)-1] = '\0';
classtitle[strlen(classtitle)-1] = '\0';
/* If a title was specified, split both strings at the slash */
if ((to_title = strstr(to_class, "/")) != NULL) {
*(to_title++) = '\0';
/* Convert to UCS-2 */
to_title_ucs = convert_utf8_to_ucs2(to_title, &to_title_ucs_len);
if ((client = get_matching_client(conn, classtitle, NULL)) == NULL) {
free(classtitle);
LOG("No matching client found.\n");
return;
}
LOG("Should jump to class \"%s\" / title \"%s\"\n", to_class, to_title);
for (int workspace = 0; workspace < 10; workspace++) {
if (workspaces[workspace].screen == NULL)
continue;
FOR_TABLE(&(workspaces[workspace])) {
Container *con = workspaces[workspace].table[cols][rows];
Client *client;
CIRCLEQ_FOREACH(client, &(con->clients), clients) {
LOG("Checking client with class=%s, name=%s\n", client->window_class, client->name);
if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len))
continue;
set_focus(conn, client, true);
goto done;
}
}
}
done:
free(to_class);
FREE(to_title_ucs);
free(classtitle);
set_focus(conn, client, true);
}
/*
@ -763,7 +713,7 @@ void parse_command(xcb_connection_t *conn, const char *command) {
}
LOG("Killing current window\n");
kill_window(conn, CUR_CELL->currently_focused);
client_kill(conn, CUR_CELL->currently_focused);
return;
}

View File

@ -87,7 +87,7 @@ void load_configuration(const char *override_configpath) {
/* exec-lines (autostart) */
if (strcasecmp(key, "exec") == 0) {
Autostart *new = smalloc(sizeof(Autostart));
struct Autostart *new = smalloc(sizeof(struct Autostart));
new->command = sstrdup(value);
TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
continue;
@ -133,6 +133,44 @@ void load_configuration(const char *override_configpath) {
continue;
}
/* assign window class[/window title] → workspace */
if (strcasecmp(key, "assign") == 0) {
LOG("assign: \"%s\"\n", value);
char *class_title = sstrdup(value);
char *target;
/* If the window class/title is quoted we skip quotes */
if (class_title[0] == '"') {
class_title++;
char *end = strchr(class_title, '"');
if (end == NULL)
die("Malformatted assignment, couldn't find finishing quote\n");
*end = '\0';
} else {
/* If it is not quoted, we terminate it at the first space */
char *end = strchr(class_title, ' ');
if (end == NULL)
die("Malformed assignment, couldn't find terminating space\n");
*end = '\0';
}
/* The target is the last argument separated by a space */
if ((target = strrchr(value, ' ')) == NULL)
die("Malformed assignment, couldn't find target\n");
target++;
if (atoi(target) < 1 || atoi(target) > 10)
die("Malformed assignment, invalid workspace number\n");
LOG("assignment parsed: \"%s\" to \"%s\"\n", class_title, target);
struct Assignment *new = smalloc(sizeof(struct Assignment));
new->windowclass_title = class_title;
new->workspace = atoi(target);
TAILQ_INSERT_TAIL(&assignments, new, assignments);
continue;
}
fprintf(stderr, "Unknown configfile option: %s\n", key);
exit(1);
}

View File

@ -32,6 +32,7 @@
#include "config.h"
#include "queue.h"
#include "resize.h"
#include "client.h"
/* After mapping/unmapping windows, a notify event is generated. However, we dont want it,
since itd trigger an infinite loop of switching between the different windows when
@ -501,7 +502,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
con->workspace->fullscreen_client = NULL;
/* Remove the client from the list of clients */
remove_client_from_container(conn, client, con);
client_remove_from_container(conn, client, con);
/* Set focus to the last focused client in this container */
con->currently_focused = get_last_focused_client(conn, con, NULL);

View File

@ -55,6 +55,9 @@ struct bindings_head bindings = TAILQ_HEAD_INITIALIZER(bindings);
/* The list of exec-lines */
struct autostarts_head autostarts = TAILQ_HEAD_INITIALIZER(autostarts);
/* The list of assignments */
struct assignments_head assignments = TAILQ_HEAD_INITIALIZER(assignments);
/* This is a list of Stack_Windows, global, for easier/faster access on expose events */
struct stack_wins_head stack_wins = SLIST_HEAD_INITIALIZER(stack_wins);
@ -102,17 +105,21 @@ void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_
if (!attr) {
wa.tag = TAG_COOKIE;
wa.u.cookie = xcb_get_window_attributes(conn, window);
attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0);
}
geom = xcb_get_geometry_reply(conn, geomc, 0);
if (attr && geom) {
reparent_window(conn, window, attr->visual, geom->root, geom->depth,
geom->x, geom->y, geom->width, geom->height);
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS);
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS);
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
return;
}
if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL)
goto out;
/* Reparent the window and add it to our list of managed windows */
reparent_window(conn, window, attr->visual, geom->root, geom->depth,
geom->x, geom->y, geom->width, geom->height);
/* Generate callback events for every property we watch */
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS);
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS);
xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
free(geom);
out:
@ -131,7 +138,8 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
int16_t x, int16_t y, uint16_t width, uint16_t height) {
xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie;
xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
utf8_title_cookie, title_cookie, class_cookie;
uint32_t mask = 0;
uint32_t values[3];
@ -147,6 +155,9 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128);
title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128);
class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128);
Client *new = table_get(&by_child, child);
@ -191,6 +202,8 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
/* Yo dawg, I heard you like windows, so I create a window around your window… */
new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
/* Set WM_STATE_NORMAL because GTK applications dont want to drag & drop if we dont.
* Also, xprop(1) needs that to work. */
long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, new->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
@ -227,15 +240,16 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
xcb_atom_t *atom;
xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
for (int i = 0; i < xcb_get_property_value_length(preply); i++)
if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
LOG("Window is a dock.\n");
new->dock = true;
new->titlebar_position = TITLEBAR_OFF;
new->force_reconfigure = true;
new->container = NULL;
SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
}
for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
if (atom[i] != atoms[_NET_WM_WINDOW_TYPE_DOCK])
continue;
LOG("Window is a dock.\n");
new->dock = true;
new->titlebar_position = TITLEBAR_OFF;
new->force_reconfigure = true;
new->container = NULL;
SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
}
}
if (new->dock) {
@ -257,18 +271,65 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", height);
new->desired_height = height;
}
} else {
/* If its not a dock, we can check on which workspace we should put it. */
/* Firstly, we need to get the windows class / title. We asked for the properties at the
* top of this function, get them now and pass them to our callback function for window class / title
* changes. It is important that the client was already inserted into the by_child table,
* because the callbacks wont work otherwise. */
preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
preply = xcb_get_property_reply(conn, title_cookie, NULL);
handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
preply = xcb_get_property_reply(conn, class_cookie, NULL);
handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
LOG("DEBUG: should have all infos now\n");
struct Assignment *assign;
TAILQ_FOREACH(assign, &assignments, assignments) {
if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
continue;
LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
assign->windowclass_title, assign->workspace);
if (c_ws->screen->current_workspace == (assign->workspace-1)) {
LOG("We are already there, no need to do anything\n");
break;
}
LOG("Changin container/workspace and unmapping the client\n");
Workspace *t_ws = &(workspaces[assign->workspace-1]);
if (t_ws->screen == NULL) {
LOG("initializing new workspace, setting num to %d\n", assign->workspace);
t_ws->screen = c_ws->screen;
/* Copy the dimensions from the virtual screen */
memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect));
}
new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
new->workspace = t_ws;
xcb_unmap_window(conn, new->frame);
break;
}
}
/* Focus the new window if were not in fullscreen mode and if it is not a dock window */
if (CUR_CELL->workspace->fullscreen_client == NULL) {
if (!new->dock) {
CUR_CELL->currently_focused = new;
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
if (CUR_CELL->workspace->fullscreen_client != NULL) {
if (new->container == CUR_CELL) {
/* If we are in fullscreen, we should lower the window to not be annoying */
uint32_t values[] = { XCB_STACK_MODE_BELOW };
xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
}
} else if (!new->dock) {
/* Focus the new window if were not in fullscreen mode and if it is not a dock window */
if (new->container->workspace->fullscreen_client == NULL) {
new->container->currently_focused = new;
if (new->container == CUR_CELL)
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
}
} else {
/* If we are in fullscreen, we should lower the window to not be annoying */
uint32_t values[] = { XCB_STACK_MODE_BELOW };
xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
}
/* Insert into the currently active container, if its not a dock window */
@ -276,8 +337,8 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
/* Insert after the old active client, if existing. If it does not exist, the
container is empty and it does not matter, where we insert it */
if (old_focused != NULL && !old_focused->dock)
CIRCLEQ_INSERT_AFTER(&(CUR_CELL->clients), old_focused, new, clients);
else CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
}
@ -287,14 +348,15 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
(state = xcb_get_property_value(preply)) != NULL)
/* Check all set _NET_WM_STATEs */
for (int i = 0; i < xcb_get_property_value_length(preply); i++)
if (state[i] == atoms[_NET_WM_STATE_FULLSCREEN]) {
/* If the window got the fullscreen state, we just toggle fullscreen
and dont event bother to redraw the layout that would not change
anything anyways */
toggle_fullscreen(conn, new);
return;
}
for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
continue;
/* If the window got the fullscreen state, we just toggle fullscreen
and dont event bother to redraw the layout that would not change
anything anyways */
toggle_fullscreen(conn, new);
return;
}
render_layout(conn);
}
@ -531,7 +593,7 @@ int main(int argc, char *argv[], char *env[]) {
}
/* Autostarting exec-lines */
Autostart *exec;
struct Autostart *exec;
TAILQ_FOREACH(exec, &autostarts, autostarts) {
LOG("auto-starting %s\n", exec->command);
start_application(exec->command);

View File

@ -27,6 +27,7 @@
#include "layout.h"
#include "util.h"
#include "xcb.h"
#include "client.h"
static iconv_t conversion_descriptor = 0;
struct keyvalue_table_head by_parent = TAILQ_HEAD_INITIALIZER(by_parent);
@ -224,25 +225,6 @@ char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
return buffer;
}
/*
* Removes the given client from the container, either because it will be inserted into another
* one or because it was unmapped
*
*/
void remove_client_from_container(xcb_connection_t *conn, Client *client, Container *container) {
CIRCLEQ_REMOVE(&(container->clients), client, clients);
SLIST_REMOVE(&(container->workspace->focus_stack), client, Client, focus_clients);
/* If the container will be empty now and is in stacking mode, we need to
unmap the stack_win */
if (CIRCLEQ_EMPTY(&(container->clients)) && container->mode == MODE_STACK) {
struct Stack_Window *stack_win = &(container->stack_win);
stack_win->rect.height = 0;
xcb_unmap_window(conn, stack_win->window);
}
}
/*
* Returns the client which comes next in focus stack (= was selected before) for
* the given container, optionally excluding the given client.
@ -435,17 +417,6 @@ void switch_layout_mode(xcb_connection_t *conn, Container *container, int mode)
set_focus(conn, container->currently_focused, true);
}
/*
* Warps the pointer into the given client (in the middle of it, to be specific), therefore
* selecting it
*
*/
void warp_pointer_into(xcb_connection_t *conn, Client *client) {
int mid_x = client->rect.width / 2,
mid_y = client->rect.height / 2;
xcb_warp_pointer(conn, XCB_NONE, client->child, 0, 0, 0, 0, mid_x, mid_y);
}
/*
* Toggles fullscreen mode for the given client. It updates the data structures and
* reconfigures (= resizes/moves) the client and its frame to the full size of the
@ -508,52 +479,55 @@ void toggle_fullscreen(xcb_connection_t *conn, Client *client) {
}
/*
* Returns true if the client supports the given protocol atom (like WM_DELETE_WINDOW)
* Gets the first matching client for the given window class/window title.
* If the paramater specific is set to a specific client, only this one
* will be checked.
*
*/
static bool client_supports_protocol(xcb_connection_t *conn, Client *client, xcb_atom_t atom) {
xcb_get_property_cookie_t cookie;
xcb_get_wm_protocols_reply_t protocols;
bool result = false;
Client *get_matching_client(xcb_connection_t *conn, const char *window_classtitle,
Client *specific) {
char *to_class, *to_title, *to_title_ucs = NULL;
int to_title_ucs_len;
Client *matching = NULL;
cookie = xcb_get_wm_protocols_unchecked(conn, client->child, atoms[WM_PROTOCOLS]);
if (xcb_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1)
return false;
to_class = sstrdup(window_classtitle);
/* Check if the clients protocols have the requested atom set */
for (uint32_t i = 0; i < protocols.atoms_len; i++)
if (protocols.atoms[i] == atom)
result = true;
xcb_get_wm_protocols_reply_wipe(&protocols);
return result;
}
/*
* Kills the given window using WM_DELETE_WINDOW or xcb_kill_window
*
*/
void kill_window(xcb_connection_t *conn, Client *window) {
/* If the client does not support WM_DELETE_WINDOW, we kill it the hard way */
if (!client_supports_protocol(conn, window, atoms[WM_DELETE_WINDOW])) {
LOG("Killing window the hard way\n");
xcb_kill_client(conn, window->child);
return;
/* If a title was specified, split both strings at the slash */
if ((to_title = strstr(to_class, "/")) != NULL) {
*(to_title++) = '\0';
/* Convert to UCS-2 */
to_title_ucs = convert_utf8_to_ucs2(to_title, &to_title_ucs_len);
}
xcb_client_message_event_t ev;
/* If we were given a specific client we only check if that one matches */
if (specific != NULL) {
if (client_matches_class_name(specific, to_class, to_title, to_title_ucs, to_title_ucs_len))
matching = specific;
goto done;
}
memset(&ev, 0, sizeof(xcb_client_message_event_t));
LOG("Getting clients for class \"%s\" / title \"%s\"\n", to_class, to_title);
for (int workspace = 0; workspace < 10; workspace++) {
if (workspaces[workspace].screen == NULL)
continue;
ev.response_type = XCB_CLIENT_MESSAGE;
ev.window = window->child;
ev.type = atoms[WM_PROTOCOLS];
ev.format = 32;
ev.data.data32[0] = atoms[WM_DELETE_WINDOW];
ev.data.data32[1] = XCB_CURRENT_TIME;
FOR_TABLE(&(workspaces[workspace])) {
Container *con = workspaces[workspace].table[cols][rows];
Client *client;
LOG("Sending WM_DELETE to the client\n");
xcb_send_event(conn, false, window->child, XCB_EVENT_MASK_NO_EVENT, (char*)&ev);
xcb_flush(conn);
CIRCLEQ_FOREACH(client, &(con->clients), clients) {
LOG("Checking client with class=%s, name=%s\n", client->window_class, client->name);
if (!client_matches_class_name(client, to_class, to_title, to_title_ucs, to_title_ucs_len))
continue;
matching = client;
goto done;
}
}
}
done:
free(to_class);
FREE(to_title_ucs);
return matching;
}