Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1b0b6ac
hotplug: Add ability to register device connection/disconnection call…
DJm00n May 1, 2023
79a3516
Merge branch 'master' into connection-callback
Youw Mar 11, 2024
ce92386
Windows hotplug: Mutex for callback actions (#646)
k1-801 Apr 6, 2024
c3a2775
Connection callback: add stubs for netbsd (#668)
k1-801 Apr 6, 2024
60b40d9
Linux Hotplug: connection-callback implementation for libusb backend …
k1-801 Apr 6, 2024
4537833
Linux Hotplug: Connection-callback implementation for hidraw (#647)
k1-801 Apr 6, 2024
4f73d1f
Hotplug implementation for MacOS (HidManager approach) (#653)
k1-801 Apr 6, 2024
cfed154
Merge branch 'master' into connection-callback
Youw Apr 6, 2024
b6606ca
Connection callback: fix LeaveCriticalSection not being called after …
d3xMachina Aug 20, 2024
da500c6
Fix possible deadlock in Connection-callback feature (#676)
k1-801 Sep 6, 2024
19112a2
Merge branch 'master' into connection-callback
Youw May 18, 2025
ec2cd2f
Merge branch 'master' into connection-callback
Youw Jan 15, 2026
12a30f1
Merge branch 'master' into connection-callback
Youw Mar 1, 2026
4398a7b
Merge branch 'master' into connection-callback
mcuee Mar 31, 2026
91145c9
fix console log
Youw Mar 31, 2026
9f2c472
Apply suggestions from code review
Youw Mar 31, 2026
5360e03
better device unplug handling on Windows
Youw Mar 31, 2026
8bd8ff7
code review fix
Youw Mar 31, 2026
1889e9f
libusb: Fix high CPU usage in callback thread (#783)
Copilot Apr 24, 2026
885e3e4
ci: use setup-msvc-dev action instead of a hard-coded vcvars path (#817)
Youw Jun 14, 2026
ab7c490
Hotplug docs: thread-safety contract and async ENUMERATE semantics (#…
Youw Jul 13, 2026
2dbb689
Merge branch 'master' into connection-callback
Youw Jul 13, 2026
65a3235
hotplug: fix C++ portability build across all backends
Youw Jul 13, 2026
acf58af
hotplug docs: state the global-error caveat for the thread-safe hotpl…
Youw Jul 14, 2026
e9567ca
hotplug docs: calls made from within a callback do not set the global…
Youw Jul 14, 2026
68af344
hotplug docs: note the unspecified invalid-arg string and the implici…
Youw Jul 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@ doxygen/html/

# Visual Studio Code + CMake
.vscode/

# Local build directory
build/
318 changes: 318 additions & 0 deletions hidapi/hidapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,324 @@ extern "C" {
*/
void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs);

/** @brief Callback handle.

Since version 0.16.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 16, 0)

Callback handles are generated by hid_hotplug_register_callback()
and can be used to deregister callbacks. Callback handles are
unique and are not reused while the library remains initialized,
so it is safe to call hid_hotplug_deregister_callback() on an
already deregistered callback: it fails with -1 and never
affects another callback.

A valid callback handle is always a positive value;
0 is never a valid handle and may be used as a sentinel.

@ingroup API
*/
typedef int hid_hotplug_callback_handle;

/** @brief Hotplug events.

Since version 0.16.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 16, 0)

@ingroup API
*/
typedef enum {
/** A device has been plugged in and may be opened.
Opening can still fail, e.g. due to insufficient permissions
or if the device has already disconnected again. */
HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED = (1 << 0),

/** A device has left and is no longer available.
If the application holds an open handle to the device, it is
still the application's responsibility to close it with
hid_close().
*/
HID_API_HOTPLUG_EVENT_DEVICE_LEFT = (1 << 1)
} hid_hotplug_event;

/** @brief Hotplug flags.

Since version 0.16.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 16, 0)

@ingroup API
*/
typedef enum {
/** Arm the callback and fire it for all matching devices that are
already connected at registration time.

hid_hotplug_register_callback() takes a snapshot of the
matching connected devices, and the callback is invoked
asynchronously, on the same internal event context that
delivers live events (see #hid_hotplug_callback_fn), once
for each device in that snapshot. Devices whose arrival is
detected after the snapshot is taken are reported as regular
live events: each device connection is reported to the
callback exactly once - either by the initial pass or as a
live event, never both and never neither. (A device that
disconnects and reconnects is a new connection and is
reported again.)

The initial pass is delivered before any live events for
this callback. In particular, a callback registered with
this flag for both event types never observes a
#HID_API_HOTPLUG_EVENT_DEVICE_LEFT for a matching device
whose arrival it has not been told about first.

These synthetic "arrived" events are delivered only when
#HID_API_HOTPLUG_EVENT_DEVICE_ARRIVED is present in the
requested events mask.

hid_hotplug_register_callback() returning does not imply
the initial pass has been delivered yet: the synthetic
events may fire before or after it returns. */
HID_API_HOTPLUG_ENUMERATE = (1 << 0)
} hid_hotplug_flag;

/** @brief Hotplug callback function type.

Since version 0.16.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 16, 0)

Called by HIDAPI when a device matching the registration filter
is connected or disconnected. See #hid_hotplug_register_callback.

@par Execution context

The callback is only ever invoked on HIDAPI's internal event
context, never on an application thread (including the
application's main thread). This includes the synthetic
"arrived" events requested with #HID_API_HOTPLUG_ENUMERATE:
they are delivered asynchronously on that same context and are
never delivered from within the hid_hotplug_register_callback()
call itself. (When a hotplug callback itself registers a new
callback with #HID_API_HOTPLUG_ENUMERATE, the new registration's
synthetic events are delivered later, on this same event
context.)

Every callback invocation holds an internal hotplug mutex for
its duration: any other thread calling
hid_hotplug_register_callback() or
hid_hotplug_deregister_callback() will block until the callback
returns. The mutex is re-entrant: calling those functions from
within the callback itself (i.e. on the event context, which
already holds the mutex) does not block and cannot deadlock -
that is what makes them safe to call from inside the callback
(see below). Keep the callback short.

When multiple callbacks are registered, each event is delivered
to every matching callback sequentially, in the order the
callbacks were registered.

@par What the callback may call

The hotplug API itself is thread-safe (see "Thread safety" under
#hid_hotplug_register_callback) and the following calls are always
safe from within the callback:

- hid_hotplug_register_callback()
- hid_hotplug_deregister_callback() (including on its own handle)
- hid_error(dev) with a non-NULL device handle, provided no
other thread uses that same handle concurrently

HIDAPI calls made from within the callback do not update the
global error string: the callback runs on HIDAPI's internal
event context, and internal contexts never write that string
(an application has no way to serialize against them, so writing
it there would be a use-after-free waiting to happen). Failures
are still reported through return values as usual, and
hid_error(dev) still works for a device handle - only
hid_error(NULL) is left untouched by calls made from the
callback.

Any other HIDAPI function follows HIDAPI's general thread-safety
rule (see the Multi-threading Notes in the project wiki): it is
the application's responsibility to serialize hid_init / hid_exit /
hid_enumerate / hid_open* / hid_close / hid_error(NULL) across all
threads, including the hotplug callback thread (hid_exit()
additionally must never be called from within the callback
itself - see below). If your application
already calls those functions only from one thread, calling them
from the hotplug callback adds a second thread and is therefore
UNSAFE unless the application adds synchronisation itself. The
recommended pattern is to copy the needed fields of @p device out
of the callback and handle open/close on your own thread.

Calling hid_exit() from within the callback has undefined behavior:
hid_exit() joins the hotplug thread, which would be joining itself.

@par The device parameter

The @p device pointer is owned by HIDAPI and is valid only for the
duration of the call. To keep any of its fields (path,
serial_number, manufacturer_string, etc.) beyond the callback,
copy them out; pointer fields must be deep-copied, as all strings
are owned by HIDAPI.

The @p device->next pointer is always NULL. Each callback
invocation describes exactly one device; compound or composite
devices that expose multiple interfaces produce multiple callback
invocations (typically delivered in quick succession).

For #HID_API_HOTPLUG_EVENT_DEVICE_LEFT events @p device points to
a copy captured when the device arrived (or was enumerated): all
fields, including the strings, are valid and describe the device
as it was while connected. A "left" event is delivered for any
matching device that disconnects while the callback is
registered, including devices that were already connected
before the registration (their arrival is reported to this
callback only if #HID_API_HOTPLUG_ENUMERATE was used). When the
callback has observed the device's arrival, the path field
matches the one reported then and may be used to correlate the
two events.

@par Return value

Return 0 to keep the callback registered. Return any non-zero
value to have HIDAPI deregister the callback; no further events
for this handle will be delivered, and the handle is freed as if
hid_hotplug_deregister_callback() had been called. This applies
to both live events and to the synthetic "arrived" events of
#HID_API_HOTPLUG_ENUMERATE: a non-zero return during the
initial pass stops the remainder of that pass and deregisters
the callback.

@ingroup API

@param callback_handle The handle of this callback.
@param device The hid_device_info of the device this event occurred on.
@param event Event that occurred: exactly one value (not a mask)
of \ref hid_hotplug_event.
@param user_data User data provided when this callback was registered
(may be NULL).

@returns
0 to stay registered; any non-zero value to be deregistered.
*/
typedef int (HID_API_CALL *hid_hotplug_callback_fn)(
hid_hotplug_callback_handle callback_handle,
struct hid_device_info *device,
hid_hotplug_event event,
void *user_data);

/** @brief Register a HID hotplug callback function.

Since version 0.16.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 16, 0)

If @p vendor_id is set to 0 then any vendor matches.
If @p product_id is set to 0 then any product matches.
If @p vendor_id and @p product_id are both set to 0, then all HID devices will be notified.

If HIDAPI is not initialized yet, this function initializes it
implicitly (as if by hid_init()). On some backends this binds
HIDAPI's device-monitoring facilities to the calling thread (for
example, the macOS backend schedules its run loop there). An
application that cares which thread owns those facilities should
call hid_init() explicitly from that thread first, rather than
relying on the implicit initialization performed here.

When #HID_API_HOTPLUG_ENUMERATE is set, the synthetic "arrived"
events are delivered asynchronously on HIDAPI's internal event
context (see #hid_hotplug_flag): they may fire before or after
this function returns, but never from within this call itself,
and never on an application thread.
@p callback_handle (when non-NULL) is written before any events
can be delivered, and the callback always receives its own
handle as a parameter, so the callback may deregister itself
even during that initial pass.

@par Thread safety

hid_hotplug_register_callback() and hid_hotplug_deregister_callback()
are thread-safe with respect to each other and to HIDAPI's
internal hotplug machinery. They may be called from any thread,
including from within a hotplug callback. This is a deliberate
exception to HIDAPI's general "not thread-safe" rule (see the
Multi-threading Notes in the project wiki).

The one caveat is the global error string: on failure these two
functions set it, like every other HIDAPI function that reports
an error via hid_error(NULL). They therefore have to be
serialized against hid_error(NULL) - which the application is
already required to serialize across all threads - even though
they need no serialization against each other. HIDAPI's own
internal threads never write the global error string, so an
application that serializes its own hid_error(NULL) calls
against its other HIDAPI calls is safe.

The first successful call to hid_hotplug_register_callback()
starts HIDAPI's internal hotplug machinery (on most platforms an
internal thread), which runs until either (a) the last callback
is deregistered, or (b) hid_exit() is called. hid_exit()
deregisters any callbacks that are still registered and
invalidates their handles. hid_exit() must not be called from
within a hotplug callback (see #hid_hotplug_callback_fn).

@ingroup API

@param vendor_id The Vendor ID (VID) of the types of device to notify about.
@param product_id The Product ID (PID) of the types of device to notify about.
@param events Bitwise or of hotplug events that will trigger this callback.
See \ref hid_hotplug_event.
@param flags Bitwise or of hotplug flags that affect registration.
See \ref hid_hotplug_flag.
@param callback The callback function that will be called on device connection/disconnection.
See \ref hid_hotplug_callback_fn.
@param user_data The user data you wanted to provide to your callback function.
@param callback_handle Pointer to store the handle of the allocated callback
(Optionally NULL). On failure, *callback_handle (when
non-NULL) is set to 0.

@returns
This function returns 0 on success or -1 on error.
Call hid_error(NULL) to get the failure reason.
Registration fails if @p callback is NULL, if @p events
contains no valid #hid_hotplug_event bit, or if @p events
or @p flags contain unknown bits. When more than one argument
is invalid, which one the failure string names is
unspecified and may differ between backends; only the -1
return and the zeroed @p callback_handle are guaranteed.

@note On backends without hotplug support (e.g. NetBSD)
this function always returns -1.
*/
int HID_API_EXPORT HID_API_CALL hid_hotplug_register_callback(unsigned short vendor_id, unsigned short product_id, int events, int flags, hid_hotplug_callback_fn callback, void *user_data, hid_hotplug_callback_handle *callback_handle);

/** @brief Deregister a callback from a HID hotplug.

Since version 0.16.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 16, 0)

Thread-safe. May be called from any thread, including from within
a hotplug callback (on its own handle or on another callback's
handle). Calling it on a handle that was already deregistered,
or on a handle that was never valid, is safe: it has no effect
and returns -1.

When called from any thread other than HIDAPI's internal event
context, this function does not return until an in-progress
invocation of the callback (if any) has completed; once it
returns, the callback will not be invoked again - including any
undelivered synthetic events of #HID_API_HOTPLUG_ENUMERATE -
and it is safe to release any resources the callback uses
(e.g. whatever @p user_data points to). When called from within
a hotplug callback, the deregistered callback will not be
invoked once the currently executing invocation returns.

See "Thread safety" on #hid_hotplug_register_callback for the
full thread-safety contract of the hotplug API.

@ingroup API

@param callback_handle The handle of the callback to deregister.

@returns
This function returns 0 when the callback was found and
deregistered, or -1 on error (including when
@p callback_handle is not a registered handle).
*/
int HID_API_EXPORT HID_API_CALL hid_hotplug_deregister_callback(hid_hotplug_callback_handle callback_handle);

/** @brief Open a HID device using a Vendor ID (VID), Product ID
(PID) and optionally a serial number.

Expand Down
Loading
Loading