diff options
| author | Aram Drevekenin <aram@poor.dev> | 2026-06-10 16:50:55 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-06-10 16:50:55 +0200 |
| commit | b6a5ad0e5a93d9f584b3f7a0fc90f497a09cbdb0 (patch) | |
| tree | 38c77a381cfb937b428752a588c09a45c81434c9 /zellij-utils/src/kdl | |
| parent | feat: PWA support for the web client (#5184) (diff) | |
| download | sl0ppij-b6a5ad0e5a93d9f584b3f7a0fc90f497a09cbdb0.tar.gz sl0ppij-b6a5ad0e5a93d9f584b3f7a0fc90f497a09cbdb0.tar.xz sl0ppij-b6a5ad0e5a93d9f584b3f7a0fc90f497a09cbdb0.zip | |
Mobile layout (#5241)
* phase 2+3
* initial mobile plugin (phases 4 + 5 + 6)
* some bugfixes
* implement stage 7
* mobile interface improvements
* improve mobile pane/tab picker
* more ui improvements and fixes
* various mobile fixes
* some mobile fixes
* mobile keyboard triggering fix
* some cleanups
* initial plugin keyboard implementation
* improve keyboard hit zones
* improve keyboard
* pinch resize
* pinching fixes
* implement scrolling + panning
* initial keyboard resizing
* fix: mobile first render issues
* keyboard responsiveness
* fix some issues with the keyboard
* implement fit
* fix mobile scrolling
* add tilde cell to compact
* improve mobile keyboard
* fix starting mobile terminal font to natural keyboard size
* fix mobile resize blank screen flash
* shadow focus for mobile
* more keyboard fixes
* improve mobile status bar
* fix: render plugins to mobile clients
* allow opening new panes/tabs from the mobile plugin
* allow configuring
* fix dot slash
* improve mobile native soft keyboard interaction
* switch to native keyboard - initial implementation
* dirty hacks, but phone keyboard works now
* adjust keyboard help strip
* fix cursor in mobile
* fix keyboard and help line
* rename keyboard to modifier bar
* allow starting new session
* make clicking session name bring us to new session menu
* allow switching to desktop from the mobile ui
* mobile welcome screen initial implementation
* mobile welcome screen fixes
* various welcome screen fixes
* more mobile fixes
* moar mobile fixes
* mobile ui redesign
* fix: swallowed backspace bug
* switch session screen improvement
* touch up some interfaces
* refactor: move mobile state to own struct
* refactor: move fit resizing to the server
* remove dead flash code
* some cleanups
* refactor: clear fit on close
* refactor: improve fit api
* refactor: pane sync functions
* refactor: mouse
* refactor: break into modules
* some cleanups for the sessions screen
* fix scrolling and other stuff
* refactor: sessions screen
* refactor: panes screen
* refactor: new session screen
* refactor: menu screen
* refactor: render
* cleanups
* refactor: plugin structure
* remove unused welcome screen path
* Rename api
* refactor input.js
* cleanup useless comments
* fit/mobile refactoring
* take out the garbage
* remove more comments
* remove mobile flash on startup/attach
* fix: various mobile startup issues
* fix space bugs
* cleanup
* rustfmt
* fix windows build
* adjust modifier bar ui
* add pr
Diffstat (limited to 'zellij-utils/src/kdl')
5 files changed, 212 insertions, 2 deletions
diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs index c7cc5366..0164ef92 100644 --- a/zellij-utils/src/kdl/mod.rs +++ b/zellij-utils/src/kdl/mod.rs @@ -81,6 +81,7 @@ macro_rules! parse_kdl_action_arguments { "Confirm" => Ok(Action::Confirm), "Deny" => Ok(Action::Deny), "ToggleMouseMode" => Ok(Action::ToggleMouseMode), + "ToggleMobileMode" => Ok(Action::ToggleMobileMode), "PreviousSwapLayout" => Ok(Action::PreviousSwapLayout), "NextSwapLayout" => Ok(Action::NextSwapLayout), "Clear" => Ok(Action::ClearScreen), @@ -1240,6 +1241,7 @@ impl Action { Some(node) }, Action::ToggleMouseMode => Some(KdlNode::new("ToggleMouseMode")), + Action::ToggleMobileMode => Some(KdlNode::new("ToggleMobileMode")), Action::PreviousSwapLayout => Some(KdlNode::new("PreviousSwapLayout")), Action::NextSwapLayout => Some(KdlNode::new("NextSwapLayout")), Action::BreakPane => Some(KdlNode::new("BreakPane")), @@ -1582,6 +1584,9 @@ impl TryFrom<(&KdlNode, &Options)> for Action { "ToggleMouseMode" => { parse_kdl_action_arguments!(action_name, action_arguments, kdl_action) }, + "ToggleMobileMode" => { + parse_kdl_action_arguments!(action_name, action_arguments, kdl_action) + }, "Detach" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action), "SwitchSession" => { let name = kdl_get_string_property_or_child_value!(kdl_action, "name") @@ -2824,6 +2829,39 @@ impl Options { let mouse_click_through = kdl_property_first_arg_as_bool_or_error!(kdl_options, "mouse_click_through") .map(|(v, _)| v); + let mobile_layout = + match kdl_property_first_arg_as_string_or_error!(kdl_options, "mobile_layout") { + Some((value, entry)) => { + use crate::input::options::MobileLayoutConfiguration; + match value.parse::<MobileLayoutConfiguration>() { + Ok(v) => Some(v), + Err(e) => return Err(kdl_parsing_error!(e, entry)), + } + }, + None => None, + }; + let mobile_threshold_cols = + match kdl_property_first_arg_as_i64_or_error!(kdl_options, "mobile_threshold_cols") { + Some((value, _)) if value >= 0 => Some(value as u16), + Some((value, entry)) => { + return Err(kdl_parsing_error!( + format!("mobile_threshold_cols must be >= 0, found '{}'", value), + entry + )); + }, + None => None, + }; + let mobile_threshold_rows = + match kdl_property_first_arg_as_i64_or_error!(kdl_options, "mobile_threshold_rows") { + Some((value, _)) if value >= 0 => Some(value as u16), + Some((value, entry)) => { + return Err(kdl_parsing_error!( + format!("mobile_threshold_rows must be >= 0, found '{}'", value), + entry + )); + }, + None => None, + }; Ok(Options { simplified_ui, @@ -2873,6 +2911,9 @@ impl Options { enforce_https_for_localhost, post_command_discovery_hook, client_async_worker_tasks, + mobile_layout, + mobile_threshold_cols, + mobile_threshold_rows, }) } pub fn from_string(stringified_keybindings: &String) -> Result<Self, ConfigError> { @@ -4218,6 +4259,94 @@ impl Options { None } } + fn mobile_layout_to_kdl(&self, add_comments: bool) -> Option<KdlNode> { + use crate::input::options::MobileLayoutConfiguration; + let comment_text = format!( + "{}\n{}\n{}\n{}\n{}\n{}\n{}", + " ", + "// When attached clients land in the mobile UI plugin.", + "// Options:", + "// - \"web\" (Default — web clients only, gated on mobile_threshold_cols/rows)", + "// - \"always\" (any client, gated on mobile_threshold_cols/rows)", + "// - \"never\"", + "// ", + ); + let create_node = |value: MobileLayoutConfiguration| -> KdlNode { + let mut node = KdlNode::new("mobile_layout"); + let s = match value { + MobileLayoutConfiguration::Web => "web", + MobileLayoutConfiguration::Always => "always", + MobileLayoutConfiguration::Never => "never", + }; + node.push(KdlValue::String(s.to_string())); + node + }; + if let Some(value) = self.mobile_layout { + let mut node = create_node(value); + if add_comments { + node.set_leading(format!("{}\n", comment_text)); + } + Some(node) + } else if add_comments { + let mut node = create_node(MobileLayoutConfiguration::Web); + node.set_leading(format!("{}\n// ", comment_text)); + Some(node) + } else { + None + } + } + fn mobile_threshold_cols_to_kdl(&self, add_comments: bool) -> Option<KdlNode> { + let comment_text = format!( + "{}\n{}\n{}", + " ", + "// Column breakpoint for mobile_layout (web/always). 0 = always match.", + "// Default: 60", + ); + let create_node = |value: u16| -> KdlNode { + let mut node = KdlNode::new("mobile_threshold_cols"); + node.push(KdlValue::Base10(value as i64)); + node + }; + if let Some(value) = self.mobile_threshold_cols { + let mut node = create_node(value); + if add_comments { + node.set_leading(format!("{}\n", comment_text)); + } + Some(node) + } else if add_comments { + let mut node = create_node(60); + node.set_leading(format!("{}\n// ", comment_text)); + Some(node) + } else { + None + } + } + fn mobile_threshold_rows_to_kdl(&self, add_comments: bool) -> Option<KdlNode> { + let comment_text = format!( + "{}\n{}\n{}", + " ", + "// Row breakpoint for mobile_layout (web/always). 0 = always match.", + "// Default: 30", + ); + let create_node = |value: u16| -> KdlNode { + let mut node = KdlNode::new("mobile_threshold_rows"); + node.push(KdlValue::Base10(value as i64)); + node + }; + if let Some(value) = self.mobile_threshold_rows { + let mut node = create_node(value); + if add_comments { + node.set_leading(format!("{}\n", comment_text)); + } + Some(node) + } else if add_comments { + let mut node = create_node(30); + node.set_leading(format!("{}\n// ", comment_text)); + Some(node) + } else { + None + } + } fn client_async_worker_tasks_to_kdl(&self, add_comments: bool) -> Option<KdlNode> { let comment_text = r#" // Number of async worker tasks to spawn per active client. @@ -4396,6 +4525,15 @@ impl Options { { nodes.push(client_async_worker_tasks); } + if let Some(mobile_layout) = self.mobile_layout_to_kdl(add_comments) { + nodes.push(mobile_layout); + } + if let Some(mobile_threshold_cols) = self.mobile_threshold_cols_to_kdl(add_comments) { + nodes.push(mobile_threshold_cols); + } + if let Some(mobile_threshold_rows) = self.mobile_threshold_rows_to_kdl(add_comments) { + nodes.push(mobile_threshold_rows); + } nodes } } @@ -7111,6 +7249,9 @@ fn config_options_to_string() { support_kitty_keyboard_protocol false web_server true web_sharing "disabled" + mobile_layout "always" + mobile_threshold_cols 72 + mobile_threshold_rows 0 "##; let document: KdlDocument = fake_config.parse().unwrap(); let deserialized = Options::from_kdl(&document).unwrap(); @@ -7158,6 +7299,9 @@ fn config_options_to_string_with_comments() { support_kitty_keyboard_protocol false web_server true web_sharing "disabled" + mobile_layout "always" + mobile_threshold_cols 72 + mobile_threshold_rows 0 "##; let document: KdlDocument = fake_config.parse().unwrap(); let deserialized = Options::from_kdl(&document).unwrap(); @@ -7192,6 +7336,37 @@ fn config_options_to_string_without_options() { } #[test] +fn mobile_layout_kdl_round_trip_for_every_variant() { + use crate::input::options::MobileLayoutConfiguration; + let cases = [ + ("web", MobileLayoutConfiguration::Web, (60, 30)), + ("always", MobileLayoutConfiguration::Always, (0, 0)), + ("never", MobileLayoutConfiguration::Never, (40, 0)), + ]; + for (value, expected, (cols, rows)) in cases { + let fake_config = format!( + r##" + mobile_layout "{value}" + mobile_threshold_cols {cols} + mobile_threshold_rows {rows} + "## + ); + let document: KdlDocument = fake_config.parse().unwrap(); + let parsed = Options::from_kdl(&document).unwrap(); + assert_eq!(parsed.mobile_layout, Some(expected), "case: {value}"); + assert_eq!(parsed.mobile_threshold_cols, Some(cols), "case: {value}"); + assert_eq!(parsed.mobile_threshold_rows, Some(rows), "case: {value}"); + + let mut serialized = Options::to_kdl(&parsed, false); + let mut fake_document = KdlDocument::new(); + fake_document.nodes_mut().append(&mut serialized); + let reparsed = + Options::from_kdl(&fake_document.to_string().parse::<KdlDocument>().unwrap()).unwrap(); + assert_eq!(parsed, reparsed, "round-trip mismatch for {value}"); + } +} + +#[test] fn config_options_to_string_with_some_options() { let fake_config = r##" default_layout "compact" diff --git a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string.snap b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string.snap index 6eda59b9..1ab39935 100644 --- a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string.snap +++ b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string.snap @@ -261,6 +261,7 @@ plugins { filepicker location="zellij:strider" { cwd "/" } + mobile location="zellij:mobile" plugin-manager location="zellij:plugin-manager" session-manager location="zellij:session-manager" status-bar location="zellij:status-bar" diff --git a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string_with_comments.snap b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string_with_comments.snap index c49fa8f9..5dbede33 100644 --- a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string_with_comments.snap +++ b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string_with_comments.snap @@ -264,6 +264,7 @@ plugins { filepicker location="zellij:strider" { cwd "/" } + mobile location="zellij:mobile" plugin-manager location="zellij:plugin-manager" session-manager location="zellij:session-manager" status-bar location="zellij:status-bar" @@ -582,4 +583,19 @@ web_client { // typically work best. Set to 0 to use the number of (physical) CPU cores. // Note: This only applies to web clients at the moment. // client_async_worker_tasks 4 - + +// When attached clients land in the mobile UI plugin. +// Options: +// - "web" (Default — web clients only, gated on mobile_threshold_cols/rows) +// - "always" (any client, gated on mobile_threshold_cols/rows) +// - "never" +// +// mobile_layout "web" + +// Column breakpoint for mobile_layout (web/always). 0 = always match. +// Default: 60 +// mobile_threshold_cols 60 + +// Row breakpoint for mobile_layout (web/always). 0 = always match. +// Default: 30 +// mobile_threshold_rows 30 diff --git a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__config_options_to_string.snap b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__config_options_to_string.snap index 6c78ea96..43c82f7e 100644 --- a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__config_options_to_string.snap +++ b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__config_options_to_string.snap @@ -31,3 +31,6 @@ disable_session_metadata true support_kitty_keyboard_protocol false web_server true web_sharing "disabled" +mobile_layout "always" +mobile_threshold_cols 72 +mobile_threshold_rows 0 diff --git a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__config_options_to_string_with_comments.snap b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__config_options_to_string_with_comments.snap index eda40849..347a2074 100644 --- a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__config_options_to_string_with_comments.snap +++ b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__config_options_to_string_with_comments.snap @@ -301,4 +301,19 @@ web_sharing "disabled" // typically work best. Set to 0 to use the number of (physical) CPU cores. // Note: This only applies to web clients at the moment. // client_async_worker_tasks 4 - + +// When attached clients land in the mobile UI plugin. +// Options: +// - "web" (Default — web clients only, gated on mobile_threshold_cols/rows) +// - "always" (any client, gated on mobile_threshold_cols/rows) +// - "never" +// +mobile_layout "always" + +// Column breakpoint for mobile_layout (web/always). 0 = always match. +// Default: 60 +mobile_threshold_cols 72 + +// Row breakpoint for mobile_layout (web/always). 0 = always match. +// Default: 30 +mobile_threshold_rows 0 |
