Geeky Nuggets

Python skype notifier for Ubuntu

January 14, 2011 | 33 Minute Read

What’s the best way to learn a new programming language ? Well, according to this question the best (only ?) way to learn a new language is just to code in that language.

Why Python ?

Because it’s named after the Monty Python

No, seriously, that’s why :I wrote 20 short programs in Python yesterday.  It was wonderful.  Perl, I'm leaving you.No, not this “that’s why”! That one

If you are just starting out as a programmer, I cannot recommend Learn Python The Hard Way highly enough. It is an excellent introduction to the ideas and skills you will need as a programmer, starting from the very basics, like you’ve never seen a for loop in your life. Actually the first 10 or so lessons do not include ANY control structures!

If on the other hand you already have some coding experience, I recomend Dive Into Python. Although it’s no quite as hands on as Learn Python the Hard Way, it’s a very good book, only covering the basics insofar as they are specific to python.

Why for Ubuntu?

That’s what I use. Simple as that. Also python and pygtk are included by default, making distribution much easier.

Why Skype?

Haven’t found out yet. I started developping a backup application in pygtk and glade, a front end to rsync, but got sidetracked reading about the chnges to Ubuntu’s application notifier, ended up interested in the messaging notifier, and decided to do something with that.

Skype seemed like the obvious target, given that’s it’s the only communication application I use that lacks a messaging menu indicator. It does have a systray icon though, so my app is probably superfluous…

Anyway, to the coding!

#!/usr/bin/env python

import indicate
import gobject
import pynotify
import gtk
import hashlib
import Skype4Py
import urllib
import os

The first line tell the host system to use python to interpret that file

The next lines import the required python modules.

Next, we have to define the main class and declare some variables:

class skypeIndicator:
    notifShown={}
    oldcount={}
    count={}
    indicator={}

This is the clas initialization function

    def __init__(self):
        self.skype= Skype4Py.Skype()
        self.loadSkype()
        #create notification icon
        self.server = indicate.indicate_server_ref_default()
        self.server.set_type("message.im")
        self.server.set_desktop_file("/usr/share/applications/skype.desktop")
        self.server.connect("server-display", self.server_display)
        self.server.show()
        self.create_indicators()

The first line creates the skype API instance, after which we call the loadSkype() function, which checks if skype is loaded and if not, starts it. We’ll look at that function later on. Next, we create the notification server instance, choose an icon, connect the function server_display to the server-display event (when the icon is clicked on) and finally run the create_indicators function that looks for skype messages and displays them accordingly.

	def loadSkype(self):
		try:
			if not self.skype.Client.IsRunning:
				self.skype.Client.Start()
		except:
			#
			print "Please open skype first"
			self.noSkype()
		try:
			self.skype.Attach()
		except Skype4Py.errors.ISkypeAPIError:
			print "Please open skype first"
			self.noSkype()

This basically tries to start skype if it is not already started and calls noSkype() if it couldn’t start it. noSype() shows a notification message to let the user know that they have to start Skype. Error shown if Skype is not running

