Skip to content

Commit

Permalink
#1716: move sync flag to the per-connection keyboard config object
Browse files Browse the repository at this point in the history
git-svn-id: https://xpra.org/svn/Xpra/trunk@21178 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed Dec 6, 2018
1 parent 4d8c2cd commit 25f0f95
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 68 deletions.
6 changes: 4 additions & 2 deletions src/xpra/server/keyboard_config_base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# This file is part of Xpra.
# Copyright (C) 2010-2016 Antoine Martin <[email protected]>
# Copyright (C) 2010-2018 Antoine Martin <[email protected]>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.

Expand All @@ -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):
Expand All @@ -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""
Expand Down
57 changes: 20 additions & 37 deletions src/xpra/server/mixins/input_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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])
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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):
Expand All @@ -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
Expand All @@ -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:
Expand Down
3 changes: 2 additions & 1 deletion src/xpra/server/rfb/rfb_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
76 changes: 49 additions & 27 deletions src/xpra/server/source/input_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
log = Logger("keyboard")

from xpra.server.source.stub_source_mixin import StubSourceMixin
from xpra.keyboard.mask import DEFAULT_MODIFIER_MEANINGS


"""
Expand Down Expand Up @@ -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 {}
Expand All @@ -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):
Expand Down
4 changes: 4 additions & 0 deletions src/xpra/x11/server_keyboard_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion src/xpra/x11/x11_server_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 25f0f95

Please sign in to comment.