1 ''' Top-level python bindings for the lircd socket interface. '''    28 from abc 
import ABCMeta, abstractmethod
    40 _DEFAULT_PROG = 
'lircd-client'    44     ''' Get default value for the lircd socket path, using (falling priority):    46        - The environment variable LIRC_SOCKET_PATH.    47        - The 'output' value in the lirc_options.conf file if value and the    48          corresponding file exists.    49        - A hardcoded default lirc.config.VARRUNDIR/lirc/lircd, possibly    53     if 'LIRC_SOCKET_PATH' in os.environ:
    54         return os.environ[
'LIRC_SOCKET_PATH']
    55     path = lirc.config.SYSCONFDIR + 
'/lirc/lirc_options.conf'    56     parser = configparser.SafeConfigParser()
    59     except configparser.Error:
    62         if parser.has_section(
'lircd'):
    64                 path = str(parser.get(
'lircd', 
'output'))
    65                 if os.path.exists(path):
    67             except configparser.NoOptionError:
    69     return lirc.config.VARRUNDIR + 
'/lirc/lircd'    73     ''' Get default path to the lircrc file according to (falling priority):    75        - $XDG_CONFIG_HOME/lircrc if environment variable and file exists.    76        - ~/.config/lircrc if it exists.    77        - ~/.lircrc if it exists    78        - A hardcoded default lirc.config.SYSCONFDIR/lirc/lircrc, whether    81     if 'XDG_CONFIG_HOME' in os.environ:
    82         path = os.path.join(os.environ[
'XDG_CONFIG_HOME'], 
'lircrc')
    83         if os.path.exists(path):
    85     path = os.path.join(os.path.expanduser(
'~'), 
'.config' 'lircrc')
    86     if os.path.exists(path):
    88     path = os.path.join(os.path.expanduser(
'~'), 
'.lircrc')
    89     if os.path.exists(path):
    91     return os.path.join(lirc.config.SYSCONFDIR, 
'lirc', 
'lircrc')
    94 class BadPacketException(Exception):
    95     ''' Malformed or otherwise unparsable packet received. '''    99 class TimeoutException(Exception):
   100     ''' Timeout receiving data from remote host.'''   155     ''' Abstract interface for all connections. '''   160     def __exit__(self, exc_type, exc, traceback):
   164     def readline(self, timeout: float = 
None) -> str:
   165         ''' Read a buffered line   169               - If set to 0 immediately return either a line or None.   170               - If set to None (default mode) use blocking read.   172         Returns: code string as described in lircd(8) without trailing   175         Raises: TimeoutException  if timeout > 0 expires.   181         ''' Return the file nr used for IO, suitable for select() etc. '''   186         ''' Return true if next readline(None) won't block . '''   191         ''' Close/release all resources '''   195 class RawConnection(AbstractConnection):
   196     ''' Interface to receive code strings as described in lircd(8).   199       - socket_path: lircd output socket path, see get_default_socket_path()   201       - prog: Program name used in lircrc decoding, see ircat(1). Could be   202         omitted if only raw keypresses should be read.   207     def __init__(self, socket_path: str = 
None, prog: str = _DEFAULT_PROG):
   209             os.environ[
'LIRC_SOCKET_PATH'] = socket_path
   212         _client.lirc_deinit()
   213         fd = _client.lirc_init(prog)
   214         self.
_socket = socket.fromfd(fd, socket.AF_UNIX, socket.SOCK_STREAM)
   215         self.
_select = selectors.DefaultSelector()
   219     def readline(self, timeout: float = 
None) -> str:
   220         ''' Implements AbstractConnection.readline(). '''   225                 start + timeout - time.clock() 
if timeout 
else timeout)
   229                         "readline: no data within %f seconds" % timeout)
   234         return line.decode(
'ascii', 
'ignore')
   237         ''' Implements AbstractConnection.fileno(). '''   241         ''' Implements AbstractConnection.has_data() '''   245         ''' Implements AbstractConnection.close() '''   247         _client.lirc_deinit()
   250 AbstractConnection.register(RawConnection)          
   254     ''' Interface to receive lircrc-translated keypresses. This is basically   255     built on top of lirc_code2char() and as such supporting centralized   256     translations using lircrc_class. See lircrcd(8).   259       - program: string, used to identify client. See ircat(1)   260       - lircrc: lircrc file path. See get_default_lircrc_path() for defaults.   261       - socket_path: lircd output socket path,  see get_default_socket_path()   266     def __init__(self, program: str,
   267                  lircrc_path: str = 
None,
   268                  socket_path: str = 
None):
   272             raise FileNotFoundError(
'Cannot find lircrc config file.')
   274         self.
