1010
1111#[ cfg( not( target_os = "windows" ) ) ]
1212use std:: error:: Error ;
13+ #[ cfg( not( target_os = "windows" ) ) ]
14+ use std:: sync:: atomic:: { AtomicBool , Ordering } ;
1315use std:: sync:: { Arc , LazyLock } ;
1416
1517#[ cfg( not( target_os = "windows" ) ) ]
@@ -266,6 +268,7 @@ pub(crate) struct AsyncHidChannel {
266268 reader : Mutex < DeviceReader > ,
267269 writer : Mutex < DeviceWriter > ,
268270 info : DeviceInfo ,
271+ connected : AtomicBool ,
269272 /// Whether the device exposes only the long HID++ report (a BLE-direct
270273 /// peripheral on macOS). Reported via `supports_short_long_hidpp` so the
271274 /// `hidpp` channel up-converts outgoing short messages to long.
@@ -284,9 +287,16 @@ impl AsyncHidChannel {
284287 reader : Mutex :: new ( reader) ,
285288 writer : Mutex :: new ( writer) ,
286289 info,
290+ connected : AtomicBool :: new ( true ) ,
287291 long_only,
288292 }
289293 }
294+
295+ fn mark_disconnected ( & self ) {
296+ if self . connected . swap ( false , Ordering :: AcqRel ) {
297+ debug ! ( name = %self . info. name, "HID channel disconnected" ) ;
298+ }
299+ }
290300}
291301
292302#[ cfg( not( target_os = "windows" ) ) ]
@@ -302,8 +312,15 @@ impl RawHidChannel for AsyncHidChannel {
302312
303313 async fn write_report ( & self , src : & [ u8 ] ) -> Result < usize , Box < dyn Error + Send + Sync > > {
304314 let mut w = self . writer . lock ( ) . await ;
305- w. write_output_report ( src) . await ?;
306- Ok ( src. len ( ) )
315+ match w. write_output_report ( src) . await {
316+ Ok ( ( ) ) => Ok ( src. len ( ) ) ,
317+ Err ( e) => {
318+ if matches ! ( e, async_hid:: HidError :: Disconnected ) {
319+ self . mark_disconnected ( ) ;
320+ }
321+ Err ( e. into ( ) )
322+ }
323+ }
307324 }
308325
309326 async fn read_report ( & self , buf : & mut [ u8 ] ) -> Result < usize , Box < dyn Error + Send + Sync > > {
@@ -320,11 +337,18 @@ impl RawHidChannel for AsyncHidChannel {
320337 // until the inventory watcher evicts the channel), so park instead.
321338 // The contract guarantees every caller races this future against
322339 // the channel's close signal, which tears the read down on drop.
323- Err ( async_hid:: HidError :: Disconnected ) => std:: future:: pending ( ) . await ,
340+ Err ( async_hid:: HidError :: Disconnected ) => {
341+ self . mark_disconnected ( ) ;
342+ std:: future:: pending ( ) . await
343+ }
324344 Err ( e) => Err ( e. into ( ) ) ,
325345 }
326346 }
327347
348+ fn is_connected ( & self ) -> bool {
349+ self . connected . load ( Ordering :: Acquire )
350+ }
351+
328352 fn supports_short_long_hidpp ( & self ) -> Option < ( bool , bool ) > {
329353 // USB / receiver collections carry both reports; BLE-direct collections
330354 // are long-only (no short report on macOS), where the `hidpp` channel
0 commit comments