Now that I’ve got a new computer I decided to give gdesklets a try and I have to say, I am not excited. Most of them don’t even work, and I don’t really like those which do work, so I ended up with only one single desklet enabled, “Quote of the Day”, and for a single widget I don’t want to have gdesklets running. So, considering that I’ve got some spare hours and that I haven’t coded much lately, I decided to try to create a standalone version of it.
So, after deciding that I’d use Python and try to get this done with GTK+, I needed a window with two properties: a) being “on the desktop”, and b) having a transparent background. I could achieve both things without too much hassle, keep reading to know how!
Widget on the desktop
After a quick search I found this post, which claims that this can be achieved by giving our window the type hint “gtk.gdk.WINDOW_TYPE_HINT_DESKTOP”. I tried it out and, yes!, there we have our widget on the desktop… But wait, I just clicked next to it and it disappeared! Weird… So it seems like clicking somewhere on the real desktop places the widget behind it (or does something else with it) and then it’ s no longer visible. I looked up the hint in the documentation and there it says that it actually is to implement a desktop, so I stopped messing with this and looked for something else.
The solution I found was a quite ovious one: just give our window all the properties we expect a widget to have. This is done with the following code (assuming that we have a gtk.Widget instance called, well, widget):
widget.set_skip_taskbar_hint(True) widget.set_skip_pager_hint(True) widget.set_keep_below(True) widget.set_decorated(False) widget.stick()
I am not going to stop and explain each of them as their name already explains it all; if you want to know a bit more about some of them you’ll find more details in GTK+’s docs. This code achieves the purpose, but it has a little flaw: although our window behaves pretty much like a widget, it still is a window, so clicking on “Show desktop” will hide it. Gdesklets has the same problem, though, so I guess we can live with it. [Update: See below for an improved variant of this code.].
Window with a transparent background
A widget isn’t a true widget if it hasn’t some fancy transparency, so this is an important point, and I had absolutely no clue on how to solve it. Fortunately, the PyGTK+ docs came to the rescue, with “A gtk.gdk.Window Composited Windows example”. The example shows how to place a semi-transparent button upon a colored window, but simplyfing it a bit I could get it to do what I want: make the background of the window itself transparent. Here is the result:
import gtk import cairo def transparent_expose(widget, event): cr = widget.window.cairo_create() cr.set_operator(cairo.OPERATOR_CLEAR) region = gtk.gdk.region_rectangle(event.area) cr.region(region) cr.fill() return False # And in the GUI part, after defining a gtk.Window called "window": screen = window.get_screen() rgba = screen.get_rgba_colormap() window.set_colormap(rgba) window.set_app_paintable(True) window.connect("expose-event", transparent_expose)
Putting it all together
Now that we know how to implement both characteristics, we just have to put them together. To make it easier to reuse the code if I later decide to create more widgets, I wrote thi as a gtk.Window subclass:
import gtk import cairo def transparent_expose(widget, event): cr = widget.window.cairo_create() cr.set_operator(cairo.OPERATOR_CLEAR) region = gtk.gdk.region_rectangle(event.area) cr.region(region) cr.fill() return False class DesktopWindow(gtk.Window): # Based upon the composited window example from: # http://www.pygtk.org/docs/pygtk/class-gdkwindow.html def __init__(self, *args): gtk.Window.__init__(self, *args) self.set_skip_taskbar_hint(True) self.set_skip_pager_hint(True) self.set_keep_below(True) self.set_decorated(False) self.stick() screen = self.get_screen() rgba = screen.get_rgba_colormap() self.set_colormap(rgba) self.set_app_paintable(True) self.connect("expose-event", transparent_expose)
I saved this as common.py, and now I can just import it into the file for my widget and use common.DesktopWindow() instead of Gtk.Window() to automatically get both, the transparent background and the widget behavior.
That’s it. Another day I’ll finish writing the actual widget (still have some other work to do now) and eventually publish it for whoever may want to use it. I may also make a module out of the above code (with some additional stuff, like possibly drag-and-drop support, position saving, right-click menu, etc). I hope this post will be useful to someone!
Ah, and if you know of a better way to achieve this (beside using some alternative to gdesklets, which would take out the fun of this project :P), please tell me!
Update: Improved version
Hans Rödtang pointed out that using the type hint “gtk.gdk.WINDOW_TYPE_HINT_DOCK” may solve the problem with our widget being hidden when the “Show desktop” button is pressed. Effectively, it does, and it also makes it unnecessary to set some of the properties we need manually, so here is an improved version of the DesktopWindow class:
class DesktopWindow(gtk.Window): def __init__(self, *args): gtk.Window.__init__(self, *args) self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DOCK) self.set_keep_below(True) self.set_decorated(False) self.stick() screen = self.get_screen() rgba = screen.get_rgba_colormap() self.set_colormap(rgba) self.set_app_paintable(True) self.connect("expose-event", transparent_expose)