_lircrc = _client.lirc_readconfig(lircrc_path)
   278     def readline(self, timeout: float = 
None):
   279         ''' Implements AbstractConnection.readline(). '''   286             if not strings 
or len(strings) == 0:
   294         ''' Implements AbstractConnection.has_data() '''   298         ''' Implements AbstractConnection.fileno(). '''   302         ''' Implements AbstractConnection.close() '''   304         _client.lirc_freeconfig(self.
_lircrc)
   307 AbstractConnection.register(LircdConnection)     
   364     ''' Extends the parent with a send() method. '''   366     def __init__(self, socket_path: str = 
None):
   367         RawConnection.__init__(self, socket_path)
   369     def send(self, command: (bytearray, str)):
   370         ''' Send  single line over socket '''   371         if not isinstance(command, bytearray):
   372             command = command.encode(
'ascii')
   373         while len(command) > 0:
   375             command = command[sent:]
   379     ''' Public reply parser result, available when completed. '''   386     ''' Command, parser and connection container with a run() method. '''   388     def __init__(self, cmd: str,
   389                  connection: AbstractConnection,
   390                  timeout: float = 0.4):
   391         self.
_conn = connection
   395     def run(self, timeout: float = 
None):
   396         ''' Run the command and return a Reply. Timeout as of   397         AbstractConnection.readline()   400         while not self.
_parser.is_completed():
   409     ''' The status/result from parsing a command reply.   412         result: Enum Result, reflects parser state.   413         success: bool, reflects SUCCESS/ERROR.   414         data: List of lines, the command DATA payload.   415         sighup: bool, reflects if a SIGHUP package has been received   416                 (these are otherwise ignored).   417         last_line: str, last input line (for error messages).   420         self.
result = Result.INCOMPLETE
   428     ''' Handles the actual parsing of a command reply.  '''   432         self._state = self._State.BEGIN
   433         self._lines_expected = 
None   434         self._buffer = bytearray(0)
   436     def is_completed(self) -> bool:
   437         ''' Returns true if no more reply input is required. '''   440     def feed(self, line: str):
   441         ''' Enter a line of data into parsing FSM, update state. '''   444             self._State.BEGIN: self._begin,
   445             self._State.COMMAND: self._command,
   446             self._State.RESULT: self._result,
   447             self._State.DATA: self._data,
   448             self._State.LINE_COUNT: self._line_count,
   449             self._State.LINES: self._lines,
   450             self._State.END: self._end,
   451             self._State.SIGHUP_END: self._sighup_end
   458         if self.
_state == self._State.DONE:
   468         ''' Internal FSM state. '''   480     def _bad_packet_exception(self, line):
   483             'Cannot parse: %s\nat state: %s\n' % (line, self.
_state))
   485     def _begin(self, line):
   487             self._state = self._State.COMMAND
   489     def _command(self, line):
   491             self._bad_packet_exception(line)
   492         elif line == 
'SIGHUP':
   493             self._state = self._State.SIGHUP_END
   496             self._state = self._State.RESULT
   498     def _result(self, line):
   499         if line 
