1/*
2 * Copyright © 2024 Thomas E. Dickey
3 * Copyright © 2002 Keith Packard
4 *
5 * Permission to use, copy, modify, distribute, and sell this software and its
6 * documentation for any purpose is hereby granted without fee, provided that
7 * the above copyright notice appear in all copies and that both that
8 * copyright notice and this permission notice appear in supporting
9 * documentation, and that the name of Keith Packard not be used in
10 * advertising or publicity pertaining to distribution of the software without
11 * specific, written prior permission.  Keith Packard makes no
12 * representations about the suitability of this software for any purpose.  It
13 * is provided "as is" without express or implied warranty.
14 *
15 * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
16 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
17 * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
18 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
19 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
20 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
21 * PERFORMANCE OF THIS SOFTWARE.
22 */
23
24#include "xcursorint.h"
25#include <X11/Xlibint.h>
26#include <ctype.h>
27#include <unistd.h>		/* for getpid */
28
29static XcursorDisplayInfo *_XcursorDisplayInfo;
30
31static void
32_XcursorFreeDisplayInfo (XcursorDisplayInfo *info)
33{
34    if (info->theme)
35	free (info->theme);
36
37    if (info->theme_from_config)
38	free (info->theme_from_config);
39
40    while (info->fonts)
41    {
42      XcursorFontInfo *fi = info->fonts;
43      info->fonts = fi->next;
44      free (fi);
45    }
46
47    free (info);
48}
49
50static int
51_XcursorCloseDisplay (Display *dpy, XExtCodes *codes)
52{
53    XcursorDisplayInfo  *info, **prev;
54
55    (void) codes;	/* UNUSED */
56
57    /*
58     * Unhook from the global list
59     */
60    _XLockMutex (_Xglobal_lock);
61    for (prev = &_XcursorDisplayInfo; (info = *prev); prev = &(*prev)->next)
62	if (info->display == dpy)
63	{
64            *prev = info->next;
65	    break;
66	}
67    _XUnlockMutex (_Xglobal_lock);
68
69    if (info)
70	_XcursorFreeDisplayInfo (info);
71    return 0;
72}
73
74static int
75_XcursorDefaultParseBool (char *v)
76{
77    char    c0;
78
79    c0 = *v;
80    if (isupper ((int)c0))
81	c0 = (char) tolower (c0);
82    if (c0 == 't' || c0 == 'y' || c0 == '1')
83	return 1;
84    if (c0 == 'f' || c0 == 'n' || c0 == '0')
85	return 0;
86    if (c0 == 'o')
87    {
88	char c1 = v[1];
89	if (isupper ((int)c1))
90	    c1 = (char) tolower (c1);
91	if (c1 == 'n')
92	    return 1;
93	if (c1 == 'f')
94	    return 0;
95    }
96    return -1;
97}
98
99XcursorDisplayInfo *
100_XcursorGetDisplayInfo (Display *dpy)
101{
102    XcursorDisplayInfo	*info, **prev, *old;
103    int			event_base, error_base;
104    int			major, minor;
105    char		*v;
106    int			i;
107
108    _XLockMutex (_Xglobal_lock);
109    for (prev = &_XcursorDisplayInfo; (info = *prev); prev = &(*prev)->next)
110    {
111	if (info->display == dpy)
112	{
113	    /*
114	     * MRU the list
115	     */
116	    if (prev != &_XcursorDisplayInfo)
117	    {
118		*prev = info->next;
119		info->next = _XcursorDisplayInfo;
120		_XcursorDisplayInfo = info;
121	    }
122	    break;
123	}
124    }
125    _XUnlockMutex (_Xglobal_lock);
126    if (info)
127        return info;
128    info = (XcursorDisplayInfo *) malloc (sizeof (XcursorDisplayInfo));
129    if (!info)
130	return NULL;
131    info->next = NULL;
132    info->display = dpy;
133
134    info->codes = XAddExtension (dpy);
135    if (!info->codes)
136    {
137	free (info);
138	return NULL;
139    }
140    (void) XESetCloseDisplay (dpy, info->codes->extension, _XcursorCloseDisplay);
141
142    /*
143     * The debugging-trace for new info-blocks begins here.
144     * As a reminder that multiple processes/threads use this library,
145     * the current process-id is logged.
146     */
147    traceOpts((T_CALLED(_XcursorGetDisplayInfo) " info %p, pid %d\n",
148	      (void*)info, getpid()));
149
150    /*
151     * Check whether the display supports the Render CreateCursor request
152     */
153    info->has_render_cursor = XcursorFalse;
154    info->has_anim_cursor = XcursorFalse;
155    if (XRenderQueryExtension (dpy, &event_base, &error_base) &&
156	XRenderQueryVersion (dpy, &major, &minor))
157    {
158	if (major > 0 || minor >= 5)
159	{
160	    info->has_render_cursor = XcursorTrue;
161	    v = getenv ("XCURSOR_CORE");
162	    if (!v)
163		v = XGetDefault (dpy, "Xcursor", "core");
164	    if (v && _XcursorDefaultParseBool (v) == 1)
165		info->has_render_cursor = XcursorFalse;
166	    traceOpts((T_OPTION(XCURSOR_CORE) ": %d\n", info->has_render_cursor));
167	}
168	if (info->has_render_cursor && (major > 0 || minor >= 8))
169	{
170	    info->has_anim_cursor = XcursorTrue;
171	    v = getenv ("XCURSOR_ANIM");
172	    if (!v)
173		v = XGetDefault (dpy, "Xcursor", "anim");
174	    if (v && _XcursorDefaultParseBool (v) == 0)
175		info->has_anim_cursor = XcursorFalse;
176	    traceOpts((T_OPTION(XCURSOR_ANIM) ": %d\n", info->has_anim_cursor));
177	}
178    }
179
180    info->size = 0;
181
182    /*
183     * Get desired cursor size
184     */
185    v = getenv ("XCURSOR_SIZE");
186    if (!v)
187	v = XGetDefault (dpy, "Xcursor", "size");
188    if (v)
189	info->size = atoi (v);
190    traceOpts((T_OPTION(XCURSOR_SIZE) ": %d\n", info->size));
191
192    /*
193     * Use the Xft size to guess a size; make cursors 16 "points" tall
194     */
195    if (info->size == 0)
196    {
197	int dpi = 0;
198	v = XGetDefault (dpy, "Xft", "dpi");
199	if (v)
200	    dpi = atoi (v);
201	if (dpi)
202	    info->size = dpi * 16 / 72;
203	traceOpts((T_OPTION(XCURSOR_SIZE) ": %d\n", info->size));
204    }
205
206    /*
207     * Use display size to guess a size
208     */
209    if (info->size == 0)
210    {
211	int dim;
212
213	if (DisplayHeight (dpy, DefaultScreen (dpy)) <
214	    DisplayWidth (dpy, DefaultScreen (dpy)))
215	    dim = DisplayHeight (dpy, DefaultScreen (dpy));
216	else
217	    dim = DisplayWidth (dpy, DefaultScreen (dpy));
218	/*
219	 * 16 pixels on a display of dimension 768
220	 */
221	info->size = dim / 48;
222	traceOpts((T_OPTION(XCURSOR_SIZE) ": %d\n", info->size));
223    }
224
225    info->theme = NULL;
226    info->theme_from_config = NULL;
227
228    /*
229     * Provide for making cursors resized to match the requested size
230     */
231    info->resized_cursors = XcursorFalse;
232    v = getenv ("XCURSOR_RESIZED");
233    if (!v)
234	v = XGetDefault (dpy, "Xcursor", "resized");
235    if (v)
236    {
237	i = _XcursorDefaultParseBool (v);
238	if (i >= 0)
239	    info->resized_cursors = i;
240    }
241    traceOpts((T_OPTION(XCURSOR_RESIZED) ": %d\n", info->resized_cursors));
242
243    /*
244     * Get the desired theme
245     */
246    v = getenv ("XCURSOR_THEME");
247    if (!v)
248	v = XGetDefault (dpy, "Xcursor", "theme");
249    if (v)
250    {
251	info->theme = strdup (v);
252	info->theme_from_config = strdup (v);
253    }
254    traceOpts((T_OPTION(XCURSOR_THEME) ": %s\n", NonNull(info->theme)));
255
256    /*
257     * Get the desired dither
258     */
259    info->dither = XcursorDitherThreshold;
260    v = getenv ("XCURSOR_DITHER");
261    if (!v)
262	v = XGetDefault (dpy, "Xcursor", "dither");
263    if (v)
264    {
265	if (!strcmp (v, "threshold"))
266	    info->dither = XcursorDitherThreshold;
267	if (!strcmp (v, "median"))
268	    info->dither = XcursorDitherMedian;
269	if (!strcmp (v, "ordered"))
270	    info->dither = XcursorDitherOrdered;
271	if (!strcmp (v, "diffuse"))
272	    info->dither = XcursorDitherDiffuse;
273    }
274    traceOpts((T_OPTION(XCURSOR_DITHER) ": %d\n", info->dither));
275
276    info->theme_core = False;
277    /*
278     * Find out if core cursors should
279     * be themed
280     */
281    v = getenv ("XCURSOR_THEME_CORE");
282    if (!v)
283	v = XGetDefault (dpy, "Xcursor", "theme_core");
284    if (v)
285    {
286	i = _XcursorDefaultParseBool (v);
287	if (i >= 0)
288	    info->theme_core = i;
289    }
290    traceOpts((T_OPTION(XCURSOR_THEME_CORE) ": %d\n", info->theme_core));
291
292    info->fonts = NULL;
293    for (i = 0; i < NUM_BITMAPS; i++)
294	info->bitmaps[i].bitmap = None;
295
296    /*
297     * Link new info info list, making sure another
298     * thread hasn't inserted something into the list while
299     * this one was busy setting up the data
300     */
301    _XLockMutex (_Xglobal_lock);
302    for (old = _XcursorDisplayInfo; old; old = old->next)
303	if (old->display == dpy)
304	    break;
305    if (old)
306    {
307	_XcursorFreeDisplayInfo (info);
308	info = old;
309    }
310    else
311    {
312	info->next = _XcursorDisplayInfo;
313	_XcursorDisplayInfo = info;
314    }
315    _XUnlockMutex (_Xglobal_lock);
316
317    returnAddr(info);
318}
319
320XcursorBool
321XcursorSupportsARGB (Display *dpy)
322{
323    XcursorDisplayInfo	*info = _XcursorGetDisplayInfo (dpy);
324
325    return info && info->has_render_cursor;
326}
327
328XcursorBool
329XcursorSupportsAnim (Display *dpy)
330{
331    XcursorDisplayInfo	*info = _XcursorGetDisplayInfo (dpy);
332
333    return info && info->has_anim_cursor;
334}
335
336XcursorBool
337XcursorSetDefaultSize (Display *dpy, int size)
338{
339    XcursorDisplayInfo	*info = _XcursorGetDisplayInfo (dpy);
340
341    if (!info)
342	return XcursorFalse;
343    info->size = size;
344    return XcursorTrue;
345}
346
347int
348XcursorGetDefaultSize (Display *dpy)
349{
350    XcursorDisplayInfo	*info = _XcursorGetDisplayInfo (dpy);
351
352    if (!info)
353	return 0;
354    return info->size;
355}
356
357XcursorBool
358XcursorSetResizable (Display *dpy, XcursorBool flag)
359{
360    XcursorDisplayInfo	*info = _XcursorGetDisplayInfo (dpy);
361
362    if (!info)
363	return XcursorFalse;
364    info->resized_cursors = flag;
365    return XcursorTrue;
366}
367
368XcursorBool
369XcursorGetResizable (Display *dpy)
370{
371    XcursorDisplayInfo	*info = _XcursorGetDisplayInfo (dpy);
372
373    if (!info)
374	return 0;
375    return info->resized_cursors;
376}
377
378XcursorBool
379XcursorSetTheme (Display *dpy, const char *theme)
380{
381    XcursorDisplayInfo	*info = _XcursorGetDisplayInfo (dpy);
382    char		*copy;
383
384    if (!info)
385	return XcursorFalse;
386
387    if (!theme)
388	theme = info->theme_from_config;
389
390    if (theme)
391    {
392	copy = strdup (theme);
393	if (!copy)
394	    return XcursorFalse;
395    }
396    else
397	copy = NULL;
398    if (info->theme)
399	free (info->theme);
400    info->theme = copy;
401    return XcursorTrue;
402}
403
404char *
405XcursorGetTheme (Display *dpy)
406{
407    XcursorDisplayInfo	*info = _XcursorGetDisplayInfo (dpy);
408
409    if (!info)
410	return NULL;
411    return info->theme;
412}
413
414XcursorBool
415XcursorGetThemeCore (Display *dpy)
416{
417    XcursorDisplayInfo	*info = _XcursorGetDisplayInfo (dpy);
418
419    if (!info)
420	return XcursorFalse;
421    return info->theme_core;
422}
423
424XcursorBool
425XcursorSetThemeCore (Display *dpy, XcursorBool theme_core)
426{
427    XcursorDisplayInfo	*info = _XcursorGetDisplayInfo (dpy);
428
429    if (!info)
430	return XcursorFalse;
431    info->theme_core = theme_core;
432    return XcursorTrue;
433}
434