Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/x11/xkbhelper.py : 8%
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# This file is part of Xpra.
2# Copyright (C) 2011-2020 Antoine Martin <antoine@xpra.org>
3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
4# later version. See the file COPYING for details.
6import os
8#ensure that we use gtk as display source:
9from xpra.x11.gtk_x11.gdk_display_source import init_gdk_display_source
10from xpra.util import std, csv, envbool
11from xpra.os_util import bytestostr
12from xpra.gtk_common.error import xsync, xlog
13from xpra.x11.bindings.keyboard_bindings import X11KeyboardBindings #@UnresolvedImport
14from xpra.keyboard.layouts import parse_xkbmap_query
15from xpra.log import Logger
17init_gdk_display_source()
18X11Keyboard = X11KeyboardBindings()
20log = Logger("x11", "keyboard")
22XKB = envbool("XPRA_XKB", True)
23DEBUG_KEYSYMS = [x for x in os.environ.get("XPRA_DEBUG_KEYSYMS", "").split(",") if len(x)>0]
25#keys we choose not to map if the free space in the keymap is too limited
26#this list was generated using:
27#$ DISPLAY=:1 xmodmap -pke | awk -F= '{print $2}' | xargs -n 1 echo | sort -u | grep XF | xargs
28OPTIONAL_KEYS = [
29 "XF86AudioForward", "XF86AudioLowerVolume", "XF86AudioMedia", "XF86AudioMicMute", "XF86AudioMute",
30 "XF86AudioNext", "XF86AudioPause", "XF86AudioPlay", "XF86AudioPrev", "XF86AudioRaiseVolume",
31 "XF86AudioRecord", "XF86AudioRewind", "XF86AudioStop",
32 "XF86Back", "XF86Battery", "XF86Bluetooth", "XF86Calculator", "XF86ClearGrab", "XF86Close",
33 "XF86Copy", "XF86Cut", "XF86Display", "XF86Documents", "XF86DOS", "XF86Eject", "XF86Explorer",
34 "XF86Favorites", "XF86Finance", "XF86Forward", "XF86Game", "XF86Go", "XF86HomePage", "XF86KbdBrightnessDown",
35 "XF86KbdBrightnessUp", "XF86KbdLightOnOff",
36 "XF86Launch1", "XF86Launch2", "XF86Launch3", "XF86Launch4", "XF86Launch5", "XF86Launch6",
37 "XF86Launch7", "XF86Launch8", "XF86Launch9", "XF86LaunchA", "XF86LaunchB",
38 "XF86Mail", "XF86MailForward", "XF86MenuKB", "XF86Messenger", "XF86MonBrightnessDown",
39 "XF86MonBrightnessUp", "XF86MyComputer",
40 "XF86New", "XF86Next_VMode", "XF86Open", "XF86Paste", "XF86Phone", "XF86PowerOff",
41 "XF86Prev_VMode", "XF86Reload", "XF86Reply", "XF86RotateWindows", "XF86Save", "XF86ScreenSaver",
42 "XF86ScrollDown", "XF86ScrollUp", "XF86Search", "XF86Send", "XF86Shop", "XF86Sleep", "XF86Suspend",
43 "XF86Switch_VT_1", "XF86Switch_VT_10", "XF86Switch_VT_11", "XF86Switch_VT_12", "XF86Switch_VT_2", "XF86Switch_VT_3",
44 "XF86Switch_VT_4", "XF86Switch_VT_5", "XF86Switch_VT_6", "XF86Switch_VT_7", "XF86Switch_VT_8", "XF86Switch_VT_9",
45 "XF86Tools", "XF86TouchpadOff", "XF86TouchpadOn", "XF86TouchpadToggle", "XF86Ungrab", "XF86WakeUp", "XF86WebCam",
46 "XF86WLAN", "XF86WWW", "XF86Xfer",
47 ]
50def clean_keyboard_state():
51 with xlog:
52 X11Keyboard.ungrab_all_keys()
53 with xlog:
54 X11Keyboard.set_layout_group(0)
55 with xlog:
56 X11Keyboard.unpress_all_keys()
58################################################################################
59# keyboard layouts
61def do_set_keymap(xkbmap_layout, xkbmap_variant, xkbmap_options,
62 xkbmap_query, xkbmap_query_struct):
63 """ xkbmap_layout is the generic layout name (used on non posix platforms)
64 xkbmap_variant is the layout variant (may not be set)
65 xkbmap_print is the output of "setxkbmap -print" on the client
66 xkbmap_query is the output of "setxkbmap -query" on the client
67 xkbmap_query_struct is xkbmap_query parsed into a dictionary
68 Use those to try to setup the correct keyboard map for the client
69 so that all the keycodes sent will be mapped
70 """
71 #First we try to use data from setxkbmap -query,
72 #preferably as structured data:
73 if xkbmap_query and not xkbmap_query_struct:
74 xkbmap_query_struct = parse_xkbmap_query(xkbmap_query)
75 if xkbmap_query_struct:
76 log("do_set_keymap using xkbmap_query struct=%s", xkbmap_query_struct)
77 #The xkbmap_query_struct data will look something like this:
78 # {
79 # b"rules" : b"evdev",
80 # b"model" : b"pc105",
81 # b"layout" : b"gb",
82 # b"options" : b"grp:shift_caps_toggle",
83 # }
84 #parse the data into a dict:
85 rules = xkbmap_query_struct.get(b"rules")
86 model = xkbmap_query_struct.get(b"model")
87 layout = xkbmap_query_struct.get(b"layout")
88 variant = xkbmap_query_struct.get(b"variant")
89 options = xkbmap_query_struct.get(b"options")
90 if layout:
91 log.info("setting keymap: %s",
92 csv("%s=%s" % (std(k), std(v)) for k,v in xkbmap_query_struct.items()
93 if k in ("rules", "model", "layout") and v))
94 safe_setxkbmap(rules, model, layout, variant, options)
95 else:
96 safe_setxkbmap(rules, model, "", "", "")
97 #fallback for non X11 clients:
98 layout = xkbmap_layout or "us"
99 log.info("setting keyboard layout to '%s'", std(layout))
100 safe_setxkbmap("evdev", "pc105", layout, xkbmap_variant, xkbmap_options)
102def safe_setxkbmap(rules, model, layout, variant, options):
103 #(we execute the options separately in case that fails..)
104 try:
105 X11Keyboard.setxkbmap(rules, model, layout, variant, options)
106 return
107 except Exception:
108 log("safe_setxkbmap%s", (rules, model, layout, variant, options), exc_info=True)
109 log.warn("Warning: failed to set exact keymap,")
110 log.warn(" rules=%s, model=%s, layout=%s, variant=%s, options=%s",
111 bytestostr(rules), bytestostr(model), bytestostr(layout), bytestostr(variant), bytestostr(options))
112 if options:
113 #try again with no options:
114 try:
115 X11Keyboard.setxkbmap(rules, model, layout, variant, "")
116 except Exception:
117 log("setxkbmap", exc_info=True)
118 log.error("Error: failed to set exact keymap")
119 log.error(" even without applying any options..")
122################################################################################
123# keycodes
125def apply_xmodmap(instructions):
126 try:
127 with xsync:
128 unset = X11Keyboard.set_xmodmap(instructions)
129 except Exception as e:
130 log("apply_xmodmap(%s)", instructions, exc_info=True)
131 log.error("Error configuring modifier map: %s", e)
132 unset = instructions
133 if unset is None:
134 #None means an X11 error occurred, re-do all:
135 unset = instructions
136 return unset
139def get_keycode_mappings():
140 if XKB and X11Keyboard.hasXkb():
141 return X11Keyboard.get_xkb_keycode_mappings()
142 return X11Keyboard.get_keycode_mappings()
144def set_keycode_translation(xkbmap_x11_keycodes, xkbmap_keycodes):
145 """
146 Translate the given keycodes into the existing keymap
147 """
148 #get the list of keycodes (either from x11 keycodes or gtk keycodes):
149 if xkbmap_x11_keycodes:
150 dump_dict(xkbmap_x11_keycodes)
151 keycodes = indexed_mappings(xkbmap_x11_keycodes)
152 else:
153 keycodes = gtk_keycodes_to_mappings(xkbmap_keycodes)
154 log("set_keycode_translation(%s, %s)", xkbmap_x11_keycodes, xkbmap_keycodes)
155 log(" keycodes=%s", keycodes)
156 #keycodes = {
157 # 9: set([('', 1), ('Escape', 4), ('', 3), ('Escape', 0), ('Escape', 2)]),
158 # 10: set([('onesuperior', 4), ('onesuperior', 8), ('exclam', 1), ('1', 6), ('exclam', 3), ('1', 2), ('exclamdown', 9), ('exclamdown', 5), ('1', 0), ('exclam', 7)]),
159 x11_keycodes = get_keycode_mappings()
160 log(" x11_keycodes=%s", x11_keycodes)
161 #x11_keycodes = {
162 # 8: ['Mode_switch', '', 'Mode_switch', '', 'Mode_switch'],
163 # 9: ['Escape', '', 'Escape', '', 'Escape'],
164 #for faster lookups:
165 x11_keycodes_for_keysym = {}
166 for keycode, keysyms in x11_keycodes.items():
167 for keysym in keysyms:
168 x11_keycodes_for_keysym.setdefault(keysym, set()).add(keycode)
169 def find_keycode(kc, keysym, i):
170 keycodes = tuple(x11_keycodes_for_keysym.get(keysym, set()))
171 if keysym in DEBUG_KEYSYMS:
172 log.info("set_keycode_translation: find_keycode%s x11 keycodes=%s", (kc, keysym, i), keycodes)
173 def rlog(keycode, msg):
174 if keysym in DEBUG_KEYSYMS:
175 log.info("set_keycode_translation: find_keycode%s=%s (%s)", (kc, keysym, i), keycode, msg)
176 if not keycodes:
177 return None
178 #no other option, use it:
179 for keycode in keycodes:
180 defs = x11_keycodes.get(keycode)
181 if keysym in DEBUG_KEYSYMS:
182 log.info("server x11 keycode %i: %s", keycode, defs)
183 assert defs, "bug: keycode %i not found in %s" % (keycode, x11_keycodes)
184 if len(defs)>i and defs[i]==keysym:
185 rlog(keycode, "exact index match")
186 return keycode, True
187 #if possible, use the same one:
188 if kc in keycodes:
189 rlog(kc, "using same keycode as client")
190 return kc, False
191 keycode = keycodes[0]
192 rlog(keycode, "using first match")
193 return keycode, False
194 #generate the translation map:
195 trans = {}
196 for keycode, defs in keycodes.items():
197 if bool(set(DEBUG_KEYSYMS) & set(bytestostr(d[0]) for d in defs)):
198 log.info("client keycode=%i, defs=%s", keycode, defs)
199 for bkeysym, i in tuple(defs): #ie: (b'1', 0) or (b'A', 1), etc
200 keysym = bytestostr(bkeysym)
201 m = find_keycode(keycode, keysym, i)
202 if m:
203 x11_keycode, index_matched = m
204 trans[(keycode, keysym)] = x11_keycode
205 trans[keysym] = x11_keycode
206 if index_matched:
207 trans[(keysym, i)] = x11_keycode
208 if not xkbmap_x11_keycodes:
209 #now add all the keycodes we may not have mapped yet
210 #(present in x11_keycodes but not keycodes)
211 for keycode, keysyms in x11_keycodes.items():
212 for i, keysym in enumerate(keysyms):
213 if keysym not in trans:
214 if keysym in DEBUG_KEYSYMS:
215 log.info("x11 keycode %s: %s", keycode, keysym)
216 trans[keysym] = keycode
217 key = (keysym, i)
218 if key not in trans:
219 if keysym in DEBUG_KEYSYMS:
220 log.info("x11 keycode %s: %s", keycode, key)
221 trans[key] = keycode
222 log("set_keycode_translation(..)=%s", trans)
223 return trans
225def set_all_keycodes(xkbmap_x11_keycodes, xkbmap_keycodes, preserve_server_keycodes, modifiers):
226 """
227 Clients that have access to raw x11 keycodes should provide
228 an xkbmap_x11_keycodes map, we otherwise fallback to using
229 the xkbmap_keycodes gtk keycode list.
230 We try to preserve the initial keycodes if asked to do so,
231 we retrieve them from the current server keymap and combine
232 them with the given keycodes.
233 The modifiers dict can be obtained by calling
234 get_modifiers_from_meanings or get_modifiers_from_keycodes.
235 We use it to ensure that two modifiers are not
236 mapped to the same keycode (which is not allowed).
237 We return a translation map for keycodes after setting them up,
238 the key is (keycode, keysym) and the value is the server keycode.
239 """
240 log("set_all_keycodes(%s.., %s.., %s.., %s)",
241 str(xkbmap_x11_keycodes)[:60], str(xkbmap_keycodes)[:60], str(preserve_server_keycodes)[:60], modifiers)
243 #so we can validate entries:
244 keysym_to_modifier = {}
245 for modifier, keysyms in modifiers.items():
246 for keysym in keysyms:
247 existing_mod = keysym_to_modifier.get(keysym)
248 if existing_mod and existing_mod!=modifier:
249 log.error("ERROR: keysym %s is mapped to both %s and %s !", keysym, modifier, existing_mod)
250 else:
251 keysym_to_modifier[keysym] = modifier
252 if keysym in DEBUG_KEYSYMS:
253 log.info("set_all_keycodes() keysym_to_modifier[%s]=%s", keysym, modifier)
254 log("keysym_to_modifier=%s", keysym_to_modifier)
256 def modifiers_for(entries):
257 """ entries can only point to a single modifier - verify """
258 modifiers = set()
259 l = log
260 for keysym, _ in entries:
261 modifier = keysym_to_modifier.get(keysym)
262 if modifier:
263 modifiers.add(modifier)
264 if keysym in DEBUG_KEYSYMS:
265 l = log.info
266 l("modifiers_for(%s)=%s", entries, modifiers)
267 return modifiers
269 def filter_mappings(mappings, drop_extra_keys=False):
270 filtered = {}
271 invalid_keysyms = set()
272 def estr(entries):
273 try:
274 return csv(tuple(set(x[0] for x in entries)))
275 except Exception:
276 return csv(tuple(entries))
277 for keycode, entries in mappings.items():
278 mods = modifiers_for(entries)
279 if len(mods)>1:
280 log.warn("Warning: keymapping changed:")
281 log.warn(" keycode %s points to %i modifiers: %s", keycode, len(mods), csv(tuple(mods)))
282 log.warn(" from definition: %s", estr(entries))
283 for mod in mods:
284 emod = [entry for entry in entries if mod in modifiers_for([entry])]
285 log.warn(" %s: %s", mod, estr(emod))
286 #keep just the first one:
287 mods = tuple(mods)[:1]
288 mod = mods[0]
289 entries = [entry for entry in entries if mod in modifiers_for([entry])]
290 log.warn(" keeping: %s for %s", estr(entries), mod)
291 #now remove entries for keysyms we don't have:
292 f_entries = set((keysym, index) for keysym, index in entries
293 if keysym and X11Keyboard.parse_keysym(keysym) is not None)
294 for keysym, _ in entries:
295 if keysym and X11Keyboard.parse_keysym(keysym) is None:
296 invalid_keysyms.add(keysym)
297 if not f_entries:
298 log("keymapping removed invalid keycode entry %s pointing to only unknown keysyms: %s",
299 keycode, entries)
300 continue
301 if drop_extra_keys:
302 if not any(keysym for keysym, index in entries if (
303 X11Keyboard.parse_keysym(keysym) is not None and keysym not in OPTIONAL_KEYS)
304 ):
305 log("keymapping removed keycode entry %s pointing to optional keys: %s", keycode, entries)
306 continue
307 filtered[keycode] = f_entries
308 if invalid_keysyms:
309 log.warn("Warning: the following keysyms are invalid and have not been mapped")
310 log.warn(" %s", csv(invalid_keysyms))
311 return filtered
313 #get the list of keycodes (either from x11 keycodes or gtk keycodes):
314 if xkbmap_x11_keycodes:
315 log("using x11 keycodes: %s", xkbmap_x11_keycodes)
316 dump_dict(xkbmap_x11_keycodes)
317 keycodes = indexed_mappings(xkbmap_x11_keycodes)
318 else:
319 log("using gtk keycodes: %s", xkbmap_keycodes)
320 keycodes = gtk_keycodes_to_mappings(xkbmap_keycodes)
321 #filter to ensure only valid entries remain:
322 log("keycodes=%s", keycodes)
323 keycodes = filter_mappings(keycodes)
325 #now lookup the current keycodes (if we need to preserve them)
326 preserve_keycode_entries = {}
327 if preserve_server_keycodes:
328 preserve_keycode_entries = X11Keyboard.get_keycode_mappings()
329 log("preserved mappings:")
330 dump_dict(preserve_keycode_entries)
332 kcmin, kcmax = X11Keyboard.get_minmax_keycodes()
333 for try_harder in (False, True):
334 filtered_preserve_keycode_entries = filter_mappings(indexed_mappings(preserve_keycode_entries), try_harder)
335 log("filtered_preserve_keycode_entries=%s", filtered_preserve_keycode_entries)
336 trans, new_keycodes, missing_keycodes = translate_keycodes(kcmin, kcmax, keycodes,
337 filtered_preserve_keycode_entries,
338 keysym_to_modifier,
339 try_harder)
340 if not missing_keycodes:
341 break
342 instructions = keymap_to_xmodmap(new_keycodes)
343 unset = apply_xmodmap(instructions)
344 log("unset=%s", unset)
345 return trans
347def dump_dict(d):
348 for k,v in d.items():
349 log("%s\t\t=\t%s", k, v)
351def group_by_keycode(entries):
352 keycodes = {}
353 log_keycodes = []
354 for keysym, keycode, index in entries:
355 keycodes.setdefault(keycode, set()).add((keysym, index))
356 if keysym in DEBUG_KEYSYMS:
357 log_keycodes.append(keycode)
358 if log_keycodes:
359 log.info("group_by_keycode: %s", dict((keycode, keycodes.get(keycode)) for keycode in log_keycodes))
360 return keycodes
362def indexed_mappings(raw_mappings):
363 indexed = {}
364 for keycode, keysyms in raw_mappings.items():
365 pairs = set()
366 l = log
367 for i, keysym in enumerate(keysyms):
368 if keysym in DEBUG_KEYSYMS:
369 l = log.info
370 pairs.add((keysym, i))
371 indexed[keycode] = pairs
372 l("indexed_mappings: %s=%s", keycode, pairs)
373 return indexed
376def gtk_keycodes_to_mappings(gtk_mappings):
377 """
378 Takes gtk keycodes as obtained by get_gtk_keymap, in the form:
379 #[(keyval, keyname, keycode, group, level), ..]
380 And returns a list of entries in the form:
381 [[keysym, keycode, index], ..]
382 """
383 #use the keycodes supplied by gtk:
384 mappings = {}
385 for _, name, keycode, group, level in gtk_mappings:
386 if keycode<0:
387 continue #ignore old 'add_if_missing' client side code
388 index = group*2+level
389 mappings.setdefault(keycode, set()).add((name, index))
390 return mappings
392def x11_keycodes_to_list(x11_mappings):
393 """
394 Takes x11 keycodes as obtained by get_keycode_mappings(), in the form:
395 #{keycode : [keysyms], ..}
396 And returns a list of entries in the form:
397 [[keysym, keycode, index], ..]
398 """
399 entries = []
400 if x11_mappings:
401 for keycode, keysyms in x11_mappings.items():
402 index = 0
403 for keysym in keysyms:
404 if keysym:
405 entries.append([keysym, int(keycode), index])
406 if keysym in DEBUG_KEYSYMS:
407 log.info("x11_keycodes_to_list: (%s, %s) : %s", keycode, keysyms, (keysym, int(keycode), index))
408 index += 1
409 return entries
412def translate_keycodes(kcmin, kcmax, keycodes, preserve_keycode_entries, keysym_to_modifier, try_harder=False):
413 """
414 The keycodes given may not match the range that the server supports,
415 or some of those keycodes may not be usable (only one modifier can
416 be mapped to a single keycode) or we want to preserve a keycode,
417 or modifiers want to use the same keycode (which is not possible),
418 so we return a translation map for those keycodes that have been
419 remapped.
420 The preserve_keycodes is a dict containing {keycode:[entries]}
421 for keys we want to preserve the keycode for.
422 Note: a client_keycode of '0' is valid (osx uses that),
423 but server_keycode generally starts at 8...
424 """
425 log("translate_keycodes(%s, %s, %s, %s, %s, %s)",
426 kcmin, kcmax, keycodes, preserve_keycode_entries, keysym_to_modifier, try_harder)
427 #list of free keycodes we can use:
428 free_keycodes = [i for i in range(kcmin, kcmax+1) if i not in preserve_keycode_entries]
429 keycode_trans = {} #translation map from client keycode to our server keycode
430 server_keycodes = {} #the new keycode definitions
431 missing_keycodes = [] #the groups of entries we failed to map due to lack of free keycodes
433 #to do faster lookups:
434 preserve_keysyms_map = {}
435 for keycode, entries in preserve_keycode_entries.items():
436 for keysym, _ in entries:
437 preserve_keysyms_map.setdefault(keysym, set()).add(keycode)
438 for k in DEBUG_KEYSYMS:
439 log.info("preserve_keysyms_map[%s]=%s", k, preserve_keysyms_map.get(k))
441 def do_assign(keycode, server_keycode, entries, override_server_keycode=False):
442 """ may change the keycode if needed
443 in which case we update the entries and populate 'keycode_trans'
444 """
445 l = log
446 for name, _ in entries:
447 if name in DEBUG_KEYSYMS:
448 l = log.info
449 if (server_keycode in server_keycodes) and not override_server_keycode:
450 l("assign: server keycode %s already in use: %s", server_keycode, server_keycodes.get(server_keycode))
451 server_keycode = -1
452 elif server_keycode>0 and (server_keycode<kcmin or server_keycode>kcmax):
453 l("assign: keycode %s out of range (%s to %s)", server_keycode, kcmin, kcmax)
454 server_keycode = -1
455 if server_keycode<=0:
456 if free_keycodes:
457 server_keycode = free_keycodes[0]
458 l("set_keycodes key %s using free keycode=%s", entries, server_keycode)
459 else:
460 msg = "set_keycodes: no free keycodes!, cannot translate %s: %s", server_keycode, tuple(entries)
461 if try_harder:
462 log.error(*msg)
463 else:
464 l(*msg)
465 missing_keycodes.append(entries)
466 server_keycode = -1
467 if server_keycode>0:
468 l("set_keycodes key %s (%s) mapped to keycode=%s", keycode, tuple(entries), server_keycode)
469 #can't use it any more!
470 if server_keycode in free_keycodes:
471 free_keycodes.remove(server_keycode)
472 #record it in trans map:
473 for name, _ in entries:
474 if keycode>=0 and server_keycode!=keycode:
475 keycode_trans[(keycode, name)] = server_keycode
476 l("keycode_trans[(%s, %s)]=%s", keycode, bytestostr(name), server_keycode)
477 keycode_trans[name] = server_keycode
478 l("keycode_trans[%s]=%s", bytestostr(name), server_keycode)
479 server_keycodes[server_keycode] = entries
480 return server_keycode
482 def assign(client_keycode, entries):
483 if not entries:
484 return 0
485 #all the keysyms for this keycode:
486 keysyms = set(keysym for keysym, _ in entries)
487 if not keysyms:
488 return 0
489 if len(keysyms)==1 and tuple(keysyms)[0]=='0xffffff':
490 log("skipped invalid keysym: %s / %s", client_keycode, entries)
491 return 0
492 l = log
493 if [k for k in keysyms if k in DEBUG_KEYSYMS]:
494 l = log.info
495 l("assign(%s, %s)", client_keycode, entries)
497 if not preserve_keycode_entries:
498 return do_assign(client_keycode, client_keycode, entries)
499 if len(keysyms)==1:
500 #only one keysym, replace with single entry
501 entries = set([(tuple(keysyms)[0], 0)])
503 #the candidate preserve entries: those that have at least one of the keysyms:
504 preserve_keycode_matches = {}
505 for keysym in tuple(keysyms):
506 keycodes = preserve_keysyms_map.get(keysym, [])
507 for keycode in keycodes:
508 v = preserve_keycode_entries.get(keycode)
509 preserve_keycode_matches[keycode] = v
510 l("preserve_keycode_matches[%s]=%s", keycode, v)
512 if not preserve_keycode_matches:
513 l("no preserve matches for %s", tuple(entries))
514 return do_assign(client_keycode, -1, entries) #nothing to preserve
516 l("preserve matches for %s : %s", entries, preserve_keycode_matches)
517 #direct superset:
518 for p_keycode, p_entries in preserve_keycode_matches.items():
519 if entries.issubset(p_entries):
520 l("found direct preserve superset for client keycode %s %s -> server keycode %s %s",
521 client_keycode, tuple(entries), p_keycode, tuple(p_entries))
522 return do_assign(client_keycode, p_keycode, p_entries, override_server_keycode=True)
523 if p_entries.issubset(entries):
524 l("found direct superset of preserve for client keycode %s %s -> server keycode %s %s",
525 client_keycode, tuple(entries), p_keycode, tuple(p_entries))
526 return do_assign(client_keycode, p_keycode, entries, override_server_keycode=True)
528 #ignoring indexes, but requiring at least as many keysyms:
529 for p_keycode, p_entries in preserve_keycode_matches.items():
530 p_keysyms = set(keysym for keysym,_ in p_entries)
531 if keysyms.issubset(p_keysyms):
532 if len(p_entries)>len(entries):
533 l("found keysym preserve superset with more keys for %s : %s", tuple(entries), tuple(p_entries))
534 return do_assign(client_keycode, p_keycode, p_entries, override_server_keycode=True)
535 if p_keysyms.issubset(keysyms):
536 l("found keysym superset of preserve with more keys for %s : %s", tuple(entries), tuple(p_entries))
537 return do_assign(client_keycode, p_keycode, entries, override_server_keycode=True)
539 if try_harder:
540 #try to match the main key only:
541 main_key = set((keysym, index) for keysym, index in entries if index==0)
542 if len(main_key)==1:
543 for p_keycode, p_entries in preserve_keycode_matches.items():
544 p_keysyms = set(keysym for keysym,_ in p_entries)
545 if main_key.issubset(p_entries):
546 l("found main key superset for %s : %s", main_key, tuple(p_entries))
547 return do_assign(client_keycode, p_keycode, p_entries, override_server_keycode=True)
549 l("no matches for %s", tuple(entries))
550 return do_assign(client_keycode, -1, entries)
552 #now try to assign each keycode:
553 for keycode in sorted(keycodes.keys()):
554 entries = keycodes.get(keycode)
555 log("assign(%s, %s)", keycode, entries)
556 assign(keycode, entries)
558 #add all the other preserved ones that have not been mapped to any client keycode:
559 for server_keycode, entries in preserve_keycode_entries.items():
560 if server_keycode not in server_keycodes:
561 do_assign(-1, server_keycode, entries)
563 #find all keysyms assigned so far:
564 all_keysyms = set()
565 for entries in server_keycodes.values():
566 for x in [keysym for keysym, _ in entries]:
567 all_keysyms.add(x)
568 log("all_keysyms=%s", all_keysyms)
570 #defined keysyms for modifiers if some are missing:
571 for keysym, modifier in keysym_to_modifier.items():
572 if keysym not in all_keysyms:
573 l = log
574 if keysym in DEBUG_KEYSYMS:
575 l = log.info
576 l("found missing keysym %s for modifier %s, will add it", keysym, modifier)
577 new_keycode = set([(keysym, 0)])
578 server_keycode = assign(-1, new_keycode)
579 l("assigned keycode %s for key '%s' of modifier '%s'", server_keycode, keysym, modifier)
581 log("translated keycodes=%s", keycode_trans)
582 log("%s free keycodes=%s", len(free_keycodes), free_keycodes)
583 return keycode_trans, server_keycodes, missing_keycodes
586def keymap_to_xmodmap(trans_keycodes):
587 """
588 Given a dict with keycodes as keys and lists of keyboard entries as values,
589 (keysym, keycode, index)
590 produce a list of xmodmap instructions to set the x11 keyboard to match it,
591 in the form:
592 ("keycode", keycode, [keysyms])
593 """
594 missing_keysyms = [] #the keysyms lookups which failed
595 instructions = []
596 all_entries = []
597 for entries in trans_keycodes.values():
598 all_entries += entries
599 keysyms_per_keycode = max([index for _, index in all_entries])+1
600 for server_keycode, entries in trans_keycodes.items():
601 keysyms = [None]*keysyms_per_keycode
602 names = [""]*keysyms_per_keycode
603 sentries = sorted(entries, key=lambda x:x[1])
604 for name, index in sentries:
605 assert 0<=index<keysyms_per_keycode
606 try:
607 keysym = X11Keyboard.parse_keysym(name)
608 except Exception:
609 keysym = None
610 if keysym is None:
611 if name!="":
612 missing_keysyms.append(name)
613 else:
614 if keysyms[index] is not None:
615 #if the client provides multiple keysyms for the same index,
616 #replace with the new one if the old one exists elsewhere,
617 #or skip it if we have another entry for it
618 can_override = any(True for i,v in enumerate(keysyms) if i<index and v==keysyms[index])
619 can_skip = any(True for i,v in enumerate(keysyms) if i!=index and v==keysym)
620 if can_override or can_skip:
621 l = log.debug
622 else:
623 l = log.warn
624 l("Warning: more than one keysym for keycode %-3i at index %i:", server_keycode, index)
625 l(" entries=%s", csv(tuple(sentries)))
626 l(" keysyms=%s", csv(keysyms))
627 l(" assigned keysym=%s", names[index])
628 l(" wanted keysym=%s", name)
629 if can_override:
630 l(" current value also found at another index, overriding it")
631 elif can_skip:
632 l(" new value also found elsewhere, skipping it")
633 else:
634 continue
635 names[index] = name
636 keysyms[index] = keysym
637 if name in DEBUG_KEYSYMS:
638 log.info("keymap_to_xmodmap: keysyms[%s]=%s (%s)", index, keysym, name)
639 #remove empty keysyms:
640 while keysyms and keysyms[0] is None:
641 keysyms = keysyms[1:]
642 l = log
643 if [k for k in keysyms if k in DEBUG_KEYSYMS]:
644 l = log.info
645 l("%s: %s -> %s", server_keycode, names, keysyms)
646 instructions.append(("keycode", server_keycode, keysyms))
648 if missing_keysyms:
649 log.error("cannot find the X11 keysym for the following key names: %s", set(missing_keysyms))
650 log("instructions=%s", instructions)
651 return instructions
654################################################################################
655# modifiers
657def clear_modifiers(_modifiers):
658 instructions = []
659 for i in range(0, 8):
660 instructions.append(("clear", i))
661 apply_xmodmap(instructions)
663def set_modifiers(modifiers):
664 """
665 modifiers is a dict: {modifier : [keynames]}
666 Note: the same keysym cannot appear in more than one modifier
667 """
668 instructions = []
669 for modifier, keynames in modifiers.items():
670 mod = X11Keyboard.parse_modifier(bytestostr(modifier))
671 if mod>=0:
672 instructions.append(("add", mod, keynames))
673 else:
674 log.error("Error: unknown modifier %s", modifier)
675 log("set_modifiers: %s", instructions)
676 def apply_or_trim(instructions):
677 err = apply_xmodmap(instructions)
678 log("set_modifiers: err=%s", err)
679 if err:
680 log("set_modifiers %s failed, retrying one more at a time", instructions)
681 l = len(instructions)
682 for i in range(1, l):
683 subset = instructions[:i]
684 log("set_modifiers testing with [:%s]=%s", i, subset)
685 err = apply_xmodmap(subset)
686 log("err=%s", err)
687 if err:
688 log.warn("removing problematic modifier mapping: %s", instructions[i-1])
689 instructions = instructions[:i-1]+instructions[i:]
690 return apply_or_trim(instructions)
691 apply_or_trim(instructions)
692 return modifiers
695def get_modifiers_from_meanings(xkbmap_mod_meanings):
696 """
697 xkbmap_mod_meanings maps a keyname to a modifier
698 returns keynames_for_mod: {modifier : [keynames]}
699 """
700 #first generate a {modifier : [keynames]} dict:
701 modifiers = {}
702 for keyname, modifier in xkbmap_mod_meanings.items():
703 l = modifiers.setdefault(bytestostr(modifier), [])
704 kn = bytestostr(keyname)
705 if kn not in l:
706 l.append(kn)
707 log("get_modifiers_from_meanings(%s) modifier dict=%s", xkbmap_mod_meanings, modifiers)
708 return modifiers
710def get_modifiers_from_keycodes(xkbmap_keycodes, add_default_modifiers=True):
711 """
712 Some platforms can't tell us about modifier mappings
713 So we try to find matches from the defaults below:
714 """
715 from xpra.keyboard.mask import DEFAULT_MODIFIER_MEANINGS
716 pref = DEFAULT_MODIFIER_MEANINGS
717 #keycodes are: {keycode : (keyval, name, keycode, group, level)}
718 matches = {}
719 log("get_modifiers_from_keycodes(%s...)", str(xkbmap_keycodes)[:160])
720 all_keynames = set()
721 for entry in xkbmap_keycodes:
722 _, keyname, _, _, _ = entry
723 modifier = pref.get(keyname)
724 if modifier:
725 keynames = matches.setdefault(modifier, [])
726 if keyname not in keynames:
727 keynames.add(keyname)
728 all_keynames.add(keyname)
729 if add_default_modifiers:
730 #try to add missings ones (magic!)
731 defaults = {}
732 for keyname, modifier in DEFAULT_MODIFIER_MEANINGS.items():
733 if keyname in all_keynames:
734 continue #aleady defined
735 if modifier not in matches:
736 #define it since it is completely missing
737 keynames = defaults.setdefault(modifier, [])
738 if keyname not in keynames:
739 keynames.append(keyname)
740 elif modifier in ["shift", "lock", "control", "mod1", "mod2"] or keyname=="ISO_Level3_Shift":
741 #these ones we always add them, even if a record for this modifier already exists
742 keynames = matches.setdefault(modifier, [])
743 if keyname not in keynames:
744 keynames.append(keyname)
745 log("get_modifiers_from_keycodes(...) adding defaults: %s", defaults)
746 matches.update(defaults)
747 log("get_modifiers_from_keycodes(...)=%s", matches)
748 return matches
750def map_missing_modifiers(keynames_for_mod):
751 x11_keycodes = X11Keyboard.get_keycode_mappings()
752 min_keycode, max_keycode = X11Keyboard.get_minmax_keycodes()
753 free_keycodes = [x for x in range(min_keycode, max_keycode) if x not in x11_keycodes]
754 log("map_missing_modifiers(%s) min_keycode=%i max_keycode=%i, free_keycodes=%s",
755 keynames_for_mod, min_keycode, max_keycode, free_keycodes)
756 keysyms_to_keycode = {}
757 for keycode, keysyms in x11_keycodes.items():
758 for keysym in keysyms:
759 keysyms_to_keycode.setdefault(keysym, []).append(keycode)
760 xmodmap_changes = []
761 for mod, keysyms in keynames_for_mod.items():
762 missing = []
763 for keysym in keysyms:
764 if keysym not in keysyms_to_keycode:
765 missing.append(keysym)
766 if missing:
767 log("map_missing_modifiers: no keycode found for modifier keys %s (%s)", csv(missing), mod)
768 if not free_keycodes:
769 log.warn("Warning: keymap is full, cannot add '%s' for modifier '%s'", keysym, mod)
770 else:
771 keycode = free_keycodes.pop()
772 xmodmap_changes.append(("keycode", keycode, missing))
773 if xmodmap_changes:
774 log("xmodmap_changes=%s", xmodmap_changes)
775 X11Keyboard.set_xmodmap(xmodmap_changes)