Hide keyboard shortcuts

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 

8 

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 

13 

14keylog = Logger("keyboard") 

15mouselog = Logger("mouse") 

16 

17""" 

18Mixin for servers that handle input devices 

19(keyboard, mouse, etc) 

20""" 

21class InputServer(StubServerMixin): 

22 

23 def __init__(self): 

24 self.input_devices = "auto" 

25 self.input_devices_format = None 

26 self.input_devices_data = None 

27 

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 

41 

42 def setup(self): 

43 self.watch_keymap_changes() 

44 

45 def cleanup(self): 

46 self.clear_keys_pressed() 

47 self.keyboard_config = None 

48 

49 def reset_focus(self): 

50 self.clear_keys_pressed() 

51 

52 def last_client_exited(self): 

53 self.clear_keys_pressed() 

54 

55 def get_info(self, _proto): 

56 return {"keyboard" : self.get_keyboard_info()} 

57 

58 def get_server_features(self, _source=None): 

59 return { 

60 "input-devices" : self.input_devices, 

61 "pointer.relative" : True, 

62 } 

63 

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 } 

71 

72 def parse_hello(self, ss, caps, send_ui): 

73 if send_ui: 

74 self.parse_hello_ui_keyboard(ss, caps) 

75 

76 def watch_keymap_changes(self): 

77 pass 

78 

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 

83 

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) 

95 

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 

111 

112 

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) 

124 

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) 

143 

144 def set_keyboard_layout_group(self, grp): 

145 #only actually implemented in X11ServerBase 

146 pass 

147 

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() 

181 

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) 

184 

185 def fake_key(self, keycode, press): 

186 pass 

187 

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) 

228 

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 

233 

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) 

242 

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 

250 

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() 

287 

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) 

296 

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() 

302 

303 def clear_keys_pressed(self): 

304 pass 

305 

306 def get_keyboard_config(self, _props=None): 

307 return None 

308 

309 def set_keyboard_repeat(self, key_repeat): 

310 pass 

311 

312 def set_keymap(self, ss, force=False): 

313 pass 

314 

315 

316 ###################################################################### 

317 # pointer: 

318 def _move_pointer(self, wid, pos, *args): 

319 raise NotImplementedError() 

320 

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 

342 

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 

351 

352 def do_process_mouse_common(self, proto, wid, pointer, *args): 

353 return pointer 

354 

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:]) 

365 

366 def do_process_button_action(self, proto, wid, button, pressed, pointer, modifiers, *args): 

367 pass 

368 

369 

370 def _update_modifiers(self, proto, wid, modifiers): 

371 pass 

372 

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) 

391 

392 

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() 

402 

403 def setup_input_devices(self): 

404 pass 

405 

406 

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 }) 

414 

415 

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 })