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-2019 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 

10from threading import Thread, Lock 

11 

12from xpra.server.server_core import ServerCore 

13from xpra.server.mixins.server_base_controlcommands import ServerBaseControlCommands 

14from xpra.server.background_worker import add_work_item 

15from xpra.net.common import may_log_packet 

16from xpra.os_util import monotonic_time, bytestostr, strtobytes, WIN32 

17from xpra.util import ( 

18 typedict, flatten_dict, updict, merge_dicts, envbool, envint, 

19 SERVER_EXIT, SERVER_ERROR, SERVER_SHUTDOWN, DETACH_REQUEST, 

20 NEW_CLIENT, DONE, SESSION_BUSY, 

21 ) 

22from xpra.net.bytestreams import set_socket_timeout 

23from xpra.server import server_features 

24from xpra.server import EXITING_CODE 

25from xpra.log import Logger 

26 

27SERVER_BASES = [ServerCore, ServerBaseControlCommands] 

28if server_features.notifications: 

29 from xpra.server.mixins.notification_forwarder import NotificationForwarder 

30 SERVER_BASES.append(NotificationForwarder) 

31if server_features.webcam: 

32 from xpra.server.mixins.webcam_server import WebcamServer 

33 SERVER_BASES.append(WebcamServer) 

34if server_features.clipboard: 

35 from xpra.server.mixins.clipboard_server import ClipboardServer 

36 SERVER_BASES.append(ClipboardServer) 

37if server_features.audio: 

38 from xpra.server.mixins.audio_server import AudioServer 

39 SERVER_BASES.append(AudioServer) 

40if server_features.fileprint: 

41 from xpra.server.mixins.fileprint_server import FilePrintServer 

42 SERVER_BASES.append(FilePrintServer) 

43if server_features.mmap: 

44 from xpra.server.mixins.mmap_server import MMAP_Server 

45 SERVER_BASES.append(MMAP_Server) 

46if server_features.input_devices: 

47 from xpra.server.mixins.input_server import InputServer 

48 SERVER_BASES.append(InputServer) 

49if server_features.commands: 

50 from xpra.server.mixins.child_command_server import ChildCommandServer 

51 SERVER_BASES.append(ChildCommandServer) 

52if server_features.dbus: 

53 from xpra.server.mixins.dbusrpc_server import DBUS_RPC_Server 

54 SERVER_BASES.append(DBUS_RPC_Server) 

55if server_features.encoding: 

56 from xpra.server.mixins.encoding_server import EncodingServer 

57 SERVER_BASES.append(EncodingServer) 

58if server_features.logging: 

59 from xpra.server.mixins.logging_server import LoggingServer 

60 SERVER_BASES.append(LoggingServer) 

61if server_features.network_state: 

62 from xpra.server.mixins.networkstate_server import NetworkStateServer 

63 SERVER_BASES.append(NetworkStateServer) 

64if server_features.shell: 

65 from xpra.server.mixins.shell_server import ShellServer 

66 SERVER_BASES.append(ShellServer) 

67if server_features.display: 

68 from xpra.server.mixins.display_manager import DisplayManager 

69 SERVER_BASES.append(DisplayManager) 

70if server_features.windows: 

71 from xpra.server.mixins.window_server import WindowServer 

72 SERVER_BASES.append(WindowServer) 

73SERVER_BASES = tuple(SERVER_BASES) 

74ServerBaseClass = type('ServerBaseClass', SERVER_BASES, {}) 

75 

76log = Logger("server") 

77netlog = Logger("network") 

78httplog = Logger("http") 

79timeoutlog = Logger("timeout") 

80screenlog = Logger("screen") 

81 

82log("ServerBaseClass%s", SERVER_BASES) 

83 

84CLIENT_CAN_SHUTDOWN = envbool("XPRA_CLIENT_CAN_SHUTDOWN", True) 

85INIT_THREAD_TIMEOUT = envint("XPRA_INIT_THREAD_TIMEOUT", 10) 

86MDNS_CLIENT_COUNT = envbool("XPRA_MDNS_CLIENT_COUNT", True) 

87 

88 

89""" 

90This is the base class for seamless and desktop servers. (not proxy servers) 

91It provides all the generic functions but is not tied 

92to a specific backend (X11 or otherwise). 

93See GTKServerBase/X11ServerBase and other platform specific subclasses. 

94""" 

95class ServerBase(ServerBaseClass): 

96 

97 def __init__(self): 

98 for c in SERVER_BASES: 

99 c.__init__(self) 

100 log("ServerBase.__init__()") 

101 self.init_uuid() 

102 

103 self._authenticated_packet_handlers = {} 

104 self._authenticated_ui_packet_handlers = {} 

105 

106 self.display_pid = 0 

107 self._server_sources = {} 

108 self.client_properties = {} 

109 self.ui_driver = None 

