Package screenlets :: Module session
[hide private]
[frames] | no frames]

Source Code for Module screenlets.session

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