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) 2011 Serviware (Arthur Huillet, <ahuillet@serviware.com>) 

4# Copyright (C) 2010-2020 Antoine Martin <antoine@xpra.org> 

5# Copyright (C) 2008 Nathaniel Smith <njs@pobox.com> 

6# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 

7# later version. See the file COPYING for details. 

8 

9import os 

10import threading 

11from gi.repository import Gdk 

12 

13from xpra.x11.bindings.core_bindings import set_context_check, X11CoreBindings #@UnresolvedImport 

14from xpra.x11.bindings.randr_bindings import RandRBindings #@UnresolvedImport 

15from xpra.x11.bindings.keyboard_bindings import X11KeyboardBindings #@UnresolvedImport 

16from xpra.x11.bindings.window_bindings import X11WindowBindings #@UnresolvedImport 

17from xpra.gtk_common.error import XError, xswallow, xsync, xlog, trap, verify_sync 

18from xpra.gtk_common.gtk_util import get_default_root_window 

19from xpra.server.server_uuid import save_uuid, get_uuid 

20from xpra.x11.vfb_util import parse_resolution 

21from xpra.x11.fakeXinerama import find_libfakeXinerama, save_fakeXinerama_config, cleanup_fakeXinerama 

22from xpra.x11.gtk_x11.prop import prop_get, prop_set 

23from xpra.x11.gtk_x11.gdk_display_source import close_gdk_display_source 

24from xpra.x11.gtk_x11.gdk_bindings import init_x11_filter, cleanup_x11_filter, cleanup_all_event_receivers 

25from xpra.common import MAX_WINDOW_SIZE 

26from xpra.os_util import monotonic_time, strtobytes 

27from xpra.util import typedict, iround, envbool, first_time, XPRA_DPI_NOTIFICATION_ID 

28from xpra.net.compression import Compressed 

29from xpra.server.gtk_server_base import GTKServerBase 

30from xpra.x11.xkbhelper import clean_keyboard_state 

31from xpra.scripts.config import FALSE_OPTIONS 

32from xpra.log import Logger 

33 

34set_context_check(verify_sync) 

35RandR = RandRBindings() 

36X11Keyboard = X11KeyboardBindings() 

37X11Core = X11CoreBindings() 

38X11Window = X11WindowBindings() 

39 

40 

41log = Logger("x11", "server") 

42keylog = Logger("x11", "server", "keyboard") 

43mouselog = Logger("x11", "server", "mouse") 

44grablog = Logger("server", "grab") 

45cursorlog = Logger("server", "cursor") 

46screenlog = Logger("server", "screen") 

47xinputlog = Logger("xinput") 

48 

49 

50ALWAYS_NOTIFY_MOTION = envbool("XPRA_ALWAYS_NOTIFY_MOTION", False) 

51FAKE_X11_INIT_ERROR = envbool("XPRA_FAKE_X11_INIT_ERROR", False) 

52 

53 

54class XTestPointerDevice: 

55 

56 def __repr__(self): 

57 return "XTestPointerDevice" 

58 

59 def move_pointer(self, screen_no, x, y, *_args): 

60 mouselog("xtest_fake_motion(%i, %s, %s)", screen_no, x, y) 

61 with xsync: 

62 X11Keyboard.xtest_fake_motion(screen_no, x, y) 

63 

64 def click(self, button, pressed, *_args): 

65 mouselog("xtest_fake_button(%i, %s)", button, pressed) 

66 with xsync: 

67 X11Keyboard.xtest_fake_button(button, pressed) 

68 

69 def close(self): 

70 pass 

71 

72 def has_precise_wheel(self): 

73 return False 

74 

75 

76class X11ServerCore(GTKServerBase): 

77 """ 

78 Base class for X11 servers, 

79 adds X11 specific methods to GTKServerBase. 

80 (see XpraServer or XpraX11ShadowServer for actual implementations) 

81 """ 

82 

83 def __init__(self): 

84 self.screen_number = Gdk.Screen.get_default().get_number() 

85 self.root_window = get_default_root_window() 

86 self.pointer_device = XTestPointerDevice() 

87 self.touchpad_device = None 

88 self.pointer_device_map = {} 

89 self.keys_pressed = {} 

90 self.last_mouse_user = None 

91 self.libfakeXinerama_so = None 

92 self.initial_resolution = None 

93 self.x11_filter = False 

94 self.randr_sizes_added = [] 

95 GTKServerBase.__init__(self) 

96 log("XShape=%s", X11Window.displayHasXShape()) 

97 

98 def init(self, opts): 

99 self.do_init(opts) 

100 super().init(opts) 

101 

102 def server_init(self): 

103 self.x11_init() 

104 from xpra.server import server_features 

105 if server_features.windows: 

106 from xpra.x11.x11_window_filters import init_x11_window_filters 

107 init_x11_window_filters() 

108 super().server_init() 

109 

110 def do_init(self, opts): 

111 try: 

112 self.initial_resolution = parse_resolution(opts.resize_display) 

113 except ValueError: 

114 pass 

115 self.randr = bool(self.initial_resolution) or not (opts.resize_display in FALSE_OPTIONS) 

116 self.randr_exact_size = False 

117 self.fake_xinerama = "no" #only enabled in seamless server 

118 self.current_xinerama_config = None 

119 #x11 keyboard bits: 

120 self.current_keyboard_group = None 

121 

122 

123 def x11_init(self): 

124 if FAKE_X11_INIT_ERROR: 

125 raise Exception("fake x11 init error") 

126 self.init_fake_xinerama() 

127 with xlog: 

128 clean_keyboard_state() 

129 with xlog: 

130 if not X11Keyboard.hasXFixes() and self.cursors: 

131 log.error("Error: cursor forwarding support disabled") 

132 if not X11Keyboard.hasXTest(): 

133 log.error("Error: keyboard and mouse disabled") 

134 elif not X11Keyboard.hasXkb(): 

