Logo Search packages:      
Sourcecode: adesklets version File versions

events_handler.py

00001 """
adesklets events handler module
"""
import re
import posix_signal
import types
import string
import sys

from signal_handler import Signal_handler
from singleton import _Singleton, _States

00013 class _Events_handler(Signal_handler, _States):
    """
    Low level events handler
    """
    def __init__(self):
        _States.__init__(self)
        
        self.comm = self._States__shared_state['communicator']
        print >> self.comm, "events_set_echo", 1
        print >> self.comm, "events_set_send_sigusr1", 1
        
        Signal_handler.__init__(self,['USR1', 'ALRM', 'TERM'],self._fire_event)
        
    def _fire_event(self, signo, frame):
        pass

00029     def _flush(self):
        """
        Complety purges stdin and stdout of communicator
        from its content
        """
        for command in ['out','err']:
            try:
                while self.comm.__class__.__dict__[command](self.comm):
                    pass
            except ADESKLETSError:
                pass

    def __del__(self):
        Signal_handler.__del__(self)
        print >> self.comm, "events_set_echo", 0
        print >> self.comm, "events_set_send_sigusr1", 0
        print >> self.comm, "events_reset_all"
        self._flush()

00048 class Events_handler(_Singleton,_Events_handler):
    """
    High-level events handler

    Made to be used as a base class in applications wanting to catch
    adesklets related events. 

    class My_events_handler(Events_handler):
      def __init__(self):
            Events_handler.__init__(self)
        def __del__(self):
            Events_handler.__del__(self)
        ...

    from there, you just have to redefine the methods you are interested in,
    choosing between ready(), quit(), alarm(), menu_fire(), background_grab(),
    button_press(), button_release(), motion_notify(), enter_notify() and
    leave_notify() - see short descriptions below.

    As soon as you will instanciate an object from your class, all
    events corresponding to functions you 'overloaded' will
    automatically be caugth and will cause your object methods to be
    appropriately called.

    Note 1: all methods events but alarm() are protected critical
    sections, if POSIX reliable signals are supported on your
    architecture. This means they will not get interrupted by normal
    adesklets operation. No adesklets events will ever be lost either:
    they are garanteed to be all processed in the order they arrived,
    except for one case: quit() signal is always processed right after
    the current event method returns.
    
      WARNING 1: Of course, this means that events are not guaranteed
        to be processed as they arrive: they can be arbitrarily delayed
        since former events processing could have taken any amount of time.
        This is the reason you are encouraged to take advantage of the
        'delayed' flag passed to most methods (and to write quick to compute
        methods!). For instance, depending of your desklet, you may want
        to ignore all motion_notify events that were not processed right
        away, since they are no longuer relevant for giving quick feedback
        to the user.

        WARNING 2: As stated above, the alarm() method, used for periodic
        action, could get interrupted. You are responsible for providing
        proper exception recovery in case of problem with IO, for instance.
        Alternatively, you can use the posix_signal API, already imported,
        to make your own signal management, or use the block()
        unblock() methods provided. Please note it is probably
        a bad idea to have a long-running alarm() method blocking signals
        without any possibility of beeing interrupted.
        
    Note 2: every time an object from a class deriving from Events_handler
    is instanciated, the content of all communications channel to adesklets
    interpreter is flushed.
    
    Note 3: for obvious reasons, at most one instance of Events_handler's
    children can exist at any given time.

    Note 4: when using the python interpreter in interactive mode,
            calls to Events_handler's functions could block, depending
            of the platform/python version you use. This is due to
            the way python handle access to various streams, including
            stdout and stderr. If this happens to you, there are
            two workarounds:
                - Invoke python with the -u swith or the environment variable
                PYTHONUNBUFFERED=1 - this works many times
                - Avoid interactive mode - directly build a test script
                and run it. :-)

    Note 5: It you need to (from adesklets 0.3.0), you also have the two
    methods Events_handler::get_events() and Events_handler::set_events()
    that give you an easy way to change at run time what events are catched,
    and using what unbounded methods.

    This is especially convenient if you want to program interruptable
    timed 'effects' (see test/fading.py), while not having them
    interrupetd by the same set of events you normally use.
    """

    _events = [ ('MenuFire'       , 'menu_fire'     ),
                ('BackgroundGrab' , 'background_grab'),
                ('ButtonPress'    , 'button_press'   ),
                ('ButtonRelease'  , 'button_release' ),
                ('MotionNotify'   , 'motion_notify'  ),
                ('EnterNotify'    , 'enter_notify'   ),
                ('LeaveNotify'    , 'leave_notify'   ) ]
     
    def __init__(self):
        _Singleton.__init__(self,'Events_handler')
        _States.__init__(self)
        _Events_handler.__init__(self)
        
        self.__re_event=re.compile('^event:')
        self.__events_re = []
        self.__posix_signal_support = hasattr(posix_signal,'sigprocmask')
        
        for expr, func_name in self._events:
            # Build a regular expression, then find corresponding function
            # reference.
            if self.__class__.__dict__.has_key(func_name):
                name=self.__class__.__dict__[func_name]
                print >> self.comm, "event_catch", expr
            else:
                name=None
            self.__events_re.append(
                [re.compile('^event: '+ string.lower(expr) + '(.*)'), name])
        self._flush()

        self.block()
        self.ready()
        self.unblock()
        self._alarm()

