gtk/gdk/wayland/gdkdevice-wayland.c
Matthias Clasen 1275a2cb8d wayland: Use a viewport for pointer surfaces
This should give us more flexibility for buffer size vs surface
size.

Unfortunately, mutter doesn't play along currently, so this is
only useful for kwin, weston or sway.
2024-04-04 01:07:28 +02:00

758 lines
22 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;
tablet = gdk_wayland_seat_find_tablet (seat, device);
if (pointer->cursor)
{
buffer = _gdk_wayland_cursor_get_buffer (GDK_WAYLAND_DISPLAY (seat->display),
pointer->cursor,
pointer->current_output_scale,
pointer->pointer_surface_viewport != NULL,
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 (pointer->pointer_surface_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;
}