Full Text

This is the first draft of a little project I hacked together in Python. Everything I know about Python I learned today, so let’s all go ahead and agree that this is the worst code ever written except for the large parts I stole from Old Nabble. It probably won’t work for you, and no I don’t know how to fix it. I’m just hoping some smart people will think this is a good idea and make it better.

What I’m doing is creating custom window decorations for an instant messenger contact list window. In this case I’m using Empathy. However it should work with any contact list window. In fact, if you make the background the right size, this would work with any window at all! Really? No. Maybe, in theory. I have no idea. Anyway this isn’t a skin, but it might be a step in that direction.

Code after the fold, still very much in development.

Read This:

  • While the script is running, it will create a new window that hijacks your existing contact list window. So open the IM client first, then run the script (“python clist_decorate.py”). It might break things. If it does, kill it, then log out and back in, and everything should be fine. Then again, we’ve established that I don’t know what I’m doing, so use this at your own risk.
  • You probably need to be running a compositing manager like compiz for this to work. Tested only with Empathy on Ubuntu 10.04 on a fast 64-bit computer.
  • The window exists in the widget layer. To drag the window, hold down the alt key.

User-configurable variables are near the top of the script.

  • You’ll need to tell the code what the contact list window is called so the code can properly hijack the window: in Empathy it’s “Contact List”; in Pidgin it’s “Buddy List”. The code will preserve the window title so you can adjust opacity settings in compiz.
  • Also specify the background image you want to use; here are the iPhone and “base white” backgrounds I’ve made so far. Don’t like those? Make your own. It should be an alpha-blended png the actual size of the contact list you want.
  • If you make your own background, edit the height, width, and padding variables as needed. For Empathy, width must be 250 or more else the status selector disappears (that probably varies depending upon what GTK theme you’re using).

To-Do (can you do any of this stuff?)

  • Background image scaling
  • Error handling
  • Can methods in gtk_Box or gtk_Socket control elements of the socketed window such as background color and opacity? Or can we maybe do it with DBus? This seems to be the next step towards a real skin.
  • Application icon
  • Theme-reading, so all user-configurable variables are in portable XML files

Updates

  • 10.30.2010 — New decoration. “Gaia light” (preview, full skin image) adapted from a miranda theme by the same name. Image settings: height 1050, width 250, padding 40, 14, 50, 8.
  • 10.30.2010 — New decoration. “Brushed” (preview, full skin image). Image settings: height 1050, width 250, padding 32, 9, 43, 8.

Python Code (or download a Zip file with the backgrounds and code)

import sys, gobject, pango, pygtk, gtk, cairo, gobject, subprocess, re

############################################################################
# CREDITS
############################################################################

# Base code for cairo transparent window shell:
# http://old.nabble.com/cairo-%2B-transparent-png-%2B-XShape-td21225420.html

# The rest of this inexcusable hackery:
# http://j.modjeska.us?p=167

############################################################################
# USER CONFIG
############################################################################

# Background image (needs to be the same size as the final contact list
# until a smart person sorts out scaling):

backgroundimage = 'gaialight.png'

# Window name to be skinned (must be exact match)
winname = 'Contact List'

# Padding adjustment (to situate contact list within skin)

padding_top = 40
padding_right = 14
padding_bottom = 50
padding_left = 8

# (optional) Additional padding depth
extra_padding = 0

# Set window width & height (needs to match background image set above)

clist_w = 250
clist_h = 1000

############################################################################

# Init gtk_socket
socket = gtk.Socket()

