aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-utils
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2026-06-10 16:50:55 +0200
committerGitHub <noreply@github.com>2026-06-10 16:50:55 +0200
commitb6a5ad0e5a93d9f584b3f7a0fc90f497a09cbdb0 (patch)
tree38c77a381cfb937b428752a588c09a45c81434c9 /zellij-utils
parentfeat: PWA support for the web client (#5184) (diff)
downloadsl0ppij-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')
-rw-r--r--zellij-utils/assets/config/default.kdl1
-rwxr-xr-xzellij-utils/assets/plugins/mobile.wasmbin0 -> 1505278 bytes
-rw-r--r--zellij-utils/assets/prost/api.action.rs4
-rw-r--r--zellij-utils/assets/prost/api.event.rs21
-rw-r--r--zellij-utils/assets/prost/api.plugin_command.rs91
-rw-r--r--zellij-utils/assets/prost_ipc/client_server_contract.rs101
-rw-r--r--zellij-utils/src/client_server_contract/client_to_server.proto12
-rw-r--r--zellij-utils/src/client_server_contract/common_types.proto14
-rw-r--r--zellij-utils/src/client_server_contract/server_to_client.proto5
-rw-r--r--zellij-utils/src/consts.rs33
-rw-r--r--zellij-utils/src/data.rs27
-rw-r--r--zellij-utils/src/errors.rs13
-rw-r--r--zellij-utils/src/input/actions.rs1
-rw-r--r--zellij-utils/src/input/mod.rs251
-rw-r--r--zellij-utils/src/input/mouse.rs78
-rw-r--r--zellij-utils/src/input/options.rs289
-rw-r--r--zellij-utils/src/input/plugins.rs1
-rw-r--r--zellij-utils/src/input/web_client.rs27
-rw-r--r--zellij-utils/src/ipc.rs28
-rw-r--r--zellij-utils/src/ipc/protobuf_conversion.rs91
-rw-r--r--zellij-utils/src/ipc/tests/roundtrip_tests.rs47
-rw-r--r--zellij-utils/src/ipc/tests/socket_tests.rs28
-rw-r--r--zellij-utils/src/kdl/mod.rs175
-rw-r--r--zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string.snap1
-rw-r--r--zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string_with_comments.snap18
-rw-r--r--zellij-utils/src/kdl/snapshots/zellij_utils__kdl__config_options_to_string.snap3
-rw-r--r--zellij-utils/src/kdl/snapshots/zellij_utils__kdl__config_options_to_string_with_comments.snap17
-rw-r--r--zellij-utils/src/pane_size.rs10
-rw-r--r--zellij-utils/src/plugin_api/action.proto2
-rw-r--r--zellij-utils/src/plugin_api/action.rs7
-rw-r--r--zellij-utils/src/plugin_api/event.proto9
-rw-r--r--zellij-utils/src/plugin_api/event.rs62
-rw-r--r--zellij-utils/src/plugin_api/plugin_command.proto46
-rw-r--r--zellij-utils/src/plugin_api/plugin_command.rs223
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_config_options.snap3
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options.snap3
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-3.snap3
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap16
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap16
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap16
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options.snap3
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap16
-rw-r--r--zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap16
43 files changed, 1598 insertions, 230 deletions
diff --git a/zellij-utils/assets/config/default.kdl b/zellij-utils/assets/config/default.kdl
index 3ea42850..61aa4bb8 100644
--- a/zellij-utils/assets/config/default.kdl
+++ b/zellij-utils/assets/config/default.kdl
@@ -242,6 +242,7 @@ plugins {
configuration location="zellij:configuration"
plugin-manager location="zellij:plugin-manager"
about location="zellij:about"
+ mobile location="zellij:mobile"
}
// Plugins to load in the background when a new session starts
diff --git a/zellij-utils/assets/plugins/mobile.wasm b/zellij-utils/assets/plugins/mobile.wasm
new file mode 100755
index 00000000..d31d600e
--- /dev/null
+++ b/zellij-utils/assets/plugins/mobile.wasm
Binary files differ
diff --git a/zellij-utils/assets/prost/api.action.rs b/zellij-utils/assets/prost/api.action.rs
index 17bdc9c0..8d331b60 100644
--- a/zellij-utils/assets/prost/api.action.rs
+++ b/zellij-utils/assets/prost/api.action.rs
@@ -793,6 +793,10 @@ pub struct MouseEventPayload {
pub line: i64,
#[prost(int64, tag="11")]
pub column: i64,
+ #[prost(bool, tag="12")]
+ pub wheel_left: bool,
+ #[prost(bool, tag="13")]
+ pub wheel_right: bool,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
diff --git a/zellij-utils/assets/prost/api.event.rs b/zellij-utils/assets/prost/api.event.rs
index 38976384..eadb9394 100644
--- a/zellij-utils/assets/prost/api.event.rs
+++ b/zellij-utils/assets/prost/api.event.rs
@@ -9,7 +9,7 @@ pub struct EventNameList {
pub struct Event {
#[prost(enumeration="EventType", tag="1")]
pub name: i32,
- #[prost(oneof="event::Payload", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40")]
+ #[prost(oneof="event::Payload", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41")]
pub payload: ::core::option::Option<event::Payload>,
}
/// Nested message and enum types in `Event`.
@@ -95,6 +95,8 @@ pub mod event {
CommandChangedPayload(super::CommandChangedPayload),
#[prost(message, tag="40")]
HostTerminalThemeChangedPayload(super::HostTerminalThemeChangedPayload),
+ #[prost(message, tag="41")]
+ SoftKeyboardVisibilityChangedPayload(super::SoftKeyboardVisibilityChangedPayload),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
@@ -105,6 +107,12 @@ pub struct HostTerminalThemeChangedPayload {
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct SoftKeyboardVisibilityChangedPayload {
+ #[prost(bool, tag="1")]
+ pub visible: bool,
+}
+#[allow(clippy::derive_partial_eq_without_eq)]
+#[derive(Clone, PartialEq, ::prost::Message)]
pub struct CwdChangedPayload {
#[prost(message, optional, tag="1")]
pub pane_id: ::core::option::Option<PaneId>,
@@ -703,6 +711,8 @@ pub struct PaneContents {
pub lines_above_viewport: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
#[prost(string, repeated, tag="4")]
pub lines_below_viewport: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
+ #[prost(message, optional, tag="5")]
+ pub cursor: ::core::option::Option<super::action::Position>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
@@ -839,6 +849,7 @@ pub enum EventType {
InitialKeybinds = 44,
CommandChanged = 45,
HostTerminalThemeChanged = 46,
+ SoftKeyboardVisibilityChanged = 47,
}
impl EventType {
/// String value of the enum field names used in the ProtoBuf definition.
@@ -893,6 +904,7 @@ impl EventType {
EventType::InitialKeybinds => "InitialKeybinds",
EventType::CommandChanged => "CommandChanged",
EventType::HostTerminalThemeChanged => "HostTerminalThemeChanged",
+ EventType::SoftKeyboardVisibilityChanged => "SoftKeyboardVisibilityChanged",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
@@ -944,6 +956,7 @@ impl EventType {
"InitialKeybinds" => Some(Self::InitialKeybinds),
"CommandChanged" => Some(Self::CommandChanged),
"HostTerminalThemeChanged" => Some(Self::HostTerminalThemeChanged),
+ "SoftKeyboardVisibilityChanged" => Some(Self::SoftKeyboardVisibilityChanged),
_ => None,
}
}
@@ -1069,6 +1082,8 @@ pub enum MouseEventName {
MouseHold = 4,
MouseRelease = 5,
MouseHover = 6,
+ MouseScrollLeft = 7,
+ MouseScrollRight = 8,
}
impl MouseEventName {
/// String value of the enum field names used in the ProtoBuf definition.
@@ -1084,6 +1099,8 @@ impl MouseEventName {
MouseEventName::MouseHold => "MouseHold",
MouseEventName::MouseRelease => "MouseRelease",
MouseEventName::MouseHover => "MouseHover",
+ MouseEventName::MouseScrollLeft => "MouseScrollLeft",
+ MouseEventName::MouseScrollRight => "MouseScrollRight",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
@@ -1096,6 +1113,8 @@ impl MouseEventName {
"MouseHold" => Some(Self::MouseHold),
"MouseRelease" => Some(Self::MouseRelease),
"MouseHover" => Some(Self::MouseHover),
+ "MouseScrollLeft" => Some(Self::MouseScrollLeft),
+ "MouseScrollRight" => Some(Self::MouseScrollRight),
_ => None,
}
}
diff --git a/zellij-utils/assets/prost/api.plugin_command.rs b/zellij-utils/assets/prost/api.plugin_command.rs
index 1cb4e5d0..b43bd004 100644
--- a/zellij-utils/assets/prost/api.plugin_command.rs
+++ b/zellij-utils/assets/prost/api.plugin_command.rs
@@ -3,7 +3,7 @@
pub struct PluginCommand {
#[prost(enumeration="CommandName", tag="1")]
pub name: i32,
- #[prost(oneof="plugin_command::Payload", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163")]
+ #[prost(oneof="plugin_command::Payload", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 167, 168, 169")]
pub payload: ::core::option::Option<plugin_command::Payload>,
}
/// Nested message and enum types in `PluginCommand`.
@@ -309,6 +309,16 @@ pub mod plugin_command {
KillSessionsAndReplyPayload(super::KillSessionsPayload),
#[prost(string, tag="163")]
DeleteDeadSessionAndReplyPayload(::prost::alloc::string::String),
+ #[prost(message, tag="164")]
+ SetSoftKeyboardPayload(super::SetSoftKeyboardPayload),
+ #[prost(message, tag="165")]
+ SetTabFitPayload(super::SetTabFitPayload),
+ #[prost(message, tag="167")]
+ SetShadowFocusPayload(super::PaneId),
+ #[prost(message, tag="168")]
+ NewTabUnfocusedPayload(super::NewTabUnfocusedPayload),
+ #[prost(message, tag="169")]
+ NewTiledPaneInTabPayload(super::NewTiledPaneInTabPayload),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
@@ -321,6 +331,20 @@ pub struct NewTabPayload {
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct NewTabUnfocusedPayload {
+ #[prost(string, optional, tag="1")]
+ pub name: ::core::option::Option<::prost::alloc::string::String>,
+ #[prost(string, optional, tag="2")]
+ pub cwd: ::core::option::Option<::prost::alloc::string::String>,
+}
+#[allow(clippy::derive_partial_eq_without_eq)]
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct NewTiledPaneInTabPayload {
+ #[prost(uint32, tag="1")]
+ pub tab_position: u32,
+}
+#[allow(clippy::derive_partial_eq_without_eq)]
+#[derive(Clone, PartialEq, ::prost::Message)]
pub struct DumpSessionLayoutPayload {
#[prost(uint32, optional, tag="1")]
pub tab_index: ::core::option::Option<u32>,
@@ -1483,6 +1507,29 @@ pub mod new_tab_response {
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct NewTabUnfocusedResponse {
+ #[prost(oneof="new_tab_unfocused_response::Result", tags="1, 2")]
+ pub result: ::core::option::Option<new_tab_unfocused_response::Result>,
+}
+/// Nested message and enum types in `NewTabUnfocusedResponse`.
+pub mod new_tab_unfocused_response {
+ #[allow(clippy::derive_partial_eq_without_eq)]
+#[derive(Clone, PartialEq, ::prost::Oneof)]
+ pub enum Result {
+ #[prost(uint64, tag="1")]
+ TabId(u64),
+ #[prost(bool, tag="2")]
+ None(bool),
+ }
+}
+#[allow(clippy::derive_partial_eq_without_eq)]
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct NewTiledPaneInTabResponse {
+ #[prost(message, optional, tag="1")]
+ pub pane_id: ::core::option::Option<PaneId>,
+}
+#[allow(clippy::derive_partial_eq_without_eq)]
+#[derive(Clone, PartialEq, ::prost::Message)]
pub struct NewTabsResponse {
/// Only includes successfully created tabs
#[prost(uint64, repeated, tag="1")]
@@ -1955,6 +2002,30 @@ pub struct DeleteAllDeadSessionsResponse {
#[prost(string, optional, tag="1")]
pub error: ::core::option::Option<::prost::alloc::string::String>,
}
+#[allow(clippy::derive_partial_eq_without_eq)]
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct SetSoftKeyboardPayload {
+ #[prost(bool, tag="1")]
+ pub on: bool,
+}
+#[allow(clippy::derive_partial_eq_without_eq)]
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct Size {
+ #[prost(uint32, tag="1")]
+ pub rows: u32,
+ #[prost(uint32, tag="2")]
+ pub cols: u32,
+}
+#[allow(clippy::derive_partial_eq_without_eq)]
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct SetTabFitPayload {
+ #[prost(uint32, tag="1")]
+ pub tab_id: u32,
+ #[prost(message, optional, tag="2")]
+ pub pane_id: ::core::option::Option<PaneId>,
+ #[prost(message, optional, tag="3")]
+ pub size: ::core::option::Option<Size>,
+}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum CommandName {
@@ -2152,6 +2223,12 @@ pub enum CommandName {
KillSessionsAndReply = 212,
DeleteDeadSessionAndReply = 213,
DeleteAllDeadSessionsAndReply = 214,
+ SetSoftKeyboard = 215,
+ SetTabFit = 216,
+ SetShadowFocus = 219,
+ NewTabUnfocused = 220,
+ NewTiledPaneInTab = 221,
+ ExitMobileMode = 222,
}
impl CommandName {
/// String value of the enum field names used in the ProtoBuf definition.
@@ -2354,6 +2431,12 @@ impl CommandName {
CommandName::KillSessionsAndReply => "KillSessionsAndReply",
CommandName::DeleteDeadSessionAndReply => "DeleteDeadSessionAndReply",
CommandName::DeleteAllDeadSessionsAndReply => "DeleteAllDeadSessionsAndReply",
+ CommandName::SetSoftKeyboard => "SetSoftKeyboard",
+ CommandName::SetTabFit => "SetTabFit",
+ CommandName::SetShadowFocus => "SetShadowFocus",
+ CommandName::NewTabUnfocused => "NewTabUnfocused",
+ CommandName::NewTiledPaneInTab => "NewTiledPaneInTab",
+ CommandName::ExitMobileMode => "ExitMobileMode",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
@@ -2553,6 +2636,12 @@ impl CommandName {
"KillSessionsAndReply" => Some(Self::KillSessionsAndReply),
"DeleteDeadSessionAndReply" => Some(Self::DeleteDeadSessionAndReply),
"DeleteAllDeadSessionsAndReply" => Some(Self::DeleteAllDeadSessionsAndReply),
+ "SetSoftKeyboard" => Some(Self::SetSoftKeyboard),
+ "SetTabFit" => Some(Self::SetTabFit),
+ "SetShadowFocus" => Some(Self::SetShadowFocus),
+ "NewTabUnfocused" => Some(Self::NewTabUnfocused),
+ "NewTiledPaneInTab" => Some(Self::NewTiledPaneInTab),
+ "ExitMobileMode" => Some(Self::ExitMobileMode),
_ => None,
}
}
diff --git a/zellij-utils/assets/prost_ipc/client_server_contract.rs b/zellij-utils/assets/prost_ipc/client_server_contract.rs
index 142609f7..4ef94968 100644
--- a/zellij-utils/assets/prost_ipc/client_server_contract.rs
+++ b/zellij-utils/assets/prost_ipc/client_server_contract.rs
@@ -119,7 +119,7 @@ pub struct RgbColor {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Action {
- #[prost(oneof="action::ActionType", tags="1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139")]
+ #[prost(oneof="action::ActionType", tags="1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140")]
pub action_type: ::core::option::Option<action::ActionType>,
}
/// Nested message and enum types in `Action`.
@@ -407,6 +407,8 @@ pub mod action {
SetLightTheme(super::SetLightThemeAction),
#[prost(message, tag="139")]
ToggleTheme(super::ToggleThemeAction),
+ #[prost(message, tag="140")]
+ ToggleMobileMode(super::ToggleMobileModeAction),
}
}
// Action message definitions (all 92 variants)
@@ -586,6 +588,10 @@ pub struct ToggleMouseModeAction {
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct ToggleMobileModeAction {
+}
+#[allow(clippy::derive_partial_eq_without_eq)]
+#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PreviousSwapLayoutAction {
}
#[allow(clippy::derive_partial_eq_without_eq)]
@@ -1706,6 +1712,10 @@ pub struct MouseEvent {
pub ctrl: bool,
#[prost(message, optional, tag="10")]
pub position: ::core::option::Option<Position>,
+ #[prost(bool, tag="11")]
+ pub wheel_left: bool,
+ #[prost(bool, tag="12")]
+ pub wheel_right: bool,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
@@ -1949,6 +1959,12 @@ pub struct Options {
pub theme_dark: ::core::option::Option<::prost::alloc::string::String>,
#[prost(string, optional, tag="47")]
pub theme_light: ::core::option::Option<::prost::alloc::string::String>,
+ #[prost(enumeration="MobileLayout", optional, tag="48")]
+ pub mobile_layout: ::core::option::Option<i32>,
+ #[prost(uint32, optional, tag="49")]
+ pub mobile_threshold_cols: ::core::option::Option<u32>,
+ #[prost(uint32, optional, tag="50")]
+ pub mobile_threshold_rows: ::core::option::Option<u32>,
}
/// Pane-targeting action messages
#[allow(clippy::derive_partial_eq_without_eq)]
@@ -2876,10 +2892,42 @@ impl WebSharing {
}
}
}
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
+#[repr(i32)]
+pub enum MobileLayout {
+ Unspecified = 0,
+ Web = 1,
+ Always = 2,
+ Never = 3,
+}
+impl MobileLayout {
+ /// String value of the enum field names used in the ProtoBuf definition.
+ ///
+ /// The values are not transformed in any way and thus are considered stable
+ /// (if the ProtoBuf definition does not change) and safe for programmatic use.
+ pub fn as_str_name(&self) -> &'static str {
+ match self {
+ MobileLayout::Unspecified => "MOBILE_LAYOUT_UNSPECIFIED",
+ MobileLayout::Web => "MOBILE_LAYOUT_WEB",
+ MobileLayout::Always => "MOBILE_LAYOUT_ALWAYS",
+ MobileLayout::Never => "MOBILE_LAYOUT_NEVER",
+ }
+ }
+ /// Creates an enum from field names used in the ProtoBuf definition.
+ pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
+ match value {
+ "MOBILE_LAYOUT_UNSPECIFIED" => Some(Self::Unspecified),
+ "MOBILE_LAYOUT_WEB" => Some(Self::Web),
+ "MOBILE_LAYOUT_ALWAYS" => Some(Self::Always),
+ "MOBILE_LAYOUT_NEVER" => Some(Self::Never),
+ _ => None,
+ }
+ }
+}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ClientToServerMsg {
- #[prost(oneof="client_to_server_msg::Message", tags="1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20")]
+ #[prost(oneof="client_to_server_msg::Message", tags="1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21")]
pub message: ::core::option::Option<client_to_server_msg::Message>,
}
/// Nested message and enum types in `ClientToServerMsg`.
@@ -2927,6 +2975,8 @@ pub mod client_to_server_msg {
ForwardedReplyFromHost(super::ForwardedReplyFromHostMsg),
#[prost(message, tag="20")]
HostTerminalThemeChanged(super::HostTerminalThemeChangedMsg),
+ #[prost(message, tag="21")]
+ SoftKeyboardVisibilityChanged(super::SoftKeyboardVisibilityChangedMsg),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
@@ -2964,6 +3014,8 @@ pub struct ColorRegistersMsg {
pub struct TerminalResizeMsg {
#[prost(message, optional, tag="1")]
pub new_size: ::core::option::Option<Size>,
+ #[prost(enumeration="ResizeCause", tag="2")]
+ pub cause: i32,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
@@ -3072,6 +3124,41 @@ pub struct HostTerminalThemeChangedMsg {
#[prost(enumeration="HostTerminalThemeIndication", tag="1")]
pub mode: i32,
}
+#[allow(clippy::derive_partial_eq_without_eq)]
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct SoftKeyboardVisibilityChangedMsg {
+ #[prost(bool, tag="1")]
+ pub visible: bool,
+}
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
+#[repr(i32)]
+pub enum ResizeCause {
+ Viewport = 0,
+ RenderingPreference = 1,
+ SizeSettled = 2,
+}
+impl ResizeCause {
+ /// String value of the enum field names used in the ProtoBuf definition.
+ ///
+ /// The values are not transformed in any way and thus are considered stable
+ /// (if the ProtoBuf definition does not change) and safe for programmatic use.
+ pub fn as_str_name(&self) -> &'static str {
+ match self {
+ ResizeCause::Viewport => "RESIZE_CAUSE_VIEWPORT",
+ ResizeCause::RenderingPreference => "RESIZE_CAUSE_RENDERING_PREFERENCE",
+ ResizeCause::SizeSettled => "RESIZE_CAUSE_SIZE_SETTLED",
+ }
+ }
+ /// Creates an enum from field names used in the ProtoBuf definition.
+ pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
+ match value {
+ "RESIZE_CAUSE_VIEWPORT" => Some(Self::Viewport),
+ "RESIZE_CAUSE_RENDERING_PREFERENCE" => Some(Self::RenderingPreference),
+ "RESIZE_CAUSE_SIZE_SETTLED" => Some(Self::SizeSettled),
+ _ => None,
+ }
+ }
+}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum HostTerminalThemeIndication {
@@ -3101,7 +3188,7 @@ impl HostTerminalThemeIndication {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ServerToClientMsg {
- #[prost(oneof="server_to_client_msg::Message", tags="1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16")]
+ #[prost(oneof="server_to_client_msg::Message", tags="1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17")]
pub message: ::core::option::Option<server_to_client_msg::Message>,
}
/// Nested message and enum types in `ServerToClientMsg`.
@@ -3141,6 +3228,8 @@ pub mod server_to_client_msg {
SubscribedPaneClosed(super::SubscribedPaneClosedMsg),
#[prost(message, tag="16")]
ForwardQueryToHost(super::ForwardQueryToHostMsg),
+ #[prost(message, tag="17")]
+ SetSoftKeyboard(super::SetSoftKeyboardMsg),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
@@ -3248,3 +3337,9 @@ pub struct ForwardQueryToHostMsg {
#[prost(bytes="vec", tag="2")]
pub query_bytes: ::prost::alloc::vec::Vec<u8>,
}
+#[allow(clippy::derive_partial_eq_without_eq)]
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct SetSoftKeyboardMsg {
+ #[prost(bool, tag="1")]
+ pub on: bool,
+}
diff --git a/zellij-utils/src/client_server_contract/client_to_server.proto b/zellij-utils/src/client_server_contract/client_to_server.proto
index 834feccd..bc7c0c06 100644
--- a/zellij-utils/src/client_server_contract/client_to_server.proto
+++ b/zellij-utils/src/client_server_contract/client_to_server.proto
@@ -25,6 +25,7 @@ message ClientToServerMsg {
DesktopNotificationResponseMsg desktop_notification_response = 18;
ForwardedReplyFromHostMsg forwarded_reply_from_host = 19;
HostTerminalThemeChangedMsg host_terminal_theme_changed = 20;
+ SoftKeyboardVisibilityChangedMsg soft_keyboard_visibility_changed = 21;
}
}
@@ -50,6 +51,13 @@ message ColorRegistersMsg {
message TerminalResizeMsg {
Size new_size = 1;
+ ResizeCause cause = 2;
+}
+
+enum ResizeCause {
+ RESIZE_CAUSE_VIEWPORT = 0;
+ RESIZE_CAUSE_RENDERING_PREFERENCE = 1;
+ RESIZE_CAUSE_SIZE_SETTLED = 2;
}
message FirstClientConnectedMsg {
@@ -125,3 +133,7 @@ enum HostTerminalThemeIndication {
HOST_TERMINAL_THEME_INDICATION_DARK = 0;
HOST_TERMINAL_THEME_INDICATION_LIGHT = 1;
}
+
+message SoftKeyboardVisibilityChangedMsg {
+ bool visible = 1;
+}
diff --git a/zellij-utils/src/client_server_contract/common_types.proto b/zellij-utils/src/client_server_contract/common_types.proto
index b592eb8d..1dfc3994 100644
--- a/zellij-utils/src/client_server_contract/common_types.proto
+++ b/zellij-utils/src/client_server_contract/common_types.proto
@@ -277,6 +277,7 @@ message Action {
SetDarkThemeAction set_dark_theme = 137;
SetLightThemeAction set_light_theme = 138;
ToggleThemeAction toggle_theme = 139;
+ ToggleMobileModeAction toggle_mobile_mode = 140;
}
}
@@ -332,6 +333,7 @@ message CopyAction {}
message ConfirmAction {}
message DenyAction {}
message ToggleMouseModeAction {}
+message ToggleMobileModeAction {}
message PreviousSwapLayoutAction {}
message NextSwapLayoutAction {}
message TabLayoutInfo {
@@ -1012,6 +1014,8 @@ message MouseEvent {
bool alt = 8;
bool ctrl = 9;
Position position = 10;
+ bool wheel_left = 11;
+ bool wheel_right = 12;
}
enum MouseEventType {
@@ -1174,6 +1178,9 @@ message Options {
optional bool mouse_click_through = 45;
optional string theme_dark = 46;
optional string theme_light = 47;
+ optional MobileLayout mobile_layout = 48;
+ optional uint32 mobile_threshold_cols = 49;
+ optional uint32 mobile_threshold_rows = 50;
}
enum OnForceClose {
@@ -1201,6 +1208,13 @@ enum WebSharing {
WEB_SHARING_DISABLED = 3;
}
+enum MobileLayout {
+ MOBILE_LAYOUT_UNSPECIFIED = 0;
+ MOBILE_LAYOUT_WEB = 1;
+ MOBILE_LAYOUT_ALWAYS = 2;
+ MOBILE_LAYOUT_NEVER = 3;
+}
+
// Pane-targeting action messages
message ScrollUpByPaneIdAction { PaneId pane_id = 1; }
message ScrollDownByPaneIdAction { PaneId pane_id = 1; }
diff --git a/zellij-utils/src/client_server_contract/server_to_client.proto b/zellij-utils/src/client_server_contract/server_to_client.proto
index b2fc3bd6..a8c4779d 100644
--- a/zellij-utils/src/client_server_contract/server_to_client.proto
+++ b/zellij-utils/src/client_server_contract/server_to_client.proto
@@ -21,6 +21,7 @@ message ServerToClientMsg {
PaneRenderUpdateMsg pane_render_update = 14;
SubscribedPaneClosedMsg subscribed_pane_closed = 15;
ForwardQueryToHostMsg forward_query_to_host = 16;
+ SetSoftKeyboardMsg set_soft_keyboard = 17;
}
}
@@ -94,3 +95,7 @@ message ForwardQueryToHostMsg {
uint32 token = 1;
bytes query_bytes = 2;
}
+
+message SetSoftKeyboardMsg {
+ bool on = 1;
+}
diff --git a/zellij-utils/src/consts.rs b/zellij-utils/src/consts.rs
index 0e8b5112..2b3982b0 100644
--- a/zellij-utils/src/consts.rs
+++ b/zellij-utils/src/consts.rs
@@ -171,16 +171,12 @@ mod not_wasm {
add_plugin!(assets, "multiple-select.wasm");
add_plugin!(assets, "layout-manager.wasm");
add_plugin!(assets, "link.wasm");
+ add_plugin!(assets, "mobile.wasm");
assets
};
}
}
-/// Check if a filesystem entry is an IPC socket.
-///
-/// On Unix, this checks `FileTypeExt::is_socket()`. On non-Unix platforms,
-/// this checks `is_file()` to detect marker files created by `ipc_bind()`
-/// and `ipc_bind_async()` alongside kernel-level named pipes.
#[cfg(unix)]
pub fn is_ipc_socket(file_type: &std::fs::FileType) -> bool {
use std::os::unix::fs::FileTypeExt;
@@ -192,10 +188,6 @@ pub fn is_ipc_socket(file_type: &std::fs::FileType) -> bool {
file_type.is_file()
}
-/// Connect to an IPC socket at the given path.
-///
-/// On Unix, this uses Unix domain sockets via `GenericFilePath`.
-/// On Windows, this uses named pipes via `GenericNamespaced`.
#[cfg(unix)]
pub fn ipc_connect(path: &std::path::Path) -> std::io::Result<interprocess::local_socket::Stream> {
use interprocess::local_socket::{prelude::*, GenericFilePath, Stream as LocalSocketStream};
@@ -211,11 +203,6 @@ pub fn ipc_connect(path: &std::path::Path) -> std::io::Result<interprocess::loca
LocalSocketStream::connect(ns_name)
}
-/// Create an IPC listener bound to the given path.
-///
-/// On Unix, this uses Unix domain sockets via `GenericFilePath`.
-/// On Windows, this uses named pipes via `GenericNamespaced` and creates
-/// a marker file for session discovery.
#[cfg(unix)]
pub fn ipc_bind(path: &std::path::Path) -> std::io::Result<interprocess::local_socket::Listener> {
use interprocess::local_socket::{prelude::*, GenericFilePath, ListenerOptions};
@@ -233,11 +220,6 @@ pub fn ipc_bind(path: &std::path::Path) -> std::io::Result<interprocess::local_s
Ok(listener)
}
-/// Create an async (tokio) IPC listener bound to the given path.
-///
-/// On Unix, this uses Unix domain sockets via `GenericFilePath`.
-/// On Windows, this uses named pipes via `GenericNamespaced` and creates
-/// a marker file for session discovery.
#[cfg(unix)]
pub fn ipc_bind_async(
path: &std::path::Path,
@@ -259,9 +241,6 @@ pub fn ipc_bind_async(
Ok(listener)
}
-/// Connect to the reply pipe for a given IPC path (Windows only).
-///
-/// Uses `path-reply` as the named pipe for the server→client direction.
#[cfg(windows)]
pub fn ipc_connect_reply(
path: &std::path::Path,
@@ -272,9 +251,6 @@ pub fn ipc_connect_reply(
LocalSocketStream::connect(ns_name)
}
-/// Create an IPC listener for the reply pipe (Windows only).
-///
-/// Binds to `path-reply` as the named pipe for the server→client direction.
#[cfg(windows)]
pub fn ipc_bind_reply(
path: &std::path::Path,
@@ -297,12 +273,7 @@ mod unix_only {
use nix::unistd::Uid;
use std::env::temp_dir;
- // Maximum length of a Unix domain socket path (from sockaddr_un.sun_path).
- // macOS (and other BSDs) use 104, Linux/Android/Solaris use 108.
- // The not(target_os = "macos") fallback of 108 is used for all other Unix
- // platforms — this is correct for Linux/Android/Solaris and only 4 bytes
- // over for BSDs, which would cause a slightly late error rather than a
- // missed one.
+ // Maximum sockaddr_un.sun_path length: 104 on macOS/BSD, 108 on Linux/Android/Solaris.
#[cfg(target_os = "macos")]
pub const ZELLIJ_SOCK_MAX_LENGTH: usize = 104;
#[cfg(not(target_os = "macos"))]
diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs
index 6f10a054..16ff560e 100644
--- a/zellij-utils/src/data.rs
+++ b/zellij-utils/src/data.rs
@@ -5,7 +5,7 @@ use crate::input::keybinds::Keybinds;
use crate::input::layout::{
Layout, PercentOrFixed, Run, RunPlugin, RunPluginLocation, RunPluginOrAlias,
};
-use crate::pane_size::PaneGeom;
+use crate::pane_size::{PaneGeom, Size};
use crate::position::Position;
use crate::shared::{colors as default_colors, eightbit_to_rgb};
use clap::ArgEnum;
@@ -894,8 +894,10 @@ impl fmt::Display for ResizeStrategy {
// left click) and the `ScrollUp` and `ScrollDown` events could probably be
// merged into a single `Scroll(isize)` event.
pub enum Mouse {
- ScrollUp(usize), // number of lines
- ScrollDown(usize), // number of lines
+ ScrollUp(usize), // number of lines
+ ScrollDown(usize), // number of lines
+ ScrollLeft(usize),
+ ScrollRight(usize),
LeftClick(isize, usize), // line and column
RightClick(isize, usize), // line and column
Hold(isize, usize), // line and column
@@ -1028,6 +1030,7 @@ pub enum Event {
InitialKeybinds(KeybindsVec),
/// The host terminal indicated its color palette theme mode (CSI 2031 / DSR 997).
HostTerminalThemeChanged(HostTerminalThemeMode),
+ SoftKeyboardVisibilityChanged(bool),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
@@ -2432,6 +2435,7 @@ pub struct PaneContents {
pub lines_below_viewport: Vec<String>,
pub viewport: Vec<String>,
pub selected_text: Option<SelectedText>,
+ pub cursor: Option<(usize, usize)>,
}
/// Extract text from a line between two column positions, accounting for wide characters
@@ -2526,6 +2530,7 @@ impl PaneContents {
selected_text: SelectedText::from_positions(selection_start, selection_end),
lines_above_viewport,
lines_below_viewport,
+ cursor: None,
}
}
@@ -3366,6 +3371,13 @@ pub enum PluginCommand {
name: Option<String>,
cwd: Option<String>,
},
+ NewTabUnfocused {
+ name: Option<String>,
+ cwd: Option<String>,
+ },
+ NewTiledPaneInTab {
+ tab_position: usize,
+ },
GoToNextTab,
GoToPreviousTab,
Resize(Resize),
@@ -3610,6 +3622,13 @@ pub enum PluginCommand {
KillSessionsAndReply(Vec<String>), // one or more session names; sends a response back
DeleteDeadSessionAndReply(String), // session name; sends a response back
DeleteAllDeadSessionsAndReply, // no payload; sends a response back
+ SetSoftKeyboard(bool),
+ SetTabFit {
+ tab_id: usize,
+ fit: Option<(PaneId, Size)>,
+ },
+ SetShadowFocus(PaneId),
+ ExitMobileMode,
}
// Response type for plugin API methods that open a pane in a new tab
@@ -3621,6 +3640,7 @@ pub struct OpenPaneInNewTabResponse {
// Response types for plugin API methods that create tabs
pub type NewTabResponse = Option<usize>;
+pub type NewTabUnfocusedResponse = Option<usize>;
pub type NewTabsResponse = Vec<usize>;
pub type FocusOrCreateTabResponse = Option<usize>;
pub type BreakPanesToNewTabResponse = Option<usize>;
@@ -3641,6 +3661,7 @@ pub type OpenTerminalInPlaceResponse = Option<PaneId>;
pub type OpenTerminalNearPluginResponse = Option<PaneId>;
pub type OpenTerminalFloatingNearPluginResponse = Option<PaneId>;
pub type OpenTerminalInPlaceOfPluginResponse = Option<PaneId>;
+pub type NewTiledPaneInTabResponse = Option<PaneId>;
pub type OpenCommandPaneResponse = Option<PaneId>;
pub type OpenCommandPaneFloatingResponse = Option<PaneId>;
diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs
index 6fa15141..8e4bc66c 100644
--- a/zellij-utils/src/errors.rs
+++ b/zellij-utils/src/errors.rs
@@ -331,6 +331,9 @@ pub enum ScreenContext {
ToggleTab,
AddClient,
RemoveClient,
+ SuppressRenderUntilMobile,
+ MobileSizeSettled,
+ ForceMobileUngate,
UpdateSearch,
SearchDown,
SearchUp,
@@ -399,6 +402,7 @@ pub enum ScreenContext {
PageScrollUpInPaneId,
PageScrollDownInPaneId,
TogglePaneIdFullscreen,
+ SetTabFit,
TogglePaneEmbedOrEjectForPaneId,
CloseTabWithIndex,
BreakPanesToNewTab,
@@ -462,6 +466,12 @@ pub enum ScreenContext {
PluginSubscribedToAnsiPaneContents,
UpdateBackgroundPluginSubscriptions,
BroadcastModeUpdate,
+ EnterMobileMode,
+ ExitMobileMode,
+ ToggleMobileMode,
+ ReevaluateMobileMode,
+ SetSoftKeyboard,
+ SetShadowFocus,
}
/// Stack call representations corresponding to the different types of [`PtyInstruction`]s.
@@ -516,6 +526,8 @@ pub enum PluginContext {
NewTab,
OverrideLayout,
ApplyCachedEvents,
+ HoldMobileRender,
+ ReleaseMobileRender,
ApplyCachedWorkerMessages,
PostMessageToPluginWorker,
PostMessageToPlugin,
@@ -637,6 +649,7 @@ pub enum BackgroundJobContext {
StopFlashPaneBell,
FlashTabBell,
StopFlashTabBell,
+ MobileGateTimeout,
Exit,
}
diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs
index 7f6fabfe..cf5fc448 100644
--- a/zellij-utils/src/input/actions.rs
+++ b/zellij-utils/src/input/actions.rs
@@ -412,6 +412,7 @@ pub enum Action {
option: SearchOption,
},
ToggleMouseMode,
+ ToggleMobileMode,
PreviousSwapLayout,
NextSwapLayout,
/// Override the layout of the active tab
diff --git a/zellij-utils/src/input/mod.rs b/zellij-utils/src/input/mod.rs
index 74091850..c103e580 100644
--- a/zellij-utils/src/input/mod.rs
+++ b/zellij-utils/src/input/mod.rs
@@ -163,112 +163,149 @@ mod not_wasm {
let alt = modifiers.contains(KeyModifiers::ALT);
let ctrl = modifiers.contains(KeyModifiers::CONTROL);
- let (event_type, left, right, middle, wheel_up, wheel_down) = match event.kind {
- MouseEventKind::Down(CButton::Left) => (
- mouse::MouseEventType::Press,
- true,
- false,
- false,
- false,
- false,
- ),
- MouseEventKind::Down(CButton::Right) => (
- mouse::MouseEventType::Press,
- false,
- true,
- false,
- false,
- false,
- ),
- MouseEventKind::Down(CButton::Middle) => (
- mouse::MouseEventType::Press,
- false,
- false,
- true,
- false,
- false,
- ),
- MouseEventKind::Up(CButton::Left) => (
- mouse::MouseEventType::Release,
- true,
- false,
- false,
- false,
- false,
- ),
- MouseEventKind::Up(CButton::Right) => (
- mouse::MouseEventType::Release,
- false,
- true,
- false,
- false,
- false,
- ),
- MouseEventKind::Up(CButton::Middle) => (
- mouse::MouseEventType::Release,
- false,
- false,
- true,
- false,
- false,
- ),
- MouseEventKind::Drag(CButton::Left) => (
- mouse::MouseEventType::Motion,
- true,
- false,
- false,
- false,
- false,
- ),
- MouseEventKind::Drag(CButton::Right) => (
- mouse::MouseEventType::Motion,
- false,
- true,
- false,
- false,
- false,
- ),
- MouseEventKind::Drag(CButton::Middle) => (
- mouse::MouseEventType::Motion,
- false,
- false,
- true,
- false,
- false,
- ),
- MouseEventKind::Moved => (
- mouse::MouseEventType::Motion,
- false,
- false,
- false,
- false,
- false,
- ),
- MouseEventKind::ScrollUp => (
- mouse::MouseEventType::Press,
- false,
- false,
- false,
- true,
- false,
- ),
- MouseEventKind::ScrollDown => (
- mouse::MouseEventType::Press,
- false,
- false,
- false,
- false,
- true,
- ),
- MouseEventKind::ScrollLeft | MouseEventKind::ScrollRight => (
- mouse::MouseEventType::Motion,
- false,
- false,
- false,
- false,
- false,
- ),
- };
+ let (event_type, left, right, middle, wheel_up, wheel_down, wheel_left, wheel_right) =
+ match event.kind {
+ MouseEventKind::Down(CButton::Left) => (
+ mouse::MouseEventType::Press,
+ true,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ ),
+ MouseEventKind::Down(CButton::Right) => (
+ mouse::MouseEventType::Press,
+ false,
+ true,
+ false,
+ false,
+ false,
+ false,
+ false,
+ ),
+ MouseEventKind::Down(CButton::Middle) => (
+ mouse::MouseEventType::Press,
+ false,
+ false,
+ true,
+ false,
+ false,
+ false,
+ false,
+ ),
+ MouseEventKind::Up(CButton::Left) => (
+ mouse::MouseEventType::Release,
+ true,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ ),
+ MouseEventKind::Up(CButton::Right) => (
+ mouse::MouseEventType::Release,
+ false,
+ true,
+ false,
+ false,
+ false,
+ false,
+ false,
+ ),
+ MouseEventKind::Up(CButton::Middle) => (
+ mouse::MouseEventType::Release,
+ false,
+ false,
+ true,
+ false,
+ false,
+ false,
+ false,
+ ),
+ MouseEventKind::Drag(CButton::Left) => (
+ mouse::MouseEventType::Motion,
+ true,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ ),
+ MouseEventKind::Drag(CButton::Right) => (
+ mouse::MouseEventType::Motion,
+ false,
+ true,
+ false,
+ false,
+ false,
+ false,
+ false,
+ ),
+ MouseEventKind::Drag(CButton::Middle) => (
+ mouse::MouseEventType::Motion,
+ false,
+ false,
+ true,
+ false,
+ false,
+ false,
+ false,
+ ),
+ MouseEventKind::Moved => (
+ mouse::MouseEventType::Motion,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ ),
+ MouseEventKind::ScrollUp => (
+ mouse::MouseEventType::Press,
+ false,
+ false,
+ false,
+ true,
+ false,
+ false,
+ false,
+ ),
+ MouseEventKind::ScrollDown => (
+ mouse::MouseEventType::Press,
+ false,
+ false,
+ false,
+ false,
+ true,
+ false,
+ false,
+ ),
+ MouseEventKind::ScrollLeft => (
+ mouse::MouseEventType::Press,
+ false,
+ false,
+ false,
+ false,
+ false,
+ true,
+ false,
+ ),
+ MouseEventKind::ScrollRight => (
+ mouse::MouseEventType::Press,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ true,
+ ),
+ };
mouse::MouseEvent {
event_type,
@@ -277,6 +314,8 @@ mod not_wasm {
middle,
wheel_up,
wheel_down,
+ wheel_left,
+ wheel_right,
shift,
alt,
ctrl,
diff --git a/zellij-utils/src/input/mouse.rs b/zellij-utils/src/input/mouse.rs
index a8fcb5a7..231dc589 100644
--- a/zellij-utils/src/input/mouse.rs
+++ b/zellij-utils/src/input/mouse.rs
@@ -16,6 +16,10 @@ pub struct MouseEvent {
pub middle: bool,
pub wheel_up: bool,
pub wheel_down: bool,
+ #[serde(default)]
+ pub wheel_left: bool,
+ #[serde(default)]
+ pub wheel_right: bool,
// Keyboard modifier flags can be encoded with events too. They
// are not often passed on the wire (instead used for
@@ -51,6 +55,8 @@ impl MouseEvent {
middle: false,
wheel_up: false,
wheel_down: false,
+ wheel_left: false,
+ wheel_right: false,
shift: false,
alt: false,
ctrl: false,
@@ -66,6 +72,8 @@ impl MouseEvent {
middle: false,
wheel_up: false,
wheel_down: false,
+ wheel_left: false,
+ wheel_right: false,
shift: false,
alt: false,
ctrl: false,
@@ -81,6 +89,8 @@ impl MouseEvent {
middle: false,
wheel_up: false,
wheel_down: false,
+ wheel_left: false,
+ wheel_right: false,
shift: false,
alt: false,
ctrl: false,
@@ -96,6 +106,8 @@ impl MouseEvent {
middle: false,
wheel_up: false,
wheel_down: false,
+ wheel_left: false,
+ wheel_right: false,
shift: false,
alt: false,
ctrl: false,
@@ -111,6 +123,8 @@ impl MouseEvent {
middle: true,
wheel_up: false,
wheel_down: false,
+ wheel_left: false,
+ wheel_right: false,
shift: false,
alt: false,
ctrl: false,
@@ -126,6 +140,8 @@ impl MouseEvent {
middle: true,
wheel_up: false,
wheel_down: false,
+ wheel_left: false,
+ wheel_right: false,
shift: false,
alt: false,
ctrl: false,
@@ -141,6 +157,8 @@ impl MouseEvent {
middle: false,
wheel_up: false,
wheel_down: false,
+ wheel_left: false,
+ wheel_right: false,
shift: false,
alt: false,
ctrl: false,
@@ -156,6 +174,8 @@ impl MouseEvent {
middle: false,
wheel_up: false,
wheel_down: false,
+ wheel_left: false,
+ wheel_right: false,
shift: false,
alt: false,
ctrl: false,
@@ -171,6 +191,8 @@ impl MouseEvent {
middle: false,
wheel_up: false,
wheel_down: false,
+ wheel_left: false,
+ wheel_right: false,
shift: false,
alt: false,
ctrl: false,
@@ -186,6 +208,8 @@ impl MouseEvent {
middle: false,
wheel_up: false,
wheel_down: false,
+ wheel_left: false,
+ wheel_right: false,
shift: false,
alt: false,
ctrl: false,
@@ -201,6 +225,8 @@ impl MouseEvent {
middle: true,
wheel_up: false,
wheel_down: false,
+ wheel_left: false,
+ wheel_right: false,
shift: false,
alt: false,
ctrl: false,
@@ -216,6 +242,8 @@ impl MouseEvent {
middle: false,
wheel_up: false,
wheel_down: false,
+ wheel_left: false,
+ wheel_right: false,
shift: false,
alt: true,
ctrl: false,
@@ -231,6 +259,8 @@ impl MouseEvent {
middle: false,
wheel_up: false,
wheel_down: false,
+ wheel_left: false,
+ wheel_right: false,
shift: false,
alt: true,
ctrl: false,
@@ -246,6 +276,8 @@ impl MouseEvent {
middle: false,
wheel_up: false,
wheel_down: false,
+ wheel_left: false,
+ wheel_right: false,
shift: false,
alt: false,
ctrl: true,
@@ -261,6 +293,8 @@ impl MouseEvent {
middle: false,
wheel_up: false,
wheel_down: false,
+ wheel_left: false,
+ wheel_right: false,
shift: false,
alt: false,
ctrl: true,
@@ -276,6 +310,8 @@ impl MouseEvent {
middle: false,
wheel_up: false,
wheel_down: false,
+ wheel_left: false,
+ wheel_right: false,
shift: false,
alt: false,
ctrl: true,
@@ -291,6 +327,8 @@ impl MouseEvent {
middle: false,
wheel_up: false,
wheel_down: false,
+ wheel_left: false,
+ wheel_right: false,
shift: false,
alt: true,
ctrl: false,
@@ -306,6 +344,8 @@ impl MouseEvent {
middle: false,
wheel_up: true,
wheel_down: false,
+ wheel_left: false,
+ wheel_right: false,
shift: false,
alt: false,
ctrl: false,
@@ -321,6 +361,8 @@ impl MouseEvent {
middle: false,
wheel_up: false,
wheel_down: true,
+ wheel_left: false,
+ wheel_right: false,
shift: false,
alt: false,
ctrl: false,
@@ -336,6 +378,8 @@ impl MouseEvent {
middle: false,
wheel_up: true,
wheel_down: false,
+ wheel_left: false,
+ wheel_right: false,
shift: false,
alt: false,
ctrl: true,
@@ -351,6 +395,8 @@ impl MouseEvent {
middle: false,
wheel_up: false,
wheel_down: true,
+ wheel_left: false,
+ wheel_right: false,
shift: false,
alt: false,
ctrl: true,
@@ -358,6 +404,38 @@ impl MouseEvent {
};
event
}
+ pub fn new_scroll_left_event(position: Position) -> Self {
+ MouseEvent {
+ event_type: MouseEventType::Press,
+ left: false,
+ right: false,
+ middle: false,
+ wheel_up: false,
+ wheel_down: false,
+ wheel_left: true,
+ wheel_right: false,
+ shift: false,
+ alt: false,
+ ctrl: false,
+ position,
+ }
+ }
+ pub fn new_scroll_right_event(position: Position) -> Self {
+ MouseEvent {
+ event_type: MouseEventType::Press,
+ left: false,
+ right: false,
+ middle: false,
+ wheel_up: false,
+ wheel_down: false,
+ wheel_left: false,
+ wheel_right: true,
+ shift: false,
+ alt: false,
+ ctrl: false,
+ position,
+ }
+ }
}
impl Default for MouseEvent {
diff --git a/zellij-utils/src/input/options.rs b/zellij-utils/src/input/options.rs
index 854ef271..8f91cb47 100644
--- a/zellij-utils/src/input/options.rs
+++ b/zellij-utils/src/input/options.rs
@@ -16,6 +16,65 @@ pub enum OnForceClose {
Detach,
}
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, ArgEnum)]
+pub enum MobileLayoutConfiguration {
+ #[serde(alias = "web")]
+ Web,
+ #[serde(alias = "always")]
+ Always,
+ #[serde(alias = "never")]
+ Never,
+}
+
+impl Default for MobileLayoutConfiguration {
+ fn default() -> Self {
+ Self::Web
+ }
+}
+
+impl FromStr for MobileLayoutConfiguration {
+ type Err = String;
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "Web" | "web" => Ok(Self::Web),
+ "Always" | "always" => Ok(Self::Always),
+ "Never" | "never" => Ok(Self::Never),
+ _ => Err(format!("No such mobile_layout: {}", s)),
+ }
+ }
+}
+
+impl MobileLayoutConfiguration {
+ pub fn should_route_to_mobile(
+ self,
+ is_web_client: bool,
+ viewport_cols: usize,
+ viewport_rows: usize,
+ threshold_cols: u16,
+ threshold_rows: u16,
+ ) -> bool {
+ let cols_reported = viewport_cols > 0;
+ let rows_reported = viewport_rows > 0;
+ let cols_match =
+ cols_reported && (threshold_cols == 0 || viewport_cols <= threshold_cols as usize);
+ let rows_match =
+ rows_reported && (threshold_rows == 0 || viewport_rows <= threshold_rows as usize);
+ let size_match = cols_match || rows_match;
+ match self {
+ MobileLayoutConfiguration::Always => size_match,
+ MobileLayoutConfiguration::Never => false,
+ MobileLayoutConfiguration::Web => is_web_client && size_match,
+ }
+ }
+
+ pub fn may_route_web_client_to_mobile(self) -> bool {
+ matches!(
+ self,
+ MobileLayoutConfiguration::Web | MobileLayoutConfiguration::Always
+ )
+ }
+}
+
impl Default for OnForceClose {
fn default() -> Self {
Self::Detach
@@ -278,6 +337,21 @@ pub struct Options {
/// NOTE: This only applies to web clients at the moment.
#[clap(long)]
pub client_async_worker_tasks: Option<usize>,
+
+ /// When a newly-attaching client should land in the mobile UI plugin (web, always, never)
+ #[clap(long, arg_enum, hide_possible_values = true, value_parser)]
+ #[serde(default)]
+ pub mobile_layout: Option<MobileLayoutConfiguration>,
+
+ /// Column breakpoint for mobile_layout (0 to always match)
+ #[clap(long, value_parser)]
+ #[serde(default)]
+ pub mobile_threshold_cols: Option<u16>,
+
+ /// Row breakpoint for mobile_layout (0 to always match)
+ #[clap(long, value_parser)]
+ #[serde(default)]
+ pub mobile_threshold_rows: Option<u16>,
}
#[derive(ArgEnum, Deserialize, Serialize, Debug, Clone, Copy, PartialEq)]
@@ -384,6 +458,9 @@ impl Options {
let client_async_worker_tasks = other
.client_async_worker_tasks
.or(self.client_async_worker_tasks);
+ let mobile_layout = other.mobile_layout.or(self.mobile_layout);
+ let mobile_threshold_cols = other.mobile_threshold_cols.or(self.mobile_threshold_cols);
+ let mobile_threshold_rows = other.mobile_threshold_rows.or(self.mobile_threshold_rows);
Options {
simplified_ui,
@@ -433,6 +510,9 @@ impl Options {
enforce_https_for_localhost,
post_command_discovery_hook,
client_async_worker_tasks,
+ mobile_layout,
+ mobile_threshold_cols,
+ mobile_threshold_rows,
}
}
@@ -519,6 +599,9 @@ impl Options {
let client_async_worker_tasks = other
.client_async_worker_tasks
.or(self.client_async_worker_tasks);
+ let mobile_layout = other.mobile_layout.or(self.mobile_layout);
+ let mobile_threshold_cols = other.mobile_threshold_cols.or(self.mobile_threshold_cols);
+ let mobile_threshold_rows = other.mobile_threshold_rows.or(self.mobile_threshold_rows);
Options {
simplified_ui,
@@ -568,6 +651,9 @@ impl Options {
enforce_https_for_localhost,
post_command_discovery_hook,
client_async_worker_tasks,
+ mobile_layout,
+ mobile_threshold_cols,
+ mobile_threshold_rows,
}
}
@@ -579,3 +665,206 @@ impl Options {
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ const SMALL: (usize, usize) = (40, 20);
+ const WIDE_SHORT: (usize, usize) = (200, 20);
+ const TALL_NARROW: (usize, usize) = (40, 200);
+ const LARGE: (usize, usize) = (200, 200);
+
+ fn route(
+ layout: MobileLayoutConfiguration,
+ is_web: bool,
+ viewport: (usize, usize),
+ thresholds: (u16, u16),
+ ) -> bool {
+ layout.should_route_to_mobile(is_web, viewport.0, viewport.1, thresholds.0, thresholds.1)
+ }
+
+ #[test]
+ fn never_never_routes_to_mobile() {
+ for &is_web in &[true, false] {
+ for &viewport in &[SMALL, WIDE_SHORT, TALL_NARROW, LARGE] {
+ for &thresholds in &[(60, 30), (0, 0), (0, 30), (60, 0)] {
+ assert!(
+ !route(MobileLayoutConfiguration::Never, is_web, viewport, thresholds),
+ "Never must never route (is_web={is_web} viewport={viewport:?} thresholds={thresholds:?})",
+ );
+ }
+ }
+ }
+ }
+
+ #[test]
+ fn web_requires_web_client() {
+ assert!(route(MobileLayoutConfiguration::Web, true, SMALL, (60, 30)));
+ assert!(!route(
+ MobileLayoutConfiguration::Web,
+ false,
+ SMALL,
+ (60, 30)
+ ));
+ }
+
+ #[test]
+ fn web_respects_size_in_either_dimension() {
+ assert!(route(
+ MobileLayoutConfiguration::Web,
+ true,
+ TALL_NARROW,
+ (60, 30)
+ ));
+ assert!(route(
+ MobileLayoutConfiguration::Web,
+ true,
+ WIDE_SHORT,
+ (60, 30)
+ ));
+ assert!(!route(
+ MobileLayoutConfiguration::Web,
+ true,
+ LARGE,
+ (60, 30)
+ ));
+ }
+
+ #[test]
+ fn always_routes_any_client_on_size_match() {
+ assert!(route(
+ MobileLayoutConfiguration::Always,
+ true,
+ SMALL,
+ (60, 30)
+ ));
+ assert!(route(
+ MobileLayoutConfiguration::Always,
+ false,
+ SMALL,
+ (60, 30)
+ ));
+ assert!(!route(
+ MobileLayoutConfiguration::Always,
+ true,
+ LARGE,
+ (60, 30)
+ ));
+ assert!(!route(
+ MobileLayoutConfiguration::Always,
+ false,
+ LARGE,
+ (60, 30)
+ ));
+ }
+
+ #[test]
+ fn zero_threshold_makes_dimension_unconditional() {
+ assert!(route(
+ MobileLayoutConfiguration::Always,
+ false,
+ LARGE,
+ (0, 30)
+ ));
+ assert!(route(
+ MobileLayoutConfiguration::Always,
+ false,
+ LARGE,
+ (60, 0)
+ ));
+ assert!(route(
+ MobileLayoutConfiguration::Always,
+ false,
+ LARGE,
+ (0, 0)
+ ));
+ assert!(route(MobileLayoutConfiguration::Web, true, LARGE, (0, 0)));
+ assert!(!route(MobileLayoutConfiguration::Web, false, LARGE, (0, 0)));
+ }
+
+ #[test]
+ fn zero_viewport_is_treated_as_unknown() {
+ assert!(!route(
+ MobileLayoutConfiguration::Always,
+ true,
+ (0, 0),
+ (60, 30)
+ ));
+ assert!(!route(
+ MobileLayoutConfiguration::Web,
+ true,
+ (0, 0),
+ (60, 30)
+ ));
+ assert!(!route(
+ MobileLayoutConfiguration::Always,
+ true,
+ (0, 0),
+ (0, 0)
+ ));
+ assert!(route(
+ MobileLayoutConfiguration::Always,
+ true,
+ (40, 0),
+ (60, 30)
+ ));
+ assert!(route(
+ MobileLayoutConfiguration::Always,
+ true,
+ (0, 20),
+ (60, 30)
+ ));
+ }
+
+ #[test]
+ fn boundary_inclusive_at_threshold() {
+ assert!(route(
+ MobileLayoutConfiguration::Always,
+ false,
+ (60, 200),
+ (60, 30)
+ ));
+ assert!(route(
+ MobileLayoutConfiguration::Always,
+ false,
+ (200, 30),
+ (60, 30)
+ ));
+ assert!(!route(
+ MobileLayoutConfiguration::Always,
+ false,
+ (61, 31),
+ (60, 30)
+ ));
+ }
+
+ #[test]
+ fn from_str_accepts_canonical_and_lowercase() {
+ assert_eq!(
+ "web".parse::<MobileLayoutConfiguration>(),
+ Ok(MobileLayoutConfiguration::Web)
+ );
+ assert_eq!(
+ "Web".parse::<MobileLayoutConfiguration>(),
+ Ok(MobileLayoutConfiguration::Web)
+ );
+ assert_eq!(
+ "always".parse::<MobileLayoutConfiguration>(),
+ Ok(MobileLayoutConfiguration::Always)
+ );
+ assert_eq!(
+ "never".parse::<MobileLayoutConfiguration>(),
+ Ok(MobileLayoutConfiguration::Never)
+ );
+ assert!("auto".parse::<MobileLayoutConfiguration>().is_err());
+ }
+
+ #[test]
+ fn default_is_web() {
+ assert_eq!(
+ MobileLayoutConfiguration::default(),
+ MobileLayoutConfiguration::Web
+ );
+ }
+}
diff --git a/zellij-utils/src/input/plugins.rs b/zellij-utils/src/input/plugins.rs
index 31e48b43..7f9bf427 100644
--- a/zellij-utils/src/input/plugins.rs
+++ b/zellij-utils/src/input/plugins.rs
@@ -69,6 +69,7 @@ impl PluginConfig {
|| tag == "multiple-select"
|| tag == "layout-manager"
|| tag == "link"
+ || tag == "mobile"
{
Some(PluginConfig {
path: PathBuf::from(&tag),
diff --git a/zellij-utils/src/input/web_client.rs b/zellij-utils/src/input/web_client.rs
index de09efad..9607adc0 100644
--- a/zellij-utils/src/input/web_client.rs
+++ b/zellij-utils/src/input/web_client.rs
@@ -239,6 +239,7 @@ pub struct WebClientConfig {
pub cursor_style: Option<CursorStyle>,
pub mac_option_is_meta: bool,
pub base_url: Option<String>,
+ pub font_size: Option<u16>,
}
impl Default for WebClientConfig {
@@ -251,6 +252,7 @@ impl Default for WebClientConfig {
cursor_style: None,
mac_option_is_meta: true, // TODO: yes? no?
base_url: None,
+ font_size: None,
}
}
}
@@ -289,6 +291,24 @@ impl WebClientConfig {
web_client_config.base_url = Some(base_url.to_owned());
}
+ if let Some(font_size_node) = kdl_get_child!(kdl, "font_size") {
+ let raw = font_size_node
+ .entries()
+ .iter()
+ .find_map(|e| e.value().as_i64());
+ if let Some(value) = raw {
+ if value > 0 && value <= u16::MAX as i64 {
+ web_client_config.font_size = Some(value as u16);
+ } else {
+ return Err(ConfigError::new_kdl_error(
+ format!("font_size must be between 1 and {}", u16::MAX),
+ font_size_node.span().offset(),
+ font_size_node.span().len(),
+ ));
+ }
+ }
+ }
+
Ok(web_client_config)
}
@@ -338,6 +358,12 @@ impl WebClientConfig {
web_client_children.nodes_mut().push(base_url_node);
}
+ if let Some(font_size) = self.font_size {
+ let mut font_size_node = KdlNode::new("font_size");
+ font_size_node.push(KdlValue::Base10(font_size as i64));
+ web_client_children.nodes_mut().push(font_size_node);
+ }
+
web_client_node.set_children(web_client_children);
web_client_node
}
@@ -351,6 +377,7 @@ impl WebClientConfig {
merged.cursor_style = other.cursor_style;
merged.mac_option_is_meta = other.mac_option_is_meta;
merged.base_url = other.base_url;
+ merged.font_size = other.font_size;
merged
}
}
diff --git a/zellij-utils/src/ipc.rs b/zellij-utils/src/ipc.rs
index a837a0af..d5edbbb4 100644
--- a/zellij-utils/src/ipc.rs
+++ b/zellij-utils/src/ipc.rs
@@ -81,6 +81,14 @@ pub struct ColorRegister {
pub color: String,
}
+#[derive(Default, Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
+pub enum ResizeCause {
+ #[default]
+ Viewport,
+ RenderingPreference,
+ SizeSettled,
+}
+
impl PixelDimensions {
pub fn merge(&mut self, other: PixelDimensions) {
if let Some(text_area_size) = other.text_area_size {
@@ -113,6 +121,8 @@ pub enum ClientToServerMsg {
},
TerminalResize {
new_size: Size,
+ #[serde(default)]
+ cause: ResizeCause,
},
FirstClientConnected {
cli_assets: CliAssets,
@@ -156,21 +166,16 @@ pub enum ClientToServerMsg {
DesktopNotificationResponse {
raw_bytes: Vec<u8>,
},
- /// Raw reply bytes observed by the client inside a forwarding window
- /// closed by the Primary-DA barrier. The server routes these bytes
- /// verbatim to the pane whose app issued the whitelisted query
- /// (tracked on the server by `token`).
ForwardedReplyFromHost {
token: u32,
reply_bytes: Vec<u8>,
},
- /// The host terminal reported a color-palette theme mode (DSR 997 reply
- /// to `CSI ? 996 n`, or unsolicited notification while `CSI ? 2031 h`
- /// is enabled). The server propagates this to the active configured
- /// theme and to subscribed plugins/panes.
HostTerminalThemeChanged {
mode: HostTerminalThemeMode,
},
+ SoftKeyboardVisibilityChanged {
+ visible: bool,
+ },
}
// Types of messages sent from the server to the client
@@ -201,6 +206,9 @@ pub enum ServerToClientMsg {
output: String,
},
QueryTerminalSize,
+ SetSoftKeyboard {
+ on: bool,
+ },
StartWebServer,
RenamedSession {
name: String,
@@ -215,10 +223,6 @@ pub enum ServerToClientMsg {
SubscribedPaneClosed {
pane_id: PaneId,
},
- /// Instruct the client to write `query_bytes` followed by the
- /// Primary-DA barrier to stdout, open a forwarding window keyed by
- /// `token`, and reply with a `ForwardedReplyFromHost` once the
- /// barrier closes or the window times out.
ForwardQueryToHost {
token: u32,
query_bytes: Vec<u8>,
diff --git a/zellij-utils/src/ipc/protobuf_conversion.rs b/zellij-utils/src/ipc/protobuf_conversion.rs
index 8025d66f..d1bfb7d4 100644
--- a/zellij-utils/src/ipc/protobuf_conversion.rs
+++ b/zellij-utils/src/ipc/protobuf_conversion.rs
@@ -10,8 +10,9 @@ use crate::{
HostTerminalThemeIndication as ProtoHostTerminalThemeIndication,
InputMode as ProtoInputMode, KeyMsg, KillSessionMsg, LayoutMetadata as ProtoLayoutMetadata,
LogErrorMsg, LogMsg, PaneMetadata as ProtoPaneMetadata, PaneRenderUpdateMsg,
- QueryTerminalSizeMsg, RenamedSessionMsg, RenderMsg,
- ServerToClientMsg as ProtoServerToClientMsg, StartWebServerMsg, SubscribeToPaneRendersMsg,
+ QueryTerminalSizeMsg, RenamedSessionMsg, RenderMsg, ResizeCause as ProtoResizeCause,
+ ServerToClientMsg as ProtoServerToClientMsg, SetSoftKeyboardMsg,
+ SoftKeyboardVisibilityChangedMsg, StartWebServerMsg, SubscribeToPaneRendersMsg,
SubscribedPaneClosedMsg, SwitchSessionMsg, TabMetadata as ProtoTabMetadata,
TerminalPixelDimensionsMsg, TerminalResizeMsg, UnblockCliPipeInputMsg,
UnblockInputThreadMsg, WebServerStartedMsg,
@@ -19,7 +20,7 @@ use crate::{
data::{HostTerminalThemeMode, InputMode, PaneId},
errors::prelude::*,
ipc::{
- ClientToServerMsg, ColorRegister, ExitReason, PaneReference, PixelDimensions,
+ ClientToServerMsg, ColorRegister, ExitReason, PaneReference, PixelDimensions, ResizeCause,
ServerToClientMsg,
},
};
@@ -51,9 +52,11 @@ impl From<ClientToServerMsg> for ProtoClientToServerMsg {
color_registers: color_registers.into_iter().map(|cr| cr.into()).collect(),
})
},
- ClientToServerMsg::TerminalResize { new_size } => {
+ ClientToServerMsg::TerminalResize { new_size, cause } => {
+ let cause_proto: ProtoResizeCause = cause.into();
client_to_server_msg::Message::TerminalResize(TerminalResizeMsg {
new_size: Some(new_size.into()),
+ cause: cause_proto as i32,
})
},
ClientToServerMsg::FirstClientConnected {
@@ -146,6 +149,11 @@ impl From<ClientToServerMsg> for ProtoClientToServerMsg {
},
)
},
+ ClientToServerMsg::SoftKeyboardVisibilityChanged { visible } => {
+ client_to_server_msg::Message::SoftKeyboardVisibilityChanged(
+ SoftKeyboardVisibilityChangedMsg { visible },
+ )
+ },
};
ProtoClientToServerMsg {
@@ -193,11 +201,15 @@ impl TryFrom<ProtoClientToServerMsg> for ClientToServerMsg {
})
},
Some(client_to_server_msg::Message::TerminalResize(resize)) => {
+ let cause = ProtoResizeCause::from_i32(resize.cause)
+ .unwrap_or(ProtoResizeCause::Viewport)
+ .into();
Ok(ClientToServerMsg::TerminalResize {
new_size: resize
.new_size
.ok_or_else(|| anyhow!("Missing new_size"))?
.try_into()?,
+ cause,
})
},
Some(client_to_server_msg::Message::FirstClientConnected(first_client)) => {
@@ -287,6 +299,11 @@ impl TryFrom<ProtoClientToServerMsg> for ClientToServerMsg {
mode: proto_mode.into(),
})
},
+ Some(client_to_server_msg::Message::SoftKeyboardVisibilityChanged(msg)) => {
+ Ok(ClientToServerMsg::SoftKeyboardVisibilityChanged {
+ visible: msg.visible,
+ })
+ },
None => Err(anyhow!("Empty ClientToServerMsg message")),
}
}
@@ -372,6 +389,9 @@ impl From<ServerToClientMsg> for ProtoServerToClientMsg {
query_bytes,
})
},
+ ServerToClientMsg::SetSoftKeyboard { on } => {
+ server_to_client_msg::Message::SetSoftKeyboard(SetSoftKeyboardMsg { on })
+ },
};
ProtoServerToClientMsg {
@@ -484,6 +504,9 @@ impl TryFrom<ProtoServerToClientMsg> for ServerToClientMsg {
query_bytes: msg.query_bytes,
})
},
+ Some(server_to_client_msg::Message::SetSoftKeyboard(msg)) => {
+ Ok(ServerToClientMsg::SetSoftKeyboard { on: msg.on })
+ },
None => Err(anyhow!("Empty ServerToClientMsg message")),
}
}
@@ -732,6 +755,17 @@ impl From<crate::input::options::Options>
visual_bell: options.visual_bell,
focus_follows_mouse: options.focus_follows_mouse,
mouse_click_through: options.mouse_click_through,
+ mobile_layout: options.mobile_layout.map(|m| {
+ use crate::client_server_contract::client_server_contract::MobileLayout as ProtoMobileLayout;
+ use crate::input::options::MobileLayoutConfiguration;
+ match m {
+ MobileLayoutConfiguration::Web => ProtoMobileLayout::Web as i32,
+ MobileLayoutConfiguration::Always => ProtoMobileLayout::Always as i32,
+ MobileLayoutConfiguration::Never => ProtoMobileLayout::Never as i32,
+ }
+ }),
+ mobile_threshold_cols: options.mobile_threshold_cols.map(|v| v as u32),
+ mobile_threshold_rows: options.mobile_threshold_rows.map(|v| v as u32),
}
}
}
@@ -744,8 +778,8 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Options>
options: crate::client_server_contract::client_server_contract::Options,
) -> Result<Self> {
use crate::client_server_contract::client_server_contract::{
- Clipboard as ProtoClipboard, OnForceClose as ProtoOnForceClose,
- WebSharing as ProtoWebSharing,
+ Clipboard as ProtoClipboard, MobileLayout as ProtoMobileLayout,
+ OnForceClose as ProtoOnForceClose, WebSharing as ProtoWebSharing,
};
Ok(Self {
@@ -829,6 +863,23 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Options>
visual_bell: options.visual_bell,
focus_follows_mouse: options.focus_follows_mouse,
mouse_click_through: options.mouse_click_through,
+ mobile_layout: options
+ .mobile_layout
+ .map(|m| match ProtoMobileLayout::from_i32(m) {
+ Some(ProtoMobileLayout::Web) => {
+ Ok(crate::input::options::MobileLayoutConfiguration::Web)
+ },
+ Some(ProtoMobileLayout::Always) => {
+ Ok(crate::input::options::MobileLayoutConfiguration::Always)
+ },
+ Some(ProtoMobileLayout::Never) => {
+ Ok(crate::input::options::MobileLayoutConfiguration::Never)
+ },
+ _ => Err(anyhow!("Invalid MobileLayout value: {}", m)),
+ })
+ .transpose()?,
+ mobile_threshold_cols: options.mobile_threshold_cols.map(|v| v as u16),
+ mobile_threshold_rows: options.mobile_threshold_rows.map(|v| v as u16),
})
}
}
@@ -1419,6 +1470,9 @@ impl From<crate::input::actions::Action>
crate::input::actions::Action::ToggleMouseMode => {
ActionType::ToggleMouseMode(ToggleMouseModeAction {})
},
+ crate::input::actions::Action::ToggleMobileMode => ActionType::ToggleMobileMode(
+ crate::client_server_contract::client_server_contract::ToggleMobileModeAction {},
+ ),
crate::input::actions::Action::PreviousSwapLayout => {
ActionType::PreviousSwapLayout(PreviousSwapLayoutAction {})
},
@@ -2284,6 +2338,7 @@ impl TryFrom<crate::client_server_contract::client_server_contract::Action>
})
},
ActionType::ToggleMouseMode(_) => Ok(crate::input::actions::Action::ToggleMouseMode),
+ ActionType::ToggleMobileMode(_) => Ok(crate::input::actions::Action::ToggleMobileMode),
ActionType::PreviousSwapLayout(_) => {
Ok(crate::input::actions::Action::PreviousSwapLayout)
},
@@ -3013,6 +3068,26 @@ impl TryFrom<ProtoExitReason> for ExitReason {
}
}
+impl From<ResizeCause> for ProtoResizeCause {
+ fn from(cause: ResizeCause) -> Self {
+ match cause {
+ ResizeCause::Viewport => ProtoResizeCause::Viewport,
+ ResizeCause::RenderingPreference => ProtoResizeCause::RenderingPreference,
+ ResizeCause::SizeSettled => ProtoResizeCause::SizeSettled,
+ }
+ }
+}
+
+impl From<ProtoResizeCause> for ResizeCause {
+ fn from(cause: ProtoResizeCause) -> Self {
+ match cause {
+ ProtoResizeCause::Viewport => ResizeCause::Viewport,
+ ProtoResizeCause::RenderingPreference => ResizeCause::RenderingPreference,
+ ProtoResizeCause::SizeSettled => ResizeCause::SizeSettled,
+ }
+ }
+}
+
impl From<HostTerminalThemeMode> for ProtoHostTerminalThemeIndication {
fn from(mode: HostTerminalThemeMode) -> Self {
match mode {
@@ -3580,6 +3655,8 @@ impl From<crate::input::mouse::MouseEvent>
middle: event.middle,
wheel_up: event.wheel_up,
wheel_down: event.wheel_down,
+ wheel_left: event.wheel_left,
+ wheel_right: event.wheel_right,
shift: event.shift,
alt: event.alt,
ctrl: event.ctrl,
@@ -4167,6 +4244,8 @@ impl TryFrom<crate::client_server_contract::client_server_contract::MouseEvent>
middle: event.middle,
wheel_up: event.wheel_up,
wheel_down: event.wheel_down,
+ wheel_left: event.wheel_left,
+ wheel_right: event.wheel_right,
shift: event.shift,
alt: event.alt,
ctrl: event.ctrl,
diff --git a/zellij-utils/src/ipc/tests/roundtrip_tests.rs b/zellij-utils/src/ipc/tests/roundtrip_tests.rs
index 4847b679..280805ab 100644
--- a/zellij-utils/src/ipc/tests/roundtrip_tests.rs
+++ b/zellij-utils/src/ipc/tests/roundtrip_tests.rs
@@ -13,9 +13,10 @@ use crate::input::layout::{
TiledPaneLayout,
};
use crate::input::mouse::{MouseEvent, MouseEventType};
-use crate::input::options::{Clipboard, OnForceClose, Options};
+use crate::input::options::{Clipboard, MobileLayoutConfiguration, OnForceClose, Options};
use crate::ipc::{
- ClientToServerMsg, ColorRegister, ExitReason, PaneReference, PixelDimensions, ServerToClientMsg,
+ ClientToServerMsg, ColorRegister, ExitReason, PaneReference, PixelDimensions, ResizeCause,
+ ServerToClientMsg,
};
use crate::pane_size::{Size, SizeInPixels};
use crate::position::Position;
@@ -24,12 +25,6 @@ use std::path::PathBuf;
#[test]
fn server_client_contract() {
- // here we test all possible values of each of the nested types in the server/client contract
- // in the context of its message.
- //
- // we do a "roundtrip" test, meaning we take each message, encode it to protobuf bytes and
- // decode it back to its rust type and then assert its equality with the original type to make
- // sure they are identical and did not lose any information
test_client_messages();
test_server_messages();
}
@@ -378,12 +373,18 @@ fn test_client_messages() {
});
test_client_roundtrip!(ClientToServerMsg::TerminalResize {
new_size: Size { cols: 80, rows: 24 },
+ cause: ResizeCause::Viewport,
});
test_client_roundtrip!(ClientToServerMsg::TerminalResize {
new_size: Size {
cols: 200,
rows: 50
},
+ cause: ResizeCause::Viewport,
+ });
+ test_client_roundtrip!(ClientToServerMsg::TerminalResize {
+ new_size: Size { cols: 40, rows: 38 },
+ cause: ResizeCause::RenderingPreference,
});
test_client_roundtrip!(ClientToServerMsg::FirstClientConnected {
cli_assets: CliAssets::default(),
@@ -478,6 +479,9 @@ fn test_client_messages() {
visual_bell: Some(true),
focus_follows_mouse: Some(false),
mouse_click_through: Some(false),
+ mobile_layout: Some(MobileLayoutConfiguration::Always),
+ mobile_threshold_cols: Some(72),
+ mobile_threshold_rows: Some(40),
}),
layout: None,
terminal_window_size: Size { rows: 80, cols: 42 },
@@ -670,7 +674,6 @@ fn test_client_messages() {
is_web_client: true,
});
test_client_roundtrip!(ClientToServerMsg::AttachClient {
- // cli_assets tested extensively ijn FirstClientConnected, we can skip it here
cli_assets: CliAssets::default(),
tab_position_to_focus: None,
pane_to_focus: None,
@@ -725,7 +728,6 @@ fn test_client_messages() {
});
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::Write {
- // KeyWithModifier is tested extensively in the Key ClientToServerMsg
key_with_modifier: Some(KeyWithModifier::new(BareKey::Char('a'))),
bytes: "a".as_bytes().to_vec(),
is_kitty_keyboard_protocol: true,
@@ -744,7 +746,6 @@ fn test_client_messages() {
});
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::SwitchToMode {
- // InputMode is tested extensively in the Options conversion tests above
input_mode: InputMode::Locked,
},
terminal_id: Some(1),
@@ -753,7 +754,6 @@ fn test_client_messages() {
});
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::SwitchModeForAllClients {
- // InputMode is tested extensively in the Options conversion tests above
input_mode: InputMode::Locked,
},
terminal_id: Some(1),
@@ -834,7 +834,6 @@ fn test_client_messages() {
});
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::MoveFocus {
- // Direction is tested extensively elsewhere
direction: Direction::Up,
},
terminal_id: Some(1),
@@ -2235,7 +2234,6 @@ fn test_client_messages() {
});
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::Run {
- // RunCommandAction serialization roundtrip is tested extensively upwards of here
command: RunCommandAction {
command: PathBuf::from("/path/to/command"),
args: vec![],
@@ -2260,7 +2258,6 @@ fn test_client_messages() {
});
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::LaunchOrFocusPlugin {
- // RunPluginOrAlias serialization roundtrip is tested extensively upwards
plugin: RunPluginOrAlias::RunPlugin(RunPlugin::default()),
should_float: true,
move_to_focused_tab: true,
@@ -2275,7 +2272,6 @@ fn test_client_messages() {
});
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::LaunchOrFocusPlugin {
- // RunPluginOrAlias serialization roundtrip is tested extensively upwards
plugin: RunPluginOrAlias::Alias(PluginAlias::default()),
should_float: false,
move_to_focused_tab: false,
@@ -2290,7 +2286,6 @@ fn test_client_messages() {
});
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::LaunchPlugin {
- // RunPluginOrAlias serialization roundtrip is tested extensively upwards
plugin: RunPluginOrAlias::RunPlugin(RunPlugin::default()),
should_float: true,
should_open_in_place: true,
@@ -2305,7 +2300,6 @@ fn test_client_messages() {
});
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::LaunchPlugin {
- // RunPluginOrAlias serialization roundtrip is tested extensively upwards
plugin: RunPluginOrAlias::Alias(PluginAlias::default()),
should_float: false,
should_open_in_place: false,
@@ -2327,6 +2321,8 @@ fn test_client_messages() {
middle: true,
wheel_up: true,
wheel_down: true,
+ wheel_left: true,
+ wheel_right: true,
shift: true,
alt: true,
ctrl: true,
@@ -2346,6 +2342,8 @@ fn test_client_messages() {
middle: true,
wheel_up: true,
wheel_down: true,
+ wheel_left: true,
+ wheel_right: true,
shift: true,
alt: true,
ctrl: true,
@@ -2365,6 +2363,8 @@ fn test_client_messages() {
middle: true,
wheel_up: true,
wheel_down: true,
+ wheel_left: true,
+ wheel_right: true,
shift: true,
alt: true,
ctrl: true,
@@ -2530,7 +2530,6 @@ fn test_client_messages() {
client_id: Some(100),
is_cli_client: true,
});
- // tab_id roundtrip tests - verify tab_id survives serialization
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::NewTiledPane {
direction: Some(Direction::Right),
@@ -3280,7 +3279,6 @@ fn test_client_messages() {
scrollback: Some(0),
ansi: true,
});
- // Pane-targeting roundtrips
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::ScrollUpByPaneId {
pane_id: PaneId::Terminal(1),
@@ -3416,7 +3414,6 @@ fn test_client_messages() {
client_id: Some(100),
is_cli_client: true,
});
- // ANSI flag roundtrip tests
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::DumpScreen {
file_path: Some("/path".to_owned()),
@@ -3500,7 +3497,6 @@ fn test_client_messages() {
client_id: Some(100),
is_cli_client: true,
});
- // Tab-targeting roundtrips
test_client_roundtrip!(ClientToServerMsg::Action {
action: Action::UndoRenameTabByTabId { id: 5 },
terminal_id: Some(1),
@@ -3720,7 +3716,6 @@ fn test_server_messages() {
#[test]
fn set_pane_color_wire_roundtrip() {
- // Test actual byte-level encode/decode, not just struct conversion
use prost::Message;
let original = ClientToServerMsg::Action {
@@ -3734,21 +3729,17 @@ fn set_pane_color_wire_roundtrip() {
is_cli_client: true,
};
- // Rust -> proto struct
let proto: crate::client_server_contract::client_server_contract::ClientToServerMsg =
original.clone().into();
- // proto struct -> bytes (what goes over the wire)
let bytes = proto.encode_to_vec();
- // bytes -> proto struct (what the server does)
let decoded_proto =
crate::client_server_contract::client_server_contract::ClientToServerMsg::decode(
&bytes[..],
)
.expect("Failed to decode protobuf bytes");
- // proto struct -> Rust
let roundtrip: ClientToServerMsg = decoded_proto
.try_into()
.expect("Failed to convert decoded protobuf back to Rust");
@@ -3782,14 +3773,12 @@ fn rename_active_pane_wire_roundtrip() {
let proto: crate::client_server_contract::client_server_contract::ClientToServerMsg =
original.clone().into();
- // proto struct → bytes → proto struct (the actual wire path)
let mut buf = Vec::new();
proto.encode(&mut buf).expect("Failed to encode protobuf");
let decoded =
crate::client_server_contract::client_server_contract::ClientToServerMsg::decode(&buf[..])
.expect("Failed to decode protobuf bytes");
- // proto struct → Rust
let roundtrip: ClientToServerMsg = decoded
.try_into()
.expect("Failed to convert decoded protobuf back to Rust");
diff --git a/zellij-utils/src/ipc/tests/socket_tests.rs b/zellij-utils/src/ipc/tests/socket_tests.rs
index d0e9ce65..b1f8c15d 100644
--- a/zellij-utils/src/ipc/tests/socket_tests.rs
+++ b/zellij-utils/src/ipc/tests/socket_tests.rs
@@ -1,5 +1,5 @@
use crate::ipc::{
- ClientToServerMsg, IpcReceiverWithContext, IpcSenderWithContext, ServerToClientMsg,
+ ClientToServerMsg, IpcReceiverWithContext, IpcSenderWithContext, ResizeCause, ServerToClientMsg,
};
use crate::pane_size::Size;
use interprocess::local_socket::{prelude::*, ListenerOptions};
@@ -162,9 +162,7 @@ fn bidirectional_communication_via_fd_duplication() {
let stream = listener.incoming().next().unwrap().expect("accept failed");
let mut sender: IpcSenderWithContext<ServerToClientMsg> = IpcSenderWithContext::new(stream);
- // Create a receiver from the same socket via dup()
let mut receiver: IpcReceiverWithContext<ClientToServerMsg> = sender.get_receiver();
-
sender
.send_server_msg(ServerToClientMsg::Connected)
.expect("send failed");
@@ -181,7 +179,6 @@ fn bidirectional_communication_via_fd_duplication() {
let stream = connect_stream(&name);
let mut sender: IpcSenderWithContext<ClientToServerMsg> = IpcSenderWithContext::new(stream);
- // Create a receiver from the same socket via dup()
let mut receiver: IpcReceiverWithContext<ServerToClientMsg> = sender.get_receiver();
let msg = receiver.recv_server_msg();
@@ -220,6 +217,7 @@ fn multiple_messages_in_sequence() {
rows: 50,
cols: 120,
},
+ cause: ResizeCause::Viewport,
})
.expect("send 2 failed");
sender
@@ -237,9 +235,10 @@ fn multiple_messages_in_sequence() {
let (msg2, _) = receiver.recv_client_msg().expect("missing message 2");
match msg2 {
- ClientToServerMsg::TerminalResize { new_size } => {
+ ClientToServerMsg::TerminalResize { new_size, cause } => {
assert_eq!(new_size.rows, 50);
assert_eq!(new_size.cols, 120);
+ assert_eq!(cause, ResizeCause::Viewport);
},
other => panic!("expected TerminalResize, got: {:?}", other),
}
@@ -264,7 +263,6 @@ fn receiver_returns_none_on_closed_connection() {
sender
.send_client_msg(ClientToServerMsg::ConnStatus)
.expect("send failed");
- // sender drops here, closing the connection
}
});
@@ -277,16 +275,10 @@ fn receiver_returns_none_on_closed_connection() {
let msg = receiver.recv_client_msg();
assert!(msg.is_some(), "should receive the sent message");
- // After the sender is dropped, subsequent reads should return None
let msg = receiver.recv_client_msg();
assert!(msg.is_none(), "should return None after connection closed");
}
-// --- Session discovery tests ---
-// These test the OS-specific mechanics used by get_sessions() in sessions.rs:
-// FileTypeExt::is_socket() for identifying socket files, and the assert_socket
-// probing pattern (connect, send ConnStatus, expect Connected).
-
#[cfg(unix)]
#[test]
fn is_socket_identifies_bound_unix_socket() {
@@ -318,12 +310,9 @@ fn is_socket_rejects_regular_file() {
#[test]
fn session_probe_accepts_responding_socket() {
- // Simulates the assert_socket() pattern from sessions.rs:
- // A real Zellij server responds to ConnStatus with Connected.
let (_guard, name) = new_ipc();
let listener = bind_listener(&name);
- // Spawn a fake "server" that responds to ConnStatus with Connected
let server = std::thread::spawn(move || {
let stream = listener.incoming().next().unwrap().expect("accept failed");
let mut receiver: IpcReceiverWithContext<ClientToServerMsg> =
@@ -338,7 +327,6 @@ fn session_probe_accepts_responding_socket() {
.expect("send failed");
});
- // Client-side probing (mirrors assert_socket in sessions.rs)
let stream = connect_stream(&name);
let mut sender: IpcSenderWithContext<ClientToServerMsg> = IpcSenderWithContext::new(stream);
sender
@@ -357,15 +345,11 @@ fn session_probe_accepts_responding_socket() {
#[test]
fn session_probe_rejects_dead_socket() {
- // Simulates discovering a stale socket file with no listener.
- // get_sessions() filters these out via assert_socket() which tries to connect.
let (_guard, name) = new_ipc();
- // Bind and immediately drop the listener to create a stale socket file
{
let _listener = bind_listener(&name);
}
- // Listener is dropped -- the socket file may still exist but nobody is listening
let result = try_connect(&name);
assert!(
@@ -380,21 +364,17 @@ fn socket_directory_enumeration_finds_sockets() {
use interprocess::local_socket::GenericFilePath;
use std::os::unix::fs::FileTypeExt;
- // Simulates the readdir + is_socket() filtering pattern from get_sessions().
let dir = tempfile::TempDir::new().expect("failed to create temp dir");
- // Create a socket
let sock_path = dir.path().join("test-session");
let _listener = ListenerOptions::new()
.name(sock_path.as_path().to_fs_name::<GenericFilePath>().unwrap())
.create_sync()
.expect("bind failed");
- // Create a regular file (should be filtered out)
let file_path = dir.path().join("not-a-session");
std::fs::write(&file_path, b"data").expect("write failed");
- // Enumerate the directory, filtering for sockets (same pattern as get_sessions)
let entries: Vec<String> = std::fs::read_dir(dir.path())
.expect("read_dir failed")
.filter_map(|entry| {
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
diff --git a/zellij-utils/src/pane_size.rs b/zellij-utils/src/pane_size.rs
index 4ba87326..454e73c5 100644
--- a/zellij-utils/src/pane_size.rs
+++ b/zellij-utils/src/pane_size.rs
@@ -68,7 +68,7 @@ pub struct Offset {
pub left: usize,
}
-#[derive(Clone, Copy, Default, PartialEq, Debug, Serialize, Deserialize)]
+#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Serialize, Deserialize)]
pub struct Size {
pub rows: usize,
pub cols: usize,
@@ -80,6 +80,14 @@ pub struct SizeInPixels {
pub width: usize,
}
+#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Serialize, Deserialize)]
+pub struct Insets {
+ pub top: usize,
+ pub bottom: usize,
+ pub left: usize,
+ pub right: usize,
+}
+
#[derive(Eq, Clone, Copy, PartialEq, Debug, Serialize, Deserialize, Hash)]
pub struct Dimension {
pub constraint: Constraint,
diff --git a/zellij-utils/src/plugin_api/action.proto b/zellij-utils/src/plugin_api/action.proto
index 28a8337e..7eedf393 100644
--- a/zellij-utils/src/plugin_api/action.proto
+++ b/zellij-utils/src/plugin_api/action.proto
@@ -586,6 +586,8 @@ message MouseEventPayload {
bool ctrl = 9;
int64 line = 10;
int64 column = 11;
+ bool wheel_left = 12;
+ bool wheel_right = 13;
}
message RunCommandAction {
diff --git a/zellij-utils/src/plugin_api/action.rs b/zellij-utils/src/plugin_api/action.rs
index cd3e1522..34b3adce 100644
--- a/zellij-utils/src/plugin_api/action.rs
+++ b/zellij-utils/src/plugin_api/action.rs
@@ -1943,7 +1943,8 @@ impl TryFrom<Action> for ProtobufAction {
| Action::SaveSession
| Action::ListTabs { .. }
| Action::CurrentTabInfo { .. }
- | Action::SetPaneColor { .. } => Err("Unsupported action"),
+ | Action::SetPaneColor { .. }
+ | Action::ToggleMobileMode => Err("Unsupported action"),
}
}
}
@@ -2097,6 +2098,8 @@ impl TryFrom<ProtobufMouseEventPayload> for MouseEvent {
middle: protobuf_event.middle as bool,
wheel_up: protobuf_event.wheel_up as bool,
wheel_down: protobuf_event.wheel_down as bool,
+ wheel_left: protobuf_event.wheel_left as bool,
+ wheel_right: protobuf_event.wheel_right as bool,
shift: protobuf_event.shift as bool,
alt: protobuf_event.alt as bool,
ctrl: protobuf_event.ctrl as bool,
@@ -2119,6 +2122,8 @@ impl TryFrom<MouseEvent> for ProtobufMouseEventPayload {
middle: event.middle as bool,
wheel_up: event.wheel_up as bool,
wheel_down: event.wheel_down as bool,
+ wheel_left: event.wheel_left as bool,
+ wheel_right: event.wheel_right as bool,
shift: event.shift as bool,
alt: event.alt as bool,
ctrl: event.ctrl as bool,
diff --git a/zellij-utils/src/plugin_api/event.proto b/zellij-utils/src/plugin_api/event.proto
index 5334f434..2567c657 100644
--- a/zellij-utils/src/plugin_api/event.proto
+++ b/zellij-utils/src/plugin_api/event.proto
@@ -69,6 +69,7 @@ enum EventType {
InitialKeybinds = 44;
CommandChanged = 45;
HostTerminalThemeChanged = 46;
+ SoftKeyboardVisibilityChanged = 47;
}
message EventNameList {
@@ -117,6 +118,7 @@ message Event {
InitialKeybindsPayload initial_keybinds_payload = 38;
CommandChangedPayload command_changed_payload = 39;
HostTerminalThemeChangedPayload host_terminal_theme_changed_payload = 40;
+ SoftKeyboardVisibilityChangedPayload soft_keyboard_visibility_changed_payload = 41;
}
}
@@ -124,6 +126,10 @@ message HostTerminalThemeChangedPayload {
HostTerminalThemeIndication mode = 1;
}
+message SoftKeyboardVisibilityChangedPayload {
+ bool visible = 1;
+}
+
enum HostTerminalThemeIndication {
Dark = 0;
Light = 1;
@@ -305,6 +311,8 @@ enum MouseEventName {
MouseHold = 4;
MouseRelease = 5;
MouseHover = 6;
+ MouseScrollLeft = 7;
+ MouseScrollRight = 8;
}
message TabUpdatePayload {
@@ -512,6 +520,7 @@ message PaneContents {
optional SelectedText selected_text = 2;
repeated string lines_above_viewport = 3;
repeated string lines_below_viewport = 4;
+ optional action.Position cursor = 5;
}
message PaneScrollbackResponse {
diff --git a/zellij-utils/src/plugin_api/event.rs b/zellij-utils/src/plugin_api/event.rs
index af421fac..5ba106d0 100644
--- a/zellij-utils/src/plugin_api/event.rs
+++ b/zellij-utils/src/plugin_api/event.rs
@@ -26,6 +26,7 @@ pub use super::generated_api::api::{
PluginConfigurationChangedPayload as ProtobufPluginConfigurationChangedPayload,
PluginInfo as ProtobufPluginInfo, ResurrectableSession as ProtobufResurrectableSession,
SelectedText as ProtobufSelectedText, SessionManifest as ProtobufSessionManifest,
+ SoftKeyboardVisibilityChangedPayload as ProtobufSoftKeyboardVisibilityChangedPayload,
SyntaxError as ProtobufSyntaxError, TabInfo as ProtobufTabInfo,
TabMetadata as ProtobufTabMetadata, UserActionPayload as ProtobufUserActionPayload,
WebServerStatusPayload as ProtobufWebServerStatusPayload, WebSharing as ProtobufWebSharing,
@@ -588,6 +589,14 @@ impl TryFrom<ProtobufEvent> for Event {
},
_ => Err("Malformed payload for HostTerminalThemeChanged Event"),
},
+ Some(ProtobufEventType::SoftKeyboardVisibilityChanged) => {
+ match protobuf_event.payload {
+ Some(ProtobufEventPayload::SoftKeyboardVisibilityChangedPayload(p)) => {
+ Ok(Event::SoftKeyboardVisibilityChanged(p.visible))
+ },
+ _ => Err("Malformed payload for SoftKeyboardVisibilityChanged Event"),
+ }
+ },
None => Err("Unknown Protobuf Event"),
}
}
@@ -1129,6 +1138,15 @@ impl TryFrom<Event> for ProtobufEvent {
payload: Some(event::Payload::HostTerminalThemeChangedPayload(payload)),
})
},
+ Event::SoftKeyboardVisibilityChanged(visible) => {
+ let payload = ProtobufSoftKeyboardVisibilityChangedPayload { visible };
+ Ok(ProtobufEvent {
+ name: ProtobufEventType::SoftKeyboardVisibilityChanged as i32,
+ payload: Some(event::Payload::SoftKeyboardVisibilityChangedPayload(
+ payload,
+ )),
+ })
+ },
Event::InitialKeybinds(keybinds) => {
let mut protobuf_keybinds: Vec<ProtobufInputModeKeybinds> = vec![];
for (input_mode, input_mode_keybinds) in keybinds {
@@ -1624,6 +1642,22 @@ impl TryFrom<MouseEventPayload> for Mouse {
),
_ => Err("Malformed payload for mouse hover"),
},
+ Some(MouseEventName::MouseScrollLeft) => {
+ match mouse_event_payload.mouse_event_payload {
+ Some(mouse_event_payload::MouseEventPayload::LineCount(line_count)) => {
+ Ok(Mouse::ScrollLeft(line_count as usize))
+ },
+ _ => Err("Malformed payload for mouse scroll left"),
+ }
+ },
+ Some(MouseEventName::MouseScrollRight) => {
+ match mouse_event_payload.mouse_event_payload {
+ Some(mouse_event_payload::MouseEventPayload::LineCount(line_count)) => {
+ Ok(Mouse::ScrollRight(line_count as usize))
+ },
+ _ => Err("Malformed payload for mouse scroll right"),
+ }
+ },
None => Err("Malformed payload for MouseEventName"),
}
}
@@ -1690,6 +1724,18 @@ impl TryFrom<Mouse> for MouseEventPayload {
},
)),
}),
+ Mouse::ScrollLeft(cols) => Ok(MouseEventPayload {
+ mouse_event_name: MouseEventName::MouseScrollLeft as i32,
+ mouse_event_payload: Some(mouse_event_payload::MouseEventPayload::LineCount(
+ cols as u32,
+ )),
+ }),
+ Mouse::ScrollRight(cols) => Ok(MouseEventPayload {
+ mouse_event_name: MouseEventName::MouseScrollRight as i32,
+ mouse_event_payload: Some(mouse_event_payload::MouseEventPayload::LineCount(
+ cols as u32,
+ )),
+ }),
}
}
}
@@ -2078,6 +2124,9 @@ impl TryFrom<ProtobufEventType> for EventType {
ProtobufEventType::HighlightClicked => EventType::HighlightClicked,
ProtobufEventType::InitialKeybinds => EventType::InitialKeybinds,
ProtobufEventType::HostTerminalThemeChanged => EventType::HostTerminalThemeChanged,
+ ProtobufEventType::SoftKeyboardVisibilityChanged => {
+ EventType::SoftKeyboardVisibilityChanged
+ },
})
}
}
@@ -2132,6 +2181,9 @@ impl TryFrom<EventType> for ProtobufEventType {
EventType::HighlightClicked => ProtobufEventType::HighlightClicked,
EventType::InitialKeybinds => ProtobufEventType::InitialKeybinds,
EventType::HostTerminalThemeChanged => ProtobufEventType::HostTerminalThemeChanged,
+ EventType::SoftKeyboardVisibilityChanged => {
+ ProtobufEventType::SoftKeyboardVisibilityChanged
+ },
})
}
}
@@ -2986,12 +3038,16 @@ impl TryFrom<ProtobufPaneContents> for PaneContents {
.selected_text
.map(|st| st.try_into())
.transpose()?;
+ let cursor = protobuf_contents
+ .cursor
+ .map(|p| (p.column as usize, p.line as usize));
Ok(PaneContents {
viewport: protobuf_contents.viewport,
selected_text,
lines_above_viewport: protobuf_contents.lines_above_viewport,
lines_below_viewport: protobuf_contents.lines_below_viewport,
+ cursor,
})
}
}
@@ -3003,12 +3059,17 @@ impl TryFrom<PaneContents> for ProtobufPaneContents {
.selected_text
.map(|st| st.try_into())
.transpose()?;
+ let cursor = pane_contents.cursor.map(|(x, y)| ProtobufPosition {
+ line: y as i64,
+ column: x as i64,
+ });
Ok(ProtobufPaneContents {
viewport: pane_contents.viewport,
selected_text,
lines_above_viewport: pane_contents.lines_above_viewport,
lines_below_viewport: pane_contents.lines_below_viewport,
+ cursor,
})
}
}
@@ -3104,6 +3165,7 @@ fn serialize_pane_render_report_with_ansi_event_with_data() {
selected_text: None,
lines_above_viewport: vec![],
lines_below_viewport: vec![],
+ cursor: None,
},
);
let event = Event::PaneRenderReportWithAnsi(pane_contents_map);
diff --git a/zellij-utils/src/plugin_api/plugin_command.proto b/zellij-utils/src/plugin_api/plugin_command.proto
index 729159e3..c65bcbfd 100644
--- a/zellij-utils/src/plugin_api/plugin_command.proto
+++ b/zellij-utils/src/plugin_api/plugin_command.proto
@@ -207,6 +207,12 @@ enum CommandName {
KillSessionsAndReply = 212;
DeleteDeadSessionAndReply = 213;
DeleteAllDeadSessionsAndReply = 214;
+ SetSoftKeyboard = 215;
+ SetTabFit = 216;
+ SetShadowFocus = 219;
+ NewTabUnfocused = 220;
+ NewTiledPaneInTab = 221;
+ ExitMobileMode = 222;
}
message PluginCommand {
@@ -361,6 +367,11 @@ message PluginCommand {
GetSessionListPayload get_session_list_payload = 161;
KillSessionsPayload kill_sessions_and_reply_payload = 162;
string delete_dead_session_and_reply_payload = 163;
+ SetSoftKeyboardPayload set_soft_keyboard_payload = 164;
+ SetTabFitPayload set_tab_fit_payload = 165;
+ PaneId set_shadow_focus_payload = 167;
+ NewTabUnfocusedPayload new_tab_unfocused_payload = 168;
+ NewTiledPaneInTabPayload new_tiled_pane_in_tab_payload = 169;
}
}
@@ -369,6 +380,15 @@ message NewTabPayload {
optional string cwd = 2;
}
+message NewTabUnfocusedPayload {
+ optional string name = 1;
+ optional string cwd = 2;
+}
+
+message NewTiledPaneInTabPayload {
+ uint32 tab_position = 1;
+}
+
message DumpSessionLayoutPayload {
optional uint32 tab_index = 1;
}
@@ -1057,6 +1077,17 @@ message NewTabResponse {
}
}
+message NewTabUnfocusedResponse {
+ oneof result {
+ uint64 tab_id = 1;
+ bool none = 2;
+ }
+}
+
+message NewTiledPaneInTabResponse {
+ optional PaneId pane_id = 1;
+}
+
message NewTabsResponse {
repeated uint64 tab_ids = 1; // Only includes successfully created tabs
}
@@ -1328,3 +1359,18 @@ message DeleteDeadSessionResponse {
message DeleteAllDeadSessionsResponse {
optional string error = 1; // absent => success
}
+
+message SetSoftKeyboardPayload {
+ bool on = 1;
+}
+
+message Size {
+ uint32 rows = 1;
+ uint32 cols = 2;
+}
+
+message SetTabFitPayload {
+ uint32 tab_id = 1;
+ PaneId pane_id = 2;
+ Size size = 3;
+}
diff --git a/zellij-utils/src/plugin_api/plugin_command.rs b/zellij-utils/src/plugin_api/plugin_command.rs
index fa77739a..42b0774f 100644
--- a/zellij-utils/src/plugin_api/plugin_command.rs
+++ b/zellij-utils/src/plugin_api/plugin_command.rs
@@ -13,9 +13,9 @@ pub use super::generated_api::api::{
get_focused_pane_info_response, get_pane_cwd_response, get_pane_pid_response,
get_pane_running_command_response, get_session_list_response, hide_floating_panes_response,
highlight_style::Style as ProtobufHighlightStyleVariant, new_tab_response,
- parse_layout_response, plugin_command::Payload, rename_layout_response,
- save_layout_response, save_session_response, show_floating_panes_response,
- BreakPanesToNewTabPayload,
+ new_tab_unfocused_response, parse_layout_response, plugin_command::Payload,
+ rename_layout_response, save_layout_response, save_session_response,
+ show_floating_panes_response, BreakPanesToNewTabPayload,
BreakPanesToNewTabResponse as ProtobufBreakPanesToNewTabResponse,
BreakPanesToTabWithIdPayload,
BreakPanesToTabWithIdResponse as ProtobufBreakPanesToTabWithIdResponse,
@@ -65,8 +65,10 @@ pub use super::generated_api::api::{
KillSessionsResponse as ProtobufKillSessionsResponse, ListTokensResponse,
LoadNewPluginPayload, MessageToPluginPayload, MovePaneWithPaneIdInDirectionPayload,
MovePaneWithPaneIdPayload, MovePayload, NewPluginArgs as ProtobufNewPluginArgs,
- NewTabPayload, NewTabResponse as ProtobufNewTabResponse,
+ NewTabPayload, NewTabResponse as ProtobufNewTabResponse, NewTabUnfocusedPayload,
+ NewTabUnfocusedResponse as ProtobufNewTabUnfocusedResponse,
NewTabsResponse as ProtobufNewTabsResponse, NewTabsWithLayoutInfoPayload,
+ NewTiledPaneInTabPayload, NewTiledPaneInTabResponse as ProtobufNewTiledPaneInTabResponse,
OpenCommandPaneBackgroundResponse as ProtobufOpenCommandPaneBackgroundResponse,
OpenCommandPaneFloatingNearPluginPayload,
OpenCommandPaneFloatingNearPluginResponse as ProtobufOpenCommandPaneFloatingNearPluginResponse,
@@ -119,13 +121,15 @@ pub use super::generated_api::api::{
ScrollToBottomInPaneIdPayload, ScrollToTopInPaneIdPayload, ScrollUpInPaneIdPayload,
SessionListSnapshot as ProtobufSessionListSnapshot, SetFloatingPanePinnedPayload,
SetPaneBorderlessPayload, SetPaneColorPayload, SetPaneRegexHighlightsPayload,
- SetSelfMouseSelectionSupportPayload, SetTimeoutPayload, ShowCursorPayload,
+ SetSelfMouseSelectionSupportPayload,
+ SetSoftKeyboardPayload as ProtobufSetSoftKeyboardPayload,
+ SetTabFitPayload as ProtobufSetTabFitPayload, SetTimeoutPayload, ShowCursorPayload,
ShowFloatingPanesPayload as ProtobufShowFloatingPanesPayload,
ShowFloatingPanesResponse as ProtobufShowFloatingPanesResponse, ShowPaneWithIdPayload,
- StackPanesPayload, SubscribePayload, SwitchSessionPayload, SwitchTabToIdPayload,
- SwitchTabToPayload, TogglePaneBorderlessPayload, TogglePaneEmbedOrEjectForPaneIdPayload,
- TogglePaneIdFullscreenPayload, UnsubscribePayload, WebRequestPayload,
- WriteCharsToPaneIdPayload, WriteToPaneIdPayload,
+ Size as ProtobufSize, StackPanesPayload, SubscribePayload, SwitchSessionPayload,
+ SwitchTabToIdPayload, SwitchTabToPayload, TogglePaneBorderlessPayload,
+ TogglePaneEmbedOrEjectForPaneIdPayload, TogglePaneIdFullscreenPayload, UnsubscribePayload,
+ WebRequestPayload, WriteCharsToPaneIdPayload, WriteToPaneIdPayload,
},
plugin_permission::PermissionType as ProtobufPermissionType,
resize::ResizeAction as ProtobufResizeAction,
@@ -141,11 +145,26 @@ use crate::data::{
};
use crate::input::actions::Action;
use crate::input::layout::PercentOrFixed;
+use crate::pane_size::Size;
use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::path::PathBuf;
+fn size_from_protobuf(size: ProtobufSize) -> Size {
+ Size {
+ rows: size.rows as usize,
+ cols: size.cols as usize,
+ }
+}
+
+fn size_to_protobuf(size: Size) -> ProtobufSize {
+ ProtobufSize {
+ rows: size.rows as u32,
+ cols: size.cols as u32,
+ }
+}
+
impl Into<FloatingPaneCoordinates> for ProtobufFloatingPaneCoordinates {
fn into(self) -> FloatingPaneCoordinates {
FloatingPaneCoordinates {
@@ -900,6 +919,27 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
}),
_ => Err("Mismatched payload for NewTab"),
},
+ Some(CommandName::NewTabUnfocused) => match protobuf_plugin_command.payload {
+ Some(Payload::NewTabUnfocusedPayload(payload)) => {
+ Ok(PluginCommand::NewTabUnfocused {
+ name: payload.name,
+ cwd: payload.cwd,
+ })
+ },
+ None => Ok(PluginCommand::NewTabUnfocused {
+ name: None,
+ cwd: None,
+ }),
+ _ => Err("Mismatched payload for NewTabUnfocused"),
+ },
+ Some(CommandName::NewTiledPaneInTab) => match protobuf_plugin_command.payload {
+ Some(Payload::NewTiledPaneInTabPayload(payload)) => {
+ Ok(PluginCommand::NewTiledPaneInTab {
+ tab_position: payload.tab_position as usize,
+ })
+ },
+ _ => Err("Mismatched payload for NewTiledPaneInTab"),
+ },
Some(CommandName::GoToNextTab) => {
if protobuf_plugin_command.payload.is_some() {
return Err("GoToNextTab should not have a payload");
@@ -1448,6 +1488,38 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
Some(CommandName::DeleteAllDeadSessionsAndReply) => {
Ok(PluginCommand::DeleteAllDeadSessionsAndReply)
},
+ Some(CommandName::SetSoftKeyboard) => match protobuf_plugin_command.payload {
+ Some(Payload::SetSoftKeyboardPayload(payload)) => {
+ Ok(PluginCommand::SetSoftKeyboard(payload.on))
+ },
+ _ => Err("Mismatched payload for SetSoftKeyboard"),
+ },
+ Some(CommandName::SetTabFit) => match protobuf_plugin_command.payload {
+ Some(Payload::SetTabFitPayload(payload)) => {
+ let fit = match (payload.pane_id, payload.size) {
+ (Some(pane_id), Some(size)) => {
+ Some((pane_id.try_into()?, size_from_protobuf(size)))
+ },
+ (None, None) => None,
+ _ => return Err("Malformed set_tab_fit_payload payload"),
+ };
+ Ok(PluginCommand::SetTabFit {
+ tab_id: payload.tab_id as usize,
+ fit,
+ })
+ },
+ _ => Err("Mismatched payload for SetTabFit"),
+ },
+ Some(CommandName::ExitMobileMode) => match protobuf_plugin_command.payload {
+ Some(_) => Err("ExitMobileMode should have no payload, found a payload"),
+ None => Ok(PluginCommand::ExitMobileMode),
+ },
+ Some(CommandName::SetShadowFocus) => match protobuf_plugin_command.payload {
+ Some(Payload::SetShadowFocusPayload(pane_id)) => {
+ Ok(PluginCommand::SetShadowFocus(pane_id.try_into()?))
+ },
+ _ => Err("Mismatched payload for SetShadowFocus"),
+ },
Some(CommandName::DumpSessionLayout) => match protobuf_plugin_command.payload {
Some(Payload::DumpSessionLayoutPayload(payload)) => {
Ok(PluginCommand::DumpSessionLayout {
@@ -2890,6 +2962,21 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
name: CommandName::NewTab as i32,
payload: Some(Payload::NewTabPayload(NewTabPayload { name, cwd })),
}),
+ PluginCommand::NewTabUnfocused { name, cwd } => Ok(ProtobufPluginCommand {
+ name: CommandName::NewTabUnfocused as i32,
+ payload: Some(Payload::NewTabUnfocusedPayload(NewTabUnfocusedPayload {
+ name,
+ cwd,
+ })),
+ }),
+ PluginCommand::NewTiledPaneInTab { tab_position } => Ok(ProtobufPluginCommand {
+ name: CommandName::NewTiledPaneInTab as i32,
+ payload: Some(Payload::NewTiledPaneInTabPayload(
+ NewTiledPaneInTabPayload {
+ tab_position: tab_position as u32,
+ },
+ )),
+ }),
PluginCommand::GoToNextTab => Ok(ProtobufPluginCommand {
name: CommandName::GoToNextTab as i32,
payload: None,
@@ -3315,6 +3402,32 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
name: CommandName::DeleteAllDeadSessionsAndReply as i32,
payload: None,
}),
+ PluginCommand::SetSoftKeyboard(on) => Ok(ProtobufPluginCommand {
+ name: CommandName::SetSoftKeyboard as i32,
+ payload: Some(Payload::SetSoftKeyboardPayload(
+ ProtobufSetSoftKeyboardPayload { on },
+ )),
+ }),
+ PluginCommand::SetTabFit { tab_id, fit } => {
+ let (pane_id, size) = match fit {
+ Some((pane_id, size)) => {
+ (Some(pane_id.try_into()?), Some(size_to_protobuf(size)))
+ },
+ None => (None, None),
+ };
+ Ok(ProtobufPluginCommand {
+ name: CommandName::SetTabFit as i32,
+ payload: Some(Payload::SetTabFitPayload(ProtobufSetTabFitPayload {
+ tab_id: tab_id as u32,
+ pane_id,
+ size,
+ })),
+ })
+ },
+ PluginCommand::ExitMobileMode => Ok(ProtobufPluginCommand {
+ name: CommandName::ExitMobileMode as i32,
+ payload: None,
+ }),
PluginCommand::DumpSessionLayout { tab_index } => Ok(ProtobufPluginCommand {
name: CommandName::DumpSessionLayout as i32,
payload: tab_index.map(|idx| {
@@ -3437,6 +3550,10 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
name: CommandName::SendSigintToPaneId as i32,
payload: Some(Payload::SendSigintToPaneIdPayload(pane_id.try_into()?)),
}),
+ PluginCommand::SetShadowFocus(pane_id) => Ok(ProtobufPluginCommand {
+ name: CommandName::SetShadowFocus as i32,
+ payload: Some(Payload::SetShadowFocusPayload(pane_id.try_into()?)),
+ }),
PluginCommand::SendSigkillToPaneId(pane_id) => Ok(ProtobufPluginCommand {
name: CommandName::SendSigkillToPaneId as i32,
payload: Some(Payload::SendSigkillToPaneIdPayload(pane_id.try_into()?)),
@@ -4433,7 +4550,8 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
// Conversion implementations for tab creation response types
use crate::data::{
BreakPanesToNewTabResponse, BreakPanesToTabWithIdResponse, BreakPanesToTabWithIndexResponse,
- FocusOrCreateTabResponse, NewTabResponse, NewTabsResponse, OpenCommandPaneBackgroundResponse,
+ FocusOrCreateTabResponse, NewTabResponse, NewTabUnfocusedResponse, NewTabsResponse,
+ NewTiledPaneInTabResponse, OpenCommandPaneBackgroundResponse,
OpenCommandPaneFloatingNearPluginResponse, OpenCommandPaneFloatingResponse,
OpenCommandPaneInPlaceOfPaneIdResponse, OpenCommandPaneInPlaceOfPluginResponse,
OpenCommandPaneInPlaceResponse, OpenCommandPaneNearPluginResponse, OpenCommandPaneResponse,
@@ -4468,6 +4586,47 @@ impl From<NewTabResponse> for ProtobufNewTabResponse {
}
}
+impl TryFrom<ProtobufNewTabUnfocusedResponse> for NewTabUnfocusedResponse {
+ type Error = &'static str;
+ fn try_from(protobuf: ProtobufNewTabUnfocusedResponse) -> Result<Self, Self::Error> {
+ match protobuf.result {
+ Some(new_tab_unfocused_response::Result::TabId(id)) => Ok(Some(id as usize)),
+ Some(new_tab_unfocused_response::Result::None(_)) | None => Ok(None),
+ }
+ }
+}
+
+impl From<NewTabUnfocusedResponse> for ProtobufNewTabUnfocusedResponse {
+ fn from(response: NewTabUnfocusedResponse) -> Self {
+ match response {
+ Some(tab_id) => ProtobufNewTabUnfocusedResponse {
+ result: Some(new_tab_unfocused_response::Result::TabId(tab_id as u64)),
+ },
+ None => ProtobufNewTabUnfocusedResponse {
+ result: Some(new_tab_unfocused_response::Result::None(true)),
+ },
+ }
+ }
+}
+
+impl TryFrom<ProtobufNewTiledPaneInTabResponse> for NewTiledPaneInTabResponse {
+ type Error = &'static str;
+ fn try_from(protobuf: ProtobufNewTiledPaneInTabResponse) -> Result<Self, Self::Error> {
+ match protobuf.pane_id {
+ Some(pane_id) => Ok(Some(pane_id.try_into()?)),
+ None => Ok(None),
+ }
+ }
+}
+
+impl From<NewTiledPaneInTabResponse> for ProtobufNewTiledPaneInTabResponse {
+ fn from(response: NewTiledPaneInTabResponse) -> Self {
+ ProtobufNewTiledPaneInTabResponse {
+ pane_id: response.map(|p| p.try_into().unwrap()),
+ }
+ }
+}
+
impl TryFrom<ProtobufNewTabsResponse> for NewTabsResponse {
type Error = &'static str;
fn try_from(protobuf: ProtobufNewTabsResponse) -> Result<Self, Self::Error> {
@@ -5053,3 +5212,47 @@ impl From<OpenPluginPaneFloatingResponse> for ProtobufOpenPluginPaneFloatingResp
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::data::{PaneId, PluginCommand};
+ use crate::pane_size::Size;
+
+ #[test]
+ fn set_tab_fit_set_protobuf_round_trip() {
+ let original = PluginCommand::SetTabFit {
+ tab_id: 7,
+ fit: Some((PaneId::Terminal(3), Size { rows: 24, cols: 80 })),
+ };
+ let protobuf: ProtobufPluginCommand = original.try_into().expect("encode");
+ let decoded: PluginCommand = protobuf.try_into().expect("decode");
+ match decoded {
+ PluginCommand::SetTabFit { tab_id, fit } => {
+ assert_eq!(tab_id, 7);
+ assert_eq!(
+ fit,
+ Some((PaneId::Terminal(3), Size { rows: 24, cols: 80 }))
+ );
+ },
+ other => panic!("expected SetTabFit, got {:?}", other),
+ }
+ }
+
+ #[test]
+ fn set_tab_fit_clear_protobuf_round_trip() {
+ let original = PluginCommand::SetTabFit {
+ tab_id: 7,
+ fit: None,
+ };
+ let protobuf: ProtobufPluginCommand = original.try_into().expect("encode");
+ let decoded: PluginCommand = protobuf.try_into().expect("decode");
+ match decoded {
+ PluginCommand::SetTabFit { tab_id, fit } => {
+ assert_eq!(tab_id, 7);
+ assert_eq!(fit, None);
+ },
+ other => panic!("expected SetTabFit, got {:?}", other),
+ }
+ }
+}
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_config_options.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_config_options.snap
index a13669e5..0d51e89b 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_config_options.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_config_options.snap
@@ -52,4 +52,7 @@ Options {
enforce_https_for_localhost: None,
post_command_discovery_hook: None,
client_async_worker_tasks: None,
+ mobile_layout: None,
+ mobile_threshold_cols: None,
+ mobile_threshold_rows: None,
}
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options.snap
index 9a0e7eda..1b0c154a 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options.snap
@@ -52,4 +52,7 @@ Options {
enforce_https_for_localhost: None,
post_command_discovery_hook: None,
client_async_worker_tasks: None,
+ mobile_layout: None,
+ mobile_threshold_cols: None,
+ mobile_threshold_rows: None,
}
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-3.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-3.snap
index 52734174..08b89aac 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-3.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-3.snap
@@ -50,4 +50,7 @@ Options {
enforce_https_for_localhost: None,
post_command_discovery_hook: None,
client_async_worker_tasks: None,
+ mobile_layout: None,
+ mobile_threshold_cols: None,
+ mobile_threshold_rows: None,
}
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap
index 2b17d5a3..a8a144a8 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap
@@ -6028,6 +6028,9 @@ Config {
enforce_https_for_localhost: None,
post_command_discovery_hook: None,
client_async_worker_tasks: None,
+ mobile_layout: None,
+ mobile_threshold_cols: None,
+ mobile_threshold_rows: None,
},
themes: {},
plugins: PluginAliases {
@@ -6082,6 +6085,18 @@ Config {
"/",
),
},
+ "mobile": RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: Zellij(
+ PluginTag(
+ "mobile",
+ ),
+ ),
+ configuration: PluginUserConfiguration(
+ {},
+ ),
+ initial_cwd: None,
+ },
"plugin-manager": RunPlugin {
_allow_exec_host_cmd: false,
location: Zellij(
@@ -6189,5 +6204,6 @@ Config {
cursor_style: None,
mac_option_is_meta: true,
base_url: None,
+ font_size: None,
},
}
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap
index 1f1e8268..4792ab0c 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap
@@ -6028,6 +6028,9 @@ Config {
enforce_https_for_localhost: None,
post_command_discovery_hook: None,
client_async_worker_tasks: None,
+ mobile_layout: None,
+ mobile_threshold_cols: None,
+ mobile_threshold_rows: None,
},
themes: {},
plugins: PluginAliases {
@@ -6082,6 +6085,18 @@ Config {
"/",
),
},
+ "mobile": RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: Zellij(
+ PluginTag(
+ "mobile",
+ ),
+ ),
+ configuration: PluginUserConfiguration(
+ {},
+ ),
+ initial_cwd: None,
+ },
"plugin-manager": RunPlugin {
_allow_exec_host_cmd: false,
location: Zellij(
@@ -6193,5 +6208,6 @@ Config {
cursor_style: None,
mac_option_is_meta: true,
base_url: None,
+ font_size: None,
},
}
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap
index 8ad32dc7..8f5baed6 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap
@@ -137,6 +137,9 @@ Config {
enforce_https_for_localhost: None,
post_command_discovery_hook: None,
client_async_worker_tasks: None,
+ mobile_layout: None,
+ mobile_threshold_cols: None,
+ mobile_threshold_rows: None,
},
themes: {},
plugins: PluginAliases {
@@ -191,6 +194,18 @@ Config {
"/",
),
},
+ "mobile": RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: Zellij(
+ PluginTag(
+ "mobile",
+ ),
+ ),
+ configuration: PluginUserConfiguration(
+ {},
+ ),
+ initial_cwd: None,
+ },
"plugin-manager": RunPlugin {
_allow_exec_host_cmd: false,
location: Zellij(
@@ -298,5 +313,6 @@ Config {
cursor_style: None,
mac_option_is_meta: true,
base_url: None,
+ font_size: None,
},
}
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options.snap
index 74927ae6..40822e6e 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options.snap
@@ -52,4 +52,7 @@ Options {
enforce_https_for_localhost: None,
post_command_discovery_hook: None,
client_async_worker_tasks: None,
+ mobile_layout: None,
+ mobile_threshold_cols: None,
+ mobile_threshold_rows: None,
}
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap
index 28c7b32d..2cb1c9af 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap
@@ -6028,6 +6028,9 @@ Config {
enforce_https_for_localhost: None,
post_command_discovery_hook: None,
client_async_worker_tasks: None,
+ mobile_layout: None,
+ mobile_threshold_cols: None,
+ mobile_threshold_rows: None,
},
themes: {
"other-theme-from-config": Theme {
@@ -7853,6 +7856,18 @@ Config {
"/",
),
},
+ "mobile": RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: Zellij(
+ PluginTag(
+ "mobile",
+ ),
+ ),
+ configuration: PluginUserConfiguration(
+ {},
+ ),
+ initial_cwd: None,
+ },
"plugin-manager": RunPlugin {
_allow_exec_host_cmd: false,
location: Zellij(
@@ -7960,5 +7975,6 @@ Config {
cursor_style: None,
mac_option_is_meta: true,
base_url: None,
+ font_size: None,
},
}
diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap
index 43f29c48..431c8fc4 100644
--- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap
+++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap
@@ -6028,6 +6028,9 @@ Config {
enforce_https_for_localhost: None,
post_command_discovery_hook: None,
client_async_worker_tasks: None,
+ mobile_layout: None,
+ mobile_threshold_cols: None,
+ mobile_threshold_rows: None,
},
themes: {},
plugins: PluginAliases {
@@ -6082,6 +6085,18 @@ Config {
"/",
),
},
+ "mobile": RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: Zellij(
+ PluginTag(
+ "mobile",
+ ),
+ ),
+ configuration: PluginUserConfiguration(
+ {},
+ ),
+ initial_cwd: None,
+ },
"plugin-manager": RunPlugin {
_allow_exec_host_cmd: false,
location: Zellij(
@@ -6189,5 +6204,6 @@ Config {
cursor_style: None,
mac_option_is_meta: true,
base_url: None,
+ font_size: None,
},
}