aboutsummaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-14 02:50:00 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-14 02:50:00 +0300
commited4253d6031c9a3b12ddd6dfc7d7bf39480ae761 (patch)
tree315cea3ee7b5ad3a7c3f9fb31272064a0eed5dd2 /src/server
parent287bf51640f8e1bed227cb27d93377d962851196 (diff)
feat: add tmux-style mouse capture config
Diffstat (limited to 'src/server')
-rw-r--r--src/server/headless.rs42
-rw-r--r--src/server/protocol.rs15
2 files changed, 57 insertions, 0 deletions
diff --git a/src/server/headless.rs b/src/server/headless.rs
index f490ec45..3ff3e587 100644
--- a/src/server/headless.rs
+++ b/src/server/headless.rs
@@ -205,6 +205,8 @@ struct ClientConnection {
graphics_cache: crate::kitty_graphics::HostGraphicsCache,
/// Whether a render was skipped because the render channel was full.
render_pending: bool,
+ /// Last host mouse capture mode sent to this client.
+ host_mouse_capture_active: Option<bool>,
/// Channels for sending framed ServerMessage data to the client writer thread.
writer: Option<ClientWriter>,
}
@@ -228,6 +230,7 @@ impl ClientConnection {
render_state: ClientRenderState::new(render_encoding),
graphics_cache: crate::kitty_graphics::HostGraphicsCache::default(),
render_pending: false,
+ host_mouse_capture_active: None,
writer,
}
}
@@ -416,6 +419,7 @@ impl HeadlessServer {
}
self.drain_client_sound_config_reload_request();
+ self.stream_host_mouse_capture_mode();
self.app.sync_headless_animation_timer(now);
@@ -1423,6 +1427,44 @@ impl HeadlessServer {
changed
}
+ fn stream_host_mouse_capture_mode(&mut self) {
+ let enabled = self.app.state.should_capture_host_mouse();
+ let serialized = match Self::frame_server_message(&ServerMessage::MouseCapture { enabled })
+ {
+ Ok(framed) => framed,
+ Err(err) => {
+ warn!(err = %err, "failed to serialize mouse capture mode for clients");
+ return;
+ }
+ };
+
+ let mut broken_clients: Vec<u64> = Vec::new();
+ for (&client_id, client) in &mut self.clients {
+ if client.host_mouse_capture_active == Some(enabled) {
+ continue;
+ }
+ let Some(writer) = &client.writer else {
+ continue;
+ };
+ if writer.control.send(serialized.clone()).is_err() {
+ debug!(
+ client_id,
+ "client writer channel closed during mouse capture update"
+ );
+ broken_clients.push(client_id);
+ continue;
+ }
+ client.host_mouse_capture_active = Some(enabled);
+ }
+
+ for client_id in broken_clients {
+ let foreground_changed = self.remove_client(client_id);
+ if foreground_changed {
+ self.resize_shared_runtime_to_effective_size();
+ }
+ }
+ }
+
/// Renders the current state to client-sized virtual buffers and streams
/// frames to all connected clients.
fn render_and_stream(&mut self) {
diff --git a/src/server/protocol.rs b/src/server/protocol.rs
index 8748c41c..e0a3c4a0 100644
--- a/src/server/protocol.rs
+++ b/src/server/protocol.rs
@@ -294,6 +294,12 @@ pub enum ServerMessage {
/// Client-local sound config changed on disk; refresh it without reconnecting.
ReloadSoundConfig,
+
+ /// Whether the client should currently capture host mouse input.
+ MouseCapture {
+ /// True when Herdr mouse UI is enabled or the focused pane app requests mouse reporting.
+ enabled: bool,
+ },
}
// ---------------------------------------------------------------------------
@@ -799,6 +805,15 @@ mod tests {
assert_eq!(msg, decoded);
}
+ #[test]
+ fn server_mouse_capture_roundtrip() {
+ let msg = ServerMessage::MouseCapture { enabled: true };
+ let encoded = bincode::serde::encode_to_vec(&msg, bincode::config::standard()).unwrap();
+ let (decoded, _): (ServerMessage, _) =
+ bincode::serde::decode_from_slice(&encoded, bincode::config::standard()).unwrap();
+ assert_eq!(msg, decoded);
+ }
+
// ---- Framing ----
#[test]