110 self.sharing = None 

111 self.lock = None 

112 self.init_thread = None 

113 self.init_thread_callbacks = [] 

114 self.init_thread_lock = Lock() 

115 

116 self.idle_timeout = 0 

117 #duplicated from Server Source... 

118 self.client_shutdown = CLIENT_CAN_SHUTDOWN 

119 self.http_stream_check_timers = {} 

120 

121 self.init_packet_handlers() 

122 self.init_aliases() 

123 

124 

125 def idle_add(self, *args, **kwargs): 

126 raise NotImplementedError() 

127 

128 def timeout_add(self, *args, **kwargs): 

129 raise NotImplementedError() 

130 

131 def source_remove(self, timer): 

132 raise NotImplementedError() 

133 

134 

135 def server_event(self, *args): 

136 for s in self._server_sources.values(): 

137 s.send_server_event(*args) 

138 if self.dbus_server: 

139 self.dbus_server.Event(str(args[0]), [str(x) for x in args[1:]]) 

140 

141 def get_server_source(self, proto): 

142 return self._server_sources.get(proto) 

143 

144 

145 def init(self, opts): 

146 #from now on, use the logger for parsing errors: 

147 from xpra.scripts import config 

148 config.warn = log.warn 

149 for c in SERVER_BASES: 

150 start = monotonic_time() 

151 c.init(self, opts) 

152 end = monotonic_time() 

153 log("%3ims in %s.init", 1000*(end-start), c) 

154 self.sharing = opts.sharing 

155 self.lock = opts.lock 

156 self.idle_timeout = opts.idle_timeout 

157 self.bandwidth_detection = opts.bandwidth_detection 

158 

159 def setup(self): 

160 log("starting component init") 

161 for c in SERVER_BASES: 

162 start = monotonic_time() 

163 c.setup(self) 

164 end = monotonic_time() 

165 log("%3ims in %s.setup", 1000*(end-start), c) 

166 self.init_thread = Thread(target=self.threaded_init) 

167 self.init_thread.start() 

168 

169 def threaded_init(self): 

170 log("threaded_init() start") 

171 from xpra.platform import threaded_server_init 

172 threaded_server_init() 

173 for c in SERVER_BASES: 

174 if c!=ServerCore: 

175 try: 

176 c.threaded_setup(self) 

177 except Exception: 

178 log.error("Error during threaded setup of %s", c, exc_info=True) 

179 #populate the platform info cache: 

180 from xpra.version_util import get_platform_info 

181 get_platform_info() 

182 with self.init_thread_lock: 

183 for cb in self.init_thread_callbacks: 

184 try: 

185 cb() 

186 except Exception as e: 

187 log("threaded_init()", exc_info=True) 

188 log.error("Error in initialization thread callback %s", cb) 

189 log.error(" %s", e) 

190 log("threaded_init() end") 

191 

192 def after_threaded_init(self, callback): 

193 with self.init_thread_lock: 

194 if not self.init_thread or not self.init_thread.is_alive(): 

195 callback() 

196 else: 

197 self.init_thread_callbacks.append(callback) 

198 

199 def wait_for_threaded_init(self): 

200 if not self.init_thread: 

201 #looks like we didn't make it as far as calling setup() 

202 log("wait_for_threaded_init() no init thread") 

203 return 

204 log("wait_for_threaded_init() %s.is_alive()=%s", self.init_thread, self.init_thread.is_alive()) 

205 if self.init_thread.is_alive(): 

206 log.info("waiting for initialization thread to complete") 

207 self.init_thread.join(INIT_THREAD_TIMEOUT) 

208 if self.init_thread.is_alive(): 

209 log.warn("Warning: initialization thread is still active") 

210 

211 

212 def server_is_ready(self): 

213 ServerCore.server_is_ready(self) 

214 self.server_event("ready") 

215 

216 

217 def do_cleanup(self): 

218 self.server_event("exit") 

219 self.wait_for_threaded_init() 

220 self.cancel_http_stream_check_timers() 

221 for c in SERVER_BASES: 

222 if c!=ServerCore: 

223 c.cleanup(self) 

224 

225 

226 ###################################################################### 

227 # shutdown / exit commands: 

228 def _process_exit_server(self, _proto, _packet): 

229 log.info("Exiting in response to client request") 

230 self.cleanup_all_protocols(SERVER_EXIT) 

231 self.timeout_add(500, self.clean_quit, EXITING_CODE) 

232 

233 def _process_shutdown_server(self, _proto, _packet): 

234 if not self.client_shutdown: 

235 log.warn("Warning: ignoring shutdown request") 

236 return 

237 log.info("Shutting down in response to client request") 

238 self.cleanup_all_protocols(SERVER_SHUTDOWN) 

239 self.timeout_add(500, self.clean_quit) 

240 

241 

