add hidapi stub fix for HID crash on systems without hidraw access

When /dev/hidraw* devices are not accessible, liblinphone crashes in
HidDevice::stopPollTimer(). This adds a stub hidapi implementation
that returns empty device lists, preventing the crash while disabling
USB headset button support.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Eduard Wisch 2026-02-06 14:43:49 +01:00
parent ca4380bfd0
commit d00cff194f
2 changed files with 496 additions and 0 deletions

View file

@ -0,0 +1,52 @@
# HID Crash Fix for Linphone
## Problem
Linphone crashes on startup with SIGSEGV in `LinphonePrivate::HidDevice::stopPollTimer()` when the user has no read access to `/dev/hidraw*` devices.
## Root Cause
The liblinphone SDK's HID subsystem (for USB headset button support) does not handle permission errors gracefully. When `hid_enumerate()` fails or returns devices that cannot be opened, the subsequent timer handling crashes.
## Solution
Replace `libhidapi-hidraw.so.0.15.0` inside the AppImage with a stub implementation that:
- Returns empty device lists from `hid_enumerate()`
- Returns NULL from all `hid_open*()` functions
- Returns safe error values from all other operations
This disables USB headset button support but prevents the crash.
## Build
```bash
gcc -shared -fPIC -O2 -Wall -Wextra -Wpedantic -std=c11 \
-o libhidapi-hidraw.so.0.15.0 hidapi_dummy.c
```
## Integration into AppImage
```bash
# Extract
./Linphone-*.AppImage --appimage-extract
# Replace library
cp libhidapi-hidraw.so.0.15.0 squashfs-root/lib/
# Repackage
ARCH=x86_64 appimagetool squashfs-root Linphone-Fixed.AppImage
```
## Alternative: System-wide Fix
Instead of patching the AppImage, grant HID access to users:
```bash
echo 'KERNEL=="hidraw*", TAG+="uaccess", MODE="0666"' | \
sudo tee /etc/udev/rules.d/99-hidraw-permissions.rules
sudo udevadm control --reload-rules && sudo udevadm trigger
```
## Files
- `hidapi_dummy.c` - Complete HIDAPI 0.15.0 stub implementation (25 functions)

View file

