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