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