diff --git a/Cargo.lock b/Cargo.lock
index 58411ab..496717f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,6 +2,16 @@
# It is not intended for manual editing.
version = 4
+[[package]]
+name = "Inflector"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
+dependencies = [
+ "lazy_static",
+ "regex",
+]
+
[[package]]
name = "addr2line"
version = "0.24.2"
@@ -361,9 +371,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "clap"
-version = "4.5.38"
+version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000"
+checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
dependencies = [
"clap_builder",
"clap_derive",
@@ -371,9 +381,9 @@ dependencies = [
[[package]]
name = "clap_builder"
-version = "4.5.38"
+version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120"
+checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
dependencies = [
"anstream",
"anstyle",
@@ -383,9 +393,9 @@ dependencies = [
[[package]]
name = "clap_derive"
-version = "4.5.32"
+version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
+checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
dependencies = [
"heck",
"proc-macro2",
@@ -1462,6 +1472,16 @@ dependencies = [
"objc2-core-foundation",
]
+[[package]]
+name = "objc2-io-kit"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a"
+dependencies = [
+ "libc",
+ "objc2-core-foundation",
+]
+
[[package]]
name = "object"
version = "0.36.7"
@@ -1941,6 +1961,12 @@ dependencies = [
"windows-sys 0.59.0",
]
+[[package]]
+name = "rustversion"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
+
[[package]]
name = "ryu"
version = "1.0.20"
@@ -2069,6 +2095,25 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+[[package]]
+name = "strum"
+version = "0.27.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32"
+
+[[package]]
+name = "strum_macros"
+version = "0.27.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn 2.0.101",
+]
+
[[package]]
name = "syn"
version = "1.0.109"
@@ -2093,15 +2138,16 @@ dependencies = [
[[package]]
name = "sysinfo"
-version = "0.34.2"
+version = "0.35.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4b93974b3d3aeaa036504b8eefd4c039dced109171c1ae973f1dc63b2c7e4b2"
+checksum = "3c3ffa3e4ff2b324a57f7aeb3c349656c7b127c3c189520251a648102a92496e"
dependencies = [
"libc",
"memchr",
"ntapi",
"objc2-core-foundation",
- "windows 0.57.0",
+ "objc2-io-kit",
+ "windows",
]
[[package]]
@@ -2131,7 +2177,7 @@ checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9"
dependencies = [
"quick-xml",
"thiserror 2.0.12",
- "windows 0.61.1",
+ "windows",
"windows-version",
]
@@ -2501,16 +2547,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
-[[package]]
-name = "windows"
-version = "0.57.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143"
-dependencies = [
- "windows-core 0.57.0",
- "windows-targets 0.52.6",
-]
-
[[package]]
name = "windows"
version = "0.61.1"
@@ -2518,7 +2554,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419"
dependencies = [
"windows-collections",
- "windows-core 0.61.2",
+ "windows-core",
"windows-future",
"windows-link",
"windows-numerics",
@@ -2530,19 +2566,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
dependencies = [
- "windows-core 0.61.2",
-]
-
-[[package]]
-name = "windows-core"
-version = "0.57.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d"
-dependencies = [
- "windows-implement 0.57.0",
- "windows-interface 0.57.0",
- "windows-result 0.1.2",
- "windows-targets 0.52.6",
+ "windows-core",
]
[[package]]
@@ -2551,10 +2575,10 @@ version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
dependencies = [
- "windows-implement 0.60.0",
- "windows-interface 0.59.1",
+ "windows-implement",
+ "windows-interface",
"windows-link",
- "windows-result 0.3.4",
+ "windows-result",
"windows-strings",
]
@@ -2564,22 +2588,11 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
dependencies = [
- "windows-core 0.61.2",
+ "windows-core",
"windows-link",
"windows-threading",
]
-[[package]]
-name = "windows-implement"
-version = "0.57.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.101",
-]
-
[[package]]
name = "windows-implement"
version = "0.60.0"
@@ -2591,17 +2604,6 @@ dependencies = [
"syn 2.0.101",
]
-[[package]]
-name = "windows-interface"
-version = "0.57.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.101",
-]
-
[[package]]
name = "windows-interface"
version = "0.59.1"
@@ -2625,19 +2627,10 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
dependencies = [
- "windows-core 0.61.2",
+ "windows-core",
"windows-link",
]
-[[package]]
-name = "windows-result"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
-dependencies = [
- "windows-targets 0.52.6",
-]
-
[[package]]
name = "windows-result"
version = "0.3.4"
@@ -2867,7 +2860,7 @@ dependencies = [
[[package]]
name = "worf"
-version = "0.2.0"
+version = "0.4.0"
dependencies = [
"clap",
"crossbeam",
@@ -2896,15 +2889,30 @@ dependencies = [
"wl-clipboard-rs",
]
+[[package]]
+name = "worf-hyprspace"
+version = "0.1.0"
+dependencies = [
+ "Inflector",
+ "clap",
+ "env_logger",
+ "hyprland",
+ "log",
+ "nix",
+ "regex",
+ "serde",
+ "strum",
+ "strum_macros",
+ "worf",
+]
+
[[package]]
name = "worf-hyprswitch"
version = "0.1.0"
dependencies = [
- "dirs 6.0.0",
"env_logger",
"freedesktop-icons",
"hyprland",
- "log",
"rayon",
"sysinfo",
"toml",
diff --git a/Cargo.toml b/Cargo.toml
index fbd68fa..fc39350 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,6 +3,7 @@ members = [
"worf",
"examples/worf-warden",
"examples/worf-hyprswitch",
+ "examples/worf-hyprspace",
]
resolver = "3"
diff --git a/examples/images/hyprspace.png b/examples/images/hyprspace.png
new file mode 100644
index 0000000..f89112b
Binary files /dev/null and b/examples/images/hyprspace.png differ
diff --git a/examples/worf-hyprspace/Cargo.toml b/examples/worf-hyprspace/Cargo.toml
new file mode 100644
index 0000000..cce0224
--- /dev/null
+++ b/examples/worf-hyprspace/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "worf-hyprspace"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+worf = {path = "../../worf"}
+env_logger = "0.11.8"
+hyprland = "0.4.0-beta.2"
+clap = "4.5.40"
+serde = "1.0.219"
+strum = "0.27.1"
+strum_macros = "0.27.1"
+Inflector = "0.11"
+regex = "1.11.1"
+log = "0.4.27"
+nix = "0.30.1"
diff --git a/examples/worf-hyprspace/Readme.md b/examples/worf-hyprspace/Readme.md
new file mode 100644
index 0000000..ac877ef
--- /dev/null
+++ b/examples/worf-hyprspace/Readme.md
@@ -0,0 +1,15 @@
+# Worf Hyprspace
+
+This allows to manage workspaces in hyprland using the Worf API.
+Inspired by https://github.com/sslater11/hyprland-dynamic-workspaces-manager
+
+
+
+## Features
+-Auto: Automatic detection of mode
+-Rename: Change the name of the chosen workspace
+-SwitchToWorkspace: Change the active workspace
+-MoveCurrentWindowToOtherWorkspace: Move the focused window to a new workspace and follow it
+-MoveCurrentWindowToOtherWorkspaceSilent: Move the focused window to a new workspace and don't follow it
+-MoveAllWindowsToOtherWorkSpace: Move all windows to a new workspace
+-DeleteWorkspace: Close all windows and go to another workspace
diff --git a/examples/worf-hyprspace/src/main.rs b/examples/worf-hyprspace/src/main.rs
new file mode 100644
index 0000000..ded3088
--- /dev/null
+++ b/examples/worf-hyprspace/src/main.rs
@@ -0,0 +1,615 @@
+use std::{
+ env,
+ fmt::{Display, Formatter},
+ str::FromStr,
+ sync::{Arc, Mutex, RwLock},
+ thread::sleep,
+ time::{Duration, Instant},
+};
+
+use clap::Parser;
+use hyprland::data::Client;
+use hyprland::dispatch::WindowIdentifier;
+use hyprland::{
+ data::{Workspace, Workspaces},
+ dispatch::{Dispatch, DispatchType, WorkspaceIdentifierWithSpecial},
+ prelude::HyprData,
+ shared::HyprDataActive,
+};
+use nix::libc::{SIGTERM, kill};
+use regex::Regex;
+use serde::Deserialize;
+use strum::IntoEnumIterator;
+use strum_macros::EnumIter;
+use worf::gui::{
+ self, ArcFactory, ArcProvider, ExpandMode, ItemFactory, ItemProvider, MenuItem, ProviderData,
+ Selection,
+};
+
+#[derive(Clone)]
+struct Action {
+ workspace: Option,
+ mode: Mode,
+}
+
+#[derive(Clone)]
+struct HyprspaceProvider {
+ cfg: HyprSpaceConfig,
+ search_ignored_words: Vec,
+ detected_mode: Option,
+}
+
+#[derive(Debug, Clone, Deserialize, EnumIter, PartialEq, Eq)]
+enum Mode {
+ Auto,
+ Rename,
+ SwitchToWorkspace,
+ MoveCurrentWindowToOtherWorkspace,
+ MoveCurrentWindowToOtherWorkspaceSilent,
+ MoveAllWindowsToOtherWorkSpace,
+ DeleteWorkspace,
+}
+
+impl FromStr for Mode {
+ type Err = String;
+
+ fn from_str(s: &str) -> Result {
+ match s.to_lowercase().as_str() {
+ "auto" => Ok(Mode::Auto),
+ "rename" => Ok(Mode::Rename),
+ "switchtoworkspace" => Ok(Mode::SwitchToWorkspace),
+ "movecurrentwindowtootherworkspace" => Ok(Mode::MoveCurrentWindowToOtherWorkspace),
+ "movecurrentwindowtootherworkspacesilent" => {
+ Ok(Mode::MoveCurrentWindowToOtherWorkspaceSilent)
+ }
+ "moveallwindowstootherworkspace" => Ok(Mode::MoveCurrentWindowToOtherWorkspace),
+ "deleteworkspace" => Ok(Mode::DeleteWorkspace),
+ _ => Err(format!("Invalid mode: {s}")),
+ }
+ }
+}
+
+impl Display for Mode {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ let variant = format!("{self:?}");
+ // Convert PascalCase to Title Case with spaces
+ let spaced = inflector::cases::titlecase::to_title_case(&variant);
+ write!(f, "{spaced}")
+ }
+}
+
+#[derive(Debug, Clone, Parser, Deserialize)]
+#[clap(about = "Worf-Hyprspace is a Hyprland workspace manager built on top of Worf")]
+struct HyprSpaceConfig {
+ #[command(flatten)]
+ worf: worf::config::Config,
+
+ #[arg(long)]
+ hypr_space_mode: Option,
+
+ #[arg(long)]
+ add_id_prefix: Option,
+
+ #[arg(long)]
+ max_workspace_id: Option,
+}
+
+impl HyprSpaceConfig {
+ fn hypr_space_mode(&self) -> Mode {
+ self.hypr_space_mode.clone().unwrap_or(Mode::Auto)
+ }
+
+ fn add_id_prefix(&self) -> bool {
+ self.add_id_prefix.unwrap_or(true)
+ }
+
+ fn max_workspace_id(&self) -> i32 {
+ self.max_workspace_id.unwrap_or(10)
+ }
+}
+
+impl HyprspaceProvider {
+ fn new(cfg: &HyprSpaceConfig, search_ignored_words: Vec) -> Result {
+ Ok(Self {
+ cfg: cfg.clone(),
+ search_ignored_words,
+ detected_mode: None,
+ })
+ }
+}
+
+impl ItemProvider for HyprspaceProvider {
+ fn get_elements(&mut self, query: Option<&str>) -> ProviderData {
+ let auto = if self.cfg.hypr_space_mode() == Mode::Auto {
+ query.and_then(|q| {
+ Mode::iter()
+ .find(|m| m.to_string().to_lowercase().trim() == q.to_lowercase())
+ .map(|m| {
+ self.detected_mode = Some(m.clone());
+ ProviderData {
+ items: Some(get_modes_actions(
+ &m,
+ query,
+ self.search_ignored_words.as_ref(),
+ )),
+ }
+ })
+ })
+ } else {
+ self.detected_mode = None;
+ None
+ };
+ auto.unwrap_or(ProviderData {
+ items: Some(get_modes_actions(
+ &self.cfg.hypr_space_mode(),
+ query,
+ self.search_ignored_words.as_ref(),
+ )),
+ })
+ }
+
+ fn get_sub_elements(&mut self, item: &MenuItem) -> ProviderData {
+ if let Some(mode) = Mode::iter()
+ .find(|m| {
+ m.to_string()
+ .to_lowercase()
+ .trim()
+ .contains(&item.label.to_lowercase())
+ })
+ .map(|m| {
+ self.detected_mode = Some(m.clone());
+ ProviderData {
+ items: Some(get_modes_actions(
+ &m,
+ Some(&item.label),
+ self.search_ignored_words.as_ref(),
+ )),
+ }
+ })
+ {
+ mode
+ } else {
+ ProviderData { items: None }
+ }
+ }
+}
+
+impl ItemFactory for HyprspaceProvider {
+ fn new_menu_item(&self, label: String) -> Option