diff options
Diffstat (limited to 'i3lock.c')
| -rw-r--r-- | i3lock.c | 1318 |
1 files changed, 1318 insertions, 0 deletions
diff --git a/i3lock.c b/i3lock.c new file mode 100644 index 0000000..134fdda --- /dev/null +++ b/i3lock.c | |||
| @@ -0,0 +1,1318 @@ | |||
| 1 | /* | ||
| 2 | * vim:ts=4:sw=4:expandtab | ||
| 3 | * | ||
| 4 | * © 2010 Michael Stapelberg | ||
| 5 | * | ||
| 6 | * See LICENSE for licensing information | ||
| 7 | * | ||
| 8 | */ | ||
| 9 | #include <config.h> | ||
| 10 | |||
| 11 | #include <stdio.h> | ||
| 12 | #include <stdlib.h> | ||
| 13 | #include <pwd.h> | ||
| 14 | #include <sys/types.h> | ||
| 15 | #include <string.h> | ||
| 16 | #include <unistd.h> | ||
| 17 | #include <stdbool.h> | ||
| 18 | #include <stdint.h> | ||
| 19 | #include <xcb/xcb.h> | ||
| 20 | #include <xcb/xkb.h> | ||
| 21 | #include <err.h> | ||
| 22 | #include <errno.h> | ||
| 23 | #include <assert.h> | ||
| 24 | #ifdef __OpenBSD__ | ||
| 25 | #include <bsd_auth.h> | ||
| 26 | #else | ||
| 27 | #include <security/pam_appl.h> | ||
| 28 | #endif | ||
| 29 | #include <getopt.h> | ||
| 30 | #include <string.h> | ||
| 31 | #include <ev.h> | ||
| 32 | #include <sys/mman.h> | ||
| 33 | #include <xkbcommon/xkbcommon.h> | ||
| 34 | #include <xkbcommon/xkbcommon-compose.h> | ||
| 35 | #include <xkbcommon/xkbcommon-x11.h> | ||
| 36 | #include <cairo.h> | ||
| 37 | #include <cairo/cairo-xcb.h> | ||
| 38 | #ifdef __OpenBSD__ | ||
| 39 | #include <strings.h> /* explicit_bzero(3) */ | ||
| 40 | #endif | ||
| 41 | #include <xcb/xcb_aux.h> | ||
| 42 | #include <xcb/randr.h> | ||
| 43 | |||
| 44 | #include "i3lock.h" | ||
| 45 | #include "xcb.h" | ||
| 46 | #include "cursors.h" | ||
| 47 | #include "unlock_indicator.h" | ||
| 48 | #include "randr.h" | ||
| 49 | #include "dpi.h" | ||
| 50 | |||
| 51 | #define TSTAMP_N_SECS(n) (n * 1.0) | ||
| 52 | #define TSTAMP_N_MINS(n) (60 * TSTAMP_N_SECS(n)) | ||
| 53 | #define START_TIMER(timer_obj, timeout, callback) \ | ||
| 54 | timer_obj = start_timer(timer_obj, timeout, callback) | ||
| 55 | #define STOP_TIMER(timer_obj) \ | ||
| 56 | timer_obj = stop_timer(timer_obj) | ||
| 57 | |||
| 58 | typedef void (*ev_callback_t)(EV_P_ ev_timer *w, int revents); | ||
| 59 | static void input_done(void); | ||
| 60 | |||
| 61 | char color[7] = "ffffff"; | ||
| 62 | uint32_t last_resolution[2]; | ||
| 63 | xcb_window_t win; | ||
| 64 | static xcb_cursor_t cursor; | ||
| 65 | #ifndef __OpenBSD__ | ||
| 66 | static pam_handle_t *pam_handle; | ||
| 67 | static bool pam_cleanup; | ||
| 68 | #endif | ||
| 69 | int input_position = 0; | ||
| 70 | /* Holds the password you enter (in UTF-8). */ | ||
| 71 | static char password[512]; | ||
| 72 | static bool beep = false; | ||
| 73 | bool debug_mode = false; | ||
| 74 | bool unlock_indicator = true; | ||
| 75 | char *modifier_string = NULL; | ||
| 76 | static bool dont_fork = false; | ||
| 77 | struct ev_loop *main_loop; | ||
| 78 | static struct ev_timer *clear_auth_wrong_timeout; | ||
| 79 | static struct ev_timer *clear_indicator_timeout; | ||
| 80 | static struct ev_timer *discard_passwd_timeout; | ||
| 81 | extern unlock_state_t unlock_state; | ||
| 82 | extern auth_state_t auth_state; | ||
| 83 | int failed_attempts = 0; | ||
| 84 | bool show_failed_attempts = false; | ||
| 85 | bool retry_verification = false; | ||
| 86 | |||
| 87 | static struct xkb_state *xkb_state; | ||
| 88 | static struct xkb_context *xkb_context; | ||
| 89 | static struct xkb_keymap *xkb_keymap; | ||
| 90 | static struct xkb_compose_table *xkb_compose_table; | ||
| 91 | static struct xkb_compose_state *xkb_compose_state; | ||
| 92 | static uint8_t xkb_base_event; | ||
| 93 | static uint8_t xkb_base_error; | ||
| 94 | static int randr_base = -1; | ||
| 95 | |||
| 96 | cairo_surface_t *img = NULL; | ||
| 97 | bool tile = false; | ||
| 98 | bool ignore_empty_password = false; | ||
| 99 | bool skip_repeated_empty_password = false; | ||
| 100 | |||
| 101 | /* isutf, u8_dec © 2005 Jeff Bezanson, public domain */ | ||
| 102 | #define isutf(c) (((c)&0xC0) != 0x80) | ||
| 103 | |||
| 104 | /* | ||
| 105 | * Decrements i to point to the previous unicode glyph | ||
| 106 | * | ||
| 107 | */ | ||
| 108 | void u8_dec(char *s, int *i) { | ||
| 109 | (void)(isutf(s[--(*i)]) || isutf(s[--(*i)]) || isutf(s[--(*i)]) || --(*i)); | ||
| 110 | } | ||
| 111 | |||
| 112 | /* | ||
| 113 | * Loads the XKB keymap from the X11 server and feeds it to xkbcommon. | ||
| 114 | * Necessary so that we can properly let xkbcommon track the keyboard state and | ||
| 115 | * translate keypresses to utf-8. | ||
| 116 | * | ||
| 117 | */ | ||
| 118 | static bool load_keymap(void) { | ||
| 119 | if (xkb_context == NULL) { | ||
| 120 | if ((xkb_context = xkb_context_new(0)) == NULL) { | ||
| 121 | fprintf(stderr, "[i3lock] could not create xkbcommon context\n"); | ||
| 122 | return false; | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | xkb_keymap_unref(xkb_keymap); | ||
| 127 | |||
| 128 | int32_t device_id = xkb_x11_get_core_keyboard_device_id(conn); | ||
| 129 | DEBUG("device = %d\n", device_id); | ||
| 130 | if ((xkb_keymap = xkb_x11_keymap_new_from_device(xkb_context, conn, device_id, 0)) == NULL) { | ||
| 131 | fprintf(stderr, "[i3lock] xkb_x11_keymap_new_from_device failed\n"); | ||
| 132 | return false; | ||
| 133 | } | ||
| 134 | |||
| 135 | struct xkb_state *new_state = | ||
| 136 | xkb_x11_state_new_from_device(xkb_keymap, conn, device_id); | ||
| 137 | if (new_state == NULL) { | ||
| 138 | fprintf(stderr, "[i3lock] xkb_x11_state_new_from_device failed\n"); | ||
| 139 | return false; | ||
| 140 | } | ||
| 141 | |||
| 142 | xkb_state_unref(xkb_state); | ||
| 143 | xkb_state = new_state; | ||
| 144 | |||
| 145 | return true; | ||
| 146 | } | ||
| 147 | |||
| 148 | /* | ||
| 149 | * Loads the XKB compose table from the given locale. | ||
| 150 | * | ||
| 151 | */ | ||
| 152 | static bool load_compose_table(const char *locale) { | ||
| 153 | xkb_compose_table_unref(xkb_compose_table); | ||
| 154 | |||
| 155 | if ((xkb_compose_table = xkb_compose_table_new_from_locale(xkb_context, locale, 0)) == NULL) { | ||
| 156 | fprintf(stderr, "[i3lock] xkb_compose_table_new_from_locale failed\n"); | ||
| 157 | return false; | ||
| 158 | } | ||
| 159 | |||
| 160 | struct xkb_compose_state *new_compose_state = xkb_compose_state_new(xkb_compose_table, 0); | ||
| 161 | if (new_compose_state == NULL) { | ||
| 162 | fprintf(stderr, "[i3lock] xkb_compose_state_new failed\n"); | ||
| 163 | return false; | ||
| 164 | } | ||
| 165 | |||
| 166 | xkb_compose_state_unref(xkb_compose_state); | ||
| 167 | xkb_compose_state = new_compose_state; | ||
| 168 | |||
| 169 | return true; | ||
| 170 | } | ||
| 171 | |||
| 172 | /* | ||
| 173 | * Clears the memory which stored the password to be a bit safer against | ||
| 174 | * cold-boot attacks. | ||
| 175 | * | ||
| 176 | */ | ||
| 177 | static void clear_password_memory(void) { | ||
| 178 | #ifdef __OpenBSD__ | ||
| 179 | /* Use explicit_bzero(3) which was explicitly designed not to be | ||
| 180 | * optimized out by the compiler. */ | ||
| 181 | explicit_bzero(password, strlen(password)); | ||
| 182 | #else | ||
| 183 | /* A volatile pointer to the password buffer to prevent the compiler from | ||
| 184 | * optimizing this out. */ | ||
| 185 | volatile char *vpassword = password; | ||
| 186 | for (size_t c = 0; c < sizeof(password); c++) | ||
| 187 | /* We store a non-random pattern which consists of the (irrelevant) | ||
| 188 | * index plus (!) the value of the beep variable. This prevents the | ||
| 189 | * compiler from optimizing the calls away, since the value of 'beep' | ||
| 190 | * is not known at compile-time. */ | ||
| 191 | vpassword[c] = c + (int)beep; | ||
| 192 | #endif | ||
| 193 | } | ||
| 194 | |||
| 195 | ev_timer *start_timer(ev_timer *timer_obj, ev_tstamp timeout, ev_callback_t callback) { | ||
| 196 | if (timer_obj) { | ||
| 197 | ev_timer_stop(main_loop, timer_obj); | ||
| 198 | ev_timer_set(timer_obj, timeout, 0.); | ||
| 199 | ev_timer_start(main_loop, timer_obj); | ||
| 200 | } else { | ||
| 201 | /* When there is no memory, we just don’t have a timeout. We cannot | ||
| 202 | * exit() here, since that would effectively unlock the screen. */ | ||
| 203 | timer_obj = calloc(sizeof(struct ev_timer), 1); | ||
| 204 | if (timer_obj) { | ||
| 205 | ev_timer_init(timer_obj, callback, timeout, 0.); | ||
| 206 | ev_timer_start(main_loop, timer_obj); | ||
| 207 | } | ||
| 208 | } | ||
| 209 | return timer_obj; | ||
| 210 | } | ||
| 211 | |||
| 212 | ev_timer *stop_timer(ev_timer *timer_obj) { | ||
| 213 | if (timer_obj) { | ||
| 214 | ev_timer_stop(main_loop, timer_obj); | ||
| 215 | free(timer_obj); | ||
| 216 | } | ||
| 217 | return NULL; | ||
| 218 | } | ||
| 219 | |||
| 220 | /* | ||
| 221 | * Neccessary calls after ending input via enter or others | ||
| 222 | * | ||
| 223 | */ | ||
| 224 | static void finish_input(void) { | ||
| 225 | password[input_position] = '\0'; | ||
| 226 | unlock_state = STATE_KEY_PRESSED; | ||
| 227 | redraw_screen(); | ||
| 228 | input_done(); | ||
| 229 | } | ||
| 230 | |||
| 231 | /* | ||
| 232 | * Resets auth_state to STATE_AUTH_IDLE 2 seconds after an unsuccessful | ||
| 233 | * authentication event. | ||
| 234 | * | ||
| 235 | */ | ||
| 236 | static void clear_auth_wrong(EV_P_ ev_timer *w, int revents) { | ||
| 237 | DEBUG("clearing auth wrong\n"); | ||
| 238 | auth_state = STATE_AUTH_IDLE; | ||
| 239 | redraw_screen(); | ||
| 240 | |||
| 241 | /* Clear modifier string. */ | ||
| 242 | if (modifier_string != NULL) { | ||
| 243 | free(modifier_string); | ||
| 244 | modifier_string = NULL; | ||
| 245 | } | ||
| 246 | |||
| 247 | /* Now free this timeout. */ | ||
| 248 | STOP_TIMER(clear_auth_wrong_timeout); | ||
| 249 | |||
| 250 | /* retry with input done during auth verification */ | ||
| 251 | if (retry_verification) { | ||
| 252 | retry_verification = false; | ||
| 253 | finish_input(); | ||
| 254 | } | ||
| 255 | } | ||
| 256 | |||
| 257 | static void clear_indicator_cb(EV_P_ ev_timer *w, int revents) { | ||
| 258 | clear_indicator(); | ||
| 259 | STOP_TIMER(clear_indicator_timeout); | ||
| 260 | } | ||
| 261 | |||
| 262 | static void clear_input(void) { | ||
| 263 | input_position = 0; | ||
| 264 | clear_password_memory(); | ||
| 265 | password[input_position] = '\0'; | ||
| 266 | } | ||
| 267 | |||
| 268 | static void discard_passwd_cb(EV_P_ ev_timer *w, int revents) { | ||
| 269 | clear_input(); | ||
| 270 | STOP_TIMER(discard_passwd_timeout); | ||
| 271 | } | ||
| 272 | |||
| 273 | static void input_done(void) { | ||
| 274 | STOP_TIMER(clear_auth_wrong_timeout); | ||
| 275 | auth_state = STATE_AUTH_VERIFY; | ||
| 276 | unlock_state = STATE_STARTED; | ||
| 277 | redraw_screen(); | ||
| 278 | |||
| 279 | #ifdef __OpenBSD__ | ||
| 280 | struct passwd *pw; | ||
| 281 | |||
| 282 | if (!(pw = getpwuid(getuid()))) | ||
| 283 | errx(1, "unknown uid %u.", getuid()); | ||
| 284 | |||
| 285 | if (auth_userokay(pw->pw_name, NULL, NULL, password) != 0) { | ||
| 286 | DEBUG("successfully authenticated\n"); | ||
| 287 | clear_password_memory(); | ||
| 288 | |||
| 289 | ev_break(EV_DEFAULT, EVBREAK_ALL); | ||
| 290 | return; | ||
| 291 | } | ||
| 292 | #else | ||
| 293 | if (pam_authenticate(pam_handle, 0) == PAM_SUCCESS) { | ||
| 294 | DEBUG("successfully authenticated\n"); | ||
| 295 | clear_password_memory(); | ||
| 296 | |||
| 297 | /* PAM credentials should be refreshed, this will for example update any kerberos tickets. | ||
| 298 | * Related to credentials pam_end() needs to be called to cleanup any temporary | ||
| 299 | * credentials like kerberos /tmp/krb5cc_pam_* files which may of been left behind if the | ||
| 300 | * refresh of the credentials failed. */ | ||
| 301 | pam_setcred(pam_handle, PAM_REFRESH_CRED); | ||
| 302 | pam_cleanup = true; | ||
| 303 | |||
| 304 | ev_break(EV_DEFAULT, EVBREAK_ALL); | ||
| 305 | return; | ||
| 306 | } | ||
| 307 | #endif | ||
| 308 | |||
| 309 | if (debug_mode) | ||
| 310 | fprintf(stderr, "Authentication failure\n"); | ||
| 311 | |||
| 312 | /* Get state of Caps and Num lock modifiers, to be displayed in | ||
| 313 | * STATE_AUTH_WRONG state */ | ||
| 314 | xkb_mod_index_t idx, num_mods; | ||
| 315 | const char *mod_name; | ||
| 316 | |||
| 317 | num_mods = xkb_keymap_num_mods(xkb_keymap); | ||
| 318 | |||
| 319 | for (idx = 0; idx < num_mods; idx++) { | ||
| 320 | if (!xkb_state_mod_index_is_active(xkb_state, idx, XKB_STATE_MODS_EFFECTIVE)) | ||
| 321 | continue; | ||
| 322 | |||
| 323 | mod_name = xkb_keymap_mod_get_name(xkb_keymap, idx); | ||
| 324 | if (mod_name == NULL) | ||
| 325 | continue; | ||
| 326 | |||
| 327 | /* Replace certain xkb names with nicer, human-readable ones. */ | ||
| 328 | if (strcmp(mod_name, XKB_MOD_NAME_CAPS) == 0) | ||
| 329 | mod_name = "Caps Lock"; | ||
| 330 | else if (strcmp(mod_name, XKB_MOD_NAME_ALT) == 0) | ||
| 331 | mod_name = "Alt"; | ||
| 332 | else if (strcmp(mod_name, XKB_MOD_NAME_NUM) == 0) | ||
| 333 | mod_name = "Num Lock"; | ||
| 334 | else if (strcmp(mod_name, XKB_MOD_NAME_LOGO) == 0) | ||
| 335 | mod_name = "Super"; | ||
| 336 | |||
| 337 | char *tmp; | ||
| 338 | if (modifier_string == NULL) { | ||
| 339 | if (asprintf(&tmp, "%s", mod_name) != -1) | ||
| 340 | modifier_string = tmp; | ||
| 341 | } else if (asprintf(&tmp, "%s, %s", modifier_string, mod_name) != -1) { | ||
| 342 | free(modifier_string); | ||
| 343 | modifier_string = tmp; | ||
| 344 | } | ||
| 345 | } | ||
| 346 | |||
| 347 | auth_state = STATE_AUTH_WRONG; | ||
| 348 | failed_attempts += 1; | ||
| 349 | clear_input(); | ||
| 350 | if (unlock_indicator) | ||
| 351 | redraw_screen(); | ||
| 352 | |||
| 353 | /* Clear this state after 2 seconds (unless the user enters another | ||
| 354 | * password during that time). */ | ||
| 355 | ev_now_update(main_loop); | ||
| 356 | START_TIMER(clear_auth_wrong_timeout, TSTAMP_N_SECS(2), clear_auth_wrong); | ||
| 357 | |||
| 358 | /* Cancel the clear_indicator_timeout, it would hide the unlock indicator | ||
| 359 | * too early. */ | ||
| 360 | STOP_TIMER(clear_indicator_timeout); | ||
| 361 | |||
| 362 | /* beep on authentication failure, if enabled */ | ||
| 363 | if (beep) { | ||
| 364 | xcb_bell(conn, 100); | ||
| 365 | xcb_flush(conn); | ||
| 366 | } | ||
| 367 | } | ||
| 368 | |||
| 369 | static void redraw_timeout(EV_P_ ev_timer *w, int revents) { | ||
| 370 | redraw_screen(); | ||
| 371 | STOP_TIMER(w); | ||
| 372 | } | ||
| 373 | |||
| 374 | static bool skip_without_validation(void) { | ||
| 375 | if (input_position != 0) | ||
| 376 | return false; | ||
| 377 | |||
| 378 | if (skip_repeated_empty_password || ignore_empty_password) | ||
| 379 | return true; | ||
| 380 | |||
| 381 | return false; | ||
| 382 | } | ||
| 383 | |||
| 384 | /* | ||
| 385 | * Handle key presses. Fixes state, then looks up the key symbol for the | ||
| 386 | * given keycode, then looks up the key symbol (as UCS-2), converts it to | ||
| 387 | * UTF-8 and stores it in the password array. | ||
| 388 | * | ||
| 389 | */ | ||
| 390 | static void handle_key_press(xcb_key_press_event_t *event) { | ||
| 391 | xkb_keysym_t ksym; | ||
| 392 | char buffer[128]; | ||
| 393 | int n; | ||
| 394 | bool ctrl; | ||
| 395 | bool composed = false; | ||
| 396 | |||
| 397 | ksym = xkb_state_key_get_one_sym(xkb_state, event->detail); | ||
| 398 | ctrl = xkb_state_mod_name_is_active(xkb_state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_DEPRESSED); | ||
| 399 | |||
| 400 | /* The buffer will be null-terminated, so n >= 2 for 1 actual character. */ | ||
| 401 | memset(buffer, '\0', sizeof(buffer)); | ||
| 402 | |||
| 403 | if (xkb_compose_state && xkb_compose_state_feed(xkb_compose_state, ksym) == XKB_COMPOSE_FEED_ACCEPTED) { | ||
| 404 | switch (xkb_compose_state_get_status(xkb_compose_state)) { | ||
| 405 | case XKB_COMPOSE_NOTHING: | ||
| 406 | break; | ||
| 407 | case XKB_COMPOSE_COMPOSING: | ||
| 408 | return; | ||
| 409 | case XKB_COMPOSE_COMPOSED: | ||
| 410 | /* xkb_compose_state_get_utf8 doesn't include the terminating byte in the return value | ||
| 411 | * as xkb_keysym_to_utf8 does. Adding one makes the variable n consistent. */ | ||
| 412 | n = xkb_compose_state_get_utf8(xkb_compose_state, buffer, sizeof(buffer)) + 1; | ||
| 413 | ksym = xkb_compose_state_get_one_sym(xkb_compose_state); | ||
| 414 | composed = true; | ||
| 415 | break; | ||
| 416 | case XKB_COMPOSE_CANCELLED: | ||
| 417 | xkb_compose_state_reset(xkb_compose_state); | ||
| 418 | return; | ||
| 419 | } | ||
| 420 | } | ||
| 421 | |||
| 422 | if (!composed) { | ||
| 423 | n = xkb_keysym_to_utf8(ksym, buffer, sizeof(buffer)); | ||
| 424 | } | ||
| 425 | |||
| 426 | switch (ksym) { | ||
| 427 | case XKB_KEY_j: | ||
| 428 | case XKB_KEY_m: | ||
| 429 | case XKB_KEY_Return: | ||
| 430 | case XKB_KEY_KP_Enter: | ||
| 431 | case XKB_KEY_XF86ScreenSaver: | ||
| 432 | if ((ksym == XKB_KEY_j || ksym == XKB_KEY_m) && !ctrl) | ||
| 433 | break; | ||
| 434 | |||
| 435 | if (auth_state == STATE_AUTH_WRONG) { | ||
| 436 | retry_verification = true; | ||
| 437 | return; | ||
| 438 | } | ||
| 439 | |||
| 440 | if (skip_without_validation()) { | ||
| 441 | clear_input(); | ||
| 442 | return; | ||
| 443 | } | ||
| 444 | finish_input(); | ||
| 445 | skip_repeated_empty_password = true; | ||
| 446 | return; | ||
| 447 | default: | ||
| 448 | skip_repeated_empty_password = false; | ||
| 449 | // A new password is being entered, but a previous one is pending. | ||
| 450 | // Discard the old one and clear the retry_verification flag. | ||
| 451 | if (retry_verification) { | ||
| 452 | retry_verification = false; | ||
| 453 | clear_input(); | ||
| 454 | } | ||
| 455 | } | ||
| 456 | |||
| 457 | switch (ksym) { | ||
| 458 | case XKB_KEY_u: | ||
| 459 | case XKB_KEY_Escape: | ||
| 460 | if ((ksym == XKB_KEY_u && ctrl) || | ||
| 461 | ksym == XKB_KEY_Escape) { | ||
| 462 | DEBUG("C-u pressed\n"); | ||
| 463 | clear_input(); | ||
| 464 | /* Also hide the unlock indicator */ | ||
| 465 | if (unlock_indicator) | ||
| 466 | clear_indicator(); | ||
| 467 | return; | ||
| 468 | } | ||
| 469 | break; | ||
| 470 | |||
| 471 | case XKB_KEY_Delete: | ||
| 472 | case XKB_KEY_KP_Delete: | ||
| 473 | /* Deleting forward doesn’t make sense, as i3lock doesn’t allow you | ||
| 474 | * to move the cursor when entering a password. We need to eat this | ||
| 475 | * key press so that it won’t be treated as part of the password, | ||
| 476 | * see issue #50. */ | ||
| 477 | return; | ||
| 478 | |||
| 479 | case XKB_KEY_h: | ||
| 480 | case XKB_KEY_BackSpace: | ||
| 481 | if (ksym == XKB_KEY_h && !ctrl) | ||
| 482 | break; | ||
| 483 | |||
| 484 | if (input_position == 0) { | ||
| 485 | START_TIMER(clear_indicator_timeout, 1.0, clear_indicator_cb); | ||
| 486 | unlock_state = STATE_NOTHING_TO_DELETE; | ||
| 487 | redraw_screen(); | ||
| 488 | return; | ||
| 489 | } | ||
| 490 | |||
| 491 | /* decrement input_position to point to the previous glyph */ | ||
| 492 | u8_dec(password, &input_position); | ||
| 493 | password[input_position] = '\0'; | ||
| 494 | |||
| 495 | /* Hide the unlock indicator after a bit if the password buffer is | ||
| 496 | * empty. */ | ||
| 497 | START_TIMER(clear_indicator_timeout, 1.0, clear_indicator_cb); | ||
| 498 | unlock_state = STATE_BACKSPACE_ACTIVE; | ||
| 499 | redraw_screen(); | ||
| 500 | unlock_state = STATE_KEY_PRESSED; | ||
| 501 | return; | ||
| 502 | } | ||
| 503 | |||
| 504 | if ((input_position + 8) >= (int)sizeof(password)) | ||
| 505 | return; | ||
| 506 | |||
| 507 | #if 0 | ||
| 508 | /* FIXME: handle all of these? */ | ||
| 509 | printf("is_keypad_key = %d\n", xcb_is_keypad_key(sym)); | ||
| 510 | printf("is_private_keypad_key = %d\n", xcb_is_private_keypad_key(sym)); | ||
| 511 | printf("xcb_is_cursor_key = %d\n", xcb_is_cursor_key(sym)); | ||
| 512 | printf("xcb_is_pf_key = %d\n", xcb_is_pf_key(sym)); | ||
| 513 | printf("xcb_is_function_key = %d\n", xcb_is_function_key(sym)); | ||
| 514 | printf("xcb_is_misc_function_key = %d\n", xcb_is_misc_function_key(sym)); | ||
| 515 | printf("xcb_is_modifier_key = %d\n", xcb_is_modifier_key(sym)); | ||
| 516 | #endif | ||
| 517 | |||
| 518 | if (n < 2) | ||
| 519 | return; | ||
| 520 | |||
| 521 | /* store it in the password array as UTF-8 */ | ||
| 522 | memcpy(password + input_position, buffer, n - 1); | ||
| 523 | input_position += n - 1; | ||
| 524 | DEBUG("current password = %.*s\n", input_position, password); | ||
| 525 | |||
| 526 | if (unlock_indicator) { | ||
| 527 | unlock_state = STATE_KEY_ACTIVE; | ||
| 528 | redraw_screen(); | ||
| 529 | unlock_state = STATE_KEY_PRESSED; | ||
| 530 | |||
| 531 | struct ev_timer *timeout = NULL; | ||
| 532 | START_TIMER(timeout, TSTAMP_N_SECS(0.25), redraw_timeout); | ||
| 533 | STOP_TIMER(clear_indicator_timeout); | ||
| 534 | } | ||
| 535 | |||
| 536 | START_TIMER(discard_passwd_timeout, TSTAMP_N_MINS(3), discard_passwd_cb); | ||
| 537 | } | ||
| 538 | |||
| 539 | /* | ||
| 540 | * A visibility notify event will be received when the visibility (= can the | ||
| 541 | * user view the complete window) changes, so for example when a popup overlays | ||
| 542 | * some area of the i3lock window. | ||
| 543 | * | ||
| 544 | * In this case, we raise our window on top so that the popup (or whatever is | ||
| 545 | * hiding us) gets hidden. | ||
| 546 | * | ||
| 547 | */ | ||
| 548 | static void handle_visibility_notify(xcb_connection_t *conn, | ||
| 549 | xcb_visibility_notify_event_t *event) { | ||
| 550 | if (event->state != XCB_VISIBILITY_UNOBSCURED) { | ||
| 551 | uint32_t values[] = {XCB_STACK_MODE_ABOVE}; | ||
| 552 | xcb_configure_window(conn, event->window, XCB_CONFIG_WINDOW_STACK_MODE, values); | ||
| 553 | xcb_flush(conn); | ||
| 554 | } | ||
| 555 | } | ||
| 556 | |||
| 557 | /* | ||
| 558 | * Called when the keyboard mapping changes. We update our symbols. | ||
| 559 | * | ||
| 560 | * We ignore errors — if the new keymap cannot be loaded it’s better if the | ||
| 561 | * screen stays locked and the user intervenes by using killall i3lock. | ||
| 562 | * | ||
| 563 | */ | ||
| 564 | static void process_xkb_event(xcb_generic_event_t *gevent) { | ||
| 565 | union xkb_event { | ||
| 566 | struct { | ||
| 567 | uint8_t response_type; | ||
| 568 | uint8_t xkbType; | ||
| 569 | uint16_t sequence; | ||
| 570 | xcb_timestamp_t time; | ||
| 571 | uint8_t deviceID; | ||
| 572 | } any; | ||
| 573 | xcb_xkb_new_keyboard_notify_event_t new_keyboard_notify; | ||
| 574 | xcb_xkb_map_notify_event_t map_notify; | ||
| 575 | xcb_xkb_state_notify_event_t state_notify; | ||
| 576 | } *event = (union xkb_event *)gevent; | ||
| 577 | |||
| 578 | DEBUG("process_xkb_event for device %d\n", event->any.deviceID); | ||
| 579 | |||
| 580 | if (event->any.deviceID != xkb_x11_get_core_keyboard_device_id(conn)) | ||
| 581 | return; | ||
| 582 | |||
| 583 | /* | ||
| 584 | * XkbNewKkdNotify and XkbMapNotify together capture all sorts of keymap | ||
| 585 | * updates (e.g. xmodmap, xkbcomp, setxkbmap), with minimal redundent | ||
| 586 | * recompilations. | ||
| 587 | */ | ||
| 588 | switch (event->any.xkbType) { | ||
| 589 | case XCB_XKB_NEW_KEYBOARD_NOTIFY: | ||
| 590 | if (event->new_keyboard_notify.changed & XCB_XKB_NKN_DETAIL_KEYCODES) | ||
| 591 | (void)load_keymap(); | ||
| 592 | break; | ||
| 593 | |||
| 594 | case XCB_XKB_MAP_NOTIFY: | ||
| 595 | (void)load_keymap(); | ||
| 596 | break; | ||
| 597 | |||
| 598 | case XCB_XKB_STATE_NOTIFY: | ||
| 599 | xkb_state_update_mask(xkb_state, | ||
| 600 | event->state_notify.baseMods, | ||
| 601 | event->state_notify.latchedMods, | ||
| 602 | event->state_notify.lockedMods, | ||
| 603 | event->state_notify.baseGroup, | ||
| 604 | event->state_notify.latchedGroup, | ||
| 605 | event->state_notify.lockedGroup); | ||
| 606 | break; | ||
| 607 | } | ||
| 608 | } | ||
| 609 | |||
| 610 | /* | ||
| 611 | * Called when the properties on the root window change, e.g. when the screen | ||
| 612 | * resolution changes. If so we update the window to cover the whole screen | ||
| 613 | * and also redraw the image, if any. | ||
| 614 | * | ||
| 615 | */ | ||
| 616 | void handle_screen_resize(void) { | ||
| 617 | xcb_get_geometry_cookie_t geomc; | ||
| 618 | xcb_get_geometry_reply_t *geom; | ||
| 619 | geomc = xcb_get_geometry(conn, screen->root); | ||
| 620 | if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL) | ||
| 621 | return; | ||
| 622 | |||
| 623 | if (last_resolution[0] == geom->width && | ||
| 624 | last_resolution[1] == geom->height) { | ||
| 625 | free(geom); | ||
| 626 | return; | ||
| 627 | } | ||
| 628 | |||
| 629 | last_resolution[0] = geom->width; | ||
| 630 | last_resolution[1] = geom->height; | ||
| 631 | |||
| 632 | free(geom); | ||
| 633 | |||
| 634 | redraw_screen(); | ||
| 635 | |||
| 636 | uint32_t mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; | ||
| 637 | xcb_configure_window(conn, win, mask, last_resolution); | ||
| 638 | xcb_flush(conn); | ||
| 639 | |||
| 640 | randr_query(screen->root); | ||
| 641 | redraw_screen(); | ||
| 642 | } | ||
| 643 | |||
| 644 | static ssize_t read_raw_image_native(uint32_t *dest, FILE *src, size_t width, size_t height, int pixstride) { | ||
| 645 | ssize_t count = 0; | ||
| 646 | for (size_t y = 0; y < height; y++) { | ||
| 647 | size_t n = fread(&dest[y * pixstride], 1, width * 4, src); | ||
| 648 | count += n; | ||
| 649 | if (n < (size_t)(width * 4)) | ||
| 650 | break; | ||
| 651 | } | ||
| 652 | |||
| 653 | return count; | ||
| 654 | } | ||
| 655 | |||
| 656 | struct raw_pixel_format { | ||
| 657 | int bpp; | ||
| 658 | int red; | ||
| 659 | int green; | ||
| 660 | int blue; | ||
| 661 | }; | ||
| 662 | |||
| 663 | static ssize_t read_raw_image_fmt(uint32_t *dest, FILE *src, size_t width, size_t height, int pixstride, | ||
| 664 | struct raw_pixel_format fmt) { | ||
| 665 | unsigned char *buf = malloc(width * fmt.bpp); | ||
| 666 | if (buf == NULL) | ||
| 667 | return -1; | ||
| 668 | |||
| 669 | ssize_t count = 0; | ||
| 670 | for (size_t y = 0; y < height; y++) { | ||
| 671 | size_t n = fread(buf, 1, width * fmt.bpp, src); | ||
| 672 | count += n; | ||
| 673 | if (n < (size_t)(width * fmt.bpp)) | ||
| 674 | break; | ||
| 675 | |||
| 676 | for (size_t x = 0; x < width; ++x) { | ||
| 677 | int idx = x * fmt.bpp; | ||
| 678 | dest[y * pixstride + x] = 0 | | ||
| 679 | (buf[idx + fmt.red]) << 16 | | ||
| 680 | (buf[idx + fmt.green]) << 8 | | ||
| 681 | (buf[idx + fmt.blue]); | ||
| 682 | } | ||
| 683 | } | ||
| 684 | |||
| 685 | free(buf); | ||
| 686 | return count; | ||
| 687 | } | ||
| 688 | |||
| 689 | // Pre-defind pixel formats (<bytes per pixel>, <red pixel>, <green pixel>, <blue pixel>) | ||
| 690 | static const struct raw_pixel_format raw_fmt_rgb = {3, 0, 1, 2}; | ||
| 691 | static const struct raw_pixel_format raw_fmt_rgbx = {4, 0, 1, 2}; | ||
| 692 | static const struct raw_pixel_format raw_fmt_xrgb = {4, 1, 2, 3}; | ||
| 693 | static const struct raw_pixel_format raw_fmt_bgr = {3, 2, 1, 0}; | ||
| 694 | static const struct raw_pixel_format raw_fmt_bgrx = {4, 2, 1, 0}; | ||
| 695 | static const struct raw_pixel_format raw_fmt_xbgr = {4, 3, 2, 1}; | ||
| 696 | |||
| 697 | static cairo_surface_t *read_raw_image(const char *image_path, const char *image_raw_format) { | ||
| 698 | cairo_surface_t *img; | ||
| 699 | |||
| 700 | #define RAW_PIXFMT_MAXLEN 6 | ||
| 701 | #define STRINGIFY1(x) #x | ||
| 702 | #define STRINGIFY(x) STRINGIFY1(x) | ||
| 703 | /* Parse format as <width>x<height>:<pixfmt> */ | ||
| 704 | char pixfmt[RAW_PIXFMT_MAXLEN + 1]; | ||
| 705 | size_t w, h; | ||
| 706 | const char *fmt = "%zux%zu:%" STRINGIFY(RAW_PIXFMT_MAXLEN) "s"; | ||
| 707 | if (sscanf(image_raw_format, fmt, &w, &h, pixfmt) != 3) { | ||
| 708 | fprintf(stderr, "Invalid image format: \"%s\"\n", image_raw_format); | ||
| 709 | return NULL; | ||
| 710 | } | ||
| 711 | #undef RAW_PIXFMT_MAXLEN | ||
| 712 | #undef STRINGIFY1 | ||
| 713 | #undef STRINGIFY | ||
| 714 | |||
| 715 | /* Create image surface */ | ||
| 716 | img = cairo_image_surface_create(CAIRO_FORMAT_RGB24, w, h); | ||
| 717 | if (cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { | ||
| 718 | fprintf(stderr, "Could not create surface: %s\n", | ||
| 719 | cairo_status_to_string(cairo_surface_status(img))); | ||
| 720 | return NULL; | ||
| 721 | } | ||
| 722 | cairo_surface_flush(img); | ||
| 723 | |||
| 724 | /* Use uint32_t* because cairo uses native endianness */ | ||
| 725 | uint32_t *data = (uint32_t *)cairo_image_surface_get_data(img); | ||
| 726 | const int pixstride = cairo_image_surface_get_stride(img) / 4; | ||
| 727 | |||
| 728 | FILE *f = fopen(image_path, "r"); | ||
| 729 | if (f == NULL) { | ||
| 730 | fprintf(stderr, "Could not open image \"%s\": %s\n", | ||
| 731 | image_path, strerror(errno)); | ||
| 732 | cairo_surface_destroy(img); | ||
| 733 | return NULL; | ||
| 734 | } | ||
| 735 | |||
| 736 | /* Read the image, respecting cairo's stride, according to the pixfmt */ | ||
| 737 | ssize_t size, count; | ||
| 738 | if (strcmp(pixfmt, "native") == 0) { | ||
| 739 | /* If the pixfmt is 'native', just read each line directly into the buffer */ | ||
| 740 | size = w * h * 4; | ||
| 741 | count = read_raw_image_native(data, f, w, h, pixstride); | ||
| 742 | } else { | ||
| 743 | const struct raw_pixel_format *fmt = NULL; | ||
| 744 | |||
| 745 | if (strcmp(pixfmt, "rgb") == 0) | ||
| 746 | fmt = &raw_fmt_rgb; | ||
| 747 | else if (strcmp(pixfmt, "rgbx") == 0) | ||
| 748 | fmt = &raw_fmt_rgbx; | ||
| 749 | else if (strcmp(pixfmt, "xrgb") == 0) | ||
| 750 | fmt = &raw_fmt_xrgb; | ||
| 751 | else if (strcmp(pixfmt, "bgr") == 0) | ||
| 752 | fmt = &raw_fmt_bgr; | ||
| 753 | else if (strcmp(pixfmt, "bgrx") == 0) | ||
| 754 | fmt = &raw_fmt_bgrx; | ||
| 755 | else if (strcmp(pixfmt, "xbgr") == 0) | ||
| 756 | fmt = &raw_fmt_xbgr; | ||
| 757 | |||
| 758 | if (fmt == NULL) { | ||
| 759 | fprintf(stderr, "Unknown raw pixel format: %s\n", pixfmt); | ||
| 760 | fclose(f); | ||
| 761 | cairo_surface_destroy(img); | ||
| 762 | return NULL; | ||
| 763 | } | ||
| 764 | |||
| 765 | size = w * h * fmt->bpp; | ||
| 766 | count = read_raw_image_fmt(data, f, w, h, pixstride, *fmt); | ||
| 767 | } | ||
| 768 | |||
| 769 | cairo_surface_mark_dirty(img); | ||
| 770 | |||
| 771 | if (count < size) { | ||
| 772 | if (count < 0 || ferror(f)) { | ||
| 773 | fprintf(stderr, "Failed to read image \"%s\": %s\n", | ||
| 774 | image_path, strerror(errno)); | ||
| 775 | fclose(f); | ||
| 776 | cairo_surface_destroy(img); | ||
| 777 | return NULL; | ||
| 778 | } else { | ||
| 779 | /* Print a warning if the file contains less data than expected, | ||
| 780 | * but don't abort. It's useful to see how the image looks even if it's wrong. */ | ||
| 781 | fprintf(stderr, "Warning: expected to read %zi bytes from \"%s\", read %zi\n", | ||
| 782 | size, image_path, count); | ||
| 783 | } | ||
| 784 | } | ||
| 785 | |||
| 786 | fclose(f); | ||
| 787 | return img; | ||
| 788 | } | ||
| 789 | |||
| 790 | static bool verify_png_image(const char *image_path) { | ||
| 791 | if (!image_path) { | ||
| 792 | return false; | ||
| 793 | } | ||
| 794 | |||
| 795 | /* Check file exists and has correct PNG header */ | ||
| 796 | FILE *png_file = fopen(image_path, "r"); | ||
| 797 | if (png_file == NULL) { | ||
| 798 | fprintf(stderr, "Image file path \"%s\" cannot be opened: %s\n", image_path, strerror(errno)); | ||
| 799 | return false; | ||
| 800 | } | ||
| 801 | unsigned char png_header[8]; | ||
| 802 | memset(png_header, '\0', sizeof(png_header)); | ||
| 803 | int bytes_read = fread(png_header, 1, sizeof(png_header), png_file); | ||
| 804 | fclose(png_file); | ||
| 805 | if (bytes_read != sizeof(png_header)) { | ||
| 806 | fprintf(stderr, "Could not read PNG header from \"%s\"\n", image_path); | ||
| 807 | return false; | ||
| 808 | } | ||
| 809 | |||
| 810 | // Check PNG header according to the specification, available at: | ||
| 811 | // https://www.w3.org/TR/2003/REC-PNG-20031110/#5PNG-file-signature | ||
| 812 | static unsigned char PNG_REFERENCE_HEADER[8] = {137, 80, 78, 71, 13, 10, 26, 10}; | ||
| 813 | if (memcmp(PNG_REFERENCE_HEADER, png_header, sizeof(png_header)) != 0) { | ||
| 814 | fprintf(stderr, "File \"%s\" does not start with a PNG header. i3lock currently only supports loading PNG files.\n", image_path); | ||
| 815 | return false; | ||
| 816 | } | ||
| 817 | return true; | ||
| 818 | } | ||
| 819 | |||
| 820 | #ifndef __OpenBSD__ | ||
| 821 | /* | ||
| 822 | * Callback function for PAM. We only react on password request callbacks. | ||
| 823 | * | ||
| 824 | */ | ||
| 825 | static int conv_callback(int num_msg, const struct pam_message **msg, | ||
| 826 | struct pam_response **resp, void *appdata_ptr) { | ||
| 827 | if (num_msg == 0) | ||
| 828 | return 1; | ||
| 829 | |||
| 830 | /* PAM expects an array of responses, one for each message */ | ||
| 831 | if ((*resp = calloc(num_msg, sizeof(struct pam_response))) == NULL) { | ||
| 832 | perror("calloc"); | ||
| 833 | return 1; | ||
| 834 | } | ||
| 835 | |||
| 836 | for (int c = 0; c < num_msg; c++) { | ||
| 837 | if (msg[c]->msg_style != PAM_PROMPT_ECHO_OFF && | ||
| 838 | msg[c]->msg_style != PAM_PROMPT_ECHO_ON) | ||
| 839 | continue; | ||
| 840 | |||
| 841 | /* return code is currently not used but should be set to zero */ | ||
| 842 | resp[c]->resp_retcode = 0; | ||
| 843 | if ((resp[c]->resp = strdup(password)) == NULL) { | ||
| 844 | perror("strdup"); | ||
| 845 | return 1; | ||
| 846 | } | ||
| 847 | } | ||
| 848 | |||
| 849 | return 0; | ||
| 850 | } | ||
| 851 | #endif | ||
| 852 | |||
| 853 | /* | ||
| 854 | * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb. | ||
| 855 | * See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop | ||
| 856 | * | ||
| 857 | */ | ||
| 858 | static void xcb_got_event(EV_P_ struct ev_io *w, int revents) { | ||
| 859 | /* empty, because xcb_prepare_cb and xcb_check_cb are used */ | ||
| 860 | } | ||
| 861 | |||
| 862 | /* | ||
| 863 | * Flush before blocking (and waiting for new events) | ||
| 864 | * | ||
| 865 | */ | ||
| 866 | static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) { | ||
| 867 | xcb_flush(conn); | ||
| 868 | } | ||
| 869 | |||
| 870 | /* | ||
| 871 | * Try closing logind sleep lock fd passed over from xss-lock, in case we're | ||
| 872 | * being run from there. | ||
| 873 | * | ||
| 874 | */ | ||
| 875 | static void maybe_close_sleep_lock_fd(void) { | ||
| 876 | const char *sleep_lock_fd = getenv("XSS_SLEEP_LOCK_FD"); | ||
| 877 | char *endptr; | ||
| 878 | if (sleep_lock_fd && *sleep_lock_fd != 0) { | ||
| 879 | long int fd = strtol(sleep_lock_fd, &endptr, 10); | ||
| 880 | if (*endptr == 0) { | ||
| 881 | close(fd); | ||
| 882 | } | ||
| 883 | } | ||
| 884 | } | ||
| 885 | |||
| 886 | /* | ||
| 887 | * Instead of polling the X connection socket we leave this to | ||
| 888 | * xcb_poll_for_event() which knows better than we can ever know. | ||
| 889 | * | ||
| 890 | */ | ||
| 891 | static void xcb_check_cb(EV_P_ ev_check *w, int revents) { | ||
| 892 | xcb_generic_event_t *event; | ||
| 893 | |||
| 894 | if (xcb_connection_has_error(conn)) | ||
| 895 | errx(EXIT_FAILURE, "X11 connection broke, did your server terminate?"); | ||
| 896 | |||
| 897 | while ((event = xcb_poll_for_event(conn)) != NULL) { | ||
| 898 | if (event->response_type == 0) { | ||
| 899 | xcb_generic_error_t *error = (xcb_generic_error_t *)event; | ||
| 900 | if (debug_mode) | ||
| 901 | fprintf(stderr, "X11 Error received! sequence 0x%x, error_code = %d\n", | ||
| 902 | error->sequence, error->error_code); | ||
| 903 | free(event); | ||
| 904 | continue; | ||
| 905 | } | ||
| 906 | |||
| 907 | /* Strip off the highest bit (set if the event is generated) */ | ||
| 908 | int type = (event->response_type & 0x7F); | ||
| 909 | |||
| 910 | switch (type) { | ||
| 911 | case XCB_KEY_PRESS: | ||
| 912 | handle_key_press((xcb_key_press_event_t *)event); | ||
| 913 | break; | ||
| 914 | |||
| 915 | case XCB_VISIBILITY_NOTIFY: | ||
| 916 | handle_visibility_notify(conn, (xcb_visibility_notify_event_t *)event); | ||
| 917 | break; | ||
| 918 | |||
| 919 | case XCB_MAP_NOTIFY: | ||
| 920 | maybe_close_sleep_lock_fd(); | ||
| 921 | if (!dont_fork) { | ||
| 922 | /* After the first MapNotify, we never fork again. We don’t | ||
| 923 | * expect to get another MapNotify, but better be sure… */ | ||
| 924 | dont_fork = true; | ||
| 925 | |||
| 926 | /* In the parent process, we exit */ | ||
| 927 | if (fork() != 0) | ||
| 928 | exit(0); | ||
| 929 | |||
| 930 | ev_loop_fork(EV_DEFAULT); | ||
| 931 | } | ||
| 932 | break; | ||
| 933 | |||
| 934 | case XCB_CONFIGURE_NOTIFY: | ||
| 935 | handle_screen_resize(); | ||
| 936 | break; | ||
| 937 | |||
| 938 | default: | ||
| 939 | if (type == xkb_base_event) { | ||
| 940 | process_xkb_event(event); | ||
| 941 | } | ||
| 942 | if (randr_base > -1 && | ||
| 943 | type == randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY) { | ||
| 944 | randr_query(screen->root); | ||
| 945 | handle_screen_resize(); | ||
| 946 | } | ||
| 947 | } | ||
| 948 | |||
| 949 | free(event); | ||
| 950 | } | ||
| 951 | } | ||
| 952 | |||
| 953 | /* | ||
| 954 | * This function is called from a fork()ed child and will raise the i3lock | ||
| 955 | * window when the window is obscured, even when the main i3lock process is | ||
| 956 | * blocked due to the authentication backend. | ||
| 957 | * | ||
| 958 | */ | ||
| 959 | static void raise_loop(xcb_window_t window) { | ||
| 960 | xcb_connection_t *conn; | ||
| 961 | xcb_generic_event_t *event; | ||
| 962 | int screens; | ||
| 963 | |||
| 964 | if (xcb_connection_has_error((conn = xcb_connect(NULL, &screens))) > 0) | ||
| 965 | errx(EXIT_FAILURE, "Cannot open display"); | ||
| 966 | |||
| 967 | /* We need to know about the window being obscured or getting destroyed. */ | ||
| 968 | xcb_change_window_attributes(conn, window, XCB_CW_EVENT_MASK, | ||
| 969 | (uint32_t[]){ | ||
| 970 | XCB_EVENT_MASK_VISIBILITY_CHANGE | | ||
| 971 | XCB_EVENT_MASK_STRUCTURE_NOTIFY}); | ||
| 972 | xcb_flush(conn); | ||
| 973 | |||
| 974 | DEBUG("Watching window 0x%08x\n", window); | ||
| 975 | while ((event = xcb_wait_for_event(conn)) != NULL) { | ||
| 976 | if (event->response_type == 0) { | ||
| 977 | xcb_generic_error_t *error = (xcb_generic_error_t *)event; | ||
| 978 | DEBUG("X11 Error received! sequence 0x%x, error_code = %d\n", | ||
| 979 | error->sequence, error->error_code); | ||
| 980 | free(event); | ||
| 981 | continue; | ||
| 982 | } | ||
| 983 | /* Strip off the highest bit (set if the event is generated) */ | ||
| 984 | int type = (event->response_type & 0x7F); | ||
| 985 | DEBUG("Read event of type %d\n", type); | ||
| 986 | switch (type) { | ||
| 987 | case XCB_VISIBILITY_NOTIFY: | ||
| 988 | handle_visibility_notify(conn, (xcb_visibility_notify_event_t *)event); | ||
| 989 | break; | ||
| 990 | case XCB_UNMAP_NOTIFY: | ||
| 991 | DEBUG("UnmapNotify for 0x%08x\n", (((xcb_unmap_notify_event_t *)event)->window)); | ||
| 992 | if (((xcb_unmap_notify_event_t *)event)->window == window) | ||
| 993 | exit(EXIT_SUCCESS); | ||
| 994 | break; | ||
| 995 | case XCB_DESTROY_NOTIFY: | ||
| 996 | DEBUG("DestroyNotify for 0x%08x\n", (((xcb_destroy_notify_event_t *)event)->window)); | ||
| 997 | if (((xcb_destroy_notify_event_t *)event)->window == window) | ||
| 998 | exit(EXIT_SUCCESS); | ||
| 999 | break; | ||
| 1000 | default: | ||
| 1001 | DEBUG("Unhandled event type %d\n", type); | ||
| 1002 | break; | ||
| 1003 | } | ||
| 1004 | free(event); | ||
| 1005 | } | ||
| 1006 | } | ||
| 1007 | |||
| 1008 | int main(int argc, char *argv[]) { | ||
| 1009 | struct passwd *pw; | ||
| 1010 | char *username; | ||
| 1011 | char *image_path = NULL; | ||
| 1012 | char *image_raw_format = NULL; | ||
| 1013 | #ifndef __OpenBSD__ | ||
| 1014 | int ret; | ||
| 1015 | struct pam_conv conv = {conv_callback, NULL}; | ||
| 1016 | #endif | ||
| 1017 | int curs_choice = CURS_NONE; | ||
| 1018 | int o; | ||
| 1019 | int longoptind = 0; | ||
| 1020 | struct option longopts[] = { | ||
| 1021 | {"version", no_argument, NULL, 'v'}, | ||
| 1022 | {"nofork", no_argument, NULL, 'n'}, | ||
| 1023 | {"beep", no_argument, NULL, 'b'}, | ||
| 1024 | {"dpms", no_argument, NULL, 'd'}, | ||
| 1025 | {"color", required_argument, NULL, 'c'}, | ||
| 1026 | {"pointer", required_argument, NULL, 'p'}, | ||
| 1027 | {"debug", no_argument, NULL, 0}, | ||
| 1028 | {"help", no_argument, NULL, 'h'}, | ||
| 1029 | {"no-unlock-indicator", no_argument, NULL, 'u'}, | ||
| 1030 | {"image", required_argument, NULL, 'i'}, | ||
| 1031 | {"raw", required_argument, NULL, 0}, | ||
| 1032 | {"tiling", no_argument, NULL, 't'}, | ||
| 1033 | {"ignore-empty-password", no_argument, NULL, 'e'}, | ||
| 1034 | {"inactivity-timeout", required_argument, NULL, 'I'}, | ||
| 1035 | {"show-failed-attempts", no_argument, NULL, 'f'}, | ||
| 1036 | {NULL, no_argument, NULL, 0}}; | ||
| 1037 | |||
| 1038 | if ((pw = getpwuid(getuid())) == NULL) | ||
| 1039 | err(EXIT_FAILURE, "getpwuid() failed"); | ||
| 1040 | if ((username = pw->pw_name) == NULL) | ||
| 1041 | errx(EXIT_FAILURE, "pw->pw_name is NULL."); | ||
| 1042 | |||
| 1043 | char *optstring = "hvnbdc:p:ui:teI:f"; | ||
| 1044 | while ((o = getopt_long(argc, argv, optstring, longopts, &longoptind)) != -1) { | ||
| 1045 | switch (o) { | ||
| 1046 | case 'v': | ||
| 1047 | errx(EXIT_SUCCESS, "version " I3LOCK_VERSION " © 2010 Michael Stapelberg"); | ||
| 1048 | case 'n': | ||
| 1049 | dont_fork = true; | ||
| 1050 | break; | ||
| 1051 | case 'b': | ||
| 1052 | beep = true; | ||
| 1053 | break; | ||
| 1054 | case 'd': | ||
| 1055 | fprintf(stderr, "DPMS support has been removed from i3lock. Please see the manpage i3lock(1).\n"); | ||
| 1056 | break; | ||
| 1057 | case 'I': { | ||
| 1058 | fprintf(stderr, "Inactivity timeout only makes sense with DPMS, which was removed. Please see the manpage i3lock(1).\n"); | ||
| 1059 | break; | ||
| 1060 | } | ||
| 1061 | case 'c': { | ||
| 1062 | char *arg = optarg; | ||
| 1063 | |||
| 1064 | /* Skip # if present */ | ||
| 1065 | if (arg[0] == '#') | ||
| 1066 | arg++; | ||
| 1067 | |||
| 1068 | if (strlen(arg) != 6 || sscanf(arg, "%06[0-9a-fA-F]", color) != 1) | ||
| 1069 | errx(EXIT_FAILURE, "color is invalid, it must be given in 3-byte hexadecimal format: rrggbb"); | ||
| 1070 | |||
| 1071 | break; | ||
| 1072 | } | ||
| 1073 | case 'u': | ||
| 1074 | unlock_indicator = false; | ||
| 1075 | break; | ||
| 1076 | case 'i': | ||
| 1077 | image_path = strdup(optarg); | ||
| 1078 | break; | ||
| 1079 | case 't': | ||
| 1080 | tile = true; | ||
| 1081 | break; | ||
| 1082 | case 'p': | ||
| 1083 | if (!strcmp(optarg, "win")) { | ||
| 1084 | curs_choice = CURS_WIN; | ||
| 1085 | } else if (!strcmp(optarg, "default")) { | ||
| 1086 | curs_choice = CURS_DEFAULT; | ||
| 1087 | } else { | ||
| 1088 | errx(EXIT_FAILURE, "i3lock: Invalid pointer type given. Expected one of \"win\" or \"default\"."); | ||
| 1089 | } | ||
| 1090 | break; | ||
| 1091 | case 'e': | ||
| 1092 | ignore_empty_password = true; | ||
| 1093 | break; | ||
| 1094 | case 0: | ||
| 1095 | if (strcmp(longopts[longoptind].name, "debug") == 0) | ||
| 1096 | debug_mode = true; | ||
| 1097 | else if (strcmp(longopts[longoptind].name, "raw") == 0) | ||
| 1098 | image_raw_format = strdup(optarg); | ||
| 1099 | break; | ||
| 1100 | case 'f': | ||
| 1101 | show_failed_attempts = true; | ||
| 1102 | break; | ||
| 1103 | default: | ||
| 1104 | errx(EXIT_FAILURE, "Syntax: i3lock [-v] [-n] [-b] [-d] [-c color] [-u] [-p win|default]" | ||
| 1105 | " [-i image.png] [-t] [-e] [-I timeout] [-f]"); | ||
| 1106 | } | ||
| 1107 | } | ||
| 1108 | |||
| 1109 | /* We need (relatively) random numbers for highlighting a random part of | ||
| 1110 | * the unlock indicator upon keypresses. */ | ||
| 1111 | srand(time(NULL)); | ||
| 1112 | |||
| 1113 | #ifndef __OpenBSD__ | ||
| 1114 | /* Initialize PAM */ | ||
| 1115 | if ((ret = pam_start("i3lock", username, &conv, &pam_handle)) != PAM_SUCCESS) | ||
| 1116 | errx(EXIT_FAILURE, "PAM: %s", pam_strerror(pam_handle, ret)); | ||
| 1117 | |||
| 1118 | if ((ret = pam_set_item(pam_handle, PAM_TTY, getenv("DISPLAY"))) != PAM_SUCCESS) | ||
| 1119 | errx(EXIT_FAILURE, "PAM: %s", pam_strerror(pam_handle, ret)); | ||
| 1120 | #endif | ||
| 1121 | |||
| 1122 | /* Using mlock() as non-super-user seems only possible in Linux. | ||
| 1123 | * Users of other operating systems should use encrypted swap/no swap | ||
| 1124 | * (or remove the ifdef and run i3lock as super-user). | ||
| 1125 | * Alas, swap is encrypted by default on OpenBSD so swapping out | ||
| 1126 | * is not necessarily an issue. */ | ||
| 1127 | #if defined(__linux__) | ||
| 1128 | /* Lock the area where we store the password in memory, we don’t want it to | ||
| 1129 | * be swapped to disk. Since Linux 2.6.9, this does not require any | ||
| 1130 | * privileges, just enough bytes in the RLIMIT_MEMLOCK limit. */ | ||
| 1131 | if (mlock(password, sizeof(password)) != 0) | ||
| 1132 | err(EXIT_FAILURE, "Could not lock page in memory, check RLIMIT_MEMLOCK"); | ||
| 1133 | #endif | ||
| 1134 | |||
| 1135 | /* Double checking that connection is good and operatable with xcb */ | ||
| 1136 | int screennr; | ||
| 1137 | if ((conn = xcb_connect(NULL, &screennr)) == NULL || | ||
| 1138 | xcb_connection_has_error(conn)) | ||
| 1139 | errx(EXIT_FAILURE, "Could not connect to X11, maybe you need to set DISPLAY?"); | ||
| 1140 | |||
| 1141 | if (xkb_x11_setup_xkb_extension(conn, | ||
| 1142 | XKB_X11_MIN_MAJOR_XKB_VERSION, | ||
| 1143 | XKB_X11_MIN_MINOR_XKB_VERSION, | ||
| 1144 | 0, | ||
| 1145 | NULL, | ||
| 1146 | NULL, | ||
| 1147 | &xkb_base_event, | ||
| 1148 | &xkb_base_error) != 1) | ||
| 1149 | errx(EXIT_FAILURE, "Could not setup XKB extension."); | ||
| 1150 | |||
| 1151 | static const xcb_xkb_map_part_t required_map_parts = | ||
| 1152 | (XCB_XKB_MAP_PART_KEY_TYPES | | ||
| 1153 | XCB_XKB_MAP_PART_KEY_SYMS | | ||
| 1154 | XCB_XKB_MAP_PART_MODIFIER_MAP | | ||
| 1155 | XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS | | ||
| 1156 | XCB_XKB_MAP_PART_KEY_ACTIONS | | ||
| 1157 | XCB_XKB_MAP_PART_VIRTUAL_MODS | | ||
| 1158 | XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP); | ||
| 1159 | |||
| 1160 | static const xcb_xkb_event_type_t required_events = | ||
| 1161 | (XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY | | ||
| 1162 | XCB_XKB_EVENT_TYPE_MAP_NOTIFY | | ||
| 1163 | XCB_XKB_EVENT_TYPE_STATE_NOTIFY); | ||
| 1164 | |||
| 1165 | xcb_xkb_select_events( | ||
| 1166 | conn, | ||
| 1167 | xkb_x11_get_core_keyboard_device_id(conn), | ||
| 1168 | required_events, | ||
| 1169 | 0, | ||
| 1170 | required_events, | ||
| 1171 | required_map_parts, | ||
| 1172 | required_map_parts, | ||
| 1173 | 0); | ||
| 1174 | |||
| 1175 | /* When we cannot initially load the keymap, we better exit */ | ||
| 1176 | if (!load_keymap()) | ||
| 1177 | errx(EXIT_FAILURE, "Could not load keymap"); | ||
| 1178 | |||
| 1179 | const char *locale = getenv("LC_ALL"); | ||
| 1180 | if (!locale || !*locale) | ||
| 1181 | locale = getenv("LC_CTYPE"); | ||
| 1182 | if (!locale || !*locale) | ||
| 1183 | locale = getenv("LANG"); | ||
| 1184 | if (!locale || !*locale) { | ||
| 1185 | if (debug_mode) | ||
| 1186 | fprintf(stderr, "Can't detect your locale, fallback to C\n"); | ||
| 1187 | locale = "C"; | ||
| 1188 | } | ||
| 1189 | |||
| 1190 | load_compose_table(locale); | ||
| 1191 | |||
| 1192 | screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; | ||
| 1193 | |||
| 1194 | init_dpi(); | ||
| 1195 | |||
| 1196 | randr_init(&randr_base, screen->root); | ||
| 1197 | randr_query(screen->root); | ||
| 1198 | |||
| 1199 | last_resolution[0] = screen->width_in_pixels; | ||
| 1200 | last_resolution[1] = screen->height_in_pixels; | ||
| 1201 | |||
| 1202 | xcb_change_window_attributes(conn, screen->root, XCB_CW_EVENT_MASK, | ||
| 1203 | (uint32_t[]){XCB_EVENT_MASK_STRUCTURE_NOTIFY}); | ||
| 1204 | |||
| 1205 | if (image_raw_format != NULL && image_path != NULL) { | ||
| 1206 | /* Read image. 'read_raw_image' returns NULL on error, | ||
| 1207 | * so we don't have to handle errors here. */ | ||
| 1208 | img = read_raw_image(image_path, image_raw_format); | ||
| 1209 | } else if (verify_png_image(image_path)) { | ||
| 1210 | /* Create a pixmap to render on, fill it with the background color */ | ||
| 1211 | img = cairo_image_surface_create_from_png(image_path); | ||
| 1212 | /* In case loading failed, we just pretend no -i was specified. */ | ||
| 1213 | if (cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { | ||
| 1214 | fprintf(stderr, "Could not load image \"%s\": %s\n", | ||
| 1215 | image_path, cairo_status_to_string(cairo_surface_status(img))); | ||
| 1216 | img = NULL; | ||
| 1217 | } | ||
| 1218 | } | ||
| 1219 | |||
| 1220 | free(image_path); | ||
| 1221 | free(image_raw_format); | ||
| 1222 | |||
| 1223 | /* Pixmap on which the image is rendered to (if any) */ | ||
| 1224 | xcb_pixmap_t bg_pixmap = draw_image(last_resolution); | ||
| 1225 | |||
| 1226 | xcb_window_t stolen_focus = find_focused_window(conn, screen->root); | ||
| 1227 | |||
| 1228 | /* Open the fullscreen window, already with the correct pixmap in place */ | ||
| 1229 | win = open_fullscreen_window(conn, screen, color, bg_pixmap); | ||
| 1230 | xcb_free_pixmap(conn, bg_pixmap); | ||
| 1231 | |||
| 1232 | cursor = create_cursor(conn, screen, win, curs_choice); | ||
| 1233 | |||
| 1234 | /* Display the "locking…" message while trying to grab the pointer/keyboard. */ | ||
| 1235 | auth_state = STATE_AUTH_LOCK; | ||
| 1236 | if (!grab_pointer_and_keyboard(conn, screen, cursor, 1000)) { | ||
| 1237 | DEBUG("stole focus from X11 window 0x%08x\n", stolen_focus); | ||
| 1238 | |||
| 1239 | /* Set the focus to i3lock, possibly closing context menus which would | ||
| 1240 | * otherwise prevent us from grabbing keyboard/pointer. | ||
| 1241 | * | ||
| 1242 | * We cannot use set_focused_window because _NET_ACTIVE_WINDOW only | ||
| 1243 | * works for managed windows, but i3lock uses an unmanaged window | ||
| 1244 | * (override_redirect=1). */ | ||
| 1245 | xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT /* revert_to */, win, XCB_CURRENT_TIME); | ||
| 1246 | if (!grab_pointer_and_keyboard(conn, screen, cursor, 9000)) { | ||
| 1247 | auth_state = STATE_I3LOCK_LOCK_FAILED; | ||
| 1248 | redraw_screen(); | ||
| 1249 | sleep(1); | ||
| 1250 | errx(EXIT_FAILURE, "Cannot grab pointer/keyboard"); | ||
| 1251 | } | ||
| 1252 | } | ||
| 1253 | |||
| 1254 | pid_t pid = fork(); | ||
| 1255 | /* The pid == -1 case is intentionally ignored here: | ||
| 1256 | * While the child process is useful for preventing other windows from | ||
| 1257 | * popping up while i3lock blocks, it is not critical. */ | ||
| 1258 | if (pid == 0) { | ||
| 1259 | /* Child */ | ||
| 1260 | close(xcb_get_file_descriptor(conn)); | ||
| 1261 | maybe_close_sleep_lock_fd(); | ||
| 1262 | raise_loop(win); | ||
| 1263 | exit(EXIT_SUCCESS); | ||
| 1264 | } | ||
| 1265 | |||
| 1266 | /* Load the keymap again to sync the current modifier state. Since we first | ||
| 1267 | * loaded the keymap, there might have been changes, but starting from now, | ||
| 1268 | * we should get all key presses/releases due to having grabbed the | ||
| 1269 | * keyboard. */ | ||
| 1270 | (void)load_keymap(); | ||
| 1271 | |||
| 1272 | /* Initialize the libev event loop. */ | ||
| 1273 | main_loop = EV_DEFAULT; | ||
| 1274 | if (main_loop == NULL) | ||
| 1275 | errx(EXIT_FAILURE, "Could not initialize libev. Bad LIBEV_FLAGS?"); | ||
| 1276 | |||
| 1277 | /* Explicitly call the screen redraw in case "locking…" message was displayed */ | ||
| 1278 | auth_state = STATE_AUTH_IDLE; | ||
| 1279 | redraw_screen(); | ||
| 1280 | |||
| 1281 | struct ev_io *xcb_watcher = calloc(sizeof(struct ev_io), 1); | ||
| 1282 | struct ev_check *xcb_check = calloc(sizeof(struct ev_check), 1); | ||
| 1283 | struct ev_prepare *xcb_prepare = calloc(sizeof(struct ev_prepare), 1); | ||
| 1284 | |||
| 1285 | ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ); | ||
| 1286 | ev_io_start(main_loop, xcb_watcher); | ||
| 1287 | |||
| 1288 | ev_check_init(xcb_check, xcb_check_cb); | ||
| 1289 | ev_check_start(main_loop, xcb_check); | ||
| 1290 | |||
| 1291 | ev_prepare_init(xcb_prepare, xcb_prepare_cb); | ||
| 1292 | ev_prepare_start(main_loop, xcb_prepare); | ||
| 1293 | |||
| 1294 | /* Invoke the event callback once to catch all the events which were | ||
| 1295 | * received up until now. ev will only pick up new events (when the X11 | ||
| 1296 | * file descriptor becomes readable). */ | ||
| 1297 | ev_invoke(main_loop, xcb_check, 0); | ||
| 1298 | ev_loop(main_loop, 0); | ||
| 1299 | |||
| 1300 | #ifndef __OpenBSD__ | ||
| 1301 | if (pam_cleanup) { | ||
| 1302 | pam_end(pam_handle, PAM_SUCCESS); | ||
| 1303 | } | ||
| 1304 | #endif | ||
| 1305 | |||
| 1306 | if (stolen_focus == XCB_NONE) { | ||
| 1307 | return 0; | ||
| 1308 | } | ||
| 1309 | |||
| 1310 | DEBUG("restoring focus to X11 window 0x%08x\n", stolen_focus); | ||
| 1311 | xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); | ||
| 1312 | xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME); | ||
| 1313 | xcb_destroy_window(conn, win); | ||
| 1314 | set_focused_window(conn, screen->root, stolen_focus); | ||
| 1315 | xcb_aux_sync(conn); | ||
| 1316 | |||
| 1317 | return 0; | ||
| 1318 | } | ||
