1 /* 2 * vim:ts=4:sw=4:expandtab 3 * 4 * © 2010 Michael Stapelberg 5 * © 2021 Raymond Li 6 * 7 * xcb.c: contains all functions which use XCB to talk to X11. Mostly wrappers 8 * around the rather complicated/ugly parts of the XCB API. 9 * 10 */ 11 #include <xcb/xcb.h> 12 #include <xcb/xcb_image.h> 13 #include <xcb/xcb_atom.h> 14 #include <xcb/xcb_aux.h> 15 #include <xcb/composite.h> 16 #include <xcb/xkb.h> 17 #include <xkbcommon/xkbcommon.h> 18 #include <xkbcommon/xkbcommon-x11.h> 19 #include <stdio.h> 20 #include <stdlib.h> 21 #include <stdbool.h> 22 #include <string.h> 23 #include <unistd.h> 24 #include <assert.h> 25 #include <err.h> 26 #include <time.h> 27 #include <sys/time.h> 28 29 #include "cursors.h" 30 #include "i3lock.h" 31 #include "xcb.h" 32 #include "unlock_indicator.h" 33 34 extern auth_state_t auth_state; 35 extern bool composite; 36 extern bool debug_mode; 37 38 xcb_connection_t *conn; 39 xcb_screen_t *screen; 40 41 static xcb_atom_t _NET_WM_BYPASS_COMPOSITOR = XCB_NONE; 42 void _init_net_wm_bypass_compositor(xcb_connection_t *conn) { 43 if (_NET_WM_BYPASS_COMPOSITOR != XCB_NONE) { 44 /* already initialized */ 45 return; 46 } 47 xcb_generic_error_t *err; 48 xcb_intern_atom_reply_t *atom_reply = xcb_intern_atom_reply( 49 conn, 50 xcb_intern_atom(conn, 0, strlen("_NET_WM_BYPASS_COMPOSITOR"), "_NET_WM_BYPASS_COMPOSITOR"), 51 &err); 52 if (atom_reply == NULL) { 53 fprintf(stderr, "X11 Error %d\n", err->error_code); 54 free(err); 55 return; 56 } 57 _NET_WM_BYPASS_COMPOSITOR = atom_reply->atom; 58 free(atom_reply); 59 } 60 61 #define curs_invisible_width 8 62 #define curs_invisible_height 8 63 64 static unsigned char curs_invisible_bits[] = { 65 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 66 67 #define curs_windows_width 11 68 #define curs_windows_height 19 69 70 static unsigned char curs_windows_bits[] = { 71 0xfe, 0x07, 0xfc, 0x07, 0xfa, 0x07, 0xf6, 0x07, 0xee, 0x07, 0xde, 0x07, 72 0xbe, 0x07, 0x7e, 0x07, 0xfe, 0x06, 0xfe, 0x05, 0x3e, 0x00, 0xb6, 0x07, 73 0x6a, 0x07, 0x6c, 0x07, 0xde, 0x06, 0xdf, 0x06, 0xbf, 0x05, 0xbf, 0x05, 74 0x7f, 0x06}; 75 76 #define mask_windows_width 11 77 #define mask_windows_height 19 78 79 static unsigned char mask_windows_bits[] = { 80 0x01, 0x00, 0x03, 0x00, 0x07, 0x00, 0x0f, 0x00, 0x1f, 0x00, 0x3f, 0x00, 81 0x7f, 0x00, 0xff, 0x00, 0xff, 0x01, 0xff, 0x03, 0xff, 0x07, 0x7f, 0x00, 82 0xf7, 0x00, 0xf3, 0x00, 0xe1, 0x01, 0xe0, 0x01, 0xc0, 0x03, 0xc0, 0x03, 83 0x80, 0x01}; 84 85 static uint32_t get_colorpixel(char *hex) { 86 char strgroups[4][3] = {{hex[0], hex[1], '\0'}, 87 {hex[2], hex[3], '\0'}, 88 {hex[4], hex[5], '\0'}, 89 {hex[6], hex[4], '\0'}}; 90 uint32_t rgb16[4] = {(strtol(strgroups[0], NULL, 16)), 91 (strtol(strgroups[1], NULL, 16)), 92 (strtol(strgroups[2], NULL, 16)), 93 (strtol(strgroups[3], NULL, 16))}; 94 95 return (rgb16[3] << 24) + (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2]; 96 } 97 98 xcb_visualtype_t *get_visualtype_by_depth(uint16_t depth, xcb_screen_t *root_screen) { 99 xcb_depth_iterator_t depth_iter; 100 101 depth_iter = xcb_screen_allowed_depths_iterator(root_screen); 102 for (; depth_iter.rem; xcb_depth_next(&depth_iter)) { 103 if (depth_iter.data->depth != depth) 104 continue; 105 106 xcb_visualtype_iterator_t visual_iter; 107 108 visual_iter = xcb_depth_visuals_iterator(depth_iter.data); 109 if (!visual_iter.rem) 110 continue; 111 return visual_iter.data; 112 } 113 return NULL; 114 } 115 116 117 xcb_visualtype_t *get_root_visual_type(xcb_screen_t *screen) { 118 xcb_visualtype_t *visual_type = NULL; 119 xcb_depth_iterator_t depth_iter; 120 xcb_visualtype_iterator_t visual_iter; 121 122 for (depth_iter = xcb_screen_allowed_depths_iterator(screen); 123 depth_iter.rem; 124 xcb_depth_next(&depth_iter)) { 125 for (visual_iter = xcb_depth_visuals_iterator(depth_iter.data); 126 visual_iter.rem; 127 xcb_visualtype_next(&visual_iter)) { 128 if (screen->root_visual != visual_iter.data->visual_id) 129 continue; 130 131 visual_type = visual_iter.data; 132 return visual_type; 133 } 134 } 135 136 return NULL; 137 } 138 139 xcb_pixmap_t create_bg_pixmap(xcb_connection_t *conn, xcb_drawable_t win, u_int32_t *resolution, char *color) { 140 xcb_pixmap_t bg_pixmap = xcb_generate_id(conn); 141 xcb_create_pixmap(conn, 32, bg_pixmap, win, resolution[0], resolution[1]); 142 143 /* Generate a Graphics Context and fill the pixmap with background color 144 * (for images that are smaller than your screen) */ 145 xcb_gcontext_t gc = xcb_generate_id(conn); 146 uint32_t values[] = {get_colorpixel(color)}; 147 xcb_create_gc(conn, gc, bg_pixmap, XCB_GC_FOREGROUND, values); 148 xcb_rectangle_t rect = {0, 0, resolution[0], resolution[1]}; 149 xcb_poly_fill_rectangle(conn, bg_pixmap, gc, 1, &rect); 150 xcb_free_gc(conn, gc); 151 152 return bg_pixmap; 153 } 154 155 xcb_window_t open_fullscreen_window(xcb_connection_t *conn, xcb_screen_t *scr, char *color) { 156 uint32_t mask = 0; 157 uint32_t values[5]; 158 xcb_window_t win = xcb_generate_id(conn); 159 xcb_window_t parent_win = scr->root; 160 161 if (composite) { 162 /* Check whether the composite extension is available */ 163 const xcb_query_extension_reply_t *extension_query = NULL; 164 xcb_generic_error_t *error = NULL; 165 xcb_composite_get_overlay_window_cookie_t cookie; 166 xcb_composite_get_overlay_window_reply_t *composite_reply = NULL; 167 168 extension_query = xcb_get_extension_data(conn, &xcb_composite_id); 169 if (extension_query && extension_query->present) { 170 /* When composition is used, we need to use the composite overlay 171 * window instead of the normal root window to be able to cover 172 * composited windows */ 173 cookie = xcb_composite_get_overlay_window(conn, scr->root); 174 composite_reply = xcb_composite_get_overlay_window_reply(conn, cookie, &error); 175 176 if (!error && composite_reply) { 177 parent_win = composite_reply->overlay_win; 178 } 179 180 free(composite_reply); 181 free(error); 182 } 183 } 184 185 186 xcb_visualid_t visual = get_visualtype_by_depth(32, scr)->visual_id; 187 xcb_colormap_t win_colormap = xcb_generate_id(conn); 188 xcb_create_colormap(conn, XCB_COLORMAP_ALLOC_NONE, win_colormap, scr->root, visual); 189 190 mask |= XCB_CW_BACK_PIXEL; 191 values[0] = get_colorpixel(color); 192 193 mask |= XCB_CW_BORDER_PIXEL; 194 values[1] = 0x00000000; 195 196 mask |= XCB_CW_OVERRIDE_REDIRECT; 197 values[2] = 1; 198 199 mask |= XCB_CW_EVENT_MASK; 200 values[3] = XCB_EVENT_MASK_EXPOSURE | 201 XCB_EVENT_MASK_KEY_PRESS | 202 XCB_EVENT_MASK_KEY_RELEASE | 203 XCB_EVENT_MASK_VISIBILITY_CHANGE | 204 XCB_EVENT_MASK_STRUCTURE_NOTIFY; 205 206 mask |= XCB_CW_COLORMAP; 207 values[4] = win_colormap; 208 209 xcb_create_window(conn, 210 32, 211 win, /* the window id */ 212 parent_win, 213 0, 0, 214 scr->width_in_pixels, 215 scr->height_in_pixels, /* dimensions */ 216 0, /* border = 0, we draw our own */ 217 XCB_WINDOW_CLASS_INPUT_OUTPUT, 218 visual, /* copy visual from parent */ 219 mask, 220 values); 221 222 char *name = "i3lock"; 223 xcb_change_property(conn, 224 XCB_PROP_MODE_REPLACE, 225 win, 226 XCB_ATOM_WM_NAME, 227 XCB_ATOM_STRING, 228 8, 229 strlen(name), 230 name); 231 232 xcb_change_property(conn, 233 XCB_PROP_MODE_REPLACE, 234 win, 235 XCB_ATOM_WM_CLASS, 236 XCB_ATOM_STRING, 237 8, 238 2 * (strlen("i3lock") + 1), 239 "i3lock\0i3lock\0"); 240 241 const uint32_t bypass_compositor = 1; /* disable compositing */ 242 _init_net_wm_bypass_compositor(conn); 243 xcb_change_property(conn, 244 XCB_PROP_MODE_REPLACE, 245 win, 246 _NET_WM_BYPASS_COMPOSITOR, 247 XCB_ATOM_CARDINAL, 248 32, 249 1, 250 &bypass_compositor); 251 252 /* Map the window (= make it visible) */ 253 xcb_map_window(conn, win); 254 255 /* Raise window (put it on top) */ 256 values[0] = XCB_STACK_MODE_ABOVE; 257 xcb_configure_window(conn, win, XCB_CONFIG_WINDOW_STACK_MODE, values); 258 259 /* Ensure that the window is created and set up before returning */ 260 xcb_aux_sync(conn); 261 262 return win; 263 } 264 265 /* 266 * Repeatedly tries to grab pointer and keyboard (up to the specified number of 267 * tries). 268 * 269 * Returns true if the grab succeeded, false if not. 270 * 271 */ 272 bool grab_pointer_and_keyboard(xcb_connection_t *conn, xcb_screen_t *screen, xcb_cursor_t cursor, int tries) { 273 xcb_grab_pointer_cookie_t pcookie; 274 xcb_grab_pointer_reply_t *preply; 275 276 xcb_grab_keyboard_cookie_t kcookie; 277 xcb_grab_keyboard_reply_t *kreply; 278 279 const suseconds_t screen_redraw_timeout = 100000; /* 100ms */ 280 281 /* Using few variables to trigger a redraw_screen() if too many tries */ 282 bool redrawn = false; 283 struct timeval start; 284 if (gettimeofday(&start, NULL) == -1) { 285 err(EXIT_FAILURE, "gettimeofday"); 286 } 287 288 while (tries-- > 0) { 289 pcookie = xcb_grab_pointer( 290 conn, 291 false, /* get all pointer events specified by the following mask */ 292 screen->root, /* grab the root window */ 293 XCB_NONE, /* which events to let through */ 294 XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */ 295 XCB_GRAB_MODE_ASYNC, /* keyboard mode */ 296 XCB_NONE, /* confine_to = in which window should the cursor stay */ 297 cursor, /* we change the cursor to whatever the user wanted */ 298 XCB_CURRENT_TIME); 299 300 if ((preply = xcb_grab_pointer_reply(conn, pcookie, NULL)) && 301 preply->status == XCB_GRAB_STATUS_SUCCESS) { 302 free(preply); 303 break; 304 } 305 306 /* In case the grab failed, we still need to free the reply */ 307 free(preply); 308 309 /* Make this quite a bit slower */ 310 usleep(50); 311 312 struct timeval now; 313 if (gettimeofday(&now, NULL) == -1) { 314 err(EXIT_FAILURE, "gettimeofday"); 315 } 316 317 struct timeval elapsed; 318 timersub(&now, &start, &elapsed); 319 320 if (!redrawn && 321 (tries % 100) == 0 && 322 elapsed.tv_usec >= screen_redraw_timeout) { 323 redraw_screen(); 324 redrawn = true; 325 } 326 } 327 328 while (tries-- > 0) { 329 kcookie = xcb_grab_keyboard( 330 conn, 331 true, /* report events */ 332 screen->root, /* grab the root window */ 333 XCB_CURRENT_TIME, 334 XCB_GRAB_MODE_ASYNC, /* process events as normal, do not require sync */ 335 XCB_GRAB_MODE_ASYNC); 336 337 if ((kreply = xcb_grab_keyboard_reply(conn, kcookie, NULL)) && 338 kreply->status == XCB_GRAB_STATUS_SUCCESS) { 339 free(kreply); 340 break; 341 } 342 343 /* In case the grab failed, we still need to free the reply */ 344 free(kreply); 345 346 /* Make this quite a bit slower */ 347 usleep(50); 348 349 struct timeval now; 350 if (gettimeofday(&now, NULL) == -1) { 351 err(EXIT_FAILURE, "gettimeofday"); 352 } 353 354 struct timeval elapsed; 355 timersub(&now, &start, &elapsed); 356 357 /* Trigger a screen redraw if 100ms elapsed */ 358 if (!redrawn && 359 (tries % 100) == 0 && 360 elapsed.tv_usec >= screen_redraw_timeout) { 361 redraw_screen(); 362 redrawn = true; 363 } 364 } 365 366 return (tries > 0); 367 } 368 369 xcb_cursor_t create_cursor(xcb_connection_t *conn, xcb_screen_t *screen, xcb_window_t win, int choice) { 370 xcb_pixmap_t bitmap; 371 xcb_pixmap_t mask; 372 xcb_cursor_t cursor; 373 374 unsigned char *curs_bits; 375 unsigned char *mask_bits; 376 int curs_w, curs_h; 377 378 switch (choice) { 379 case CURS_NONE: 380 curs_bits = curs_invisible_bits; 381 mask_bits = curs_invisible_bits; 382 curs_w = curs_invisible_width; 383 curs_h = curs_invisible_height; 384 break; 385 case CURS_WIN: 386 curs_bits = curs_windows_bits; 387 mask_bits = mask_windows_bits; 388 curs_w = curs_windows_width; 389 curs_h = curs_windows_height; 390 break; 391 case CURS_DEFAULT: 392 default: 393 return XCB_NONE; /* XCB_NONE is xcb's way of saying "don't change the cursor" */ 394 } 395 396 bitmap = xcb_create_pixmap_from_bitmap_data(conn, 397 win, 398 curs_bits, 399 curs_w, 400 curs_h, 401 1, 402 screen->white_pixel, 403 screen->black_pixel, 404 NULL); 405 406 mask = xcb_create_pixmap_from_bitmap_data(conn, 407 win, 408 mask_bits, 409 curs_w, 410 curs_h, 411 1, 412 screen->white_pixel, 413 screen->black_pixel, 414 NULL); 415 416 cursor = xcb_generate_id(conn); 417 418 xcb_create_cursor(conn, 419 cursor, 420 bitmap, 421 mask, 422 65535, 65535, 65535, 423 0, 0, 0, 424 0, 0); 425 426 xcb_free_pixmap(conn, bitmap); 427 xcb_free_pixmap(conn, mask); 428 429 return cursor; 430 } 431 432 static xcb_atom_t _NET_ACTIVE_WINDOW = XCB_NONE; 433 void _init_net_active_window(xcb_connection_t *conn) { 434 if (_NET_ACTIVE_WINDOW != XCB_NONE) { 435 /* already initialized */ 436 return; 437 } 438 xcb_generic_error_t *err; 439 xcb_intern_atom_reply_t *atom_reply = xcb_intern_atom_reply( 440 conn, 441 xcb_intern_atom(conn, 0, strlen("_NET_ACTIVE_WINDOW"), "_NET_ACTIVE_WINDOW"), 442 &err); 443 if (atom_reply == NULL) { 444 fprintf(stderr, "X11 Error %d\n", err->error_code); 445 free(err); 446 return; 447 } 448 _NET_ACTIVE_WINDOW = atom_reply->atom; 449 free(atom_reply); 450 } 451 452 xcb_window_t find_focused_window(xcb_connection_t *conn, const xcb_window_t root) { 453 xcb_window_t result = XCB_NONE; 454 455 _init_net_active_window(conn); 456 457 xcb_get_property_reply_t *prop_reply = xcb_get_property_reply( 458 conn, 459 xcb_get_property_unchecked( 460 conn, false, root, _NET_ACTIVE_WINDOW, XCB_GET_PROPERTY_TYPE_ANY, 0, 1 /* word */), 461 NULL); 462 if (prop_reply == NULL) { 463 goto out; 464 } 465 if (xcb_get_property_value_length(prop_reply) == 0) { 466 goto out_prop; 467 } 468 if (prop_reply->type != XCB_ATOM_WINDOW) { 469 goto out_prop; 470 } 471 472 result = *((xcb_window_t *)xcb_get_property_value(prop_reply)); 473 474 out_prop: 475 free(prop_reply); 476 out: 477 return result; 478 } 479 480 void set_focused_window(xcb_connection_t *conn, const xcb_window_t root, const xcb_window_t window) { 481 xcb_client_message_event_t ev; 482 memset(&ev, '\0', sizeof(xcb_client_message_event_t)); 483 484 _init_net_active_window(conn); 485 486 ev.response_type = XCB_CLIENT_MESSAGE; 487 ev.window = window; 488 ev.type = _NET_ACTIVE_WINDOW; 489 ev.format = 32; 490 ev.data.data32[0] = 2; /* 2 = pager */ 491 492 xcb_send_event(conn, false, root, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char *)&ev); 493 xcb_flush(conn); 494 } 495 496 xcb_pixmap_t capture_bg_pixmap(xcb_connection_t *conn, xcb_screen_t *scr, u_int32_t * resolution) { 497 xcb_pixmap_t bg_pixmap = xcb_generate_id(conn); 498 xcb_create_pixmap(conn, scr->root_depth, bg_pixmap, scr->root, resolution[0], resolution[1]); 499 xcb_gcontext_t gc = xcb_generate_id(conn); 500 uint32_t values[] = { scr->black_pixel, 1}; 501 xcb_create_gc(conn, gc, bg_pixmap, XCB_GC_FOREGROUND | XCB_GC_SUBWINDOW_MODE, values); 502 xcb_rectangle_t rect = { 0, 0, resolution[0], resolution[1] }; 503 xcb_poly_fill_rectangle(conn, bg_pixmap, gc, 1, &rect); 504 xcb_copy_area(conn, scr->root, bg_pixmap, gc, 0, 0, 0, 0, resolution[0], resolution[1]); 505 xcb_flush(conn); 506 xcb_free_gc(conn, gc); 507 return bg_pixmap; 508 } 509 510 static char * get_atom_name(xcb_connection_t* conn, xcb_atom_t atom) { 511 xcb_get_atom_name_reply_t *reply = NULL; 512 char *name; 513 int length; 514 char* answer = NULL; 515 516 if (atom == 0) 517 return "<empty>"; 518 519 xcb_get_atom_name_cookie_t cookie; 520 xcb_generic_error_t *error = NULL; 521 522 cookie = xcb_get_atom_name(conn, atom); 523 524 reply = xcb_get_atom_name_reply(conn, cookie, &error); 525 if (!reply || error) 526 return "<invalid>"; 527 528 length = xcb_get_atom_name_name_length(reply); 529 name = xcb_get_atom_name_name(reply); 530 531 answer = malloc(sizeof(char) * (length + 1)); 532 strncpy(answer, name, length); 533 answer[length] = '\0'; 534 free(error); 535 free(reply); 536 return answer; 537 } 538 539 540 uint8_t xcb_get_key_group_index(xcb_connection_t *conn) { 541 xcb_xkb_get_state_cookie_t cookie; 542 xcb_xkb_get_state_reply_t* reply = NULL; 543 cookie = xcb_xkb_get_state(conn, XCB_XKB_ID_USE_CORE_KBD); 544 reply = xcb_xkb_get_state_reply(conn, cookie, NULL); 545 uint8_t ans = reply->group; 546 free(reply); 547 return ans; 548 } 549 550 551 char* xcb_get_key_group_names(xcb_connection_t *conn) { 552 uint8_t xkb_base_event; 553 uint8_t xkb_base_error; 554 if (xkb_x11_setup_xkb_extension(conn, 555 XKB_X11_MIN_MAJOR_XKB_VERSION, 556 XKB_X11_MIN_MINOR_XKB_VERSION, 557 0, 558 NULL, 559 NULL, 560 &xkb_base_event, 561 &xkb_base_error) != 1) 562 errx(EXIT_FAILURE, "Could not setup XKB extension."); 563 564 565 xcb_xkb_get_names_reply_t *reply = NULL; 566 567 568 xcb_generic_error_t *error = NULL; 569 xcb_xkb_get_names_cookie_t cookie; 570 571 cookie = xcb_xkb_get_names(conn, 572 XCB_XKB_ID_USE_CORE_KBD, 573 all_name_details); 574 575 reply = xcb_xkb_get_names_reply(conn, cookie, &error); 576 if (!reply || error) 577 errx(1, "couldn't get reply for get_names"); 578 579 xcb_xkb_get_names_value_list_t list; 580 581 void *buffer; 582 583 buffer = xcb_xkb_get_names_value_list(reply); 584 xcb_xkb_get_names_value_list_unpack(buffer, 585 reply->nTypes, 586 reply->indicators, 587 reply->virtualMods, 588 reply->groupNames, 589 reply->nKeys, 590 reply->nKeyAliases, 591 reply->nRadioGroups, 592 reply->which, 593 &list); 594 595 /* dump group names. */ 596 597 int length; 598 xcb_atom_t *iter; 599 uint8_t index; 600 char* answer = NULL; 601 length = xcb_xkb_get_names_value_list_groups_length(reply, &list); 602 iter = xcb_xkb_get_names_value_list_groups(&list); 603 index = xcb_get_key_group_index(conn); 604 605 for (int i = 0; i < length; i++) { 606 xcb_atom_t group_name = *iter; 607 char* name = get_atom_name(conn, group_name); 608 DEBUG("group_name %d: %s\n", i, name); 609 if (i == index) { 610 answer = name; 611 } else { 612 free(name); 613 } 614 iter++; 615 } 616 free(reply); 617 free(error); 618 return answer; 619 }