242 def get_mdns_info(self) -> dict: 

243 mdns_info = ServerCore.get_mdns_info(self) 

244 if MDNS_CLIENT_COUNT: 

245 mdns_info["clients"] = len(self._server_sources) 

246 return mdns_info 

247 

248 

249 ###################################################################### 

250 # handle new connections: 

251 def handle_sharing(self, proto, ui_client=True, detach_request=False, share=False, uuid=None): 

252 share_count = 0 

253 disconnected = 0 

254 existing_sources = set(ss for p,ss in self._server_sources.items() if p!=proto) 

255 is_existing_client = uuid and any(ss.uuid==uuid for ss in existing_sources) 

256 log("handle_sharing%s lock=%s, sharing=%s, existing sources=%s, is existing client=%s", 

257 (proto, ui_client, detach_request, share, uuid), 

258 self.lock, self.sharing, existing_sources, is_existing_client) 

259 #if other clients are connected, verify we can steal or share: 

260 if existing_sources and not is_existing_client: 

261 if self.sharing is True or (self.sharing is None and share and all(ss.share for ss in existing_sources)): 

262 log("handle_sharing: sharing with %s", tuple(existing_sources)) 

263 elif self.lock is True: 

264 self.disconnect_client(proto, SESSION_BUSY, "this session is locked") 

265 return False, 0, 0 

266 elif self.lock is not False and any(ss.lock for ss in existing_sources): 

267 self.disconnect_client(proto, SESSION_BUSY, "a client has locked this session") 

268 return False, 0, 0 

269 for p,ss in tuple(self._server_sources.items()): 

270 if detach_request and p!=proto: 

271 self.disconnect_client(p, DETACH_REQUEST) 

272 disconnected += 1 

273 elif uuid and ss.uuid==uuid and ui_client and ss.ui_client: 

274 self.disconnect_client(p, NEW_CLIENT, "new connection from the same uuid") 

275 disconnected += 1 

276 elif ui_client and ss.ui_client: 

277 #check if existing sessions are willing to share: 

278 if self.sharing is True: 

279 share_count += 1 

280 elif self.sharing is False: 

281 self.disconnect_client(p, NEW_CLIENT, "this session does not allow sharing") 

282 disconnected += 1 

283 else: 

284 assert self.sharing is None 

285 if not share: 

286 self.disconnect_client(p, NEW_CLIENT, "the new client does not wish to share") 

287 disconnected += 1 

288 elif not ss.share: 

289 self.disconnect_client(p, NEW_CLIENT, "this client had not enabled sharing") 

290 disconnected += 1 

291 else: 

292 share_count += 1 

293 

294 #don't accept this connection if we're going to exit-with-client: 

295 accepted = True 

296 if disconnected>0 and share_count==0 and self.exit_with_client: 

297 self.disconnect_client(proto, SERVER_EXIT, "last client has exited") 

298 accepted = False 

299 return accepted, share_count, disconnected 

300 

301 def hello_oked(self, proto, packet, c, auth_caps): 

302 if ServerCore.hello_oked(self, proto, packet, c, auth_caps): 

303 #has been handled 

304 return 

305 if not c.boolget("steal", True) and self._server_sources: 

306 self.disconnect_client(proto, SESSION_BUSY, "this session is already active") 

307 return 

308 if c.boolget("screenshot_request"): 

309 self.send_screenshot(proto) 

310 return 

311 #added in 2.2: 

312 generic_request = c.strget("request") 

313 def is_req(mode): 

314 return generic_request==mode or c.boolget("%s_request" % mode, False) 

315 detach_request = is_req("detach") 

316 stop_request = is_req("stop_request") 

317 exit_request = is_req("exit_request") 

318 event_request = is_req("event_request") 

319 print_request = is_req("print_request") 

320 is_request = detach_request or stop_request or exit_request or event_request or print_request 

321 if not is_request: 

322 #"normal" connection, so log welcome message: 

323 log.info("Handshake complete; enabling connection") 

324 else: 

325 log("handling request %s", generic_request) 

326 self.server_event("handshake-complete") 

327 

328 # Things are okay, we accept this connection, and may disconnect previous one(s) 

329 # (but only if this is going to be a UI session - control sessions can co-exist) 

330 ui_client = c.boolget("ui_client", True) 

331 share = c.boolget("share") 

332 uuid = c.strget("uuid") 

333 accepted, share_count, disconnected = self.handle_sharing(proto, ui_client, detach_request, share, uuid) 

334 if not accepted: 

335 return 

336 

337 if detach_request: 

338 self.disconnect_client(proto, DONE, "%i other clients have been disconnected" % disconnected) 

339 return 

340 

341 if not is_request and ui_client: 

342 #a bit of explanation: 

343 #normally these things are synchronized using xsettings, which we handle already 

344 #but non-posix clients have no such thing, 

