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.
5
6import os
7
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, typedict
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
16
17init_gdk_display_source()
18X11Keyboard = X11KeyboardBindings()
19
20log = Logger("x11", "keyboard")
21
22XKB = envbool("XPRA_XKB", True)
23DEBUG_KEYSYMS = [x for x in os.environ.get("XPRA_DEBUG_KEYSYMS", "").split(",") if len(x)>0]
24
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    ]
48
49
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()
57
58################################################################################
59# keyboard layouts
60
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    xkbmap_query_struct = typedict(xkbmap_query_struct)
76    if xkbmap_query_struct:
77        log("do_set_keymap using xkbmap_query struct=%s", xkbmap_query_struct)
78        #The xkbmap_query_struct data will look something like this:
79        #    {
80        #    b"rules"       : b"evdev",
81        #    b"model"       : b"pc105",
82        #    b"layout"      : b"gb",
83        #    b"options"     : b"grp:shift_caps_toggle",
84        #    }
85        #parse the data into a dict:
86        rules = xkbmap_query_struct.strget("rules")
87        model = xkbmap_query_struct.strget("model")
88        layout = xkbmap_query_struct.strget("layout")
89        variant = xkbmap_query_struct.strget("variant")
90        options = xkbmap_query_struct.strget("options")
91        if layout:
92            log.info("setting keymap: %s",
93                     csv("%s=%s" % (std(k), std(v)) for k,v in xkbmap_query_struct.items()
94                         if k in ("rules", "model", "layout", "variant", "options") and v))
95            if safe_setxkbmap(rules, model, layout, variant, options):
96                return
97        else:
98            if safe_setxkbmap(rules, model, "", "", ""):
99                return
100    #fallback for non X11 clients:
101    layout = xkbmap_layout or "us"
102    log.info("setting keyboard layout to '%s'", std(layout))
103    safe_setxkbmap("evdev", "pc105", layout, xkbmap_variant, xkbmap_options)
104
105def safe_setxkbmap(rules, model, layout, variant, options):
106    #(we execute the options separately in case that fails..)
107    try:
108        X11Keyboard.setxkbmap(rules, model, layout, variant, options)
109        return True
110    except Exception:
111        log("safe_setxkbmap%s", (rules, model, layout, variant, options), exc_info=True)
112        log.warn("Warning: failed to set exact keymap,")
113        log.warn(" rules=%s, model=%s, layout=%s, variant=%s, options=%s",
114                 bytestostr(rules), bytestostr(model), bytestostr(layout), bytestostr(variant), bytestostr(options))
115    if options:
116        #try again with no options:
117        try:
118            X11Keyboard.setxkbmap(rules, model, layout, variant, "")
119            return True
120        except Exception:
121            log("setxkbmap", exc_info=True)
122            log.error("Error: failed to set exact keymap")
123            log.error(" even without applying any options..")
124    return False
125
126################################################################################
127# keycodes
128
129def apply_xmodmap(instructions):
130    try:
131        with xsync:
132            unset = X11Keyboard.set_xmodmap(instructions)
133    except Exception as e:
134        log("apply_xmodmap(%s)", instructions, exc_info=True)
135        log.error("Error configuring modifier map: %s", e)
136        unset = instructions
137    if unset is None:
138        #None means an X11 error occurred, re-do all:
139        unset = instructions
140    return unset
141
142
143def get_keycode_mappings():
144    if XKB and X11Keyboard.hasXkb():
145        return X11Keyboard.get_xkb_keycode_mappings()
146    return X11Keyboard.get_keycode_mappings()
147
148def set_keycode_translation(xkbmap_x11_keycodes, xkbmap_keycodes):
149    """
150        Translate the given keycodes into the existing keymap
151    """
152    #get the list of keycodes (either from x11 keycodes or gtk keycodes):
153    if xkbmap_x11_keycodes:
154        dump_dict(xkbmap_x11_keycodes)
155        keycodes = indexed_mappings(xkbmap_x11_keycodes)
156    else:
157        keycodes = gtk_keycodes_to_mappings(xkbmap_keycodes)
158    log("set_keycode_translation(%s, %s)", xkbmap_x11_keycodes, xkbmap_keycodes)
159    log(" keycodes=%s", keycodes)
160    #keycodes = {
161    #     9: set([('', 1), ('Escape', 4), ('', 3), ('Escape', 0), ('Escape', 2)]),
162    #     10: set([('onesuperior', 4), ('onesuperior', 8), ('exclam', 1), ('1', 6), ('exclam', 3), ('1', 2), ('exclamdown', 9), ('exclamdown', 5), ('1', 0), ('exclam', 7)]),
163    x11_keycodes = get_keycode_mappings()
164    log(" x11_keycodes=%s", x11_keycodes)
165    #x11_keycodes = {
166    #    8: ['Mode_switch', '', 'Mode_switch', '', 'Mode_switch'],
167    #    9: ['Escape', '', 'Escape', '', 'Escape'],
168    #for faster lookups:
169    x11_keycodes_for_keysym = {}
170    for keycode, keysyms in x11_keycodes.items():
171        for keysym in keysyms:
172            x11_keycodes_for_keysym.setdefault(keysym, set()).add(keycode)
173    def find_keycode(kc, keysym, i):
174        keycodes = tuple(x11_keycodes_for_keysym.get(keysym, set()))
175        if keysym in DEBUG_KEYSYMS:
176            log.info("set_keycode_translation: find_keycode%s x11 keycodes=%s", (kc, keysym, i), keycodes)
177        def rlog(keycode, msg):
178            if keysym in DEBUG_KEYSYMS:
179                log.info("set_keycode_translation: find_keycode%s=%s (%s)", (kc, keysym, i), keycode, msg)
180        if not keycodes:
181            return None
182        #no other option, use it:
183        for keycode in keycodes:
184            defs = x11_keycodes.get(keycode)
185            if keysym in DEBUG_KEYSYMS:
186                log.info("server x11 keycode %i: %s", keycode, defs)
187            assert defs, "bug: keycode %i not found in %s" % (keycode, x11_keycodes)
188            if len(defs)>i and defs[i]==keysym:
189                rlog(keycode, "exact index match")
190                return keycode, True
191        #if possible, use the same one:
192        if kc in keycodes:
193            rlog(kc, "using same keycode as client")
194            return kc, False
195        keycode = keycodes[0]
196        rlog(keycode, "using first match")
197        return keycode, False
198    #generate the translation map:
199    trans = {}
200    for keycode, defs in keycodes.items():
201        if bool(set(DEBUG_KEYSYMS) & set(bytestostr(d[0]) for d in defs)):
202            log.info("client keycode=%i, defs=%s", keycode, defs)
203        for bkeysym, i in tuple(defs):             #ie: (b'1', 0) or (b'A', 1), etc
204            keysym = bytestostr(bkeysym)
205            m = find_keycode(keycode, keysym, i)
206            if m:
207                x11_keycode, index_matched = m
208                trans[(keycode, keysym)] = x11_keycode
209                trans[keysym] = x11_keycode
210                if index_matched:
211                    trans[(keysym, i)] = x11_keycode
212    if not xkbmap_x11_keycodes:
213        #now add all the keycodes we may not have mapped yet
214        #(present in x11_keycodes but not keycodes)
215        for keycode, keysyms in x11_keycodes.items():
216            for i, keysym in enumerate(keysyms):
217                if keysym not in trans:
218                    if keysym in DEBUG_KEYSYMS:
219                        log.info("x11 keycode %s: %s", keycode, keysym)
220                    trans[keysym] = keycode
221                key = (keysym, i)
222                if key not in trans:
223                    if keysym in DEBUG_KEYSYMS:
224                        log.info("x11 keycode %s: %s", keycode, key)
225                    trans[key] = keycode
226    log("set_keycode_translation(..)=%s", trans)
227    return trans
228
229def set_all_keycodes(xkbmap_x11_keycodes, xkbmap_keycodes, preserve_server_keycodes, modifiers):
230    """
231        Clients that have access to raw x11 keycodes should provide
232        an xkbmap_x11_keycodes map, we otherwise fallback to using
233        the xkbmap_keycodes gtk keycode list.
234        We try to preserve the initial keycodes if asked to do so,
235        we retrieve them from the current server keymap and combine
236        them with the given keycodes.
237        The modifiers dict can be obtained by calling
238        get_modifiers_from_meanings or get_modifiers_from_keycodes.
239        We use it to ensure that two modifiers are not
240        mapped to the same keycode (which is not allowed).
241        We return a translation map for keycodes after setting them up,
242        the key is (keycode, keysym) and the value is the server keycode.
243    """
244    log("set_all_keycodes(%s.., %s.., %s.., %s)",
245        str(xkbmap_x11_keycodes)[:60], str(xkbmap_keycodes)[:60], str(preserve_server_keycodes)[:60], modifiers)
246
247    #so we can validate entries:
248    keysym_to_modifier = {}
249    for modifier, keysyms in modifiers.items():
250        for keysym in keysyms:
251            existing_mod = keysym_to_modifier.get(keysym)
252            if existing_mod and existing_mod!=modifier:
253                log.error("ERROR: keysym %s is mapped to both %s and %s !", keysym, modifier, existing_mod)
254            else:
255                keysym_to_modifier[keysym] = modifier
256                if keysym in DEBUG_KEYSYMS:
257                    log.info("set_all_keycodes() keysym_to_modifier[%s]=%s", keysym, modifier)
258    log("keysym_to_modifier=%s", keysym_to_modifier)
259
260    def modifiers_for(entries):
261        """ entries can only point to a single modifier - verify """
262        modifiers = set()
263        l = log
264        for keysym, _ in entries:
265            modifier = keysym_to_modifier.get(keysym)
266            if modifier:
267                modifiers.add(modifier)
268            if keysym in DEBUG_KEYSYMS:
269                l = log.info
270        l("modifiers_for(%s)=%s", entries, modifiers)
271        return modifiers
272
273    def filter_mappings(mappings, drop_extra_keys=False):
274        filtered = {}
275        invalid_keysyms = set()
276        def estr(entries):
277            try:
278                return csv(tuple(set(x[0] for x in entries)))
279            except Exception:
280                return csv(tuple(entries))
281        for keycode, entries in mappings.items():
282            mods = modifiers_for(entries)
283            if len(mods)>1:
284                log.warn("Warning: keymapping changed:")
285                log.warn(" keycode %s points to %i modifiers: %s", keycode, len(mods), csv(tuple(mods)))
286                log.warn(" from definition: %s", estr(entries))
287                for mod in mods:
288                    emod = [entry for entry in entries if mod in modifiers_for([entry])]
289                    log.warn(" %s: %s", mod, estr(emod))
290                #keep just the first one:
291                mods = tuple(mods)[:1]
292                mod = mods[0]
293                entries = [entry for entry in entries if mod in modifiers_for([entry])]
294                log.warn(" keeping: %s for %s", estr(entries), mod)
295            #now remove entries for keysyms we don't have:
296            f_entries = set((keysym, index) for keysym, index in entries
297                            if keysym and X11Keyboard.parse_keysym(keysym) is not None)
298            for keysym, _ in entries:
299                if keysym and X11Keyboard.parse_keysym(keysym) is None:
300                    invalid_keysyms.add(keysym)
301            if not f_entries:
302                log("keymapping removed invalid keycode entry %s pointing to only unknown keysyms: %s",
303                    keycode, entries)
304                continue
305            if drop_extra_keys:
306                if not any(keysym for keysym, index in entries if (
307                    X11Keyboard.parse_keysym(keysym) is not None and keysym not in OPTIONAL_KEYS)
308                ):
309                    log("keymapping removed keycode entry %s pointing to optional keys: %s", keycode, entries)
310                    continue
311            filtered[keycode] = f_entries
312        if invalid_keysyms:
313            log.warn("Warning: the following keysyms are invalid and have not been mapped")
314            log.warn(" %s", csv(invalid_keysyms))
315        return filtered
316
317    #get the list of keycodes (either from x11 keycodes or gtk keycodes):
318    if xkbmap_x11_keycodes:
319        log("using x11 keycodes: %s", xkbmap_x11_keycodes)
320        dump_dict(xkbmap_x11_keycodes)
321        keycodes = indexed_mappings(xkbmap_x11_keycodes)
322    else:
323        log("using gtk keycodes: %s", xkbmap_keycodes)
324        keycodes = gtk_keycodes_to_mappings(xkbmap_keycodes)
325    #filter to ensure only valid entries remain:
326    log("keycodes=%s", keycodes)
327    keycodes = filter_mappings(keycodes)
328
329    #now lookup the current keycodes (if we need to preserve them)
330    preserve_keycode_entries = {}
331    if preserve_server_keycodes:
332        preserve_keycode_entries = X11Keyboard.get_keycode_mappings()
333        log("preserved mappings:")
334        dump_dict(preserve_keycode_entries)
335
336    kcmin, kcmax = X11Keyboard.get_minmax_keycodes()
337    for try_harder in (False, True):
338        filtered_preserve_keycode_entries = filter_mappings(indexed_mappings(preserve_keycode_entries), try_harder)
339        log("filtered_preserve_keycode_entries=%s", filtered_preserve_keycode_entries)
340        trans, new_keycodes, missing_keycodes = translate_keycodes(kcmin, kcmax, keycodes,
341                                                                   filtered_preserve_keycode_entries,
342                                                                   keysym_to_modifier,
343                                                                   try_harder)
344        if not missing_keycodes:
345            break
346    instructions = keymap_to_xmodmap(new_keycodes)
347    unset = apply_xmodmap(instructions)
348    log("unset=%s", unset)
349    return trans
350
351def dump_dict(d):
352    for k,v in d.items():
353        log("%s\t\t=\t%s", k, v)
354
355def group_by_keycode(entries):
356    keycodes = {}
357    log_keycodes = []
358    for keysym, keycode, index in entries:
359        keycodes.setdefault(keycode, set()).add((keysym, index))
360        if keysym in DEBUG_KEYSYMS:
361            log_keycodes.append(keycode)
362    if log_keycodes:
363        log.info("group_by_keycode: %s", dict((keycode, keycodes.get(keycode)) for keycode in log_keycodes))
364    return keycodes
365
366def indexed_mappings(raw_mappings):
367    indexed = {}
368    for keycode, keysyms in raw_mappings.items():
369        pairs = set()
370        l = log
371        for i, keysym in enumerate(keysyms):
372            if keysym in DEBUG_KEYSYMS:
373                l = log.info
374            pairs.add((keysym, i))
375        indexed[keycode] = pairs
376        l("indexed_mappings: %s=%s", keycode, pairs)
377    return indexed
378
379
380def gtk_keycodes_to_mappings(gtk_mappings):
381    """
382        Takes gtk keycodes as obtained by get_gtk_keymap, in the form:
383        #[(keyval, keyname, keycode, group, level), ..]
384        And returns a list of entries in the form:
385        [[keysym, keycode, index], ..]
386    """
387    #use the keycodes supplied by gtk:
388    mappings = {}
389    for _, name, keycode, group, level in gtk_mappings:
390        if keycode<0:
391            continue            #ignore old 'add_if_missing' client side code
392        index = group*2+level
393        mappings.setdefault(keycode, set()).add((name, index))
394    return mappings
395
396def x11_keycodes_to_list(x11_mappings):
397    """
398        Takes x11 keycodes as obtained by get_keycode_mappings(), in the form:
399        #{keycode : [keysyms], ..}
400        And returns a list of entries in the form:
401        [[keysym, keycode, index], ..]
402    """
403    entries = []
404    if x11_mappings:
405        for keycode, keysyms in x11_mappings.items():
406            index = 0
407            for keysym in keysyms:
408                if keysym:
409                    entries.append([keysym, int(keycode), index])
410                    if keysym in DEBUG_KEYSYMS:
411                        log.info("x11_keycodes_to_list: (%s, %s) : %s", keycode, keysyms, (keysym, int(keycode), index))
412                index += 1
413    return entries
414
415
416def translate_keycodes(kcmin, kcmax, keycodes, preserve_keycode_entries, keysym_to_modifier, try_harder=False):
417    """
418        The keycodes given may not match the range that the server supports,
419        or some of those keycodes may not be usable (only one modifier can
420        be mapped to a single keycode) or we want to preserve a keycode,
421        or modifiers want to use the same keycode (which is not possible),
422        so we return a translation map for those keycodes that have been
423        remapped.
424        The preserve_keycodes is a dict containing {keycode:[entries]}
425        for keys we want to preserve the keycode for.
426        Note: a client_keycode of '0' is valid (osx uses that),
427        but server_keycode generally starts at 8...
428    """
429    log("translate_keycodes(%s, %s, %s, %s, %s, %s)",
430        kcmin, kcmax, keycodes, preserve_keycode_entries, keysym_to_modifier, try_harder)
431    #list of free keycodes we can use:
432    free_keycodes = [i for i in range(kcmin, kcmax+1) if i not in preserve_keycode_entries]
433    keycode_trans = {}              #translation map from client keycode to our server keycode
434    server_keycodes = {}            #the new keycode definitions
435    missing_keycodes = []           #the groups of entries we failed to map due to lack of free keycodes
436
437    #to do faster lookups:
438    preserve_keysyms_map = {}
439    for keycode, entries in preserve_keycode_entries.items():
440        for keysym, _ in entries:
441            preserve_keysyms_map.setdefault(keysym, set()).add(keycode)
442    for k in DEBUG_KEYSYMS:
443        log.info("preserve_keysyms_map[%s]=%s", k, preserve_keysyms_map.get(k))
444
445    def do_assign(keycode, server_keycode, entries, override_server_keycode=False):
446        """ may change the keycode if needed
447            in which case we update the entries and populate 'keycode_trans'
448        """
449        l = log
450        for name, _ in entries:
451            if name in DEBUG_KEYSYMS:
452                l = log.info
453        if (server_keycode in server_keycodes) and not override_server_keycode:
454            l("assign: server keycode %s already in use: %s", server_keycode, server_keycodes.get(server_keycode))
455            server_keycode = -1
456        elif server_keycode>0 and (server_keycode<kcmin or server_keycode>kcmax):
457            l("assign: keycode %s out of range (%s to %s)", server_keycode, kcmin, kcmax)
458            server_keycode = -1
459        if server_keycode<=0:
460            if free_keycodes:
461                server_keycode = free_keycodes[0]
462                l("set_keycodes key %s using free keycode=%s", entries, server_keycode)
463            else:
464                msg = "set_keycodes: no free keycodes!, cannot translate %s: %s", server_keycode, tuple(entries)
465                if try_harder:
466                    log.error(*msg)
467                else:
468                    l(*msg)
469                missing_keycodes.append(entries)
470                server_keycode = -1
471        if server_keycode>0:
472            l("set_keycodes key %s (%s) mapped to keycode=%s", keycode, tuple(entries), server_keycode)
473            #can't use it any more!
474            if server_keycode in free_keycodes:
475                free_keycodes.remove(server_keycode)
476            #record it in trans map:
477            for name, _ in entries:
478                if keycode>=0 and server_keycode!=keycode:
479                    keycode_trans[(keycode, name)] = server_keycode
480                    l("keycode_trans[(%s, %s)]=%s", keycode, bytestostr(name), server_keycode)
481                keycode_trans[name] = server_keycode
482                l("keycode_trans[%s]=%s", bytestostr(name), server_keycode)
483            server_keycodes[server_keycode] = entries
484        return server_keycode
485
486    def assign(client_keycode, entries):
487        if not entries:
488            return 0
489        #all the keysyms for this keycode:
490        keysyms = set(keysym for keysym, _ in entries)
491        if not keysyms:
492            return 0
493        if len(keysyms)==1 and tuple(keysyms)[0]=='0xffffff':
494            log("skipped invalid keysym: %s / %s", client_keycode, entries)
495            return 0
496        l = log
497        if [k for k in keysyms if k in DEBUG_KEYSYMS]:
498            l = log.info
499        l("assign(%s, %s)", client_keycode, entries)
500
501        if not preserve_keycode_entries:
502            return do_assign(client_keycode, client_keycode, entries)
503        if len(keysyms)==1:
504            #only one keysym, replace with single entry
505            entries = set([(tuple(keysyms)[0], 0)])
506
507        #the candidate preserve entries: those that have at least one of the keysyms:
508        preserve_keycode_matches = {}
509        for keysym in tuple(keysyms):
510            keycodes = preserve_keysyms_map.get(keysym, [])
511            for keycode in keycodes:
512                v = preserve_keycode_entries.get(keycode)
513                preserve_keycode_matches[keycode] = v
514                l("preserve_keycode_matches[%s]=%s", keycode, v)
515
516        if not preserve_keycode_matches:
517            l("no preserve matches for %s", tuple(entries))
518            return do_assign(client_keycode, -1, entries)         #nothing to preserve
519
520        l("preserve matches for %s : %s", entries, preserve_keycode_matches)
521        #direct superset:
522        for p_keycode, p_entries in preserve_keycode_matches.items():
523            if entries.issubset(p_entries):
524                l("found direct preserve superset 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, p_entries, override_server_keycode=True)
527            if p_entries.issubset(entries):
528                l("found direct superset of preserve for client keycode %s %s -> server keycode %s %s",
529                  client_keycode, tuple(entries), p_keycode, tuple(p_entries))
530                return do_assign(client_keycode, p_keycode, entries, override_server_keycode=True)
531
532        #ignoring indexes, but requiring at least as many keysyms:
533        for p_keycode, p_entries in preserve_keycode_matches.items():
534            p_keysyms = set(keysym for keysym,_ in p_entries)
535            if keysyms.issubset(p_keysyms):
536                if len(p_entries)>len(entries):
537                    l("found keysym preserve superset with more keys for %s : %s", tuple(entries), tuple(p_entries))
538                    return do_assign(client_keycode, p_keycode, p_entries, override_server_keycode=True)
539            if p_keysyms.issubset(keysyms):
540                l("found keysym superset of preserve with more keys for %s : %s", tuple(entries), tuple(p_entries))
541                return do_assign(client_keycode, p_keycode, entries, override_server_keycode=True)
542
543        if try_harder:
544            #try to match the main key only:
545            main_key = set((keysym, index) for keysym, index in entries if index==0)
546            if len(main_key)==1:
547                for p_keycode, p_entries in preserve_keycode_matches.items():
548                    p_keysyms = set(keysym for keysym,_ in p_entries)
549                    if main_key.issubset(p_entries):
550                        l("found main key superset for %s : %s", main_key, tuple(p_entries))
551                        return do_assign(client_keycode, p_keycode, p_entries, override_server_keycode=True)
552
553        l("no matches for %s", tuple(entries))
554        return do_assign(client_keycode, -1, entries)
555
556    #now try to assign each keycode:
557    for keycode in sorted(keycodes.keys()):
558        entries = keycodes.get(keycode)
559        log("assign(%s, %s)", keycode, entries)
560        assign(keycode, entries)
561
562    #add all the other preserved ones that have not been mapped to any client keycode:
563    for server_keycode, entries in preserve_keycode_entries.items():
564        if server_keycode not in server_keycodes:
565            do_assign(-1, server_keycode, entries)
566
567    #find all keysyms assigned so far:
568    all_keysyms = set()
569    for entries in server_keycodes.values():
570        for x in [keysym for keysym, _ in entries]:
571            all_keysyms.add(x)
572    log("all_keysyms=%s", all_keysyms)
573
574    #defined keysyms for modifiers if some are missing:
575    for keysym, modifier in keysym_to_modifier.items():
576        if keysym not in all_keysyms:
577            l = log
578            if keysym in DEBUG_KEYSYMS:
579                l = log.info
580            l("found missing keysym %s for modifier %s, will add it", keysym, modifier)
581            new_keycode = set([(keysym, 0)])
582            server_keycode = assign(-1, new_keycode)
583            l("assigned keycode %s for key '%s' of modifier '%s'", server_keycode, keysym, modifier)
584
585    log("translated keycodes=%s", keycode_trans)
586    log("%s free keycodes=%s", len(free_keycodes), free_keycodes)
587    return keycode_trans, server_keycodes, missing_keycodes
588
589
590def keymap_to_xmodmap(trans_keycodes):
591    """
592        Given a dict with keycodes as keys and lists of keyboard entries as values,
593        (keysym, keycode, index)
594        produce a list of xmodmap instructions to set the x11 keyboard to match it,
595        in the form:
596        ("keycode", keycode, [keysyms])
597    """
598    missing_keysyms = []            #the keysyms lookups which failed
599    instructions = []
600    all_entries = []
601    for entries in trans_keycodes.values():
602        all_entries += entries
603    keysyms_per_keycode = max([index for _, index in all_entries])+1
604    for server_keycode, entries in trans_keycodes.items():
605        keysyms = [None]*keysyms_per_keycode
606        names = [""]*keysyms_per_keycode
607        sentries = sorted(entries, key=lambda x:x[1])
608        for name, index in sentries:
609            assert 0<=index<keysyms_per_keycode
610            try:
611                keysym = X11Keyboard.parse_keysym(name)
612            except Exception:
613                keysym = None
614            if keysym is None:
615                if name!="":
616                    missing_keysyms.append(name)
617            else:
618                if keysyms[index] is not None:
619                    #if the client provides multiple keysyms for the same index,
620                    #replace with the new one if the old one exists elsewhere,
621                    #or skip it if we have another entry for it
622                    can_override = any(True for i,v in enumerate(keysyms) if i<index and v==keysyms[index])
623                    can_skip = any(True for i,v in enumerate(keysyms) if i!=index and v==keysym)
624                    if can_override or can_skip:
625                        l = log.debug
626                    else:
627                        l = log.warn
628                    l("Warning: more than one keysym for keycode %-3i at index %i:", server_keycode, index)
629                    l(" entries=%s", csv(tuple(sentries)))
630                    l(" keysyms=%s", csv(keysyms))
631                    l(" assigned keysym=%s", names[index])
632                    l(" wanted keysym=%s", name)
633                    if can_override:
634                        l(" current value also found at another index, overriding it")
635                    elif can_skip:
636                        l(" new value also found elsewhere, skipping it")
637                    else:
638                        continue
639                names[index] = name
640                keysyms[index] = keysym
641                if name in DEBUG_KEYSYMS:
642                    log.info("keymap_to_xmodmap: keysyms[%s]=%s (%s)", index, keysym, name)
643        #remove empty keysyms:
644        while keysyms and keysyms[0] is None:
645            keysyms = keysyms[1:]
646        l = log
647        if [k for k in keysyms if k in DEBUG_KEYSYMS]:
648            l = log.info
649        l("%s: %s -> %s", server_keycode, names, keysyms)
650        instructions.append(("keycode", server_keycode, keysyms))
651
652    if missing_keysyms:
653        log.error("cannot find the X11 keysym for the following key names: %s", set(missing_keysyms))
654    log("instructions=%s", instructions)
655    return  instructions
656
657
658################################################################################
659# modifiers
660
661def clear_modifiers(_modifiers):
662    instructions = []
663    for i in range(0, 8):
664        instructions.append(("clear", i))
665    apply_xmodmap(instructions)
666
667def set_modifiers(modifiers):
668    """
669        modifiers is a dict: {modifier : [keynames]}
670        Note: the same keysym cannot appear in more than one modifier
671    """
672    instructions = []
673    for modifier, keynames in modifiers.items():
674        mod = X11Keyboard.parse_modifier(bytestostr(modifier))
675        if mod>=0:
676            instructions.append(("add", mod, keynames))
677        else:
678            log.error("Error: unknown modifier %s", modifier)
679    log("set_modifiers: %s", instructions)
680    def apply_or_trim(instructions):
681        err = apply_xmodmap(instructions)
682        log("set_modifiers: err=%s", err)
683        if err:
684            log("set_modifiers %s failed, retrying one more at a time", instructions)
685            l = len(instructions)
686            for i in range(1, l):
687                subset = instructions[:i]
688                log("set_modifiers testing with [:%s]=%s", i, subset)
689                err = apply_xmodmap(subset)
690                log("err=%s", err)
691                if err:
692                    log.warn("removing problematic modifier mapping: %s", instructions[i-1])
693                    instructions = instructions[:i-1]+instructions[i:]
694                    return apply_or_trim(instructions)
695    apply_or_trim(instructions)
696    return  modifiers
697
698
699def get_modifiers_from_meanings(xkbmap_mod_meanings):
700    """
701        xkbmap_mod_meanings maps a keyname to a modifier
702        returns keynames_for_mod: {modifier : [keynames]}
703    """
704    #first generate a {modifier : [keynames]} dict:
705    modifiers = {}
706    for keyname, modifier in xkbmap_mod_meanings.items():
707        l = modifiers.setdefault(bytestostr(modifier), [])
708        kn = bytestostr(keyname)
709        if kn not in l:
710            l.append(kn)
711    log("get_modifiers_from_meanings(%s) modifier dict=%s", xkbmap_mod_meanings, modifiers)
712    return modifiers
713
714def get_modifiers_from_keycodes(xkbmap_keycodes, add_default_modifiers=True):
715    """
716        Some platforms can't tell us about modifier mappings
717        So we try to find matches from the defaults below:
718    """
719    from xpra.keyboard.mask import DEFAULT_MODIFIER_MEANINGS
720    pref = DEFAULT_MODIFIER_MEANINGS
721    #keycodes are: {keycode : (keyval, name, keycode, group, level)}
722    matches = {}
723    log("get_modifiers_from_keycodes(%s...)", str(xkbmap_keycodes)[:160])
724    all_keynames = set()
725    for entry in xkbmap_keycodes:
726        _, keyname, _, _, _ = entry
727        modifier = pref.get(keyname)
728        if modifier:
729            keynames = matches.setdefault(modifier, [])
730            if keyname not in keynames:
731                keynames.append(keyname)
732            all_keynames.add(keyname)
733    if add_default_modifiers:
734        #try to add missings ones (magic!)
735        defaults = {}
736        for keyname, modifier in DEFAULT_MODIFIER_MEANINGS.items():
737            if keyname in all_keynames:
738                continue            #aleady defined
739            if modifier not in matches:
740                #define it since it is completely missing
741                keynames = defaults.setdefault(modifier, [])
742                if keyname not in keynames:
743                    keynames.append(keyname)
744            elif modifier in ["shift", "lock", "control", "mod1", "mod2"] or keyname=="ISO_Level3_Shift":
745                #these ones we always add them, even if a record for this modifier already exists
746                keynames = matches.setdefault(modifier, [])
747                if keyname not in keynames:
748                    keynames.append(keyname)
749        log("get_modifiers_from_keycodes(...) adding defaults: %s", defaults)
750        matches.update(defaults)
751    log("get_modifiers_from_keycodes(...)=%s", matches)
752    return matches
753
754def map_missing_modifiers(keynames_for_mod):
755    x11_keycodes = X11Keyboard.get_keycode_mappings()
756    min_keycode, max_keycode = X11Keyboard.get_minmax_keycodes()
757    free_keycodes = [x for x in range(min_keycode, max_keycode) if x not in x11_keycodes]
758    log("map_missing_modifiers(%s) min_keycode=%i max_keycode=%i, free_keycodes=%s",
759        keynames_for_mod, min_keycode, max_keycode, free_keycodes)
760    keysyms_to_keycode = {}
761    for keycode, keysyms in x11_keycodes.items():
762        for keysym in keysyms:
763            keysyms_to_keycode.setdefault(keysym, []).append(keycode)
764    xmodmap_changes = []
765    for mod, keysyms in keynames_for_mod.items():
766        missing = []
767        for keysym in keysyms:
768            if keysym not in keysyms_to_keycode:
769                missing.append(keysym)
770        if missing:
771            log("map_missing_modifiers: no keycode found for modifier keys %s (%s)", csv(missing), mod)
772            if not free_keycodes:
773                log.warn("Warning: keymap is full, cannot add '%s' for modifier '%s'", keysym, mod)
774            else:
775                keycode = free_keycodes.pop()
776                xmodmap_changes.append(("keycode", keycode, missing))
777    if xmodmap_changes:
778        log("xmodmap_changes=%s", xmodmap_changes)
779        X11Keyboard.set_xmodmap(xmodmap_changes)
780