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-2020 Antoine Martin <antoine@xpra.org> 

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

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

6#pylint: disable-msg=E1101 

7 

8import os.path 

9 

10from xpra.util import parse_scaling_value, csv, from0to100 

11from xpra.os_util import load_binary_file, bytestostr, strtobytes 

12from xpra.simple_stats import std_unit 

13from xpra.scripts.config import parse_bool, FALSE_OPTIONS, TRUE_OPTIONS 

14from xpra.server.control_command import ArgsControlCommand, ControlError 

15from xpra.server.mixins.stub_server_mixin import StubServerMixin 

16from xpra.log import Logger 

17 

18log = Logger("command") 

19 

20TOGGLE_FEATURES = ( 

21 "bell", "randr", "cursors", "notifications", "dbus-proxy", "clipboard", 

22 "start-new-commands", "client-shutdown", "webcam", 

23 ) 

24 

25 

26""" 

27Control commands for ServerBase 

28""" 

29class ServerBaseControlCommands(StubServerMixin): 

30 

31 def setup(self): 

32 self.add_control_commands() 

33 

34 

35 def add_control_commands(self): 

36 def parse_boolean_value(v): 

37 if str(v).lower() in TRUE_OPTIONS: 

38 return True 

39 if str(v).lower() in FALSE_OPTIONS: 

40 return False 

41 raise ControlError("a boolean is required, not %s" % v) 

42 def parse_4intlist(v): 

43 if not v: 

44 return [] 

45 l = [] 

46 #ie: v = " (0,10,100,20), (200,300,20,20)" 

47 while v: 

48 v = v.strip().strip(",").strip() #ie: "(0,10,100,20)" 

49 lp = v.find("(") 

50 assert lp==0, "invalid leading characters: %s" % v[:lp] 

51 rp = v.find(")") 

52 assert (lp+1)<rp 

53 item = v[lp+1:rp].strip() #"0,10,100,20" 

54 items = [int(x) for x in item] # 0,10,100,20 

55 assert len(items)==4, "expected 4 numbers but got %i" % len(items) 

56 l.append(items) 

57 return l 

58 

