1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23
24#if defined(SDL_VIDEO_DRIVER_X11) && defined(SDL_VIDEO_DRIVER_X11_XFIXES)
25
26#include "SDL_x11video.h"
27#include "SDL_x11xfixes.h"
28#include "../../events/SDL_mouse_c.h"
29#include "../../events/SDL_touch_c.h"
30
31static bool xfixes_initialized = true;
32static int xfixes_selection_notify_event = 0;
33
34static int query_xfixes_version(Display *display, int major, int minor)
35{
36 // We don't care if this fails, so long as it sets major/minor on it's way out the door.
37 X11_XFixesQueryVersion(display, &major, &minor);
38 return (major * 1000) + minor;
39}
40
41static bool xfixes_version_atleast(const int version, const int wantmajor, const int wantminor)
42{
43 return version >= ((wantmajor * 1000) + wantminor);
44}
45
46void X11_InitXfixes(SDL_VideoDevice *_this)
47{
48 SDL_VideoData *data = _this->internal;
49
50 int version = 0;
51 int event, error;
52 int fixes_opcode;
53
54 Atom XA_CLIPBOARD = data->atoms.CLIPBOARD;
55
56 if (!SDL_X11_HAVE_XFIXES ||
57 !X11_XQueryExtension(data->display, "XFIXES", &fixes_opcode, &event, &error)) {
58 return;
59 }
60
61 // Selection tracking is available in all versions of XFixes
62 xfixes_selection_notify_event = event + XFixesSelectionNotify;
63 X11_XFixesSelectSelectionInput(data->display, DefaultRootWindow(data->display),
64 XA_CLIPBOARD, XFixesSetSelectionOwnerNotifyMask);
65 X11_XFixesSelectSelectionInput(data->display, DefaultRootWindow(data->display),
66 XA_PRIMARY, XFixesSetSelectionOwnerNotifyMask);
67
68 // We need at least 5.0 for barriers.
69 version = query_xfixes_version(data->display, 5, 0);
70 if (!xfixes_version_atleast(version, 5, 0)) {
71 return; // X server does not support the version we want at all.
72 }
73
74 xfixes_initialized = 1;
75}
76
77bool X11_XfixesIsInitialized(void)
78{
79 return xfixes_initialized;
80}
81
82int X11_GetXFixesSelectionNotifyEvent(void)
83{
84 return xfixes_selection_notify_event;
85}
86
87bool X11_SetWindowMouseRect(SDL_VideoDevice *_this, SDL_Window *window)
88{
89 if (SDL_RectEmpty(&window->mouse_rect)) {
90 X11_ConfineCursorWithFlags(_this, window, NULL, 0);
91 } else {
92 if (window->flags & SDL_WINDOW_INPUT_FOCUS) {
93 X11_ConfineCursorWithFlags(_this, window, &window->mouse_rect, 0);
94 } else {
95 // Save the state for when we get focus again
96 SDL_WindowData *wdata = window->internal;
97
98 SDL_memcpy(&wdata->barrier_rect, &window->mouse_rect, sizeof(wdata->barrier_rect));
99
100 wdata->pointer_barrier_active = true;
101 }
102 }
103
104 return true;
105}
106
107bool X11_ConfineCursorWithFlags(SDL_VideoDevice *_this, SDL_Window *window, const SDL_Rect *rect, int flags)
108{
109 /* Yaakuro: For some reason Xfixes when confining inside a rect where the
110 * edges exactly match, a rectangle the cursor 'slips' out of the barrier.
111 * To prevent that the lines for the barriers will span the whole screen.
112 */
113 SDL_VideoData *data = _this->internal;
114 SDL_WindowData *wdata;
115
116 if (!X11_XfixesIsInitialized()) {
117 return SDL_Unsupported();
118 }
119
120 // If there is already a set of barriers active, disable them.
121 if (data->active_cursor_confined_window) {
122 X11_DestroyPointerBarrier(_this, data->active_cursor_confined_window);
123 }
124
125 SDL_assert(window != NULL);
126 wdata = window->internal;
127
128 /* If user did not specify an area to confine, destroy the barrier that was/is assigned to
129 * this window it was assigned */
130 if (rect) {
131 int x1, y1, x2, y2;
132 SDL_Rect bounds;
133 SDL_GetWindowPosition(window, &bounds.x, &bounds.y);
134 SDL_GetWindowSize(window, &bounds.w, &bounds.h);
135
136 /** Negative values are not allowed. Clip values relative to the specified window. */
137 x1 = bounds.x + SDL_max(rect->x, 0);
138 y1 = bounds.y + SDL_max(rect->y, 0);
139 x2 = SDL_min(bounds.x + rect->x + rect->w, bounds.x + bounds.w);
140 y2 = SDL_min(bounds.y + rect->y + rect->h, bounds.y + bounds.h);
141
142 if ((wdata->barrier_rect.x != rect->x) ||
143 (wdata->barrier_rect.y != rect->y) ||
144 (wdata->barrier_rect.w != rect->w) ||
145 (wdata->barrier_rect.h != rect->h)) {
146 wdata->barrier_rect = *rect;
147 }
148
149 // Use the display bounds to ensure the barriers don't have corner gaps
150 SDL_GetDisplayBounds(SDL_GetDisplayForWindow(window), &bounds);
151
152 /** Create the left barrier */
153 wdata->barrier[0] = X11_XFixesCreatePointerBarrier(data->display, wdata->xwindow,
154 x1, bounds.y,
155 x1, bounds.y + bounds.h,
156 BarrierPositiveX,
157 0, NULL);
158 /** Create the right barrier */
159 wdata->barrier[1] = X11_XFixesCreatePointerBarrier(data->display, wdata->xwindow,
160 x2, bounds.y,
161 x2, bounds.y + bounds.h,
162 BarrierNegativeX,
163 0, NULL);
164 /** Create the top barrier */
165 wdata->barrier[2] = X11_XFixesCreatePointerBarrier(data->display, wdata->xwindow,
166 bounds.x, y1,
167 bounds.x + bounds.w, y1,
168 BarrierPositiveY,
169 0, NULL);
170 /** Create the bottom barrier */
171 wdata->barrier[3] = X11_XFixesCreatePointerBarrier(data->display, wdata->xwindow,
172 bounds.x, y2,
173 bounds.x + bounds.w, y2,
174 BarrierNegativeY,
175 0, NULL);
176
177 X11_XFlush(data->display);
178
179 // Lets remember current active confined window.
180 data->active_cursor_confined_window = window;
181
182 /* User activated the confinement for this window. We use this later to reactivate
183 * the confinement if it got deactivated by FocusOut or UnmapNotify */
184 wdata->pointer_barrier_active = true;
185 } else {
186 X11_DestroyPointerBarrier(_this, window);
187
188 // Only set barrier inactive when user specified NULL and not handled by focus out.
189 if (flags != X11_BARRIER_HANDLED_BY_EVENT) {
190 wdata->pointer_barrier_active = false;
191 }
192 }
193 return true;
194}
195
196void X11_DestroyPointerBarrier(SDL_VideoDevice *_this, SDL_Window *window)
197{
198 int i;
199 SDL_VideoData *data = _this->internal;
200 if (window) {
201 SDL_WindowData *wdata = window->internal;
202
203 for (i = 0; i < 4; i++) {
204 if (wdata->barrier[i] > 0) {
205 X11_XFixesDestroyPointerBarrier(data->display, wdata->barrier[i]);
206 wdata->barrier[i] = 0;
207 }
208 }
209 X11_XFlush(data->display);
210 }
211 data->active_cursor_confined_window = NULL;
212}
213
214#endif // SDL_VIDEO_DRIVER_X11 && SDL_VIDEO_DRIVER_X11_XFIXES
215