Mythstatus.py

From MythTV Official Wiki
Revision as of 20:17, 22 October 2011 by DAP (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Important.png Note: The correct title of this article is mythstatus.py. It appears incorrectly here due to technical restrictions.


Author Douglas Peale
Description Displays an icon in the Gnome Panel indicating how many shows are currently being recorded. Tool tip displays names of shows being recorded as well as the next scheduled recording.
Supports Version24.png  


Screen Shot

Screenshot-1.png Screenshot.png

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

Script.png mythstatus.py

#!/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

Script.png mythstatus.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]