all repos — openbox @ 624ffe397bb52757f486208b754489cdb77fd923

openbox fork - make it a bit more like ryudo

obt/keyboard.c (raw)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-

   obt/keyboard.c for the Openbox window manager
   Copyright (c) 2007        Dana Jansens

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   See the COPYING file for a copy of the GNU General Public License.
*/

#include "obt/display.h"
#include "obt/keyboard.h"

#include <X11/Xlib.h>
#include <X11/keysym.h>

struct _ObtIC
{
    guint ref;
    XIC xic;
    Window client;
    Window focus;
};

/* These masks are constants and the modifier keys are bound to them as
   anyone sees fit:
        ShiftMask (1<<0), LockMask (1<<1), ControlMask (1<<2), Mod1Mask (1<<3),
        Mod2Mask (1<<4), Mod3Mask (1<<5), Mod4Mask (1<<6), Mod5Mask (1<<7)
*/
#define NUM_MASKS 8
#define ALL_MASKS 0xff /* an or'ing of all 8 keyboard masks */

/* Get the bitflag for the n'th modifier mask */
#define nth_mask(n) (1 << n)

static void set_modkey_mask(guchar mask, KeySym sym);
static void xim_init(void);
void obt_keyboard_shutdown();
void obt_keyboard_context_renew(ObtIC *ic);

static XModifierKeymap *modmap;
static KeySym *keymap;
static gint min_keycode, max_keycode, keysyms_per_keycode;
/* This is a bitmask of the different masks for each modifier key */
static guchar modkeys_keys[OBT_KEYBOARD_NUM_MODKEYS];

static gboolean alt_l = FALSE;
static gboolean meta_l = FALSE;
static gboolean super_l = FALSE;
static gboolean hyper_l = FALSE;

static gboolean started = FALSE;

static XIM xim = NULL;
static XIMStyle xim_style = 0;
static GSList *xic_all = NULL;

void obt_keyboard_reload(void)
{
    gint i, j, k;

    if (started) obt_keyboard_shutdown(); /* free stuff */
    started = TRUE;

    xim_init();

    /* reset the keys to not be bound to any masks */
    for (i = 0; i < OBT_KEYBOARD_NUM_MODKEYS; ++i)
        modkeys_keys[i] = 0;

    modmap = XGetModifierMapping(obt_display);
    /* note: modmap->max_keypermod can be 0 when there is no valid key layout
       available */

    XDisplayKeycodes(obt_display, &min_keycode, &max_keycode);
    keymap = XGetKeyboardMapping(obt_display, min_keycode,
                                 max_keycode - min_keycode + 1,
                                 &keysyms_per_keycode);

    alt_l = meta_l = super_l = hyper_l = FALSE;

    /* go through each of the modifier masks (eg ShiftMask, CapsMask...) */
    for (i = 0; i < NUM_MASKS; ++i) {
        /* go through each keycode that is bound to the mask */
        for (j = 0; j < modmap->max_keypermod; ++j) {
            KeySym sym;
            /* get a keycode that is bound to the mask (i) */
            KeyCode keycode = modmap->modifiermap[i*modmap->max_keypermod + j];
            if (keycode) {
                /* go through each keysym bound to the given keycode */
                for (k = 0; k < keysyms_per_keycode; ++k) {
                    sym = keymap[(keycode-min_keycode) * keysyms_per_keycode +
                                 k];
                    if (sym != NoSymbol) {
                        /* bind the key to the mask (e.g. Alt_L => Mod1Mask) */
                        set_modkey_mask(nth_mask(i), sym);
                    }
                }
            }
        }
    }

    /* CapsLock, Shift, and Control are special and hard-coded */
    modkeys_keys[OBT_KEYBOARD_MODKEY_CAPSLOCK] = LockMask;
    modkeys_keys[OBT_KEYBOARD_MODKEY_SHIFT] = ShiftMask;
    modkeys_keys[OBT_KEYBOARD_MODKEY_CONTROL] = ControlMask;
}

