From 7810cbb79fb6a7f4f11c1bd87bf8207926492f13 Mon Sep 17 00:00:00 2001 From: oldosfan Date: Sat, 15 Oct 2022 07:47:44 +0000 Subject: [PATCH] Check in new files for pointer constraint support * pointer-constraints-unstable-v1.xml: * pointer_constraints.c: New files. --- pointer-constraints-unstable-v1.xml | 339 +++++ pointer_constraints.c | 2021 +++++++++++++++++++++++++++ 2 files changed, 2360 insertions(+) create mode 100644 pointer-constraints-unstable-v1.xml create mode 100644 pointer_constraints.c diff --git a/pointer-constraints-unstable-v1.xml b/pointer-constraints-unstable-v1.xml new file mode 100644 index 0000000..efd64b6 --- /dev/null +++ b/pointer-constraints-unstable-v1.xml @@ -0,0 +1,339 @@ + + + + + Copyright © 2014 Jonas Ådahl + Copyright © 2015 Red Hat Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol specifies a set of interfaces used for adding constraints to + the motion of a pointer. Possible constraints include confining pointer + motions to a given region, or locking it to its current position. + + In order to constrain the pointer, a client must first bind the global + interface "wp_pointer_constraints" which, if a compositor supports pointer + constraints, is exposed by the registry. Using the bound global object, the + client uses the request that corresponds to the type of constraint it wants + to make. See wp_pointer_constraints for more details. + + Warning! The protocol described in this file is experimental and backward + incompatible changes may be made. Backward compatible changes may be added + together with the corresponding interface version bump. Backward + incompatible changes are done by bumping the version number in the protocol + and interface names and resetting the interface version. Once the protocol + is to be declared stable, the 'z' prefix and the version number in the + protocol and interface names are removed and the interface version number is + reset. + + + + + The global interface exposing pointer constraining functionality. It + exposes two requests: lock_pointer for locking the pointer to its + position, and confine_pointer for locking the pointer to a region. + + The lock_pointer and confine_pointer requests create the objects + wp_locked_pointer and wp_confined_pointer respectively, and the client can + use these objects to interact with the lock. + + For any surface, only one lock or confinement may be active across all + wl_pointer objects of the same seat. If a lock or confinement is requested + when another lock or confinement is active or requested on the same surface + and with any of the wl_pointer objects of the same seat, an + 'already_constrained' error will be raised. + + + + + These errors can be emitted in response to wp_pointer_constraints + requests. + + + + + + + These values represent different lifetime semantics. They are passed + as arguments to the factory requests to specify how the constraint + lifetimes should be managed. + + + + A oneshot pointer constraint will never reactivate once it has been + deactivated. See the corresponding deactivation event + (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) for + details. + + + + + A persistent pointer constraint may again reactivate once it has + been deactivated. See the corresponding deactivation event + (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) for + details. + + + + + + + Used by the client to notify the server that it will no longer use this + pointer constraints object. + + + + + + The lock_pointer request lets the client request to disable movements of + the virtual pointer (i.e. the cursor), effectively locking the pointer + to a position. This request may not take effect immediately; in the + future, when the compositor deems implementation-specific constraints + are satisfied, the pointer lock will be activated and the compositor + sends a locked event. + + The protocol provides no guarantee that the constraints are ever + satisfied, and does not require the compositor to send an error if the + constraints cannot ever be satisfied. It is thus possible to request a + lock that will never activate. + + There may not be another pointer constraint of any kind requested or + active on the surface for any of the wl_pointer objects of the seat of + the passed pointer when requesting a lock. If there is, an error will be + raised. See general pointer lock documentation for more details. + + The intersection of the region passed with this request and the input + region of the surface is used to determine where the pointer must be + in order for the lock to activate. It is up to the compositor whether to + warp the pointer or require some kind of user interaction for the lock + to activate. If the region is null the surface input region is used. + + A surface may receive pointer focus without the lock being activated. + + The request creates a new object wp_locked_pointer which is used to + interact with the lock as well as receive updates about its state. See + the the description of wp_locked_pointer for further information. + + Note that while a pointer is locked, the wl_pointer objects of the + corresponding seat will not emit any wl_pointer.motion events, but + relative motion events will still be emitted via wp_relative_pointer + objects of the same seat. wl_pointer.axis and wl_pointer.button events + are unaffected. + + + + + + + + + + + The confine_pointer request lets the client request to confine the + pointer cursor to a given region. This request may not take effect + immediately; in the future, when the compositor deems implementation- + specific constraints are satisfied, the pointer confinement will be + activated and the compositor sends a confined event. + + The intersection of the region passed with this request and the input + region of the surface is used to determine where the pointer must be + in order for the confinement to activate. It is up to the compositor + whether to warp the pointer or require some kind of user interaction for + the confinement to activate. If the region is null the surface input + region is used. + + The request will create a new object wp_confined_pointer which is used + to interact with the confinement as well as receive updates about its + state. See the the description of wp_confined_pointer for further + information. + + + + + + + + + + + + The wp_locked_pointer interface represents a locked pointer state. + + While the lock of this object is active, the wl_pointer objects of the + associated seat will not emit any wl_pointer.motion events. + + This object will send the event 'locked' when the lock is activated. + Whenever the lock is activated, it is guaranteed that the locked surface + will already have received pointer focus and that the pointer will be + within the region passed to the request creating this object. + + To unlock the pointer, send the destroy request. This will also destroy + the wp_locked_pointer object. + + If the compositor decides to unlock the pointer the unlocked event is + sent. See wp_locked_pointer.unlock for details. + + When unlocking, the compositor may warp the cursor position to the set + cursor position hint. If it does, it will not result in any relative + motion events emitted via wp_relative_pointer. + + If the surface the lock was requested on is destroyed and the lock is not + yet activated, the wp_locked_pointer object is now defunct and must be + destroyed. + + + + + Destroy the locked pointer object. If applicable, the compositor will + unlock the pointer. + + + + + + Set the cursor position hint relative to the top left corner of the + surface. + + If the client is drawing its own cursor, it should update the position + hint to the position of its own cursor. A compositor may use this + information to warp the pointer upon unlock in order to avoid pointer + jumps. + + The cursor position hint is double buffered. The new hint will only take + effect when the associated surface gets it pending state applied. See + wl_surface.commit for details. + + + + + + + + Set a new region used to lock the pointer. + + The new lock region is double-buffered. The new lock region will + only take effect when the associated surface gets its pending state + applied. See wl_surface.commit for details. + + For details about the lock region, see wp_locked_pointer. + + + + + + + Notification that the pointer lock of the seat's pointer is activated. + + + + + + Notification that the pointer lock of the seat's pointer is no longer + active. If this is a oneshot pointer lock (see + wp_pointer_constraints.lifetime) this object is now defunct and should + be destroyed. If this is a persistent pointer lock (see + wp_pointer_constraints.lifetime) this pointer lock may again + reactivate in the future. + + + + + + + The wp_confined_pointer interface represents a confined pointer state. + + This object will send the event 'confined' when the confinement is + activated. Whenever the confinement is activated, it is guaranteed that + the surface the pointer is confined to will already have received pointer + focus and that the pointer will be within the region passed to the request + creating this object. It is up to the compositor to decide whether this + requires some user interaction and if the pointer will warp to within the + passed region if outside. + + To unconfine the pointer, send the destroy request. This will also destroy + the wp_confined_pointer object. + + If the compositor decides to unconfine the pointer the unconfined event is + sent. The wp_confined_pointer object is at this point defunct and should + be destroyed. + + + + + Destroy the confined pointer object. If applicable, the compositor will + unconfine the pointer. + + + + + + Set a new region used to confine the pointer. + + The new confine region is double-buffered. The new confine region will + only take effect when the associated surface gets its pending state + applied. See wl_surface.commit for details. + + If the confinement is active when the new confinement region is applied + and the pointer ends up outside of newly applied region, the pointer may + warped to a position within the new confinement region. If warped, a + wl_pointer.motion event will be emitted, but no + wp_relative_pointer.relative_motion event. + + The compositor may also, instead of using the new region, unconfine the + pointer. + + For details about the confine region, see wp_confined_pointer. + + + + + + + Notification that the pointer confinement of the seat's pointer is + activated. + + + + + + Notification that the pointer confinement of the seat's pointer is no + longer active. If this is a oneshot pointer confinement (see + wp_pointer_constraints.lifetime) this object is now defunct and should + be destroyed. If this is a persistent pointer confinement (see + wp_pointer_constraints.lifetime) this pointer confinement may again + reactivate in the future. + + + + + diff --git a/pointer_constraints.c b/pointer_constraints.c new file mode 100644 index 0000000..5bc5157 --- /dev/null +++ b/pointer_constraints.c @@ -0,0 +1,2021 @@ +/* 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 . */ + +#include +#include +#include + +#include +#include + +#include "compositor.h" +#include "pointer-constraints-unstable-v1.h" + +typedef struct _BarrierLine BarrierLine; +typedef enum _BarrierEdges BarrierEdges; + +typedef struct _PointerConfinementDataRecord PointerConfinementDataRecord; +typedef struct _PointerConfinement PointerConfinement; + +enum + { + IsOneShot = 1, + IsActive = (1 << 1), + IsDead = (1 << 2), + IsLock = (1 << 3), + IsCursorPositionHintSet = (1 << 4), + PendingRegion = (1 << 10), + PendingCursorPositionHint = (1 << 11), + }; + +struct _PointerConfinement +{ + /* The next and last confinements. */ + PointerConfinement *next, *last; + + /* The surface associated with the confinement. */ + Surface *surface; + + /* The seat associated with the confinement. */ + Seat *seat; + + /* The seat destruction key. */ + void *seat_key; + + /* The resource of the confinement. */ + struct wl_resource *resource; + + /* The region to confine the pointer to, or NULL. */ + pixman_region32_t *region; + + /* Any pending region. */ + pixman_region32_t *pending_region; + + /* A list of pointer barriers currently applied. */ + XIDList *applied_barriers; + + /* Any barrier lines currently applied. */ + BarrierLine *lines; + + /* Any commit callback. */ + CommitCallback *commit_callback; + + /* The number of such barrier lines. */ + int nlines; + + /* Various flags, i.e.: whether or not this is a 1-shot confinement, + and whether or not this is a lock. */ + int flags; + + /* The root_x and root_y of the last lock applied. */ + int root_x, root_y; + + /* The last known cursor position relative to the surface. */ + double last_cursor_x, last_cursor_y; + + /* The cursor position hint. */ + double cursor_position_x, cursor_position_y; + + /* Pending values. */ + double pending_x, pending_y; +}; + +/* Which edges are set. */ +enum _BarrierEdges + { + TopEdgeClosed = 1, + LeftEdgeClosed = (1 << 1), + BottomEdgeClosed = (1 << 2), + RightEdgeClosed = (1 << 3), + AllEdgesClosed = 0xf, + }; + +/* The extents of previously seen rectangles in the pointer + barrier. */ + +struct _BarrierLine +{ + /* The rectangle. */ + int x1, y1, x2, y2; + + /* Which edges are set. */ + int edges; +}; + +struct _PointerConfinementDataRecord +{ + /* List of all pointer confinements. */ + PointerConfinement confinements; +}; + +#define Int16Maximum 0x7fff +#define Int16Minimum (-1 - Int16Maximum) + +/* The pointer constraints global. */ +static struct wl_global *pointer_constraints_global; + +static BarrierLine * +FindLastBandEnd (BarrierLine *lines, BarrierLine *current) +{ + int y1; + + y1 = current->y1; + + while (--current >= lines) + { + if (current->y1 != y1) + return current; + } + + return NULL; +} + +static void +LineMove (BarrierLine *dest, BarrierLine *src, size_t nlines) +{ + memmove (dest, src, nlines * sizeof *dest); +} + +/* Close the top edge of the line at *IDX, an index into the line + buffer LINES. Open any intersecting edges of lines above. + + Update IDX with the index of the last line in the line buffer. */ + +static void +MaybeCloseTopEdge (BarrierLine *lines, int *idx, int max_lines) +{ + BarrierLine original, *last_band_start, *tem; + + /* Maybe close lines[*idx]'s top edge, if it does not overlap with + any previous line belonging to the last band. If it does, spit + the given line if the overlap is partial. */ + + original = lines[*idx]; + last_band_start = FindLastBandEnd (lines, &lines[*idx]); + + if (!last_band_start) + { + /* This is the first band, so close the top edge. */ + lines[*idx].edges |= TopEdgeClosed; + return; + } + + if (last_band_start->y2 != original.y1) + { + /* The last band does not touch this one; close its bottom + edge. */ + lines[*idx].edges |= TopEdgeClosed; + return; + } + + /* Mark the top edge as closed for now. */ + lines[*idx].edges |= TopEdgeClosed; + + /* Find overlaps between the last band and this one, and split and + open up the top edge for each overlap. */ + for (tem = last_band_start; + tem >= lines && tem->y2 == last_band_start->y2; --tem) + { + /* There are two categories of overlaps we care about. The + first is where the current line is entirely inside the line + above. + + +-------------------------------+ + | | <------- line above + +-------------------------------+ + +------------------------+ + | | <----------- current line + +------------------------+ */ + + if (tem->x1 <= lines[*idx].x1 && tem->x2 >= lines[*idx].x2) + { + /* In that case, it must be transformed to: + + +--+------------------------+---+ + | %(1) %(2)| + +--+ +---+ + + + + | | + +------------------------+ + + Where % denotes the places where tem must be split. */ + + if (tem->x1 != lines[*idx].x1) + { + /* Here, tem->x1 is less than lines[*idx]->x1. That + means we must actually do the split at %(1). */ + + if (*idx + 1 >= max_lines) + { + /* Fail if too many lines would be used. */ + *idx = max_lines; + return; + } + + /* Split tem. */ + LineMove (tem + 1, tem, max_lines - (tem - lines) - 1); + tem[1].edges &= ~LeftEdgeClosed; + tem->edges &= ~RightEdgeClosed; + + /* Increase idx to compensate for the movement. */ + (*idx)++; + + /* Finish the split. */ + tem->x2 = lines[*idx].x1; + tem[1].x1 = tem->x2; + + if (tem[1].x2 != lines[*idx].x2) + { + if (*idx + 1 >= max_lines) + { + /* Fail if too many lines would be used. */ + *idx = max_lines; + return; + } + + /* Split tem[1] off at %(2). */ + LineMove (tem + 2, tem + 1, max_lines - (tem - lines) - 2); + tem[2].edges &= ~LeftEdgeClosed; + tem[1].edges &= ~RightEdgeClosed; + + /* Increase idx to compensate for the movement. */ + (*idx)++; + + /* Finish the split. */ + tem[1].x2 = lines[*idx].x2; + tem[2].x1 = lines[*idx].x2; + } + + /* Open tem[1]'s bottom edge. */ + tem[1].edges &= ~BottomEdgeClosed; + } + else + { + /* Here, tem->x1 is equal to lines[*idx].x1. That + means the split at %(1) is unnecessary. */ + + if (tem->x2 != lines[*idx].x2) + { + if (*idx + 1 >= max_lines) + { + /* Fail if too many lines would be used. */ + *idx = max_lines; + return; + } + + /* Split tem off at %(2). */ + LineMove (tem + 1, tem, max_lines - (tem - lines) - 1); + tem[1].edges &= ~LeftEdgeClosed; + tem->edges &= ~RightEdgeClosed; + + /* Increase idx to compensate for the movement. */ + (*idx)++; + + /* Finish the split. */ + tem->x2 = lines[*idx].x2; + tem[1].x1 = lines[*idx].x2; + } + + /* Open tem's bottom edge. */ + tem->edges &= ~BottomEdgeClosed; + } + + /* Open lines[*idx]'s top edge. */ + lines[*idx].edges &= ~TopEdgeClosed; + + return; + } + + /* The second category is where the current line overlaps with + the line above. + + +-----------------------------------------------+ + tem->x1 | | tem->x2 + +-----------------------------------------------+ + +------------------------------------------------+ +lines[*idx].x1 | | lines[*idx].x2 + +------------------------------------------------+ + + (In other words, tem->x1 < lines[*idx].x1 + && tem->x2 < lines[*idx].x2 + && tem->x1 < lines[*idx].x2 + && tem->x2 > lines[*idx].x1) + + The case shown above is particularly nasty, because it + requires splitting both tem and lines[*idx]. This is case + A. + + +-------------------+ + tem->x1 | | tem->x2 + +-------------------+ + +------------------------------------------------+ +lines[*idx].x1 | | lines[*idx].x2 + +------------------------------------------------+ + + (In other words, tem->x1 > lines[*idx].x1 + && tem->x2 > lines[*idx].x2 + && tem->x1 < lines[*idx].x2 + && tem->x2 > lines[*idx].x1) + + The case shown above also requires splitting both lines. + This is case B. + + +------------------------------------------------+ + | | + +------------------------------------------------+ + +--------------------------------------------------------+ + | | + +--------------------------------------------------------+ + + (In other words, tem->x1 >= lines[*idx].x1 + && tem->x2 <= lines[*idx].x2) + + This case only requires splitting the current line. It is + case C. */ + + if (tem->x1 < lines[*idx].x1 + && tem->x2 < lines[*idx].x2 + && tem->x1 < lines[*idx].x2 + && tem->x2 > lines[*idx].x1) + { + /* Case A. Perform the following splits and transforms: + + +----%(1)---------------------------------------+ + | | + +----%(1) + + + %(2)--+ + | | + +------------------------------------------%(2)--+ */ + + if (*idx + 2 >= max_lines) + { + /* Fail if too many lines would be used. */ + *idx = max_lines; + return; + } + + /* Do the first split. */ + LineMove (tem + 1, tem, max_lines - (tem - lines) - 1); + tem[1].edges &= ~LeftEdgeClosed; + tem->edges &= ~RightEdgeClosed; + + /* Increase idx to compensate for the movement. */ + (*idx)++; + + /* Finish the split. */ + tem->x2 = lines[*idx].x1; + tem[1].x1 = lines[*idx].x1; + + /* Do the second split. Leave idx intact. */ + LineMove (&lines[*idx + 1], &lines[*idx], max_lines - *idx - 1); + lines[*idx + 1].edges &= ~LeftEdgeClosed; + lines[*idx].edges &= ~RightEdgeClosed; + + /* Finish the second split. */ + lines[*idx].x2 = tem[1].x2; + lines[*idx + 1].x1 = tem[1].x2; + + /* Open lines[*idx]'s top edge and tem[1]'s bottom edge. */ + lines[*idx].edges &= ~TopEdgeClosed; + tem[1].edges &= ~BottomEdgeClosed; + + /* Increment idx. */ + (*idx)++; + + return; + } + else if (tem->x1 > lines[*idx].x1 + && tem->x2 > lines[*idx].x2 + && tem->x1 < lines[*idx].x2 + && tem->x2 > lines[*idx].x1) + { + /* Case B. Perform the following splits and transforms: + + +----------%(1)-----+ + | | + + %(1)-----+ + +-------------------------------------%(2) + + | | + +-------------------------------------%(2)-------+ */ + + if (*idx + 2 >= max_lines) + { + /* Fail if too many lines would be used. */ + *idx = max_lines; + return; + } + + /* Do the first split. */ + LineMove (tem + 1, tem, max_lines - (tem - lines) - 1); + tem[1].edges &= ~LeftEdgeClosed; + tem->edges &= ~RightEdgeClosed; + + /* Increase idx to compensate for movement. */ + (*idx)++; + + /* Finish the split. */ + tem->x2 = lines[*idx].x2; + tem[1].x1 = lines[*idx].x2; + + /* Do the second split. Do not increase idx. */ + LineMove (&lines[*idx + 1], &lines[*idx], max_lines - *idx - 1); + lines[*idx + 1].edges &= ~LeftEdgeClosed; + lines[*idx].edges &= ~RightEdgeClosed; + + /* Finish the second split. */ + lines[*idx].x2 = tem->x1; + lines[*idx + 1].x1 = tem->x1; + + /* Now, open lines[*idx + 1]'s top edge and tem's bottom + edge. */ + lines[*idx + 1].edges &= ~TopEdgeClosed; + tem->edges &= ~BottomEdgeClosed; + + /* Finally, resolve edges for line[*idx], which may still + have overlaps to the top. */ + MaybeCloseTopEdge (lines, idx, max_lines); + + /* And increment *idx. */ + (*idx)++; + + return; + } + else if (tem->x1 >= lines[*idx].x1 + && tem->x2 <= lines[*idx].x2) + { + /* Case C. Do the following splits and transforms: + + +------------------------------------------------+ + | | + + + + +---%(1) %(2)+ + | | + +---%(1)---------------------------------------------%(2)+ */ + + if (tem->x1 != lines[*idx].x1) + { + if (*idx + 1 >= max_lines) + { + /* Fail if too many lines would be used. */ + *idx = max_lines; + return; + } + + /* Do the first split. Do not increase idx yet. */ + LineMove (&lines[*idx + 1], &lines[*idx], + max_lines - *idx - 1); + lines[*idx + 1].edges &= ~LeftEdgeClosed; + lines[*idx].edges &= ~RightEdgeClosed; + + /* Finish the split. */ + lines[*idx].x2 = tem->x1; + lines[*idx + 1].x1 = tem->x1; + + if (tem->x2 != lines[*idx + 1].x2) + { + if (*idx + 1 >= max_lines) + { + /* Fail if too many lines would be used. */ + *idx = max_lines; + return; + } + + /* Do the second split. */ + LineMove (&lines[*idx + 2], &lines[*idx + 1], + max_lines - *idx - 2); + lines[*idx + 2].edges &= ~LeftEdgeClosed; + lines[*idx + 1].edges &= ~RightEdgeClosed; + + /* Finish the split. */ + lines[*idx + 1].x2 = tem->x2; + lines[*idx + 2].x1 = tem->x2; + + /* Now, we are in a very complicated state of + affairs. idx is the index of a line that must be + processed again. idx + 1 is the index of the + line we are really processing, and idx + 2 is a + rectangle that does not overlap with anything + above (or otherwise case B or the first category + would have run first). First, open tem's bottom + corner and lines[idx + 1]'s top corner. */ + tem->edges &= ~BottomEdgeClosed; + lines[*idx + 1].edges &= ~TopEdgeClosed; + + /* Next, process splits for lines[*idx]. */ + MaybeCloseTopEdge (lines, idx, max_lines); + + /* Finally, increment idx by 2. */ + *idx += 2; + } + else + { + /* The second split is not required. Simply open + tem's bottom edge, and lines[*idx + 1]'s top + edge. */ + tem->edges &= ~BottomEdgeClosed; + lines[*idx + 1].edges &= ~TopEdgeClosed; + + /* And process splits for lines[*idx]. */ + MaybeCloseTopEdge (lines, idx, max_lines); + + /* Increment idx by 1. */ + (*idx)++; + } + } + else + { + if (*idx + 1 >= max_lines) + { + /* Fail if too many lines would be used. */ + *idx = max_lines; + return; + } + + /* Do the second split. The first split is not + required, as it would generate an empty line. The + second split is always required here, because + otherwise this would be a first category overlap. */ + LineMove (&lines[*idx + 1], &lines[*idx], + max_lines - *idx - 1); + lines[*idx + 1].edges &= ~LeftEdgeClosed; + lines[*idx].edges &= ~RightEdgeClosed; + + /* Finish the split. */ + lines[*idx].x2 = tem->x2; + lines[*idx + 1].x1 = tem->x2; + + /* idx is the index of the overlapping line. */ + tem->edges &= ~BottomEdgeClosed; + lines[*idx].edges &= ~TopEdgeClosed; + + /* Increment idx. */ + (*idx)++; + } + + return; + } + } +} + +static BarrierLine * +ComputeBarrier (pixman_region32_t *region, int *nlines) +{ + BarrierLine *lines; + pixman_box32_t *boxes; + int i, nrects, l, max_lines; + + /* Compute a list of rectangles along with edges that compose a + pointer barrier confining the pointer within the region specified + by REGION. The rectangles in the region must be in the XY banded + order used by the X server. */ + + boxes = pixman_region32_rectangles (region, &nrects); + l = 0; + + /* Each rectangle can be split into 3 rectangles, potentially more + taking into account left splitting. Assume there are 6 lines for + each rectangle; should it take any more, just error. */ + max_lines = nrects * 6; + lines = XLMalloc (max_lines * sizeof *lines); + + for (i = 0; i < nrects; ++i) + { + /* Compute which sides of each rectangle are "open", i.e. face + towards another rectangle. */ + + if (!l) + { + /* Initialize the first line. The rectangle is definitely + closed on the top and the left, and may be open on the + right and bottom. */ + lines[l].x1 = boxes[i].x1; + lines[l].y1 = boxes[i].y1; + lines[l].x2 = boxes[i].x2; + lines[l].y2 = boxes[i].y2; + lines[l].edges = (TopEdgeClosed | LeftEdgeClosed + | RightEdgeClosed | BottomEdgeClosed); + + l++; + + if (l >= max_lines) + /* L is too big. */ + goto failure; + } + else + { + if (lines[l - 1].y1 == boxes[i].y1) + { + /* The rect is in the same band as the line above. This + means that only its x1 and x2 are different. If x1 + == last.x2, extend last to x2. */ + if (boxes[i].x1 == lines[l - 1].x2) + { + l -= 1; + lines[l].x2 = boxes[i].x2; + } + else + { + lines[l].x1 = boxes[i].x1; + lines[l].y1 = boxes[i].y1; + lines[l].x2 = boxes[i].x2; + lines[l].y2 = boxes[i].y2; + + /* The left and right edges are definitely closed. + The bottom edge won't be opened until the next + line starts being processed. */ + lines[l].edges = (LeftEdgeClosed + | BottomEdgeClosed + | RightEdgeClosed); + } + + /* Compute whether or not the top edge is closed. */ + MaybeCloseTopEdge (lines, &l, max_lines); + + /* l is now the index of the last used line. Increase + it. */ + l++; + + if (l >= max_lines) + /* L is too big. */ + goto failure; + } + else + { + /* Initialize the first line of this new band. The + rectangle in this band closed on the left, and may be + open on the right, top and bottom. */ + lines[l].x1 = boxes[i].x1; + lines[l].y1 = boxes[i].y1; + lines[l].x2 = boxes[i].x2; + lines[l].y2 = boxes[i].y2; + lines[l].edges = (LeftEdgeClosed + | BottomEdgeClosed + | RightEdgeClosed); + + /* Compute whether or not the top edge is closed. */ + MaybeCloseTopEdge (lines, &l, max_lines); + + /* l is now the index of the last used line. Increase + it. */ + l++; + + if (l >= max_lines) + /* L is too big. */ + goto failure; + } + } + } + + /* Close the right edge of the last line. */ + if (l) + lines[l - 1].edges |= RightEdgeClosed; + + *nlines = l; + return lines; + + failure: + XLFree (lines); + return NULL; +} + +static void +Destroy (struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy (resource); +} + + + +static void +FreePointerConfinementDataRecord (void *pointer) +{ + PointerConfinementDataRecord *data; + PointerConfinement *confinement; + + data = pointer; + + /* Assert that the record has been initialized correctly. */ + XLAssert (data->confinements.next != NULL); + + /* Detach each confinement. */ + confinement = data->confinements.next; + + while (confinement != &data->confinements) + { + confinement->surface = NULL; + confinement->commit_callback = NULL; + confinement = confinement->next; + } +} + +static void +InitConfinementData (PointerConfinementDataRecord *data) +{ + /* This record has already been initialized. */ + if (data->confinements.next) + return; + + /* Initialize the pointer confinement data. */ + data->confinements.next = &data->confinements; + data->confinements.last = &data->confinements; +} + +static void +DestroyConfinedPointer (struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy (resource); +} + +static void +DestroyLockedPointer (struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy (resource); +} + +/* Forward declaration. */ +static void RecheckPointerConfinement (Seat *, PointerConfinement *); + +static void +HandleSurfaceCommit (Surface *surface, void *data) +{ + PointerConfinement *confinement; + + confinement = data; + + if (confinement->flags & PendingRegion) + { + /* Free any existing region. */ + if (confinement->region) + { + pixman_region32_fini (confinement->region); + XLFree (confinement->region); + } + + /* Apply the new region. */ + confinement->region = confinement->pending_region; + confinement->pending_region = NULL; + + if (confinement->seat) + /* If the seat is set, recheck pointer confinement. If this + region change would cause the confinement to deactivate, + deactivate it and vice versa. */ + RecheckPointerConfinement (confinement->seat, confinement); + + /* Clear the pending region flag. */ + confinement->flags &= ~PendingRegion; + } + + if (confinement->flags & PendingCursorPositionHint) + { + confinement->cursor_position_x = confinement->pending_x; + confinement->cursor_position_y = confinement->pending_y; + confinement->flags &= ~PendingCursorPositionHint; + confinement->flags |= IsCursorPositionHintSet; + } +} + +static void +SetCursorPositionHint (struct wl_client *client, struct wl_resource *resource, + wl_fixed_t surface_x, wl_fixed_t surface_y) +{ + PointerConfinement *confinement; + + confinement = wl_resource_get_user_data (resource); + + if (!confinement) + /* This is a resource created for an inert seat. */ + return; + + confinement->pending_x = wl_fixed_to_double (surface_x); + confinement->pending_y = wl_fixed_to_double (surface_y); + confinement->flags |= PendingCursorPositionHint; + + if (!confinement->commit_callback && confinement->surface) + /* Attach a commit callback so we know when to apply the + region. */ + confinement->commit_callback + = XLSurfaceRunAtCommit (confinement->surface, HandleSurfaceCommit, + confinement); +} + +static void +SetRegion (struct wl_client *client, struct wl_resource *resource, + struct wl_resource *region_resource) +{ + PointerConfinement *confinement; + pixman_region32_t *new_region; + + confinement = wl_resource_get_user_data (resource); + + if (!confinement) + /* This is a resource created for an inert seat. */ + return; + + /* The region changed. First, copy the new region to the old + one. */ + if (!region_resource) + { + if (confinement->pending_region) + pixman_region32_fini (confinement->pending_region); + XLFree (confinement->pending_region); + confinement->pending_region = NULL; + } + else + { + if (!confinement->pending_region) + { + confinement->pending_region + = XLMalloc (sizeof *confinement->pending_region); + pixman_region32_init (confinement->pending_region); + } + + /* Copy the new region over. */ + new_region = wl_resource_get_user_data (region_resource); + pixman_region32_copy (confinement->pending_region, new_region); + } + + confinement->flags |= PendingRegion; + + if (!confinement->commit_callback && confinement->surface) + /* Attach a commit callback so we know when to apply the + region. */ + confinement->commit_callback + = XLSurfaceRunAtCommit (confinement->surface, HandleSurfaceCommit, + confinement); +} + +static const struct zwp_confined_pointer_v1_interface confined_pointer_impl = + { + .destroy = DestroyConfinedPointer, + .set_region = SetRegion, + }; + +static const struct zwp_locked_pointer_v1_interface locked_pointer_impl = + { + .destroy = DestroyLockedPointer, + .set_cursor_position_hint = SetCursorPositionHint, + /* SetRegion can be shared between both types of resources. */ + .set_region = SetRegion, + }; + +static void +FreeSingleBarrier (XID xid) +{ + XFixesDestroyPointerBarrier (compositor.display, xid); +} + +/* Forward declaration. */ +static void DeactivateConfinement (PointerConfinement *); + +static void +HandleResourceDestroy (struct wl_resource *resource) +{ + PointerConfinement *confinement; + + confinement = wl_resource_get_user_data (resource); + + /* Deactivate the confinement if it is active. */ + if (confinement->flags & IsActive) + DeactivateConfinement (confinement); + + if (confinement->surface) + { + /* Detach the confinement. */ + confinement->next->last = confinement->last; + confinement->last->next = confinement->next; + + /* Clear the surface field in case some code later on relies on + that. */ + confinement->surface = NULL; + + /* Remove the commit callback. */ + XLSurfaceCancelCommitCallback (confinement->commit_callback); + confinement->commit_callback = NULL; + } + + /* Free all pointer barriers activated. */ + XIDListFree (confinement->applied_barriers, + FreeSingleBarrier); + confinement->applied_barriers = NULL; + + /* Free lines if they are set. */ + XLFree (confinement->lines); + confinement->lines = NULL; + + /* Free the seat key. */ + if (confinement->seat_key) + XLSeatCancelDestroyListener (confinement->seat_key); + + /* Free the confinement. */ + if (confinement->region) + pixman_region32_fini (confinement->region); + XLFree (confinement->region); + + /* And any pending region. */ + if (confinement->pending_region) + pixman_region32_fini (confinement->pending_region); + XLFree (confinement->pending_region); + + /* Free the resource data. */ + XLFree (confinement); +} + +static void +HandleSeatDestroyed (void *data) +{ + PointerConfinement *confinement; + + /* Since the seat is gone, it no longer makes any sense to keep the + confinement around any longer. Clear the surface field and + remove the confinement from the surface. */ + + confinement = data; + + /* Deactivate the confinement. */ + if (confinement->flags & IsActive) + DeactivateConfinement (confinement); + + if (confinement->surface) + { + /* Detach the confinement. */ + confinement->next->last = confinement->last; + confinement->last->next = confinement->next; + + /* Clear the confinement surface field. */ + confinement->surface = NULL; + + /* Cancel the commit callback. */ + XLSurfaceCancelCommitCallback (confinement->commit_callback); + confinement->commit_callback = NULL; + } + + confinement->seat = NULL; + confinement->seat_key = NULL; + + /* Free all pointer barriers previously activated. */ + XIDListFree (confinement->applied_barriers, + FreeSingleBarrier); + confinement->applied_barriers = NULL; +} + + + +static void +RecheckPointerConfinement (Seat *seat, PointerConfinement *confinement) +{ + Surface *surface; + double x, y, root_x, root_y; + + XLSeatGetMouseData (seat, &surface, &x, &y, &root_x, &root_y); + + if (surface == confinement->surface) + /* Check if the surface contains the pointer barrier. */ + XLPointerBarrierCheck (seat, surface, x, y, root_x, root_y); + else if (!surface && confinement->flags & IsActive) + /* The pointer is not in that surface anymore, and it is + active. */ + DeactivateConfinement (confinement); +} + +static PointerConfinement * +FindConfinement (PointerConfinementDataRecord *data, Seat *seat) +{ + PointerConfinement *confinement; + + confinement = data->confinements.next; + while (confinement != &data->confinements) + { + if (confinement->seat == seat) + return confinement; + } + + return NULL; +} + +static void +LockPointer (struct wl_client *client, struct wl_resource *resource, + uint32_t id, struct wl_resource *surface_resource, + struct wl_resource *pointer_resource, + struct wl_resource *region_resource, uint32_t lifetime) +{ + PointerConfinement *confinement; + PointerConfinementDataRecord *data; + Surface *surface; + Pointer *pointer; + Seat *seat; + struct wl_resource *dummy_resource; + + /* Pointer locking is implemented very similarly to pointer + constraints. The major difference is that reporting pointer + events is not allowed when the lock is in effect, and the + constraint rectangle is always 1x1 around the pointer. */ + + surface = wl_resource_get_user_data (surface_resource); + pointer = wl_resource_get_user_data (pointer_resource); + seat = XLPointerGetSeat (pointer); + + /* If seat is inert, then the confinement will never trigger. + Simply create a blank zwp_locked_pointer_v1 resource that never + does anything. */ + if (XLSeatIsInert (seat)) + { + dummy_resource = wl_resource_create (client, + &zwp_locked_pointer_v1_interface, + wl_resource_get_version (resource), + id); + + if (!dummy_resource) + wl_resource_post_no_memory (resource); + else + wl_resource_set_implementation (dummy_resource, &locked_pointer_impl, + NULL, NULL); + + return; + } + + data = XLSurfaceGetClientData (surface, PointerConfinementData, + sizeof *data, + FreePointerConfinementDataRecord); + + /* Potentially initialize confinement data. */ + InitConfinementData (data); + +#define AlreadyConstrained \ + ZWP_POINTER_CONSTRAINTS_V1_ERROR_ALREADY_CONSTRAINED + + if (FindConfinement (data, seat)) + { + wl_resource_post_error (resource, AlreadyConstrained, + "pointer constraint already requested on" + " the given surface"); + return; + } + +#undef AlreadyConstrained + + if (lifetime != ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT + && lifetime != ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT) + { + /* The lifetime specified is invalid. */ + wl_resource_post_error (resource, WL_DISPLAY_ERROR_INVALID_OBJECT, + "invalid constraint lifetime"); + return; + } + + confinement = XLCalloc (1, sizeof *confinement); + + /* Try to create the locked pointer resource. */ + confinement->resource + = wl_resource_create (client, &zwp_locked_pointer_v1_interface, + wl_resource_get_version (resource), id); + + if (!confinement->resource) + { + wl_resource_post_no_memory (resource); + XLFree (confinement); + return; + } + + if (region_resource) + { + /* Copy over the confinement region. */ + confinement->region = XLMalloc (sizeof *confinement->region); + pixman_region32_init (confinement->region); + pixman_region32_copy (confinement->region, + wl_resource_get_user_data (region_resource)); + } + + /* Mark this as a lock. */ + confinement->flags |= IsLock; + + if (lifetime == ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT) + confinement->flags |= IsOneShot; + + /* Attach the surface. */ + confinement->surface = surface; + + /* Attach the seat. */ + confinement->seat = seat; + confinement->seat_key + = XLSeatRunOnDestroy (seat, HandleSeatDestroyed, confinement); + + /* Link the confinement onto the list. */ + confinement->next = data->confinements.next; + confinement->last = &data->confinements; + data->confinements.next->last = confinement; + data->confinements.next = confinement; + + /* Attach the resource implementation. */ + wl_resource_set_implementation (confinement->resource, + &locked_pointer_impl, + confinement, + HandleResourceDestroy); + + /* Check if the pointer can be confined. */ + RecheckPointerConfinement (seat, confinement); +} + +static void +ConfinePointer (struct wl_client *client, struct wl_resource *resource, + uint32_t id, struct wl_resource *surface_resource, + struct wl_resource *pointer_resource, + struct wl_resource *region_resource, uint32_t lifetime) +{ + PointerConfinement *confinement; + PointerConfinementDataRecord *data; + Surface *surface; + Pointer *pointer; + Seat *seat; + struct wl_resource *dummy_resource; + + surface = wl_resource_get_user_data (surface_resource); + pointer = wl_resource_get_user_data (pointer_resource); + seat = XLPointerGetSeat (pointer); + + /* If seat is inert, then the confinement will never trigger. + Simply create a blank zwp_confined_pointer_v1 resource that never + does anything. */ + if (XLSeatIsInert (seat)) + { + dummy_resource = wl_resource_create (client, + &zwp_confined_pointer_v1_interface, + wl_resource_get_version (resource), + id); + + if (!dummy_resource) + wl_resource_post_no_memory (resource); + else + wl_resource_set_implementation (dummy_resource, &confined_pointer_impl, + NULL, NULL); + + return; + } + + data = XLSurfaceGetClientData (surface, PointerConfinementData, + sizeof *data, + FreePointerConfinementDataRecord); + + /* Potentially initialize the confinement data. */ + InitConfinementData (data); + +#define AlreadyConstrained \ + ZWP_POINTER_CONSTRAINTS_V1_ERROR_ALREADY_CONSTRAINED + + if (FindConfinement (data, seat)) + { + wl_resource_post_error (resource, AlreadyConstrained, + "pointer constraint already requested on" + " the given surface"); + return; + } + +#undef AlreadyConstrained + + if (lifetime != ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT + && lifetime != ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT) + { + /* The lifetime specified is invalid. */ + wl_resource_post_error (resource, WL_DISPLAY_ERROR_INVALID_OBJECT, + "invalid constraint lifetime"); + return; + } + + confinement = XLCalloc (1, sizeof *confinement); + + /* Try to create the confined pointer resource. */ + confinement->resource + = wl_resource_create (client, &zwp_confined_pointer_v1_interface, + wl_resource_get_version (resource), id); + + if (!confinement->resource) + { + wl_resource_post_no_memory (resource); + XLFree (confinement); + return; + } + + if (region_resource) + { + /* Copy over the confinement region. */ + confinement->region = XLMalloc (sizeof *confinement->region); + pixman_region32_init (confinement->region); + pixman_region32_copy (confinement->region, + wl_resource_get_user_data (region_resource)); + } + + if (lifetime == ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT) + confinement->flags |= IsOneShot; + + /* Attach the surface. */ + confinement->surface = surface; + + /* Attach the seat. */ + confinement->seat = seat; + confinement->seat_key + = XLSeatRunOnDestroy (seat, HandleSeatDestroyed, confinement); + + /* Link the confinement onto the list. */ + confinement->next = data->confinements.next; + confinement->last = &data->confinements; + data->confinements.next->last = confinement; + data->confinements.next = confinement; + + /* Attach the resource implementation. */ + wl_resource_set_implementation (confinement->resource, + &confined_pointer_impl, + confinement, + HandleResourceDestroy); + + /* Check if the pointer can be confined. */ + RecheckPointerConfinement (seat, confinement); +} + +static struct zwp_pointer_constraints_v1_interface pointer_constraints_impl = + { + .destroy = Destroy, + .lock_pointer = LockPointer, + .confine_pointer = ConfinePointer, + }; + +static void +HandleBind (struct wl_client *client, void *data, uint32_t version, + uint32_t id) +{ + struct wl_resource *resource; + + resource = wl_resource_create (client, &zwp_pointer_constraints_v1_interface, + version, id); + + if (!resource) + { + wl_client_post_no_memory (client); + return; + } + + wl_resource_set_implementation (resource, &pointer_constraints_impl, + NULL, NULL); +} + +#ifdef DEBUG + +static GC +GetDebugGC (Window window) +{ + static GC gc; + XGCValues gcvalues; + XColor color; + + if (gc) + return gc; + + color.red = 0; + color.green = 0; + color.blue = 0; + + if (!XAllocColor (compositor.display, compositor.colormap, + &color)) + abort (); + + gcvalues.foreground = color.pixel; + gcvalues.line_width = 1; + gcvalues.subwindow_mode = IncludeInferiors; + + gc = XCreateGC (compositor.display, window, + GCForeground | GCLineWidth | GCSubwindowMode, + &gcvalues); + return gc; +} + +#endif + +static void +ApplyLines (Window window, PointerConfinement *confinement, + BarrierLine *lines, int nlines, int root_x, int root_y) +{ + int i, device_id; + PointerBarrier barrier; +#ifdef DEBUG + GC gc; + + gc = GetDebugGC (window); +#endif + + /* Free all pointer barriers previously activated. */ + XIDListFree (confinement->applied_barriers, + FreeSingleBarrier); + confinement->applied_barriers = NULL; + + i = 0; + + /* Set the pointer device. */ + device_id = XLSeatGetPointerDevice (confinement->seat); + + if (nlines == 1 && lines[0].edges == AllEdgesClosed) + { + /* lines[0] is a perfect rectangle, and also the only rectangle. + The X server has trouble keeping the mouse confined to really + accurate barriers, so in this case create 4 intersecting + barriers where each barrier extends along its blocking axis + to the edges of the screen. */ + + /* Top. */ + barrier + = XFixesCreatePointerBarrier (compositor.display, window, + Int16Minimum, root_y + lines[0].y1, + Int16Maximum, root_y + lines[0].y1, + BarrierPositiveY, 1, &device_id); + + confinement->applied_barriers + = XIDListPrepend (confinement->applied_barriers, + barrier); + + /* Bottom. */ + barrier + = XFixesCreatePointerBarrier (compositor.display, window, + Int16Minimum, root_y + lines[0].y2 - 1, + Int16Maximum, root_y + lines[0].y2 - 1, + BarrierNegativeY, 1, &device_id); + + confinement->applied_barriers + = XIDListPrepend (confinement->applied_barriers, + barrier); + + /* Left. */ + barrier + = XFixesCreatePointerBarrier (compositor.display, window, + root_x + lines[0].x1, Int16Minimum, + root_x + lines[0].x1, Int16Maximum, + BarrierPositiveX, 1, &device_id); + + confinement->applied_barriers + = XIDListPrepend (confinement->applied_barriers, + barrier); + + /* Right. */ + barrier + = XFixesCreatePointerBarrier (compositor.display, window, + root_x + lines[0].x2 - 1, Int16Minimum, + root_x + lines[0].x2 - 1, Int16Maximum, + BarrierNegativeX, 1, &device_id); + + confinement->applied_barriers + = XIDListPrepend (confinement->applied_barriers, + barrier); + + return; + } + + /* For each rectangle, draw the lines that are set. */ + for (i = 0; i < nlines; ++i) + { + /* Due to X server bugs, this fails to constrain the pointer + reliably. */ + + if (lines[i].edges & TopEdgeClosed) + { + barrier = XFixesCreatePointerBarrier (compositor.display, + window, + root_x + lines[i].x1, + root_y + lines[i].y1, + root_x + lines[i].x2 - 1, + root_y + lines[i].y1, + BarrierPositiveY, + 1, &device_id); + +#ifdef DEBUG + XDrawLine (compositor.display, window, gc, lines[i].x1, + lines[i].y1, lines[i].x2 - 1, lines[i].y1); +#endif + + confinement->applied_barriers + = XIDListPrepend (confinement->applied_barriers, + barrier); + } + + if (lines[i].edges & LeftEdgeClosed) + { + barrier = XFixesCreatePointerBarrier (compositor.display, + window, + root_x + lines[i].x1, + root_y + lines[i].y1, + root_x + lines[i].x1, + root_y + lines[i].y2 - 1, + BarrierPositiveX, + 1, &device_id); +#ifdef DEBUG + XDrawLine (compositor.display, window, gc, lines[i].x1, + lines[i].y1, lines[i].x1, lines[i].y2 - 1); +#endif + + confinement->applied_barriers + = XIDListPrepend (confinement->applied_barriers, + barrier); + } + + if (lines[i].edges & RightEdgeClosed) + { + barrier = XFixesCreatePointerBarrier (compositor.display, + window, + root_x + lines[i].x2 - 1, + root_y + lines[i].y1, + root_x + lines[i].x2 - 1, + root_y + lines[i].y2 - 1, + BarrierNegativeX, + 1, &device_id); +#ifdef DEBUG + XDrawLine (compositor.display, window, gc, lines[i].x2 - 1, + lines[i].y1, lines[i].x2 - 1, lines[i].y2 - 1); +#endif + + confinement->applied_barriers + = XIDListPrepend (confinement->applied_barriers, + barrier); + } + + if (lines[i].edges & BottomEdgeClosed) + { + barrier = XFixesCreatePointerBarrier (compositor.display, + window, + root_x + lines[i].x1, + root_y + lines[i].y2 - 1, + root_x + lines[i].x2 - 1, + root_y + lines[i].y2 - 1, + BarrierNegativeY, + 1, &device_id); +#ifdef DEBUG + XDrawLine (compositor.display, window, gc, lines[i].x1, + lines[i].y2 - 1, lines[i].x2 - 1, lines[i].y2 - 1); +#endif + + confinement->applied_barriers + = XIDListPrepend (confinement->applied_barriers, + barrier); + } + } +} + +static Bool +DrawPointerBarriers (PointerConfinement *confinement, + pixman_region32_t *region, + int *root_x_return, int *root_y_return) +{ + BarrierLine *lines; + int nlines, root_x, root_y; + Window window, child; + + XLFree (confinement->lines); + confinement->lines = NULL; + confinement->nlines = 0; + + if (!confinement->surface) + return False; + + window = XLWindowFromSurface (confinement->surface); + + /* Decompose the region into rectangles that can contain up to 4 + lines. */ + lines = ComputeBarrier (region, &nlines); + + if (!lines) + return False; + + if (root_x_return && root_y_return + && *root_x_return != INT_MIN + && *root_y_return != INT_MIN) + { + /* This mechanism is simply used to avoid redundant syncs when + recursively processing subsurfaces. */ + root_x = *root_x_return; + root_y = *root_y_return; + } + else + { + /* Obtain the root-window relative coordinates of the window. */ + XTranslateCoordinates (compositor.display, window, + DefaultRootWindow (compositor.display), + 0, 0, &root_x, &root_y, &child); + + if (root_x_return) + *root_x_return = root_x; + + if (root_y_return) + *root_y_return = root_y; + } + + /* Apply the lines. */ + ApplyLines (window, confinement, lines, nlines, root_x, root_y); + + /* Set the lines. */ + confinement->lines = lines; + confinement->nlines = nlines; + + return True; +} + +static void +DrawLock (PointerConfinement *confinement, double root_x_subpixel, + double root_y_subpixel) +{ + Window window; + int device_id, root_x, root_y; + PointerBarrier barrier; + + root_x = lrint (root_x_subpixel); + root_y = lrint (root_y_subpixel); + + /* Draw a lock, meaning a 1x1 rectangle around root_x and root_y. + Truncate root_x and root_y, like the X server. This serves to + prevent the cursor from moving onscreen. */ + + window = XLWindowFromSurface (confinement->surface); + + /* Free all pointer barriers previously activated. */ + XIDListFree (confinement->applied_barriers, + FreeSingleBarrier); + confinement->applied_barriers = NULL; + + /* Set the pointer device. */ + device_id = XLSeatGetPointerDevice (confinement->seat); + + /* Top. */ + barrier = XFixesCreatePointerBarrier (compositor.display, window, + Int16Minimum, root_y, + Int16Maximum, root_y, + BarrierPositiveY, 1, &device_id); + + confinement->applied_barriers + = XIDListPrepend (confinement->applied_barriers, + barrier); + + /* Bottom. */ + barrier = XFixesCreatePointerBarrier (compositor.display, window, + Int16Minimum, root_y + 1, + Int16Maximum, root_y + 1, + BarrierNegativeY, 1, &device_id); + + confinement->applied_barriers + = XIDListPrepend (confinement->applied_barriers, + barrier); + + /* Left. */ + barrier = XFixesCreatePointerBarrier (compositor.display, window, + root_x, Int16Minimum, + root_x, Int16Maximum, + BarrierPositiveX, 1, &device_id); + confinement->applied_barriers + = XIDListPrepend (confinement->applied_barriers, + barrier); + + /* Right. */ + barrier = XFixesCreatePointerBarrier (compositor.display, window, + root_x + 1, Int16Minimum, + root_x + 1, Int16Maximum, + BarrierNegativeX, 1, &device_id); + confinement->applied_barriers + = XIDListPrepend (confinement->applied_barriers, + barrier); + + /* Set the last root_x and root_y. */ + confinement->root_x = root_x; + confinement->root_y = root_y; + + /* Warp the pointer to root_x by root_y, after rounding it. */ + XIWarpPointer (compositor.display, device_id, None, + DefaultRootWindow (compositor.display), + 0.0, 0.0, 0.0, 0.0, root_x, root_y); +} + +static void +WarpToHint (PointerConfinement *confinement) +{ + int offset_x, offset_y; + Window window, child; + int root_x, root_y, device_id; + + if (!confinement->surface || !confinement->seat) + return; + + window = XLWindowFromSurface (confinement->surface); + device_id = XLSeatGetPointerDevice (confinement->seat); + + if (!window) + return; + + ViewTranslate (confinement->surface->view, 0, 0, &offset_x, + &offset_y); + XTranslateCoordinates (compositor.display, window, + DefaultRootWindow (compositor.display), + 0, 0, &root_x, &root_y, &child); + + /* Warp the pointer to the right position. */ + XIWarpPointer (compositor.display, device_id, None, + window, 0.0, 0.0, 0.0, 0.0, + confinement->cursor_position_x - offset_x, + confinement->cursor_position_y - offset_y); +} + +static void +DeactivateConfinement (PointerConfinement *confinement) +{ + confinement->flags &= ~IsActive; + XIDListFree (confinement->applied_barriers, + FreeSingleBarrier); + confinement->applied_barriers = NULL; + + /* Free lines if they are set. */ + XLFree (confinement->lines); + confinement->lines = NULL; + confinement->nlines = 0; + + /* Unlock the pointer on the seat. */ + if (confinement->seat) + XLSeatUnlockPointer (confinement->seat); + + /* Send unconfined. */ + if (confinement->flags & IsLock) + { + zwp_locked_pointer_v1_send_unlocked (confinement->resource); + + /* Apply the cursor position hint if it is set. */ + if (confinement->flags & IsCursorPositionHintSet) + WarpToHint (confinement); + } + else + zwp_confined_pointer_v1_send_unconfined (confinement->resource); + + /* If the confinement is one shot, mark it as dead. */ + if (confinement->flags & IsOneShot) + confinement->flags |= IsDead; +} + +static void +RecomputeConfinement (PointerConfinement *confinement, int *root_x, + int *root_y) +{ + Surface *surface; + pixman_region32_t intersection; + int offset_x, offset_y; + + /* This should not be called for locks, which must not require + reconfinement. */ + + surface = confinement->surface; + pixman_region32_init (&intersection); + + if (confinement->region) + pixman_region32_intersect (&intersection, confinement->region, + &surface->current_state.input); + else + pixman_region32_copy (&intersection, &surface->current_state.input); + + /* Scale the intersection to window coordinates. */ + XLScaleRegion (&intersection, &intersection, surface->factor, + surface->factor); + + /* Intersect with the view bounds. */ + pixman_region32_intersect_rect (&intersection, &intersection, + 0, 0, ViewWidth (surface->view), + ViewHeight (surface->view)); + + /* Translate the region by the offset of the view into the + subcompositor. */ + ViewTranslate (surface->view, 0, 0, &offset_x, &offset_y); + pixman_region32_translate (&intersection, -offset_x, -offset_y); + + /* Send the confined message. */ + zwp_confined_pointer_v1_send_confined (confinement->resource); + + /* Draw each pointer barrier for confinement. */ + if (!DrawPointerBarriers (confinement, &intersection, + root_x, root_y)) + /* Rendering the confinement failed. This is one of the + oddball regions that require too much memory to + process. */ + DeactivateConfinement (confinement); + + pixman_region32_fini (&intersection); +} + +static void +RewarpPointer (PointerConfinement *confinement, int *root_x_return, + int *root_y_return) +{ + int offset_x, offset_y; + Window window, child; + int root_x, root_y; + + /* Warp the pointer back to its position in the surface, to keep it + locked. */ + XLAssert (confinement->surface != NULL); + + /* Get the subcompositor window. */ + window = XLWindowFromSurface (confinement->surface); + + if (!window) + return; + + /* First, get the offset of the view into the subcompositor. */ + ViewTranslate (confinement->surface->view, 0, 0, &offset_x, + &offset_y); + + if (root_x_return && root_y_return + && *root_x_return != INT_MIN + && *root_y_return != INT_MIN) + { + /* This mechanism is simply used to avoid redundant syncs when + recursively processing subsurfaces. */ + root_x = *root_x_return; + root_y = *root_y_return; + } + else + { + /* Obtain the root-window relative coordinates of the window. */ + XTranslateCoordinates (compositor.display, window, + DefaultRootWindow (compositor.display), + 0, 0, &root_x, &root_y, &child); + + if (root_x_return) + *root_x_return = root_x; + + if (root_y_return) + *root_y_return = root_y; + } + + /* And lock the pointer to the right spot. */ + DrawLock (confinement, + confinement->last_cursor_x - offset_x + root_x, + confinement->last_cursor_y - offset_y + root_y); +} + +static void +Reconfine (Surface *surface, int *root_x, int *root_y, + Bool process_subsurfaces) +{ + PointerConfinementDataRecord *record; + PointerConfinement *confinement; + XLList *tem; + + /* If the subsurface is attached to some surface without a window, + return. */ + if (!XLWindowFromSurface (surface)) + return; + + record = surface->client_data[PointerConfinementData]; + + if (!record) + return; + + confinement = record->confinements.next; + + while (confinement != &record->confinements) + { + if (confinement->flags & IsActive) + { + if (!(confinement->flags & IsLock)) + RecomputeConfinement (confinement, root_x, root_y); + else + RewarpPointer (confinement, root_x, root_y); + } + + confinement = confinement->next; + } + + if (!process_subsurfaces) + return; + + /* Process subsurfaces recursively as well. */ + for (tem = surface->subsurfaces; tem; tem = tem->next) + Reconfine (surface, root_x, root_y, True); +} + +void +XLPointerBarrierLeft (Seat *seat, Surface *surface) +{ + PointerConfinementDataRecord *record; + PointerConfinement *confinement; + + /* The pointer has now left the given surface. If there is an + active confinement for that surface and seat, disable it. */ + + record = surface->client_data[PointerConfinementData]; + + if (!record) + return; + + confinement = FindConfinement (record, seat); + + if (confinement && confinement->flags & IsActive) + DeactivateConfinement (confinement); +} + +void +XLPointerBarrierCheck (Seat *seat, Surface *dispatch, double x, double y, + double root_x, double root_y) +{ + PointerConfinement *confinement; + PointerConfinementDataRecord *record; + pixman_region32_t intersection; + pixman_box32_t box; + int offset_x, offset_y; + + record = dispatch->client_data[PointerConfinementData]; + + if (!record) + return; + + confinement = FindConfinement (record, seat); + + if (!confinement) + return; + + if (confinement->flags & IsDead) + /* The confinement is a 1 shot confinement that has been used + up. */ + return; + + /* Initialize the confinement region. */ + pixman_region32_init (&intersection); + + if (confinement->region) + pixman_region32_intersect (&intersection, confinement->region, + &dispatch->current_state.input); + else + pixman_region32_copy (&intersection, &dispatch->current_state.input); + + /* Scale the intersection to window coordinates. */ + XLScaleRegion (&intersection, &intersection, dispatch->factor, + dispatch->factor); + + /* If X and Y are in the pointer confinement area, then activate the + confinement. */ + if (pixman_region32_contains_point (&intersection, x, y, &box)) + { + if (!(confinement->flags & IsActive)) + { + /* Intersect with the view bounds. This is done after + pixman_region32_contains_point because X and Y must be + within the correct bounds for this function to be called + in the first place. */ + pixman_region32_intersect_rect (&intersection, &intersection, + 0, 0, ViewWidth (dispatch->view), + ViewHeight (dispatch->view)); + + /* Translate the region by the offset of the view into the + subcompositor. */ + ViewTranslate (dispatch->view, 0, 0, &offset_x, &offset_y); + pixman_region32_translate (&intersection, -offset_x, -offset_y); + + /* Activate the confinement. Set the IsActive flag. */ + confinement->flags |= IsActive; + + /* Send the confined message. */ + if (confinement->flags & IsLock) + { + zwp_locked_pointer_v1_send_locked (confinement->resource); + + /* Lock the seat on the pointer. */ + XLSeatLockPointer (confinement->seat); + + /* Draw the lock. */ + DrawLock (confinement, root_x, root_y); + confinement->last_cursor_x = x; + confinement->last_cursor_y = y; + } + else + { + zwp_confined_pointer_v1_send_confined (confinement->resource); + + /* Draw each pointer barrier for confinement. */ + if (!DrawPointerBarriers (confinement, &intersection, + NULL, NULL)) + /* Rendering the confinement failed. This is one of the + oddball regions that require too much memory to + process. */ + DeactivateConfinement (confinement); + } + } + else if (confinement->flags & IsLock) + { + /* This is a mess, but so are the interactions between + Xfixes and subpixel pointer movement... */ + if ((confinement->root_x - root_x) < 1.0 + && (confinement->root_x - root_x) > -1.0 + && (confinement->root_y - root_y) < 1.0 + && (confinement->root_y - root_y) > -1.0 + && confinement->applied_barriers) + goto finish; + + /* The pointer moved while locked; redraw all the locks. */ + DrawLock (confinement, root_x, root_y); + confinement->last_cursor_x = x; + confinement->last_cursor_y = y; + } + } + else if (confinement->flags & IsActive) + /* The pointer moved out of the active confinement. Deactivate + it. */ + DeactivateConfinement (confinement); + + finish: + pixman_region32_fini (&intersection); +} + +/* Motion functions. These functions notice that the position of a + surface has changed. One is called when a surface with an X window + that accepts input changes its position relative to the root window. + + The other is called when a subsurface changes its position relative + to the parent. + + Both functions then look up any active constraints and move them + accordingly. */ + +void +XLPointerConstraintsSurfaceMovedTo (Surface *surface, int root_x, + int root_y) +{ + PointerConfinementDataRecord *record; + PointerConfinement *confinement; + Window window; + XLList *tem; + + /* Since root_x and root_y are already known, there is no need to + query for the position manually. Simply move the lines for the + surface's window and each of its subsurfaces. */ + + record = surface->client_data[PointerConfinementData]; + + if (!record) + return; + + window = XLWindowFromSurface (surface); + + confinement = record->confinements.next; + while (confinement != &record->confinements) + { + /* Reapply the lines with the new root window coordinates. */ + + if (confinement->lines) + ApplyLines (window, confinement, confinement->lines, + confinement->nlines, root_x, root_y); + else if (confinement->flags & IsActive + && confinement->flags & IsLock) + /* Warp the pointer back to where it originally was relative + to the surface. */ + RewarpPointer (confinement, &root_x, &root_y); + + /* Move to the next confinement. */ + confinement = confinement->next; + } + + /* Do the same for each subsurface. */ + for (tem = surface->subsurfaces; tem; tem = tem->next) + XLPointerConstraintsSurfaceMovedTo (surface, root_x, root_y); +} + +void +XLPointerConstraintsSubsurfaceMoved (Surface *surface) +{ + int root_x, root_y; + + root_x = INT_MIN; + root_y = INT_MIN; + + Reconfine (surface, &root_x, &root_y, True); +} + +void +XLPointerConstraintsReconfineSurface (Surface *surface) +{ + int root_x, root_y; + + root_x = INT_MIN; + root_y = INT_MIN; + + Reconfine (surface, &root_x, &root_y, False); +} + +void +XLInitPointerConstraints (void) +{ + pointer_constraints_global + = wl_global_create (compositor.wl_display, + &zwp_pointer_constraints_v1_interface, + 1, NULL, HandleBind); +}