1
# This file is part of Parti.
2
# Copyright (C) 2008, 2009 Nathaniel Smith <njs@pobox.com>
3
# Parti is released under the terms of the GNU GPL v2, or, at your option, any
4
# later version. See the file COPYING for details.
8
from wimpiggy.util import one_arg_signal
9
from wimpiggy.error import *
10
from wimpiggy.lowlevel import (get_display_for,
11
get_modifier_map, grab_key, ungrab_all_keys,
12
add_event_receiver, remove_event_receiver)
14
from wimpiggy.log import Logger
17
class HotkeyManager(gobject.GObject):
19
"hotkey": (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_DETAILED,
20
gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
22
"wimpiggy-key-press-event": one_arg_signal,
25
def __init__(self, window):
26
gobject.GObject.__init__(self)
30
disp = get_display_for(self.window)
31
self.keymap = gtk.gdk.keymap_get_for_display(disp)
32
self.keymap_id = self.keymap.connect("keys-changed",
36
add_event_receiver(self.window, self)
39
self.keymap.disconnect(self.keymap_id)
43
trap.swallow(self.unbind_all)
44
remove_event_receiver(self.window, self)
47
def _keys_changed(self, *args):
48
self.modifier_map = grok_modifier_map(self.window)
49
self.nuisances = set()
51
if not(i & ~self.modifier_map["nuisance"]):
53
trap.swallow(self._rebind)
55
def _rebind(self, *args):
57
gtk.gdk.x11_grab_server()
61
gtk.gdk.x11_ungrab_server()
63
def _unbind_all(self):
64
ungrab_all_keys(self.window)
67
self.normalized_hotkeys = {}
68
for hotkey, target in self.hotkeys.iteritems():
69
modifier_mask, keycodes = parse_key(hotkey, self.keymap,
71
for keycode in keycodes:
72
# Claim a passive grab on all the different forms of this key
73
for nuisance_mask in self.nuisances:
74
grab_key(self.window, keycode,
75
modifier_mask | nuisance_mask)
76
# Save off the normalized form to make it easy to lookup later
77
# when we see the key appear
78
unparsed = unparse_key(modifier_mask, keycode,
79
self.keymap, self.modifier_map)
80
self.normalized_hotkeys[unparsed] = target
82
def do_wimpiggy_key_press_event(self, event):
83
log("got hotkey event, maybe")
84
unparsed = unparse_key(event.state, event.hardware_keycode,
85
self.keymap, self.modifier_map)
86
log("unparsed = %s", unparsed)
87
if unparsed in self.normalized_hotkeys:
88
target = self.normalized_hotkeys[unparsed]
89
self.emit("hotkey::%s" % (target,), target)
91
def add_hotkeys(self, hotkeys):
92
self.hotkeys.update(hotkeys)
95
def del_hotkeys(self, keys):
101
gobject.type_register(HotkeyManager)
104
def grok_modifier_map(display_source):
105
"""Return an dict mapping modifier names to corresponding X modifier
124
"Scroll_Lock": "scroll",
136
disp = get_display_for(display_source)
137
(max_keypermod, keycodes) = get_modifier_map(disp)
138
assert len(keycodes) == 8 * max_keypermod
139
keymap = gtk.gdk.keymap_get_for_display(disp)
141
for j in range(max_keypermod):
142
keycode = keycodes[i * max_keypermod + j]
144
entries = keymap.get_entries_for_keycode(keycode)
146
# This keycode has no entry in the keymap:
148
for (keyval, _, _, _) in entries:
149
keyval_name = gtk.gdk.keyval_name(keyval)
150
if keyval_name in meanings:
151
modifier_map[meanings[keyval_name]] |= (1 << i)
152
modifier_map["nuisance"] = (modifier_map["lock"]
153
| modifier_map["scroll"]
154
| modifier_map["num"])
157
def parse_key(name, keymap, modifier_map):
159
name = name.strip().lower()
160
while name.startswith("<"):
161
ket = name.index(">")
162
modifier_name = name[1:ket]
163
extra_mask = modifier_map[modifier_name]
165
modifier_mask |= extra_mask
169
keycodes.append(int(name))
171
keyval = gtk.gdk.keyval_from_name(name)
172
entries = keymap.get_entries_for_keyval(keyval)
173
for entry in entries:
174
keycodes.append(entry[0])
175
modifier_mask &= ~modifier_map["nuisance"]
176
return (modifier_mask, keycodes)
178
def unparse_key(modifier_mask, keycode, keymap, modifier_map):
180
modifier_mask &= ~modifier_map["nuisance"]
182
keyval_entries = keymap.get_entries_for_keycode(keycode)
183
if keyval_entries is not None:
184
for keyval_entry in keyval_entries:
186
name = gtk.gdk.keyval_name(keyval_entry[0])
191
mods = modifier_map.keys()
192
def sort_modn_to_end(a, b):
193
a = (a.startswith("mod"), a)
194
b = (b.startswith("mod"), b)
196
mods.sort(sort_modn_to_end)
198
mask = modifier_map[mod]
199
if mask & modifier_mask:
200
name = "<%s>%s" % (mod, name)
201
modifier_mask &= ~mask