345 #and we don't want to expose that as an interface 

346 #(it's not very nice and it is very X11 specific) 

347 #also, clients may want to override what is in their xsettings.. 

348 #so if the client specifies what it wants to use, we patch the xsettings with it 

349 #(the actual xsettings part is done in update_all_server_settings in the X11 specific subclasses) 

350 if share_count>0: 

351 log.info("sharing with %s other client(s)", share_count) 

352 self.dpi = 0 

353 self.xdpi = 0 

354 self.ydpi = 0 

355 self.double_click_time = -1 

356 self.double_click_distance = -1, -1 

357 self.antialias = {} 

358 self.cursor_size = 24 

359 else: 

360 self.dpi = c.intget("dpi", 0) 

361 self.xdpi = c.intget("dpi.x", 0) 

362 self.ydpi = c.intget("dpi.y", 0) 

363 self.double_click_time = c.intget("double_click.time", -1) 

364 self.double_click_distance = c.intpair("double_click.distance", (-1, -1)) 

365 self.antialias = c.dictget("antialias", {}) 

366 self.cursor_size = c.intget("cursor.size", 0) 

367 #FIXME: this belongs in DisplayManager! 

368 screenlog("dpi=%s, dpi.x=%s, dpi.y=%s, antialias=%s, cursor_size=%s", 

369 self.dpi, self.xdpi, self.ydpi, self.antialias, self.cursor_size) 

370 log("double-click time=%s, distance=%s", self.double_click_time, self.double_click_distance) 

371 #if we're not sharing, reset all the settings: 

372 reset = share_count==0 

373 self.update_all_server_settings(reset) 

374 

375 self.accept_client(proto, c) 

376 #use blocking sockets from now on: 

377 if not WIN32: 

378 set_socket_timeout(proto._conn, None) 

379 

380 def drop_client(reason="unknown", *args): 

381 self.disconnect_client(proto, reason, *args) 

382 cc_class = self.get_client_connection_class(c) 

383 ss = cc_class(proto, drop_client, 

384 self.session_name, self, 

385 self.idle_add, self.timeout_add, self.source_remove, 

386 self.setting_changed, 

387 self._socket_dir, self.unix_socket_paths, not is_request, 

388 self.bandwidth_limit, self.bandwidth_detection, 

389 ) 

390 log("process_hello clientconnection=%s", ss) 

391 try: 

392 ss.parse_hello(c) 

393 except: 

394 #close it already 

395 ss.close() 

396 raise 

397 self._server_sources[proto] = ss 

398 add_work_item(self.mdns_update, False) 

399 #process ui half in ui thread: 

400 send_ui = ui_client and not is_request 

401 self.idle_add(self._process_hello_ui, ss, c, auth_caps, send_ui, share_count) 

402 

403 def get_client_connection_class(self, caps): 

404 from xpra.server.source.client_connection_factory import get_client_connection_class 

405 return get_client_connection_class(caps) 

406 

407 

408 def _process_hello_ui(self, ss, c, auth_caps, send_ui : bool, share_count : int): 

409 #adds try:except around parse hello ui code: 

410 try: 

411 if self._closing: 

412 raise Exception("server is shutting down") 

413 

414 self.notify_new_user(ss) 

415 

416 self.parse_hello(ss, c, send_ui) 

417 #send_hello will take care of sending the current and max screen resolutions 

418 root_w, root_h = self.get_root_window_size() 

419 self.send_hello(ss, root_w, root_h, auth_caps) 

420 self.add_new_client(ss, c, send_ui, share_count) 

421 self.send_initial_data(ss, c, send_ui, share_count) 

422 self.client_startup_complete(ss) 

423 

424 if self._closing: 

425 raise Exception("server is shutting down") 

426 except Exception as e: 

427 #log exception but don't disclose internal details to the client 

428 p = ss.protocol 

429 log("_process_hello_ui%s", (ss, c, auth_caps, send_ui, share_count), exc_info=True) 

430 log.error("Error: processing new connection from %s:", p or ss) 

431 log.error(" %s", e) 

432 if p: 

433 self.disconnect_client(p, SERVER_ERROR, "error accepting new connection") 

434 

435 def parse_hello(self, ss, c, send_ui): 

436 for bc in SERVER_BASES: 

437 if bc!=ServerCore: 

438 bc.parse_hello(self, ss, c, send_ui) 

439 

440 def add_new_client(self, ss, c, send_ui, share_count): 

441 for bc in SERVER_BASES: 

442 if bc!=ServerCore: 

443 bc.add_new_client(self, ss, c, send_ui, share_count) 

444 

445 def send_initial_data(self, ss, c, send_ui, share_count): 

446 for bc in SERVER_BASES: 

447 if bc!=ServerCore: 

448 bc.send_initial_data(self, ss, c, send_ui, share_count) 

449 