135 log.error("Error: limited keyboard support") 

136 with xsync: 

137 self.init_x11_atoms() 

138 with xlog: 

139 if self.randr: 

140 self.init_randr() 

141 with xlog: 

142 self.init_cursor() 

143 with xlog: 

144 self.x11_filter = init_x11_filter() 

145 assert self.x11_filter 

146 

147 def init_fake_xinerama(self): 

148 if self.fake_xinerama in FALSE_OPTIONS: 

149 self.libfakeXinerama_so = None 

150 elif os.path.isabs(self.fake_xinerama): 

151 self.libfakeXinerama_so = self.fake_xinerama 

152 else: 

153 self.libfakeXinerama_so = find_libfakeXinerama() 

154 

155 def init_randr(self): 

156 self.randr = RandR.has_randr() 

157 screenlog("randr=%s", self.randr) 

158 #check the property first, 

159 #because we may be inheriting this display, 

160 #in which case the screen sizes list may be longer than 1 

161 eprop = prop_get(self.root_window, "_XPRA_RANDR_EXACT_SIZE", "u32", ignore_errors=True, raise_xerrors=False) 

162 screenlog("_XPRA_RANDR_EXACT_SIZE=%s", eprop) 

163 self.randr_exact_size = eprop==1 

164 if not self.randr_exact_size: 

165 #ugly hackish way of detecting Xvfb with randr, 

166 #assume that it has only one resolution pre-defined: 

167 sizes = RandR.get_xrr_screen_sizes() 

168 if len(sizes)==1: 

169 self.randr_exact_size = True 

170 prop_set(self.root_window, "_XPRA_RANDR_EXACT_SIZE", "u32", 1) 

171 elif not sizes: 

172 #xwayland? 

173 self.randr = False 

174 self.randr_exact_size = False 

175 screenlog("randr=%s, exact size=%s", self.randr, self.randr_exact_size) 

176 screenlog("randr enabled: %s", self.randr) 

177 if not self.randr: 

178 screenlog.warn("Warning: no X11 RandR support on %s", os.environ.get("DISPLAY")) 

179 

180 

181 def init_cursor(self): 

182 #cursor: 

183 self.default_cursor_image = None 

184 self.last_cursor_serial = None 

185 self.last_cursor_image = None 

186 self.send_cursor_pending = False 

187 def get_default_cursor(): 

188 self.default_cursor_image = X11Keyboard.get_cursor_image() 

189 cursorlog("get_default_cursor=%s", self.default_cursor_image) 

190 trap.swallow_synced(get_default_cursor) 

191 X11Keyboard.selectCursorChange(True) 

192 

193 def get_display_bit_depth(self): 

194 with xlog: 

195 return X11Window.get_depth(X11Window.getDefaultRootWindow()) 

196 return 0 

197 

198 

199 def init_x11_atoms(self): 

200 #some applications (like openoffice), do not work properly 

201 #if some x11 atoms aren't defined, so we define them in advance: 

202 atom_names = tuple("_NET_WM_WINDOW_TYPE"+wtype for wtype in ( 

203 "", 

204 "_NORMAL", 

205 "_DESKTOP", 

206 "_DOCK", 

207 "_TOOLBAR", 

208 "_MENU", 

209 "_UTILITY", 

210 "_SPLASH", 

211 "_DIALOG", 

212 "_DROPDOWN_MENU", 

213 "_POPUP_MENU", 

214 "_TOOLTIP", 

215 "_NOTIFICATION", 

216 "_COMBO", 

217 "_DND", 

218 "_NORMAL" 

219 )) 

220 X11Core.intern_atoms(atom_names) 

221 

222 

223 def set_keyboard_layout_group(self, grp): 

224 if not self.keyboard_config: 

225 keylog("set_keyboard_layout_group(%i) ignored, no config", grp) 

226 return 

227 if not self.keyboard_config.xkbmap_layout_groups: 

228 keylog("set_keyboard_layout_group(%i) ignored, no layout groups support", grp) 

229 #not supported by the client that owns the current keyboard config, 

230 #so make sure we stick to the default group: 

231 grp = 0 

232 if not X11Keyboard.hasXkb(): 

233 keylog("set_keyboard_layout_group(%i) ignored, no Xkb support", grp) 

234 return 

235 if grp<0: 

236 grp = 0 

237 if self.current_keyboard_group!=grp: 

238 keylog("set_keyboard_layout_group(%i) ignored, value unchanged", grp) 

239 return 

240 keylog("set_keyboard_layout_group(%i) config=%s, current keyboard group=%s", 

241 grp, self.keyboard_config, self.current_keyboard_group) 

242 try: 

243 with xsync: 

244 self.current_keyboard_group = X11Keyboard.set_layout_group(grp) 

245 except XError as e: 

246 keylog("set_keyboard_layout_group group=%s", grp, exc_info=True) 

247 keylog.error("Error: failed to set keyboard layout group '%s'", grp) 

248 keylog.error(" %s", e) 

249 

250 def init_packet_handlers(self): 

251 GTKServerBase.init_packet_handlers(self) 

252 self.add_packet_handler("force-ungrab", self._process_force_ungrab) 

253 self.add_packet_handler("wheel-motion", self._process_wheel_motion) 

254 

255 

256 def init_virtual_devices(self, _devices): 

257 self.input_devices = "xtest" 

258 

259 

260 def get_child_env(self) -> dict: 

261 #adds fakeXinerama: 

262 env = super().get_child_env() 

263 if self.fake_xinerama and self.libfakeXinerama_so: 

264 env["LD_PRELOAD"] = self.libfakeXinerama_so 

265 return env 

266 

267 def do_cleanup(self): 

268 if self.x11_filter: 

269 self.x11_filter = False 

270 cleanup_x11_filter() 

271 #try a few times: 

272 #errors happen because windows are being destroyed 

273 #(even more so when we cleanup) 

