From 25f0f95221000b2ce02f6d60686545555fcce87f Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Thu, 6 Dec 2018 00:53:51 +0000 Subject: [PATCH] #1716: move sync flag to the per-connection keyboard config object git-svn-id: https://xpra.org/svn/Xpra/trunk@21178 3bb7dfac-3a0b-4e04-842a-767bc560f471 --- src/xpra/server/keyboard_config_base.py | 6 +- src/xpra/server/mixins/input_server.py | 57 +++++++------------ src/xpra/server/rfb/rfb_server.py | 3 +- src/xpra/server/source/input_mixin.py | 76 ++++++++++++++++--------- src/xpra/x11/server_keyboard_config.py | 4 ++ src/xpra/x11/x11_server_core.py | 3 +- 6 files changed, 81 insertions(+), 68 deletions(-) diff --git a/src/xpra/server/keyboard_config_base.py b/src/xpra/server/keyboard_config_base.py index d0ae2d297a..c16d799769 100644 --- a/src/xpra/server/keyboard_config_base.py +++ b/src/xpra/server/keyboard_config_base.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # This file is part of Xpra. -# Copyright (C) 2010-2016 Antoine Martin +# Copyright (C) 2010-2018 Antoine Martin # Xpra is released under the terms of the GNU GPL v2, or, at your option, any # later version. See the file COPYING for details. @@ -15,6 +15,7 @@ class KeyboardConfigBase(object): def __init__(self): self.enabled = True self.owner = None + self.sync = True self.pressed_translation = {} def __repr__(self): @@ -24,10 +25,11 @@ def get_info(self): return { "enabled" : self.enabled, "owner" : self.owner or "", + "sync" : self.sync, } def parse_options(self, props): - pass + self.sync = props.boolget("keyboard_sync", True) def get_hash(self): return b"" diff --git a/src/xpra/server/mixins/input_server.py b/src/xpra/server/mixins/input_server.py index 6b3ade425f..f61d9769d2 100644 --- a/src/xpra/server/mixins/input_server.py +++ b/src/xpra/server/mixins/input_server.py @@ -9,7 +9,6 @@ keylog = Logger("keyboard") mouselog = Logger("mouse") -from xpra.keyboard.mask import DEFAULT_MODIFIER_MEANINGS from xpra.os_util import monotonic_time, bytestostr from xpra.util import typedict from xpra.server.mixins.stub_server_mixin import StubServerMixin @@ -29,7 +28,6 @@ def __init__(self): self.xkbmap_mod_meanings = {} self.keyboard_config = None self.keymap_changing = False #to ignore events when we know we are changing the configuration - self.keyboard_sync = True self.key_repeat = None #ugly: we're duplicating the value pair from "key_repeat" here: self.key_repeat_delay = -1 @@ -57,16 +55,6 @@ def last_client_exited(self): def get_info(self, _proto): return {"keyboard" : self.get_keyboard_info()} - def get_ui_info(self, proto, client_uuids=None, *args): - info = {} - if self.keyboard_config: - info["keyboard"] = { - "state" : { - "modifiers" : self.keyboard_config.get_current_mask(), - }, - } - return info - def get_server_features(self, _source=None): return { "toggle_keyboard_sync" : True, @@ -93,10 +81,10 @@ def parse_hello_ui_keyboard(self, ss, c): other_ui_clients = [s.uuid for s in self._server_sources.values() if s!=ss and s.ui_client] #parse client config: ss.keyboard_config = self.get_keyboard_config(c) + keylog.error("ss.keyboard_config.sync=%s", ss.keyboard_config.sync) if not other_ui_clients: #so only activate this feature afterwards: - self.keyboard_sync = c.boolget("keyboard_sync", True) self.key_repeat = c.intpair("key_repeat", (0, 0)) self.set_keyboard_repeat(self.key_repeat) #always clear modifiers before setting a new keymap @@ -110,7 +98,6 @@ def parse_hello_ui_keyboard(self, ss, c): def get_keyboard_info(self): start = monotonic_time() info = { - "sync" : self.keyboard_sync, "repeat" : { "delay" : self.key_repeat_delay, "interval" : self.key_repeat_interval, @@ -180,7 +167,8 @@ def _process_key_action(self, proto, packet): #for example, used by win32 to send Caps_Lock/Num_Lock changes if keycode>=0: try: - self._handle_key(wid, pressed, keyname, keyval, keycode, modifiers) + is_mod = ss.is_modifier(keyname, keycode) + self._handle_key(wid, pressed, keyname, keyval, keycode, modifiers, is_mod, ss.keyboard_config.sync) except Exception as e: keylog("process_key_action%s", (proto, packet), exc_info=True) keylog.error("Error: failed to %s key", ["unpress", "press"][pressed]) @@ -191,23 +179,15 @@ def _process_key_action(self, proto, packet): def get_keycode(self, ss, client_keycode, keyname, pressed, modifiers): return ss.get_keycode(client_keycode, keyname, pressed, modifiers) - def is_modifier(self, keyname, keycode): - if keyname in DEFAULT_MODIFIER_MEANINGS.keys(): - return True - #keyboard config should always exist if we are here? - if self.keyboard_config: - return self.keyboard_config.is_modifier(keycode) - return False - def fake_key(self, keycode, press): pass - def _handle_key(self, wid, pressed, name, keyval, keycode, modifiers): + def _handle_key(self, wid, pressed, name, keyval, keycode, modifiers, is_mod=False, sync=True): """ Does the actual press/unpress for keys Either from a packet (_process_key_action) or timeout (_key_repeat_timeout) """ - keylog("handle_key(%s,%s,%s,%s,%s,%s) keyboard_sync=%s", wid, pressed, name, keyval, keycode, modifiers, self.keyboard_sync) + keylog("handle_key(%s)", (wid, pressed, name, keyval, keycode, modifiers, is_mod, sync)) if pressed and (wid is not None) and (wid not in self._id_to_window): keylog("window %s is gone, ignoring key press", wid) return @@ -225,11 +205,10 @@ def unpress(): if keycode in self.keys_pressed: del self.keys_pressed[keycode] self.fake_key(keycode, False) - is_mod = self.is_modifier(name, keycode) if pressed: if keycode not in self.keys_pressed: press() - if not self.keyboard_sync and not is_mod: + if not sync and not is_mod: #keyboard is not synced: client manages repeat so unpress #it immediately unless this is a modifier key #(as modifiers are synced via many packets: key, focus and mouse events) @@ -241,28 +220,28 @@ def unpress(): unpress() else: keylog("handle keycode %s: key %s was already unpressed, ignoring", keycode, name) - if not is_mod and self.keyboard_sync and self.key_repeat_delay>0 and self.key_repeat_interval>0: - self._key_repeat(wid, pressed, name, keyval, keycode, modifiers, self.key_repeat_delay) + if not is_mod and sync and self.key_repeat_delay>0 and self.key_repeat_interval>0: + self._key_repeat(wid, pressed, name, keyval, keycode, modifiers, is_mod, self.key_repeat_delay) def cancel_key_repeat_timer(self): if self.key_repeat_timer: self.source_remove(self.key_repeat_timer) self.key_repeat_timer = None - def _key_repeat(self, wid, pressed, keyname, keyval, keycode, modifiers, delay_ms=0): + def _key_repeat(self, wid, pressed, keyname, keyval, keycode, modifiers, is_mod, delay_ms=0): """ Schedules/cancels the key repeat timeouts """ self.cancel_key_repeat_timer() if pressed: delay_ms = min(1500, max(250, delay_ms)) keylog("scheduling key repeat timer with delay %s for %s / %s", delay_ms, keyname, keycode) now = monotonic_time() - self.key_repeat_timer = self.timeout_add(0, self._key_repeat_timeout, now, delay_ms, wid, keyname, keyval, keycode, modifiers) + self.key_repeat_timer = self.timeout_add(0, self._key_repeat_timeout, now, delay_ms, wid, keyname, keyval, keycode, modifiers, is_mod) - def _key_repeat_timeout(self, when, delay_ms, wid, keyname, keyval, keycode, modifiers): + def _key_repeat_timeout(self, when, delay_ms, wid, keyname, keyval, keycode, modifiers, is_mod): self.key_repeat_timer = None now = monotonic_time() keylog("key repeat timeout for %s / '%s' - clearing it, now=%s, scheduled at %s with delay=%s", keyname, keycode, now, when, delay_ms) - self._handle_key(wid, False, keyname, keyval, keycode, modifiers) + self._handle_key(wid, False, keyname, keyval, keycode, modifiers, is_mod, True) self.keys_timedout[keycode] = now def _process_key_repeat(self, proto, packet): @@ -277,7 +256,7 @@ def _process_key_repeat(self, proto, packet): keycode = ss.get_keycode(client_keycode, keyname, modifiers) #key repeat uses modifiers from a pointer event, so ignore mod_pointermissing: ss.make_keymask_match(modifiers) - if not self.keyboard_sync: + if not ss.keyboard_config.sync: #this check should be redundant: clients should not send key-repeat without #having keyboard_sync enabled return @@ -292,15 +271,19 @@ def _process_key_repeat(self, proto, packet): keylog("key %s/%s, had timed out, re-pressing it", keycode, keyname) self.keys_pressed[keycode] = keyname self.fake_key(keycode, True) - self._key_repeat(wid, True, keyname, keyval, keycode, modifiers, self.key_repeat_interval) + is_mod = ss.is_modifier(keyname, keycode) + self._key_repeat(wid, True, keyname, keyval, keycode, modifiers, is_mod, self.key_repeat_interval) ss.user_event() def _process_keyboard_sync_enabled_status(self, proto, packet): assert proto in self._server_sources if self.readonly: return - self.keyboard_sync = bool(packet[1]) - keylog("toggled keyboard-sync to %s", self.keyboard_sync) + ss = self._server_sources.get(proto) + if ss is None: + return + ss.keyboard_config.sync = bool(packet[1]) + keylog.error("toggled keyboard-sync to %s for %s", self.keyboard_config.sync, ss) def _keys_changed(self, *_args): if not self.keymap_changing: diff --git a/src/xpra/server/rfb/rfb_server.py b/src/xpra/server/rfb/rfb_server.py index f08bb0f9d6..c889511993 100644 --- a/src/xpra/server/rfb/rfb_server.py +++ b/src/xpra/server/rfb/rfb_server.py @@ -159,7 +159,8 @@ def _process_rfb_KeyEvent(self, proto, packet): keycode = source.keyboard_config.get_keycode(0, keyname, modifiers) log("rfb keycode(%s)=%s", keyname, keycode) if keycode: - self._handle_key(wid, bool(pressed), keyname, keyval, keycode, modifiers) + is_mod = source.is_modifier(keyname, keycode) + self._handle_key(wid, bool(pressed), keyname, keyval, keycode, modifiers, is_mod, True) def _process_rfb_SetEncodings(self, _proto, packet): n, encodings = packet[2:4] diff --git a/src/xpra/server/source/input_mixin.py b/src/xpra/server/source/input_mixin.py index 10d235b79a..21bb9b6c87 100644 --- a/src/xpra/server/source/input_mixin.py +++ b/src/xpra/server/source/input_mixin.py @@ -9,6 +9,7 @@ log = Logger("keyboard") from xpra.server.source.stub_source_mixin import StubSourceMixin +from xpra.keyboard.mask import DEFAULT_MODIFIER_MEANINGS """ @@ -38,18 +39,27 @@ def parse_client_caps(self, c): def get_info(self): - info = { - "time" : self.double_click_time, - } - if self.double_click_distance: - info["distance"] = self.double_click_distance - return {"double-click" : info} + dc_info = {} + dct = self.double_click_time + if dct: + dc_info["time"] = dct + dcd = self.double_click_distance + if dcd: + dc_info["distance"] = dcd + info = {} + if dc_info: + info["double-click"] = dc_info + kc = self.keyboard_config + if kc: + info["keyboard"] = kc.get_info() + return info def get_caps(self): #expose the "modifier_client_keycodes" defined in the X11 server keyboard config object, #so clients can figure out which modifiers map to which keys: - if self.keyboard_config: - mck = getattr(self.keyboard_config, "modifier_client_keycodes", None) + kc = self.keyboard_config + if kc: + mck = getattr(kc, "modifier_client_keycodes", None) if mck: return {"modifier_keycodes" : mck} return {} @@ -59,46 +69,58 @@ def set_layout(self, layout, variant, options): return self.keyboard_config.set_layout(layout, variant, options) def keys_changed(self): - if self.keyboard_config: - self.keyboard_config.compute_modifier_map() - self.keyboard_config.compute_modifier_keynames() + kc = self.keyboard_config + if kc: + kc.compute_modifier_map() + kc.compute_modifier_keynames() log("keys_changed() updated keyboard config=%s", self.keyboard_config) def make_keymask_match(self, modifier_list, ignored_modifier_keycode=None, ignored_modifier_keynames=None): - if self.keyboard_config and self.keyboard_config.enabled: - self.keyboard_config.make_keymask_match(modifier_list, ignored_modifier_keycode, ignored_modifier_keynames) + kc = self.keyboard_config + if kc and kc.enabled: + kc.make_keymask_match(modifier_list, ignored_modifier_keycode, ignored_modifier_keynames) def set_default_keymap(self): log("set_default_keymap() keyboard_config=%s", self.keyboard_config) - if self.keyboard_config: - self.keyboard_config.set_default_keymap() - return self.keyboard_config + kc = self.keyboard_config + if kc: + kc.set_default_keymap() + return kc + + + def is_modifier(self, keyname, keycode): + if keyname in DEFAULT_MODIFIER_MEANINGS.keys(): + return True + #keyboard config should always exist if we are here? + kc = self.keyboard_config + if kc: + return kc.is_modifier(keycode) + return False def set_keymap(self, current_keyboard_config, keys_pressed, force=False, translate_only=False): - log("set_keymap%s", (current_keyboard_config, keys_pressed, force, translate_only)) - if self.keyboard_config and self.keyboard_config.enabled: + kc = self.keyboard_config + log("set_keymap%s keyboard_config=%s", (current_keyboard_config, keys_pressed, force, translate_only), kc) + if kc and kc.enabled: current_id = None if current_keyboard_config and current_keyboard_config.enabled: current_id = current_keyboard_config.get_hash() - keymap_id = self.keyboard_config.get_hash() + keymap_id = kc.get_hash() log("current keyboard id=%s, new keyboard id=%s", current_id, keymap_id) if force or current_id is None or keymap_id!=current_id: - self.keyboard_config.keys_pressed = keys_pressed - self.keyboard_config.set_keymap(translate_only) - self.keyboard_config.owner = self.uuid - current_keyboard_config = self.keyboard_config + kc.keys_pressed = keys_pressed + kc.set_keymap(translate_only) + kc.owner = self.uuid else: log.info("keyboard mapping already configured (skipped)") - self.keyboard_config = current_keyboard_config - return current_keyboard_config def get_keycode(self, client_keycode, keyname, pressed, modifiers): - if self.keyboard_config is None: + kc = self.keyboard_config + if kc is None: log.info("ignoring client key %s / %s since keyboard is not configured", client_keycode, keyname) return -1 - return self.keyboard_config.get_keycode(client_keycode, keyname, pressed, modifiers) + return kc.get_keycode(client_keycode, keyname, pressed, modifiers) def update_mouse(self, wid, x, y, rx, ry): diff --git a/src/xpra/x11/server_keyboard_config.py b/src/xpra/x11/server_keyboard_config.py index 8fd35a4b0c..906175b51a 100644 --- a/src/xpra/x11/server_keyboard_config.py +++ b/src/xpra/x11/server_keyboard_config.py @@ -121,6 +121,10 @@ def get_info(self): modsinfo["nuisance"] = tuple(self.xkbmap_mod_nuisance or []) info["modifier"] = modinfo info["modifiers"] = modsinfo + #this would need to always run in the UI thread: + #info["state"] = { + # "modifiers" : self.get_current_mask(), + # } log("keyboard info: %s", info) return info diff --git a/src/xpra/x11/x11_server_core.py b/src/xpra/x11/x11_server_core.py index 2897489736..fdc28e2da6 100644 --- a/src/xpra/x11/x11_server_core.py +++ b/src/xpra/x11/x11_server_core.py @@ -442,7 +442,8 @@ def set_keymap(self, server_source, force=False): other_ui_clients = [s.uuid for s in self._server_sources.values() if s!=server_source and s.ui_client] translate_only = len(other_ui_clients)>0 with xsync: - self.keyboard_config = server_source.set_keymap(self.keyboard_config, self.keys_pressed, force, translate_only) + server_source.set_keymap(self.keyboard_config, self.keys_pressed, force, translate_only) + self.keyboard_config = server_source.keyboard_config finally: # re-enable via idle_add to give all the pending # events a chance to run first (and get ignored)