I’ve used my USB sound card for almost a decade now, but somehow never got around to set up the remote controller until very recently. This post documents how to setup LIRC for the Creative Sound Blaster X-Fi Surround 5.1 (SB1090) external USB sound card with the Creative RM-850 remote controller under Linux.
Setup
This setup was originally done on a computer running Gentoo Linux, using the following component versions:
- Linux kernel: 5.8.18
- systemd: 247-rc2
- udev: 232
- lirc: 0.10.1
however it should be generic enough to be applicable to most recent Linux distributions. My sound card is attached via an USB 3.0 hub which is connected to a Thunderbolt docking station used by my laptop.

Kernel
Both the sound card and it’s remote control capabilities are enabled by the USB ALSA driver in the kernel. Additionally LIRC requires the user level driver support by default. The following kernel options have to be enabled:
CONFIG_INPUT_UINPUT
CONFIG_SND_USB_AUDIO
Device Drivers --->
<M> Sound card support --->
<M> Advanced Linux Sound Architecture --->
[*] USB sound devices --->
<M> USB Audio/MIDI driver
Input device support --->
Generic input layer --->
<*> Miscellaneous devices --->
<M> User level driver support
The Linux kernel will automatically load the snd_usb_audio
module when the
sound card is connected.
LIRC
Installing packages
Enable the lirc
USE flag for PulseAudio and VLC, and if needed also adjust
the flags for LIRC itself in /etc/portage/package.use
app-misc/lirc -gtk
media-sound/pulseaudio lirc
media-video/vlc lirc
Update packages by running:
# emerge -1 pulseaudio vlc
This will automatically pull in LIRC and all required dependencies.
Configuration
First LIRC needs to be told to use the right driver and hardware. The following
fields need to be updated in the daemon section of
/etc/lirc/lirc_options.conf
to be:
[lircd]
driver = alsa_usb
device = hw:S51
Then a configuration for the remote controller must be placed under
/etc/lirc.lircd.conf.d
, while the default one needs to be disabled:
# mv devinput.lircd.conf devinput.lircd.conf.dist
The LIRC project hosts a collection of remote controller configuration files, however their version for the RM-850 remote did not work for me for some reason. Could it be that the EU version of the device is different to the US one?
After playing around with different configurations, the RM-1500
remote ended
up partially working. So I took the general settings from there and mapped out
all the keys of the RM-850 remote into a new configuration file. Once done,
I saved it as /etc/lirc/lircd.conf.d/RM-850.lircd.conf
:
# Config created with lirc-0.10.1 on 2020-11-08
# Created by Zoltan Puskas <zoltan|sinustrom.info>
#
# Brand: Creative
# Model no. of remote control: RM-850
# Supported devices: Sound Blaster X-Fi Surround 5.1 SB1090
#
# This is for the integrated IR receiver in Creative USB audio devices
# when accessed with LIRC's alsa_usb driver (requires the snd-usb-audio
# driver from ALSA 1.0.9 or later).
#
begin remote
name RM-850
bits 8
gap 300000
begin codes
KEY_POWER 0x01 # power
CMSS 0x0c # X-Fi CMSS-3D on|off
KEY_MUTE 0x0d # mute
KEY_RECORD 0x0e # rec
KEY_VOLUMEDOWN 0x0f # volume -
KEY_VOLUMEUP 0x10 # volume +
KEY_STOPEJECT 0x11 # stop/eject
KEY_PLAYPAUSE 0x12 # play/pause
KEY_PREVIOUS 0x14 # previous
KEY_NEXT 0x15 # next
KEY_OPTION 0x18 # option
KEY_RETURN 0x1a # return
KEY_START 0x1b # start
KEY_CANCEL 0x1c # cancel
KEY_UP 0x1d # up
KEY_OK 0x1f # ok
KEY_DOWN 0x21 # down
KEY_CMSSPLUS 0x22 # CMSS +
KEY_CMSSMINUS 0x23 # CMSS -
KEY_CRYSTAL 0x24 # X-Fi crystalizer on/off
KEY_CRYSTALPLUS 0x25 # crystalizer +
KEY_CRYSTALMINUS 0x26 # crystalizer -
KEY_LEFT 0x27 # left
KEY_RIGHT 0x28 # right
end codes
end remote
Above key map also works for the wheel on the sound card itself, which emits volume up/down when rotated and mute when pressed.
Starting the service
Starting the LIRC service socket:
# systemctl start lircd.socket
In order to have it started upon boot we also have to enable it:
# systemctl enable lircd.socket
The lircd.service
will be automatically started once something tries to
connect to the socket and use it.
User action configuration
Actions need to be assigned to each button event, which can be done through
configuration local to the user. Below is minimalistic example configuration
mapping volume actions to the global volume events (i.e. as if we used the
keyboard), saved to ~/.config/lircrc
:
# Control volume the same as if it was done from the keyboard
begin
remote = RM-850
prog = irexec
config = /usr/bin/xdotool key XF86AudioLowerVolume
button = KEY_VOLUMEDOWN
repeat = 1
end
begin
remote = RM-850
prog = irexec
config = /usr/bin/xdotool key XF86AudioRaiseVolume
button = KEY_VOLUMEUP
repeat = 1
end
begin
remote = RM-850
prog = irexec
config = /usr/bin/xdotool key XF86AudioMute
button = KEY_MUTE
end
Above configuration assumes x11-misc/xdotool
is installed on the system. It’s
also best to use absolute paths for the binaries passed to irexec
, to avoid
potential issues.
After starting irexec
by running:
$ irexec -d
the remote controller should trigger the configured actions.
Automating cold- and hot plug
At this point the remote controller setup works, but only for the current boot session. It would be nice if the setup could also handle:
- future boots, when the card is connected (i.e. cold plug)
- future boots, when the card is disconnected (i.e. on the move)
- plugging in the sound card at any time (i.e. hot plug)
- unplugging the sound card at any time
UDEV
First let’s create an udev rule that will handle the sound card and save it
as /etc/udev/rules.d/77-sbxfi51.rules
:
# When the sound card USB device is plugged in make sure lircd.socket service is started if it's not running yet
ACTION=="add", SUBSYSTEM=="usb", ATTRS{idProduct}=="3042", ATTRS{idVendor}=="041e", TAG+="systemd", ENV{SYSTEMD_WANTS}+="lircd.socket"
# When the device is recognized as SB X-Fi 5.1 card let's add a systemd device named dev-snd-sbxfi51.device and ensure our user service is started
ACTION=="add", SUBSYSTEMS=="sound", ATTRS{id}=="S51", TAG+="systemd", SYMLINK+="snd/sbxfi51", ENV{SYSTEMD_ALIAS}="/dev/snd/sbxfi51", ENV{SYSTEMD_WANTS}+="lircd.socket", ENV{SYSTEMD_USER_WANTS}+="irexec.service"
# When the device is unplugged stop all LIRC services
ACTION=="remove", SUBSYSTEM=="usb", ENV{PRODUCT}=="41e/3042/*", TAG+="systemd"
The reason we need to start lircd.socket
is because it could be stopped
after an unplug event, despite it being started by default on boot.
Also note that as per udev(7)
“Starting daemons or other long-running processes is not allowed; the forked
processes, detached or not, will be unconditionally killed after the event
handling has finished. In order to activate long-running processes from udev
rules, provide a service unit and pull it in from a udev device using the
SYSTEMD_WANTS
device property.”, i.e. any services we want to start should be
done so through a systemd unit.
We use the SYSTEMD_ALIAS
to have a predictable systemd device name, as the
default name depends on the USB port the sound card has been plugged into and
thus subject to change.
Finally we stop lircd.socket
during an unplug event to save system resources
as well as ensure proper operation during a subsequent hot plug event.
Once the udev rule is put in place it’s time to let udev know about it:
# udevadm control --reload-rules
System services
Next we need to adjust the default LIRC services for a more optimal operation.
First let’s ensure that lircd.socket
is stopped when the sound card is
unplugged:
# systemctl edit lircd.socket
and add
[Unit]
BindsTo=dev-snd-sbxfi51.device
StopWhenUnneeded=yes
Then also let’s make sure lircd.service
depends on lircd.socket
, so it will
be automatically stopped when the socket goes away:
# systemctl edit lircd.service
and add
[Unit]
Requires=lircd.socket
Let’s not forget to reload systemd, so it can pick up the new configuration:
# systemctl daemon-reload
User service
Finally let’s create a user systemd service that will start and stop irexec
based on the sound card’s availability while the user is logged in. Let’s
create the service directory fist:
$ mkdir -p ~/.config/systemd/user
and then place the user service configuration there as
~/.config/systemd/user/irexec.service
:
[Unit]
Documentation=man:irexec(1)
Documentation=man:lircrc(5)
Description=LIRC executor - handle events from IR remotes decoded by lircd(8)
After=lircd.socket
BindsTo=dev-snd-sbxfi51.device
StopWhenUnneeded=yes
ConditionPathIsSymbolicLink=/dev/snd/sbxfi51
[Service]
Type=simple
Environment=DISPLAY=:0
Environment=XAUTHORITY=%h/.Xauthority
ExecStart=/usr/bin/irexec %h/.config/lircrc
[Install]
WantedBy=default.target
This service depends on both the socket being active as well as the sound card
device being present, which are handled by the previously set up udev rules. It
will also shut itself down when the device is unplugged. Note that while
irexec
would exit by itself when lircd.service
dies, it’s cleaner and more
explicit this way.
The environment settings are needed as the unit starts before the desktop environment is done fully initializing, so pointing it to the right X session is required, otherwise the service will not work after logging in even though it has been launched.
Once the configuration is in place we can let systemd know about the new unit and also enable it:
$ systemctl --user daemon-reload
$ systemctl --user enable irexec.service
After this all the services should automatically start and stop in the correct order as the sound card is plugged in or removed, ensuring that not only we have a working remote controller but also that we only run services on demand.
Debugging and testing
Getting to the above working configuration actually was not that straightforward and involved quite a bit of reading and experimentation. So if you plan to make your own custom setup or just got stuck while trying to replicate this one, these tips might help you out.
Reading
Reading man pages cleared up a lot of confusion and helped to understand the behaviour of the sytem components better. Recommended man pages for customizing the setup:
- lircd(8)
- lircd.conf(5)
- lircrc(5)
- systemd.device(5)
- systemd.socket(5)
- systemd.unit(5)
- systemctl(1)
- udev(7)
- udevadm(8)
Additional pages worth reading:
IR receiver testing
Testing is done with irw
by simply running it and then pressing buttons and
checking if everything works. This needs the LIRC service to be running.
Example result:
$ irw
0000000000000010 00 KEY_VOLUMEUP RM-850
000000000000001d 00 KEY_UP RM-850
000000000000001d 00 KEY_UP RM-850
0000000000000012 00 KEY_PLAYPAUSE RM-850
0000000000000011 00 KEY_STOPEJECT RM-850
0000000000000010 00 KEY_VOLUMEUP RM-850
0000000000000010 01 KEY_VOLUMEUP RM-850
000000000000000f 00 KEY_VOLUMEDOWN RM-850
UDEV
Monitoring udev events:
$ udevadm monitor --environment --udev
When writing “remove” rules it’s important to use udev environment variables, as by the time this rule runs the device is already removed from the system and the usual attributes are not available any more (e.g. what was used to match on the addition rules)! The above command will help to find out the relevant valid fields and environment variables.
Information on the device, it’s tree, and all attributes (replace device path with yours):
$ udevadm info -a -n /dev/snd/by-id/usb-Creative_Technology_SB_X-Fi_Surround_5.1__blank_-00
Systemd
Monitor the status of systemd services as the device is plugged in and out:
$ watch -n1 systemctl status lircd.service lircd.socket
$ watch -n1 systemctl --user status irexec.service
List devices recognized by systemd:
$ systemctl --all --full -t device
For example the sound card will appear as
dev-snd-by\x2dpath-pci\x2d0000:0c:00.0\x2dusb\x2d0:3.2:1.0.device
by default,
however after adding the custom udev rule the dev-snd-sbxfi51.device
alias
will also appear with loaded, active, and plugged statuses.
In case of failures it’s worth to look at the logs too:
$ journalctl --unit lircd.socket
$ journalctl --unit lircd.service
$ journalctl --user --user-unit irexec.service