274 #and we don't really care too much about this 

275 for l in (log, log, log, log, log.warn): 

276 try: 

277 with xsync: 

278 cleanup_all_event_receivers() 

279 #all went well, we're done 

280 break 

281 except Exception as e: 

282 l("failed to remove event receivers: %s", e) 

283 if self.fake_xinerama: 

284 cleanup_fakeXinerama() 

285 with xswallow: 

286 clean_keyboard_state() 

287 GTKServerBase.do_cleanup(self) 

288 log("close_gdk_display_source()") 

289 close_gdk_display_source() 

290 

291 

292 def get_uuid(self): 

293 return get_uuid() 

294 

295 def save_uuid(self): 

296 save_uuid(str(self.uuid)) 

297 

298 def set_keyboard_repeat(self, key_repeat): 

299 if key_repeat: 

300 self.key_repeat_delay, self.key_repeat_interval = key_repeat 

301 if self.key_repeat_delay>0 and self.key_repeat_interval>0: 

302 X11Keyboard.set_key_repeat_rate(self.key_repeat_delay, self.key_repeat_interval) 

303 keylog.info("setting key repeat rate from client: %sms delay / %sms interval", 

304 self.key_repeat_delay, self.key_repeat_interval) 

305 else: 

306 #dont do any jitter compensation: 

307 self.key_repeat_delay = -1 

308 self.key_repeat_interval = -1 

309 #but do set a default repeat rate: 

310 X11Keyboard.set_key_repeat_rate(500, 30) 

311 keylog("keyboard repeat disabled") 

312 

313 def make_hello(self, source): 

314 capabilities = super().make_hello(source) 

315 capabilities["server_type"] = "Python/gtk/x11" 

316 if source.wants_features: 

317 capabilities.update({ 

318 "resize_screen" : self.randr, 

319 "resize_exact" : self.randr_exact_size, 

320 "force_ungrab" : True, 

321 "keyboard.fast-switching" : True, 

322 "wheel.precise" : self.pointer_device.has_precise_wheel(), 

323 "touchpad-device" : bool(self.touchpad_device), 

324 }) 

325 if self.randr: 

326 sizes = self.get_all_screen_sizes() 

327 if len(sizes)>1: 

328 capabilities["screen-sizes"] = sizes 

329 if self.default_cursor_image and source.wants_default_cursor: 

330 capabilities["cursor.default"] = self.default_cursor_image 

331 return capabilities 

332 

333 def do_get_info(self, proto, server_sources) -> dict: 

334 start = monotonic_time() 

335 info = super().do_get_info(proto, server_sources) 

336 sinfo = info.setdefault("server", {}) 

337 sinfo.update({ 

338 "type" : "Python/gtk/x11", 

339 "fakeXinerama" : bool(self.libfakeXinerama_so), 

340 "libfakeXinerama" : self.libfakeXinerama_so or "", 

341 }) 

342 log("X11ServerBase.do_get_info took %ims", (monotonic_time()-start)*1000) 

343 return info 

344 

345 def get_ui_info(self, proto, wids=None, *args) -> dict: 

346 log("do_get_info thread=%s", threading.current_thread()) 

347 info = super().get_ui_info(proto, wids, *args) 

348 #this is added here because the server keyboard config doesn't know about "keys_pressed".. 

349 if not self.readonly: 

350 with xlog: 

351 info.setdefault("keyboard", {}).update({ 

352 "state" : { 

353 "keys_pressed" : tuple(self.keys_pressed.keys()), 

354 "keycodes-down" : X11Keyboard.get_keycodes_down(), 

355 }, 

356 "fast-switching" : True, 

357 "layout-group" : X11Keyboard.get_layout_group(), 

358 }) 

359 sinfo = info.setdefault("server", {}) 

360 try: 

361 from xpra.x11.gtk_x11.composite import CompositeHelper 

362 sinfo["XShm"] = CompositeHelper.XShmEnabled 

363 except ImportError: 

364 pass 

365 #cursor: 

366 info.setdefault("cursor", {}).update(self.get_cursor_info()) 

367 with xswallow: 

368 sinfo.update({ 

369 "Xkb" : X11Keyboard.hasXkb(), 

370 "XTest" : X11Keyboard.hasXTest(), 

371 }) 

372 #randr: 

373 if self.randr: 

374 with xlog: 

375 sizes = self.get_all_screen_sizes() 

376 if len(sizes)>=0: 

377 sinfo["randr"] = { 

378 "" : True, 

379 "options" : tuple(reversed(sorted(sizes))), 

380 "exact" : self.randr_exact_size, 

381 } 

382 return info 

383 

384 

385 def get_cursor_info(self) -> dict: 

386 #(NOT from UI thread) 

387 #copy to prevent race: 

388 cd = self.last_cursor_image 

389 if cd is None: 

390 return {"" : "None"} 

391 dci = self.default_cursor_image 

392 cinfo = { 

393 "is-default" : bool(dci) and len(dci)>=8 and len(cd)>=8 and cd[7]==dci[7], 

394 } 

395 #all but pixels: 

396 for i, x in enumerate(("x", "y", "width", "height", "xhot", "yhot", "serial", None, "name")): 

397 if x: 

398 v = cd[i] or "" 

399 cinfo[x] = v 

400 return cinfo 

401 

402 def get_window_info(self, window) -> dict: 

403 info = super().get_window_info(window) 

404 info["XShm"] = window.uses_XShm() 

405 info["geometry"] = window.get_geometry() 

406 return info 

407 

408 

409 def get_keyboard_config(self, props=typedict()): 

410 from xpra.x11.server_keyboard_config import KeyboardConfig 

411 keyboard_config = KeyboardConfig() 

412 keyboard_config.enabled = props.boolget("keyboard", True) 

413 keyboard_config.parse_options(props) 

414 keyboard_config.xkbmap_layout = props.strget("xkbmap_layout") 

