Hide keyboard shortcuts

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. 

5 

6from time import time 

7from gi.repository import GLib 

8 

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 

14 

15log = Logger("tray") 

16 

17SAVE = envbool("XPRA_SAVE_SYSTRAY", False) 

18 

19 

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 

29 

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 

39 

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) 

47 

48 def set_alpha(self): 

49 #nothing to do, 

50 #trays aren't really windows and transparency is always supported 

51 pass 

52 

53 def get_backing_class(self): 

54 return TrayBacking 

55 

56 def is_OR(self): 

57 return True 

58 

59 def is_tray(self): 

60 return True 

61 

62 def get_window(self): 

63 return None 

64 

65 def get_geometry(self): 

66 return self._geometry or ClientTray.DEFAULT_GEOMETRY 

67 

68 def get_tray_geometry(self): 

69 tw = self.tray_widget 

70 if not tw: 

71 return None 

72 return tw.get_geometry() 

73 

74 def get_tray_size(self): 

75 tw = self.tray_widget 

76 if not tw: 

77 return None 

78 return tw.get_size() 

79 

80 

81 def freeze(self): 

82 pass 

83 

84 

85 def send_configure(self): 

86 self.reconfigure(True) 

87 

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) 

123 

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) 

130 

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) 

139 

140 def update_metadata(self, metadata): 

141 log("%s.update_metadata(%s)", self, metadata) 

142 

143 def update_icon(self, img): 

144 #this is the window icon... not the tray icon! 

145 pass 

146 

147 

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)) 

151 

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) 

169 

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) 

180 

181 

182 def destroy(self): 

183 tw = self.tray_widget 

184 if tw: 

185 self.tray_widget = None 

186 tw.cleanup() 

187 

188 def __repr__(self): 

189 return "ClientTray(%i:%s)" % (self._id, self.title) 

190 

191 

192class TrayBacking(WindowBackingBase): 

193 """ 

194 This backing only stores the rgb pixels so 

195 we can use them with the real widget. 

196 """ 

197 

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 

202 

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 

207 

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 } 

214 

215 def idle_add(self, *args, **kwargs): 

216 return GLib.idle_add(*args, **kwargs) 

217 

218 def paint_scroll(self, _img_data, _options, callbacks): 

219 raise Exception("scroll should not be used with tray icons") 

220 

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 

233 

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