diff --git a/include/data.h b/include/data.h index 959068e0..3a059e7b 100644 --- a/include/data.h +++ b/include/data.h @@ -398,6 +398,9 @@ struct Window { /** The _NET_WM_WINDOW_TYPE for this window. */ xcb_atom_t window_type; + /** The _NET_WM_DESKTOP for this window. */ + uint32_t wm_desktop; + /** Whether the window says it is a dock window */ enum { W_NODOCK = 0, W_DOCK_TOP = 1, diff --git a/include/ewmh.h b/include/ewmh.h index 7ed9b544..2a55ab9f 100644 --- a/include/ewmh.h +++ b/include/ewmh.h @@ -36,6 +36,13 @@ void ewmh_update_desktop_names(void); */ void ewmh_update_desktop_viewport(void); +/** + * Updates _NET_WM_DESKTOP for all windows. + * A request will only be made if the cached value differs from the calculated value. + * + */ +void ewmh_update_wm_desktop(void); + /** * Updates _NET_ACTIVE_WINDOW with the currently focused window. * @@ -96,3 +103,21 @@ void ewmh_setup_hints(void); * */ void ewmh_update_workarea(void); + +/** + * Returns the workspace container as enumerated by the EWMH desktop model. + * Returns NULL if no workspace could be found for the index. + * + * This is the reverse of ewmh_get_workspace_index. + * + */ +Con *ewmh_get_workspace_by_index(uint32_t idx); + +/** + * Returns the EWMH desktop index for the workspace the given container is on. + * Returns NET_WM_DESKTOP_NONE if the desktop index cannot be determined. + * + * This is the reverse of ewmh_get_workspace_by_index. + * + */ +uint32_t ewmh_get_workspace_index(Con *con); diff --git a/include/workspace.h b/include/workspace.h index 1bee64e0..0ff5cd30 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -14,6 +14,14 @@ #include "tree.h" #include "randr.h" +/* We use NET_WM_DESKTOP_NONE for cases where we cannot determine the EWMH + * desktop index for a window. We cannot use a negative value like -1 since we + * need to use uint32_t as we actually need the full range of it. This is + * technically dangerous, but it's safe to assume that we will never have more + * than 4294967279 workspaces open at a time. */ +#define NET_WM_DESKTOP_NONE 0xFFFFFFF0 +#define NET_WM_DESKTOP_ALL 0xFFFFFFFF + /** * Returns a pointer to the workspace with the given number (starting at 0), * creating the workspace if necessary (by allocating the necessary amount of diff --git a/src/commands.c b/src/commands.c index 525a30fc..482560b8 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1542,6 +1542,8 @@ void cmd_sticky(I3_CMD, const char *action) { * sure it gets pushed to the front now. */ output_push_sticky_windows(focused); + ewmh_update_wm_desktop(); + cmd_output->needs_tree_render = true; ysuccess(true); } diff --git a/src/con.c b/src/con.c index a3a2f2e3..cd17f9e5 100644 --- a/src/con.c +++ b/src/con.c @@ -1080,6 +1080,7 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi CALL(parent, on_remove_child); ipc_send_window_event("move", con); + ewmh_update_wm_desktop(); return true; } diff --git a/src/ewmh.c b/src/ewmh.c index a5c90175..05f4d3cd 100644 --- a/src/ewmh.c +++ b/src/ewmh.c @@ -21,24 +21,9 @@ xcb_window_t ewmh_window; * */ void ewmh_update_current_desktop(void) { - Con *focused_ws = con_get_workspace(focused); - Con *output; - uint32_t idx = 0; - /* We count to get the index of this workspace because named workspaces - * don’t have the ->num property */ - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { - Con *ws; - TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) { - if (STARTS_WITH(ws->name, "__")) - continue; - - if (ws == focused_ws) { - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, - A__NET_CURRENT_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, &idx); - return; - } - ++idx; - } + const uint32_t idx = ewmh_get_workspace_index(focused); + if (idx != NET_WM_DESKTOP_NONE) { + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_CURRENT_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, &idx); } } @@ -138,6 +123,71 @@ void ewmh_update_desktop_viewport(void) { A__NET_DESKTOP_VIEWPORT, XCB_ATOM_CARDINAL, 32, current_position, &viewports); } +static void ewmh_update_wm_desktop_recursively(Con *con, const uint32_t desktop) { + /* Recursively call this to descend through the entire subtree. */ + Con *child; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + ewmh_update_wm_desktop_recursively(child, desktop); + } + /* If con is a workspace, we also need to go through the floating windows on it. */ + if (con->type == CT_WORKSPACE) { + TAILQ_FOREACH(child, &(con->floating_head), floating_windows) { + ewmh_update_wm_desktop_recursively(child, desktop); + } + } + + if (!con_has_managed_window(con)) + return; + + const xcb_window_t window = con->window->id; + + uint32_t wm_desktop = desktop; + /* Sticky windows are only actually sticky when they are floating or inside + * a floating container. This is technically still slightly wrong, since + * sticky windows will only be on all workspaces on this output, but we + * ignore multi-monitor situations for this since the spec isn't too + * precise on this anyway. */ + if (con_is_sticky(con) && con_is_floating(con)) { + wm_desktop = NET_WM_DESKTOP_ALL; + } + + /* If this is the cached value, we don't need to do anything. */ + if (con->window->wm_desktop == wm_desktop) + return; + con->window->wm_desktop = wm_desktop; + + if (wm_desktop != NET_WM_DESKTOP_NONE) { + DLOG("Setting _NET_WM_DESKTOP = %d for window 0x%08x.\n", wm_desktop, window); + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, window, A__NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, &wm_desktop); + } else { + /* If we can't determine the workspace index, delete the property. We'd + * rather not set it than lie. */ + ELOG("Failed to determine the proper EWMH desktop index for window 0x%08x, deleting _NET_WM_DESKTOP.\n", window); + xcb_delete_property(conn, window, A__NET_WM_DESKTOP); + } +} + +/* + * Updates _NET_WM_DESKTOP for all windows. + * A request will only be made if the cached value differs from the calculated value. + * + */ +void ewmh_update_wm_desktop(void) { + uint32_t desktop = 0; + + Con *output; + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + Con *workspace; + TAILQ_FOREACH(workspace, &(output_get_content(output)->nodes_head), nodes) { + if (con_is_internal(workspace)) + continue; + + ewmh_update_wm_desktop_recursively(workspace, desktop); + ++desktop; + } + } +} + /* * Updates _NET_ACTIVE_WINDOW with the currently focused window. * @@ -270,3 +320,61 @@ void ewmh_setup_hints(void) { xcb_map_window(conn, ewmh_window); xcb_configure_window(conn, ewmh_window, XCB_CONFIG_WINDOW_STACK_MODE, (uint32_t[]){XCB_STACK_MODE_BELOW}); } + +/* + * Returns the workspace container as enumerated by the EWMH desktop model. + * Returns NULL if no workspace could be found for the index. + * + * This is the reverse of ewmh_get_workspace_index. + * + */ +Con *ewmh_get_workspace_by_index(uint32_t idx) { + if (idx == NET_WM_DESKTOP_NONE) + return NULL; + + uint32_t current_index = 0; + + Con *output; + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + Con *workspace; + TAILQ_FOREACH(workspace, &(output_get_content(output)->nodes_head), nodes) { + if (con_is_internal(workspace)) + continue; + + if (current_index == idx) + return workspace; + + ++current_index; + } + } + + return NULL; +} + +/* + * Returns the EWMH desktop index for the workspace the given container is on. + * Returns NET_WM_DESKTOP_NONE if the desktop index cannot be determined. + * + * This is the reverse of ewmh_get_workspace_by_index. + * + */ +uint32_t ewmh_get_workspace_index(Con *con) { + uint32_t index = 0; + + Con *workspace = con_get_workspace(con); + Con *output; + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + Con *current; + TAILQ_FOREACH(current, &(output_get_content(output)->nodes_head), nodes) { + if (con_is_internal(current)) + continue; + + if (current == workspace) + return index; + + ++index; + } + } + + return NET_WM_DESKTOP_NONE; +} diff --git a/src/handlers.c b/src/handlers.c index 6cbc54f2..50e218c8 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -503,6 +503,9 @@ static void handle_unmap_notify_event(xcb_unmap_notify_event_t *event) { goto ignore_end; } + /* Since we close the container, we need to unset _NET_WM_DESKTOP according to the spec. */ + xcb_delete_property(conn, event->window, A__NET_WM_DESKTOP); + tree_close_internal(con, DONT_KILL_WINDOW, false, false); tree_render(); @@ -735,7 +738,9 @@ static void handle_client_message(xcb_client_message_event_t *event) { con->sticky = !con->sticky; DLOG("New sticky status for con = %p is %i.\n", con, con->sticky); + ewmh_update_sticky(con->window->id, con->sticky); output_push_sticky_windows(focused); + ewmh_update_wm_desktop(); } tree_render(); @@ -839,32 +844,48 @@ static void handle_client_message(xcb_client_message_event_t *event) { * a request to focus the given workspace. See * http://standards.freedesktop.org/wm-spec/latest/ar01s03.html#idm140251368135008 * */ - Con *output; - uint32_t idx = 0; DLOG("Request to change current desktop to index %d\n", event->data.data32[0]); - - TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { - Con *ws; - TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) { - if (STARTS_WITH(ws->name, "__")) - continue; - - if (idx == event->data.data32[0]) { - /* data32[1] is a timestamp used to prevent focus race conditions */ - if (event->data.data32[1]) - last_timestamp = event->data.data32[1]; - - DLOG("Handling request to focus workspace %s\n", ws->name); - - workspace_show(ws); - tree_render(); - - return; - } - - ++idx; - } + Con *ws = ewmh_get_workspace_by_index(event->data.data32[0]); + if (ws == NULL) { + ELOG("Could not determine workspace for this index, ignoring request.\n"); + return; } + + DLOG("Handling request to focus workspace %s\n", ws->name); + workspace_show(ws); + tree_render(); + } else if (event->type == A__NET_WM_DESKTOP) { + uint32_t index = event->data.data32[0]; + DLOG("Request to move window %d to EWMH desktop index %d\n", event->window, index); + + Con *con = con_by_window_id(event->window); + if (con == NULL) { + DLOG("Couldn't find con for window %d, ignoring the request.\n", event->window); + return; + } + + if (index == NET_WM_DESKTOP_ALL) { + /* The window is requesting to be visible on all workspaces, so + * let's float it and make it sticky. */ + DLOG("The window was requested to be visible on all workspaces, making it sticky and floating.\n"); + + floating_enable(con, false); + + con->sticky = true; + ewmh_update_sticky(con->window->id, true); + output_push_sticky_windows(focused); + } else { + Con *ws = ewmh_get_workspace_by_index(index); + if (ws == NULL) { + ELOG("Could not determine workspace for this index, ignoring request.\n"); + return; + } + + con_move_to_workspace(con, ws, false, false, true); + } + + tree_render(); + ewmh_update_wm_desktop(); } else if (event->type == A__NET_CLOSE_WINDOW) { /* * Pagers wanting to close a window MUST send a _NET_CLOSE_WINDOW @@ -915,8 +936,7 @@ static void handle_client_message(xcb_client_message_event_t *event) { break; } } else { - DLOG("unhandled clientmessage\n"); - return; + DLOG("Skipping client message for unhandled type %d\n", event->type); } } diff --git a/src/manage.c b/src/manage.c index 2bcb47f3..93272f1b 100644 --- a/src/manage.c +++ b/src/manage.c @@ -90,7 +90,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki utf8_title_cookie, title_cookie, class_cookie, leader_cookie, transient_cookie, role_cookie, startup_id_cookie, wm_hints_cookie, - wm_normal_hints_cookie, motif_wm_hints_cookie, wm_user_time_cookie; + wm_normal_hints_cookie, motif_wm_hints_cookie, wm_user_time_cookie, wm_desktop_cookie; geomc = xcb_get_geometry(conn, d); @@ -162,6 +162,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki wm_normal_hints_cookie = xcb_icccm_get_wm_normal_hints(conn, window); motif_wm_hints_cookie = GET_PROPERTY(A__MOTIF_WM_HINTS, 5 * sizeof(uint64_t)); wm_user_time_cookie = GET_PROPERTY(A__NET_WM_USER_TIME, UINT32_MAX); + wm_desktop_cookie = GET_PROPERTY(A__NET_WM_DESKTOP, UINT32_MAX); DLOG("Managing window 0x%08x\n", window); @@ -194,6 +195,16 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki char *startup_ws = startup_workspace_for_window(cwindow, startup_id_reply); DLOG("startup workspace = %s\n", startup_ws); + /* Get _NET_WM_DESKTOP if it was set. */ + xcb_get_property_reply_t *wm_desktop_reply; + wm_desktop_reply = xcb_get_property_reply(conn, wm_desktop_cookie, NULL); + cwindow->wm_desktop = NET_WM_DESKTOP_NONE; + if (wm_desktop_reply != NULL && xcb_get_property_value_length(wm_desktop_reply) != 0) { + uint32_t *wm_desktops = xcb_get_property_value(wm_desktop_reply); + cwindow->wm_desktop = (int32_t)wm_desktops[0]; + } + FREE(wm_desktop_reply); + /* check if the window needs WM_TAKE_FOCUS */ cwindow->needs_take_focus = window_supports_protocol(cwindow->id, A_WM_TAKE_FOCUS); @@ -244,6 +255,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki nc = con_for_window(search_at, cwindow, &match); const bool match_from_restart_mode = (match && match->restart_mode); if (nc == NULL) { + Con *wm_desktop_ws = NULL; + /* If not, check if it is assigned to a specific workspace */ if ((assignment = assignment_for(cwindow, A_TO_WORKSPACE))) { DLOG("Assignment matches (%p)\n", match); @@ -258,9 +271,23 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki /* set the urgency hint on the window if the workspace is not visible */ if (!workspace_is_visible(assigned_ws)) urgency_hint = true; + } else if (cwindow->wm_desktop != NET_WM_DESKTOP_NONE && + cwindow->wm_desktop != NET_WM_DESKTOP_ALL && + (wm_desktop_ws = ewmh_get_workspace_by_index(cwindow->wm_desktop)) != NULL) { + /* If _NET_WM_DESKTOP is set to a specific desktop, we open it + * there. Note that we ignore the special value 0xFFFFFFFF here + * since such a window will be made sticky anyway. */ + + DLOG("Using workspace %p / %s because _NET_WM_DESKTOP = %d.\n", + wm_desktop_ws, wm_desktop_ws->name, cwindow->wm_desktop); + + nc = con_descend_tiling_focused(wm_desktop_ws); + if (nc->type == CT_WORKSPACE) + nc = tree_open_con(nc, cwindow); + else + nc = tree_open_con(nc->parent, cwindow); } else if (startup_ws) { - /* If it’s not assigned, but was started on a specific workspace, - * we want to open it there */ + /* If it was started on a specific workspace, we want to open it there. */ DLOG("Using workspace on which this application was started (%s)\n", startup_ws); nc = con_descend_tiling_focused(workspace_get(startup_ws, NULL)); DLOG("focused on ws %s: %p / %s\n", startup_ws, nc, nc->name); @@ -393,6 +420,12 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki if (xcb_reply_contains_atom(state_reply, A__NET_WM_STATE_STICKY)) nc->sticky = true; + if (cwindow->wm_desktop == NET_WM_DESKTOP_ALL) { + DLOG("This window has _NET_WM_DESKTOP = 0xFFFFFFFF. Will float it and make it sticky.\n"); + nc->sticky = true; + want_floating = true; + } + FREE(state_reply); FREE(type_reply); @@ -574,6 +607,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki * needs to be on the final workspace first. */ con_set_urgency(nc, urgency_hint); + /* Update _NET_WM_DESKTOP. We invalidate the cached value first to force an update. */ + cwindow->wm_desktop = NET_WM_DESKTOP_NONE; + ewmh_update_wm_desktop(); + + /* If a sticky window was mapped onto another workspace, make sure to pop it to the front. */ + output_push_sticky_windows(focused); + geom_out: free(geom); out: diff --git a/src/move.c b/src/move.c index bd228a1c..87f78ee3 100644 --- a/src/move.c +++ b/src/move.c @@ -206,6 +206,7 @@ void tree_move(Con *con, int direction) { DLOG("Swapped.\n"); ipc_send_window_event("move", con); + ewmh_update_wm_desktop(); return; } @@ -214,6 +215,7 @@ void tree_move(Con *con, int direction) { * try to move it to a workspace on a different output */ move_to_output_directed(con, direction); ipc_send_window_event("move", con); + ewmh_update_wm_desktop(); return; } @@ -274,4 +276,5 @@ end: tree_flatten(croot); ipc_send_window_event("move", con); + ewmh_update_wm_desktop(); } diff --git a/src/workspace.c b/src/workspace.c index 923bfc83..ba19cb5f 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -101,6 +101,7 @@ Con *workspace_get(const char *num, bool *created) { ewmh_update_number_of_desktops(); ewmh_update_desktop_names(); ewmh_update_desktop_viewport(); + ewmh_update_wm_desktop(); if (created != NULL) *created = true; } else if (created != NULL) { @@ -463,6 +464,7 @@ static void _workspace_show(Con *workspace) { ewmh_update_number_of_desktops(); ewmh_update_desktop_names(); ewmh_update_desktop_viewport(); + ewmh_update_wm_desktop(); } } diff --git a/testcases/t/253-multiple-net-wm-state-atoms.t b/testcases/t/253-multiple-net-wm-state-atoms.t index 0ce7f9fd..bbd6c521 100644 --- a/testcases/t/253-multiple-net-wm-state-atoms.t +++ b/testcases/t/253-multiple-net-wm-state-atoms.t @@ -20,7 +20,6 @@ use X11::XCB qw(:all); sub get_wm_state { sync_with_i3; - my $atom = $x->atom(name => '_NET_WM_STATE_HIDDEN'); my ($con) = @_; my $cookie = $x->get_property( diff --git a/testcases/t/529-net-wm-desktop.t b/testcases/t/529-net-wm-desktop.t new file mode 100644 index 00000000..f6a3b218 --- /dev/null +++ b/testcases/t/529-net-wm-desktop.t @@ -0,0 +1,350 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Tests for _NET_WM_DESKTOP. +# Ticket: #2153 +use i3test i3_autostart => 0; +use X11::XCB qw(:all); + +############################################################################### + +sub get_net_wm_desktop { + sync_with_i3; + + my ($con) = @_; + my $cookie = $x->get_property( + 0, + $con->{id}, + $x->atom(name => '_NET_WM_DESKTOP')->id, + $x->atom(name => 'CARDINAL')->id, + 0, + 1 + ); + + my $reply = $x->get_property_reply($cookie->{sequence}); + return undef if $reply->{length} != 1; + + return unpack("L", $reply->{value}); +} + +sub send_net_wm_desktop { + my ($con, $idx) = @_; + my $msg = pack "CCSLLLLLL", + X11::XCB::CLIENT_MESSAGE, 32, 0, + $con->{id}, + $x->atom(name => '_NET_WM_DESKTOP')->id, + $idx, 0, 0, 0, 0; + + $x->send_event(0, $x->get_root_window(), X11::XCB::EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg); + sync_with_i3; +} + +sub open_window_with_net_wm_desktop { + my $idx = shift; + my $window = open_window( + before_map => sub { + my ($window) = @_; + $x->change_property( + PROP_MODE_REPLACE, + $window->id, + $x->atom(name => '_NET_WM_DESKTOP')->id, + $x->atom(name => 'CARDINAL')->id, + 32, 1, + pack('L', $idx), + ); + }, + ); + + return $window; +} + +# We need to kill all windows in between tests since they survive the i3 restart +# and will interfere with the following tests. +sub kill_windows { + sync_with_i3; + cmd '[title="Window.*"] kill'; +} + +############################################################################### + +my ($config, $config_mm, $pid, $con); + +$config = <{floating_nodes}}, 1, 'The window is floating'); +ok(get_ws('0')->{floating_nodes}->[0]->{nodes}->[0]->{sticky}, 'The window is sticky'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# _NET_WM_DESKTOP is updated when the window is moved to another workspace +# on the same output. +############################################################################### + +$pid = launch_with_config($config); + +cmd 'workspace 0'; +open_window; +cmd 'workspace 1'; +open_window; +cmd 'workspace 0'; +$con = open_window; + +cmd 'move window to workspace 1'; + +is(get_net_wm_desktop($con), 1, '_NET_WM_DESKTOP is updated when moving the window'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# _NET_WM_DESKTOP is updated when the floating window is moved to another +# workspace on the same output. +############################################################################### + +$pid = launch_with_config($config); + +cmd 'workspace 0'; +open_window; +cmd 'workspace 1'; +open_window; +cmd 'workspace 0'; +$con = open_window; +cmd 'floating enable'; + +cmd 'move window to workspace 1'; + +is(get_net_wm_desktop($con), 1, '_NET_WM_DESKTOP is updated when moving the window'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# _NET_WM_DESKTOP is updated when the window is moved to another workspace +# on another output. +############################################################################### + +$pid = launch_with_config($config_mm); + +cmd 'workspace 0'; +open_window; +cmd 'workspace 10'; +open_window; +cmd 'workspace 0'; +$con = open_window; + +cmd 'move window to workspace 10'; + +is(get_net_wm_desktop($con), 1, '_NET_WM_DESKTOP is updated when moving the window'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# _NET_WM_DESKTOP is removed when the window is withdrawn. +############################################################################### + +$pid = launch_with_config($config); + +$con = open_window; +is(get_net_wm_desktop($con), 0, '_NET_WM_DESKTOP is set (sanity check)'); + +$con->unmap; +wait_for_unmap($con); + +is(get_net_wm_desktop($con), undef, '_NET_WM_DESKTOP is removed'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# A _NET_WM_DESKTOP client message sent to the root window moves a window +# to the correct workspace. +############################################################################### + +$pid = launch_with_config($config); + +cmd 'workspace 0'; +open_window; +cmd 'workspace 1'; +open_window; +cmd 'workspace 0'; + +$con = open_window; +is_num_children('0', 2, 'The window is on workspace 0'); + +send_net_wm_desktop($con, 1); + +is_num_children('0', 1, 'The window is no longer on workspace 0'); +is_num_children('1', 2, 'The window is now on workspace 1'); +is(get_net_wm_desktop($con), 1, '_NET_WM_DESKTOP is updated'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# A _NET_WM_DESKTOP client message sent to the root window can make a window +# sticky. +############################################################################### + +$pid = launch_with_config($config); + +cmd 'workspace 0'; +$con = open_window; + +send_net_wm_desktop($con, 0xFFFFFFFF); + +is(get_net_wm_desktop($con), 0xFFFFFFFF, '_NET_WM_DESKTOP is updated'); +is(@{get_ws('0')->{floating_nodes}}, 1, 'The window is floating'); +ok(get_ws('0')->{floating_nodes}->[0]->{nodes}->[0]->{sticky}, 'The window is sticky'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# _NET_WM_DESKTOP is updated when a new workspace with a lower number is +# opened and closed. +############################################################################### + +$pid = launch_with_config($config); + +cmd 'workspace 1'; +$con = open_window; +is(get_net_wm_desktop($con), 0, '_NET_WM_DESKTOP is set sanity check)'); + +cmd 'workspace 0'; +is(get_net_wm_desktop($con), 1, '_NET_WM_DESKTOP is updated'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# _NET_WM_DESKTOP is updated when a window is made sticky by command. +############################################################################### + +$pid = launch_with_config($config); + +cmd 'workspace 0'; +$con = open_window; +cmd 'floating enable'; +is(get_net_wm_desktop($con), 0, '_NET_WM_DESKTOP is set sanity check)'); + +cmd 'sticky enable'; +is(get_net_wm_desktop($con), 0xFFFFFFFF, '_NET_WM_DESKTOP is updated'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### +# _NET_WM_DESKTOP is updated when a window is made sticky by client message. +############################################################################### + +$pid = launch_with_config($config); + +cmd 'workspace 0'; +$con = open_window; +cmd 'floating enable'; +is(get_net_wm_desktop($con), 0, '_NET_WM_DESKTOP is set sanity check)'); + +my $msg = pack "CCSLLLLLL", + X11::XCB::CLIENT_MESSAGE, 32, 0, + $con->{id}, + $x->atom(name => '_NET_WM_STATE')->id, + 1, + $x->atom(name => '_NET_WM_STATE_STICKY')->id, + 0, 0, 0; + +$x->send_event(0, $x->get_root_window(), X11::XCB::EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg); +sync_with_i3; + +is(get_net_wm_desktop($con), 0xFFFFFFFF, '_NET_WM_DESKTOP is updated'); + +kill_windows; +exit_gracefully($pid); + +############################################################################### + +done_testing;