59 for cmd in ( 

60 ArgsControlCommand("focus", "give focus to the window id", validation=[int]), 

61 ArgsControlCommand("map", "maps the window id", validation=[int]), 

62 ArgsControlCommand("unmap", "unmaps the window id", validation=[int]), 

63 #window source: 

64 ArgsControlCommand("suspend", "suspend screen updates", max_args=0), 

65 ArgsControlCommand("resume", "resume screen updates", max_args=0), 

66 ArgsControlCommand("ungrab", "cancels any grabs", max_args=0), 

67 #server globals: 

68 ArgsControlCommand("idle-timeout", "set the idle tiemout", validation=[int]), 

69 ArgsControlCommand("server-idle-timeout", "set the server idle timeout", validation=[int]), 

70 ArgsControlCommand("start", "executes the command arguments in the server context", min_args=1), 

71 ArgsControlCommand("start-child", "executes the command arguments in the server context, as a 'child' (honouring exit-with-children)", min_args=1), 

72 ArgsControlCommand("toggle-feature", "toggle a server feature on or off, one of: %s" % csv(TOGGLE_FEATURES), min_args=1, max_args=2, validation=[str, parse_boolean_value]), 

73 #network and transfers: 

74 ArgsControlCommand("print", "sends the file to the client(s) for printing", min_args=1), 

75 ArgsControlCommand("open-url", "open the URL on the client(s)", min_args=1, max_args=2), 

76 ArgsControlCommand("send-file", "sends the file to the client(s)", min_args=1, max_args=4), 

77 ArgsControlCommand("send-notification", "sends a notification to the client(s)", min_args=4, max_args=5, validation=[int]), 

78 ArgsControlCommand("close-notification", "send the request to close an existing notification to the client(s)", min_args=1, max_args=2, validation=[int]), 

79 ArgsControlCommand("compression", "sets the packet compressor", min_args=1, max_args=1), 

80 ArgsControlCommand("encoder", "sets the packet encoder", min_args=1, max_args=1), 

81 ArgsControlCommand("clipboard-direction", "restrict clipboard transfers", min_args=1, max_args=1), 

82 ArgsControlCommand("clipboard-limits", "restrict clipboard transfers size", min_args=2, max_args=2, validation=[int, int]), 

83 ArgsControlCommand("set-lock", "modify the lock attribute", min_args=1, max_args=1), 

84 ArgsControlCommand("set-sharing", "modify the sharing attribute", min_args=1, max_args=1), 

85 ArgsControlCommand("set-ui-driver", "set the client connection driving the session", min_args=1, max_args=1), 

86 #session and clients: 

87 ArgsControlCommand("client", "forwards a control command to the client(s)", min_args=1), 

88 ArgsControlCommand("client-property", "set a client property", min_args=4, max_args=5, validation=[int]), 

89 ArgsControlCommand("name", "set the session name", min_args=1, max_args=1), 

90 ArgsControlCommand("key", "press or unpress a key", min_args=1, max_args=2), 

91 ArgsControlCommand("sound-output", "control sound forwarding", min_args=1, max_args=2), 

92 #windows: 

93 ArgsControlCommand("workspace", "move a window to a different workspace", min_args=2, max_args=2, validation=[int, int]), 

94 ArgsControlCommand("move", "move a window", min_args=3, max_args=3, validation=[int, int, int]), 

95 ArgsControlCommand("resize", "resize a window", min_args=3, max_args=3, validation=[int, int, int]), 

96 ArgsControlCommand("moveresize", "move and resize a window", min_args=5, max_args=5, validation=[int, int, int, int, int]), 

97 ArgsControlCommand("scaling-control", "set the scaling-control aggressiveness (from 0 to 100)", min_args=1, validation=[from0to100]), 

98 ArgsControlCommand("scaling", "set a specific scaling value", min_args=1, validation=[parse_scaling_value]), 

99 ArgsControlCommand("auto-refresh", "set a specific auto-refresh value", min_args=1, validation=[float]), 

100 ArgsControlCommand("refresh", "refresh some or all windows", min_args=0), 

101 ArgsControlCommand("encoding", "picture encoding", min_args=1, max_args=1), 

102 ArgsControlCommand("video-region-enabled", "enable video region", min_args=2, max_args=2, validation=[int, parse_boolean_value]), 

103 ArgsControlCommand("video-region-detection","enable video detection", min_args=2, max_args=2, validation=[int, parse_boolean_value]), 

104 ArgsControlCommand("video-region-exclusion-zones","set window regions to exclude from video regions: 'WID,(x,y,w,h),(x,y,w,h),..', ie: '1 (0,10,100,20),(200,300,20,20)'", min_args=2, max_args=2, validation=[int, parse_4intlist]), 

105 ArgsControlCommand("video-region", "set the video region", min_args=5, max_args=5, validation=[int, int, int, int, int]), 

106 ArgsControlCommand("reset-video-region", "reset video region heuristics", min_args=1, max_args=1, validation=[int]), 

107 ArgsControlCommand("lock-batch-delay", "set a specific batch delay for a window", min_args=2, max_args=2, validation=[int, int]), 

108 ArgsControlCommand("unlock-batch-delay", "let the heuristics calculate the batch delay again for a window (following a 'lock-batch-delay')", min_args=1, max_args=1, validation=[int]), 

109 ArgsControlCommand("remove-window-filters", "remove all window filters", min_args=0, max_args=0), 

110 ArgsControlCommand("add-window-filter", "add a window filter", min_args=4, max_args=5), 

111 ): 

112 cmd.do_run = getattr(self, "control_command_%s" % cmd.name.replace("-", "_")) 

113 self.control_commands[cmd.name] = cmd 

114 #encoding bits: 

115 for name in ("quality", "min-quality", "speed", "min-speed"): 

116 fn = getattr(self, "control_command_%s" % name.replace("-", "_")) 

117 self.control_commands[name] = ArgsControlCommand(name, "set encoding %s (from 0 to 100)" % name, run=fn, min_args=1, validation=[from0to100]) 

118 

119 

120 ######################################### 

121 # Control Commands 

122 ######################################### 

123 def control_command_focus(self, wid): 

124 if self.readonly: 

125 return 

126 assert type(wid)==int, "argument should have been an int, but found %s" % type(wid) 

