diff --git a/doc/ROADMAP.md b/doc/ROADMAP.md index 3976343a..90004b29 100644 --- a/doc/ROADMAP.md +++ b/doc/ROADMAP.md @@ -20,7 +20,7 @@ Support for visual effects to improve usability, but not for pure show. * Menu, based on toolkit. * [done] Root menu: Basic actions (quit, lock, next- or previous workspace). * [done] Menu shown on right-button-down, items trigger on right-button-up. - * When invoked on unclaimed button, exits menu on button release. + * [done] When invoked on unclaimed button, exits menu on button release. * Available as window menu in windows. * Available as (hardcoded) application menu. * Menu with submenus. diff --git a/src/action.c b/src/action.c index 6e2917c4..a6ed9d3c 100644 --- a/src/action.c +++ b/src/action.c @@ -260,6 +260,7 @@ void wlmaker_action_execute(wlmaker_server_t *server_ptr, server_ptr, &server_ptr->style.window, &server_ptr->style.menu, + false, server_ptr->env_ptr); } diff --git a/src/root_menu.c b/src/root_menu.c index 34fa1522..34d1c344 100644 --- a/src/root_menu.c +++ b/src/root_menu.c @@ -74,6 +74,7 @@ wlmaker_root_menu_t *wlmaker_root_menu_create( wlmaker_server_t *server_ptr, const wlmtk_window_style_t *window_style_ptr, const wlmtk_menu_style_t *menu_style_ptr, + bool right_click_mode, wlmtk_env_t *env_ptr) { wlmaker_root_menu_t *root_menu_ptr = logged_calloc( @@ -88,6 +89,11 @@ wlmaker_root_menu_t *wlmaker_root_menu_create( wlmaker_root_menu_destroy(root_menu_ptr); return NULL; } + if (right_click_mode) { + wlmtk_menu_set_mode( + wlmaker_root_menu_menu(server_ptr->root_menu_ptr), + WLMTK_MENU_MODE_RIGHTCLICK); + } for (const wlmaker_root_menu_item_t *i_ptr = &_wlmaker_root_menu_items[0]; i_ptr->text_ptr != NULL; @@ -137,9 +143,13 @@ wlmaker_root_menu_t *wlmaker_root_menu_create( } wlmtk_window_set_title(root_menu_ptr->window_ptr, "Root Menu"); wlmtk_window_set_server_side_decorated(root_menu_ptr->window_ptr, true); - wlmtk_window_set_properties( - root_menu_ptr->window_ptr, - WLMTK_WINDOW_PROPERTY_CLOSABLE); + uint32_t properties = 0; + if (right_click_mode) { + properties |= WLMTK_WINDOW_PROPERTY_RIGHTCLICK; + } else { + properties |= WLMTK_WINDOW_PROPERTY_CLOSABLE; + } + wlmtk_window_set_properties(root_menu_ptr->window_ptr, properties); return root_menu_ptr; } diff --git a/src/root_menu.h b/src/root_menu.h index 8adf1fe3..6b638dd6 100644 --- a/src/root_menu.h +++ b/src/root_menu.h @@ -38,6 +38,7 @@ extern "C" { * @param window_style_ptr * @param menu_style_ptr * @param env_ptr + * @param right_click_mode * * @return Handle of the root menu, or NULL on error. */ @@ -45,6 +46,7 @@ wlmaker_root_menu_t *wlmaker_root_menu_create( wlmaker_server_t *server_ptr, const wlmtk_window_style_t *window_style_ptr, const wlmtk_menu_style_t *menu_style_ptr, + bool right_click_mode, wlmtk_env_t *env_ptr); /** diff --git a/src/server.c b/src/server.c index eb4b6f29..18e5a036 100644 --- a/src/server.c +++ b/src/server.c @@ -798,18 +798,19 @@ void _wlmaker_server_unclaimed_button_event_handler( server_ptr, &server_ptr->style.window, &server_ptr->style.menu, + true, server_ptr->env_ptr); if (NULL != server_ptr->root_menu_ptr) { - wlmtk_menu_set_mode( - wlmaker_root_menu_menu(server_ptr->root_menu_ptr), - WLMTK_MENU_MODE_RIGHTCLICK); wlmtk_window_t *window_ptr = wlmaker_root_menu_window( server_ptr->root_menu_ptr); wlmtk_workspace_t *workspace_ptr = wlmtk_root_get_current_workspace(server_ptr->root_ptr); wlmtk_workspace_map_window(workspace_ptr, window_ptr); + wlmtk_container_pointer_grab( + wlmtk_window_element(window_ptr)->parent_container_ptr, + wlmtk_window_element(window_ptr)); // TODO(kaeser@gubbe.ch): Keep the menu window's position entirely // within the desktop area. diff --git a/src/toolkit/container.c b/src/toolkit/container.c index 7accbf0b..8f7daba2 100644 --- a/src/toolkit/container.c +++ b/src/toolkit/container.c @@ -58,6 +58,8 @@ static bool _wlmtk_container_element_pointer_axis( struct wlr_pointer_axis_event *wlr_pointer_axis_event_ptr); static void _wlmtk_container_element_pointer_enter( wlmtk_element_t *element_ptr); +static void _wlmtk_container_element_pointer_grab_cancel( + wlmtk_element_t *element_ptr); static void _wlmtk_container_element_keyboard_blur( wlmtk_element_t *element_ptr); static bool _wlmtk_container_element_keyboard_event( @@ -86,6 +88,7 @@ static const wlmtk_element_vmt_t container_element_vmt = { .pointer_button = _wlmtk_container_element_pointer_button, .pointer_axis = _wlmtk_container_element_pointer_axis, .pointer_enter = _wlmtk_container_element_pointer_enter, + .pointer_grab_cancel = _wlmtk_container_element_pointer_grab_cancel, .keyboard_blur = _wlmtk_container_element_keyboard_blur, .keyboard_event = _wlmtk_container_element_keyboard_event, }; @@ -177,6 +180,9 @@ void wlmtk_container_add_element( BS_ASSERT(NULL == element_ptr->parent_container_ptr); BS_ASSERT(NULL == element_ptr->wlr_scene_node_ptr); + // Before adding the element: Clear potentially set grabs in the child. + wlmtk_element_pointer_grab_cancel(element_ptr); + bs_dllist_push_front( &container_ptr->elements, wlmtk_dlnode_from_element(element_ptr)); @@ -235,6 +241,15 @@ void wlmtk_container_remove_element( &container_ptr->elements, wlmtk_dlnode_from_element(element_ptr)); + if (container_ptr->pointer_grab_element_ptr == element_ptr) { + _wlmtk_container_element_pointer_grab_cancel( + &container_ptr->super_element); + if (NULL != container_ptr->super_element.parent_container_ptr) { + wlmtk_container_pointer_grab_release( + container_ptr->super_element.parent_container_ptr, + &container_ptr->super_element); + } + } if (container_ptr->left_button_element_ptr == element_ptr) { container_ptr->left_button_element_ptr = NULL; } @@ -288,6 +303,59 @@ void wlmtk_container_update_pointer_focus(wlmtk_container_t *container_ptr) } } +/* ------------------------------------------------------------------------- */ +void wlmtk_container_pointer_grab( + wlmtk_container_t *container_ptr, + wlmtk_element_t *element_ptr) +{ + BS_ASSERT(NULL != element_ptr); + BS_ASSERT(container_ptr == element_ptr->parent_container_ptr); + // We only accept elements that have a grab_cancel method. + BS_ASSERT(NULL != element_ptr->vmt.pointer_grab_cancel); + + if (container_ptr->pointer_grab_element_ptr == element_ptr) return; + + // Cancel a currently-held grab. + _wlmtk_container_element_pointer_grab_cancel( + &container_ptr->super_element); + + // Then, setup the grab. + container_ptr->pointer_grab_element_ptr = element_ptr; + if (NULL != container_ptr->super_element.parent_container_ptr) { + wlmtk_container_pointer_grab( + container_ptr->super_element.parent_container_ptr, + &container_ptr->super_element); + } + + if (NULL != container_ptr->pointer_focus_element_ptr && + container_ptr->pointer_focus_element_ptr != element_ptr) { + wlmtk_element_pointer_motion( + container_ptr->pointer_focus_element_ptr, + NAN, NAN, 0); + } +} + +/* ------------------------------------------------------------------------- */ +void wlmtk_container_pointer_grab_release( + wlmtk_container_t *container_ptr, + wlmtk_element_t *element_ptr) +{ + BS_ASSERT(NULL != element_ptr); + BS_ASSERT(container_ptr == element_ptr->parent_container_ptr); + + if (container_ptr->pointer_grab_element_ptr != element_ptr) return; + + container_ptr->pointer_grab_element_ptr = NULL; + if (NULL != container_ptr->super_element.parent_container_ptr) { + wlmtk_container_pointer_grab_release( + container_ptr->super_element.parent_container_ptr, + &container_ptr->super_element); + } else { + // Re-trigger focus computation, from top-level. + wlmtk_container_update_pointer_focus(container_ptr); + } +} + /* ------------------------------------------------------------------------- */ void wlmtk_container_set_keyboard_focus_element( wlmtk_container_t *container_ptr, @@ -511,6 +579,12 @@ bool _wlmtk_container_element_pointer_button( element_ptr, wlmtk_container_t, super_element); bool accepted = false; + if (NULL != container_ptr->pointer_grab_element_ptr) { + return wlmtk_element_pointer_button( + container_ptr->pointer_grab_element_ptr, + button_event_ptr); + } + // TODO: Generalize this for non-LEFT buttons. if (BTN_LEFT == button_event_ptr->button) { @@ -586,6 +660,12 @@ bool _wlmtk_container_element_pointer_axis( wlmtk_container_t *container_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_container_t, super_element); + if (NULL != container_ptr->pointer_grab_element_ptr) { + return wlmtk_element_pointer_axis( + container_ptr->pointer_grab_element_ptr, + wlr_pointer_axis_event_ptr); + } + if (NULL == container_ptr->pointer_focus_element_ptr) return false; return wlmtk_element_pointer_axis( @@ -601,6 +681,27 @@ void _wlmtk_container_element_pointer_enter( // Nothing. Do not call parent. } +/* ------------------------------------------------------------------------- */ +/** + * Implements @ref wlmtk_element_vmt_t::pointer_grab_cancel. + * + * Cancels an existing pointer grab. + * + * @param element_ptr + */ +void _wlmtk_container_element_pointer_grab_cancel( + wlmtk_element_t *element_ptr) +{ + wlmtk_container_t *container_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_container_t, super_element); + + if (NULL == container_ptr->pointer_grab_element_ptr) return; + + wlmtk_element_pointer_grab_cancel( + container_ptr->pointer_grab_element_ptr); + container_ptr->pointer_grab_element_ptr = NULL; +} + /* ------------------------------------------------------------------------- */ /** Implements @ref wlmtk_element_vmt_t::keyboard_blur. Blurs all children. */ void _wlmtk_container_element_keyboard_blur(wlmtk_element_t *element_ptr) @@ -685,6 +786,16 @@ bool update_pointer_focus_at( double y, uint32_t time_msec) { + if (NULL != container_ptr->pointer_grab_element_ptr) { + int x_pos, y_pos; + wlmtk_element_get_position( + container_ptr->pointer_grab_element_ptr, &x_pos, &y_pos); + wlmtk_element_pointer_motion( + container_ptr->pointer_grab_element_ptr, + x - x_pos, y - y_pos, time_msec); + return true; + } + for (bs_dllist_node_t *dlnode_ptr = container_ptr->elements.head_ptr; dlnode_ptr != NULL; dlnode_ptr = dlnode_ptr->next_ptr) { @@ -811,6 +922,8 @@ static void test_pointer_focus_move(bs_test_t *test_ptr); static void test_pointer_focus_layered(bs_test_t *test_ptr); static void test_pointer_button(bs_test_t *test_ptr); static void test_pointer_axis(bs_test_t *test_ptr); +static void test_pointer_grab(bs_test_t *test_ptr); +static void test_pointer_grab_events(bs_test_t *test_ptr); static void test_keyboard_event(bs_test_t *test_ptr); static void test_keyboard_focus(bs_test_t *test_ptr); @@ -825,6 +938,8 @@ const bs_test_case_t wlmtk_container_test_cases[] = { { 1, "pointer_focus_layered", test_pointer_focus_layered }, { 1, "pointer_button", test_pointer_button }, { 1, "pointer_axis", test_pointer_axis }, + { 1, "pointer_grab", test_pointer_grab }, + { 1, "pointer_grab_events", test_pointer_grab_events }, { 1, "keyboard_event", test_keyboard_event }, { 1, "keyboard_focus", test_keyboard_focus }, { 0, NULL, NULL } @@ -1550,6 +1665,143 @@ void test_pointer_button(bs_test_t *test_ptr) wlmtk_container_fini(&container); } +/* ------------------------------------------------------------------------- */ +/** + * Tests @ref wlmtk_container_pointer_grab and + * @ref wlmtk_container_pointer_grab_release. + */ +void test_pointer_grab(bs_test_t *test_ptr) +{ + wlmtk_container_t c, p; + BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, wlmtk_container_init(&c, NULL)); + BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, wlmtk_container_init(&p, NULL)); + wlmtk_container_add_element(&p, &c.super_element); + + wlmtk_fake_element_t *fe1_ptr = wlmtk_fake_element_create(); + BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe1_ptr); + wlmtk_container_add_element(&c, &fe1_ptr->element); + + wlmtk_fake_element_t *fe2_ptr = wlmtk_fake_element_create(); + BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe2_ptr); + wlmtk_container_add_element(&c, &fe2_ptr->element); + fe1_ptr->pointer_grab_cancel_called = false; + fe2_ptr->pointer_grab_cancel_called = false; + + // Basic grab/release flow: Will not call pointer_grab_cancel(). + wlmtk_container_pointer_grab(&c, &fe1_ptr->element); + BS_TEST_VERIFY_EQ(test_ptr, &fe1_ptr->element, c.pointer_grab_element_ptr); + BS_TEST_VERIFY_EQ(test_ptr, &c.super_element, p.pointer_grab_element_ptr); + wlmtk_container_pointer_grab_release(&c, &fe1_ptr->element); + BS_TEST_VERIFY_FALSE(test_ptr, fe1_ptr->pointer_grab_cancel_called); + BS_TEST_VERIFY_FALSE(test_ptr, fe2_ptr->pointer_grab_cancel_called); + BS_TEST_VERIFY_EQ(test_ptr, NULL, c.pointer_grab_element_ptr); + BS_TEST_VERIFY_EQ(test_ptr, NULL, p.pointer_grab_element_ptr); + + // Grab that is taken over by the other element: Must be cancelled. + wlmtk_container_pointer_grab(&c, &fe1_ptr->element); + wlmtk_container_pointer_grab(&c, &fe2_ptr->element); + BS_TEST_VERIFY_TRUE(test_ptr, fe1_ptr->pointer_grab_cancel_called); + BS_TEST_VERIFY_FALSE(test_ptr, fe2_ptr->pointer_grab_cancel_called); + BS_TEST_VERIFY_EQ(test_ptr, &fe2_ptr->element, c.pointer_grab_element_ptr); + BS_TEST_VERIFY_EQ(test_ptr, &c.super_element, p.pointer_grab_element_ptr); + + // When removing element with the grab: Call cancel first. + wlmtk_container_remove_element(&c, &fe2_ptr->element); + BS_TEST_VERIFY_TRUE(test_ptr, fe2_ptr->pointer_grab_cancel_called); + wlmtk_element_destroy(&fe2_ptr->element); + BS_TEST_VERIFY_EQ( test_ptr, NULL, c.pointer_grab_element_ptr); + BS_TEST_VERIFY_EQ(test_ptr, NULL, p.pointer_grab_element_ptr); + + wlmtk_container_remove_element(&p, &c.super_element); + wlmtk_container_fini(&p); + wlmtk_container_fini(&c); +} + +/* ------------------------------------------------------------------------- */ +/** Tests that element with the pointer grab receives pointer events. */ +void test_pointer_grab_events(bs_test_t *test_ptr) +{ + wlmtk_container_t c; + BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, wlmtk_container_init(&c, NULL)); + + wlmtk_fake_element_t *fe1_ptr = wlmtk_fake_element_create(); + BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe1_ptr); + wlmtk_element_set_visible(&fe1_ptr->element, true); + fe1_ptr->dimensions.width = 10; + fe1_ptr->dimensions.height = 10; + wlmtk_container_add_element(&c, &fe1_ptr->element); + + wlmtk_fake_element_t *fe2_ptr = wlmtk_fake_element_create(); + BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe2_ptr); + wlmtk_element_set_visible(&fe2_ptr->element, true); + wlmtk_element_set_position(&fe2_ptr->element, 10, 0); + fe2_ptr->dimensions.width = 10; + fe2_ptr->dimensions.height = 10; + wlmtk_container_add_element(&c, &fe2_ptr->element); + + // Move pointer into first element: Must see 'enter' and 'motion'. + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_motion(&c.super_element, 5, 5, 42)); + BS_TEST_VERIFY_TRUE(test_ptr, fe1_ptr->pointer_motion_called); + fe1_ptr->pointer_motion_called = false; + BS_TEST_VERIFY_TRUE(test_ptr, fe1_ptr->pointer_enter_called); + fe1_ptr->pointer_enter_called = false; + + // 2nd element grabs pointer. Axis and button events must go there. + wlmtk_container_pointer_grab(&c, &fe2_ptr->element); + // 1st element must get notified to no longer have pointer focus. + BS_TEST_VERIFY_TRUE(test_ptr, fe1_ptr->pointer_leave_called); + fe1_ptr->pointer_motion_called = false; + fe1_ptr->pointer_leave_called = false; + wlmtk_button_event_t button_event = { + .button = BTN_LEFT, .type = WLMTK_BUTTON_DOWN + }; + wlmtk_element_pointer_button(&c.super_element, &button_event); + BS_TEST_VERIFY_FALSE(test_ptr, fe1_ptr->pointer_button_called); + BS_TEST_VERIFY_TRUE(test_ptr, fe2_ptr->pointer_button_called); + struct wlr_pointer_axis_event axis_event = {}; + wlmtk_element_pointer_axis(&c.super_element, &axis_event); + BS_TEST_VERIFY_FALSE(test_ptr, fe1_ptr->pointer_axis_called); + BS_TEST_VERIFY_TRUE(test_ptr, fe2_ptr->pointer_axis_called); + + // A motion within the 1st element: Trigger an out-of-area motion + // event to 2nd element. + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_motion(&c.super_element, 8, 5, 43)); + BS_TEST_VERIFY_FALSE(test_ptr, fe1_ptr->pointer_motion_called); + BS_TEST_VERIFY_TRUE(test_ptr, fe2_ptr->pointer_motion_called); + fe2_ptr->pointer_motion_called = false; + + // A motion into the 2nd element: Trigger motion and enter(). + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_motion(&c.super_element, 13, 5, 43)); + BS_TEST_VERIFY_FALSE(test_ptr, fe1_ptr->pointer_motion_called); + BS_TEST_VERIFY_TRUE(test_ptr, fe2_ptr->pointer_motion_called); + fe2_ptr->pointer_motion_called = false; + BS_TEST_VERIFY_TRUE(test_ptr, fe2_ptr->pointer_enter_called); + fe2_ptr->pointer_enter_called = false; + + // A motion back into the 2nd element: Trigger motion and leave(). + BS_TEST_VERIFY_TRUE( + test_ptr, + wlmtk_element_pointer_motion(&c.super_element, 8, 5, 43)); + BS_TEST_VERIFY_FALSE(test_ptr, fe1_ptr->pointer_motion_called); + BS_TEST_VERIFY_TRUE(test_ptr, fe2_ptr->pointer_motion_called); + fe2_ptr->pointer_motion_called = false; + BS_TEST_VERIFY_TRUE(test_ptr, fe2_ptr->pointer_leave_called); + fe2_ptr->pointer_leave_called = false; + + // Second element releases the grab. 1st element must receive enter(). + wlmtk_container_pointer_grab_release(&c, &fe2_ptr->element); + BS_TEST_VERIFY_TRUE(test_ptr, fe1_ptr->pointer_motion_called); + BS_TEST_VERIFY_TRUE(test_ptr, fe1_ptr->pointer_enter_called); + + wlmtk_container_fini(&c); +} + /* ------------------------------------------------------------------------- */ /** Tests that axis events are forwarded to element with pointer focus. */ void test_pointer_axis(bs_test_t *test_ptr) diff --git a/src/toolkit/container.h b/src/toolkit/container.h index 19c43402..6ab54fcd 100644 --- a/src/toolkit/container.h +++ b/src/toolkit/container.h @@ -76,6 +76,8 @@ struct _wlmtk_container_t { /** Stores the element with current pointer focus. May be NULL. */ wlmtk_element_t *pointer_focus_element_ptr; + /** Stores the element with current pointer grab. May be NULL. */ + wlmtk_element_t *pointer_grab_element_ptr; /** Stores the element which received WLMTK_BUTTON_DOWN for BTN_LEFT. */ wlmtk_element_t *left_button_element_ptr; /** Stores the element with current keyboard focus. May be NULL. */ @@ -159,7 +161,9 @@ void wlmtk_container_add_element_atop( /** * Removes `element_ptr` from the container. * - * Expects that `container_ptr` is `element_ptr`'s parent container. + * Expects that `container_ptr` is `element_ptr`'s parent container. In case + * `element_ptr` holds a pointer grab, @ref wlmtk_element_pointer_grab_cancel + * will be called. * * @param container_ptr * @param element_ptr @@ -187,6 +191,36 @@ void wlmtk_container_raise_element_to_top( */ void wlmtk_container_update_pointer_focus(wlmtk_container_t *container_ptr); +/** + * Requests a pointer grab from `container_ptr` for `element_ptr`. + * + * Will cancel any existing grab held by elements other than `element_ptr`, and + * propagates the grab to the parent container. + * When a pointer grab is held, pointer events will be routed exclusively to + * the element holding the pointer grab. + * + * @param container_ptr + * @param element_ptr Must be a child of this container. + */ +void wlmtk_container_pointer_grab( + wlmtk_container_t *container_ptr, + wlmtk_element_t *element_ptr); + +/** + * Releases a pointer grab in `container_ptr` held by `element_ptr`. + * + * If the grab is held by an element other than `element_ptr`, nothing is done. + * Otherwise, the pointer grab is released, and the release is propagated to + * the parent container. + * + * @param container_ptr + * @param element_ptr Must be a child of this container. + */ +void wlmtk_container_pointer_grab_release( + wlmtk_container_t *container_ptr, + wlmtk_element_t *element_ptr); + + /** * Reports `element_ptr` as having keyboard focus, and registers it as such in * this container. Will propagate @ref wlmtk_container_t::super_element to diff --git a/src/toolkit/element.c b/src/toolkit/element.c index b2d199b4..8f1f1b1b 100644 --- a/src/toolkit/element.c +++ b/src/toolkit/element.c @@ -130,6 +130,10 @@ wlmtk_element_vmt_t wlmtk_element_extend( if (NULL != element_vmt_ptr->pointer_leave) { element_ptr->vmt.pointer_leave = element_vmt_ptr->pointer_leave; } + if (NULL != element_vmt_ptr->pointer_grab_cancel) { + element_ptr->vmt.pointer_grab_cancel = + element_vmt_ptr->pointer_grab_cancel; + } if (NULL != element_vmt_ptr->keyboard_blur) { element_ptr->vmt.keyboard_blur = element_vmt_ptr->keyboard_blur; } @@ -431,6 +435,8 @@ static void fake_pointer_enter( wlmtk_element_t *element_ptr); static void fake_pointer_leave( wlmtk_element_t *element_ptr); +static void fake_pointer_grab_cancel( + wlmtk_element_t *element_ptr); static void fake_keyboard_blur( wlmtk_element_t *element_ptr); static bool fake_keyboard_event( @@ -451,6 +457,7 @@ static const wlmtk_element_vmt_t fake_element_vmt = { .pointer_axis = fake_pointer_axis, .pointer_enter = fake_pointer_enter, .pointer_leave = fake_pointer_leave, + .pointer_grab_cancel = fake_pointer_grab_cancel, .keyboard_blur = fake_keyboard_blur, .keyboard_event = fake_keyboard_event, }; @@ -615,6 +622,16 @@ void fake_pointer_leave( fake_element_ptr->pointer_leave_called = true; } +/* ------------------------------------------------------------------------- */ +/** Records calls to @ref wlmtk_element_vmt_t::pointer_grab_cancel. */ +void fake_pointer_grab_cancel( + wlmtk_element_t *element_ptr) +{ + wlmtk_fake_element_t *fake_element_ptr = BS_CONTAINER_OF( + element_ptr, wlmtk_fake_element_t, element); + fake_element_ptr->pointer_grab_cancel_called = true; +} + /* ------------------------------------------------------------------------- */ /** Registers losing keyboard focus. */ void fake_keyboard_blur(wlmtk_element_t *element_ptr) diff --git a/src/toolkit/element.h b/src/toolkit/element.h index 8f9d0e6f..bda8512b 100644 --- a/src/toolkit/element.h +++ b/src/toolkit/element.h @@ -141,6 +141,18 @@ struct _wlmtk_element_vmt_t { */ void (*pointer_leave)(wlmtk_element_t *element_ptr); + /** + * Cancels a held pointer grab. + * + * Required to have an implementation by any element that requests a + * pointer grab through @ref wlmtk_container_pointer_grab. + * + * Private: Must only to be called by the parent container. + * + * @param element_ptr + */ + void (*pointer_grab_cancel)(wlmtk_element_t *element_ptr); + /** * Blurs (de-activates) keyboard focus for the element. Propagates to child * elements, where available. @@ -431,6 +443,15 @@ static inline bool wlmtk_element_pointer_axis( element_ptr, wlr_pointer_axis_event_ptr); } +/** Calls optional @ref wlmtk_element_vmt_t::pointer_grab_cancel. */ +static inline void wlmtk_element_pointer_grab_cancel( + wlmtk_element_t *element_ptr) +{ + if (NULL != element_ptr->vmt.pointer_grab_cancel) { + element_ptr->vmt.pointer_grab_cancel(element_ptr); + } +} + /** Calls @ref wlmtk_element_vmt_t::keyboard_event. */ static inline bool wlmtk_element_keyboard_event( wlmtk_element_t *element_ptr, @@ -488,6 +509,8 @@ typedef struct { wlmtk_button_event_t pointer_button_event; /** Indicates @ref wlmtk_element_vmt_t::pointer_axis() was called. */ bool pointer_axis_called; + /** Indicates @ref wlmtk_element_vmt_t::pointer_grab_cancel() was called. */ + bool pointer_grab_cancel_called; /** Whether the fake element has keyboare focus. */ bool has_keyboard_focus; /** Indicates that @ref wlmtk_element_vmt_t::keyboard_event() was called. */ diff --git a/src/toolkit/menu.c b/src/toolkit/menu.c index 5293782f..9473b559 100644 --- a/src/toolkit/menu.c +++ b/src/toolkit/menu.c @@ -31,8 +31,6 @@ static void _wlmtk_menu_set_item_mode( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); -/* == Data ================================================================= */ - /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ diff --git a/src/toolkit/titlebar_title.c b/src/toolkit/titlebar_title.c index 8c332c81..5f9459a1 100644 --- a/src/toolkit/titlebar_title.c +++ b/src/toolkit/titlebar_title.c @@ -184,7 +184,7 @@ bool _wlmtk_titlebar_title_element_pointer_button( wlmtk_titlebar_title_t *titlebar_title_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_titlebar_title_t, super_buffer.super_element); - if (button_event_ptr->button != BTN_LEFT) return true; + if (button_event_ptr->button != BTN_LEFT) return false; switch (button_event_ptr->type) { case WLMTK_BUTTON_DOWN: diff --git a/src/toolkit/toolkit.md b/src/toolkit/toolkit.md index c8a06099..e4f3f118 100644 --- a/src/toolkit/toolkit.md +++ b/src/toolkit/toolkit.md @@ -465,3 +465,42 @@ Dock <|-- IconArea An "application" that is *iconified* will be shown in the icon area. This is irrespective of whether there is already an icon shown for that "application". +### Input grab + +* See: https://wayland-book.com/seat.html +* See: https://wayland.app/protocols/xdg-shell#xdg_popup:request:grab + +So, when a XDG popup requests a grab: From that moment on, the coresponding +wlr_surface (and the related client) should keep receiving events. But not +others. Once the grab is broken, the popup is supposed to be dismissed. + +So far, we been thinking of passing events from root element along the +containers. On a grab, each container would lock the 'grabbing' element. +(and inform the grab-holder when another element claims the grab; so +would need a cancel_gab method). + +When the menu requests grab: we also want all pointer and input events +going there. When the grab is broken => menu is to close. + +container_grab(c, element) -> setup grab for element + -> will call to parent container as container_grab(parent_c, c.super_element) +element_grab_cancel(element) -> cancel a held grab (this is FYI) + + +void *wlmtk_container_pointer_grab(wlmtk_container_t *, wlmtk_element_t *); +void wlmtk_container_pointer_grab_release(wlmtk_container_t *, wlmtk_element_t *); +void wlmtk_element_pointer_grab_cancel(wlmtk_element_t *element_ptr); + + +For Keyboard: +* we have that mechanism partly with container::keyboard_focus_element_ptr +* we have keyboard routing through "set_keyboard_focus_element + (through wlmtk_surface_t in wlmtk_surface_:set_activated) + +For Pointer or Touch: +* Not done (yet). + +=> HOWEVER: This will route *only* to the surface holding the grab. + (this would prevent cursor updates? That's actually how X11 chrome + popups/menus are working currently) + so... that's probably good/desired. diff --git a/src/toolkit/window.c b/src/toolkit/window.c index 295855c4..39e15210 100644 --- a/src/toolkit/window.c +++ b/src/toolkit/window.c @@ -763,6 +763,22 @@ bool _wlmtk_window_element_pointer_button( wlmtk_window_t *window_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_window_t, super_bordered.super_container.super_element); + // In right-click mode: Any out-of-window action will close it. + // TODO(kaeser@gubbe.ch): This should be a specific window mode, and should + // have a handler method when leaving that mode (eg. left release within + // the window). + if (window_ptr->properties & WLMTK_WINDOW_PROPERTY_RIGHTCLICK) { + bool rv = window_ptr->orig_super_element_vmt.pointer_button( + element_ptr, button_event_ptr); + if (BTN_RIGHT == button_event_ptr->button && + WLMTK_BUTTON_UP == button_event_ptr->type && + !rv) { + wlmtk_window_request_close(window_ptr); + return true; + } + return rv; + } + // Permit drag-move with the (hardcoded) modifier. // TODO(kaeser@gubbe.ch): This should be changed to make use of "DRAG" // events, with corresponding modifiers. Do so, once added to toolkit. diff --git a/src/toolkit/window.h b/src/toolkit/window.h index bc484594..fb738361 100644 --- a/src/toolkit/window.h +++ b/src/toolkit/window.h @@ -56,7 +56,14 @@ typedef enum { /** Can be iconified. Server-side decorations include icnonify button. */ WLMTK_WINDOW_PROPERTY_ICONIFIABLE = UINT32_C(1) << 1, /** Can be closed. Server-side decorations include close button. */ - WLMTK_WINDOW_PROPERTY_CLOSABLE = UINT32_C(1) << 2 + WLMTK_WINDOW_PROPERTY_CLOSABLE = UINT32_C(1) << 2, + + /** + * Kludge: a window that closes on right-click-release. + * The window's element must pointer_grab. + * TODO(kaeser@gubbe.ch): This should be... better. + */ + WLMTK_WINDOW_PROPERTY_RIGHTCLICK = UINT32_C(1) << 3 } wlmtk_window_property_t; /**