415 keyboard_config.xkbmap_variant = props.strget("xkbmap_variant") 

416 keyboard_config.xkbmap_options = props.strget("xkbmap_options") 

417 keylog("get_keyboard_config(..)=%s", keyboard_config) 

418 return keyboard_config 

419 

420 

421 def set_keymap(self, server_source, force=False): 

422 if self.readonly: 

423 return 

424 try: 

425 #prevent _keys_changed() from firing: 

426 #(using a flag instead of keymap.disconnect(handler) as this did not seem to work!) 

427 self.keymap_changing = True 

428 

429 #if sharing, don't set the keymap, translate the existing one: 

430 other_ui_clients = [s.uuid for s in self._server_sources.values() if s!=server_source and s.ui_client] 

431 translate_only = len(other_ui_clients)>0 

432 with xsync: 

433 server_source.set_keymap(self.keyboard_config, self.keys_pressed, force, translate_only) #pylint: disable=access-member-before-definition 

434 self.keyboard_config = server_source.keyboard_config 

435 finally: 

436 # re-enable via idle_add to give all the pending 

437 # events a chance to run first (and get ignored) 

438 def reenable_keymap_changes(*args): 

439 keylog("reenable_keymap_changes(%s)", args) 

440 self.keymap_changing = False 

441 self._keys_changed() 

442 self.idle_add(reenable_keymap_changes) 

443 

444 

445 def clear_keys_pressed(self): 

446 if self.readonly: 

447 return 

448 keylog("clear_keys_pressed()") 

449 #make sure the timer doesn't fire and interfere: 

450 self.cancel_key_repeat_timer() 

451 #clear all the keys we know about: 

452 if self.keys_pressed: 

453 keylog("clearing keys pressed: %s", self.keys_pressed) 

454 with xsync: 

455 for keycode in self.keys_pressed: 

456 self.fake_key(keycode, False) 

457 self.keys_pressed = {} 

458 #this will take care of any remaining ones we are not aware of: 

459 #(there should not be any - but we want to be certain) 

460 clean_keyboard_state() 

461 

462 

463 def get_cursor_sizes(self): 

464 display = Gdk.Display.get_default() 

465 return display.get_default_cursor_size(), display.get_maximal_cursor_size() 

466 

467 def get_cursor_image(self): 

468 #must be called from the UI thread! 

469 with xlog: 

470 return X11Keyboard.get_cursor_image() 

471 

472 def get_cursor_data(self): 

473 #must be called from the UI thread! 

474 cursor_image = self.get_cursor_image() 

475 if cursor_image is None: 

476 cursorlog("get_cursor_data() failed to get cursor image") 

477 return None, [] 

478 self.last_cursor_image = cursor_image 

479 pixels = self.last_cursor_image[7] 

480 cursorlog("get_cursor_image() cursor=%s", cursor_image[:7]+["%s bytes" % len(pixels)]+cursor_image[8:]) 

481 if self.default_cursor_image is not None and str(pixels)==str(self.default_cursor_image[7]): 

482 cursorlog("get_cursor_data(): default cursor - clearing it") 

483 cursor_image = None 

484 cursor_sizes = self.get_cursor_sizes() 

485 return (cursor_image, cursor_sizes) 

486 

487 

488 def get_all_screen_sizes(self): 

489 #workaround for #2910: the resolutions we add are not seen by XRRSizes! 

490 # so we keep track of the ones we have added ourselves: 

491 sizes = list(RandR.get_xrr_screen_sizes()) 

492 for w, h in self.randr_sizes_added: 

493 if (w, h) not in sizes: 

494 sizes.append((w, h)) 

495 return tuple(sizes) 

496 

497 def get_max_screen_size(self): 

498 max_w, max_h = self.root_window.get_geometry()[2:4] 

499 if self.randr: 

500 sizes = self.get_all_screen_sizes() 

501 if len(sizes)>=1: 

502 for w,h in sizes: 

503 max_w = max(max_w, w) 

504 max_h = max(max_h, h) 

505 if max_w>MAX_WINDOW_SIZE or max_h>MAX_WINDOW_SIZE: 

506 screenlog.warn("Warning: maximum screen size is very large: %sx%s", max_w, max_h) 

507 screenlog.warn(" you may encounter window sizing problems") 

508 screenlog("get_max_screen_size()=%s", (max_w, max_h)) 

509 return max_w, max_h 

510 

511 

512 def configure_best_screen_size(self): 

513 #return ServerBase.set_best_screen_size(self) 

514 """ sets the screen size to use the largest width and height used by any of the clients """ 

515 root_w, root_h = self.root_window.get_geometry()[2:4] 

516 if not self.randr: 

517 return root_w, root_h 

518 sss = self._server_sources.values() 

519 if len(sss)>1: 

520 screenlog.info("screen used by %i clients:", len(sss)) 

521 bigger = True 

522 max_w, max_h = 0, 0 

523 min_w, min_h = 16384, 16384 

524 for ss in sss: 

525 client_size = ss.desktop_size 

526 if not client_size: 

527 size = "unknown" 

528 else: 

529 w, h = client_size 

530 size = "%ix%i" % (w, h) 

531 max_w = max(max_w, w) 

532 max_h = max(max_h, h) 

533 if w>0: 

534 min_w = min(min_w, w) 

535 if h>0: 

536 min_h = min(min_h, h) 

537 bigger = bigger and ss.screen_resize_bigger 

538 if len(sss)>1: 

539 screenlog.info("* %s: %s", ss.uuid, size) 

540 if bigger: 

541 w, h = max_w, max_h 

542 else: 

543 w, h = min_w, min_h 

544 screenlog("current server resolution is %ix%i", root_w, root_h) 

545 screenlog("maximum client resolution is %ix%i", max_w, max_h) 

546 screenlog("minimum client resolution is %ix%i", min_w, min_h) 