# Define the transparent window shell
class clist_window:

	def __init__(self):

		# Init new window, standard settings
		self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
		self.window.set_events(gtk.gdk.ALL_EVENTS_MASK)

		# Discard window decorations
		self.window.set_decorated(0)

		# Keep window title so you can mess with it in compiz
		self.window.set_title(winname)

		# Size of the final contact list window
		self.window.set_default_size(clist_w, clist_h)
               
		# Init colors, alpha transparency
		self.window.set_app_paintable(1)
		self.gtk_screen = self.window.get_screen()
		colormap = self.gtk_screen.get_rgba_colormap()
		if colormap == None:
			colormap = self.gtk_screen.get_rgb_colormap()
		gtk.widget_set_default_colormap(colormap)
		if not self.window.is_composited():
			self.supports_alpha = False
		else:
			self.supports_alpha = True
               
		# Specify window behaviors
		self.window.connect("expose_event", self.expose)
		self.window.connect("destroy", gtk.main_quit)

		# Create vbox (parent) vertical column
		self.vbox = gtk.VBox(False, 0)
		self.top_empty = gtk.VBox(False, extra_padding)
		self.hbox_container = gtk.VBox(False, 0)
		self.bottom_empty = gtk.VBox(False, extra_padding)
		self.window.add(self.vbox)

		# Create hbox horizontal row
		self.hbox_container = gtk.HBox(False, 0)
		self.left_empty = gtk.HBox(False, extra_padding)
		self.socket_embed = gtk.HBox(False, 0)
		self.right_empty = gtk.HBox(False, extra_padding)

		# Populate the hbox with three content areas
		self.hbox_container.pack_start(self.left_empty, False, False, padding_left)
		self.hbox_container.pack_start(self.socket_embed, True, True, 0)
		self.hbox_container.pack_start(self.right_empty, False, False, padding_right)

		# Populate the vbox with three content areas
		self.vbox.pack_start(self.top_empty, False, False, padding_top)
		self.vbox.pack_start(self.hbox_container, True, True, 0)
		self.vbox.pack_start(self.bottom_empty, False, False, padding_bottom)

		# Setup content of empty hboxes
		self.frameh1 = gtk.Fixed()
		self.left_empty.add (self.frameh1)
		self.frameh2 = gtk.Fixed()
		self.right_empty.add (self.frameh2)

		# Setup content of empty vboxes
		self.frame = gtk.Fixed()
		self.top_empty.add (self.frame)
		self.frame2 = gtk.Fixed()
		self.bottom_empty.add (self.frame2)

		# Setup content of hbox socket_embed
		# This is the only part of the grid that does anything
		self.socket_embed.add (socket)

	# Setup window 
	def expose (self, widget, event):

		self.ctx = self.window.window.cairo_create()
		self.ctx.save()
		if self.supports_alpha == False:
			self.ctx.set_source_rgb(1, 1, 1)
		else:
			self.ctx.set_source_rgba(1, 1, 1,0)
		self.ctx.set_operator (cairo.OPERATOR_SOURCE)
		self.ctx.paint()
		self.ctx.restore()
		self.ctx.rectangle(event.area.x, event.area.y,
				event.area.width, event.area.height)
		self.ctx.clip()
		self.draw_image(self.ctx,0,0,backgroundimage)

	# Draw the background using source image
	def draw_image(self,ctx,x,y, pix):

		ctx.save()
		ctx.translate(x, y)
		pixbuf = gtk.gdk.pixbuf_new_from_file(pix)
		format = cairo.FORMAT_RGB24
		if pixbuf.get_has_alpha():
			format = cairo.FORMAT_ARGB32
       
		iw = pixbuf.get_width()
		ih = pixbuf.get_height()
		image = cairo.ImageSurface(format, iw, ih)
		image = ctx.set_source_pixbuf(pixbuf, 0, 0)
               
		ctx.paint()
		puxbuf = None
		image = None
		ctx.restore()
		ctx.clip()

	# Get window ID of open contact list
	def winid(self):
	
		proc = subprocess.Popen(['xwininfo', '-int', '-name', winname], 
				stdout=subprocess.PIPE,
				)
		stdout_value = proc.communicate()[0]
		getwindowid = re.search("(Window id:)(.+?)(\")", repr(stdout_value))
		winid = int(getwindowid.group(2))

		return winid

	# Display the window
	def show_window(self):

		self.window.show_all()
		while gtk.events_pending():
			gtk.main_iteration()
		self.window.present()
		self.window.grab_focus()
		self.p = 1

if __name__ == "__main__":

	m = clist_window()
	socket.add_id(m.winid())
	m.show_window()
	gtk.main() 
  • Digg
  • del.icio.us
  • Google Bookmarks
  • Reddit
  • StumbleUpon
  • Technorati
  • Facebook
  • LinkedIn
  • Twitter
Posted in Code  |  4 Comments

4 Responses to “Inexcusable Hackery: Redecorate Your Contact List (including Empathy)”

  • I’ve tried to run your py.script on my Ubuntu but it didn’t work ;( Would you like to help me? Can you write steps to install/run your script?

  • Jeremy says:

    Monique — I doubt I can be much help. I really just hacked this together with minimal knowledge of python. What sort of error are you getting, and when does it occur?

  • Santiago says:

    hi, I have NO knowledge of python and would like to learn how to run this.

    I have the files on a clist_decorate folder on my home folder. so I run terminal and go:

    cd clist_decorate
    clist_decorate$ sudo chmod +x clist_decorate.py
    clist_decorate$ /home/fenrir88/clist_decorate/clist_decorate.py

    /home/fenrir88/clist_decorate/clist_decorate.py: line 1: import: command not found
    /home/fenrir88/clist_decorate/clist_decorate.py: line 20: backgroundimage: command not found
    /home/fenrir88/clist_decorate/clist_decorate.py: line 29: winname: command not found
    /home/fenrir88/clist_decorate/clist_decorate.py: line 33: padding_top: command not found
    /home/fenrir88/clist_decorate/clist_decorate.py: line 34: padding_right: command not found
    /home/fenrir88/clist_decorate/clist_decorate.py: line 35: padding_bottom: command not found
    /home/fenrir88/clist_decorate/clist_decorate.py: line 36: padding_left: command not found
    /home/fenrir88/clist_decorate/clist_decorate.py: line 45: extra_padding: command not found
    /home/fenrir88/clist_decorate/clist_decorate.py: line 48: clist_w: command not found
    /home/fenrir88/clist_decorate/clist_decorate.py: line 49: clist_h: command not found
    /home/fenrir88/clist_decorate/clist_decorate.py: line 54: syntax error near unexpected token `(‘
    /home/fenrir88/clist_decorate/clist_decorate.py: line 54: `socket = gtk.Socket()’

    all I get from that is that my pc doesn’t get python commands… but already installed all add ons and plug ins and stuff like that to my ubuntu machine.

    Any ideas about what to do are most welcomed. I really like empathy, but being able to skin the chat window and not the contact list is Very Boring. Cheers.

  • Jeremy says:

    Hi Santiago. Looks like you’re not running the script with python. Instead of trying to execute the script directly, type ‘python clist_decorate.py’. Let me know if that helps.

Leave a Reply