@@ -502,14 +502,20 @@ func (p *evdevProxy) emitPointer(event waylandEvdevEvent) {
502502// emitKey re-emits a key event of the proxy's own making, with the sync report
503503// that makes it a complete frame.
504504func (p * evdevProxy ) emitKey (code uint16 , value int32 ) {
505+ frame := keyFrame (code , value )
506+ p .write (p .uinputFd , & frame [0 ], len (frame ))
507+ }
508+
509+ // keyFrame is one key event and the sync report that completes it.
510+ func keyFrame (code uint16 , value int32 ) [2 ]C.struct_input_event {
505511 var frame [2 ]C.struct_input_event
506512 frame [0 ]._type = C .ushort (evdevEventKey )
507513 frame [0 ].code = C .ushort (code )
508514 frame [0 ].value = C .int (value )
509515 frame [1 ]._type = C .ushort (evdevEventSyn )
510516 frame [1 ].code = C .ushort (evdevSynReport )
511517
512- p . write ( p . uinputFd , & frame [ 0 ], len ( frame ))
518+ return frame
513519}
514520
515521// write puts count events on one proxy device, whole. Anything less means the
@@ -542,6 +548,8 @@ func (p *evdevProxy) failOpen(err error) {
542548 return
543549 }
544550
551+ p .releaseForwarded ()
552+
545553 p .capture .ungrabAll ()
546554
547555 // The keys now reach the compositor as well, so a session reading them
@@ -571,6 +579,38 @@ func (p *evdevProxy) failOpen(err error) {
571579 )
572580}
573581
582+ // releaseForwarded re-emits a release on the proxy keyboard for every key the
583+ // rule counts as forwarded and down, then zeroes the rule. Failing open is the
584+ // one time a keyboard is let go without waiting for it to be idle: a key down
585+ // at that instant had its press re-emitted here, and its release goes to the
586+ // physical device next, whose libinput never saw the press and drops it. Left
587+ // down on the proxy keyboard it would stay down for the daemon's lifetime, and
588+ // a compositor that merges modifiers across keyboards (Hyprland) would put
589+ // that Super on every key typed afterwards. Base keys go up before modifiers,
590+ // the way a chord is let go of. The writes bypass the forwarding flag, which
591+ // failOpen has cleared; on a proxy that stopped taking writes they fail as the
592+ // last one did, and there is nothing more to do for it.
593+ func (p * evdevProxy ) releaseForwarded () {
594+ var keys , modifiers []uint16
595+
596+ for code := range uint16 (evdevKeyCodeCount ) {
597+ switch {
598+ case ! p .rule .isDown (code ):
599+ case p .capture .modifierName (code ) != "" :
600+ modifiers = append (modifiers , code )
601+ default :
602+ keys = append (keys , code )
603+ }
604+ }
605+
606+ p .rule = forwardRule {}
607+
608+ for _ , code := range append (keys , modifiers ... ) {
609+ frame := keyFrame (code , evdevValueRelease )
610+ C .neru_evdev_write_events (p .uinputFd , & frame [0 ], C .int (len (frame )))
611+ }
612+ }
613+
574614// forwardWithheld hands a press the session had withheld to the compositor
575615// after all, together with every modifier the user is physically holding that
576616// the session withheld too. Each is marked forwarded, so its repeats and its
0 commit comments