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