void obt_keyboard_shutdown(void)
{
    GSList *it;

    XFreeModifiermap(modmap);
    modmap = NULL;
    XFree(keymap);
    keymap = NULL;
    for (it = xic_all; it; it = g_slist_next(it)) {
        ObtIC* ic = it->data;
        if (ic->xic) {
            XDestroyIC(ic->xic);
            ic->xic = NULL;
        }
    }
    if (xim) XCloseIM(xim);
    xim = NULL;
    xim_style = 0;
    started = FALSE;
}

void xim_init(void)
{
    GSList *it;
    gchar *aname, *aclass;

    aname = g_strdup(g_get_prgname());
    if (!aname) aname = g_strdup("obt");

    aclass = g_strdup(aname);
    if (g_ascii_islower(aclass[0]))
        aclass[0] = g_ascii_toupper(aclass[0]);

    xim = XOpenIM(obt_display, NULL, aname, aclass);

    if (!xim)
        g_message("Failed to open an Input Method");
    else {
        XIMStyles *xim_styles = NULL;
        char *r;

        /* get the input method styles */
        r = XGetIMValues(xim, XNQueryInputStyle, &xim_styles, NULL);
        if (r || !xim_styles)
            g_message("Input Method does not support any styles");
        if (xim_styles) {
            int i;

            /* find a style that doesnt need preedit or status */
            for (i = 0; i < xim_styles->count_styles; ++i) {
                if (xim_styles->supported_styles[i] == 
                    (XIMPreeditNothing | XIMStatusNothing))
                {
                    xim_style = xim_styles->supported_styles[i];
                    break;
                }
            }
            XFree(xim_styles);
        }

        if (!xim_style) {
            g_message("Input Method does not support a usable style");

            XCloseIM(xim);
            xim = NULL;
        }
    }

    /* any existing contexts need to be recreated for the new input method */
    for (it = xic_all; it; it = g_slist_next(it))
        obt_keyboard_context_renew(it->data);

    g_free(aclass);
    g_free(aname);
}

ObtModkeysKey obt_keyboard_keyevent_to_modkey(XEvent *e)
{
    KeySym sym;

    g_return_val_if_fail(e->type == KeyPress || e->type == KeyRelease,
                         OBT_KEYBOARD_MODKEY_NONE);

    XLookupString(&e->xkey, NULL, 0, &sym, NULL);

    switch (sym) {
    case XK_Num_Lock: return OBT_KEYBOARD_MODKEY_NUMLOCK;
    case XK_Scroll_Lock: return OBT_KEYBOARD_MODKEY_SCROLLLOCK;
    case XK_Caps_Lock: return OBT_KEYBOARD_MODKEY_SHIFT;
    case XK_Alt_L:
    case XK_Alt_R: return OBT_KEYBOARD_MODKEY_ALT;
    case XK_Super_L:
    case XK_Super_R: return OBT_KEYBOARD_MODKEY_SUPER;
    case XK_Hyper_L:
    case XK_Hyper_R: return OBT_KEYBOARD_MODKEY_SUPER;
    case XK_Meta_L:
    case XK_Meta_R: return OBT_KEYBOARD_MODKEY_SUPER;
    case XK_Control_L:
    case XK_Control_R: return OBT_KEYBOARD_MODKEY_CONTROL;
    case XK_Shift_L:
    case XK_Shift_R: return OBT_KEYBOARD_MODKEY_SHIFT;
    default: return OBT_KEYBOARD_MODKEY_NONE;
    }
}

guint obt_keyboard_keyevent_to_modmask(XEvent *e)
{
    g_return_val_if_fail(e->type == KeyPress || e->type == KeyRelease, 0);

    return obt_keyboard_modkey_to_modmask(obt_keyboard_keyevent_to_modkey(e));
}