in [
'SUCCESS', 
'ERROR']:
   500             self.success = line == 
'SUCCESS'   501             self._state = self._State.DATA
   503             self._bad_packet_exception(line)
   505     def _data(self, line):
   507             self._state = self._State.DONE
   509             self._state = self._State.LINE_COUNT
   511             self._bad_packet_exception(line)
   513     def _line_count(self, line):
   515             self._lines_expected = int(line)
   517             self._bad_packet_exception(line)
   518         if self._lines_expected == 0:
   519             self._state = self._State.END
   521             self._state = self._State.LINES
   523     def _lines(self, line):
   524         self.data.append(line)
   525         if len(self.data) >= self._lines_expected:
   526             self._state = self._State.END
   528     def _end(self, line):
   530             self._bad_packet_exception(line)
   531         self._state = self._State.DONE
   533     def _sighup_end(self, line):
   535             ReplyParser.__init__(self)
   538             self._bad_packet_exception(line)
   556     ''' Simulate a button press, see SIMULATE in lircd(8) manpage.  '''   559     def __init__(self, connection: AbstractConnection,
   560                  remote: str, key: str, repeat: int = 1, keycode: int = 0):
   561         cmd = 
'SIMULATE %016d %02d %s %s\n' % \
   562             (int(keycode), int(repeat), key, remote)
   563         Command.__init__(self, cmd, connection)
   567     ''' List available remotes, see LIST in lircd(8) manpage. '''   569     def __init__(self, connection: AbstractConnection):
   570         Command.__init__(self, 
'LIST\n', connection)
   574     ''' List available keys in given remote, see LIST in lircd(8) manpage. '''   576     def __init__(self, connection: AbstractConnection, remote: str):
   577         Command.__init__(self, 
'LIST %s\n' % remote, connection)
   581     ''' Start repeating given key, see SEND_START in lircd(8) manpage. '''   583     def __init__(self, connection: AbstractConnection,
   584                  remote: str, key: str):
   585         cmd = 
'SEND_START %s %s\n' % (remote, key)
   586         Command.__init__(self, cmd, connection)
   590     ''' Stop repeating given key, see SEND_STOP in lircd(8) manpage. '''   592     def __init__(self, connection: AbstractConnection,
   593                  remote: str, key: str):
   594         cmd = 
'SEND_STOP %s %s\n' % (remote, key)
   595         Command.__init__(self, cmd, connection)
   599     ''' Send given key, see SEND_ONCE in lircd(8) manpage. '''   601     def __init__(self, connection: AbstractConnection,
   602                  remote: str, keys: str):
   604             raise ValueError(
'No keys to send given')
   605         cmd = 
'SEND_ONCE %s %s\n' % (remote, 
' '.join(keys))
   606         Command.__init__(self, cmd, connection)
   610     ''' Set transmitters to use, see SET_TRANSMITTERS in lircd(8) manpage.   613         transmitter: Either a bitmask or a list of int describing active   617     def __init__(self, connection: AbstractConnection,
   618                  transmitters: (int, list)):
   619         if isinstance(transmitters, list):
   621             for transmitter 
in transmitters:
   622                 mask |= (1 << (int(transmitter) - 1))
   625         cmd = 
'SET_TRANSMITTERS %d\n' % mask
   626         Command.__init__(self, cmd, connection)
   630     ''' Get lircd version, see VERSION in lircd(8) manpage. '''   632     def __init__(self, connection: AbstractConnection):
   633         Command.__init__(self, 
'VERSION\n', connection)
   637     ''' Set a driver option value, see DRV_OPTION in lircd(8) manpage. '''   639     def __init__(self, connection: AbstractConnection,
   640                  option: str, value: str):
   641         cmd = 
'DRV_OPTION %s %s\n' % (option, value)
   642         Command.__init__(self, cmd, connection)
   646     ''' Start/stop logging lircd output , see SET_INPUTLOG in lircd(8)   650     def __init__(self, connection: AbstractConnection,
   651                  logfile: str = 
None):
   652         cmd = 