547 screenlog("want: %s, so using %ix%i", "bigger" if bigger else "smaller", w, h) 

548 if w<=0 or h<=0: 

549 #invalid - use fallback 

550 return root_w, root_h 

551 return self.set_screen_size(w, h, bigger) 

552 

553 def get_best_screen_size(self, desired_w, desired_h, bigger=True): 

554 return self.do_get_best_screen_size(desired_w, desired_h, bigger) 

555 

556 def do_get_best_screen_size(self, desired_w, desired_h, bigger=True): 

557 if not self.randr: 

558 return desired_w, desired_h 

559 screen_sizes = self.get_all_screen_sizes() 

560 if (desired_w, desired_h) in screen_sizes: 

561 return desired_w, desired_h 

562 if self.randr_exact_size: 

563 try: 

564 with xsync: 

565 v = RandR.add_screen_size(desired_w, desired_h) 

566 if v: 

567 #we have to wait a little bit 

568 #to make sure that everything sees the new resolution 

569 #(ideally this method would be split in two and this would be a callback) 

570 self.randr_sizes_added.append(v) 

571 import time 

572 time.sleep(0.5) 

573 return v 

574 except XError as e: 

575 screenlog("add_screen_size(%s, %s)", desired_w, desired_h, exc_info=True) 

576 screenlog.warn("Warning: failed to add resolution %ix%i:", desired_w, desired_h) 

577 screenlog.warn(" %s", e) 

578 #re-query: 

579 screen_sizes = self.get_all_screen_sizes() 

580 #try to find the best screen size to resize to: 

581 new_size = None 

582 closest = {} 

583 for w,h in screen_sizes: 

584 if (w<desired_w)==bigger or (h<desired_h)==bigger: 

585 distance = abs(desired_w*desired_h - w*h) 

586 closest[distance] = (w, h) 

587 continue #size is too small/big for client 

588 if new_size: 

589 ew,eh = new_size #pylint: disable=unpacking-non-sequence 

590 if (ew*eh<w*h)==bigger: 

591 continue #we found a better (smaller/bigger) candidate already 

592 new_size = w,h 

593 if not new_size: 

594 screenlog.warn("Warning: no matching resolution found for %sx%s", desired_w, desired_h) 

595 if closest: 

596 min_dist = sorted(closest.keys())[0] 

597 new_size = closest[min_dist] 

598 screenlog.warn(" using %sx%s instead", *new_size) 

599 else: 

600 root_w, root_h = self.root_window.get_size() 

601 return root_w, root_h 

602 screenlog("best %s resolution for client(%sx%s) is: %s", 

603 ["smaller", "bigger"][bigger], desired_w, desired_h, new_size) 

604 w, h = new_size 

605 return w, h 

606 

607 def set_screen_size(self, desired_w, desired_h, bigger=True): 

608 screenlog("set_screen_size%s", (desired_w, desired_h, bigger)) 

609 root_w, root_h = self.root_window.get_geometry()[2:4] 

610 if not self.randr: 

611 return root_w,root_h 

612 if desired_w==root_w and desired_h==root_h and not self.fake_xinerama: 

613 return root_w,root_h #unlikely: perfect match already! 

614 #clients may supply "xdpi" and "ydpi" (v0.15 onwards), or just "dpi", or nothing... 

615 xdpi = self.xdpi or self.dpi 

616 ydpi = self.ydpi or self.dpi 

617 screenlog("set_screen_size(%s, %s, %s) xdpi=%s, ydpi=%s", 

618 desired_w, desired_h, bigger, xdpi, ydpi) 

619 if xdpi<=0 or ydpi<=0: 

620 #use some sane defaults: either the command line option, or fallback to 96 

621 #(96 is better than nothing, because we do want to set the dpi 

622 # to avoid Xdummy setting a crazy dpi from the virtual screen dimensions) 

623 xdpi = self.default_dpi or 96 

624 ydpi = self.default_dpi or 96 

625 #find the "physical" screen dimensions, so we can calculate the required dpi 

626 #(and do this before changing the resolution) 

627 wmm, hmm = 0, 0 

628 client_w, client_h = 0, 0 

629 sss = self._server_sources.values() 

630 for ss in sss: 

631 for s in ss.screen_sizes: 

632 if len(s)>=10: 

633 #(display_name, width, height, width_mm, height_mm, monitors, 

634 # work_x, work_y, work_width, work_height) 

635 client_w = max(client_w, s[1]) 

636 client_h = max(client_h, s[2]) 

637 wmm = max(wmm, s[3]) 

638 hmm = max(hmm, s[4]) 

639 if wmm>0 and hmm>0 and client_w>0 and client_h>0: 

640 #calculate "real" dpi: 

641 xdpi = iround(client_w * 25.4 / wmm) 

642 ydpi = iround(client_h * 25.4 / hmm) 

643 screenlog("calculated DPI: %s x %s (from w: %s / %s, h: %s / %s)", 

644 xdpi, ydpi, client_w, wmm, client_h, hmm) 

645 self.set_dpi(xdpi, ydpi) 

646 

647 #try to find the best screen size to resize to: 

648 w, h = self.get_best_screen_size(desired_w, desired_h, bigger) 

649 

650 #fakeXinerama: 

651 ui_clients = [s for s in self._server_sources.values() if s.ui_client] 

652 source = None 

653 screen_sizes = [] 

654 if len(ui_clients)==1: 

655 source = ui_clients[0] 

656 screen_sizes = source.screen_sizes 

657 else: 

658 screenlog("fakeXinerama can only be enabled for a single client (found %s)" % len(ui_clients)) 

659 xinerama_changed = save_fakeXinerama_config(self.fake_xinerama and len(ui_clients)==1, source, screen_sizes) 

660 #we can only keep things unchanged if xinerama was also unchanged 

661 #(many apps will only query xinerama again if they get a randr notification) 

662 if (w==root_w and h==root_h) and not xinerama_changed: 