450 def client_startup_complete(self, ss): 

451 ss.startup_complete() 

452 self.server_event("startup-complete", ss.uuid) 

453 if not self.start_after_connect_done: #pylint: disable=access-member-before-definition 

454 self.start_after_connect_done = True 

455 self.exec_after_connect_commands() 

456 self.exec_on_connect_commands() 

457 

458 def sanity_checks(self, proto, c): 

459 server_uuid = c.strget("server_uuid") 

460 if server_uuid: 

461 if server_uuid==self.uuid: 

462 self.send_disconnect(proto, "cannot connect a client running on the same display" 

463 +" that the server it connects to is managing - this would create a loop!") 

464 return False 

465 log.warn("This client is running within the Xpra server %s", server_uuid) 

466 return True 

467 

468 

469 def update_all_server_settings(self, reset=False): 

470 pass #may be overriden in subclasses (ie: x11 server) 

471 

472 

473 ###################################################################### 

474 # hello: 

475 def get_server_features(self, server_source=None): 

476 #these are flags that have been added over time with new versions 

477 #to expose new server features: 

478 f = { 

479 "toggle_keyboard_sync" : True, #v4.0 clients assume this is always available 

480 } 

481 for c in SERVER_BASES: 

482 if c!=ServerCore: 

483 merge_dicts(f, c.get_server_features(self, server_source)) 

484 return f 

485 

486 def make_hello(self, source): 

487 capabilities = super().make_hello(source) 

488 for c in SERVER_BASES: 

489 if c!=ServerCore: 

490 merge_dicts(capabilities, c.get_caps(self, source)) 

491 capabilities["server_type"] = "base" 

492 if source.wants_display: 

493 capabilities.update({ 

494 "max_desktop_size" : self.get_max_screen_size(), 

495 "display" : os.environ.get("DISPLAY", "Main"), 

496 }) 

497 if source.wants_features: 

498 capabilities.update({ 

499 "client-shutdown" : self.client_shutdown, 

500 "sharing" : self.sharing is not False, 

501 "sharing-toggle" : self.sharing is None, 

502 "lock" : self.lock is not False, 

503 "lock-toggle" : self.lock is None, 

504 "windows" : server_features.windows, 

505 "keyboard" : server_features.input_devices, 

506 "pointer" : server_features.input_devices, 

507 }) 

508 capabilities.update(flatten_dict(self.get_server_features(source))) 

509 capabilities["configure.pointer"] = True #v4 clients assume this is enabled 

510 return capabilities 

511 

512 def send_hello(self, server_source, root_w, root_h, server_cipher): 

513 capabilities = self.make_hello(server_source) 

514 if server_source.wants_encodings and server_features.windows: 

515 try: 

516 from xpra.codecs.loader import codec_versions 

517 except ImportError: 

518 log("no codecs", exc_info=True) 

519 else: 

520 def add_encoding_caps(d): 

521 updict(d, "encoding", codec_versions, "version") 

522 for k,v in self.get_encoding_info().items(): 

523 if k=="": 

524 k = "encodings" 

525 else: 

526 k = "encodings.%s" % k 

527 d[k] = v 

528 if server_source.encodings_packet: 

529 #we can send it later, 

530 #when the init thread has finished: 

531 def send_encoding_caps(): 

532 d = {} 

533 add_encoding_caps(d) 

534 #make sure the 'hello' packet goes out first: 

535 self.idle_add(server_source.send_async, "encodings", d) 

536 self.after_threaded_init(send_encoding_caps) 

537 else: 

538 self.wait_for_threaded_init() 

539 add_encoding_caps(capabilities) 

540 #check for mmap: 

541 if getattr(self, "mmap_size", 0)==0: 

542 self.after_threaded_init(server_source.print_encoding_info) 

543 if server_source.wants_display: 

544 capabilities.update({ 

545 "actual_desktop_size" : (root_w, root_h), 

546 "root_window_size" : (root_w, root_h), 

547 }) 

548 if self._aliases and server_source.wants_aliases: 

549 reverse_aliases = {} 

550 for i, packet_type in self._aliases.items(): 

551 reverse_aliases[packet_type] = i 

552 capabilities["aliases"] = reverse_aliases 

553 if server_cipher: 

554 capabilities.update(server_cipher) 

555 server_source.send_hello(capabilities) 

556 

557 

558 ###################################################################### 

559 # info: 

560 def _process_info_request(self, proto, packet): 

561 log("process_info_request(%s, %s)", proto, packet) 

562 #ignoring the list of client uuids supplied in packet[1] 

563 ss = self.get_server_source(proto) 

564 if not ss: 

565 return 

566 categories = None 

567 #if len(packet>=2): 

568 # uuid = packet[1] 

569 if len(packet)>=4: 

570 categories = tuple(bytestostr(x) for x in packet[3]) 

