text: #!/usr/bin/env python2.7
  
  """Adds a GTK status-bar icon allowing one-click control of the screensaver."""
  
  LICENSE = """\
  Copyright (c) 2012 Ian Good <ian.good@rackspace.com>
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
  
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  SOFTWARE.
  """  # NOQA
  
  import os
  import sys
  import subprocess
  import argparse
  import pkg_resources
  
  import dbus
  import gobject
  import gtk
  
  __version__ = pkg_resources.require('screensaver-icon')[0].version
  
  
  class State(object):
  
      def __init__(self, args):
          self.icon = Icon(self, args)
          self.screensaver = XScreensaver(self, args)
          self.pidgin = Pidgin(self, args)
  
      def main(self):
          gobject.timeout_add(2500, self.screensaver.refresh_on_status)
          try:
              gtk.main()
          finally:
              self.screensaver.kill_watch_process()
  
      def on_status_changed(self, new):
          self.icon.set_status(new)
  
      def refresh_on_status(self, *args):
          self.screensaver.refresh_on_status()
  
      def got_blank_trigger(self):
          if self.icon.get_away_on_lock():
              self.pidgin.set_away()
  
      def got_unblank_trigger(self):
          if self.icon.get_away_on_lock():
              self.pidgin.remove_away()
  
      def got_lock_trigger(self):
          if self.icon.get_away_on_lock():
              self.pidgin.set_away()
  
      def icon_clicked(self):
          self.screensaver.toggle_on()
  
  
  class Pidgin(object):
  
      def __init__(self, state, args):
          self.state = state
          self.prev = None
  
      def _set_status(self, to_away):
          purple = self._get_purple()
          if not purple:
              return
  
          away = purple.PurpleSavedstatusGetIdleaway()
          current = purple.PurpleSavedstatusGetCurrent()
  
          if to_away:
              self.prev = current
              purple.PurpleSavedstatusActivate(away)
          else:
              purple.PurpleSavedstatusActivate(self.prev)
              self.prev = None
  
      def set_away(self):
          self._set_status(True)
  
      def remove_away(self):
          if self.prev:
              gobject.timeout_add(1000, self._set_status, False)
  
      def _get_purple(self):
          try:
              bus = dbus.SessionBus()
              dbus_obj = bus.get_object('im.pidgin.purple.PurpleService',
                                        '/im/pidgin/purple/PurpleObject')
              p = dbus.Interface(dbus_obj, 'im.pidgin.purple.PurpleInterface')
              return p
          except dbus.exceptions.DBusException:
              return None
  
  
  class XScreensaver(object):
  
      def __init__(self, state, args):
          self.state = state
  
          self._toggling = False
          self._on_status_process = None
          self._watch_process = None
          self._start_watch()
  
      def kill_watch_process(self):
          try:
              self._watch_process.terminate()
          except OSError:
              pass
  
      def toggle_on(self):
          self._toggling = True
          self.refresh_on_status()
  
      def refresh_on_status(self):
          if self._on_status_process:
              return
  
          p = subprocess.Popen(['xscreensaver-command', '-version'],
                               stdin=subprocess.PIPE,
                               stdout=subprocess.PIPE,
                               stderr=subprocess.STDOUT)
          p.stdin.close()
          gobject.io_add_watch(p.stdout, gobject.IO_HUP,
                               self._on_status_finished)
          self._on_status_process = p
  
      def _start_watch(self, *extras):
          if self._watch_process and not self._watch_process.poll():
              self._watch_process.wait()
          p = subprocess.Popen(['xscreensaver-command', '-watch'],
                               stdin=subprocess.PIPE,
                               stdout=subprocess.PIPE)
          p.stdin.close()
          self._watch_process = p
          gobject.io_add_watch(p.stdout, gobject.IO_IN, self._get_watch_data)
          gobject.io_add_watch(p.stdout, gobject.IO_HUP, self._start_watch)
          return False
  
      def _on_status_finished(self, f, cond):
          p = self._on_status_process
          if p:
              self._on_status_process = None
              p.wait()
              self.state.on_status_changed(p.returncode == 0)
              if self._toggling:
                  self._toggling = False
                  if p.returncode == 0:
                      self._turn_off()
                  else:
                      self._turn_on()
          return False
  
      def _turn_on(self):
          devnull = open('/dev/null', 'w')
          p = subprocess.Popen(['xscreensaver', '-nosplash'],
                               stdin=subprocess.PIPE,
                               stdout=devnull, stderr=devnull)
          p.stdin.close()
          gobject.timeout_add(1000, self.refresh_on_status)
  
      def _turn_off(self):
          p = subprocess.Popen(['xscreensaver-command', '-exit'],
                               stdin=subprocess.PIPE,
                               stdout=subprocess.PIPE,
                               stderr=subprocess.STDOUT)
          p.communicate()
          gobject.timeout_add(500, self.refresh_on_status)
  
      def _get_watch_data(self, f, cond):
          line = f.readline()
          if line.startswith("BLANK"):
              self.state.got_blank_trigger()
          elif line.startswith("UNBLANK"):
              self.state.got_unblank_trigger()
          elif line.startswith("LOCK"):
              self.state.got_lock_trigger()
          return True
  
  
  class Icon(object):
  
      def __init__(self, state, args):
          self.state = state
          self.icon = None
          self.status = None
          self._away_on_lock = True
          self._load_icons(args)
  
      def _load_icons(self, args):
          def load_default(name):
              from pkg_resources import resource_filename
              resource_name = 'icons/{0}.png'.format(name)
              fn = resource_filename(__name__, resource_name)
              return gtk.gdk.pixbuf_new_from_file(fn)
  
          if args.onicon:
              self._on_icon = gtk.gdk.pixbuf_new_from_file(args.onicon)
          else:
              self._on_icon = load_default('on')
  
          if args.officon:
              self._off_icon = gtk.gdk.pixbuf_new_from_file(args.officon)
          else:
              self._off_icon = load_default('off')
  
      def _set_icon_pixbuf(self, icon):
          pixbuf = self._on_icon if self.status else self._off_icon
          icon.set_from_pixbuf(pixbuf)
  
      def _create_icon(self):
          self.icon = gtk.StatusIcon()
          self._set_icon_pixbuf(self.icon)
          self.icon.connect('popup-menu', self._right_click)
          self.icon.connect('activate', self._left_click)
          self.icon.set_tooltip("Screensaver Icon")
  
      def _change_away_on_lock(self, item):
          self._away_on_lock = item.get_active()
  
      def get_away_on_lock(self):
          return self._away_on_lock
  
      def set_status(self, status):
          self.status = status
          if not self.icon:
              self._create_icon()
          else:
              self._set_icon_pixbuf(self.icon)
  
      def _right_click(self, icon, button, timestamp):
          menu = gtk.Menu()
  
          if self.status:
              status = gtk.MenuItem("Running")
          else:
              status = gtk.MenuItem("Stopped")
          status.set_sensitive(False)
  
          aol = gtk.CheckMenuItem("Away On Lock")
          aol.set_active(self._away_on_lock)
  
          refresh = gtk.ImageMenuItem("Refresh")
          about = gtk.ImageMenuItem("About")
          quit = gtk.ImageMenuItem("Quit")
  
          img = gtk.image_new_from_stock(gtk.STOCK_REFRESH, gtk.ICON_SIZE_MENU)
          img.show()
          refresh.set_image(img)
  
          img = gtk.image_new_from_stock(gtk.STOCK_ABOUT, gtk.ICON_SIZE_MENU)
          img.show()
          about.set_image(img)
  
          img = gtk.image_new_from_stock(gtk.STOCK_QUIT, gtk.ICON_SIZE_MENU)
          img.show()
          quit.set_image(img)
  
          aol.connect("toggled", self._change_away_on_lock)
          refresh.connect("activate", self.state.refresh_on_status)
          about.connect("activate", self._show_about_dialog)
          quit.connect("activate", gtk.main_quit)
  
          menu.append(status)
          menu.append(gtk.SeparatorMenuItem())
          menu.append(aol)
          menu.append(refresh)
          menu.append(about)
          menu.append(gtk.SeparatorMenuItem())
          menu.append(quit)
  
          menu.show_all()
  
          menu.popup(None, None, gtk.status_icon_position_menu,
                     button, timestamp, icon)
  
      def _left_click(self, icon):
          self.state.icon_clicked()
  
      def _show_about_dialog(self, widget):
          about = gtk.AboutDialog()
  
          about.set_destroy_with_parent(True)
          about.set_name("Screensaver Icon")
          about.set_version(__version__)
          about.set_authors(["Ian Good <ian.good@rackspace.com>"])
          about.set_license(LICENSE)
  
          about.set_comments("""Provides a status-bar icon letting single-click \
  enabling a disabling of the xscreensaver daemon. Also, pidgin sessions will \
  be marked "Away" while the screensaver is engaged.""")
  
          about.run()
          about.destroy()
  
  
  def _parse_args():
      parser = argparse.ArgumentParser(description=__doc__)
      parser.add_argument('-v', '--version', action='version',
                          version='%(prog)s '+__version__)
      parser.add_argument('-f', '--foreground', action='store_true',
                          help='Run in the foreground, do not daemonize.')
      parser.add_argument('--on-icon', dest='onicon', metavar='FILE',
                          help='Use FILE icon indicating screensaver is on and '
                          'activated.')
      parser.add_argument('--off-icon', dest='officon', metavar='FILE',
                          help='Use FILE icon indicating screensaver is off and '
                          'disabled.')
      return parser.parse_args()
  
  
  # Daemonize the current process.
  def _daemonize():
  
      # Fork once.
      try:
          pid = os.fork()
          if pid > 0:
              os._exit(0)
      except OSError:
          return
  
      # Set some options to detach from the terminal.
      os.chdir('/')
      os.setsid()
      os.umask(0)
  
      # Fork again.
      try:
          pid = os.fork()
          if pid > 0:
              os._exit(0)
      except OSError:
          return
  
      # Find the OS /dev/null equivalent.
      nullfile = getattr(os, 'devnull', '/dev/null')
  
      # Redirect all standard I/O to /dev/null.
      sys.stdout.flush()
      sys.stderr.flush()
      si = file(nullfile, 'r')
      so = file(nullfile, 'a+')
      se = file(nullfile, 'a+', 0)
      os.dup2(si.fileno(), sys.stdin.fileno())
      os.dup2(so.fileno(), sys.stdout.fileno())
      os.dup2(se.fileno(), sys.stderr.fileno())
  
  
  def main():
      args = _parse_args()
  
      state = State(args)
      if not args.foreground:
          _daemonize()
  
      state.main()
  
  
  if __name__ == '__main__':
      main()
  
  # vim:et:fdm=marker:sts=4:sw=4:ts=4
  