diff options
| author | Simeon Simeonov | 2020-03-12 12:30:51 +0100 |
|---|---|---|
| committer | Simeon Simeonov | 2020-03-12 12:30:51 +0100 |
| commit | 69a05d516bd0c7e369a0ba1fb78aac57a5ea26f2 (patch) | |
| tree | 87e37110ea0e328533a41c3b16b64036ff132641 /xcb.c | |
Initial commit for the last i3lock commit at this time f3b061233e28ad0cf920c9421adc2459b6920812
Diffstat (limited to 'xcb.c')
| -rw-r--r-- | xcb.c | 434 |
1 files changed, 434 insertions, 0 deletions
| @@ -0,0 +1,434 @@ | |||
| 1 | /* | ||
| 2 | * vim:ts=4:sw=4:expandtab | ||
| 3 | * | ||
| 4 | * © 2010 Michael Stapelberg | ||
| 5 | * | ||
| 6 | * xcb.c: contains all functions which use XCB to talk to X11. Mostly wrappers | ||
| 7 | * around the rather complicated/ugly parts of the XCB API. | ||
| 8 | * | ||
| 9 | */ | ||
| 10 | #include <xcb/xcb.h> | ||
| 11 | #include <xcb/xcb_image.h> | ||
| 12 | #include <xcb/xcb_atom.h> | ||
| 13 | #include <xcb/xcb_aux.h> | ||
| 14 | #include <stdio.h> | ||
| 15 | #include <stdlib.h> | ||
| 16 | #include <stdbool.h> | ||
| 17 | #include <string.h> | ||
| 18 | #include <unistd.h> | ||
| 19 | #include <assert.h> | ||
| 20 | #include <err.h> | ||
| 21 | #include <time.h> | ||
| 22 | #include <sys/time.h> | ||
| 23 | |||
| 24 | #include "cursors.h" | ||
| 25 | #include "unlock_indicator.h" | ||
| 26 | |||
| 27 | extern auth_state_t auth_state; | ||
| 28 | |||
| 29 | xcb_connection_t *conn; | ||
| 30 | xcb_screen_t *screen; | ||
| 31 | |||
| 32 | static xcb_atom_t _NET_WM_BYPASS_COMPOSITOR = XCB_NONE; | ||
| 33 | void _init_net_wm_bypass_compositor(xcb_connection_t *conn) { | ||
| 34 | if (_NET_WM_BYPASS_COMPOSITOR != XCB_NONE) { | ||
| 35 | /* already initialized */ | ||
| 36 | return; | ||
| 37 | } | ||
| 38 | xcb_generic_error_t *err; | ||
| 39 | xcb_intern_atom_reply_t *atom_reply = xcb_intern_atom_reply( | ||
| 40 | conn, | ||
| 41 | xcb_intern_atom(conn, 0, strlen("_NET_WM_BYPASS_COMPOSITOR"), "_NET_WM_BYPASS_COMPOSITOR"), | ||
| 42 | &err); | ||
| 43 | if (atom_reply == NULL) { | ||
| 44 | fprintf(stderr, "X11 Error %d\n", err->error_code); | ||
| 45 | free(err); | ||
| 46 | return; | ||
| 47 | } | ||
| 48 | _NET_WM_BYPASS_COMPOSITOR = atom_reply->atom; | ||
| 49 | free(atom_reply); | ||
| 50 | } | ||
| 51 | |||
| 52 | #define curs_invisible_width 8 | ||
| 53 | #define curs_invisible_height 8 | ||
| 54 | |||
| 55 | static unsigned char curs_invisible_bits[] = { | ||
| 56 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| 57 | |||
| 58 | #define curs_windows_width 11 | ||
| 59 | #define curs_windows_height 19 | ||
| 60 | |||
| 61 | static unsigned char curs_windows_bits[] = { | ||
| 62 | 0xfe, 0x07, 0xfc, 0x07, 0xfa, 0x07, 0xf6, 0x07, 0xee, 0x07, 0xde, 0x07, | ||
| 63 | 0xbe, 0x07, 0x7e, 0x07, 0xfe, 0x06, 0xfe, 0x05, 0x3e, 0x00, 0xb6, 0x07, | ||
| 64 | 0x6a, 0x07, 0x6c, 0x07, 0xde, 0x06, 0xdf, 0x06, 0xbf, 0x05, 0xbf, 0x05, | ||
| 65 | 0x7f, 0x06}; | ||
| 66 | |||
| 67 | #define mask_windows_width 11 | ||
| 68 | #define mask_windows_height 19 | ||
| 69 | |||
| 70 | static unsigned char mask_windows_bits[] = { | ||
| 71 | 0x01, 0x00, 0x03, 0x00, 0x07, 0x00, 0x0f, 0x00, 0x1f, 0x00, 0x3f, 0x00, | ||
| 72 | 0x7f, 0x00, 0xff, 0x00, 0xff, 0x01, 0xff, 0x03, 0xff, 0x07, 0x7f, 0x00, | ||
| 73 | 0xf7, 0x00, 0xf3, 0x00, 0xe1, 0x01, 0xe0, 0x01, 0xc0, 0x03, 0xc0, 0x03, | ||
| 74 | 0x80, 0x01}; | ||
| 75 | |||
| 76 | static uint32_t get_colorpixel(char *hex) { | ||
| 77 | char strgroups[3][3] = {{hex[0], hex[1], '\0'}, | ||
| 78 | {hex[2], hex[3], '\0'}, | ||
| 79 | {hex[4], hex[5], '\0'}}; | ||
| 80 | uint32_t rgb16[3] = {(strtol(strgroups[0], NULL, 16)), | ||
| 81 | (strtol(strgroups[1], NULL, 16)), | ||
| 82 | (strtol(strgroups[2], NULL, 16))}; | ||
| 83 | |||
| 84 | return (rgb16[0] << 16) + (rgb16[1] << 8) + rgb16[2]; | ||
| 85 | } | ||
| 86 | |||
| 87 | xcb_visualtype_t *get_root_visual_type(xcb_screen_t *screen) { | ||
| 88 | xcb_visualtype_t *visual_type = NULL; | ||
| 89 | xcb_depth_iterator_t depth_iter; | ||
| 90 | xcb_visualtype_iterator_t visual_iter; | ||
| 91 | |||
| 92 | for (depth_iter = xcb_screen_allowed_depths_iterator(screen); | ||
| 93 | depth_iter.rem; | ||
| 94 | xcb_depth_next(&depth_iter)) { | ||
| 95 | for (visual_iter = xcb_depth_visuals_iterator(depth_iter.data); | ||
| 96 | visual_iter.rem; | ||
| 97 | xcb_visualtype_next(&visual_iter)) { | ||
| 98 | if (screen->root_visual != visual_iter.data->visual_id) | ||
| 99 | continue; | ||
| 100 | |||
| 101 | visual_type = visual_iter.data; | ||
| 102 | return visual_type; | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | return NULL; | ||
| 107 | } | ||
| 108 | |||
| 109 | xcb_pixmap_t create_bg_pixmap(xcb_connection_t *conn, xcb_screen_t *scr, u_int32_t *resolution, char *color) { | ||
| 110 | xcb_pixmap_t bg_pixmap = xcb_generate_id(conn); | ||
| 111 | xcb_create_pixmap(conn, scr->root_depth, bg_pixmap, scr->root, | ||
| 112 | resolution[0], resolution[1]); | ||
| 113 | |||
| 114 | /* Generate a Graphics Context and fill the pixmap with background color | ||
| 115 | * (for images that are smaller than your screen) */ | ||
| 116 | xcb_gcontext_t gc = xcb_generate_id(conn); | ||
| 117 | uint32_t values[] = {get_colorpixel(color)}; | ||
| 118 | xcb_create_gc(conn, gc, bg_pixmap, XCB_GC_FOREGROUND, values); | ||
| 119 | xcb_rectangle_t rect = {0, 0, resolution[0], resolution[1]}; | ||
| 120 | xcb_poly_fill_rectangle(conn, bg_pixmap, gc, 1, &rect); | ||
| 121 | xcb_free_gc(conn, gc); | ||
| 122 | |||
| 123 | return bg_pixmap; | ||
| 124 | } | ||
| 125 | |||
| 126 | xcb_window_t open_fullscreen_window(xcb_connection_t *conn, xcb_screen_t *scr, char *color, xcb_pixmap_t pixmap) { | ||
| 127 | uint32_t mask = 0; | ||
| 128 | uint32_t values[3]; | ||
| 129 | xcb_window_t win = xcb_generate_id(conn); | ||
| 130 | |||
| 131 | if (pixmap == XCB_NONE) { | ||
| 132 | mask |= XCB_CW_BACK_PIXEL; | ||
| 133 | values[0] = get_colorpixel(color); | ||
| 134 | } else { | ||
| 135 | mask |= XCB_CW_BACK_PIXMAP; | ||
| 136 | values[0] = pixmap; | ||
| 137 | } | ||
| 138 | |||
| 139 | mask |= XCB_CW_OVERRIDE_REDIRECT; | ||
| 140 | values[1] = 1; | ||
| 141 | |||
| 142 | mask |= XCB_CW_EVENT_MASK; | ||
| 143 | values[2] = XCB_EVENT_MASK_EXPOSURE | | ||
| 144 | XCB_EVENT_MASK_KEY_PRESS | | ||
| 145 | XCB_EVENT_MASK_KEY_RELEASE | | ||
| 146 | XCB_EVENT_MASK_VISIBILITY_CHANGE | | ||
| 147 | XCB_EVENT_MASK_STRUCTURE_NOTIFY; | ||
| 148 | |||
| 149 | xcb_create_window(conn, | ||
| 150 | XCB_COPY_FROM_PARENT, | ||
| 151 | win, /* the window id */ | ||
| 152 | scr->root, /* parent == root */ | ||
| 153 | 0, 0, | ||
| 154 | scr->width_in_pixels, | ||
| 155 | scr->height_in_pixels, /* dimensions */ | ||
| 156 | 0, /* border = 0, we draw our own */ | ||
| 157 | XCB_WINDOW_CLASS_INPUT_OUTPUT, | ||
| 158 | XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */ | ||
| 159 | mask, | ||
| 160 | values); | ||
| 161 | |||
| 162 | char *name = "i3lock"; | ||
| 163 | xcb_change_property(conn, | ||
| 164 | XCB_PROP_MODE_REPLACE, | ||
| 165 | win, | ||
| 166 | XCB_ATOM_WM_NAME, | ||
| 167 | XCB_ATOM_STRING, | ||
| 168 | 8, | ||
| 169 | strlen(name), | ||
| 170 | name); | ||
| 171 | |||
| 172 | xcb_change_property(conn, | ||
| 173 | XCB_PROP_MODE_REPLACE, | ||
| 174 | win, | ||
| 175 | XCB_ATOM_WM_CLASS, | ||
| 176 | XCB_ATOM_STRING, | ||
| 177 | 8, | ||
| 178 | 2 * (strlen("i3lock") + 1), | ||
| 179 | "i3lock\0i3lock\0"); | ||
| 180 | |||
| 181 | const uint32_t bypass_compositor = 1; /* disable compositing */ | ||
| 182 | _init_net_wm_bypass_compositor(conn); | ||
| 183 | xcb_change_property(conn, | ||
| 184 | XCB_PROP_MODE_REPLACE, | ||
| 185 | win, | ||
| 186 | _NET_WM_BYPASS_COMPOSITOR, | ||
| 187 | XCB_ATOM_CARDINAL, | ||
| 188 | 32, | ||
| 189 | 1, | ||
| 190 | &bypass_compositor); | ||
| 191 | |||
| 192 | /* Map the window (= make it visible) */ | ||
| 193 | xcb_map_window(conn, win); | ||
| 194 | |||
| 195 | /* Raise window (put it on top) */ | ||
| 196 | values[0] = XCB_STACK_MODE_ABOVE; | ||
| 197 | xcb_configure_window(conn, win, XCB_CONFIG_WINDOW_STACK_MODE, values); | ||
| 198 | |||
| 199 | /* Ensure that the window is created and set up before returning */ | ||
| 200 | xcb_aux_sync(conn); | ||
| 201 | |||
| 202 | return win; | ||
| 203 | } | ||
| 204 | |||
| 205 | /* | ||
| 206 | * Repeatedly tries to grab pointer and keyboard (up to the specified number of | ||
| 207 | * tries). | ||
| 208 | * | ||
| 209 | * Returns true if the grab succeeded, false if not. | ||
| 210 | * | ||
| 211 | */ | ||
| 212 | bool grab_pointer_and_keyboard(xcb_connection_t *conn, xcb_screen_t *screen, xcb_cursor_t cursor, int tries) { | ||
| 213 | xcb_grab_pointer_cookie_t pcookie; | ||
| 214 | xcb_grab_pointer_reply_t *preply; | ||
| 215 | |||
| 216 | xcb_grab_keyboard_cookie_t kcookie; | ||
| 217 | xcb_grab_keyboard_reply_t *kreply; | ||
| 218 | |||
| 219 | const suseconds_t screen_redraw_timeout = 100000; /* 100ms */ | ||
| 220 | |||
| 221 | /* Using few variables to trigger a redraw_screen() if too many tries */ | ||
| 222 | bool redrawn = false; | ||
| 223 | struct timeval start; | ||
| 224 | if (gettimeofday(&start, NULL) == -1) { | ||
| 225 | err(EXIT_FAILURE, "gettimeofday"); | ||
| 226 | } | ||
| 227 | |||
| 228 | while (tries-- > 0) { | ||
| 229 | pcookie = xcb_grab_pointer( | ||
| 230 | conn, | ||
| 231 | false, /* get all pointer events specified by the following mask */ | ||
| 232 | screen->root, /* grab the root window */ | ||
| 233 | XCB_NONE, /* which events to let through */ | ||
| 234 | XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */ | ||
| 235 | XCB_GRAB_MODE_ASYNC, /* keyboard mode */ | ||
| 236 | XCB_NONE, /* confine_to = in which window should the cursor stay */ | ||
| 237 | cursor, /* we change the cursor to whatever the user wanted */ | ||
| 238 | XCB_CURRENT_TIME); | ||
| 239 | |||
| 240 | if ((preply = xcb_grab_pointer_reply(conn, pcookie, NULL)) && | ||
| 241 | preply->status == XCB_GRAB_STATUS_SUCCESS) { | ||
| 242 | free(preply); | ||
| 243 | break; | ||
| 244 | } | ||
| 245 | |||
| 246 | /* In case the grab failed, we still need to free the reply */ | ||
| 247 | free(preply); | ||
| 248 | |||
| 249 | /* Make this quite a bit slower */ | ||
| 250 | usleep(50); | ||
| 251 | |||
| 252 | struct timeval now; | ||
| 253 | if (gettimeofday(&now, NULL) == -1) { | ||
| 254 | err(EXIT_FAILURE, "gettimeofday"); | ||
| 255 | } | ||
| 256 | |||
| 257 | struct timeval elapsed; | ||
| 258 | timersub(&now, &start, &elapsed); | ||
| 259 | |||
| 260 | if (!redrawn && | ||
| 261 | (tries % 100) == 0 && | ||
| 262 | elapsed.tv_usec >= screen_redraw_timeout) { | ||
| 263 | redraw_screen(); | ||
| 264 | redrawn = true; | ||
| 265 | } | ||
| 266 | } | ||
| 267 | |||
| 268 | while (tries-- > 0) { | ||
| 269 | kcookie = xcb_grab_keyboard( | ||
| 270 | conn, | ||
| 271 | true, /* report events */ | ||
| 272 | screen->root, /* grab the root window */ | ||
| 273 | XCB_CURRENT_TIME, | ||
| 274 | XCB_GRAB_MODE_ASYNC, /* process events as normal, do not require sync */ | ||
| 275 | XCB_GRAB_MODE_ASYNC); | ||
| 276 | |||
| 277 | if ((kreply = xcb_grab_keyboard_reply(conn, kcookie, NULL)) && | ||
| 278 | kreply->status == XCB_GRAB_STATUS_SUCCESS) { | ||
| 279 | free(kreply); | ||
| 280 | break; | ||
| 281 | } | ||
| 282 | |||
| 283 | /* In case the grab failed, we still need to free the reply */ | ||
| 284 | free(kreply); | ||
| 285 | |||
| 286 | /* Make this quite a bit slower */ | ||
| 287 | usleep(50); | ||
| 288 | |||
| 289 | struct timeval now; | ||
| 290 | if (gettimeofday(&now, NULL) == -1) { | ||
| 291 | err(EXIT_FAILURE, "gettimeofday"); | ||
| 292 | } | ||
| 293 | |||
| 294 | struct timeval elapsed; | ||
| 295 | timersub(&now, &start, &elapsed); | ||
| 296 | |||
| 297 | /* Trigger a screen redraw if 100ms elapsed */ | ||
| 298 | if (!redrawn && | ||
| 299 | (tries % 100) == 0 && | ||
| 300 | elapsed.tv_usec >= screen_redraw_timeout) { | ||
| 301 | redraw_screen(); | ||
| 302 | redrawn = true; | ||
| 303 | } | ||
| 304 | } | ||
| 305 | |||
| 306 | return (tries > 0); | ||
| 307 | } | ||
| 308 | |||
| 309 | xcb_cursor_t create_cursor(xcb_connection_t *conn, xcb_screen_t *screen, xcb_window_t win, int choice) { | ||
| 310 | xcb_pixmap_t bitmap; | ||
| 311 | xcb_pixmap_t mask; | ||
| 312 | xcb_cursor_t cursor; | ||
| 313 | |||
| 314 | unsigned char *curs_bits; | ||
| 315 | unsigned char *mask_bits; | ||
| 316 | int curs_w, curs_h; | ||
| 317 | |||
| 318 | switch (choice) { | ||
| 319 | case CURS_NONE: | ||
| 320 | curs_bits = curs_invisible_bits; | ||
| 321 | mask_bits = curs_invisible_bits; | ||
| 322 | curs_w = curs_invisible_width; | ||
| 323 | curs_h = curs_invisible_height; | ||
| 324 | break; | ||
| 325 | case CURS_WIN: | ||
| 326 | curs_bits = curs_windows_bits; | ||
| 327 | mask_bits = mask_windows_bits; | ||
| 328 | curs_w = curs_windows_width; | ||
| 329 | curs_h = curs_windows_height; | ||
| 330 | break; | ||
| 331 | case CURS_DEFAULT: | ||
| 332 | default: | ||
| 333 | return XCB_NONE; /* XCB_NONE is xcb's way of saying "don't change the cursor" */ | ||
| 334 | } | ||
| 335 | |||
| 336 | bitmap = xcb_create_pixmap_from_bitmap_data(conn, | ||
| 337 | win, | ||
| 338 | curs_bits, | ||
| 339 | curs_w, | ||
| 340 | curs_h, | ||
| 341 | 1, | ||
| 342 | screen->white_pixel, | ||
| 343 | screen->black_pixel, | ||
| 344 | NULL); | ||
| 345 | |||
| 346 | mask = xcb_create_pixmap_from_bitmap_data(conn, | ||
| 347 | win, | ||
| 348 | mask_bits, | ||
| 349 | curs_w, | ||
| 350 | curs_h, | ||
| 351 | 1, | ||
| 352 | screen->white_pixel, | ||
| 353 | screen->black_pixel, | ||
| 354 | NULL); | ||
| 355 | |||
| 356 | cursor = xcb_generate_id(conn); | ||
| 357 | |||
| 358 | xcb_create_cursor(conn, | ||
| 359 | cursor, | ||
| 360 | bitmap, | ||
| 361 | mask, | ||
| 362 | 65535, 65535, 65535, | ||
| 363 | 0, 0, 0, | ||
| 364 | 0, 0); | ||
| 365 | |||
| 366 | xcb_free_pixmap(conn, bitmap); | ||
| 367 | xcb_free_pixmap(conn, mask); | ||
| 368 | |||
| 369 | return cursor; | ||
| 370 | } | ||
| 371 | |||
| 372 | static xcb_atom_t _NET_ACTIVE_WINDOW = XCB_NONE; | ||
| 373 | void _init_net_active_window(xcb_connection_t *conn) { | ||
| 374 | if (_NET_ACTIVE_WINDOW != XCB_NONE) { | ||
| 375 | /* already initialized */ | ||
| 376 | return; | ||
| 377 | } | ||
| 378 | xcb_generic_error_t *err; | ||
| 379 | xcb_intern_atom_reply_t *atom_reply = xcb_intern_atom_reply( | ||
| 380 | conn, | ||
| 381 | xcb_intern_atom(conn, 0, strlen("_NET_ACTIVE_WINDOW"), "_NET_ACTIVE_WINDOW"), | ||
| 382 | &err); | ||
| 383 | if (atom_reply == NULL) { | ||
| 384 | fprintf(stderr, "X11 Error %d\n", err->error_code); | ||
| 385 | free(err); | ||
| 386 | return; | ||
| 387 | } | ||
| 388 | _NET_ACTIVE_WINDOW = atom_reply->atom; | ||
| 389 | free(atom_reply); | ||
| 390 | } | ||
| 391 | |||
| 392 | xcb_window_t find_focused_window(xcb_connection_t *conn, const xcb_window_t root) { | ||
| 393 | xcb_window_t result = XCB_NONE; | ||
| 394 | |||
| 395 | _init_net_active_window(conn); | ||
| 396 | |||
| 397 | xcb_get_property_reply_t *prop_reply = xcb_get_property_reply( | ||
| 398 | conn, | ||
| 399 | xcb_get_property_unchecked( | ||
| 400 | conn, false, root, _NET_ACTIVE_WINDOW, XCB_GET_PROPERTY_TYPE_ANY, 0, 1 /* word */), | ||
| 401 | NULL); | ||
| 402 | if (prop_reply == NULL) { | ||
| 403 | goto out; | ||
| 404 | } | ||
| 405 | if (xcb_get_property_value_length(prop_reply) == 0) { | ||
| 406 | goto out_prop; | ||
| 407 | } | ||
| 408 | if (prop_reply->type != XCB_ATOM_WINDOW) { | ||
| 409 | goto out_prop; | ||
| 410 | } | ||
| 411 | |||
| 412 | result = *((xcb_window_t *)xcb_get_property_value(prop_reply)); | ||
| 413 | |||
| 414 | out_prop: | ||
| 415 | free(prop_reply); | ||
| 416 | out: | ||
| 417 | return result; | ||
| 418 | } | ||
| 419 | |||
| 420 | void set_focused_window(xcb_connection_t *conn, const xcb_window_t root, const xcb_window_t window) { | ||
| 421 | xcb_client_message_event_t ev; | ||
| 422 | memset(&ev, '\0', sizeof(xcb_client_message_event_t)); | ||
| 423 | |||
| 424 | _init_net_active_window(conn); | ||
| 425 | |||
| 426 | ev.response_type = XCB_CLIENT_MESSAGE; | ||
| 427 | ev.window = window; | ||
| 428 | ev.type = _NET_ACTIVE_WINDOW; | ||
| 429 | ev.format = 32; | ||
| 430 | ev.data.data32[0] = 2; /* 2 = pager */ | ||
| 431 | |||
| 432 | xcb_send_event(conn, false, root, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char *)&ev); | ||
| 433 | xcb_flush(conn); | ||
| 434 | } | ||