571 def info_callback(_proto, info): 

572 assert proto==_proto 

573 if categories: 

574 info = dict((k,v) for k,v in info.items() if k in categories) 

575 ss.send_info_response(info) 

576 self.get_all_info(info_callback, proto, None) 

577 

578 def send_hello_info(self, proto): 

579 self.wait_for_threaded_init() 

580 start = monotonic_time() 

581 def cb(proto, info): 

582 self.do_send_info(proto, info) 

583 end = monotonic_time() 

584 log.info("processed info request from %s in %ims", 

585 proto._conn, (end-start)*1000) 

586 self.get_all_info(cb, proto, None) 

587 

588 def get_ui_info(self, proto, client_uuids=None, *args) -> dict: 

589 """ info that must be collected from the UI thread 

590 (ie: things that query the display) 

591 """ 

592 info = {"server" : {"max_desktop_size" : self.get_max_screen_size()}} 

593 for c in SERVER_BASES: 

594 try: 

595 merge_dicts(info, c.get_ui_info(self, proto, client_uuids, *args)) 

596 except Exception: 

597 log.error("Error gathering UI info on %s", c, exc_info=True) 

598 return info 

599 

600 

601 def get_info(self, proto=None, client_uuids=None) -> dict: 

602 log("ServerBase.get_info%s", (proto, client_uuids)) 

603 start = monotonic_time() 

604 info = ServerCore.get_info(self, proto) 

605 if client_uuids: 

606 sources = [ss for ss in self._server_sources.values() if ss.uuid in client_uuids] 

607 else: 

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

609 log("info-request: sources=%s", sources) 

610 dgi = self.do_get_info(proto, sources) 

611 #ugly alert: merge nested dictionaries, 

612 #ie: do_get_info may return a dictionary for "server" and we already have one, 

613 # so we update it with the new values 

614 for k,v in dgi.items(): 

615 cval = info.get(k) 

616 if cval is None: 

617 info[k] = v 

618 continue 

619 cval.update(v) 

620 log("ServerBase.get_info took %.1fms", 1000.0*(monotonic_time()-start)) 

621 return info 

622 

623 def get_packet_handlers_info(self) -> dict: 

624 info = ServerCore.get_packet_handlers_info(self) 

625 info.update({ 

626 "authenticated" : sorted(self._authenticated_packet_handlers.keys()), 

627 "ui" : sorted(self._authenticated_ui_packet_handlers.keys()), 

628 }) 

629 return info 

630 

631 

632 def get_features_info(self) -> dict: 

633 i = { 

634 "sharing" : self.sharing is not False, 

635 "idle_timeout" : self.idle_timeout, 

636 } 

637 i.update(self.get_server_features()) 

638 return i 

639 

640 def do_get_info(self, proto, server_sources=None) -> dict: 

641 start = monotonic_time() 

642 info = {} 

643 def up(prefix, d): 

644 merge_dicts(info, {prefix : d}) 

645 

646 for c in SERVER_BASES: 

647 try: 

648 merge_dicts(info, c.get_info(self, proto)) 

649 except Exception as e: 

650 log("do_get_info%s", (proto, server_sources), exc_info=True) 

651 log.error("Error collecting information from %s", c) 

652 log.error(" %s", e) 

653 

654 up("features", self.get_features_info()) 

655 up("network", { 

656 "sharing" : self.sharing is not False, 

657 "sharing-toggle" : self.sharing is None, 

658 "lock" : self.lock is not False, 

659 "lock-toggle" : self.lock is None, 

660 }) 

661 

662 # other clients: 

663 info["clients"] = { 

664 "" : sum(1 for p in self._server_sources if p!=proto), 

665 "unauthenticated" : sum(1 for p in self._potential_protocols if ((p is not proto) and (p not in self._server_sources))), 

666 } 

667 #find the server source to report on: 

668 n = len(server_sources or []) 

669 if n==1: 

670 ss = server_sources[0] 

671 up("client", ss.get_info()) 

672 elif n>1: 

673 cinfo = {} 

674 for i, ss in enumerate(server_sources): 

675 sinfo = ss.get_info() 

676 sinfo["ui-driver"] = self.ui_driver==ss.uuid 

677 cinfo[i] = sinfo 

678 up("client", cinfo) 

679 log("ServerBase.do_get_info took %ims", (monotonic_time()-start)*1000) 

680 return info 

681 

682 

683 def _process_server_settings(self, proto, packet): 

684 #only used by x11 servers 

685 pass 

686 

687 

688 def _set_client_properties(self, proto, wid, window, new_client_properties): 

689 """ 

690 Allows us to keep window properties for a client after disconnection. 

691 (we keep it in a map with the client's uuid as key) 

692 """ 

693 ss = self.get_server_source(proto) 

694 if ss: 

695 ss.set_client_properties(wid, window, typedict(new_client_properties)) 

