tenseleyflow/hyprkvm / c751269

Browse files

Reset modifier keys after releasing input grab

When control returns from remote machine, the local compositor never
saw key-up events for modifiers that were released while input was
grabbed. This caused "stuck" modifier state where the compositor
thought SUPER was still pressed, preventing keybinds from working.

Fix: send synthetic key-up events for all modifier keys (Shift, Ctrl,
Alt, Super) via virtual keyboard after stopping the input grab.

Also adds debug logging to IPC server for troubleshooting.
Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
c751269b7a5c5a5900a5821607e3848aa5762b53
Parents
ae69da8
Tree
be0d545

1 changed file

StatusFile+-
M hyprkvm-daemon/src/main.rs 33 5
hyprkvm-daemon/src/main.rsmodified
@@ -859,6 +859,37 @@ async fn run_daemon(config_path: &std::path::Path) -> anyhow::Result<()> {
859859
                         info!("Stopping input capture");
860860
                         capture_direction = None;
861861
                         input_grabber.stop();
862
+
863
+                        // After ungrabbing, we need to reset modifier state in the compositor.
864
+                        // The compositor didn't see key-ups for modifiers that were released
865
+                        // while we had input grabbed. Send synthetic key-ups for all modifiers.
866
+                        if input_emulator.is_none() {
867
+                            match input::InputEmulator::new() {
868
+                                Ok(emu) => {
869
+                                    input_emulator = Some(emu);
870
+                                }
871
+                                Err(e) => {
872
+                                    tracing::warn!("Failed to create emulator for modifier reset: {}", e);
873
+                                }
874
+                            }
875
+                        }
876
+                        if let Some(ref mut emu) = input_emulator {
877
+                            tracing::debug!("Sending modifier key-ups to reset compositor state");
878
+                            // Release all modifier keys
879
+                            const MODIFIER_KEYCODES: &[u32] = &[
880
+                                42,   // KEY_LEFTSHIFT
881
+                                54,   // KEY_RIGHTSHIFT
882
+                                29,   // KEY_LEFTCTRL
883
+                                97,   // KEY_RIGHTCTRL
884
+                                56,   // KEY_LEFTALT
885
+                                100,  // KEY_RIGHTALT
886
+                                125,  // KEY_LEFTMETA (Super)
887
+                                126,  // KEY_RIGHTMETA (Super)
888
+                            ];
889
+                            for &keycode in MODIFIER_KEYCODES {
890
+                                emu.keyboard.key(keycode, hyprkvm_common::KeyState::Released);
891
+                            }
892
+                        }
862893
                     }
863894
                     transfer::TransferEvent::StartInjection { from } => {
864895
                         info!("Starting input injection from {:?}", from);
@@ -1107,12 +1138,10 @@ async fn handle_move(direction: &str) -> anyhow::Result<()> {
11071138
     use hyprkvm_common::protocol::{IpcRequest, IpcResponse};
11081139
 
11091140
     let dir: Direction = direction.parse()?;
1110
-    tracing::debug!("CLI: handle_move {:?}", dir);
11111141
 
11121142
     // Try to connect to daemon
11131143
     match ipc::IpcClient::connect().await {
11141144
         Ok(mut client) => {
1115
-            tracing::debug!("CLI: connected to daemon");
11161145
             // Ask daemon to handle the move (it does movefocus internally)
11171146
             let request = IpcRequest::Move { direction: dir };
11181147
             match client.request(&request).await {
@@ -1120,8 +1149,7 @@ async fn handle_move(direction: &str) -> anyhow::Result<()> {
11201149
                     tracing::info!("Transferred control to {}", to_machine);
11211150
                 }
11221151
                 Ok(IpcResponse::DoLocalMove) => {
1123
-                    // Daemon already did movefocus, nothing more to do
1124
-                    tracing::debug!("CLI: local move handled by daemon");
1152
+                    // Daemon handled it
11251153
                 }
11261154
                 Ok(IpcResponse::Error { message }) => {
11271155
                     tracing::warn!("Daemon error: {}", message);
@@ -1130,7 +1158,7 @@ async fn handle_move(direction: &str) -> anyhow::Result<()> {
11301158
                     tracing::warn!("Unexpected response from daemon");
11311159
                 }
11321160
                 Err(e) => {
1133
-                    tracing::warn!("CLI: IPC request failed: {}, falling back to local", e);
1161
+                    tracing::debug!("IPC request failed: {}, falling back to local", e);
11341162
                     do_local_move(dir).await?;
11351163
                 }
11361164
             }