@ -0,0 +1,444 @@
/**
* @file hidapi_dummy.c
* @brief Stub implementation of HIDAPI 0.15.0 to prevent crashes when HID access fails.
*
* This library provides a complete no-op implementation of the HIDAPI interface.
* All device enumeration returns empty lists, all device operations fail gracefully.
*
* Purpose: Linphone's liblinphone SDK crashes when it cannot access /dev/hidraw* devices
* (for USB headset button support). This stub library prevents those crashes by
* intercepting all HID calls and returning safe, predictable values.
*
* Build: gcc -shared -fPIC -O2 -Wall -Wextra -o libhidapi-hidraw.so.0 hidapi_dummy.c
* Usage: LD_PRELOAD=/path/to/libhidapi-hidraw.so.0 ./application
*
* @author Generated for Linphone crash workaround
* @version 0.15.0 (matching HIDAPI version)
* @see https://github.com/libusb/hidapi
*/
#define _GNU_SOURCE
#include <stddef.h>
#include <wchar.h>
/* ============================================================================
* Type Definitions (matching hidapi.h)
* ============================================================================ */
/** Opaque device handle - we never actually allocate these */
struct hid_device_;
typedef struct hid_device_ hid_device;
/** HID bus types */
typedef enum {
HID_API_BUS_UNKNOWN = 0x00,
HID_API_BUS_USB = 0x01,
HID_API_BUS_BLUETOOTH = 0x02,
HID_API_BUS_I2C = 0x03,
HID_API_BUS_SPI = 0x04,
} hid_bus_type;
/** Device information structure */
struct hid_device_info {
char *path;
unsigned short vendor_id;
unsigned short product_id;
wchar_t *serial_number;
unsigned short release_number;
wchar_t *manufacturer_string;
wchar_t *product_string;
unsigned short usage_page;
unsigned short usage;
int interface_number;
struct hid_device_info *next;
hid_bus_type bus_type;
};
/** Version information structure */
struct hid_api_version {
int major;
int minor;
int patch;
};
/* ============================================================================
* Static Data
* ============================================================================ */
/** Version info matching HIDAPI 0.15.0 */
static const struct hid_api_version g_version = {
.major = 0,
.minor = 15,
.patch = 0
};
/** Version string */
static const char g_version_str[] = "0.15.0-stub";
/** Error message for NULL device operations */
static const wchar_t g_error_no_device[] = L"No HID devices available (stub library)";
/** Error message for successful operations */
static const wchar_t g_error_none[] = L"Success";
/** Global initialization state */
static int g_initialized = 0;
/* ============================================================================
* Library Initialization / Cleanup
* ============================================================================ */
/**
* @brief Initialize the HIDAPI library.
* @return 0 on success (always succeeds)
*/
int hid_init(void) {
g_initialized = 1;
return 0;
}
/**
* @brief Finalize the HIDAPI library.
* @return 0 on success (always succeeds)
*/
int hid_exit(void) {
g_initialized = 0;
return 0;
}
/* ============================================================================
* Device Enumeration
* ============================================================================ */
/**
* @brief Enumerate HID devices.
* @param vendor_id Vendor ID filter (0 = any)
* @param product_id Product ID filter (0 = any)
* @return NULL (no devices available in stub)
*/
struct hid_device_info* hid_enumerate(unsigned short vendor_id, unsigned short product_id) {
(void)vendor_id;
(void)product_id;
/* Ensure library is initialized */
if (!g_initialized) {
hid_init();
}
/* Return empty list - no devices */
return NULL;
}
/**
* @brief Free an enumeration linked list.
* @param devs Pointer to device list (ignored, nothing to free)
*/
void hid_free_enumeration(struct hid_device_info *devs) {
(void)devs;
/* Nothing to free - we never allocate device info */
}
/* ============================================================================
* Device Open / Close
* ============================================================================ */
/**
* @brief Open a HID device by VID/PID.
* @param vendor_id Vendor ID
* @param product_id Product ID
* @param serial_number Serial number (optional)
* @return NULL (device not found)
*/
hid_device* hid_open(unsigned short vendor_id, unsigned short product_id,
const wchar_t *serial_number) {
(void)vendor_id;
(void)product_id;
(void)serial_number;
if (!g_initialized) {
hid_init();
}
return NULL;
}
/**
* @brief Open a HID device by path.
* @param path Device path (e.g., /dev/hidraw0)
* @return NULL (device not found)
*/
hid_device* hid_open_path(const char *path) {
(void)path;
if (!g_initialized) {
hid_init();
}
return NULL;
}
/**
* @brief Close a HID device.
* @param dev Device handle (ignored)
*/
void hid_close(hid_device *dev) {
(void)dev;
/* Nothing to close */
}
/* ============================================================================
* Device I/O Operations
* ============================================================================ */
/**
* @brief Write an output report to a HID device.
* @param dev Device handle
* @param data Data buffer
* @param length Buffer length
* @return -1 (error - no device)
*/
int hid_write(hid_device *dev, const unsigned char *data, size_t length) {
(void)dev;
(void)data;
(void)length;
return -1;
}
/**
* @brief Read an input report with timeout.
* @param dev Device handle
* @param data Buffer for data
* @param length Buffer size
* @param milliseconds Timeout (-1 for blocking)
* @return 0 (no data available)
*/
int hid_read_timeout(hid_device *dev, unsigned char *data, size_t length,
int milliseconds) {
(void)dev;
(void)data;
(void)length;
(void)milliseconds;
return 0;
}
/**
* @brief Read an input report.
* @param dev Device handle
* @param data Buffer for data
* @param length Buffer size
* @return 0 (no data available)
*/
int hid_read(hid_device *dev, unsigned char *data, size_t length) {
(void)dev;
(void)data;
(void)length;
return 0;
}
/**
* @brief Set non-blocking mode.
* @param dev Device handle
* @param nonblock 1 for non-blocking, 0 for blocking
* @return -1 (error - no device)
*/
int hid_set_nonblocking(hid_device *dev, int nonblock) {
(void)dev;
(void)nonblock;
return -1;
}
/* ============================================================================
* Feature / Output / Input Reports
* ============================================================================ */
/**
* @brief Send a feature report.
* @param dev Device handle
* @param data Report data
* @param length Data length
* @return -1 (error - no device)
*/
int hid_send_feature_report(hid_device *dev, const unsigned char *data,
size_t length) {
(void)dev;
(void)data;
(void)length;
return -1;
}
/**
* @brief Get a feature report.
* @param dev Device handle
* @param data Buffer for report
* @param length Buffer size
* @return -1 (error - no device)
*/
int hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) {
(void)dev;
(void)data;
(void)length;
return -1;
}
/**
* @brief Send an output report (since 0.15.0).
* @param dev Device handle
* @param data Report data
* @param length Data length
* @return -1 (error - no device)
*/
int hid_send_output_report(hid_device *dev, const unsigned char *data,
size_t length) {
(void)dev;
(void)data;
(void)length;
return -1;
}
/**
* @brief Get an input report.
* @param dev Device handle
* @param data Buffer for report
* @param length Buffer size
* @return -1 (error - no device)
*/
int hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) {
(void)dev;
(void)data;
(void)length;
return -1;
}
/**
* @brief Get a report descriptor (since 0.14.0).
* @param dev Device handle
* @param buf Buffer for descriptor
* @param buf_size Buffer size
* @return -1 (error - no device)
*/
int hid_get_report_descriptor(hid_device *dev, unsigned char *buf,
size_t buf_size) {
(void)dev;
(void)buf;
(void)buf_size;
return -1;
}
/* ============================================================================
* Device Information Retrieval
* ============================================================================ */
/**
* @brief Get manufacturer string.
* @param dev Device handle
* @param string Buffer for string
* @param maxlen Buffer size in wchar_t units
* @return -1 (error - no device)
*/
int hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) {
(void)dev;
(void)string;
(void)maxlen;
return -1;
}
/**
* @brief Get product string.
* @param dev Device handle
* @param string Buffer for string
* @param maxlen Buffer size in wchar_t units
* @return -1 (error - no device)
*/
int hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) {
(void)dev;
(void)string;
(void)maxlen;
return -1;
}
/**
* @brief Get serial number string.
* @param dev Device handle
* @param string Buffer for string
* @param maxlen Buffer size in wchar_t units
* @return -1 (error - no device)
*/
int hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) {
(void)dev;
(void)string;
(void)maxlen;
return -1;
}
/**
* @brief Get device info structure (since 0.13.0).
* @param dev Device handle
* @return NULL (no device info available)
*/
struct hid_device_info* hid_get_device_info(hid_device *dev) {
(void)dev;
return NULL;
}
/**
* @brief Get indexed string.
* @param dev Device handle
* @param string_index String index
* @param string Buffer for string
* @param maxlen Buffer size in wchar_t units
* @return -1 (error - no device)
*/
int hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string,
size_t maxlen) {
(void)dev;
(void)string_index;
(void)string;
(void)maxlen;
return -1;
}
/* ============================================================================
* Error Handling
* ============================================================================ */
/**
* @brief Get last error string.
* @param dev Device handle (NULL for global error)
* @return Error description (never NULL)
*/
const wchar_t* hid_error(hid_device *dev) {
if (dev == NULL) {
return g_error_no_device;
}
return g_error_none;
}
/**
* @brief Get last read error string (since 0.15.0).
* @param dev Device handle
* @return Error description (never NULL)
*/
const wchar_t* hid_read_error(hid_device *dev) {
(void)dev;
return g_error_none;
}
/* ============================================================================
* Version Information
* ============================================================================ */
/**
* @brief Get library version structure.
* @return Pointer to static version info
*/
const struct hid_api_version* hid_version(void) {
return &g_version;
}
/**
* @brief Get library version string.
* @return Version string
*/
const char* hid_version_str(void) {
return g_version_str;
}