127 self._focus(None, wid, None) 

128 return "gave focus to window %s" % wid 

129 

130 def control_command_map(self, wid): 

131 if self.readonly: 

132 return 

133 assert type(wid)==int, "argument should have been an int, but found %s" % type(wid) 

134 window = self._id_to_window.get(wid) 

135 assert window, "window %i not found" % wid 

136 if window.is_tray(): 

137 return "cannot map tray window %s" % wid 

138 if window.is_OR(): 

139 return "cannot map override redirect window %s" % wid 

140 dm = getattr(self, "_desktop_manager", None) 

141 assert dm, "%r does not have a desktop manager" % self 

142 dm.show_window(window) 

143 #window.set_owner(dm) 

144 #iconic = window.get_property("iconic") 

145 #if iconic: 

146 # window.set_property("iconic", False) 

147 #w, h = window.get_geometry()[2:4] 

148 #self.refresh_window_area(window, 0, 0, w, h) 

149 self.repaint_root_overlay() 

150 return "mapped window %s" % wid 

151 

152 def control_command_unmap(self, wid): 

153 if self.readonly: 

154 return 

155 assert type(wid)==int, "argument should have been an int, but found %s" % type(wid) 

156 window = self._id_to_window.get(wid) 

157 assert window, "window %i not found" % wid 

158 if window.is_tray(): 

159 return "cannot map tray window %s" % wid 

160 if window.is_OR(): 

161 return "cannot map override redirect window %s" % wid 

162 dm = getattr(self, "_desktop_manager", None) 

163 assert dm, "%r does not have a desktop manager" % self 

164 dm.hide_window(window) 

165 self.repaint_root_overlay() 

166 return "unmapped window %s" % wid 

167 

168 def control_command_suspend(self): 

169 for csource in tuple(self._server_sources.values()): 

170 csource.suspend(True, self._id_to_window) 

171 return "suspended %s clients" % len(self._server_sources) 

172 

173 def control_command_resume(self): 

174 for csource in tuple(self._server_sources.values()): 

175 csource.resume(True, self._id_to_window) 

176 return "resumed %s clients" % len(self._server_sources) 

177 

178 def control_command_ungrab(self): 

179 for csource in tuple(self._server_sources.values()): 

180 csource.pointer_ungrab(-1) 

181 return "ungrabbed %s clients" % len(self._server_sources) 

182 

183 def control_command_idle_timeout(self, t): 

184 self.idle_timeout = t 

185 for csource in tuple(self._server_sources.values()): 

186 csource.idle_timeout = t 

187 csource.schedule_idle_timeout() 

188 return "idle-timeout set to %s" % t 

189 

190 def control_command_server_idle_timeout(self, t): 

191 self.server_idle_timeout = t 

192 reschedule = len(self._server_sources)==0 

193 self.reset_server_timeout(reschedule) 

194 return "server-idle-timeout set to %s" % t 

195 

196 def control_command_start(self, *args): 

197 return self.do_control_command_start(True, *args) 

198 def control_command_start_child(self, *args): 

199 return self.do_control_command_start(False, *args) 

200 def do_control_command_start(self, ignore, *args): 

201 if not self.start_new_commands: 

202 raise ControlError("this feature is currently disabled") 

203 proc = self.start_command(" ".join(args), args, ignore, shell=True) 

204 if not proc: 

205 raise ControlError("failed to start new child command %s" % str(args)) 

206 return "new %scommand started with pid=%s" % (["child ", ""][ignore], proc.pid) 

207 

208 def control_command_toggle_feature(self, feature, state=None): 

209 log("control_command_toggle_feature(%s, %s)", feature, state) 

210 if feature not in TOGGLE_FEATURES: 

211 msg = "invalid feature '%s'" % feature 

212 log.warn(msg) 

213 return msg 

214 fn = feature.replace("-", "_") 

215 if not hasattr(self, feature): 

216 msg = "attribute '%s' not found - bug?" % feature 

217 log.warn(msg) 

218 return msg 

219 cur = getattr(self, fn, None) 

220 if state is None: 

221 #if the new state is not specified, just negate the value 

222 state = not cur 

223 setattr(self, fn, state) 

224 self.setting_changed(feature, state) 

