Bugfix: Eliminate race condition, fix dock windows
There was a race condition when mapping a window and not setting the event mask before. Therefore, the ReparentNotify and (more important) the UnmapNotify generated by reparenting were not received, thus leaving the awaiting_useless_unmap variable of the client "true". To just make it work, in previous commits the DestroyNotify handler was introduced. Fortunately, with fixing this race condition by first setting the event mask and mapping the window afterwards, we can remove this handler. As for the dock windows, there were quite some occurences were client->container was used without checking if the client is inside a container at all. Furthermore, the client’s strut containing the space to reserve at the screen edge is now checked and the desired height is set to the window’s height if the strut contains 0 or if no strut was specified at all.
This commit is contained in:
parent
434345aa30
commit
408b2bdb39
|
@ -204,6 +204,8 @@ struct Client {
|
||||||
|
|
||||||
/* Backpointer. A client is inside a container */
|
/* Backpointer. A client is inside a container */
|
||||||
Container *container;
|
Container *container;
|
||||||
|
/* Because dock clients don’t have a container, we have this workspace-backpointer */
|
||||||
|
Workspace *workspace;
|
||||||
|
|
||||||
/* x, y, width, height of the frame */
|
/* x, y, width, height of the frame */
|
||||||
Rect rect;
|
Rect rect;
|
||||||
|
|
|
@ -18,7 +18,6 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
|
||||||
int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_event_t *event);
|
int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_event_t *event);
|
||||||
int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_notify_event_t *event);
|
int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_notify_event_t *event);
|
||||||
int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure_request_event_t *event);
|
int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure_request_event_t *event);
|
||||||
int handle_destroy_notify_event(void *data, xcb_connection_t *conn, xcb_destroy_notify_event_t *event);
|
|
||||||
int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_notify_event_t *event);
|
int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_notify_event_t *event);
|
||||||
int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
|
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);
|
xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop);
|
||||||
|
|
|
@ -158,7 +158,9 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_
|
||||||
|
|
||||||
/* Do plausibility checks: This event may be useless for us if it occurs on a window
|
/* Do plausibility checks: This event may be useless for us if it occurs on a window
|
||||||
which is in a stacked container but not the focused one */
|
which is in a stacked container but not the focused one */
|
||||||
if (client->container->mode == MODE_STACK && client->container->currently_focused != client) {
|
if (client->container != NULL &&
|
||||||
|
client->container->mode == MODE_STACK &&
|
||||||
|
client->container->currently_focused != client) {
|
||||||
LOG("Plausibility check says: no\n");
|
LOG("Plausibility check says: no\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -410,6 +412,7 @@ int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_eve
|
||||||
wa.u.override_redirect = reply->override_redirect;
|
wa.u.override_redirect = reply->override_redirect;
|
||||||
LOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence);
|
LOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence);
|
||||||
add_ignore_event(event->sequence);
|
add_ignore_event(event->sequence);
|
||||||
|
|
||||||
manage_window(prophs, conn, event->window, wa);
|
manage_window(prophs, conn, event->window, wa);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -536,12 +539,18 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (client->dock) {
|
||||||
|
LOG("Removing from dock clients\n");
|
||||||
|
SLIST_REMOVE(&(client->workspace->dock_clients), client, Client, dock_clients);
|
||||||
|
}
|
||||||
|
|
||||||
LOG("child of 0x%08x.\n", client->frame);
|
LOG("child of 0x%08x.\n", client->frame);
|
||||||
xcb_reparent_window(conn, client->child, root, 0, 0);
|
xcb_reparent_window(conn, client->child, root, 0, 0);
|
||||||
xcb_destroy_window(conn, client->frame);
|
xcb_destroy_window(conn, client->frame);
|
||||||
xcb_flush(conn);
|
xcb_flush(conn);
|
||||||
table_remove(byParent, client->frame);
|
table_remove(byParent, client->frame);
|
||||||
|
|
||||||
|
if (client->container != NULL)
|
||||||
cleanup_table(conn, client->container->workspace);
|
cleanup_table(conn, client->container->workspace);
|
||||||
|
|
||||||
free(client);
|
free(client);
|
||||||
|
@ -551,20 +560,6 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* This handler makes sure that windows are closed correctly as we drop the first unmap event we
|
|
||||||
* get because they are sometimes generated by remapping.
|
|
||||||
*
|
|
||||||
* FIXME: Rather than providing this event, I’d rather know why some applications need to have
|
|
||||||
* unmap events ignored (like VLC’s open dialog, just open it two times) while others don’t (GIMP).
|
|
||||||
* For now, just make it work.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
int handle_destroy_notify_event(void *data, xcb_connection_t *conn, xcb_destroy_notify_event_t *event) {
|
|
||||||
LOG("Destroynotify, faking unmapnotify\n");
|
|
||||||
return handle_unmap_notify_event(data, conn, (xcb_unmap_notify_event_t*)event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called when a window changes its title
|
* Called when a window changes its title
|
||||||
*
|
*
|
||||||
|
@ -608,6 +603,10 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
|
||||||
if (old_name != NULL)
|
if (old_name != NULL)
|
||||||
free(old_name);
|
free(old_name);
|
||||||
|
|
||||||
|
/* If the client is a dock window, we don’t need to render anything */
|
||||||
|
if (client->dock)
|
||||||
|
return 1;
|
||||||
|
|
||||||
if (client->container->mode == MODE_STACK)
|
if (client->container->mode == MODE_STACK)
|
||||||
render_container(conn, client->container);
|
render_container(conn, client->container);
|
||||||
else decorate_window(conn, client, client->frame, client->titlegc, 0);
|
else decorate_window(conn, client, client->frame, client->titlegc, 0);
|
||||||
|
@ -649,6 +648,11 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG("got client %s\n", client->name);
|
LOG("got client %s\n", client->name);
|
||||||
|
if (client->dock) {
|
||||||
|
LOG("this is a dock\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (client->container->mode != MODE_STACK)
|
if (client->container->mode != MODE_STACK)
|
||||||
decorate_window(conn, client, client->frame, client->titlegc, 0);
|
decorate_window(conn, client, client->frame, client->titlegc, 0);
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -179,7 +179,7 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
static void reposition_client(xcb_connection_t *conn, Client *client) {
|
static void reposition_client(xcb_connection_t *conn, Client *client) {
|
||||||
LOG("frame needs to be pushed to %dx%d\n", client->rect.x, client->rect.y);
|
LOG("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
|
/* Note: We can use a pointer to client->x like an array of uint32_ts
|
||||||
because it is followed by client->y by definition */
|
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));
|
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x));
|
||||||
|
@ -192,7 +192,7 @@ static void reposition_client(xcb_connection_t *conn, Client *client) {
|
||||||
static void resize_client(xcb_connection_t *conn, Client *client) {
|
static void resize_client(xcb_connection_t *conn, Client *client) {
|
||||||
i3Font *font = load_font(conn, config.font);
|
i3Font *font = load_font(conn, config.font);
|
||||||
|
|
||||||
LOG("resizing client to %d x %d\n", client->rect.width, client->rect.height);
|
LOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height);
|
||||||
xcb_configure_window(conn, client->frame,
|
xcb_configure_window(conn, client->frame,
|
||||||
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
|
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
|
||||||
&(client->rect.width));
|
&(client->rect.width));
|
||||||
|
@ -205,7 +205,7 @@ static void resize_client(xcb_connection_t *conn, Client *client) {
|
||||||
XCB_CONFIG_WINDOW_WIDTH |
|
XCB_CONFIG_WINDOW_WIDTH |
|
||||||
XCB_CONFIG_WINDOW_HEIGHT;
|
XCB_CONFIG_WINDOW_HEIGHT;
|
||||||
Rect *rect = &(client->child_rect);
|
Rect *rect = &(client->child_rect);
|
||||||
switch (client->container->mode) {
|
switch ((client->container != NULL ? client->container->mode : MODE_DEFAULT)) {
|
||||||
case MODE_STACK:
|
case MODE_STACK:
|
||||||
rect->x = 2;
|
rect->x = 2;
|
||||||
rect->y = 0;
|
rect->y = 0;
|
||||||
|
@ -397,6 +397,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) {
|
static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int *height) {
|
||||||
Client *client;
|
Client *client;
|
||||||
SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients) {
|
SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients) {
|
||||||
|
LOG("client is at %d, should be at %d\n", client->rect.y, *height);
|
||||||
if (client->force_reconfigure |
|
if (client->force_reconfigure |
|
||||||
update_if_necessary(&(client->rect.x), 0) |
|
update_if_necessary(&(client->rect.x), 0) |
|
||||||
update_if_necessary(&(client->rect.y), *height))
|
update_if_necessary(&(client->rect.y), *height))
|
||||||
|
|
72
src/mainx.c
72
src/mainx.c
|
@ -126,14 +126,23 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
|
||||||
int16_t x, int16_t y, uint16_t width, uint16_t height) {
|
int16_t x, int16_t y, uint16_t width, uint16_t height) {
|
||||||
|
|
||||||
xcb_get_property_cookie_t wm_type_cookie, strut_cookie;
|
xcb_get_property_cookie_t wm_type_cookie, strut_cookie;
|
||||||
|
uint32_t mask = 0;
|
||||||
|
uint32_t values[3];
|
||||||
|
|
||||||
|
/* We are interested in property changes */
|
||||||
|
mask = XCB_CW_EVENT_MASK;
|
||||||
|
values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE |
|
||||||
|
XCB_EVENT_MASK_STRUCTURE_NOTIFY |
|
||||||
|
XCB_EVENT_MASK_ENTER_WINDOW;
|
||||||
|
xcb_change_window_attributes(conn, child, mask, values);
|
||||||
|
|
||||||
|
/* Map the window first to avoid flickering */
|
||||||
|
xcb_map_window(conn, child);
|
||||||
|
|
||||||
/* Place requests for properties ASAP */
|
/* Place requests for properties ASAP */
|
||||||
wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
|
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);
|
strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
|
||||||
|
|
||||||
/* Map the window first to avoid flickering */
|
|
||||||
xcb_map_window(conn, child);
|
|
||||||
|
|
||||||
Client *new = table_get(byChild, child);
|
Client *new = table_get(byChild, child);
|
||||||
|
|
||||||
/* Events for already managed windows should already be filtered in manage_window() */
|
/* Events for already managed windows should already be filtered in manage_window() */
|
||||||
|
@ -142,19 +151,20 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
|
||||||
LOG("reparenting new client\n");
|
LOG("reparenting new client\n");
|
||||||
new = calloc(sizeof(Client), 1);
|
new = calloc(sizeof(Client), 1);
|
||||||
new->force_reconfigure = true;
|
new->force_reconfigure = true;
|
||||||
uint32_t mask = 0;
|
|
||||||
uint32_t values[3];
|
|
||||||
|
|
||||||
/* Update the data structures */
|
/* Update the data structures */
|
||||||
Client *old_focused = CUR_CELL->currently_focused;
|
Client *old_focused = CUR_CELL->currently_focused;
|
||||||
|
|
||||||
new->container = CUR_CELL;
|
new->container = CUR_CELL;
|
||||||
|
new->workspace = new->container->workspace;
|
||||||
|
|
||||||
new->frame = xcb_generate_id(conn);
|
new->frame = xcb_generate_id(conn);
|
||||||
new->child = child;
|
new->child = child;
|
||||||
new->rect.width = width;
|
new->rect.width = width;
|
||||||
new->rect.height = height;
|
new->rect.height = height;
|
||||||
|
|
||||||
|
mask = 0;
|
||||||
|
|
||||||
/* Don’t generate events for our new window, it should *not* be managed */
|
/* Don’t generate events for our new window, it should *not* be managed */
|
||||||
mask |= XCB_CW_OVERRIDE_REDIRECT;
|
mask |= XCB_CW_OVERRIDE_REDIRECT;
|
||||||
values[0] = 1;
|
values[0] = 1;
|
||||||
|
@ -199,19 +209,6 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* We are interested in property changes */
|
|
||||||
mask = XCB_CW_EVENT_MASK;
|
|
||||||
values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE |
|
|
||||||
XCB_EVENT_MASK_STRUCTURE_NOTIFY |
|
|
||||||
XCB_EVENT_MASK_ENTER_WINDOW;
|
|
||||||
cookie = xcb_change_window_attributes_checked(conn, child, mask, values);
|
|
||||||
if (xcb_request_check(conn, cookie) != NULL) {
|
|
||||||
LOG("Could not change window attributes, aborting\n");
|
|
||||||
xcb_destroy_window(conn, new->frame);
|
|
||||||
free(new);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Put our data structure (Client) into the table */
|
/* Put our data structure (Client) into the table */
|
||||||
table_put(byParent, new->frame, new);
|
table_put(byParent, new->frame, new);
|
||||||
table_put(byChild, child, new);
|
table_put(byChild, child, new);
|
||||||
|
@ -222,16 +219,6 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
|
||||||
1 /* left mouse button */,
|
1 /* left mouse button */,
|
||||||
XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
|
XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
|
||||||
|
|
||||||
/* Focus the new window if we’re not in fullscreen mode */
|
|
||||||
if (CUR_CELL->workspace->fullscreen_client == NULL) {
|
|
||||||
CUR_CELL->currently_focused = new;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
|
/* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
|
||||||
xcb_atom_t *atom;
|
xcb_atom_t *atom;
|
||||||
xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
|
xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
|
||||||
|
@ -247,6 +234,7 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (new->dock) {
|
||||||
/* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
|
/* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
|
||||||
uint32_t *strut;
|
uint32_t *strut;
|
||||||
preply = xcb_get_property_reply(conn, strut_cookie, NULL);
|
preply = xcb_get_property_reply(conn, strut_cookie, NULL);
|
||||||
|
@ -256,14 +244,34 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
|
||||||
with maximum horizontal size.
|
with maximum horizontal size.
|
||||||
TODO: bars at the top */
|
TODO: bars at the top */
|
||||||
new->desired_height = strut[3];
|
new->desired_height = strut[3];
|
||||||
LOG("the client wants to be %d pixels height\n", new->desired_height);
|
if (new->desired_height == 0) {
|
||||||
|
LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", height);
|
||||||
|
new->desired_height = height;
|
||||||
|
}
|
||||||
|
LOG("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", height);
|
||||||
|
new->desired_height = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus the new window if we’re 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);
|
||||||
|
}
|
||||||
|
} 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 it’s not a dock window */
|
/* Insert into the currently active container, if it’s not a dock window */
|
||||||
if (!new->dock) {
|
if (!new->dock) {
|
||||||
/* Insert after the old active client, if existing. If it does not exist, the
|
/* 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 */
|
container is empty and it does not matter, where we insert it */
|
||||||
if (old_focused != NULL)
|
if (old_focused != NULL && !old_focused->dock)
|
||||||
CIRCLEQ_INSERT_AFTER(&(CUR_CELL->clients), old_focused, new, clients);
|
CIRCLEQ_INSERT_AFTER(&(CUR_CELL->clients), old_focused, new, clients);
|
||||||
else CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
|
else CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
|
||||||
|
|
||||||
|
@ -408,10 +416,6 @@ int main(int argc, char *argv[], char *env[]) {
|
||||||
it any longer. Usually, the client destroys the window shortly afterwards. */
|
it any longer. Usually, the client destroys the window shortly afterwards. */
|
||||||
xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, NULL);
|
xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, NULL);
|
||||||
|
|
||||||
/* Destroy notify is a step further than unmap notify. We handle it to make sure
|
|
||||||
that windows whose unmap notify was falsely ignored will get deleted properly */
|
|
||||||
xcb_event_set_destroy_notify_handler(&evenths, handle_destroy_notify_event, NULL);
|
|
||||||
|
|
||||||
/* Configure notify = window’s configuration (geometry, stacking, …). We only need
|
/* Configure notify = window’s configuration (geometry, stacking, …). We only need
|
||||||
it to set up ignore the following enter_notify events */
|
it to set up ignore the following enter_notify events */
|
||||||
xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, NULL);
|
xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, NULL);
|
||||||
|
|
|
@ -233,7 +233,7 @@ void set_focus(xcb_connection_t *conn, Client *client) {
|
||||||
|
|
||||||
/* If we’re in stacking mode, this renders the container to update changes in the title
|
/* If we’re in stacking mode, this renders the container to update changes in the title
|
||||||
bars and to raise the focused client */
|
bars and to raise the focused client */
|
||||||
if ((old_client != NULL) && (old_client != client))
|
if ((old_client != NULL) && (old_client != client) && !old_client->dock)
|
||||||
redecorate_window(conn, old_client);
|
redecorate_window(conn, old_client);
|
||||||
|
|
||||||
SLIST_REMOVE(&(client->container->workspace->focus_stack), client, Client, focus_clients);
|
SLIST_REMOVE(&(client->container->workspace->focus_stack), client, Client, focus_clients);
|
||||||
|
|
Loading…
Reference in New Issue