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