This page is about redesigning lircd to a number smaller components. Other tools e. g., irrecord is not affected.
Why revise design?
In order to stay relevant, we need to modernize lirc:
- To handle multiple input/output devices.
- To handle hotplugging of new devices.
- To add a modern, event-driven interface to make future application
integration easier.
- We need to become more open to be able cooperate with other communities.
- Create a codebase which is more agile and flexible.
- In the long run, there should be paths to use other rendering engines than
current which is showing it's age.
Basic concepts for a new design:
- We should be provide the same socket interface as current lircd w r t receiving
and sending IR commands. We might drop other, non-documented features. This is
about keeping the application support.
- We should keep the driver interface. Keeping a stable ground for devs to integrate
new hardware is essential.
- We should move away from the basic model with one lircd process connected
to one socket to a model where several lircd instances possibly are connected
to a single socket.
- Rather than to enhance the already complicated lircd process we should try to
split it into smaller pieces with well-defined interfaces.
- The synchronization now performed in the lircd main loop should if possible
be delegated to some existing mechanism. This is the glue that connects all the
lircd current functionality to one, single piece of tightly coupled code.
What is lircd?
In order to create a new design for lircd, we need to understand the basic blocks in
current code:
- The core is IR decoding/encoding. There are really no well-defined interfaces to this.
Basic concepts here is "IR commands", the user level representation of a remote
button press and the low-level IR data.
- When sending, lircd converts IR commmands to a sequence of low-level IR data
using the database, usually in /etc/lircd.conf and/or /etc/lircd.conf.d/.
- When receiving, lircd converts the low-level IR data to IR commands using the
same database.
- A driver adapter which can load and use a device-specific user-space driver.
Drivers have an established API to lircd.
- A socket interface which allows:
- Listening to keypresses from remote.
- Sending of IR commands.
- A command handling interface implementing the socket commands. This interface is described
in the manual ("Writing applications for LIRC.")
- An API built on top of the socket interface providing the functions described in
the manual ("The lirc_client API").
- Code which either gets input from or sends data to a remote lircd
instance over the network (the "--listen" and "--connect" runtime options).
- Code which send all received data to the kernel uinput device, implementing the
--uinput runtime opiton.
- A main loop which handles both static parties (data from the kernel device,
data from the socket interface, possibly TCP data) and dynamically connected
socket clients.
Why LIRC?
If we change the overall design, we need to be careful not to harm LIRC's strong points:
- The strong application support in the Linux HTPC area..
- The only framework for handling IR remotes available in all Linux distros
(besides the kernel).
- The easiest way to handle remotes not supported by the kernel, attracting developers
and power users.
- Added functionality compared to the kernel device interface attracting applications.
Why dbus:
My idea is that we base a new design on dbus which acts as a dispatcher between multiple
clients and multiple lircd instances. There is a number or reasons to use Dbus:
- Dbus solves the problem of passing messages between different users (i. e., the
lircd user and the session.)
- Dbus is fast enough to handle keypresses without user-visible lag (?).
- It is language independent with bindings to most languages and the two common
main loops (GLib and Qt).
- It is already part of any Linux system we target. Even more so when it shortly will
become part of the kernel(kdbus). There is also an OSX port.
- The asynchronous, message based design fits the LIRC bill.
A new design
- Use Dbus as dispatcher. All components and clients connects to the bus which
passes messages and synchronizes. The only synchronization done within lircd is
the full duplex send/receive conflict on the kernel device. This means that
components are defined by their dbus interface.
- Limit current lircd to
- Decoding of data from kernel which are broadcasted as dbus events.
- Handling dbus commands, notably for sending.
Practically, this means that lircd exposes a dbus interface supporting
listening to/sending IR commands.
- Client connects to lircd either
- Directly to the socket to send data.
- Indirectly to the socket using the LIRC API to listen or send.
- Directly to the Dbus interface to listen or send.
- The TCP module is a full duplex module reading from the remote lircd instance
providing data on dbus in the same way as a "normal" lircd instance,
implementing the runtime --listen and --connect options.
- The uinput module listens to dbus IR events a feeds them to the kernel
uinput device, implementing the --uinput runtime option.
- The socket interface + command handling is a separate module handling dbus-socket
conversion. Each connected client is a separate process, dbus handles
synchronization.
- We can now start several lircd input instances, all connected to same dbus and
socket interface -> solves multiple input devices. In other configurations we
use different sockets as today.
- The lircd instance is only defined by the dbus and kernel device interface. This
means that we have a framework where we can test different implementations. We now
need a new name for the kernel device/dbus thing. Let's call it 'connector'. Our
first connector will be a stripped down lircd. However, we should design this so
that we could use something enterely different later.
- We need a new Manager module which loads/unloads correct connector + configuration when
configuration changes or udev signals devices are removed/added. This exposes the primary
Dbus client interface, since it's the only instance with a given name which always exists.
- The configuration is a little tricky. However, with a little care, we should be able to
do this in a more or less implementation-agnostic way. First shot is that a
configuration is a dictionary of strings, a YAML or xml file or something similar.
Like this ![Design sketch](http://leamas.fedorapeople.org/lirc/v2/design.png)
There are some thoughts on the same topic with a different perspective. In this parlance, a connector is basically a combined listener/sender with a Dbus interface.