Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/server/mixins/server_base_controlcommands.py : 39%
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
8import os.path
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
18log = Logger("command")
20TOGGLE_FEATURES = (
21 "bell", "randr", "cursors", "notifications", "dbus-proxy", "clipboard",
22 "start-new-commands", "client-shutdown", "webcam",
23 )
26"""
27Control commands for ServerBase
28"""
29class ServerBaseControlCommands(StubServerMixin):
31 def setup(self):
32 self.add_control_commands()
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
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])
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
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
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
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)
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)
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)
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
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
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)
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)
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
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
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
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
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))
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))
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)))
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)
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
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)
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
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
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)
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)
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)
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
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
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)
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)
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)
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)
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)
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)
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)
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
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
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
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)
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)
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)
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)
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
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)
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()
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
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
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
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)
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)
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)
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)
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)
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)
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)
684 def init_packet_handlers(self):
685 self._authenticated_packet_handlers.update({
686 "command_request" : self._process_command_request,
687 })