Initial support for workspace pinning and moving

Adds support for cosmic-workspace-v2 pin, unpin, move_after, and
move_before requests.

Both features need some work with workspaces span displays mode, so that
will need more fixes later.

We also want to generate a unique id for pinned workspaces to send in
the ext-workspace-v1 protocol. But that isn't a strict requirement for
anything. So I haven't yet fully implemented that. We'll also want to
persist other things, like workspace naming when that's added.

Overall, though, with separate workspaces per display, this is working
pretty well.
This commit is contained in:
Ian Douglas Scott 2025-01-31 14:13:33 -08:00 committed by Victoria Brekenfeld
parent d1f4e7b12d
commit 96e9bf3b81
14 changed files with 622 additions and 118 deletions

123
Cargo.lock generated
View file

@ -40,10 +40,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"getrandom", "getrandom 0.2.15",
"once_cell", "once_cell",
"version_check", "version_check",
"zerocopy", "zerocopy 0.7.35",
] ]
[[package]] [[package]]
@ -798,7 +798,7 @@ dependencies = [
[[package]] [[package]]
name = "cosmic-client-toolkit" name = "cosmic-client-toolkit"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/pop-os//cosmic-protocols?rev=67df697#67df697105486fa4c9dd6ce00889c8b0526c9bb4" source = "git+https://github.com/pop-os//cosmic-protocols?branch=main#bc4af9183e0967802d7fbe91ba811a29ca6a3b67"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.8.0",
"cosmic-protocols", "cosmic-protocols",
@ -840,7 +840,7 @@ dependencies = [
"ordered-float", "ordered-float",
"png", "png",
"profiling", "profiling",
"rand", "rand 0.9.0",
"regex", "regex",
"reis", "reis",
"ron", "ron",
@ -874,6 +874,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"cosmic-config", "cosmic-config",
"input", "input",
"libdisplay-info",
"serde", "serde",
] ]
@ -921,7 +922,7 @@ dependencies = [
[[package]] [[package]]
name = "cosmic-protocols" name = "cosmic-protocols"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/pop-os//cosmic-protocols?rev=67df697#67df697105486fa4c9dd6ce00889c8b0526c9bb4" source = "git+https://github.com/pop-os//cosmic-protocols?branch=main#bc4af9183e0967802d7fbe91ba811a29ca6a3b67"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.8.0",
"wayland-backend", "wayland-backend",
@ -1907,7 +1908,19 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"wasi", "wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasi 0.14.2+wasi-0.2.4",
] ]
[[package]] [[package]]
@ -3248,7 +3261,7 @@ checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [ dependencies = [
"libc", "libc",
"log", "log",
"wasi", "wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
@ -3872,7 +3885,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
dependencies = [ dependencies = [
"phf_shared", "phf_shared",
"rand", "rand 0.8.5",
] ]
[[package]] [[package]]
@ -4010,7 +4023,7 @@ version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [ dependencies = [
"zerocopy", "zerocopy 0.7.35",
] ]
[[package]] [[package]]
@ -4112,6 +4125,12 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "r-efi"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.5" version = "0.8.5"
@ -4119,8 +4138,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [ dependencies = [
"libc", "libc",
"rand_chacha", "rand_chacha 0.3.1",
"rand_core", "rand_core 0.6.4",
]
[[package]]
name = "rand"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.3",
"zerocopy 0.8.24",
] ]
[[package]] [[package]]
@ -4130,7 +4160,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [ dependencies = [
"ppv-lite86", "ppv-lite86",
"rand_core", "rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.3",
] ]
[[package]] [[package]]
@ -4139,7 +4179,16 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [ dependencies = [
"getrandom", "getrandom 0.2.15",
]
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom 0.3.2",
] ]
[[package]] [[package]]
@ -4209,7 +4258,7 @@ version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
dependencies = [ dependencies = [
"getrandom", "getrandom 0.2.15",
"libredox", "libredox",
"thiserror 1.0.69", "thiserror 1.0.69",
] ]
@ -4714,7 +4763,7 @@ dependencies = [
"pixman", "pixman",
"pkg-config", "pkg-config",
"profiling", "profiling",
"rand", "rand 0.8.5",
"rustix", "rustix",
"scopeguard", "scopeguard",
"smallvec", "smallvec",
@ -4999,7 +5048,7 @@ checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand",
"getrandom", "getrandom 0.2.15",
"once_cell", "once_cell",
"rustix", "rustix",
"windows-sys 0.52.0", "windows-sys 0.52.0",
@ -5582,6 +5631,15 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasi"
version = "0.14.2+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
dependencies = [
"wit-bindgen-rt",
]
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.100" version = "0.2.100"
@ -6356,6 +6414,15 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags 2.8.0",
]
[[package]] [[package]]
name = "write16" name = "write16"
version = "1.0.0" version = "1.0.0"
@ -6548,7 +6615,7 @@ dependencies = [
"hex", "hex",
"nix 0.29.0", "nix 0.29.0",
"ordered-stream", "ordered-stream",
"rand", "rand 0.8.5",
"serde", "serde",
"serde_repr", "serde_repr",
"sha1", "sha1",
@ -6599,7 +6666,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"zerocopy-derive", "zerocopy-derive 0.7.35",
]
[[package]]
name = "zerocopy"
version = "0.8.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879"
dependencies = [
"zerocopy-derive 0.8.24",
] ]
[[package]] [[package]]
@ -6613,6 +6689,17 @@ dependencies = [
"syn 2.0.96", "syn 2.0.96",
] ]
[[package]]
name = "zerocopy-derive"
version = "0.8.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.96",
]
[[package]] [[package]]
name = "zerofrom" name = "zerofrom"
version = "0.1.5" version = "0.1.5"

View file