663 screenlog.info("best resolution matching %sx%s is unchanged: %sx%s", desired_w, desired_h, w, h) 

664 return root_w, root_h 

665 try: 

666 if (w==root_w and h==root_h) and xinerama_changed: 

667 #xinerama was changed, but the RandR resolution will not be... 

668 #and we need a RandR change to force applications to re-query it 

669 #so we temporarily switch to another resolution to force 

670 #the change! (ugly! but this works) 

671 with xsync: 

672 temp = {} 

673 for tw,th in self.get_all_screen_sizes(): 

674 if tw!=w or th!=h: 

675 #use the number of extra pixels as key: 

676 #(so we can choose the closest resolution) 

677 temp[abs((tw*th) - (w*h))] = (tw, th) 

678 if not temp: 

679 screenlog.warn("cannot find a temporary resolution for Xinerama workaround!") 

680 else: 

681 k = sorted(temp.keys())[0] 

682 tw, th = temp[k] 

683 screenlog.info("temporarily switching to %sx%s as a Xinerama workaround", tw, th) 

684 with xsync: 

685 RandR.set_screen_size(tw, th) 

686 with xsync: 

687 RandR.get_screen_size() 

688 #Xdummy with randr 1.2: 

689 screenlog("using XRRSetScreenConfigAndRate with %ix%i", w, h) 

690 with xsync: 

691 RandR.set_screen_size(w, h) 

692 if self.randr_exact_size: 

693 #Xvfb with randr > 1.2: the resolution has been added 

694 #we can use XRRSetScreenSize: 

695 try: 

696 with xsync: 

697 RandR.xrr_set_screen_size(w, h, self.xdpi or self.dpi or 96, self.ydpi or self.dpi or 96) 

698 except XError: 

699 screenlog("XRRSetScreenSize failed", exc_info=True) 

700 screenlog("calling RandR.get_screen_size()") 

701 with xsync: 

702 root_w, root_h = RandR.get_screen_size() 

703 screenlog("RandR.get_screen_size()=%s,%s", root_w, root_h) 

704 screenlog("RandR.get_vrefresh()=%s", RandR.get_vrefresh()) 

705 if root_w!=w or root_h!=h: 

706 screenlog.warn("Warning: tried to set resolution to %ix%i", w, h) 

707 screenlog.warn(" and ended up with %ix%i", root_w, root_h) 

708 else: 

709 msg = "server virtual display now set to %sx%s" % (root_w, root_h) 

710 if desired_w!=root_w or desired_h!=root_h: 

711 msg += " (best match for %sx%s)" % (desired_w, desired_h) 

712 screenlog.info(msg) 

713 def show_dpi(): 

714 wmm, hmm = RandR.get_screen_size_mm() #ie: (1280, 1024) 

715 screenlog("RandR.get_screen_size_mm=%s,%s", wmm, hmm) 

716 actual_xdpi = iround(root_w * 25.4 / wmm) 

717 actual_ydpi = iround(root_h * 25.4 / hmm) 

718 if abs(actual_xdpi-xdpi)<=1 and abs(actual_ydpi-ydpi)<=1: 

719 screenlog.info("DPI set to %s x %s", actual_xdpi, actual_ydpi) 

720 screenlog("wanted: %s x %s", xdpi, ydpi) 

721 else: 

722 #should this be a warning: 

723 l = screenlog.info 

724 maxdelta = max(abs(actual_xdpi-xdpi), abs(actual_ydpi-ydpi)) 

725 if maxdelta>=10: 

726 l = log.warn 

727 messages = [ 

728 "DPI set to %s x %s (wanted %s x %s)" % (actual_xdpi, actual_ydpi, xdpi, ydpi), 

729 ] 

730 if maxdelta>=10: 

731 messages.append("you may experience scaling problems, such as huge or small fonts, etc") 

732 messages.append("to fix this issue, try the dpi switch, or use a patched Xorg dummy driver") 

733 self.notify_dpi_warning("\n".join(messages)) 

734 for i,message in enumerate(messages): 

735 l("%s%s", ["", " "][i>0], message) 

736 #show dpi via idle_add so server has time to change the screen size (mm) 

737 self.idle_add(show_dpi) 

738 except Exception as e: 

739 screenlog.error("ouch, failed to set new resolution: %s", e, exc_info=True) 

740 return root_w, root_h 

741 

742 def notify_dpi_warning(self, body): 

743 sources = tuple(self._server_sources.values()) 

744 if len(sources)==1: 

745 ss = sources[0] 

746 if first_time("DPI-warning-%s" % ss.uuid): 

747 sources[0].may_notify(XPRA_DPI_NOTIFICATION_ID, "DPI Issue", body, icon_name="font") 

748 

749 

750 def _process_server_settings(self, _proto, packet): 

751 settings = packet[1] 

752 log("process_server_settings: %s", settings) 

753 self.update_server_settings(settings) 

754 

755 def update_server_settings(self, _settings, _reset=False): 

756 #implemented in the X11 xpra server only for now 

757 #(does not make sense to update a shadow server) 

758 log("ignoring server settings update in %s", self) 

759 

760 

761 def _process_force_ungrab(self, proto, _packet): 

762 #ignore the window id: wid = packet[1] 

763 grablog("force ungrab from %s", proto) 

764 self.X11_ungrab() 

765 

766 def X11_ungrab(self): 

767 grablog("X11_ungrab") 

768 with xsync: 

769 X11Core.UngrabKeyboard() 

770 X11Core.UngrabPointer() 

771 

772 

773 def fake_key(self, keycode, press): 

774 keylog("fake_key(%s, %s)", keycode, press) 

775 mink, maxk = X11Keyboard.get_minmax_keycodes() 

776 if keycode<mink or keycode>maxk: 

777 return 

778 with xsync: 

779 X11Keyboard.xtest_fake_key(keycode, press) 

780 

781 