225 return "%s set to %s" % (feature, state) 

226 

227 def _control_get_sources(self, client_uuids_str, _attr=None): 

228 #find the client uuid specified as a string: 

229 if client_uuids_str=="UI": 

230 sources = [ss for ss in self._server_sources.values() if ss.ui_client] 

231 client_uuids = [ss.uuid for ss in sources] 

232 notfound = () 

233 elif client_uuids_str=="*": 

234 sources = self._server_sources.values() 

235 client_uuids = [ss.uuid for ss in sources] 

236 else: 

237 client_uuids = client_uuids_str.split(",") 

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

239 uuids = tuple(ss.uuid for ss in sources) 

240 notfound = any(x for x in client_uuids if x not in uuids) 

241 if notfound: 

242 log.warn("client connection not found for uuid(s): %s", notfound) 

243 return sources 

244 

245 def control_command_send_notification(self, nid, title, message, client_uuids): 

246 if not self.notifications: 

247 msg = "notifications are disabled" 

248 log(msg) 

249 return msg 

250 sources = self._control_get_sources(client_uuids) 

251 log("control_command_send_notification(%i, %s, %s, %s) will send to sources %s (matching %s)", 

252 nid, title, message, client_uuids, sources, client_uuids) 

253 count = 0 

254 for source in sources: 

255 if source.notify(0, nid, "control channel", 0, "", title, message, [], {}, 10, ""): 

256 count += 1 

257 msg = "notification id %i: message sent to %i clients" % (nid, count) 

258 log(msg) 

259 return msg 

260 

261 def control_command_close_notification(self, nid, client_uuids): 

262 if not self.notifications: 

263 msg = "notifications are disabled" 

264 log(msg) 

265 return msg 

266 sources = self._control_get_sources(client_uuids) 

267 log("control_command_close_notification(%s, %s) will send to %s", nid, client_uuids, sources) 

268 for source in sources: 

269 source.notify_close(nid) 

270 msg = "notification id %i: close request sent to %i clients" % (nid, len(sources)) 

271 log(msg) 

272 return msg 

273 

274 

275 def control_command_open_url(self, url, client_uuids="*"): 

276 #find the clients: 

277 sources = self._control_get_sources(client_uuids) 

278 if not sources: 

279 raise ControlError("no clients found matching: %s" % client_uuids) 

280 clients = 0 

281 for ss in sources: 

282 if ss.send_open_url(url): 

283 clients += 1 

284 return "url sent to %i clients" % clients 

285 

286 def control_command_send_file(self, filename, openit="open", client_uuids="*", maxbitrate=0): 

287 #we always get the values as strings from the command interface, 

288 #but those may actually be utf8 encoded binary strings, 

289 #so we may have to do an ugly roundtrip: 

290 try: 

291 filename = strtobytes(filename).decode("utf8") 

292 except: 

293 filename = bytestostr(filename) 

294 openit = str(openit).lower() in ("open", "true", "1") 

295 return self.do_control_file_command("send file", client_uuids, filename, "file_transfer", (False, openit)) 

296 

297 def control_command_print(self, filename, printer="", client_uuids="*", maxbitrate=0, title="", *options_strs): 

298 #FIXME: printer and bitrate are ignored 

299 #parse options into a dict: 

300 options = {} 

301 for arg in options_strs: 

302 argp = arg.split("=", 1) 

303 if len(argp)==2 and len(argp[0])>0: 

304 options[argp[0]] = argp[1] 

305 return self.do_control_file_command("print", client_uuids, filename, "printing", (True, True, options)) 

306 

307 def do_control_file_command(self, command_type, client_uuids, filename, source_flag_name, send_file_args): 

308 #find the clients: 

309 sources = self._control_get_sources(client_uuids) 

310 if not sources: 

311 raise ControlError("no clients found matching: %s" % client_uuids) 

312 def checksize(file_size): 

313 if file_size>self.file_transfer.file_size_limit: 

314 raise ControlError("file '%s' is too large: %sB (limit is %sB)" % ( 

315 filename, std_unit(file_size), std_unit(self.file_transfer.file_size_limit))) 

316 

317 #find the file and load it: 

318 actual_filename = os.path.abspath(os.path.expanduser(filename)) 

