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