Changeset 423:959722a16e8d


Ignore:
Timestamp:
02/16/13 13:43:47 (11 years ago)
Author:
István Váradi <ivaradi@…>
Branch:
xplane
hg-Phase:
(<MercurialRepository 1 'hg:/home/ivaradi/mlx/hg' '/'>, 'public')
Message:

Implemented hotkey support for X-Plane

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/mlx/xplane.py

    r421 r423  
    1515from xplra import TYPE_INT, TYPE_FLOAT, TYPE_DOUBLE
    1616from xplra import TYPE_FLOAT_ARRAY, TYPE_INT_ARRAY, TYPE_BYTE_ARRAY
     17from xplra import HOTKEY_MODIFIER_SHIFT, HOTKEY_MODIFIER_CONTROL
    1718
    1819#------------------------------------------------------------------------------
     
    3031
    3132_mps2knots = 3600.0 / 1852
     33
     34#------------------------------------------------------------------------------
     35
     36class Request(object):
     37    """Base class for one-shot requests."""
     38    def __init__(self, handler, callback, extra):
     39        """Construct the request."""
     40        self._handler = handler
     41        self._callback = callback
     42        self._extra = extra
     43        self._result = None
     44
     45    def process(self, time):
     46        """Process the request.
     47
     48        Return True if the request has succeeded, False if data validation
     49        has failed for a reading request. An exception may also be thrown
     50        if there is some lower-level communication problem."""
     51        if self._process(time):
     52            Handler._callSafe(lambda: self._callback(self._result,
     53                                                     self._extra))
     54            return True
     55        else:
     56            return False
     57
     58    def fail(self):
     59        """Handle the failure of this request."""
     60        Handler._callSafe(lambda: self._callback(False, self._extra))
     61
     62class DataRequest(Request):
     63    """A simple, one-shot data read or write request."""
     64    def __init__(self, handler, forWrite, data, callback, extra,
     65                 validator = None):
     66        """Construct the request."""
     67        super(DataRequest, self).__init__(handler, callback, extra)
     68
     69        self._forWrite = forWrite
     70        self._validator = validator
     71
     72        xplane = handler._xplane
     73        self._multiBuffer = xplane.createMultiSetter() if forWrite \
     74                            else xplane.createMultiGetter()
     75
     76        Handler._setupMultiBuffer(self._multiBuffer,
     77                                  [(d[0], d[1]) for d in data])
     78
     79        if forWrite:
     80            index = 0
     81            for (_, _, value) in data:
     82                self._multiBuffer[index] = value
     83                index += 1
     84
     85
     86    def fail(self):
     87        """Handle the failure of this request."""
     88        if self._forWrite:
     89            super(DataRequest, self).fail()
     90        else:
     91            Handler._callSafe(lambda: self._callback(None, self._extra))
     92
     93    def _process(self, time):
     94        """Process the request."""
     95        if self._forWrite:
     96            self._multiBuffer.execute()
     97            self._result = True
     98            return True
     99        elif Handler._performRead(self._multiBuffer,
     100                                  self._extra, self._validator):
     101            self._result = self._multiBuffer
     102            return True
     103        else:
     104            return False
     105
     106class ShowMessageRequest(Request):
     107    """Request to show a message in the simulator window."""
     108    def __init__(self, handler, message, duration, callback, extra):
     109        """Construct the request."""
     110        super(ShowMessageRequest, self).__init__(handler,
     111                                                         callback, extra)
     112        self._message = message
     113        self._duration = duration
     114
     115    def _process(self, time):
     116        """Process the request."""
     117        self._handler._xplane.showMessage(self._message, self._duration)
     118        self._result = True
     119        return True
     120
     121class RegisterHotkeysRequest(Request):
     122    """Request to register hotkeys with the simulator."""
     123    def __init__(self, handler, hotkeyCodes, callback, extra):
     124        """Construct the request."""
     125        super(RegisterHotkeysRequest, self).__init__(handler,
     126                                                     callback,
     127                                                     extra)
     128        self._hotkeyCodes = hotkeyCodes
     129
     130    def _process(self, time):
     131        """Process the request."""
     132        self._handler._xplane.registerHotkeys(self._hotkeyCodes)
     133        self._result = True
     134        return True
     135
     136class UnregisterHotkeysRequest(Request):
     137    """Request to register hotkeys with the simulator."""
     138    def _process(self, time):
     139        """Process the request."""
     140        self._handler._xplane.unregisterHotkeys()
     141        self._result = True
     142        return True
     143
     144class PeriodicRequest(object):
     145    """A periodic request."""
     146    def __init__(self, handler, id, period, callback, extra):
     147        """Construct the periodic request."""
     148        self._handler = handler
     149        self._id = id
     150        self._period = period
     151        self._nextFire = time.time()
     152        self._callback = callback
     153        self._extra = extra
     154        self._result = None
     155
     156    @property
     157    def id(self):
     158        """Get the ID of this periodic request."""
     159        return self._id
     160
     161    @property
     162    def nextFire(self):
     163        """Get the next firing time."""
     164        return self._nextFire
     165
     166    def process(self, now):
     167        """Check if this request should be executed, and if so, do so.
     168
     169        now is the time at which the request is being executed. If this
     170        function is called too early, nothing is done, and True is
     171        returned.
     172
     173        Return True if the request has succeeded, False if data validation
     174        has failed. An exception may also be thrown if there is some
     175        lower-level communication problem."""
     176        if now<self._nextFire:
     177            return True
     178
     179        isOK = self._process(time)
     180
     181        if isOK:
     182            Handler._callSafe(lambda: self._callback(self._result,
     183                                                     self._extra))
     184            now = time.time()
     185            while self._nextFire <= now:
     186                self._nextFire += self._period
     187
     188        return isOK
     189
     190    def fail(self):
     191        """Handle the failure of this request."""
     192        pass
     193
     194    def __cmp__(self, other):
     195        """Compare two periodic requests. They are ordered by their next
     196        firing times."""
     197        return cmp(self._nextFire, other._nextFire)
     198
     199class PeriodicDataRequest(PeriodicRequest):
     200    """A periodic request."""
     201    def __init__(self, handler, id, period, data, callback, extra,
     202                 validator):
     203        """Construct the periodic request."""
     204        super(PeriodicDataRequest, self).__init__(handler, id, period,
     205                                                  callback, extra)
     206        self._validator = validator
     207        self._multiGetter = handler._xplane.createMultiGetter()
     208        Handler._setupMultiBuffer(self._multiGetter, data)
     209
     210    def _process(self, now):
     211        """Process the request."""
     212        if Handler._performRead(self._multiGetter,
     213                                self._extra, self._validator):
     214            self._result = self._multiGetter
     215            return True
     216        else:
     217            return False
     218
     219#------------------------------------------------------------------------------
     220
     221class HotkeysStateRequest(PeriodicRequest):
     222    """Periodic hotkey query request."""
     223    def _process(self, now):
     224        """Process the request."""
     225        self._result = self._handler._xplane.queryHotkeys()
     226        return True
    32227
    33228#------------------------------------------------------------------------------
     
    97292
    98293    @staticmethod
    99     def _performRead(multiGetter, callback, extra, validator):
     294    def _performRead(multiGetter, extra, validator):
    100295        """Perform a read request.
    101296
     
    112307            if validator is None or \
    113308               Handler._callSafe(lambda: validator(multiGetter, extra)):
    114                 Handler._callSafe(lambda: callback(multiGetter, extra))
    115309                return True
    116310            else:
     
    118312        return False
    119313
    120     class Request(object):
    121         """A simple, one-shot request."""
    122         def __init__(self, handler, forWrite, data, callback, extra,
    123                      validator = None):
    124             """Construct the request."""
    125             self._forWrite = forWrite
    126             self._callback = callback
    127             self._extra = extra
    128             self._validator = validator
    129 
    130             xplane = handler._xplane
    131             self._multiBuffer = xplane.createMultiSetter() if forWrite \
    132                                 else xplane.createMultiGetter()
    133 
    134             Handler._setupMultiBuffer(self._multiBuffer,
    135                                       [(d[0], d[1]) for d in data])
    136 
    137             if forWrite:
    138                 index = 0
    139                 for (_, _, value) in data:
    140                     self._multiBuffer[index] = value
    141                     index += 1
    142 
    143         def process(self, time):
    144             """Process the request.
    145 
    146             Return True if the request has succeeded, False if data validation
    147             has failed for a reading request. An exception may also be thrown
    148             if there is some lower-level communication problem."""
    149             if self._forWrite:
    150                 self._multiBuffer.execute()
    151                 Handler._callSafe(lambda: self._callback(True, self._extra))
    152                 return True
    153             else:
    154                 return Handler._performRead(self._multiBuffer, self._callback,
    155                                             self._extra, self._validator)
    156 
    157         def fail(self):
    158             """Handle the failure of this request."""
    159             if self._forWrite:
    160                 Handler._callSafe(lambda: self._callback(False, self._extra))
    161             else:
    162                 Handler._callSafe(lambda: self._callback(None, self._extra))
    163 
    164     class PeriodicRequest(object):
    165         """A periodic request."""
    166         def __init__(self, handler, id, period, data, callback, extra,
    167                      validator):
    168             """Construct the periodic request."""
    169             self._id = id
    170             self._period = period
    171             self._nextFire = time.time()
    172             self._callback = callback
    173             self._extra = extra
    174             self._validator = validator
    175 
    176             self._multiGetter = handler._xplane.createMultiGetter()
    177             Handler._setupMultiBuffer(self._multiGetter, data)
    178 
    179         @property
    180         def id(self):
    181             """Get the ID of this periodic request."""
    182             return self._id
    183 
    184         @property
    185         def nextFire(self):
    186             """Get the next firing time."""
    187             return self._nextFire
    188 
    189         def process(self, time):
    190             """Check if this request should be executed, and if so, do so.
    191 
    192             time is the time at which the request is being executed. If this
    193             function is called too early, nothing is done, and True is
    194             returned.
    195 
    196             Return True if the request has succeeded, False if data validation
    197             has failed. An exception may also be thrown if there is some
    198             lower-level communication problem."""
    199             if time<self._nextFire:
    200                 return True
    201 
    202             isOK = Handler._performRead(self._multiGetter, self._callback,
    203                                         self._extra, self._validator)
    204 
    205             if isOK:
    206                 while self._nextFire <= time:
    207                     self._nextFire += self._period
    208 
    209             return isOK
    210 
    211         def fail(self):
    212             """Handle the failure of this request."""
    213             pass
    214 
    215         def __cmp__(self, other):
    216             """Compare two periodic requests. They are ordered by their next
    217             firing times."""
    218             return cmp(self._nextFire, other._nextFire)
    219 
    220     class ShowMessageRequest(object):
    221         """Request to show a message in the simulator window."""
    222         def __init__(self, handler, message, duration, callback, extra):
    223             """Construct the request."""
    224             self._handler = handler
    225             self._message = message
    226             self._duration = duration
    227             self._callback = callback
    228             self._extra = extra
    229 
    230         def process(self, time):
    231             """Process the request.
    232 
    233             Return True if the request has succeeded. An exception may also be
    234             thrown if there is some lower-level communication problem."""
    235             self._handler._xplane.showMessage(self._message, self._duration)
    236             Handler._callSafe(lambda: self._callback(True, self._extra))
    237             return True
    238 
    239         def fail(self):
    240             """Handle the failure of this request."""
    241             Handler._callSafe(lambda: self._callback(False, self._extra))
    242 
    243314    def __init__(self, connectionListener,
    244315                 connectAttempts = -1, connectInterval = 0.2):
     
    276347        """
    277348        with self._requestCondition:
    278             self._requests.append(Handler.Request(self, False, data,
    279                                                   callback, extra,
    280                                                   validator))
     349            self._requests.append(DataRequest(self, False, data,
     350                                              callback, extra,
     351                                              validator))
    281352            self._requestCondition.notify()
    282353
     
    295366        """
    296367        with self._requestCondition:
    297             request = Handler.Request(self, True, data, callback, extra)
     368            request = DataRequest(self, True, data, callback, extra)
    298369            #print "xplane.Handler.requestWrite", request
    299370            self._requests.append(request)
     
    311382            id = self._nextPeriodicID
    312383            self._nextPeriodicID += 1
    313             request = Handler.PeriodicRequest(self, id, period, data, callback,
    314                                               extra, validator)
     384            request = PeriodicDataRequest(self, id, period,
     385                                          data, callback,
     386                                          extra, validator)
    315387            self._periodicRequests.append(request)
    316388            self._requestCondition.notify()
     
    329401        """Request showing a message in the simulator."""
    330402        with self._requestCondition:
    331             self._requests.append(Handler.ShowMessageRequest(self,
    332                                                              message, duration,
    333                                                              callback, extra))
     403            self._requests.append(ShowMessageRequest(self,
     404                                                     message, duration,
     405                                                     callback, extra))
     406            self._requestCondition.notify()
     407
     408    def registerHotkeys(self, hotkeys, callback, extra = None):
     409        """Request registering the given hotkeys."""
     410        with self._requestCondition:
     411            self._requests.append(RegisterHotkeysRequest(self, hotkeys,
     412                                                         callback, extra))
     413            self._requestCondition.notify()
     414
     415    def requestHotkeysState(self, period, callback, extra = None):
     416        """Request a periodic query of the hotkey status."""
     417        with self._requestCondition:
     418            id = self._nextPeriodicID
     419            self._nextPeriodicID += 1
     420            request = HotkeysStateRequest(self, id, period, callback, extra)
     421            self._periodicRequests.append(request)
     422            self._requestCondition.notify()
     423            return id
     424
     425    def unregisterHotkeys(self, callback, extra = None):
     426        """Request unregistering the hotkeys."""
     427        with self._requestCondition:
     428            self._requests.append(UnregisterHotkeysRequest(self,
     429                                                           callback, extra))
    334430            self._requestCondition.notify()
    335431
     
    557653
    558654    @staticmethod
    559     def _appendHotkeyData(data, offset, hotkey):
    560         """Append the data for the given hotkey to the given array, that is
    561         intended to be passed to requestWrite call on the handler."""
    562         data.append((offset + 0, "b", ord(hotkey.key)))
    563 
    564         modifiers = 0
    565         if hotkey.ctrl: modifiers |= 0x02
    566         if hotkey.shift: modifiers |= 0x01
    567         data.append((offset + 1, "b", modifiers))
    568 
    569         data.append((offset + 2, "b", 0))
    570 
    571         data.append((offset + 3, "b", 0))
     655    def _getHotkeyCode(hotkey):
     656        """Get the hotkey code for the given hot key."""
     657        code = ord(hotkey.key)
     658        if hotkey.shift: code |= HOTKEY_MODIFIER_SHIFT
     659        if hotkey.ctrl: code |= HOTKEY_MODIFIER_CONTROL
     660        return code
    572661
    573662    def __init__(self, connectionListener, connectAttempts = -1,
     
    619708
    620709        self._hotkeyLock = threading.Lock()
    621         self._hotkeys = None
     710        self._hotkeyCodes = None
    622711        self._hotkeySetID = 0
    623712        self._hotkeySetGeneration = 0
     
    749838        - the ID of the hotkey set as returned by this function,
    750839        - the list of the indexes of the hotkeys that were pressed."""
    751         pass
    752         # TODO: implement hotkey handling for X-Plane
    753         # with self._hotkeyLock:
    754         #     assert self._hotkeys is None
    755 
    756         #     self._hotkeys = hotkeys
    757         #     self._hotkeySetID += 1
    758         #     self._hotkeySetGeneration = 0
    759         #     self._hotkeyCallback = callback
    760 
    761         #     self._handler.requestRead([(0x320c, "u")],
    762         #                               self._handleNumHotkeys,
    763         #                               (self._hotkeySetID,
    764         #                                self._hotkeySetGeneration))
    765 
    766         #     return self._hotkeySetID
     840        with self._hotkeyLock:
     841            assert self._hotkeyCodes is None
     842
     843            self._hotkeyCodes = \
     844              [self._getHotkeyCode(hotkey) for hotkey in hotkeys]
     845            self._hotkeySetID += 1
     846            self._hotkeySetGeneration = 0
     847            self._hotkeyCallback = callback
     848
     849            self._handler.registerHotkeys(self._hotkeyCodes,
     850                                          self._handleHotkeysRegistered,
     851                                          (self._hotkeySetID,
     852                                           self._hotkeySetGeneration))
     853
     854            return self._hotkeySetID
    767855
    768856    def clearHotkeys(self):
     
    777865        clearHotkeys(), this stored ID should be cleared so that the check
    778866        fails for sure."""
    779         pass
    780         # with self._hotkeyLock:
    781         #     if self._hotkeys is not None:
    782         #         self._hotkeys = None
    783         #         self._hotkeySetID += 1
    784         #         self._hotkeyCallback = None
    785         #         self._clearHotkeyRequest()
     867        with self._hotkeyLock:
     868            if self._hotkeyCodes is not None:
     869                self._hotkeyCodes = None
     870                self._hotkeySetID += 1
     871                self._hotkeyCallback = None
     872                self._clearHotkeyRequest()
    786873
    787874    def disconnect(self, closingMessage = None, duration = 3):
     
    803890        simulator of the given type."""
    804891        self._fsType = fsType
    805         # TODO: implement hotkey handling for X-Plane
    806         # with self._hotkeyLock:
    807         #     if self._hotkeys is not None:
    808         #         self._hotkeySetGeneration += 1
    809 
    810         #         self._handler.requestRead([(0x320c, "u")],
    811         #                                   self._handleNumHotkeys,
    812         #                                   (self._hotkeySetID,
    813         #                                    self._hotkeySetGeneration))
     892        with self._hotkeyLock:
     893            if self._hotkeyCodes is not None:
     894                self._hotkeySetGeneration += 1
     895
     896                self._handler.registerHotkeys(self._hotkeyCodes,
     897                                              self._handleHotkeysRegistered,
     898                                              (self._hotkeySetID,
     899                                               self._hotkeySetGeneration))
     900
    814901        self._connectionListener.connected(fsType, descriptor)
    815902
     
    10241111            self._handler.disconnect()
    10251112
    1026     def _handleNumHotkeys(self, data, (id, generation)):
    1027         """Handle the result of the query of the number of hotkeys"""
    1028         pass
    1029         # TODO: implement hotkey handling for X-Plane
    1030         # with self._hotkeyLock:
    1031         #     if id==self._hotkeySetID and generation==self._hotkeySetGeneration:
    1032         #         numHotkeys = data[0]
    1033         #         print "xplra.Simulator._handleNumHotkeys: numHotkeys:", numHotkeys
    1034         #         data = [(0x3210 + i*4, "d") for i in range(0, numHotkeys)]
    1035         #         self._handler.requestRead(data, self._handleHotkeyTable,
    1036         #                                   (id, generation))
    1037 
    1038     def _setupHotkeys(self, data):
    1039         """Setup the hiven hotkeys and return the data to be written.
    1040 
    1041         If there were hotkeys set previously, they are reused as much as
    1042         possible. Any of them not reused will be cleared."""
    1043         # TODO: implement hotkey handling for X-Plane
    1044         hotkeys = self._hotkeys
    1045         numHotkeys = len(hotkeys)
    1046 
    1047         oldHotkeyOffsets = set([] if self._hotkeyOffets is None else
    1048                                self._hotkeyOffets)
    1049 
    1050         self._hotkeyOffets = []
    1051         numOffsets = 0
    1052 
    1053         while oldHotkeyOffsets:
    1054             offset = oldHotkeyOffsets.pop()
    1055             self._hotkeyOffets.append(offset)
    1056             numOffsets += 1
    1057 
    1058             if numOffsets>=numHotkeys:
    1059                 break
    1060 
    1061         for i in range(0, len(data)):
    1062             if numOffsets>=numHotkeys:
    1063                 break
    1064 
    1065             if data[i]==0:
    1066                 self._hotkeyOffets.append(0x3210 + i*4)
    1067                 numOffsets += 1
    1068 
    1069         writeData = []
    1070         for i in range(0, numOffsets):
    1071             Simulator._appendHotkeyData(writeData,
    1072                                         self._hotkeyOffets[i],
    1073                                         hotkeys[i])
    1074 
    1075         for offset in oldHotkeyOffsets:
    1076             writeData.append((offset, "u", long(0)))
    1077 
    1078         return writeData
    1079 
    1080     def _handleHotkeyTable(self, data, (id, generation)):
    1081         """Handle the result of the query of the hotkey table."""
    1082         with self._hotkeyLock:
    1083             if id==self._hotkeySetID and generation==self._hotkeySetGeneration:
    1084                 writeData = self._setupHotkeys(data)
    1085                 self._handler.requestWrite(writeData,
    1086                                            self._handleHotkeysWritten,
    1087                                            (id, generation))
    1088 
    1089     def _handleHotkeysWritten(self, success, (id, generation)):
     1113    def _handleHotkeysRegistered(self, success, (id, generation)):
    10901114        """Handle the result of the hotkeys having been written."""
    10911115        with self._hotkeyLock:
    10921116            if success and id==self._hotkeySetID and \
    10931117            generation==self._hotkeySetGeneration:
    1094                 data = [(offset + 3, "b") for offset in self._hotkeyOffets]
    1095 
    10961118                self._hotkeyRequestID = \
    1097                     self._handler.requestPeriodicRead(0.5, data,
    1098                                                       self._handleHotkeys,
    1099                                                       (id, generation))
     1119                  self._handler.requestHotkeysState(0.5,
     1120                                                    self._handleHotkeys,
     1121                                                    (id, generation))
    11001122
    11011123    def _handleHotkeys(self, data, (id, generation)):
     
    11141136
    11151137        if hotkeysPressed:
    1116             data = []
    1117             for index in hotkeysPressed:
    1118                 data.append((offsets[index]+3, "b", int(0)))
    1119             self._handler.requestWrite(data, self._handleHotkeysCleared)
    1120 
    11211138            callback(id, hotkeysPressed)
    1122 
    1123     def _handleHotkeysCleared(self, sucess, extra):
    1124         """Callback for the hotkey-clearing write request."""
    11251139
    11261140    def _clearHotkeyRequest(self):
    11271141        """Clear the hotkey request in the handler if there is any."""
    11281142        if self._hotkeyRequestID is not None:
     1143            self._handler.unregisterHotkeys(self._hotkeysUnregistered)
    11291144            self._handler.clearPeriodic(self._hotkeyRequestID)
    11301145            self._hotkeyRequestID = None
     1146
     1147    def _hotkeysUnregistered(self):
     1148        """Called when the hotkeys have been unregistered."""
     1149        pass
    11311150
    11321151#------------------------------------------------------------------------------
Note: See TracChangeset for help on using the changeset viewer.