forked from 12to11/12to11

* 12to11-test.xml (test_manager) <error>: New error `resize_rejected'. <resize_edge>: New enum. (test_surface) <move_resize, resize_finished>: New events and requests. * compositor.h: Update prototypes. * seat.c (CancelResizeOperation): Fix dangling pointer. (FakePointerEdge): (HandlePointerEdge): Always use builtin resize on test seats. (XLMoveToplevel): Return status code. * test.c (struct _TestSurface): New field `resize_callbacks'. (HandleResizeDone): New function. (DestroyBacking): Free resize callbacks. (GetResizeDimensions, MoveResize): New functions. (test_surface_impl, GetTestSurface): Attach new role hooks etc. * tests/seat_test.c (enum test_expect_event_kind): New event kind. (struct test_recorded_button_event): Add serial field. Set it. (struct test_recorded_resize_finished_event): New struct. (enum test_kind): New test. (test_names): Name that test. (LAST_TEST): Set it to the new test. (run_resize_test, test_single_step): Implement the new test. (expect_button_event): Return the serial of the button event. (expect_surface_enter, handle_test_surface_resize_finished) (test_surface_listener, handle_pointer_button) (handle_keyboard_enter): Adjust for changes to event handling.
6442 lines
158 KiB
C
6442 lines
158 KiB
C
/* Wayland compositor running on top of an X server.
|
||
|
||
Copyright (C) 2022 to various contributors.
|
||
|
||
This file is part of 12to11.
|
||
|
||
12to11 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 3 of the License, or (at your
|
||
option) any later version.
|
||
|
||
12to11 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 12to11. If not, see <https://www.gnu.org/licenses/>. */
|
||
|
||
#include <sys/stat.h>
|
||
#include <sys/fcntl.h>
|
||
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
|
||
#include <math.h>
|
||
|
||
#include <errno.h>
|
||
|
||
#include "compositor.h"
|
||
|
||
#include <X11/extensions/shape.h>
|
||
|
||
#include <X11/XKBlib.h>
|
||
|
||
#include <X11/extensions/XKBfile.h>
|
||
#include <X11/extensions/XKM.h>
|
||
|
||
#include <X11/extensions/XInput2.h>
|
||
#include <linux/input-event-codes.h>
|
||
|
||
#include "xdg-shell.h"
|
||
#include "pointer-gestures-unstable-v1.h"
|
||
|
||
/* X11 event opcode, event base, and error base for the input
|
||
extension. */
|
||
|
||
int xi2_opcode, xi_first_event, xi_first_error;
|
||
|
||
/* The version of the input extension in use. */
|
||
|
||
int xi2_major, xi2_minor;
|
||
|
||
/* The current keymap file descriptor. */
|
||
|
||
static int keymap_fd;
|
||
|
||
/* XKB event type. */
|
||
|
||
static int xkb_event_type;
|
||
|
||
/* Keymap currently in use. */
|
||
|
||
static XkbDescPtr xkb_desc;
|
||
|
||
/* Assocation between device IDs and seat objects. This includes both
|
||
keyboard and and pointer devices. */
|
||
|
||
static XLAssocTable *seats;
|
||
|
||
/* Association between device IDs and "source device info" objects.
|
||
This includes both pointer and keyboard devices. */
|
||
static XLAssocTable *devices;
|
||
|
||
/* List of all seats that are not inert. */
|
||
|
||
XLList *live_seats;
|
||
|
||
/* This is a mask of all keyboard state. */
|
||
#define AllKeyMask \
|
||
(ShiftMask | LockMask | ControlMask | Mod1Mask | Mod2Mask | Mod3Mask \
|
||
| Mod4Mask)
|
||
|
||
enum
|
||
{
|
||
IsInert = 1,
|
||
IsWindowMenuShown = (1 << 2),
|
||
IsDragging = (1 << 3),
|
||
IsDropped = (1 << 4),
|
||
IsTextInputSeat = (1 << 5),
|
||
IsPointerLocked = (1 << 6),
|
||
IsSurfaceCoordSet = (1 << 7),
|
||
IsExternalGrabApplied = (1 << 8),
|
||
IsInPinchGesture = (1 << 9),
|
||
IsInSwipeGesture = (1 << 10),
|
||
IsTestSeat = (1 << 11),
|
||
IsTestDeviceSpecified = (1 << 12),
|
||
};
|
||
|
||
enum
|
||
{
|
||
StateIsRaw = 1,
|
||
};
|
||
|
||
enum
|
||
{
|
||
AnyVerticalAxis = 1,
|
||
AnyHorizontalAxis = (1 << 1),
|
||
};
|
||
|
||
typedef struct _Seat Seat;
|
||
typedef struct _SeatClientInfo SeatClientInfo;
|
||
typedef struct _Pointer Pointer;
|
||
typedef struct _Keyboard Keyboard;
|
||
typedef struct _RelativePointer RelativePointer;
|
||
typedef struct _SeatCursor SeatCursor;
|
||
typedef struct _ResizeDoneCallback ResizeDoneCallback;
|
||
typedef struct _ScrollValuator ScrollValuator;
|
||
typedef struct _DestroyListener DestroyListener;
|
||
typedef struct _DeviceInfo DeviceInfo;
|
||
typedef struct _ModifierChangeCallback ModifierChangeCallback;
|
||
typedef struct _CursorRing CursorRing;
|
||
|
||
typedef enum _ResizeEdge ResizeEdge;
|
||
typedef enum _WhatEdge WhatEdge;
|
||
typedef enum _Direction Direction;
|
||
|
||
enum _ResizeEdge
|
||
{
|
||
NoneEdge = 65535,
|
||
TopLeftEdge = 0,
|
||
TopEdge = 1,
|
||
TopRightEdge = 2,
|
||
RightEdge = 3,
|
||
BottomRightEdge = 4,
|
||
BottomEdge = 5,
|
||
BottomLeftEdge = 6,
|
||
LeftEdge = 7,
|
||
MoveEdge = 8,
|
||
};
|
||
|
||
enum _WhatEdge
|
||
{
|
||
APointerEdge,
|
||
AKeyboardEdge,
|
||
};
|
||
|
||
enum _Direction
|
||
{
|
||
Vertical,
|
||
Horizontal,
|
||
};
|
||
|
||
enum
|
||
{
|
||
ResizeAxisTop = 1,
|
||
ResizeAxisLeft = (1 << 1),
|
||
ResizeAxisRight = (1 << 2),
|
||
ResizeAxisBottom = (1 << 3),
|
||
ResizeAxisMove = (1 << 16),
|
||
};
|
||
|
||
enum
|
||
{
|
||
DeviceCanFingerScroll = 1,
|
||
DeviceCanEdgeScroll = 2,
|
||
};
|
||
|
||
/* Array indiced by ResizeEdge containing axes along which the edge
|
||
resizes. */
|
||
|
||
static int resize_edges[] =
|
||
{
|
||
ResizeAxisTop | ResizeAxisLeft,
|
||
ResizeAxisTop,
|
||
ResizeAxisTop | ResizeAxisRight,
|
||
ResizeAxisRight,
|
||
ResizeAxisRight | ResizeAxisBottom,
|
||
ResizeAxisBottom,
|
||
ResizeAxisBottom | ResizeAxisLeft,
|
||
ResizeAxisLeft,
|
||
ResizeAxisMove,
|
||
};
|
||
|
||
#define CursorRingElements 2
|
||
#define CursorRingBusy 3
|
||
|
||
struct _CursorRing
|
||
{
|
||
/* The width and height of the RenderTargets within. */
|
||
int width, height;
|
||
|
||
/* Array of render targets. */
|
||
RenderTarget targets[CursorRingElements];
|
||
|
||
/* Array of pixmaps. */
|
||
Pixmap pixmaps[CursorRingElements];
|
||
|
||
/* Index of target being used. -1 means nothing is being used. */
|
||
short used;
|
||
};
|
||
|
||
struct _DestroyListener
|
||
{
|
||
/* Function called when seat is destroyed. */
|
||
void (*destroy) (void *);
|
||
|
||
/* Data for that function. */
|
||
void *data;
|
||
|
||
/* Next and last destroy listeners in this list. */
|
||
DestroyListener *next, *last;
|
||
};
|
||
|
||
struct _SeatCursor
|
||
{
|
||
/* The parent role. Note that there is no wl_resource associated
|
||
with it. */
|
||
Role role;
|
||
|
||
/* The current cursor. */
|
||
Cursor cursor;
|
||
|
||
/* The seat this cursor is for. */
|
||
Seat *seat;
|
||
|
||
/* The subcompositor for this cursor. */
|
||
Subcompositor *subcompositor;
|
||
|
||
/* The frame callback for this cursor. */
|
||
void *cursor_frame_key;
|
||
|
||
/* Ring of render targets for cursors. This allows updating the
|
||
cursor while not creating a new render target each time. */
|
||
CursorRing *cursor_ring;
|
||
|
||
/* The hotspot of the cursor. */
|
||
int hotspot_x, hotspot_y;
|
||
|
||
/* Whether or not this cursor is currently keeping the cursor clock
|
||
active. */
|
||
Bool holding_cursor_clock;
|
||
};
|
||
|
||
struct _ResizeDoneCallback
|
||
{
|
||
/* Function called when a resize operation finishes. */
|
||
void (*done) (void *, void *);
|
||
|
||
/* Data for this callback. */
|
||
void *data;
|
||
|
||
/* The next and last callbacks in this list. */
|
||
ResizeDoneCallback *next, *last;
|
||
};
|
||
|
||
struct _ScrollValuator
|
||
{
|
||
/* The next scroll valuator in this list. */
|
||
ScrollValuator *next;
|
||
|
||
/* The serial of the last event to have updated this valuator. */
|
||
unsigned long enter_serial;
|
||
|
||
/* The current value of this valuator. */
|
||
double value;
|
||
|
||
/* The increment of this valuator. */
|
||
double increment;
|
||
|
||
/* The number of this valuator. */
|
||
int number;
|
||
|
||
/* The direction of this valuator. */
|
||
Direction direction;
|
||
};
|
||
|
||
struct _Pointer
|
||
{
|
||
/* The seat this pointer object refers to. */
|
||
Seat *seat;
|
||
|
||
/* The struct wl_resource associated with this pointer. */
|
||
struct wl_resource *resource;
|
||
|
||
/* The next and last pointer devices attached to the seat client
|
||
info. */
|
||
Pointer *next, *last;
|
||
|
||
/* The seat client info associated with this pointer resource. */
|
||
SeatClientInfo *info;
|
||
|
||
/* Some state. */
|
||
int state;
|
||
};
|
||
|
||
struct _Keyboard
|
||
{
|
||
/* The seat this keyboard object refers to. */
|
||
Seat *seat;
|
||
|
||
/* The struct wl_resource associated with this keyboard. */
|
||
struct wl_resource *resource;
|
||
|
||
/* The seat client info associated with this keyboard resource. */
|
||
SeatClientInfo *info;
|
||
|
||
/* The next and last keyboard attached to the seat client info and
|
||
the seat. */
|
||
Keyboard *next, *next1, *last, *last1;
|
||
};
|
||
|
||
struct _RelativePointer
|
||
{
|
||
/* The seat this relative pointer refers to. */
|
||
Seat *seat;
|
||
|
||
/* The struct wl_resource associated with this relative pointer. */
|
||
struct wl_resource *resource;
|
||
|
||
/* The seat client info associated with this relative pointer
|
||
resource. */
|
||
SeatClientInfo *info;
|
||
|
||
/* The next and last relative pointers attached to the seat client
|
||
info. */
|
||
RelativePointer *next, *last;
|
||
};
|
||
|
||
struct _SwipeGesture
|
||
{
|
||
/* The seat this swipe gesture refers to. */
|
||
Seat *seat;
|
||
|
||
/* The struct wl_resource associated with this swipe gesture. */
|
||
struct wl_resource *resource;
|
||
|
||
/* The seat client info associated with this swipe gesture
|
||
resource. */
|
||
SeatClientInfo *info;
|
||
|
||
/* The next and last swipe gestures attached to the seat client
|
||
info. */
|
||
SwipeGesture *next, *last;
|
||
};
|
||
|
||
struct _PinchGesture
|
||
{
|
||
/* The seat this pinch gesture refers to. */
|
||
Seat *seat;
|
||
|
||
/* The struct wl_resource associated with this pinch gesture. */
|
||
struct wl_resource *resource;
|
||
|
||
/* The seat client info associated with this pinch gesture. */
|
||
SeatClientInfo *info;
|
||
|
||
/* The next and last pinch gestures attached to the seat client
|
||
info. */
|
||
PinchGesture *next, *last;
|
||
};
|
||
|
||
struct _SeatClientInfo
|
||
{
|
||
/* The next and last structures in the client info chain. */
|
||
SeatClientInfo *next, *last;
|
||
|
||
/* The client corresponding to this object. */
|
||
struct wl_client *client;
|
||
|
||
/* Number of references to this seat client information. */
|
||
int refcount;
|
||
|
||
/* The serial of the last enter event sent. */
|
||
uint32_t last_enter_serial;
|
||
|
||
/* List of pointer objects on this seat for this client. */
|
||
Pointer pointers;
|
||
|
||
/* List of keyboard objects on this seat for this client. */
|
||
Keyboard keyboards;
|
||
|
||
/* List of relative pointers on this seat for this client. */
|
||
RelativePointer relative_pointers;
|
||
|
||
/* List of swipe gestures on this seat for this client. */
|
||
SwipeGesture swipe_gestures;
|
||
|
||
/* List of pinch gestures on this seat for this client. */
|
||
PinchGesture pinch_gestures;
|
||
};
|
||
|
||
struct _ModifierChangeCallback
|
||
{
|
||
/* Callback run when modifiers change. */
|
||
void (*changed) (unsigned int, void *);
|
||
|
||
/* Data for the callback. */
|
||
void *data;
|
||
|
||
/* Next and last callbacks in this list. */
|
||
ModifierChangeCallback *next, *last;
|
||
};
|
||
|
||
struct _Seat
|
||
{
|
||
/* The last user time. */
|
||
Timestamp last_user_time;
|
||
|
||
/* The last time the focus changed into a surface. */
|
||
Timestamp last_focus_time;
|
||
|
||
/* When the last external grab was applied. */
|
||
Time external_grab_time;
|
||
|
||
/* wl_global associated with this seat. */
|
||
struct wl_global *global;
|
||
|
||
/* XI device ID of the master keyboard device. */
|
||
int master_keyboard;
|
||
|
||
/* XI device ID of the master pointer device. */
|
||
int master_pointer;
|
||
|
||
/* Number of references to this seat. */
|
||
int refcount;
|
||
|
||
/* Some flags associated with this seat. */
|
||
int flags;
|
||
|
||
/* The currently focused surface. */
|
||
Surface *focus_surface;
|
||
|
||
/* The destroy callback attached to that surface. */
|
||
DestroyCallback *focus_destroy_callback;
|
||
|
||
/* The last surface seen. */
|
||
Surface *last_seen_surface;
|
||
|
||
/* The destroy callback attached to that surface. */
|
||
DestroyCallback *last_seen_surface_callback;
|
||
|
||
/* The surface on which the last pointer click was made. */
|
||
Surface *last_button_press_surface;
|
||
|
||
/* The destroy callback attached to that surface. */
|
||
DestroyCallback *last_button_press_surface_callback;
|
||
|
||
/* Unmap callback used for cancelling the grab. */
|
||
UnmapCallback *grab_unmap_callback;
|
||
|
||
/* The subcompositor that the mouse pointer is inside. */
|
||
Subcompositor *last_seen_subcompositor;
|
||
|
||
/* The window for that subcompositor. */
|
||
Window last_seen_subcompositor_window;
|
||
|
||
/* The destroy callback for the subcompositor. */
|
||
SubcompositorDestroyCallback *subcompositor_callback;
|
||
|
||
/* How many times the grab is held on this seat. */
|
||
int grab_held;
|
||
|
||
/* Modifier masks. */
|
||
unsigned int base, locked, latched;
|
||
|
||
/* Current base, locked and latched group. Normalized by the X
|
||
server. */
|
||
int base_group, locked_group, latched_group;
|
||
|
||
/* Current effective group. Also normalized. */
|
||
int effective_group;
|
||
|
||
/* Bitmask of whether or not a key was pressed. Length of the
|
||
mask is the max_keycode >> 3 + 1. */
|
||
unsigned char *key_pressed;
|
||
|
||
/* The current cursor attached to this seat. */
|
||
SeatCursor *cursor;
|
||
|
||
/* The icon surface. */
|
||
IconSurface *icon_surface;
|
||
|
||
/* Callbacks run after a resize completes. */
|
||
ResizeDoneCallback resize_callbacks;
|
||
|
||
/* The drag-and-drop grab window. This is a 1x1 InputOnly window
|
||
with an empty input region at 0, 0, used to differentiate between
|
||
events delivered to a surface during drag and drop, and events
|
||
delivered due to the grab. */
|
||
Window grab_window;
|
||
|
||
/* List of scroll valuators on this seat. */
|
||
ScrollValuator *valuators;
|
||
|
||
/* Serial of the last crossing event. */
|
||
unsigned long last_crossing_serial;
|
||
|
||
/* List of destroy listeners. */
|
||
DestroyListener destroy_listeners;
|
||
|
||
/* Surface currently being resized, if any. */
|
||
Surface *resize_surface;
|
||
|
||
/* Unmap callback for that surface. */
|
||
UnmapCallback *resize_surface_callback;
|
||
|
||
/* The last edge used to obtain a grab. */
|
||
WhatEdge last_grab_edge;
|
||
|
||
/* The last timestamp used to obtain a grab. */
|
||
Time last_grab_time;
|
||
|
||
/* When it was sent. */
|
||
Time its_press_time;
|
||
|
||
/* The time of the last key event sent. */
|
||
Time its_depress_time;
|
||
|
||
/* The name of the seat. */
|
||
char *name;
|
||
|
||
/* The grab surface. While it exists, events for different clients
|
||
will be reported relative to it. */
|
||
Surface *grab_surface;
|
||
|
||
/* The unmap callback. */
|
||
UnmapCallback *grab_surface_callback;
|
||
|
||
/* The data source for drag-and-drop. */
|
||
DataSource *data_source;
|
||
|
||
/* The destroy callback for the data source. */
|
||
void *data_source_destroy_callback;
|
||
|
||
/* The surface on which this drag operation started. */
|
||
Surface *drag_start_surface;
|
||
|
||
/* The UnmapCallback for that surface. */
|
||
UnmapCallback *drag_start_unmap_callback;
|
||
|
||
/* The last surface to be entered during drag-and-drop. */
|
||
Surface *drag_last_surface;
|
||
|
||
/* The destroy callback for that surface. */
|
||
DestroyCallback *drag_last_surface_destroy_callback;
|
||
|
||
/* The time the active grab was acquired. */
|
||
Time drag_grab_time;
|
||
|
||
/* The button of the last button event sent, and the root_x and
|
||
root_y of the last button or motion event. */
|
||
int last_button, its_root_x, its_root_y;
|
||
|
||
/* The serial of the last button event sent. */
|
||
uint32_t last_button_serial;
|
||
|
||
/* The serial of the last button press event sent. GTK 4 sends this
|
||
even when grabbing a popup in response to a button release
|
||
event. */
|
||
uint32_t last_button_press_serial;
|
||
|
||
/* The last serial used to obtain a grab. */
|
||
uint32_t last_grab_serial;
|
||
|
||
/* The serial of the last key event sent. */
|
||
uint32_t last_keyboard_serial;
|
||
|
||
/* The serial of the last keyboard enter event sent. */
|
||
uint32_t last_enter_serial;
|
||
|
||
/* Whether or not a resize is in progress. */
|
||
Bool resize_in_progress;
|
||
|
||
/* Where that resize started. */
|
||
int resize_start_root_x, resize_start_root_y;
|
||
|
||
/* Where the pointer was last seen. */
|
||
int resize_last_root_x, resize_last_root_y;
|
||
|
||
/* The dimensions of the surface when it was first seen. */
|
||
int resize_width, resize_height;
|
||
|
||
/* The axises. */
|
||
int resize_axis_flags;
|
||
|
||
/* The button for the resize. */
|
||
int resize_button;
|
||
|
||
/* The time used to obtain the resize grab. */
|
||
Time resize_time;
|
||
|
||
/* The attached data device, if any. */
|
||
DataDevice *data_device;
|
||
|
||
/* List of seat client information. */
|
||
SeatClientInfo client_info;
|
||
|
||
/* List of all attached keyboards. */
|
||
Keyboard keyboards;
|
||
|
||
/* The root_x and root_y of the last motion or crossing event. */
|
||
double last_motion_x, last_motion_y;
|
||
|
||
/* The x and y of the last surface movement. */
|
||
double last_surface_x, last_surface_y;
|
||
|
||
/* List of all modifier change callbacks attached to this seat. */
|
||
ModifierChangeCallback modifier_callbacks;
|
||
|
||
/* Array of keys currently held down. */
|
||
struct wl_array keys;
|
||
};
|
||
|
||
struct _DeviceInfo
|
||
{
|
||
/* Some flags associated with this device. */
|
||
int flags;
|
||
|
||
/* The libinput scroll pixel distance, if available. Else 15. */
|
||
int scroll_pixel_distance;
|
||
};
|
||
|
||
#define SetMask(ptr, event) \
|
||
(((unsigned char *) (ptr))[(event) >> 3] |= (1 << ((event) & 7)))
|
||
#define ClearMask(ptr, event) \
|
||
(((unsigned char *) (ptr))[(event) >> 3] &= ~(1 << ((event) & 7)))
|
||
#define MaskIsSet(ptr, event) \
|
||
(((unsigned char *) (ptr))[(event) >> 3] & (1 << ((event) & 7)))
|
||
#define MaskLen(event) \
|
||
(((event) >> 3) + 1)
|
||
|
||
/* Text input functions. */
|
||
static TextInputFuncs *input_funcs;
|
||
|
||
#define CursorFromRole(role) ((SeatCursor *) (role))
|
||
|
||
|
||
|
||
static Bool
|
||
QueryPointer (Seat *seat, Window relative_to, double *x, double *y)
|
||
{
|
||
XIButtonState buttons;
|
||
XIModifierState modifiers;
|
||
XIGroupState group;
|
||
double root_x, root_y, win_x, win_y;
|
||
Window root, child;
|
||
Bool same_screen;
|
||
|
||
buttons.mask = NULL;
|
||
same_screen = False;
|
||
|
||
/* First, initialize default values in case the pointer is on a
|
||
different screen. */
|
||
*x = 0;
|
||
*y = 0;
|
||
|
||
if (XIQueryPointer (compositor.display, seat->master_pointer,
|
||
relative_to, &root, &child, &root_x, &root_y,
|
||
&win_x, &win_y, &buttons, &modifiers,
|
||
&group))
|
||
{
|
||
*x = win_x;
|
||
*y = win_y;
|
||
same_screen = True;
|
||
}
|
||
|
||
/* buttons.mask must be freed manually, even if the pointer is on a
|
||
different screen. */
|
||
if (buttons.mask)
|
||
XFree (buttons.mask);
|
||
|
||
return same_screen;
|
||
}
|
||
|
||
static void
|
||
FinalizeSeatClientInfo (Seat *seat)
|
||
{
|
||
SeatClientInfo *info, *last;
|
||
|
||
info = seat->client_info.next;
|
||
|
||
while (info != &seat->client_info)
|
||
{
|
||
last = info;
|
||
info = info->next;
|
||
|
||
/* Mark this as invalid, so it won't be unchained later on. */
|
||
last->last = NULL;
|
||
last->next = NULL;
|
||
}
|
||
}
|
||
|
||
static SeatClientInfo *
|
||
GetSeatClientInfo (Seat *seat, struct wl_client *client)
|
||
{
|
||
SeatClientInfo *info;
|
||
|
||
info = seat->client_info.next;
|
||
|
||
while (info != &seat->client_info)
|
||
{
|
||
if (info->client == client)
|
||
return info;
|
||
|
||
info = info->next;
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
static SeatClientInfo *
|
||
CreateSeatClientInfo (Seat *seat, struct wl_client *client)
|
||
{
|
||
SeatClientInfo *info;
|
||
|
||
/* See if client has already created something on the seat. */
|
||
info = GetSeatClientInfo (seat, client);
|
||
|
||
/* Otherwise, create it ourselves. */
|
||
if (!info)
|
||
{
|
||
info = XLCalloc (1, sizeof *info);
|
||
info->next = seat->client_info.next;
|
||
info->last = &seat->client_info;
|
||
seat->client_info.next->last = info;
|
||
seat->client_info.next = info;
|
||
|
||
info->client = client;
|
||
info->pointers.next = &info->pointers;
|
||
info->pointers.last = &info->pointers;
|
||
info->keyboards.next = &info->keyboards;
|
||
info->keyboards.last = &info->keyboards;
|
||
info->relative_pointers.next = &info->relative_pointers;
|
||
info->relative_pointers.last = &info->relative_pointers;
|
||
info->swipe_gestures.next = &info->swipe_gestures;
|
||
info->swipe_gestures.last = &info->swipe_gestures;
|
||
info->pinch_gestures.next = &info->pinch_gestures;
|
||
info->pinch_gestures.last = &info->pinch_gestures;
|
||
}
|
||
|
||
/* Increase the reference count of info. */
|
||
info->refcount++;
|
||
|
||
/* Return info. */
|
||
return info;
|
||
}
|
||
|
||
static void
|
||
ReleaseSeatClientInfo (SeatClientInfo *info)
|
||
{
|
||
if (--info->refcount)
|
||
return;
|
||
|
||
/* Assert that there are no more keyboards or pointers attached. */
|
||
XLAssert (info->keyboards.next == &info->keyboards);
|
||
XLAssert (info->pointers.next == &info->pointers);
|
||
XLAssert (info->relative_pointers.next == &info->relative_pointers);
|
||
|
||
/* Unlink the client info structure if it is still linked. */
|
||
if (info->next)
|
||
{
|
||
info->next->last = info->last;
|
||
info->last->next = info->next;
|
||
}
|
||
|
||
/* Free the client info. */
|
||
XLFree (info);
|
||
}
|
||
|
||
static void
|
||
RetainSeat (Seat *seat)
|
||
{
|
||
seat->refcount++;
|
||
}
|
||
|
||
static CursorRing *
|
||
MakeCursorRing (int width, int height)
|
||
{
|
||
CursorRing *ring;
|
||
|
||
ring = XLCalloc (1, sizeof *ring);
|
||
ring->width = width;
|
||
ring->height = height;
|
||
ring->used = -1;
|
||
|
||
return ring;
|
||
}
|
||
|
||
static void
|
||
MaybeCreateCursor (CursorRing *ring, int index)
|
||
{
|
||
XLAssert (index < CursorRingElements);
|
||
|
||
/* If the cursor has already been created, return. */
|
||
if (ring->pixmaps[index])
|
||
return;
|
||
|
||
ring->pixmaps[index]
|
||
= XCreatePixmap (compositor.display,
|
||
DefaultRootWindow (compositor.display),
|
||
ring->width, ring->height,
|
||
compositor.n_planes);
|
||
ring->targets[index]
|
||
= RenderTargetFromPixmap (ring->pixmaps[index]);
|
||
|
||
/* For simplicity reasons we do not handle idle notifications
|
||
asynchronously. */
|
||
RenderSetNeedWaitForIdle (ring->targets[index]);
|
||
}
|
||
|
||
static int
|
||
GetUnusedCursor (CursorRing *ring)
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; i < CursorRingElements; ++i)
|
||
{
|
||
if (ring->used != i)
|
||
{
|
||
/* Create the cursor contents if they have not yet been
|
||
created. */
|
||
MaybeCreateCursor (ring, i);
|
||
|
||
return i;
|
||
}
|
||
}
|
||
|
||
return CursorRingBusy;
|
||
}
|
||
|
||
static void
|
||
FreeCursorRing (CursorRing *ring)
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; i < CursorRingElements; ++i)
|
||
{
|
||
if (!ring->pixmaps[i])
|
||
/* This element wasn't created. */
|
||
continue;
|
||
|
||
/* Free the target and pixmap. */
|
||
RenderDestroyRenderTarget (ring->targets[i]);
|
||
XFreePixmap (compositor.display, ring->pixmaps[i]);
|
||
}
|
||
|
||
/* Free the ring itself. */
|
||
XLFree (ring);
|
||
}
|
||
|
||
static void
|
||
ResizeCursorRing (CursorRing *ring, int width, int height)
|
||
{
|
||
int i;
|
||
|
||
if (width == ring->width && height == ring->height)
|
||
return;
|
||
|
||
/* Destroy the pixmaps currently in the cursor ring. */
|
||
|
||
for (i = 0; i < CursorRingElements; ++i)
|
||
{
|
||
if (!ring->pixmaps[i])
|
||
/* This element wasn't created. */
|
||
continue;
|
||
|
||
/* Free the target and pixmap. */
|
||
RenderDestroyRenderTarget (ring->targets[i]);
|
||
XFreePixmap (compositor.display, ring->pixmaps[i]);
|
||
|
||
/* Mark this element as free. */
|
||
ring->pixmaps[i] = None;
|
||
}
|
||
|
||
/* Reinitialize the cursor ring with new data. */
|
||
ring->width = width;
|
||
ring->height = height;
|
||
ring->used = -1;
|
||
}
|
||
|
||
static void
|
||
UpdateCursorOutput (SeatCursor *cursor, int root_x, int root_y)
|
||
{
|
||
int hotspot_x, hotspot_y;
|
||
|
||
if (!cursor->role.surface)
|
||
/* The surface has been destroyed, so there is no point in
|
||
continuing. */
|
||
return;
|
||
|
||
/* Scale the hotspot coordinates up by the scale factor specified in
|
||
the surface. */
|
||
hotspot_x = cursor->hotspot_x * cursor->role.surface->factor;
|
||
hotspot_y = cursor->hotspot_y * cursor->role.surface->factor;
|
||
|
||
/* We use a rectangle 1 pixel wide and tall, originating from the
|
||
hotspot of the pointer. */
|
||
XLUpdateSurfaceOutputs (cursor->role.surface, root_x + hotspot_x,
|
||
root_y + hotspot_y, 1, 1);
|
||
}
|
||
|
||
static Window
|
||
CursorWindow (SeatCursor *cursor)
|
||
{
|
||
/* When dragging, use the surface on which the active grab is
|
||
set. */
|
||
if (cursor->seat->flags & IsDragging)
|
||
return cursor->seat->grab_window;
|
||
|
||
/* The cursor should be cleared along with
|
||
seat->last_seen_surface. */
|
||
XLAssert (cursor->seat->last_seen_surface != NULL);
|
||
|
||
return XLWindowFromSurface (cursor->seat->last_seen_surface);
|
||
}
|
||
|
||
static void
|
||
HandleCursorFrame (void *data, struct timespec time)
|
||
{
|
||
SeatCursor *cursor;
|
||
|
||
cursor = data;
|
||
|
||
if (cursor->role.surface)
|
||
XLSurfaceRunFrameCallbacks (cursor->role.surface, time);
|
||
}
|
||
|
||
static void
|
||
StartCursorClock (SeatCursor *cursor)
|
||
{
|
||
if (cursor->holding_cursor_clock)
|
||
return;
|
||
|
||
cursor->cursor_frame_key
|
||
= XLAddCursorClockCallback (HandleCursorFrame,
|
||
cursor);
|
||
cursor->holding_cursor_clock = True;
|
||
}
|
||
|
||
static void
|
||
EndCursorClock (SeatCursor *cursor)
|
||
{
|
||
if (!cursor->holding_cursor_clock)
|
||
return;
|
||
|
||
XLStopCursorClockCallback (cursor->cursor_frame_key);
|
||
cursor->holding_cursor_clock = False;
|
||
}
|
||
|
||
static void
|
||
FreeCursor (SeatCursor *cursor)
|
||
{
|
||
Window window;
|
||
|
||
if (cursor->role.surface)
|
||
XLSurfaceReleaseRole (cursor->role.surface,
|
||
&cursor->role);
|
||
|
||
/* Now any attached views should have been released, so free the
|
||
subcompositor. */
|
||
SubcompositorFree (cursor->subcompositor);
|
||
|
||
cursor->seat->cursor = NULL;
|
||
|
||
window = CursorWindow (cursor);
|
||
|
||
if (cursor->cursor != None)
|
||
XFreeCursor (compositor.display, cursor->cursor);
|
||
|
||
if (!(cursor->seat->flags & IsInert) && window)
|
||
XIDefineCursor (compositor.display,
|
||
cursor->seat->master_pointer,
|
||
window, InitDefaultCursor ());
|
||
|
||
/* And release the cursor ring. */
|
||
if (cursor->cursor_ring)
|
||
FreeCursorRing (cursor->cursor_ring);
|
||
|
||
/* Maybe release the cursor clock if it was active for this
|
||
cursor. */
|
||
EndCursorClock (cursor);
|
||
XLFree (cursor);
|
||
}
|
||
|
||
static void
|
||
FreeValuators (Seat *seat)
|
||
{
|
||
ScrollValuator *last, *tem;
|
||
|
||
tem = seat->valuators;
|
||
|
||
while (tem)
|
||
{
|
||
last = tem;
|
||
tem = tem->next;
|
||
|
||
XLFree (last);
|
||
}
|
||
|
||
seat->valuators = NULL;
|
||
}
|
||
|
||
static void
|
||
FreeDestroyListeners (Seat *seat)
|
||
{
|
||
DestroyListener *listener, *last;
|
||
|
||
listener = seat->destroy_listeners.next;
|
||
|
||
while (listener != &seat->destroy_listeners)
|
||
{
|
||
last = listener;
|
||
listener = listener->next;
|
||
|
||
XLFree (last);
|
||
}
|
||
}
|
||
|
||
static void
|
||
FreeModifierCallbacks (Seat *seat)
|
||
{
|
||
ModifierChangeCallback *callback, *last;
|
||
|
||
callback = seat->modifier_callbacks.next;
|
||
|
||
while (callback != &seat->modifier_callbacks)
|
||
{
|
||
last = callback;
|
||
callback = callback->next;
|
||
|
||
XLFree (last);
|
||
}
|
||
}
|
||
|
||
static void
|
||
ReleaseSeat (Seat *seat)
|
||
{
|
||
if (--seat->refcount)
|
||
return;
|
||
|
||
if (seat->icon_surface)
|
||
XLReleaseIconSurface (seat->icon_surface);
|
||
|
||
if (seat->focus_destroy_callback)
|
||
XLSurfaceCancelRunOnFree (seat->focus_destroy_callback);
|
||
|
||
if (seat->last_seen_surface_callback)
|
||
XLSurfaceCancelRunOnFree (seat->last_seen_surface_callback);
|
||
|
||
if (seat->last_button_press_surface_callback)
|
||
XLSurfaceCancelRunOnFree (seat->last_button_press_surface_callback);
|
||
|
||
if (seat->drag_last_surface_destroy_callback)
|
||
XLSurfaceCancelRunOnFree (seat->drag_last_surface_destroy_callback);
|
||
|
||
if (seat->grab_surface_callback)
|
||
XLSurfaceCancelUnmapCallback (seat->grab_surface_callback);
|
||
|
||
if (seat->grab_unmap_callback)
|
||
XLSurfaceCancelUnmapCallback (seat->grab_unmap_callback);
|
||
|
||
if (seat->resize_surface_callback)
|
||
XLSurfaceCancelUnmapCallback (seat->resize_surface_callback);
|
||
|
||
if (seat->drag_start_unmap_callback)
|
||
XLSurfaceCancelUnmapCallback (seat->drag_start_unmap_callback);
|
||
|
||
if (seat->data_source_destroy_callback)
|
||
XLDataSourceCancelDestroyCallback (seat->data_source_destroy_callback);
|
||
|
||
if (seat->subcompositor_callback)
|
||
SubcompositorRemoveDestroyCallback (seat->subcompositor_callback);
|
||
|
||
if (seat->grab_window != None)
|
||
XDestroyWindow (compositor.display, seat->grab_window);
|
||
|
||
wl_array_release (&seat->keys);
|
||
|
||
if (seat->cursor)
|
||
FreeCursor (seat->cursor);
|
||
|
||
if (seat->data_device)
|
||
{
|
||
XLDataDeviceClearSeat (seat->data_device);
|
||
XLReleaseDataDevice (seat->data_device);
|
||
}
|
||
|
||
FinalizeSeatClientInfo (seat);
|
||
FreeValuators (seat);
|
||
FreeDestroyListeners (seat);
|
||
FreeModifierCallbacks (seat);
|
||
|
||
XLFree (seat->name);
|
||
XLFree (seat->key_pressed);
|
||
XLFree (seat);
|
||
}
|
||
|
||
static void
|
||
ComputeHotspot (SeatCursor *cursor, int min_x, int min_y,
|
||
int *x, int *y)
|
||
{
|
||
int dx, dy;
|
||
int hotspot_x, hotspot_y;
|
||
|
||
if (!cursor->role.surface)
|
||
{
|
||
/* Can this really happen? */
|
||
*x = min_x + cursor->hotspot_x;
|
||
*y = min_y + cursor->hotspot_y;
|
||
return;
|
||
}
|
||
|
||
/* Scale the hotspot coordinates up by the scale. */
|
||
hotspot_x = cursor->hotspot_x * cursor->role.surface->factor;
|
||
hotspot_y = cursor->hotspot_y * cursor->role.surface->factor;
|
||
|
||
/* Apply the surface offsets to the hotspot as well. */
|
||
dx = (cursor->role.surface->current_state.x
|
||
* cursor->role.surface->factor);
|
||
dy = (cursor->role.surface->current_state.y
|
||
* cursor->role.surface->factor);
|
||
|
||
*x = min_x + hotspot_x - dx;
|
||
*y = min_y + hotspot_y - dy;
|
||
}
|
||
|
||
static void
|
||
ApplyCursor (SeatCursor *cursor, RenderTarget target,
|
||
int min_x, int min_y)
|
||
{
|
||
Window window;
|
||
int x, y;
|
||
Picture picture;
|
||
|
||
if (cursor->cursor)
|
||
XFreeCursor (compositor.display, cursor->cursor);
|
||
|
||
ComputeHotspot (cursor, min_x, min_y, &x, &y);
|
||
|
||
picture = RenderPictureFromTarget (target);
|
||
cursor->cursor = XRenderCreateCursor (compositor.display,
|
||
picture, MAX (0, x),
|
||
MAX (0, y));
|
||
RenderFreePictureFromTarget (picture);
|
||
|
||
window = CursorWindow (cursor);
|
||
|
||
if (!(cursor->seat->flags & IsInert) && window != None)
|
||
XIDefineCursor (compositor.display,
|
||
cursor->seat->master_pointer,
|
||
window, cursor->cursor);
|
||
}
|
||
|
||
static void
|
||
UpdateCursorFromSubcompositor (SeatCursor *cursor)
|
||
{
|
||
RenderTarget target;
|
||
int min_x, min_y, max_x, max_y, width, height, x, y;
|
||
Bool need_clear;
|
||
int index;
|
||
|
||
/* First, compute the bounds of the subcompositor. */
|
||
SubcompositorBounds (cursor->subcompositor,
|
||
&min_x, &min_y, &max_x, &max_y);
|
||
|
||
/* Then, its width and height. */
|
||
width = max_x - min_x + 1;
|
||
height = max_y - min_y + 1;
|
||
|
||
/* If the cursor hotspot extends outside width and height, extend
|
||
the picture. */
|
||
ComputeHotspot (cursor, min_x, min_y, &x, &y);
|
||
|
||
if (x < 0 || y < 0 || x >= width || y >= height)
|
||
{
|
||
if (x >= width)
|
||
width = x;
|
||
if (y >= height)
|
||
height = y;
|
||
|
||
if (x < 0)
|
||
width += -x;
|
||
if (y < 0)
|
||
height += -y;
|
||
|
||
need_clear = True;
|
||
}
|
||
else
|
||
need_clear = False;
|
||
|
||
if (cursor->cursor_ring)
|
||
/* If the width or height of the cursor ring changed, resize its
|
||
contents. */
|
||
ResizeCursorRing (cursor->cursor_ring, width, height);
|
||
else
|
||
/* Otherwise, there is not yet a cursor ring. Create one. */
|
||
cursor->cursor_ring = MakeCursorRing (width, height);
|
||
|
||
/* Get an unused cursor from the cursor ring. */
|
||
index = GetUnusedCursor (cursor->cursor_ring);
|
||
XLAssert (index != CursorRingBusy);
|
||
|
||
/* Get the target and pixmap. */
|
||
target = cursor->cursor_ring->targets[index];
|
||
|
||
/* If the bounds extend beyond the subcompositor, clear the
|
||
picture. */
|
||
if (need_clear)
|
||
RenderClearRectangle (target, 0, 0, width, height);
|
||
|
||
/* Garbage the subcompositor, since cursor contents are not
|
||
preserved. */
|
||
SubcompositorGarbage (cursor->subcompositor);
|
||
|
||
/* Set the right transform if the hotspot is negative. */
|
||
SubcompositorSetProjectiveTransform (cursor->subcompositor,
|
||
MAX (0, -x), MAX (0, -x));
|
||
|
||
SubcompositorSetTarget (cursor->subcompositor, &target);
|
||
SubcompositorUpdate (cursor->subcompositor);
|
||
SubcompositorSetTarget (cursor->subcompositor, NULL);
|
||
|
||
/* Apply the new cursor. */
|
||
ApplyCursor (cursor, target, min_x, min_y);
|
||
|
||
/* Set it as the cursor being used. */
|
||
cursor->cursor_ring->used = index;
|
||
}
|
||
|
||
static void
|
||
UpdateCursor (SeatCursor *cursor, int x, int y)
|
||
{
|
||
cursor->hotspot_x = x;
|
||
cursor->hotspot_y = y;
|
||
|
||
UpdateCursorFromSubcompositor (cursor);
|
||
}
|
||
|
||
static void
|
||
ApplyEmptyCursor (SeatCursor *cursor)
|
||
{
|
||
Window window;
|
||
|
||
if (cursor->cursor)
|
||
XFreeCursor (compositor.display, cursor->cursor);
|
||
|
||
cursor->cursor = None;
|
||
window = CursorWindow (cursor);
|
||
|
||
if (window != None)
|
||
XIDefineCursor (compositor.display,
|
||
cursor->seat->master_pointer,
|
||
window, InitDefaultCursor ());
|
||
|
||
if (cursor->cursor_ring)
|
||
/* This means no cursor in the ring is currently being used. */
|
||
cursor->cursor_ring->used = -1;
|
||
}
|
||
|
||
static void
|
||
Commit (Surface *surface, Role *role)
|
||
{
|
||
SeatCursor *cursor;
|
||
|
||
cursor = CursorFromRole (role);
|
||
|
||
if (SubcompositorIsEmpty (cursor->subcompositor))
|
||
{
|
||
ApplyEmptyCursor (cursor);
|
||
return;
|
||
}
|
||
|
||
UpdateCursorFromSubcompositor (cursor);
|
||
|
||
/* If the surface now has frame callbacks, start the cursor frame
|
||
clock. */
|
||
|
||
if (surface->current_state.frame_callbacks.next
|
||
!= &surface->current_state.frame_callbacks)
|
||
StartCursorClock (cursor);
|
||
}
|
||
|
||
static void
|
||
Teardown (Surface *surface, Role *role)
|
||
{
|
||
role->surface = NULL;
|
||
|
||
ViewUnparent (surface->view);
|
||
ViewUnparent (surface->under);
|
||
|
||
ViewSetSubcompositor (surface->view, NULL);
|
||
ViewSetSubcompositor (surface->under, NULL);
|
||
}
|
||
|
||
static Bool
|
||
Setup (Surface *surface, Role *role)
|
||
{
|
||
SeatCursor *cursor;
|
||
|
||
cursor = CursorFromRole (role);
|
||
role->surface = surface;
|
||
|
||
/* First, attach the subcompositor. */
|
||
ViewSetSubcompositor (surface->under, cursor->subcompositor);
|
||
ViewSetSubcompositor (surface->view, cursor->subcompositor);
|
||
|
||
/* Then, insert the view. */
|
||
SubcompositorInsert (cursor->subcompositor, surface->under);
|
||
SubcompositorInsert (cursor->subcompositor, surface->view);
|
||
|
||
return True;
|
||
}
|
||
|
||
static void
|
||
ReleaseBuffer (Surface *surface, Role *role, ExtBuffer *buffer)
|
||
{
|
||
SeatCursor *cursor;
|
||
int i;
|
||
|
||
cursor = CursorFromRole (role);
|
||
|
||
/* Cursors are generally committed only once, so syncing here is
|
||
OK in terms of efficiency. */
|
||
for (i = 0; i < CursorRingElements; ++i)
|
||
{
|
||
if (cursor->cursor_ring->pixmaps[i])
|
||
RenderWaitForIdle (XLRenderBufferFromBuffer (buffer),
|
||
cursor->cursor_ring->targets[i]);
|
||
}
|
||
|
||
XLReleaseBuffer (buffer);
|
||
}
|
||
|
||
static void
|
||
SubsurfaceUpdate (Surface *surface, Role *role)
|
||
{
|
||
SeatCursor *cursor;
|
||
|
||
cursor = CursorFromRole (role);
|
||
|
||
/* A desync subsurface's contents changed. Update the cursor
|
||
again. */
|
||
UpdateCursorFromSubcompositor (cursor);
|
||
}
|
||
|
||
static void
|
||
MakeCurrentCursor (Seat *seat, Surface *surface, int x, int y)
|
||
{
|
||
SeatCursor *role;
|
||
Window window;
|
||
|
||
window = XLWindowFromSurface (seat->last_seen_surface);
|
||
|
||
if (window == None || (seat->flags & IsInert))
|
||
return;
|
||
|
||
role = XLCalloc (1, sizeof *role);
|
||
XIDefineCursor (compositor.display,
|
||
seat->master_pointer,
|
||
window,
|
||
InitDefaultCursor ());
|
||
|
||
role->hotspot_x = x;
|
||
role->hotspot_y = y;
|
||
role->seat = seat;
|
||
|
||
ApplyEmptyCursor (role);
|
||
|
||
/* Set up role callbacks. */
|
||
|
||
role->role.funcs.commit = Commit;
|
||
role->role.funcs.teardown = Teardown;
|
||
role->role.funcs.setup = Setup;
|
||
role->role.funcs.release_buffer = ReleaseBuffer;
|
||
role->role.funcs.subsurface_update = SubsurfaceUpdate;
|
||
|
||
/* Set up the subcompositor. */
|
||
|
||
role->subcompositor = MakeSubcompositor ();
|
||
|
||
if (!XLSurfaceAttachRole (surface, &role->role))
|
||
abort ();
|
||
|
||
seat->cursor = role;
|
||
|
||
/* Tell the cursor surface what output(s) it is in. */
|
||
|
||
UpdateCursorOutput (role, seat->last_motion_x,
|
||
seat->last_motion_y);
|
||
|
||
/* If something was committed, update the cursor now. */
|
||
|
||
if (!SubcompositorIsEmpty (role->subcompositor))
|
||
UpdateCursorFromSubcompositor (role);
|
||
}
|
||
|
||
static void
|
||
SetCursor (struct wl_client *client, struct wl_resource *resource,
|
||
uint32_t serial, struct wl_resource *surface_resource,
|
||
int32_t hotspot_x, int32_t hotspot_y)
|
||
{
|
||
Surface *surface, *seen;
|
||
Pointer *pointer;
|
||
Seat *seat;
|
||
|
||
pointer = wl_resource_get_user_data (resource);
|
||
seat = pointer->seat;
|
||
seen = seat->last_seen_surface;
|
||
|
||
if (serial < pointer->info->last_enter_serial)
|
||
return;
|
||
|
||
if (!surface_resource)
|
||
{
|
||
if (!seen || (wl_resource_get_client (seen->resource)
|
||
!= client))
|
||
return;
|
||
|
||
if (seat->cursor)
|
||
FreeCursor (seat->cursor);
|
||
|
||
return;
|
||
}
|
||
|
||
surface = wl_resource_get_user_data (surface_resource);
|
||
|
||
/* Do nothing at all if the last seen surface isn't owned by client
|
||
and we are not updating the current pointer surface. */
|
||
|
||
if (!seat->cursor
|
||
|| surface->role != &seat->cursor->role)
|
||
{
|
||
if (!seen || (wl_resource_get_client (seen->resource)
|
||
!= client))
|
||
return;
|
||
}
|
||
|
||
/* If surface already has another role, raise an error. */
|
||
|
||
if (surface->role_type != AnythingType
|
||
&& surface->role_type != CursorType)
|
||
{
|
||
wl_resource_post_error (resource, WL_POINTER_ERROR_ROLE,
|
||
"surface already has or had a different role");
|
||
return;
|
||
}
|
||
|
||
if (surface->role && !seat->cursor
|
||
&& surface->role != &seat->cursor->role)
|
||
{
|
||
wl_resource_post_error (resource, WL_POINTER_ERROR_ROLE,
|
||
"surface already has a cursor role"
|
||
" on another seat");
|
||
return;
|
||
}
|
||
|
||
if (surface->role)
|
||
{
|
||
UpdateCursor (CursorFromRole (surface->role),
|
||
hotspot_x, hotspot_y);
|
||
return;
|
||
}
|
||
|
||
/* Free any cursor that already exists. */
|
||
if (seat->cursor)
|
||
FreeCursor (seat->cursor);
|
||
|
||
MakeCurrentCursor (seat, surface, hotspot_x, hotspot_y);
|
||
}
|
||
|
||
static void
|
||
ReleasePointer (struct wl_client *client, struct wl_resource *resource)
|
||
{
|
||
wl_resource_destroy (resource);
|
||
}
|
||
|
||
static void
|
||
ReleaseKeyboard (struct wl_client *client, struct wl_resource *resource)
|
||
{
|
||
wl_resource_destroy (resource);
|
||
}
|
||
|
||
static const struct wl_pointer_interface wl_pointer_impl =
|
||
{
|
||
.set_cursor = SetCursor,
|
||
.release = ReleasePointer,
|
||
};
|
||
|
||
static const struct wl_keyboard_interface wl_keyboard_impl =
|
||
{
|
||
.release = ReleaseKeyboard,
|
||
};
|
||
|
||
static void
|
||
HandlePointerResourceDestroy (struct wl_resource *resource)
|
||
{
|
||
Pointer *pointer;
|
||
|
||
pointer = wl_resource_get_user_data (resource);
|
||
pointer->last->next = pointer->next;
|
||
pointer->next->last = pointer->last;
|
||
|
||
ReleaseSeatClientInfo (pointer->info);
|
||
ReleaseSeat (pointer->seat);
|
||
|
||
XLFree (pointer);
|
||
}
|
||
|
||
static void
|
||
HandleKeyboardResourceDestroy (struct wl_resource *resource)
|
||
{
|
||
Keyboard *keyboard;
|
||
|
||
keyboard = wl_resource_get_user_data (resource);
|
||
keyboard->last->next = keyboard->next;
|
||
keyboard->next->last = keyboard->last;
|
||
keyboard->last1->next1 = keyboard->next1;
|
||
keyboard->next1->last1 = keyboard->last1;
|
||
|
||
ReleaseSeatClientInfo (keyboard->info);
|
||
ReleaseSeat (keyboard->seat);
|
||
|
||
XLFree (keyboard);
|
||
}
|
||
|
||
static void
|
||
GetPointer (struct wl_client *client, struct wl_resource *resource,
|
||
uint32_t id)
|
||
{
|
||
Seat *seat;
|
||
SeatClientInfo *info;
|
||
Pointer *pointer;
|
||
struct wl_resource *pointer_resource;
|
||
|
||
pointer_resource
|
||
= wl_resource_create (client, &wl_pointer_interface,
|
||
wl_resource_get_version (resource),
|
||
id);
|
||
|
||
if (!pointer_resource)
|
||
{
|
||
wl_resource_post_no_memory (resource);
|
||
return;
|
||
}
|
||
|
||
pointer = XLSafeMalloc (sizeof *pointer);
|
||
|
||
if (!pointer)
|
||
{
|
||
wl_resource_post_no_memory (resource);
|
||
wl_resource_destroy (pointer_resource);
|
||
|
||
return;
|
||
}
|
||
|
||
seat = wl_resource_get_user_data (resource);
|
||
RetainSeat (seat);
|
||
|
||
memset (pointer, 0, sizeof *pointer);
|
||
|
||
info = CreateSeatClientInfo (seat, client);
|
||
pointer->resource = pointer_resource;
|
||
pointer->seat = seat;
|
||
pointer->info = info;
|
||
pointer->next = info->pointers.next;
|
||
pointer->last = &info->pointers;
|
||
|
||
/* This flag means the pointer object has just been created, and
|
||
button presses should send a corresponding entry event. */
|
||
pointer->state |= StateIsRaw;
|
||
|
||
info->pointers.next->last = pointer;
|
||
info->pointers.next = pointer;
|
||
|
||
wl_resource_set_implementation (pointer_resource, &wl_pointer_impl,
|
||
pointer, HandlePointerResourceDestroy);
|
||
}
|
||
|
||
static void
|
||
SendRepeatKeys (struct wl_resource *resource)
|
||
{
|
||
if (wl_resource_get_version (resource) < 4)
|
||
return;
|
||
|
||
wl_keyboard_send_repeat_info (resource,
|
||
1000 / xkb_desc->ctrls->repeat_interval,
|
||
xkb_desc->ctrls->repeat_delay);
|
||
}
|
||
|
||
static void
|
||
UpdateSingleKeyboard (Keyboard *keyboard)
|
||
{
|
||
struct stat statb;
|
||
|
||
if (fstat (keymap_fd, &statb) < 0)
|
||
{
|
||
perror ("fstat");
|
||
exit (0);
|
||
}
|
||
|
||
wl_keyboard_send_keymap (keyboard->resource,
|
||
WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1,
|
||
keymap_fd, statb.st_size);
|
||
|
||
SendRepeatKeys (keyboard->resource);
|
||
}
|
||
|
||
static void
|
||
GetKeyboard (struct wl_client *client, struct wl_resource *resource,
|
||
uint32_t id)
|
||
{
|
||
SeatClientInfo *info;
|
||
Seat *seat;
|
||
Keyboard *keyboard;
|
||
struct wl_resource *keyboard_resource;
|
||
|
||
keyboard_resource
|
||
= wl_resource_create (client, &wl_keyboard_interface,
|
||
wl_resource_get_version (resource),
|
||
id);
|
||
|
||
if (!keyboard_resource)
|
||
{
|
||
wl_resource_post_no_memory (resource);
|
||
return;
|
||
}
|
||
|
||
keyboard = XLSafeMalloc (sizeof *keyboard);
|
||
|
||
if (!keyboard)
|
||
{
|
||
wl_resource_post_no_memory (resource);
|
||
wl_resource_destroy (keyboard_resource);
|
||
|
||
return;
|
||
}
|
||
|
||
seat = wl_resource_get_user_data (resource);
|
||
RetainSeat (seat);
|
||
|
||
memset (keyboard, 0, sizeof *keyboard);
|
||
|
||
info = CreateSeatClientInfo (seat, client);
|
||
keyboard->resource = keyboard_resource;
|
||
|
||
/* First, link the keyboard onto the seat client info. */
|
||
keyboard->info = info;
|
||
keyboard->next = info->keyboards.next;
|
||
keyboard->last = &info->keyboards;
|
||
info->keyboards.next->last = keyboard;
|
||
info->keyboards.next = keyboard;
|
||
|
||
/* Then, the seat. */
|
||
keyboard->seat = seat;
|
||
keyboard->next1 = seat->keyboards.next1;
|
||
keyboard->last1 = &seat->keyboards;
|
||
seat->keyboards.next1->last1 = keyboard;
|
||
seat->keyboards.next1 = keyboard;
|
||
|
||
wl_resource_set_implementation (keyboard_resource, &wl_keyboard_impl,
|
||
keyboard, HandleKeyboardResourceDestroy);
|
||
|
||
UpdateSingleKeyboard (keyboard);
|
||
|
||
/* Update the keyboard's focus surface too. */
|
||
|
||
if (seat->focus_surface
|
||
&& (wl_resource_get_client (seat->focus_surface->resource)
|
||
== client))
|
||
wl_keyboard_send_enter (keyboard_resource,
|
||
wl_display_next_serial (compositor.wl_display),
|
||
seat->focus_surface->resource, &seat->keys);
|
||
}
|
||
|
||
static void
|
||
GetTouch (struct wl_client *client, struct wl_resource *resource,
|
||
uint32_t id)
|
||
{
|
||
wl_resource_post_error (resource, WL_SEAT_ERROR_MISSING_CAPABILITY,
|
||
"touch support not yet implemented");
|
||
}
|
||
|
||
static void
|
||
Release (struct wl_client *client, struct wl_resource *resource)
|
||
{
|
||
wl_resource_destroy (resource);
|
||
}
|
||
|
||
static const struct wl_seat_interface wl_seat_impl =
|
||
{
|
||
.get_pointer = GetPointer,
|
||
.get_keyboard = GetKeyboard,
|
||
.get_touch = GetTouch,
|
||
.release = Release,
|
||
};
|
||
|
||
static void
|
||
HandleResourceDestroy (struct wl_resource *resource)
|
||
{
|
||
Seat *seat;
|
||
|
||
seat = wl_resource_get_user_data (resource);
|
||
ReleaseSeat (seat);
|
||
}
|
||
|
||
static void
|
||
HandleBind1 (struct wl_client *client, Seat *seat, uint32_t version,
|
||
uint32_t id)
|
||
{
|
||
struct wl_resource *resource;
|
||
|
||
resource = wl_resource_create (client, &wl_seat_interface,
|
||
version, id);
|
||
|
||
if (!resource)
|
||
{
|
||
wl_client_post_no_memory (client);
|
||
return;
|
||
}
|
||
|
||
wl_resource_set_implementation (resource, &wl_seat_impl, seat,
|
||
HandleResourceDestroy);
|
||
|
||
wl_seat_send_capabilities (resource, (WL_SEAT_CAPABILITY_POINTER
|
||
| WL_SEAT_CAPABILITY_KEYBOARD));
|
||
|
||
if (wl_resource_get_version (resource) > 2)
|
||
wl_seat_send_name (resource, seat->name);
|
||
|
||
RetainSeat (seat);
|
||
}
|
||
|
||
static void
|
||
HandleBind (struct wl_client *client, void *data, uint32_t version,
|
||
uint32_t id)
|
||
{
|
||
HandleBind1 (client, data, version, id);
|
||
}
|
||
|
||
static void
|
||
AddValuator (Seat *seat, XIScrollClassInfo *info)
|
||
{
|
||
ScrollValuator *valuator;
|
||
|
||
valuator = XLCalloc (1, sizeof *valuator);
|
||
valuator->next = seat->valuators;
|
||
valuator->increment = info->increment;
|
||
valuator->number = info->number;
|
||
|
||
if (info->scroll_type == XIScrollTypeHorizontal)
|
||
valuator->direction = Horizontal;
|
||
else
|
||
valuator->direction = Vertical;
|
||
|
||
seat->valuators = valuator;
|
||
}
|
||
|
||
static void
|
||
UpdateValuators (Seat *seat, XIDeviceInfo *device)
|
||
{
|
||
int i;
|
||
|
||
/* First, free any existing valuators. */
|
||
FreeValuators (seat);
|
||
|
||
/* Next, add each valuator. */
|
||
for (i = 0; i < device->num_classes; ++i)
|
||
{
|
||
if (device->classes[i]->type == XIScrollClass)
|
||
AddValuator (seat, (XIScrollClassInfo *) device->classes[i]);
|
||
}
|
||
}
|
||
|
||
static void
|
||
InitSeatCommon (Seat *seat)
|
||
{
|
||
seat->client_info.next = &seat->client_info;
|
||
seat->client_info.last = &seat->client_info;
|
||
|
||
seat->keyboards.next1 = &seat->keyboards;
|
||
seat->keyboards.last1 = &seat->keyboards;
|
||
|
||
seat->resize_callbacks.next = &seat->resize_callbacks;
|
||
seat->resize_callbacks.last = &seat->resize_callbacks;
|
||
|
||
seat->destroy_listeners.next = &seat->destroy_listeners;
|
||
seat->destroy_listeners.last = &seat->destroy_listeners;
|
||
|
||
seat->modifier_callbacks.next = &seat->modifier_callbacks;
|
||
seat->modifier_callbacks.last = &seat->modifier_callbacks;
|
||
|
||
wl_array_init (&seat->keys);
|
||
}
|
||
|
||
static void
|
||
MakeSeatForDevicePair (int master_keyboard, int master_pointer,
|
||
XIDeviceInfo *pointer_info)
|
||
{
|
||
Seat *seat;
|
||
XkbStateRec state;
|
||
unsigned long mask;
|
||
|
||
seat = XLCalloc (1, sizeof *seat);
|
||
seat->master_keyboard = master_keyboard;
|
||
seat->master_pointer = master_pointer;
|
||
seat->name = XLStrdup (pointer_info->name);
|
||
seat->global = wl_global_create (compositor.wl_display,
|
||
&wl_seat_interface, 8,
|
||
seat, HandleBind);
|
||
|
||
InitSeatCommon (seat);
|
||
|
||
XLMakeAssoc (seats, master_keyboard, seat);
|
||
XLMakeAssoc (seats, master_pointer, seat);
|
||
|
||
if (!live_seats)
|
||
/* This is the first seat; make it the input seat. */
|
||
seat->flags |= IsTextInputSeat;
|
||
|
||
live_seats = XLListPrepend (live_seats, seat);
|
||
|
||
/* Now update the seat state from the X server. */
|
||
CatchXErrors ();
|
||
|
||
XkbGetState (compositor.display, master_keyboard, &state);
|
||
|
||
if (UncatchXErrors (NULL))
|
||
/* If the device was disabled or removed, a HierarchyChange event
|
||
will be sent shortly afterwards, causing the seat to be
|
||
destroyed. In that case, not selecting for modifier changes
|
||
will be inconsequential. */
|
||
return;
|
||
|
||
seat->base = state.base_mods;
|
||
seat->locked = state.locked_mods;
|
||
seat->latched = state.latched_mods;
|
||
seat->base_group = state.base_group;
|
||
seat->locked_group = state.locked_group;
|
||
seat->latched_group = state.latched_group;
|
||
seat->effective_group = state.group;
|
||
|
||
/* If a keymap is already attached, initialize seat->key_pressed
|
||
now. */
|
||
if (xkb_desc)
|
||
seat->key_pressed
|
||
= XLCalloc (MaskLen (xkb_desc->max_key_code
|
||
- xkb_desc->min_key_code), 1);
|
||
|
||
/* And select for XKB events from the master keyboard device. If
|
||
the server does not support accessing input extension devices
|
||
with Xkb, an error will result. */
|
||
|
||
mask = 0;
|
||
|
||
mask |= XkbModifierStateMask;
|
||
mask |= XkbModifierBaseMask;
|
||
mask |= XkbModifierLatchMask;
|
||
mask |= XkbModifierLockMask;
|
||
mask |= XkbGroupStateMask;
|
||
mask |= XkbGroupBaseMask;
|
||
mask |= XkbGroupLatchMask;
|
||
mask |= XkbGroupLockMask;
|
||
|
||
CatchXErrors ();
|
||
XkbSelectEventDetails (compositor.display, master_keyboard,
|
||
/* Now enable everything in that mask. */
|
||
XkbStateNotify, mask, mask);
|
||
UncatchXErrors (NULL);
|
||
|
||
UpdateValuators (seat, pointer_info);
|
||
RetainSeat (seat);
|
||
}
|
||
|
||
static void
|
||
UpdateScrollMethods (DeviceInfo *info, int deviceid)
|
||
{
|
||
unsigned char *data;
|
||
Status rc;
|
||
Atom actual_type;
|
||
int actual_format;
|
||
unsigned long nitems, bytes_after;
|
||
|
||
data = NULL;
|
||
|
||
/* This only works with the libinput driver. */
|
||
rc = XIGetProperty (compositor.display, deviceid,
|
||
libinput_Scroll_Methods_Available,
|
||
0, 3, False, XIAnyPropertyType,
|
||
&actual_type, &actual_format,
|
||
&nitems, &bytes_after, &data);
|
||
|
||
/* If there aren't enough items in the data, or the format is wrong,
|
||
return. */
|
||
if (rc != Success || nitems < 3 || actual_format != 8 || !data)
|
||
{
|
||
if (data)
|
||
XFree (data);
|
||
|
||
return;
|
||
}
|
||
|
||
/* First, clear all flags that this function sets. */
|
||
info->flags &= ~DeviceCanFingerScroll;
|
||
info->flags &= ~DeviceCanEdgeScroll;
|
||
|
||
if (data[0])
|
||
info->flags |= DeviceCanFingerScroll;
|
||
|
||
if (data[1])
|
||
info->flags |= DeviceCanEdgeScroll;
|
||
|
||
if (data)
|
||
XFree (data);
|
||
}
|
||
|
||
static void
|
||
UpdateScrollPixelDistance (DeviceInfo *info, int deviceid)
|
||
{
|
||
unsigned char *data;
|
||
Status rc;
|
||
Atom actual_type;
|
||
int actual_format;
|
||
unsigned long nitems, bytes_after;
|
||
|
||
data = NULL;
|
||
|
||
/* This only works with the libinput driver. */
|
||
rc = XIGetProperty (compositor.display, deviceid,
|
||
libinput_Scrolling_Pixel_Distance,
|
||
0, 1, False, XIAnyPropertyType,
|
||
&actual_type, &actual_format,
|
||
&nitems, &bytes_after, &data);
|
||
|
||
/* If there aren't enough items in the data, or the format is wrong,
|
||
return. */
|
||
if (rc != Success || nitems < 1 || actual_format != 32 || !data)
|
||
{
|
||
if (data)
|
||
XFree (data);
|
||
|
||
/* Set the distance to the default, 15. */
|
||
info->scroll_pixel_distance = 15;
|
||
|
||
return;
|
||
}
|
||
|
||
/* Now set the scroll pixel distance. */
|
||
info->scroll_pixel_distance = ((long *) data)[0];
|
||
|
||
/* And free the data. */
|
||
XLFree (data);
|
||
}
|
||
|
||
static void
|
||
RecordDeviceInformation (XIDeviceInfo *deviceinfo)
|
||
{
|
||
DeviceInfo *info;
|
||
|
||
info = XLLookUpAssoc (devices, deviceinfo->deviceid);
|
||
|
||
/* If info doesn't exist, allocate it now. */
|
||
if (!info)
|
||
{
|
||
info = XLMalloc (sizeof *info);
|
||
XLMakeAssoc (devices, deviceinfo->deviceid, info);
|
||
}
|
||
|
||
/* Now clear info->flags, and determine what the device can do. */
|
||
info->flags = 0;
|
||
|
||
/* Initialize scrolling attributes pertinent to pointer devices. */
|
||
if (deviceinfo->use == XISlavePointer)
|
||
{
|
||
/* Catch errors in case the device disappears. */
|
||
CatchXErrors ();
|
||
|
||
/* Obtain the "libinput Scroll Methods Enabled" property and use it
|
||
to compute what scroll methods are available. This naturally
|
||
only works with the libinput driver. */
|
||
UpdateScrollMethods (info, deviceinfo->deviceid);
|
||
|
||
/* Obtain the "libinput Scrolling Pixel Distance" property and
|
||
use it if available. If not, default to 15. */
|
||
UpdateScrollPixelDistance (info, deviceinfo->deviceid);
|
||
|
||
/* Uncatch errors. */
|
||
UncatchXErrors (NULL);
|
||
}
|
||
}
|
||
|
||
static void
|
||
SetupInitialDevices (void)
|
||
{
|
||
XIDeviceInfo *deviceinfo;
|
||
int ndevices, i;
|
||
|
||
deviceinfo = XIQueryDevice (compositor.display,
|
||
XIAllDevices, &ndevices);
|
||
|
||
if (!deviceinfo)
|
||
return;
|
||
|
||
for (i = 0; i < ndevices; ++i)
|
||
{
|
||
if (deviceinfo[i].use == XIMasterPointer)
|
||
MakeSeatForDevicePair (deviceinfo[i].attachment,
|
||
deviceinfo[i].deviceid,
|
||
&deviceinfo[i]);
|
||
|
||
RecordDeviceInformation (&deviceinfo[i]);
|
||
}
|
||
|
||
XIFreeDeviceInfo (deviceinfo);
|
||
}
|
||
|
||
static void
|
||
RunResizeDoneCallbacks (Seat *seat)
|
||
{
|
||
ResizeDoneCallback *callback, *last;
|
||
|
||
callback = seat->resize_callbacks.next;
|
||
|
||
while (callback != &seat->resize_callbacks)
|
||
{
|
||
last = callback;
|
||
callback = callback->next;
|
||
|
||
last->done (last, last->data);
|
||
XLFree (last);
|
||
}
|
||
|
||
/* Empty the list since all elements are free again. */
|
||
seat->resize_callbacks.next = &seat->resize_callbacks;
|
||
seat->resize_callbacks.last = &seat->resize_callbacks;
|
||
}
|
||
|
||
/* Forward declarations. */
|
||
static void TransformToSurface (Surface *, double, double, double *, double *);
|
||
static Surface *FindSurfaceUnder (Subcompositor *, double, double);
|
||
static void EnteredSurface (Seat *, Surface *, Time, double, double, Bool);
|
||
|
||
static void
|
||
CancelResizeOperation (Seat *seat, Time time, Subcompositor *subcompositor,
|
||
XIDeviceEvent *xev)
|
||
{
|
||
Surface *dispatch;
|
||
double x, y;
|
||
|
||
/* Stop the resize operation. */
|
||
XLSurfaceCancelUnmapCallback (seat->resize_surface_callback);
|
||
seat->resize_surface = NULL;
|
||
seat->resize_surface_callback = NULL;
|
||
|
||
/* Run resize completion callbacks. */
|
||
RunResizeDoneCallbacks (seat);
|
||
|
||
/* Ungrab the pointer. */
|
||
XIUngrabDevice (compositor.display, seat->master_pointer,
|
||
time);
|
||
|
||
if (!subcompositor)
|
||
return;
|
||
|
||
/* If there's no focus surface, look up the surface and enter
|
||
it. The grab should not be held at this point. */
|
||
dispatch = FindSurfaceUnder (subcompositor, xev->event_x, xev->event_y);
|
||
|
||
if (dispatch)
|
||
{
|
||
TransformToSurface (dispatch, xev->event_x, xev->event_y, &x, &y);
|
||
|
||
/* Enter the surface. */
|
||
EnteredSurface (seat, dispatch, xev->time, x, y, False);
|
||
}
|
||
|
||
/* Otherwise, there should be no need to leave any entered surface,
|
||
since the entered surface should be NULL already. */
|
||
}
|
||
|
||
static Bool
|
||
InterceptButtonEventForResize (Seat *seat, Subcompositor *subcompositor,
|
||
XIDeviceEvent *xev)
|
||
{
|
||
if (xev->type == XI_ButtonPress)
|
||
return True;
|
||
|
||
/* If the button starting the resize has been released, cancel the
|
||
resize operation. */
|
||
if (xev->detail == seat->resize_button)
|
||
CancelResizeOperation (seat, xev->time, subcompositor, xev);
|
||
|
||
return True;
|
||
}
|
||
|
||
/* Forward declaration. */
|
||
|
||
static Bool HandleValuatorMotion (Seat *, Surface *, double, double,
|
||
XIDeviceEvent *);
|
||
|
||
#define MoveLeft(flags, i) ((flags) & ResizeAxisLeft ? (i) : 0)
|
||
#define MoveTop(flags, i) ((flags) & ResizeAxisTop ? (i) : 0)
|
||
|
||
static void
|
||
HandleMovement (Seat *seat, int west, int north)
|
||
{
|
||
XLSurfaceMoveBy (seat->resize_surface, west, north);
|
||
}
|
||
|
||
static Bool
|
||
InterceptMotionEventForResize (Seat *seat, XIDeviceEvent *xev)
|
||
{
|
||
int root_x, root_y, diff_x, diff_y, abs_diff_x, abs_diff_y;
|
||
|
||
root_x = lrint (xev->root_x);
|
||
root_y = lrint (xev->root_y);
|
||
|
||
/* Handle valuator motion anyway. Otherwise, the values could get
|
||
out of date. */
|
||
HandleValuatorMotion (seat, NULL, xev->event_x, xev->event_y, xev);
|
||
|
||
if (root_x == seat->resize_last_root_x
|
||
&& root_y == seat->resize_last_root_y)
|
||
/* No motion really happened. */
|
||
return True;
|
||
|
||
/* If this is a move and not a resize, simply move the surface's
|
||
window. */
|
||
if (seat->resize_axis_flags & ResizeAxisMove)
|
||
{
|
||
HandleMovement (seat, seat->resize_last_root_x - root_x,
|
||
seat->resize_last_root_y - root_y);
|
||
|
||
seat->resize_last_root_x = root_x;
|
||
seat->resize_last_root_y = root_y;
|
||
return True;
|
||
}
|
||
|
||
/* Compute the amount by which to move the window. The movement is
|
||
towards the geographical north and west. */
|
||
diff_x = seat->resize_last_root_x - root_x;
|
||
diff_y = seat->resize_last_root_y - root_y;
|
||
|
||
abs_diff_x = 0;
|
||
abs_diff_y = 0;
|
||
|
||
if (seat->resize_axis_flags & ResizeAxisLeft)
|
||
/* diff_x will move the surface leftwards. This is by how
|
||
much to extend the window the other way as well. */
|
||
abs_diff_x = seat->resize_start_root_x - root_x;
|
||
|
||
if (seat->resize_axis_flags & ResizeAxisTop)
|
||
/* Likewise for diff_y. */
|
||
abs_diff_y = seat->resize_start_root_y - root_y;
|
||
|
||
if (seat->resize_axis_flags & ResizeAxisRight)
|
||
/* diff_x is computed differently here, since root_x grows in the
|
||
correct resize direction. */
|
||
abs_diff_x = root_x - seat->resize_start_root_x;
|
||
|
||
if (seat->resize_axis_flags & ResizeAxisBottom)
|
||
/* The same applies for the direction of root_y. */
|
||
abs_diff_y = root_y - seat->resize_start_root_y;
|
||
|
||
if (!abs_diff_x && !abs_diff_y)
|
||
/* No resizing has to take place. */
|
||
return True;
|
||
|
||
seat->resize_last_root_x = root_x;
|
||
seat->resize_last_root_y = root_y;
|
||
|
||
/* Now, post a new configure event. Upon ack, also move the window
|
||
leftwards and topwards by diff_x and diff_y, should the resize
|
||
direction go that way. */
|
||
XLSurfacePostResize (seat->resize_surface,
|
||
MoveLeft (seat->resize_axis_flags, diff_x),
|
||
MoveTop (seat->resize_axis_flags, diff_y),
|
||
seat->resize_width + abs_diff_x,
|
||
seat->resize_height + abs_diff_y);
|
||
|
||
return True;
|
||
}
|
||
|
||
static Bool
|
||
InterceptResizeEvent (Seat *seat, Subcompositor *subcompositor,
|
||
XIDeviceEvent *xev)
|
||
{
|
||
if (!seat->resize_surface)
|
||
return False;
|
||
|
||
switch (xev->evtype)
|
||
{
|
||
case XI_ButtonRelease:
|
||
return InterceptButtonEventForResize (seat, subcompositor, xev);
|
||
|
||
case XI_Motion:
|
||
return InterceptMotionEventForResize (seat, xev);
|
||
}
|
||
|
||
return True;
|
||
}
|
||
|
||
static void
|
||
RunDestroyListeners (Seat *seat)
|
||
{
|
||
DestroyListener *listeners;
|
||
|
||
listeners = seat->destroy_listeners.next;
|
||
|
||
while (listeners != &seat->destroy_listeners)
|
||
{
|
||
listeners->destroy (listeners->data);
|
||
listeners = listeners->next;
|
||
}
|
||
}
|
||
|
||
/* Forward declaration. */
|
||
static void SetFocusSurface (Seat *, Surface *);
|
||
|
||
static void
|
||
NoticeDeviceDisabled (int deviceid)
|
||
{
|
||
Seat *seat, *new;
|
||
DeviceInfo *info;
|
||
|
||
/* First, see if there is any deviceinfo related to the disabled
|
||
device. If there is, free it. */
|
||
info = XLLookUpAssoc (devices, deviceid);
|
||
|
||
if (info)
|
||
{
|
||
XLDeleteAssoc (devices, deviceid);
|
||
XLFree (info);
|
||
}
|
||
|
||
/* It doesn't matter if this is the keyboard or pointer, since
|
||
paired master devices are always destroyed together. */
|
||
|
||
seat = XLLookUpAssoc (seats, deviceid);
|
||
|
||
/* Test seats should not be destroyed here. */
|
||
|
||
if (seat && !(seat->flags & IsTestSeat))
|
||
{
|
||
/* The device has been disabled, mark the seat inert and
|
||
dereference it. The seat is still referred to by the
|
||
global. */
|
||
|
||
seat->flags |= IsInert;
|
||
|
||
/* Set the focus surface to NULL, so surfaces don't mistakenly
|
||
treat themselves as still focused. */
|
||
|
||
SetFocusSurface (seat, NULL);
|
||
|
||
/* Run destroy handlers. */
|
||
|
||
RunDestroyListeners (seat);
|
||
|
||
/* Since the seat is now inert, remove it from the assoc
|
||
table and destroy the global. */
|
||
|
||
XLDeleteAssoc (seats, seat->master_keyboard);
|
||
XLDeleteAssoc (seats, seat->master_pointer);
|
||
|
||
/* Also remove it from the list of live seats. */
|
||
|
||
live_seats = XLListRemove (live_seats, seat);
|
||
|
||
/* Run and remove all resize completion callbacks. */
|
||
|
||
RunResizeDoneCallbacks (seat);
|
||
|
||
/* Finally, destroy the global. */
|
||
|
||
wl_global_destroy (seat->global);
|
||
|
||
/* If it was the input seat, then find a new seat to take its
|
||
place. */
|
||
if (seat->flags & IsTextInputSeat
|
||
&& live_seats)
|
||
{
|
||
new = live_seats->data;
|
||
|
||
/* This results in nondeterministic selection of input
|
||
seats, and as such can be confusing to the user. */
|
||
new->flags |= IsTextInputSeat;
|
||
}
|
||
|
||
/* And release the seat. */
|
||
|
||
ReleaseSeat (seat);
|
||
}
|
||
}
|
||
|
||
static void
|
||
NoticeDeviceEnabled (int deviceid)
|
||
{
|
||
XIDeviceInfo *info;
|
||
int ndevices;
|
||
|
||
CatchXErrors ();
|
||
info = XIQueryDevice (compositor.display, deviceid,
|
||
&ndevices);
|
||
UncatchXErrors (NULL);
|
||
|
||
if (info && info->use == XIMasterPointer)
|
||
/* ndevices doesn't have to checked here. */
|
||
MakeSeatForDevicePair (info->attachment, deviceid, info);
|
||
|
||
if (info)
|
||
{
|
||
/* Update device information for this device. */
|
||
RecordDeviceInformation (info);
|
||
|
||
/* And free the device. */
|
||
XIFreeDeviceInfo (info);
|
||
}
|
||
}
|
||
|
||
static void
|
||
NoticeSlaveAttached (int deviceid)
|
||
{
|
||
XIDeviceInfo *info;
|
||
int ndevices;
|
||
|
||
CatchXErrors ();
|
||
info = XIQueryDevice (compositor.display, deviceid,
|
||
&ndevices);
|
||
UncatchXErrors (NULL);
|
||
|
||
/* A slave device was attached. Take this opportunity to update its
|
||
device information. */
|
||
|
||
if (info)
|
||
{
|
||
/* Update device information for this device. */
|
||
RecordDeviceInformation (info);
|
||
|
||
/* And free the device. */
|
||
XIFreeDeviceInfo (info);
|
||
}
|
||
}
|
||
|
||
static void
|
||
HandleHierarchyEvent (XIHierarchyEvent *event)
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; i < event->num_info; ++i)
|
||
{
|
||
if (event->info[i].flags & XIDeviceDisabled)
|
||
NoticeDeviceDisabled (event->info[i].deviceid);
|
||
else if (event->info[i].flags & XIDeviceEnabled)
|
||
NoticeDeviceEnabled (event->info[i].deviceid);
|
||
else if (event->info[i].flags & XISlaveAttached)
|
||
NoticeSlaveAttached (event->info[i].deviceid);
|
||
}
|
||
}
|
||
|
||
#define KeyIsPressed(seat, keycode) \
|
||
(MaskIsSet ((seat)->key_pressed, \
|
||
(keycode) - xkb_desc->min_key_code))
|
||
#define KeySetPressed(seat, keycode, pressed) \
|
||
(!pressed \
|
||
? ClearMask ((seat)->key_pressed, \
|
||
(keycode) - xkb_desc->min_key_code) \
|
||
: SetMask ((seat)->key_pressed, \
|
||
(keycode) - xkb_desc->min_key_code)) \
|
||
|
||
#define WaylandKeycode(keycode) ((keycode) - 8)
|
||
|
||
static void
|
||
InsertKeyIntoSeat (Seat *seat, int32_t keycode)
|
||
{
|
||
int32_t *data;
|
||
|
||
data = wl_array_add (&seat->keys, sizeof *data);
|
||
|
||
if (data)
|
||
*data = keycode;
|
||
}
|
||
|
||
static void
|
||
ArrayRemove (struct wl_array *array, void *item, size_t size)
|
||
{
|
||
size_t bytes;
|
||
char *arith;
|
||
|
||
arith = item;
|
||
|
||
bytes = array->size - (arith + size
|
||
- (char *) array->data);
|
||
if (bytes > 0)
|
||
memmove (item, arith + size, bytes);
|
||
array->size -= size;
|
||
}
|
||
|
||
static void
|
||
RemoveKeyFromSeat (Seat *seat, int32_t keycode)
|
||
{
|
||
int32_t *data;
|
||
|
||
wl_array_for_each (data, &seat->keys)
|
||
{
|
||
if (*data == keycode)
|
||
{
|
||
ArrayRemove (&seat->keys, data, sizeof *data);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
static SeatClientInfo *
|
||
ClientInfoForResource (Seat *seat, struct wl_resource *resource)
|
||
{
|
||
return GetSeatClientInfo (seat, wl_resource_get_client (resource));
|
||
}
|
||
|
||
static void
|
||
SendKeyboardKey (Seat *seat, Surface *focus, Time time,
|
||
uint32_t key, uint32_t state)
|
||
{
|
||
Keyboard *keyboard;
|
||
SeatClientInfo *info;
|
||
uint32_t serial;
|
||
|
||
serial = wl_display_next_serial (compositor.wl_display);
|
||
seat->last_keyboard_serial = serial;
|
||
|
||
info = ClientInfoForResource (seat, focus->resource);
|
||
|
||
if (!info)
|
||
return;
|
||
|
||
keyboard = info->keyboards.next;
|
||
|
||
for (; keyboard != &info->keyboards; keyboard = keyboard->next)
|
||
wl_keyboard_send_key (keyboard->resource, serial, time,
|
||
key, state);
|
||
}
|
||
|
||
static void
|
||
HandleKeyPressed (Seat *seat, KeyCode keycode, Time time)
|
||
{
|
||
if (KeyIsPressed (seat, keycode))
|
||
return;
|
||
|
||
KeySetPressed (seat, keycode, True);
|
||
InsertKeyIntoSeat (seat, WaylandKeycode (keycode));
|
||
}
|
||
|
||
static void
|
||
HandleKeyReleased (Seat *seat, KeyCode keycode, Time time)
|
||
{
|
||
if (!KeyIsPressed (seat, keycode))
|
||
return;
|
||
|
||
KeySetPressed (seat, keycode, False);
|
||
RemoveKeyFromSeat (seat, WaylandKeycode (keycode));
|
||
}
|
||
|
||
static void
|
||
HandleRawKey (XIRawEvent *event)
|
||
{
|
||
Seat *seat;
|
||
|
||
/* We select for raw events from the X server in order to track the
|
||
keys that are currently pressed. In order to respect grabs, key
|
||
press and release events are only reported in response to
|
||
regular device events. */
|
||
|
||
seat = XLLookUpAssoc (seats, event->deviceid);
|
||
|
||
if (!seat)
|
||
return;
|
||
|
||
if (event->detail < xkb_desc->min_key_code
|
||
|| event->detail > xkb_desc->max_key_code)
|
||
return;
|
||
|
||
if (event->evtype == XI_RawKeyPress)
|
||
HandleKeyPressed (seat, event->detail, event->time);
|
||
else
|
||
HandleKeyReleased (seat, event->detail, event->time);
|
||
|
||
/* This is used for tracking grabs. */
|
||
seat->its_depress_time = event->time;
|
||
|
||
/* Update the last user time. send_event events can have a
|
||
different timestamp not synchronized with that of the server. */
|
||
if (!event->send_event)
|
||
seat->last_user_time = TimestampFromServerTime (event->time);
|
||
}
|
||
|
||
static void
|
||
HandleResizeComplete (Seat *seat)
|
||
{
|
||
Surface *surface;
|
||
XEvent msg;
|
||
|
||
surface = seat->last_button_press_surface;
|
||
|
||
if (!surface || !XLWindowFromSurface (surface))
|
||
goto finish;
|
||
|
||
/* We might have gotten the button release before the window manager
|
||
set the grab. Cancel the resize operation in that case. */
|
||
|
||
memset (&msg, 0, sizeof msg);
|
||
msg.xclient.type = ClientMessage;
|
||
msg.xclient.window = XLWindowFromSurface (surface);
|
||
msg.xclient.format = 32;
|
||
msg.xclient.message_type = _NET_WM_MOVERESIZE;
|
||
msg.xclient.data.l[0] = seat->its_root_x;
|
||
msg.xclient.data.l[1] = seat->its_root_y;
|
||
msg.xclient.data.l[2] = 11; /* _NET_WM_MOVERESIZE_CANCEL. */
|
||
msg.xclient.data.l[3] = seat->last_button;
|
||
msg.xclient.data.l[4] = 1; /* Source indication. */
|
||
|
||
XSendEvent (compositor.display,
|
||
DefaultRootWindow (compositor.display),
|
||
False,
|
||
SubstructureRedirectMask | SubstructureNotifyMask,
|
||
&msg);
|
||
|
||
finish:
|
||
|
||
/* Now say that resize operations have stopped. */
|
||
seat->resize_in_progress = False;
|
||
|
||
/* And run callbacks. */
|
||
RunResizeDoneCallbacks (seat);
|
||
}
|
||
|
||
/* Forward declarations. */
|
||
|
||
static int GetXButton (int);
|
||
static void SendButton (Seat *, Surface *, Time, uint32_t, uint32_t,
|
||
double, double);
|
||
|
||
static void
|
||
HandleRawButton (XIRawEvent *event)
|
||
{
|
||
Seat *seat;
|
||
int button;
|
||
double win_x, win_y;
|
||
double dispatch_x, dispatch_y;
|
||
Window window;
|
||
|
||
seat = XLLookUpAssoc (seats, event->deviceid);
|
||
|
||
if (!seat)
|
||
return;
|
||
|
||
if (seat->resize_in_progress
|
||
|| seat->flags & IsWindowMenuShown)
|
||
{
|
||
if (seat->last_seen_surface)
|
||
{
|
||
window = XLWindowFromSurface (seat->last_seen_surface);
|
||
|
||
if (window == None)
|
||
goto complete;
|
||
|
||
button = GetXButton (event->detail);
|
||
|
||
if (button < 0)
|
||
goto complete;
|
||
|
||
/* When a RawButtonPress is received while resizing is still
|
||
in progress, release the button on the current surface.
|
||
|
||
Since leave and entry events generated by grabs are
|
||
ignored, the client will not get leave events correctly
|
||
while Metacity is resizing a frame. This results in
|
||
programs such as GTK tracking the button state
|
||
incorrectly if the pointer never leaves the surface
|
||
during a resize operation.
|
||
|
||
Something similar applies to the window menu.
|
||
|
||
(It would be good to avoid this sync by fetching the
|
||
actual modifier values from the raw event.) */
|
||
|
||
if (QueryPointer (seat, window, &win_x, &win_y))
|
||
{
|
||
/* Otherwise, the pointer is on a different screen! */
|
||
TransformToSurface (seat->last_seen_surface,
|
||
win_x, win_y, &dispatch_x, &dispatch_y);
|
||
|
||
/* And finally the button release. */
|
||
SendButton (seat, seat->last_seen_surface, event->time,
|
||
button, WL_POINTER_BUTTON_STATE_RELEASED,
|
||
dispatch_x, dispatch_y);
|
||
}
|
||
}
|
||
|
||
complete:
|
||
|
||
if (event->detail == seat->last_button
|
||
&& seat->resize_in_progress)
|
||
HandleResizeComplete (seat);
|
||
}
|
||
}
|
||
|
||
static void
|
||
HandleDeviceChanged (XIDeviceChangedEvent *event)
|
||
{
|
||
Seat *seat;
|
||
XIDeviceInfo *info;
|
||
int ndevices;
|
||
|
||
seat = XLLookUpAssoc (seats, event->deviceid);
|
||
|
||
if (!seat || event->deviceid != seat->master_pointer)
|
||
return;
|
||
|
||
/* Now, update scroll valuators from the new device info. */
|
||
|
||
CatchXErrors ();
|
||
info = XIQueryDevice (compositor.display, event->deviceid,
|
||
&ndevices);
|
||
UncatchXErrors (NULL);
|
||
|
||
if (!info)
|
||
/* The device was disabled, return now. */
|
||
return;
|
||
|
||
UpdateValuators (seat, info);
|
||
XIFreeDeviceInfo (info);
|
||
}
|
||
|
||
static void
|
||
HandlePropertyChanged (XIPropertyEvent *event)
|
||
{
|
||
DeviceInfo *info;
|
||
|
||
info = XLLookUpAssoc (devices, event->deviceid);
|
||
|
||
if (!info)
|
||
return;
|
||
|
||
if (event->property == libinput_Scroll_Methods_Available)
|
||
/* Update scroll methods for the device whose property
|
||
changed. */
|
||
UpdateScrollMethods (info, event->deviceid);
|
||
else if (event->property == libinput_Scrolling_Pixel_Distance)
|
||
/* Update the scroll pixel distance. */
|
||
UpdateScrollPixelDistance (info, event->deviceid);
|
||
}
|
||
|
||
static Seat *
|
||
FindSeatByDragWindow (Window window)
|
||
{
|
||
Seat *seat;
|
||
XLList *tem;
|
||
|
||
for (tem = live_seats; tem; tem = tem->next)
|
||
{
|
||
seat = tem->data;
|
||
|
||
if (seat->grab_window == window)
|
||
return seat;
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
static Bool
|
||
HandleDragMotionEvent (XIDeviceEvent *xev)
|
||
{
|
||
Seat *seat;
|
||
|
||
seat = FindSeatByDragWindow (xev->event);
|
||
|
||
if (!seat)
|
||
return False;
|
||
|
||
/* When an event is received for the drag window, it means the event
|
||
is outside any surface. Dispatch it to the external drag and
|
||
drop code. */
|
||
|
||
/* Move the drag-and-drop icon window. */
|
||
if (seat->icon_surface)
|
||
XLMoveIconSurface (seat->icon_surface, xev->root_x,
|
||
xev->root_y);
|
||
|
||
/* Update information used for resize tracking. */
|
||
seat->its_root_x = xev->root_x;
|
||
seat->its_root_y = xev->root_y;
|
||
|
||
/* Dispatch the drag motion to external programs. */
|
||
if (seat->data_source)
|
||
XLDoDragMotion (seat, xev->root_x, xev->root_y);
|
||
|
||
return True;
|
||
}
|
||
|
||
/* Forward declaration. */
|
||
|
||
static void DragButton (Seat *, XIDeviceEvent *);
|
||
|
||
static Bool
|
||
HandleDragButtonEvent (XIDeviceEvent *xev)
|
||
{
|
||
Seat *seat;
|
||
|
||
seat = FindSeatByDragWindow (xev->event);
|
||
|
||
if (!seat)
|
||
return False;
|
||
|
||
DragButton (seat, xev);
|
||
return True;
|
||
}
|
||
|
||
static Bool
|
||
HandleOneGenericEvent (XGenericEventCookie *xcookie)
|
||
{
|
||
switch (xcookie->evtype)
|
||
{
|
||
case XI_HierarchyChanged:
|
||
HandleHierarchyEvent (xcookie->data);
|
||
return True;
|
||
|
||
case XI_DeviceChanged:
|
||
HandleDeviceChanged (xcookie->data);
|
||
return True;
|
||
|
||
case XI_PropertyEvent:
|
||
HandlePropertyChanged (xcookie->data);
|
||
return True;
|
||
|
||
case XI_RawKeyPress:
|
||
case XI_RawKeyRelease:
|
||
HandleRawKey (xcookie->data);
|
||
return True;
|
||
|
||
case XI_RawButtonRelease:
|
||
HandleRawButton (xcookie->data);
|
||
return True;
|
||
|
||
case XI_Motion:
|
||
return HandleDragMotionEvent (xcookie->data);
|
||
|
||
case XI_ButtonPress:
|
||
case XI_ButtonRelease:
|
||
return HandleDragButtonEvent (xcookie->data);
|
||
}
|
||
|
||
return False;
|
||
}
|
||
|
||
static void
|
||
SelectDeviceEvents (void)
|
||
{
|
||
XIEventMask mask;
|
||
size_t length;
|
||
|
||
length = XIMaskLen (XI_LASTEVENT);
|
||
mask.mask = alloca (length);
|
||
mask.mask_len = length;
|
||
mask.deviceid = XIAllDevices;
|
||
|
||
memset (mask.mask, 0, length);
|
||
|
||
XISetMask (mask.mask, XI_PropertyEvent);
|
||
XISetMask (mask.mask, XI_HierarchyChanged);
|
||
XISetMask (mask.mask, XI_DeviceChanged);
|
||
|
||
XISelectEvents (compositor.display,
|
||
DefaultRootWindow (compositor.display),
|
||
&mask, 1);
|
||
|
||
memset (mask.mask, 0, length);
|
||
|
||
mask.deviceid = XIAllMasterDevices;
|
||
|
||
XISetMask (mask.mask, XI_RawKeyPress);
|
||
XISetMask (mask.mask, XI_RawKeyRelease);
|
||
XISetMask (mask.mask, XI_RawButtonRelease);
|
||
|
||
XISelectEvents (compositor.display,
|
||
DefaultRootWindow (compositor.display),
|
||
&mask, 1);
|
||
}
|
||
|
||
static void
|
||
ClearFocusSurface (void *data)
|
||
{
|
||
Seat *seat;
|
||
|
||
seat = data;
|
||
|
||
seat->focus_surface = NULL;
|
||
seat->focus_destroy_callback = NULL;
|
||
|
||
XLPrimarySelectionHandleFocusChange (seat);
|
||
|
||
/* Tell any input method about the focus change. */
|
||
if (input_funcs)
|
||
input_funcs->focus_out (seat);
|
||
}
|
||
|
||
static void
|
||
SendKeyboardLeave (Seat *seat, Surface *focus)
|
||
{
|
||
Keyboard *keyboard;
|
||
uint32_t serial;
|
||
SeatClientInfo *info;
|
||
|
||
serial = wl_display_next_serial (compositor.wl_display);
|
||
info = ClientInfoForResource (seat, focus->resource);
|
||
|
||
if (!info)
|
||
return;
|
||
|
||
keyboard = info->keyboards.next;
|
||
|
||
for (; keyboard != &info->keyboards; keyboard = keyboard->next)
|
||
wl_keyboard_send_leave (keyboard->resource,
|
||
serial, focus->resource);
|
||
}
|
||
|
||
static void
|
||
UpdateSingleModifiers (Seat *seat, Keyboard *keyboard, uint32_t serial)
|
||
{
|
||
wl_keyboard_send_modifiers (keyboard->resource, serial,
|
||
seat->base, seat->latched,
|
||
seat->locked,
|
||
seat->effective_group);
|
||
}
|
||
|
||
static void
|
||
SendKeyboardEnter (Seat *seat, Surface *enter)
|
||
{
|
||
Keyboard *keyboard;
|
||
uint32_t serial;
|
||
SeatClientInfo *info;
|
||
|
||
serial = wl_display_next_serial (compositor.wl_display);
|
||
info = ClientInfoForResource (seat, enter->resource);
|
||
|
||
if (!info)
|
||
return;
|
||
|
||
/* For some reason Firefox doesn't use the serial of the user event
|
||
(key or button press) that triggered the activation, but the
|
||
serial of the last keyboard entry event on the seat. */
|
||
seat->last_enter_serial = serial;
|
||
|
||
keyboard = info->keyboards.next;
|
||
|
||
for (; keyboard != &info->keyboards; keyboard = keyboard->next)
|
||
{
|
||
wl_keyboard_send_enter (keyboard->resource, serial,
|
||
enter->resource, &seat->keys);
|
||
UpdateSingleModifiers (seat, keyboard, serial);
|
||
}
|
||
}
|
||
|
||
static void
|
||
SendKeyboardModifiers (Seat *seat, Surface *focus)
|
||
{
|
||
Keyboard *keyboard;
|
||
uint32_t serial;
|
||
SeatClientInfo *info;
|
||
|
||
serial = wl_display_next_serial (compositor.wl_display);
|
||
info = ClientInfoForResource (seat, focus->resource);
|
||
|
||
if (!info)
|
||
return;
|
||
|
||
keyboard = info->keyboards.next;
|
||
|
||
for (; keyboard != &info->keyboards; keyboard = keyboard->next)
|
||
UpdateSingleModifiers (seat, keyboard, serial);
|
||
}
|
||
|
||
static void
|
||
HackKeyboardModifiers (Seat *seat, Surface *focus, int effective,
|
||
int group)
|
||
{
|
||
Keyboard *keyboard;
|
||
uint32_t serial;
|
||
SeatClientInfo *info;
|
||
|
||
serial = wl_display_next_serial (compositor.wl_display);
|
||
info = ClientInfoForResource (seat, focus->resource);
|
||
|
||
if (!info)
|
||
return;
|
||
|
||
keyboard = info->keyboards.next;
|
||
|
||
for (; keyboard != &info->keyboards; keyboard = keyboard->next)
|
||
/* It is wrong to send the new modifiers in seat->based, but I
|
||
don't know anything better. */
|
||
wl_keyboard_send_modifiers (keyboard->resource, serial,
|
||
effective, 0, 0, group);
|
||
}
|
||
|
||
static void
|
||
SendUpdatedModifiers (Seat *seat)
|
||
{
|
||
ModifierChangeCallback *callback;
|
||
|
||
for (callback = seat->modifier_callbacks.next;
|
||
callback != &seat->modifier_callbacks;
|
||
callback = callback->next)
|
||
/* Send the effective modifiers. */
|
||
callback->changed (seat->base | seat->locked | seat->latched,
|
||
callback->data);
|
||
|
||
/* If drag and drop is in progress, update the data source
|
||
actions. */
|
||
if (seat->flags & IsDragging && seat->data_source
|
||
/* Don't do this during external drag and drop. */
|
||
&& seat->drag_last_surface)
|
||
XLDataSourceUpdateDeviceActions (seat->data_source);
|
||
|
||
if (seat->focus_surface)
|
||
SendKeyboardModifiers (seat, seat->focus_surface);
|
||
}
|
||
|
||
static void
|
||
UpdateModifiersForSeat (Seat *seat,
|
||
unsigned int base, unsigned int locked,
|
||
unsigned int latched, int base_group,
|
||
int locked_group, int latched_group,
|
||
int effective_group)
|
||
{
|
||
seat->base = base;
|
||
seat->locked = locked;
|
||
seat->latched = latched;
|
||
seat->base_group = base_group;
|
||
seat->locked_group = locked_group;
|
||
seat->latched_group = latched_group;
|
||
seat->effective_group = effective_group;
|
||
|
||
SendUpdatedModifiers (seat);
|
||
}
|
||
|
||
static void
|
||
SetFocusSurface (Seat *seat, Surface *focus)
|
||
{
|
||
if (focus == seat->focus_surface)
|
||
return;
|
||
|
||
if (seat->focus_surface)
|
||
{
|
||
SendKeyboardLeave (seat, seat->focus_surface);
|
||
|
||
/* Tell the surface it's no longer focused. */
|
||
XLSurfaceNoteFocus (seat->focus_surface, SurfaceFocusOut);
|
||
|
||
/* Cancel any grab that may be associated with shortcut
|
||
inhibition. */
|
||
XLReleaseShortcutInhibition (seat, seat->focus_surface);
|
||
|
||
XLSurfaceCancelRunOnFree (seat->focus_destroy_callback);
|
||
seat->focus_destroy_callback = NULL;
|
||
seat->focus_surface = NULL;
|
||
|
||
if (input_funcs)
|
||
/* Tell any input method about the change. */
|
||
input_funcs->focus_out (seat);
|
||
}
|
||
|
||
if (!focus)
|
||
{
|
||
/* These changes must be handled even if there is no more focus
|
||
surface. */
|
||
XLPrimarySelectionHandleFocusChange (seat);
|
||
return;
|
||
}
|
||
|
||
/* Apply any shortcut inhibition. */
|
||
XLCheckShortcutInhibition (seat, focus);
|
||
|
||
if (input_funcs)
|
||
/* Tell any input method about the change. */
|
||
input_funcs->focus_in (seat, focus);
|
||
|
||
seat->focus_surface = focus;
|
||
seat->focus_destroy_callback
|
||
= XLSurfaceRunOnFree (focus, ClearFocusSurface, seat);
|
||
|
||
SendKeyboardEnter (seat, focus);
|
||
|
||
/* Tell the surface it's now focused. */
|
||
XLSurfaceNoteFocus (seat->focus_surface, SurfaceFocusIn);
|
||
|
||
XLPrimarySelectionHandleFocusChange (seat);
|
||
|
||
if (seat->data_device)
|
||
XLDataDeviceHandleFocusChange (seat->data_device);
|
||
}
|
||
|
||
static void
|
||
DispatchFocusIn (Surface *surface, XIFocusInEvent *event)
|
||
{
|
||
Seat *seat;
|
||
|
||
seat = XLLookUpAssoc (seats, event->deviceid);
|
||
|
||
if (!seat)
|
||
return;
|
||
|
||
/* Record the time the focus changed for the external grab. */
|
||
|
||
if (!event->send_event)
|
||
seat->last_focus_time = TimestampFromServerTime (event->time);
|
||
|
||
SetFocusSurface (seat, surface);
|
||
}
|
||
|
||
static void
|
||
DispatchFocusOut (Surface *surface, XIFocusOutEvent *event)
|
||
{
|
||
Seat *seat;
|
||
|
||
seat = XLLookUpAssoc (seats, event->deviceid);
|
||
|
||
if (!seat)
|
||
return;
|
||
|
||
if (seat->focus_surface == surface)
|
||
SetFocusSurface (seat, NULL);
|
||
}
|
||
|
||
static Surface *
|
||
FindSurfaceUnder (Subcompositor *subcompositor, double x, double y)
|
||
{
|
||
int x_off, y_off;
|
||
View *view;
|
||
|
||
/* Do not round these figures. Instead, cut off the fractional,
|
||
like the X server does when deciding when to set the cursor. */
|
||
view = SubcompositorLookupView (subcompositor, x, y,
|
||
&x_off, &y_off);
|
||
|
||
if (view)
|
||
return ViewGetData (view);
|
||
|
||
return NULL;
|
||
}
|
||
|
||
/* Forward declaration. */
|
||
|
||
static void CancelDrag (Seat *, Window, double, double);
|
||
|
||
static void
|
||
DragLeave (Seat *seat)
|
||
{
|
||
if (seat->drag_last_surface)
|
||
{
|
||
if (seat->flags & IsDragging)
|
||
XLDataDeviceSendLeave (seat, seat->drag_last_surface,
|
||
seat->data_source);
|
||
else
|
||
/* If nothing is being dragged anymore, avoid sending flags to
|
||
the source after drop or cancel. */
|
||
XLDataDeviceSendLeave (seat, seat->drag_last_surface,
|
||
NULL);
|
||
|
||
XLSurfaceCancelRunOnFree (seat->drag_last_surface_destroy_callback);
|
||
|
||
seat->drag_last_surface_destroy_callback = NULL;
|
||
seat->drag_last_surface = NULL;
|
||
}
|
||
}
|
||
|
||
static void
|
||
HandleDragLastSurfaceDestroy (void *data)
|
||
{
|
||
Seat *seat;
|
||
|
||
seat = data;
|
||
|
||
/* Unfortunately there's no way to send a leave message to the
|
||
client, as the surface's resource no longer exists. Oh well. */
|
||
|
||
seat->drag_last_surface = NULL;
|
||
seat->drag_last_surface_destroy_callback = NULL;
|
||
}
|
||
|
||
static void
|
||
DragEnter (Seat *seat, Surface *surface, double x, double y)
|
||
{
|
||
if (seat->drag_last_surface)
|
||
DragLeave (seat);
|
||
|
||
/* If no data source is specified, only send motion events to
|
||
surfaces created by the same client. */
|
||
if (!seat->data_source
|
||
&& (wl_resource_get_client (seat->drag_start_surface->resource)
|
||
!= wl_resource_get_client (surface->resource)))
|
||
return;
|
||
|
||
seat->drag_last_surface = surface;
|
||
seat->drag_last_surface_destroy_callback
|
||
= XLSurfaceRunOnFree (surface, HandleDragLastSurfaceDestroy,
|
||
seat);
|
||
|
||
XLDataDeviceSendEnter (seat, surface, x, y, seat->data_source);
|
||
}
|
||
|
||
static void
|
||
DragMotion (Seat *seat, Surface *surface, double x, double y,
|
||
Time time)
|
||
{
|
||
if (!seat->drag_last_surface)
|
||
return;
|
||
|
||
if (surface != seat->drag_last_surface)
|
||
return;
|
||
|
||
XLDataDeviceSendMotion (seat, surface, x, y, time);
|
||
}
|
||
|
||
static int
|
||
MaskPopCount (XIButtonState *mask)
|
||
{
|
||
int population, i;
|
||
|
||
population = 0;
|
||
|
||
for (i = 0; i < mask->mask_len; ++i)
|
||
population += Popcount (mask->mask[i]);
|
||
|
||
return population;
|
||
}
|
||
|
||
static void
|
||
DragButton (Seat *seat, XIDeviceEvent *xev)
|
||
{
|
||
if (xev->evtype != XI_ButtonRelease)
|
||
return;
|
||
|
||
/* If a button release event is received with only 1 button
|
||
remaining, then the drag is complete; send the drop. */
|
||
if (MaskPopCount (&xev->buttons) == 1)
|
||
{
|
||
/* Drop on any external drag and drop that may be in
|
||
progress. */
|
||
if (seat->data_source && XLDoDragDrop (seat))
|
||
XLDataSourceSendDropPerformed (seat->data_source);
|
||
else
|
||
{
|
||
if (seat->drag_last_surface)
|
||
{
|
||
if (!seat->data_source
|
||
|| XLDataSourceCanDrop (seat->data_source))
|
||
{
|
||
XLDataDeviceSendDrop (seat, seat->drag_last_surface);
|
||
XLDataSourceSendDropPerformed (seat->data_source);
|
||
}
|
||
else
|
||
/* Otherwise, the data source is not eligible for
|
||
dropping; simply send cancel. */
|
||
XLDataSourceSendDropCancelled (seat->data_source);
|
||
}
|
||
else if (seat->data_source)
|
||
XLDataSourceSendDropCancelled (seat->data_source);
|
||
}
|
||
|
||
/* This means that CancelDrag will not send the drop cancelled
|
||
event to the data source again. */
|
||
seat->flags |= IsDropped;
|
||
|
||
CancelDrag (seat, xev->event, xev->event_x,
|
||
xev->event_y);
|
||
}
|
||
}
|
||
|
||
static void
|
||
SendMotion (Seat *seat, Surface *surface, double x, double y,
|
||
Time time)
|
||
{
|
||
Pointer *pointer;
|
||
uint32_t serial;
|
||
SeatClientInfo *info;
|
||
|
||
serial = wl_display_next_serial (compositor.wl_display);
|
||
info = ClientInfoForResource (seat, surface->resource);
|
||
|
||
if (!info)
|
||
return;
|
||
|
||
pointer = info->pointers.next;
|
||
|
||
for (; pointer != &info->pointers; pointer = pointer->next)
|
||
{
|
||
if (pointer->state & StateIsRaw)
|
||
{
|
||
wl_pointer_send_enter (pointer->resource, serial,
|
||
surface->resource,
|
||
wl_fixed_from_double (x),
|
||
wl_fixed_from_double (y));
|
||
pointer->info->last_enter_serial = serial;
|
||
}
|
||
|
||
/* If the seat is locked, don't send any motion events at
|
||
all. */
|
||
|
||
if (!(seat->flags & IsPointerLocked))
|
||
wl_pointer_send_motion (pointer->resource, time,
|
||
wl_fixed_from_double (x),
|
||
wl_fixed_from_double (y));
|
||
|
||
if (wl_resource_get_version (pointer->resource) >= 5)
|
||
wl_pointer_send_frame (pointer->resource);
|
||
|
||
pointer->state &= ~StateIsRaw;
|
||
}
|
||
}
|
||
|
||
static void
|
||
SendRelativeMotion (Seat *seat, Surface *surface, double dx, double dy,
|
||
Time time)
|
||
{
|
||
SeatClientInfo *info;
|
||
uint64_t microsecond_time;
|
||
RelativePointer *relative_pointer;
|
||
|
||
/* Unfortunately there is no way to determine whether or not a
|
||
valuator specified in a raw event really corresponds to pointer
|
||
motion, so we can't get unaccelerated deltas. It may be worth
|
||
wiring up raw events for the X.org server, since we do know how
|
||
it specifically behaves. */
|
||
|
||
info = ClientInfoForResource (seat, surface->resource);
|
||
|
||
if (!info)
|
||
return;
|
||
|
||
/* Hmm... */
|
||
microsecond_time = time * 1000;
|
||
|
||
relative_pointer = info->relative_pointers.next;
|
||
while (relative_pointer != &info->relative_pointers)
|
||
{
|
||
/* Send the relative deltas. */
|
||
XLRelativePointerSendRelativeMotion (relative_pointer->resource,
|
||
microsecond_time, dx, dy);
|
||
|
||
/* Move to the next relative pointer. */
|
||
relative_pointer = relative_pointer->next;
|
||
}
|
||
}
|
||
|
||
static void
|
||
SendLeave (Seat *seat, Surface *surface)
|
||
{
|
||
Pointer *pointer;
|
||
uint32_t serial;
|
||
SeatClientInfo *info;
|
||
|
||
serial = wl_display_next_serial (compositor.wl_display);
|
||
info = ClientInfoForResource (seat, surface->resource);
|
||
|
||
if (!info)
|
||
return;
|
||
|
||
pointer = info->pointers.next;
|
||
|
||
for (; pointer != &info->pointers; pointer = pointer->next)
|
||
{
|
||
wl_pointer_send_leave (pointer->resource, serial,
|
||
surface->resource);
|
||
|
||
/* Apparently this is necessary on both leave and enter
|
||
events. */
|
||
if (wl_resource_get_version (pointer->resource) >= 5)
|
||
wl_pointer_send_frame (pointer->resource);
|
||
}
|
||
}
|
||
|
||
static Bool
|
||
SendEnter (Seat *seat, Surface *surface, double x, double y)
|
||
{
|
||
Pointer *pointer;
|
||
uint32_t serial;
|
||
Bool sent;
|
||
SeatClientInfo *info;
|
||
|
||
serial = wl_display_next_serial (compositor.wl_display);
|
||
sent = False;
|
||
info = ClientInfoForResource (seat, surface->resource);
|
||
|
||
if (!info)
|
||
return False;
|
||
|
||
pointer = info->pointers.next;
|
||
|
||
if (pointer != &info->pointers)
|
||
/* If no pointer devices have been created, don't set the
|
||
serial. */
|
||
info->last_enter_serial = serial;
|
||
|
||
for (; pointer != &info->pointers; pointer = pointer->next)
|
||
{
|
||
pointer->state &= ~StateIsRaw;
|
||
|
||
wl_pointer_send_enter (pointer->resource, serial,
|
||
surface->resource,
|
||
wl_fixed_from_double (x),
|
||
wl_fixed_from_double (y));
|
||
|
||
/* Apparently this is necessary on both leave and enter
|
||
events. */
|
||
if (wl_resource_get_version (pointer->resource) >= 5)
|
||
wl_pointer_send_frame (pointer->resource);
|
||
|
||
sent = True;
|
||
}
|
||
|
||
return sent;
|
||
}
|
||
|
||
static void
|
||
SendButton (Seat *seat, Surface *surface, Time time,
|
||
uint32_t button, uint32_t state, double x,
|
||
double y)
|
||
{
|
||
Pointer *pointer;
|
||
uint32_t serial;
|
||
SeatClientInfo *info;
|
||
|
||
serial = wl_display_next_serial (compositor.wl_display);
|
||
|
||
/* This is later used to track the seat for resize operations. */
|
||
seat->last_button_serial = serial;
|
||
|
||
if (state == WL_POINTER_BUTTON_STATE_PRESSED)
|
||
/* This is used for popup grabs. */
|
||
seat->last_button_press_serial = serial;
|
||
|
||
info = ClientInfoForResource (seat, surface->resource);
|
||
|
||
if (!info)
|
||
return;
|
||
|
||
pointer = info->pointers.next;
|
||
|
||
for (; pointer != &info->pointers; pointer = pointer->next)
|
||
{
|
||
if (pointer->state & StateIsRaw)
|
||
{
|
||
wl_pointer_send_enter (pointer->resource, serial,
|
||
surface->resource,
|
||
wl_fixed_from_double (x),
|
||
wl_fixed_from_double (y));
|
||
pointer->info->last_enter_serial = serial;
|
||
}
|
||
|
||
wl_pointer_send_button (pointer->resource,
|
||
serial, time, button, state);
|
||
|
||
if (wl_resource_get_version (pointer->resource) >= 5)
|
||
wl_pointer_send_frame (pointer->resource);
|
||
|
||
pointer->state &= ~StateIsRaw;
|
||
}
|
||
}
|
||
|
||
static void
|
||
ClearGrabSurface (void *data)
|
||
{
|
||
Seat *seat;
|
||
|
||
seat = data;
|
||
|
||
/* Cancel the unmap callback. */
|
||
XLSurfaceCancelUnmapCallback (seat->grab_surface_callback);
|
||
|
||
seat->grab_surface = NULL;
|
||
seat->grab_surface_callback = NULL;
|
||
}
|
||
|
||
static void
|
||
SwapGrabSurface (Seat *seat, Surface *surface)
|
||
{
|
||
if (seat->grab_surface == surface)
|
||
return;
|
||
|
||
if (seat->grab_surface)
|
||
{
|
||
XLSurfaceCancelUnmapCallback (seat->grab_surface_callback);
|
||
seat->grab_surface = NULL;
|
||
seat->grab_surface_callback = NULL;
|
||
}
|
||
|
||
if (surface)
|
||
{
|
||
seat->grab_surface = surface;
|
||
seat->grab_surface_callback
|
||
= XLSurfaceRunAtUnmap (surface, ClearGrabSurface, seat);
|
||
}
|
||
}
|
||
|
||
static void
|
||
ClearLastSeenSurface (void *data)
|
||
{
|
||
Seat *seat;
|
||
|
||
seat = data;
|
||
|
||
/* The surface underneath the pointer was destroyed, so clear the
|
||
cursor. */
|
||
if (seat->cursor)
|
||
FreeCursor (seat->cursor);
|
||
|
||
seat->last_seen_surface = NULL;
|
||
seat->last_seen_surface_callback = NULL;
|
||
}
|
||
|
||
static void
|
||
UndefineCursorOn (Seat *seat, Surface *surface)
|
||
{
|
||
Window window;
|
||
|
||
window = XLWindowFromSurface (surface);
|
||
|
||
if (window == None)
|
||
return;
|
||
|
||
XIUndefineCursor (compositor.display,
|
||
seat->master_pointer,
|
||
window);
|
||
|
||
/* In addition to undefining the seat specific cursor, also undefine
|
||
the core cursor specified during window creation. */
|
||
XUndefineCursor (compositor.display, window);
|
||
}
|
||
|
||
/* Forward declaration. */
|
||
|
||
static void SendGesturePinchEnd (Seat *, Surface *, Time, int);
|
||
static void SendGestureSwipeEnd (Seat *, Surface *, Time, int);
|
||
|
||
static void
|
||
EnteredSurface (Seat *seat, Surface *surface, Time time,
|
||
double x, double y, Bool preserve_cursor)
|
||
{
|
||
Time gesture_time;
|
||
|
||
if (seat->grab_held && surface != seat->last_seen_surface)
|
||
/* If the seat is grabbed, delay this for later. */
|
||
return;
|
||
|
||
if (seat->last_seen_surface == surface)
|
||
return;
|
||
|
||
/* The surface currently entered changed (or will change). Cancel
|
||
any ongoing gestures. */
|
||
|
||
if (seat->flags & IsInPinchGesture
|
||
/* Not sure if this can actually be NULL here. */
|
||
&& seat->last_seen_surface)
|
||
{
|
||
/* If time is 0 (CurrentTime), then just use the last user
|
||
time. */
|
||
gesture_time = time ? time : seat->last_user_time.milliseconds;
|
||
|
||
/* Send the gesture end event. */
|
||
SendGesturePinchEnd (seat, seat->last_seen_surface,
|
||
gesture_time, 1);
|
||
|
||
/* And clear the flag so further updates are not sent. */
|
||
seat->flags &= ~IsInPinchGesture;
|
||
}
|
||
|
||
if (seat->flags & IsInSwipeGesture
|
||
/* Not sure if this can actually be NULL here. */
|
||
&& seat->last_seen_surface)
|
||
{
|
||
/* If time is 0 (CurrentTime), then just use the last user
|
||
time. */
|
||
gesture_time = time ? time : seat->last_user_time.milliseconds;
|
||
|
||
/* Send the gesture end event. */
|
||
SendGestureSwipeEnd (seat, seat->last_seen_surface,
|
||
gesture_time, 1);
|
||
|
||
/* And clear the flag so further updates are not sent. */
|
||
seat->flags &= ~IsInSwipeGesture;
|
||
}
|
||
|
||
if (seat->last_seen_surface)
|
||
{
|
||
if (seat->flags & IsDragging)
|
||
DragLeave (seat);
|
||
else
|
||
{
|
||
SendLeave (seat, seat->last_seen_surface);
|
||
|
||
/* The surface underneath the pointer was destroyed, so
|
||
clear the cursor. */
|
||
if (seat->cursor && !preserve_cursor)
|
||
FreeCursor (seat->cursor);
|
||
}
|
||
|
||
/* Cancel any pointer confinement. */
|
||
XLPointerBarrierLeft (seat, seat->last_seen_surface);
|
||
|
||
/* Clear the surface. */
|
||
XLSurfaceCancelRunOnFree (seat->last_seen_surface_callback);
|
||
seat->last_seen_surface = NULL;
|
||
seat->last_seen_surface_callback = NULL;
|
||
|
||
/* Mark the last surface motion coords as no longer set. */
|
||
seat->flags &= ~IsSurfaceCoordSet;
|
||
}
|
||
|
||
if (surface)
|
||
{
|
||
seat->last_seen_surface = surface;
|
||
seat->last_seen_surface_callback
|
||
= XLSurfaceRunOnFree (surface, ClearLastSeenSurface, seat);
|
||
seat->last_surface_x = x;
|
||
seat->last_surface_y = y;
|
||
|
||
if (seat->flags & IsDragging)
|
||
DragEnter (seat, surface, x, y);
|
||
else if (!SendEnter (seat, surface, x, y))
|
||
/* Apparently what is done by other compositors when no
|
||
wl_pointer object exists for the surface's client is to
|
||
revert back to the default cursor. */
|
||
UndefineCursorOn (seat, surface);
|
||
}
|
||
}
|
||
|
||
static void
|
||
TransformToSurface (Surface *surface, double event_x, double event_y,
|
||
double *view_x_out, double *view_y_out)
|
||
{
|
||
int int_x, int_y, x, y;
|
||
double view_x, view_y;
|
||
View *view;
|
||
|
||
/* Use the surface's view. */
|
||
view = surface->view;
|
||
|
||
/* Even though event_x and event_y are doubles, they cannot exceed
|
||
65535.0, so this cannot overflow. */
|
||
int_x = (int) event_x;
|
||
int_y = (int) event_y;
|
||
|
||
ViewTranslate (view, int_x, int_y, &x, &y);
|
||
|
||
/* Add the fractional part back to the final result. */
|
||
view_x = ((double) x) + event_x - int_x;
|
||
view_y = ((double) y) + event_y - int_y;
|
||
|
||
/* Finally, transform the coordinates by the global output
|
||
scale. */
|
||
*view_x_out = view_x / surface->factor;
|
||
*view_y_out = view_y / surface->factor;
|
||
}
|
||
|
||
static Bool
|
||
CanDeliverEvents (Seat *seat, Surface *dispatch)
|
||
{
|
||
if (!seat->grab_surface)
|
||
return True;
|
||
|
||
/* Otherwise, an owner-events grab is in effect; only dispatch
|
||
events to the client who owns the grab. */
|
||
return (wl_resource_get_client (dispatch->resource)
|
||
== wl_resource_get_client (seat->grab_surface->resource));
|
||
}
|
||
|
||
static void
|
||
TranslateCoordinates (Window source, Window target, double x, double y,
|
||
double *x_out, double *y_out)
|
||
{
|
||
Window child_return;
|
||
int int_x, int_y, t1, t2;
|
||
|
||
int_x = (int) x;
|
||
int_y = (int) y;
|
||
|
||
XTranslateCoordinates (compositor.display, source,
|
||
target, int_x, int_y, &t1, &t2,
|
||
&child_return);
|
||
|
||
/* Add the fractional part back. */
|
||
*x_out = (x - int_x) + t1;
|
||
*y_out = (y - int_y) + t2;
|
||
}
|
||
|
||
static Surface *
|
||
ComputeGrabPosition (Seat *seat, Surface *dispatch,
|
||
double *event_x, double *event_y)
|
||
{
|
||
Window toplevel, grab;
|
||
|
||
toplevel = XLWindowFromSurface (dispatch);
|
||
grab = XLWindowFromSurface (seat->grab_surface);
|
||
|
||
TranslateCoordinates (toplevel, grab, *event_x, *event_y,
|
||
event_x, event_y);
|
||
return seat->grab_surface;
|
||
}
|
||
|
||
static void
|
||
TranslateGrabPosition (Seat *seat, Window window, double *event_x,
|
||
double *event_y)
|
||
{
|
||
Window grab;
|
||
|
||
grab = XLWindowFromSurface (seat->grab_surface);
|
||
|
||
TranslateCoordinates (window, grab, *event_x, *event_y,
|
||
event_x, event_y);
|
||
return;
|
||
}
|
||
|
||
static void
|
||
HandleSubcompositorDestroy (void *data)
|
||
{
|
||
Seat *seat;
|
||
|
||
seat = data;
|
||
seat->last_seen_subcompositor = NULL;
|
||
seat->subcompositor_callback = NULL;
|
||
}
|
||
|
||
static void
|
||
DispatchEntryExit (Subcompositor *subcompositor, XIEnterEvent *event)
|
||
{
|
||
Seat *seat;
|
||
Surface *dispatch;
|
||
double x, y, event_x, event_y;
|
||
|
||
seat = XLLookUpAssoc (seats, event->deviceid);
|
||
|
||
if (!seat)
|
||
return;
|
||
|
||
/* Set the last seen subcompositor, or clear it on XI_Leave. The
|
||
last seen subcompositor is used to determine the surface to which
|
||
a grab will be released.
|
||
|
||
All entry and exit events must be respected here, including those
|
||
resulting from grabs! Otherwise, if some other client has
|
||
grabbed the pointer, last_seen_subcompositor will not be kept up
|
||
to date. */
|
||
|
||
if (event->evtype == XI_Leave
|
||
|| subcompositor != seat->last_seen_subcompositor)
|
||
{
|
||
if (seat->last_seen_subcompositor)
|
||
SubcompositorRemoveDestroyCallback (seat->subcompositor_callback);
|
||
|
||
seat->last_seen_subcompositor = NULL;
|
||
seat->subcompositor_callback = NULL;
|
||
|
||
if (event->evtype == XI_Enter)
|
||
{
|
||
/* Attach the new subcompositor. */
|
||
seat->last_seen_subcompositor = subcompositor;
|
||
seat->subcompositor_callback
|
||
= SubcompositorOnDestroy (subcompositor,
|
||
HandleSubcompositorDestroy,
|
||
seat);
|
||
|
||
/* Also set the window used. */
|
||
seat->last_seen_subcompositor_window = event->event;
|
||
}
|
||
}
|
||
|
||
if (event->mode == XINotifyUngrab
|
||
&& seat->grab_surface)
|
||
/* Any explicit grab was released, so release the grab surface as
|
||
well. */
|
||
SwapGrabSurface (seat, NULL);
|
||
|
||
if (event->mode == XINotifyUngrab
|
||
&& seat->flags & IsDragging)
|
||
/* The active grab was released. */
|
||
CancelDrag (seat, event->event, event->event_x,
|
||
event->event_y);
|
||
|
||
if (event->evtype == XI_Leave
|
||
&& (event->mode == XINotifyGrab
|
||
|| event->mode == XINotifyUngrab))
|
||
/* Ignore grab-related weirdness in XI_Leave events. */
|
||
return;
|
||
|
||
if (event->evtype == XI_Enter
|
||
&& event->mode == XINotifyGrab)
|
||
/* Accepting entry events with XINotifyGrab leads to bad results
|
||
when they arrive on a popup that has just been grabbed. */
|
||
return;
|
||
|
||
seat->flags &= ~IsWindowMenuShown;
|
||
seat->last_crossing_serial = event->serial;
|
||
|
||
if (event->evtype == XI_Leave)
|
||
dispatch = NULL;
|
||
else
|
||
dispatch = FindSurfaceUnder (subcompositor, event->event_x,
|
||
event->event_y);
|
||
|
||
event_x = event->event_x;
|
||
event_y = event->event_y;
|
||
|
||
if (seat->grab_surface)
|
||
{
|
||
/* If the grab surface is set, translate the coordinates to
|
||
it and use it instead. */
|
||
TranslateGrabPosition (seat, event->event,
|
||
&event_x, &event_y);
|
||
dispatch = seat->grab_surface;
|
||
|
||
goto after_dispatch_set;
|
||
}
|
||
|
||
if (!dispatch)
|
||
EnteredSurface (seat, NULL, event->time, 0, 0, False);
|
||
else
|
||
{
|
||
/* If dispatching during an active grab, and the event is for
|
||
the wrong client, translate the coordinates to the grab
|
||
window. */
|
||
if (!CanDeliverEvents (seat, dispatch))
|
||
dispatch = ComputeGrabPosition (seat, dispatch,
|
||
&event_x, &event_y);
|
||
|
||
after_dispatch_set:
|
||
|
||
TransformToSurface (dispatch, event_x, event_y, &x, &y);
|
||
EnteredSurface (seat, dispatch, event->time, x, y, False);
|
||
|
||
/* If this entry event was the result of a grab, also send
|
||
motion now, as a corresponding XI_Motion will not arrive
|
||
later. */
|
||
if (event->mode == XINotifyUngrab)
|
||
SendMotion (seat, dispatch, x, y, event->time);
|
||
}
|
||
|
||
seat->last_motion_x = event->root_x;
|
||
seat->last_motion_y = event->root_y;
|
||
}
|
||
|
||
static Bool
|
||
ProcessValuator (Seat *seat, XIDeviceEvent *event, ScrollValuator *valuator,
|
||
double value, double *total_x, double *total_y, int *flags)
|
||
{
|
||
double diff;
|
||
Bool valid;
|
||
|
||
valid = False;
|
||
|
||
if (seat->last_crossing_serial > valuator->enter_serial)
|
||
/* The valuator is out of date. Set its serial, value, and
|
||
return. */
|
||
goto out;
|
||
|
||
diff = value - valuator->value;
|
||
|
||
if (valuator->direction == Horizontal)
|
||
*total_x += diff / valuator->increment;
|
||
else
|
||
*total_y += diff / valuator->increment;
|
||
|
||
if (valuator->direction == Horizontal)
|
||
*flags |= AnyVerticalAxis;
|
||
else
|
||
*flags |= AnyHorizontalAxis;
|
||
|
||
valid = True;
|
||
|
||
out:
|
||
valuator->value = value;
|
||
valuator->enter_serial = event->serial;
|
||
|
||
return valid;
|
||
}
|
||
|
||
static ScrollValuator *
|
||
FindScrollValuator (Seat *seat, int number)
|
||
{
|
||
ScrollValuator *valuator;
|
||
|
||
valuator = seat->valuators;
|
||
|
||
for (; valuator; valuator = valuator->next)
|
||
{
|
||
if (valuator->number == number)
|
||
return valuator;
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
static void
|
||
InterpolateAxes (Surface *surface, DeviceInfo *info,
|
||
double movement_x, double movement_y,
|
||
double *x_out, double *y_out)
|
||
{
|
||
if (!info)
|
||
{
|
||
/* Multiply the deltas by 15 if no device was found. */
|
||
*x_out = movement_x * 15;
|
||
*y_out = movement_y * 15;
|
||
|
||
return;
|
||
}
|
||
|
||
/* Multiply these deltas by the scrolling pixel distance to obtain
|
||
the original delta. */
|
||
*x_out = movement_x * info->scroll_pixel_distance;
|
||
*y_out = movement_y * info->scroll_pixel_distance;
|
||
}
|
||
|
||
static void
|
||
SendScrollAxis (Seat *seat, Surface *surface, Time time,
|
||
double x, double y, double axis_x, double axis_y,
|
||
int flags, int sourceid)
|
||
{
|
||
Pointer *pointer;
|
||
uint32_t serial;
|
||
SeatClientInfo *info;
|
||
DeviceInfo *deviceinfo;
|
||
|
||
serial = wl_display_next_serial (compositor.wl_display);
|
||
info = ClientInfoForResource (seat, surface->resource);
|
||
|
||
if (!info)
|
||
return;
|
||
|
||
pointer = info->pointers.next;
|
||
deviceinfo = XLLookUpAssoc (devices, sourceid);
|
||
|
||
for (; pointer != &info->pointers; pointer = pointer->next)
|
||
{
|
||
if (pointer->state & StateIsRaw)
|
||
{
|
||
wl_pointer_send_enter (pointer->resource, serial,
|
||
surface->resource,
|
||
wl_fixed_from_double (x),
|
||
wl_fixed_from_double (y));
|
||
pointer->info->last_enter_serial = serial;
|
||
}
|
||
|
||
if (wl_resource_get_version (pointer->resource) < 8
|
||
/* Send pixel-wise axis events from devices that are most
|
||
likely touchpads. */
|
||
|| (deviceinfo
|
||
&& (deviceinfo->flags & DeviceCanFingerScroll
|
||
|| deviceinfo->flags & DeviceCanEdgeScroll)))
|
||
{
|
||
/* Interpolate the increment-relative axis values to pixel
|
||
values. */
|
||
InterpolateAxes (surface, deviceinfo, axis_x, axis_y,
|
||
&axis_x, &axis_y);
|
||
|
||
if (axis_x != 0.0)
|
||
wl_pointer_send_axis (pointer->resource, time,
|
||
WL_POINTER_AXIS_HORIZONTAL_SCROLL,
|
||
wl_fixed_from_double (axis_x));
|
||
|
||
if (axis_y != 0.0)
|
||
wl_pointer_send_axis (pointer->resource, time,
|
||
WL_POINTER_AXIS_VERTICAL_SCROLL,
|
||
wl_fixed_from_double (axis_y));
|
||
}
|
||
else
|
||
{
|
||
/* Send value120 events if they are available; those events
|
||
make more sense. */
|
||
|
||
if (axis_x != 0.0)
|
||
wl_pointer_send_axis_value120 (pointer->resource,
|
||
WL_POINTER_AXIS_HORIZONTAL_SCROLL,
|
||
axis_x * 120);
|
||
|
||
if (axis_y != 0.0)
|
||
wl_pointer_send_axis_value120 (pointer->resource,
|
||
WL_POINTER_AXIS_VERTICAL_SCROLL,
|
||
axis_y * 120);
|
||
}
|
||
|
||
if (axis_y == 0.0 && axis_x == 0.0)
|
||
{
|
||
/* This behavior is specific to a few X device
|
||
drivers! */
|
||
|
||
if (wl_resource_get_version (pointer->resource) >= 5)
|
||
{
|
||
/* wl_pointer_send_axis_stop is only present on
|
||
version 5 or later. */
|
||
|
||
if (flags & AnyVerticalAxis)
|
||
wl_pointer_send_axis_stop (pointer->resource, time,
|
||
WL_POINTER_AXIS_VERTICAL_SCROLL);
|
||
|
||
if (flags & AnyHorizontalAxis)
|
||
wl_pointer_send_axis_stop (pointer->resource, time,
|
||
WL_POINTER_AXIS_HORIZONTAL_SCROLL);
|
||
}
|
||
}
|
||
|
||
if (wl_resource_get_version (pointer->resource) >= 5)
|
||
{
|
||
/* Send the source of this axis movement if it can be
|
||
determined. We assume that axis movement from any
|
||
device capable finger or edge scrolling comes from a
|
||
touchpad. */
|
||
|
||
if (deviceinfo
|
||
&& (deviceinfo->flags & DeviceCanFingerScroll
|
||
|| deviceinfo->flags & DeviceCanEdgeScroll))
|
||
wl_pointer_send_axis_source (pointer->resource,
|
||
WL_POINTER_AXIS_SOURCE_FINGER);
|
||
}
|
||
|
||
if (axis_x != 0.0 || axis_y != 0.0
|
||
|| flags || pointer->state & StateIsRaw)
|
||
{
|
||
if (wl_resource_get_version (pointer->resource) >= 5)
|
||
wl_pointer_send_frame (pointer->resource);
|
||
}
|
||
|
||
pointer->state &= ~StateIsRaw;
|
||
}
|
||
}
|
||
|
||
static Bool
|
||
HandleValuatorMotion (Seat *seat, Surface *dispatch, double x, double y,
|
||
XIDeviceEvent *event)
|
||
{
|
||
double total_x, total_y, *values;
|
||
ScrollValuator *valuator;
|
||
int i, flags;
|
||
Bool value;
|
||
|
||
total_x = 0.0;
|
||
total_y = 0.0;
|
||
value = False;
|
||
values = event->valuators.values;
|
||
flags = 0;
|
||
|
||
for (i = 0; i < event->valuators.mask_len * 8; ++i)
|
||
{
|
||
if (!XIMaskIsSet (event->valuators.mask, i))
|
||
continue;
|
||
|
||
valuator = FindScrollValuator (seat, i);
|
||
|
||
if (!valuator)
|
||
/* We still have to increment values even if we don't know
|
||
about the valuator in question. */
|
||
goto next;
|
||
|
||
value |= ProcessValuator (seat, event, valuator, *values,
|
||
&total_x, &total_y, &flags);
|
||
|
||
next:
|
||
values++;
|
||
}
|
||
|
||
if (value && dispatch)
|
||
SendScrollAxis (seat, dispatch, event->time, x, y, total_x,
|
||
total_y, flags,
|
||
/* Also pass the event source device ID, which is
|
||
used in an attempt to determine the axis
|
||
source. */
|
||
event->sourceid);
|
||
return value;
|
||
}
|
||
|
||
static void
|
||
CheckPointerBarrier (Seat *seat, Surface *dispatch, double x, double y,
|
||
double root_x, double root_y)
|
||
{
|
||
/* Check if DISPATCH has a pointer confinement that would be
|
||
activated by this motion. */
|
||
|
||
XLPointerBarrierCheck (seat, dispatch, x, y, root_x, root_y);
|
||
}
|
||
|
||
static void
|
||
DispatchMotion (Subcompositor *subcompositor, XIDeviceEvent *xev)
|
||
{
|
||
Seat *seat;
|
||
Surface *dispatch, *actual_dispatch;
|
||
double x, y, event_x, event_y;
|
||
|
||
seat = XLLookUpAssoc (seats, xev->deviceid);
|
||
|
||
if (!seat)
|
||
return;
|
||
|
||
if (InterceptResizeEvent (seat, subcompositor, xev))
|
||
return;
|
||
|
||
/* Move the drag-and-drop icon window. */
|
||
if (seat->icon_surface)
|
||
XLMoveIconSurface (seat->icon_surface, xev->root_x,
|
||
xev->root_y);
|
||
|
||
/* Update information used for resize tracking. */
|
||
seat->its_root_x = xev->root_x;
|
||
seat->its_root_y = xev->root_y;
|
||
seat->its_press_time = xev->time;
|
||
|
||
/* Update the last user time. */
|
||
|
||
if (!xev->send_event)
|
||
seat->last_user_time = TimestampFromServerTime (xev->time);
|
||
|
||
actual_dispatch = FindSurfaceUnder (subcompositor, xev->event_x,
|
||
xev->event_y);
|
||
|
||
if (seat->grab_held)
|
||
dispatch = seat->last_seen_surface;
|
||
else
|
||
dispatch = actual_dispatch;
|
||
|
||
event_x = xev->event_x;
|
||
event_y = xev->event_y;
|
||
|
||
if (!dispatch)
|
||
{
|
||
if (seat->grab_surface)
|
||
{
|
||
/* If the grab surface is set, translate the coordinates to
|
||
it and use it instead. */
|
||
TranslateGrabPosition (seat, xev->event,
|
||
&event_x, &event_y);
|
||
dispatch = seat->grab_surface;
|
||
|
||
goto after_dispatch_set;
|
||
}
|
||
|
||
EnteredSurface (seat, dispatch, xev->time, 0, 0, False);
|
||
|
||
/* If drag and drop is in progress, handle "external" drag and
|
||
drop. */
|
||
if (seat->flags & IsDragging
|
||
&& seat->data_source)
|
||
XLDoDragMotion (seat, xev->root_x, xev->root_y);
|
||
|
||
return;
|
||
}
|
||
|
||
/* Update the outputs the pointer surface is currently displayed
|
||
inside, since it evidently moved. */
|
||
if (seat->cursor)
|
||
UpdateCursorOutput (seat->cursor, xev->root_x,
|
||
xev->root_y);
|
||
|
||
/* If dispatching during an active grab, and the event is for the
|
||
wrong client, translate the coordinates to the grab window. */
|
||
if (!CanDeliverEvents (seat, dispatch))
|
||
dispatch = ComputeGrabPosition (seat, dispatch,
|
||
&event_x, &event_y);
|
||
|
||
after_dispatch_set:
|
||
|
||
if (seat->flags & IsDragging
|
||
&& seat->data_source)
|
||
/* Inside a surface; cancel external drag and drop. */
|
||
XLDoDragLeave (seat);
|
||
|
||
TransformToSurface (dispatch, event_x, event_y, &x, &y);
|
||
EnteredSurface (seat, dispatch, xev->time, x, y, False);
|
||
|
||
if (!HandleValuatorMotion (seat, dispatch, x, y, xev))
|
||
{
|
||
if (seat->flags & IsDragging)
|
||
DragMotion (seat, dispatch, x, y, xev->time);
|
||
else
|
||
{
|
||
/* Send the motion event. */
|
||
SendMotion (seat, dispatch, x, y, xev->time);
|
||
|
||
/* Send relative motion. Relative motion is handled by
|
||
subtracting x and y from seat->last_surface_x and
|
||
seat->last_surface_y, unless pointer motion reporting is
|
||
locked, in which case XI barrier motion events are used
|
||
instead. */
|
||
if (x - seat->last_surface_x != 0.0
|
||
|| y - seat->last_surface_y != 0.0)
|
||
SendRelativeMotion (seat, dispatch, x - seat->last_surface_x,
|
||
y - seat->last_surface_y, xev->time);
|
||
|
||
/* Check if this motion would cause a pointer constraint to
|
||
activate. */
|
||
CheckPointerBarrier (seat, dispatch, x, y, xev->root_x,
|
||
xev->root_y);
|
||
}
|
||
|
||
/* Set the last movement location. */
|
||
seat->last_surface_x = x;
|
||
seat->last_surface_y = y;
|
||
seat->flags |= IsSurfaceCoordSet;
|
||
}
|
||
|
||
/* These values are for tracking the output that a cursor is in. */
|
||
|
||
seat->last_motion_x = xev->root_x;
|
||
seat->last_motion_y = xev->root_y;
|
||
}
|
||
|
||
static int
|
||
GetXButton (int detail)
|
||
{
|
||
switch (detail)
|
||
{
|
||
case Button1:
|
||
return BTN_LEFT;
|
||
|
||
case Button2:
|
||
return BTN_MIDDLE;
|
||
|
||
case Button3:
|
||
return BTN_RIGHT;
|
||
|
||
default:
|
||
return -1;
|
||
}
|
||
}
|
||
|
||
static void
|
||
CancelGrab1 (Seat *seat, Subcompositor *subcompositor,
|
||
Time time, double x, double y)
|
||
{
|
||
Surface *surface;
|
||
|
||
/* Look up the surface under subcompositor at x, y and enter it, in
|
||
response to implicit grab termination. */
|
||
|
||
surface = FindSurfaceUnder (subcompositor, x, y);
|
||
|
||
if (surface)
|
||
TransformToSurface (surface, x, y, &x, &y);
|
||
|
||
EnteredSurface (seat, surface, time, x, y, False);
|
||
}
|
||
|
||
static void
|
||
CancelGrab (Seat *seat, Time time, Window source, double x,
|
||
double y)
|
||
{
|
||
if (!seat->grab_held)
|
||
return;
|
||
|
||
if (--seat->grab_held)
|
||
return;
|
||
|
||
/* More or less how this works: translate x and y from source to
|
||
last_seen_subcompositor_window, and look up the surface in the
|
||
last_seen_subcompositor, if present. */
|
||
if (seat->last_seen_subcompositor)
|
||
{
|
||
/* Avoid translating coordinates if not necessary. */
|
||
if (source != seat->last_seen_subcompositor_window)
|
||
TranslateCoordinates (source, seat->last_seen_subcompositor_window,
|
||
x, y, &x, &y);
|
||
|
||
/* And cancel the grab. */
|
||
CancelGrab1 (seat, seat->last_seen_subcompositor, time, x, y);
|
||
}
|
||
else
|
||
/* Otherwise, just leave the surface. */
|
||
EnteredSurface (seat, NULL, time, 0, 0, False);
|
||
|
||
/* Cancel the unmap callback. */
|
||
XLSurfaceCancelUnmapCallback (seat->grab_unmap_callback);
|
||
seat->grab_unmap_callback = NULL;
|
||
}
|
||
|
||
static void
|
||
CancelGrabEarly (Seat *seat)
|
||
{
|
||
/* Do this to make sure the grab is immediately canceled. */
|
||
seat->grab_held = 1;
|
||
|
||
/* Cancelling the grab should also result in the unmap callback
|
||
being cancelled. */
|
||
CancelGrab (seat, seat->its_press_time,
|
||
DefaultRootWindow (compositor.display),
|
||
seat->its_root_x, seat->its_root_y);
|
||
}
|
||
|
||
static void
|
||
HandleGrabUnmapped (void *data)
|
||
{
|
||
CancelGrabEarly (data);
|
||
}
|
||
|
||
static void
|
||
LockSurfaceFocus (Seat *seat)
|
||
{
|
||
UnmapCallback *callback;
|
||
|
||
/* As long as an active grab is held, ignore the passive grab. */
|
||
if (seat->grab_surface)
|
||
return;
|
||
|
||
seat->grab_held++;
|
||
|
||
if (seat->grab_held == 1)
|
||
{
|
||
/* Also cancel the grab upon the surface being unmapped. */
|
||
callback = XLSurfaceRunAtUnmap (seat->last_seen_surface,
|
||
HandleGrabUnmapped, seat);
|
||
seat->grab_unmap_callback = callback;
|
||
}
|
||
}
|
||
|
||
static void
|
||
ClearLastButtonPressSurface (void *data)
|
||
{
|
||
Seat *seat;
|
||
|
||
seat = data;
|
||
|
||
seat->last_button_press_surface = NULL;
|
||
seat->last_button_press_surface_callback = NULL;
|
||
}
|
||
|
||
static void
|
||
SetButtonSurface (Seat *seat, Surface *surface)
|
||
{
|
||
DestroyCallback *callback;
|
||
|
||
if (surface == seat->last_button_press_surface)
|
||
return;
|
||
|
||
callback = seat->last_button_press_surface_callback;
|
||
|
||
if (seat->last_button_press_surface)
|
||
{
|
||
XLSurfaceCancelRunOnFree (callback);
|
||
seat->last_button_press_surface_callback = NULL;
|
||
seat->last_button_press_surface = NULL;
|
||
}
|
||
|
||
if (!surface)
|
||
return;
|
||
|
||
seat->last_button_press_surface = surface;
|
||
seat->last_button_press_surface_callback
|
||
= XLSurfaceRunOnFree (surface, ClearLastButtonPressSurface, seat);
|
||
}
|
||
|
||
static void
|
||
DispatchButton (Subcompositor *subcompositor, XIDeviceEvent *xev)
|
||
{
|
||
Seat *seat;
|
||
Surface *dispatch, *actual_dispatch;
|
||
double x, y, event_x, event_y;
|
||
int button;
|
||
uint32_t state;
|
||
|
||
if (xev->flags & XIPointerEmulated)
|
||
return;
|
||
|
||
seat = XLLookUpAssoc (seats, xev->deviceid);
|
||
|
||
if (!seat)
|
||
return;
|
||
|
||
if (InterceptResizeEvent (seat, subcompositor, xev))
|
||
return;
|
||
|
||
if (seat->flags & IsDragging)
|
||
{
|
||
DragButton (seat, xev);
|
||
return;
|
||
}
|
||
|
||
button = GetXButton (xev->detail);
|
||
|
||
if (button < 0)
|
||
return;
|
||
|
||
actual_dispatch = FindSurfaceUnder (subcompositor, xev->event_x,
|
||
xev->event_y);
|
||
|
||
if (seat->grab_held)
|
||
dispatch = seat->last_seen_surface;
|
||
else
|
||
dispatch = actual_dispatch;
|
||
|
||
event_x = xev->event_x;
|
||
event_y = xev->event_y;
|
||
|
||
if (!dispatch)
|
||
{
|
||
if (seat->grab_surface)
|
||
{
|
||
/* If the grab surface is set, translate the coordinates to
|
||
it and use it instead. */
|
||
TranslateGrabPosition (seat, xev->event,
|
||
&event_x, &event_y);
|
||
dispatch = seat->grab_surface;
|
||
|
||
goto after_dispatch_set;
|
||
}
|
||
|
||
EnteredSurface (seat, dispatch, xev->time, 0, 0, False);
|
||
return;
|
||
}
|
||
|
||
/* If dispatching during an active grab, and the event is for the
|
||
wrong client, translate the coordinates to the grab window. */
|
||
if (!CanDeliverEvents (seat, dispatch))
|
||
dispatch = ComputeGrabPosition (seat, dispatch,
|
||
&event_x, &event_y);
|
||
|
||
after_dispatch_set:
|
||
|
||
TransformToSurface (dispatch, xev->event_x, xev->event_y,
|
||
&x, &y);
|
||
EnteredSurface (seat, dispatch, xev->time, x, y,
|
||
False);
|
||
|
||
state = (xev->evtype == XI_ButtonPress
|
||
? WL_POINTER_BUTTON_STATE_PRESSED
|
||
: WL_POINTER_BUTTON_STATE_RELEASED);
|
||
|
||
SendButton (seat, dispatch, xev->time, button,
|
||
state, x, y);
|
||
|
||
if (xev->evtype == XI_ButtonPress)
|
||
{
|
||
/* These values are used for resize grip tracking. */
|
||
seat->its_root_x = lrint (xev->root_x);
|
||
seat->its_root_y = lrint (xev->root_y);
|
||
seat->its_press_time = xev->time;
|
||
seat->last_button = xev->detail;
|
||
|
||
SetButtonSurface (seat, dispatch);
|
||
}
|
||
|
||
if (xev->evtype == XI_ButtonPress)
|
||
LockSurfaceFocus (seat);
|
||
else
|
||
CancelGrab (seat, xev->time, xev->event,
|
||
xev->event_x, xev->event_y);
|
||
}
|
||
|
||
static void
|
||
DispatchKey (XIDeviceEvent *xev)
|
||
{
|
||
Seat *seat;
|
||
KeyCode keycode;
|
||
|
||
seat = XLLookUpAssoc (seats, xev->deviceid);
|
||
|
||
if (!seat)
|
||
return;
|
||
|
||
/* Report key state changes here. A side effect is that the key
|
||
state reported in enter events will include grabbed keys, but
|
||
that seems to be an acceptable tradeoff. */
|
||
|
||
if (seat->focus_surface)
|
||
{
|
||
keycode = 0;
|
||
|
||
if (input_funcs
|
||
&& seat->flags & IsTextInputSeat
|
||
&& input_funcs->filter_input (seat, seat->focus_surface,
|
||
xev, &keycode))
|
||
/* The input method decided to filter the key. */
|
||
return;
|
||
|
||
/* Ignore repeated keys. */
|
||
if (xev->flags & XIKeyRepeat)
|
||
return;
|
||
|
||
/* If the input method specified a keycode, use it. */
|
||
if (!keycode)
|
||
keycode = xev->detail;
|
||
|
||
if (xev->evtype == XI_KeyPress)
|
||
SendKeyboardKey (seat, seat->focus_surface,
|
||
xev->time, WaylandKeycode (keycode),
|
||
WL_KEYBOARD_KEY_STATE_PRESSED);
|
||
else
|
||
SendKeyboardKey (seat, seat->focus_surface,
|
||
xev->time, WaylandKeycode (keycode),
|
||
WL_KEYBOARD_KEY_STATE_RELEASED);
|
||
}
|
||
}
|
||
|
||
static void
|
||
DispatchBarrierHit (XIBarrierEvent *barrier)
|
||
{
|
||
Seat *seat;
|
||
|
||
seat = XLLookUpAssoc (seats, barrier->deviceid);
|
||
|
||
if (!seat)
|
||
return;
|
||
|
||
/* Report a barrier hit event as relative motion. */
|
||
|
||
if (seat->last_seen_surface)
|
||
SendRelativeMotion (seat, seat->last_seen_surface,
|
||
barrier->dx, barrier->dy,
|
||
barrier->time);
|
||
|
||
/* Set the last user time. */
|
||
|
||
if (!barrier->send_event)
|
||
seat->last_user_time = TimestampFromServerTime (barrier->time);
|
||
}
|
||
|
||
static void
|
||
SendGesturePinchBegin (Seat *seat, Surface *dispatch, Time time,
|
||
int detail)
|
||
{
|
||
PinchGesture *gesture;
|
||
uint32_t serial;
|
||
SeatClientInfo *info;
|
||
|
||
serial = wl_display_next_serial (compositor.wl_display);
|
||
info = ClientInfoForResource (seat, dispatch->resource);
|
||
|
||
if (!info)
|
||
return;
|
||
|
||
gesture = info->pinch_gestures.next;
|
||
|
||
for (; gesture != &info->pinch_gestures; gesture = gesture->next)
|
||
zwp_pointer_gesture_pinch_v1_send_begin (gesture->resource,
|
||
serial, time,
|
||
dispatch->resource,
|
||
detail);
|
||
}
|
||
|
||
static void
|
||
SendGesturePinchUpdate (Seat *seat, Surface *dispatch, Time time,
|
||
double dx, double dy, double scale, double rotation)
|
||
{
|
||
PinchGesture *gesture;
|
||
SeatClientInfo *info;
|
||
|
||
info = ClientInfoForResource (seat, dispatch->resource);
|
||
|
||
if (!info)
|
||
return;
|
||
|
||
gesture = info->pinch_gestures.next;
|
||
|
||
for (; gesture != &info->pinch_gestures; gesture = gesture->next)
|
||
zwp_pointer_gesture_pinch_v1_send_update (gesture->resource,
|
||
time,
|
||
wl_fixed_from_double (dx),
|
||
wl_fixed_from_double (dy),
|
||
wl_fixed_from_double (scale),
|
||
wl_fixed_from_double (rotation));
|
||
}
|
||
|
||
static void
|
||
SendGesturePinchEnd (Seat *seat, Surface *dispatch, Time time, int cancelled)
|
||
{
|
||
PinchGesture *gesture;
|
||
SeatClientInfo *info;
|
||
uint32_t serial;
|
||
|
||
info = ClientInfoForResource (seat, dispatch->resource);
|
||
|
||
if (!info)
|
||
return;
|
||
|
||
gesture = info->pinch_gestures.next;
|
||
serial = wl_display_next_serial (compositor.wl_display);
|
||
|
||
for (; gesture != &info->pinch_gestures; gesture = gesture->next)
|
||
zwp_pointer_gesture_pinch_v1_send_end (gesture->resource,
|
||
serial, time, cancelled);
|
||
}
|
||
|
||
static void
|
||
DispatchGesturePinch (Subcompositor *subcompositor, XIGesturePinchEvent *pinch)
|
||
{
|
||
Seat *seat;
|
||
Surface *dispatch, *actual_dispatch;
|
||
double x, y, event_x, event_y;
|
||
|
||
seat = XLLookUpAssoc (seats, pinch->deviceid);
|
||
|
||
if (!seat)
|
||
return;
|
||
|
||
/* Move the icon surface. */
|
||
if (seat->icon_surface)
|
||
XLMoveIconSurface (seat->icon_surface, pinch->root_x,
|
||
pinch->root_y);
|
||
|
||
/* Update information used for resize tracking. */
|
||
seat->its_root_x = pinch->root_x;
|
||
seat->its_root_y = pinch->root_y;
|
||
seat->its_press_time = pinch->time;
|
||
|
||
/* Update the last user time. */
|
||
|
||
if (!pinch->send_event)
|
||
seat->last_user_time = TimestampFromServerTime (pinch->time);
|
||
|
||
/* Now find the dispatch surface so we can enter it if required.
|
||
Most of this code is copied from DispatchMotion; it should
|
||
probably be moved to a separate function. */
|
||
actual_dispatch = FindSurfaceUnder (subcompositor, pinch->event_x,
|
||
pinch->event_y);
|
||
|
||
if (seat->grab_held)
|
||
dispatch = seat->last_seen_surface;
|
||
else
|
||
dispatch = actual_dispatch;
|
||
|
||
event_x = pinch->event_y;
|
||
event_y = pinch->event_y;
|
||
|
||
if (!dispatch)
|
||
{
|
||
if (seat->grab_surface)
|
||
{
|
||
/* If the grab surface is set, translate the coordinates to
|
||
it and use it instead. */
|
||
TranslateGrabPosition (seat, pinch->event,
|
||
&event_x, &event_y);
|
||
dispatch = seat->grab_surface;
|
||
|
||
goto after_dispatch_set;
|
||
}
|
||
|
||
EnteredSurface (seat, dispatch, pinch->time, 0, 0, False);
|
||
return;
|
||
}
|
||
|
||
after_dispatch_set:
|
||
TransformToSurface (dispatch, event_x, event_y, &x, &y);
|
||
EnteredSurface (seat, dispatch, pinch->time, x, y, False);
|
||
|
||
/* Now do the actual event dispatch. */
|
||
switch (pinch->evtype)
|
||
{
|
||
case XI_GesturePinchBegin:
|
||
/* Send a motion event, in case the position changed. */
|
||
SendMotion (seat, dispatch, x, y, pinch->time);
|
||
|
||
/* Send a begin event. */
|
||
SendGesturePinchBegin (seat, dispatch, pinch->time, pinch->detail);
|
||
|
||
/* Say that the seat is in the middle of a pinch gesture, so it
|
||
can be cancelled should the pointer move out of this
|
||
surface. */
|
||
seat->flags |= IsInPinchGesture;
|
||
break;
|
||
|
||
case XI_GesturePinchUpdate:
|
||
/* The gesture sequence was cancelled for some other reason. */
|
||
if (!(seat->flags & IsInPinchGesture))
|
||
return;
|
||
|
||
/* Send an update event. */
|
||
SendGesturePinchUpdate (seat, dispatch, pinch->time,
|
||
pinch->delta_x, pinch->delta_y,
|
||
pinch->scale, pinch->delta_angle);
|
||
break;
|
||
|
||
case XI_GesturePinchEnd:
|
||
/* The gesture sequence was cancelled for some other reason. */
|
||
if (!(seat->flags & IsInPinchGesture))
|
||
return;
|
||
|
||
/* Send an end event. */
|
||
SendGesturePinchEnd (seat, dispatch, pinch->time,
|
||
pinch->flags & XIGesturePinchEventCancelled);
|
||
break;
|
||
}
|
||
}
|
||
|
||
static void
|
||
SendGestureSwipeBegin (Seat *seat, Surface *dispatch, Time time,
|
||
int detail)
|
||
{
|
||
SwipeGesture *gesture;
|
||
uint32_t serial;
|
||
SeatClientInfo *info;
|
||
|
||
serial = wl_display_next_serial (compositor.wl_display);
|
||
info = ClientInfoForResource (seat, dispatch->resource);
|
||
|
||
if (!info)
|
||
return;
|
||
|
||
gesture = info->swipe_gestures.next;
|
||
|
||
for (; gesture != &info->swipe_gestures; gesture = gesture->next)
|
||
zwp_pointer_gesture_swipe_v1_send_begin (gesture->resource,
|
||
serial, time,
|
||
dispatch->resource,
|
||
detail);
|
||
}
|
||
|
||
static void
|
||
SendGestureSwipeUpdate (Seat *seat, Surface *dispatch, Time time,
|
||
double dx, double dy)
|
||
{
|
||
SwipeGesture *gesture;
|
||
SeatClientInfo *info;
|
||
|
||
info = ClientInfoForResource (seat, dispatch->resource);
|
||
|
||
if (!info)
|
||
return;
|
||
|
||
gesture = info->swipe_gestures.next;
|
||
|
||
for (; gesture != &info->swipe_gestures; gesture = gesture->next)
|
||
zwp_pointer_gesture_swipe_v1_send_update (gesture->resource,
|
||
time,
|
||
wl_fixed_from_double (dx),
|
||
wl_fixed_from_double (dy));
|
||
}
|
||
|
||
static void
|
||
SendGestureSwipeEnd (Seat *seat, Surface *dispatch, Time time, int cancelled)
|
||
{
|
||
SwipeGesture *gesture;
|
||
SeatClientInfo *info;
|
||
uint32_t serial;
|
||
|
||
info = ClientInfoForResource (seat, dispatch->resource);
|
||
|
||
if (!info)
|
||
return;
|
||
|
||
gesture = info->swipe_gestures.next;
|
||
serial = wl_display_next_serial (compositor.wl_display);
|
||
|
||
for (; gesture != &info->swipe_gestures; gesture = gesture->next)
|
||
zwp_pointer_gesture_swipe_v1_send_end (gesture->resource,
|
||
serial, time, cancelled);
|
||
}
|
||
|
||
static void
|
||
DispatchGestureSwipe (Subcompositor *subcompositor, XIGestureSwipeEvent *swipe)
|
||
{
|
||
Seat *seat;
|
||
Surface *dispatch, *actual_dispatch;
|
||
double x, y, event_x, event_y;
|
||
|
||
seat = XLLookUpAssoc (seats, swipe->deviceid);
|
||
|
||
if (!seat)
|
||
return;
|
||
|
||
/* Move the icon surface. */
|
||
if (seat->icon_surface)
|
||
XLMoveIconSurface (seat->icon_surface, swipe->root_x,
|
||
swipe->root_y);
|
||
|
||
/* Update information used for resize tracking. */
|
||
seat->its_root_x = swipe->root_x;
|
||
seat->its_root_y = swipe->root_y;
|
||
seat->its_press_time = swipe->time;
|
||
|
||
/* Update the last user time. */
|
||
|
||
if (!swipe->send_event)
|
||
seat->last_user_time = TimestampFromServerTime (swipe->time);
|
||
|
||
/* Now find the dispatch surface so we can enter it if required.
|
||
Most of this code is copied from DispatchMotion; it should
|
||
probably be moved to a separate function. */
|
||
actual_dispatch = FindSurfaceUnder (subcompositor, swipe->event_x,
|
||
swipe->event_y);
|
||
|
||
if (seat->grab_held)
|
||
dispatch = seat->last_seen_surface;
|
||
else
|
||
dispatch = actual_dispatch;
|
||
|
||
event_x = swipe->event_y;
|
||
event_y = swipe->event_y;
|
||
|
||
if (!dispatch)
|
||
{
|
||
if (seat->grab_surface)
|
||
{
|
||
/* If the grab surface is set, translate the coordinates to
|
||
it and use it instead. */
|
||
TranslateGrabPosition (seat, swipe->event,
|
||
&event_x, &event_y);
|
||
dispatch = seat->grab_surface;
|
||
|
||
goto after_dispatch_set;
|
||
}
|
||
|
||
EnteredSurface (seat, dispatch, swipe->time, 0, 0, False);
|
||
return;
|
||
}
|
||
|
||
after_dispatch_set:
|
||
TransformToSurface (dispatch, event_x, event_y, &x, &y);
|
||
EnteredSurface (seat, dispatch, swipe->time, x, y, False);
|
||
|
||
/* Now do the actual event dispatch. */
|
||
switch (swipe->evtype)
|
||
{
|
||
case XI_GestureSwipeBegin:
|
||
/* Send a motion event, in case the position changed. */
|
||
SendMotion (seat, dispatch, x, y, swipe->time);
|
||
|
||
/* Send a begin event. */
|
||
SendGestureSwipeBegin (seat, dispatch, swipe->time, swipe->detail);
|
||
|
||
/* Say that the seat is in the middle of a swipe gesture, so it
|
||
can be cancelled should the pointer move out of this
|
||
surface. */
|
||
seat->flags |= IsInSwipeGesture;
|
||
break;
|
||
|
||
case XI_GestureSwipeUpdate:
|
||
/* The gesture sequence was cancelled for some other reason. */
|
||
if (!(seat->flags & IsInSwipeGesture))
|
||
return;
|
||
|
||
/* Send an update event. */
|
||
SendGestureSwipeUpdate (seat, dispatch, swipe->time,
|
||
swipe->delta_x, swipe->delta_y);
|
||
break;
|
||
|
||
case XI_GestureSwipeEnd:
|
||
/* The gesture sequence was cancelled for some other reason. */
|
||
if (!(seat->flags & IsInSwipeGesture))
|
||
return;
|
||
|
||
/* Send an end event. */
|
||
SendGestureSwipeEnd (seat, dispatch, swipe->time,
|
||
swipe->flags & XIGestureSwipeEventCancelled);
|
||
break;
|
||
}
|
||
}
|
||
|
||
static void
|
||
WriteKeymap (void)
|
||
{
|
||
FILE *file;
|
||
XkbFileInfo result;
|
||
Bool ok;
|
||
int fd;
|
||
|
||
if (keymap_fd != -1)
|
||
close (keymap_fd);
|
||
|
||
keymap_fd = XLOpenShm ();
|
||
|
||
if (keymap_fd < 0)
|
||
{
|
||
fprintf (stderr, "Failed to allocate keymap fd\n");
|
||
exit (1);
|
||
}
|
||
|
||
memset (&result, 0, sizeof result);
|
||
result.type = XkmKeymapFile;
|
||
result.xkb = xkb_desc;
|
||
|
||
fd = fcntl (keymap_fd, F_DUPFD_CLOEXEC, 0);
|
||
|
||
if (fd < 0)
|
||
{
|
||
perror ("fcntl");
|
||
exit (1);
|
||
}
|
||
|
||
file = fdopen (fd, "w");
|
||
|
||
if (!file)
|
||
{
|
||
perror ("fdopen");
|
||
exit (1);
|
||
}
|
||
|
||
ok = XkbWriteXKBFile (file, &result,
|
||
/* libxkbcommon doesn't read comments in
|
||
virtual_modifier lines. */
|
||
False, NULL, NULL);
|
||
|
||
if (!ok)
|
||
fprintf (stderr, "Warning: the XKB keymap could not be written\n"
|
||
"Programs might not continue to interpret keyboard input"
|
||
" correctly.\n");
|
||
|
||
fclose (file);
|
||
}
|
||
|
||
static void
|
||
AfterMapUpdate (void)
|
||
{
|
||
if (XkbGetIndicatorMap (compositor.display, ~0, xkb_desc) != Success)
|
||
{
|
||
fprintf (stderr, "Could not load indicator map\n");
|
||
exit (1);
|
||
}
|
||
|
||
if (XkbGetControls (compositor.display,
|
||
XkbAllControlsMask, xkb_desc) != Success)
|
||
{
|
||
fprintf (stderr, "Could not load keyboard controls\n");
|
||
exit (1);
|
||
}
|
||
|
||
if (XkbGetCompatMap (compositor.display,
|
||
XkbAllCompatMask, xkb_desc) != Success)
|
||
{
|
||
fprintf (stderr, "Could not load compatibility map\n");
|
||
exit (1);
|
||
}
|
||
|
||
if (XkbGetNames (compositor.display,
|
||
XkbAllNamesMask, xkb_desc) != Success)
|
||
{
|
||
fprintf (stderr, "Could not load names\n");
|
||
exit (1);
|
||
}
|
||
}
|
||
|
||
static void
|
||
UpdateKeymapInfo (void)
|
||
{
|
||
XLList *tem;
|
||
Seat *seat;
|
||
Keyboard *keyboard;
|
||
|
||
for (tem = live_seats; tem; tem = tem->next)
|
||
{
|
||
seat = tem->data;
|
||
|
||
if (!seat->key_pressed)
|
||
/* max_keycode is small enough for this to not matter
|
||
memory-wise. */
|
||
seat->key_pressed
|
||
= XLCalloc (MaskLen (xkb_desc->max_key_code
|
||
- xkb_desc->min_key_code), 1);
|
||
else
|
||
seat->key_pressed
|
||
= XLRealloc (seat->key_pressed,
|
||
MaskLen (xkb_desc->max_key_code
|
||
- xkb_desc->min_key_code));
|
||
|
||
for (keyboard = seat->keyboards.next1;
|
||
keyboard != &seat->keyboards;
|
||
keyboard = keyboard->next1)
|
||
UpdateSingleKeyboard (keyboard);
|
||
}
|
||
}
|
||
|
||
static void
|
||
SetupKeymap (void)
|
||
{
|
||
int xkb_major, xkb_minor, xkb_op, xkb_error_code;
|
||
xkb_major = XkbMajorVersion;
|
||
xkb_minor = XkbMinorVersion;
|
||
|
||
if (!XkbLibraryVersion (&xkb_major, &xkb_minor)
|
||
|| !XkbQueryExtension (compositor.display, &xkb_op, &xkb_event_type,
|
||
&xkb_error_code, &xkb_major, &xkb_minor))
|
||
{
|
||
fprintf (stderr, "Failed to set up Xkb\n");
|
||
exit (1);
|
||
}
|
||
|
||
xkb_desc = XkbGetMap (compositor.display,
|
||
XkbAllMapComponentsMask,
|
||
XkbUseCoreKbd);
|
||
|
||
if (!xkb_desc)
|
||
{
|
||
fprintf (stderr, "Failed to retrieve keymap from X server\n");
|
||
exit (1);
|
||
}
|
||
|
||
AfterMapUpdate ();
|
||
WriteKeymap ();
|
||
|
||
XkbSelectEvents (compositor.display, XkbUseCoreKbd,
|
||
XkbMapNotifyMask | XkbNewKeyboardNotifyMask,
|
||
XkbMapNotifyMask | XkbNewKeyboardNotifyMask);
|
||
UpdateKeymapInfo ();
|
||
}
|
||
|
||
static Bool
|
||
HandleXkbEvent (XkbEvent *event)
|
||
{
|
||
Seat *seat;
|
||
|
||
if (event->any.xkb_type == XkbMapNotify
|
||
|| event->any.xkb_type == XkbNewKeyboardNotify)
|
||
{
|
||
XkbRefreshKeyboardMapping (&event->map);
|
||
XkbFreeKeyboard (xkb_desc, XkbAllMapComponentsMask,
|
||
True);
|
||
|
||
xkb_desc = XkbGetMap (compositor.display,
|
||
XkbAllMapComponentsMask,
|
||
XkbUseCoreKbd);
|
||
|
||
if (!xkb_desc)
|
||
{
|
||
fprintf (stderr, "Failed to retrieve keymap from X server\n");
|
||
exit (1);
|
||
}
|
||
|
||
AfterMapUpdate ();
|
||
WriteKeymap ();
|
||
UpdateKeymapInfo ();
|
||
|
||
return True;
|
||
}
|
||
else if (event->any.xkb_type == XkbStateNotify)
|
||
{
|
||
/* Look up the seat to which this event corresponds. */
|
||
seat = XLLookUpAssoc (seats, event->state.device);
|
||
|
||
/* And update the modifiers for that seat. */
|
||
if (seat)
|
||
UpdateModifiersForSeat (seat,
|
||
event->state.base_mods,
|
||
event->state.locked_mods,
|
||
event->state.latched_mods,
|
||
event->state.base_group,
|
||
event->state.locked_group,
|
||
event->state.latched_group,
|
||
event->state.group);
|
||
return True;
|
||
}
|
||
|
||
return False;
|
||
}
|
||
|
||
static Seat *
|
||
IdentifySeat (WhatEdge *edge, uint32_t serial)
|
||
{
|
||
Seat *seat;
|
||
XLList *tem;
|
||
|
||
for (tem = live_seats; tem; tem = tem->next)
|
||
{
|
||
seat = tem->data;
|
||
|
||
if (seat->last_button_serial == serial
|
||
|| seat->last_button_press_serial == serial)
|
||
{
|
||
/* This serial belongs to a button press. */
|
||
*edge = APointerEdge;
|
||
return seat;
|
||
}
|
||
|
||
if (seat->last_keyboard_serial == serial)
|
||
{
|
||
/* This serial belongs to a keyboard press. */
|
||
*edge = AKeyboardEdge;
|
||
return seat;
|
||
}
|
||
}
|
||
|
||
/* No seat was found. */
|
||
return NULL;
|
||
}
|
||
|
||
static Timestamp
|
||
GetLastUserTime (Seat *seat)
|
||
{
|
||
return seat->last_user_time;
|
||
}
|
||
|
||
static Bool
|
||
HandleKeyboardEdge (Seat *seat, Surface *target, uint32_t serial,
|
||
ResizeEdge edge)
|
||
{
|
||
Surface *surface;
|
||
XEvent msg;
|
||
|
||
surface = seat->last_button_press_surface;
|
||
|
||
if (!surface || surface != target)
|
||
return False;
|
||
|
||
memset (&msg, 0, sizeof msg);
|
||
msg.xclient.type = ClientMessage;
|
||
msg.xclient.window = XLWindowFromSurface (surface);
|
||
msg.xclient.format = 32;
|
||
msg.xclient.message_type = _NET_WM_MOVERESIZE;
|
||
msg.xclient.data.l[0] = seat->its_root_x;
|
||
msg.xclient.data.l[1] = seat->its_root_y;
|
||
msg.xclient.data.l[2] = edge;
|
||
msg.xclient.data.l[3] = seat->last_button;
|
||
msg.xclient.data.l[4] = edge == MoveEdge ? 10 : 9;
|
||
|
||
/* Release all grabs to the pointer device in question. */
|
||
XIUngrabDevice (compositor.display, seat->master_pointer,
|
||
seat->its_press_time);
|
||
|
||
/* Also release all grabs to the keyboard device. */
|
||
XIUngrabDevice (compositor.display, seat->master_keyboard,
|
||
seat->its_press_time);
|
||
|
||
/* Clear the grab immediately since it is no longer used. */
|
||
|
||
if (seat->grab_held)
|
||
CancelGrabEarly (seat);
|
||
|
||
/* Send leave to any surface that the pointer is currently within.
|
||
The position of the pointer is then restored upon entry. */
|
||
EnteredSurface (seat, NULL, seat->its_press_time, 0, 0, False);
|
||
|
||
/* Send the message to the window manager. */
|
||
XSendEvent (compositor.display,
|
||
DefaultRootWindow (compositor.display),
|
||
False,
|
||
SubstructureRedirectMask | SubstructureNotifyMask,
|
||
&msg);
|
||
|
||
/* There's no way to determine whether or not a keyboard resize has
|
||
ended. */
|
||
return False;
|
||
}
|
||
|
||
static void
|
||
HandleResizeUnmapped (void *data)
|
||
{
|
||
Seat *seat;
|
||
|
||
seat = data;
|
||
CancelResizeOperation (seat, seat->resize_time,
|
||
NULL, NULL);
|
||
}
|
||
|
||
static Bool
|
||
FakePointerEdge (Seat *seat, Surface *target, uint32_t serial,
|
||
ResizeEdge edge)
|
||
{
|
||
Cursor cursor;
|
||
Status state;
|
||
Window window;
|
||
XIEventMask mask;
|
||
size_t length;
|
||
|
||
if (edge == NoneEdge)
|
||
return False;
|
||
|
||
if (seat->resize_surface)
|
||
/* Some surface is already being resized. Prohibit this resize
|
||
request. */
|
||
return False;
|
||
|
||
window = XLWindowFromSurface (target);
|
||
|
||
if (window == None)
|
||
/* No window exists. */
|
||
return False;
|
||
|
||
seat->resize_start_root_x = seat->its_root_x;
|
||
seat->resize_start_root_y = seat->its_root_y;
|
||
|
||
seat->resize_last_root_x = seat->its_root_x;
|
||
seat->resize_last_root_y = seat->its_root_y;
|
||
|
||
/* Get an appropriate cursor. */
|
||
cursor = (seat->cursor ? seat->cursor->cursor : None);
|
||
|
||
/* Get the dimensions of the surface when it was first seen.
|
||
This can fail if the surface does not support the operation. */
|
||
if (!XLSurfaceGetResizeDimensions (target, &seat->resize_width,
|
||
&seat->resize_height))
|
||
return False;
|
||
|
||
/* Set up the event mask for the pointer grab. */
|
||
length = XIMaskLen (XI_LASTEVENT);
|
||
mask.mask = alloca (length);
|
||
mask.mask_len = length;
|
||
mask.deviceid = XIAllMasterDevices;
|
||
|
||
memset (mask.mask, 0, length);
|
||
|
||
XISetMask (mask.mask, XI_FocusIn);
|
||
XISetMask (mask.mask, XI_FocusOut);
|
||
XISetMask (mask.mask, XI_Enter);
|
||
XISetMask (mask.mask, XI_Leave);
|
||
XISetMask (mask.mask, XI_Motion);
|
||
XISetMask (mask.mask, XI_ButtonPress);
|
||
XISetMask (mask.mask, XI_ButtonRelease);
|
||
|
||
if (!(seat->flags & IsTestSeat))
|
||
{
|
||
/* Grab the pointer, and don't let go until the button is
|
||
released. */
|
||
state = XIGrabDevice (compositor.display, seat->master_pointer,
|
||
window, seat->its_press_time, cursor,
|
||
XIGrabModeAsync, XIGrabModeAsync, False, &mask);
|
||
|
||
if (state != Success)
|
||
return False;
|
||
}
|
||
|
||
/* On the other hand, cancel focus locking and leave the surface,
|
||
since we will not be reporting motion events until the resize
|
||
operation completes. */
|
||
|
||
if (seat->grab_held)
|
||
{
|
||
CancelGrabEarly (seat);
|
||
|
||
/* CancelGrabEarly may not have left the focus surface if the
|
||
pointer remains on it. Force a leave event to be sent. */
|
||
EnteredSurface (seat, NULL, seat->its_press_time, 0, 0, False);
|
||
}
|
||
|
||
/* Set the surface as the surface undergoing resize. */
|
||
seat->resize_surface = target;
|
||
seat->resize_surface_callback
|
||
= XLSurfaceRunAtUnmap (seat->resize_surface,
|
||
HandleResizeUnmapped, seat);
|
||
seat->resize_axis_flags = resize_edges[edge];
|
||
seat->resize_button = seat->last_button;
|
||
seat->resize_time = seat->its_press_time;
|
||
|
||
return True;
|
||
}
|
||
|
||
static Bool
|
||
HandlePointerEdge (Seat *seat, Surface *target, uint32_t serial,
|
||
ResizeEdge edge)
|
||
{
|
||
Surface *surface;
|
||
XEvent msg;
|
||
|
||
surface = seat->last_button_press_surface;
|
||
|
||
if (!surface || surface != target)
|
||
return False;
|
||
|
||
if (!XLWmSupportsHint (_NET_WM_MOVERESIZE)
|
||
|| (seat->flags & IsTestSeat)
|
||
|| getenv ("USE_BUILTIN_RESIZE"))
|
||
return FakePointerEdge (seat, target, serial, edge);
|
||
|
||
memset (&msg, 0, sizeof msg);
|
||
msg.xclient.type = ClientMessage;
|
||
msg.xclient.window = XLWindowFromSurface (surface);
|
||
msg.xclient.format = 32;
|
||
msg.xclient.message_type = _NET_WM_MOVERESIZE;
|
||
msg.xclient.data.l[0] = seat->its_root_x;
|
||
msg.xclient.data.l[1] = seat->its_root_y;
|
||
msg.xclient.data.l[2] = edge;
|
||
msg.xclient.data.l[3] = seat->last_button;
|
||
msg.xclient.data.l[4] = 1; /* Source indication. */
|
||
|
||
/* Release all grabs to the pointer device in question. */
|
||
XIUngrabDevice (compositor.display, seat->master_pointer,
|
||
seat->its_press_time);
|
||
|
||
/* Also clear the core grab, even though it's not used anywhere. */
|
||
XUngrabPointer (compositor.display, seat->its_press_time);
|
||
|
||
/* Clear the grab immediately since it is no longer used. */
|
||
if (seat->grab_held)
|
||
CancelGrabEarly (seat);
|
||
|
||
/* Send the message to the window manager. */
|
||
XSendEvent (compositor.display,
|
||
DefaultRootWindow (compositor.display),
|
||
False,
|
||
SubstructureRedirectMask | SubstructureNotifyMask,
|
||
&msg);
|
||
|
||
/* Assume a resize is in progress. Stop resizing upon
|
||
seat->last_button being released. */
|
||
return seat->resize_in_progress = True;
|
||
}
|
||
|
||
static Bool
|
||
StartResizeTracking (Seat *seat, Surface *surface, uint32_t serial,
|
||
ResizeEdge edge)
|
||
{
|
||
WhatEdge type;
|
||
|
||
/* Seat cannot be NULL here, but -Wanalyzer disagrees. */
|
||
XLAssert (seat != NULL);
|
||
|
||
if (seat != IdentifySeat (&type, serial))
|
||
return False;
|
||
|
||
if (type == AKeyboardEdge)
|
||
return HandleKeyboardEdge (seat, surface, serial, edge);
|
||
else
|
||
return HandlePointerEdge (seat, surface, serial, edge);
|
||
}
|
||
|
||
Bool
|
||
XLHandleOneXEventForSeats (XEvent *event)
|
||
{
|
||
if (event->type == GenericEvent
|
||
&& event->xgeneric.extension == xi2_opcode)
|
||
return HandleOneGenericEvent (&event->xcookie);
|
||
|
||
if (event->type == xkb_event_type)
|
||
return HandleXkbEvent ((XkbEvent *) event);
|
||
|
||
return False;
|
||
}
|
||
|
||
Window
|
||
XLGetGEWindowForSeats (XEvent *event)
|
||
{
|
||
XIFocusInEvent *focusin;
|
||
XIEnterEvent *enter;
|
||
XIDeviceEvent *xev;
|
||
XIBarrierEvent *barrier;
|
||
XIGesturePinchEvent *pinch;
|
||
XIGestureSwipeEvent *swipe;
|
||
|
||
if (event->type == GenericEvent
|
||
&& event->xgeneric.extension == xi2_opcode)
|
||
{
|
||
switch (event->xgeneric.evtype)
|
||
{
|
||
case XI_FocusIn:
|
||
case XI_FocusOut:
|
||
focusin = event->xcookie.data;
|
||
return focusin->event;
|
||
|
||
case XI_Motion:
|
||
case XI_ButtonPress:
|
||
case XI_ButtonRelease:
|
||
case XI_KeyPress:
|
||
case XI_KeyRelease:
|
||
xev = event->xcookie.data;
|
||
return xev->event;
|
||
|
||
case XI_Enter:
|
||
case XI_Leave:
|
||
enter = event->xcookie.data;
|
||
return enter->event;
|
||
|
||
case XI_BarrierHit:
|
||
barrier = event->xcookie.data;
|
||
return barrier->event;
|
||
|
||
case XI_GesturePinchBegin:
|
||
case XI_GesturePinchEnd:
|
||
case XI_GesturePinchUpdate:
|
||
pinch = event->xcookie.data;
|
||
return pinch->event;
|
||
|
||
case XI_GestureSwipeBegin:
|
||
case XI_GestureSwipeEnd:
|
||
case XI_GestureSwipeUpdate:
|
||
swipe = event->xcookie.data;
|
||
return swipe->event;
|
||
}
|
||
}
|
||
|
||
return None;
|
||
}
|
||
|
||
void
|
||
XLSelectStandardEvents (Window window)
|
||
{
|
||
XIEventMask mask;
|
||
size_t length;
|
||
|
||
length = XIMaskLen (XI_LASTEVENT);
|
||
mask.mask = alloca (length);
|
||
mask.mask_len = length;
|
||
mask.deviceid = XIAllMasterDevices;
|
||
|
||
memset (mask.mask, 0, length);
|
||
|
||
XISetMask (mask.mask, XI_FocusIn);
|
||
XISetMask (mask.mask, XI_FocusOut);
|
||
XISetMask (mask.mask, XI_Enter);
|
||
XISetMask (mask.mask, XI_Leave);
|
||
XISetMask (mask.mask, XI_Motion);
|
||
XISetMask (mask.mask, XI_ButtonPress);
|
||
XISetMask (mask.mask, XI_ButtonRelease);
|
||
XISetMask (mask.mask, XI_KeyPress);
|
||
XISetMask (mask.mask, XI_KeyRelease);
|
||
XISetMask (mask.mask, XI_BarrierHit);
|
||
|
||
if (xi2_major > 2 || xi2_minor >= 4)
|
||
{
|
||
/* Select for gesture events whenever supported. */
|
||
|
||
XISetMask (mask.mask, XI_GesturePinchBegin);
|
||
XISetMask (mask.mask, XI_GesturePinchUpdate);
|
||
XISetMask (mask.mask, XI_GesturePinchEnd);
|
||
XISetMask (mask.mask, XI_GestureSwipeBegin);
|
||
XISetMask (mask.mask, XI_GestureSwipeUpdate);
|
||
XISetMask (mask.mask, XI_GestureSwipeEnd);
|
||
}
|
||
|
||
XISelectEvents (compositor.display, window, &mask, 1);
|
||
}
|
||
|
||
void
|
||
XLDispatchGEForSeats (XEvent *event, Surface *surface,
|
||
Subcompositor *subcompositor)
|
||
{
|
||
if (event->xgeneric.evtype == XI_FocusIn)
|
||
DispatchFocusIn (surface, event->xcookie.data);
|
||
else if (event->xgeneric.evtype == XI_FocusOut)
|
||
DispatchFocusOut (surface, event->xcookie.data);
|
||
else if (event->xgeneric.evtype == XI_Enter
|
||
|| event->xgeneric.evtype == XI_Leave)
|
||
DispatchEntryExit (subcompositor, event->xcookie.data);
|
||
else if (event->xgeneric.evtype == XI_Motion)
|
||
DispatchMotion (subcompositor, event->xcookie.data);
|
||
else if (event->xgeneric.evtype == XI_ButtonPress
|
||
|| event->xgeneric.evtype == XI_ButtonRelease)
|
||
DispatchButton (subcompositor, event->xcookie.data);
|
||
else if (event->xgeneric.evtype == XI_KeyPress
|
||
|| event->xgeneric.evtype == XI_KeyRelease)
|
||
DispatchKey (event->xcookie.data);
|
||
else if (event->xgeneric.evtype == XI_BarrierHit)
|
||
DispatchBarrierHit (event->xcookie.data);
|
||
else if (event->xgeneric.evtype == XI_GesturePinchBegin
|
||
|| event->xgeneric.evtype == XI_GesturePinchUpdate
|
||
|| event->xgeneric.evtype == XI_GesturePinchEnd)
|
||
DispatchGesturePinch (subcompositor, event->xcookie.data);
|
||
else if (event->xgeneric.evtype == XI_GestureSwipeBegin
|
||
|| event->xgeneric.evtype == XI_GestureSwipeUpdate
|
||
|| event->xgeneric.evtype == XI_GestureSwipeEnd)
|
||
DispatchGestureSwipe (subcompositor, event->xcookie.data);
|
||
}
|
||
|
||
Cursor
|
||
InitDefaultCursor (void)
|
||
{
|
||
static Cursor empty_cursor;
|
||
Pixmap pixmap;
|
||
char no_data[1];
|
||
XColor color;
|
||
|
||
if (empty_cursor == None)
|
||
{
|
||
no_data[0] = 0;
|
||
|
||
pixmap = XCreateBitmapFromData (compositor.display,
|
||
DefaultRootWindow (compositor.display),
|
||
no_data, 1, 1);
|
||
color.pixel = 0;
|
||
color.red = 0;
|
||
color.green = 0;
|
||
color.blue = 0;
|
||
color.flags = DoRed | DoGreen | DoBlue;
|
||
|
||
empty_cursor = XCreatePixmapCursor (compositor.display,
|
||
pixmap, pixmap,
|
||
&color, &color, 0, 0);
|
||
|
||
XFreePixmap (compositor.display, pixmap);
|
||
}
|
||
|
||
return empty_cursor;
|
||
}
|
||
|
||
Bool
|
||
XLResizeToplevel (Seat *seat, Surface *surface, uint32_t serial,
|
||
uint32_t xdg_edge)
|
||
{
|
||
ResizeEdge edge;
|
||
|
||
if (seat->resize_in_progress)
|
||
return False;
|
||
|
||
switch (xdg_edge)
|
||
{
|
||
case XDG_TOPLEVEL_RESIZE_EDGE_NONE:
|
||
edge = NoneEdge;
|
||
break;
|
||
|
||
case XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT:
|
||
edge = TopLeftEdge;
|
||
break;
|
||
|
||
case XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT:
|
||
edge = TopRightEdge;
|
||
break;
|
||
|
||
case XDG_TOPLEVEL_RESIZE_EDGE_TOP:
|
||
edge = TopEdge;
|
||
break;
|
||
|
||
case XDG_TOPLEVEL_RESIZE_EDGE_RIGHT:
|
||
edge = RightEdge;
|
||
break;
|
||
|
||
case XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM:
|
||
edge = BottomEdge;
|
||
break;
|
||
|
||
case XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT:
|
||
edge = BottomRightEdge;
|
||
break;
|
||
|
||
case XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT:
|
||
edge = BottomLeftEdge;
|
||
break;
|
||
|
||
case XDG_TOPLEVEL_RESIZE_EDGE_LEFT:
|
||
edge = LeftEdge;
|
||
break;
|
||
|
||
default:
|
||
edge = NoneEdge;
|
||
break;
|
||
}
|
||
|
||
return StartResizeTracking (seat, surface, serial, edge);
|
||
}
|
||
|
||
Bool
|
||
XLMoveToplevel (Seat *seat, Surface *surface, uint32_t serial)
|
||
{
|
||
return StartResizeTracking (seat, surface, serial, MoveEdge);
|
||
}
|
||
|
||
void *
|
||
XLSeatRunAfterResize (Seat *seat, void (*func) (void *, void *),
|
||
void *data)
|
||
{
|
||
ResizeDoneCallback *callback;
|
||
|
||
callback = XLMalloc (sizeof *callback);
|
||
callback->next = seat->resize_callbacks.next;
|
||
callback->last = &seat->resize_callbacks;
|
||
|
||
seat->resize_callbacks.next->last = callback;
|
||
seat->resize_callbacks.next = callback;
|
||
|
||
callback->data = data;
|
||
callback->done = func;
|
||
|
||
return callback;
|
||
}
|
||
|
||
void
|
||
XLSeatCancelResizeCallback (void *key)
|
||
{
|
||
ResizeDoneCallback *callback;
|
||
|
||
callback = key;
|
||
callback->last->next = callback->next;
|
||
callback->next->last = callback->last;
|
||
|
||
callback->last = callback;
|
||
callback->next = callback;
|
||
|
||
XLFree (callback);
|
||
}
|
||
|
||
void *
|
||
XLSeatRunOnDestroy (Seat *seat, void (*destroy_func) (void *),
|
||
void *data)
|
||
{
|
||
DestroyListener *listener;
|
||
|
||
if (seat->flags & IsInert)
|
||
return NULL;
|
||
|
||
listener = XLMalloc (sizeof *listener);
|
||
listener->next = seat->destroy_listeners.next;
|
||
listener->last = &seat->destroy_listeners;
|
||
|
||
listener->destroy = destroy_func;
|
||
listener->data = data;
|
||
|
||
seat->destroy_listeners.next->last = listener;
|
||
seat->destroy_listeners.next = listener;
|
||
|
||
return listener;
|
||
}
|
||
|
||
void
|
||
XLSeatCancelDestroyListener (void *key)
|
||
{
|
||
DestroyListener *listener;
|
||
|
||
listener = key;
|
||
listener->next->last = listener->last;
|
||
listener->last->next = listener->next;
|
||
|
||
XLFree (listener);
|
||
}
|
||
|
||
Bool
|
||
XLSeatExplicitlyGrabSurface (Seat *seat, Surface *surface, uint32_t serial)
|
||
{
|
||
Status state;
|
||
Window window;
|
||
WhatEdge edge;
|
||
Time time;
|
||
XIEventMask mask;
|
||
size_t length;
|
||
Cursor cursor;
|
||
|
||
if (seat->flags & IsInert
|
||
/* This would interfere with the drag-and-drop grab. */
|
||
|| seat->flags & IsDragging)
|
||
return False;
|
||
|
||
window = XLWindowFromSurface (surface);
|
||
|
||
if (!window)
|
||
return False;
|
||
|
||
if (serial && serial == seat->last_grab_serial)
|
||
{
|
||
/* This probably means we are trying to revert the grab to a
|
||
popup's parent after the child is destroyed. */
|
||
|
||
edge = seat->last_grab_edge;
|
||
time = seat->last_grab_time;
|
||
}
|
||
else
|
||
{
|
||
if (seat != IdentifySeat (&edge, serial))
|
||
return False;
|
||
|
||
if (edge == AKeyboardEdge)
|
||
time = seat->its_depress_time;
|
||
else
|
||
time = seat->its_press_time;
|
||
}
|
||
|
||
/* Record these values; they can be used to revert the grab to the
|
||
parent. */
|
||
seat->last_grab_serial = serial;
|
||
seat->last_grab_edge = edge;
|
||
seat->last_grab_time = time;
|
||
|
||
length = XIMaskLen (XI_LASTEVENT);
|
||
mask.mask = alloca (length);
|
||
mask.mask_len = length;
|
||
mask.deviceid = XIAllMasterDevices;
|
||
|
||
memset (mask.mask, 0, length);
|
||
|
||
XISetMask (mask.mask, XI_FocusIn);
|
||
XISetMask (mask.mask, XI_FocusOut);
|
||
XISetMask (mask.mask, XI_Enter);
|
||
XISetMask (mask.mask, XI_Leave);
|
||
XISetMask (mask.mask, XI_Motion);
|
||
XISetMask (mask.mask, XI_ButtonPress);
|
||
XISetMask (mask.mask, XI_ButtonRelease);
|
||
XISetMask (mask.mask, XI_KeyPress);
|
||
XISetMask (mask.mask, XI_KeyRelease);
|
||
|
||
cursor = (seat->cursor ? seat->cursor->cursor : None);
|
||
|
||
state = XIGrabDevice (compositor.display, seat->master_pointer,
|
||
window, time, cursor, XIGrabModeAsync,
|
||
XIGrabModeAsync, True, &mask);
|
||
|
||
if (state != Success)
|
||
return False;
|
||
|
||
/* The grab was obtained. Since this grab is owner_events, remove
|
||
any focus locking. */
|
||
if (seat->grab_held)
|
||
CancelGrabEarly (seat);
|
||
|
||
/* Now, grab the keyboard. Note that we just grab the keyboard so
|
||
that keyboard focus cannot be changed, which is not very crucial,
|
||
so it is allowed to fail. The keyboard grab is not owner_events,
|
||
which is important for input method events to be filtered
|
||
correctly. */
|
||
|
||
state = XIGrabDevice (compositor.display, seat->master_keyboard,
|
||
window, time, None, XIGrabModeAsync,
|
||
XIGrabModeAsync, False, &mask);
|
||
|
||
/* Cancel any external grab that might be applied if the keyboard
|
||
grab succeeded. */
|
||
if (state == Success)
|
||
seat->flags &= ~IsExternalGrabApplied;
|
||
|
||
/* And record the grab surface, so that owner_events can be
|
||
implemented correctly. */
|
||
SwapGrabSurface (seat, surface);
|
||
|
||
return True;
|
||
}
|
||
|
||
DataDevice *
|
||
XLSeatGetDataDevice (Seat *seat)
|
||
{
|
||
return seat->data_device;
|
||
}
|
||
|
||
void
|
||
XLSeatSetDataDevice (Seat *seat, DataDevice *data_device)
|
||
{
|
||
seat->data_device = data_device;
|
||
|
||
XLRetainDataDevice (data_device);
|
||
}
|
||
|
||
Bool
|
||
XLSeatIsInert (Seat *seat)
|
||
{
|
||
return seat->flags & IsInert;
|
||
}
|
||
|
||
Bool
|
||
XLSeatIsClientFocused (Seat *seat, struct wl_client *client)
|
||
{
|
||
struct wl_client *surface_client;
|
||
|
||
if (!seat->focus_surface)
|
||
return False;
|
||
|
||
surface_client
|
||
= wl_resource_get_client (seat->focus_surface->resource);
|
||
|
||
return client == surface_client;
|
||
}
|
||
|
||
Surface *
|
||
XLSeatGetFocus (Seat *seat)
|
||
{
|
||
return seat->focus_surface;
|
||
}
|
||
|
||
void
|
||
XLSeatShowWindowMenu (Seat *seat, Surface *surface, int root_x,
|
||
int root_y)
|
||
{
|
||
XEvent msg;
|
||
Window window;
|
||
|
||
if (!XLWmSupportsHint (_GTK_SHOW_WINDOW_MENU))
|
||
return;
|
||
|
||
if (seat->flags & IsDragging)
|
||
/* The window menu cannot be displayed while the drag-and-drop
|
||
grab is in effect. */
|
||
return;
|
||
|
||
window = XLWindowFromSurface (surface);
|
||
|
||
if (window == None)
|
||
return;
|
||
|
||
/* Ungrab the pointer. Also cancel any focus locking, if
|
||
active. */
|
||
XIUngrabDevice (compositor.display, seat->master_pointer,
|
||
seat->its_press_time);
|
||
|
||
/* Also clear the core grab, even though it's not used anywhere. */
|
||
XUngrabPointer (compositor.display, seat->its_press_time);
|
||
|
||
/* Cancel focus locking. */
|
||
if (seat->grab_held)
|
||
CancelGrabEarly (seat);
|
||
|
||
/* Signal that the window menu is now shown. The assumption is that
|
||
the window manager will grab the pointer device; the flag is then
|
||
cleared once once any kind of crossing event is received.
|
||
|
||
This is race-prone for two reasons. If the window manager does
|
||
not receive the event in time, the last-grab-time could have
|
||
changed. Since there is no timestamp provided in the
|
||
_GTK_SHOW_WINDOW_MENU message, there is no way for the window
|
||
manager to know if the time it issued the grab is valid, as it
|
||
will need to obtain the grab time via a server roundtrip. In
|
||
addition, there is no way for the client to cancel the window
|
||
menu grab, should it receive an event changing the last-grab-time
|
||
before the window manager has a chance to grab the pointer.
|
||
|
||
So the conclusion is that _GTK_SHOW_WINDOW_MENU is defective from
|
||
improper design. In the meantime, the solution mentioned above
|
||
seems to work well enough in most cases. */
|
||
seat->flags |= IsWindowMenuShown;
|
||
|
||
/* Send the message to the window manager with the device to grab,
|
||
and the coordinates where the window menu should be shown. */
|
||
memset (&msg, 0, sizeof msg);
|
||
msg.xclient.type = ClientMessage;
|
||
msg.xclient.window = window;
|
||
msg.xclient.format = 32;
|
||
msg.xclient.message_type = _GTK_SHOW_WINDOW_MENU;
|
||
msg.xclient.data.l[0] = seat->master_pointer;
|
||
msg.xclient.data.l[1] = root_x;
|
||
msg.xclient.data.l[2] = root_y;
|
||
|
||
XSendEvent (compositor.display,
|
||
DefaultRootWindow (compositor.display),
|
||
False,
|
||
SubstructureRedirectMask | SubstructureNotifyMask,
|
||
&msg);
|
||
}
|
||
|
||
static void
|
||
ForceEntry (Seat *seat, Window source, double x, double y)
|
||
{
|
||
Surface *surface;
|
||
Window target;
|
||
|
||
if (seat->last_seen_surface)
|
||
{
|
||
surface = seat->last_seen_surface;
|
||
target = XLWindowFromSurface (surface);
|
||
|
||
if (target == None)
|
||
{
|
||
if (source != target)
|
||
/* If the source is something other than the target,
|
||
translate the coordinates to the target. */
|
||
TranslateCoordinates (source, target, x, y, &x, &y);
|
||
|
||
/* Finally, translate the coordinates to the target
|
||
view. */
|
||
TransformToSurface (surface, x, y, &x, &y);
|
||
}
|
||
}
|
||
else
|
||
return;
|
||
|
||
if (!SendEnter (seat, surface, x, y))
|
||
/* Apparently what is done by other compositors when no
|
||
wl_pointer object exists for the surface's client is to
|
||
revert back to the default cursor. */
|
||
UndefineCursorOn (seat, surface);
|
||
}
|
||
|
||
static void
|
||
CancelDrag (Seat *seat, Window event_source, double x, double y)
|
||
{
|
||
if (!(seat->flags & IsDragging))
|
||
return;
|
||
|
||
/* If the last seen surface is now different from the drag start
|
||
surface, clear the cursor on the latter. */
|
||
if (seat->drag_start_surface != seat->last_seen_surface
|
||
&& seat->cursor)
|
||
FreeCursor (seat->cursor);
|
||
|
||
if (seat->data_source)
|
||
XLDoDragFinish (seat);
|
||
|
||
/* And cancel the drag flag. */
|
||
seat->flags &= ~IsDragging;
|
||
|
||
/* Clear the surface and free its unmap callback. */
|
||
seat->drag_start_surface = NULL;
|
||
XLSurfaceCancelUnmapCallback (seat->drag_start_unmap_callback);
|
||
|
||
/* Release the active grab as well, and leave any surface we
|
||
entered. */
|
||
if (seat->drag_last_surface)
|
||
DragLeave (seat);
|
||
|
||
XIUngrabDevice (compositor.display, seat->master_pointer,
|
||
seat->drag_grab_time);
|
||
|
||
if (seat->data_source)
|
||
{
|
||
/* Attach a NULL drag device to this source. */
|
||
XLDataSourceAttachDragDevice (seat->data_source, NULL);
|
||
|
||
/* Cancel the destroy callback. */
|
||
XLDataSourceCancelDestroyCallback (seat->data_source_destroy_callback);
|
||
|
||
/* If a data source is attached, clear it now. */
|
||
seat->data_source = NULL;
|
||
seat->data_source_destroy_callback = NULL;
|
||
}
|
||
|
||
/* If nothing was dropped, emit the cancelled event. */
|
||
if (seat->data_source && !(seat->flags & IsDropped))
|
||
XLDataSourceSendDropCancelled (seat->data_source);
|
||
|
||
/* Next, enter the last seen surface. This is necessary when
|
||
releasing the pointer on top of a different surface after the
|
||
drag and drop operation completes. */
|
||
ForceEntry (seat, event_source, x, y);
|
||
|
||
/* Destroy the grab window. */
|
||
XDestroyWindow (compositor.display, seat->grab_window);
|
||
seat->grab_window = None;
|
||
|
||
/* Cancel the icon surface. */
|
||
if (seat->icon_surface)
|
||
XLReleaseIconSurface (seat->icon_surface);
|
||
seat->icon_surface = NULL;
|
||
}
|
||
|
||
static void
|
||
HandleDragSurfaceUnmapped (void *data)
|
||
{
|
||
Seat *seat;
|
||
double root_x, root_y;
|
||
|
||
seat = data;
|
||
|
||
/* Cancel the drag and drop operation. We don't know where the
|
||
pointer is ATM, so query for that information. */
|
||
|
||
QueryPointer (seat, DefaultRootWindow (compositor.display),
|
||
&root_x, &root_y);
|
||
|
||
/* And cancel the drag with the pointer position. */
|
||
|
||
CancelDrag (seat, DefaultRootWindow (compositor.display),
|
||
root_x, root_y);
|
||
}
|
||
|
||
static void
|
||
HandleDataSourceDestroyed (void *data)
|
||
{
|
||
Seat *seat;
|
||
double root_x, root_y;
|
||
|
||
seat = data;
|
||
|
||
/* Clear those fields first, since their contents are now
|
||
destroyed. */
|
||
seat->data_source = NULL;
|
||
seat->data_source_destroy_callback = NULL;
|
||
|
||
/* Cancel the drag and drop operation. We don't know where the
|
||
pointer is ATM, so query for that information. */
|
||
|
||
QueryPointer (seat, DefaultRootWindow (compositor.display),
|
||
&root_x, &root_y);
|
||
|
||
/* And cancel the drag with the pointer position. */
|
||
|
||
CancelDrag (seat, DefaultRootWindow (compositor.display),
|
||
root_x, root_y);
|
||
}
|
||
|
||
static Window
|
||
MakeGrabWindow (void)
|
||
{
|
||
Window window;
|
||
XSetWindowAttributes attrs;
|
||
|
||
/* Make the window override redirect. */
|
||
attrs.override_redirect = True;
|
||
|
||
/* The window has to be mapped and visible, or the grab will
|
||
fail. */
|
||
window = XCreateWindow (compositor.display,
|
||
DefaultRootWindow (compositor.display),
|
||
0, 0, 1, 1, 0, CopyFromParent, InputOnly,
|
||
CopyFromParent, CWOverrideRedirect, &attrs);
|
||
|
||
/* Clear the input region of the window. */
|
||
XShapeCombineRectangles (compositor.display, window,
|
||
ShapeInput, 0, 0, NULL, 0, ShapeSet,
|
||
Unsorted);
|
||
|
||
/* Map and return it. */
|
||
XMapRaised (compositor.display, window);
|
||
return window;
|
||
}
|
||
|
||
void
|
||
XLSeatBeginDrag (Seat *seat, DataSource *data_source, Surface *start_surface,
|
||
Surface *icon_surface, uint32_t serial)
|
||
{
|
||
Window window;
|
||
Time time;
|
||
XIEventMask mask;
|
||
size_t length;
|
||
WhatEdge edge;
|
||
Status state;
|
||
|
||
/* If the surface is unmapped or window-less, don't allow dragging
|
||
from it. */
|
||
|
||
window = XLWindowFromSurface (start_surface);
|
||
|
||
if (window == None)
|
||
return;
|
||
|
||
/* To begin a drag and drop session, first look up the given serial.
|
||
Then, acquire an active grab on the pointer with owner_events set
|
||
to true.
|
||
|
||
While the grab is active, all crossing and motion event dispatch
|
||
is hijacked to send events to the appropriate data device manager
|
||
instead.
|
||
|
||
If, for some reason, data_source is destroyed, or the source
|
||
surface is destroyed, the grab is cancelled and the drag and drop
|
||
operation terminates. Otherwise, drop is sent to the correct
|
||
data device manager once all the pointer buttons are
|
||
released. */
|
||
|
||
if (seat->flags & IsDragging)
|
||
return;
|
||
|
||
if (seat != IdentifySeat (&edge, serial))
|
||
return;
|
||
|
||
if (edge == AKeyboardEdge)
|
||
return;
|
||
|
||
/* Use the time of the last button press or release. */
|
||
time = seat->its_press_time;
|
||
|
||
/* Initialize the event mask used for the grab. */
|
||
length = XIMaskLen (XI_LASTEVENT);
|
||
mask.mask = alloca (length);
|
||
mask.mask_len = length;
|
||
mask.deviceid = XIAllMasterDevices;
|
||
|
||
memset (mask.mask, 0, length);
|
||
|
||
/* These are just the events we want reported relative to the grab
|
||
window. */
|
||
|
||
XISetMask (mask.mask, XI_Enter);
|
||
XISetMask (mask.mask, XI_Leave);
|
||
XISetMask (mask.mask, XI_Motion);
|
||
XISetMask (mask.mask, XI_ButtonPress);
|
||
XISetMask (mask.mask, XI_ButtonRelease);
|
||
|
||
/* Create the window that will be used for the grab. */
|
||
|
||
XLAssert (seat->grab_window == None);
|
||
seat->grab_window = MakeGrabWindow ();
|
||
|
||
/* Record that the drag has started, temporarily, for cursor
|
||
updates. */
|
||
seat->flags |= IsDragging;
|
||
|
||
/* Move the cursor to the drag grab window. */
|
||
if (seat->cursor)
|
||
UpdateCursorFromSubcompositor (seat->cursor);
|
||
|
||
/* Unset the drag flag again. */
|
||
seat->flags &= ~IsDragging;
|
||
|
||
/* Now, try to grab the pointer device with events reported relative
|
||
to the grab window. */
|
||
|
||
state = XIGrabDevice (compositor.display, seat->master_pointer,
|
||
seat->grab_window, time, None, XIGrabModeAsync,
|
||
XIGrabModeAsync, True, &mask);
|
||
|
||
if (state != Success)
|
||
{
|
||
/* Destroy the grab window. */
|
||
XDestroyWindow (compositor.display, seat->grab_window);
|
||
seat->grab_window = None;
|
||
|
||
return;
|
||
}
|
||
|
||
/* Since the active grab is now held and is owner_events, remove
|
||
focus locking. */
|
||
if (seat->grab_held)
|
||
CancelGrabEarly (seat);
|
||
|
||
/* Record the originating surface of the grab and add an unmap
|
||
callback. */
|
||
seat->drag_start_surface = start_surface;
|
||
seat->drag_start_unmap_callback
|
||
= XLSurfaceRunAtUnmap (start_surface, HandleDragSurfaceUnmapped,
|
||
seat);
|
||
|
||
seat->flags &= ~IsDragging;
|
||
|
||
/* Since dragging has started, leave the last seen surface now.
|
||
Preserve the cursor, since that surface is where the cursor is
|
||
currently set. */
|
||
EnteredSurface (seat, NULL, CurrentTime, 0, 0, True);
|
||
|
||
if (data_source)
|
||
{
|
||
/* Record the data source. */
|
||
XLDataSourceAttachDragDevice (data_source,
|
||
seat->data_device);
|
||
seat->data_source = data_source;
|
||
|
||
/* Add a destroy callback. */
|
||
seat->data_source_destroy_callback
|
||
= XLDataSourceAddDestroyCallback (data_source,
|
||
HandleDataSourceDestroyed,
|
||
seat);
|
||
}
|
||
else
|
||
/* This should've been destroyed by CancelGrab. */
|
||
XLAssert (seat->data_source == NULL);
|
||
|
||
/* If the icon surface was specified, give it the right type and
|
||
attach the role. */
|
||
if (icon_surface)
|
||
{
|
||
/* Note that the caller is responsible for validating the type
|
||
of the icon surface. */
|
||
icon_surface->role_type = DndIconType;
|
||
seat->icon_surface = XLGetIconSurface (icon_surface);
|
||
|
||
/* Move the icon surface to the last known root window position
|
||
of the pointer. */
|
||
XLMoveIconSurface (seat->icon_surface, seat->its_root_x,
|
||
seat->its_root_y);
|
||
}
|
||
|
||
/* Record that the drag has really started. */
|
||
seat->drag_grab_time = time;
|
||
seat->flags |= IsDragging;
|
||
seat->flags &= ~IsDropped;
|
||
}
|
||
|
||
Timestamp
|
||
XLSeatGetLastUserTime (Seat *seat)
|
||
{
|
||
return GetLastUserTime (seat);
|
||
}
|
||
|
||
void
|
||
XLInitSeats (void)
|
||
{
|
||
int rc;
|
||
|
||
/* This is the version of the input extension that we want. */
|
||
xi2_major = 2;
|
||
xi2_minor = 4;
|
||
|
||
if (XQueryExtension (compositor.display, "XInputExtension",
|
||
&xi2_opcode, &xi_first_event, &xi_first_error))
|
||
{
|
||
rc = XIQueryVersion (compositor.display, &xi2_major, &xi2_minor);
|
||
|
||
if (xi2_major < 2 || (xi2_major == 2 && xi2_minor < 3) || rc)
|
||
{
|
||
fprintf (stderr, "version 2.3 or later of of the X Input Extension is"
|
||
" not present on the X server\n");
|
||
exit (1);
|
||
}
|
||
}
|
||
|
||
seats = XLCreateAssocTable (25);
|
||
devices = XLCreateAssocTable (25);
|
||
keymap_fd = -1;
|
||
|
||
SelectDeviceEvents ();
|
||
SetupInitialDevices ();
|
||
SetupKeymap ();
|
||
}
|
||
|
||
DataSource *
|
||
XLSeatGetDragDataSource (Seat *seat)
|
||
{
|
||
return seat->data_source;
|
||
}
|
||
|
||
void *
|
||
XLSeatAddModifierCallback (Seat *seat, void (*changed) (unsigned int, void *),
|
||
void *data)
|
||
{
|
||
ModifierChangeCallback *callback;
|
||
|
||
callback = XLMalloc (sizeof *callback);
|
||
callback->next = seat->modifier_callbacks.next;
|
||
callback->last = &seat->modifier_callbacks;
|
||
seat->modifier_callbacks.next->last = callback;
|
||
seat->modifier_callbacks.next = callback;
|
||
|
||
callback->changed = changed;
|
||
callback->data = data;
|
||
|
||
return callback;
|
||
}
|
||
|
||
void
|
||
XLSeatRemoveModifierCallback (void *key)
|
||
{
|
||
ModifierChangeCallback *callback;
|
||
|
||
callback = key;
|
||
callback->next->last = callback->last;
|
||
callback->last->next = callback->next;
|
||
|
||
XLFree (callback);
|
||
}
|
||
|
||
unsigned int
|
||
XLSeatGetEffectiveModifiers (Seat *seat)
|
||
{
|
||
return seat->base | seat->locked | seat->latched;
|
||
}
|
||
|
||
Bool
|
||
XLSeatResizeInProgress (Seat *seat)
|
||
{
|
||
return seat->resize_in_progress;
|
||
}
|
||
|
||
void
|
||
XLSeatSetTextInputFuncs (TextInputFuncs *funcs)
|
||
{
|
||
input_funcs = funcs;
|
||
}
|
||
|
||
int
|
||
XLSeatGetKeyboardDevice (Seat *seat)
|
||
{
|
||
return seat->master_keyboard;
|
||
}
|
||
|
||
int
|
||
XLSeatGetPointerDevice (Seat *seat)
|
||
{
|
||
return seat->master_pointer;
|
||
}
|
||
|
||
Seat *
|
||
XLSeatGetInputMethodSeat (void)
|
||
{
|
||
XLList *list;
|
||
Seat *seat;
|
||
|
||
for (list = live_seats; list; list = list->next)
|
||
{
|
||
seat = list->data;
|
||
|
||
if (seat->flags & IsTextInputSeat)
|
||
return seat;
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
void
|
||
XLSeatDispatchCoreKeyEvent (Seat *seat, Surface *surface, XEvent *event)
|
||
{
|
||
unsigned int effective;
|
||
unsigned int state, group;
|
||
|
||
/* Dispatch a core event generated by an input method to SEAT. If
|
||
SURFACE is no longer the focus surface, refrain from doing
|
||
anything. If a keycode can be found for KEYSYM, use that
|
||
keycode. */
|
||
|
||
if (surface != seat->focus_surface)
|
||
return;
|
||
|
||
/* Get the group and state of the key event. */
|
||
group = event->xkey.state >> 13;
|
||
state = event->xkey.state & AllKeyMask;
|
||
|
||
/* Get the effective state of the seat. */
|
||
effective = seat->base | seat->latched | seat->locked;
|
||
|
||
if (group != seat->effective_group || state != effective)
|
||
/* The modifiers in the provided core event are different from
|
||
what the focus surface was previously sent. Send a new
|
||
modifier event with the effective state provided in the give
|
||
core event. */
|
||
HackKeyboardModifiers (seat, surface, effective, group);
|
||
|
||
/* Then send the event. */
|
||
if (event->xkey.type == KeyPress)
|
||
SendKeyboardKey (seat, seat->focus_surface, event->xkey.time,
|
||
WaylandKeycode (event->xkey.keycode),
|
||
WL_KEYBOARD_KEY_STATE_PRESSED);
|
||
else
|
||
SendKeyboardKey (seat, seat->focus_surface, event->xkey.time,
|
||
WaylandKeycode (event->xkey.keycode),
|
||
WL_KEYBOARD_KEY_STATE_RELEASED);
|
||
|
||
/* Restore the modifiers. */
|
||
if (group != seat->effective_group || state != effective)
|
||
SendKeyboardModifiers (seat, surface);
|
||
}
|
||
|
||
Seat *
|
||
XLPointerGetSeat (Pointer *pointer)
|
||
{
|
||
return pointer->seat;
|
||
}
|
||
|
||
void
|
||
XLSeatGetMouseData (Seat *seat, Surface **last_seen_surface,
|
||
double *last_surface_x, double *last_surface_y,
|
||
double *its_root_x, double *its_root_y)
|
||
{
|
||
*last_seen_surface = seat->last_seen_surface;
|
||
*last_surface_x = seat->last_surface_x;
|
||
*last_surface_y = seat->last_surface_y;
|
||
*its_root_x = seat->its_root_x;
|
||
*its_root_y = seat->its_root_y;
|
||
}
|
||
|
||
void
|
||
XLSeatLockPointer (Seat *seat)
|
||
{
|
||
seat->flags |= IsPointerLocked;
|
||
}
|
||
|
||
void
|
||
XLSeatUnlockPointer (Seat *seat)
|
||
{
|
||
seat->flags &= ~IsPointerLocked;
|
||
}
|
||
|
||
RelativePointer *
|
||
XLSeatGetRelativePointer (Seat *seat, struct wl_resource *resource)
|
||
{
|
||
RelativePointer *relative_pointer;
|
||
SeatClientInfo *info;
|
||
|
||
/* Create a relative pointer object for the relative pointer
|
||
resource RESOURCE. */
|
||
|
||
relative_pointer = XLCalloc (1, sizeof *relative_pointer);
|
||
info = CreateSeatClientInfo (seat, wl_resource_get_client (resource));
|
||
|
||
/* Link the relative pointer onto the seat client info. */
|
||
relative_pointer->next = info->relative_pointers.next;
|
||
relative_pointer->last = &info->relative_pointers;
|
||
info->relative_pointers.next->last = relative_pointer;
|
||
info->relative_pointers.next = relative_pointer;
|
||
relative_pointer->info = info;
|
||
|
||
/* Then, the seat. */
|
||
relative_pointer->seat = seat;
|
||
RetainSeat (seat);
|
||
|
||
/* Add the resource. */
|
||
relative_pointer->resource = resource;
|
||
|
||
return relative_pointer;
|
||
}
|
||
|
||
void
|
||
XLSeatDestroyRelativePointer (RelativePointer *relative_pointer)
|
||
{
|
||
relative_pointer->last->next = relative_pointer->next;
|
||
relative_pointer->next->last = relative_pointer->last;
|
||
|
||
ReleaseSeatClientInfo (relative_pointer->info);
|
||
ReleaseSeat (relative_pointer->seat);
|
||
|
||
XLFree (relative_pointer);
|
||
}
|
||
|
||
SwipeGesture *
|
||
XLSeatGetSwipeGesture (Seat *seat, struct wl_resource *resource)
|
||
{
|
||
SwipeGesture *swipe_gesture;
|
||
SeatClientInfo *info;
|
||
|
||
/* Create a swipe gesture object for the resource RESOURCE. */
|
||
swipe_gesture = XLCalloc (1, sizeof *swipe_gesture);
|
||
|
||
/* Obtain or create the seat client info. */
|
||
info = CreateSeatClientInfo (seat, wl_resource_get_client (resource));
|
||
|
||
/* Link the gesture onto it. */
|
||
swipe_gesture->next = info->swipe_gestures.next;
|
||
swipe_gesture->last = &info->swipe_gestures;
|
||
info->swipe_gestures.next->last = swipe_gesture;
|
||
info->swipe_gestures.next = swipe_gesture;
|
||
swipe_gesture->info = info;
|
||
|
||
/* Set the seat and resource. */
|
||
swipe_gesture->seat = seat;
|
||
swipe_gesture->resource = resource;
|
||
RetainSeat (seat);
|
||
|
||
return swipe_gesture;
|
||
}
|
||
|
||
PinchGesture *
|
||
XLSeatGetPinchGesture (Seat *seat, struct wl_resource *resource)
|
||
{
|
||
PinchGesture *pinch_gesture;
|
||
SeatClientInfo *info;
|
||
|
||
/* Create a pinch gesture object for the resource RESOURCE. */
|
||
pinch_gesture = XLCalloc (1, sizeof *pinch_gesture);
|
||
|
||
/* Obtain or create the seat client info. */
|
||
info = CreateSeatClientInfo (seat, wl_resource_get_client (resource));
|
||
|
||
/* Link the gesture onto it. */
|
||
pinch_gesture->next = info->pinch_gestures.next;
|
||
pinch_gesture->last = &info->pinch_gestures;
|
||
info->pinch_gestures.next->last = pinch_gesture;
|
||
info->pinch_gestures.next = pinch_gesture;
|
||
pinch_gesture->info = info;
|
||
|
||
/* Set the seat and resource. */
|
||
pinch_gesture->seat = seat;
|
||
pinch_gesture->resource = resource;
|
||
RetainSeat (seat);
|
||
|
||
return pinch_gesture;
|
||
}
|
||
|
||
void
|
||
XLSeatDestroySwipeGesture (SwipeGesture *swipe_gesture)
|
||
{
|
||
swipe_gesture->last->next = swipe_gesture->next;
|
||
swipe_gesture->next->last = swipe_gesture->last;
|
||
|
||
ReleaseSeatClientInfo (swipe_gesture->info);
|
||
ReleaseSeat (swipe_gesture->seat);
|
||
|
||
XLFree (swipe_gesture);
|
||
}
|
||
|
||
void
|
||
XLSeatDestroyPinchGesture (PinchGesture *pinch_gesture)
|
||
{
|
||
pinch_gesture->last->next = pinch_gesture->next;
|
||
pinch_gesture->next->last = pinch_gesture->last;
|
||
|
||
ReleaseSeatClientInfo (pinch_gesture->info);
|
||
ReleaseSeat (pinch_gesture->seat);
|
||
|
||
XLFree (pinch_gesture);
|
||
}
|
||
|
||
Bool
|
||
XLSeatApplyExternalGrab (Seat *seat, Surface *surface)
|
||
{
|
||
Window window;
|
||
Status state;
|
||
XIEventMask mask;
|
||
size_t length;
|
||
|
||
/* Grab the toplevel SURFACE on SEAT. */
|
||
|
||
window = XLWindowFromSurface (surface);
|
||
|
||
if (!window)
|
||
return None;
|
||
|
||
length = XIMaskLen (XI_LASTEVENT);
|
||
mask.mask = alloca (length);
|
||
mask.mask_len = length;
|
||
mask.deviceid = XIAllMasterDevices;
|
||
|
||
memset (mask.mask, 0, length);
|
||
|
||
/* Grab focus and key events. */
|
||
XISetMask (mask.mask, XI_FocusIn);
|
||
XISetMask (mask.mask, XI_FocusOut);
|
||
XISetMask (mask.mask, XI_KeyPress);
|
||
XISetMask (mask.mask, XI_KeyRelease);
|
||
|
||
state = XIGrabDevice (compositor.display, seat->master_keyboard,
|
||
window, seat->last_focus_time.milliseconds, None,
|
||
XIGrabModeAsync, XIGrabModeAsync, True, &mask);
|
||
if (state == Success)
|
||
{
|
||
/* Mark an external grab as having been applied. */
|
||
seat->flags |= IsExternalGrabApplied;
|
||
|
||
/* Record the time when it was applied. */
|
||
seat->external_grab_time = seat->last_focus_time.milliseconds;
|
||
|
||
return True;
|
||
}
|
||
|
||
return False;
|
||
}
|
||
|
||
void
|
||
XLSeatCancelExternalGrab (Seat *seat)
|
||
{
|
||
if (!(seat->flags & IsExternalGrabApplied))
|
||
return;
|
||
|
||
/* Cancel the external grab. */
|
||
XIUngrabDevice (compositor.display, seat->master_keyboard,
|
||
seat->external_grab_time);
|
||
}
|
||
|
||
KeyCode
|
||
XLKeysymToKeycode (KeySym keysym, XEvent *event)
|
||
{
|
||
unsigned int i, mods_return;
|
||
KeySym keysym_return;
|
||
|
||
if (!xkb_desc)
|
||
return 0;
|
||
|
||
/* Look up the keycode correspnding to the given keysym. Return 0
|
||
if there is none. */
|
||
|
||
for (i = xkb_desc->min_key_code; i <= xkb_desc->max_key_code; ++i)
|
||
{
|
||
if (XkbTranslateKeyCode (xkb_desc, i, event->xkey.state,
|
||
&mods_return, &keysym_return)
|
||
&& keysym_return == keysym)
|
||
return i;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
Bool
|
||
XLSeatCheckActivationSerial (Seat *seat, uint32_t serial)
|
||
{
|
||
/* Check if the specified serial can be used to activate surfaces on
|
||
behalf of seat. */
|
||
|
||
return ((seat->last_button_press_serial
|
||
&& serial >= seat->last_button_press_serial)
|
||
|| (seat->last_button_serial
|
||
&& serial >= seat->last_button_serial)
|
||
|| (seat->last_keyboard_serial
|
||
&& serial >= seat->last_keyboard_serial)
|
||
/* Also allow using the serial at which the focus last
|
||
changed, if there currently is a keyboard focus. */
|
||
|| (serial == seat->last_enter_serial
|
||
&& seat->focus_surface));
|
||
}
|
||
|
||
/* This is a particularly ugly hack, but there is no other way to
|
||
expose all the internals needed by test_seat.c. */
|
||
|
||
#include "test_seat.c"
|