12to11/seat.c
hujianwei bd3d0a96e0 Fix dispatch of leave events with built-in move
* 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.
2022-11-24 02:23:11 +00:00

6442 lines
158 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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"