696 #filter out encoding properties, which are expected to be set everytime: 

697 ncp = {} 

698 for k,v in new_client_properties.items(): 

699 if v is None: 

700 log.warn("removing invalid None property for %s", k) 

701 continue 

702 k = strtobytes(k) 

703 if not k.startswith(b"encoding"): 

704 ncp[k] = v 

705 if ncp: 

706 log("set_client_properties updating window %s of source %s with %s", wid, ss.uuid, ncp) 

707 client_properties = self.client_properties.setdefault(wid, {}).setdefault(ss.uuid, {}) 

708 client_properties.update(ncp) 

709 

710 

711 ###################################################################### 

712 # settings toggle: 

713 def setting_changed(self, setting, value): 

714 #tell all the clients (that can) about the new value for this setting 

715 for ss in tuple(self._server_sources.values()): 

716 ss.send_setting_change(setting, value) 

717 

718 def _process_set_deflate(self, proto, packet): 

719 level = packet[1] 

720 log("client has requested compression level=%s", level) 

721 proto.set_compression_level(level) 

722 #echo it back to the client: 

723 ss = self.get_server_source(proto) 

724 if ss: 

725 ss.set_deflate(level) 

726 

727 def _process_sharing_toggle(self, proto, packet): 

728 assert self.sharing is None 

729 ss = self.get_server_source(proto) 

730 if not ss: 

731 return 

732 sharing = bool(packet[1]) 

733 ss.share = sharing 

734 if not sharing: 

735 #disconnect other users: 

736 for p,ss in tuple(self._server_sources.items()): 

737 if p!=proto: 

738 self.disconnect_client(p, DETACH_REQUEST, 

739 "client %i no longer wishes to share the session" % ss.counter) 

740 

741 def _process_lock_toggle(self, proto, packet): 

742 assert self.lock is None 

743 ss = self.get_server_source(proto) 

744 if ss: 

745 ss.lock = bool(packet[1]) 

746 log("lock set to %s for client %i", ss.lock, ss.counter) 

747 

748 

749 

750 

751 ###################################################################### 

752 # http server and http audio stream: 

753 def get_http_info(self) -> dict: 

754 info = ServerCore.get_http_info(self) 

755 info["clients"] = len(self._server_sources) 

756 return info 

757 

758 def get_http_scripts(self) -> dict: 

759 scripts = {} 

760 for c in SERVER_BASES: 

761 scripts.update(c.get_http_scripts(self)) 

762 httplog("scripts=%s", scripts) 

763 return scripts 

764 

765 def cancel_http_stream_check_timers(self): 

766 for ts in tuple(self.http_stream_check_timers.keys()): 

767 v = self.http_stream_check_timers.pop(ts, None) 

768 if v: 

769 timer, stop = v 

770 self.source_remove(timer) 

771 try: 

772 stop() 

773 except Exception: 

774 httplog("error on %s", stop, exc_info=True) 

775 

776 

777 ###################################################################### 

778 # client connections: 

779 def init_sockets(self, sockets): 

780 for c in SERVER_BASES: 

781 c.init_sockets(self, sockets) 

782 

783 def cleanup_protocol(self, protocol): 

784 netlog("cleanup_protocol(%s)", protocol) 

785 #this ensures that from now on we ignore any incoming packets coming 

786 #from this connection as these could potentially set some keys pressed, etc 

787 try: 

788 self._potential_protocols.remove(protocol) 

789 except ValueError: 

790 pass 

791 source = self._server_sources.pop(protocol, None) 

792 if source: 

793 self.cleanup_source(source) 

794 add_work_item(self.mdns_update, False) 

795 for c in SERVER_BASES: 

796 c.cleanup_protocol(self, protocol) 

797 return source 

798 

799 def cleanup_source(self, source): 

800 self.server_event("connection-lost", source.uuid) 

801 remaining_sources = tuple(self._server_sources.values()) 

802 if self.ui_driver==source.uuid: 

803 if len(remaining_sources)==1: 

804 self.set_ui_driver(remaining_sources[0]) 

805 else: 

806 self.set_ui_driver(None) 

807 source.close() 

808 netlog("cleanup_source(%s) remaining sources: %s", source, remaining_sources) 

809 netlog.info("xpra client %i disconnected.", source.counter) 

810 has_client = len(remaining_sources)>0 

811 if not has_client: 

812 self.idle_add(self.last_client_exited) 

813 

814 def last_client_exited(self): 

815 #must run from the UI thread (modifies focus and keys) 

816 netlog("last_client_exited() exit_with_client=%s", self.exit_with_client) 

817 self.reset_server_timeout(True) 

818 for c in SERVER_BASES: 

819 if c!=ServerCore: 

820 try: 

821 c.last_client_exited(self) 

822 except Exception: 