319 try: 

320 stat = os.stat(actual_filename) 

321 log("os.stat(%s)=%s", actual_filename, stat) 

322 except os.error: 

323 log("os.stat(%s)", actual_filename, exc_info=True) 

324 else: 

325 checksize(stat.st_size) 

326 if not os.path.exists(actual_filename): 

327 raise ControlError("file '%s' does not exist" % filename) 

328 data = load_binary_file(actual_filename) 

329 if data is None: 

330 raise ControlError("failed to load '%s'" % actual_filename) 

331 #verify size: 

332 file_size = len(data) 

333 checksize(file_size) 

334 #send it to each client: 

335 for ss in sources: 

336 #ie: ServerSource.file_transfer (found in FileTransferAttributes) 

337 if not getattr(ss, source_flag_name): 

338 log.warn("Warning: cannot %s '%s'", command_type, filename) 

339 log.warn(" client %s does not support this feature", ss) 

340 elif file_size>ss.file_size_limit: 

341 log.warn("Warning: cannot %s '%s'", command_type, filename) 

342 log.warn(" client %s file size limit is %sB (file is %sB)", 

343 ss, std_unit(ss.file_size_limit), std_unit(file_size)) 

344 else: 

345 ss.send_file(filename, "", data, file_size, *send_file_args) 

346 return "%s of '%s' to %s initiated" % (command_type, filename, client_uuids) 

347 

348 

349 def control_command_remove_window_filters(self): 

350 #modify the existing list object, 

351 #which is referenced by all the sources 

352 l = len(self.window_filters) 

353 self.window_filters[:] = [] 

354 return "removed %i window-filters" % l 

355 

356 def control_command_add_window_filter(self, object_name, property_name, operator, value, client_uuids=""): 

357 from xpra.server.window import filters 

358 window_filter = filters.get_window_filter(object_name, property_name, operator, value) 

359 #log("%s%s=%s", filters.get_window_filter, (object_name, property_name, operator, value), window_filter) 

360 if client_uuids=="*": 

361 #applies to all sources: 

362 self.window_filters.append(("*", window_filter)) 

363 else: 

364 for client_uuid in client_uuids.split(","): 

365 self.window_filters.append((client_uuid, window_filter)) 

366 return "added window-filter: %s for client uuids=%s" % (window_filter, client_uuids) 

367 

368 

369 def control_command_compression(self, compression): 

370 c = compression.lower() 

371 from xpra.net import compression 

372 opts = compression.get_enabled_compressors() #ie: [lz4, lzo, zlib] 

373 if c not in opts: 

374 raise ControlError("compressor argument must be one of: %s" % csv(opts)) 

375 for cproto in tuple(self._server_sources.keys()): 

376 cproto.enable_compressor(c) 

377 self.all_send_client_command("enable_%s" % c) 

378 return "compressors set to %s" % compression 

379 

380 def control_command_encoder(self, encoder): 

381 e = encoder.lower() 

382 from xpra.net import packet_encoding 

383 opts = packet_encoding.get_enabled_encoders() #ie: [rencode, bencode, yaml] 

384 if e not in opts: 

385 raise ControlError("encoder argument must be one of: %s" % csv(opts)) 

386 for cproto in tuple(self._server_sources.keys()): 

387 cproto.enable_encoder(e) 

388 self.all_send_client_command("enable_%s" % e) 

389 return "encoders set to %s" % encoder 

390 

391 

392 def all_send_client_command(self, *client_command): 

393 """ forwards the command to all clients """ 

394 for source in tuple(self._server_sources.values()): 

395 # forwards to *the* client, if there is *one* 

396 if client_command[0] not in source.control_commands: 

397 log.info("client command '%s' not forwarded to client %s (not supported)", client_command, source) 

398 else: 

399 source.send_client_command(*client_command) 

400 

401 def control_command_client(self, *args): 

402 self.all_send_client_command(*args) 

403 return "client control command %s forwarded to clients" % str(args) 

404 

405 def control_command_client_property(self, wid, uuid, prop, value, conv=None): 

406 wid = int(wid) 

407 conv_fn = { 

408 "int" : int, 

409 "float" : float, 

410 "" : str, 

411 }.get(conv) 

412 assert conv_fn 

