summaryrefslogtreecommitdiff
path: root/i3lock.c
diff options
context:
space:
mode:
authorMichael Stapelberg <michael@stapelberg.de>2012-10-26 20:24:55 +0200
committerMichael Stapelberg <michael@stapelberg.de>2012-10-26 20:39:59 +0200
commite1d86a327945111b99148650dc536c9ad8598cc1 (patch)
treef120f2e29e47cfa7eb494f811c3282449cf3d720 /i3lock.c
parent1d08065fe02304f078c9b34250148777f3d0a734 (diff)
use libxkbcommon for input handling
Thanks to Ran Benita and Daniel Stone (the libxkbcommon authors) for answering my questions and reviewing this code. With this commit, input handling should be more correct with using less code (in i3lock, that is).
Diffstat (limited to 'i3lock.c')
-rw-r--r--i3lock.c280
1 files changed, 121 insertions, 159 deletions
diff --git a/i3lock.c b/i3lock.c
index 8d4eb11..48ab06b 100644
--- a/i3lock.c
+++ b/i3lock.c
@@ -14,17 +14,17 @@
#include <stdint.h>
#include <xcb/xcb.h>
#include <xcb/dpms.h>
-#include <xcb/xcb_keysyms.h>
#include <err.h>
#include <assert.h>
#include <security/pam_appl.h>
-/* FIXME: can we get rid of this header? */
-#include <X11/Xutil.h>
-#include <X11/keysym.h>
+#include <X11/Xlib-xcb.h>
#include <getopt.h>
#include <string.h>
#include <ev.h>
#include <sys/mman.h>
+#include <X11/XKBlib.h>
+#include <X11/extensions/XKBfile.h>
+#include <xkbcommon/xkbcommon.h>
#ifndef NOLIBCAIRO
@@ -33,27 +33,21 @@
#endif
#include "i3lock.h"
-#include "keysym2ucs.h"
-#include "ucs2_to_utf8.h"
#include "xcb.h"
#include "cursors.h"
#include "unlock_indicator.h"
#include "xinerama.h"
+/* We need this for libxkbfile */
+static Display *display;
char color[7] = "ffffff";
uint32_t last_resolution[2];
xcb_window_t win;
static xcb_cursor_t cursor;
-static xcb_key_symbols_t *symbols;
static pam_handle_t *pam_handle;
int input_position = 0;
/* Holds the password you enter (in UTF-8). */
static char password[512];
-static bool modeswitch_active = false;
-static bool iso_level3_shift_active = false;
-static bool iso_level5_shift_active = false;
-static int numlockmask;
-static int capslockmask;
static bool beep = false;
bool debug_mode = false;
static bool dpms = false;
@@ -64,11 +58,91 @@ static struct ev_timer *clear_pam_wrong_timeout;
extern unlock_state_t unlock_state;
extern pam_state_t pam_state;
+static struct xkb_state *xkb_state;
+static struct xkb_context *xkb_context;
+static struct xkb_keymap *xkb_keymap;
+
#ifndef NOLIBCAIRO
cairo_surface_t *img = NULL;
bool tile = false;
#endif
+/* isutf, u8_dec © 2005 Jeff Bezanson, public domain */
+#define isutf(c) (((c) & 0xC0) != 0x80)
+
+/*
+ * Decrements i to point to the previous unicode glyph
+ *
+ */
+void u8_dec(char *s, int *i) {
+ (void)(isutf(s[--(*i)]) || isutf(s[--(*i)]) || isutf(s[--(*i)]) || --(*i));
+}
+
+/*
+ * Loads the XKB keymap from the X11 server and feeds it to xkbcommon.
+ * Necessary so that we can properly let xkbcommon track the keyboard state and
+ * translate keypresses to utf-8.
+ *
+ * Ideally, xkbcommon would ship something like this itself, but as of now
+ * (version 0.2.0), it doesn’t.
+ *
+ */
+static bool load_keymap(void) {
+ bool ret = false;
+ XkbFileInfo result;
+ memset(&result, '\0', sizeof(result));
+ result.xkb = XkbGetKeyboard(display, XkbAllMapComponentsMask, XkbUseCoreKbd);
+ if (result.xkb == NULL) {
+ fprintf(stderr, "[i3lock] XKB: XkbGetKeyboard failed\n");
+ return false;
+ }
+
+ FILE *temp = tmpfile();
+ if (temp == NULL) {
+ fprintf(stderr, "[i3lock] could not create tempfile\n");
+ return false;
+ }
+
+ bool ok = XkbWriteXKBKeymap(temp, &result, false, false, NULL, NULL);
+ if (!ok) {
+ fprintf(stderr, "[i3lock] XkbWriteXKBKeymap failed\n");
+ goto out;
+ }
+
+ rewind(temp);
+
+ if (xkb_context == NULL) {
+ if ((xkb_context = xkb_context_new(0)) == NULL) {
+ fprintf(stderr, "[i3lock] could not create xkbcommon context\n");
+ goto out;
+ }
+ }
+
+ if (xkb_keymap != NULL)
+ xkb_keymap_unref(xkb_keymap);
+
+ if ((xkb_keymap = xkb_keymap_new_from_file(xkb_context, temp, XKB_KEYMAP_FORMAT_TEXT_V1, 0)) == NULL) {
+ fprintf(stderr, "[i3lock] xkb_keymap_new_from_file failed\n");
+ goto out;
+ }
+
+ struct xkb_state *new_state = xkb_state_new(xkb_keymap);
+ if (new_state == NULL) {
+ fprintf(stderr, "[i3lock] xkb_state_new failed\n");
+ goto out;
+ }
+
+ if (xkb_state != NULL)
+ xkb_state_unref(xkb_state);
+ xkb_state = new_state;
+
+ ret = true;
+out:
+ XkbFreeKeyboard(result.xkb, XkbAllComponentsMask, true);
+ fclose(temp);
+ return ret;
+}
+
/*
* Clears the memory which stored the password to be a bit safer against
* cold-boot attacks.
@@ -154,24 +228,7 @@ static void input_done(void) {
*
*/
static void handle_key_release(xcb_key_release_event_t *event) {
- DEBUG("releasing key %d, state raw = %d, modeswitch_active = %d, iso_level3_shift_active = %d, iso_level5_shift_active = %d\n",
- event->detail, event->state, modeswitch_active, iso_level3_shift_active, iso_level5_shift_active);
-
- /* We don’t care about the column here and just use the first symbol. Since
- * we only check for Mode_switch and ISO_Level3_Shift, this *should* work.
- * Also, if we would use the current column, we would look in the wrong
- * place. */
- xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, 0);
- if (sym == XK_Mode_switch) {
- //printf("Mode switch disabled\n");
- modeswitch_active = false;
- } else if (sym == XK_ISO_Level3_Shift) {
- iso_level3_shift_active = false;
- } else if (sym == XK_ISO_Level5_Shift) {
- iso_level5_shift_active = false;
- }
- DEBUG("release done. modeswitch_active = %d, iso_level3_shift_active = %d, iso_level5_shift_active = %d\n",
- modeswitch_active, iso_level3_shift_active, iso_level5_shift_active);
+ xkb_state_update_key(xkb_state, event->detail, XKB_KEY_UP);
}
static void redraw_timeout(EV_P_ ev_timer *w, int revents) {
@@ -188,67 +245,23 @@ static void redraw_timeout(EV_P_ ev_timer *w, int revents) {
*
*/
static void handle_key_press(xcb_key_press_event_t *event) {
- DEBUG("keypress %d, state raw = %d, modeswitch_active = %d, iso_level3_shift_active = %d\n",
- event->detail, event->state, modeswitch_active, iso_level3_shift_active);
-
- xcb_keysym_t sym0, sym1, sym;
- /* For each keycode, there is a list of symbols. The list could look like this:
- * $ xmodmap -pke | grep 'keycode 38'
- * keycode 38 = a A adiaeresis Adiaeresis o O
- * In non-X11 terminology, the symbols for the keycode 38 (the key labeled
- * with "a" on my keyboard) are "a A ä Ä o O".
- * Another form to display the same information is using xkbcomp:
- * $ xkbcomp $DISPLAY /tmp/xkb.dump
- * Then open /tmp/xkb.dump and search for '\<a\>' (in VIM regexp-language):
- *
- * symbols[Group1]= [ a, A, o, O ],
- * symbols[Group2]= [ adiaeresis, Adiaeresis ]
- *
- * So there are two *groups*, one containing 'a A' and one containing 'ä
- * Ä'. You can use Mode_switch to switch between these groups. You can use
- * ISO_Level3_Shift to reach the 'o O' part of the first group (it’s the
- * same group, just an even higher shift level).
- *
- * So, using the "logical" XKB information, the following lookup will be
- * performed:
- *
- * Neither Mode_switch nor ISO_Level3_Shift active: group 1, column 0 and 1
- * Mode_switch active: group 2, column 0 and 1
- * ISO_Level3_Shift active: group 1, column 2 and 3
- *
- * Using the column index which xcb_key_press_lookup_keysym uses (and
- * xmodmap prints out), the following lookup will be performed:
- *
- * Neither Mode_switch nor ISO_Level3_Shift active: column 0 and 1
- * Mode_switch active: column 2 and 3
- * ISO_Level3_Shift active: column 4 and 5
- */
- int base_column = 0;
- if (modeswitch_active)
- base_column = 2;
- if (iso_level3_shift_active)
- base_column = 4;
- if (iso_level5_shift_active)
- base_column = 6;
- sym0 = xcb_key_press_lookup_keysym(symbols, event, base_column);
- sym1 = xcb_key_press_lookup_keysym(symbols, event, base_column + 1);
- switch (sym0) {
- case XK_Mode_switch:
- DEBUG("Mode switch enabled\n");
- modeswitch_active = true;
- return;
- case XK_ISO_Level3_Shift:
- DEBUG("ISO_Level3_Shift enabled\n");
- iso_level3_shift_active = true;
- return;
- case XK_ISO_Level5_Shift:
- DEBUG("ISO_Level5_Shift enabled\n");
- iso_level5_shift_active = true;
- return;
- case XK_Return:
- case XK_KP_Enter:
+ xkb_keysym_t ksym;
+ char buffer[128];
+ int n;
+
+ ksym = xkb_state_key_get_one_sym(xkb_state, event->detail);
+ xkb_state_update_key(xkb_state, event->detail, XKB_KEY_DOWN);
+
+ /* The buffer will be null-terminated, so n >= 2 for 1 actual character. */
+ memset(buffer, '\0', sizeof(buffer));
+ n = xkb_keysym_to_utf8(ksym, buffer, sizeof(buffer));
+
+ switch (ksym) {
+ case XKB_KEY_Return:
+ case XKB_KEY_KP_Enter:
+ password[input_position] = '\0';
input_done();
- case XK_Escape:
+ case XKB_KEY_Escape:
input_position = 0;
clear_password_memory();
password[input_position] = '\0';
@@ -261,7 +274,7 @@ static void handle_key_press(xcb_key_press_event_t *event) {
unlock_state = STATE_KEY_PRESSED;
return;
- case XK_BackSpace:
+ case XKB_KEY_BackSpace:
if (input_position == 0)
return;
@@ -281,44 +294,6 @@ static void handle_key_press(xcb_key_press_event_t *event) {
if ((input_position + 8) >= sizeof(password))
return;
- /* Whether the user currently holds down the shift key. */
- bool shift = (event->state & XCB_MOD_MASK_SHIFT);
-
- /* Whether Caps Lock (all lowercase alphabetic keys will be replaced by
- * their uppercase variant) is active at the moment. */
- bool capslock = (event->state & capslockmask);
-
- DEBUG("shift = %d, capslock = %d\n",
- shift, capslock);
-
- if ((event->state & numlockmask) && xcb_is_keypad_key(sym1)) {
- /* this key was a keypad key */
- if (shift)
- sym = sym0;
- else sym = sym1;
- } else {
- xcb_keysym_t upper, lower;
- XConvertCase(sym0, (KeySym*)&lower, (KeySym*)&upper);
- DEBUG("sym0 = %c (%d), sym1 = %c (%d), lower = %c (%d), upper = %c (%d)\n",
- sym0, sym0, sym1, sym1, lower, lower, upper, upper);
- /* If there is no difference between the uppercase and lowercase
- * variant of this key, we consider Caps Lock off — it is only relevant
- * for alphabetic keys, unlike Shift Lock. */
- if (lower == upper) {
- capslock = false;
- DEBUG("lower == upper, now shift = %d, capslock = %d\n",
- shift, capslock);
- }
-
- /* In two different cases we need to use the uppercase keysym:
- * 1) The user holds shift, no lock is active.
- * 2) Any of the two locks is active.
- */
- if ((shift && !capslock) || (!shift && capslock))
- sym = sym1;
- else sym = sym0;
- }
-
#if 0
/* FIXME: handle all of these? */
printf("is_keypad_key = %d\n", xcb_is_keypad_key(sym));
@@ -330,26 +305,12 @@ static void handle_key_press(xcb_key_press_event_t *event) {
printf("xcb_is_modifier_key = %d\n", xcb_is_modifier_key(sym));
#endif
- if (xcb_is_modifier_key(sym) || xcb_is_cursor_key(sym))
+ if (n < 2)
return;
- DEBUG("resolved to keysym = %c (%d)\n", sym, sym);
-
- /* convert the keysym to UCS */
- uint16_t ucs = keysym2ucs(sym);
- if ((int16_t)ucs == -1) {
- if (debug_mode)
- fprintf(stderr, "Keysym could not be converted to UCS, skipping\n");
- return;
- }
-
- /* store the UCS in a string to convert it */
- uint8_t inp[3] = {(ucs & 0xFF00) >> 8, (ucs & 0xFF), 0};
- DEBUG("input part = %s\n", inp);
-
/* store it in the password array as UTF-8 */
- input_position += convert_ucs_to_utf8((char*)inp, password + input_position);
- password[input_position] = '\0';
+ memcpy(password+input_position, buffer, n-1);
+ input_position += n-1;
DEBUG("current password = %s\n", password);
unlock_state = STATE_KEY_ACTIVE;
@@ -387,9 +348,9 @@ static void handle_visibility_notify(xcb_visibility_notify_event_t *event) {
*
*/
static void handle_mapping_notify(xcb_mapping_notify_event_t *event) {
- xcb_refresh_keyboard_mapping(symbols, event);
-
- numlockmask = get_mod_mask(conn, symbols, XK_Num_Lock);
+ /* We ignore errors — if the new keymap cannot be loaded it’s better if the
+ * screen stays locked and the user intervenes by using killall i3lock. */
+ (void)load_keymap();
}
/*
@@ -548,7 +509,6 @@ int main(int argc, char *argv[]) {
#endif
int ret;
struct pam_conv conv = {conv_callback, NULL};
- int nscreen;
int curs_choice = CURS_NONE;
int o;
int optind = 0;
@@ -657,10 +617,19 @@ int main(int argc, char *argv[]) {
#endif
/* Initialize connection to X11 */
- if ((conn = xcb_connect(NULL, &nscreen)) == NULL ||
- xcb_connection_has_error(conn))
+ if ((display = XOpenDisplay(NULL)) == NULL)
+ errx(EXIT_FAILURE, "Could not connect to X11, maybe you need to set DISPLAY?");
+ XSetEventQueueOwner(display, XCBOwnsEventQueue);
+ conn = XGetXCBConnection(display);
+
+ /* Double checking that connection is good and operatable with xcb */
+ if (xcb_connection_has_error(conn))
errx(EXIT_FAILURE, "Could not connect to X11, maybe you need to set DISPLAY?");
+ /* When we cannot initially load the keymap, we better exit */
+ if (!load_keymap())
+ errx(EXIT_FAILURE, "Could not load keymap");
+
xinerama_init();
xinerama_query_screens();
@@ -710,13 +679,6 @@ int main(int argc, char *argv[]) {
grab_pointer_and_keyboard(conn, screen, cursor);
- symbols = xcb_key_symbols_alloc(conn);
- numlockmask = get_mod_mask(conn, symbols, XK_Num_Lock);
- capslockmask = get_mod_mask(conn, symbols, XK_Caps_Lock);
-
- DEBUG("numlock mask = %d\n", numlockmask);
- DEBUG("caps lock mask = %d\n", capslockmask);
-
if (dpms)
dpms_turn_off_screen(conn);