Mythstatus.py
Note: The correct title of this article is mythstatus.py. It appears incorrectly here due to technical restrictions.
Screen Shot
Instructions
- Note that this applet has been broken by the update to Ubuntu 11.10. Even though the gnome classic still allows pannel applets, the python-gnomeapplet package no longer exists, so the applet won't run.
python-gnomeapplet must be installed for this applet to work.
Place the various files in the places described by the comments at the beginning of the .py file
You may need to log out and log back in before the panel applet will show up in the "Add to Panel" window accessed by right clicking on the gnome panel.
The icon displayed in the Gnome Panel will change with the state of the back end.
When idle it will show a picture of a TV that is off
When recording it will show color bars with a red circle on it.
When recording more than one show, it will show the number of current recordings in the red circle.
If the applet can not communicate with the back end, it will show a broken television.
The Script
#!/usr/bin/env python
# copyright Douglas Peale 2010
# released under GPLv3
#This is a Gnome Pannel Application.
#It displays the number of currently recording tuners on a MythTV system.
#Tooltip shows currently recording programs, when they will end, and when the next recordings will begin
#mythstatus.py should be put in /usr/bin
#mythstatus.server should be put in /usr/lib/bonobo/servers
#icons should be put in /usr/share/pixmaps/mythstatus
#Currently used icons: tv_color_bars.svg, tv_snow.svg, tv_broken.svg, tv_off.svg
import sys
import os
import MythTV
import pygtk
pygtk.require('2.0')
import gtk
import gnomeapplet
import gobject
# Changing debug to a path/filename will cause the applet to remap stdout & stderr to that file
# It appears that when run as a panel applet, no path is configured, and it is run as the logged
# in user, so an absolute path to a directory you have write access to is required
debug=None
#debug="/home/user/mythstatus.log"
class MythStatus:
####################################################
def myth_status_update(self):
try:
#If the connection to the backend is not already establised, attempt to establish the connection.
if not self.backend:
self.backend=MythTV.MythBE()
#get a list of recorders (tuners)
recorders=self.backend.getRecorderList()
#count how many of those recorders are busy recording something
recording=0
for recorder in recorders:
if self.backend.isRecording(recorder):
recording+=1
if recording > 0:
#change the icon to indicate that the system is recording and set lable to number of recorders
if self.state != 'TV_color_bars':
self.state='TV_color_bars'
self.image.set_from_pixbuf(self.icon[self.state])
if self.rec_state != 'TV_recording':
self.rec_state='TV_recording'
self.rec_image.set_from_pixbuf(self.icon[self.rec_state])
if recording > 1 :
self.set_label(str(recording))
else:
#don't bother with number if only one recorder
self.set_label("")
else:
self.set_label('')
if self.state != 'TV_off':
self.state='TV_off'
self.image.set_from_pixbuf(self.icon[self.state])
if self.rec_state != 'TV_blank':
self.rec_state='TV_blank'
self.rec_image.set_from_pixbuf(self.icon[self.rec_state])
except BaseException as err:
#Something went wrong. Display broken TV icon since we can't talk to the backend
print(str(err))
self.errstr=str(err)
self.set_label('')
self.backend=None
if self.state != 'TV_broken':
self.state='TV_broken'
self.image.set_from_pixbuf(self.icon[self.state])
self.tooltipuptodate=False
return True
####################################################
def myth_status_tooltip(self,widget,x,y,keyboard_mode,tooltip):
#Unfortunately, this gets called for every mouse event while the mouse is over the widget.
#What I was looking for and did not find was a way to change the tooltip just before it is displayed.
#This is the closest thing I could find. To prevent unnecessary communication with the backend, only update the tooltip
#if the panel app has been updated since this function was last called.
#Only do this once per update
if not self.tooltipuptodate:
try:
#make the tooltip an empty string
self.tooltiptext=""
#Avoid creating a new exception that would overwrite an existing one.
if self.backend:
#get a list of recorders (tuners)
recorders=self.backend.getRecorderList()
isrecording=False
for recorder in recorders:
if self.backend.isRecording(recorder):
#add a title if you found a recording recorder
if not isrecording:
self.tooltiptext="Currently Recording:\n"
isrecording=True
else:
self.tooltiptext+='\n'
#add the title & subtitle of the activerecording
recording=self.backend.getCurrentRecording(recorder)
self.tooltiptext+=str(recording.title)+': '+str(recording.subtitle)+'\n'
#add the time the recording will end
if recording.recendts.hour>12:
hour=recording.recendts.hour-12
ampm=' PM'
else:
if recording.recendts.hour == 0:
hour=12
else:
hour=recording.recendts.hour
ampm=' AM'
self.tooltiptext+='ends at %d:%02d%s'%(hour,recording.recendts.minute,ampm)
#Set tool tip to display the next upcoming recording
#This function returns a list of recordings. I'm not sure what it does if there is nothing scheduled.
recordings=self.backend.getUpcomingRecordings()
if recordings!=[]:
if isrecording:
self.tooltiptext+='\n\n'
self.tooltiptext+='Next Recordings:\n'
#list all the recordings starting at the same time
for recording in recordings:
if recording.starttime != recordings[0].starttime:
break
self.tooltiptext+=str(recording.title)+': '+str(recording.subtitle)+'\n'
#build a nicly formatted start time string
self.tooltiptext+='at %4d/%02d/%02d '%(recordings[0].recstartts.year,recordings[0].recstartts.month,recordings[0].recstartts.day)
if recordings[0].recstartts.hour>12:
hour=recordings[0].recstartts.hour-12
ampm=' PM'
else:
if recordings[0].recstartts.hour == 0:
hour=12
else:
hour=recordings[0].recstartts.hour
ampm=' AM'
self.tooltiptext+='%d:%02d%s'%(hour,recordings[0].recstartts.minute,ampm)
else:
self.tooltiptext="Nothing is scheduled to record"
else:
self.tooltiptext="Not communicating with back end"
except BaseException as err:
#Disconnect from the backend so we will attempt to re-establish communication on the next update
print(str(err))
self.errstr=str(err)
self.tooltiptext="Can't communicate with back end"
self.backend=None
if self.state != 'TV_broken':
self.state='TV_broken'
self.image.set_from_pixbuf(self.icon[self.state])
if self.errstr != "":
self.tooltiptext+="\n\nLast Exception:\n"+self.errstr
# I must set this every time, even if most of the time it is never used because if mouse is moved since this was set, the
# tooltip is discarded and a new empty tooltip created.
tooltip.set_text(self.tooltiptext)
self.tooltipuptodate=True
return True
####################################################
#If the button is clicked, fork the process, and have the child start mythfrontend
def start_front_end(self,widget,event):
if event.type == gtk.gdk.BUTTON_PRESS :
if event.button == 1 :
if not os.fork():
os.execlp("mythfrontend","mythfrontend","--service")
####################################################
def change_size(self,size_orient,user=0):
#Calculate size of icons based on orientation and size hint such that the 16x9 aspect ratio is preserved and the result will fit in the panel.
if (self.applet.get_orient() == gnomeapplet.ORIENT_UP) or (self.applet.get_orient() == gnomeapplet.ORIENT_DOWN):
self.height=self.applet.get_size()-2
self.width=(self.height*16)//9
else:
self.width=self.applet.get_size()-2
self.height=self.width*9//16
self.rec_size=min([(gtk.Label('0').size_request()[1]*5)//4,self.height])
try:
self.icon['TV_color_bars']=gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/pixmaps/mythstatus/tv_color_bars.svg",self.width,self.height)
self.icon['TV_off']=gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/pixmaps/mythstatus/tv_off.svg",self.width,self.height)
self.icon['TV_broken']=gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/pixmaps/mythstatus/tv_broken.svg",self.width,self.height)
self.icon['TV_snow']=gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/pixmaps/mythstatus/tv_snow.svg",self.width,self.height)
self.icon['TV_blank']=gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/pixmaps/mythstatus/tv_blank.svg",self.rec_size,self.rec_size)
self.icon['TV_recording']=gtk.gdk.pixbuf_new_from_file_at_size("/usr/share/pixmaps/mythstatus/tv_recording.svg",self.rec_size,self.rec_size)
self.image.set_from_pixbuf(self.icon[self.state])
self.rec_image.set_from_pixbuf(self.icon[self.rec_state])
self.fixed.move(self.rec_image,max([0,(self.width-self.rec_size)//2]),max([0,(self.height-self.rec_size)//2]))
except BaseException as err:
self.errstr=str(err)
print(self.errstr)
####################################################
def set_label(self,label):
self.label.set_text(label)
size=self.label.size_request()
self.fixed.move(self.label,max([0,(self.width-size[0])//2]),max([0,(self.height-size[1])//2]))
####################################################
def __init__(self,applet,iid):
#I don't know what to do with these yet, but I'll keep a copy so the garbage collector can't eat them
self.applet=applet
self.iid=iid
self.errstr=""
#If debugging, hijack stdout & stderr so error messages & debug prints can actually be looked at.
if debug:
try:
#Panel app runs as user, but does not have users '~/' directory defined, so absolute path must be used.
dbgfile=open(debug,'w')
sys.stdout=dbgfile
sys.stderr=dbgfile
except BaseException as err:
self.errstr=str(err)
#Create the variable, but don't Connect to MythBackend here.
self.backend=None
#set up timer so I can update the count every 10 seconds. Could be longer, but that makes debugging very annoying.
gobject.timeout_add_seconds(10,self.myth_status_update)
#Enable tool tip, and connect handler so I can change tool tip before it is displayed.
self.applet.set_property("has-tooltip",True)
self.applet.connect("query-tooltip",self.myth_status_tooltip)
self.applet.connect("button-press-event",self.start_front_end)
self.applet.connect("change-size",self.change_size)
self.applet.connect("change-orient",self.change_size)
#use change size function to import properly sized icons
self.state='TV_snow'
self.rec_state='TV_blank'
self.icon={}
self.image=gtk.Image()
self.rec_image=gtk.Image()
self.fixed=gtk.Fixed()
self.applet.add(self.fixed)
self.fixed.put(self.image,0,0)
self.fixed.put(self.rec_image,0,0)
self.change_size(0)
#Put a label in the window
self.label = gtk.Label("test")
self.fixed.put(self.label,0,0)
self.set_label("test")
self.tooltipuptodate=False
self.applet.show_all()
####################################################
def mythstatus_factory(applet, iid):
MythStatus(applet,iid)
return True
if len(sys.argv) == 2 and sys.argv[1] == "run-in-window":
# user wants to run in a window so he can see error messages in the consol he ran from.
print("Running in window")
main_window = gtk.Window(gtk.WINDOW_TOPLEVEL)
main_window.set_title("MythTV Status Debug Window")
main_window.connect("destroy", gtk.main_quit)
app = gnomeapplet.Applet()
mythstatus_factory(app, None)
app.reparent(main_window)
main_window.show_all()
gtk.main()
sys.exit()
else:
gnomeapplet.bonobo_factory("OAFIID:GNOME_MythstatusApplet_Factory",
gnomeapplet.Applet.__gtype__,
"MythStatus", "0", mythstatus_factory)
The Server
<oaf_info>
<oaf_server iid="OAFIID:GNOME_MythstatusApplet_Factory"
type="exe" location="/usr/bin/mythstatus.py">
<oaf_attribute name="repo_ids" type="stringv">
<item value="IDL:Bonobo/GenericFactory:1.0"/>
<item value="IDL:Bonobo/Unknown:1.0"/>
</oaf_attribute>
<oaf_attribute name="name" type="string" value="MythTV Status"/>
<oaf_attribute name="description" type="string" value="MythTV Backend Status Monitor"/>
</oaf_server>
<oaf_server iid="OAFIID:GNOME_MythstatusApplet"
type="factory" location="OAFIID:GNOME_MythstatusApplet_Factory">
<oaf_attribute name="repo_ids" type="stringv">
<item value="IDL:GNOME/Vertigo/PanelAppletShell:1.0"/>
<item value="IDL:Bonobo/Control:1.0"/>
<item value="IDL:Bonobo/Unknown:1.0"/>
</oaf_attribute>
<oaf_attribute name="name" type="string" value="MythTV Status"/>
<oaf_attribute name="description" type="string" value="MythTV Backend Status Monitor"/>
<oaf_attribute name="panel:category" type="string" value="Utility"/>
<oaf_attribute name="panel:icon" type="string" value="/usr/share/pixmaps/mythstatus/tv_color_bars.svg"/>
</oaf_server>
</oaf_info>
The Icons
Note that the Wiki capitalizes the first letter of the file name. The files must be renamed to all lower case after downloading.
tv_colorbars.svg[1]
tv_broken.svg[2]
tv_off.svg[3]
tv_snow.svg[4]
tv_recording.svg[5]
tv_blank.svg[6]