00161     def block(self):
        """
        Convenience functions to block signals generated
        by adesklets, if supported by your platform

        WARNING: you never need to call it for any event but alarm()
        """
        if self.__posix_signal_support:
            posix_signal.sigprocmask(posix_signal.SIG_BLOCK,
                                     [posix_signal.SIGTERM,
                                      posix_signal.SIGUSR1,
                                      posix_signal.SIGALRM])
00173     def unblock(self):
        """
        Convenience functions to block signals generated
        by adesklets, if supported by your platform
        
        WARNING: you never need to call it for any event but alarm()
        """
        if self.__posix_signal_support:
            posix_signal.sigprocmask(posix_signal.SIG_UNBLOCK,
                                     [posix_signal.SIGTERM,
                                      posix_signal.SIGUSR1,
                                      posix_signal.SIGALRM])
            
    def _fire_event(self, signo, frame):
        if signo==posix_signal.SIGALRM:
            self._alarm()
            return
        # define a critical section: everything inside the handler should be
        # protected from further signals call
        self.block()
        if signo==posix_signal.SIGTERM:
            self._quit()
        else:
            delayed=False
            while 1:
                # 'TERM' signal should be processed in priority
                if delayed:
                    if posix_signal.SIGTERM in posix_signal.sigpending():
                        posix_signal.sigsuspend(posix_signal.SIGUSR1|
                                                posix_signal.SIGALRM)
                event=self.comm.err()
                if event!=None:
                    if self.__re_event.match(event):
                        # Now, we know we have both an event and a match
                        for expr, func in self.__events_re:
                            match=expr.match(event)
                            if match:
                                # Unpack, then convert arguments to integer
                                # as needed, then call proper function
                                func(self,delayed,
                                     *map(lambda str : self._to_int(str),
                                          [x for x in
                                           re.split('\W+',match.expand('\\1'))
                                           if x != '']))
                    delayed=True
                else:
                    break
        self.unblock()

00222     def pause(self):
        """
        Put python intepreter to sleep forever, only waking up to
        process events.
        """
        while 1:
            posix_signal.pause()

00230     def ready(self):
        """
        Called only once when adesklets is ready to receive command
        """
        pass
00235     def quit(self):
        """
        Called only once when adesklets is about to quit:
        the python interpreter will exit right after this function returns.

        Note: this function is guaranteed not to be interrupted
        by others signals from adesklets, but SIGKILL, whether you have POSIX
        reliable signal support or not.
        """
        pass
00245     def alarm(self):
        """
        Called periodically. Usefull to perform any perdiodic operation.
        Delay until next call is determined from return value (which must be
        an integer), in seconds (zero means not to reschedule). First call is
        automatically scheduled right after the ready() event method returns.
        """
        return 0
00253     def menu_fire(self, delayed, menu_id, item):
        """
        Called whenever a catchable menu selection returns.
        `item' may be None if no selection was made.
        """
        pass
00259     def background_grab(self, delayed):
        """
        Called whenever adesklets' background image is updated
        """
        pass
00264     def button_press(self, delayed, x, y, button):
        """
        Called whenever a button is pressed
        """
        pass
00269     def button_release(self, delayed, x, y, button):
        """
        Called whenever a button is released
        """
        pass
00274     def motion_notify(self, delayed, x, y):
        """
        Called whenever pointer is moved inside the window
        """
        pass
00279     def enter_notify(self, delayed, x, y):
        """
        Called whenever pointer enters the window
        """
        pass
00284     def leave_notify(self, delayed, x, y):
        """
        Called whenever pointer leaves the window
        """
        pass
        
    def _quit(self):
        Signal_handler.clear(self,['USR1','ALRM'])
        self.quit()
        sys.exit(0)

    def _alarm(self):
        timeout=self.alarm()
        if type(timeout)!=types.IntType:
            timeout=0
        posix_signal.alarm(timeout)
        pass
        
00302     def _to_int(self,str):
        """
        Try to perform the conversion.
        If it fails, return basic object.
        """
        try:
            result=int(str)
        except ValueError:
            return str
        else:
            return result

00314     def get_events(self):
        """
        This returns a dictionary of all events catched by the desklet
        at the time of calling. 

        For reliable use, it should always be called when signals are blocked
        (See Events_handler class description above)
        """
        return dict(list(self._get_events()))

00324     def set_events(self,events):
        """
        This overrides the catched events using a dictionnary compatible to
        the one sent back by Events_handler::get_events().

        This gives the desklet programmer a way to dynamically change
        what signals are caught, which may be useful when working
        with indirect mode programming (adesklets.start() command and others
        alike).
        """
        self.__events_re=[]
        print >> self.comm, 'events_reset_all'
        for expr, func in events.items():
            self.__events_re.append(
                [re.compile('^event: '+ string.lower(expr) + '(.*)'),
                 {0:None,1:func}[callable(func)]])
            if callable(func):
                print >> self.comm, "event_catch", expr
    
    def _get_events(self):
        for expr, func in self.__events_re:
            mapped = False
            for event in self._events:
                if expr.match('event: ' + string.lower(event[0])):
                    mapped = True
                    yield event[0], func
            if not mapped:
                yield event[0], None
        
    def __del__(self):
        _Events_handler.__del__(self)
        _States.__del__(self)
        _Singleton.__del__(self)

Generated by  Doxygen 1.6.0   Back to index