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