'SET_INPUTLOG' + (
' ' + logfile 
if logfile 
else '') + 
'\n'   653         Command.__init__(self, cmd, connection)
   667     ''' Identify client using the prog token, see IDENT in lircrcd(8) '''   669     def __init__(self, connection: AbstractConnection,
   672             raise ValueError(
'The prog argument cannot be None')
   673         cmd = 
'IDENT {}\n'.format(prog)
   674         Command.__init__(self, cmd, connection)
   678     '''Translate a keypress to application string, see CODE in lircrcd(8) '''   680     def __init__(self, connection: AbstractConnection,
   683             raise ValueError(
'The prog argument cannot be None')
   684         Command.__init__(self, 
'CODE {}\n'.format(code), connection)
   688     '''Get current translation mode, see GETMODE in lircrcd(8) '''   690     def __init__(self, connection: AbstractConnection):
   691         Command.__init__(self, 
"GETMODE\n", connection)
   695     '''Set current translation mode, see SETMODE in lircrcd(8) '''   697     def __init__(self, connection: AbstractConnection,
   700             raise ValueError(
'The mode argument cannot be None')
   701         Command.__init__(self, 
'SETMODE {}\n'.format(mode), connection)
 def close(self)
Implements AbstractConnection.close() 
Get lircd version, see VERSION in lircd(8) manpage. 
Send given key, see SEND_ONCE in lircd(8) manpage. 
List available keys in given remote, see LIST in lircd(8) manpage. 
Simulate a button press, see SIMULATE in lircd(8) manpage. 
result
Enum Result, reflects parser state. 
def has_data(self)
Return true if next readline(None) won't block . 
success
bool, reflects SUCCESS/ERROR. 
def fileno(self)
Implements AbstractConnection.fileno(). 
sighup
bool, reflects if a SIGHUP package has been received 
def send
Send single line over socket. 
Public reply parser result, available when completed. 
def readline
Implements AbstractConnection.readline(). 
def close(self)
Close/release all resources. 
Identify client using the prog token, see IDENT in lircrcd(8) 
Command, parser and connection container with a run() method. 
def has_data(self)
Implements AbstractConnection.has_data() 
def fileno(self)
Return the file nr used for IO, suitable for select() etc. 
def run
Run the command and return a Reply. 
last_line
str, last input line (for error messages). 
Interface to receive code strings as described in lircd(8). 
Interface to receive lircrc-translated keypresses. 
Extends the parent with a send() method. 
The status/result from parsing a command reply. 
Abstract interface for all connections. 
def readline
Implements AbstractConnection.readline(). 
data
List of lines, the command DATA payload. 
Set transmitters to use, see SET_TRANSMITTERS in lircd(8) manpage. 
def has_data(self)
Implements AbstractConnection.has_data() 
def fileno(self)
Implements AbstractConnection.fileno(). 
def get_default_lircrc_path()
Get default path to the lircrc file according to (falling priority): 
Timeout receiving data from remote host. 
Malformed or otherwise unparsable packet received. 
Handles the actual parsing of a command reply. 
Start/stop logging lircd output , see SET_INPUTLOG in lircd(8) manpage. 
Start repeating given key, see SEND_START in lircd(8) manpage. 
Get current translation mode, see GETMODE in lircrcd(8) 
Stop repeating given key, see SEND_STOP in lircd(8) manpage. 
Set a driver option value, see DRV_OPTION in lircd(8) manpage. 
Translate a keypress to application string, see CODE in lircrcd(8) 
def get_default_socket_path()
Get default value for the lircd socket path, using (falling priority): 
List available remotes, see LIST in lircd(8) manpage. 
def readline
Read a buffered line. 
def close(self)
Implements AbstractConnection.close() 
Set current translation mode, see SETMODE in lircrcd(8)