782 def do_xpra_cursor_event(self, event): 

783 if not self.cursors: 

784 return 

785 if self.last_cursor_serial==event.cursor_serial: 

786 cursorlog("ignoring cursor event %s with the same serial number %s", event, self.last_cursor_serial) 

787 return 

788 cursorlog("cursor_event: %s", event) 

789 self.last_cursor_serial = event.cursor_serial 

790 from xpra.server.source.windows_mixin import WindowsMixin 

791 for ss in self._server_sources.values(): 

792 if isinstance(ss, WindowsMixin): 

793 ss.send_cursor() 

794 

795 

796 def _motion_signaled(self, model, event): 

797 mouselog("motion_signaled(%s, %s) last mouse user=%s", model, event, self.last_mouse_user) 

798 #find the window model for this gdk window: 

799 wid = self._window_to_id.get(model) 

800 if not wid: 

801 return 

802 for ss in self._server_sources.values(): 

803 if ALWAYS_NOTIFY_MOTION or self.last_mouse_user is None or self.last_mouse_user!=ss.uuid: 

804 ss.update_mouse(wid, event.x_root, event.y_root, event.x, event.y) 

805 

806 

807 def do_xpra_xkb_event(self, event): 

808 #X11: XKBNotify 

809 log("WindowModel.do_xpra_xkb_event(%r)" % event) 

810 if event.subtype!="bell": 

811 log.error("do_xpra_xkb_event(%r) unknown event type: %s" % (event, event.type)) 

812 return 

813 #bell events on our windows will come through the bell signal, 

814 #this method is a catch-all for events on windows we don't manage, 

815 #so we use wid=0 for that: 

816 wid = 0 

817 for ss in self._server_sources.values(): 

818 name = strtobytes(event.bell_name or "") 

819 ss.bell(wid, event.device, event.percent, event.pitch, event.duration, event.bell_class, event.bell_id, name) 

820 

821 

822 def _bell_signaled(self, wm, event): 

823 log("bell signaled on window %#x", event.window.get_xid()) 

824 if not self.bell: 

825 return 

826 wid = 0 

827 if event.window!=get_default_root_window() and event.window_model is not None: 

828 wid = self._window_to_id.get(event.window_model, 0) 

829 log("_bell_signaled(%s,%r) wid=%s", wm, event, wid) 

830 from xpra.server.source.windows_mixin import WindowsMixin 

831 for ss in self._server_sources.values(): 

832 if isinstance(ss, WindowsMixin): 

833 name = strtobytes(event.bell_name or "") 

834 ss.bell(wid, event.device, event.percent, event.pitch, event.duration, event.bell_class, event.bell_id, name) 

835 

836 

837 def get_screen_number(self, _wid): 

838 #maybe this should be in all cases (it is in desktop_server): 

839 #model = self._id_to_window.get(wid) 

840 #return model.client_window.get_screen().get_number() 

841 #return Gdk.Display.get_default().get_default_screen().get_number() 

842 #-1 uses the current screen 

843 return -1 

844 

845 

846 def cleanup_input_devices(self): 

847 pass 

848 

849 

850 def setup_input_devices(self): 

851 from xpra.server import server_features 

852 xinputlog("setup_input_devices() input_devices feature=%s", server_features.input_devices) 

853 if not server_features.input_devices: 

854 return 

855 xinputlog("setup_input_devices() format=%s, input_devices=%s", self.input_devices_format, self.input_devices) 

856 xinputlog("setup_input_devices() input_devices_data=%s", self.input_devices_data) 

857 #xinputlog("setup_input_devices() input_devices_data=%s", self.input_devices_data) 

858 xinputlog("setup_input_devices() pointer device=%s", self.pointer_device) 

859 xinputlog("setup_input_devices() touchpad device=%s", self.touchpad_device) 

860 self.pointer_device_map = {} 

861 if not self.touchpad_device: 

862 #no need to assign anything, we only have one device anyway 

863 return 

864 #if we find any absolute pointer devices, 

865 #map them to the "touchpad_device" 

866 XIModeAbsolute = 1 

867 for deviceid, device_data in self.input_devices_data.items(): 

868 name = device_data.get("name") 

869 #xinputlog("[%i]=%s", deviceid, device_data) 

870 xinputlog("[%i]=%s", deviceid, name) 

871 if device_data.get("use")!="slave pointer": 

872 continue 

873 classes = device_data.get("classes") 

874 if not classes: 

875 continue 

876 #look for absolute pointer devices: 

877 touchpad_axes = [] 

878 for i, defs in classes.items(): 

879 xinputlog(" [%i]=%s", i, defs) 

880 mode = defs.get("mode") 

881 label = defs.get("label") 

882 if not mode or mode!=XIModeAbsolute: 

883 continue 

884 if defs.get("min", -1)==0 and defs.get("max", -1)==(2**24-1): 

885 touchpad_axes.append((i, label)) 

886 if len(touchpad_axes)==2: 

887 xinputlog.info("found touchpad device: %s", name) 

888 xinputlog("axes: %s", touchpad_axes) 

889 self.pointer_device_map[deviceid] = self.touchpad_device 

890 

891 

892 def _process_wheel_motion(self, proto, packet): 

893 assert self.pointer_device.has_precise_wheel() 

894 wid, button, distance, pointer, modifiers, _buttons = packet[1:7] 

895 with xsync: 

896 if self.do_process_mouse_common(proto, wid, pointer): 

897 self._update_modifiers(proto, wid, modifiers) 

898 self.pointer_device.wheel_motion(button, distance/1000.0) #pylint: disable=no-member 

899 

900 def get_pointer_device(self, deviceid): 

901 #mouselog("get_pointer_device(%i) input_devices_data=%s", deviceid, self.input_devices_data) 

902 if self.input_devices_data: 

903 device_data = self.input_devices_data.get(deviceid) 

904 if device_data: 

