Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/client/client_tray.py : 25%
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) 2010-2019 Antoine Martin <antoine@xpra.org>
3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
4# later version. See the file COPYING for details.
6from time import time
7from gi.repository import GLib
9from xpra.client.client_widget_base import ClientWidgetBase
10from xpra.client.window_backing_base import WindowBackingBase
11from xpra.os_util import memoryview_to_bytes
12from xpra.util import envbool
13from xpra.log import Logger
15log = Logger("tray")
17SAVE = envbool("XPRA_SAVE_SYSTRAY", False)
20class ClientTray(ClientWidgetBase):
21 """
22 This acts like a widget and we use the TrayBacking
23 to capture the tray pixels and forward them
24 to the real tray widget class.
25 """
26 DEFAULT_LOCATION = [0, 0]
27 DEFAULT_SIZE = [64, 64]
28 DEFAULT_GEOMETRY = DEFAULT_LOCATION + DEFAULT_SIZE
30 def __init__(self, client, wid, w, h, metadata, tray_widget, mmap_enabled, mmap_area):
31 log("ClientTray%s", (client, wid, w, h, tray_widget, mmap_enabled, mmap_area))
32 super().__init__(client, 0, wid, True)
33 self._metadata = metadata
34 self.title = metadata.strget("title", "")
35 self.tray_widget = tray_widget
36 self._geometry = None
37 self._window_alpha = True
38 self.group_leader = None
40 self.mmap_enabled = mmap_enabled
41 self.mmap = mmap_area
42 self._backing = None
43 self.new_backing(w, h)
44 self.idle_add(self.reconfigure)
45 #things may have settled by now
46 self.timeout_add(1000, self.send_configure)
48 def set_alpha(self):
49 #nothing to do,
50 #trays aren't really windows and transparency is always supported
51 pass
53 def get_backing_class(self):
54 return TrayBacking
56 def is_OR(self):
57 return True
59 def is_tray(self):
60 return True
62 def get_window(self):
63 return None
65 def get_geometry(self):
66 return self._geometry or ClientTray.DEFAULT_GEOMETRY
68 def get_tray_geometry(self):
69 tw = self.tray_widget
70 if not tw:
71 return None
72 return tw.get_geometry()
74 def get_tray_size(self):
75 tw = self.tray_widget
76 if not tw:
77 return None
78 return tw.get_size()
81 def freeze(self):
82 pass
85 def send_configure(self):
86 self.reconfigure(True)
88 def reconfigure(self, force_send_configure=False):
89 geometry = None
90 tw = self.tray_widget
91 if tw:
92 geometry = tw.get_geometry()
93 log("%s.reconfigure(%s) geometry=%s", self, force_send_configure, geometry)
94 if geometry is None:
95 if self._geometry or not tw:
96 geometry = self.get_geometry()
97 else:
98 #make one up as best we can - maybe we have the size at least?
99 size = tw.get_size()
100 log("%s.reconfigure() guessing location using size=%s", self, size)
101 geometry = ClientTray.DEFAULT_LOCATION + list(size or ClientTray.DEFAULT_SIZE)
102 x, y, w, h = geometry
103 if w<=1 or h<=1:
104 w, h = ClientTray.DEFAULT_SIZE
105 geometry = x, y, w, h
106 if force_send_configure or self._geometry is None or geometry!=self._geometry:
107 self._geometry = geometry
108 client_properties = {
109 "encoding.transparency" : True,
110 "encodings.rgb_formats" : ["RGBA", "RGB", "RGBX"],
111 }
112 if tw:
113 orientation = tw.get_orientation()
114 if orientation:
115 client_properties["orientation"] = orientation
116 #scale to server coordinates
117 sx, sy, sw, sh = self._client.crect(x, y, w, h)
118 log("%s.reconfigure(%s) sending configure for geometry=%s : %s",
119 self, force_send_configure, geometry, (sx, sy, sw, sh, client_properties))
120 self._client.send("configure-window", self._id, sx, sy, sw, sh, client_properties)
121 if self._size!=(w, h):
122 self.new_backing(w, h)
124 def move_resize(self, x, y, w, h):
125 log("%s.move_resize(%s, %s, %s, %s)", self, x, y, w, h)
126 w = max(1, w)
127 h = max(1, h)
128 self._geometry = x, y, w, h
129 self.reconfigure(True)
131 def new_backing(self, w, h):
132 self._size = w, h
133 data = None
134 if self._backing:
135 data = self._backing.data
136 self._backing = TrayBacking(self._id, w, h, self._has_alpha, data)
137 if self.mmap_enabled:
138 self._backing.enable_mmap(self.mmap)
140 def update_metadata(self, metadata):
141 log("%s.update_metadata(%s)", self, metadata)
143 def update_icon(self, img):
144 #this is the window icon... not the tray icon!
145 pass
148 def draw_region(self, x, y, width, height, coding, img_data, rowstride, packet_sequence, options, callbacks):
149 log("%s.draw_region%s", self,
150 (x, y, width, height, coding, "%s bytes" % len(img_data), rowstride, packet_sequence, options, callbacks))
152 #note: a new backing may be assigned between the time we call draw_region
153 # and the time we get the callback (as the draw may use idle_add)
154 backing = self._backing
155 def after_draw_update_tray(success, message=None):
156 log("%s.after_draw_update_tray(%s, %s)", self, success, message)
157 if not success:
158 log.warn("after_draw_update_tray(%s, %s) options=%s", success, message, options)
159 return
160 tray_data = backing.data
161 log("tray backing=%s, data: %s", backing, tray_data is not None)
162 if tray_data is None:
163 log.warn("Warning: no pixel data in tray backing for window %i", backing.wid)
164 return
165 self.idle_add(self.set_tray_icon, tray_data)
166 self.idle_add(self.reconfigure)
167 callbacks.append(after_draw_update_tray)
168 backing.draw_region(x, y, width, height, coding, img_data, rowstride, options, callbacks)
170 def set_tray_icon(self, tray_data):
171 enc, w, h, rowstride, pixels, options = tray_data
172 log("%s.set_tray_icon(%s, %s, %s, %s, %s bytes)", self, enc, w, h, rowstride, len(pixels))
173 has_alpha = enc=="rgb32"
174 tw = self.tray_widget
175 if tw:
176 #some tray implementations can't deal with memoryviews..
177 if isinstance(pixels, (memoryview, bytearray)):
178 pixels = memoryview_to_bytes(pixels)
179 tw.set_icon_from_data(pixels, has_alpha, w, h, rowstride, options)
182 def destroy(self):
183 tw = self.tray_widget
184 if tw:
185 self.tray_widget = None
186 tw.cleanup()
188 def __repr__(self):
189 return "ClientTray(%i:%s)" % (self._id, self.title)
192class TrayBacking(WindowBackingBase):
193 """
194 This backing only stores the rgb pixels so
195 we can use them with the real widget.
196 """
198 #keep it simple: only accept 32-bit RGB(X),
199 #all tray implementations support alpha
200 RGB_MODES = ("RGBA", "RGBX")
201 HAS_ALPHA = True
203 def __init__(self, wid, _w, _h, _has_alpha, data=None):
204 self.data = data
205 super().__init__(wid, True)
206 self._backing = object() #pretend we have a backing structure
208 def get_encoding_properties(self):
209 #override so we skip all csc caps:
210 return {
211 "encodings.rgb_formats" : self.RGB_MODES,
212 "encoding.transparency" : True,
213 }
215 def idle_add(self, *args, **kwargs):
216 return GLib.idle_add(*args, **kwargs)
218 def paint_scroll(self, _img_data, _options, callbacks):
219 raise Exception("scroll should not be used with tray icons")
221 def _do_paint_rgb24(self, img_data, x, y, width, height, render_width, render_height, rowstride, options):
222 log("TrayBacking(%i)._do_paint_rgb24%s",
223 self.wid, ("%s bytes" % len(img_data), x, y, width, height, render_width, render_height, rowstride, options))
224 assert width==render_width and height==render_height, "tray rgb must not use scaling"
225 self.data = ("rgb24", width, height, rowstride, img_data[:], options)
226 if SAVE:
227 from PIL import Image
228 img = Image.frombytes("RGB", (width, height), img_data, "raw", "BGR", width*3, 1)
229 filename = "./tray-%s.png" % time()
230 img.save(filename, "PNG")
231 log.info("tray rgb24 update saved to %s", filename)
232 return True
234 def _do_paint_rgb32(self, img_data, x, y, width, height, render_width, render_height, rowstride, options):
235 log("TrayBacking(%i)._do_paint_rgb32%s",
236 self.wid, ("%s bytes" % len(img_data), x, y, width, height, render_width, render_height, rowstride, options))
237 assert width==render_width and height==render_height, "tray rgb must not use scaling"
238 self.data = ("rgb32", width, height, rowstride, img_data[:], options)
239 if SAVE:
240 from PIL import Image
241 img = Image.frombytes("RGBA", (width, height), img_data, "raw", "BGRA", width*4, 1)
242 filename = "./tray-%s.png" % time()
243 img.save(filename, "PNG")
244 log.info("tray rgb32 update saved to %s", filename)
245 return True