Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/x11/gtk_x11/tray.py : 51%
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) 2012-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 gi.repository import GObject, Gdk, GdkX11
8from xpra.util import envint
9from xpra.gtk_common.gobject_util import one_arg_signal
10from xpra.gtk_common.error import xswallow, xsync, xlog
11from xpra.x11.gtk_x11.prop import prop_set, prop_get
12from xpra.gtk_common.gtk_util import (
13 get_default_root_window, GDKWindow,
14 )
15from xpra.x11.bindings.window_bindings import constants, X11WindowBindings #@UnresolvedImport
16from xpra.x11.gtk_x11.gdk_bindings import (
17 add_event_receiver, #@UnresolvedImport
18 remove_event_receiver, #@UnresolvedImport
19 )
20from xpra.log import Logger
22X11Window = X11WindowBindings()
24log = Logger("x11", "tray")
27CurrentTime = constants["CurrentTime"]
28XNone = constants["XNone"]
29StructureNotifyMask = constants["StructureNotifyMask"]
32XEMBED_VERSION = 0
34# XEmbed
35XEMBED_EMBEDDED_NOTIFY = 0
36XEMBED_WINDOW_ACTIVATE = 1
37XEMBED_WINDOW_DEACTIVATE = 2
38XEMBED_REQUEST_FOCUS = 3
39XEMBED_FOCUS_IN = 4
40XEMBED_FOCUS_OUT = 5
41XEMBED_FOCUS_NEXT = 6
42XEMBED_FOCUS_PREV = 7
43# 8-9 were used for XEMBED_GRAB_KEY/XEMBED_UNGRAB_KEY */
44XEMBED_MODALITY_ON = 10
45XEMBED_MODALITY_OFF = 11
46XEMBED_REGISTER_ACCELERATOR = 12
47XEMBED_UNREGISTER_ACCELERATOR = 13
48XEMBED_ACTIVATE_ACCELERATOR = 14
49# A detail code is required for XEMBED_FOCUS_IN. The following values are valid:
50# Details for XEMBED_FOCUS_IN:
51XEMBED_FOCUS_CURRENT = 0
52XEMBED_FOCUS_FIRST = 1
53XEMBED_FOCUS_LAST = 2
55SELECTION = "_NET_SYSTEM_TRAY_S0"
56TRAY_VISUAL = "_NET_SYSTEM_TRAY_VISUAL"
57TRAY_ORIENTATION = "_NET_SYSTEM_TRAY_ORIENTATION"
59TRAY_ORIENTATION_HORZ = 0
60TRAY_ORIENTATION_VERT = 1
62XPRA_TRAY_WINDOW_PROPERTY = "_xpra_tray_window_"
64SYSTEM_TRAY_REQUEST_DOCK = 0
65SYSTEM_TRAY_BEGIN_MESSAGE = 1
66SYSTEM_TRAY_CANCEL_MESSAGE = 2
68#TRANSPARENCY = False
69TRANSPARENCY = True
71#Java can send this message to the tray (no idea why):
72IGNORED_MESSAGE_TYPES = ("_GTK_LOAD_ICONTHEMES", )
75MAX_TRAY_SIZE = envint("XPRA_MAX_TRAY_SIZE", 64)
78def get_tray_window(tray_window):
79 return getattr(tray_window, XPRA_TRAY_WINDOW_PROPERTY, None)
81def set_tray_window(tray_window, window):
82 setattr(tray_window, XPRA_TRAY_WINDOW_PROPERTY, window.get_xid())
84def set_tray_visual(tray_window, gdk_visual):
85 prop_set(tray_window, TRAY_VISUAL, "visual", gdk_visual)
87def set_tray_orientation(tray_window, orientation):
88 prop_set(tray_window, TRAY_ORIENTATION, "u32", orientation)
91class SystemTray(GObject.GObject):
92 """ This is an X11 system tray area,
93 owning the "_NET_SYSTEM_TRAY_S0" selection,
94 X11 client applications can request to embed their tray icon in it,
95 the xpra server can request to "move_resize" to where the xpra client has it mapped.
96 """
98 __gsignals__ = {
99 "xpra-unmap-event": one_arg_signal,
100 "xpra-client-message-event": one_arg_signal,
101 }
103 def __init__(self):
104 GObject.GObject.__init__(self)
105 self.tray_window = None
106 self.window_trays = {}
107 self.tray_windows = {}
108 self.setup_tray_window()
110 def cleanup(self):
111 log("SystemTray.cleanup()")
112 root = get_default_root_window()
113 def undock(window):
114 log("undocking %s", window)
115 wxid = window.get_xid()
116 rxid = root.get_xid()
117 X11Window.Unmap(wxid)
118 X11Window.Reparent(wxid, rxid, 0, 0)
119 with xlog:
120 owner = X11Window.XGetSelectionOwner(SELECTION)
121 if owner==self.tray_window.get_xid():
122 X11Window.XSetSelectionOwner(0, SELECTION)
123 log("SystemTray.cleanup() reset %s selection owner to %#x",
124 SELECTION, X11Window.XGetSelectionOwner(SELECTION))
125 else:
126 log.warn("Warning: we were no longer the tray selection owner")
127 remove_event_receiver(self.tray_window, self)
128 tray_windows = self.tray_windows
129 self.tray_windows = {}
130 for window, tray_window in tray_windows.items():
131 with xswallow:
132 undock(window)
133 tray_window.destroy()
134 self.tray_window.destroy()
135 self.tray_window = None
136 log("SystemTray.cleanup() done")
138 def setup_tray_window(self):
139 display = Gdk.Display.get_default()
140 root = get_default_root_window()
141 screen = root.get_screen()
142 owner = X11Window.XGetSelectionOwner(SELECTION)
143 log("setup tray: current selection owner=%#x", owner)
144 if owner!=XNone:
145 raise Exception("%s already owned by %s" % (SELECTION, owner))
146 visual = screen.get_system_visual()
147 if TRANSPARENCY:
148 visual = screen.get_rgba_visual()
149 if visual is None:
150 log.warn("setup tray: using rgb visual fallback")
151 visual = screen.get_rgb_visual()
152 assert visual is not None, "failed to obtain visual"
153 self.tray_window = GDKWindow(root, width=1, height=1,
154 title="Xpra-SystemTray",
155 visual=visual)
156 xtray = self.tray_window.get_xid()
157 set_tray_visual(self.tray_window, visual)
158 set_tray_orientation(self.tray_window, TRAY_ORIENTATION_HORZ)
159 log("setup tray: tray window %#x", xtray)
160 display.request_selection_notification(Gdk.Atom.intern(SELECTION, False))
161 try:
162 with xsync:
163 setsel = X11Window.XSetSelectionOwner(xtray, SELECTION)
164 log("setup tray: set selection owner returned %s, owner=%#x",
165 setsel, X11Window.XGetSelectionOwner(SELECTION))
166 event_mask = StructureNotifyMask
167 log("setup tray: sending client message")
168 xid = root.get_xid()
169 X11Window.sendClientMessage(xid, xid, False, event_mask, "MANAGER",
170 CurrentTime, SELECTION, xtray)
171 owner = X11Window.XGetSelectionOwner(SELECTION)
172 assert owner==xtray, "we failed to get ownership of the tray selection"
173 add_event_receiver(self.tray_window, self)
174 log("setup tray: done")
175 except Exception:
176 log("setup_tray failure", exc_info=True)
177 self.cleanup()
178 raise
180 def do_xpra_client_message_event(self, event):
181 if event.message_type=="_NET_SYSTEM_TRAY_OPCODE" and event.window==self.tray_window and event.format==32:
182 opcode = event.data[1]
183 if opcode==SYSTEM_TRAY_REQUEST_DOCK:
184 xid = event.data[2]
185 log("tray docking request from %#x", xid)
186 window = GdkX11.X11Window.foreign_new_for_display(event.display, xid)
187 log("tray docking window %s", window)
188 if window:
189 from gi.repository import GLib
190 GLib.idle_add(self.dock_tray, xid)
191 elif opcode==SYSTEM_TRAY_BEGIN_MESSAGE:
192 timeout = event.data[2]
193 mlen = event.data[3]
194 mid = event.data[4]
195 log.info("tray begin message timeout=%s, mlen=%s, mid=%s - not handled yet!", timeout, mlen, mid)
196 elif opcode==SYSTEM_TRAY_CANCEL_MESSAGE:
197 mid = event.data[2]
198 log.info("tray cancel message for mid=%s - not handled yet!", mid)
199 elif event.message_type=="_NET_SYSTEM_TRAY_MESSAGE_DATA":
200 assert event.format==8
201 log.info("tray message data - not handled yet!")
202 elif event.message_type in IGNORED_MESSAGE_TYPES:
203 log("do_xpra_client_message_event(%s) in ignored message type list", event)
204 else:
205 log.info("do_xpra_client_message_event(%s)", event)
207 def dock_tray(self, xid):
208 log("dock_tray(%#x)", xid)
209 try:
210 with xsync:
211 X11Window.getGeometry(xid)
212 self.do_dock_tray(xid)
213 except Exception as e:
214 log.warn("Warning: failed to dock tray %#x:", xid)
215 log.warn(" %s", e)
216 log.warn(" the application may retry later")
218 def do_dock_tray(self, xid):
219 root = get_default_root_window()
220 window = GdkX11.X11Window.foreign_new_for_display(root.get_display(), xid)
221 if window is None:
222 log.warn("could not find gdk window for tray window %#x", xid)
223 return
224 log("dock_tray: root=%s, window=%s", root, window)
225 w, h = window.get_geometry()[2:4]
226 log("dock_tray: geometry=%s", (w, h))
227 if w==0 and h==0:
228 log("dock_tray: invalid tray geometry, ignoring this request")
229 return
230 em = Gdk.EventMask
231 event_mask = em.STRUCTURE_MASK | em.EXPOSURE_MASK | em.PROPERTY_CHANGE_MASK
232 window.set_events(event_mask=event_mask)
233 add_event_receiver(window, self)
234 w = max(1, min(MAX_TRAY_SIZE, w))
235 h = max(1, min(MAX_TRAY_SIZE, h))
236 title = prop_get(window, "_NET_WM_NAME", "utf8", ignore_errors=True)
237 if title is None:
238 title = prop_get(window, "WM_NAME", "latin1", ignore_errors=True)
239 if title is None:
240 title = ""
241 xid = root.get_xid()
242 log("dock_tray(%#x) gdk window=%#x, geometry=%s, title=%s",
243 xid, xid, window.get_geometry(), title)
244 visual = window.get_visual()
245 tray_window = GDKWindow(root, width=w, height=h,
246 event_mask = event_mask,
247 title=title,
248 x=-200, y=-200,
249 override_redirect=True,
250 visual=visual)
251 log("dock_tray(%#x) setting tray properties", xid)
252 set_tray_window(tray_window, window)
253 tray_window.show()
254 self.tray_windows[window] = tray_window
255 self.window_trays[tray_window] = window
256 log("dock_tray(%#x) resizing and reparenting", xid)
257 window.resize(w, h)
258 xwin = window.get_xid()
259 xtray = tray_window.get_xid()
260 X11Window.Withdraw(xwin)
261 X11Window.Reparent(xwin, xtray, 0, 0)
262 X11Window.MapRaised(xwin)
263 log("dock_tray(%#x) new tray container window %#x", xid, xtray)
264 rect = Gdk.Rectangle()
265 rect.width = w
266 rect.height = h
267 tray_window.invalidate_rect(rect, True)
268 X11Window.send_xembed_message(xwin, XEMBED_EMBEDDED_NOTIFY, 0, xtray, XEMBED_VERSION)
270 def move_resize(self, window, x, y, w, h):
271 #see SystemTrayWindowModel.move_resize:
272 window.move_resize(x, y, w, h)
273 embedded_window = self.window_trays[window.client_window]
274 embedded_window.resize(w, h)
275 log("system tray moved to %sx%s and resized to %sx%s", x, y, w, h)
277 def do_xpra_unmap_event(self, event):
278 tray_window = self.tray_windows.get(event.window)
279 log("SystemTray.do_xpra_unmap_event(%s) container window=%s", event, tray_window)
280 if tray_window:
281 tray_window.destroy()
282 del self.tray_windows[event.window]
283 del self.window_trays[tray_window]
285GObject.type_register(SystemTray)