Mythstatus.py
From MythTV Official Wiki
Revision as of 01:14, 18 December 2010 by DAP (talk | contribs) (Created page with '{{Wrongtitle|mythstatus.py}} {{Script info |author=Douglas Peale |short=Gnome Panel Applet to display back end status. |long=Displays an icon in the Gnome Panel indicating how ma…')
Note: The correct title of this article is mythstatus.py. It appears incorrectly here due to technical restrictions.
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 Gnome Server File
<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
(I'm not sure how to add the icons yet, they are .svg files)