Skip to content

Commit

Permalink
#1172 / #1282: initial raw keyboard support for X11 and keyboard layo…
Browse files Browse the repository at this point in the history
…ut/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
  • Loading branch information
totaam committed Aug 13, 2016
1 parent 31f3bd6 commit a4be913
Show file tree
Hide file tree
Showing 14 changed files with 260 additions and 89 deletions.
5 changes: 3 additions & 2 deletions rpmbuild/xpra.spec
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
3 changes: 0 additions & 3 deletions src/etc/xpra/conf.d/40_client.conf.in
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 27 additions & 0 deletions src/etc/xpra/conf.d/42_client_keyboard.conf.in
Original file line number Diff line number Diff line change
@@ -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
21 changes: 21 additions & 0 deletions src/man/xpra.1
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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
Expand Down
5 changes: 1 addition & 4 deletions src/xpra/client/gtk_base/gtk_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions src/xpra/client/gtk_base/gtk_keyboard_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 10 additions & 10 deletions src/xpra/client/gtk_base/gtk_tray_menu_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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))
Expand Down
87 changes: 78 additions & 9 deletions src/xpra/client/keyboard_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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 = ""
Expand Down Expand Up @@ -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]+"...")
Expand All @@ -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()")
Expand Down Expand Up @@ -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)
Expand All @@ -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()]))
35 changes: 11 additions & 24 deletions src/xpra/client/ui_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 []

Expand Down Expand Up @@ -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()
Expand Down
Loading

0 comments on commit a4be913

Please sign in to comment.