summaryrefslogtreecommitdiff
path: root/plugins/common/msd-keygrab.c
blob: d112ae9a02e8aa3be9b03fa32d0be5b559c7bf75 (plain)
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
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
 *
 * Copyright (C) 2001-2003 Bastien Nocera <hadess@hadess.net>
 * Copyright (C) 2006-2007 William Jon McCann <mccann@jhu.edu>
 * Copyright (C) 2008 Jens Granseuer <jensgr@gmx.net>
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "config.h"

#include <gdk/gdk.h>
#include <gdk/gdkx.h>
#ifdef HAVE_X11_EXTENSIONS_XKB_H
#include <X11/XKBlib.h>
#include <X11/extensions/XKB.h>
#include <gdk/gdkkeysyms.h>
#endif

#include "eggaccelerators.h"

#include "msd-keygrab.h"

/* these are the mods whose combinations are ignored by the keygrabbing code */
static GdkModifierType msd_ignored_mods = 0;

/* these are the ones we actually use for global keys, we always only check
 * for these set */
static GdkModifierType msd_used_mods = 0;

static void
setup_modifiers (void)
{
        if (msd_used_mods == 0 || msd_ignored_mods == 0) {
                GdkModifierType dynmods;

                /* default modifiers */
                msd_ignored_mods = \
                        0x2000 /*Xkb modifier*/ | GDK_LOCK_MASK | GDK_HYPER_MASK;
		msd_used_mods = \
                        GDK_SHIFT_MASK | GDK_CONTROL_MASK |\
                        GDK_MOD1_MASK | GDK_MOD2_MASK | GDK_MOD3_MASK | GDK_MOD4_MASK |\
                        GDK_MOD5_MASK | GDK_SUPER_MASK | GDK_META_MASK;

                /* NumLock can be assigned to varying keys so we need to
                 * resolve and ignore it specially */
                dynmods = 0;
                egg_keymap_resolve_virtual_modifiers (gdk_keymap_get_for_display (gdk_display_get_default ()),
                                                      EGG_VIRTUAL_NUM_LOCK_MASK,
                                                      &dynmods);

                msd_ignored_mods |= dynmods;
                msd_used_mods &= ~dynmods;
	}
}

static void
grab_key_real (guint      keycode,
               GdkWindow *root,
               gboolean   grab,
               int        mask)
{
        if (grab) {
                XGrabKey (GDK_DISPLAY_XDISPLAY(gdk_display_get_default()),
                          keycode,
                          mask,
                          GDK_WINDOW_XID (root),
                          True,
                          GrabModeAsync,
                          GrabModeAsync);
        } else {
                XUngrabKey (GDK_DISPLAY_XDISPLAY(gdk_display_get_default()),
                            keycode,
                            mask,
                            GDK_WINDOW_XID (root));
        }
}

/* Grab the key. In order to ignore MSD_IGNORED_MODS we need to grab
 * all combinations of the ignored modifiers and those actually used
 * for the binding (if any).
 *
 * inspired by all_combinations from mate-panel/mate-panel/global-keys.c
 *
 * This may generate X errors.  The correct way to use this is like:
 *
 *        gdk_error_trap_push ();
 *
 *        grab_key_unsafe (key, grab, screens);
 *
 *        gdk_flush ();
 *        if (gdk_error_trap_pop ())
 *                g_warning ("Grab failed, another application may already have access to key '%u'",
 *                           key->keycode);
 *
 * This is not done in the function itself, to allow doing multiple grab_key
 * operations with one flush only.
 */
#define N_BITS 32
void
grab_key_unsafe (Key                 *key,
                 gboolean             grab,
                 GSList              *screens)
{
        int   indexes[N_BITS]; /* indexes of bits we need to flip */
        int   i;
        int   bit;
        int   bits_set_cnt;
        int   uppervalue;
        guint mask;

        setup_modifiers ();

        mask = msd_ignored_mods & ~key->state & GDK_MODIFIER_MASK;

        bit = 0;
        /* store the indexes of all set bits in mask in the array */
        for (i = 0; mask; ++i, mask >>= 1) {
                if (mask & 0x1) {
                        indexes[bit++] = i;
                }
        }

        bits_set_cnt = bit;

        uppervalue = 1 << bits_set_cnt;
        /* grab all possible modifier combinations for our mask */
        for (i = 0; i < uppervalue; ++i) {
                GSList *l;
                int     j;
                int     result = 0;

                /* map bits in the counter to those in the mask */
                for (j = 0; j < bits_set_cnt; ++j) {
                        if (i & (1 << j)) {
                                result |= (1 << indexes[j]);
                        }
                }

                for (l = screens; l; l = l->next) {
                        GdkScreen *screen = l->data;
                        guint *code;

                        for (code = key->keycodes; *code; ++code) {
                                grab_key_real (*code,
                                               gdk_screen_get_root_window (screen),
                                               grab,
                                               result | key->state);
                        }
                }
        }
}

static gboolean
have_xkb (Display *dpy)
{
	static int have_xkb = -1;

	if (have_xkb == -1) {
#ifdef HAVE_X11_EXTENSIONS_XKB_H
		int opcode, error_base, major, minor, xkb_event_base;

		have_xkb = XkbQueryExtension (dpy,
					      &opcode,
					      &xkb_event_base,
					      &error_base,
					      &major,
					      &minor)
			&& XkbUseExtension (dpy, &major, &minor);
#else
		have_xkb = 0;
#endif
	}

	return have_xkb;
}

gboolean
key_uses_keycode (const Key *key, guint keycode)
{
	if (key->keycodes != NULL) {
		guint *c;

		for (c = key->keycodes; *c; ++c) {
			if (*c == keycode)
				return TRUE;
		}
	}
	return FALSE;
}

gboolean
match_key (Key *key, XEvent *event)
{
	guint keyval;
	GdkModifierType consumed;
	gint group;

	if (key == NULL)
		return FALSE;

	setup_modifiers ();

#ifdef HAVE_X11_EXTENSIONS_XKB_H
	if (have_xkb (event->xkey.display))
		group = XkbGroupForCoreState (event->xkey.state);
	else
#endif
		group = (event->xkey.state & GDK_KEY_Mode_switch) ? 1 : 0;

	/* Check if we find a keysym that matches our current state */
	if (gdk_keymap_translate_keyboard_state (gdk_keymap_get_for_display (gdk_display_get_default ()), event->xkey.keycode,
					     event->xkey.state, group,
					     &keyval, NULL, NULL, &consumed)) {
		guint lower, upper;

		gdk_keyval_convert_case (keyval, &lower, &upper);

		/* If we are checking against the lower version of the
		 * keysym, we might need the Shift state for matching,
		 * so remove it from the consumed modifiers */
		if (lower == key->keysym)
			consumed &= ~GDK_SHIFT_MASK;

		return ((lower == key->keysym || upper == key->keysym)
			&& (event->xkey.state & ~consumed & msd_used_mods) == key->state);
	}

	/* The key we passed doesn't have a keysym, so try with just the keycode */
        return (key->state == (event->xkey.state & msd_used_mods)
                && key_uses_keycode (key, event->xkey.keycode));
}