guint obt_keyboard_only_modmasks(guint mask)
{
    mask &= ALL_MASKS;
    /* strip off these lock keys. they shouldn't affect key bindings */
    mask &= ~LockMask; /* use the LockMask, not what capslock is bound to,
                          because you could bind it to something else and it
                          should work as that modifier then. i think capslock
                          is weird in xkb. */
    mask &= ~obt_keyboard_modkey_to_modmask(OBT_KEYBOARD_MODKEY_NUMLOCK);
    mask &= ~obt_keyboard_modkey_to_modmask(OBT_KEYBOARD_MODKEY_SCROLLLOCK);
    return mask;
}

guint obt_keyboard_modkey_to_modmask(ObtModkeysKey key)
{
    return modkeys_keys[key];
}

static void set_modkey_mask(guchar mask, KeySym sym)
{
    /* find what key this is, and bind it to the mask */

    if (sym == XK_Num_Lock)
        modkeys_keys[OBT_KEYBOARD_MODKEY_NUMLOCK] |= mask;
    else if (sym == XK_Scroll_Lock)
        modkeys_keys[OBT_KEYBOARD_MODKEY_SCROLLLOCK] |= mask;

    else if (sym == XK_Super_L && super_l)
        modkeys_keys[OBT_KEYBOARD_MODKEY_SUPER] |= mask;
    else if (sym == XK_Super_L && !super_l)
        /* left takes precident over right, so erase any masks the right
           key may have set */
        modkeys_keys[OBT_KEYBOARD_MODKEY_SUPER] = mask, super_l = TRUE;
    else if (sym == XK_Super_R && !super_l)
        modkeys_keys[OBT_KEYBOARD_MODKEY_SUPER] |= mask;

    else if (sym == XK_Hyper_L && hyper_l)
        modkeys_keys[OBT_KEYBOARD_MODKEY_HYPER] |= mask;
    else if (sym == XK_Hyper_L && !hyper_l)
        modkeys_keys[OBT_KEYBOARD_MODKEY_HYPER] = mask, hyper_l = TRUE;
    else if (sym == XK_Hyper_R && !hyper_l)
        modkeys_keys[OBT_KEYBOARD_MODKEY_HYPER] |= mask;

    else if (sym == XK_Alt_L && alt_l)
        modkeys_keys[OBT_KEYBOARD_MODKEY_ALT] |= mask;
    else if (sym == XK_Alt_L && !alt_l)
        modkeys_keys[OBT_KEYBOARD_MODKEY_ALT] = mask, alt_l = TRUE;
    else if (sym == XK_Alt_R && !alt_l)
        modkeys_keys[OBT_KEYBOARD_MODKEY_ALT] |= mask;

    else if (sym == XK_Meta_L && meta_l)
        modkeys_keys[OBT_KEYBOARD_MODKEY_META] |= mask;
    else if (sym == XK_Meta_L && !meta_l)
        modkeys_keys[OBT_KEYBOARD_MODKEY_META] = mask, meta_l = TRUE;
    else if (sym == XK_Meta_R && !meta_l)
        modkeys_keys[OBT_KEYBOARD_MODKEY_META] |= mask;

    /* CapsLock, Shift, and Control are special and hard-coded */
}

KeyCode* obt_keyboard_keysym_to_keycode(KeySym sym)
{
    KeyCode *ret;
    gint i, j, n;

    ret = g_new(KeyCode, 1);
    n = 0;
    ret[n] = 0;

    /* go through each keycode and look for the keysym */
    for (i = min_keycode; i <= max_keycode; ++i)
        for (j = 0; j < keysyms_per_keycode; ++j)
            if (sym == keymap[(i-min_keycode) * keysyms_per_keycode + j]) {
                ret = g_renew(KeyCode, ret, ++n + 1);
                ret[n-1] = i;
                ret[n] = 0;
            }
    return ret;
}

