From dd3e5d2b52fb6d594db9abf0c4d3198d98c49fb0 Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Thu, 20 Oct 2022 17:02:18 +0200 Subject: [PATCH] compositor: Handle keyboard events in window drag operations This is code largely copied from src/core/keybindings.c, taking care of keyboard interaction during window drag operations. Part-of: --- src/compositor/meta-window-drag.c | 633 ++++++++++++++++++++++++++++++ 1 file changed, 633 insertions(+) diff --git a/src/compositor/meta-window-drag.c b/src/compositor/meta-window-drag.c index 05b395651..2547ff2de 100644 --- a/src/compositor/meta-window-drag.c +++ b/src/compositor/meta-window-drag.c @@ -24,6 +24,7 @@ #include "meta-window-drag.h" #include "compositor/compositor-private.h" +#include "core/edge-resistance.h" #include "core/window-private.h" #include "meta/meta-enum-types.h" @@ -399,6 +400,636 @@ warp_grab_pointer (MetaWindowDrag *window_drag, return TRUE; } +static void +update_keyboard_resize (MetaWindowDrag *window_drag, + gboolean update_cursor) +{ + int x, y; + + warp_grab_pointer (window_drag, + window_drag->effective_grab_window, + window_drag->grab_op, + &x, &y); + + if (update_cursor) + meta_window_drag_update_cursor (window_drag); +} + +static void +update_keyboard_move (MetaWindowDrag *window_drag) +{ + int x, y; + + warp_grab_pointer (window_drag, + window_drag->effective_grab_window, + window_drag->grab_op, + &x, &y); +} + +static gboolean +is_modifier (xkb_keysym_t keysym) +{ + switch (keysym) + { + case XKB_KEY_Shift_L: + case XKB_KEY_Shift_R: + case XKB_KEY_Control_L: + case XKB_KEY_Control_R: + case XKB_KEY_Caps_Lock: + case XKB_KEY_Shift_Lock: + case XKB_KEY_Meta_L: + case XKB_KEY_Meta_R: + case XKB_KEY_Alt_L: + case XKB_KEY_Alt_R: + case XKB_KEY_Super_L: + case XKB_KEY_Super_R: + case XKB_KEY_Hyper_L: + case XKB_KEY_Hyper_R: + return TRUE; + default: + return FALSE; + } +} + +static gboolean +process_mouse_move_resize_grab (MetaWindowDrag *window_drag, + MetaWindow *window, + ClutterKeyEvent *event) +{ + MetaDisplay *display = meta_window_get_display (window); + + /* don't care about releases, but eat them, don't end grab */ + if (event->type == CLUTTER_KEY_RELEASE) + return TRUE; + + if (event->keyval == CLUTTER_KEY_Escape) + { + MetaTileMode tile_mode; + + /* Hide the tiling preview if necessary */ + if (display->preview_tile_mode != META_TILE_NONE) + meta_display_hide_tile_preview (display); + + /* Restore the original tile mode */ + tile_mode = window_drag->tile_mode; + window->tile_monitor_number = window_drag->tile_monitor_number; + + /* End move or resize and restore to original state. If the + * window was a maximized window that had been "shaken loose" we + * need to remaximize it. In normal cases, we need to do a + * moveresize now to get the position back to the original. + */ + if (window->shaken_loose || tile_mode == META_TILE_MAXIMIZED) + meta_window_maximize (window, META_MAXIMIZE_BOTH); + else if (tile_mode != META_TILE_NONE) + meta_window_restore_tile (window, + tile_mode, + window_drag->initial_window_pos.width, + window_drag->initial_window_pos.height); + else + meta_window_move_resize_frame (window_drag->effective_grab_window, + TRUE, + window_drag->initial_window_pos.x, + window_drag->initial_window_pos.y, + window_drag->initial_window_pos.width, + window_drag->initial_window_pos.height); + + /* End grab */ + return FALSE; + } + + return TRUE; +} + +static gboolean +process_keyboard_move_grab (MetaWindowDrag *window_drag, + MetaWindow *window, + ClutterKeyEvent *event) +{ + MetaEdgeResistanceFlags flags; + gboolean handled; + MetaRectangle frame_rect; + int x, y; + int incr; + + handled = FALSE; + + /* don't care about releases, but eat them, don't end grab */ + if (event->type == CLUTTER_KEY_RELEASE) + return TRUE; + + /* don't end grab on modifier key presses */ + if (is_modifier (event->keyval)) + return TRUE; + + meta_window_get_frame_rect (window, &frame_rect); + x = frame_rect.x; + y = frame_rect.y; + + flags = META_EDGE_RESISTANCE_KEYBOARD_OP | META_EDGE_RESISTANCE_WINDOWS; + + if ((event->modifier_state & CLUTTER_SHIFT_MASK) != 0) + flags |= META_EDGE_RESISTANCE_SNAP; + +#define SMALL_INCREMENT 1 +#define NORMAL_INCREMENT 10 + + if (flags & META_EDGE_RESISTANCE_SNAP) + incr = 1; + else if (event->modifier_state & CLUTTER_CONTROL_MASK) + incr = SMALL_INCREMENT; + else + incr = NORMAL_INCREMENT; + + if (event->keyval == CLUTTER_KEY_Escape) + { + /* End move and restore to original state. If the window was a + * maximized window that had been "shaken loose" we need to + * remaximize it. In normal cases, we need to do a moveresize + * now to get the position back to the original. + */ + if (window->shaken_loose) + meta_window_maximize (window, META_MAXIMIZE_BOTH); + else + meta_window_move_resize_frame (window_drag->effective_grab_window, + TRUE, + window_drag->initial_window_pos.x, + window_drag->initial_window_pos.y, + window_drag->initial_window_pos.width, + window_drag->initial_window_pos.height); + } + + /* When moving by increments, we still snap to edges if the move + * to the edge is smaller than the increment. This is because + * Shift + arrow to snap is sort of a hidden feature. This way + * people using just arrows shouldn't get too frustrated. + */ + switch (event->keyval) + { + case CLUTTER_KEY_KP_Home: + case CLUTTER_KEY_KP_Prior: + case CLUTTER_KEY_Up: + case CLUTTER_KEY_KP_Up: + y -= incr; + handled = TRUE; + break; + case CLUTTER_KEY_KP_End: + case CLUTTER_KEY_KP_Next: + case CLUTTER_KEY_Down: + case CLUTTER_KEY_KP_Down: + y += incr; + handled = TRUE; + break; + } + + switch (event->keyval) + { + case CLUTTER_KEY_KP_Home: + case CLUTTER_KEY_KP_End: + case CLUTTER_KEY_Left: + case CLUTTER_KEY_KP_Left: + x -= incr; + handled = TRUE; + break; + case CLUTTER_KEY_KP_Prior: + case CLUTTER_KEY_KP_Next: + case CLUTTER_KEY_Right: + case CLUTTER_KEY_KP_Right: + x += incr; + handled = TRUE; + break; + } + + if (handled) + { + meta_topic (META_DEBUG_KEYBINDINGS, + "Computed new window location %d,%d due to keypress", + x, y); + + meta_window_edge_resistance_for_move (window, + &x, + &y, + flags); + + meta_window_move_frame (window, TRUE, x, y); + update_keyboard_move (window_drag); + } + + return handled; +} + +static gboolean +process_keyboard_resize_grab_op_change (MetaWindowDrag *window_drag, + MetaWindow *window, + ClutterKeyEvent *event) +{ + gboolean handled; + + handled = FALSE; + switch (window_drag->grab_op) + { + case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN: + switch (event->keyval) + { + case CLUTTER_KEY_Up: + case CLUTTER_KEY_KP_Up: + window_drag->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_N; + handled = TRUE; + break; + case CLUTTER_KEY_Down: + case CLUTTER_KEY_KP_Down: + window_drag->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_S; + handled = TRUE; + break; + case CLUTTER_KEY_Left: + case CLUTTER_KEY_KP_Left: + window_drag->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_W; + handled = TRUE; + break; + case CLUTTER_KEY_Right: + case CLUTTER_KEY_KP_Right: + window_drag->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_E; + handled = TRUE; + break; + } + break; + + case META_GRAB_OP_KEYBOARD_RESIZING_S: + switch (event->keyval) + { + case CLUTTER_KEY_Left: + case CLUTTER_KEY_KP_Left: + window_drag->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_W; + handled = TRUE; + break; + case CLUTTER_KEY_Right: + case CLUTTER_KEY_KP_Right: + window_drag->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_E; + handled = TRUE; + break; + } + break; + + case META_GRAB_OP_KEYBOARD_RESIZING_N: + switch (event->keyval) + { + case CLUTTER_KEY_Left: + case CLUTTER_KEY_KP_Left: + window_drag->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_W; + handled = TRUE; + break; + case CLUTTER_KEY_Right: + case CLUTTER_KEY_KP_Right: + window_drag->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_E; + handled = TRUE; + break; + } + break; + + case META_GRAB_OP_KEYBOARD_RESIZING_W: + switch (event->keyval) + { + case CLUTTER_KEY_Up: + case CLUTTER_KEY_KP_Up: + window_drag->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_N; + handled = TRUE; + break; + case CLUTTER_KEY_Down: + case CLUTTER_KEY_KP_Down: + window_drag->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_S; + handled = TRUE; + break; + } + break; + + case META_GRAB_OP_KEYBOARD_RESIZING_E: + switch (event->keyval) + { + case CLUTTER_KEY_Up: + case CLUTTER_KEY_KP_Up: + window_drag->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_N; + handled = TRUE; + break; + case CLUTTER_KEY_Down: + case CLUTTER_KEY_KP_Down: + window_drag->grab_op = META_GRAB_OP_KEYBOARD_RESIZING_S; + handled = TRUE; + break; + } + break; + + case META_GRAB_OP_KEYBOARD_RESIZING_SE: + case META_GRAB_OP_KEYBOARD_RESIZING_NE: + case META_GRAB_OP_KEYBOARD_RESIZING_SW: + case META_GRAB_OP_KEYBOARD_RESIZING_NW: + break; + + default: + g_assert_not_reached (); + break; + } + + if (handled) + { + update_keyboard_resize (window_drag, TRUE); + return TRUE; + } + + return FALSE; +} + +static gboolean +process_keyboard_resize_grab (MetaWindowDrag *window_drag, + MetaWindow *window, + ClutterKeyEvent *event) +{ + MetaRectangle frame_rect; + gboolean handled; + int height_inc; + int width_inc; + int width, height; + MetaEdgeResistanceFlags flags; + MetaGravity gravity; + + handled = FALSE; + + /* don't care about releases, but eat them, don't end grab */ + if (event->type == CLUTTER_KEY_RELEASE) + return TRUE; + + /* don't end grab on modifier key presses */ + if (is_modifier (event->keyval)) + return TRUE; + + if (event->keyval == CLUTTER_KEY_Escape) + { + /* End resize and restore to original state. */ + meta_window_move_resize_frame (window_drag->effective_grab_window, + TRUE, + window_drag->initial_window_pos.x, + window_drag->initial_window_pos.y, + window_drag->initial_window_pos.width, + window_drag->initial_window_pos.height); + + return FALSE; + } + + if (process_keyboard_resize_grab_op_change (window_drag, window, event)) + return TRUE; + + width = window->rect.width; + height = window->rect.height; + + meta_window_get_frame_rect (window, &frame_rect); + width = frame_rect.width; + height = frame_rect.height; + + gravity = meta_resize_gravity_from_grab_op (window_drag->grab_op); + + flags = META_EDGE_RESISTANCE_KEYBOARD_OP; + + if ((event->modifier_state & CLUTTER_SHIFT_MASK) != 0) + flags |= META_EDGE_RESISTANCE_SNAP; + +#define SMALL_INCREMENT 1 +#define NORMAL_INCREMENT 10 + + if (flags & META_EDGE_RESISTANCE_SNAP) + { + height_inc = 1; + width_inc = 1; + } + else if (event->modifier_state & CLUTTER_CONTROL_MASK) + { + width_inc = SMALL_INCREMENT; + height_inc = SMALL_INCREMENT; + } + else + { + width_inc = NORMAL_INCREMENT; + height_inc = NORMAL_INCREMENT; + } + + /* If this is a resize increment window, make the amount we resize + * the window by match that amount (well, unless snap resizing...) + */ + if (window->size_hints.width_inc > 1) + width_inc = window->size_hints.width_inc; + if (window->size_hints.height_inc > 1) + height_inc = window->size_hints.height_inc; + + switch (event->keyval) + { + case CLUTTER_KEY_Up: + case CLUTTER_KEY_KP_Up: + switch (gravity) + { + case META_GRAVITY_NORTH: + case META_GRAVITY_NORTH_WEST: + case META_GRAVITY_NORTH_EAST: + /* Move bottom edge up */ + height -= height_inc; + break; + + case META_GRAVITY_SOUTH: + case META_GRAVITY_SOUTH_WEST: + case META_GRAVITY_SOUTH_EAST: + /* Move top edge up */ + height += height_inc; + break; + + case META_GRAVITY_EAST: + case META_GRAVITY_WEST: + case META_GRAVITY_CENTER: + case META_GRAVITY_NONE: + case META_GRAVITY_STATIC: + g_assert_not_reached (); + break; + } + + handled = TRUE; + break; + + case CLUTTER_KEY_Down: + case CLUTTER_KEY_KP_Down: + switch (gravity) + { + case META_GRAVITY_NORTH: + case META_GRAVITY_NORTH_WEST: + case META_GRAVITY_NORTH_EAST: + /* Move bottom edge down */ + height += height_inc; + break; + + case META_GRAVITY_SOUTH: + case META_GRAVITY_SOUTH_WEST: + case META_GRAVITY_SOUTH_EAST: + /* Move top edge down */ + height -= height_inc; + break; + + case META_GRAVITY_EAST: + case META_GRAVITY_WEST: + case META_GRAVITY_CENTER: + case META_GRAVITY_NONE: + case META_GRAVITY_STATIC: + g_assert_not_reached (); + break; + } + + handled = TRUE; + break; + + case CLUTTER_KEY_Left: + case CLUTTER_KEY_KP_Left: + switch (gravity) + { + case META_GRAVITY_EAST: + case META_GRAVITY_SOUTH_EAST: + case META_GRAVITY_NORTH_EAST: + /* Move left edge left */ + width += width_inc; + break; + + case META_GRAVITY_WEST: + case META_GRAVITY_SOUTH_WEST: + case META_GRAVITY_NORTH_WEST: + /* Move right edge left */ + width -= width_inc; + break; + + case META_GRAVITY_NORTH: + case META_GRAVITY_SOUTH: + case META_GRAVITY_CENTER: + case META_GRAVITY_NONE: + case META_GRAVITY_STATIC: + g_assert_not_reached (); + break; + } + + handled = TRUE; + break; + + case CLUTTER_KEY_Right: + case CLUTTER_KEY_KP_Right: + switch (gravity) + { + case META_GRAVITY_EAST: + case META_GRAVITY_SOUTH_EAST: + case META_GRAVITY_NORTH_EAST: + /* Move left edge right */ + width -= width_inc; + break; + + case META_GRAVITY_WEST: + case META_GRAVITY_SOUTH_WEST: + case META_GRAVITY_NORTH_WEST: + /* Move right edge right */ + width += width_inc; + break; + + case META_GRAVITY_NORTH: + case META_GRAVITY_SOUTH: + case META_GRAVITY_CENTER: + case META_GRAVITY_NONE: + case META_GRAVITY_STATIC: + g_assert_not_reached (); + break; + } + + handled = TRUE; + break; + + default: + break; + } + + /* fixup hack (just paranoia, not sure it's required) */ + if (height < 1) + height = 1; + if (width < 1) + width = 1; + + if (handled) + { + meta_topic (META_DEBUG_KEYBINDINGS, + "Computed new window size due to keypress: " + "%dx%d, gravity %s", + width, height, meta_gravity_to_string (gravity)); + + /* Do any edge resistance/snapping */ + meta_window_edge_resistance_for_resize (window, + &width, + &height, + gravity, + flags); + + meta_window_resize_frame_with_gravity (window, + TRUE, + width, + height, + gravity); + + update_keyboard_resize (window_drag, FALSE); + } + + return handled; +} + +static void +process_key_event (MetaWindowDrag *window_drag, + ClutterKeyEvent *event) +{ + MetaWindow *window; + gboolean keep_grab = TRUE; + + window = window_drag->effective_grab_window; + if (!window) + return; + + if (window_drag->grab_op & META_GRAB_OP_WINDOW_FLAG_KEYBOARD) + { + if (window_drag->grab_op == META_GRAB_OP_KEYBOARD_MOVING) + { + meta_topic (META_DEBUG_KEYBINDINGS, + "Processing event for keyboard move"); + keep_grab = process_keyboard_move_grab (window_drag, window, event); + } + else + { + meta_topic (META_DEBUG_KEYBINDINGS, + "Processing event for keyboard resize"); + keep_grab = process_keyboard_resize_grab (window_drag, window, event); + } + } + else if (window_drag->grab_op & META_GRAB_OP_MOVING) + { + meta_topic (META_DEBUG_KEYBINDINGS, + "Processing event for mouse-only move/resize"); + keep_grab = process_mouse_move_resize_grab (window_drag, window, event); + } + + if (!keep_grab) + meta_window_drag_end (window_drag); +} + +static gboolean +on_window_drag_event (MetaWindowDrag *window_drag, + ClutterEvent *event) +{ + switch (event->type) + { + case CLUTTER_KEY_PRESS: + case CLUTTER_KEY_RELEASE: + process_key_event (window_drag, &event->key); + break; + default: + break; + } + + return CLUTTER_EVENT_PROPAGATE; +} + gboolean meta_window_drag_begin (MetaWindowDrag *window_drag, uint32_t timestamp) @@ -466,6 +1097,8 @@ meta_window_drag_begin (MetaWindowDrag *window_drag, window_drag->handler = clutter_actor_new (); clutter_actor_set_name (window_drag->handler, "Window drag helper"); + g_signal_connect_swapped (window_drag->handler, "event", + G_CALLBACK (on_window_drag_event), window_drag); clutter_actor_add_child (stage, window_drag->handler); window_drag->grab = clutter_stage_grab (CLUTTER_STAGE (stage),