dsimple.c revision 05bee9bc
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	switch (event->response_type & 0x7f) {
209	case XCB_BUTTON_PRESS:
210	{
211	    xcb_button_press_event_t *bp = (xcb_button_press_event_t *)event;
212
213	    if (target_win == XCB_WINDOW_NONE) {
214		target_win = bp->child; /* window selected */
215		if (target_win == XCB_WINDOW_NONE)
216		    target_win = root;
217	    }
218	    buttons++;
219	    break;
220	}
221	case XCB_BUTTON_RELEASE:
222	    if (buttons > 0) /* there may have been some down before we started */
223		buttons--;
224	    break;
225	default:
226	    /* just discard all other events */
227	    break;
228	}
229	free (event);
230    }
231
232    xcb_ungrab_pointer (dpy, XCB_TIME_CURRENT_TIME); /* Done with pointer */
233
234    if (!descend || (target_win == root))
235	return (target_win);
236
237    target_win = Find_Client (dpy, root, target_win);
238
239    return (target_win);
240}
241
242
243/*
244 * Window_With_Name: routine to locate a window with a given name on a display.
245 *                   If no window with the given name is found, 0 is returned.
246 *                   If more than one window has the given name, the first
247 *                   one found will be returned.  Only top and its subwindows
248 *                   are looked at.  Normally, top should be the RootWindow.
249 */
250
251struct wininfo_cookies {
252    xcb_get_property_cookie_t get_net_wm_name;
253    xcb_get_property_cookie_t get_wm_name;
254    xcb_query_tree_cookie_t query_tree;
255};
256
257#ifndef USE_XCB_ICCCM
258# define xcb_icccm_get_wm_name(Dpy, Win) \
259    xcb_get_property (Dpy, False, Win, XCB_ATOM_WM_NAME, \
260		      XCB_GET_PROPERTY_TYPE_ANY, 0, BUFSIZ)
261#endif
262
263static xcb_atom_t atom_net_wm_name, atom_utf8_string;
264
265# define xcb_get_net_wm_name(Dpy, Win)			 \
266    xcb_get_property (Dpy, False, Win, atom_net_wm_name, \
267		      atom_utf8_string, 0, BUFSIZ)
268
269
270static xcb_window_t
271recursive_Window_With_Name  (
272    xcb_connection_t *dpy,
273    xcb_window_t window,
274    struct wininfo_cookies *cookies,
275    const char *name,
276    size_t namelen)
277{
278    xcb_window_t *children;
279    unsigned int nchildren;
280    int i;
281    xcb_window_t w = 0;
282    xcb_generic_error_t *err;
283    xcb_query_tree_reply_t *tree;
284    struct wininfo_cookies *child_cookies;
285    xcb_get_property_reply_t *prop;
286
287    if (cookies->get_net_wm_name.sequence) {
288	prop = xcb_get_property_reply (dpy, cookies->get_net_wm_name, &err);
289
290	if (prop) {
291	    if (prop->type == atom_utf8_string) {
292		const char *prop_name = xcb_get_property_value (prop);
293		int prop_name_len = xcb_get_property_value_length (prop);
294
295		/* can't use strcmp, since prop.name is not null terminated */
296		if ((namelen == prop_name_len) &&
297		    memcmp (prop_name, name, namelen) == 0) {
298		    w = window;
299		}
300	    }
301	    free (prop);
302	} else if (err) {
303	    if (err->response_type == 0)
304		Print_X_Error (dpy, err);
305	    return 0;
306	}
307    }
308
309    if (w) {
310	xcb_discard_reply (dpy, cookies->get_wm_name.sequence);
311    } else {
312#ifdef USE_XCB_ICCCM
313	xcb_icccm_get_text_property_reply_t nameprop;
314
315	if (xcb_icccm_get_wm_name_reply (dpy, cookies->get_wm_name,
316					 &nameprop, &err)) {
317	    /* can't use strcmp, since nameprop.name is not null terminated */
318	    if ((namelen == nameprop.name_len) &&
319		memcmp (nameprop.name, name, namelen) == 0) {
320		w = window;
321	    }
322
323	    xcb_icccm_get_text_property_reply_wipe (&nameprop);
324	}
325#else
326	prop = xcb_get_property_reply (dpy, cookies->get_wm_name, &err);
327
328	if (prop) {
329	    if (prop->type == XCB_ATOM_STRING) {
330		const char *prop_name = xcb_get_property_value (prop);
331		int prop_name_len = xcb_get_property_value_length (prop);
332
333		/* can't use strcmp, since prop.name is not null terminated */
334		if ((namelen == prop_name_len) &&
335		    memcmp (prop_name, name, namelen) == 0) {
336		    w = window;
337		}
338	    }
339	    free (prop);
340	}
341#endif
342	else if (err) {
343	    if (err->response_type == 0)
344		Print_X_Error (dpy, err);
345	    return 0;
346	}
347    }
348
349    if (w)
350    {
351	xcb_discard_reply (dpy, cookies->query_tree.sequence);
352	return w;
353    }
354
355    tree = xcb_query_tree_reply (dpy, cookies->query_tree, &err);
356    if (!tree) {
357	if (err->response_type == 0)
358	    Print_X_Error (dpy, err);
359	return 0;
360    }
361
362    nchildren = xcb_query_tree_children_length (tree);
363    children = xcb_query_tree_children (tree);
364    child_cookies = calloc(nchildren, sizeof(struct wininfo_cookies));
365
366    if (child_cookies == NULL)
367	Fatal_Error("Failed to allocate memory in recursive_Window_With_Name");
368
369    for (i = 0; i < nchildren; i++) {
370	if (atom_net_wm_name && atom_utf8_string)
371	    child_cookies[i].get_net_wm_name =
372		xcb_get_net_wm_name (dpy, children[i]);
373	child_cookies[i].get_wm_name = xcb_icccm_get_wm_name (dpy, children[i]);
374	child_cookies[i].query_tree = xcb_query_tree (dpy, children[i]);
375    }
376    xcb_flush (dpy);
377
378    for (i = 0; i < nchildren; i++) {
379	w = recursive_Window_With_Name (dpy, children[i],
380					&child_cookies[i], name, namelen);
381	if (w)
382	    break;
383    }
384
385    if (w)
386    {
387	/* clean up remaining replies */
388	for (/* keep previous i */; i < nchildren; i++) {
389	    if (child_cookies[i].get_net_wm_name.sequence)
390		xcb_discard_reply (dpy,
391				   child_cookies[i].get_net_wm_name.sequence);
392	    xcb_discard_reply (dpy, child_cookies[i].get_wm_name.sequence);
393	    xcb_discard_reply (dpy, child_cookies[i].query_tree.sequence);
394	}
395    }
396
397    free (child_cookies);
398    free (tree); /* includes storage for children[] */
399    return (w);
400}
401
402xcb_window_t
403Window_With_Name (
404    xcb_connection_t *dpy,
405    xcb_window_t top,
406    const char *name)
407{
408    struct wininfo_cookies cookies;
409
410    atom_net_wm_name = Get_Atom (dpy, "_NET_WM_NAME");
411    atom_utf8_string = Get_Atom (dpy, "UTF8_STRING");
412
413    if (atom_net_wm_name && atom_utf8_string)
414	cookies.get_net_wm_name = xcb_get_net_wm_name (dpy, top);
415    cookies.get_wm_name = xcb_icccm_get_wm_name (dpy, top);
416    cookies.query_tree = xcb_query_tree (dpy, top);
417    xcb_flush (dpy);
418    return recursive_Window_With_Name(dpy, top, &cookies, name, strlen(name));
419}
420
421
422/*
423 * Standard fatal error routine - call like printf
424 */
425void Fatal_Error (const char *msg, ...)
426{
427    va_list args;
428    fflush (stdout);
429    fflush (stderr);
430    fprintf (stderr, "%s: error: ", program_name);
431    va_start (args, msg);
432    vfprintf (stderr, msg, args);
433    va_end (args);
434    fprintf (stderr, "\n");
435    exit (EXIT_FAILURE);
436}
437
438/*
439 * Print X error information like the default Xlib error handler
440 */
441void
442Print_X_Error (
443    xcb_connection_t *dpy,
444    xcb_generic_error_t *err
445    )
446{
447    char buffer[256] = "";
448
449    if ((err == NULL) || (err->response_type != 0)) /* not an error */
450	return;
451
452    /* Todo: find a more user friendly way to show request/extension info */
453    if (err->error_code >= 128)
454    {
455	fprintf (stderr, "X Extension Error:  Error code %d\n",
456		 err->error_code);
457    }
458    else
459    {
460	switch (err->error_code)
461	{
462	    case XCB_REQUEST:
463		snprintf (buffer, sizeof(buffer), "Bad Request");
464		break;
465
466	    case XCB_VALUE:
467		snprintf (buffer, sizeof(buffer),
468			  "Bad Value: 0x%x", err->resource_id);
469		break;
470
471	    case XCB_WINDOW:
472		snprintf (buffer, sizeof(buffer),
473			  "Bad Window: 0x%x", err->resource_id);
474		break;
475
476	    case XCB_PIXMAP:
477		snprintf (buffer, sizeof(buffer),
478			  "Bad Pixmap: 0x%x", err->resource_id);
479		break;
480
481	    case XCB_ATOM:
482		snprintf (buffer, sizeof(buffer),
483			  "Bad Atom: 0x%x", err->resource_id);
484		break;
485
486	    case XCB_CURSOR:
487		snprintf (buffer, sizeof(buffer),
488			  "Bad Cursor: 0x%x", err->resource_id);
489		break;
490
491	    case XCB_FONT:
492		snprintf (buffer, sizeof(buffer),
493			  "Bad Font: 0x%x", err->resource_id);
494		break;
495
496	    case XCB_MATCH:
497		snprintf (buffer, sizeof(buffer), "Bad Match");
498		break;
499
500	    case XCB_DRAWABLE:
501		snprintf (buffer, sizeof(buffer),
502			  "Bad Drawable: 0x%x", err->resource_id);
503		break;
504
505	    case XCB_ACCESS:
506		snprintf (buffer, sizeof(buffer), "Access Denied");
507		break;
508
509	    case XCB_ALLOC:
510		snprintf (buffer, sizeof(buffer),
511			  "Server Memory Allocation Failure");
512		break;
513
514	    case XCB_COLORMAP:
515		snprintf (buffer, sizeof(buffer),
516			  "Bad Color: 0x%x", err->resource_id);
517		break;
518
519	    case XCB_G_CONTEXT:
520		snprintf (buffer, sizeof(buffer),
521			  "Bad GC: 0x%x", err->resource_id);
522		break;
523
524	    case XCB_ID_CHOICE:
525		snprintf (buffer, sizeof(buffer),
526			  "Bad XID: 0x%x", err->resource_id);
527		break;
528
529	    case XCB_NAME:
530		snprintf (buffer, sizeof(buffer),
531			  "Bad Name");
532		break;
533
534	    case XCB_LENGTH:
535		snprintf (buffer, sizeof(buffer),
536			  "Bad Request Length");
537		break;
538
539	    case XCB_IMPLEMENTATION:
540		snprintf (buffer, sizeof(buffer),
541			  "Server Implementation Failure");
542		break;
543
544	    default:
545		snprintf (buffer, sizeof(buffer), "Unknown error");
546		break;
547	}
548	fprintf (stderr, "X Error: %d: %s\n", err->error_code, buffer);
549    }
550
551    fprintf (stderr, "  Request Major code: %d\n", err->major_code);
552    if (err->major_code >= 128)
553    {
554	fprintf (stderr, "  Request Minor code: %d\n", err->minor_code);
555    }
556
557    fprintf (stderr, "  Request serial number: %d\n", err->full_sequence);
558}
559
560/*
561 * Cache for atom lookups in either direction
562 */
563struct atom_cache_entry {
564    xcb_atom_t atom;
565    const char *name;
566    xcb_intern_atom_cookie_t intern_atom;
567    struct atom_cache_entry *next;
568};
569
570static struct atom_cache_entry *atom_cache;
571
572/*
573 * Send a request to the server for an atom by name
574 * Does not create the atom if it is not already present
575 */
576struct atom_cache_entry *Intern_Atom (xcb_connection_t * dpy, const char *name)
577{
578    struct atom_cache_entry *a;
579
580    for (a = atom_cache ; a != NULL ; a = a->next) {
581	if (strcmp (a->name, name) == 0)
582	    return a; /* already requested or found */
583    }
584
585    a = calloc(1, sizeof(struct atom_cache_entry));
586    if (a != NULL) {
587	a->name = name;
588	a->intern_atom = xcb_intern_atom (dpy, False, strlen (name), (name));
589	a->next = atom_cache;
590	atom_cache = a;
591    }
592    return a;
593}
594
595/* Get an atom by name when it is needed. */
596xcb_atom_t Get_Atom (xcb_connection_t * dpy, const char *name)
597{
598    struct atom_cache_entry *a = Intern_Atom (dpy, name);
599
600    if (a == NULL)
601	return XCB_ATOM_NONE;
602
603    if (a->atom == XCB_ATOM_NONE) {
604	xcb_intern_atom_reply_t *reply;
605
606	reply = xcb_intern_atom_reply(dpy, a->intern_atom, NULL);
607	if (reply) {
608	    a->atom = reply->atom;
609	    free (reply);
610	} else {
611	    a->atom = -1;
612	}
613    }
614    if (a->atom == -1) /* internal error */
615	return XCB_ATOM_NONE;
616
617    return a->atom;
618}
619
620/* Get the name for an atom when it is needed. */
621const char *Get_Atom_Name (xcb_connection_t * dpy, xcb_atom_t atom)
622{
623    struct atom_cache_entry *a;
624
625    for (a = atom_cache ; a != NULL ; a = a->next) {
626	if (a->atom == atom)
627	    return a->name; /* already requested or found */
628    }
629
630    a = calloc(1, sizeof(struct atom_cache_entry));
631    if (a != NULL) {
632	xcb_get_atom_name_cookie_t cookie = xcb_get_atom_name (dpy, atom);
633	xcb_get_atom_name_reply_t *reply
634	    = xcb_get_atom_name_reply (dpy, cookie, NULL);
635
636	a->atom = atom;
637	if (reply) {
638	    int len = xcb_get_atom_name_name_length (reply);
639	    char *name = malloc(len + 1);
640	    if (name) {
641		memcpy (name, xcb_get_atom_name_name (reply), len);
642		name[len] = '\0';
643		a->name = name;
644	    }
645	    free (reply);
646	}
647
648	a->next = atom_cache;
649	atom_cache = a;
650
651	return a->name;
652    }
653    return NULL;
654}
655