Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/x11/models/core.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# This file is part of Xpra.
2# Copyright (C) 2008, 2009 Nathaniel Smith <njs@pobox.com>
3# Copyright (C) 2011-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.
7import os
8import signal
9from gi.repository import GObject, Gdk, GLib
11from xpra.util import envbool, first_time
12from xpra.os_util import bytestostr
13from xpra.x11.common import Unmanageable
14from xpra.gtk_common.gobject_util import one_arg_signal, two_arg_signal
15from xpra.gtk_common.error import XError, xsync, xswallow
16from xpra.x11.bindings.window_bindings import X11WindowBindings, constants, SHAPE_KIND #@UnresolvedImport
17from xpra.x11.models.model_stub import WindowModelStub
18from xpra.x11.gtk_x11.composite import CompositeHelper
19from xpra.x11.gtk_x11.prop import prop_get, prop_set, prop_type_get, PYTHON_TYPES
20from xpra.x11.gtk_x11.send_wm import send_wm_delete_window
21from xpra.x11.gtk_x11.gdk_bindings import add_event_receiver, remove_event_receiver
22from xpra.log import Logger
24log = Logger("x11", "window")
25metalog = Logger("x11", "window", "metadata")
26shapelog = Logger("x11", "window", "shape")
27grablog = Logger("x11", "window", "grab")
28framelog = Logger("x11", "window", "frame")
29geomlog = Logger("x11", "window", "geometry")
32X11Window = X11WindowBindings()
33em = Gdk.EventMask
34ADDMASK = em.STRUCTURE_MASK | em.PROPERTY_CHANGE_MASK | em.FOCUS_CHANGE_MASK | em.POINTER_MOTION_MASK
36FORCE_QUIT = envbool("XPRA_FORCE_QUIT", True)
37XSHAPE = envbool("XPRA_XSHAPE", True)
38FRAME_EXTENTS = envbool("XPRA_FRAME_EXTENTS", True)
40# Re-stacking:
41Above = 0
42Below = 1
43TopIf = 2
44BottomIf = 3
45Opposite = 4
46RESTACKING_STR = {
47 Above : "Above",
48 Below : "Below",
49 TopIf : "TopIf",
50 BottomIf : "BottomIf",
51 Opposite : "Opposite",
52 }
54# grab stuff:
55NotifyNormal = constants["NotifyNormal"]
56NotifyGrab = constants["NotifyGrab"]
57NotifyUngrab = constants["NotifyUngrab"]
58NotifyWhileGrabbed = constants["NotifyWhileGrabbed"]
59NotifyNonlinearVirtual = constants["NotifyNonlinearVirtual"]
60GRAB_CONSTANTS = {
61 NotifyNormal : "NotifyNormal",
62 NotifyGrab : "NotifyGrab",
63 NotifyUngrab : "NotifyUngrab",
64 NotifyWhileGrabbed : "NotifyWhileGrabbed",
65 }
66DETAIL_CONSTANTS = {}
67for dconst in (
68 "NotifyAncestor", "NotifyVirtual", "NotifyInferior",
69 "NotifyNonlinear", "NotifyNonlinearVirtual", "NotifyPointer",
70 "NotifyPointerRoot", "NotifyDetailNone",
71 ):
72 DETAIL_CONSTANTS[constants[dconst]] = dconst
73grablog("pointer grab constants: %s", GRAB_CONSTANTS)
74grablog("detail constants: %s", DETAIL_CONSTANTS)
76#these properties are not handled, and we don't want to spam the log file
77#whenever an app decides to change them:
78PROPERTIES_IGNORED = os.environ.get("XPRA_X11_PROPERTIES_IGNORED", "_NET_WM_OPAQUE_REGION").split(",")
79#make it easier to debug property changes, just add them here:
80#ie: {"WM_PROTOCOLS" : ["atom"]}
81X11_PROPERTIES_DEBUG = {}
82PROPERTIES_DEBUG = [prop_debug.strip()
83 for prop_debug in os.environ.get("XPRA_WINDOW_PROPERTIES_DEBUG", "").split(",")]
84X11PROPERTY_SYNC = envbool("XPRA_X11PROPERTY_SYNC", True)
85X11PROPERTY_SYNC_BLACKLIST = os.environ.get("XPRA_X11PROPERTY_SYNC_BLACKLIST",
86 "_GTK,WM_,_NET,Xdnd").split(",")
89def sanestr(s):
90 return (s or "").strip("\0").replace("\0", " ")
93class CoreX11WindowModel(WindowModelStub):
94 """
95 The utility superclass for all GTK2 / X11 window models,
96 it wraps an X11 window (the "client-window").
97 Defines the common properties and signals,
98 sets up the composite helper so we get the damage events.
99 The x11_property_handlers sync X11 window properties into Python objects,
100 the py_property_handlers do it in the other direction.
101 """
102 __common_properties__ = {
103 #the actual X11 client window
104 "client-window": (GObject.TYPE_PYOBJECT,
105 "gtk.gdk.Window representing the client toplevel", "",
106 GObject.ParamFlags.READABLE),
107 #the X11 window id
108 "xid": (GObject.TYPE_INT,
109 "X11 window id", "",
110 -1, 65535, -1,
111 GObject.ParamFlags.READABLE),
112 #FIXME: this is an ugly virtual property
113 "geometry": (GObject.TYPE_PYOBJECT,
114 "current coordinates (x, y, w, h, border) for the window", "",
115 GObject.ParamFlags.READABLE),
116 #bits per pixel
117 "depth": (GObject.TYPE_INT,
118 "window bit depth", "",
119 -1, 64, -1,
120 GObject.ParamFlags.READABLE),
121 #if the window depth is 32 bit
122 "has-alpha": (GObject.TYPE_BOOLEAN,
123 "Does the window use transparency", "",
124 False,
125 GObject.ParamFlags.READABLE),
126 #from WM_CLIENT_MACHINE
127 "client-machine": (GObject.TYPE_PYOBJECT,
128 "Host where client process is running", "",
129 GObject.ParamFlags.READABLE),
130 #from _NET_WM_PID
131 "pid": (GObject.TYPE_INT,
132 "PID of owning process", "",
133 -1, 65535, -1,
134 GObject.ParamFlags.READABLE),
135 #from _NET_WM_NAME or WM_NAME
136 "title": (GObject.TYPE_PYOBJECT,
137 "Window title (unicode or None)", "",
138 GObject.ParamFlags.READABLE),
139 #from WM_WINDOW_ROLE
140 "role" : (GObject.TYPE_PYOBJECT,
141 "The window's role (ICCCM session management)", "",
142 GObject.ParamFlags.READABLE),
143 #from WM_PROTOCOLS via XGetWMProtocols
144 "protocols": (GObject.TYPE_PYOBJECT,
145 "Supported WM protocols", "",
146 GObject.ParamFlags.READABLE),
147 #from WM_COMMAND
148 "command": (GObject.TYPE_PYOBJECT,
149 "Command used to start or restart the client", "",
150 GObject.ParamFlags.READABLE),
151 #from WM_CLASS via getClassHint
152 "class-instance": (GObject.TYPE_PYOBJECT,
153 "Classic X 'class' and 'instance'", "",
154 GObject.ParamFlags.READABLE),
155 #ShapeNotify events will populate this using XShapeQueryExtents
156 "shape": (GObject.TYPE_PYOBJECT,
157 "Window XShape data", "",
158 GObject.ParamFlags.READABLE),
159 #synced to "_NET_FRAME_EXTENTS"
160 "frame": (GObject.TYPE_PYOBJECT,
161 "Size of the window frame, as per _NET_FRAME_EXTENTS", "",
162 GObject.ParamFlags.READWRITE),
163 #synced to "_NET_WM_ALLOWED_ACTIONS"
164 "allowed-actions": (GObject.TYPE_PYOBJECT,
165 "Supported WM actions", "",
166 GObject.ParamFlags.READWRITE),
167 }
169 __common_signals__ = {
170 #signals we emit:
171 "unmanaged" : one_arg_signal,
172 "restack" : two_arg_signal,
173 "initiate-moveresize" : one_arg_signal,
174 "grab" : one_arg_signal,
175 "ungrab" : one_arg_signal,
176 "bell" : one_arg_signal,
177 "client-contents-changed" : one_arg_signal,
178 "motion" : one_arg_signal,
179 #x11 events we catch (and often re-emit as something else):
180 "xpra-property-notify-event" : one_arg_signal,
181 "xpra-xkb-event" : one_arg_signal,
182 "xpra-shape-event" : one_arg_signal,
183 "xpra-configure-event" : one_arg_signal,
184 "xpra-unmap-event" : one_arg_signal,
185 "xpra-client-message-event" : one_arg_signal,
186 "xpra-focus-in-event" : one_arg_signal,
187 "xpra-focus-out-event" : one_arg_signal,
188 "xpra-motion-event" : one_arg_signal,
189 "x11-property-changed" : one_arg_signal,
190 }
192 #things that we expose:
193 _property_names = [
194 "xid", "depth", "has-alpha",
195 "client-machine", "pid",
196 "title", "role",
197 "command", "shape",
198 "class-instance", "protocols",
199 ]
200 #exposed and changing (should be watched for notify signals):
201 _dynamic_property_names = ["title", "command", "shape", "class-instance", "protocols"]
202 #should not be exported to the clients:
203 _internal_property_names = ["frame", "allowed-actions"]
204 _initial_x11_properties = ["_NET_WM_PID", "WM_CLIENT_MACHINE",
205 #_NET_WM_NAME is redundant, as it calls the same handler as "WM_NAME"
206 "WM_NAME", "_NET_WM_NAME",
207 "WM_PROTOCOLS", "WM_CLASS", "WM_WINDOW_ROLE"]
208 _DEFAULT_NET_WM_ALLOWED_ACTIONS = []
209 _MODELTYPE = "Core"
210 _scrub_x11_properties = [
211 "WM_STATE",
212 #"_NET_WM_STATE", # "..it should leave the property in place when it is shutting down"
213 "_NET_FRAME_EXTENTS", "_NET_WM_ALLOWED_ACTIONS"]
215 def __init__(self, client_window):
216 super().__init__()
217 self.xid = client_window.get_xid()
218 log("new window %#x", self.xid)
219 self.client_window = client_window
220 self.client_window_saved_events = self.client_window.get_events()
221 self._composite = None
222 self._damage_forward_handle = None
223 self._setup_done = False
224 self._kill_count = 0
225 self._internal_set_property("client-window", client_window)
228 def __repr__(self):
229 try:
230 return "%s(%#x)" % (type(self).__name__, self.xid)
231 except AttributeError:
232 return repr(self)
235 #########################################
236 # Setup and teardown
237 #########################################
239 def call_setup(self):
240 """
241 Call this method to prepare the window:
242 * makes sure it still exists
243 (by querying its geometry which may raise an XError)
244 * setup composite redirection
245 * calls setup
246 The difficulty comes from X11 errors and synchronization:
247 we want to catch errors and undo what we've done.
248 The mix of GTK and pure-X11 calls is not helping.
249 """
250 try:
251 with xsync:
252 geom = X11Window.geometry_with_border(self.xid)
253 if geom is None:
254 raise Unmanageable("window %#x disappeared already" % self.xid)
255 self._internal_set_property("geometry", geom[:4])
256 self._read_initial_X11_properties()
257 except XError as e:
258 log("failed to manage %#x", self.xid, exc_info=True)
259 raise Unmanageable(e)
260 add_event_receiver(self.client_window, self)
261 # Keith Packard says that composite state is undefined following a
262 # reparent, so I'm not sure doing this here in the superclass,
263 # before we reparent, actually works... let's wait and see.
264 try:
265 self._composite = CompositeHelper(self.client_window)
266 with xsync:
267 self._composite.setup()
268 if X11Window.displayHasXShape():
269 X11Window.XShapeSelectInput(self.xid)
270 except Exception as e:
271 remove_event_receiver(self.client_window, self)
272 log("%s %#x does not support compositing: %s", self._MODELTYPE, self.xid, e)
273 with xswallow:
274 self._composite.destroy()
275 self._composite = None
276 if isinstance(e, Unmanageable):
277 raise
278 raise Unmanageable(e)
279 #compositing is now enabled,
280 #from now on we must call setup_failed to clean things up
281 self._managed = True
282 try:
283 with xsync:
284 self.setup()
285 except XError as e:
286 log("failed to setup %#x", self.xid, exc_info=True)
287 try:
288 with xsync:
289 self.setup_failed(e)
290 except Exception as ex:
291 log.error("error in cleanup handler: %s", ex)
292 raise Unmanageable(e)
293 self._setup_done = True
295 def setup_failed(self, e):
296 log("cannot manage %s %#x: %s", self._MODELTYPE, self.xid, e)
297 self.do_unmanaged(False)
299 def setup(self):
300 # Start listening for important events.
301 X11Window.addDefaultEvents(self.xid)
302 self._damage_forward_handle = self._composite.connect("contents-changed", self._forward_contents_changed)
303 self._setup_property_sync()
306 def unmanage(self, exiting=False):
307 if self._managed:
308 self.emit("unmanaged", exiting)
310 def do_unmanaged(self, wm_exiting):
311 if not self._managed:
312 return
313 self._managed = False
314 log("%s.do_unmanaged(%s) damage_forward_handle=%s, composite=%s",
315 self._MODELTYPE, wm_exiting, self._damage_forward_handle, self._composite)
316 remove_event_receiver(self.client_window, self)
317 GLib.idle_add(self.managed_disconnect)
318 if self._composite:
319 if self._damage_forward_handle:
320 self._composite.disconnect(self._damage_forward_handle)
321 self._damage_forward_handle = None
322 self._composite.destroy()
323 self._composite = None
324 self._scrub_x11()
327 #########################################
328 # Damage / Composite
329 #########################################
331 def acknowledge_changes(self):
332 c = self._composite
333 assert c, "composite window destroyed outside the UI thread?"
334 c.acknowledge_changes()
336 def _forward_contents_changed(self, _obj, event):
337 if self._managed:
338 self.emit("client-contents-changed", event)
340 def uses_XShm(self) -> bool:
341 c = self._composite
342 return c and c.has_xshm()
344 def get_image(self, x, y, width, height):
345 return self._composite.get_image(x, y, width, height)
348 def _setup_property_sync(self):
349 metalog("setup_property_sync()")
350 #python properties which trigger an X11 property to be updated:
351 for prop, cb in self._py_property_handlers.items():
352 self.connect("notify::%s" % prop, cb)
353 #initial sync:
354 for cb in self._py_property_handlers.values():
355 cb(self)
356 #this one is special, and overriden in BaseWindow too:
357 self.managed_connect("notify::protocols", self._update_can_focus)
359 def _update_can_focus(self, *_args):
360 can_focus = "WM_TAKE_FOCUS" in self.get_property("protocols")
361 self._updateprop("can-focus", can_focus)
363 def _read_initial_X11_properties(self):
364 """ This is called within an XSync context,
365 so that X11 calls can raise XErrors,
366 pure GTK calls are not allowed. (they would trap the X11 error and crash!)
367 Calling _updateprop is safe, because setup has not completed yet,
368 so the property update will not fire notify()
369 """
370 metalog("read_initial_X11_properties() core")
371 #immutable ones:
372 depth = X11Window.get_depth(self.xid)
373 metalog("initial X11 properties: xid=%#x, depth=%i", self.xid, depth)
374 self._updateprop("depth", depth)
375 self._updateprop("xid", self.xid)
376 self._updateprop("has-alpha", depth==32)
377 self._updateprop("allowed-actions", self._DEFAULT_NET_WM_ALLOWED_ACTIONS)
378 self._updateprop("shape", self._read_xshape())
379 #note: some of those are technically mutable,
380 #but we don't export them as "dynamic" properties, so this won't be propagated
381 #maybe we want to catch errors parsing _NET_WM_ICON ?
382 metalog("initial X11_properties: querying %s", self._initial_x11_properties)
383 #to make sure we don't call the same handler twice which is pointless
384 #(the same handler may handle more than one X11 property)
385 handlers = set()
386 for mutable in self._initial_x11_properties:
387 handler = self._x11_property_handlers.get(mutable)
388 if not handler:
389 log.error("BUG: unknown initial X11 property: %s", mutable)
390 elif handler not in handlers:
391 handlers.add(handler)
392 try:
393 handler(self)
394 except XError:
395 log("handler %s failed", handler, exc_info=True)
396 #these will be caught in call_setup()
397 raise
398 except Exception:
399 #try to continue:
400 log.error("Error parsing initial property '%s':", mutable, exc_info=True)
402 def _scrub_x11(self):
403 metalog("scrub_x11() x11 properties=%s", self._scrub_x11_properties)
404 if not self._scrub_x11_properties:
405 return
406 with xswallow:
407 for prop in self._scrub_x11_properties:
408 X11Window.XDeleteProperty(self.xid, prop)
411 #########################################
412 # XShape
413 #########################################
415 def _read_xshape(self, x=0, y=0):
416 if not X11Window.displayHasXShape() or not XSHAPE:
417 return {}
418 extents = X11Window.XShapeQueryExtents(self.xid)
419 if not extents:
420 shapelog("read_shape for window %#x: no extents", self.xid)
421 return {}
422 #w,h = X11Window.getGeometry(xid)[2:4]
423 shapelog("read_shape for window %#x: extents=%s", self.xid, extents)
424 bextents = extents[0]
425 cextents = extents[1]
426 if bextents[0]==0 and cextents[0]==0:
427 shapelog("read_shape for window %#x: none enabled", self.xid)
428 return {}
429 v = {
430 "x" : x,
431 "y" : y,
432 "Bounding.extents" : bextents,
433 "Clip.extents" : cextents,
434 }
435 for kind, kind_name in SHAPE_KIND.items(): # @UndefinedVariable
436 rectangles = X11Window.XShapeGetRectangles(self.xid, kind)
437 v[kind_name+".rectangles"] = rectangles
438 shapelog("_read_shape()=%s", v)
439 return v
442 ################################
443 # Property reading
444 ################################
446 def get_dimensions(self):
447 #just extracts the size from the geometry:
448 return self.get_property("geometry")[2:4]
450 def get_geometry(self):
451 return self.get_property("geometry")[:4]
454 #########################################
455 # Python objects synced to X11 properties
456 #########################################
458 def prop_set(self, key, ptype, value):
459 prop_set(self.client_window, key, ptype, value)
462 def _sync_allowed_actions(self, *_args):
463 actions = self.get_property("allowed-actions") or []
464 metalog("sync_allowed_actions: setting _NET_WM_ALLOWED_ACTIONS=%s on %#x", actions, self.xid)
465 with xswallow:
466 prop_set(self.client_window, "_NET_WM_ALLOWED_ACTIONS", ["atom"], actions)
467 def _handle_frame_changed(self, *_args):
468 #legacy name for _sync_frame() called from Wm
469 self._sync_frame()
470 def _sync_frame(self, *_args):
471 if not FRAME_EXTENTS:
472 return
473 v = self.get_property("frame")
474 framelog("sync_frame: frame(%#x)=%s", self.xid, v)
475 if not v and (not self.is_OR() and not self.is_tray()):
476 root = self.client_window.get_screen().get_root_window()
477 v = prop_get(root, "DEFAULT_NET_FRAME_EXTENTS", ["u32"], ignore_errors=True)
478 if not v:
479 #default for OR, or if we don't have any other value:
480 v = (0, 0, 0, 0)
481 framelog("sync_frame: setting _NET_FRAME_EXTENTS=%s on %#x", v, self.xid)
482 with xswallow:
483 prop_set(self.client_window, "_NET_FRAME_EXTENTS", ["u32"], v)
485 _py_property_handlers = {
486 "allowed-actions" : _sync_allowed_actions,
487 "frame" : _sync_frame,
488 }
491 #########################################
492 # X11 properties synced to Python objects
493 #########################################
495 def prop_get(self, key, ptype, ignore_errors=None, raise_xerrors=False):
496 """
497 Get an X11 property from the client window,
498 using the automatic type conversion code from prop.py
499 Ignores property errors during setup_client.
500 """
501 if ignore_errors is None and (not self._setup_done or not self._managed):
502 ignore_errors = True
503 return prop_get(self.client_window, key, ptype, ignore_errors=bool(ignore_errors), raise_xerrors=raise_xerrors)
506 def do_xpra_property_notify_event(self, event):
507 #X11: PropertyNotify
508 assert event.window is self.client_window
509 self._handle_property_change(str(event.atom))
511 def _handle_property_change(self, name):
512 #ie: _handle_property_change("_NET_WM_NAME")
513 metalog("Property changed on %#x: %s", self.xid, name)
514 x11proptype = X11_PROPERTIES_DEBUG.get(name)
515 if x11proptype is not None:
516 metalog.info("%s=%s", name, self.prop_get(name, x11proptype, True, False))
517 if name in PROPERTIES_IGNORED:
518 return
519 if X11PROPERTY_SYNC and not any (name.startswith(x) for x in X11PROPERTY_SYNC_BLACKLIST):
520 try:
521 with xsync:
522 prop_type = prop_type_get(self.client_window, name)
523 metalog("_handle_property_change(%s) property type=%s", name, prop_type)
524 if prop_type:
525 dtype, dformat = prop_type
526 ptype = PYTHON_TYPES.get(bytestostr(dtype))
527 if ptype:
528 value = self.prop_get(name, ptype, ignore_errors=True)
529 if value is None:
530 #retry using scalar type:
531 value = self.prop_get(name, (ptype,), ignore_errors=True)
532 metalog("_handle_property_change(%s) value=%s", name, value)
533 if value:
534 self.emit("x11-property-changed", (name, ptype, dformat, value))
535 return
536 except Exception:
537 metalog("_handle_property_change(%s)", name, exc_info=True)
538 self.emit("x11-property-changed", (name, "", 0, ""))
539 handler = self._x11_property_handlers.get(name)
540 if handler:
541 try:
542 with xsync:
543 handler(self)
544 except XError as e:
545 log("_handle_property_change", exc_info=True)
546 log.error("Error processing property change for '%s'", name)
547 log.error(" on window %#x", self.xid)
548 log.error(" %s", e)
550 #specific properties:
551 def _handle_pid_change(self):
552 pid = self.prop_get("_NET_WM_PID", "u32") or -1
553 metalog("_NET_WM_PID=%s", pid)
554 self._updateprop("pid", pid)
556 def _handle_client_machine_change(self):
557 client_machine = self.prop_get("WM_CLIENT_MACHINE", "latin1")
558 metalog("WM_CLIENT_MACHINE=%s", client_machine)
559 self._updateprop("client-machine", client_machine)
561 def _handle_wm_name_change(self):
562 name = self.prop_get("_NET_WM_NAME", "utf8", True)
563 metalog("_NET_WM_NAME=%s", name)
564 if name is None:
565 name = self.prop_get("WM_NAME", "latin1", True)
566 metalog("WM_NAME=%s", name)
567 if self._updateprop("title", sanestr(name)):
568 metalog("wm_name changed")
570 def _handle_role_change(self):
571 role = self.prop_get("WM_WINDOW_ROLE", "latin1")
572 metalog("WM_WINDOW_ROLE=%s", role)
573 self._updateprop("role", role)
575 def _handle_protocols_change(self):
576 with xsync:
577 protocols = X11Window.XGetWMProtocols(self.xid)
578 metalog("WM_PROTOCOLS=%s", protocols)
579 self._updateprop("protocols", protocols)
581 def _handle_command_change(self):
582 command = self.prop_get("WM_COMMAND", "latin1")
583 metalog("WM_COMMAND=%s", command)
584 if command:
585 command = command.strip("\0")
586 self._updateprop("command", command)
588 def _handle_class_change(self):
589 class_instance = X11Window.getClassHint(self.xid)
590 if class_instance:
591 class_instance = tuple(v.decode("latin1") for v in class_instance)
592 metalog("WM_CLASS=%s", class_instance)
593 self._updateprop("class-instance", class_instance)
595 #these handlers must not generate X11 errors (must use XSync)
596 _x11_property_handlers = {
597 "_NET_WM_PID" : _handle_pid_change,
598 "WM_CLIENT_MACHINE" : _handle_client_machine_change,
599 "WM_NAME" : _handle_wm_name_change,
600 "_NET_WM_NAME" : _handle_wm_name_change,
601 "WM_WINDOW_ROLE" : _handle_role_change,
602 "WM_PROTOCOLS" : _handle_protocols_change,
603 "WM_COMMAND" : _handle_command_change,
604 "WM_CLASS" : _handle_class_change,
605 }
608 #########################################
609 # X11 Events
610 #########################################
612 def do_xpra_unmap_event(self, _event):
613 self.unmanage()
615 def do_xpra_destroy_event(self, event):
616 if event.delivered_to is self.client_window:
617 # This is somewhat redundant with the unmap signal, because if you
618 # destroy a mapped window, then a UnmapNotify is always generated.
619 # However, this allows us to catch the destruction of unmapped
620 # ("iconified") windows, and also catch any mistakes we might have
621 # made with unmap heuristics. I love the smell of XDestroyWindow in
622 # the morning. It makes for simple code:
623 self.unmanage()
626 def process_client_message_event(self, event):
627 # FIXME
628 # Need to listen for:
629 # _NET_CURRENT_DESKTOP
630 # _NET_WM_PING responses
631 # and maybe:
632 # _NET_RESTACK_WINDOW
633 # _NET_WM_STATE (more fully)
634 if event.message_type=="_NET_CLOSE_WINDOW":
635 log.info("_NET_CLOSE_WINDOW received by %s", self)
636 self.request_close()
637 return True
638 if event.message_type=="_NET_REQUEST_FRAME_EXTENTS":
639 framelog("_NET_REQUEST_FRAME_EXTENTS")
640 self._handle_frame_changed()
641 return True
642 if event.message_type=="_NET_MOVERESIZE_WINDOW":
643 #this is overriden in WindowModel, skipped everywhere else:
644 geomlog("_NET_MOVERESIZE_WINDOW skipped on %s (data=%s)", self, event.data)
645 return True
646 if event.message_type=="":
647 log("empty message type: %s", event)
648 if first_time("empty-x11-window-message-type-%#x" % event.window.get_xid()):
649 log.warn("Warning: empty message type received for window %#x:", event.window.get_xid())
650 log.warn(" %s", event)
651 log.warn(" further messages will be silently ignored")
652 return True
653 #not handled:
654 return False
656 def do_xpra_configure_event(self, event):
657 if self.client_window is None or not self._managed:
658 return
659 #shouldn't the border width always be 0?
660 geom = (event.x, event.y, event.width, event.height)
661 geomlog("CoreX11WindowModel.do_xpra_configure_event(%s) client_window=%#x, new geometry=%s",
662 event, self.xid, geom)
663 self._updateprop("geometry", geom)
666 def do_xpra_shape_event(self, event):
667 shapelog("shape event: %s, kind=%s", event, SHAPE_KIND.get(event.kind, event.kind)) # @UndefinedVariable
668 cur_shape = self.get_property("shape")
669 if cur_shape and cur_shape.get("serial", 0)>=event.serial:
670 shapelog("same or older xshape serial no: %#x (current=%#x)", event.serial, cur_shape.get("serial", 0))
671 return
672 #remove serial before comparing dicts:
673 cur_shape.pop("serial", None)
674 #read new xshape:
675 with xswallow:
676 #should we pass the x and y offsets here?
677 #v = self._read_xshape(event.x, event.y)
678 if event.shaped:
679 v = self._read_xshape()
680 else:
681 v = {}
682 if cur_shape==v:
683 shapelog("xshape unchanged")
684 return
685 v["serial"] = int(event.serial)
686 shapelog("xshape updated with serial %#x", event.serial)
687 self._internal_set_property("shape", v)
690 def do_xpra_xkb_event(self, event):
691 #X11: XKBNotify
692 log("WindowModel.do_xpra_xkb_event(%r)" % event)
693 if event.subtype!="bell":
694 log.error("WindowModel.do_xpra_xkb_event(%r) unknown event type: %s" % (event, event.type))
695 return
696 event.window_model = self
697 self.emit("bell", event)
699 def do_xpra_client_message_event(self, event):
700 #X11: ClientMessage
701 log("do_xpra_client_message_event(%s)", event)
702 if not event.data or len(event.data)!=5:
703 log.warn("invalid event data: %s", event.data)
704 return
705 if not self.process_client_message_event(event):
706 log.warn("do_xpra_client_message_event(%s) not handled", event)
709 def do_xpra_focus_in_event(self, event):
710 #X11: FocusIn
711 grablog("focus_in_event(%s) mode=%s, detail=%s",
712 event, GRAB_CONSTANTS.get(event.mode), DETAIL_CONSTANTS.get(event.detail, event.detail))
713 if event.mode==NotifyNormal and event.detail==NotifyNonlinearVirtual:
714 self.emit("restack", Above, None)
715 else:
716 self.may_emit_grab(event)
718 def do_xpra_focus_out_event(self, event):
719 #X11: FocusOut
720 grablog("focus_out_event(%s) mode=%s, detail=%s",
721 event, GRAB_CONSTANTS.get(event.mode), DETAIL_CONSTANTS.get(event.detail, event.detail))
722 self.may_emit_grab(event)
724 def may_emit_grab(self, event):
725 if event.mode==NotifyGrab:
726 grablog("emitting grab on %s", self)
727 self.emit("grab", event)
728 if event.mode==NotifyUngrab:
729 grablog("emitting ungrab on %s", self)
730 self.emit("ungrab", event)
733 def do_xpra_motion_event(self, event):
734 self.emit("motion", event)
737 ################################
738 # Actions
739 ################################
741 def raise_window(self):
742 X11Window.XRaiseWindow(self.client_window.get_xid())
744 def set_active(self):
745 root = self.client_window.get_screen().get_root_window()
746 prop_set(root, "_NET_ACTIVE_WINDOW", "u32", self.xid)
749 ################################
750 # Killing clients:
751 ################################
753 def request_close(self):
754 if b"WM_DELETE_WINDOW" in self.get_property("protocols"):
755 with xswallow:
756 send_wm_delete_window(self.client_window)
757 else:
758 title = self.get_property("title")
759 xid = self.get_property("xid")
760 if FORCE_QUIT:
761 log.info("window %#x ('%s') does not support WM_DELETE_WINDOW", xid, title)
762 log.info(" using force_quit")
763 # You don't wanna play ball? Then no more Mr. Nice Guy!
764 self.force_quit()
765 else:
766 log.warn("window %#x ('%s') cannot be closed,", xid, title)
767 log.warn(" it does not support WM_DELETE_WINDOW")
768 log.warn(" and FORCE_QUIT is disabled")
770 def force_quit(self):
771 pid = self.get_property("pid")
772 machine = self.get_property("client-machine")
773 from socket import gethostname
774 localhost = gethostname()
775 log("force_quit() pid=%s, machine=%s, localhost=%s", pid, machine, localhost)
776 def XKill():
777 with xswallow:
778 X11Window.XKillClient(self.xid)
779 if pid > 0 and machine is not None and machine == localhost:
780 if pid==os.getpid():
781 log.warn("force_quit() refusing to kill ourselves!")
782 return
783 if self._kill_count==0:
784 #first time around: just send a SIGINT and hope for the best
785 try:
786 os.kill(pid, signal.SIGINT)
787 except OSError:
788 log.warn("failed to kill(SIGINT) client with pid %s", pid)
789 else:
790 #the more brutal way: SIGKILL + XKill
791 try:
792 os.kill(pid, signal.SIGKILL)
793 except OSError:
794 log.warn("failed to kill(SIGKILL) client with pid %s", pid)
795 XKill()
796 self._kill_count += 1
797 return
798 XKill()