This next function is where the meat of the process takes place. Please read the inline comments to understand how it works, and ask for clarification by posting a comment.

	def create_indicators(self):

		"""Loads skype messages, displays them as notification bubbles and also shows them in the messaging menu"""

		#initialize count dictionaries
		self.count={}
		#get unread messages from skype, set self.unread variable
		self.get_messages()

		#self.unread is a dictionary having the username of the sender as key and a list of messages as value
		for name in self.unread:

			# Here we look at the first message from this user to set the messaging menu indicator
			# we only want one indicator per user
			msg=self.unread[name][0]

			#initialize message count for this user
			if name not in self.count:
				self.count[name]=0
			if name not in self.oldcount:
				self.oldcount[name]=0

			# if this user doesn't have his indicator yet
			if name not in self.indicator:
				# create indicator
				self.indicator[name] = indicate.Indicator()
				print "creating indicator"

				# Set indicator properties
				self.fullname=self.name_from_handle(name)
				self.indicator[name].set_property("subtype", "im")
				self.indicator[name].set_property("sender", self.fullname )
				self.indicator[name].set_property("handle", name)

				#this gets the most user-friendly name available for this user
				user=self.user_from_handle(name)

				# get an avatar for this user
				try:
					# This will only work on windows
					self.file=name + '.jpg'
					user.SaveAvatarToFile(file)
				except Skype4Py.errors.ISkypeError:
					# So on linux we use a generated monster ID. Fun but useless!
					h=hashlib.md5(name).hexdigest()
					#TODO find a way to get skype avatars on linux
					urllib.urlretrieve('http://friedcellcollective.net/monsterid/monster/%s/64' % h,name + '.jpg')
					self.file=name + '.jpg'

				#convert the imge to a pixbuf
				pixbuf=gtk.gdk.pixbuf_new_from_file(self.file)
				# for use in the indicator
				self.indicator[name].set_property_icon("icon", pixbuf)

				# set the timestamp of the indicator (this is what makes the indicator display the time since the message was received
				self.indicator[name].set_property_time("time", msg.Timestamp)

				self.indicator[name].show()
				# when the user clicks on the indicator message, open the skype messaging window for this user
				self.indicator[name].connect("user-display", self.display_msg)
				
			msgbody = ''
			#reverse list so latest message is at the bottom
			for eachmsg in self.unread[name][::-1]:
				# msgbody contains all the messages from that user so far
				msgbody += eachmsg.Body + "\n"

			# We set this person's indicator body to the compound text
			self.indicator[name].set_property("body", msgbody)

			# if there are more than one message from this user, we set the indicator count to be displayed in the messaging menu.
			# Otherwise the time elapsed since receiving the message will be shown
			if self.count[name] > 1:
				self.indicator[name].set_property("count", str(self.count[name]))
			
			# If a new message arrived since last time checked, mark notification as not shown
			if self.count[name] > self.oldcount[name]:
				self.notifShown[name]=False

			#If notification marked as not shown, show it
			if not self.notifShown.get(name, False) and self.showNotification(self.fullname, msgbody, self.file):
				#mark notification as shown
				self.notifShown[name]=True

				self.indicator[name].set_property("draw-attention", "true")
				self.indicator[name].show()
				print "notification shown for", name

			print "%d messages from %s" %(self.count[name],name)
		# Set oldcountt variable for next loop
		self.oldcount=self.count
		# Loop runs as long as true is returned
		return True

When a new messages arrives, this is what the messaging menu looks like Messaging menu open qith unread messages

The function that gets unread skype messages is as follows For each message, we add it to a list containg the messages from a particular user in the self.unread dictionary.

	def get_messages(self):
		print "checking messages"
		self.unread={}

		for msg in self.skype.MissedMessages:
			display_name = msg.FromHandle
			if display_name not in self.count:
				self.count[display_name]=0
			if not display_name in self.unread:
				self.unread[display_name]=[]
			
			self.unread[display_name].append(msg)
			self.count[display_name]+=1
		return self.unread

The next two functions are in charge of getting the skype user username as well as the friendliest name available.

	def name_from_handle(self,handle):
		user=self.skype.User(handle)
		if user.FullName:
			return user.FullName
		elif user.DisplayName:
			return user.DisplayName
		else:
			return handle

	def user_from_handle(self,handle):
		return self.skype.User(handle)

Below is the generic function in charge of showing popup notifications.

	def showNotification(self, title, message,file=None):
		'''takes a title and a message to display the email notification. Returns the
        created notification object'''

		n = pynotify.Notification(title, message, "notification-message-im")
		if file is not None:
			n.set_property("icon-name",os.getcwd() + "/" + file)
		n.show()

		return n

This is what it looks like with three new messages The Ubuntu skype indicator with three new messages

The next one shows the popup notification that skype is not loaded :

	def noSkype(self):
		'''Shows a notification if skype is not started'''
		title='Start Skype'
		message='Please start skype otherwise this won\'t work'
		n = self.showNotification(title, message)
		n.set_property("icon-name",gtk.STOCK_DIALOG_WARNING)
		n.show()

		return n

