diff --git a/fixes/hidapi-crash-fix/README.md b/fixes/hidapi-crash-fix/README.md new file mode 100644 index 00000000..34996d8b --- /dev/null +++ b/fixes/hidapi-crash-fix/README.md @@ -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) diff --git a/fixes/hidapi-crash-fix/hidapi_dummy.c b/fixes/hidapi-crash-fix/hidapi_dummy.c new file mode 100644 index 00000000..bd7459c5 --- /dev/null +++ b/fixes/hidapi-crash-fix/hidapi_dummy.c @@ -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 +#include + +/* ============================================================================ + * 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; +}