Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/x11/models/base.py : 55%
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) 2008, 2009 Nathaniel Smith <njs@pobox.com>
3# Copyright (C) 2011-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.
7from gi.repository import GObject
9from xpra.util import WORKSPACE_UNSET, WORKSPACE_ALL
10from xpra.x11.models.core import CoreX11WindowModel, xswallow, Above, RESTACKING_STR
11from xpra.x11.bindings.window_bindings import X11WindowBindings, constants #@UnresolvedImport
12from xpra.server.window.content_guesser import guess_content_type, get_content_type_properties
13from xpra.x11.gtk_x11.gdk_bindings import get_pywindow, get_pyatom #@UnresolvedImport
14from xpra.x11.gtk_x11.prop import prop_set, prop_get, prop_del
15from xpra.log import Logger
17log = Logger("x11", "window")
18workspacelog = Logger("x11", "window", "workspace")
19metalog = Logger("x11", "window", "metadata")
22dbus_helper = None
23query_actions = None
26X11Window = X11WindowBindings()
28#_NET_WM_STATE:
29_NET_WM_STATE_REMOVE = 0
30_NET_WM_STATE_ADD = 1
31_NET_WM_STATE_TOGGLE = 2
32STATE_STRING = {
33 _NET_WM_STATE_REMOVE : "REMOVE",
34 _NET_WM_STATE_ADD : "ADD",
35 _NET_WM_STATE_TOGGLE : "TOGGLE",
36 }
37IconicState = constants["IconicState"]
38NormalState = constants["NormalState"]
39ICONIC_STATE_STRING = {
40 IconicState : "Iconic",
41 NormalState : "Normal",
42 }
44#add user friendly workspace logging:
45WORKSPACE_STR = {
46 WORKSPACE_UNSET : "UNSET",
47 WORKSPACE_ALL : "ALL",
48 }
49def workspacestr(w):
50 return WORKSPACE_STR.get(w, w)
53class BaseWindowModel(CoreX11WindowModel):
54 """
55 Extends CoreX11WindowModel to add properties
56 that we find on real X11 windows (as opposed to trays).
57 Also wraps access to _NET_WM_STATE using simpler gobject booleans.
58 """
59 __common_properties__ = CoreX11WindowModel.__common_properties__.copy()
60 __common_properties__.update({
61 #from WM_TRANSIENT_FOR
62 "transient-for": (GObject.TYPE_PYOBJECT,
63 "Transient for (or None)", "",
64 GObject.ParamFlags.READABLE),
65 #from _NET_WM_WINDOW_OPACITY
66 "opacity": (GObject.TYPE_INT64,
67 "Opacity", "",
68 -1, 0xffffffff, -1,
69 GObject.ParamFlags.READABLE),
70 #from WM_HINTS.window_group
71 "group-leader": (GObject.TYPE_PYOBJECT,
72 "Window group leader as a pair: (xid, gdk window)", "",
73 GObject.ParamFlags.READABLE),
74 #from WM_HINTS.urgency or _NET_WM_STATE
75 "attention-requested": (GObject.TYPE_BOOLEAN,
76 "Urgency hint from client, or us", "",
77 False,
78 GObject.ParamFlags.READWRITE),
79 #from WM_HINTS.input or WM_TAKE_FOCUS
80 "can-focus": (GObject.TYPE_BOOLEAN,
81 "Does this window ever accept keyboard input?", "",
82 True,
83 GObject.ParamFlags.READWRITE),
84 #from _NET_WM_BYPASS_COMPOSITOR
85 "bypass-compositor": (GObject.TYPE_INT,
86 "hint that the window would benefit from running uncomposited ", "",
87 0, 2, 0,
88 GObject.ParamFlags.READABLE),
89 #from _NET_WM_FULLSCREEN_MONITORS
90 "fullscreen-monitors": (GObject.TYPE_PYOBJECT,
91 "List of 4 monitor indices indicating the top, bottom, left, and right edges"+
92 " of the window when the fullscreen state is enabled", "",
93 GObject.ParamFlags.READABLE),
94 #from _NET_WM_STRUT_PARTIAL or _NET_WM_STRUT
95 "strut": (GObject.TYPE_PYOBJECT,
96 "Struts requested by window, or None", "",
97 GObject.ParamFlags.READABLE),
98 #for our own use:
99 "content-type": (GObject.TYPE_PYOBJECT,
100 "What type of content is shown in this window", "",
101 GObject.ParamFlags.READABLE),
102 #from _XPRA_QUALITY
103 "quality": (GObject.TYPE_INT,
104 "Quality", "",
105 -1, 100, -1,
106 GObject.ParamFlags.READABLE),
107 #from _XPRA_SPEED
108 "speed": (GObject.TYPE_INT,
109 "Speed", "",
110 -1, 100, -1,
111 GObject.ParamFlags.READABLE),
112 #from _XPRA_ENCODING
113 "encoding": (GObject.TYPE_PYOBJECT,
114 "Encoding", "",
115 GObject.ParamFlags.READABLE),
116 #from _NET_WM_DESKTOP
117 "workspace": (GObject.TYPE_UINT,
118 "The workspace this window is on", "",
119 0, 2**32-1, WORKSPACE_UNSET,
120 GObject.ParamFlags.READWRITE),
121 #set initially only by the window model class
122 #(derived from XGetWindowAttributes.override_redirect)
123 "override-redirect": (GObject.TYPE_BOOLEAN,
124 "Is the window of type override-redirect", "",
125 False,
126 GObject.ParamFlags.READABLE),
127 #from _NET_WM_WINDOW_TYPE
128 "window-type": (GObject.TYPE_PYOBJECT,
129 "Window type",
130 "NB, most preferred comes first, then fallbacks",
131 GObject.ParamFlags.READABLE),
132 #this value is synced to "_NET_WM_STATE":
133 "state": (GObject.TYPE_PYOBJECT,
134 "State, as per _NET_WM_STATE", "",
135 GObject.ParamFlags.READABLE),
136 #all the attributes below are virtual attributes from WM_STATE:
137 "modal": (GObject.TYPE_PYOBJECT,
138 "Modal", "",
139 GObject.ParamFlags.READWRITE),
140 "fullscreen": (GObject.TYPE_BOOLEAN,
141 "Fullscreen-ness of window", "",
142 False,
143 GObject.ParamFlags.READWRITE),
144 "focused": (GObject.TYPE_BOOLEAN,
145 "Is the window focused", "",
146 False,
147 GObject.ParamFlags.READWRITE),
148 "maximized": (GObject.TYPE_BOOLEAN,
149 "Is the window maximized", "",
150 False,
151 GObject.ParamFlags.READWRITE),
152 "above": (GObject.TYPE_BOOLEAN,
153 "Is the window on top of most windows", "",
154 False,
155 GObject.ParamFlags.READWRITE),
156 "below": (GObject.TYPE_BOOLEAN,
157 "Is the window below most windows", "",
158 False,
159 GObject.ParamFlags.READWRITE),
160 "shaded": (GObject.TYPE_BOOLEAN,
161 "Is the window shaded", "",
162 False,
163 GObject.ParamFlags.READWRITE),
164 "skip-taskbar": (GObject.TYPE_BOOLEAN,
165 "Should the window be included on a taskbar", "",
166 False,
167 GObject.ParamFlags.READWRITE),
168 "skip-pager": (GObject.TYPE_BOOLEAN,
169 "Should the window be included on a pager", "",
170 False,
171 GObject.ParamFlags.READWRITE),
172 "sticky": (GObject.TYPE_BOOLEAN,
173 "Is the window's position fixed on the screen", "",
174 False,
175 GObject.ParamFlags.READWRITE),
176 })
177 _property_names = CoreX11WindowModel._property_names + [
178 "transient-for", "fullscreen-monitors", "bypass-compositor",
179 "group-leader", "window-type", "workspace", "strut", "opacity",
180 "content-type",
181 #virtual attributes:
182 "fullscreen", "focused", "maximized", "above", "below", "shaded",
183 "skip-taskbar", "skip-pager", "sticky",
184 ]
185 _dynamic_property_names = CoreX11WindowModel._dynamic_property_names + [
186 "attention-requested", "content-type",
187 "workspace", "opacity",
188 "fullscreen", "focused", "maximized", "above", "below", "shaded",
189 "skip-taskbar", "skip-pager", "sticky",
190 "quality", "speed", "encoding",
191 ]
192 _internal_property_names = CoreX11WindowModel._internal_property_names+["state"]
193 _initial_x11_properties = CoreX11WindowModel._initial_x11_properties + [
194 "WM_TRANSIENT_FOR",
195 "_NET_WM_WINDOW_TYPE",
196 "_NET_WM_DESKTOP",
197 "_NET_WM_FULLSCREEN_MONITORS",
198 "_NET_WM_BYPASS_COMPOSITOR",
199 "_NET_WM_STRUT",
200 #redundant as it uses the same handler as _NET_WM_STRUT:
201 "_NET_WM_STRUT_PARTIAL",
202 "_NET_WM_WINDOW_OPACITY",
203 "WM_HINTS",
204 "_XPRA_CONTENT_TYPE",
205 "_XPRA_QUALITY",
206 "_XPRA_SPEED",
207 "_XPRA_ENCODING",
208 ]
209 _DEFAULT_NET_WM_ALLOWED_ACTIONS = ["_NET_WM_ACTION_%s" % x for x in (
210 "CLOSE", "MOVE", "RESIZE", "FULLSCREEN",
211 "MINIMIZE", "SHADE", "STICK",
212 "MAXIMIZE_HORZ", "MAXIMIZE_VERT",
213 "CHANGE_DESKTOP", "ABOVE", "BELOW")]
214 _MODELTYPE = "Base"
216 def __init__(self, client_window):
217 super().__init__(client_window)
218 self.last_unmap_serial = 0
219 self._input_field = True # The WM_HINTS input field
220 #watch for changes to properties that are used to derive the content-type:
221 for x in get_content_type_properties():
222 if x in self.get_dynamic_property_names():
223 self.connect("notify::%s" % x, self._content_type_related_property_change)
225 def serial_after_last_unmap(self, serial) -> bool:
226 #"The serial member is set from the serial number reported in the protocol
227 # but expanded from the 16-bit least-significant bits to a full 32-bit value"
228 if serial>self.last_unmap_serial:
229 return True
230 #the serial can wrap around:
231 if self.last_unmap_serial-serial>=2**15:
232 return True
233 return False
236 def _read_initial_X11_properties(self):
237 metalog("%s.read_initial_X11_properties()", self._MODELTYPE)
238 self._updateprop("state", frozenset(self._read_wm_state()))
239 super()._read_initial_X11_properties()
241 def _guess_window_type(self) -> str:
242 #query the X11 property directly,
243 #in case the python property isn't set yet
244 if not self.is_OR():
245 transient_for = self.prop_get("WM_TRANSIENT_FOR", "window")
246 if transient_for is not None:
247 # EWMH says that even if it's transient-for, we MUST check to
248 # see if it's override-redirect (and if so treat as NORMAL).
249 # But we wouldn't be here if this was override-redirect.
250 # (OverrideRedirectWindowModel overrides this method)
251 return "_NET_WM_WINDOW_TYPE_DIALOG"
252 return "_NET_WM_WINDOW_TYPE_NORMAL"
255 ################################
256 # Actions
257 ################################
259 def move_to_workspace(self, workspace : int):
260 #we send a message to ourselves, we could also just update the property
261 current = self.get_property("workspace")
262 if current==workspace:
263 workspacelog("move_to_workspace(%s) unchanged", workspacestr(workspace))
264 return
265 workspacelog("move_to_workspace(%s) current=%s", workspacestr(workspace), workspacestr(current))
266 with xswallow:
267 if workspace==WORKSPACE_UNSET:
268 workspacelog("removing _NET_WM_DESKTOP property from window %#x", self.xid)
269 prop_del(self.client_window, "_NET_WM_DESKTOP")
270 else:
271 workspacelog("setting _NET_WM_DESKTOP=%s on window %#x", workspacestr(workspace), self.xid)
272 prop_set(self.client_window, "_NET_WM_DESKTOP", "u32", workspace)
275 #########################################
276 # Python objects synced to X11 properties
277 #########################################
279 def _sync_state(self, *_args):
280 state = self.get_property("state")
281 metalog("sync_state: setting _NET_WM_STATE=%s on %#x", state, self.xid)
282 with xswallow:
283 prop_set(self.client_window, "_NET_WM_STATE", ["atom"], state)
285 def _sync_iconic(self, *_args):
286 def set_state(state):
287 log("_handle_iconic_update: set_state(%s)", state)
288 with xswallow:
289 prop_set(self.client_window, "WM_STATE", "state", state)
291 if self.get("iconic"):
292 set_state(IconicState)
293 self._state_add("_NET_WM_STATE_HIDDEN")
294 else:
295 set_state(NormalState)
296 self._state_remove("_NET_WM_STATE_HIDDEN")
299 _py_property_handlers = dict(CoreX11WindowModel._py_property_handlers)
300 _py_property_handlers.update({
301 "state" : _sync_state,
302 "iconic" : _sync_iconic,
303 })
306 #########################################
307 # X11 properties synced to Python objects
308 #########################################
310 def _handle_transient_for_change(self):
311 transient_for = self.prop_get("WM_TRANSIENT_FOR", "window")
312 metalog("WM_TRANSIENT_FOR=%s", transient_for)
313 # May be None
314 self._updateprop("transient-for", transient_for)
316 def _handle_window_type_change(self):
317 window_types = self.prop_get("_NET_WM_WINDOW_TYPE", ["atom"])
318 metalog("_NET_WM_WINDOW_TYPE=%s", window_types)
319 if not window_types:
320 window_type = self._guess_window_type()
321 metalog("guessed window type=%s", window_type)
322 #atom = Gdk.Atom.intern(window_type, False)
323 window_types = [window_type]
324 #normalize them (hide _NET_WM_WINDOW_TYPE prefix):
325 window_types = [str(wt).replace("_NET_WM_WINDOW_TYPE_", "").replace("_NET_WM_TYPE_", "") for wt in window_types]
326 self._updateprop("window-type", window_types)
328 def _handle_workspace_change(self):
329 workspace = self.prop_get("_NET_WM_DESKTOP", "u32", True)
330 if workspace is None:
331 workspace = WORKSPACE_UNSET
332 workspacelog("_NET_WM_DESKTOP=%s for window %#x", workspacestr(workspace), self.xid)
333 self._updateprop("workspace", workspace)
335 def _handle_fullscreen_monitors_change(self):
336 fsm = self.prop_get("_NET_WM_FULLSCREEN_MONITORS", ["u32"], True)
337 metalog("_NET_WM_FULLSCREEN_MONITORS=%s", fsm)
338 self._updateprop("fullscreen-monitors", fsm)
340 def _handle_bypass_compositor_change(self):
341 bypass = self.prop_get("_NET_WM_BYPASS_COMPOSITOR", "u32", True) or 0
342 metalog("_NET_WM_BYPASS_COMPOSITOR=%s", bypass)
343 self._updateprop("bypass-compositor", bypass)
345 def _handle_wm_strut_change(self):
346 strut = self.prop_get("_NET_WM_STRUT_PARTIAL", "strut-partial")
347 metalog("_NET_WM_STRUT_PARTIAL=%s", strut)
348 if strut is None:
349 strut = self.prop_get("_NET_WM_STRUT", "strut")
350 metalog("_NET_WM_STRUT=%s", strut)
351 # Might be None:
352 self._updateprop("strut", strut)
354 def _handle_opacity_change(self):
355 opacity = self.prop_get("_NET_WM_WINDOW_OPACITY", "u32", True) or -1
356 metalog("_NET_WM_WINDOW_OPACITY=%s", opacity)
357 self._updateprop("opacity", opacity)
359 def _handle_wm_hints_change(self):
360 with xswallow:
361 wm_hints = X11Window.getWMHints(self.xid)
362 metalog("getWMHints(%#x)=%s", self.xid, wm_hints)
363 if wm_hints is None:
364 return
365 # GdkWindow or None
366 group_leader = None
367 if "window_group" in wm_hints:
368 xid = wm_hints.get("window_group")
369 try:
370 group_leader = xid, get_pywindow(xid)
371 except Exception:
372 group_leader = xid, None
373 self._updateprop("group-leader", group_leader)
374 self._updateprop("attention-requested", wm_hints.get("urgency", False))
375 _input = wm_hints.get("input")
376 metalog("wm_hints.input = %s", _input)
377 #we only set this value once:
378 #(input_field always starts as True, and we then set it to an int)
379 if self._input_field is True and _input is not None:
380 #keep the value as an int to differentiate from the start value:
381 self._input_field = int(_input)
382 self._update_can_focus()
384 def _update_can_focus(self, *_args):
385 can_focus = bool(self._input_field) or "WM_TAKE_FOCUS" in self.get_property("protocols")
386 self._updateprop("can-focus", can_focus)
389 def _content_type_related_property_change(self, *_args):
390 self._update_content_type()
392 def _handle_xpra_content_type_change(self):
393 self._update_content_type()
395 def _update_content_type(self):
396 #watch for changes to properties that are used to derive the content-type:
397 content_type = self.prop_get("_XPRA_CONTENT_TYPE", "latin1", True)
398 #the _XPRA_CONTENT_TYPE property takes precedence
399 if not content_type:
400 content_type = guess_content_type(self)
401 if not content_type and self.is_tray():
402 content_type = "picture"
403 metalog("_update_content_type() %s", content_type)
404 self._updateprop("content-type", content_type)
407 def _handle_xpra_quality_change(self):
408 quality = self.prop_get("_XPRA_QUALITY", "u32", True) or -1
409 metalog("quality=%s", quality)
410 self._updateprop("quality", max(-1, min(100, quality)))
412 def _handle_xpra_speed_change(self):
413 speed = self.prop_get("_XPRA_SPEED", "u32", True) or -1
414 metalog("speed=%s", speed)
415 self._updateprop("speed", max(-1, min(100, speed)))
417 def _handle_xpra_encoding_change(self):
418 encoding = self.prop_get("_XPRA_ENCODING", "latin1", True) or ""
419 metalog("encoding=%s", encoding)
420 self._updateprop("encoding", encoding)
423 _x11_property_handlers = CoreX11WindowModel._x11_property_handlers.copy()
424 _x11_property_handlers.update({
425 "WM_TRANSIENT_FOR" : _handle_transient_for_change,
426 "_NET_WM_WINDOW_TYPE" : _handle_window_type_change,
427 "_NET_WM_DESKTOP" : _handle_workspace_change,
428 "_NET_WM_FULLSCREEN_MONITORS" : _handle_fullscreen_monitors_change,
429 "_NET_WM_BYPASS_COMPOSITOR" : _handle_bypass_compositor_change,
430 "_NET_WM_STRUT" : _handle_wm_strut_change,
431 "_NET_WM_STRUT_PARTIAL" : _handle_wm_strut_change,
432 "_NET_WM_WINDOW_OPACITY" : _handle_opacity_change,
433 "WM_HINTS" : _handle_wm_hints_change,
434 "_XPRA_CONTENT_TYPE" : _handle_xpra_content_type_change,
435 "_XPRA_QUALITY" : _handle_xpra_quality_change,
436 "_XPRA_SPEED" : _handle_xpra_speed_change,
437 "_XPRA_ENCODING" : _handle_xpra_encoding_change,
438 })
441 #########################################
442 # _NET_WM_STATE
443 #########################################
444 # A few words about _NET_WM_STATE are in order. Basically, it is a set of
445 # flags. Clients are allowed to set the initial value of this X property
446 # to anything they like, when their window is first mapped; after that,
447 # though, only the window manager is allowed to touch this property. So
448 # we store its value (or at least, our idea as to its value, the X server
449 # in principle could disagree) as the "state" property. There are
450 # basically two things we need to accomplish:
451 # 1) Whenever our property is modified, we mirror that modification into
452 # the X server. This is done by connecting to our own notify::state
453 # signal.
454 # 2) As a more user-friendly interface to these state flags, we provide
455 # several boolean properties like "attention-requested".
456 # These are virtual boolean variables; they are actually backed
457 # directly by the "state" property, and reading/writing them in fact
458 # accesses the "state" set directly. This is done by overriding
459 # do_set_property and do_get_property.
460 _state_properties = {
461 "attention-requested" : ("_NET_WM_STATE_DEMANDS_ATTENTION", ),
462 "fullscreen" : ("_NET_WM_STATE_FULLSCREEN", ),
463 "maximized" : ("_NET_WM_STATE_MAXIMIZED_VERT", "_NET_WM_STATE_MAXIMIZED_HORZ"),
464 "shaded" : ("_NET_WM_STATE_SHADED", ),
465 "above" : ("_NET_WM_STATE_ABOVE", ),
466 "below" : ("_NET_WM_STATE_BELOW", ),
467 "sticky" : ("_NET_WM_STATE_STICKY", ),
468 "skip-taskbar" : ("_NET_WM_STATE_SKIP_TASKBAR", ),
469 "skip-pager" : ("_NET_WM_STATE_SKIP_PAGER", ),
470 "modal" : ("_NET_WM_STATE_MODAL", ),
471 "focused" : ("_NET_WM_STATE_FOCUSED", ),
472 }
473 _state_properties_reversed = {}
474 for k, states in tuple(_state_properties.items()):
475 for x in states:
476 _state_properties_reversed[x] = k
478 def _state_add(self, *state_names):
479 curr = set(self.get_property("state"))
480 add = [s for s in state_names if s not in curr]
481 if add:
482 for x in add:
483 curr.add(x)
484 #note: _sync_state will update _NET_WM_STATE here:
485 self._internal_set_property("state", frozenset(curr))
486 self._state_notify(add)
488 def _state_remove(self, *state_names):
489 curr = set(self.get_property("state"))
490 discard = [s for s in state_names if s in curr]
491 if discard:
492 for x in discard:
493 curr.discard(x)
494 #note: _sync_state will update _NET_WM_STATE here:
495 self._internal_set_property("state", frozenset(curr))
496 self._state_notify(discard)
498 def _state_notify(self, state_names):
499 notify_props = set()
500 for x in state_names:
501 if x in self._state_properties_reversed:
502 notify_props.add(self._state_properties_reversed[x])
503 for x in tuple(notify_props):
504 self.notify(x)
506 def _state_isset(self, state_name):
507 return state_name in self.get_property("state")
509 def _read_wm_state(self):
510 wm_state = self.prop_get("_NET_WM_STATE", ["atom"])
511 metalog("read _NET_WM_STATE=%s", wm_state)
512 return wm_state or []
515 def do_set_property(self, pspec, value):
516 #intercept state properties to route via update_state()
517 if pspec.name in self._state_properties:
518 #virtual property for WM_STATE:
519 self.update_wm_state(pspec.name, value)
520 return
521 super().do_set_property(pspec, value)
523 def do_get_property(self, pspec):
524 #intercept state properties to route via get_wm_state()
525 if pspec.name in self._state_properties:
526 #virtual property for WM_STATE:
527 return self.get_wm_state(pspec.name)
528 return super().do_get_property(pspec)
531 def update_wm_state(self, prop, b):
532 state_names = self._state_properties.get(prop)
533 assert state_names, "invalid window state %s" % prop
534 if b:
535 self._state_add(*state_names)
536 else:
537 self._state_remove(*state_names)
539 def get_wm_state(self, prop):
540 state_names = self._state_properties.get(prop)
541 assert state_names, "invalid window state %s" % prop
542 #this is a virtual property for WM_STATE:
543 #return True if any is set (only relevant for maximized)
544 for x in state_names:
545 if self._state_isset(x):
546 return True
547 return False
550 #########################################
551 # X11 Events
552 #########################################
554 def process_client_message_event(self, event):
555 # FIXME
556 # Need to listen for:
557 # _NET_CURRENT_DESKTOP
558 # _NET_WM_PING responses
559 # and maybe:
560 # _NET_WM_STATE (more fully)
562 if event.message_type=="_NET_WM_STATE":
563 def update_wm_state(prop):
564 current = self.get_property(prop)
565 mode = event.data[0]
566 if mode==_NET_WM_STATE_ADD:
567 v = True
568 elif mode==_NET_WM_STATE_REMOVE:
569 v = False
570 elif mode==_NET_WM_STATE_TOGGLE:
571 v = not bool(current)
572 else:
573 log.warn("Warning: invalid mode for _NET_WM_STATE: %s", mode)
574 return
575 log("process_client_message_event(%s) window %s=%s after %s (current state=%s)",
576 event, prop, v, STATE_STRING.get(mode, mode), current)
577 if v!=current:
578 self.update_wm_state(prop, v)
579 atom1 = get_pyatom(event.window, event.data[1])
580 log("_NET_WM_STATE: %s", atom1)
581 if atom1=="_NET_WM_STATE_FULLSCREEN":
582 update_wm_state("fullscreen")
583 elif atom1=="_NET_WM_STATE_ABOVE":
584 update_wm_state("above")
585 elif atom1=="_NET_WM_STATE_BELOW":
586 update_wm_state("below")
587 elif atom1=="_NET_WM_STATE_SHADED":
588 update_wm_state("shaded")
589 elif atom1=="_NET_WM_STATE_STICKY":
590 update_wm_state("sticky")
591 elif atom1=="_NET_WM_STATE_SKIP_TASKBAR":
592 update_wm_state("skip-taskbar")
593 elif atom1=="_NET_WM_STATE_SKIP_PAGER":
594 update_wm_state("skip-pager")
595 get_pyatom(event.window, event.data[2])
596 elif atom1 in ("_NET_WM_STATE_MAXIMIZED_VERT", "_NET_WM_STATE_MAXIMIZED_HORZ"):
597 atom2 = get_pyatom(event.window, event.data[2])
598 #we only have one state for both, so we require both to be set:
599 if atom1!=atom2 and atom2 in ("_NET_WM_STATE_MAXIMIZED_VERT", "_NET_WM_STATE_MAXIMIZED_HORZ"):
600 update_wm_state("maximized")
601 elif atom1=="_NET_WM_STATE_HIDDEN":
602 log("ignoring 'HIDDEN' _NET_WM_STATE: %s", event)
603 #we don't honour those because they make little sense, see:
604 #https://mail.gnome.org/archives/wm-spec-list/2005-May/msg00004.html
605 elif atom1=="_NET_WM_STATE_MODAL":
606 update_wm_state("modal")
607 elif atom1=="_NET_WM_STATE_DEMANDS_ATTENTION":
608 update_wm_state("attention-requested")
609 else:
610 log.info("Unhandled _NET_WM_STATE request: '%s'", event, atom1)
611 log.info(" event%s", event)
612 return True
613 if event.message_type=="WM_CHANGE_STATE":
614 iconic = event.data[0]
615 log("WM_CHANGE_STATE: %s, serial=%s, last unmap serial=%#x",
616 ICONIC_STATE_STRING.get(iconic, iconic), event.serial, self.last_unmap_serial)
617 if (
618 iconic in (IconicState, NormalState) and
619 self.serial_after_last_unmap(event.serial) and
620 not self.is_OR() and not self.is_tray()
621 ):
622 self._updateprop("iconic", iconic==IconicState)
623 return True
624 if event.message_type=="_NET_WM_MOVERESIZE":
625 log("_NET_WM_MOVERESIZE: %s", event)
626 self.emit("initiate-moveresize", event)
627 return True
628 if event.message_type=="_NET_ACTIVE_WINDOW":
629 #to filter based on the source indication:
630 #ACTIVE_WINDOW_SOURCE = tuple(int(x) for x in os.environ.get("XPRA_ACTIVE_WINDOW_SOURCE", "0,1").split(","))
631 #if event.data[0] in ACTIVE_WINDOW_SOURCE:
632 log("_NET_ACTIVE_WINDOW: %s", event)
633 self.set_active()
634 self.emit("restack", Above, None)
635 return True
636 if event.message_type=="_NET_WM_DESKTOP":
637 workspace = int(event.data[0])
638 #query the workspace count on the root window
639 #since we cannot access Wm from here..
640 root = self.client_window.get_screen().get_root_window()
641 ndesktops = prop_get(root, "_NET_NUMBER_OF_DESKTOPS", "u32", ignore_errors=True)
642 workspacelog("received _NET_WM_DESKTOP: workspace=%s, number of desktops=%s",
643 workspacestr(workspace), ndesktops)
644 if ndesktops>0 and (
645 workspace in (WORKSPACE_UNSET, WORKSPACE_ALL) or
646 0<=workspace<ndesktops
647 ):
648 self.move_to_workspace(workspace)
649 else:
650 workspacelog.warn("invalid _NET_WM_DESKTOP request: workspace=%s, number of desktops=%s",
651 workspacestr(workspace), ndesktops)
652 return True
653 if event.message_type=="_NET_WM_FULLSCREEN_MONITORS":
654 log("_NET_WM_FULLSCREEN_MONITORS: %s", event)
655 #TODO: we should validate the indexes instead of copying them blindly!
656 #TODO: keep track of source indication so we can forward that to the client
657 m1, m2, m3, m4 = event.data[0], event.data[1], event.data[2], event.data[3]
658 N = 16 #FIXME: arbitrary limit
659 if m1<0 or m1>=N or m2<0 or m2>=N or m3<0 or m3>=N or m4<0 or m4>=N:
660 log.warn("invalid list of _NET_WM_FULLSCREEN_MONITORS - ignored")
661 return False
662 monitors = [m1, m2, m3, m4]
663 log("_NET_WM_FULLSCREEN_MONITORS: monitors=%s", monitors)
664 prop_set(self.client_window, "_NET_WM_FULLSCREEN_MONITORS", ["u32"], monitors)
665 return True
666 if event.message_type=="_NET_RESTACK_WINDOW":
667 source = {1 : "application", 2 : "pager"}.get(event.data[0], "default (%s)" % event.data[0])
668 sibling_window = event.data[1]
669 log("%s sent to window %#x for sibling %#x from %s with detail=%s",
670 event.message_type, event.window, sibling_window, source, RESTACKING_STR.get(event.detail, event.detail))
671 self.emit("restack", event.detail, sibling_window)
672 return True
673 #TODO: maybe we should process _NET_MOVERESIZE_WINDOW here?
674 # it may make sense to apply it to the client_window
675 # whereas the code in WindowModel assumes there is a corral window
676 #not handled:
677 return super().process_client_message_event(event)