gtk/gdk/wayland/gdkdevice-wayland.c
Matthias Clasen 3d802177be wayland: No viewporter for cursors
The outlook for mutter supporting this in GNOME 47 are cloudy,
so lets flip the switch back. You can still set
USE_POINTER_VIEWPORT in the environment to try this code.
2024-07-31 07:44:48 -04:00

763 lines
23 KiB
C

/* GDK - The GIMP Drawing Kit
* Copyright (C) 2009 Carlos Garnacho <carlosg@gnome.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gdkdevice-wayland-private.h"
#include "gdkprivate-wayland.h"
#include "gdkcursorprivate.h"
#include "gdkeventsprivate.h"
#define GDK_SLOT_TO_EVENT_SEQUENCE(s) ((GdkEventSequence *) GUINT_TO_POINTER((s) + 1))
#define GDK_EVENT_SEQUENCE_TO_SLOT(s) (GPOINTER_TO_UINT(s) - 1)
typedef struct
{
GdkWaylandTouchData *emulating_touch; /* Only used on wd->logical_touch */
GdkWaylandPointerData *pointer;
} GdkWaylandDevicePrivate;
G_DEFINE_TYPE_WITH_PRIVATE (GdkWaylandDevice, gdk_wayland_device, GDK_TYPE_DEVICE)
static void
gdk_wayland_device_set_surface_cursor (GdkDevice *device,
GdkSurface *surface,
GdkCursor *cursor)
{
GdkWaylandSeat *seat = GDK_WAYLAND_SEAT (gdk_device_get_seat (device));
GdkWaylandDevice *wayland_device = GDK_WAYLAND_DEVICE (device);
GdkWaylandPointerData *pointer =
gdk_wayland_device_get_pointer (wayland_device);
if (device == seat->logical_touch)
return;
if (seat->grab_cursor)
cursor = seat->grab_cursor;
if (pointer->cursor != NULL &&
cursor != NULL &&
gdk_cursor_equal (cursor, pointer->cursor))
return;
if (cursor == NULL)
{
if (!pointer->cursor_is_default)
{
g_clear_object (&pointer->cursor);
pointer->cursor = gdk_cursor_new_from_name ("default", NULL);
pointer->cursor_is_default = TRUE;
gdk_wayland_seat_stop_cursor_animation (seat, pointer);
gdk_wayland_device_update_surface_cursor (device);
}
else
{
/* Nothing to do, we'already using the default cursor */
}
}
else
{
g_set_object (&pointer->cursor, cursor);
pointer->cursor_is_default = FALSE;
gdk_wayland_seat_stop_cursor_animation (seat, pointer);
gdk_wayland_device_update_surface_cursor (device);
}
}
static GdkGrabStatus
gdk_wayland_device_grab (GdkDevice *device,
GdkSurface *surface,
gboolean owner_events,
GdkEventMask event_mask,
GdkSurface *confine_to,
GdkCursor *cursor,
guint32 time_)
{
GdkWaylandSeat *wayland_seat = GDK_WAYLAND_SEAT (gdk_device_get_seat (device));
GdkWaylandDevice *wayland_device = GDK_WAYLAND_DEVICE (device);
GdkWaylandPointerData *pointer =
gdk_wayland_device_get_pointer (wayland_device);
if (GDK_IS_DRAG_SURFACE (surface) &&
gdk_surface_get_mapped (surface))
{
g_warning ("Surface %p is already mapped at the time of grabbing. "
"gdk_seat_grab() should be used to simultaneously grab input "
"and show this popup. You may find oddities ahead.",
surface);
}
gdk_wayland_device_maybe_emit_grab_crossing (device, surface, time_);
if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD)
{
/* Device is a keyboard */
gdk_wayland_surface_inhibit_shortcuts (surface,
gdk_device_get_seat (device));
return GDK_GRAB_SUCCESS;
}
else
{
/* Device is a pointer */
if (pointer->grab_surface != NULL &&
time_ != 0 && pointer->grab_time > time_)
{
return GDK_GRAB_ALREADY_GRABBED;
}
if (time_ == 0)
time_ = pointer->time;
pointer->grab_surface = surface;
pointer->grab_time = time_;
_gdk_wayland_surface_set_grab_seat (surface, GDK_SEAT (wayland_seat));
g_clear_object (&wayland_seat->cursor);
if (cursor)
wayland_seat->cursor = g_object_ref (cursor);
gdk_wayland_device_update_surface_cursor (device);
}
return GDK_GRAB_SUCCESS;
}
static void
gdk_wayland_device_ungrab (GdkDevice *device,
guint32 time_)
{
GdkWaylandDevice *wayland_device = GDK_WAYLAND_DEVICE (device);
GdkWaylandPointerData *pointer =
gdk_wayland_device_get_pointer (wayland_device);
GdkSurface *prev_focus;
prev_focus = gdk_wayland_device_maybe_emit_ungrab_crossing (device, time_);
if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD)
{
/* Device is a keyboard */
if (prev_focus)
gdk_wayland_surface_restore_shortcuts (prev_focus,
gdk_device_get_seat (device));
}
else
{
/* Device is a pointer */
gdk_wayland_device_update_surface_cursor (device);
if (pointer->grab_surface)
_gdk_wayland_surface_set_grab_seat (pointer->grab_surface,
NULL);
}
}
static GdkSurface *
gdk_wayland_device_surface_at_position (GdkDevice *device,
double *win_x,
double *win_y,
GdkModifierType *mask)
{
GdkWaylandDevice *wayland_device = GDK_WAYLAND_DEVICE (device);
GdkWaylandPointerData *pointer;
pointer = gdk_wayland_device_get_pointer (wayland_device);
if (!pointer)
return NULL;
if (win_x)
*win_x = pointer->surface_x;
if (win_y)
*win_y = pointer->surface_y;
if (mask)
*mask = gdk_wayland_device_get_modifiers (device);
return pointer->focus;
}
static void
gdk_wayland_device_class_init (GdkWaylandDeviceClass *klass)
{
GdkDeviceClass *device_class = GDK_DEVICE_CLASS (klass);
device_class->set_surface_cursor = gdk_wayland_device_set_surface_cursor;
device_class->grab = gdk_wayland_device_grab;
device_class->ungrab = gdk_wayland_device_ungrab;
device_class->surface_at_position = gdk_wayland_device_surface_at_position;
}
static void
gdk_wayland_device_init (GdkWaylandDevice *device_core)
{
GdkDevice *device;
device = GDK_DEVICE (device_core);
_gdk_device_add_axis (device, GDK_AXIS_X, 0, 0, 1);
_gdk_device_add_axis (device, GDK_AXIS_Y, 0, 0, 1);
}
GdkWaylandPointerData *
gdk_wayland_device_get_pointer (GdkWaylandDevice *wayland_device)
{
GdkWaylandDevicePrivate *priv =
gdk_wayland_device_get_instance_private (wayland_device);
return priv->pointer;
}
void
gdk_wayland_device_set_pointer (GdkWaylandDevice *wayland_device,
GdkWaylandPointerData *pointer)
{
GdkWaylandDevicePrivate *priv =
gdk_wayland_device_get_instance_private (wayland_device);
priv->pointer = pointer;
}
GdkWaylandTouchData *
gdk_wayland_device_get_emulating_touch (GdkWaylandDevice *wayland_device)
{
GdkWaylandDevicePrivate *priv =
gdk_wayland_device_get_instance_private (wayland_device);
return priv->emulating_touch;
}
void
gdk_wayland_device_set_emulating_touch (GdkWaylandDevice *wayland_device,
GdkWaylandTouchData *touch)
{
GdkWaylandDevicePrivate *priv =
gdk_wayland_device_get_instance_private (wayland_device);
priv->emulating_touch = touch;
}
gboolean
gdk_wayland_device_update_surface_cursor (GdkDevice *device)
{
GdkWaylandSeat *seat = GDK_WAYLAND_SEAT (gdk_device_get_seat (device));
GdkWaylandDevice *wayland_device = GDK_WAYLAND_DEVICE (device);
GdkWaylandPointerData *pointer =
gdk_wayland_device_get_pointer (wayland_device);
struct wl_buffer *buffer;
int x, y, w, h;
double scale;
guint next_image_index, next_image_delay;
gboolean retval = G_SOURCE_REMOVE;
GdkWaylandTabletData *tablet;
gboolean use_viewport = FALSE;
tablet = gdk_wayland_seat_find_tablet (seat, device);
if (pointer->pointer_surface_viewport &&
g_getenv ("USE_POINTER_VIEWPORT"))
use_viewport = TRUE;
if (pointer->cursor)
{
buffer = _gdk_wayland_cursor_get_buffer (GDK_WAYLAND_DISPLAY (seat->display),
pointer->cursor,
pointer->current_output_scale,
use_viewport,
pointer->cursor_image_index,
&x, &y, &w, &h, &scale);
}
else
{
pointer->cursor_timeout_id = 0;
return G_SOURCE_REMOVE;
}
if (tablet)
{
if (!tablet->current_tool)
{
pointer->cursor_timeout_id = 0;
return G_SOURCE_REMOVE;
}
zwp_tablet_tool_v2_set_cursor (tablet->current_tool->wp_tablet_tool,
pointer->enter_serial,
pointer->pointer_surface,
x, y);
}
else if (seat->wl_pointer)
{
wl_pointer_set_cursor (seat->wl_pointer,
pointer->enter_serial,
pointer->pointer_surface,
x, y);
}
else
{
pointer->cursor_timeout_id = 0;
return G_SOURCE_REMOVE;
}
if (buffer)
{
wl_surface_attach (pointer->pointer_surface, buffer, 0, 0);
if (use_viewport)
{
wp_viewport_set_source (pointer->pointer_surface_viewport,
wl_fixed_from_int (0),
wl_fixed_from_int (0),
wl_fixed_from_double (w * scale),
wl_fixed_from_double (h * scale));
wp_viewport_set_destination (pointer->pointer_surface_viewport, w, h);
}
else if (wl_surface_get_version (pointer->pointer_surface) >= WL_SURFACE_SET_BUFFER_SCALE_SINCE_VERSION)
wl_surface_set_buffer_scale (pointer->pointer_surface, scale);
wl_surface_damage (pointer->pointer_surface, 0, 0, w, h);
wl_surface_commit (pointer->pointer_surface);
}
else
{
wl_surface_attach (pointer->pointer_surface, NULL, 0, 0);
wl_surface_commit (pointer->pointer_surface);
}
next_image_index =
_gdk_wayland_cursor_get_next_image_index (GDK_WAYLAND_DISPLAY (seat->display),
pointer->cursor,
pointer->current_output_scale,
pointer->cursor_image_index,
&next_image_delay);
if (next_image_index != pointer->cursor_image_index)
{
if (next_image_delay != pointer->cursor_image_delay ||
pointer->cursor_timeout_id == 0)
{
guint id;
GSource *source;
gdk_wayland_seat_stop_cursor_animation (seat, pointer);
/* Queue timeout for next frame */
id = g_timeout_add (next_image_delay,
(GSourceFunc) gdk_wayland_device_update_surface_cursor,
device);
source = g_main_context_find_source_by_id (NULL, id);
g_source_set_static_name (source, "[gtk] gdk_wayland_device_update_surface_cursor");
pointer->cursor_timeout_id = id;
}
else
retval = G_SOURCE_CONTINUE;
pointer->cursor_image_index = next_image_index;
pointer->cursor_image_delay = next_image_delay;
}
else
gdk_wayland_seat_stop_cursor_animation (seat, pointer);
return retval;
}
GdkModifierType
gdk_wayland_device_get_modifiers (GdkDevice *device)
{
GdkWaylandSeat *seat = GDK_WAYLAND_SEAT (gdk_device_get_seat (device));
GdkWaylandDevice *wayland_device = GDK_WAYLAND_DEVICE (device);
GdkWaylandPointerData *pointer =
gdk_wayland_device_get_pointer (wayland_device);
GdkModifierType mask;
mask = seat->key_modifiers;
if (pointer)
mask |= pointer->button_modifiers;
return mask;
}
void
gdk_wayland_device_query_state (GdkDevice *device,
GdkSurface *surface,
double *win_x,
double *win_y,
GdkModifierType *mask)
{
GdkWaylandDevice *wayland_device = GDK_WAYLAND_DEVICE (device);
GdkWaylandPointerData *pointer;
double x, y;
if (mask)
*mask = gdk_wayland_device_get_modifiers (device);
pointer = gdk_wayland_device_get_pointer (wayland_device);
if (pointer->focus == surface)
{
x = pointer->surface_x;
y = pointer->surface_y;
}
else
{
x = y = -1;
}
if (win_x)
*win_x = x;
if (win_y)
*win_y = y;
}
GdkSurface *
gdk_wayland_device_get_focus (GdkDevice *device)
{
GdkWaylandSeat *wayland_seat = GDK_WAYLAND_SEAT (gdk_device_get_seat (device));
GdkWaylandDevice *wayland_device = GDK_WAYLAND_DEVICE (device);
GdkWaylandPointerData *pointer;
if (device == wayland_seat->logical_keyboard)
return wayland_seat->keyboard_focus;
else
{
pointer = gdk_wayland_device_get_pointer (wayland_device);
if (pointer)
return pointer->focus;
}
return NULL;
}
static void
emulate_touch_crossing (GdkSurface *surface,
GdkSurface *child_surface,
GdkDevice *device,
GdkDevice *source,
GdkWaylandTouchData *touch,
GdkEventType type,
GdkCrossingMode mode,
guint32 time_)
{
GdkEvent *event;
event = gdk_crossing_event_new (type,
surface,
device,
time_,
0,
touch->x, touch->y,
mode,
GDK_NOTIFY_NONLINEAR);
_gdk_wayland_display_deliver_event (gdk_surface_get_display (surface), event);
}
void
gdk_wayland_device_unset_touch_grab (GdkDevice *gdk_device,
GdkEventSequence *sequence)
{
GdkWaylandSeat *seat;
GdkWaylandTouchData *touch;
GdkEvent *event;
g_return_if_fail (GDK_IS_WAYLAND_DEVICE (gdk_device));
seat = GDK_WAYLAND_SEAT (gdk_device_get_seat (gdk_device));
touch = gdk_wayland_seat_get_touch (seat,
GDK_EVENT_SEQUENCE_TO_SLOT (sequence));
if (touch ==
gdk_wayland_device_get_emulating_touch (GDK_WAYLAND_DEVICE (seat->logical_touch)))
{
gdk_wayland_device_set_emulating_touch (GDK_WAYLAND_DEVICE (seat->logical_touch),
NULL);
emulate_touch_crossing (touch->surface, NULL,
seat->logical_touch, seat->touch,
touch, GDK_LEAVE_NOTIFY, GDK_CROSSING_NORMAL,
GDK_CURRENT_TIME);
}
event = gdk_touch_event_new (GDK_TOUCH_CANCEL,
GDK_SLOT_TO_EVENT_SEQUENCE (touch->id),
touch->surface,
seat->logical_touch,
GDK_CURRENT_TIME,
gdk_wayland_device_get_modifiers (seat->logical_touch),
touch->x, touch->y,
NULL,
touch->initial_touch);
_gdk_wayland_display_deliver_event (seat->display, event);
}
/**
* gdk_wayland_device_get_wl_seat: (skip)
* @device: (type GdkWaylandDevice): a `GdkDevice`
*
* Returns the Wayland `wl_seat` of a `GdkDevice`.
*
* Returns: (transfer none): a Wayland `wl_seat`
*/
struct wl_seat *
gdk_wayland_device_get_wl_seat (GdkDevice *device)
{
GdkWaylandSeat *seat;
g_return_val_if_fail (GDK_IS_WAYLAND_DEVICE (device), NULL);
seat = GDK_WAYLAND_SEAT (gdk_device_get_seat (device));
return seat->wl_seat;
}
/**
* gdk_wayland_device_get_wl_pointer: (skip)
* @device: (type GdkWaylandDevice): a `GdkDevice`
*
* Returns the Wayland `wl_pointer` of a `GdkDevice`.
*
* Returns: (transfer none): a Wayland `wl_pointer`
*/
struct wl_pointer *
gdk_wayland_device_get_wl_pointer (GdkDevice *device)
{
GdkWaylandSeat *seat;
g_return_val_if_fail (GDK_IS_WAYLAND_DEVICE (device), NULL);
seat = GDK_WAYLAND_SEAT (gdk_device_get_seat (device));
return seat->wl_pointer;
}
/**
* gdk_wayland_device_get_wl_keyboard: (skip)
* @device: (type GdkWaylandDevice): a `GdkDevice`
*
* Returns the Wayland `wl_keyboard` of a `GdkDevice`.
*
* Returns: (transfer none): a Wayland `wl_keyboard`
*/
struct wl_keyboard *
gdk_wayland_device_get_wl_keyboard (GdkDevice *device)
{
GdkWaylandSeat *seat;
g_return_val_if_fail (GDK_IS_WAYLAND_DEVICE (device), NULL);
seat = GDK_WAYLAND_SEAT (gdk_device_get_seat (device));
return seat->wl_keyboard;
}
/**
* gdk_wayland_device_get_xkb_keymap:
* @device: (type GdkWaylandDevice): a `GdkDevice`
*
* Returns the `xkb_keymap` of a `GdkDevice`.
*
* Returns: (transfer none): a `struct xkb_keymap`
*
* Since: 4.4
*/
struct xkb_keymap *
gdk_wayland_device_get_xkb_keymap (GdkDevice *device)
{
GdkWaylandSeat *seat = GDK_WAYLAND_SEAT (gdk_device_get_seat (device));
return _gdk_wayland_keymap_get_xkb_keymap (seat->keymap);
}
GdkKeymap *
_gdk_wayland_device_get_keymap (GdkDevice *device)
{
GdkWaylandSeat *seat = GDK_WAYLAND_SEAT (gdk_device_get_seat (device));
return seat->keymap;
}
/**
* gdk_wayland_device_get_data_device: (skip)
* @gdk_device: (type GdkWaylandDevice): a `GdkDevice`
*
* Returns the Wayland `wl_data_device` of a `GdkDevice`.
*
* Returns: (transfer none): a Wayland `wl_data_device`
*/
struct wl_data_device *
gdk_wayland_device_get_data_device (GdkDevice *gdk_device)
{
GdkWaylandSeat *seat;
g_return_val_if_fail (GDK_IS_WAYLAND_DEVICE (gdk_device), NULL);
seat = GDK_WAYLAND_SEAT (gdk_device_get_seat (gdk_device));
return seat->data_device;
}
/**
* gdk_wayland_device_set_selection: (skip)
* @gdk_device: (type GdkWaylandDevice): a `GdkDevice`
* @source: the data source for the selection
*
* Sets the selection of the `GdkDevice.
*
* This is calling wl_data_device_set_selection() on
* the `wl_data_device` of @gdk_device.
*/
void
gdk_wayland_device_set_selection (GdkDevice *gdk_device,
struct wl_data_source *source)
{
GdkWaylandSeat *seat;
guint32 serial;
g_return_if_fail (GDK_IS_WAYLAND_DEVICE (gdk_device));
seat = GDK_WAYLAND_SEAT (gdk_device_get_seat (gdk_device));
serial = _gdk_wayland_seat_get_last_implicit_grab_serial (seat, NULL);
wl_data_device_set_selection (seat->data_device, source, serial);
}
/**
* gdk_wayland_device_get_node_path:
* @device: (type GdkWaylandDevice): a `GdkDevice`
*
* Returns the `/dev/input/event*` path of this device.
*
* For `GdkDevice`s that possibly coalesce multiple hardware
* devices (eg. mouse, keyboard, touch,...), this function
* will return %NULL.
*
* This is most notably implemented for devices of type
* %GDK_SOURCE_PEN, %GDK_SOURCE_TABLET_PAD.
*
* Returns: (nullable) (transfer none): the `/dev/input/event*`
* path of this device
*/
const char *
gdk_wayland_device_get_node_path (GdkDevice *device)
{
GdkWaylandTabletData *tablet;
GdkWaylandTabletPadData *pad;
GdkSeat *seat;
g_return_val_if_fail (GDK_IS_DEVICE (device), NULL);
seat = gdk_device_get_seat (device);
tablet = gdk_wayland_seat_find_tablet (GDK_WAYLAND_SEAT (seat), device);
if (tablet)
return tablet->path;
pad = gdk_wayland_seat_find_pad (GDK_WAYLAND_SEAT (seat), device);
if (pad)
return pad->path;
return NULL;
}
static void
emulate_focus (GdkSurface *surface,
GdkDevice *device,
gboolean focus_in,
guint32 time_)
{
GdkEvent *event = gdk_focus_event_new (surface, device, focus_in);
_gdk_wayland_display_deliver_event (gdk_surface_get_display (surface), event);
}
static void
emulate_crossing (GdkSurface *surface,
GdkSurface *child_surface,
GdkDevice *device,
GdkEventType type,
GdkCrossingMode mode,
guint32 time_)
{
GdkEvent *event;
GdkModifierType state;
double x, y;
gdk_surface_get_device_position (surface, device, &x, &y, &state);
event = gdk_crossing_event_new (type,
surface,
device,
time_,
state,
x, y,
mode,
GDK_NOTIFY_NONLINEAR);
_gdk_wayland_display_deliver_event (gdk_surface_get_display (surface), event);
}
static void
device_emit_grab_crossing (GdkDevice *device,
GdkSurface *from,
GdkSurface *to,
GdkCrossingMode mode,
guint32 time_)
{
if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD)
{
if (from)
emulate_focus (from, device, FALSE, time_);
if (to)
emulate_focus (to, device, TRUE, time_);
}
else
{
if (from)
emulate_crossing (from, to, device, GDK_LEAVE_NOTIFY, mode, time_);
if (to)
emulate_crossing (to, from, device, GDK_ENTER_NOTIFY, mode, time_);
}
}
void
gdk_wayland_device_maybe_emit_grab_crossing (GdkDevice *device,
GdkSurface *window,
guint32 time)
{
GdkSurface *surface = gdk_wayland_device_get_focus (device);
GdkSurface *focus = window;
if (focus != surface)
device_emit_grab_crossing (device, focus, window, GDK_CROSSING_GRAB, time);
}
GdkSurface*
gdk_wayland_device_maybe_emit_ungrab_crossing (GdkDevice *device,
guint32 time_)
{
GdkDeviceGrabInfo *grab;
GdkSurface *focus = NULL;
GdkSurface *surface = NULL;
GdkSurface *prev_focus = NULL;
focus = gdk_wayland_device_get_focus (device);
grab = _gdk_display_get_last_device_grab (gdk_device_get_display (device), device);
if (grab)
{
prev_focus = grab->surface;
surface = grab->surface;
}
if (focus != surface)
device_emit_grab_crossing (device, prev_focus, focus, GDK_CROSSING_UNGRAB, time_);
return prev_focus;
}