/
keyboard.py
228 lines (206 loc) · 8.47 KB
/
keyboard.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# This file is part of Xpra.
# Copyright (C) 2010 Nathaniel Smith <njs@pobox.com>
# Copyright (C) 2011-2022 Antoine Martin <antoine@xpra.org>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.
import os
from xpra.platform.keyboard_base import KeyboardBase
from xpra.keyboard.mask import MODIFIER_MAP
from xpra.log import Logger
from xpra.os_util import is_X11, is_Wayland, bytestostr
if not is_Wayland():
from xpra.gtk_common.error import xsync
log = Logger("keyboard", "posix")
class Keyboard(KeyboardBase):
def __init__(self):
super().__init__()
if not is_Wayland():
try:
from xpra.x11.bindings.keyboard_bindings import X11KeyboardBindings #@UnresolvedImport
self.keyboard_bindings = X11KeyboardBindings()
except Exception as e:
log.error("Error: failed to load posix keyboard bindings")
log.error(" %s", str(e) or type(e))
def init_vars(self):
super().init_vars()
self.keymap_modifiers = None
self.keyboard_bindings = None
def __repr__(self):
return "xposix.Keyboard"
def get_keymap_modifiers(self):
if self.keymap_modifiers is None:
self.keymap_modifiers = self.do_get_keymap_modifiers()
return self.keymap_modifiers
def do_get_keymap_modifiers(self):
if not self.keyboard_bindings:
log.warn("keyboard bindings are not available")
log.warn(" expect keyboard mapping problems")
if is_Wayland():
log.warn(" (incomplete wayland support)")
return {}, [], ["mod2", ]
return {}, [], []
try:
with xsync:
mod_mappings = self.keyboard_bindings.get_modifier_mappings()
if mod_mappings:
#ie: {"shift" : ["Shift_L", "Shift_R"], "mod1" : "Meta_L", ...]}
log("modifier mappings=%s", mod_mappings)
meanings = {}
for modifier,keys in mod_mappings.items():
for _,keyname in keys:
meanings[keyname] = modifier
#probably a GTK bug? but easier to put here
mod_missing = []
numlock_mod = meanings.get("Num_Lock", [])
if numlock_mod:
mod_missing.append(numlock_mod)
return meanings, [], mod_missing
except Exception:
log.error("failed to use native get_modifier_mappings", exc_info=True)
return {}, [], []
def get_x11_keymap(self):
if not self.keyboard_bindings:
return {}
try:
with xsync:
return self.keyboard_bindings.get_keycode_mappings()
except Exception:
log.error("Error: failed to use raw x11 keymap", exc_info=True)
return {}
def get_locale_status(self):
#parse the output into a dictionary:
# $ localectl status
# System Locale: LANG=en_GB.UTF-8
# VC Keymap: gb
# X11 Layout: gb
from subprocess import getoutput
out = getoutput("localectl status")
if not out:
return {}
locale = {}
for line in bytestostr(out).splitlines():
parts = line.lstrip(" ").split(": ")
if len(parts)==2:
locale[parts[0]]=parts[1]
log("locale(%s)=%s", out, locale)
return locale
def get_keymap_spec(self):
log("get_keymap_spec() keyboard_bindings=%s", self.keyboard_bindings)
if is_Wayland() or not self.keyboard_bindings:
locale = self.get_locale_status()
query_struct = {}
if locale:
layout = locale.get("X11 Layout")
if layout:
query_struct["layout"] = layout
log("query_struct(%s)=%s", locale, query_struct)
return query_struct
with xsync:
query_struct = self.keyboard_bindings.getXkbProperties()
log("get_keymap_spec()=%r", query_struct)
return query_struct
def get_xkb_rules_names_property(self):
#parses the "_XKB_RULES_NAMES" X11 property
if not is_X11():
return ""
xkb_rules_names = ""
# pylint: disable=import-outside-toplevel
from xpra.platform.xposix.gui import _get_X11_root_property
prop = _get_X11_root_property("_XKB_RULES_NAMES", "STRING")
log("get_xkb_rules_names_property() _XKB_RULES_NAMES=%s", prop)
#ie: 'evdev\x00pc104\x00gb,us\x00,\x00\x00'
if prop:
xkb_rules_names = bytestostr(prop).split("\0")
#ie: ['evdev', 'pc104', 'gb,us', ',', '', '']
log("get_xkb_rules_names_property()=%s", xkb_rules_names)
return xkb_rules_names
def get_all_x11_layouts(self):
repository = "/usr/share/X11/xkb/rules/base.xml"
if os.path.exists(repository):
try:
import lxml.etree #pylint: disable=import-outside-toplevel
except ImportError:
log("cannot parse xml", exc_info=True)
else:
with open(repository, "r", encoding="latin1") as f:
tree = lxml.etree.parse(f) #pylint: disable=c-extension-no-member
x11_layouts = {}
for layout in tree.xpath("//layout"):
layout = layout.xpath("./configItem/name")[0].text
x11_layouts[layout] = layout
#for variant in layout.xpath("./variantList/variant/configItem/name"):
# variant_name = variant.text
return x11_layouts
from subprocess import Popen, PIPE #pylint: disable=import-outside-toplevel
try:
proc = Popen(["localectl", "list-x11-keymap-layouts"], stdout=PIPE, stderr=PIPE)
out = proc.communicate()[0]
log("get_all_x11_layouts() proc=%s", proc)
log("get_all_x11_layouts() returncode=%s", proc.returncode)
if proc.wait()==0:
x11_layouts = {}
for line in out.splitlines():
layout = line.decode().split("/")[-1]
if layout:
x11_layouts[layout] = layout
return x11_layouts
except OSError:
log("get_all_x11_layouts()", exc_info=True)
return {"us" : "English"}
def get_layout_spec(self):
layout = ""
layouts = []
variant = ""
options = ""
v = None
if self.keyboard_bindings:
with xsync:
props = self.keyboard_bindings.getXkbProperties()
v = props.get("layout")
variant = props.get("variant", "")
options = props.get("options", "")
else:
locale = self.get_locale_status()
v = locale.get("X11 Layout")
if not v:
#fallback:
v = self.get_xkb_rules_names_property()
#ie: ['evdev', 'pc104', 'gb,us', ',', '', '']
if v and len(v)>=3:
v = v[2]
if v:
layouts = v.split(",")
layout = v
def s(v):
try:
return v.decode("latin1")
except Exception:
return str(v)
return s(layout), [s(x) for x in layouts], variant, None, options
def get_keyboard_repeat(self):
v = None
if self.keyboard_bindings:
try:
v = self.keyboard_bindings.get_key_repeat_rate()
if v:
assert len(v)==2
except Exception as e:
log.error("Error: failed to get keyboard repeat rate:")
log.error(" %s", e)
v = None
log("get_keyboard_repeat()=%s", v)
return v
def update_modifier_map(self, display, mod_meanings):
try:
# pylint: disable=import-outside-toplevel
from xpra.x11.gtk_x11.keys import grok_modifier_map
self.modifier_map = grok_modifier_map(display, mod_meanings)
except ImportError:
self.modifier_map = MODIFIER_MAP
#force re-query on next call:
self.keymap_modifiers = None
try:
dn = "%s %s" % (type(display).__name__, display.get_name())
except Exception:
dn = str(display)
log(f"update_modifier_map({dn}, {mod_meanings}) modifier_map={self.modifier_map}")