gunichar obt_keyboard_keypress_to_unichar(ObtIC *ic, XEvent *ev)
{
    gunichar unikey = 0;
    KeySym sym;
    Status status;
    gchar *buf, fixbuf[4]; /* 4 is enough for a utf8 char */
    gint len, bufsz;
    gboolean got_string = FALSE;

    g_return_val_if_fail(ev->type == KeyPress, 0);

    if (!ic)
        g_warning("Using obt_keyboard_keypress_to_unichar() without an "
                  "Input Context.  No i18n support!");

    if (ic && ic->xic) {
        buf = fixbuf;
        bufsz = sizeof(fixbuf);

#ifdef X_HAVE_UTF8_STRING
        len = Xutf8LookupString(ic->xic, &ev->xkey, buf, bufsz, &sym, &status);
#else
        len = XmbLookupString(ic->xic, &ev->xkey, buf, bufsz, &sym, &status);
#endif

        if (status == XBufferOverflow) {
            buf = g_new(char, len);
            bufsz = len;

#ifdef X_HAVE_UTF8_STRING
            len = Xutf8LookupString(ic->xic, &ev->xkey, buf, bufsz, &sym,
                                    &status);
#else
            len = XmbLookupString(ic->xic, &ev->xkey, buf, bufsz, &sym,
                                  &status);
#endif
        }

        if ((status == XLookupChars || status == XLookupBoth)) {
            if ((guchar)buf[0] >= 32) { /* not an ascii control character */
#ifndef X_HAVE_UTF8_STRING
                /* convert to utf8 */
                gchar *buf2 = buf;
                buf = g_locale_to_utf8(buf2, r, NULL, NULL, NULL);
                g_free(buf2);
#endif

                got_string = TRUE;
            }
        }
        else if (status == XLookupKeySym)
            /* this key doesn't have a text representation, it is a command
               key of some sort */;
        else
            g_message("Bad keycode lookup. Keysym 0x%x Status: %s\n",
                      (guint) sym,
                      (status == XBufferOverflow ? "BufferOverflow" :
                       status == XLookupNone ? "XLookupNone" :
                       status == XLookupKeySym ? "XLookupKeySym" :
                       "Unknown status"));
    }
    else {
        buf = fixbuf;
        bufsz = sizeof(fixbuf);
        len = XLookupString(&ev->xkey, buf, bufsz, &sym, NULL);
        if ((guchar)buf[0] >= 32) /* not an ascii control character */
            got_string = TRUE;
    }

    if (got_string) {
        gunichar u = g_utf8_get_char_validated(buf, len);
        if (u && u != (gunichar)-1 && u != (gunichar)-2)
            unikey = u;
    }

    if (buf != fixbuf) g_free(buf);

    return unikey;
}

KeySym obt_keyboard_keypress_to_keysym(XEvent *ev)
{
    KeySym sym;
    gint r;

    g_return_val_if_fail(ev->type == KeyPress, None);

    sym = None;
    r = XLookupString(&ev->xkey, NULL, 0, &sym, NULL);
    return sym;
}

void obt_keyboard_context_renew(ObtIC *ic)
{
    if (xim) {
        ic->xic = XCreateIC(xim,
                            XNInputStyle, xim_style,
                            XNClientWindow, ic->client,
                            XNFocusWindow, ic->focus,
                            NULL);
        if (!ic->xic)
            g_message("Error creating Input Context for window 0x%x 0x%x\n",
                      (guint)ic->client, (guint)ic->focus);
    }
}

ObtIC* obt_keyboard_context_new(Window client, Window focus)
{
    ObtIC *ic;

    g_return_val_if_fail(client != None && focus != None, NULL);

    ic = g_slice_new(ObtIC);
    ic->ref = 1;
    ic->client = client;
    ic->focus = focus;
    ic->xic = NULL;

    obt_keyboard_context_renew(ic);
    xic_all = g_slist_prepend(xic_all, ic);

    return ic;
}

void obt_keyboard_context_ref(ObtIC *ic)
{
    ++ic->ref;
}

void obt_keyboard_context_unref(ObtIC *ic)
{
    if (--ic->ref < 1) {
        xic_all = g_slist_remove(xic_all, ic);
        XDestroyIC(ic->xic);
        g_slice_free(ObtIC, ic);
    }
}