Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/x11/server_keyboard_config.py : 42%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2# This file is part of Xpra.
3# Copyright (C) 2011 Serviware (Arthur Huillet, <ahuillet@serviware.com>)
4# Copyright (C) 2010-2019 Antoine Martin <antoine@xpra.org>
5# Copyright (C) 2008 Nathaniel Smith <njs@pobox.com>
6# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
7# later version. See the file COPYING for details.
9from gi.repository import Gdk
11from xpra.util import csv, envbool
12from xpra.os_util import bytestostr
13from xpra.gtk_common.keymap import get_gtk_keymap
14from xpra.gtk_common.gtk_util import get_default_root_window
15from xpra.gtk_common.error import xsync, xlog
16from xpra.keyboard.mask import DEFAULT_MODIFIER_NUISANCE, DEFAULT_MODIFIER_NUISANCE_KEYNAMES, mask_to_names
17from xpra.server.keyboard_config_base import KeyboardConfigBase
18from xpra.x11.gtk_x11.keys import grok_modifier_map
19from xpra.x11.xkbhelper import (
20 do_set_keymap, set_all_keycodes, set_keycode_translation,
21 get_modifiers_from_meanings, get_modifiers_from_keycodes,
22 clear_modifiers, set_modifiers, map_missing_modifiers,
23 clean_keyboard_state, get_keycode_mappings,
24 DEBUG_KEYSYMS,
25 )
26from xpra.x11.bindings.keyboard_bindings import X11KeyboardBindings #@UnresolvedImport
27from xpra.log import Logger
29log = Logger("keyboard")
31X11Keyboard = X11KeyboardBindings()
33MAP_MISSING_MODIFIERS = envbool("XPRA_MAP_MISSING_MODIFIERS", True)
34SHIFT_LOCK = envbool("XPRA_SHIFT_LOCK", False)
36ALL_X11_MODIFIERS = {
37 "shift" : 0,
38 "lock" : 1,
39 "control" : 2,
40 "mod1" : 3,
41 "mod2" : 4,
42 "mod3" : 5,
43 "mod4" : 6,
44 "mod5" : 7
45 }
47class KeyboardConfig(KeyboardConfigBase):
48 def __init__(self):
49 KeyboardConfigBase.__init__(self)
50 self.xkbmap_raw = False
51 self.xkbmap_query = None
52 self.xkbmap_query_struct = None
53 self.xkbmap_mod_meanings = {}
54 self.xkbmap_mod_managed = []
55 self.xkbmap_mod_pointermissing = []
56 self.xkbmap_mod_nuisance = set(DEFAULT_MODIFIER_NUISANCE)
57 self.xkbmap_keycodes = ()
58 self.xkbmap_x11_keycodes = []
59 self.xkbmap_layout = None
60 self.xkbmap_variant = None
61 self.xkbmap_options = None
62 self.xkbmap_layout_groups = False
64 #this is shared between clients!
65 self.keys_pressed = {}
66 #these are derived by calling set_keymap:
67 self.keynames_for_mod = {}
68 self.keycode_translation = {}
69 self.keycodes_for_modifier_keynames = {}
70 self.modifier_client_keycodes = {}
71 self.compute_modifier_map()
72 self.modifiers_filter = []
73 self.keycode_mappings = {}
75 def __repr__(self):
76 return "KeyboardConfig(%s / %s / %s)" % (self.xkbmap_layout, self.xkbmap_variant, self.xkbmap_options)
78 def get_info(self) -> dict:
79 info = KeyboardConfigBase.get_info(self)
80 #keycodes:
81 if self.keycode_translation:
82 ksinfo = info.setdefault("keysym", {})
83 kssinf = info.setdefault("keysyms", {})
84 kcinfo = info.setdefault("keycode", {})
85 for kc, keycode in self.keycode_translation.items():
86 if isinstance(kc, tuple):
87 a, b = kc
88 if isinstance(a, int):
89 client_keycode, keysym = a, b
90 ksinfo.setdefault(keysym, {})[client_keycode] = keycode
91 kcinfo.setdefault(client_keycode, {})[keysym] = keycode
92 elif isinstance(b, int):
93 keysym, index = a, b
94 kssinf.setdefault(keycode, []).append((index, keysym))
95 else:
96 kcinfo[kc] = keycode
97 if self.xkbmap_keycodes:
98 i = 0
99 kminfo = info.setdefault("keymap", {})
100 for keyval, name, keycode, group, level in self.xkbmap_keycodes:
101 kminfo[i] = (keyval, name, keycode, group, level)
102 i += 1
103 #modifiers:
104 modinfo = {}
105 modsinfo = {}
106 modinfo["filter"] = self.modifiers_filter
107 if self.modifier_client_keycodes:
108 for mod, keys in self.modifier_client_keycodes.items():
109 modinfo.setdefault(mod, {})["client_keys"] = keys
110 if self.keynames_for_mod:
111 for mod, keys in self.keynames_for_mod.items():
112 modinfo.setdefault(mod, {})["keys"] = tuple(keys)
113 if self.keycodes_for_modifier_keynames:
114 for mod, keys in self.keycodes_for_modifier_keynames.items():
115 modinfo.setdefault(mod, {})["keycodes"] = tuple(keys)
116 if self.xkbmap_mod_meanings:
117 for mod, mod_name in self.xkbmap_mod_meanings.items():
118 modinfo[mod] = mod_name
119 info["x11_keycode"] = self.xkbmap_x11_keycodes
120 for x in ("layout", "variant", "mod_managed", "mod_pointermissing", "raw", "layout_groups"):
121 v = getattr(self, "xkbmap_%s" % x)
122 if v:
123 info[x] = v
124 modsinfo["nuisance"] = tuple(self.xkbmap_mod_nuisance or [])
125 info["modifier"] = modinfo
126 info["modifiers"] = modsinfo
127 #this would need to always run in the UI thread:
128 #info["state"] = {
129 # "modifiers" : self.get_current_mask(),
130 # }
131 log("keyboard info: %s", info)
132 return info
135 def parse_options(self, props):
136 """ used by both process_hello and process_keymap
137 to set the keyboard attributes """
138 super().parse_options(props)
139 modded = {}
140 def parse_option(name, parse_fn, *parse_args):
141 prop = "xkbmap_%s" % name
142 cv = getattr(self, prop)
143 nv = parse_fn(prop, *parse_args)
144 if cv!=nv:
145 setattr(self, prop, nv)
146 modded[prop] = nv
147 #plain strings:
148 for x in ("query", ):
149 parse_option(x, props.strget)
150 #lists:
151 parse_option("keycodes", props.tupleget)
152 #dicts:
153 for x in ("mod_meanings", "x11_keycodes", "query_struct"):
154 parse_option(x, props.dictget, {})
155 #lists of strings:
156 for x in ("mod_managed", "mod_pointermissing"):
157 parse_option(x, props.strtupleget)
158 parse_option("raw", props.boolget)
159 #older clients don't specify if they support layout groups safely
160 #(MS Windows clients used base-1)
161 #so only enable it by default for X11 clients
162 parse_option("layout_groups", props.boolget, bool(self.xkbmap_query or self.xkbmap_query_struct))
163 log("assign_keymap_options(..) modified %s", modded)
164 return len(modded)>0
167 def get_hash(self):
168 """
169 This hash will be different whenever the keyboard configuration changes.
170 """
171 import hashlib
172 m = hashlib.sha1()
173 def hashadd(v):
174 m.update(("/%s" % str(v)).encode("utf8"))
175 m.update(KeyboardConfigBase.get_hash(self))
176 for x in (self.xkbmap_query, self.xkbmap_raw, \
177 self.xkbmap_mod_meanings, self.xkbmap_mod_pointermissing, \
178 self.xkbmap_keycodes, self.xkbmap_x11_keycodes):
179 hashadd(x)
180 if self.xkbmap_query_struct:
181 #flatten the dict in a predicatable order:
182 for k in sorted(self.xkbmap_query_struct.keys()):
183 hashadd(self.xkbmap_query_struct.get(k))
184 return "%s/%s/%s/%s" % (self.xkbmap_layout, self.xkbmap_variant, self.xkbmap_options, m.hexdigest())
186 def compute_modifiers(self):
187 if self.xkbmap_raw:
188 with xsync:
189 mod_mappings = X11Keyboard.get_modifier_mappings()
190 self.xkbmap_mod_meanings = {}
191 self.keycodes_for_modifier_keynames = {}
192 for mod, mod_defs in mod_mappings.items():
193 for mod_def in mod_defs:
194 for v in mod_def:
195 if isinstance(v, int):
196 l = self.keycodes_for_modifier_keynames.setdefault(mod, [])
197 else:
198 self.xkbmap_mod_meanings[v] = mod
199 l = self.keynames_for_mod.setdefault(mod, [])
200 if v not in l:
201 l.append(v)
202 else:
203 log("compute_modifiers() xkbmap_mod_meanings=%s", self.xkbmap_mod_meanings)
204 log("compute_modifiers() xkbmap_keycodes=%s", self.xkbmap_keycodes)
205 if self.xkbmap_mod_meanings:
206 #Unix-like OS provides modifier meanings:
207 self.keynames_for_mod = get_modifiers_from_meanings(self.xkbmap_mod_meanings)
208 elif self.xkbmap_keycodes:
209 #non-Unix-like OS provides just keycodes for now:
210 self.keynames_for_mod = get_modifiers_from_keycodes(self.xkbmap_keycodes, MAP_MISSING_MODIFIERS)
211 if MAP_MISSING_MODIFIERS:
212 map_missing_modifiers(self.keynames_for_mod)
213 else:
214 log.warn("Warning: client did not supply any modifier definitions")
215 self.keynames_for_mod = {}
216 log("compute_modifiers() keynames_for_mod=%s", self.keynames_for_mod)
217 log("compute_modifiers() keycodes_for_modifier_keynames=%s", self.keycodes_for_modifier_keynames)
218 log("compute_modifiers() mod_meanings=%s", self.xkbmap_mod_meanings)
221 def compute_modifier_keynames(self):
222 self.keycodes_for_modifier_keynames = {}
223 self.xkbmap_mod_nuisance = set(DEFAULT_MODIFIER_NUISANCE)
224 display = Gdk.Display.get_default()
225 keymap = Gdk.Keymap.get_for_display(display)
226 if self.keynames_for_mod:
227 for modifier, keynames in self.keynames_for_mod.items():
228 for keyname in keynames:
229 if keyname in DEFAULT_MODIFIER_NUISANCE_KEYNAMES:
230 self.xkbmap_mod_nuisance.add(modifier)
231 keyval = Gdk.keyval_from_name(bytestostr(keyname))
232 if keyval==0:
233 log.error("Error: no keyval found for keyname '%s' (modifier '%s')", keyname, modifier)
234 return []
235 entries = keymap.get_entries_for_keyval(keyval)
236 if entries:
237 keycodes = []
238 if entries[0] is True:
239 keycodes = [entry.keycode for entry in entries[1]]
240 for keycode in keycodes:
241 l = self.keycodes_for_modifier_keynames.setdefault(keyname, [])
242 if keycode not in l:
243 l.append(keycode)
244 log("compute_modifier_keynames: keycodes_for_modifier_keynames=%s", self.keycodes_for_modifier_keynames)
246 def compute_client_modifier_keycodes(self):
247 """ The keycodes for all modifiers (those are *client* keycodes!) """
248 try:
249 server_mappings = X11Keyboard.get_modifier_mappings()
250 log("compute_client_modifier_keycodes() server mappings=%s", server_mappings)
251 #update the mappings to use the keycodes the client knows about:
252 reverse_trans = {}
253 for k,v in self.keycode_translation.items():
254 reverse_trans[v] = k
255 self.modifier_client_keycodes = {}
256 self.xkbmap_mod_nuisance = set(DEFAULT_MODIFIER_NUISANCE)
257 for modifier, keys in server_mappings.items():
258 #ie: modifier=mod3, keys=[(115, 'Super_L'), (116, 'Super_R'), (127, 'Super_L')]
259 client_keydefs = []
260 for keycode,keysym in keys:
261 #ie: keycode=115, keysym=Super_L
262 client_def = reverse_trans.get(keycode, (0, keysym))
263 #ie: client_def = (99, Super_L)
264 #ie: client_def = Super_L
265 #ie: client_def = (0, Super_L)
266 if isinstance(client_def, (list, tuple)):
267 #ie:
268 # keycode, keysym:
269 # client_def = (99, Super_L)
270 # or keysym, level:
271 # client_def = (Super_L, 1)
272 client_keydefs.append(client_def)
273 elif client_def==keysym:
274 #ie: client_def = Super_L
275 client_keydefs.append((keycode, keysym))
276 #record nuisacnde modifiers:
277 if keysym in DEFAULT_MODIFIER_NUISANCE_KEYNAMES:
278 self.xkbmap_mod_nuisance.add(modifier)
279 self.modifier_client_keycodes[modifier] = client_keydefs
280 log("compute_client_modifier_keycodes() mappings=%s", self.modifier_client_keycodes)
281 log("compute_client_modifier_keycodes() mod nuisance=%s", self.xkbmap_mod_nuisance)
282 except Exception as e:
283 log.error("Error: compute_client_modifier_keycodes: %s" % e, exc_info=True)
285 def compute_modifier_map(self):
286 self.modifier_map = grok_modifier_map(Gdk.Display.get_default(), self.xkbmap_mod_meanings)
287 log("modifier_map(%s)=%s", self.xkbmap_mod_meanings, self.modifier_map)
290 def is_modifier(self, keycode):
291 for mod, keys in self.keycodes_for_modifier_keynames.items():
292 if keycode in keys:
293 log("is_modifier(%s) found modifier: %s", keycode, mod)
294 return True
295 log("is_modifier(%s) not found", keycode)
296 return False
299 def set_layout(self, layout, variant, options):
300 log("set_layout(%s, %s, %s)", layout, variant, options)
301 if layout=="en":
302 log.warn("Warning: invalid keyboard layout name '%s', using 'us' instead", layout)
303 layout = "us"
304 if layout!=self.xkbmap_layout or variant!=self.xkbmap_variant or options!=self.xkbmap_options:
305 self.xkbmap_layout = layout
306 self.xkbmap_variant = variant
307 self.xkbmap_options = options
308 return True
309 return False
312 def set_keymap(self, translate_only=False):
313 if not self.enabled:
314 return
315 log("set_keymap(%s) layout=%r, variant=%r, options=%r, query=%r",
316 translate_only, self.xkbmap_layout, self.xkbmap_variant, self.xkbmap_options,
317 self.xkbmap_query)
318 if translate_only:
319 self.keycode_translation = set_keycode_translation(self.xkbmap_x11_keycodes, self.xkbmap_keycodes)
320 self.add_gtk_keynames()
321 self.compute_modifiers()
322 self.compute_modifier_keynames()
323 self.compute_client_modifier_keycodes()
324 self.update_keycode_mappings()
325 return
327 with xlog:
328 clean_keyboard_state()
329 do_set_keymap(self.xkbmap_layout, self.xkbmap_variant, self.xkbmap_options,
330 self.xkbmap_query, self.xkbmap_query_struct)
331 log("set_keymap: xkbmap_query=%r", self.xkbmap_query)
332 with xlog:
333 #first clear all existing modifiers:
334 clean_keyboard_state()
336 if not self.xkbmap_raw:
337 has_keycodes = bool(self.xkbmap_x11_keycodes) or bool(self.xkbmap_keycodes)
338 assert has_keycodes, "client failed to provide any keycodes!"
340 clear_modifiers(ALL_X11_MODIFIERS.keys()) #just clear all of them (set or not)
341 clean_keyboard_state()
343 #now set all the keycodes:
344 #first compute the modifier maps as this may have an influence
345 #on the keycode mappings (at least for the from_keycodes case):
346 self.compute_modifiers()
347 #key translation:
348 if bool(self.xkbmap_query):
349 #native full mapping of all keycodes:
350 self.keycode_translation = set_all_keycodes(self.xkbmap_x11_keycodes, self.xkbmap_keycodes, False, self.keynames_for_mod)
351 else:
352 #if the client does not provide a full native keymap with all the keycodes,
353 #try to preserve the initial server keycodes and translate the client keycodes instead:
354 #(used by non X11 clients like osx,win32 or HTML5)
355 self.keycode_translation = set_keycode_translation(self.xkbmap_x11_keycodes, self.xkbmap_keycodes)
356 self.add_gtk_keynames()
358 #now set the new modifier mappings:
359 clean_keyboard_state()
360 log("going to set modifiers, xkbmap_mod_meanings=%s, len(xkbmap_keycodes)=%s, keynames_for_mod=%s", self.xkbmap_mod_meanings, len(self.xkbmap_keycodes or []), self.keynames_for_mod)
361 if self.keynames_for_mod:
362 set_modifiers(self.keynames_for_mod)
363 log("keynames_for_mod=%s", self.keynames_for_mod)
364 self.compute_modifier_keynames()
365 else:
366 self.keycode_translation = {}
367 log("keyboard raw mode, keycode translation left empty")
368 self.compute_modifiers()
369 self.compute_client_modifier_keycodes()
370 log("keyname_for_mod=%s", self.keynames_for_mod)
371 clean_keyboard_state()
372 self.update_keycode_mappings()
375 def add_gtk_keynames(self):
376 #add the keynames we find via gtk
377 #since we may rely on finding those keynames from the client
378 #(used with non native keymaps)
379 log("add_gtk_keynames() gtk keymap=%s", get_gtk_keymap())
380 for _, keyname, keycode, _, _ in get_gtk_keymap():
381 if keyname not in self.keycode_translation:
382 self.keycode_translation[keyname] = keycode
383 if keyname in DEBUG_KEYSYMS:
384 log.info("add_gtk_keynames: %s=%s", keyname, keycode)
386 def set_default_keymap(self):
387 """ assign a default keymap based on the current X11 server keymap
388 sets up the translation tables so we can lookup keys without
389 setting a client keymap.
390 """
391 if not self.enabled:
392 return
393 with xsync:
394 clean_keyboard_state()
395 #keycodes:
396 keycode_to_keynames = get_keycode_mappings()
397 self.keycode_translation = {}
398 #prefer keycodes that don't use the lowest level+mode:
399 default_for_keyname = {}
400 for keycode, keynames in keycode_to_keynames.items():
401 for i, keyname in enumerate(keynames):
402 self.keycode_translation[(keyname, i)] = keycode
403 if keyname in DEBUG_KEYSYMS:
404 log.info("set_default_keymap: %s=%s", (keyname, i), keycode)
405 kd = default_for_keyname.get(keyname)
406 if kd is None or kd[1]>i:
407 default_for_keyname[keyname] = (keycode, i)
408 for keyname, kd in default_for_keyname.items():
409 keycode = kd[0]
410 self.keycode_translation[keyname] = keycode
411 if keyname in DEBUG_KEYSYMS:
412 log.info("set_default_keymap: %s=%s", keyname, keycode)
413 self.add_gtk_keynames()
414 log("set_default_keymap: keycode_translation=%s", self.keycode_translation)
415 #modifiers:
416 self.keynames_for_mod = {}
417 #ie: {'control': [(37, 'Control_L'), (105, 'Control_R')], ...}
418 mod_mappings = X11Keyboard.get_modifier_mappings()
419 log("set_default_keymap: using modifier mappings=%s", mod_mappings)
420 for modifier, mappings in mod_mappings.items():
421 keynames = []
422 for m in mappings: #ie: (37, 'Control_L'), (105, 'Control_R')
423 if len(m)==2:
424 keynames.append(m[1]) #ie: 'Control_L'
425 self.keynames_for_mod[modifier] = set(keynames)
426 self.compute_modifier_keynames()
427 self.compute_client_modifier_keycodes()
428 log("set_default_keymap: keynames_for_mod=%s", self.keynames_for_mod)
429 log("set_default_keymap: keycodes_for_modifier_keynames=%s", self.keycodes_for_modifier_keynames)
430 log("set_default_keymap: modifier_map=%s", self.modifier_map)
431 self.update_keycode_mappings()
433 def update_keycode_mappings(self):
434 self.keycode_mappings = get_keycode_mappings()
437 def do_get_keycode(self, client_keycode, keyname, pressed, modifiers, keyval, keystr, group):
438 if not self.enabled:
439 log("ignoring keycode since keyboard is turned off")
440 return -1, group
441 if keyname=="0xffffff":
442 return -1, group
443 if self.xkbmap_raw:
444 return client_keycode, group
445 def kmlog(msg, *args):
446 if keyname in DEBUG_KEYSYMS:
447 l = log.info
448 else:
449 l = log
450 l(msg, *args)
451 def klog(msg, *args):
452 kmlog("do_get_keycode%s"+msg, (client_keycode, keyname, pressed, modifiers, keyval, keystr, group), *args)
453 keycode = None
454 rgroup = group
455 if self.xkbmap_query:
456 keycode = self.keycode_translation.get((client_keycode, keyname)) or client_keycode
457 klog("=%s (native keymap)", keycode)
458 else:
459 """
460 from man xmodmap:
461 The list of keysyms is assigned to the indicated keycode (which may be specified in decimal,
462 hex or octal and can be determined by running the xev program).
463 Up to eight keysyms may be attached to a key, however the last four are not used in any major
464 X server implementation.
465 The first keysym is used when no modifier key is pressed in conjunction with this key,
466 the second with Shift, the third when the Mode_switch key is used with this key and
467 the fourth when both the Mode_switch and Shift keys are used.
468 """
469 #non-native: try harder to find matching keysym
470 #first, try to honour shift state:
471 lock = ("lock" in modifiers) and (SHIFT_LOCK or (bool(keystr) and keystr.isalpha()))
472 shift = ("shift" in modifiers) ^ lock
473 mode = 0
474 numlock = 0
475 numlock_modifier = None
476 for mod, keynames in self.keynames_for_mod.items():
477 if "Num_Lock" in keynames:
478 numlock_modifier = mod
479 break
480 for mod in modifiers:
481 names = self.keynames_for_mod.get(mod, [])
482 if "Num_Lock" in names:
483 numlock = 1
484 for name in names:
485 if name in ("ISO_Level3_Shift", "Mode_switch"):
486 mode = 1
487 break
488 levels = []
489 #try to preserve the mode (harder to toggle):
490 for m in (int(bool(mode)), int(not mode)):
491 #try to preserve shift state:
492 for s in (int(bool(shift)), int(not shift)):
493 #group is comparatively easier to toggle (one function call):
494 for g in (int(bool(group)), int(not group)):
495 level = int(g)*4 + int(m)*2 + int(s)*1
496 levels.append(level)
497 kmlog("will try levels: %s", levels)
498 for level in levels:
499 keycode = self.keycode_translation.get((keyname, level))
500 if keycode:
501 keysyms = self.keycode_mappings.get(keycode)
502 klog("=%i (level=%i, shift=%s, mode=%i, keysyms=%s)", keycode, level, shift, mode, keysyms)
503 level0 = levels[0]
504 uq_keysyms = set(keysyms)
505 if len(uq_keysyms)<=1 or (len(keysyms)>level0 and keysyms[level0]==""):
506 #if the keysym we would match for this keycode is 'NoSymbol',
507 #then we can probably ignore it ('NoSymbol' shows up as "")
508 #same if there's only one actual keysym for this keycode
509 kmlog("not toggling any modifiers state for keysyms=%s", keysyms)
510 break
511 def toggle_modifier(mod):
512 keynames = self.keynames_for_mod.get(mod)
513 if keyname in keynames:
514 kmlog("not toggling '%s' since '%s' should deal with it", mod, keyname)
515 #the keycode we're returning is for this modifier,
516 #assume that this will end up doing what is needed
517 return
518 if mod in modifiers:
519 kmlog("removing '%s' from modifiers", mod)
520 modifiers.remove(mod)
521 else:
522 kmlog("adding '%s' to modifiers", mod)
523 modifiers.append(mod)
524 #keypad overrules shift state (see #2702):
525 if keyname.startswith("KP_"):
526 if numlock_modifier and not numlock:
527 toggle_modifier(numlock_modifier)
528 elif (level & 1) ^ shift:
529 #shift state does not match
530 toggle_modifier("shift")
531 if int(bool(level & 2)) ^ mode:
532 #try to set / unset mode:
533 for mod, keynames in self.keynames_for_mod.items():
534 if "ISO_Level3_Shift" in keynames or "Mode_switch" in keynames:
535 #found mode switch modified
536 toggle_modifier(mod)
537 break
538 rgroup = level//4
539 if rgroup!=group:
540 kmlog("switching group from %i to %i", group, rgroup)
541 break
542 #this should not find anything new?:
543 if keycode is None:
544 keycode = self.keycode_translation.get(keyname, -1)
545 klog("=%i, %i (keyname translation)", keycode, rgroup)
546 if keycode is None:
547 #last resort, find using the keyval:
548 display = Gdk.Display.get_default()
549 keymap = Gdk.Keymap.get_for_display(display)
550 b, entries = keymap.get_entries_for_keyval(keyval)
551 if b and entries:
552 for entry in entries:
553 if entry.group==group:
554 return entry.keycode, entry.group
555 return keycode, rgroup
558 def get_current_mask(self):
559 current_mask = get_default_root_window().get_pointer()[-1]
560 return mask_to_names(current_mask, self.modifier_map)
562 def make_keymask_match(self, modifier_list, ignored_modifier_keycode=None, ignored_modifier_keynames=None):
563 """
564 Given a list of modifiers that should be set, try to press the right keys
565 to make the server's modifier list match it.
566 Things to take into consideration:
567 * xkbmap_mod_managed is a list of modifiers which are "server-managed":
568 these never show up in the client's modifier list as it is not aware of them,
569 so we just always leave them as they are and rely on some client key event to toggle them.
570 ie: "num" on win32, which is toggled by the "Num_Lock" key presses.
571 * when called from '_handle_key', we ignore the modifier key which may be pressed
572 or released as it should be set by that key press event.
573 * when called from mouse position/click/focus events we ignore 'xkbmap_mod_pointermissing'
574 which is set by the client to indicate modifiers which are missing from mouse events.
575 ie: on win32, "lock" is missing.
576 (we know this is not a keyboard event because ignored_modifier_keynames is None..)
577 * if the modifier is a "nuisance" one ("lock", "num", "scroll") then we must
578 simulate a full keypress (down then up).
579 * some modifiers can be set by multiple keys ("shift" by both "Shift_L" and "Shift_R" for example)
580 so we try to find the matching modifier in the currently pressed keys (keys_pressed)
581 to make sure we unpress the right one.
582 """
583 if not self.keynames_for_mod:
584 log("make_keymask_match: ignored as keynames_for_mod not assigned yet")
585 return
586 if ignored_modifier_keynames is None:
587 #this is not a keyboard event, ignore modifiers in "mod_pointermissing"
588 def is_ignored(modifier, _modifier_keynames):
589 return modifier in (self.xkbmap_mod_pointermissing or [])
590 else:
591 #keyboard event: ignore the keynames specified
592 #(usually the modifier key being pressed/unpressed)
593 def is_ignored(_modifier, modifier_keynames):
594 return len(set(modifier_keynames or []) & set(ignored_modifier_keynames or []))>0
596 def filtered_modifiers_set(modifiers):
597 m = set()
598 mm = self.xkbmap_mod_managed or ()
599 for modifier in modifiers:
600 modifier = bytestostr(modifier)
601 if modifier in mm:
602 log("modifier is server managed: %s", modifier)
603 continue
604 keynames = self.keynames_for_mod.get(modifier)
605 if is_ignored(modifier, keynames):
606 log("modifier '%s' ignored (in ignored keynames=%s)", modifier, keynames)
607 continue
608 m.add(modifier)
609 log("filtered_modifiers_set(%s)=%s", modifiers, m)
610 return m
612 def change_mask(modifiers, press, info):
613 failed = []
614 for modifier in modifiers:
615 modifier = bytestostr(modifier)
616 keynames = self.keynames_for_mod.get(modifier)
617 if not keynames:
618 log.error("Error: unknown modifier '%s'", modifier)
619 log.error(" known modifiers: %s", csv(self.keynames_for_mod.keys()))
620 continue
621 #find the keycodes that match the keynames for this modifier
622 keycodes = []
623 #log.info("keynames(%s)=%s", modifier, keynames)
624 for keyname in keynames:
625 if keyname in self.keys_pressed.values():
626 #found the key which was pressed to set this modifier
627 for keycode, name in self.keys_pressed.items():
628 if name==keyname:
629 log("found the key pressed for %s: %s", modifier, name)
630 keycodes.insert(0, keycode)
631 keycodes_for_keyname = self.keycodes_for_modifier_keynames.get(keyname)
632 if keycodes_for_keyname:
633 for keycode in keycodes_for_keyname:
634 if keycode not in keycodes:
635 keycodes.append(keycode)
636 if ignored_modifier_keycode is not None and ignored_modifier_keycode in keycodes:
637 log("modifier '%s' ignored (ignored keycode=%s)", modifier, ignored_modifier_keycode)
638 continue
639 #nuisance keys (lock, num, scroll) are toggled by a
640 #full key press + key release (so act accordingly in the loop below)
641 nuisance = modifier in self.xkbmap_mod_nuisance
642 log("keynames(%s)=%s, keycodes=%s, nuisance=%s, nuisance keys=%s", modifier, keynames, keycodes, nuisance, self.xkbmap_mod_nuisance)
643 modkeycode = None
644 if not press:
645 #since we want to unpress something,
646 #let's try the keycodes we know are pressed first:
647 kdown = X11Keyboard.get_keycodes_down()
648 pressed = [x for x in keycodes if x in kdown]
649 others = [x for x in keycodes if x not in kdown]
650 keycodes = pressed+others
651 for keycode in keycodes:
652 if nuisance:
653 X11Keyboard.xtest_fake_key(keycode, True)
654 X11Keyboard.xtest_fake_key(keycode, False)
655 else:
656 X11Keyboard.xtest_fake_key(keycode, press)
657 new_mask = self.get_current_mask()
658 success = (modifier in new_mask)==press
659 if success:
660 modkeycode = keycode
661 log("change_mask(%s) %s modifier '%s' using keycode %s", info, modifier_list, modifier, keycode)
662 break #we're done for this modifier
663 log("%s %s with keycode %s did not work", info, modifier, keycode)
664 if press and not nuisance:
665 log(" trying to unpress it!")
666 X11Keyboard.xtest_fake_key(keycode, False)
667 #maybe doing the full keypress (down+up) worked:
668 new_mask = self.get_current_mask()
669 if (modifier in new_mask)==press:
670 break
671 log("change_mask(%s) %s modifier '%s' using keycode %s, success: %s", info, modifier_list, modifier, keycode, success)
672 if not modkeycode:
673 failed.append(modifier)
674 log("change_mask(%s, %s, %s) failed=%s", modifiers, press, info, failed)
675 return failed
677 with xsync:
678 current = filtered_modifiers_set(self.get_current_mask())
679 wanted = filtered_modifiers_set(modifier_list or [])
680 if current==wanted:
681 return
682 log("make_keymask_match(%s) current mask: %s, wanted: %s, ignoring=%s/%s, keys_pressed=%s", modifier_list, current, wanted, ignored_modifier_keycode, ignored_modifier_keynames, self.keys_pressed)
683 fr = change_mask(current.difference(wanted), False, "remove")
684 fa = change_mask(wanted.difference(current), True, "add")
685 if not fr or fa:
686 return
687 if fr:
688 log.warn("Warning: failed to remove the following modifiers:")
689 log.warn(" %s", csv(fr))
690 if fa:
691 log.warn("Warning: failed to add the following modifiers:")
692 log.warn(" %s", csv(fa))
693 #this should never happen.. but if it does?
694 #something didn't work, use the big hammer and start again from scratch:
695 log.warn(" keys still pressed=%s", X11Keyboard.get_keycodes_down())
696 X11Keyboard.unpress_all_keys()
697 log.warn(" doing a full keyboard reset, keys now pressed=%s", X11Keyboard.get_keycodes_down())
698 #and try to set the modifiers one last time:
699 current = filtered_modifiers_set(self.get_current_mask())
700 change_mask(current.difference(wanted), False, "remove")
701 change_mask(wanted.difference(current), True, "add")