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