1/*
2 * Copyright (c) 2010, 2023, Oracle and/or its affiliates.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 */
23/*
24
25Copyright 1993, 1998  The Open Group
26
27Permission to use, copy, modify, distribute, and sell this software and its
28documentation for any purpose is hereby granted without fee, provided that
29the above copyright notice appear in all copies and that both that
30copyright notice and this permission notice appear in supporting
31documentation.
32
33The above copyright notice and this permission notice shall be included
34in all copies or substantial portions of the Software.
35
36THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
37OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
38MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
39IN NO EVENT SHALL THE OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR
40OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
41ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
42OTHER DEALINGS IN THE SOFTWARE.
43
44Except as contained in this notice, the name of The Open Group shall
45not be used in advertising or otherwise to promote the sale, use or
46other dealings in this Software without prior written authorization
47from The Open Group.
48
49*/
50
51#include "config.h"
52
53#include <xcb/xcb.h>
54#include <xcb/xproto.h>
55#ifdef USE_XCB_ICCCM
56# include <xcb/xcb_icccm.h>
57#endif
58#ifdef USE_XCB_ERRORS
59# include <xcb/xcb_errors.h>
60#endif
61#include <X11/cursorfont.h>
62#include <stdio.h>
63#include <stdlib.h>
64#include <stdarg.h>
65#include <string.h>
66#include "clientwin.h"
67#include "dsimple.h"
68
69/*
70 * Just_display: A group of routines designed to make the writing of simple
71 *               X11 applications which open a display but do not open
72 *               any windows much faster and easier.  Unless a routine says
73 *               otherwise, it may be assumed to require program_name
74 *               to be already defined on entry.
75 *
76 * Written by Mark Lillibridge.   Last updated 7/1/87
77 */
78
79#ifdef USE_XCB_ERRORS
80xcb_errors_context_t *error_context;
81#endif
82
83/* This stuff is defined in the calling program by dsimple.h */
84const char    *program_name = "unknown_program";
85
86/*
87 * Get_Display_Name (argc, argv) - return string representing display name
88 * that would be used given the specified argument (i.e. if it's NULL, check
89 * getenv("DISPLAY") - always returns a non-NULL pointer, though it may be
90 * an unwritable constant, so is safe to printf() on platforms that crash
91 * on NULL printf arguments.
92 */
93const char *Get_Display_Name (const char *display_name)
94{
95    const char *name = display_name;
96
97    if (!name) {
98	name = getenv ("DISPLAY");
99	if (!name)
100	    name = "";
101    }
102    return (name);
103}
104
105
106/*
107 * Setup_Display_And_Screen: This routine opens up the correct display (i.e.,
108 *                           it calls Get_Display_Name) and then stores a
109 *                           pointer to it in dpy.  The default screen
110 *                           for this display is then stored in screen.
111 */
112void Setup_Display_And_Screen (
113    const char *display_name,
114    xcb_connection_t **dpy,	/* MODIFIED */
115    xcb_screen_t **screen)	/* MODIFIED */
116{
117    int screen_number, err;
118
119    /* Open Display */
120    *dpy = xcb_connect (display_name, &screen_number);
121    if ((err = xcb_connection_has_error (*dpy)) != 0) {
122        switch (err) {
123        case XCB_CONN_CLOSED_MEM_INSUFFICIENT:
124            Fatal_Error ("Failed to allocate memory in xcb_connect");
125        case XCB_CONN_CLOSED_PARSE_ERR:
126            Fatal_Error ("unable to parse display name \"%s\"",
127                         Get_Display_Name(display_name) );
128#ifdef XCB_CONN_CLOSED_INVALID_SCREEN
129        case XCB_CONN_CLOSED_INVALID_SCREEN:
130            Fatal_Error ("invalid screen %d in display \"%s\"",
131                         screen_number, Get_Display_Name(display_name));
132#endif
133        default:
134            Fatal_Error ("unable to open display \"%s\"",
135                         Get_Display_Name(display_name) );
136        }
137    }
138
139#ifdef USE_XCB_ERRORS
140    if (xcb_errors_context_new(*dpy, &error_context) != 0) {
141	fprintf (stderr, "%s: unable to load xcb-errors\n\n",
142                 program_name);
143    }
144#endif
145
146    if (screen) {
147	/* find our screen */
148	const xcb_setup_t *setup = xcb_get_setup(*dpy);
149	xcb_screen_iterator_t screen_iter = xcb_setup_roots_iterator(setup);
150	int screen_count = xcb_setup_roots_length(setup);
151	if (screen_count <= screen_number)
152	{
153	    Fatal_Error ("unable to access screen %d, max is %d",
154			 screen_number, screen_count-1 );
155	}
156
157	for (int i = 0; i < screen_number; i++)
158	    xcb_screen_next(&screen_iter);
159	*screen = screen_iter.data;
160    }
161}
162
163/*
164 * xcb equivalent of XCreateFontCursor
165 */
166static xcb_cursor_t
167Create_Font_Cursor (xcb_connection_t *dpy, uint16_t glyph)
168{
169    static xcb_font_t cursor_font;
170    xcb_cursor_t cursor;
171
172    if (!cursor_font) {
173	cursor_font = xcb_generate_id (dpy);
174	xcb_open_font (dpy, cursor_font, strlen ("cursor"), "cursor");
175    }
176
177    cursor = xcb_generate_id (dpy);
178    xcb_create_glyph_cursor (dpy, cursor, cursor_font, cursor_font,
179			     glyph, glyph + 1,
180                             0, 0, 0, 0xffff, 0xffff, 0xffff);  /* rgb, rgb */
181
182    return cursor;
183}
184
185/*
186 * Routine to let user select a window using the mouse
187 */
188
189xcb_window_t Select_Window(xcb_connection_t *dpy,
190			   const xcb_screen_t *screen,
191			   int descend)
192{
193    xcb_cursor_t cursor;
194    xcb_window_t target_win = XCB_WINDOW_NONE;
195    xcb_window_t root = screen->root;
196    int buttons = 0;
197    xcb_generic_error_t *err;
198    xcb_grab_pointer_cookie_t grab_cookie;
199    xcb_grab_pointer_reply_t *grab_reply;
200
201    /* Make the target cursor */
202    cursor = Create_Font_Cursor (dpy, XC_crosshair);
203
204    /* Grab the pointer using target cursor, letting it room all over */
205    grab_cookie = xcb_grab_pointer
206	(dpy, False, root,
207	 XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE,
208	 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC,
209	 root, cursor, XCB_TIME_CURRENT_TIME);
210    grab_reply = xcb_grab_pointer_reply (dpy, grab_cookie, &err);
211    if (grab_reply->status != XCB_GRAB_STATUS_SUCCESS)
212	Fatal_Error ("Can't grab the mouse.");
213
214    /* Let the user select a window... */
215    while ((target_win == XCB_WINDOW_NONE) || (buttons != 0)) {
216	/* allow one more event */
217	xcb_generic_event_t *event;
218
219	xcb_allow_events (dpy, XCB_ALLOW_SYNC_POINTER, XCB_TIME_CURRENT_TIME);
220	xcb_flush (dpy);
221	event = xcb_wait_for_event (dpy);
222	if (event == NULL)
223	    Fatal_Error ("Fatal IO error");
224	switch (event->response_type & 0x7f) {
225	case XCB_BUTTON_PRESS:
226	{
227	    xcb_button_press_event_t *bp = (xcb_button_press_event_t *)event;
228
229	    if (target_win == XCB_WINDOW_NONE) {
230		target_win = bp->child; /* window selected */
231		if (target_win == XCB_WINDOW_NONE)
232		    target_win = root;
233	    }
234	    buttons++;
235	    break;
236	}
237	case XCB_BUTTON_RELEASE:
238	    if (buttons > 0) /* there may have been some down before we started */
239		buttons--;
240	    break;
241	default:
242	    /* just discard all other events */
243	    break;
244	}
245	free (event);
246    }
247
248    xcb_ungrab_pointer (dpy, XCB_TIME_CURRENT_TIME); /* Done with pointer */
249
250    if (!descend || (target_win == root))
251	return (target_win);
252
253    target_win = Find_Client (dpy, root, target_win);
254
255    return (target_win);
256}
257
258
259/*
260 * Window_With_Name: routine to locate a window with a given name on a display.
261 *                   If no window with the given name is found, 0 is returned.
262 *                   If more than one window has the given name, the first
263 *                   one found will be returned.  Only top and its subwindows
264 *                   are looked at.  Normally, top should be the RootWindow.
265 */
266
267struct wininfo_cookies {
268    xcb_get_property_cookie_t get_net_wm_name;
269    xcb_get_property_cookie_t get_wm_name;
270    xcb_query_tree_cookie_t query_tree;
271};
272
273#ifndef USE_XCB_ICCCM
274# define xcb_icccm_get_wm_name(Dpy, Win) \
275    xcb_get_property (Dpy, False, Win, XCB_ATOM_WM_NAME, \
276		      XCB_GET_PROPERTY_TYPE_ANY, 0, BUFSIZ)
277#endif
278
279static xcb_atom_t atom_net_wm_name, atom_utf8_string;
280
281# define xcb_get_net_wm_name(Dpy, Win)			 \
282    xcb_get_property (Dpy, False, Win, atom_net_wm_name, \
283		      atom_utf8_string, 0, BUFSIZ)
284
285
286static xcb_window_t
287recursive_Window_With_Name  (
288    xcb_connection_t *dpy,
289    xcb_window_t window,
290    struct wininfo_cookies *cookies,
291    const char *name,
292    size_t namelen)
293{
294    xcb_window_t *children;
295    unsigned int nchildren;
296    unsigned int i;
297    xcb_window_t w = 0;
298    xcb_generic_error_t *err;
299    xcb_query_tree_reply_t *tree;
300    struct wininfo_cookies *child_cookies;
301    xcb_get_property_reply_t *prop;
302
303    if (cookies->get_net_wm_name.sequence) {
304	prop = xcb_get_property_reply (dpy, cookies->get_net_wm_name, &err);
305
306	if (prop) {
307	    if (prop->type == atom_utf8_string) {
308		const char *prop_name = xcb_get_property_value (prop);
309		int prop_name_len = xcb_get_property_value_length (prop);
310
311		/* can't use strcmp, since prop.name is not null terminated */
312		if ((namelen == (size_t) prop_name_len) &&
313		    memcmp (prop_name, name, namelen) == 0) {
314		    w = window;
315		}
316	    }
317	    free (prop);
318	} else if (err) {
319	    if (err->response_type == 0)
320		Print_X_Error (dpy, err);
321	    return 0;
322	}
323    }
324
325    if (w) {
326	xcb_discard_reply (dpy, cookies->get_wm_name.sequence);
327    } else {
328#ifdef USE_XCB_ICCCM
329	xcb_icccm_get_text_property_reply_t nameprop;
330
331	if (xcb_icccm_get_wm_name_reply (dpy, cookies->get_wm_name,
332					 &nameprop, &err)) {
333	    /* can't use strcmp, since nameprop.name is not null terminated */
334	    if ((namelen == (size_t) nameprop.name_len) &&
335		memcmp (nameprop.name, name, namelen) == 0) {
336		w = window;
337	    }
338
339	    xcb_icccm_get_text_property_reply_wipe (&nameprop);
340	}
341#else
342	prop = xcb_get_property_reply (dpy, cookies->get_wm_name, &err);
343
344	if (prop) {
345	    if (prop->type == XCB_ATOM_STRING) {
346		const char *prop_name = xcb_get_property_value (prop);
347		int prop_name_len = xcb_get_property_value_length (prop);
348
349		/* can't use strcmp, since prop.name is not null terminated */
350		if ((namelen == (size_t) prop_name_len) &&
351		    memcmp (prop_name, name, namelen) == 0) {
352		    w = window;
353		}
354	    }
355	    free (prop);
356	}
357#endif
358	else if (err) {
359	    if (err->response_type == 0)
360		Print_X_Error (dpy, err);
361	    return 0;
362	}
363    }
364
365    if (w)
366    {
367	xcb_discard_reply (dpy, cookies->query_tree.sequence);
368	return w;
369    }
370
371    tree = xcb_query_tree_reply (dpy, cookies->query_tree, &err);
372    if (!tree) {
373	if (err->response_type == 0)
374	    Print_X_Error (dpy, err);
375	return 0;
376    }
377
378    nchildren = xcb_query_tree_children_length (tree);
379    children = xcb_query_tree_children (tree);
380    child_cookies = calloc(nchildren, sizeof(struct wininfo_cookies));
381
382    if (child_cookies == NULL)
383	Fatal_Error("Failed to allocate memory in recursive_Window_With_Name");
384
385    for (i = 0; i < nchildren; i++) {
386	if (atom_net_wm_name && atom_utf8_string)
387	    child_cookies[i].get_net_wm_name =
388		xcb_get_net_wm_name (dpy, children[i]);
389	child_cookies[i].get_wm_name = xcb_icccm_get_wm_name (dpy, children[i]);
390	child_cookies[i].query_tree = xcb_query_tree (dpy, children[i]);
391    }
392    xcb_flush (dpy);
393
394    for (i = 0; i < nchildren; i++) {
395	w = recursive_Window_With_Name (dpy, children[i],
396					&child_cookies[i], name, namelen);
397	if (w)
398	    break;
399    }
400
401    if (w)
402    {
403	/* clean up remaining replies */
404	for (/* keep previous i */; i < nchildren; i++) {
405	    if (child_cookies[i].get_net_wm_name.sequence)
406		xcb_discard_reply (dpy,
407				   child_cookies[i].get_net_wm_name.sequence);
408	    xcb_discard_reply (dpy, child_cookies[i].get_wm_name.sequence);
409	    xcb_discard_reply (dpy, child_cookies[i].query_tree.sequence);
410	}
411    }
412
413    free (child_cookies);
414    free (tree); /* includes storage for children[] */
415    return (w);
416}
417
418xcb_window_t
419Window_With_Name (
420    xcb_connection_t *dpy,
421    xcb_window_t top,
422    const char *name)
423{
424    struct wininfo_cookies cookies;
425
426    atom_net_wm_name = Get_Atom (dpy, "_NET_WM_NAME");
427    atom_utf8_string = Get_Atom (dpy, "UTF8_STRING");
428
429    if (atom_net_wm_name && atom_utf8_string)
430	cookies.get_net_wm_name = xcb_get_net_wm_name (dpy, top);
431    else
432	cookies.get_net_wm_name.sequence = 0;
433    cookies.get_wm_name = xcb_icccm_get_wm_name (dpy, top);
434    cookies.query_tree = xcb_query_tree (dpy, top);
435    xcb_flush (dpy);
436    return recursive_Window_With_Name(dpy, top, &cookies, name, strlen(name));
437}
438
439
440/*
441 * Standard fatal error routine - call like printf
442 */
443void Fatal_Error (const char *msg, ...)
444{
445    va_list args;
446    fflush (stdout);
447    fflush (stderr);
448    fprintf (stderr, "%s: error: ", program_name);
449    va_start (args, msg);
450    vfprintf (stderr, msg, args);
451    va_end (args);
452    fprintf (stderr, "\n");
453    exit (EXIT_FAILURE);
454}
455
456/*
457 * Descriptions for core protocol error codes
458 *
459 * Since 0 is not used for an error code in X, we use it for unknown error.
460 */
461#define UNKNOWN_ERROR 0
462#define LAST_ERROR XCB_IMPLEMENTATION
463
464static const char *error_desc[] = {
465    [UNKNOWN_ERROR] = "Unknown error",
466    [XCB_REQUEST] = "Bad Request",
467    [XCB_VALUE] = "Bad Value",
468    [XCB_WINDOW] = "Bad Window",
469    [XCB_PIXMAP] = "Bad Pixmap",
470    [XCB_ATOM] = "Bad Atom",
471    [XCB_CURSOR] = "Bad Cursor",
472    [XCB_FONT] = "Bad Font",
473    [XCB_MATCH] = "Bad Match",
474    [XCB_DRAWABLE] = "Bad Drawable",
475    [XCB_ACCESS] = "Access Denied",
476    [XCB_ALLOC] = "Server Memory Allocation Failure",
477    [XCB_COLORMAP] = "Bad Color",
478    [XCB_G_CONTEXT] = "Bad GC",
479    [XCB_ID_CHOICE] = "Bad XID",
480    [XCB_NAME] = "Bad Name",
481    [XCB_LENGTH] = "Bad Request Length",
482    [XCB_IMPLEMENTATION] = "Server Implementation Failure",
483};
484
485/*
486 * Print X error information like the default Xlib error handler
487 */
488void
489Print_X_Error (
490    xcb_connection_t *dpy,
491    xcb_generic_error_t *err
492    )
493{
494    const char *error = NULL;
495    const char *extension = NULL;
496    const char *value_type = NULL;
497    const char *major_name = NULL;
498    const char *minor_name = NULL;
499
500    if ((err == NULL) || (err->response_type != 0)) /* not an error */
501	return;
502
503#ifdef USE_XCB_ERRORS
504    if (error_context != NULL) {
505	error = xcb_errors_get_name_for_error(error_context, err->error_code,
506					      &extension);
507	major_name = xcb_errors_get_name_for_major_code(error_context,
508							err->major_code);
509	minor_name = xcb_errors_get_name_for_minor_code(error_context,
510							err->major_code,
511							err->minor_code);
512    }
513    /* Fallback to old code if xcb-errors wasn't initialized */
514    else
515#endif
516    if (err->error_code >= 128)
517    {
518	error = "Unknown extension error";
519    }
520    else
521    {
522	if (err->error_code > 0 && err->error_code <= LAST_ERROR) {
523	    error = error_desc[err->error_code];
524	} else {
525	    error = error_desc[UNKNOWN_ERROR];
526	}
527    }
528
529    fprintf (stderr, "X Error: %d: %s\n", err->error_code, error);
530    if (extension != NULL) {
531	fprintf (stderr, "  Request extension: %s\n", extension);
532    }
533
534    fprintf (stderr, "  Request Major code: %d", err->major_code);
535    if (major_name != NULL) {
536	fprintf (stderr, " (%s)", major_name);
537    }
538    fputs ("\n", stderr);
539
540    if (err->major_code >= 128)
541    {
542	fprintf (stderr, "  Request Minor code: %d", err->minor_code);
543	if (minor_name != NULL) {
544	    fprintf (stderr, " (%s)", minor_name);
545	}
546	fputs ("\n", stderr);
547    }
548
549    switch (err->error_code)
550    {
551    case XCB_VALUE:
552	value_type = "Value";
553	break;
554    case XCB_ATOM:
555	value_type = "AtomID";
556	break;
557    case XCB_WINDOW:
558    case XCB_PIXMAP:
559    case XCB_CURSOR:
560    case XCB_FONT:
561    case XCB_DRAWABLE:
562    case XCB_COLORMAP:
563    case XCB_G_CONTEXT:
564    case XCB_ID_CHOICE:
565	value_type = "ResourceID";
566	break;
567    default:
568	value_type = NULL;
569    }
570    if (value_type != NULL) {
571	fprintf (stderr, "  %s in failed request: 0x%x\n",
572		 value_type, err->resource_id);
573    }
574
575    fprintf (stderr, "  Serial number of failed request: %d\n",
576	     err->full_sequence);
577}
578
579/*
580 * Cache for atom lookups in either direction
581 */
582struct atom_cache_entry {
583    xcb_atom_t atom;
584    const char *name;
585    xcb_intern_atom_cookie_t intern_atom;
586    struct atom_cache_entry *next;
587};
588
589static struct atom_cache_entry *atom_cache;
590
591/*
592 * Send a request to the server for an atom by name
593 * Does not create the atom if it is not already present
594 */
595struct atom_cache_entry *Intern_Atom (xcb_connection_t * dpy, const char *name)
596{
597    struct atom_cache_entry *a;
598
599    for (a = atom_cache ; a != NULL ; a = a->next) {
600	if (strcmp (a->name, name) == 0)
601	    return a; /* already requested or found */
602    }
603
604    a = calloc(1, sizeof(struct atom_cache_entry));
605    if (a != NULL) {
606	a->name = name;
607	a->intern_atom = xcb_intern_atom (dpy, False, strlen (name), (name));
608	a->next = atom_cache;
609	atom_cache = a;
610    }
611    return a;
612}
613
614/* Get an atom by name when it is needed. */
615xcb_atom_t Get_Atom (xcb_connection_t * dpy, const char *name)
616{
617    struct atom_cache_entry *a = Intern_Atom (dpy, name);
618
619    if (a == NULL)
620	return XCB_ATOM_NONE;
621
622    if (a->atom == XCB_ATOM_NONE) {
623	xcb_intern_atom_reply_t *reply;
624
625	reply = xcb_intern_atom_reply(dpy, a->intern_atom, NULL);
626	if (reply) {
627	    a->atom = reply->atom;
628	    free (reply);
629	} else {
630	    a->atom = (xcb_atom_t) -1;
631	}
632    }
633    if (a->atom == (xcb_atom_t) -1) /* internal error */
634	return XCB_ATOM_NONE;
635
636    return a->atom;
637}
638
639/* Get the name for an atom when it is needed. */
640const char *Get_Atom_Name (xcb_connection_t * dpy, xcb_atom_t atom)
641{
642    struct atom_cache_entry *a;
643
644    for (a = atom_cache ; a != NULL ; a = a->next) {
645	if (a->atom == atom)
646	    return a->name; /* already requested or found */
647    }
648
649    a = calloc(1, sizeof(struct atom_cache_entry));
650    if (a != NULL) {
651	xcb_get_atom_name_cookie_t cookie = xcb_get_atom_name (dpy, atom);
652	xcb_get_atom_name_reply_t *reply
653	    = xcb_get_atom_name_reply (dpy, cookie, NULL);
654
655	a->atom = atom;
656	if (reply) {
657	    int len = xcb_get_atom_name_name_length (reply);
658	    char *name = malloc(len + 1);
659	    if (name) {
660		memcpy (name, xcb_get_atom_name_name (reply), len);
661		name[len] = '\0';
662		a->name = name;
663	    }
664	    free (reply);
665	}
666
667	a->next = atom_cache;
668	atom_cache = a;
669
670	return a->name;
671    }
672    return NULL;
673}
674