Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/client/client_window_base.py : 54%
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# This file is part of Xpra.
2# Copyright (C) 2011 Serviware (Arthur Huillet, <ahuillet@serviware.com>)
3# Copyright (C) 2010-2020 Antoine Martin <antoine@xpra.org>
4# Copyright (C) 2008, 2010 Nathaniel Smith <njs@pobox.com>
5# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
6# later version. See the file COPYING for details.
8import os
9import re
11from xpra.client.client_widget_base import ClientWidgetBase
12from xpra.os_util import bytestostr, OSX, WIN32, is_Wayland
13from xpra.common import GRAVITY_STR
14from xpra.util import typedict, envbool, envint, WORKSPACE_UNSET, WORKSPACE_NAMES
15from xpra.log import Logger
17log = Logger("window")
18plog = Logger("paint")
19focuslog = Logger("focus")
20mouselog = Logger("mouse")
21workspacelog = Logger("workspace")
22keylog = Logger("keyboard")
23metalog = Logger("metadata")
24geomlog = Logger("geometry")
25iconlog = Logger("icon")
26alphalog = Logger("alpha")
29SIMULATE_MOUSE_DOWN = envbool("XPRA_SIMULATE_MOUSE_DOWN", True)
30PROPERTIES_DEBUG = [x.strip() for x in os.environ.get("XPRA_WINDOW_PROPERTIES_DEBUG", "").split(",")]
31SET_SIZE_CONSTRAINTS = envbool("XPRA_SET_SIZE_CONSTRAINTS", True)
32DEFAULT_GRAVITY = envint("XPRA_DEFAULT_GRAVITY", 0)
33OVERRIDE_GRAVITY = envint("XPRA_OVERRIDE_GRAVITY", 0)
36class ClientWindowBase(ClientWidgetBase):
38 def __init__(self, client, group_leader, watcher_pid, wid,
39 wx, wy, ww, wh, bw, bh,
40 metadata, override_redirect, client_properties,
41 border, max_window_size, default_cursor_data, pixel_depth,
42 headerbar="no"):
43 log("%s%s", type(self),
44 (client, group_leader, watcher_pid, wid,
45 wx, wy, ww, wh, bw, bh,
46 metadata, override_redirect, client_properties,
47 border, max_window_size, default_cursor_data, pixel_depth,
48 headerbar))
49 super().__init__(client, watcher_pid, wid, metadata.boolget("has-alpha"))
50 self._override_redirect = override_redirect
51 self.group_leader = group_leader
52 self._pos = (wx, wy)
53 self._size = (ww, wh)
54 self._client_properties = client_properties
55 self._set_initial_position = metadata.boolget("set-initial-position", False)
56 self.size_constraints = typedict()
57 self.geometry_hints = {}
58 self.content_type = ""
59 self._fullscreen = None
60 self._maximized = False
61 self._above = False
62 self._below = False
63 self._shaded = False
64 self._sticky = False
65 self._skip_pager = False
66 self._skip_taskbar = False
67 self._iconified = False
68 self._focused = False
69 self.window_gravity = OVERRIDE_GRAVITY or DEFAULT_GRAVITY
70 self.border = border
71 self.cursor_data = None
72 self.default_cursor_data = default_cursor_data
73 self.max_window_size = max_window_size
74 self.button_state = {}
75 self.pixel_depth = pixel_depth #0 for default
76 #window_offset is the delta between the location of the window requested by the server,
77 #and where we actually end up mapping it on the client
78 #(ie: when we reposition an OR window to ensure it is visible on screen)
79 self.window_offset = None
80 self.pending_refresh = []
81 self.headerbar = headerbar
83 self.init_window(metadata)
84 self.setup_window(bw, bh)
85 self.update_metadata(metadata)
87 def __repr__(self):
88 return "ClientWindow(%s)" % self._id
90 def init_window(self, metadata):
91 self._backing = None
92 self._metadata = typedict()
93 # used for only sending focus events *after* the window is mapped:
94 self._been_mapped = False
95 self._override_redirect_windows = []
96 def wn(w):
97 return WORKSPACE_NAMES.get(w, w)
98 workspace = typedict(self._client_properties).intget("workspace", None)
99 workspacelog("init_window(..) workspace from client properties %s: %s", self._client_properties, wn(workspace))
100 if workspace is not None:
101 #client properties override application specified workspace value on init only:
102 metadata[b"workspace"] = workspace
103 self._window_workspace = WORKSPACE_UNSET #will get set in set_metadata if present
104 self._desktop_workspace = self.get_desktop_workspace() #pylint: disable=assignment-from-none
105 workspacelog("init_window(..) workspace=%s, current workspace=%s",
106 wn(self._window_workspace), wn(self._desktop_workspace))
107 if self.max_window_size and b"size-constraints" not in metadata:
108 #this ensures that we will set size-constraints and honour max_window_size:
109 metadata[b"size-constraints"] = {}
110 #initialize gravity early:
111 sc = typedict(metadata.dictget("size-constraints", {}))
112 self.window_gravity = OVERRIDE_GRAVITY or sc.intget("gravity", DEFAULT_GRAVITY)
113 self.set_decorated(metadata.boolget("decorations", True))
116 def get_info(self):
117 attributes = []
118 if self._fullscreen:
119 attributes.append("fullscreen")
120 if self._maximized:
121 attributes.append("maximized")
122 if self._above:
123 attributes.append("above")
124 if self._below:
125 attributes.append("below")
126 if self._shaded:
127 attributes.append("shaded")
128 if self._sticky:
129 attributes.append("sticky")
130 if self._skip_pager:
131 attributes.append("skip-pager")
132 if self._skip_taskbar:
133 attributes.append("skip-taskbar")
134 if self._iconified:
135 attributes.append("iconified")
136 if self._focused:
137 attributes.append("focused")
138 info = super().get_info()
139 info.update({
140 "override-redirect" : self._override_redirect,
141 #"group-leader" : self.group_leader,
142 "position" : self._pos,
143 "size" : self._size,
144 "client-properties" : self._client_properties,
145 "set-initial-position" : self._set_initial_position,
146 "size-constraints" : dict(self.size_constraints),
147 "geometry-hints" : dict(self.geometry_hints),
148 "content-type" : self.content_type,
149 "attributes" : attributes,
150 "gravity" : GRAVITY_STR.get(self.window_gravity),
151 #"border" : self.border or "",
152 #cursor_data
153 "max-size" : self.max_window_size,
154 "button-state" : self.button_state,
155 "offset" : self.window_offset,
156 })
157 return info
159 def get_desktop_workspace(self):
160 return None
162 def get_window_workspace(self):
163 return None
166 def new_backing(self, bw, bh):
167 backing_class = self.get_backing_class()
168 log("new_backing(%s, %s) backing_class=%s", bw, bh, backing_class)
169 assert backing_class is not None
170 w, h = self._size
171 self._backing = self.make_new_backing(backing_class, w, h, bw, bh)
172 self._backing.border = self.border
173 self._backing.default_cursor_data = self.default_cursor_data
174 self._backing.gravity = self.window_gravity
175 return self._backing._backing
178 def destroy(self):
179 #ensure we clear reference to other windows:
180 self.group_leader = None
181 self._override_redirect_windows = []
182 self._metadata = {}
183 if self._backing:
184 self._backing.close()
185 self._backing = None
188 def setup_window(self, bw, bh):
189 self.new_backing(bw, bh)
190 #tell the server about the encoding capabilities of this backing instance:
191 #but don't bother if they're the same as what we sent as defaults
192 #(with a bit of magic to collapse the missing namespace from encoding_defaults)
193 backing_props = self._backing.get_encoding_properties()
194 encoding_defaults = self._client.encoding_defaults
195 for k in tuple(backing_props.keys()):
196 v = backing_props[k]
197 try:
198 #ie: "encodings.rgb_formats" -> "rgb_formats"
199 #ie: "encoding.full_csc_modes" -> "full_csc_modes"
200 ek = k.split(".", 1)[1]
201 except IndexError:
202 ek = k
203 dv = encoding_defaults.get(ek)
204 if dv is not None and dv==v:
205 del backing_props[k]
206 self._client_properties.update(backing_props)
209 def send(self, *args):
210 self._client.send(*args)
212 def reset_icon(self):
213 current_icon = self._current_icon
214 iconlog("reset_icon() current icon=%s", current_icon)
215 if current_icon:
216 self.update_icon(current_icon)
218 def update_icon(self, img):
219 raise NotImplementedError("override me!")
221 def apply_transient_for(self, wid):
222 raise NotImplementedError("override me!")
224 def paint_spinner(self, context, area):
225 raise NotImplementedError("override me!")
227 def _pointer_modifiers(self, event):
228 raise NotImplementedError("override me!")
231 def xget_u32_property(self, target, name):
232 raise NotImplementedError("override me!")
235 def is_OR(self):
236 return self._override_redirect
239 def update_metadata(self, metadata):
240 metalog("update_metadata(%s)", metadata)
241 if self._client.readonly:
242 metadata.update(self._force_size_constraint(*self._size))
243 self._metadata.update(metadata)
244 try:
245 self.set_metadata(metadata)
246 except Exception:
247 metalog.warn("failed to set window metadata to '%s'", metadata, exc_info=True)
249 def _force_size_constraint(self, *size):
250 return {
251 b"size-constraints" : {
252 b"maximum-size" : size,
253 b"minimum-size" : size,
254 b"base-size" : size,
255 }
256 }
258 def _get_window_title(self, metadata):
259 try:
260 title = bytestostr(self._client.title).replace("\0", "")
261 if title.find("@")<0:
262 return title
263 #perform metadata variable substitutions:
264 #full of py3k unicode headaches that don't need to be
265 default_values = {
266 "title" : "<untitled window>",
267 "client-machine" : "<unknown machine>",
268 "windowid" : str(self._id),
269 "server-machine" : getattr(self._client, "_remote_hostname", None) or "<unknown machine>",
270 "server-display" : getattr(self._client, "_remote_display", None) or "<unknown display>",
271 }
272 def getvar(var):
273 #"hostname" is magic:
274 #we try harder to find a useful value to show:
275 if var in ("hostname", "hostinfo"):
276 if var=="hostinfo" and getattr(self._client, "mmap_enabled", False):
277 #this is a local connection for sure
278 server_display = getattr(self._client, "server_display", None)
279 if server_display:
280 return server_display
281 #try to find the hostname:
282 proto = getattr(self._client, "_protocol", None)
283 if proto:
284 conn = getattr(proto, "_conn", None)
285 if conn:
286 hostname = conn.info.get("host") or bytestostr(conn.target)
287 if hostname:
288 return hostname
289 for m in ("client-machine", "server-machine"):
290 value = getvar(m)
291 if value not in (
292 "localhost",
293 "localhost.localdomain",
294 "<unknown machine>",
295 "",
296 None):
297 return value
298 return "<unknown machine>"
299 value = metadata.bytesget(var) or self._metadata.bytesget(var)
300 if value is None:
301 return default_values.get(var, "<unknown %s>" % var)
302 try:
303 return value.decode("utf-8")
304 except UnicodeDecodeError:
305 return str(value)
306 def metadata_replace(match):
307 atvar = match.group(0) #ie: '@title@'
308 var = atvar[1:len(atvar)-1] #ie: 'title'
309 if not var:
310 #atvar = "@@"
311 return "@"
312 return getvar(var)
313 sub = r"@[\w\-]*@"
314 replaced = re.sub(sub, metadata_replace, title)
315 metalog("re.sub%s=%s", (sub, metadata_replace, title), replaced)
316 return replaced
317 except Exception as e:
318 log.error("Error parsing window title:")
319 log.error(" %s", e)
320 return ""
322 def set_metadata(self, metadata):
323 metalog("set_metadata(%s)", metadata)
324 debug_props = [x for x in PROPERTIES_DEBUG if x in metadata.keys()]
325 for x in debug_props:
326 metalog.info("set_metadata: %s=%s", x, metadata.get(x))
327 #WARNING: "class-instance" needs to go first because others may realize the window
328 #(and GTK doesn't set the "class-instance" once the window is realized)
329 if b"class-instance" in metadata:
330 self.set_class_instance(*self._metadata.strtupleget("class-instance", ("xpra", "Xpra"), 2, 2))
331 self.reset_icon()
333 if b"title" in metadata:
334 title = self._get_window_title(metadata)
335 self.set_title(title)
337 if b"icon-title" in metadata:
338 icon_title = metadata.strget("icon-title")
339 self.set_icon_name(icon_title)
340 #the DE may have reset the icon now,
341 #force it to use the one we really want:
342 self.reset_icon()
344 if b"size-constraints" in metadata:
345 sc = typedict(metadata.dictget("size-constraints", {}))
346 self.size_constraints = sc
347 self._set_initial_position = sc.boolget("set-initial-position", self._set_initial_position)
348 self.set_size_constraints(sc, self.max_window_size)
350 if b"set-initial-position" in metadata:
351 #this should be redundant - but we keep it here for consistency
352 self._set_initial_position = metadata.boolget("set-initial-position")
354 if b"transient-for" in metadata:
355 wid = metadata.intget("transient-for", -1)
356 self.apply_transient_for(wid)
358 if b"modal" in metadata:
359 modal = metadata.boolget("modal")
360 self.set_modal(modal)
362 #apply window-type hint if window has not been mapped yet:
363 if b"window-type" in metadata and not self.get_mapped():
364 window_types = metadata.strtupleget("window-type")
365 self.set_window_type(window_types)
367 if b"role" in metadata:
368 role = metadata.strget("role")
369 self.set_role(role)
371 if b"xid" in metadata:
372 xid = metadata.strget("xid")
373 self.set_xid(xid)
375 if b"opacity" in metadata:
376 opacity = metadata.intget("opacity", -1)
377 if opacity<0:
378 opacity = 1
379 else:
380 opacity = min(1, opacity/0xffffffff)
381 #requires gtk>=2.12!
382 if hasattr(self, "set_opacity"):
383 self.set_opacity(opacity)
385 if b"has-alpha" in metadata:
386 new_alpha = metadata.boolget("has-alpha")
387 if new_alpha!=self._has_alpha:
388 l = alphalog
389 if not WIN32:
390 #win32 without opengl can't do transparency,
391 #so it triggers too many warnings
392 l = log.warn
393 l("Warning: window %#x changed its transparency attribute", self._id)
394 l(" from %s to %s, behaviour is undefined", self._has_alpha, new_alpha)
395 self._has_alpha = new_alpha
397 if b"maximized" in metadata:
398 maximized = metadata.boolget("maximized")
399 if maximized!=self._maximized:
400 self._maximized = maximized
401 if maximized:
402 self.maximize()
403 else:
404 self.unmaximize()
406 if b"fullscreen" in metadata:
407 fullscreen = metadata.boolget("fullscreen")
408 if self._fullscreen is None or self._fullscreen!=fullscreen:
409 self._fullscreen = fullscreen
410 self.set_fullscreen(fullscreen)
412 if b"iconic" in metadata:
413 iconified = metadata.boolget("iconic")
414 if self._iconified!=iconified:
415 self._iconified = iconified
416 if iconified:
417 self.iconify()
418 else:
419 self.deiconify()
421 if b"decorations" in metadata:
422 decorated = metadata.boolget("decorations", True)
423 was_decorated = self.get_decorated()
424 if WIN32 and decorated!=was_decorated:
425 log.info("decorations flag toggled, now %s, re-initializing window", decorated)
426 self.idle_add(self._client.reinit_window, self._id, self)
427 else:
428 self.set_decorated(metadata.boolget("decorations"))
429 self.apply_geometry_hints(self.geometry_hints)
431 if b"above" in metadata:
432 above = metadata.boolget("above")
433 if self._above!=above:
434 self._above = above
435 self.set_keep_above(above)
437 if b"below" in metadata:
438 below = metadata.boolget("below")
439 if self._below!=below:
440 self._below = below
441 self.set_keep_below(below)
443 if b"shaded" in metadata:
444 shaded = metadata.boolget("shaded")
445 if self._shaded!=shaded:
446 self._shaded = shaded
447 self.set_shaded(shaded)
449 if b"sticky" in metadata:
450 sticky = metadata.boolget("sticky")
451 if self._sticky!=sticky:
452 self._sticky = sticky
453 if sticky:
454 self.stick()
455 else:
456 self.unstick()
458 if b"skip-taskbar" in metadata:
459 skip_taskbar = metadata.boolget("skip-taskbar")
460 if self._skip_taskbar!=skip_taskbar:
461 self._skip_taskbar = skip_taskbar
462 self.set_skip_taskbar_hint(skip_taskbar)
464 if b"skip-pager" in metadata:
465 skip_pager = metadata.boolget("skip-pager")
466 if self._skip_pager!=skip_pager:
467 self._skip_pager = skip_pager
468 self.set_skip_pager_hint(skip_pager)
470 if b"workspace" in metadata:
471 self.set_workspace(metadata.intget("workspace"))
473 if b"bypass-compositor" in metadata:
474 self.set_bypass_compositor(metadata.intget("bypass-compositor"))
476 if b"strut" in metadata:
477 self.set_strut(metadata.dictget("strut", {}))
479 if b"fullscreen-monitors" in metadata:
480 self.set_fullscreen_monitors(metadata.inttupleget("fullscreen-monitors"))
482 if b"shape" in metadata:
483 self.set_shape(metadata.dictget("shape", {}))
485 if b"command" in metadata:
486 self.set_command(metadata.strget("command"))
488 if b"x11-property" in metadata:
489 self.set_x11_property(*metadata.tupleget("x11-property"))
491 if b"content-type" in metadata:
492 self.content_type = metadata.strget("content-type")
495 def set_x11_property(self, *x11_property):
496 pass
498 def set_command(self, command):
499 pass
501 def set_class_instance(self, wmclass_name, wmclass_class):
502 pass
504 def set_shape(self, shape):
505 log("set_shape(%s) not implemented", shape)
507 def set_bypass_compositor(self, v):
508 pass #see gtk client window base
510 def set_strut(self, strut):
511 pass #see gtk client window base
513 def set_fullscreen_monitors(self, fsm):
514 pass #see gtk client window base
516 def set_shaded(self, shaded):
517 pass #see gtk client window base
520 def reset_size_constraints(self):
521 self.set_size_constraints(self.size_constraints, self.max_window_size)
523 def set_size_constraints(self, size_constraints, max_window_size):
524 if not SET_SIZE_CONSTRAINTS:
525 return
526 geomlog("set_size_constraints(%s, %s)", size_constraints, max_window_size)
527 hints = typedict()
528 client = self._client
529 for (a, h1, h2) in (
530 (b"maximum-size", b"max_width", b"max_height"),
531 (b"minimum-size", b"min_width", b"min_height"),
532 (b"base-size", b"base_width", b"base_height"),
533 (b"increment", b"width_inc", b"height_inc"),
534 ):
535 v = size_constraints.intpair(a)
536 geomlog("intpair(%s)=%s", a, v)
537 if v:
538 v1, v2 = v
539 if a==b"maximum-size" and v1>=32000 and v2>=32000 and WIN32:
540 #causes problems, see #2714
541 continue
542 sv1 = client.sx(v1)
543 sv2 = client.sy(v2)
544 if a in (b"base-size", b"increment"):
545 #rounding is not allowed for these values
546 fsv1 = client.fsx(v1)
547 fsv2 = client.fsy(v2)
548 def closetoint(v):
549 #tolerate some rounding error:
550 #(ie: 2:3 scaling may not give an integer without a tiny bit of rounding)
551 return abs(int(v)-v)<0.00001
552 if not closetoint(fsv1) or not closetoint(fsv2):
553 #the scaled value is not close to an int,
554 #so we can't honour it:
555 geomlog("cannot honour '%s' due to scaling, scaled values are not both integers: %s, %s",
556 a, fsv1, fsv2)
557 continue
558 hints[h1], hints[h2] = sv1, sv2
559 if not OSX:
560 for (a, h) in (
561 (b"minimum-aspect-ratio", b"min_aspect"),
562 (b"maximum-aspect-ratio", b"max_aspect"),
563 ):
564 v = size_constraints.intpair(a)
565 if v:
566 v1, v2 = v
567 hints[h] = (v1*self._client.xscale)/(v2*self._client.yscale)
568 #apply max-size override if needed:
569 w,h = max_window_size
570 if w>0 and h>0 and not self._fullscreen:
571 #get the min size, if there is one:
572 minw = max(1, hints.intget(b"min_width", 1))
573 minh = max(1, hints.intget(b"min_height", 1))
574 #the actual max size is:
575 # * greater than the min-size
576 # * the lowest of the max-size set by the application and the one we have
577 # * ensure we honour the other hints, and round the max-size down if needed:
578 #according to the GTK docs:
579 #allowed window widths are base_width + width_inc * N where N is any integer
580 #allowed window heights are base_height + width_inc * N where N is any integer
581 maxw = hints.intget(b"max_width", 32768)
582 maxh = hints.intget(b"max_height", 32768)
583 maxw = max(minw, min(w, maxw))
584 maxh = max(minh, min(h, maxh))
585 rw = (maxw - hints.intget(b"base_width", 0)) % max(hints.intget(b"width_inc", 1), 1)
586 rh = (maxh - hints.intget(b"base_height", 0)) % max(hints.intget(b"height_inc", 1), 1)
587 maxw -= rw
588 maxh -= rh
589 #if the hints combination is invalid, it's possible that we'll end up
590 #not honouring "base" + "inc", but honouring just "min" instead:
591 maxw = max(minw, maxw)
592 maxh = max(minh, maxh)
593 geomlog("modified hints for max window size %s: %s (rw=%s, rh=%s) -> max=%sx%s",
594 max_window_size, hints, rw, rh, maxw, maxh)
595 #ensure we don't have duplicates with bytes / strings,
596 #and that keys are always "bytes":
597 #(in practice this code should never fire, just here as a reminder)
598 for x in ("max_width", "max_height"):
599 hints.pop(x, None)
600 #bug 2214: GTK3 on win32 gets confused if we specify a large max-size
601 # and it will mess up maximizing the window
602 if not WIN32 or (maxw<32000 or maxh<32000):
603 hints[b"max_width"] = maxw
604 hints[b"max_height"] = maxh
605 try:
606 geomlog("calling: %s(%s)", self.apply_geometry_hints, hints)
607 #save them so the window hooks can use the last value used:
608 self.geometry_hints = hints
609 self.apply_geometry_hints(hints)
610 except Exception:
611 geomlog("set_size_constraints%s", (size_constraints, max_window_size), exc_info=True)
612 geomlog.error("Error setting window hints:")
613 for k,v in hints.items():
614 geomlog.error(" %s=%s", bytestostr(k), v)
615 geomlog.error(" from size constraints:")
616 for k,v in size_constraints.items():
617 geomlog.error(" %s=%s", k, v)
618 self.window_gravity = OVERRIDE_GRAVITY or size_constraints.intget("gravity", DEFAULT_GRAVITY)
619 b = self._backing
620 if b:
621 b.gravity = self.window_gravity
624 def set_window_type(self, window_types):
625 pass
627 def set_workspace(self, workspace):
628 pass
630 def set_fullscreen(self, fullscreen):
631 pass
633 def set_xid(self, xid):
634 pass
637 def toggle_debug(self, *_args):
638 b = self._backing
639 log.info("toggling debug on backing %s for window %i", b, self._id)
640 if not b:
641 return
642 if b.paint_box_line_width>0:
643 b.paint_box_line_width = 0
644 else:
645 b.paint_box_line_width = b.default_paint_box_line_width
647 def increase_quality(self, *_args):
648 if self._client.quality>0:
649 #change fixed quality:
650 self._client.quality = min(100, self._client.quality + 10)
651 self._client.send_quality()
652 log("new quality=%s", self._client.quality)
653 else:
654 self._client.min_quality = min(100, self._client.min_quality + 10)
655 self._client.send_min_quality()
656 log("new min-quality=%s", self._client.min_quality)
658 def decrease_quality(self, *_args):
659 if self._client.quality>0:
660 #change fixed quality:
661 self._client.quality = max(1, self._client.quality - 10)
662 self._client.send_quality()
663 log("new quality=%s", self._client.quality)
664 else:
665 self._client.min_quality = max(0, self._client.min_quality - 10)
666 self._client.send_min_quality()
667 log("new min-quality=%s", self._client.min_quality)
669 def increase_speed(self, *_args):
670 if self._client.speed>0:
671 #change fixed speed:
672 self._client.speed = min(100, self._client.speed + 10)
673 self._client.send_speed()
674 log("new speed=%s", self._client.speed)
675 else:
676 self._client.min_speed = min(100, self._client.min_speed + 10)
677 self._client.send_min_speed()
678 log("new min-speed=%s", self._client.min_speed)
680 def decrease_speed(self, *_args):
681 if self._client.speed>0:
682 #change fixed speed:
683 self._client.speed = max(1, self._client.speed - 10)
684 self._client.send_speed()
685 log("new speed=%s", self._client.speed)
686 else:
687 self._client.min_speed = max(0, self._client.min_speed - 10)
688 self._client.send_min_speed()
689 log("new min-speed=%s", self._client.min_speed)
691 def scaleup(self, *_args):
692 self._client.scaleup()
694 def scaledown(self, *_args):
695 self._client.scaledown()
697 def scalingoff(self):
698 self._client.scalingoff()
700 def scalereset(self, *_args):
701 self._client.scalereset()
703 def magic_key(self, *args):
704 b = self.border
705 if b:
706 b.toggle()
707 log("magic_key%s border=%s", args, b)
708 self.repaint(0, 0, *self._size)
710 def repaint(self, x, y, w, h):
711 #self.queue_draw_area(0, 0, *self._size)
712 raise NotImplementedError("no repaint on %s", type(self))
714 def refresh_window(self, *args):
715 log("refresh_window(%s) wid=%s", args, self._id)
716 self._client.send_refresh(self._id)
718 def refresh_all_windows(self, *_args):
719 #this method is only here because we may want to fire it
720 #from a --key-shortcut action and the event is delivered to
721 #the "ClientWindow"
722 self._client.send_refresh_all()
724 def draw_region(self, x, y, width, height, coding, img_data, rowstride, _packet_sequence, options, callbacks):
725 """ Note: this runs from the draw thread (not UI thread) """
726 backing = self._backing
727 if not backing:
728 log("draw_region: window %s has no backing, gone?", self._id)
729 from xpra.client.window_backing_base import fire_paint_callbacks
730 fire_paint_callbacks(callbacks, -1, "no backing")
731 return
732 #only register this callback if we actually need it:
733 if backing.draw_needs_refresh:
734 if not backing.repaint_all:
735 self.pending_refresh.append((x, y, width, height))
736 if options.intget("flush", 0)==0:
737 callbacks.append(self.after_draw_refresh)
738 if coding=="void":
739 fire_paint_callbacks(callbacks)
740 return
741 backing.draw_region(x, y, width, height, coding, img_data, rowstride, options, callbacks)
743 def after_draw_refresh(self, success, message=""):
744 plog("after_draw_refresh(%s, %s) pending_refresh=%s",
745 success, message, self.pending_refresh)
746 backing = self._backing
747 if not backing:
748 return
749 if backing.repaint_all or self._client.xscale!=1 or self._client.yscale!=1 or is_Wayland():
750 #easy: just repaint the whole window:
751 rw, rh = self.get_size()
752 self.idle_add(self.repaint, 0, 0, rw, rh)
753 return
754 pr = self.pending_refresh
755 self.pending_refresh = []
756 for x, y, w, h in pr:
757 rx, ry, rw, rh = self._client.srect(x, y, w, h)
758 if self.window_offset:
759 rx += self.window_offset[0]
760 ry += self.window_offset[1]
761 self.idle_add(self.repaint, rx, ry, rw, rh)
763 def eos(self):
764 """ Note: this runs from the draw thread (not UI thread) """
765 backing = self._backing
766 if backing:
767 backing.eos()
769 def spinner(self, _ok):
770 if not self.can_have_spinner():
771 return
772 log("spinner(%s) queueing redraw")
773 #with normal windows, we just queue a draw request
774 #and let the expose event paint the spinner
775 w, h = self.get_size()
776 self.repaint(0, 0, w, h)
778 def can_have_spinner(self):
779 if self._backing is None:
780 return False
781 window_types = self._metadata.strtupleget("window-type")
782 if not window_types:
783 return False
784 return ("NORMAL" in window_types) or \
785 ("DIALOG" in window_types) or \
786 ("SPLASH" in window_types)
789 def _unfocus(self):
790 focuslog("_unfocus() wid=%s, focused=%s", self._id, self._client._focused)
791 if self._client._focused==self._id:
792 self._client.update_focus(self._id, False)
794 def quit(self):
795 self._client.quit(0)
797 def void(self):
798 pass
800 def show_window_info(self, *args):
801 from xpra.client.gtk_base.window_info import WindowInfo
802 wi = WindowInfo(self._client, self)
803 wi.show()
805 def show_session_info(self, *args):
806 self._client.show_session_info(*args)
808 def show_menu(self, *args):
809 self._client.show_menu(*args)
811 def show_start_new_command(self, *args):
812 self._client.show_start_new_command(*args)
814 def show_bug_report(self, *args):
815 self._client.show_bug_report(*args)
817 def show_file_upload(self, *args):
818 self._client.show_file_upload(*args)
821 def log(self, message=""):
822 log.info(message)
825 def keyboard_layout_changed(self, *args):
826 #used by win32 hooks to tell us about keyboard layout changes for this window
827 keylog("keyboard_layout_changed%s", args)
828 self._client.window_keyboard_layout_changed(self)
831 def dbus_call(self, *args, **kwargs):
832 #alias for rpc_call using dbus as rpc_type, see UIXpraClient.dbus_call
833 if not self._client.server_dbus_proxy:
834 log.error("Error: cannot send remote dbus call:")
835 log.error(" this server does not support dbus-proxying")
836 return
837 rpc_args = [self._id]+args
838 self._client.rpc_call("dbus", rpc_args, **kwargs)
841 def get_mouse_event_wid(self, _x, _y):
842 #overriden in GTKClientWindowBase
843 return self._id
845 def _do_motion_notify_event(self, event):
846 if self._client.readonly or self._client.server_readonly or not self._client.server_pointer:
847 return
848 pointer, relative_pointer, modifiers, buttons = self._pointer_modifiers(event)
849 wid = self.get_mouse_event_wid(*pointer)
850 mouselog("do_motion_notify_event(%s) wid=%s / focus=%s / window wid=%i, device=%s, pointer=%s, relative pointer=%s, modifiers=%s, buttons=%s", event, wid, self._client._focused, self._id, self._device_info(event), pointer, relative_pointer, modifiers, buttons)
851 pdata = pointer
852 if self._client.server_pointer_relative:
853 pdata = list(pointer)+list(relative_pointer)
854 packet = ["pointer-position", wid, pdata, modifiers, buttons]
855 self._client.send_mouse_position(packet)
857 def _device_info(self, event):
858 try:
859 return event.device.get_name()
860 except AttributeError:
861 return ""
863 def _button_action(self, button, event, depressed, *args):
864 if self._client.readonly or self._client.server_readonly or not self._client.server_pointer:
865 return
866 pointer, relative_pointer, modifiers, buttons = self._pointer_modifiers(event)
867 wid = self.get_mouse_event_wid(*pointer)
868 mouselog("_button_action(%s, %s, %s) wid=%s / focus=%s / window wid=%i, device=%s, pointer=%s, modifiers=%s, buttons=%s",
869 button, event, depressed, wid, self._client._focused, self._id, self._device_info(event), pointer, modifiers, buttons)
870 #map wheel buttons via translation table to support inverted axes:
871 server_button = button
872 if button>3:
873 server_button = self._client.wheel_map.get(button)
874 if not server_button:
875 return
876 server_buttons = []
877 for b in buttons:
878 if b>3:
879 sb = self._client.wheel_map.get(button)
880 if not sb:
881 continue
882 b = sb
883 server_buttons.append(b)
884 pdata = pointer
885 if self._client.server_pointer_relative:
886 pdata = list(pointer)+list(relative_pointer)
887 def send_button(pressed):
888 self._client.send_button(wid, server_button, pressed, pdata, modifiers, server_buttons, *args)
889 pressed_state = self.button_state.get(button, False)
890 if SIMULATE_MOUSE_DOWN and pressed_state is False and depressed is False:
891 mouselog("button action: simulating a missing mouse-down event for window %s before sending the mouse-up event", wid)
892 #(needed for some dialogs on win32):
893 send_button(True)
894 self.button_state[button] = depressed
895 send_button(depressed)
897 def do_button_press_event(self, event):
898 self._button_action(event.button, event, True)
900 def do_button_release_event(self, event):
901 self._button_action(event.button, event, False)