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