Package screenlets :: Module session

[hide private]
[frames] | no frames]

# Source Code for Module screenlets.session

      1  # This application is released under the GNU General Public License
      2  # v3 (or, at your option, any later version). You can find the full
      3  # text of the license under http://www.gnu.org/licenses/gpl.txt.
      4  # By using, editing and/or distributing this software you agree to
      5  # the terms and conditions of this license.
      6  # Thank you for using free software!
      7
      8  #  screenlets.session (c) RYX (aka Rico Pfaus) 2007 <ryx@ryxperience.com>
      9  #
     10  # INFO:
     11  # This module contains the ScreenletSession-class which handles the lower-level
     12  # things like startup, multiple instances and sessions. It should also become
     13  # the interface for load/save operations. The ScreenletSession is further
     14  # responsible for handling command-line args to the Screenlet and should maybe
     15  # offer some convenient way of setting Screenlet-options via commandline (so
     16  # one can do "NotesScreenlet --theme_name=green --scale=0.5" and launch the
     17  # Note with the given theme and scale)..
     18  #
     19  #
     20  # INFO:
     21  # - When a screenlet gets launched:
     22  #   - the first instance of a screenlet creates the Session-object (within the
     23  #     __main__-code)
     24  #   - the session object investigates the config-dir for the given Screenlet
     25  #     and restores available instances
     26  #   - else (if no instance was found) it simply creates a new instance of the
     27  #     given screenlet and runs its mainloop
     28  # - the --session argument allows setting the name of the session that will be
     29  #   used by the Screenlet (to allow multiple configs for one Screenlet)
     30  #
     31  # TODO:
     32  # - set attributes via commandline??
     33  #
     34
     35  import os
     36  import glob
     37  import random
     38  from xdg import BaseDirectory
     39
     40  import backend                  # import screenlets.backend module
     41  import services
     42  import utils
     43
     44  import dbus     # TEMPORARY!! only needed for workaround
     45  from stat import S_IRWXU, S_IRWXG, S_IRWXO
     46  import gettext
     47  import screenlets
     48  gettext.textdomain('screenlets')
     49  gettext.bindtextdomain('screenlets', screenlets.INSTALL_PREFIX +  '/share/locale')
     50
     51 -def _(s):
     52          return gettext.gettext(s)
     53
     54
     55  # temporary path for saving files for opened screenlets
     56  TMP_DIR         = '/tmp/screenlets'
     57  TMP_FILE        = 'screenlets.' + os.environ['USER'] + '.running'
     58
     59
     60 -class ScreenletSession (object):
     61          """The ScreenletSession manages instances of a Screenlet and handles
     62          saving/restoring options. Each Screenlet contains a reference to its
     63          session. Multiple instances of the same Screenlet share the same
     64          session-object."""
     65
     66          # constructor
     67 -        def __init__ (self, screenlet_classobj, backend_type='caching', name='default'):
     68                  object.__init__(self)
     69                  # check type
     70                  if not screenlet_classobj.__name__.endswith('Screenlet'):
     71                          # TODO: also check for correct type (Screenlet-subclass)!!
     72                          raise Exception(_("""ScreenletSession.__init__ has to be called with a
     73                           valid Screenlet-classobject as first argument!"""))
     74                  # init props
     75                  self.name               = name
     76                  self.screenlet  = screenlet_classobj
     77                  self.instances  = []
     78                  self.tempfile   = TMP_DIR + '/' + TMP_FILE
     79                  # check sys.args for "--session"-argument and override name, if set
     80                  self.__parse_commandline()
     81                  # set session path (and create dir-tree if not existent)
     82                  p = screenlet_classobj.__name__[:-9] + '/' + self.name + '/'
     83                  self.path = BaseDirectory.load_first_config('Screenlets/' + p)
     84                  if self.path == None:
     85                          self.path = BaseDirectory.save_config_path('Screenlets/' + p)
     86                  if self.path == None: self.path = (os.environ['HOME'] + '.config/Screenlets/' + p)
     87                  if self.path:
     88                          if backend_type == 'caching':
     89                                  self.backend = backend.CachingBackend(path=self.path)
     90                          elif backend_type == 'gconf':
     91                                  self.backend = backend.GconfBackend()
     92                  else:
     93                          # no config-dir? use dummy-backend and note about problem
     94                          self.backend = backend.ScreenletsBackend()
     95                          print _("Unable to init backend - settings will not be saved!")
     96                  # WORKAROUND: connect to daemon (ideally the daemon should watch the
     97                  #             tmpfile for changes!!)
     98                  #check for daemon
     99                  proc = os.popen("""ps axo "%p,%a" | grep "screenlets-daemon.py" | grep -v grep|cut -d',' -f1""").read()
    100
    101                  procs = proc.split('\n')
    102                  if len(procs) <= 1:
    103                          os.system('python -u ' + screenlets.INSTALL_PREFIX + '/share/screenlets-manager/screenlets-daemon.py &')
    104                          print 'No Daemon, Launching Daemon'
    105                  self.connect_daemon()
    106
    107 -        def connect_daemon (self):
    108                  """Connect to org.screenlets.ScreenletsDaemon."""
    109                  self.daemon_iface = None
    110                  bus = dbus.SessionBus()
    111                  if bus:
    112                          try:
    113                                  proxy_obj = bus.get_object(screenlets.DAEMON_BUS, screenlets.DAEMON_PATH)
    114                                  if proxy_obj:
    115                                          self.daemon_iface = dbus.Interface(proxy_obj, screenlets.DAEMON_IFACE)
    116                          except Exception, ex:
    117                                  print _("Error in screenlets.session.connect_daemon: %s") % ex
    118
    119 -        def create_instance (self, id=None, **keyword_args):
    120                  """Create a new instance with ID 'id' and add it to this session. The
    121                  function returns either the new Screenlet-instance or None."""
    122                  # if id is none or already exists
    123                  if id==None or id=='' or self.get_instance_by_id(id) != None:
    124                          print _("ID is unset or already in use - creating new one!")
    125                          id = self.__get_next_id()
    126                          dirlst = glob.glob(self.path + '*')
    127                          tdlen = len(self.path)
    128                          for filename in dirlst:
    129                                  filename = filename[tdlen:]             # strip path from filename
    130                                  print _('File: %s') % filename
    131                                  if filename.endswith(id + '.ini'):
    132                                  # create new instance
    133                                          sl = self.create_instance(id=filename[:-4], enable_saving=False)
    134                                          if sl:
    135                                                  # set options for the screenlet
    136                                                  print _("Set options in %s") % sl.__name__
    137                                                  #self.__restore_options_from_file (sl, self.path + filename)
    138                                                  self.__restore_options_from_backend(sl, self.path+filename)
    139                                                  sl.enable_saving(True)
    140                                                  # and call init handler
    141                                                  sl.finish_loading()
    142                                                  return sl
    143                  sl = self.screenlet(id=id, session=self, **keyword_args)
    144                  if sl:
    145                          self.instances.append(sl)               # add screenlet to session
    146                          # and cause initial save to store INI-file in session dir
    147                          sl.x = sl.x
    148                          return sl
    149                  return None
    150
    151 -        def delete_instance (self, id):
    152                  """Delete the given instance with ID 'id' and remove its session file.
    153                  When the last instance within the session is removed, the session dir
    154                  is completely removed."""
    155                  sl = self.get_instance_by_id(id)
    156                  if sl:
    157                          # remove instance from session
    158                          self.instances.remove(sl)
    159                          # remove session file
    160                          try:
    161                                  self.backend.delete_instance(id)
    162                          except Exception:
    163                                  print _("Failed to remove INI-file for instance (not critical).")
    164                          # if this was the last instance
    165                          if len(self.instances) == 0:
    166                                  # maybe show confirmation popup?
    167                                  print _("Removing last instance from session")
    168                                  # TODO: remove whole session directory
    169                                  print _("TODO: remove self.path: %s") % self.path
    170                                  try:
    171                                          os.rmdir(self.path)
    172                                  except:
    173                                          print _("Failed to remove session dir '%s' - not empty?") % self.name
    174                                  # ...
    175                                  # quit gtk on closing screenlet
    176                                  sl.quit_on_close = True
    177                          else:
    178                                  print _("Removing instance from session but staying alive")
    179                                  sl.quit_on_close = False
    180                          # delete screenlet instance
    181                          sl.close()
    182                          del sl
    183                          return True
    184                  return False
    185
    186 -        def get_instance_by_id (self, id):
    187                  """Return the instance with the given id from within this session."""
    188                  for inst in self.instances:
    189                          if inst.id == id:
    190                                  return inst
    191                  return None
    192
    193 -        def quit_instance (self, id):
    194                  """quit the given instance with ID 'id'"""
    195
    196                  sl = self.get_instance_by_id(id)
    197                  if sl:
    198                          print self.instances
    199                          # remove instance from session
    200
    201
    202                          if len(self.instances) == 1:
    203                                  sl.quit_on_close = True
    204                          else:
    205                                  print _("Removing instance from session but staying alive")
    206                                  sl.quit_on_close = False
    207                          self.backend.flush()
    208                          sl.close()
    209                          self.instances.remove(sl)
    210
    211                          # remove session file
    212                          return True
    213                  return False
    214
    215
    216 -        def start (self):
    217                  """Start a new session (or restore an existing session) for the
    218                  current Screenlet-class. Creates a new instance when none is found.
    219                  Returns True if everything worked well, else False."""
    220                  # check for a running instance first and use dbus-call to add
    221                  # a new instance in that case
    222                  #sln = self.screenlet.get_short_name()
    223                  sln = self.screenlet.__name__[:-9]
    224                  running = utils.list_running_screenlets()
    225                  if running and running.count(self.screenlet.__name__) > 0:
    226                  #if services.service_is_running(sln):
    227                          print _("Found a running session of %s, adding new instance by service.") % sln
    228                          srvc = services.get_service_by_name(sln)
    229                          if srvc:
    230                                  print _("Adding new instance through: %s") % str(srvc)
    231                                  srvc.add('')
    232                                  return False
    233                  # ok, we have a new session running - indicate that to the system
    234                  self.__register_screenlet()
    235                  # check for existing entries in the session with the given name
    236                  print _("Loading instances in: %s") % self.path
    237                  if self.__load_instances():
    238                          # restored existing entries?
    239                          print _("Restored instances from session '%s' ...") % self.name
    240                          # call mainloop of first instance (starts application)
    241                          #self.instances[0].main()
    242                          self.__run_session(self.instances[0])
    243                  else:
    244                          # create new first instance
    245                          print _('No instance(s) found in session-path, creating new one.')
    246                          sl = self.screenlet(session=self, id=self.__get_next_id())
    247                          if sl:
    248                                  # add screenlet to session
    249                                  self.instances.append(sl)
    250                                  # now cause a save of the options to initially create the
    251                                  # INI-file for this instance
    252                                  self.backend.save_option(sl.id, 'x', sl.x)
    253                                  # call on_init-handler
    254                                  sl.finish_loading()
    255                                  # call mainloop and give control to Screenlet
    256                                  #sl.main()
    257                                  self.__run_session(sl)
    258                          else:
    259                                  print _('Failed creating instance of: %s') % self.classobj.__name__
    260                                  # remove us from the running screenlets
    261                                  self.__unregister_screenlet()
    262                                  return False
    263                  # all went well
    264                  return True
    265
    266 -        def __register_screenlet (self):
    267                  """Create new entry for this session in the global TMP_FILE."""
    268
    269                  # if tempfile not exists, create it
    270                  if  not self.__create_tempdir():
    271                          return False # error already returned
    272
    273                  # if screenlet not already added
    274                  running = utils.list_running_screenlets()
    275                  if running == None : running = []
    276                  if running.count(self.screenlet.__name__) == 0:
    277                  # open temp file for appending data
    278                          try:
    279                                  f = open(self.tempfile, 'a') # No need to create a empty file , append will do just fine
    280                          except IOError, e:
    281                                  print _("Unable to open %s") % self.tempfile
    282                                  return False
    283                          else:
    284                                  print _("Creating new entry for %s in %s") % (self.screenlet.__name__, self.tempfile)
    285                                  f.write(self.screenlet.__name__ + '\n')
    286                                  f.close()
    287                  else: print _("Screenlet has already been added to %s") % self.tempfile
    288                  # WORKAROUND: for now we manually add this to the daemon,
    289                  #             ideally the daemon should watch the tmpdir for changes
    290                  if self.daemon_iface:
    291                          self.daemon_iface.register_screenlet(self.screenlet.__name__)
    292
    293 -        def __create_tempdir (self):
    294                  """Create the global temporary file for saving screenlets. The file is
    295                  used for indicating which screnlets are currently running."""
    296
    297                  # check for existence of TMP_DIR and create it if missing
    298                  if not os.path.isdir(TMP_DIR):
    299                          try:
    300                                  if os.path.exists(TMP_DIR):
    301                                          # something exists, but is not a directory
    302                                          os.remove(TMP_DIR)
    303
    304                                  print _("No global tempdir found, creating new one.")
    305                                  os.mkdir(TMP_DIR)
    306                                  # make the tmp directory accessible for all users
    307
    308                                  os.chmod(TMP_DIR, S_IRWXU | S_IRWXG | S_IRWXO)
    309                                  print _('Temp directory %s created.') % TMP_DIR
    310                          except OSError, e:
    311                                  print _('Error: Unable to create temp directory %s - screenlets-manager will not work properly.') % TMP_DIR
    312                                  print "Error was: %s"%e
    313                                  return False
    314                  return True
    315
    316
    317 -        def __unregister_screenlet (self, name=None):
    318                  """Delete this session's entry from the gloabl tempfile (and delete the
    319                  entire file if no more running screenlets are set."""
    320                  if not name:
    321                          name = self.screenlet.__name__
    322                  # WORKAROUND: for now we manually unregister from the daemon,
    323                  #             ideally the daemon should watch the tmpfile for changes
    324                  if self.daemon_iface:
    325                          try:
    326                                  self.daemon_iface.unregister_screenlet(name)
    327                          except Exception, ex:
    328                                  print _("Failed to unregister from daemon: %s") % ex
    329                  # /WORKAROUND
    330                  # get running screenlets
    331                  running = utils.list_running_screenlets()
    332                  if running and len(running) > 0:
    333                          pass#print _("Removing entry for %s from global tempfile %s") % (name, self.tempfile)
    334                          try:
    335                                  running.remove(name)
    336                          except:
    337                                  # not found, so ok
    338                                  print _("Entry not found. Will (obviously) not be removed.")
    339                                  return True
    340                          # still running screenlets?
    341                          if running and len(running) > 0:
    342                                  # re-save new list of running screenlets
    343                                  f = open(self.tempfile, 'w')
    344                                  if f:
    345                                          for r in running:
    346                                                  f.write(r + '\n')
    347                                          f.close()
    348                                          return True
    349                                  else:
    350                                          print _("Error global tempfile not found. Some error before?")
    351                                  return False
    352                          else:
    353                                  print _('No more screenlets running.')
    354                                  self.__delete_tempfile(name)
    355                  else:
    356                          print _('No screenlets running?')
    357                          return False
    358
    359 -        def __delete_tempfile (self, name=None):
    360                  """Delete the tempfile for this session."""
    361                  if self.tempfile and os.path.isfile(self.tempfile):
    362                          print _("Deleting global tempfile %s") % self.tempfile
    363                          try:
    364                                  os.remove(self.tempfile)
    365                                  return True
    366                          except:
    367                                  print _("Error: Failed to delete global tempfile")
    368                                  return False
    369
    370 -        def __get_next_id (self):
    371                  """Get the next ID for an instance of the assigned Screenlet."""
    372                  num = 1
    373                  sln = self.screenlet.__name__[:-9]
    374                  id = sln + str(num)
    375                  while self.get_instance_by_id(id) != None:
    376                          id = sln + str(num)
    377                          num += 1
    378                  return id
    379
    380 -        def __load_instances (self):
    381                  """Check for existing instances in the current session, create them
    382                  and store them into self.instances if any are found. Returns True if
    383                  at least one instance was found, else False."""
    384                  dirlst = glob.glob(self.path + '*')
    385                  tdlen = len(self.path)
    386                  for filename in dirlst:
    387                          filename = filename[tdlen:]             # strip path from filename
    388                          print _('File: %s') % filename
    389                          if filename.endswith('.ini'):
    390                                  # create new instance
    391                                  sl = self.create_instance(id=filename[:-4], enable_saving=False)
    392                                  if sl:
    393                                          # set options for the screenlet
    394                                          print _("Set options in %s") % sl.__name__
    395                                          #self.__restore_options_from_file (sl, self.path + filename)
    396                                          self.__restore_options_from_backend(sl, self.path+filename)
    397                                          sl.enable_saving(True)
    398                                          # and call init handler
    399                                          sl.finish_loading()
    400                                  else:
    401                                          print _("Failed to create instance of '%s'!") % filename[:-4]
    402                  # if instances were found, return True, else False
    403                  if len(self.instances) > 0:
    404                          return True
    405                  return False
    406
    407          # replacement for above function
    408 -        def __restore_options_from_backend (self, screenlet, filename):
    409                  """Restore and apply a screenlet's options from the backend."""
    410                  # disable the canvas-updates in the screenlet
    411                  screenlet.disable_updates = True
    412                  # get options for SL from backend
    413                  opts = self.backend.load_instance(screenlet.id)
    414                  if opts:
    415                          for o in opts:
    416                                  # get the attribute's Option-object from Screenlet
    417                                  opt = screenlet.get_option_by_name(o)
    418                                  # NOTE: set attribute in Screenlet by calling the
    419                                  # on_import-function for the Option (to import
    420                                  # the value as the required type)
    421                                  if opt:
    422                                          setattr(screenlet, opt.name, opt.on_import(opts[o]))
    423                  # re-enable updates and call redraw/reshape
    424                  screenlet.disable_updates = False
    425                  screenlet.redraw_canvas()
    426                  screenlet.update_shape()
    427
    428 -        def __run_session (self, main_instance):
    429                  """Run the session by calling the main handler of the given Screenlet-
    430                  instance. Handles sigkill (?) and keyboard interrupts."""
    431                  # add sigkill-handler
    432                  import signal
    433                  def on_kill(*args):
    434                          #print _("Screenlet has been killed. TODO: make this an event")
    435                          pass
    436                  signal.signal(signal.SIGTERM, on_kill)
    437                  # set name of tempfile for later (else its missing after kill)
    438                  tempfile = self.screenlet.__name__
    439                  # start
    440                  try:
    441                          # start mainloop of screenlet
    442                          main_instance.main()
    443                  except KeyboardInterrupt:
    444                          # notify when daemon is closed
    445                          self.backend.flush()
    446                          print _("Screenlet '%s' has been interrupted by keyboard. TODO: make this an event") % self.screenlet.__name__
    447                  except Exception, ex:
    448                          print _("Exception in ScreenletSession: ") + ex
    449                  # finally delete the tempfile
    450                  self.__unregister_screenlet(name=tempfile)
    451
    452 -        def __parse_commandline (self):
    453                  """Check commandline args for "--session" argument and set session
    454                  name if found. Runs only once during __init__.
    455                  TODO: handle more arguments and maybe allow setting options by
    456                  commandline"""
    457                  import sys
    458                  for arg in sys.argv[1:]:
    459                          # name of session?
    460                          if arg.startswith('--session=') and len(arg)>10:
    461                                  self.name = arg[10:]
    462
    463
    464
    465 -def create_session (classobj, backend='caching', threading=False):
    466          """A very simple utility-function to easily create/start a new session."""
    467
    468          if threading:
    469                  import gtk
    470                  gtk.gdk.threads_init()
    471          session = ScreenletSession(classobj, backend_type=backend)
    472          session.start()
    473