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:
parent
ca4380bfd0
commit
d00cff194f
2 changed files with 496 additions and 0 deletions
52
fixes/hidapi-crash-fix/README.md
Normal file
52
fixes/hidapi-crash-fix/README.md
Normal 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)
|
||||||
444
fixes/hidapi-crash-fix/hidapi_dummy.c
Normal file
444
fixes/hidapi-crash-fix/hidapi_dummy.c
Normal 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;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue