Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/server/mixins/window_server.py : 61%
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-2019 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
8from xpra.util import typedict
9from xpra.server.mixins.stub_server_mixin import StubServerMixin
10from xpra.server.source.windows_mixin import WindowsMixin
11from xpra.log import Logger
13log = Logger("window")
14focuslog = Logger("focus")
15metalog = Logger("metadata")
16geomlog = Logger("geometry")
17eventslog = Logger("events")
19def noop(*_args):
20 pass
23"""
24Mixin for servers that forward windows.
25"""
26class WindowServer(StubServerMixin):
28 def __init__(self):
29 # Window id 0 is reserved for "not a window"
30 self._max_window_id = 1
31 self._window_to_id = {}
32 self._id_to_window = {}
33 self.window_filters = []
34 self.window_min_size = 0, 0
35 self.window_max_size = 2**15-1, 2**15-1
37 def init(self, opts):
38 def parse_window_size(v, default_value=(0, 0)):
39 try:
40 #split on "," or "x":
41 pv = tuple(int(x.strip()) for x in v.replace(",", "x").split("x", 1))
42 assert len(pv)==2
43 w, h = pv
44 assert w>=0 and h>0 and w<32768 and h<32768
45 return w, h
46 except:
47 return default_value
48 self.window_min_size = parse_window_size(opts.min_size, (0, 0))
49 self.window_max_size = parse_window_size(opts.max_size, (2**15-1, 2**15-1))
50 minw, minh = self.window_min_size
51 maxw, maxh = self.window_max_size
52 self.update_size_constraints(minw, minh, maxw, maxh)
54 def setup(self):
55 self.load_existing_windows()
57 def cleanup(self):
58 for window in tuple(self._window_to_id.keys()):
59 window.unmanage()
60 #this can cause errors if we receive packets during shutdown:
61 #self._window_to_id = {}
62 #self._id_to_window = {}
65 def last_client_exited(self):
66 self._focus(None, 0, [])
69 def get_server_features(self, _source) -> dict:
70 return {
71 "window_refresh_config" : True, #v4 clients assume this is available
72 "window-filters" : True, #v4 clients assume this is available
73 }
75 def get_info(self, _proto) -> dict:
76 return {
77 "state" : {
78 "windows" : sum(int(window.is_managed()) for window in tuple(self._id_to_window.values())),
79 },
80 "filters" : tuple((uuid,repr(f)) for uuid, f in self.window_filters),
81 }
83 def get_ui_info(self, _proto, _client_uuids=None, wids=None, *_args) -> dict:
84 """ info that must be collected from the UI thread
85 (ie: things that query the display)
86 """
87 return {"windows" : self.get_windows_info(wids)}
90 def parse_hello(self, ss, caps, send_ui):
91 if send_ui:
92 self.parse_hello_ui_window_settings(ss, caps)
94 def parse_hello_ui_window_settings(self, ss, c):
95 pass
98 def add_new_client(self, *_args):
99 minw, minh = self.window_min_size
100 maxw, maxh = self.window_max_size
101 for ss in tuple(self._server_sources.values()):
102 if not isinstance(ss, WindowsMixin):
103 continue
104 cminw, cminh = ss.window_min_size
105 cmaxw, cmaxh = ss.window_max_size
106 minw = max(minw, cminw)
107 minh = max(minh, cminh)
108 if cmaxw>0:
109 maxw = min(maxw, cmaxw)
110 if cmaxh>0:
111 maxh = min(maxh, cmaxh)
112 maxw = max(1, maxw)
113 maxh = max(1, maxh)
114 if minw>0 and minw>maxw:
115 maxw = minw
116 if minh>0 and minh>maxh:
117 maxh = minh
118 self.update_size_constraints(minw, minh, maxw, maxh)
120 def update_size_constraints(self, minw, minh, maxw, maxh):
121 #subclasses may update the window models
122 pass
125 def send_initial_data(self, ss, caps, send_ui, share_count):
126 if not send_ui:
127 return
128 if not isinstance(ss, WindowsMixin):
129 return
130 self.send_initial_windows(ss, share_count>0)
131 self.send_initial_cursors(ss, share_count>0)
134 def is_shown(self, _window) -> bool:
135 return True
138 def get_window_id(self, window):
139 return self._window_to_id.get(window)
142 def reset_window_filters(self):
143 self.window_filters = []
146 def get_windows_info(self, window_ids):
147 info = {}
148 for wid, window in self._id_to_window.items():
149 if window_ids is not None and wid not in window_ids:
150 continue
151 info[wid] = self.get_window_info(window)
152 return info
154 def get_window_info(self, window):
155 from xpra.server.window.metadata import make_window_metadata
156 info = {}
157 for prop in window.get_property_names():
158 if prop=="icons" or prop is None:
159 continue
160 metadata = make_window_metadata(window, prop, get_transient_for=self.get_transient_for)
161 info.update(metadata)
162 for prop in window.get_internal_property_names():
163 metadata = make_window_metadata(window, prop)
164 info.update(metadata)
165 info.update({
166 "override-redirect" : window.is_OR(),
167 "tray" : window.is_tray(),
168 "size" : window.get_dimensions(),
169 })
170 wid = self._window_to_id.get(window)
171 if wid:
172 wprops = self.client_properties.get(wid)
173 if wprops:
174 info["client-properties"] = wprops
175 return info
177 def _update_metadata(self, window, pspec):
178 metalog("updating metadata on %s: %s", window, pspec)
179 wid = self._window_to_id.get(window)
180 if not wid:
181 return #window is already gone
182 for ss in self._server_sources.values():
183 if isinstance(ss, WindowsMixin):
184 ss.window_metadata(wid, window, pspec.name)
187 def _remove_window(self, window):
188 wid = self._window_to_id[window]
189 log("remove_window: %s - %s", wid, window)
190 for ss in self._server_sources.values():
191 if isinstance(ss, WindowsMixin):
192 ss.lost_window(wid, window)
193 del self._window_to_id[window]
194 del self._id_to_window[wid]
195 for ss in self._server_sources.values():
196 if isinstance(ss, WindowsMixin):
197 ss.remove_window(wid, window)
198 self.client_properties.pop(wid, None)
199 return wid
201 def _add_new_window_common(self, window):
202 props = window.get_dynamic_property_names()
203 metalog("add_new_window_common(%s) watching for dynamic properties: %s", window, props)
204 for prop in props:
205 window.managed_connect("notify::%s" % prop, self._update_metadata)
206 wid = self._max_window_id
207 self._max_window_id += 1
208 self._window_to_id[window] = wid
209 self._id_to_window[wid] = window
210 return wid
212 def _do_send_new_window_packet(self, ptype, window, geometry):
213 wid = self._window_to_id[window]
214 for ss in self._server_sources.values():
215 if not isinstance(ss, WindowsMixin):
216 continue
217 wprops = self.client_properties.get(wid, {}).get(ss.uuid)
218 x, y, w, h = geometry
219 #adjust if the transient-for window is not mapped in the same place by the client we send to:
220 if "transient-for" in window.get_property_names():
221 transient_for = self.get_transient_for(window)
222 if transient_for>0:
223 parent = self._id_to_window.get(transient_for)
224 parent_ws = ss.get_window_source(transient_for)
225 pos = self.get_window_position(parent)
226 geomlog("transient-for=%s : %s, ws=%s, pos=%s", transient_for, parent, parent_ws, pos)
227 if pos and parent and parent_ws:
228 mapped_at = parent_ws.mapped_at
229 if mapped_at:
230 wx, wy = pos
231 cx, cy = mapped_at[:2]
232 if wx!=cx or wy!=cy:
233 dx, dy = cx-wx, cy-wy
234 if dx or dy:
235 geomlog("adjusting new window position for client window offset: %s", (dx, dy))
236 x += dx
237 y += dy
238 ss.new_window(ptype, wid, window, x, y, w, h, wprops)
240 def _process_damage_sequence(self, proto, packet):
241 packet_sequence, wid, width, height, decode_time = packet[1:6]
242 if len(packet)>=7:
243 message = packet[6]
244 else:
245 message = ""
246 ss = self.get_server_source(proto)
247 if ss:
248 ss.client_ack_damage(packet_sequence, wid, width, height, decode_time, message)
250 def refresh_window(self, window):
251 ww, wh = window.get_dimensions()
252 self.refresh_window_area(window, 0, 0, ww, wh)
254 def refresh_window_area(self, window, x, y, width, height, options=None):
255 wid = self._window_to_id[window]
256 for ss in tuple(self._server_sources.values()):
257 damage = getattr(ss, "damage", noop)
258 damage(wid, window, x, y, width, height, options)
260 def _process_buffer_refresh(self, proto, packet):
261 """ can be used for requesting a refresh, or tuning batch config, or both """
262 wid, _, qual = packet[1:4]
263 if len(packet)>=6:
264 options = typedict(packet[4])
265 client_properties = packet[5]
266 else:
267 options = typedict({})
268 client_properties = {}
269 if wid==-1:
270 wid_windows = self._id_to_window
271 elif wid in self._id_to_window:
272 wid_windows = {wid : self._id_to_window.get(wid)}
273 else:
274 #may have been destroyed since the request was made
275 log("invalid window specified for refresh: %s", wid)
276 return
277 log("process_buffer_refresh for windows: %s options=%s, client_properties=%s",
278 wid_windows, options, client_properties)
279 batch_props = options.dictget("batch", {})
280 if batch_props or client_properties:
281 #change batch config and/or client properties
282 self.update_batch_config(proto, wid_windows, typedict(batch_props), client_properties)
283 #default to True for backwards compatibility:
284 if options.get("refresh-now", True):
285 refresh_opts = {"quality" : qual,
286 "override_options" : True}
287 self._refresh_windows(proto, wid_windows, refresh_opts)
290 def update_batch_config(self, proto, wid_windows, batch_props, client_properties):
291 ss = self.get_server_source(proto)
292 if ss is None:
293 return
294 for wid, window in wid_windows.items():
295 if window is None or not window.is_managed():
296 continue
297 self._set_client_properties(proto, wid, window, client_properties)
298 ss.update_batch(wid, window, batch_props)
300 def _refresh_windows(self, proto, wid_windows, opts):
301 ss = self.get_server_source(proto)
302 if ss is None:
303 return
304 for wid, window in wid_windows.items():
305 if window is None or not window.is_managed():
306 continue
307 if not self.is_shown(window):
308 log("window is no longer shown, ignoring buffer refresh which would fail")
309 continue
310 ss.refresh(wid, window, opts)
312 def _idle_refresh_all_windows(self, proto):
313 self.idle_add(self._refresh_windows, proto, self._id_to_window, {})
316 def get_window_position(self, _window):
317 #where the window is actually mapped on the server screen:
318 return None
320 def _window_mapped_at(self, proto, wid, window, coords=None):
321 #record where a window is mapped by a client
322 #(in order to support multiple clients and different offsets)
323 ss = self.get_server_source(proto)
324 if not ss:
325 return
326 if coords:
327 ss.map_window(wid, window, coords)
328 else:
329 ss.unmap_window(wid, window)
331 def get_transient_for(self, _window):
332 return 0
334 def _process_map_window(self, proto, packet):
335 log.info("_process_map_window(%s, %s)", proto, packet)
337 def _process_unmap_window(self, proto, packet):
338 log.info("_process_unmap_window(%s, %s)", proto, packet)
340 def _process_close_window(self, proto, packet):
341 log.info("_process_close_window(%s, %s)", proto, packet)
343 def _process_configure_window(self, proto, packet):
344 log.info("_process_configure_window(%s, %s)", proto, packet)
346 def _get_window_dict(self, wids) -> dict:
347 wd = {}
348 for wid in wids:
349 window = self._id_to_window.get(wid)
350 if window:
351 wd[wid] = window
352 return wd
354 def _process_suspend(self, proto, packet):
355 eventslog("suspend(%s)", packet[1:])
356 ui = bool(packet[1])
357 wd = self._get_window_dict(packet[2])
358 ss = self.get_server_source(proto)
359 if ss:
360 ss.suspend(ui, wd)
362 def _process_resume(self, proto, packet):
363 eventslog("resume(%s)", packet[1:])
364 ui = bool(packet[1])
365 wd = self._get_window_dict(packet[2])
366 ss = self.get_server_source(proto)
367 if ss:
368 ss.resume(ui, wd)
371 def send_initial_windows(self, ss, sharing=False):
372 raise NotImplementedError()
375 def send_initial_cursors(self, ss, sharing=False):
376 pass
379 ######################################################################
380 # focus:
381 def _process_focus(self, proto, packet):
382 if self.readonly:
383 return
384 wid = packet[1]
385 focuslog("process_focus: wid=%s", wid)
386 if len(packet)>=3:
387 modifiers = packet[2]
388 else:
389 modifiers = None
390 ss = self.get_server_source(proto)
391 if ss:
392 self._focus(ss, wid, modifiers)
393 #if the client focused one of our windows, count this as a user event:
394 if wid>0:
395 ss.user_event()
397 def _focus(self, _server_source, wid, modifiers):
398 focuslog("_focus(%s,%s)", wid, modifiers)
400 def get_focus(self) -> int:
401 #can be overriden by subclasses that do manage focus
402 #(ie: not shadow servers which only have a single window)
403 #default: no focus
404 return -1
407 def init_packet_handlers(self):
408 self.add_packet_handlers({
409 "map-window" : self._process_map_window,
410 "unmap-window" : self._process_unmap_window,
411 "configure-window" : self._process_configure_window,
412 "close-window" : self._process_close_window,
413 "focus" : self._process_focus,
414 "damage-sequence" : self._process_damage_sequence,
415 "buffer-refresh" : self._process_buffer_refresh,
416 "suspend" : self._process_suspend,
417 "resume" : self._process_resume,
418 })