A Python library for the XS4ALL USB telephone handset on Linux
(0c45:182e, Microdia / GENERIC USB Headset).
Photo by Mendel Mobach.
Read keypad events, control the ringer and call mode, and record or play audio
without the original Windows softphone. The handset has been tested with
Linux's snd-usb-audio and hid-generic drivers and PipeWire.
- Linux and Python 3.9 or later.
- Access to the handset's hidraw interface; a udev rule is included.
- For the optional audio helpers:
parec,paplay, and PulseAudio or PipeWire's PulseAudio server.
There are no third-party Python runtime dependencies.
Clone the repository and install it:
git clone https://github.com/Hack42/xs4all_handset.git
cd xs4all_handset
python3 -m venv .venv
. .venv/bin/activate
python -m pip install .To use the library in another project's virtual environment:
python -m pip install /path/to/xs4all-handsetOr install directly from GitHub in your project's environment:
python -m pip install "git+https://github.com/Hack42/xs4all_handset.git"Install the udev rule once on each machine:
sudo install -m 0644 70-xs4all-handset.rules /etc/udev/rules.d/70-xs4all-handset.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --action=change --subsystem-match=hidrawReconnect the handset if necessary. The rule gives the active local session
access to this device and creates /dev/xs4all-handset. Python does not need
to run as root. For headless services, configure appropriate device access.
The rule is also available after package installation with
xs4all-handset udev-rule.
from xs4all_handset import Handset
with Handset(talk=True) as phone:
for event in phone.events():
print(event.key, event.muted)Initialization and report acknowledgements are automatic. talk=True enables
call-dependent buttons such as mute; it does not make a telephone call.
Leaving the context stops a ringer/call mode enabled through this connection
and closes the device.
For integration into an existing loop, use phone.read_key(timeout=0.1).
It returns None if no key arrives. Events are immutable dataclasses;
event.to_dict() provides a JSON-compatible representation.
| Button | event.key |
Notes |
|---|---|---|
| Digits, star, hash | 0–9, *, # |
Tested on hardware |
| Up / down | up / down |
Codes e9 / ea |
| Call / answer | call |
Idle-mode reports 53 + 31 |
| Hang up | hangup |
Idle-mode reports 41 or 40 + 54 |
| Mute | mute |
Call mode only; event.muted is True or False |
mail |
Reports 53 + bf |
|
| Redial | recall |
Reports 40 + d0; historical API name |
| Lower / higher | lower / higher |
Codes bd / bc |
Compound protocol reports produce one key action. Unknown key codes remain
available as unknown_0xNN. Applications choose what the buttons do: events
do not automatically change system volume, mute audio, or inject keystrokes.
from xs4all_handset import Handset
with Handset() as phone:
phone.ring(seconds=1)For an event loop, use phone.start_ringing() and phone.stop_ringing().
phone.set_call_active(True) enables call mode without placing a call.
from xs4all_handset import AudioDevice
audio = AudioDevice()
recording = audio.record(5)
audio.play(recording)The default is mono, 48 kHz, signed 16-bit little-endian PCM. WAV files are
supported with audio.record_wav("voice.wav", 5) and
audio.play_wav("voice.wav"). These helpers block; applications needing
streaming may also use their usual Linux audio library.
AudioDevice(source=..., sink=..., sample_rate=..., channels=...) selects
custom devices and formats. With multiple identical handsets, select the
correct audio source and sink explicitly. Global audio defaults are unchanged.
xs4all-handset info
xs4all-handset monitor --talk --keys-only
xs4all-handset ring --seconds 1
xs4all-audio-test tone
xs4all-audio-test echo --seconds 5python -m xs4all_handset provides the same handset commands.
Diagnostic options include monitor --passive (read-only) and --seconds N.
- API reference: lifecycle, timeouts, errors, and integration.
- Examples: button monitoring and audio echo.
- Protocol notes: USB descriptors, reverse-engineering evidence, captured reports, and hardware validation.
From the repository root:
python -m pip install -e .
python -m unittest discover -v
python -m pip wheel --no-deps --wheel-dir dist .Tests use captured handset reports, local sockets, and short-lived child processes; they do not require USB hardware or an audio server.
The observed keypad, microphone, earpiece, and ringer have been tested on
hardware. Some firmware behavior remains based on static inspection; see
the protocol notes. Keep reading events to acknowledge handset traffic.
Use each Handset from one thread. Reopen the connection after unplugging
and reconnecting; reconnect is not automatic.
Copyright (C) 2026 Mendel Mobach.
The library is licensed under the GNU General Public License version 3 only
(GPL-3.0-only). See LICENSE for the full text.