413 typeinfo = "%s " % (conv or "string") 

414 value = conv_fn(value) 

415 self.client_properties.setdefault(wid, {}).setdefault(uuid, {})[prop] = value 

416 return "property '%s' set to %s value '%s' for window %i, client %s" % (prop, typeinfo, value, wid, uuid) 

417 

418 def control_command_name(self, name): 

419 self.session_name = name 

420 log.info("changed session name: %s", self.session_name) 

421 #self.all_send_client_command("name", name) not supported by any clients, don't bother! 

422 self.setting_changed("session_name", name) 

423 self.mdns_update() 

424 return "session name set to %s" % name 

425 

426 def _control_windowsources_from_args(self, *args): 

427 #converts the args to valid window ids, 

428 #then returns all the window sources for those wids 

429 if len(args)==0 or len(args)==1 and args[0]=="*": 

430 #default to all if unspecified: 

431 wids = tuple(self._id_to_window.keys()) 

432 else: 

433 wids = [] 

434 for x in args: 

435 try: 

436 wid = int(x) 

437 except: 

438 raise ControlError("invalid window id: %s" % x) 

439 if wid in self._id_to_window: 

440 wids.append(wid) 

441 else: 

442 log("window id %s does not exist", wid) 

443 wss = {} 

444 for csource in tuple(self._server_sources.values()): 

445 for wid in wids: 

446 ws = csource.window_sources.get(wid) 

447 window = self._id_to_window.get(wid) 

448 if window and ws: 

449 wss[ws] = window 

450 return wss 

451 

452 def _set_encoding_property(self, name, value, *wids): 

453 for ws in self._control_windowsources_from_args(*wids).keys(): 

454 fn = getattr(ws, "set_%s" % name.replace("-", "_")) #ie: "set_quality" 

455 fn(value) 

456 #now also update the defaults: 

457 for csource in tuple(self._server_sources.values()): 

458 csource.default_encoding_options[name] = value 

459 return "%s set to %i" % (name, value) 

460 

461 def control_command_quality(self, quality, *wids): 

462 return self._set_encoding_property("quality", quality, *wids) 

463 def control_command_min_quality(self, min_quality, *wids): 

464 return self._set_encoding_property("min-quality", min_quality, *wids) 

465 def control_command_speed(self, speed, *wids): 

466 return self._set_encoding_property("speed", speed, *wids) 

467 def control_command_min_speed(self, min_speed, *wids): 

468 return self._set_encoding_property("min-speed", min_speed, *wids) 

469 

470 def control_command_auto_refresh(self, auto_refresh, *wids): 

471 delay = int(float(auto_refresh)*1000.0) # ie: 0.5 -> 500 (milliseconds) 

472 for ws in self._control_windowsources_from_args(*wids).keys(): 

473 ws.set_auto_refresh_delay(auto_refresh) 

474 return "auto-refresh delay set to %sms for windows %s" % (delay, wids) 

475 

476 def control_command_refresh(self, *wids): 

477 for ws in self._control_windowsources_from_args(*wids).keys(): 

478 ws.full_quality_refresh({}) 

479 return "refreshed windows %s" % str(wids) 

480 

481 def control_command_scaling_control(self, scaling_control, *wids): 

482 for ws in tuple(self._control_windowsources_from_args(*wids).keys()): 

483 ws.set_scaling_control(scaling_control) 

484 ws.refresh() 

485 return "scaling-control set to %s on windows %s" % (scaling_control, wids) 

486 

487 def control_command_scaling(self, scaling, *wids): 

488 for ws in tuple(self._control_windowsources_from_args(*wids).keys()): 

489 ws.set_scaling(scaling) 

490 ws.refresh() 

491 return "scaling set to %s on windows %s" % (str(scaling), wids) 

492 

493 def control_command_encoding(self, encoding, *args): 

494 strict = None #means no change 

495 if len(args)>0 and args[0] in ("strict", "nostrict"): 

496 #remove "strict" marker 

497 strict = args[0]=="strict" 

498 args = args[1:] 

499 wids = args 

500 for ws in tuple(self._control_windowsources_from_args(*wids).keys()): 

501 ws.set_new_encoding(encoding, strict) 

502 ws.refresh() 

