From a4be9132ea512c5b046358f235ba862116d3de4b Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Sat, 13 Aug 2016 09:35:38 +0000 Subject: [PATCH] #1172 / #1282: initial raw keyboard support for X11 and keyboard layout/variant options: * remove make_keyboard_helper, just set the helper class instead * add keyboard options: raw, layout, layouts, variant, variants and options * add example in config file * minor: store more keyboard structures using lists (easier to parse in debug output than sets) * in raw keyboard mode: skip setting the keycodes and keep keycode_translation empty git-svn-id: https://xpra.org/svn/Xpra/trunk@13328 3bb7dfac-3a0b-4e04-842a-767bc560f471 --- rpmbuild/xpra.spec | 5 +- src/etc/xpra/conf.d/40_client.conf.in | 3 - .../xpra/conf.d/42_client_keyboard.conf.in | 27 ++++++ src/man/xpra.1 | 21 +++++ src/xpra/client/gtk_base/gtk_client_base.py | 5 +- .../client/gtk_base/gtk_keyboard_helper.py | 4 +- .../client/gtk_base/gtk_tray_menu_base.py | 20 ++-- src/xpra/client/keyboard_helper.py | 87 +++++++++++++++-- src/xpra/client/ui_client_base.py | 35 +++---- src/xpra/scripts/config.py | 25 +++++ src/xpra/scripts/main.py | 18 ++++ src/xpra/server/server_base.py | 1 + src/xpra/x11/server_keyboard_config.py | 94 ++++++++++++------- src/xpra/x11/xkbhelper.py | 4 +- 14 files changed, 260 insertions(+), 89 deletions(-) create mode 100644 src/etc/xpra/conf.d/42_client_keyboard.conf.in diff --git a/rpmbuild/xpra.spec b/rpmbuild/xpra.spec index 3c7804cafb..791524a175 100644 --- a/rpmbuild/xpra.spec +++ b/rpmbuild/xpra.spec @@ -113,7 +113,7 @@ %endif %if 0%{?suse_version} -#SUSE Leap aka 42.1 does not have python3-crypto, so skip the python3 build there +#SUSE Leap aka 42.1 does not have python3-crypto, so skip the python3 build there %if 0%{?suse_version} == 1315 %define with_python3 0 %endif @@ -192,7 +192,7 @@ Requires: xpra-common = %{version}-%{build_no}%{dist} Requires: sshpass %endif %if 0%{?suse_version} -#only use recommends because these are not in the standard repos: +#only use recommends because these are not in the standard repos: Recommends: python-gstreamer Recommends: cups-pdf Recommends: cups-filters @@ -401,6 +401,7 @@ rm -rf $RPM_BUILD_ROOT %config %{_sysconfdir}/xpra/conf.d/30_picture.conf %config %{_sysconfdir}/xpra/conf.d/35_webcam.conf %config %{_sysconfdir}/xpra/conf.d/40_client.conf +%config %{_sysconfdir}/xpra/conf.d/42_client_keyboard.conf %config %{_sysconfdir}/xpra/conf.d/50_server_network.conf %config %{_sysconfdir}/xpra/conf.d/55_server_x11.conf %config %{_sysconfdir}/xpra/conf.d/60_server.conf diff --git a/src/etc/xpra/conf.d/40_client.conf.in b/src/etc/xpra/conf.d/40_client.conf.in index 33cfddda08..3d5f3fc0d7 100644 --- a/src/etc/xpra/conf.d/40_client.conf.in +++ b/src/etc/xpra/conf.d/40_client.conf.in @@ -12,9 +12,6 @@ title = @title@ on @client-machine@ # Icon used by the system tray: #tray-icon = /path/to/icon.png -# Keyboard synchronization: -keyboard-sync = yes - # Client ssh command: #ssh = "C:\Program Files\Plink.exe" -ssh -noagent #ssh = /usr/bin/ssh diff --git a/src/etc/xpra/conf.d/42_client_keyboard.conf.in b/src/etc/xpra/conf.d/42_client_keyboard.conf.in new file mode 100644 index 0000000000..190c8f6a9d --- /dev/null +++ b/src/etc/xpra/conf.d/42_client_keyboard.conf.in @@ -0,0 +1,27 @@ +################################################################################ +# Client Keyboard Options + +# Keyboard synchronization: +keyboard-sync = yes + + +# Raw mode +# (unsafe: can only be used when both the client and server use the same keycodes) +# keyboard-raw = no + + +# The options below use X11 names and semantics, on all platforms + +# Layout +# (will be detected automatically, this can be used to override) +# keyboard-layout = us +# keyboard-layout = us,gb,fr + +# Layouts: the different types of keyboard layouts that may be used +# (will be detected automatically, this can be used to override) +# keyboard-layouts = us,fr +# keyboard-layouts = us,gb,fr + +# Variant: +# variant = ,dvorak +# variant = altgr-intl diff --git a/src/man/xpra.1 b/src/man/xpra.1 index e6f395e2ee..42ce55240f 100644 --- a/src/man/xpra.1 +++ b/src/man/xpra.1 @@ -105,6 +105,12 @@ xpra \- viewer for remote, persistent X applications \fIDEVICEID\fP] [\fB\-\-remote\-logging\fP=\fIyes\fP|\fIno\fP|\fIboth\fP] [\fB\-\-keyboard\-sync\fP=\fIyes\fP|\fIno\fP] +[\fB\-\-keyboard\-raw\fP=\fIyes\fP|\fIno\fP] +[\fB\-\-keyboard\-layout\fP=\fILAYOUTSTRING\fP] +[\fB\-\-keyboard\-layouts\fP=\fILAYOUTS\fP] +[\fB\-\-keyboard\-variant\fP=\fIVARIANT\fP] +[\fB\-\-keyboard\-variants\fP=\fIVARIANTS\fP] +[\fB\-\-keyboard\-options\fP=\fIOPTIONS\fP] [\fB\-\-tray\fP=\fIyes\fP|\fIno\fP] [\fB\-\-av\-sync\fP=\fIyes\fP|\fIno\fP] [\fB\-\-sound\-source\fP=\fIPLUGIN\fP] @@ -1080,6 +1086,21 @@ Disabling synchronization can prevent keys from repeating unexpectedly on high latency links but it may also disrupt applications which access the keyboard directly (games, etc.). .TP +\fB\-\-keyboard\-raw\fP=\fIyes\fP|\fIno\fP +Tells the server to process all keyboard input untranslated. +Both the client and the server must be using the same type of keyboard +interface. (ie: both using X11) +\fB\-\-keyboard\-layout\fP=\fILAYOUTSTRING\fP +The keyboard layout is normally detected automatically. +This option overrides it. +\fB\-\-keyboard\-layouts\fP=\fILAYOUTS\fP +The list of keyboard layouts to enable. +\fB\-\-keyboard\-variant\fP=\fIVARIANT\fP +Override for the keyboard layout variant. +\fB\-\-keyboard\-variants\fP=\fIVARIANTS\fP +Override for the keyboard layout variants. +\fB\-\-keyboard\-options\fP=\fIOPTIONS\fP +Override for the keyboard options sent to the server. \fB\-\-sound\-source\fP=\fIPLUGIN\fP Specifies the GStreamer sound plugin used for capturing the sound stream. This affects "speaker forwarding" on the server, and "microphone" forwarding diff --git a/src/xpra/client/gtk_base/gtk_client_base.py b/src/xpra/client/gtk_base/gtk_client_base.py index 97b7a0e541..e73d30b60e 100644 --- a/src/xpra/client/gtk_base/gtk_client_base.py +++ b/src/xpra/client/gtk_base/gtk_client_base.py @@ -57,6 +57,7 @@ def __init__(self): self.session_info = None self.bug_report = None self.start_new_command = None + self.keyboard_helper_class = GTKKeyboardHelper #opengl bits: self.client_supports_opengl = False self.opengl_enabled = False @@ -300,10 +301,6 @@ def get_window_frame_sizes(self): return wfs - def make_keyboard_helper(self, keyboard_sync, key_shortcuts): - return GTKKeyboardHelper(self.send, keyboard_sync, key_shortcuts) - - def _add_statusicon_tray(self, tray_list): #add gtk.StatusIcon tray: try: diff --git a/src/xpra/client/gtk_base/gtk_keyboard_helper.py b/src/xpra/client/gtk_base/gtk_keyboard_helper.py index 4c6635d078..1774069252 100644 --- a/src/xpra/client/gtk_base/gtk_keyboard_helper.py +++ b/src/xpra/client/gtk_base/gtk_keyboard_helper.py @@ -15,8 +15,8 @@ class GTKKeyboardHelper(KeyboardHelper): - def __init__(self, net_send, keyboard_sync, key_shortcuts): - KeyboardHelper.__init__(self, net_send, keyboard_sync, key_shortcuts) + def __init__(self, *args): + KeyboardHelper.__init__(self, *args) #used for delaying the sending of keymap changes #(as we may be getting dozens of such events at a time) self._keymap_changing = False diff --git a/src/xpra/client/gtk_base/gtk_tray_menu_base.py b/src/xpra/client/gtk_base/gtk_tray_menu_base.py index 4ead5d256b..6358a8c730 100644 --- a/src/xpra/client/gtk_base/gtk_tray_menu_base.py +++ b/src/xpra/client/gtk_base/gtk_tray_menu_base.py @@ -1005,19 +1005,19 @@ def set_layout(item): variant = item.keyboard_variant kh = self.client.keyboard_helper kh.locked = layout!="Auto" - if layout!=kh.xkbmap_layout or variant!=kh.xkbmap_variant: + if layout!=kh.layout_option or variant!=kh.variant_option: if layout=="Auto": #re-detect everything: - kh.update() - log.info("keyboard automatic mode: %s", kh.layout_str()) - kh.send_layout() - kh.send_keymap() + msg = "keyboard automatic mode" else: #use layout specified and send it: - kh.xkbmap_layout = layout - kh.xkbmap_variant = variant - log.info("new keyboard layout selected: %s", kh.layout_str()) - kh.send_layout() + kh.layout_option = layout + kh.variant_option = variant + msg = "new keyboard layout selected" + kh.update() + kh.send_layout() + kh.send_keymap() + log.info("%s: %s", msg, kh.layout_str()) l = self.checkitem(title, set_layout, active) l.set_draw_as_radio(True) l.keyboard_layout = layout @@ -1026,7 +1026,7 @@ def set_layout(item): def keysort(key): c,l = key return c.lower()+l.lower() - layout,layouts,variant,variants = self.client.keyboard_helper.keyboard.get_layout_spec() + layout,layouts,variant,variants = self.client.keyboard_helper.get_layout_spec() full_layout_list = False if len(layouts)>1: log("keyboard layouts: %s", u",".join(bytestostr(x) for x in layouts)) diff --git a/src/xpra/client/keyboard_helper.py b/src/xpra/client/keyboard_helper.py index 33d5f1209e..88d53edf47 100644 --- a/src/xpra/client/keyboard_helper.py +++ b/src/xpra/client/keyboard_helper.py @@ -8,21 +8,30 @@ from xpra.log import Logger log = Logger("keyboard") +from xpra.keyboard.layouts import xkbmap_query_tostring from xpra.keyboard.mask import DEFAULT_MODIFIER_MEANINGS, DEFAULT_MODIFIER_NUISANCE -from xpra.util import nonl, csv, print_nested_dict +from xpra.util import nonl, csv, std, print_nested_dict class KeyboardHelper(object): - def __init__(self, net_send, keyboard_sync, key_shortcuts): + def __init__(self, net_send, keyboard_sync, key_shortcuts, raw, layout, layouts, variant, variants, options): self.reset_state() self.send = net_send self.locked = False self.keyboard_sync = keyboard_sync self.key_shortcuts = self.parse_shortcuts(key_shortcuts) + #command line overrides: + self.xkbmap_raw = raw + self.layout_option = layout + self.variant_option = variant + self.layouts_option = layouts + self.variants_option = variants + self.options = options + #the platform class which allows us to map the keys: from xpra.platform.keyboard import Keyboard self.keyboard = Keyboard() - log("KeyboardHelper.__init__(%s, %s, %s) keyboard=%s", net_send, keyboard_sync, key_shortcuts, self.keyboard) + log("KeyboardHelper(%s) keyboard=%s", (net_send, keyboard_sync, key_shortcuts, raw, layout, layouts, variant, variants, options), self.keyboard) def mask_to_names(self, mask): return self.keyboard.mask_to_names(mask) @@ -37,6 +46,7 @@ def reset_state(self): self.xkbmap_mod_managed = [] self.xkbmap_mod_pointermissing = [] self.xkbmap_layout = "" + self.xkbmap_layouts = [] self.xkbmap_variant = "" self.xkbmap_variants = [] self.xkbmap_print = "" @@ -284,14 +294,50 @@ def clear_repeat(self): self.keys_pressed = {} + def get_layout_spec(self): + """ add / honour overrides """ + layout, layouts, variant, variants = self.keyboard.get_layout_spec() + log("%s.get_layout_spec()=%s", self.keyboard, (layout, layouts, variant, variants)) + def inl(v, l): + try: + if v in l: + return l + return [v]+list(l) + except: + return [v] + layout = self.layout_option or layout + layouts = inl(layout, self.layouts_option or layouts) + variant = self.variant_option or variant + variants = inl(variant, self.variants_option or variants) + return layout, layouts, self.variant_option or variant, self.variants_option or variants + + def get_keymap_spec(self): + _print, query, query_struct = self.keyboard.get_keymap_spec() + if self.layout_option: + query_struct["layout"] = self.layout_option + if self.layouts_option: + query_struct["layouts"] = csv(self.layouts_option) + if self.variant_option: + query_struct["variant"] = self.variant_option + if self.variants_option: + query_struct["variants"] = csv(self.variants_option) + if self.options: + if self.options.lower()=="none": + query_struct["options"] = "" + else: + query_struct["options"] = self.options + if self.layout_option or self.layouts_option or self.variant_option or self.variants_option or self.options: + query = xkbmap_query_tostring(query_struct) + return _print, query, query_struct + def query_xkbmap(self): - self.xkbmap_layout, _, self.xkbmap_variant, self.xkbmap_variants = self.keyboard.get_layout_spec() - self.xkbmap_print, self.xkbmap_query, self.xkbmap_query_struct = self.keyboard.get_keymap_spec() + self.xkbmap_layout, self.xkbmap_layouts, self.xkbmap_variant, self.xkbmap_variants = self.get_layout_spec() + self.xkbmap_print, self.xkbmap_query, self.xkbmap_query_struct = self.get_keymap_spec() self.xkbmap_keycodes = self.get_full_keymap() self.xkbmap_x11_keycodes = self.keyboard.get_x11_keymap() self.xkbmap_mod_meanings, self.xkbmap_mod_managed, self.xkbmap_mod_pointermissing = self.keyboard.get_keymap_modifiers() self.update_hash() - log("layout=%s, variant=%s, variants=%s", self.xkbmap_layout, self.xkbmap_variant, self.xkbmap_variants) + log("layout=%s, layouts=%s, variant=%s, variants=%s", self.xkbmap_layout, self.xkbmap_layouts, self.xkbmap_variant, self.xkbmap_variants) log("print=%s, query=%s, struct=%s", nonl(self.xkbmap_print), nonl(self.xkbmap_query), nonl(self.xkbmap_query_struct)) log("keycodes=%s", str(self.xkbmap_keycodes)[:80]+"...") log("x11 keycodes=%s", str(self.xkbmap_x11_keycodes)[:80]+"...") @@ -305,12 +351,12 @@ def update(self): self.query_xkbmap() def layout_str(self): - return " / ".join([x for x in (self.xkbmap_layout, self.xkbmap_variant) if bool(x)]) + return " / ".join([x for x in (self.layout_option or self.xkbmap_layout, self.variant_option or self.xkbmap_variant) if bool(x)]) def send_layout(self): log("send_layout()") - self.send("layout-changed", self.xkbmap_layout or "", self.xkbmap_variant or "") + self.send("layout-changed", self.layout_option or self.xkbmap_layout or "", self.variant_option or self.xkbmap_variant or "") def send_keymap(self): log("send_keymap()") @@ -338,7 +384,9 @@ def get_full_keymap(self): def get_keymap_properties(self): props = {} - for x in ("layout", "variant", "print", "query", "query_struct", "mod_meanings", + for x in ("layout", "layouts", "variant", "variants", + "raw", + "print", "query", "query_struct", "mod_meanings", "mod_managed", "mod_pointermissing", "keycodes", "x11_keycodes"): p = "xkbmap_%s" % x v = getattr(self, p) @@ -347,3 +395,24 @@ def get_keymap_properties(self): v = "" props[p] = v return props + + + def log_keyboard_info(self): + #show the user a summary of what we have detected: + kb_info = {} + if self.xkbmap_query_struct or self.xkbmap_query: + xkbqs = self.xkbmap_query_struct + if xkbqs: + #parse query into a dict + from xpra.keyboard.layouts import parse_xkbmap_query + xkbqs = parse_xkbmap_query(self.xkbmap_query) + for x in ["rules", "model", "layout"]: + v = xkbqs.get(x) + if v: + kb_info[x] = v + if self.xkbmap_layout: + kb_info["layout"] = self.xkbmap_layout + if len(kb_info)==0: + log.info(" using default keyboard settings") + else: + log.info(" keyboard settings: %s", ", ".join(["%s=%s" % (std(k), std(v)) for k,v in kb_info.items()])) diff --git a/src/xpra/client/ui_client_base.py b/src/xpra/client/ui_client_base.py index fa32deec73..16e917180b 100644 --- a/src/xpra/client/ui_client_base.py +++ b/src/xpra/client/ui_client_base.py @@ -284,6 +284,7 @@ def __init__(self): #helpers and associated flags: self.client_extras = None + self.keyboard_helper_class = KeyboardHelper self.keyboard_helper = None self.keyboard_grabbed = False self.pointer_grabbed = False @@ -427,7 +428,14 @@ def init_ui(self, opts, extra_args=[]): self.init_opengl(opts.opengl) if not self.readonly: - self.keyboard_helper = self.make_keyboard_helper(opts.keyboard_sync, opts.key_shortcut) + def noauto(v): + if not v: + return None + if str(v).lower()=="auto": + return None + return v + overrides = [noauto(getattr(opts, "keyboard_%s" % x)) for x in ("layout", "layouts", "variant", "variants", "options")] + self.keyboard_helper = self.keyboard_helper_class(self.send, opts.keyboard_sync, opts.key_shortcut, opts.keyboard_raw, *overrides) tray_icon_filename = opts.tray_icon if opts.tray: @@ -780,9 +788,6 @@ def do_get_core_encodings(self): return core_encodings - def make_keyboard_helper(self, keyboard_sync, key_shortcuts): - return KeyboardHelper(self.send, keyboard_sync, key_shortcuts) - def get_clipboard_helper_classes(self): return [] @@ -1310,27 +1315,9 @@ def make_hello(self): #don't bother sending keyboard info, as it won't be used capabilities["keyboard"] = False else: - for k,v in self.get_keymap_properties().items(): - capabilities[k] = v + capabilities.update(self.get_keymap_properties()) #show the user a summary of what we have detected: - kb_info = {} - xkbq = capabilities.get("xkbmap_query") - xkbqs = capabilities.get("xkbmap_query_struct") - if xkbqs or xkbq: - if not xkbqs: - #parse query into a dict - from xpra.keyboard.layouts import parse_xkbmap_query - xkbqs = parse_xkbmap_query(xkbq) - for x in ["rules", "model", "layout"]: - v = xkbqs.get(x) - if v: - kb_info[x] = v - if self.keyboard_helper.xkbmap_layout: - kb_info["layout"] = self.keyboard_helper.xkbmap_layout - if len(kb_info)==0: - log.info(" using default keyboard settings") - else: - log.info(" detected keyboard: %s", ", ".join(["%s=%s" % (std(k), std(v)) for k,v in kb_info.items()])) + self.keyboard_helper.log_keyboard_info() capabilities["modifiers"] = self.get_current_modifiers() u_root_w, u_root_h = self.get_root_size() diff --git a/src/xpra/scripts/config.py b/src/xpra/scripts/config.py index 184a9e2faf..e8998fb7c1 100755 --- a/src/xpra/scripts/config.py +++ b/src/xpra/scripts/config.py @@ -301,6 +301,12 @@ def read_xpra_defaults(): "tray-icon" : str, "window-icon" : str, "password-file" : str, + "keyboard-raw" : bool, + "keyboard-layout" : str, + "keyboard-layouts" : list, + "keyboard-variant" : str, + "keyboard-variants" : list, + "keyboard-options" : str, "clipboard" : str, "clipboard-direction" : str, "clipboard-filter-file" : str, @@ -509,6 +515,12 @@ def addtrailingslash(v): "tray-icon" : "", "window-icon" : "", "password-file" : "", + "keyboard-raw" : False, + "keyboard-layout" : "auto", + "keyboard-layouts" : [], + "keyboard-variant" : "auto", + "keyboard-variants" : [], + "keyboard-options" : "", "clipboard" : "yes", "clipboard-direction" : "both", "clipboard-filter-file" : "", @@ -873,6 +885,18 @@ def fixup_packetencoding(options): warn("warning: invalid packet encoder(s) specified: %s" % (", ".join(unknown))) options.packet_encoders = packet_encoders +def fixup_keyboard(options): + #variants and layouts can be specified as CSV, convert them to lists: + def p(v): + try: + from xpra.util import remove_dupes + return remove_dupes([x.strip() for x in v.split(",")]) + except: + return None + options.keyboard_layouts = p(options.keyboard_layouts) + options.keyboard_variants = p(options.keyboard_variants) + options.keyboard_raw = parse_bool("keyboard-raw", options.keyboard_raw) + def fixup_clipboard(options): cd = options.clipboard_direction.lower().replace("-", "") if cd=="toserver": @@ -893,6 +917,7 @@ def fixup_options(options, defaults={}): fixup_video_all_or_none(options) fixup_socketdirs(options, defaults) fixup_clipboard(options) + fixup_keyboard(options) #remote-xpra is meant to be a list, but the user can specify a string using the command line, #in which case we replace all the default values with this single entry: if not isinstance(options.remote_xpra, (list, tuple)): diff --git a/src/xpra/scripts/main.py b/src/xpra/scripts/main.py index 0351a05967..d75ed81ab6 100755 --- a/src/xpra/scripts/main.py +++ b/src/xpra/scripts/main.py @@ -678,6 +678,24 @@ def ignore(defaults): group.add_option("--keyboard-sync", action="store", metavar="yes|no", dest="keyboard_sync", default=defaults.keyboard_sync, help="Synchronize keyboard state. Default: %s." % enabled_str(defaults.keyboard_sync)) + group.add_option("--keyboard-raw", action="store", metavar="yes|no", + dest="keyboard_raw", default=defaults.keyboard_raw, + help="Send raw keyboard keycodes. Default: %s." % enabled_str(defaults.keyboard_raw)) + group.add_option("--keyboard-layout", action="store", metavar="LAYOUT", + dest="keyboard_layout", default=defaults.keyboard_layout, + help="The keyboard layout to use. Default: %default.") + group.add_option("--keyboard-layouts", action="store", metavar="LAYOUTS", + dest="keyboard_layouts", default=defaults.keyboard_layouts, + help="The keyboard layouts to enable. Default: %s." % csv(defaults.keyboard_layouts)) + group.add_option("--keyboard-variant", action="store", metavar="VARIANT", + dest="keyboard_variant", default=defaults.keyboard_variant, + help="The keyboard layout variant to use. Default: %default.") + group.add_option("--keyboard-variants", action="store", metavar="VARIANTS", + dest="keyboard_variants", default=defaults.keyboard_variant, + help="The keyboard layout variants to enable. Default: %s." % csv(defaults.keyboard_variants)) + group.add_option("--keyboard-options", action="store", metavar="OPTIONS", + dest="keyboard_options", default=defaults.keyboard_options, + help="The keyboard layout options to use. Default: %default.") group = optparse.OptionGroup(parser, "SSL Options", "These options apply to both client and server. Please refer to the man page for details.") diff --git a/src/xpra/server/server_base.py b/src/xpra/server/server_base.py index aa2ef46d78..bee531ecce 100644 --- a/src/xpra/server/server_base.py +++ b/src/xpra/server/server_base.py @@ -2466,6 +2466,7 @@ def _process_key_action(self, proto, packet): if self.readonly: return wid, keyname, pressed, modifiers, keyval, _, client_keycode = packet[1:8] + #group = packet[8] #unused! ss = self._server_sources.get(proto) if ss is None: return diff --git a/src/xpra/x11/server_keyboard_config.py b/src/xpra/x11/server_keyboard_config.py index b149768fb4..227cb9adeb 100644 --- a/src/xpra/x11/server_keyboard_config.py +++ b/src/xpra/x11/server_keyboard_config.py @@ -39,6 +39,7 @@ class KeyboardConfig(KeyboardConfigBase): def __init__(self): KeyboardConfigBase.__init__(self) + self.xkbmap_raw = False self.xkbmap_print = None self.xkbmap_query = None self.xkbmap_query_struct = None @@ -100,7 +101,7 @@ def get_info(self): for mod, mod_name in self.xkbmap_mod_meanings.items(): modinfo[mod] = mod_name info["x11_keycode"] = self.xkbmap_x11_keycodes - for x in ("print", "layout", "variant", "mod_managed", "mod_pointermissing"): + for x in ("print", "layout", "variant", "mod_managed", "mod_pointermissing", "raw"): v = getattr(self, "xkbmap_"+x) if v: info[x] = v @@ -134,6 +135,7 @@ def parse_option(name, parse_fn): #lists of strings: for x in ("mod_managed", "mod_pointermissing"): parse_option(x, props.strlistget) + parse_option("raw", props.boolget) log("assign_keymap_options(..) modified %s", modded) return len(modded)>0 @@ -147,7 +149,7 @@ def get_hash(self): def hashadd(v): m.update(("/%s" % str(v)).encode("utf8")) m.update(KeyboardConfigBase.get_hash(self)) - for x in (self.xkbmap_print, self.xkbmap_query, \ + for x in (self.xkbmap_print, self.xkbmap_query, self.xkbmap_raw, \ self.xkbmap_mod_meanings, self.xkbmap_mod_pointermissing, \ self.xkbmap_keycodes, self.xkbmap_x11_keycodes): hashadd(x) @@ -170,7 +172,9 @@ def compute_modifier_keynames(self): entries = keymap.get_entries_for_keyval(keyval) if entries: for keycode, _, _ in entries: - self.keycodes_for_modifier_keynames.setdefault(keyname, set()).add(keycode) + l = self.keycodes_for_modifier_keynames.setdefault(keyname, []) + if keycode not in l: + l.append(keycode) log("compute_modifier_keynames: keycodes_for_modifier_keynames=%s", self.keycodes_for_modifier_keynames) def compute_client_modifier_keycodes(self): @@ -235,41 +239,63 @@ def set_keymap(self): try: #first clear all existing modifiers: clean_keyboard_state() - clear_modifiers(ALL_X11_MODIFIERS.keys()) #just clear all of them (set or not) - #now set all the keycodes: - clean_keyboard_state() - self.keycode_translation = {} - - has_keycodes = (self.xkbmap_x11_keycodes and len(self.xkbmap_x11_keycodes)>0) or \ - (self.xkbmap_keycodes and len(self.xkbmap_keycodes)>0) - assert has_keycodes, "client failed to provide any keycodes!" - #first compute the modifier maps as this may have an influence - #on the keycode mappings (at least for the from_keycodes case): - if self.xkbmap_mod_meanings: - #Unix-like OS provides modifier meanings: - self.keynames_for_mod = get_modifiers_from_meanings(self.xkbmap_mod_meanings) - elif self.xkbmap_keycodes: - #non-Unix-like OS provides just keycodes for now: - self.keynames_for_mod = get_modifiers_from_keycodes(self.xkbmap_keycodes) + if not self.xkbmap_raw: + clear_modifiers(ALL_X11_MODIFIERS.keys()) #just clear all of them (set or not) + + #now set all the keycodes: + clean_keyboard_state() + + has_keycodes = (self.xkbmap_x11_keycodes and len(self.xkbmap_x11_keycodes)>0) or \ + (self.xkbmap_keycodes and len(self.xkbmap_keycodes)>0) + assert has_keycodes, "client failed to provide any keycodes!" + #first compute the modifier maps as this may have an influence + #on the keycode mappings (at least for the from_keycodes case): + if self.xkbmap_mod_meanings: + #Unix-like OS provides modifier meanings: + self.keynames_for_mod = get_modifiers_from_meanings(self.xkbmap_mod_meanings) + elif self.xkbmap_keycodes: + #non-Unix-like OS provides just keycodes for now: + self.keynames_for_mod = get_modifiers_from_keycodes(self.xkbmap_keycodes) + else: + log.error("missing both xkbmap_mod_meanings and xkbmap_keycodes, modifiers will probably not work as expected!") + self.keynames_for_mod = {} + #if the client does not provide a full keymap, + #try to preserve the initial server keycodes + #(used by non X11 clients like osx,win32 or Android) + preserve_server_keycodes = not self.xkbmap_print and not self.xkbmap_query + self.keycode_translation = set_all_keycodes(self.xkbmap_x11_keycodes, self.xkbmap_keycodes, preserve_server_keycodes, self.keynames_for_mod) + self.add_gtk_keynames() + + #now set the new modifier mappings: + clean_keyboard_state() + log.warn("going to set modifiers, xkbmap_mod_meanings=%s, len(xkbmap_keycodes)=%s", self.xkbmap_mod_meanings, len(self.xkbmap_keycodes or [])) + if self.keynames_for_mod: + set_modifiers(self.keynames_for_mod) + log.warn("keynames_for_mod=%s", self.keynames_for_mod) + self.compute_modifier_keynames() else: - log.error("missing both xkbmap_mod_meanings and xkbmap_keycodes, modifiers will probably not work as expected!") - self.keynames_for_mod = {} - #if the client does not provide a full keymap, - #try to preserve the initial server keycodes - #(used by non X11 clients like osx,win32 or Android) - preserve_server_keycodes = not self.xkbmap_print and not self.xkbmap_query - self.keycode_translation = set_all_keycodes(self.xkbmap_x11_keycodes, self.xkbmap_keycodes, preserve_server_keycodes, self.keynames_for_mod) - self.add_gtk_keynames() - - #now set the new modifier mappings: - clean_keyboard_state() - log("going to set modifiers, xkbmap_mod_meanings=%s, len(xkbmap_keycodes)=%s", self.xkbmap_mod_meanings, len(self.xkbmap_keycodes or [])) - if self.keynames_for_mod: - set_modifiers(self.keynames_for_mod) - self.compute_modifier_keynames() + self.keycode_translation = {} + log("keyboard raw mode, keycode translation left empty") + log("keycode mappings=%s", X11Keyboard.get_keycode_mappings()) + mod_mappings = X11Keyboard.get_modifier_mappings() + self.xkbmap_mod_meanings = {} + for mod, mod_defs in mod_mappings.items(): + for mod_def in mod_defs: + for v in mod_def: + if type(v)==int: + l = self.keycodes_for_modifier_keynames.setdefault(mod, []) + else: + self.xkbmap_mod_meanings[v] = mod + l = self.keynames_for_mod.setdefault(mod, []) + if v not in l: + l.append(v) + log("keynames_for_mod=%s", self.keynames_for_mod) + log("keycodes_for_modifier_keynames=%s", self.keycodes_for_modifier_keynames) + log("mod_meanings=%s", self.xkbmap_mod_meanings) self.compute_client_modifier_keycodes() log("keyname_for_mod=%s", self.keynames_for_mod) + clean_keyboard_state() except: log.error("error setting xmodmap", exc_info=True) diff --git a/src/xpra/x11/xkbhelper.py b/src/xpra/x11/xkbhelper.py index b0137fd79e..0df6b5818e 100644 --- a/src/xpra/x11/xkbhelper.py +++ b/src/xpra/x11/xkbhelper.py @@ -563,7 +563,9 @@ def get_modifiers_from_meanings(xkbmap_mod_meanings): #first generate a {modifier : [keynames]} dict: modifiers = {} for keyname, modifier in xkbmap_mod_meanings.items(): - modifiers.setdefault(modifier, set()).add(keyname) + l = modifiers.setdefault(modifier, []) + if keyname not in l: + l.append(keyname) log("get_modifiers_from_meanings(%s) modifier dict=%s", xkbmap_mod_meanings, modifiers) return modifiers