Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/x11/gtk_x11/window_damage.py : 48%
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) 2012-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.
8from xpra.util import envbool
9from xpra.gtk_common.gobject_util import one_arg_signal
10from xpra.x11.gtk_x11.gdk_bindings import (
11 add_event_receiver, #@UnresolvedImport
12 remove_event_receiver, #@UnresolvedImport
13 )
14from xpra.gtk_common.error import trap, xsync, xswallow, XError
15from xpra.x11.common import Unmanageable
17from xpra.x11.bindings.ximage import XImageBindings #@UnresolvedImport
18from xpra.x11.bindings.window_bindings import constants, X11WindowBindings #@UnresolvedImport
19from xpra.log import Logger
21log = Logger("x11", "window")
23XImage = XImageBindings()
24X11Window = X11WindowBindings()
25X11Window.ensure_XDamage_support()
28StructureNotifyMask = constants["StructureNotifyMask"]
29USE_XSHM = envbool("XPRA_XSHM", True)
32class WindowDamageHandler:
34 XShmEnabled = USE_XSHM
36 __common_gsignals__ = {
37 "xpra-damage-event" : one_arg_signal,
38 "xpra-unmap-event" : one_arg_signal,
39 "xpra-configure-event" : one_arg_signal,
40 "xpra-reparent-event" : one_arg_signal,
41 }
43 # This may raise XError.
44 def __init__(self, client_window, use_xshm=USE_XSHM):
45 self.client_window = client_window
46 self.xid = client_window.get_xid()
47 log("WindowDamageHandler.__init__(%#x, %s)", self.xid, use_xshm)
48 self._use_xshm = use_xshm
49 self._damage_handle = None
50 self._xshm_handle = None
51 self._contents_handle = None
52 self._border_width = 0
54 def __repr__(self):
55 return "WindowDamageHandler(%#x)" % self.xid
57 def setup(self):
58 self.invalidate_pixmap()
59 geom = X11Window.geometry_with_border(self.xid)
60 if geom is None:
61 raise Unmanageable("window %#x disappeared already" % self.xid)
62 self._border_width = geom[-1]
63 self.create_damage_handle()
64 add_event_receiver(self.client_window, self)
66 def create_damage_handle(self):
67 self._damage_handle = X11Window.XDamageCreate(self.xid)
68 log("damage handle(%#x)=%#x", self.xid, self._damage_handle)
70 def destroy(self):
71 if self.client_window is None:
72 log.warn("damage window handler for %s already cleaned up!", self)
73 return
74 #clear the reference to the window early:
75 win = self.client_window
76 self.client_window = None
77 self.do_destroy(win)
79 def do_destroy(self, win):
80 remove_event_receiver(win, self)
81 self.destroy_damage_handle()
83 def destroy_damage_handle(self):
84 log("close_damage_handle()")
85 self.invalidate_pixmap()
86 dh = self._damage_handle
87 if dh:
88 self._damage_handle = None
89 trap.swallow_synced(X11Window.XDamageDestroy, dh)
90 sh = self._xshm_handle
91 if sh:
92 self._xshm_handle = None
93 sh.cleanup()
94 #note: this should be redundant since we cleared the
95 #reference to self.client_window and shortcut out in do_get_property_contents_handle
96 #but it's cheap anyway
97 self.invalidate_pixmap()
99 def acknowledge_changes(self):
100 sh = self._xshm_handle
101 dh = self._damage_handle
102 log("acknowledge_changes() xshm handle=%s, damage handle=%s", sh, dh)
103 if sh:
104 sh.discard()
105 if dh and self.client_window:
106 #"Synchronously modifies the regions..." so unsynced?
107 if not trap.swallow_synced(X11Window.XDamageSubtract, dh):
108 self.invalidate_pixmap()
110 def invalidate_pixmap(self):
111 ch = self._contents_handle
112 log("invalidating named pixmap, contents handle=%s", ch)
113 if ch:
114 self._contents_handle = None
115 with xswallow:
116 ch.cleanup()
118 def has_xshm(self):
119 return self._use_xshm and WindowDamageHandler.XShmEnabled and XImage.has_XShm()
121 def get_xshm_handle(self):
122 if not self.has_xshm():
123 return None
124 if self._xshm_handle:
125 sw, sh = self._xshm_handle.get_size()
126 ww, wh = self.client_window.get_geometry()[2:4]
127 if sw!=ww or sh!=wh:
128 #size has changed!
129 #make sure the current wrapper gets garbage collected:
130 self._xshm_handle.cleanup()
131 self._xshm_handle = None
132 if self._xshm_handle is None:
133 #make a new one:
134 self._xshm_handle = XImage.get_XShmWrapper(self.xid)
135 if self._xshm_handle is None:
136 #failed (may retry)
137 return None
138 init_ok, retry_window, xshm_failed = self._xshm_handle.setup()
139 if not init_ok:
140 #this handle is not valid, clear it:
141 self._xshm_handle = None
142 if not retry_window:
143 #and it looks like it is not worth re-trying this window:
144 self._use_xshm = False
145 if xshm_failed:
146 log.warn("Warning: disabling XShm support following irrecoverable error")
147 WindowDamageHandler.XShmEnabled = False
148 return self._xshm_handle
150 def _set_pixmap(self):
151 self._contents_handle = XImage.get_xwindow_pixmap_wrapper(self.xid)
153 def get_contents_handle(self):
154 if not self.client_window:
155 #shortcut out
156 return None
157 if self._contents_handle is None:
158 log("refreshing named pixmap")
159 trap.swallow_synced(self._set_pixmap)
160 return self._contents_handle
163 def get_image(self, x, y, width, height):
164 handle = self.get_contents_handle()
165 if handle is None:
166 log("get_image(..) pixmap is None for window %#x", self.xid)
167 return None
169 #try XShm:
170 try:
171 with xsync:
172 shm = self.get_xshm_handle()
173 #log("get_image(..) XShm handle: %s, handle=%s, pixmap=%s", shm, handle, handle.get_pixmap())
174 if shm is not None:
175 shm_image = shm.get_image(handle.get_pixmap(), x, y, width, height)
176 #log("get_image(..) XShm image: %s", shm_image)
177 if shm_image:
178 return shm_image
179 except XError as e:
180 if e.msg.startswith("BadMatch"):
181 log("get_image(%s, %s, %s, %s) get_image BadMatch ignored (window already gone?)", x, y, width, height)
182 else:
183 log.warn("get_image(%s, %s, %s, %s) '%s'", x, y, width, height, e.msg, exc_info=True)
185 try:
186 w = min(handle.get_width(), width)
187 h = min(handle.get_height(), height)
188 if w!=width or h!=height:
189 log("get_image(%s, %s, %s, %s) clamped to pixmap dimensions: %sx%s", x, y, width, height, w, h)
190 with xsync:
191 return handle.get_image(x, y, w, h)
192 except XError as e:
193 if e.msg.startswith("BadMatch"):
194 log("get_image(%s, %s, %s, %s) get_image BadMatch ignored (window already gone?)", x, y, width, height)
195 else:
196 log.warn("get_image(%s, %s, %s, %s) get_image %s", x, y, width, height, e, exc_info=True)
197 return None
200 def do_xpra_damage_event(self, _event):
201 raise NotImplementedError()
203 def do_xpra_reparent_event(self, _event):
204 self.invalidate_pixmap()
206 def xpra_unmap_event(self, _event):
207 self.invalidate_pixmap()
209 def do_xpra_configure_event(self, event):
210 self._border_width = event.border_width
211 self.invalidate_pixmap()