From 6dfb0ca8ab976657dd8068bda1af979edfafc581 Mon Sep 17 00:00:00 2001 From: Philipp Kaeser <130065133+phkaeser@users.noreply.github.com> Date: Mon, 9 Dec 2024 22:02:29 +0100 Subject: [PATCH] Adds events for wlmtk_window_t, and a signal for window state changes. (#148) * Adds wlmtk_window_events_t.* * Wires up state-changed signal when (un)maximizing window. * Adds missing doxygen comment. * Clarify window state scope. * Wires up state_changed signal in fullscreen call. * Adds tests for window shading, and document behaviour on client-side-decoration. * Wires up state_change signal from window-shading. --- src/toolkit/window.c | 120 ++++++++++++++++++++++++++++++++++++++++++- src/toolkit/window.h | 24 +++++++++ 2 files changed, 143 insertions(+), 1 deletion(-) diff --git a/src/toolkit/window.c b/src/toolkit/window.c index 2db7f14f..a468ae23 100644 --- a/src/toolkit/window.c +++ b/src/toolkit/window.c @@ -77,6 +77,9 @@ struct _wlmtk_window_t { /** Virtual method table. */ wlmtk_window_vmt_t vmt; + /** Events for this window. */ + wlmtk_window_events_t events; + /** Box: In `super_bordered`, holds surface, title bar and resizebar. */ wlmtk_box_t box; @@ -263,6 +266,13 @@ void wlmtk_window_destroy(wlmtk_window_t *window_ptr) free(window_ptr); } +/* ------------------------------------------------------------------------- */ +wlmtk_window_events_t *wlmtk_window_events( + wlmtk_window_t *window_ptr) +{ + return &window_ptr->events; +} + /* ------------------------------------------------------------------------- */ wlmtk_element_t *wlmtk_window_element(wlmtk_window_t *window_ptr) { @@ -315,8 +325,14 @@ void wlmtk_window_set_server_side_decorated( bool decorated) { if (window_ptr->server_side_decorated == decorated) return; + + if (!decorated && wlmtk_window_is_shaded(window_ptr)) { + wlmtk_window_request_shaded(window_ptr, false); + } + window_ptr->server_side_decorated = decorated; _wlmtk_window_apply_decoration(window_ptr); + } /* ------------------------------------------------------------------------- */ @@ -430,6 +446,7 @@ void wlmtk_window_commit_maximized( if (window_ptr->maximized == maximized) return; window_ptr->maximized = maximized; + wl_signal_emit(&window_ptr->events.state_changed, window_ptr); } /* ------------------------------------------------------------------------- */ @@ -494,6 +511,7 @@ void wlmtk_window_commit_fullscreen( wlmtk_workspace_window_to_fullscreen( wlmtk_window_get_workspace(window_ptr), window_ptr, fullscreen); + wl_signal_emit(&window_ptr->events.state_changed, window_ptr); } /* ------------------------------------------------------------------------- */ @@ -517,6 +535,7 @@ void wlmtk_window_request_shaded(wlmtk_window_t *window_ptr, bool shaded) } window_ptr->shaded = shaded; + wl_signal_emit(&window_ptr->events.state_changed, window_ptr); } /* ------------------------------------------------------------------------- */ @@ -725,6 +744,8 @@ bool _wlmtk_window_init( wlmtk_box_add_element_front(&window_ptr->box, element_ptr); wlmtk_element_set_visible(element_ptr, true); + + wl_signal_init(&window_ptr->events.state_changed); return true; } @@ -1263,6 +1284,7 @@ static void test_server_side_decorated_properties(bs_test_t *test_ptr); static void test_maximize(bs_test_t *test_ptr); static void test_fullscreen(bs_test_t *test_ptr); static void test_fullscreen_unmap(bs_test_t *test_ptr); +static void test_shade(bs_test_t *test_ptr); static void test_fake(bs_test_t *test_ptr); const bs_test_case_t wlmtk_window_test_cases[] = { @@ -1276,10 +1298,18 @@ const bs_test_case_t wlmtk_window_test_cases[] = { { 1, "maximize", test_maximize }, { 1, "fullscreen", test_fullscreen }, { 1, "fullscreen_unmap", test_fullscreen_unmap }, + { 1, "shade", test_shade }, { 1, "fake", test_fake }, { 0, NULL, NULL } }; +/** For testing: Tracks whether handle_state_change was called. */ +static bool _wlmtk_window_test_handle_state_changed_called; + +static void _wlmtk_window_test_handle_state_changed( + struct wl_listener *listener_ptr, + void *data_ptr); + /* ------------------------------------------------------------------------- */ /** Tests setup and teardown. */ void test_create_destroy(bs_test_t *test_ptr) @@ -1449,12 +1479,18 @@ void test_server_side_decorated_properties(bs_test_t *test_ptr) void test_maximize(bs_test_t *test_ptr) { struct wlr_box box; + struct wl_listener listener; wlmtk_workspace_t *ws_ptr = wlmtk_workspace_create_for_test(1024, 768, 0); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ws_ptr); wlmtk_fake_window_t *fw_ptr = wlmtk_fake_window_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fw_ptr); + wlmtk_util_connect_listener_signal( + &wlmtk_window_events(fw_ptr->window_ptr)->state_changed, + &listener, + _wlmtk_window_test_handle_state_changed); + // Window must be mapped to get maximized: Need workspace dimensions. wlmtk_workspace_map_window(ws_ptr, fw_ptr->window_ptr); @@ -1485,9 +1521,11 @@ void test_maximize(bs_test_t *test_ptr) BS_TEST_VERIFY_EQ(test_ptr, 100, box.height); // Maximize. + _wlmtk_window_test_handle_state_changed_called = false; wlmtk_window_request_maximized(fw_ptr->window_ptr, true); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_maximized(fw_ptr->window_ptr)); wlmtk_fake_window_commit_size(fw_ptr); + BS_TEST_VERIFY_FALSE(test_ptr, _wlmtk_window_test_handle_state_changed_called); wlmtk_window_commit_maximized(fw_ptr->window_ptr, true); box = wlmtk_window_get_position_and_size(fw_ptr->window_ptr); BS_TEST_VERIFY_EQ(test_ptr, 0, box.x); @@ -1495,6 +1533,8 @@ void test_maximize(bs_test_t *test_ptr) BS_TEST_VERIFY_EQ(test_ptr, 960, box.width); BS_TEST_VERIFY_EQ(test_ptr, 704, box.height); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_maximized(fw_ptr->window_ptr)); + BS_TEST_VERIFY_TRUE(test_ptr, _wlmtk_window_test_handle_state_changed_called); + _wlmtk_window_test_handle_state_changed_called = false; // A second commit: should not overwrite the organic dimension. wlmtk_fake_window_commit_size(fw_ptr); @@ -1503,6 +1543,7 @@ void test_maximize(bs_test_t *test_ptr) wlmtk_window_request_maximized(fw_ptr->window_ptr, false); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_maximized(fw_ptr->window_ptr)); wlmtk_fake_window_commit_size(fw_ptr); + BS_TEST_VERIFY_FALSE(test_ptr, _wlmtk_window_test_handle_state_changed_called); wlmtk_window_commit_maximized(fw_ptr->window_ptr, false); box = wlmtk_window_get_position_and_size(fw_ptr->window_ptr); BS_TEST_VERIFY_EQ(test_ptr, 50, box.x); @@ -1510,6 +1551,7 @@ void test_maximize(bs_test_t *test_ptr) BS_TEST_VERIFY_EQ(test_ptr, 200, box.width); BS_TEST_VERIFY_EQ(test_ptr, 100, box.height); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_maximized(fw_ptr->window_ptr)); + BS_TEST_VERIFY_TRUE(test_ptr, _wlmtk_window_test_handle_state_changed_called); // TODO(kaeser@gubbe.ch): Define what should happen when a maximized // window is moved. Should it lose maximization? Should it not move? @@ -1517,6 +1559,7 @@ void test_maximize(bs_test_t *test_ptr) // Window Maker keeps maximization, but it's ... odd. wlmtk_workspace_unmap_window(ws_ptr, fw_ptr->window_ptr); + wlmtk_util_disconnect_listener(&listener); wlmtk_fake_window_destroy(fw_ptr); wlmtk_workspace_destroy(ws_ptr); } @@ -1526,12 +1569,18 @@ void test_maximize(bs_test_t *test_ptr) void test_fullscreen(bs_test_t *test_ptr) { struct wlr_box box; + struct wl_listener listener; wlmtk_workspace_t *ws_ptr = wlmtk_workspace_create_for_test(1024, 768, 0); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ws_ptr); wlmtk_fake_window_t *fw_ptr = wlmtk_fake_window_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fw_ptr); + wlmtk_util_connect_listener_signal( + &wlmtk_window_events(fw_ptr->window_ptr)->state_changed, + &listener, + _wlmtk_window_test_handle_state_changed); + wlmtk_window_set_server_side_decorated(fw_ptr->window_ptr, true); wlmtk_workspace_map_window(ws_ptr, fw_ptr->window_ptr); @@ -1552,11 +1601,13 @@ void test_fullscreen(bs_test_t *test_ptr) BS_TEST_VERIFY_FALSE(test_ptr, fw_ptr->window_ptr->inorganic_sizing); // Request fullscreen. Does not take immediate effect. + _wlmtk_window_test_handle_state_changed_called = false; wlmtk_window_request_fullscreen(fw_ptr->window_ptr, true); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_fullscreen(fw_ptr->window_ptr)); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_titlebar_is_activated(fw_ptr->window_ptr->titlebar_ptr)); + BS_TEST_VERIFY_FALSE(test_ptr, _wlmtk_window_test_handle_state_changed_called); // Only after "commit", it will take effect. wlmtk_fake_window_commit_size(fw_ptr); @@ -1567,6 +1618,8 @@ void test_fullscreen(bs_test_t *test_ptr) BS_TEST_VERIFY_EQ(test_ptr, 0, box.y); BS_TEST_VERIFY_EQ(test_ptr, 1024, box.width); BS_TEST_VERIFY_EQ(test_ptr, 768, box.height); + BS_TEST_VERIFY_TRUE(test_ptr, _wlmtk_window_test_handle_state_changed_called); + _wlmtk_window_test_handle_state_changed_called = false; BS_TEST_VERIFY_TRUE(test_ptr, fw_ptr->fake_content_ptr->activated); BS_TEST_VERIFY_EQ( @@ -1581,6 +1634,7 @@ void test_fullscreen(bs_test_t *test_ptr) // Request to end fullscreen. Not taking immediate effect. wlmtk_window_request_fullscreen(fw_ptr->window_ptr, false); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_fullscreen(fw_ptr->window_ptr)); + BS_TEST_VERIFY_FALSE(test_ptr, _wlmtk_window_test_handle_state_changed_called); // Takes effect after commit. We'll want the same position as before. wlmtk_fake_window_commit_size(fw_ptr); @@ -1600,14 +1654,16 @@ void test_fullscreen(bs_test_t *test_ptr) test_ptr, fw_ptr->window_ptr, wlmtk_workspace_get_activated_window(ws_ptr)); + BS_TEST_VERIFY_TRUE(test_ptr, _wlmtk_window_test_handle_state_changed_called); + _wlmtk_window_test_handle_state_changed_called = false; BS_TEST_VERIFY_TRUE(test_ptr, fw_ptr->window_ptr->server_side_decorated); BS_TEST_VERIFY_NEQ(test_ptr, NULL, fw_ptr->window_ptr->titlebar_ptr); BS_TEST_VERIFY_NEQ(test_ptr, NULL, fw_ptr->window_ptr->resizebar_ptr); wlmtk_workspace_unmap_window(ws_ptr, fw_ptr->window_ptr); + wlmtk_util_disconnect_listener(&listener); wlmtk_fake_window_destroy(fw_ptr); - wlmtk_workspace_destroy(ws_ptr); } @@ -1656,6 +1712,59 @@ void test_fullscreen_unmap(bs_test_t *test_ptr) wlmtk_workspace_destroy(ws_ptr); } +/* ------------------------------------------------------------------------- */ +/** Verifies that window shading hides the element and raises signal. */ +void test_shade(bs_test_t *test_ptr) +{ + struct wl_listener listener; + + wlmtk_fake_window_t *fw_ptr = wlmtk_fake_window_create(); + BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fw_ptr); + wlmtk_util_connect_listener_signal( + &wlmtk_window_events(fw_ptr->window_ptr)->state_changed, + &listener, + _wlmtk_window_test_handle_state_changed); + + BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_shaded(fw_ptr->window_ptr)); + _wlmtk_window_test_handle_state_changed_called = false; + + // Shading only works on server-side-decorated windows. + wlmtk_window_set_server_side_decorated(fw_ptr->window_ptr, true); + + wlmtk_window_request_shaded(fw_ptr->window_ptr, true); + BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_shaded(fw_ptr->window_ptr)); + BS_TEST_VERIFY_FALSE( + test_ptr, + wlmtk_content_element(&fw_ptr->fake_content_ptr->content)->visible); + BS_TEST_VERIFY_TRUE(test_ptr, _wlmtk_window_test_handle_state_changed_called); + _wlmtk_window_test_handle_state_changed_called = false; + + wlmtk_window_request_shaded(fw_ptr->window_ptr, false); + BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_shaded(fw_ptr->window_ptr)); + BS_TEST_VERIFY_TRUE(test_ptr, _wlmtk_window_test_handle_state_changed_called); + _wlmtk_window_test_handle_state_changed_called = false; + + // Shading not supported on client-side decoration. Must be disabled. + wlmtk_window_request_shaded(fw_ptr->window_ptr, true); + BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_shaded(fw_ptr->window_ptr)); + BS_TEST_VERIFY_TRUE(test_ptr, _wlmtk_window_test_handle_state_changed_called); + _wlmtk_window_test_handle_state_changed_called = false; + + wlmtk_window_set_server_side_decorated(fw_ptr->window_ptr, false); + BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_shaded(fw_ptr->window_ptr)); + BS_TEST_VERIFY_TRUE(test_ptr, _wlmtk_window_test_handle_state_changed_called); + _wlmtk_window_test_handle_state_changed_called = false; + + // Verify that 'shading' on client decorations does not do anything. + wlmtk_window_set_server_side_decorated(fw_ptr->window_ptr, false); + wlmtk_window_request_shaded(fw_ptr->window_ptr, true); + BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_shaded(fw_ptr->window_ptr)); + BS_TEST_VERIFY_FALSE(test_ptr, _wlmtk_window_test_handle_state_changed_called); + + wlmtk_util_disconnect_listener(&listener); + wlmtk_fake_window_destroy(fw_ptr); +} + /* ------------------------------------------------------------------------- */ /** Tests fake window ctor and dtor. */ void test_fake(bs_test_t *test_ptr) @@ -1665,4 +1774,13 @@ void test_fake(bs_test_t *test_ptr) wlmtk_fake_window_destroy(fake_window_ptr); } +/* ------------------------------------------------------------------------- */ +/** Reports a state change. */ +void _wlmtk_window_test_handle_state_changed( + __UNUSED__ struct wl_listener *listener_ptr, + __UNUSED__ void *data_ptr) +{ + _wlmtk_window_test_handle_state_changed_called = true; +} + /* == End of window.c ====================================================== */ diff --git a/src/toolkit/window.h b/src/toolkit/window.h index 672e215d..74cd7a91 100644 --- a/src/toolkit/window.h +++ b/src/toolkit/window.h @@ -39,6 +39,19 @@ typedef struct _wlmtk_window_t wlmtk_window_t; extern "C" { #endif // __cplusplus +/** Signals available for the @ref wlmtk_window_t class. */ +typedef struct { + /** + * Signals that the window state (maximize, iconify, ...) changed. + * + * Window state can be retrieved from: + * - @ref wlmtk_window_is_maximized + * - @ref wlmtk_window_is_fullscreen + * - @ref wlmtk_window_is_shaded + */ + struct wl_signal state_changed; +} wlmtk_window_events_t; + /** Style options for the window. */ typedef struct { /** The titlebar's style. */ @@ -85,6 +98,16 @@ wlmtk_window_t *wlmtk_window_create( const wlmtk_menu_style_t *menu_style_ptr, wlmtk_env_t *env_ptr); +/** + * Gets the set of events available to a window, for binding listeners. + * + * @param window_ptr + * + * @return Pointer to this window's @ref wlmtk_window_t::events. + */ +wlmtk_window_events_t *wlmtk_window_events( + wlmtk_window_t *window_ptr); + /** * Destroys the window. * @@ -327,6 +350,7 @@ bool wlmtk_window_is_fullscreen(wlmtk_window_t *window_ptr); * Requests the window to be "shaded", ie. rolled-up to just the title bar. * * This is supported only for server-side decorated windows. + * * @param window_ptr * @param shaded */