@@ -859,6 +859,37 @@ async fn run_daemon(config_path: &std::path::Path) -> anyhow::Result<()> { |
| 859 | 859 | info!("Stopping input capture"); |
| 860 | 860 | capture_direction = None; |
| 861 | 861 | 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 | + } |
| 862 | 893 | } |
| 863 | 894 | transfer::TransferEvent::StartInjection { from } => { |
| 864 | 895 | info!("Starting input injection from {:?}", from); |
@@ -1107,12 +1138,10 @@ async fn handle_move(direction: &str) -> anyhow::Result<()> { |
| 1107 | 1138 | use hyprkvm_common::protocol::{IpcRequest, IpcResponse}; |
| 1108 | 1139 | |
| 1109 | 1140 | let dir: Direction = direction.parse()?; |
| 1110 | | - tracing::debug!("CLI: handle_move {:?}", dir); |
| 1111 | 1141 | |
| 1112 | 1142 | // Try to connect to daemon |
| 1113 | 1143 | match ipc::IpcClient::connect().await { |
| 1114 | 1144 | Ok(mut client) => { |
| 1115 | | - tracing::debug!("CLI: connected to daemon"); |
| 1116 | 1145 | // Ask daemon to handle the move (it does movefocus internally) |
| 1117 | 1146 | let request = IpcRequest::Move { direction: dir }; |
| 1118 | 1147 | match client.request(&request).await { |
@@ -1120,8 +1149,7 @@ async fn handle_move(direction: &str) -> anyhow::Result<()> { |
| 1120 | 1149 | tracing::info!("Transferred control to {}", to_machine); |
| 1121 | 1150 | } |
| 1122 | 1151 | 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 |
| 1125 | 1153 | } |
| 1126 | 1154 | Ok(IpcResponse::Error { message }) => { |
| 1127 | 1155 | tracing::warn!("Daemon error: {}", message); |
@@ -1130,7 +1158,7 @@ async fn handle_move(direction: &str) -> anyhow::Result<()> { |
| 1130 | 1158 | tracing::warn!("Unexpected response from daemon"); |
| 1131 | 1159 | } |
| 1132 | 1160 | 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); |
| 1134 | 1162 | do_local_move(dir).await?; |
| 1135 | 1163 | } |
| 1136 | 1164 | } |