503 return "set encoding to %s%s for windows %s" % (encoding, ["", " (strict)"][int(strict or 0)], wids) 

504 

505 def control_command_clipboard_direction(self, direction, *_args): 

506 ch = self._clipboard_helper 

507 assert self.clipboard and ch 

508 direction = direction.lower() 

509 DIRECTIONS = ("to-server", "to-client", "both", "disabled") 

510 assert direction in DIRECTIONS, "invalid direction '%s', must be one of %s" % (direction, csv(DIRECTIONS)) 

511 self.clipboard_direction = direction 

512 can_send = direction in ("to-server", "both") 

513 can_receive = direction in ("to-client", "both") 

514 ch.set_direction(can_send, can_receive) 

515 msg = "clipboard direction set to '%s'" % direction 

516 log(msg) 

517 self.setting_changed("clipboard-direction", direction) 

518 return msg 

519 

520 def control_command_clipboard_limits(self, max_send, max_recv, *_args): 

521 ch = self._clipboard_helper 

522 assert self.clipboard and ch 

523 ch.set_limits(max_send, max_recv) 

524 msg = "clipboard send limit set to %d, recv limit set to %d (single copy/paste)" % (max_send, max_recv) 

525 log(msg) 

526 self.setting_changed("clipboard-limits", {'send': max_send, 'recv': max_recv}) 

527 return msg 

528 

529 def _control_video_subregions_from_wid(self, wid): 

530 if wid not in self._id_to_window: 

531 raise ControlError("invalid window %i" % wid) 

532 video_subregions = [] 

533 for ws in self._control_windowsources_from_args(wid).keys(): 

534 vs = getattr(ws, "video_subregion", None) 

535 if not vs: 

536 log.warn("Warning: cannot set video region enabled flag on window %i:", wid) 

537 log.warn(" no video subregion attribute found in %s", type(ws)) 

538 continue 

539 video_subregions.append(vs) 

540 #log("_control_video_subregions_from_wid(%s)=%s", wid, video_subregions) 

541 return video_subregions 

542 

543 def control_command_video_region_enabled(self, wid, enabled): 

544 for vs in self._control_video_subregions_from_wid(wid): 

545 vs.set_enabled(enabled) 

546 return "video region %s for window %i" % (["disabled", "enabled"][int(enabled)], wid) 

547 

548 def control_command_video_region_detection(self, wid, detection): 

549 for vs in self._control_video_subregions_from_wid(wid): 

550 vs.set_detection(detection) 

551 return "video region detection %s for window %i" % (["disabled", "enabled"][int(detection)], wid) 

552 

553 def control_command_video_region(self, wid, x, y, w, h): 

554 for vs in self._control_video_subregions_from_wid(wid): 

555 vs.set_region(x, y, w, h) 

556 return "video region set to %s for window %i" % ((x, y, w, h), wid) 

557 

558 def control_command_video_region_exclusion_zones(self, wid, zones): 

559 for vs in self._control_video_subregions_from_wid(wid): 

560 vs.set_exclusion_zones(zones) 

561 return "video exclusion zones set to %s for window %i" % (zones, wid) 

562 

563 def control_command_reset_video_region(self, wid): 

564 for vs in self._control_video_subregions_from_wid(wid): 

565 vs.reset() 

566 return "reset video region heuristics for window %i" % wid 

567 

568 

569 def control_command_lock_batch_delay(self, wid, delay): 

570 for ws in self._control_windowsources_from_args(wid).keys(): 

571 ws.lock_batch_delay(delay) 

572 

573 def control_command_unlock_batch_delay(self, wid): 

574 for ws in self._control_windowsources_from_args(wid).keys(): 

575 ws.unlock_batch_delay() 

576 

577 def control_command_set_lock(self, lock): 

578 self.lock = parse_bool("lock", lock) 

579 self.setting_changed("lock", lock is not False) 

580 self.setting_changed("lock-toggle", lock is None) 

581 return "lock set to %s" % self.lock 

582 

583 def control_command_set_sharing(self, sharing): 

584 self.sharing = parse_bool("sharing", sharing) 

585 self.setting_changed("sharing", sharing is not False) 

586 self.setting_changed("sharing-toggle", sharing is None) 

