Repository: qtile/qtile

Issue:
The Systray class doesn't honor the background parameter
It seems like it always uses black.

via Timo Schmiade the_isz@gmx.de



Discussion:
I still have this problem with master and develop (and I don't have permissions to re-open the issue...).

You are correct, my commit should have said "partial fix". The background of the overall widget is now honored, but the individual icons are still black. This is still a bug, and I've reopened.

Hi, do @cjbarnes18's recent commits (78fb5b4e025face400f58abeb05dcaa162aae188) fix this?

It's almost fixed: The systray icons still start out with a black background. However, any subsequent redraws use the correct background. So after a while, all icons have the correct background.

How about with 2f3be76f115e7e96067711a51a98728dd52a2c52?

Yup, that fixes it! Thanks a lot!

OK, after playing around a bit, I noticed that one icon has the reversed behavior now (starts correct, subsequent refreshes use black background). It's VolumeIcon (http://softwarebakery.com/maato/volumeicon.html).

I'll keep testing and report back if I find more problems.

looks like it needs it on init and on icon change.

try this 2f0963e1bc080525767f6e97a2b92ae482cb4a71

Nope, 2f0963e still has the issue. Or was this supposed to go on top of 2f3be76?

Some observations:
- It's not simply icon changes. I need to click the volume icon several times (thus switching between normal and muted icons), before the background becomes black.
- batti (http://code.google.com/p/batti-gtk/) also has the problem.
- Some other applications with changing icons don't suffer from this problem. Specifically SpiderOak and wicd-gtk.

 2f0963e is based on the develop branch, not 2f3be76.

Do these applications play nice on other desktops? if so I suspect we should be creating a new window type to base this widget on that will handle this properly at the xcbq level. 

Yes, so far it only happens in qtile. I wasn't able to trigger the misbehavior under awesome just now. And I don't remember having this issue when I was still using xmonad.

See also #465 


Resolving patch:
diff --git a/libqtile/widget/systray.py b/libqtile/widget/systray.py
--- a/libqtile/widget/systray.py
+++ b/libqtile/widget/systray.py
@@ -37,18 +37,20 @@
                            SetMode)
 import atexit
 
+XEMBED_PROTOCOL_VERSION = 0
+
 
 class Icon(window._Window):
     _windowMask = EventMask.StructureNotify | \
+        EventMask.PropertyChange | \
         EventMask.Exposure
 
     def __init__(self, win, qtile, systray):
         window._Window.__init__(self, win, qtile)
         self.systray = systray
-        self.width = systray.icon_size
-        self.height = systray.icon_size
+        self.update_size()
 
-    def handle_ConfigureNotify(self, event):
+    def update_size(self):
         icon_size = self.systray.icon_size
         self.updateHints()
 
@@ -68,67 +70,38 @@ def handle_ConfigureNotify(self, event):
 
         self.width = width
         self.height = height
-        self.window.set_attribute(backpixmap=self.systray.drawer.pixmap)
+        return False
+
+    def handle_PropertyNotify(self, e):
+        name = self.qtile.conn.atoms.get_name(e.atom)
+        if name == "_XEMBED_INFO":
+            info = self.window.get_property('_XEMBED_INFO',
+                                            type="_XEMBED_INFO", unpack=int)
+            if info and info[1]:
+                self.systray.bar.draw()
+
         return False
 
     def handle_DestroyNotify(self, event):
         wid = event.window
         del(self.qtile.windowMap[wid])
         del(self.systray.icons[wid])
-        self.systray.draw()
+        self.systray.bar.draw()
         return False
 
     handle_UnmapNotify = handle_DestroyNotify
 
 
-class TrayWindow(window._Window):
-    _windowMask = EventMask.StructureNotify | \
-        EventMask.Exposure
-
-    def __init__(self, win, qtile, systray):
-        window._Window.__init__(self, win, qtile)
-        self.systray = systray
-
-    def handle_ClientMessage(self, event):
-        atoms = self.qtile.conn.atoms
-
-        opcode = event.type
-        data = event.data.data32
-        message = data[1]
-        wid = data[2]
-
-        conn = self.qtile.conn.conn
-        parent = self.systray.bar.window.window
-
-        # message == 0 corresponds to SYSTEM_TRAY_REQUEST_DOCK
-        # TODO: handle system tray messages http://standards.freedesktop.org/systemtray-spec/systemtray-spec-latest.html
-        if opcode == atoms['_NET_SYSTEM_TRAY_OPCODE'] and message == 0:
-            try:
-                w = xcbq.Window(self.qtile.conn, wid)
-                icon = Icon(w, self.qtile, self.systray)
-                self.systray.icons[wid] = icon
-                self.qtile.windowMap[wid] = icon
-
-                # add icon window to the save-set, so it gets reparented
-                # to the root window when qtile dies
-                conn.core.ChangeSaveSet(SetMode.Insert, wid)
-
-                conn.core.ReparentWindow(wid, parent.wid, 0, 0)
-                conn.flush()
-                w.map()
-            except xcffib.xproto.DrawableError:
-                # The icon wasn't ready to be drawn yet... (NetworkManager does
-                # this sometimes), so we just forget about it and wait for the
-                # next event.
-                pass
-        return False
-
-
-class Systray(base._Widget):
+class Systray(window._Window, base._Widget):
     """
         A widget that manages system tray.
     """
+
+    _windowMask = EventMask.StructureNotify | \
+        EventMask.Exposure
+
     orientations = base.ORIENTATION_HORIZONTAL
+
     defaults = [
         ('icon_size', 20, 'Icon width'),
         ('padding', 5, 'Padding between icons'),
@@ -137,12 +110,8 @@ class Systray(base._Widget):
     def __init__(self, **config):
         base._Widget.__init__(self, bar.CALCULATED, **config)
         self.add_defaults(Systray.defaults)
-        self.traywin = None
         self.icons = {}
 
-    def button_press(self, x, y, button):
-        pass
-
     def calculate_length(self):
         width = sum([i.width for i in self.icons.values()])
         width += self.padding * len(self.icons)
@@ -150,20 +119,21 @@ def calculate_length(self):
 
     def _configure(self, qtile, bar):
         base._Widget._configure(self, qtile, bar)
-        self.qtile = qtile
+        win = qtile.conn.create_window(-1, -1, 1, 1)
+        window._Window.__init__(self, xcbq.Window(qtile.conn, win.wid), qtile)
+        qtile.windowMap[win.wid] = self
+
         self.bar = bar
         atoms = qtile.conn.atoms
-        win = qtile.conn.create_window(-1, -1, 1, 1)
-        self.traywin = TrayWindow(win, self.qtile, self)
-        qtile.windowMap[win.wid] = self.traywin
+
         qtile.conn.conn.core.SetSelectionOwner(
             win.wid,
-            atoms['_NET_SYSTEM_TRAY_S0'],
+            atoms['_NET_SYSTEM_TRAY_S{}'.format(self.bar.screen.index)],
             xcffib.CurrentTime
         )
         data = [
             xcffib.CurrentTime,
-            atoms['_NET_SYSTEM_TRAY_S0'],
+            atoms['_NET_SYSTEM_TRAY_S{}'.format(self.bar.screen.index)],
             win.wid, 0, 0
         ]
         union = ClientMessageData.synthetic(data, "I" * 5)
@@ -178,11 +148,48 @@ def _configure(self, qtile, bar):
         # cleanup before exit
         atexit.register(self.cleanup)
 
+    def handle_ClientMessage(self, event):
+        atoms = self.qtile.conn.atoms
+
+        opcode = event.type
+        data = event.data.data32
+        message = data[1]
+        wid = data[2]
+
+        conn = self.qtile.conn.conn
+        parent = self.bar.window.window
+
+        if opcode == atoms['_NET_SYSTEM_TRAY_OPCODE'] and message == 0:
+            w = xcbq.Window(self.qtile.conn, wid)
+            icon = Icon(w, self.qtile, self)
+            self.icons[wid] = icon
+            self.qtile.windowMap[wid] = icon
+
+            conn.core.ChangeSaveSet(SetMode.Insert, wid)
+            conn.core.ReparentWindow(wid, parent.wid, 0, 0)
+            conn.flush()
+
+            info = icon.window.get_property(
+                '_XEMBED_INFO',
+                type='_XEMBED_INFO',
+                unpack=int
+            )
+
+            if not info:
+                self.bar.draw()
+                return False
+
+            if info[1]:
+                self.bar.draw()
+
+        return False
+
     def draw(self):
-        self.drawer.clear(self.background or self.bar.background)
-        self.drawer.draw(offsetx=self.offset, width=self.calculate_length())
         xoffset = self.padding
+        self.drawer.clear(self.background or self.bar.background)
+        self.drawer.draw(offsetx=self.offset, width=self.length)
         for pos, icon in enumerate(self.icons.values()):
+            icon.window.set_attribute(backpixmap=self.drawer.pixmap)
             icon.place(
                 self.offset + xoffset,
                 self.bar.height // 2 - self.icon_size // 2,
@@ -190,13 +197,31 @@ def draw(self):
                 0,
                 None
             )
+            if icon.hidden:
+                icon.unhide()
+                data = [
+                    self.qtile.conn.atoms["_XEMBED_EMBEDDED_NOTIFY"],
+                    xcffib.xproto.Time.CurrentTime,
+                    0,
+                    self.bar.window.window.wid,
+                    XEMBED_PROTOCOL_VERSION
+                ]
+                u = xcffib.xproto.ClientMessageData.synthetic(data, "I" * 5)
+                event = xcffib.xproto.ClientMessageEvent.synthetic(
+                    format=32,
+                    window=icon.window.wid,
+                    type=self.qtile.conn.atoms["_XEMBED"],
+                    data=u
+                )
+                self.window.send_event(event)
+
             xoffset += icon.width + self.padding
 
     def cleanup(self):
         atoms = self.qtile.conn.atoms
         self.qtile.conn.conn.core.SetSelectionOwner(
             0,
-            atoms['_NET_SYSTEM_TRAY_S0'],
+            atoms['_NET_SYSTEM_TRAY_S{}'.format(self.bar.screen.index)],
             xcffib.CurrentTime,
         )
-        self.traywin.hide()
+        self.hide()


Test patch: