summaryrefslogtreecommitdiff
path: root/ui/gtk3/keybindingmanager.vala
blob: dd3c7bdb6f9c64541e22c83e5c7bc22c3be43be7 (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
/* vim:set et sts=4 sw=4:
valac --pkg gtk+-2.0 --pkg x11 --pkg gdk-x11-2.0 --pkg gee-1.0 keybinding-manager.vala
*/

/**
 * This class is in charge to grab keybindings on the X11 display
 * and filter X11-events and passing on such events to the registed
 * handler methods.
 *
 * @author Oliver Sauder <os@esite.ch>
 */

using Gdk;
using GLib;
using Gtk;
using X;

extern bool grab_keycode (Gdk.Display display,
                          uint keyval,
                          uint modifiers);

extern bool ungrab_keycode (Gdk.Display display,
                            uint keyval,
                            uint modifiers);

public class KeybindingManager : GLib.Object {
    /**
     * list of binded keybindings
     */
    private GLib.List<Keybinding> m_bindings = new GLib.List<Keybinding>();

    private static KeybindingManager m_instance = null;

    public static const uint MODIFIER_FILTER =
        Gdk.ModifierType.MODIFIER_MASK & ~(
        Gdk.ModifierType.MOD2_MASK |
        Gdk.ModifierType.LOCK_MASK |
        Gdk.ModifierType.MOD4_MASK |
        Gdk.ModifierType.MOD5_MASK |
        Gdk.ModifierType.BUTTON1_MASK |
        Gdk.ModifierType.BUTTON2_MASK |
        Gdk.ModifierType.BUTTON3_MASK |
        Gdk.ModifierType.BUTTON4_MASK |
        Gdk.ModifierType.BUTTON5_MASK);

    /**
     * Helper class to store keybinding
     */
    private class Keybinding {
        public Keybinding(string accelerator,
                          uint keysym,
                          Gdk.ModifierType modifiers,
                          KeybindingHandlerFunc handler) {
            this.accelerator = accelerator;
            this.keysym = keysym;
            this.modifiers = modifiers;
            this.handler = handler;
        }

        public string accelerator { get; set; }
        public uint keysym { get; set; }
        public Gdk.ModifierType modifiers { get; set; }
        public unowned KeybindingHandlerFunc handler { get; set; }
    }

    /**
     * Keybinding func needed to bind key to handler
     *
     * @param event passing on gdk event
     */
    public delegate void KeybindingHandlerFunc(Gdk.Event event);


    private  KeybindingManager() {
        Gdk.Event.handler_set(event_handler);
    }

    /**
     * Bind accelerator to given handler
     *
     * @param accelerator accelerator parsable by Gtk.accelerator_parse
     * @param handler handler called when given accelerator is pressed
     */
    public bool bind(string accelerator,
                     KeybindingHandlerFunc handler) {
        debug("Binding key " + accelerator);

        // convert accelerator
        uint keysym;
        Gdk.ModifierType modifiers;
        Gtk.accelerator_parse(accelerator, out keysym, out modifiers);

        unowned X.Display display = Gdk.x11_get_default_xdisplay();

        int keycode = display.keysym_to_keycode(keysym);

        if (keycode == 0)
            return false;

        grab_keycode (Gdk.Display.get_default(), keysym, modifiers);

        // store binding
        Keybinding binding = new Keybinding(accelerator,
                                            keysym, modifiers,
                                            handler);
        m_bindings.append(binding);

        debug("Successfully binded key " + accelerator);
        return true;
    }

    /**
     * Unbind given accelerator.
     *
     * @param accelerator accelerator parsable by Gtk.accelerator_parse
     */
    public void unbind (string accelerator) {
        debug("Unbinding key " + accelerator);

        // unbind all keys with given accelerator
        GLib.List<Keybinding> remove_bindings = new GLib.List<Keybinding>();
        foreach(Keybinding binding in m_bindings) {
            if(str_equal(accelerator, binding.accelerator)) {
                ungrab_keycode (Gdk.Display.get_default(),
                                binding.keysym,
                                binding.modifiers);
                remove_bindings.append(binding);
            }
        }

        // remove unbinded keys
        foreach (Keybinding binding in remove_bindings)
            m_bindings.remove (binding);
    }

    public static KeybindingManager get_instance () {
        if (m_instance == null)
            m_instance = new KeybindingManager ();
        return m_instance;
    }

    public static uint get_primary_modifier (uint binding_mask) {
        const uint[] masks = {
            Gdk.ModifierType.MOD5_MASK,
            Gdk.ModifierType.MOD4_MASK,
            Gdk.ModifierType.MOD3_MASK,
            Gdk.ModifierType.MOD2_MASK,
            Gdk.ModifierType.MOD1_MASK,
            Gdk.ModifierType.CONTROL_MASK,
            Gdk.ModifierType.SHIFT_MASK,
            Gdk.ModifierType.LOCK_MASK
        };
        foreach (uint mask in masks) {
            if ((binding_mask & mask) == mask)
                return mask;
        }
        return 0;
    }

    public static bool primary_modifier_still_pressed(Gdk.Event event,
                                                      uint primary_modifier) {
        Gdk.EventKey keyevent = event.key;
        if (primary_modifier == 0)
            return false;

        Gdk.Device device = event.get_device();
        Gdk.Device pointer;
        if (device.get_source() == Gdk.InputSource.KEYBOARD)
            pointer = device.get_associated_device();
        else
            pointer = device;

        uint modifier = 0;
        pointer.get_state(keyevent.window, null, out modifier);
        if ((primary_modifier & modifier) == primary_modifier)
            return true;

        return false;
    }

    public static uint keyval_to_modifier (uint keyval) {
        switch(keyval) {
            case 0xffe3: /* Control_L */
            case 0xffe4: /* Control_R */
                return Gdk.ModifierType.CONTROL_MASK;
            case 0xffe1: /* Shift_L */
            case 0xffe2: /* Shift_R */
                return Gdk.ModifierType.SHIFT_MASK;
            case 0xffe5: /* Caps_Lock */
                return Gdk.ModifierType.LOCK_MASK;
            case 0xffe9: /* Alt_L */
            case 0xffea: /* Alt_R */
                return Gdk.ModifierType.MOD1_MASK;
            case 0xffe7: /* Meta_L */
            case 0xffe8: /* Meta_R */
                return Gdk.ModifierType.META_MASK;
            case 0xffeb: /* Super_L */
            case 0xffec: /* Super_R */
                return Gdk.ModifierType.SUPER_MASK;
            case 0xffed: /* Hyper_L */
            case 0xffee: /* Hyper_R */
                return Gdk.ModifierType.HYPER_MASK;
            default:
                return 0;
        }
    }

    private void event_handler(Gdk.Event event) {
        do {
            if (event.any.window != Gdk.get_default_root_window())
                break;

            if (event.type == Gdk.EventType.KEY_PRESS) {
                uint modifiers = event.key.state & MODIFIER_FILTER;
                foreach (var binding in m_bindings) {
                    if (event.key.keyval != binding.keysym ||
                        modifiers != binding.modifiers)
                        continue;
                    binding.handler(event);
                    return;
                }
            }
        } while (false);
        Gtk.main_do_event(event);
    }
}

/*
public static int main (string[] args)
{
    Gtk.init (ref args);

    KeybindingManager manager = new KeybindingManager();
    manager.bind("<Ctrl><Alt>V", test);

    Gtk.main ();
    return 0;
}

private static void test()
{
    debug("hotkey pressed");
}
*/