And finally, the indicator events callbacks (what happens when we click on the skype indicator, or on a particular message

	def display_msg(self, indicator, timestamp):
		#hide this indicator
		indicator.hide()
		#messaging menu goes back to normal
		indicator.set_property("draw-attention", "false")
		# open the skype chat window for this user
		self.skype.Client.OpenMessageDialog(indicator.get_property("handle"))

At the end of the file, we start everything :

if __name__ == "__main__":
	skypeind=skypeIndicator()
	# Loop
	gobject.timeout_add_seconds(5, skypeind.create_indicators)
	gtk.main()

And finally, here is the complete source code :

#!/usr/bin/env python
#
#Copyright 2010 Jonathan Foucher
#
#Authors:
#    Jonathan Foucher <[email protected]>
#
#This program is free software: you can redistribute it and/or modify it 
#under the terms of either or both of the following licenses:
#
#1) the GNU Lesser General Public License version 3, as published by the 
#Free Software Foundation; and/or
#2) the GNU Lesser General Public License version 2.1, as published by 
#the Free Software Foundation.
#
#This program is distributed in the hope that it will be useful, but 
#WITHOUT ANY WARRANTY; without even the implied warranties of 
#MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR 
#PURPOSE.  See the applicable version of the GNU Lesser General Public 
#License for more details.
#
#You should have received a copy of both the GNU Lesser General Public 
#License version 3 and version 2.1 along with this program.  If not, see 
#<http://www.gnu.org/licenses/>
#


import indicate
import gobject
import pynotify
import gtk
import hashlib
import Skype4Py
import urllib
import os


class skypeIndicator:
	notifShown={}
	oldcount={}
	count={}
	indicator={}

	def __init__(self):
		#self.no_skype()
		#get skype control
		self.skype= Skype4Py.Skype()
		
		self.loadSkype()

		#create notification icon

		self.server = indicate.indicate_server_ref_default()
		self.server.set_type("message.im")
		self.server.set_desktop_file("/usr/share/applications/skype.desktop")
		self.server.connect("server-display", self.server_display)
		#self.server.set_status (indicate.STATUS_ACTIVE)
		self.server.show()
#		for slot in dir(self.server):
#			attr = getattr(self.server, slot)
#			print attr

		#self.unread={}
		#self.indicator.set_property('draw-attention', 'true');
		self.create_indicators()
		#pass
		#indicator.connect("user-display", self.display_msg)
	def loadSkype(self):
		try:
			if not self.skype.Client.IsRunning:
				self.skype.Client.Start()
		except:
			#
			print "Please open skype first"
			self.noSkype()



		try:
			self.skype.Attach()
		except Skype4Py.errors.ISkypeAPIError:
			print "Please open skype first"
			self.noSkype()
			#pass

	def create_indicators(self):

		"""Loads skype messages, displays them as notification bubbles and also shows them in the messaging menu"""

		#initialize count dictionaries
		self.count={}
		#get unread messages from skype, set self.unread variable
		self.get_messages()

		#self.unread is a dictionary having the username of the sender as key and a list of messages as value
		for name in self.unread:

			# Here we look at the first message from this user to set the messaging menu indicator
			# we only want one indicator per user
			msg=self.unread[name][0]

			#initialize message count for this user
			if name not in self.count:
				self.count[name]=0
			if name not in self.oldcount:
				self.oldcount[name]=0

			# if this user doesn't have his indicator yet
			if name not in self.indicator:
				# create indicator
				self.indicator[name] = indicate.Indicator()
				print "creating indicator"

				# Set indicator properties
				self.fullname=self.name_from_handle(name)
				self.indicator[name].set_property("subtype", "im")
				self.indicator[name].set_property("sender", self.fullname )
				self.indicator[name].set_property("handle", name)

				#this gets the most user-friendly name available for this user
				user=self.user_from_handle(name)

				# get an avatar for this user
				try:
					# This will only work on windows
					self.file=name + '.jpg'
					user.SaveAvatarToFile(file)
				except Skype4Py.errors.ISkypeError:
					# So on linux we use a generated monster ID. Fun but useless!
					h=hashlib.md5(name).hexdigest()
					#TODO find a way to get skype avatars on linux
					urllib.urlretrieve('http://friedcellcollective.net/monsterid/monster/%s/64' % h,name + '.jpg')
					self.file=name + '.jpg'

				#convert the imge to a pixbuf
				pixbuf=gtk.gdk.pixbuf_new_from_file(self.file)
				# for use in the indicator
				self.indicator[name].set_property_icon("icon", pixbuf)

				# set the timestamp of the indicator (this is what makes the indicator display the time since the message was received
				self.indicator[name].set_property_time("time", msg.Timestamp)

				self.indicator[name].show()
				# when the user clicks on the indicator message, open the skype messaging window for this user
				self.indicator[name].connect("user-display", self.display_msg)
				
			msgbody = ''
			#reverse list so latest message is at the bottom
			for eachmsg in self.unread[name][::-1]:
				# msgbody contains all the messages from that user so far
				msgbody += eachmsg.Body + "\n"

			# We set this person's indicator body to the compound text
			self.indicator[name].set_property("body", msgbody)

			# if there are more than one message from this user, we set the indicator count to be displayed in the messaging menu.
			# Otherwise the time elapsed since receiving the message will be shown
			if self.count[name] > 1:
				self.indicator[name].set_property("count", str(self.count[name]))
			
			# If a new message arrived since last time checked, mark notification as not shown
			if self.count[name] > self.oldcount[name]:
				self.notifShown[name]=False

			#If notification marked as not shown, show it
			if not self.notifShown.get(name, False) and self.showNotification(self.fullname, msgbody, self.file):
				#mark notification as shown
				self.notifShown[name]=True

				self.indicator[name].set_property("draw-attention", "true")
				self.indicator[name].show()
				print "notification shown for", name

			print "%d messages from %s" %(self.count[name],name)
		# Set oldcountt variable for next loop
		self.oldcount=self.count
		# Loop runs as long as true is returned
		return True


	def name_from_handle(self,handle):
		user=self.skype.User(handle)
		if user.FullName:
			return user.FullName
		elif user.DisplayName:
			return user.DisplayName
		else:
			return handle

	def user_from_handle(self,handle):
		return self.skype.User(handle)

	def showNotification(self, title, message,file=None):
		'''takes a title and a message to display the email notification. Returns the
        created notification object'''

		n = pynotify.Notification(title, message, "notification-message-im")
		if file is not None:
			n.set_property("icon-name",os.getcwd() + "/" + file)
		n.show()

		return n

	def noSkype(self):
		'''Shows a notification if skype is not started'''
		title='Start Skype'
		message='Please start skype otherwise this won\'t work'
		n = self.showNotification(title, message)
		n.set_property("icon-name",gtk.STOCK_DIALOG_WARNING)
		n.show()

		return n

	def get_messages(self):
		print "checking messages"
		self.unread={}

		for msg in self.skype.MissedMessages:
			display_name = msg.FromHandle
			if display_name not in self.count:
				self.count[display_name]=0
			if not display_name in self.unread:
				self.unread[display_name]=[]
			
			self.unread[display_name].append(msg)
			self.count[display_name]+=1
		return self.unread



	def server_display(self, widget, timestamp=None):
		#Show main Skype window
		self.skype.Client.Focus()

	def display_msg(self, indicator, timestamp):
		#hide this indicator
		indicator.hide()
		#messaging menu goes back to normal
		indicator.set_property("draw-attention", "false")
		# open the skype chat window for this user
		self.skype.Client.OpenMessageDialog(indicator.get_property("handle"))



if __name__ == "__main__":

	skypeind=skypeIndicator()

	# Loop
	gobject.timeout_add_seconds(5, skypeind.create_indicators)
	gtk.main()

Download the script, or fork it on the ubuntu-skype-indicator github repository

UPDATE: Before you run this script, you need to install its dependencies, python-indicate and skype4py On ubuntu, it’s as simple as running

sudo apt-get install python-skype python-indicate