587 return "sharing set to %s" % self.sharing 

588 

589 def control_command_set_ui_driver(self, uuid): 

590 ss = [s for s in self._server_sources.values() if s.uuid==uuid] 

591 if not ss: 

592 return "source not found for uuid '%s'" % uuid 

593 elif len(ss)>1: 

594 return "more than one source found for uuid '%s'" % uuid 

595 else: 

596 self.set_ui_driver(ss) 

597 return "ui-driver set to %s" % ss 

598 

599 def control_command_key(self, keycode_str, press = True): 

600 if self.readonly: 

601 return 

602 try: 

603 if keycode_str.startswith("0x"): 

604 keycode = int(keycode_str, 16) 

605 else: 

606 keycode = int(keycode_str) 

607 assert keycode>0 and keycode<=255 

608 except: 

609 raise ControlError("invalid keycode specified: '%s' (must be a number between 1 and 255)" % keycode_str) 

610 if press is not True: 

611 if press in ("1", "press"): 

612 press = True 

613 elif press in ("0", "unpress"): 

614 press = False 

615 else: 

616 raise ControlError("if present, the press argument must be one of: %s" % 

617 csv(("1", "press", "0", "unpress"))) 

618 self.fake_key(keycode, press) 

619 

620 def control_command_sound_output(self, *args): 

621 msg = [] 

622 for csource in tuple(self._server_sources.values()): 

623 msg.append("%s : %s" % (csource, csource.sound_control(*args))) 

624 return csv(msg) 

625 

626 def control_command_workspace(self, wid, workspace): 

627 window = self._id_to_window.get(wid) 

628 if not window: 

629 raise ControlError("window %s does not exist" % wid) 

630 if "workspace" not in window.get_property_names(): 

631 raise ControlError("cannot set workspace on window %s" % window) 

632 if workspace<0: 

633 raise ControlError("invalid workspace value: %s" % workspace) 

634 window.set_property("workspace", workspace) 

635 return "window %s moved to workspace %s" % (wid, workspace) 

636 

637 

638 def control_command_move(self, wid, x, y): 

639 window = self._id_to_window.get(wid) 

640 if not window: 

641 raise ControlError("window %s does not exist" % wid) 

642 ww, wh = window.get_dimensions() 

643 count = 0 

644 for source in tuple(self._server_sources.values()): 

645 move_resize_window = getattr(source, "move_resize_window", None) 

646 if move_resize_window: 

647 move_resize_window(wid, window, x, y, ww, wh) 

648 count += 1 

649 return "window %s moved to %i,%i for %i clients" % (wid, x, y, count) 

650 

651 def control_command_resize(self, wid, w, h): 

652 window = self._id_to_window.get(wid) 

653 if not window: 

654 raise ControlError("window %s does not exist" % wid) 

655 count = 0 

656 for source in tuple(self._server_sources.values()): 

657 resize_window = getattr(source, "resize_window", None) 

658 if resize_window: 

659 resize_window(wid, window, w, h) 

660 count += 1 

661 return "window %s resized to %ix%i for %i clients" % (wid, w, h, count) 

662 

663 def control_command_moveresize(self, wid, x, y, w, h): 

664 window = self._id_to_window.get(wid) 

665 if not window: 

666 raise ControlError("window %s does not exist" % wid) 

667 count = 0 

668 for source in tuple(self._server_sources.values()): 

669 move_resize_window = getattr(source, "move_resize_window", None) 

670 if move_resize_window: 

671 move_resize_window(wid, window, x, y, w, h) 

672 count += 1 

673 return "window %s moved to %i,%i and resized to %ix%i for %i clients" % (wid, x, y, w, h, count) 

674 

675 

676 def _process_command_request(self, _proto, packet): 

677 """ client sent a command request through its normal channel """ 

678 assert len(packet)>=2, "invalid command request packet (too small!)" 

679 #packet[0] = "control" 

680 #this may end up calling do_handle_command_request via the adapter 

681 code, msg = self.process_control_command(*packet[1:]) 

682 log("command request returned: %s (%s)", code, msg) 

683 

684 def init_packet_handlers(self): 

685 self._authenticated_packet_handlers.update({ 

686 "command_request" : self._process_command_request, 

687 })