905 mouselog("get_pointer_device(%i) device=%s", deviceid, device_data.get("name")) 

906 device = self.pointer_device_map.get(deviceid) or self.pointer_device 

907 return device 

908 

909 

910 def _get_pointer_abs_coordinates(self, wid, pos): 

911 #simple absolute coordinates 

912 x, y = pos[:2] 

913 from xpra.server.mixins.window_server import WindowServer 

914 if len(pos)>=4 and isinstance(self, WindowServer): 

915 #relative coordinates 

916 model = self._id_to_window.get(wid) 

917 if model: 

918 rx, ry = pos[2:4] 

919 geom = model.get_geometry() 

920 x = geom[0]+rx 

921 y = geom[1]+ry 

922 log("_get_pointer_abs_coordinates(%i, %s)=%s window geometry=%s", wid, pos, (x, y), geom) 

923 return x, y 

924 

925 def _move_pointer(self, wid, pos, deviceid=-1, *args): 

926 #(this is called within an xswallow context) 

927 screen_no = self.get_screen_number(wid) 

928 device = self.get_pointer_device(deviceid) 

929 x, y = self._get_pointer_abs_coordinates(wid, pos) 

930 mouselog("move_pointer(%s, %s, %s) screen_no=%i, device=%s, position=%s", 

931 wid, pos, deviceid, screen_no, device, (x, y)) 

932 try: 

933 device.move_pointer(screen_no, x, y, *args) 

934 except Exception as e: 

935 mouselog.error("Error: failed to move the pointer to %sx%s using %s", x, y, device) 

936 mouselog.error(" %s", e) 

937 

938 def do_process_mouse_common(self, proto, wid, pointer, deviceid=-1, *args): 

939 mouselog("do_process_mouse_common%s", tuple([proto, wid, pointer, deviceid]+list(args))) 

940 if self.readonly: 

941 return None 

942 pos = self.root_window.get_pointer()[-3:-1] 

943 uuid = None 

944 if proto: 

945 ss = self.get_server_source(proto) 

946 if ss: 

947 uuid = ss.uuid 

948 if pos!=pointer[:2] or self.input_devices=="xi": 

949 self.last_mouse_user = uuid 

950 with xswallow: 

951 self._move_pointer(wid, pointer, deviceid, *args) 

952 return pointer 

953 

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

955 if self.readonly: 

956 return 

957 ss = self.get_server_source(proto) 

958 if ss: 

959 if self.ui_driver and self.ui_driver!=ss.uuid: 

960 return 

961 ss.make_keymask_match(modifiers) 

962 if wid==self.get_focus(): 

963 ss.user_event() 

964 

965 def do_process_button_action(self, proto, wid, button, pressed, pointer, modifiers, _buttons=(), deviceid=-1, *_args): 

966 self._update_modifiers(proto, wid, modifiers) 

967 #TODO: pass extra args 

968 if self._process_mouse_common(proto, wid, pointer, deviceid): 

969 self.button_action(pointer, button, pressed, deviceid) 

970 

971 def button_action(self, pointer, button, pressed, deviceid=-1, *args): 

972 device = self.get_pointer_device(deviceid) 

973 assert device, "pointer device %s not found" % deviceid 

974 try: 

975 log("%s%s", device.click, (button, pressed, args)) 

976 with xsync: 

977 device.click(button, pressed, *args) 

978 except XError: 

979 log("button_action(%s, %s, %s, %s, %s)", pointer, button, pressed, deviceid, args, exc_info=True) 

980 log.error("Error: failed (un)press mouse button %s", button) 

981 if button>=4: 

982 log.error(" (perhaps your Xvfb does not support mousewheels?)") 

983 

984 

985 def make_screenshot_packet_from_regions(self, regions): 

986 #regions = array of (wid, x, y, PIL.Image) 

987 if not regions: 

988 log("screenshot: no regions found, returning empty 0x0 image!") 

989 return ["screenshot", 0, 0, "png", -1, ""] 

990 #in theory, we could run the rest in a non-UI thread since we're done with GTK.. 

991 minx = min(x for (_,x,_,_) in regions) 

992 miny = min(y for (_,_,y,_) in regions) 

993 maxx = max((x+img.get_width()) for (_,x,_,img) in regions) 

994 maxy = max((y+img.get_height()) for (_,_,y,img) in regions) 

995 width = maxx-minx 

996 height = maxy-miny 

997 log("screenshot: %sx%s, min x=%s y=%s", width, height, minx, miny) 

998 from PIL import Image #@UnresolvedImport 

999 screenshot = Image.new("RGBA", (width, height)) 

1000 for wid, x, y, img in reversed(regions): 

1001 pixel_format = img.get_pixel_format() 

1002 target_format = { 

1003 "XRGB" : "RGB", 

1004 "BGRX" : "RGB", 

1005 "BGRA" : "RGBA"}.get(pixel_format, pixel_format) 

1006 pixels = img.get_pixels() 

1007 w = img.get_width() 

1008 h = img.get_height() 

1009 #PIL cannot use the memoryview directly: 

1010 if isinstance(pixels, memoryview): 

1011 pixels = pixels.tobytes() 

1012 try: 

1013 window_image = Image.frombuffer(target_format, (w, h), pixels, "raw", pixel_format, img.get_rowstride()) 

1014 except Exception: 

1015 log.error("Error parsing window pixels in %s format for window %i", pixel_format, wid, exc_info=True) 

1016 continue 

1017 tx = x-minx 

1018 ty = y-miny 

1019 screenshot.paste(window_image, (tx, ty)) 

1020 from io import BytesIO 

1021 buf = BytesIO() 

1022 screenshot.save(buf, "png") 

1023 data = buf.getvalue() 

1024 buf.close() 

1025 packet = ["screenshot", width, height, "png", width*4, Compressed("png", data)] 

1026 log("screenshot: %sx%s %s", packet[1], packet[2], packet[-1]) 

1027 return packet