Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/server/mixins/input_server.py : 34%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2# This file is part of Xpra.
3# Copyright (C) 2010-2018 Antoine Martin <antoine@xpra.org>
4# Copyright (C) 2008 Nathaniel Smith <njs@pobox.com>
5# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
6# later version. See the file COPYING for details.
7#pylint: disable-msg=E1101
9from xpra.os_util import monotonic_time, bytestostr
10from xpra.util import typedict
11from xpra.server.mixins.stub_server_mixin import StubServerMixin
12from xpra.log import Logger
14keylog = Logger("keyboard")
15mouselog = Logger("mouse")
17"""
18Mixin for servers that handle input devices
19(keyboard, mouse, etc)
20"""
21class InputServer(StubServerMixin):
23 def __init__(self):
24 self.input_devices = "auto"
25 self.input_devices_format = None
26 self.input_devices_data = None
28 self.xkbmap_mod_meanings = {}
29 self.keyboard_config = None
30 self.keymap_changing = False #to ignore events when we know we are changing the configuration
31 self.key_repeat = None
32 #ugly: we're duplicating the value pair from "key_repeat" here:
33 self.key_repeat_delay = -1
34 self.key_repeat_interval = -1
35 #store list of currently pressed keys
36 #(using a dict only so we can display their names in debug messages)
37 self.keys_pressed = {}
38 self.keys_timedout = {}
39 #timers for cancelling key repeat when we get jitter
40 self.key_repeat_timer = None
42 def setup(self):
43 self.watch_keymap_changes()
45 def cleanup(self):
46 self.clear_keys_pressed()
47 self.keyboard_config = None
49 def reset_focus(self):
50 self.clear_keys_pressed()
52 def last_client_exited(self):
53 self.clear_keys_pressed()
55 def get_info(self, _proto):
56 return {"keyboard" : self.get_keyboard_info()}
58 def get_server_features(self, _source=None):
59 return {
60 "input-devices" : self.input_devices,
61 "pointer.relative" : True,
62 }
64 def get_caps(self, _source):
65 if not self.key_repeat:
66 return {}
67 return {
68 "key_repeat" : self.key_repeat,
69 "key_repeat_modifiers" : True,
70 }
72 def parse_hello(self, ss, caps, send_ui):
73 if send_ui:
74 self.parse_hello_ui_keyboard(ss, caps)
76 def watch_keymap_changes(self):
77 pass
79 def parse_hello_ui_keyboard(self, ss, c):
80 other_ui_clients = [s.uuid for s in self._server_sources.values() if s!=ss and s.ui_client]
81 #parse client config:
82 ss.keyboard_config = self.get_keyboard_config(c) #pylint: disable=assignment-from-none
84 if not other_ui_clients:
85 #so only activate this feature afterwards:
86 self.key_repeat = c.intpair("key_repeat", (0, 0))
87 self.set_keyboard_repeat(self.key_repeat)
88 #always clear modifiers before setting a new keymap
89 ss.make_keymask_match(c.strtupleget("modifiers"))
90 else:
91 self.set_keyboard_repeat(None)
92 self.key_repeat = (0, 0)
93 self.key_repeat_delay, self.key_repeat_interval = self.key_repeat
94 self.set_keymap(ss)
96 def get_keyboard_info(self) -> dict:
97 start = monotonic_time()
98 info = {
99 "repeat" : {
100 "delay" : self.key_repeat_delay,
101 "interval" : self.key_repeat_interval,
102 },
103 "keys_pressed" : tuple(self.keys_pressed.values()),
104 "modifiers" : self.xkbmap_mod_meanings,
105 }
106 kc = self.keyboard_config
107 if kc:
108 info.update(kc.get_info())
109 keylog("get_keyboard_info took %ims", (monotonic_time()-start)*1000)
110 return info
113 def _process_layout(self, proto, packet):
114 if self.readonly:
115 return
116 layout, variant = packet[1:3]
117 if len(packet)>=4:
118 options = packet[3]
119 else:
120 options = ""
121 ss = self.get_server_source(proto)
122 if ss and ss.set_layout(layout, variant, options):
123 self.set_keymap(ss, force=True)
125 def _process_keymap(self, proto, packet):
126 if self.readonly:
127 return
128 props = typedict(packet[1])
129 ss = self.get_server_source(proto)
130 if ss is None:
131 return
132 keylog("received new keymap from client")
133 other_ui_clients = [s.uuid for s in self._server_sources.values() if s!=ss and s.ui_client]
134 if other_ui_clients:
135 keylog.warn("Warning: ignoring keymap change as there are %i other clients", len(other_ui_clients))
136 return
137 kc = ss.keyboard_config
138 if kc and kc.enabled:
139 kc.parse_options(props)
140 self.set_keymap(ss, True)
141 modifiers = props.get("modifiers", [])
142 ss.make_keymask_match(modifiers)
144 def set_keyboard_layout_group(self, grp):
145 #only actually implemented in X11ServerBase
146 pass
148 def _process_key_action(self, proto, packet):
149 if self.readonly:
150 return
151 wid, keyname, pressed, modifiers, keyval, keystr, client_keycode, group = packet[1:9]
152 ss = self.get_server_source(proto)
153 if ss is None:
154 return
155 keyname = bytestostr(keyname)
156 try:
157 keystr = keystr.decode("utf8")
158 except Exception:
159 keystr = bytestostr(keystr)
160 modifiers = list(bytestostr(x) for x in modifiers)
161 self.set_ui_driver(ss)
162 keycode, group = self.get_keycode(ss, client_keycode, keyname, pressed, modifiers, keyval, keystr, group)
163 keylog("process_key_action(%s) server keycode=%s, group=%i", packet, keycode, group)
164 if group>=0 and keycode>=0:
165 self.set_keyboard_layout_group(group)
166 #currently unused: (group, is_modifier) = packet[8:10]
167 self._focus(ss, wid, None)
168 ss.make_keymask_match(modifiers, keycode, ignored_modifier_keynames=[keyname])
169 #negative keycodes are used for key events without a real keypress/unpress
170 #for example, used by win32 to send Caps_Lock/Num_Lock changes
171 if keycode>=0:
172 try:
173 is_mod = ss.is_modifier(keyname, keycode)
174 self._handle_key(wid, pressed, keyname, keyval, keycode, modifiers, is_mod, ss.keyboard_config.sync)
175 except Exception as e:
176 keylog("process_key_action%s", (proto, packet), exc_info=True)
177 keylog.error("Error: failed to %s key", ["unpress", "press"][pressed])
178 keylog.error(" %s", e)
179 keylog.error(" for keyname=%s, keyval=%i, keycode=%i", keyname, keyval, keycode)
180 ss.user_event()
182 def get_keycode(self, ss, client_keycode, keyname, pressed, modifiers, keyval, keystr, group):
183 return ss.get_keycode(client_keycode, keyname, pressed, modifiers, keyval, keystr, group)
185 def fake_key(self, keycode, press):
186 pass
188 def _handle_key(self, wid, pressed, name, keyval, keycode, modifiers, is_mod=False, sync=True):
189 """
190 Does the actual press/unpress for keys
191 Either from a packet (_process_key_action) or timeout (_key_repeat_timeout)
192 """
193 keylog("handle_key(%s)", (wid, pressed, name, keyval, keycode, modifiers, is_mod, sync))
194 if pressed and (wid is not None) and (wid not in self._id_to_window):
195 keylog("window %s is gone, ignoring key press", wid)
196 return
197 if keycode<0:
198 keylog.warn("ignoring invalid keycode=%s", keycode)
199 return
200 if keycode in self.keys_timedout:
201 del self.keys_timedout[keycode]
202 def press():
203 keylog("handle keycode pressing %3i: key '%s'", keycode, name)
204 self.keys_pressed[keycode] = name
205 self.fake_key(keycode, True)
206 def unpress():
207 keylog("handle keycode unpressing %3i: key '%s'", keycode, name)
208 if keycode in self.keys_pressed:
209 del self.keys_pressed[keycode]
210 self.fake_key(keycode, False)
211 if pressed:
212 if keycode not in self.keys_pressed:
213 press()
214 if not sync and not is_mod:
215 #keyboard is not synced: client manages repeat so unpress
216 #it immediately unless this is a modifier key
217 #(as modifiers are synced via many packets: key, focus and mouse events)
218 unpress()
219 else:
220 keylog("handle keycode %s: key %s was already pressed, ignoring", keycode, name)
221 else:
222 if keycode in self.keys_pressed:
223 unpress()
224 else:
225 keylog("handle keycode %s: key %s was already unpressed, ignoring", keycode, name)
226 if not is_mod and sync and self.key_repeat_delay>0 and self.key_repeat_interval>0:
227 self._key_repeat(wid, pressed, name, keyval, keycode, modifiers, is_mod, self.key_repeat_delay)
229 def cancel_key_repeat_timer(self):
230 if self.key_repeat_timer:
231 self.source_remove(self.key_repeat_timer)
232 self.key_repeat_timer = None
234 def _key_repeat(self, wid, pressed, keyname, keyval, keycode, modifiers, is_mod, delay_ms=0):
235 """ Schedules/cancels the key repeat timeouts """
236 self.cancel_key_repeat_timer()
237 if pressed:
238 delay_ms = min(1500, max(250, delay_ms))
239 keylog("scheduling key repeat timer with delay %s for %s / %s", delay_ms, keyname, keycode)
240 now = monotonic_time()
241 self.key_repeat_timer = self.timeout_add(delay_ms, self._key_repeat_timeout, now, delay_ms, wid, keyname, keyval, keycode, modifiers, is_mod)
243 def _key_repeat_timeout(self, when, delay_ms, wid, keyname, keyval, keycode, modifiers, is_mod):
244 self.key_repeat_timer = None
245 now = monotonic_time()
246 keylog("key repeat timeout for %s / '%s' - clearing it, now=%s, scheduled at %s with delay=%s",
247 keyname, keycode, now, when, delay_ms)
248 self._handle_key(wid, False, keyname, keyval, keycode, modifiers, is_mod, True)
249 self.keys_timedout[keycode] = now
251 def _process_key_repeat(self, proto, packet):
252 if self.readonly:
253 return
254 ss = self.get_server_source(proto)
255 if ss is None:
256 return
257 wid, keyname, keyval, client_keycode, modifiers = packet[1:6]
258 keyname = bytestostr(keyname)
259 modifiers = tuple(bytestostr(x) for x in modifiers)
260 group = 0
261 if len(packet)>=7:
262 group = packet[6]
263 keystr = ""
264 keycode, group = ss.get_keycode(client_keycode, keyname, modifiers, keyval, keystr, group)
265 if group>=0:
266 self.set_keyboard_layout_group(group)
267 #key repeat uses modifiers from a pointer event, so ignore mod_pointermissing:
268 ss.make_keymask_match(modifiers)
269 if not ss.keyboard_config.sync:
270 #this check should be redundant: clients should not send key-repeat without
271 #having keyboard_sync enabled
272 return
273 if keycode not in self.keys_pressed:
274 #the key is no longer pressed, has it timed out?
275 when_timedout = self.keys_timedout.get(keycode, None)
276 if when_timedout:
277 del self.keys_timedout[keycode]
278 now = monotonic_time()
279 if when_timedout and (now-when_timedout)<30:
280 #not so long ago, just re-press it now:
281 keylog("key %s/%s, had timed out, re-pressing it", keycode, keyname)
282 self.keys_pressed[keycode] = keyname
283 self.fake_key(keycode, True)
284 is_mod = ss.is_modifier(keyname, keycode)
285 self._key_repeat(wid, True, keyname, keyval, keycode, modifiers, is_mod, self.key_repeat_interval)
286 ss.user_event()
288 def _process_keyboard_sync_enabled_status(self, proto, packet):
289 if self.readonly:
290 return
291 ss = self.get_server_source(proto)
292 if ss is None:
293 return
294 ss.keyboard_config.sync = bool(packet[1])
295 keylog("toggled keyboard-sync to %s for %s", self.keyboard_config.sync, ss)
297 def _keys_changed(self, *_args):
298 if not self.keymap_changing:
299 for ss in self._server_sources.values():
300 if hasattr(ss, "keys_changed"):
301 ss.keys_changed()
303 def clear_keys_pressed(self):
304 pass
306 def get_keyboard_config(self, _props=None):
307 return None
309 def set_keyboard_repeat(self, key_repeat):
310 pass
312 def set_keymap(self, ss, force=False):
313 pass
316 ######################################################################
317 # pointer:
318 def _move_pointer(self, wid, pos, *args):
319 raise NotImplementedError()
321 def _adjust_pointer(self, proto, wid, pointer):
322 #the window may not be mapped at the same location by the client:
323 ss = self.get_server_source(proto)
324 window = self._id_to_window.get(wid)
325 if ss and window:
326 ws = ss.get_window_source(wid)
327 if ws:
328 mapped_at = ws.mapped_at
329 pos = self.get_window_position(window)
330 if mapped_at and pos:
331 wx, wy = pos
332 cx, cy = mapped_at[:2]
333 if wx!=cx or wy!=cy:
334 dx, dy = wx-cx, wy-cy
335 if dx!=0 or dy!=0:
336 px, py = pointer[:2]
337 ax, ay = px+dx, py+dy
338 mouselog("client %2i: server window position: %12s, client window position: %24s, pointer=%s, adjusted: %s",
339 ss.counter, pos, mapped_at, pointer, (ax, ay))
340 return [ax, ay]+list(pointer[2:])
341 return pointer
343 def _process_mouse_common(self, proto, wid, opointer, *args):
344 pointer = self._adjust_pointer(proto, wid, opointer)
345 if not pointer:
346 return None
347 #TODO: adjust args too
348 if self.do_process_mouse_common(proto, wid, pointer, *args):
349 return pointer
350 return None
352 def do_process_mouse_common(self, proto, wid, pointer, *args):
353 return pointer
355 def _process_button_action(self, proto, packet):
356 mouselog("process_button_action(%s, %s)", proto, packet)
357 if self.readonly:
358 return
359 ss = self.get_server_source(proto)
360 if ss is None:
361 return
362 ss.user_event()
363 self.set_ui_driver(ss)
364 self.do_process_button_action(proto, *packet[1:])
366 def do_process_button_action(self, proto, wid, button, pressed, pointer, modifiers, *args):
367 pass
370 def _update_modifiers(self, proto, wid, modifiers):
371 pass
373 def _process_pointer_position(self, proto, packet):
374 mouselog("_process_pointer_position(%s, %s) readonly=%s, ui_driver=%s",
375 proto, packet, self.readonly, self.ui_driver)
376 if self.readonly:
377 return
378 ss = self.get_server_source(proto)
379 if ss is None:
380 return
381 wid, pdata, modifiers = packet[1:4]
382 pointer = pdata[:2]
383 if ss.pointer_relative and len(pdata)>=4:
384 ss.mouse_last_relative_position = pdata[2:4]
385 ss.mouse_last_position = pointer
386 if self.ui_driver and self.ui_driver!=ss.uuid:
387 return
388 ss.user_event()
389 if self._process_mouse_common(proto, wid, pdata, *packet[5:]):
390 self._update_modifiers(proto, wid, modifiers)
393 ######################################################################
394 # input devices:
395 def _process_input_devices(self, _proto, packet):
396 self.input_devices_format = packet[1]
397 self.input_devices_data = packet[2]
398 from xpra.util import print_nested_dict
399 mouselog("client %s input devices:", self.input_devices_format)
400 print_nested_dict(self.input_devices_data, print_fn=mouselog)
401 self.setup_input_devices()
403 def setup_input_devices(self):
404 pass
407 #FIXME: we should not be overriding this method here
408 def send_hello(self, server_source, _root_w, _root_h, key_repeat, _server_cipher):
409 capabilities = self.make_hello(server_source)
410 if key_repeat:
411 capabilities.update({
412 "key_repeat" : key_repeat,
413 })
416 def init_packet_handlers(self):
417 self.add_packet_handlers({
418 #keyboard:
419 "set-keyboard-sync-enabled" : self._process_keyboard_sync_enabled_status,
420 "key-action" : self._process_key_action,
421 "key-repeat" : self._process_key_repeat,
422 "layout-changed" : self._process_layout,
423 "keymap-changed" : self._process_keymap,
424 #mouse:
425 "button-action" : self._process_button_action,
426 "pointer-position" : self._process_pointer_position,
427 #setup:
428 "input-devices" : self._process_input_devices,
429 })