823 log("last_client_exited calling %s", c.last_client_exited, exc_info=True) 

824 if self.exit_with_client: 

825 if not self._closing: 

826 netlog.info("Last client has disconnected, terminating") 

827 self.clean_quit(False) 

828 

829 

830 def set_ui_driver(self, source): 

831 if source and self.ui_driver==source.uuid: 

832 return 

833 log("new ui driver: %s", source) 

834 if not source: 

835 self.ui_driver = None 

836 else: 

837 self.ui_driver = source.uuid 

838 for c in SERVER_BASES: 

839 if c!=ServerCore: 

840 c.set_session_driver(self, source) 

841 

842 

843 def reset_focus(self): 

844 for c in SERVER_BASES: 

845 if c!=ServerCore: 

846 c.reset_focus(self) 

847 

848 

849 def get_all_protocols(self) -> list: 

850 return list(self._potential_protocols) + list(self._server_sources.keys()) 

851 

852 

853 def is_timedout(self, protocol): 

854 v = ServerCore.is_timedout(self, protocol) and protocol not in self._server_sources 

855 netlog("is_timedout(%s)=%s", protocol, v) 

856 return v 

857 

858 

859 def _log_disconnect(self, proto, *args): 

860 #skip logging of disconnection events for server sources 

861 #we have tagged during hello ("info_request", "exit_request", etc..) 

862 ss = self.get_server_source(proto) 

863 if ss and not ss.log_disconnect: 

864 #log at debug level only: 

865 netlog(*args) 

866 return 

867 ServerCore._log_disconnect(self, proto, *args) 

868 

869 def _disconnect_proto_info(self, proto): 

870 #only log protocol info if there is more than one client: 

871 if len(self._server_sources)>1: 

872 return " %s" % proto 

873 return "" 

874 

875 ###################################################################### 

876 # packets: 

877 def add_packet_handlers(self, defs, main_thread=True): 

878 for packet_type, handler in defs.items(): 

879 self.add_packet_handler(packet_type, handler, main_thread) 

880 

881 def add_packet_handler(self, packet_type, handler, main_thread=True): 

882 netlog("add_packet_handler%s", (packet_type, handler, main_thread)) 

883 if main_thread: 

884 handlers = self._authenticated_ui_packet_handlers 

885 else: 

886 handlers = self._authenticated_packet_handlers 

887 handlers[packet_type] = handler 

888 

889 def init_packet_handlers(self): 

890 for c in SERVER_BASES: 

891 c.init_packet_handlers(self) 

892 #no need for main thread: 

893 self.add_packet_handlers({ 

894 "sharing-toggle" : self._process_sharing_toggle, 

895 "lock-toggle" : self._process_lock_toggle, 

896 }, False) 

897 #attributes / settings: 

898 self.add_packet_handlers({ 

899 "server-settings" : self._process_server_settings, 

900 "set_deflate" : self._process_set_deflate, 

901 "shutdown-server" : self._process_shutdown_server, 

902 "exit-server" : self._process_exit_server, 

903 "info-request" : self._process_info_request, 

904 }) 

905 

906 def init_aliases(self): 

907 packet_types = list(self._default_packet_handlers.keys()) 

908 packet_types += list(self._authenticated_packet_handlers.keys()) 

909 packet_types += list(self._authenticated_ui_packet_handlers.keys()) 

910 self.do_init_aliases(packet_types) 

911 

912 def process_packet(self, proto, packet): 

913 try: 

914 handler = None 

915 packet_type = bytestostr(packet[0]) 

916 def call_handler(): 

917 may_log_packet(False, packet_type, packet) 

918 handler(proto, packet) 

919 if proto in self._server_sources: 

920 handler = self._authenticated_ui_packet_handlers.get(packet_type) 

921 if handler: 

922 netlog("process ui packet %s", packet_type) 

923 self.idle_add(call_handler) 

924 return 

925 handler = self._authenticated_packet_handlers.get(packet_type) 

926 if handler: 

927 netlog("process non-ui packet %s", packet_type) 

928 call_handler() 

929 return 

930 handler = self._default_packet_handlers.get(packet_type) 

931 if handler: 

932 netlog("process default packet %s", packet_type) 

933 call_handler() 

934 return 

935 def invalid_packet(): 

936 ss = self.get_server_source(proto) 

937 if not self._closing and not proto.is_closed() and (ss is None or not ss.is_closed()): 

938 netlog("invalid packet: %s", packet) 

939 netlog.error("Error: unknown or invalid packet type '%s'", packet_type) 

940 netlog.error(" received from %s", proto) 

941 if not ss: 

942 proto.close() 

943 self.idle_add(invalid_packet) 

944 except Exception: 

945 netlog.error("Error processing a '%s' packet", packet_type) 

946 netlog.error(" received from %s:", proto) 

947 netlog.error(" using %s", handler, exc_info=True)