@ -15,7 +15,7 @@ anyhow = {version = "1.0.51", features = ["backtrace"]}
bitflags = "2.4" bitflags = "2.4"
bytemuck = "1.12" bytemuck = "1.12"
calloop = {version = "0.14.1", features = ["executor"]} calloop = {version = "0.14.1", features = ["executor"]}
cosmic-comp-config = {path = "cosmic-comp-config"} cosmic-comp-config = {path = "cosmic-comp-config", features = ["libdisplay-info"]}
cosmic-config = {git = "https://github.com/pop-os/libcosmic/", features = ["calloop", "macro"]} cosmic-config = {git = "https://github.com/pop-os/libcosmic/", features = ["calloop", "macro"]}
cosmic-protocols = {git = "https://github.com/pop-os/cosmic-protocols", rev = "e706814", default-features = false, features = ["server"]} cosmic-protocols = {git = "https://github.com/pop-os/cosmic-protocols", rev = "e706814", default-features = false, features = ["server"]}
cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon" } cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon" }
@ -59,7 +59,7 @@ zbus = "4.4.0"
profiling = { version = "1.0" } profiling = { version = "1.0" }
rustix = { version = "0.38.32", features = ["process"] } rustix = { version = "0.38.32", features = ["process"] }
smallvec = "1.13.2" smallvec = "1.13.2"
rand = "0.8.5" rand = "0.9.0"
reis = { version = "0.4", features = ["calloop"] } reis = { version = "0.4", features = ["calloop"] }
# CLI arguments # CLI arguments
clap_lex = "0.7" clap_lex = "0.7"
@ -119,8 +119,8 @@ inherits = "release"
lto = "fat" lto = "fat"
[patch."https://github.com/pop-os/cosmic-protocols"] [patch."https://github.com/pop-os/cosmic-protocols"]
cosmic-protocols = { git = "https://github.com/pop-os//cosmic-protocols", rev = "67df697" } cosmic-protocols = { git = "https://github.com/pop-os//cosmic-protocols", branch = "main" }
cosmic-client-toolkit = { git = "https://github.com/pop-os//cosmic-protocols", rev = "67df697" } cosmic-client-toolkit = { git = "https://github.com/pop-os//cosmic-protocols", branch = "main" }
[patch."https://github.com/smithay/smithay"] [patch."https://github.com/smithay/smithay"]
smithay = { git = "https://github.com/smithay/smithay//", rev = "ce61c9b" } smithay = { git = "https://github.com/smithay/smithay//", rev = "ce61c9b" }

View file

@ -6,4 +6,5 @@ edition = "2021"
[dependencies] [dependencies]
cosmic-config = { git = "https://github.com/pop-os/libcosmic/" } cosmic-config = { git = "https://github.com/pop-os/libcosmic/" }
input = "0.9.0" input = "0.9.0"
libdisplay-info = { version = "0.2.0", optional = true }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }

View file

