mirror of
https://github.com/brl/mutter.git
synced 2025-01-01 15:32:15 +00:00
e6d0e0fda6
Since SSD X11 windows require synchronization between frame and client windows on resizes, updates do not always happen immediately but in control of external factors (i.e. when both windows become to have a coherent size). This method will be used to update the window position between resize/sync operations. Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2683>
1786 lines
53 KiB
C
1786 lines
53 KiB
C
/*
|
|
* Copyright (C) 2022 Red Hat Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
|
* 02111-1307, USA.
|
|
*
|
|
* Author: Carlos Garnacho <carlosg@gnome.org>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "meta-window-drag.h"
|
|
|
|
#include "compositor/compositor-private.h"
|
|
#include "core/edge-resistance.h"
|
|
#include "core/frame.h"
|
|
#include "core/window-private.h"
|
|
#include "meta/meta-enum-types.h"
|
|
#include "x11/window-x11.h"
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_WINDOW,
|
|
PROP_GRAB_OP,
|
|
N_PROPS,
|
|
};
|
|
|
|
static GParamSpec *props[N_PROPS] = { 0, };
|
|
|
|
enum {
|
|
ENDED,
|
|
LAST_SIGNAL,
|
|
};
|
|
|
|
static guint signals[LAST_SIGNAL] = { 0, };
|
|
|
|
struct _MetaWindowDrag {
|
|
GObject parent_class;
|
|
ClutterActor *handler;
|
|
|
|
MetaWindow *window;
|
|
MetaWindow *effective_grab_window;
|
|
MetaGrabOp grab_op;
|
|
ClutterGrab *grab;
|
|
|
|
int anchor_root_x;
|
|
int anchor_root_y;
|
|
MetaRectangle anchor_window_pos;
|
|
MetaTileMode tile_mode;
|
|
int tile_monitor_number;
|
|
int latest_motion_x;
|
|
int latest_motion_y;
|
|
MetaRectangle initial_window_pos;
|
|
int initial_x, initial_y; /* These are only relevant for */
|
|
gboolean threshold_movement_reached; /* raise_on_click == FALSE. */
|
|
unsigned int last_edge_resistance_flags;
|
|
unsigned int move_resize_later_id;
|
|
|
|
gulong unmanaging_id;
|
|
gulong size_changed_id;
|
|
};
|
|
|
|
G_DEFINE_FINAL_TYPE (MetaWindowDrag, meta_window_drag, G_TYPE_OBJECT)
|
|
|
|
static void
|
|
meta_window_drag_finalize (GObject *object)
|
|
{
|
|
MetaWindowDrag *window_drag = META_WINDOW_DRAG (object);
|
|
|
|
g_clear_pointer (&window_drag->handler, clutter_actor_destroy);
|
|
g_clear_pointer (&window_drag->grab, clutter_grab_unref);
|
|
g_clear_object (&window_drag->effective_grab_window);
|
|
|
|
G_OBJECT_CLASS (meta_window_drag_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
meta_window_drag_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
MetaWindowDrag *window_drag = META_WINDOW_DRAG (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_WINDOW:
|
|
window_drag->window = g_value_get_object (value);
|
|
break;
|
|
case PROP_GRAB_OP:
|
|
window_drag->grab_op = g_value_get_uint (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
meta_window_drag_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
MetaWindowDrag *window_drag = META_WINDOW_DRAG (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_WINDOW:
|
|
g_value_set_object (value, window_drag->window);
|
|
break;
|
|
case PROP_GRAB_OP:
|
|
g_value_set_uint (value, window_drag->grab_op);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
meta_window_drag_class_init (MetaWindowDragClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->finalize = meta_window_drag_finalize;
|
|
object_class->set_property = meta_window_drag_set_property;
|
|
object_class->get_property = meta_window_drag_get_property;
|
|
|
|
props[PROP_WINDOW] = g_param_spec_object ("window",
|
|
"Window",
|
|
"Window",
|
|
META_TYPE_WINDOW,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_STRINGS);
|
|
props[PROP_GRAB_OP] = g_param_spec_uint ("grab-op",
|
|
"Grab op",
|
|
"Grab op",
|
|
0, G_MAXUINT,
|
|
META_GRAB_OP_NONE,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_STRINGS);
|
|
|
|
signals[ENDED] =
|
|
g_signal_new ("ended",
|
|
G_TYPE_FROM_CLASS (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
0, NULL, NULL, NULL,
|
|
G_TYPE_NONE, 0);
|
|
|
|
g_object_class_install_properties (object_class, N_PROPS, props);
|
|
}
|
|
|
|
static void
|
|
meta_window_drag_init (MetaWindowDrag *window_drag)
|
|
{
|
|
}
|
|
|
|
MetaWindowDrag *
|
|
meta_window_drag_new (MetaWindow *window,
|
|
MetaGrabOp grab_op)
|
|
{
|
|
return g_object_new (META_TYPE_WINDOW_DRAG,
|
|
"window", window,
|
|
"grab-op", grab_op,
|
|
NULL);
|
|
}
|
|
|
|
static void
|
|
clear_move_resize_later (MetaWindowDrag *window_drag)
|
|
{
|
|
if (window_drag->move_resize_later_id)
|
|
{
|
|
MetaDisplay *display;
|
|
MetaCompositor *compositor;
|
|
MetaLaters *laters;
|
|
|
|
display = meta_window_get_display (window_drag->effective_grab_window);
|
|
compositor = meta_display_get_compositor (display);
|
|
laters = meta_compositor_get_laters (compositor);
|
|
meta_laters_remove (laters, window_drag->move_resize_later_id);
|
|
window_drag->move_resize_later_id = 0;
|
|
}
|
|
}
|
|
|
|
static MetaCursor
|
|
meta_cursor_for_grab_op (MetaGrabOp op)
|
|
{
|
|
op &= ~(META_GRAB_OP_WINDOW_FLAG_UNCONSTRAINED);
|
|
|
|
switch (op)
|
|
{
|
|
case META_GRAB_OP_RESIZING_SE:
|
|
case META_GRAB_OP_KEYBOARD_RESIZING_SE:
|
|
return META_CURSOR_SE_RESIZE;
|
|
break;
|
|
case META_GRAB_OP_RESIZING_S:
|
|
case META_GRAB_OP_KEYBOARD_RESIZING_S:
|
|
return META_CURSOR_SOUTH_RESIZE;
|
|
break;
|
|
case META_GRAB_OP_RESIZING_SW:
|
|
case META_GRAB_OP_KEYBOARD_RESIZING_SW:
|
|
return META_CURSOR_SW_RESIZE;
|
|
break;
|
|
case META_GRAB_OP_RESIZING_N:
|
|
case META_GRAB_OP_KEYBOARD_RESIZING_N:
|
|
return META_CURSOR_NORTH_RESIZE;
|
|
break;
|
|
case META_GRAB_OP_RESIZING_NE:
|
|
case META_GRAB_OP_KEYBOARD_RESIZING_NE:
|
|
return META_CURSOR_NE_RESIZE;
|
|
break;
|
|
case META_GRAB_OP_RESIZING_NW:
|
|
case META_GRAB_OP_KEYBOARD_RESIZING_NW:
|
|
return META_CURSOR_NW_RESIZE;
|
|
break;
|
|
case META_GRAB_OP_RESIZING_W:
|
|
case META_GRAB_OP_KEYBOARD_RESIZING_W:
|
|
return META_CURSOR_WEST_RESIZE;
|
|
break;
|
|
case META_GRAB_OP_RESIZING_E:
|
|
case META_GRAB_OP_KEYBOARD_RESIZING_E:
|
|
return META_CURSOR_EAST_RESIZE;
|
|
break;
|
|
case META_GRAB_OP_MOVING:
|
|
case META_GRAB_OP_KEYBOARD_MOVING:
|
|
case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN:
|
|
return META_CURSOR_MOVE_OR_RESIZE_WINDOW;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return META_CURSOR_DEFAULT;
|
|
}
|
|
|
|
static void
|
|
meta_window_drag_update_cursor (MetaWindowDrag *window_drag)
|
|
{
|
|
MetaDisplay *display;
|
|
MetaCursor cursor;
|
|
|
|
display = meta_window_get_display (window_drag->effective_grab_window);
|
|
|
|
cursor = meta_cursor_for_grab_op (window_drag->grab_op);
|
|
meta_display_set_cursor (display, cursor);
|
|
}
|
|
|
|
void
|
|
meta_window_drag_end (MetaWindowDrag *window_drag)
|
|
{
|
|
MetaWindow *grab_window = window_drag->effective_grab_window;
|
|
MetaGrabOp grab_op = window_drag->grab_op;
|
|
MetaDisplay *display = meta_window_get_display (grab_window);
|
|
|
|
meta_topic (META_DEBUG_WINDOW_OPS,
|
|
"Ending grab op %u", grab_op);
|
|
|
|
g_assert (grab_window != NULL);
|
|
|
|
/* Clear out the edge cache */
|
|
meta_display_cleanup_edges (display);
|
|
|
|
/* Only raise the window in orthogonal raise
|
|
* ('do-not-raise-on-click') mode if the user didn't try to move
|
|
* or resize the given window by at least a threshold amount.
|
|
* For raise on click mode, the window was raised at the
|
|
* beginning of the grab_op.
|
|
*/
|
|
if (!meta_prefs_get_raise_on_click () &&
|
|
!window_drag->threshold_movement_reached)
|
|
meta_window_raise (grab_window);
|
|
|
|
meta_window_grab_op_ended (grab_window, grab_op);
|
|
|
|
clutter_grab_dismiss (window_drag->grab);
|
|
|
|
g_clear_signal_handler (&window_drag->unmanaging_id, grab_window);
|
|
g_clear_signal_handler (&window_drag->size_changed_id, grab_window);
|
|
|
|
meta_topic (META_DEBUG_WINDOW_OPS,
|
|
"Restoring passive key grabs on %s", grab_window->desc);
|
|
meta_window_grab_keys (grab_window);
|
|
|
|
meta_display_set_cursor (display, META_CURSOR_DEFAULT);
|
|
|
|
clear_move_resize_later (window_drag);
|
|
|
|
if (meta_is_wayland_compositor ())
|
|
meta_display_sync_wayland_input_focus (display);
|
|
|
|
g_signal_emit_by_name (display, "grab-op-end", grab_window, grab_op);
|
|
|
|
g_signal_emit (window_drag, signals[ENDED], 0);
|
|
}
|
|
|
|
static void
|
|
on_grab_window_unmanaging (MetaWindow *window,
|
|
MetaWindowDrag *window_drag)
|
|
{
|
|
meta_window_drag_end (window_drag);
|
|
}
|
|
|
|
static void
|
|
on_grab_window_size_changed (MetaWindow *window,
|
|
MetaWindowDrag *window_drag)
|
|
{
|
|
meta_window_get_frame_rect (window,
|
|
&window_drag->anchor_window_pos);
|
|
}
|
|
|
|
static MetaWindow *
|
|
get_first_freefloating_window (MetaWindow *window)
|
|
{
|
|
while (meta_window_is_attached_dialog (window))
|
|
window = meta_window_get_transient_for (window);
|
|
|
|
/* Attached dialogs should always have a non-NULL transient-for */
|
|
g_assert (window != NULL);
|
|
|
|
return window;
|
|
}
|
|
|
|
/* Warp pointer to location appropriate for keyboard grab,
|
|
* return root coordinates where pointer ended up.
|
|
*/
|
|
static gboolean
|
|
warp_grab_pointer (MetaWindowDrag *window_drag,
|
|
MetaWindow *window,
|
|
MetaGrabOp grab_op,
|
|
int *x,
|
|
int *y)
|
|
{
|
|
MetaRectangle rect;
|
|
MetaRectangle display_rect = { 0 };
|
|
MetaDisplay *display;
|
|
ClutterSeat *seat;
|
|
|
|
display = window->display;
|
|
meta_display_get_size (display,
|
|
&display_rect.width,
|
|
&display_rect.height);
|
|
|
|
/* We may not have done begin_grab_op yet, i.e. may not be in a grab
|
|
*/
|
|
|
|
meta_window_get_frame_rect (window, &rect);
|
|
|
|
if (grab_op & META_GRAB_OP_WINDOW_DIR_WEST)
|
|
*x = 0;
|
|
else if (grab_op & META_GRAB_OP_WINDOW_DIR_EAST)
|
|
*x = rect.width - 1;
|
|
else
|
|
*x = rect.width / 2;
|
|
|
|
if (grab_op & META_GRAB_OP_WINDOW_DIR_NORTH)
|
|
*y = 0;
|
|
else if (grab_op & META_GRAB_OP_WINDOW_DIR_SOUTH)
|
|
*y = rect.height - 1;
|
|
else
|
|
*y = rect.height / 2;
|
|
|
|
*x += rect.x;
|
|
*y += rect.y;
|
|
|
|
/* Avoid weird bouncing at the screen edge; see bug 154706 */
|
|
*x = CLAMP (*x, 0, display_rect.width - 1);
|
|
*y = CLAMP (*y, 0, display_rect.height - 1);
|
|
|
|
meta_topic (META_DEBUG_WINDOW_OPS,
|
|
"Warping pointer to %d,%d with window at %d,%d",
|
|
*x, *y, rect.x, rect.y);
|
|
|
|
/* Need to update the grab positions so that the MotionNotify and other
|
|
* events generated by the XWarpPointer() call below don't cause complete
|
|
* funkiness. See bug 124582 and bug 122670.
|
|
*/
|
|
window_drag->anchor_root_x = *x;
|
|
window_drag->anchor_root_y = *y;
|
|
window_drag->latest_motion_x = *x;
|
|
window_drag->latest_motion_y = *y;
|
|
meta_window_get_frame_rect (window,
|
|
&window_drag->anchor_window_pos);
|
|
|
|
seat = clutter_backend_get_default_seat (clutter_get_default_backend ());
|
|
clutter_seat_warp_pointer (seat, *x, *y);
|
|
|
|
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 void
|
|
update_move_maybe_tile (MetaWindow *window,
|
|
int shake_threshold,
|
|
int x,
|
|
int y)
|
|
{
|
|
MetaWindow *window = meta_window_drag_get_window (window_drag);
|
|
MetaDisplay *display = meta_window_get_display (window);
|
|
MetaContext *context = meta_display_get_context (display);
|
|
MetaBackend *backend = meta_context_get_backend (context);
|
|
MetaMonitorManager *monitor_manager =
|
|
meta_backend_get_monitor_manager (backend);
|
|
MetaLogicalMonitor *logical_monitor;
|
|
MetaRectangle work_area;
|
|
|
|
/* For side-by-side tiling we are interested in the inside vertical
|
|
* edges of the work area of the monitor where the pointer is located,
|
|
* and in the outside top edge for maximized tiling.
|
|
*
|
|
* For maximized tiling we use the outside edge instead of the
|
|
* inside edge, because we don't want to force users to maximize
|
|
* windows they are placing near the top of their screens.
|
|
*
|
|
* The "current" idea of meta_window_get_work_area_current_monitor() and
|
|
* meta_screen_get_current_monitor() is slightly different: the former
|
|
* refers to the monitor which contains the largest part of the window,
|
|
* the latter to the one where the pointer is located.
|
|
*/
|
|
logical_monitor =
|
|
meta_monitor_manager_get_logical_monitor_at (monitor_manager, x, y);
|
|
if (!logical_monitor)
|
|
return;
|
|
|
|
meta_window_get_work_area_for_monitor (window,
|
|
logical_monitor->number,
|
|
&work_area);
|
|
|
|
/* Check if the cursor is in a position which triggers tiling
|
|
* and set tile_mode accordingly.
|
|
*/
|
|
if (meta_window_can_tile_side_by_side (window) &&
|
|
x >= logical_monitor->rect.x && x < (work_area.x + shake_threshold))
|
|
display->preview_tile_mode = META_TILE_LEFT;
|
|
else if (meta_window_can_tile_side_by_side (window) &&
|
|
x >= work_area.x + work_area.width - shake_threshold &&
|
|
x < (logical_monitor->rect.x + logical_monitor->rect.width))
|
|
display->preview_tile_mode = META_TILE_RIGHT;
|
|
else if (meta_window_can_maximize (window) &&
|
|
y >= logical_monitor->rect.y && y <= work_area.y)
|
|
display->preview_tile_mode = META_TILE_MAXIMIZED;
|
|
else
|
|
display->preview_tile_mode = META_TILE_NONE;
|
|
|
|
if (display->preview_tile_mode != META_TILE_NONE)
|
|
window->tile_monitor_number = logical_monitor->number;
|
|
}
|
|
|
|
static void
|
|
update_move (MetaWindowDrag *window_drag,
|
|
MetaEdgeResistanceFlags flags,
|
|
int x,
|
|
int y)
|
|
{
|
|
MetaWindow *window;
|
|
int dx, dy;
|
|
int new_x, new_y;
|
|
MetaRectangle old;
|
|
int shake_threshold;
|
|
MetaDisplay *display;
|
|
|
|
window = window_drag->effective_grab_window;
|
|
if (!window)
|
|
return;
|
|
|
|
display = window->display;
|
|
window_drag->latest_motion_x = x;
|
|
window_drag->latest_motion_y = y;
|
|
|
|
clear_move_resize_later (window_drag);
|
|
|
|
dx = x - window_drag->anchor_root_x;
|
|
dy = y - window_drag->anchor_root_y;
|
|
|
|
new_x = window_drag->anchor_window_pos.x + dx;
|
|
new_y = window_drag->anchor_window_pos.y + dy;
|
|
|
|
meta_verbose ("x,y = %d,%d anchor ptr %d,%d anchor pos %d,%d dx,dy %d,%d",
|
|
x, y,
|
|
window_drag->anchor_root_x,
|
|
window_drag->anchor_root_y,
|
|
window_drag->anchor_window_pos.x,
|
|
window_drag->anchor_window_pos.y,
|
|
dx, dy);
|
|
|
|
/* Don't bother doing anything if no move has been specified. (This
|
|
* happens often, even in keyboard moving, due to the warping of the
|
|
* pointer.
|
|
*/
|
|
if (dx == 0 && dy == 0)
|
|
return;
|
|
|
|
/* Originally for detaching maximized windows, but we use this
|
|
* for the zones at the sides of the monitor where trigger tiling
|
|
* because it's about the right size
|
|
*/
|
|
#define DRAG_THRESHOLD_TO_SHAKE_THRESHOLD_FACTOR 6
|
|
shake_threshold = meta_prefs_get_drag_threshold () *
|
|
DRAG_THRESHOLD_TO_SHAKE_THRESHOLD_FACTOR;
|
|
|
|
if (flags & META_EDGE_RESISTANCE_SNAP)
|
|
{
|
|
/* We don't want to tile while snapping. Also, clear any previous tile
|
|
request. */
|
|
display->preview_tile_mode = META_TILE_NONE;
|
|
window->tile_monitor_number = -1;
|
|
}
|
|
else if (meta_prefs_get_edge_tiling () &&
|
|
!META_WINDOW_MAXIMIZED (window) &&
|
|
!META_WINDOW_TILED_SIDE_BY_SIDE (window))
|
|
{
|
|
update_move_maybe_tile (window, shake_threshold, x, y);
|
|
}
|
|
|
|
/* shake loose (unmaximize) maximized or tiled window if dragged beyond
|
|
* the threshold in the Y direction. Tiled windows can also be pulled
|
|
* loose via X motion.
|
|
*/
|
|
|
|
if ((META_WINDOW_MAXIMIZED (window) && ABS (dy) >= shake_threshold) ||
|
|
(META_WINDOW_TILED_SIDE_BY_SIDE (window) && (MAX (ABS (dx), ABS (dy)) >= shake_threshold)))
|
|
{
|
|
double prop;
|
|
|
|
/* Shake loose, so that the window snaps back to maximized
|
|
* when dragged near the top; do not snap back if tiling
|
|
* is enabled, as top edge tiling can be used in that case
|
|
*/
|
|
window->shaken_loose = !meta_prefs_get_edge_tiling ();
|
|
window->tile_mode = META_TILE_NONE;
|
|
|
|
/* move the unmaximized window to the cursor */
|
|
prop =
|
|
((double) (x - window_drag->initial_window_pos.x)) /
|
|
((double) window_drag->initial_window_pos.width);
|
|
|
|
window_drag->initial_window_pos.x = x - window->saved_rect.width * prop;
|
|
|
|
/* If we started dragging the window from above the top of the window,
|
|
* pretend like we started dragging from the middle of the titlebar
|
|
* instead, as the "correct" anchoring looks wrong. */
|
|
if (window_drag->anchor_root_y < window_drag->initial_window_pos.y)
|
|
{
|
|
MetaRectangle titlebar_rect;
|
|
meta_window_get_titlebar_rect (window, &titlebar_rect);
|
|
window_drag->anchor_root_y = window_drag->initial_window_pos.y + titlebar_rect.height / 2;
|
|
}
|
|
|
|
window->saved_rect.x = window_drag->initial_window_pos.x;
|
|
window->saved_rect.y = window_drag->initial_window_pos.y;
|
|
|
|
meta_window_unmaximize (window, META_MAXIMIZE_BOTH);
|
|
return;
|
|
}
|
|
|
|
/* remaximize window on another monitor if window has been shaken
|
|
* loose or it is still maximized (then move straight)
|
|
*/
|
|
else if ((window->shaken_loose || META_WINDOW_MAXIMIZED (window)) &&
|
|
window->tile_mode != META_TILE_LEFT && window->tile_mode != META_TILE_RIGHT)
|
|
{
|
|
MetaDisplay *display = meta_window_get_display (window);
|
|
MetaContext *context = meta_display_get_context (display);
|
|
MetaBackend *backend = meta_context_get_backend (context);
|
|
MetaMonitorManager *monitor_manager =
|
|
meta_backend_get_monitor_manager (backend);
|
|
int n_logical_monitors;
|
|
const MetaLogicalMonitor *wmonitor;
|
|
MetaRectangle work_area;
|
|
int monitor;
|
|
|
|
window->tile_mode = META_TILE_NONE;
|
|
wmonitor = window->monitor;
|
|
n_logical_monitors =
|
|
meta_monitor_manager_get_num_logical_monitors (monitor_manager);
|
|
|
|
for (monitor = 0; monitor < n_logical_monitors; monitor++)
|
|
{
|
|
meta_window_get_work_area_for_monitor (window, monitor, &work_area);
|
|
|
|
/* check if cursor is near the top of a monitor work area */
|
|
if (x >= work_area.x &&
|
|
x < (work_area.x + work_area.width) &&
|
|
y >= work_area.y &&
|
|
y < (work_area.y + shake_threshold))
|
|
{
|
|
/* move the saved rect if window will become maximized on an
|
|
* other monitor so user isn't surprised on a later unmaximize
|
|
*/
|
|
if (wmonitor->number != monitor)
|
|
{
|
|
window->saved_rect.x = work_area.x;
|
|
window->saved_rect.y = work_area.y;
|
|
|
|
if (window->frame)
|
|
{
|
|
window->saved_rect.x += window->frame->child_x;
|
|
window->saved_rect.y += window->frame->child_y;
|
|
}
|
|
|
|
window->unconstrained_rect.x = window->saved_rect.x;
|
|
window->unconstrained_rect.y = window->saved_rect.y;
|
|
|
|
meta_window_unmaximize (window, META_MAXIMIZE_BOTH);
|
|
|
|
window_drag->initial_window_pos = work_area;
|
|
window_drag->anchor_root_x = x;
|
|
window_drag->anchor_root_y = y;
|
|
window->shaken_loose = FALSE;
|
|
|
|
meta_window_maximize (window, META_MAXIMIZE_BOTH);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Delay showing the tile preview slightly to make it more unlikely to
|
|
* trigger it unwittingly, e.g. when shaking loose the window or moving
|
|
* it to another monitor.
|
|
*/
|
|
meta_display_update_tile_preview (display,
|
|
window->tile_mode != META_TILE_NONE);
|
|
|
|
meta_window_get_frame_rect (window, &old);
|
|
|
|
/* Don't allow movement in the maximized directions or while tiled */
|
|
if (window->maximized_horizontally || META_WINDOW_TILED_SIDE_BY_SIDE (window))
|
|
new_x = old.x;
|
|
if (window->maximized_vertically)
|
|
new_y = old.y;
|
|
|
|
/* Do any edge resistance/snapping */
|
|
meta_window_edge_resistance_for_move (window,
|
|
&new_x,
|
|
&new_y,
|
|
flags);
|
|
|
|
meta_window_move_frame (window, TRUE, new_x, new_y);
|
|
}
|
|
|
|
static gboolean
|
|
update_move_cb (gpointer user_data)
|
|
{
|
|
MetaWindowDrag *window_drag = user_data;
|
|
|
|
window_drag->move_resize_later_id = 0;
|
|
|
|
update_move (window_drag,
|
|
window_drag->last_edge_resistance_flags,
|
|
window_drag->latest_motion_x,
|
|
window_drag->latest_motion_y);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
queue_update_move (MetaWindowDrag *window_drag,
|
|
MetaEdgeResistanceFlags flags,
|
|
int x,
|
|
int y)
|
|
{
|
|
MetaCompositor *compositor;
|
|
MetaLaters *laters;
|
|
MetaDisplay *display;
|
|
|
|
window_drag->last_edge_resistance_flags = flags;
|
|
window_drag->latest_motion_x = x;
|
|
window_drag->latest_motion_y = y;
|
|
|
|
if (window_drag->move_resize_later_id)
|
|
return;
|
|
if (!window_drag->effective_grab_window)
|
|
return;
|
|
|
|
display = meta_window_get_display (window_drag->effective_grab_window);
|
|
compositor = meta_display_get_compositor (display);
|
|
laters = meta_compositor_get_laters (compositor);
|
|
window_drag->move_resize_later_id =
|
|
meta_laters_add (laters,
|
|
META_LATER_BEFORE_REDRAW,
|
|
update_move_cb,
|
|
window_drag, NULL);
|
|
}
|
|
|
|
static void
|
|
update_resize (MetaWindowDrag *window_drag,
|
|
MetaEdgeResistanceFlags flags,
|
|
int x,
|
|
int y)
|
|
{
|
|
int dx, dy;
|
|
MetaGravity gravity;
|
|
MetaRectangle new_rect;
|
|
MetaRectangle old_rect;
|
|
MetaWindow *window;
|
|
|
|
window = window_drag->effective_grab_window;
|
|
if (!window)
|
|
return;
|
|
|
|
window_drag->latest_motion_x = x;
|
|
window_drag->latest_motion_y = y;
|
|
|
|
clear_move_resize_later (window_drag);
|
|
|
|
dx = x - window_drag->anchor_root_x;
|
|
dy = y - window_drag->anchor_root_y;
|
|
|
|
/* Attached modal dialogs are special in that size
|
|
* changes apply to both sides, so that the dialog
|
|
* remains centered to the parent.
|
|
*/
|
|
if (meta_window_is_attached_dialog (window))
|
|
{
|
|
dx *= 2;
|
|
dy *= 2;
|
|
}
|
|
|
|
new_rect.width = window_drag->anchor_window_pos.width;
|
|
new_rect.height = window_drag->anchor_window_pos.height;
|
|
|
|
/* Don't bother doing anything if no move has been specified. (This
|
|
* happens often, even in keyboard resizing, due to the warping of the
|
|
* pointer.
|
|
*/
|
|
if (dx == 0 && dy == 0)
|
|
return;
|
|
|
|
if (window_drag->grab_op == META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN)
|
|
{
|
|
MetaGrabOp op = META_GRAB_OP_WINDOW_BASE | META_GRAB_OP_WINDOW_FLAG_KEYBOARD;
|
|
|
|
if (dx > 0)
|
|
op |= META_GRAB_OP_WINDOW_DIR_EAST;
|
|
else if (dx < 0)
|
|
op |= META_GRAB_OP_WINDOW_DIR_WEST;
|
|
|
|
if (dy > 0)
|
|
op |= META_GRAB_OP_WINDOW_DIR_SOUTH;
|
|
else if (dy < 0)
|
|
op |= META_GRAB_OP_WINDOW_DIR_NORTH;
|
|
|
|
window_drag->grab_op = op;
|
|
|
|
update_keyboard_resize (window_drag, TRUE);
|
|
}
|
|
|
|
if (window_drag->grab_op & META_GRAB_OP_WINDOW_DIR_EAST)
|
|
new_rect.width += dx;
|
|
else if (window_drag->grab_op & META_GRAB_OP_WINDOW_DIR_WEST)
|
|
new_rect.width -= dx;
|
|
|
|
if (window_drag->grab_op & META_GRAB_OP_WINDOW_DIR_SOUTH)
|
|
new_rect.height += dy;
|
|
else if (window_drag->grab_op & META_GRAB_OP_WINDOW_DIR_NORTH)
|
|
new_rect.height -= dy;
|
|
|
|
meta_window_maybe_apply_size_hints (window, &new_rect);
|
|
|
|
/* If we're waiting for a request for _NET_WM_SYNC_REQUEST, we'll
|
|
* resize the window when the window responds, or when we time
|
|
* the response out.
|
|
*/
|
|
if (window->client_type == META_WINDOW_CLIENT_TYPE_X11 &&
|
|
meta_window_x11_is_awaiting_sync_response (window))
|
|
return;
|
|
|
|
meta_window_get_frame_rect (window, &old_rect);
|
|
|
|
/* One sided resizing ought to actually be one-sided, despite the fact that
|
|
* aspect ratio windows don't interact nicely with the above stuff. So,
|
|
* to avoid some nasty flicker, we enforce that.
|
|
*/
|
|
|
|
if ((window_drag->grab_op & (META_GRAB_OP_WINDOW_DIR_WEST | META_GRAB_OP_WINDOW_DIR_EAST)) == 0)
|
|
new_rect.width = old_rect.width;
|
|
|
|
if ((window_drag->grab_op & (META_GRAB_OP_WINDOW_DIR_NORTH | META_GRAB_OP_WINDOW_DIR_SOUTH)) == 0)
|
|
new_rect.height = old_rect.height;
|
|
|
|
/* compute gravity of client during operation */
|
|
gravity = meta_resize_gravity_from_grab_op (window_drag->grab_op);
|
|
g_assert (gravity >= 0);
|
|
|
|
/* Do any edge resistance/snapping */
|
|
meta_window_edge_resistance_for_resize (window,
|
|
&new_rect.width,
|
|
&new_rect.height,
|
|
gravity,
|
|
flags);
|
|
|
|
meta_window_resize_frame_with_gravity (window, TRUE,
|
|
new_rect.width, new_rect.height,
|
|
gravity);
|
|
}
|
|
|
|
static gboolean
|
|
update_resize_cb (gpointer user_data)
|
|
{
|
|
MetaWindowDrag *window_drag = user_data;
|
|
|
|
window_drag->move_resize_later_id = 0;
|
|
|
|
update_resize (window_drag,
|
|
window_drag->last_edge_resistance_flags,
|
|
window_drag->latest_motion_x,
|
|
window_drag->latest_motion_y);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
queue_update_resize (MetaWindowDrag *window_drag,
|
|
MetaEdgeResistanceFlags flags,
|
|
int x,
|
|
int y)
|
|
{
|
|
MetaCompositor *compositor;
|
|
MetaLaters *laters;
|
|
MetaDisplay *display;
|
|
|
|
window_drag->last_edge_resistance_flags = flags;
|
|
window_drag->latest_motion_x = x;
|
|
window_drag->latest_motion_y = y;
|
|
|
|
if (window_drag->move_resize_later_id)
|
|
return;
|
|
if (!window_drag->effective_grab_window)
|
|
return;
|
|
|
|
display = meta_window_get_display (window_drag->effective_grab_window);
|
|
compositor = meta_display_get_compositor (display);
|
|
laters = meta_compositor_get_laters (compositor);
|
|
window_drag->move_resize_later_id =
|
|
meta_laters_add (laters,
|
|
META_LATER_BEFORE_REDRAW,
|
|
update_resize_cb,
|
|
window_drag, NULL);
|
|
}
|
|
|
|
static void
|
|
maybe_maximize_tiled_window (MetaWindow *window)
|
|
{
|
|
MetaRectangle work_area;
|
|
gint shake_threshold;
|
|
|
|
if (!META_WINDOW_TILED_SIDE_BY_SIDE (window))
|
|
return;
|
|
|
|
shake_threshold = meta_prefs_get_drag_threshold ();
|
|
|
|
meta_window_get_work_area_for_monitor (window,
|
|
window->tile_monitor_number,
|
|
&work_area);
|
|
if (window->rect.width >= work_area.width - shake_threshold)
|
|
meta_window_maximize (window, META_MAXIMIZE_BOTH);
|
|
}
|
|
|
|
static void
|
|
check_threshold_reached (MetaWindowDrag *window_drag,
|
|
int x,
|
|
int y)
|
|
{
|
|
/* Don't bother doing the check again if we've already reached the threshold */
|
|
if (meta_prefs_get_raise_on_click () ||
|
|
window_drag->threshold_movement_reached)
|
|
return;
|
|
|
|
if (ABS (window_drag->initial_x - x) >= 8 ||
|
|
ABS (window_drag->initial_y - y) >= 8)
|
|
window_drag->threshold_movement_reached = TRUE;
|
|
}
|
|
|
|
static void
|
|
end_grab_op (MetaWindowDrag *window_drag,
|
|
const ClutterEvent *event)
|
|
{
|
|
ClutterModifierType modifiers;
|
|
MetaEdgeResistanceFlags last_flags;
|
|
MetaWindow *window;
|
|
gfloat x, y;
|
|
|
|
window = window_drag->effective_grab_window;
|
|
if (!window)
|
|
return;
|
|
|
|
clutter_event_get_coords (event, &x, &y);
|
|
modifiers = clutter_event_get_state (event);
|
|
check_threshold_reached (window_drag, x, y);
|
|
|
|
/* If the user was snap moving then ignore the button
|
|
* release because they may have let go of shift before
|
|
* releasing the mouse button and they almost certainly do
|
|
* not want a non-snapped movement to occur from the button
|
|
* release.
|
|
*/
|
|
last_flags = window_drag->last_edge_resistance_flags;
|
|
if ((last_flags & META_EDGE_RESISTANCE_SNAP) == 0)
|
|
{
|
|
MetaEdgeResistanceFlags flags = META_EDGE_RESISTANCE_DEFAULT;
|
|
|
|
if (modifiers & CLUTTER_SHIFT_MASK)
|
|
flags |= META_EDGE_RESISTANCE_SNAP;
|
|
|
|
if (modifiers & CLUTTER_CONTROL_MASK)
|
|
flags |= META_EDGE_RESISTANCE_WINDOWS;
|
|
|
|
if (meta_grab_op_is_moving (window_drag->grab_op))
|
|
{
|
|
if (window->display->preview_tile_mode != META_TILE_NONE)
|
|
meta_window_tile (window, window->display->preview_tile_mode);
|
|
else
|
|
update_move (window_drag, flags, x, y);
|
|
}
|
|
else if (meta_grab_op_is_resizing (window_drag->grab_op))
|
|
{
|
|
if (window->tile_match != NULL)
|
|
flags |= (META_EDGE_RESISTANCE_SNAP | META_EDGE_RESISTANCE_WINDOWS);
|
|
|
|
update_resize (window_drag, flags, x, y);
|
|
maybe_maximize_tiled_window (window);
|
|
}
|
|
}
|
|
window->display->preview_tile_mode = META_TILE_NONE;
|
|
meta_window_drag_end (window_drag);
|
|
}
|
|
|
|
static void
|
|
process_pointer_event (MetaWindowDrag *window_drag,
|
|
const ClutterEvent *event)
|
|
{
|
|
ClutterEventSequence *sequence = clutter_event_get_event_sequence (event);
|
|
ClutterModifierType modifier_state;
|
|
MetaEdgeResistanceFlags flags;
|
|
MetaWindow *window;
|
|
gfloat x, y;
|
|
|
|
window = window_drag->effective_grab_window;
|
|
if (!window)
|
|
return;
|
|
|
|
switch (event->type)
|
|
{
|
|
case CLUTTER_BUTTON_PRESS:
|
|
/* This is the keybinding or menu case where we've
|
|
* been dragging around the window without the button
|
|
* pressed, or the case of pressing extra mouse buttons
|
|
* while a grab op is ongoing.
|
|
*/
|
|
end_grab_op (window_drag, event);
|
|
break;
|
|
case CLUTTER_TOUCH_END:
|
|
if (!meta_display_is_pointer_emulating_sequence (window->display, sequence))
|
|
return;
|
|
|
|
end_grab_op (window_drag, event);
|
|
break;
|
|
case CLUTTER_BUTTON_RELEASE:
|
|
if (event->button.button == 1 ||
|
|
event->button.button == (unsigned int) meta_prefs_get_mouse_button_resize ())
|
|
end_grab_op (window_drag, event);
|
|
|
|
break;
|
|
case CLUTTER_TOUCH_UPDATE:
|
|
if (!meta_display_is_pointer_emulating_sequence (window->display, sequence))
|
|
return;
|
|
|
|
G_GNUC_FALLTHROUGH;
|
|
case CLUTTER_MOTION:
|
|
modifier_state = clutter_event_get_state (event);
|
|
clutter_event_get_coords (event, &x, &y);
|
|
flags = META_EDGE_RESISTANCE_DEFAULT;
|
|
|
|
if (modifier_state & CLUTTER_SHIFT_MASK)
|
|
flags |= META_EDGE_RESISTANCE_SNAP;
|
|
|
|
if (modifier_state & CLUTTER_CONTROL_MASK)
|
|
flags |= META_EDGE_RESISTANCE_WINDOWS;
|
|
|
|
check_threshold_reached (window_drag, x, y);
|
|
if (meta_grab_op_is_moving (window_drag->grab_op))
|
|
{
|
|
queue_update_move (window_drag, flags, x, y);
|
|
}
|
|
else if (meta_grab_op_is_resizing (window_drag->grab_op))
|
|
{
|
|
if (window->tile_match != NULL)
|
|
flags |= (META_EDGE_RESISTANCE_SNAP | META_EDGE_RESISTANCE_WINDOWS);
|
|
|
|
queue_update_resize (window_drag, flags, x, y);
|
|
}
|
|
break;
|
|
case CLUTTER_TOUCH_CANCEL:
|
|
end_grab_op (window_drag, event);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
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:
|
|
process_pointer_event (window_drag, event);
|
|
break;
|
|
}
|
|
|
|
return CLUTTER_EVENT_PROPAGATE;
|
|
}
|
|
|
|
gboolean
|
|
meta_window_drag_begin (MetaWindowDrag *window_drag,
|
|
uint32_t timestamp)
|
|
{
|
|
MetaWindow *window = window_drag->window, *grab_window = NULL;
|
|
MetaDisplay *display = meta_window_get_display (window);
|
|
MetaContext *context = meta_display_get_context (display);
|
|
MetaBackend *backend = meta_context_get_backend (context);
|
|
MetaGrabOp grab_op = window_drag->grab_op;
|
|
ClutterActor *stage;
|
|
int root_x, root_y;
|
|
|
|
if ((grab_op & META_GRAB_OP_KEYBOARD_MOVING) == META_GRAB_OP_KEYBOARD_MOVING)
|
|
{
|
|
warp_grab_pointer (window_drag, window, grab_op, &root_x, &root_y);
|
|
}
|
|
else
|
|
{
|
|
ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend);
|
|
ClutterSeat *seat = clutter_backend_get_default_seat (clutter_backend);
|
|
ClutterInputDevice *device;
|
|
graphene_point_t pos;
|
|
|
|
device = clutter_seat_get_pointer (seat);
|
|
clutter_seat_query_state (seat, device, NULL, &pos, NULL);
|
|
root_x = pos.x;
|
|
root_y = pos.y;
|
|
}
|
|
|
|
meta_topic (META_DEBUG_WINDOW_OPS,
|
|
"Doing grab op %u on window %s pointer pos %d,%d",
|
|
grab_op, window->desc,
|
|
root_x, root_y);
|
|
|
|
if (meta_prefs_get_raise_on_click ())
|
|
meta_window_raise (window);
|
|
else
|
|
{
|
|
window_drag->initial_x = root_x;
|
|
window_drag->initial_y = root_y;
|
|
window_drag->threshold_movement_reached = FALSE;
|
|
}
|
|
|
|
grab_window = window;
|
|
|
|
/* If we're trying to move a window, move the first
|
|
* non-attached dialog instead.
|
|
*/
|
|
if (meta_grab_op_is_moving (grab_op))
|
|
grab_window = get_first_freefloating_window (window);
|
|
|
|
g_assert (grab_window != NULL);
|
|
g_assert (grab_op != META_GRAB_OP_NONE);
|
|
|
|
/* Make sure the window is focused, otherwise the keyboard grab
|
|
* won't do a lot of good.
|
|
*/
|
|
meta_topic (META_DEBUG_FOCUS,
|
|
"Focusing %s because we're grabbing all its keys",
|
|
window->desc);
|
|
meta_window_focus (window, timestamp);
|
|
|
|
stage = meta_backend_get_stage (backend);
|
|
|
|
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),
|
|
window_drag->handler);
|
|
|
|
if ((clutter_grab_get_seat_state (window_drag->grab) &
|
|
CLUTTER_GRAB_STATE_POINTER) == 0 &&
|
|
!meta_grab_op_is_keyboard (grab_op))
|
|
{
|
|
meta_topic (META_DEBUG_WINDOW_OPS,
|
|
"Pointer grab failed on a pointer grab op");
|
|
return FALSE;
|
|
}
|
|
|
|
/* Temporarily release the passive key grabs on the window */
|
|
meta_window_ungrab_keys (grab_window);
|
|
|
|
g_set_object (&window_drag->effective_grab_window, grab_window);
|
|
window_drag->unmanaging_id =
|
|
g_signal_connect (grab_window, "unmanaging",
|
|
G_CALLBACK (on_grab_window_unmanaging), window_drag);
|
|
|
|
if (meta_grab_op_is_moving (grab_op))
|
|
{
|
|
window_drag->size_changed_id =
|
|
g_signal_connect (grab_window, "size-changed",
|
|
G_CALLBACK (on_grab_window_size_changed), window_drag);
|
|
}
|
|
|
|
window_drag->tile_mode = grab_window->tile_mode;
|
|
window_drag->tile_monitor_number = grab_window->tile_monitor_number;
|
|
window_drag->anchor_root_x = root_x;
|
|
window_drag->anchor_root_y = root_y;
|
|
window_drag->latest_motion_x = root_x;
|
|
window_drag->latest_motion_y = root_y;
|
|
window_drag->last_edge_resistance_flags = META_EDGE_RESISTANCE_DEFAULT;
|
|
|
|
meta_window_drag_update_cursor (window_drag);
|
|
|
|
clear_move_resize_later (window_drag);
|
|
|
|
meta_topic (META_DEBUG_WINDOW_OPS,
|
|
"Grab op %u on window %s successful",
|
|
grab_op, window ? window->desc : "(null)");
|
|
|
|
meta_window_get_frame_rect (window_drag->effective_grab_window,
|
|
&window_drag->initial_window_pos);
|
|
window_drag->anchor_window_pos = window_drag->initial_window_pos;
|
|
|
|
if (meta_is_wayland_compositor ())
|
|
{
|
|
meta_display_sync_wayland_input_focus (display);
|
|
meta_display_cancel_touch (display);
|
|
}
|
|
|
|
g_signal_emit_by_name (display, "grab-op-begin", grab_window, grab_op);
|
|
|
|
meta_window_grab_op_began (grab_window, grab_op);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
meta_window_drag_update_resize (MetaWindowDrag *window_drag)
|
|
{
|
|
update_resize (window_drag,
|
|
window_drag->last_edge_resistance_flags,
|
|
window_drag->latest_motion_x,
|
|
window_drag->latest_motion_y);
|
|
}
|