@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
pub mod input; pub mod input;
pub mod output;
pub mod workspace; pub mod workspace;
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
@ -25,6 +26,7 @@ pub enum NumlockState {
#[version = 1] #[version = 1]
pub struct CosmicCompConfig { pub struct CosmicCompConfig {
pub workspaces: workspace::WorkspaceConfig, pub workspaces: workspace::WorkspaceConfig,
pub pinned_workspaces: Vec<workspace::PinnedWorkspace>,
pub input_default: input::InputConfig, pub input_default: input::InputConfig,
pub input_touchpad: input::InputConfig, pub input_touchpad: input::InputConfig,
pub input_devices: HashMap<String, input::InputConfig>, pub input_devices: HashMap<String, input::InputConfig>,
@ -57,6 +59,7 @@ impl Default for CosmicCompConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
workspaces: Default::default(), workspaces: Default::default(),
pinned_workspaces: Vec::new(),
input_default: Default::default(), input_default: Default::default(),
// By default, enable tap-to-click and disable-while-typing. // By default, enable tap-to-click and disable-while-typing.
input_touchpad: input::InputConfig { input_touchpad: input::InputConfig {

View file

@ -0,0 +1,27 @@
// SPDX-License-Identifier: GPL-3.0-only
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct EdidProduct {
pub manufacturer: [char; 3],
pub product: u16,
pub serial: Option<u32>,
pub manufacture_week: i32,
pub manufacture_year: i32,
pub model_year: Option<i32>,
}
#[cfg(feature = "libdisplay-info")]
impl From<libdisplay_info::edid::VendorProduct> for EdidProduct {
fn from(vp: libdisplay_info::edid::VendorProduct) -> Self {
Self {
manufacturer: vp.manufacturer,
product: vp.product,
serial: vp.serial,
manufacture_week: vp.manufacture_week,
manufacture_year: vp.manufacture_year,
model_year: vp.model_year,
}
}
}

View file

@ -2,6 +2,8 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::output::EdidProduct;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WorkspaceConfig { pub struct WorkspaceConfig {
pub workspace_mode: WorkspaceMode, pub workspace_mode: WorkspaceMode,
@ -30,3 +32,16 @@ pub enum WorkspaceLayout {
Vertical, Vertical,
Horizontal, Horizontal,
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OutputMatch {
pub name: String,
pub edid: Option<EdidProduct>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PinnedWorkspace {
pub output: OutputMatch,
pub tiling_enabled: bool,
// TODO: name, id
}

View file

@ -43,6 +43,7 @@ pub use key_bindings::{Action, PrivateAction};
mod types; mod types;
pub use self::types::*; pub use self::types::*;
use cosmic::config::CosmicTk; use cosmic::config::CosmicTk;
pub use cosmic_comp_config::output::EdidProduct;
use cosmic_comp_config::{ use cosmic_comp_config::{
input::InputConfig, workspace::WorkspaceConfig, CosmicCompConfig, KeyboardConfig, TileBehavior, input::InputConfig, workspace::WorkspaceConfig, CosmicCompConfig, KeyboardConfig, TileBehavior,
XkbConfig, XwaylandDescaling, XwaylandEavesdropping, ZoomConfig, XkbConfig, XwaylandDescaling, XwaylandEavesdropping, ZoomConfig,
@ -94,29 +95,6 @@ impl From<Output> for OutputInfo {
} }
} }
#[derive(Debug, Deserialize, Serialize, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct EdidProduct {
pub manufacturer: [char; 3],
pub product: u16,
pub serial: Option<u32>,
pub manufacture_week: i32,
pub manufacture_year: i32,
pub model_year: Option<i32>,
}
impl From<libdisplay_info::edid::VendorProduct> for EdidProduct {
fn from(vp: libdisplay_info::edid::VendorProduct) -> Self {
Self {
manufacturer: vp.manufacturer,
product: vp.product,
serial: vp.serial,
manufacture_week: vp.manufacture_week,
manufacture_year: vp.manufacture_year,
model_year: vp.model_year,
}
}
}
#[derive(Default, Debug, Deserialize, Serialize)] #[derive(Default, Debug, Deserialize, Serialize)]
pub struct NumlockStateConfig { pub struct NumlockStateConfig {
pub last_state: bool, pub last_state: bool,

View file

@ -6,15 +6,20 @@ use layout::TilingExceptions;
use std::{ use std::{
collections::HashMap, collections::HashMap,
sync::{atomic::Ordering, Mutex}, sync::{atomic::Ordering, Mutex},
thread,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use wayland_backend::server::ClientId; use wayland_backend::server::ClientId;
use crate::wayland::{handlers::data_device, protocols::workspace::WorkspaceCapabilities}; use crate::wayland::{
handlers::data_device,
protocols::workspace::{State as WState, WorkspaceCapabilities},
};
use cosmic_comp_config::{ use cosmic_comp_config::{
workspace::{WorkspaceLayout, WorkspaceMode}, workspace::{PinnedWorkspace, WorkspaceLayout, WorkspaceMode},
TileBehavior, ZoomConfig, ZoomMovement, TileBehavior, ZoomConfig, ZoomMovement,
}; };
use cosmic_config::ConfigSet;
use cosmic_protocols::workspace::v2::server::zcosmic_workspace_handle_v2::TilingState; use cosmic_protocols::workspace::v2::server::zcosmic_workspace_handle_v2::TilingState;
use cosmic_settings_config::shortcuts::action::{Direction, FocusDirection, ResizeDirection}; use cosmic_settings_config::shortcuts::action::{Direction, FocusDirection, ResizeDirection};
use cosmic_settings_config::{shortcuts, window_rules::ApplicationException}; use cosmic_settings_config::{shortcuts, window_rules::ApplicationException};
@ -38,10 +43,7 @@ use smithay::{
}, },
output::{Output, WeakOutput}, output::{Output, WeakOutput},
reexports::{ reexports::{
wayland_protocols::ext::{ wayland_protocols::ext::session_lock::v1::server::ext_session_lock_v1::ExtSessionLockV1,
session_lock::v1::server::ext_session_lock_v1::ExtSessionLockV1,
workspace::v1::server::ext_workspace_handle_v1::State as WState,
},
wayland_server::{protocol::wl_surface::WlSurface, Client}, wayland_server::{protocol::wl_surface::WlSurface, Client},
}, },
utils::{IsAlive, Logical, Point, Rectangle, Serial, Size}, utils::{IsAlive, Logical, Point, Rectangle, Serial, Size},
@ -54,6 +56,7 @@ use smithay::{
}, },
xwayland::X11Surface, xwayland::X11Surface,
}; };
use tracing::error;
use crate::{ use crate::{
backend::render::animations::spring::{Spring, SpringParams}, backend::render::animations::spring::{Spring, SpringParams},
@ -368,11 +371,48 @@ fn create_workspace(
} }
state.set_workspace_capabilities( state.set_workspace_capabilities(
&workspace_handle, &workspace_handle,
WorkspaceCapabilities::Activate | WorkspaceCapabilities::SetTilingState, WorkspaceCapabilities::Activate
| WorkspaceCapabilities::SetTilingState
| WorkspaceCapabilities::Pin
| WorkspaceCapabilities::Move,
); );
Workspace::new(workspace_handle, output.clone(), tiling, theme.clone()) Workspace::new(workspace_handle, output.clone(), tiling, theme.clone())
} }
fn create_workspace_from_pinned(
pinned: &PinnedWorkspace,
state: &mut WorkspaceUpdateGuard<'_, State>,
output: &Output,
group_handle: &WorkspaceGroupHandle,
active: bool,
theme: cosmic::Theme,
) -> Workspace {
let workspace_handle = state
.create_workspace(
&group_handle,
if pinned.tiling_enabled {
TilingState::TilingEnabled
} else {
TilingState::FloatingOnly
},
// TODO Set id for persistent workspaces
None,
)
.unwrap();
state.add_workspace_state(&workspace_handle, WState::Pinned);
if active {
state.add_workspace_state(&workspace_handle, WState::Active);
}
state.set_workspace_capabilities(
&workspace_handle,
WorkspaceCapabilities::Activate
| WorkspaceCapabilities::SetTilingState
| WorkspaceCapabilities::Pin
| WorkspaceCapabilities::Move,
);
Workspace::from_pinned(pinned, workspace_handle, output.clone(), theme.clone())
}
/* We will probably need this again at some point /* We will probably need this again at some point
fn merge_workspaces( fn merge_workspaces(
mut workspace: Workspace, mut workspace: Workspace,
@ -545,7 +585,11 @@ impl WorkspaceSet {
xdg_activation_state: &XdgActivationState, xdg_activation_state: &XdgActivationState,
) { ) {
// add empty at the end, if necessary // add empty at the end, if necessary
if self.workspaces.last().map_or(true, |last| !last.is_empty()) { if self
.workspaces
.last()
.map_or(true, |last| !last.is_empty() || last.pinned)
{
self.add_empty_workspace(state); self.add_empty_workspace(state);
} }
@ -556,8 +600,11 @@ impl WorkspaceSet {
.iter() .iter()
.enumerate() .enumerate()
.map(|(i, workspace)| { .map(|(i, workspace)| {
let previous_is_empty = let previous_is_empty = i > 0
i > 0 && self.workspaces.get(i - 1).map_or(false, |w| w.is_empty()); && self
.workspaces
.get(i - 1)
.map_or(false, |w| w.is_empty() && !w.pinned);
let keep = if workspace.can_auto_remove(xdg_activation_state) { let keep = if workspace.can_auto_remove(xdg_activation_state) {
// Keep empty workspace if it's active, or it's the last workspace, // Keep empty workspace if it's active, or it's the last workspace,
// and the previous worspace is not both active and empty. // and the previous worspace is not both active and empty.
@ -650,6 +697,8 @@ pub struct Workspaces {
autotile: bool, autotile: bool,
autotile_behavior: TileBehavior, autotile_behavior: TileBehavior,
theme: cosmic::Theme, theme: cosmic::Theme,
// Persisted workspace to add on first `output_add`
persisted_workspaces: Vec<PinnedWorkspace>,
} }
impl Workspaces { impl Workspaces {
@ -662,6 +711,7 @@ impl Workspaces {
autotile: config.cosmic_conf.autotile, autotile: config.cosmic_conf.autotile,
autotile_behavior: config.cosmic_conf.autotile_behavior, autotile_behavior: config.cosmic_conf.autotile_behavior,
theme, theme,
persisted_workspaces: config.cosmic_conf.pinned_workspaces.clone(),
} }
} }
@ -686,6 +736,20 @@ impl Workspaces {
}); });
workspace_state.add_group_output(&set.group, &output); workspace_state.add_group_output(&set.group, &output);
// If this is the first output added, create workspaces for pinned workspaces from config
for pinned in std::mem::take(&mut self.persisted_workspaces) {
tracing::error!("pinned workspace: {:?}", pinned);
let workspace = create_workspace_from_pinned(
&pinned,
workspace_state,
output,
&set.group,
false,
self.theme.clone(),
);
set.workspaces.push(workspace);
}
// Remove workspaces that prefer this output from other sets // Remove workspaces that prefer this output from other sets
let mut moved_workspaces = self let mut moved_workspaces = self
.sets .sets
@ -836,6 +900,73 @@ impl Workspaces {
} }
} }
// Move a workspace before/after a different workspace
pub fn move_workspace(
&mut self,
handle: &WorkspaceHandle,
other_handle: &WorkspaceHandle,
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
after: bool,
) {
if handle == other_handle {
return;
}
let (Some(old_output), Some(new_output)) = (
self.space_for_handle(handle).map(|w| w.output.clone()),
self.space_for_handle(other_handle)
.map(|w| w.output.clone()),
) else {
return;
};
// Check which workspace is active on the new set; before removing from the
// old set in cause we're moving an active workspace within the same set.
let new_set = &mut self.sets[&new_output];
let previous_active_handle = new_set.workspaces[new_set.active].handle;
// Remove workspace from old set
let old_set = &mut self.sets[&old_output];
let mut workspace = if new_output != old_output {
old_set.remove_workspace(workspace_state, handle).unwrap()
} else {
// If set is the same, just remove it here without adding empty workspace,
// updating `active`, etc.
let idx = old_set
.workspaces
.iter()
.position(|w| w.handle == *handle)
.unwrap();
old_set.workspaces.remove(idx)
};
let new_set = &mut self.sets[&new_output];
if new_output != old_output {
workspace_state.remove_workspace_state(&workspace.handle, WState::Active);
workspace_state.move_workspace_to_group(new_set.group, workspace.handle);
workspace.set_output(&new_output, true);
workspace.refresh();
}
// Insert workspace into new set, relative to `other_handle`
let idx = new_set
.workspaces
.iter()
.position(|w| w.handle == *other_handle)
.unwrap();
let insert_idx = if after { idx + 1 } else { idx };
new_set.workspaces.insert(insert_idx, workspace);
new_set.active = new_set
.workspaces
.iter()
.position(|w| w.handle == previous_active_handle)
.unwrap();
new_set.update_workspace_idxs(workspace_state);
}
pub fn update_config( pub fn update_config(
&mut self, &mut self,
config: &Config, config: &Config,
@ -937,7 +1068,7 @@ impl Workspaces {
.sets .sets
.values() .values()
.flat_map(|set| set.workspaces.last()) .flat_map(|set| set.workspaces.last())
.any(|w| w.mapped().next().is_some()) .any(|w| !w.is_empty() || w.pinned)
{ {
for set in self.sets.values_mut() { for set in self.sets.values_mut() {
set.add_empty_workspace(workspace_state); set.add_empty_workspace(workspace_state);
@ -1156,6 +1287,21 @@ impl Workspaces {
self.autotile = autotile; self.autotile = autotile;
self.apply_tile_change(guard, seats); self.apply_tile_change(guard, seats);
} }
pub fn persist(&self, config: &Config) {
let pinned_workspaces: Vec<PinnedWorkspace> = self
.sets
.values()
.flat_map(|set| &set.workspaces)
.flat_map(|w| w.to_pinned())
.collect();
let config = config.cosmic_helper.clone();
thread::spawn(move || {
if let Err(err) = config.set("pinned_workspaces", pinned_workspaces) {
error!(?err, "Failed to update pinned_workspaces key");
}
});
}
} }
#[derive(Debug)] #[derive(Debug)]

View file

@ -4,7 +4,6 @@ use crate::{
element::{AsGlowRenderer, FromGlesError}, element::{AsGlowRenderer, FromGlesError},
BackdropShader, BackdropShader,
}, },
config::EdidProduct,
shell::{ shell::{
layout::{floating::FloatingLayout, tiling::TilingLayout}, layout::{floating::FloatingLayout, tiling::TilingLayout},
OverviewMode, ANIMATION_DURATION, OverviewMode, ANIMATION_DURATION,
@ -19,6 +18,7 @@ use crate::{
}, },
}, },
}; };
use cosmic_comp_config::workspace::{OutputMatch, PinnedWorkspace};
use cosmic::theme::CosmicTheme; use cosmic::theme::CosmicTheme;
use cosmic_protocols::workspace::v2::server::zcosmic_workspace_handle_v2::TilingState; use cosmic_protocols::workspace::v2::server::zcosmic_workspace_handle_v2::TilingState;
@ -74,30 +74,30 @@ use super::{
const FULLSCREEN_ANIMATION_DURATION: Duration = Duration::from_millis(200); const FULLSCREEN_ANIMATION_DURATION: Duration = Duration::from_millis(200);
#[derive(Debug, Clone, PartialEq, Eq)] // For stable workspace id, generate random 24-bit integer, as a hex string
struct OutputMatch { // Must be compared with existing workspaces work uniqueness.
name: String, // TODO: Assign an id to any workspace that is pinned
edid: Option<EdidProduct>, pub fn random_id() -> String {
let id = rand::random_range(0..(2 << 24));
format!("{:x}", id)
} }
impl OutputMatch { fn output_match_for_output(output: &Output) -> OutputMatch {
fn for_output(output: &Output) -> Self { OutputMatch {
Self { name: output.name(),
name: output.name(), edid: output.edid().cloned(),
edid: output.edid().cloned(),
}
} }
}
// If `disambguate` is true, check that edid *and* connector name match. // If `disambguate` is true, check that edid *and* connector name match.
// Otherwise, match only edid (if it exists) // Otherwise, match only edid (if it exists)
fn matches(&self, output: &Output, disambiguate: bool) -> bool { fn output_matches(output_match: &OutputMatch, output: &Output, disambiguate: bool) -> bool {
if self.edid.as_ref() != output.edid() { if output_match.edid.as_ref() != output.edid() {
false false
} else if disambiguate || self.edid.is_none() { } else if disambiguate || output_match.edid.is_none() {
self.name == output.name() output_match.name == output.name()
} else { } else {
true true
}
} }
} }
@ -109,6 +109,7 @@ pub struct Workspace {
pub minimized_windows: Vec<MinimizedWindow>, pub minimized_windows: Vec<MinimizedWindow>,
pub tiling_enabled: bool, pub tiling_enabled: bool,
pub fullscreen: Option<FullscreenSurface>, pub fullscreen: Option<FullscreenSurface>,
pub pinned: bool,
pub handle: WorkspaceHandle, pub handle: WorkspaceHandle,
pub focus_stack: FocusStacks, pub focus_stack: FocusStacks,
@ -269,7 +270,7 @@ impl Workspace {
) -> Workspace { ) -> Workspace {
let tiling_layer = TilingLayout::new(theme.clone(), &output); let tiling_layer = TilingLayout::new(theme.clone(), &output);
let floating_layer = FloatingLayout::new(theme, &output); let floating_layer = FloatingLayout::new(theme, &output);
let output_match = OutputMatch::for_output(&output); let output_match = output_match_for_output(&output);
Workspace { Workspace {
output, output,
@ -278,6 +279,7 @@ impl Workspace {
tiling_enabled, tiling_enabled,
minimized_windows: Vec::new(), minimized_windows: Vec::new(),
fullscreen: None, fullscreen: None,
pinned: false,
handle, handle,
focus_stack: FocusStacks::default(), focus_stack: FocusStacks::default(),
screencopy: ScreencopySessions::default(), screencopy: ScreencopySessions::default(),
@ -291,6 +293,55 @@ impl Workspace {
} }
} }
pub fn from_pinned(
pinned: &PinnedWorkspace,
handle: WorkspaceHandle,
output: Output,
theme: cosmic::Theme,
) -> Self {
let tiling_layer = TilingLayout::new(theme.clone(), &output);
let floating_layer = FloatingLayout::new(theme, &output);
let output_match = output_match_for_output(&output);
Workspace {
output,
tiling_layer,
floating_layer,
tiling_enabled: pinned.tiling_enabled,
minimized_windows: Vec::new(),
fullscreen: None,
pinned: true,
handle,
focus_stack: FocusStacks::default(),
screencopy: ScreencopySessions::default(),
output_stack: {
let mut queue = VecDeque::new();
queue.push_back(pinned.output.clone());
if output_match != pinned.output {
queue.push_back(output_match);
}
queue
},
backdrop_id: Id::new(),
dirty: AtomicBool::new(false),
}
}
pub fn to_pinned(&self) -> Option<PinnedWorkspace> {
let output = self.explicit_output().clone();
if self.pinned {
Some(PinnedWorkspace {
output: cosmic_comp_config::workspace::OutputMatch {
name: output.name,
edid: output.edid,
},
tiling_enabled: self.tiling_enabled,
})
} else {
None
}
}
#[profiling::function] #[profiling::function]
pub fn refresh(&mut self) { pub fn refresh(&mut self) {
// TODO: `Option::take_if` once stabilitized // TODO: `Option::take_if` once stabilitized
@ -316,9 +367,9 @@ impl Workspace {
} }
// Auto-removal of workspaces is allowed if empty, unless blocked by an // Auto-removal of workspaces is allowed if empty, unless blocked by an
// unused and unexpired activation token. // unused and unexpired activation token, or pinned.
pub fn can_auto_remove(&self, xdg_activation_state: &XdgActivationState) -> bool { pub fn can_auto_remove(&self, xdg_activation_state: &XdgActivationState) -> bool {
self.is_empty() && !self.has_activation_token(xdg_activation_state) self.is_empty() && !self.has_activation_token(xdg_activation_state) && !self.pinned
} }
pub fn refresh_focus_stack(&mut self) { pub fn refresh_focus_stack(&mut self) {
@ -389,6 +440,11 @@ impl Workspace {
&self.output &self.output
} }
/// Output workspace was originally created on, or explicitly moved to by the user
fn explicit_output(&self) -> &OutputMatch {
self.output_stack.front().unwrap()
}
// Set output the workspace is on // Set output the workspace is on
// //
// If `explicit` is `true`, the user has explicitly moved the workspace // If `explicit` is `true`, the user has explicitly moved the workspace
@ -414,21 +470,21 @@ impl Workspace {
if let Some(pos) = self if let Some(pos) = self
.output_stack .output_stack
.iter() .iter()
.position(|i| i.matches(output, true)) .position(|i| output_matches(i, output, true))
{ {
// Matched edid and connector name // Matched edid and connector name
self.output_stack.truncate(pos + 1); self.output_stack.truncate(pos + 1);
} else if let Some(pos) = self } else if let Some(pos) = self
.output_stack .output_stack
.iter() .iter()
.position(|i| i.matches(output, false)) .position(|i| output_matches(i, output, false))
{ {
// Matched edid but not connector name; truncate entries that don't match edid, // Matched edid but not connector name; truncate entries that don't match edid,
// but keep old entry in case we see two outputs with the same edid. // but keep old entry in case we see two outputs with the same edid.
self.output_stack.truncate(pos + 1); self.output_stack.truncate(pos + 1);
self.output_stack.push_back(OutputMatch::for_output(output)); self.output_stack.push_back(output_match_for_output(output));
} else { } else {
self.output_stack.push_back(OutputMatch::for_output(output)); self.output_stack.push_back(output_match_for_output(output));
} }
self.output = output.clone(); self.output = output.clone();
} }
@ -440,7 +496,7 @@ impl Workspace {
.is_some_and(|edid| self.output().edid() == Some(edid)); .is_some_and(|edid| self.output().edid() == Some(edid));
self.output_stack self.output_stack
.iter() .iter()
.any(|i| i.matches(output, disambiguate)) .any(|i| output_matches(i, output, disambiguate))
} }
pub fn unmap(&mut self, mapped: &CosmicMapped) -> Option<ManagedState> { pub fn unmap(&mut self, mapped: &CosmicMapped) -> Option<ManagedState> {

View file

@ -4,7 +4,7 @@ use crate::{
shell::WorkspaceDelta, shell::WorkspaceDelta,
utils::prelude::*, utils::prelude::*,
wayland::protocols::workspace::{ wayland::protocols::workspace::{
delegate_workspace, Request, WorkspaceHandler, WorkspaceState, delegate_workspace, Request, State as WState, WorkspaceHandler, WorkspaceState,
}, },
}; };
use cosmic_protocols::workspace::v2::server::zcosmic_workspace_handle_v2::TilingState; use cosmic_protocols::workspace::v2::server::zcosmic_workspace_handle_v2::TilingState;
@ -55,6 +55,55 @@ impl WorkspaceHandler for State {
); );
} }
} }
Request::SetPin { workspace, pinned } => {
let mut shell = self.common.shell.write().unwrap();
if let Some(workspace) = shell.workspaces.space_for_handle_mut(&workspace) {
workspace.pinned = pinned;
let mut update = self.common.workspace_state.update();
if pinned {
update.add_workspace_state(&workspace.handle, WState::Pinned);
// TODO: Also need to update on changing other properties that are saved
shell.workspaces.persist(&self.common.config);
} else {
update.remove_workspace_state(&workspace.handle, WState::Pinned);
shell.workspaces.persist(&self.common.config);
}
}
}
Request::MoveBefore {
workspace,
other_workspace,
axis,
} => {
if axis != 0 {
continue;
}
let mut shell = self.common.shell.write().unwrap();
let mut update = self.common.workspace_state.update();
shell.workspaces.move_workspace(
&workspace,
&other_workspace,
&mut update,
false,
);
}
Request::MoveAfter {
workspace,
other_workspace,
axis,
} => {
if axis != 0 {
continue;
}
let mut shell = self.common.shell.write().unwrap();
let mut update = self.common.workspace_state.update();
shell.workspaces.move_workspace(
&workspace,
&other_workspace,
&mut update,
true,
);
}
_ => {} _ => {}
} }
} }

View file

@ -1,12 +1,12 @@
use crate::{shell::ActivationKey, state::ClientState, utils::prelude::*}; use crate::{shell::ActivationKey, state::ClientState, utils::prelude::*};
use crate::{state::State, wayland::protocols::workspace::WorkspaceHandle}; use crate::{
state::State,
wayland::protocols::workspace::{State as WState, WorkspaceHandle},
};
use smithay::{ use smithay::{
delegate_xdg_activation, delegate_xdg_activation,
input::Seat, input::Seat,
reexports::{ reexports::wayland_server::protocol::wl_surface::WlSurface,
wayland_protocols::ext::workspace::v1::server::ext_workspace_handle_v1::State as WState,
wayland_server::protocol::wl_surface::WlSurface,
},
wayland::xdg_activation::{ wayland::xdg_activation::{
XdgActivationHandler, XdgActivationState, XdgActivationToken, XdgActivationTokenData, XdgActivationHandler, XdgActivationState, XdgActivationToken, XdgActivationTokenData,
}, },

View file

@ -13,7 +13,7 @@ use smithay::reexports::{
use std::sync::Mutex; use std::sync::Mutex;
use super::{ use super::{
Request, Workspace, WorkspaceCapabilities, WorkspaceData, WorkspaceGlobalData, Request, State, Workspace, WorkspaceCapabilities, WorkspaceData, WorkspaceGlobalData,
WorkspaceHandler, WorkspaceManagerData, WorkspaceState, WorkspaceHandler, WorkspaceManagerData, WorkspaceState,
}; };
@ -21,6 +21,7 @@ use super::{
pub struct CosmicWorkspaceV2DataInner { pub struct CosmicWorkspaceV2DataInner {
capabilities: Option<zcosmic_workspace_handle_v2::WorkspaceCapabilities>, capabilities: Option<zcosmic_workspace_handle_v2::WorkspaceCapabilities>,
tiling: Option<zcosmic_workspace_handle_v2::TilingState>, tiling: Option<zcosmic_workspace_handle_v2::TilingState>,
states: Option<zcosmic_workspace_handle_v2::State>,
} }
pub struct CosmicWorkspaceV2Data { pub struct CosmicWorkspaceV2Data {
@ -164,6 +165,100 @@ where
} }
} }
} }
zcosmic_workspace_handle_v2::Request::Pin => {
if let Some(workspace_handle) =
state.workspace_state().get_ext_workspace_handle(&workspace)
{
if let Ok(manager) =
workspace.data::<WorkspaceData>().unwrap().manager.upgrade()
{
let mut state = manager
.data::<WorkspaceManagerData>()
.unwrap()
.lock()
.unwrap();
state.requests.push(Request::SetPin {
workspace: workspace_handle,
pinned: true,
});
}
}
}
zcosmic_workspace_handle_v2::Request::Unpin => {
if let Some(workspace_handle) =
state.workspace_state().get_ext_workspace_handle(&workspace)
{
if let Ok(manager) =
workspace.data::<WorkspaceData>().unwrap().manager.upgrade()
{
let mut state = manager
.data::<WorkspaceManagerData>()
.unwrap()
.lock()
.unwrap();
state.requests.push(Request::SetPin {
workspace: workspace_handle,
pinned: false,
});
}
}
}
zcosmic_workspace_handle_v2::Request::MoveBefore {
other_workspace,
axis,
} => {
if let Some(workspace_handle) =
state.workspace_state().get_ext_workspace_handle(&workspace)
{
if let Some(other_workspace) = state
.workspace_state()
.get_ext_workspace_handle(&other_workspace)
{
if let Ok(manager) =
workspace.data::<WorkspaceData>().unwrap().manager.upgrade()
{
let mut state = manager
.data::<WorkspaceManagerData>()
.unwrap()
.lock()
.unwrap();
state.requests.push(Request::MoveBefore {
workspace: workspace_handle,
other_workspace,
axis,
});
}
}
}
}
zcosmic_workspace_handle_v2::Request::MoveAfter {
other_workspace,
axis,
} => {
if let Some(workspace_handle) =
state.workspace_state().get_ext_workspace_handle(&workspace)
{
if let Some(other_workspace) = state
.workspace_state()
.get_ext_workspace_handle(&other_workspace)
{
if let Ok(manager) =
workspace.data::<WorkspaceData>().unwrap().manager.upgrade()
{
let mut state = manager
.data::<WorkspaceManagerData>()
.unwrap()
.lock()
.unwrap();
state.requests.push(Request::MoveAfter {
workspace: workspace_handle,
other_workspace,
axis,
});
}
}
}
}
zcosmic_workspace_handle_v2::Request::Destroy => {} zcosmic_workspace_handle_v2::Request::Destroy => {}
_ => unreachable!(), _ => unreachable!(),
} }
@ -193,6 +288,12 @@ pub fn send_workspace_to_client(
WorkspaceCapabilities::SetTilingState => { WorkspaceCapabilities::SetTilingState => {
Some(zcosmic_workspace_handle_v2::WorkspaceCapabilities::SetTilingState) Some(zcosmic_workspace_handle_v2::WorkspaceCapabilities::SetTilingState)
} }
WorkspaceCapabilities::Pin => {
Some(zcosmic_workspace_handle_v2::WorkspaceCapabilities::Pin)
}
WorkspaceCapabilities::Move => {
Some(zcosmic_workspace_handle_v2::WorkspaceCapabilities::Move)
}
_ => None, _ => None,
}) })
.collect::<zcosmic_workspace_handle_v2::WorkspaceCapabilities>(); .collect::<zcosmic_workspace_handle_v2::WorkspaceCapabilities>();
@ -212,5 +313,21 @@ pub fn send_workspace_to_client(
changed = true; changed = true;
} }
if instance.version() >= zcosmic_workspace_handle_v2::EVT_STATE_SINCE {
let states = workspace
.states
.iter()
.filter_map(|state| match state {
State::Pinned => Some(zcosmic_workspace_handle_v2::State::Pinned),
_ => None,
})
.collect::<zcosmic_workspace_handle_v2::State>();
if handle_state.states != Some(states) {
instance.state(states);
handle_state.states = Some(states);
changed = true;
}
}
changed changed
} }

View file

@ -20,7 +20,7 @@ use smithay::{
use std::{collections::HashSet, sync::Mutex}; use std::{collections::HashSet, sync::Mutex};
use super::{ use super::{
Request, Workspace, WorkspaceCapabilities, WorkspaceGlobalData, WorkspaceGroup, Request, State, Workspace, WorkspaceCapabilities, WorkspaceGlobalData, WorkspaceGroup,
WorkspaceGroupHandle, WorkspaceHandler, WorkspaceState, WorkspaceGroupHandle, WorkspaceHandler, WorkspaceState,
}; };
@ -469,12 +469,23 @@ where
changed = true; changed = true;
} }
if handle_state.states != Some(workspace.states) { let states = workspace
instance.state(workspace.states); .states
handle_state.states = Some(workspace.states.clone()); .iter()
.filter_map(|state| match state {
State::Active => Some(ext_workspace_handle_v1::State::Active),
State::Urgent => Some(ext_workspace_handle_v1::State::Urgent),
State::Hidden => Some(ext_workspace_handle_v1::State::Hidden),
_ => None,
})
.collect();
if handle_state.states != Some(states) {
instance.state(states);
handle_state.states = Some(states);
changed = true; changed = true;
} }
// TODO ext_workspace_handle_v1::id // TODO ext_workspace_handle_v1::id
// TODO send id if pinned
if let Some(cosmic_v2_handle) = handle_state if let Some(cosmic_v2_handle) = handle_state
.cosmic_v2_handle .cosmic_v2_handle

View file

@ -5,7 +5,7 @@ use smithay::{
reexports::{ reexports::{
wayland_protocols::ext::workspace::v1::server::{ wayland_protocols::ext::workspace::v1::server::{
ext_workspace_group_handle_v1::{ExtWorkspaceGroupHandleV1, GroupCapabilities}, ext_workspace_group_handle_v1::{ExtWorkspaceGroupHandleV1, GroupCapabilities},
ext_workspace_handle_v1::{self, ExtWorkspaceHandleV1}, ext_workspace_handle_v1::ExtWorkspaceHandleV1,
ext_workspace_manager_v1::ExtWorkspaceManagerV1, ext_workspace_manager_v1::ExtWorkspaceManagerV1,
}, },
wayland_server::{ wayland_server::{
@ -38,6 +38,19 @@ bitflags::bitflags! {
const Rename = 16; const Rename = 16;
/// cosmic specific /// cosmic specific
const SetTilingState = 32; const SetTilingState = 32;
const Pin = 64;
const Move = 128;
}
}
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct State: u32 {
const Active = 1;
const Urgent = 2;
const Hidden = 4;
/// cosmic specific
const Pinned = 8;
} }
} }
@ -96,7 +109,7 @@ pub struct Workspace {
name: String, name: String,
capabilities: WorkspaceCapabilities, capabilities: WorkspaceCapabilities,
coordinates: Vec<u32>, coordinates: Vec<u32>,
states: ext_workspace_handle_v1::State, states: State,
tiling: zcosmic_workspace_handle_v2::TilingState, tiling: zcosmic_workspace_handle_v2::TilingState,
ext_id: Option<String>, ext_id: Option<String>,
} }
@ -148,6 +161,20 @@ pub enum Request {
workspace: WorkspaceHandle, workspace: WorkspaceHandle,
group: WorkspaceGroupHandle, group: WorkspaceGroupHandle,
}, },
SetPin {
workspace: WorkspaceHandle,
pinned: bool,
},
MoveBefore {
workspace: WorkspaceHandle,
other_workspace: WorkspaceHandle,
axis: u32,
},
MoveAfter {
workspace: WorkspaceHandle,
other_workspace: WorkspaceHandle,
axis: u32,
},
} }
impl<D> WorkspaceState<D> impl<D> WorkspaceState<D>
@ -166,7 +193,7 @@ where
); );
let cosmic_v2_global = dh.create_global::<D, ZcosmicWorkspaceManagerV2, _>( let cosmic_v2_global = dh.create_global::<D, ZcosmicWorkspaceManagerV2, _>(
1, 2,
WorkspaceGlobalData { WorkspaceGlobalData {
filter: Box::new(client_filter.clone()), filter: Box::new(client_filter.clone()),
}, },
@ -240,10 +267,7 @@ where
}) })
} }
pub fn workspace_states( pub fn workspace_states(&self, workspace: &WorkspaceHandle) -> Option<State> {
&self,
workspace: &WorkspaceHandle,
) -> Option<ext_workspace_handle_v1::State> {
self.groups self.groups
.iter() .iter()
.find_map(|g| Some(g.workspaces.iter().find(|w| w.id == workspace.id)?.states)) .find_map(|g| Some(g.workspaces.iter().find(|w| w.id == workspace.id)?.states))
@ -332,6 +356,7 @@ where
&mut self, &mut self,
group: &WorkspaceGroupHandle, group: &WorkspaceGroupHandle,
tiling: zcosmic_workspace_handle_v2::TilingState, tiling: zcosmic_workspace_handle_v2::TilingState,
// TODO way to add id to workspace that doesn't have it
ext_id: Option<String>, ext_id: Option<String>,
) -> Option<WorkspaceHandle> { ) -> Option<WorkspaceHandle> {
if let Some(group) = self.0.groups.iter_mut().find(|g| g.id == group.id) { if let Some(group) = self.0.groups.iter_mut().find(|g| g.id == group.id) {
@ -343,7 +368,7 @@ where
name: Default::default(), name: Default::default(),
capabilities: WorkspaceCapabilities::empty(), capabilities: WorkspaceCapabilities::empty(),
coordinates: Default::default(), coordinates: Default::default(),
states: ext_workspace_handle_v1::State::empty(), states: State::empty(),
ext_id, ext_id,
}; };
group.workspaces.push(workspace); group.workspaces.push(workspace);
@ -538,18 +563,11 @@ where
} }
} }
pub fn workspace_states( pub fn workspace_states(&self, workspace: &WorkspaceHandle) -> Option<State> {
&self,
workspace: &WorkspaceHandle,
) -> Option<ext_workspace_handle_v1::State> {
self.0.workspace_states(workspace) self.0.workspace_states(workspace)
} }
pub fn add_workspace_state( pub fn add_workspace_state(&mut self, workspace: &WorkspaceHandle, state: State) {
&mut self,
workspace: &WorkspaceHandle,
state: ext_workspace_handle_v1::State,
) {
if let Some(workspace) = self if let Some(workspace) = self
.0 .0
.groups .groups
@ -560,11 +578,7 @@ where
} }
} }
pub fn remove_workspace_state( pub fn remove_workspace_state(&mut self, workspace: &WorkspaceHandle, state: State) {
&mut self,
workspace: &WorkspaceHandle,
state: ext_workspace_handle_v1::State,
) {
if let Some(workspace) = self if let Some(workspace) = self
.0 .0
.groups .groups