Update to workspace v2, based on ext-workspace

Requires https://github.com/pop-os/cosmic-protocols/pull/51,
https://github.com/pop-os/cosmic-comp/pull/1225.

The new protocol version is a hard requirement, and this will panic
without it.
This commit is contained in:
Ian Douglas Scott 2025-02-20 14:28:56 -08:00 committed by Ian Douglas Scott
parent 7f877f72a3
commit 6b6bf454db
11 changed files with 63 additions and 68 deletions

7
Cargo.lock generated
View file

@ -1134,8 +1134,9 @@ 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=178eb0b#178eb0b14a0e5c192f64f6dee6c40341a8e5ee51" source = "git+https://github.com/pop-os/cosmic-protocols//?branch=main#6b05c2a157118979cb472a38455ba78ca9729196"
dependencies = [ dependencies = [
"bitflags 2.8.0",
"cosmic-protocols", "cosmic-protocols",
"libc", "libc",
"smithay-client-toolkit", "smithay-client-toolkit",
@ -1197,7 +1198,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=178eb0b#178eb0b14a0e5c192f64f6dee6c40341a8e5ee51" source = "git+https://github.com/pop-os/cosmic-protocols//?branch=main#6b05c2a157118979cb472a38455ba78ca9729196"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.8.0",
"wayland-backend", "wayland-backend",
@ -5863,7 +5864,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.48.0",
] ]
[[package]] [[package]]

View file

@ -44,3 +44,7 @@ mock-backend = []
[profile.dev] [profile.dev]
# Not usable at opt-level 0, at least with software renderer # Not usable at opt-level 0, at least with software renderer
opt-level = 1 opt-level = 1
[patch."https://github.com/pop-os/cosmic-protocols"]
cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols//", branch = "main" }
cosmic-client-toolkit = { git = "https://github.com/pop-os/cosmic-protocols//", branch = "main" }

View file

@ -3,14 +3,12 @@
use cosmic::{ use cosmic::{
cctk::{ cctk::{
cosmic_protocols::{ cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1,
toplevel_info::v1::client::zcosmic_toplevel_handle_v1,
workspace::v1::client::zcosmic_workspace_handle_v1,
},
wayland_client::{ wayland_client::{
protocol::{wl_output, wl_shm}, protocol::{wl_output, wl_shm},
Connection, WEnum, Connection, WEnum,
}, },
wayland_protocols::ext::workspace::v1::client::ext_workspace_handle_v1,
}, },
iced::{ iced::{
self, self,
@ -80,9 +78,9 @@ impl MockObjectId {
} }
#[derive(Eq, PartialEq, Clone, Debug, Hash)] #[derive(Eq, PartialEq, Clone, Debug, Hash)]
pub struct ZcosmicWorkspaceHandleV1(MockObjectId); pub struct ExtWorkspaceHandleV1(MockObjectId);
impl ZcosmicWorkspaceHandleV1 { impl ExtWorkspaceHandleV1 {
pub fn id(&self) -> MockObjectId { pub fn id(&self) -> MockObjectId {
self.0.clone() self.0.clone()
} }
@ -91,27 +89,21 @@ impl ZcosmicWorkspaceHandleV1 {
#[derive(Eq, PartialEq, Clone, Debug, Hash)] #[derive(Eq, PartialEq, Clone, Debug, Hash)]
pub struct ExtForeignToplevelHandleV1(MockObjectId); pub struct ExtForeignToplevelHandleV1(MockObjectId);
#[derive(Clone, Debug, Default)]
pub struct CaptureFilter {
pub workspaces_on_outputs: Vec<wl_output::WlOutput>,
pub toplevels_on_workspaces: Vec<ZcosmicWorkspaceHandleV1>,
}
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct ToplevelInfo { pub struct ToplevelInfo {
pub title: String, pub title: String,
pub app_id: String, pub app_id: String,
pub state: HashSet<zcosmic_toplevel_handle_v1::State>, pub state: HashSet<zcosmic_toplevel_handle_v1::State>,
pub output: HashSet<wl_output::WlOutput>, pub output: HashSet<wl_output::WlOutput>,
pub workspace: HashSet<ZcosmicWorkspaceHandleV1>, pub workspace: HashSet<ExtWorkspaceHandleV1>,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Workspace { pub struct Workspace {
pub handle: ZcosmicWorkspaceHandleV1, pub handle: ExtWorkspaceHandleV1,
pub name: String, pub name: String,
// pub coordinates: Vec<u32>, // pub coordinates: Vec<u32>,
pub state: Vec<WEnum<zcosmic_workspace_handle_v1::State>>, pub state: ext_workspace_handle_v1::State,
// pub capabilities: Vec<WEnum<zcosmic_workspace_handle_v1::ZcosmicWorkspaceCapabilitiesV1>>, // pub capabilities: Vec<WEnum<zcosmic_workspace_handle_v1::ZcosmicWorkspaceCapabilitiesV1>>,
// pub tiling: Option<WEnum<zcosmic_workspace_handle_v1::TilingState>>, // pub tiling: Option<WEnum<zcosmic_workspace_handle_v1::TilingState>>,
} }
@ -135,14 +127,14 @@ impl AppData {
// Add four workspaces for each output // Add four workspaces for each output
let mut new_workspaces = Vec::new(); let mut new_workspaces = Vec::new();
for i in 0..=4 { for i in 0..=4 {
let workspace_handle = ZcosmicWorkspaceHandleV1(MockObjectId::new()); let workspace_handle = ExtWorkspaceHandleV1(MockObjectId::new());
let workspace = Workspace { let workspace = Workspace {
handle: workspace_handle.clone(), handle: workspace_handle.clone(),
name: format!("Workspace {i}"), name: format!("Workspace {i}"),
state: if i == 0 { state: if i == 0 {
vec![WEnum::Value(zcosmic_workspace_handle_v1::State::Active)] ext_workspace_handle_v1::State::Active
} else { } else {
Vec::new() ext_workspace_handle_v1::State::empty()
}, },
}; };
// Add three toplevels for each workspace // Add three toplevels for each workspace

View file

@ -17,12 +17,12 @@ use std::collections::HashSet;
#[cfg(not(feature = "mock-backend"))] #[cfg(not(feature = "mock-backend"))]
mod wayland; mod wayland;
#[cfg(not(feature = "mock-backend"))] #[cfg(not(feature = "mock-backend"))]
pub use cosmic::cctk::{ pub use cosmic::cctk::{toplevel_info::ToplevelInfo, workspace::Workspace};
cosmic_protocols::workspace::v1::client::zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1,
toplevel_info::ToplevelInfo, workspace::Workspace,
};
#[cfg(not(feature = "mock-backend"))] #[cfg(not(feature = "mock-backend"))]
pub use wayland_protocols::ext::foreign_toplevel_list::v1::client::ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1; pub use wayland_protocols::ext::{
foreign_toplevel_list::v1::client::ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
workspace::v1::client::ext_workspace_handle_v1::ExtWorkspaceHandleV1,
};
#[cfg(not(feature = "mock-backend"))] #[cfg(not(feature = "mock-backend"))]
pub use wayland::subscription; pub use wayland::subscription;
@ -32,13 +32,13 @@ pub use wayland::subscription;
mod mock; mod mock;
#[cfg(feature = "mock-backend")] #[cfg(feature = "mock-backend")]
pub use mock::{ pub use mock::{
subscription, ExtForeignToplevelHandleV1, ToplevelInfo, Workspace, ZcosmicWorkspaceHandleV1, subscription, ExtForeignToplevelHandleV1, ExtWorkspaceHandleV1, ToplevelInfo, Workspace,
}; };
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct CaptureFilter { pub struct CaptureFilter {
pub workspaces_on_outputs: Vec<wl_output::WlOutput>, pub workspaces_on_outputs: Vec<wl_output::WlOutput>,
pub toplevels_on_workspaces: Vec<ZcosmicWorkspaceHandleV1>, pub toplevels_on_workspaces: Vec<ExtWorkspaceHandleV1>,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -57,7 +57,7 @@ pub struct CaptureImage {
pub enum Event { pub enum Event {
CmdSender(calloop::channel::Sender<Cmd>), CmdSender(calloop::channel::Sender<Cmd>),
Workspaces(Vec<(HashSet<wl_output::WlOutput>, Workspace)>), Workspaces(Vec<(HashSet<wl_output::WlOutput>, Workspace)>),
WorkspaceCapture(ZcosmicWorkspaceHandleV1, CaptureImage), WorkspaceCapture(ExtWorkspaceHandleV1, CaptureImage),
NewToplevel(ExtForeignToplevelHandleV1, ToplevelInfo), NewToplevel(ExtForeignToplevelHandleV1, ToplevelInfo),
UpdateToplevel(ExtForeignToplevelHandleV1, ToplevelInfo), UpdateToplevel(ExtForeignToplevelHandleV1, ToplevelInfo),
CloseToplevel(ExtForeignToplevelHandleV1), CloseToplevel(ExtForeignToplevelHandleV1),
@ -71,8 +71,8 @@ pub enum Cmd {
CloseToplevel(ExtForeignToplevelHandleV1), CloseToplevel(ExtForeignToplevelHandleV1),
MoveToplevelToWorkspace( MoveToplevelToWorkspace(
ExtForeignToplevelHandleV1, ExtForeignToplevelHandleV1,
ZcosmicWorkspaceHandleV1, ExtWorkspaceHandleV1,
wl_output::WlOutput, wl_output::WlOutput,
), ),
ActivateWorkspace(ZcosmicWorkspaceHandleV1), ActivateWorkspace(ExtWorkspaceHandleV1),
} }

View file

@ -94,7 +94,7 @@ impl AppData {
let info = self.toplevel_info_state.info(&toplevel_handle); let info = self.toplevel_info_state.info(&toplevel_handle);
if let Some(cosmic_toplevel) = info.and_then(|x| x.cosmic_toplevel.as_ref()) { if let Some(cosmic_toplevel) = info.and_then(|x| x.cosmic_toplevel.as_ref()) {
if self.toplevel_manager_state.manager.version() >= 2 { if self.toplevel_manager_state.manager.version() >= 2 {
self.toplevel_manager_state.manager.move_to_workspace( self.toplevel_manager_state.manager.move_to_ext_workspace(
&cosmic_toplevel, &cosmic_toplevel,
&workspace_handle, &workspace_handle,
&output, &output,
@ -113,11 +113,11 @@ impl AppData {
fn matches_capture_filter(&self, source: &CaptureSource) -> bool { fn matches_capture_filter(&self, source: &CaptureSource) -> bool {
match source { match source {
CaptureSource::CosmicToplevel(toplevel) => { CaptureSource::Toplevel(toplevel) => {
let info = self let info = self
.toplevel_info_state .toplevel_info_state
.toplevels() .toplevels()
.find(|info| info.cosmic_toplevel.as_ref() == Some(&toplevel)); .find(|info| info.foreign_toplevel == *toplevel);
if let Some(info) = info { if let Some(info) = info {
info.workspace.iter().any(|workspace| { info.workspace.iter().any(|workspace| {
self.capture_filter self.capture_filter
@ -128,18 +128,16 @@ impl AppData {
false false
} }
} }
CaptureSource::CosmicWorkspace(workspace) => self CaptureSource::Workspace(workspace) => self
.workspace_state .workspace_state
.workspace_groups() .workspace_groups()
.iter() .find(|g| g.workspaces.iter().any(|w| w == workspace))
.find(|g| g.workspaces.iter().any(|w| w.handle == *workspace))
.map_or(false, |group| { .map_or(false, |group| {
self.capture_filter self.capture_filter
.workspaces_on_outputs .workspaces_on_outputs
.iter() .iter()
.any(|o| group.outputs.contains(o)) .any(|o| group.outputs.contains(o))
}), }),
CaptureSource::Toplevel(_) => false,
CaptureSource::Output(_) => false, CaptureSource::Output(_) => false,
} }
} }

View file

@ -192,19 +192,19 @@ impl ScreencopyHandler for AppData {
), ),
}; };
match &capture.source { match &capture.source {
CaptureSource::CosmicToplevel(toplevel) => { CaptureSource::Toplevel(toplevel) => {
let info = self let info = self
.toplevel_info_state .toplevel_info_state
.toplevels() .toplevels()
.find(|info| info.cosmic_toplevel.as_ref() == Some(&toplevel)); .find(|info| info.foreign_toplevel == *toplevel);
if let Some(info) = info { if let Some(info) = info {
self.send_event(Event::ToplevelCapture(info.foreign_toplevel.clone(), image)) self.send_event(Event::ToplevelCapture(info.foreign_toplevel.clone(), image))
} }
} }
CaptureSource::CosmicWorkspace(workspace) => { CaptureSource::Workspace(workspace) => {
self.send_event(Event::WorkspaceCapture(workspace.clone(), image)); self.send_event(Event::WorkspaceCapture(workspace.clone(), image));
} }
CaptureSource::Output(_) | CaptureSource::Toplevel(_) => { CaptureSource::Output(_) => {
unreachable!() unreachable!()
} }
}; };

View file

@ -22,10 +22,9 @@ impl ToplevelInfoHandler for AppData {
toplevel: &ExtForeignToplevelHandleV1, toplevel: &ExtForeignToplevelHandleV1,
) { ) {
let info = self.toplevel_info_state.info(toplevel).unwrap(); let info = self.toplevel_info_state.info(toplevel).unwrap();
let cosmic_toplevel = info.cosmic_toplevel.clone().unwrap();
self.send_event(Event::NewToplevel(toplevel.clone(), info.clone())); self.send_event(Event::NewToplevel(toplevel.clone(), info.clone()));
self.add_capture_source(CaptureSource::CosmicToplevel(cosmic_toplevel)); self.add_capture_source(CaptureSource::Toplevel(toplevel.clone()));
} }
fn update_toplevel( fn update_toplevel(
@ -44,11 +43,9 @@ impl ToplevelInfoHandler for AppData {
_qh: &QueueHandle<Self>, _qh: &QueueHandle<Self>,
toplevel: &ExtForeignToplevelHandleV1, toplevel: &ExtForeignToplevelHandleV1,
) { ) {
let info = self.toplevel_info_state.info(toplevel).unwrap();
let cosmic_toplevel = info.cosmic_toplevel.clone().unwrap();
self.send_event(Event::CloseToplevel(toplevel.clone())); self.send_event(Event::CloseToplevel(toplevel.clone()));
self.remove_capture_source(CaptureSource::CosmicToplevel(cosmic_toplevel)); self.remove_capture_source(CaptureSource::Toplevel(toplevel.clone()));
} }
} }

View file

@ -15,11 +15,13 @@ impl WorkspaceHandler for AppData {
// Handle move to another output // Handle move to another output
for group in self.workspace_state.workspace_groups() { for group in self.workspace_state.workspace_groups() {
for workspace in &group.workspaces { for workspace_handle in &group.workspaces {
workspaces.push((group.outputs.iter().cloned().collect(), workspace.clone())); if let Some(workspace) = self.workspace_state.workspace_info(workspace_handle) {
workspaces.push((group.outputs.iter().cloned().collect(), workspace.clone()));
// TODO one capture per output on workspace? // TODO one capture per output on workspace?
self.add_capture_source(CaptureSource::CosmicWorkspace(workspace.handle.clone())); self.add_capture_source(CaptureSource::Workspace(workspace_handle.clone()));
}
} }
} }

View file

@ -6,7 +6,7 @@ use cosmic::{
}; };
use std::{borrow::Cow, sync::LazyLock}; use std::{borrow::Cow, sync::LazyLock};
use crate::backend::{ExtForeignToplevelHandleV1, ZcosmicWorkspaceHandleV1}; use crate::backend::{ExtForeignToplevelHandleV1, ExtWorkspaceHandleV1};
// Include `pid` in mime. Want to drag between our surfaces, but not another // Include `pid` in mime. Want to drag between our surfaces, but not another
// process, if we use Wayland object ids. // process, if we use Wayland object ids.
@ -19,7 +19,7 @@ static TOPLEVEL_MIME: LazyLock<String> =
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum DragSurface { pub enum DragSurface {
#[allow(dead_code)] #[allow(dead_code)]
Workspace(ZcosmicWorkspaceHandleV1), Workspace(ExtWorkspaceHandleV1),
Toplevel(ExtForeignToplevelHandleV1), Toplevel(ExtForeignToplevelHandleV1),
} }
@ -95,8 +95,8 @@ impl TryFrom<(Vec<u8>, std::string::String)> for DragWorkspace {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
#[repr(u8)] #[repr(u8)]
pub enum DropTarget { pub enum DropTarget {
WorkspaceSidebarEntry(ZcosmicWorkspaceHandleV1, wl_output::WlOutput), WorkspaceSidebarEntry(ExtWorkspaceHandleV1, wl_output::WlOutput),
OutputToplevels(ZcosmicWorkspaceHandleV1, wl_output::WlOutput), OutputToplevels(ExtWorkspaceHandleV1, wl_output::WlOutput),
#[allow(dead_code)] #[allow(dead_code)]
WorkspacesBar(wl_output::WlOutput), WorkspacesBar(wl_output::WlOutput),
} }

View file

@ -4,9 +4,9 @@
#![allow(clippy::single_match)] #![allow(clippy::single_match)]
use cctk::{ use cctk::{
cosmic_protocols::workspace::v1::client::zcosmic_workspace_handle_v1,
sctk::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer}, sctk::shell::wlr_layer::{Anchor, KeyboardInteractivity, Layer},
wayland_client::{protocol::wl_output, Connection, Proxy, WEnum}, wayland_client::{protocol::wl_output, Connection, Proxy},
wayland_protocols::ext::workspace::v1::client::ext_workspace_handle_v1,
}; };
use clap::Parser; use clap::Parser;
use cosmic::{ use cosmic::{
@ -43,7 +43,7 @@ mod desktop_info;
mod localize; mod localize;
mod backend; mod backend;
mod view; mod view;
use backend::{ExtForeignToplevelHandleV1, ToplevelInfo, ZcosmicWorkspaceHandleV1}; use backend::{ExtForeignToplevelHandleV1, ExtWorkspaceHandleV1, ToplevelInfo};
mod dnd; mod dnd;
mod utils; mod utils;
mod widgets; mod widgets;
@ -89,9 +89,9 @@ enum Msg {
WaylandEvent(WaylandEvent), WaylandEvent(WaylandEvent),
Wayland(backend::Event), Wayland(backend::Event),
Close, Close,
ActivateWorkspace(ZcosmicWorkspaceHandleV1), ActivateWorkspace(ExtWorkspaceHandleV1),
#[allow(dead_code)] #[allow(dead_code)]
CloseWorkspace(ZcosmicWorkspaceHandleV1), CloseWorkspace(ExtWorkspaceHandleV1),
ActivateToplevel(ExtForeignToplevelHandleV1), ActivateToplevel(ExtForeignToplevelHandleV1),
CloseToplevel(ExtForeignToplevelHandleV1), CloseToplevel(ExtForeignToplevelHandleV1),
StartDrag(DragSurface), StartDrag(DragSurface),
@ -118,7 +118,7 @@ struct Workspace {
name: String, name: String,
// img_for_output: HashMap<wl_output::WlOutput, backend::CaptureImage>, // img_for_output: HashMap<wl_output::WlOutput, backend::CaptureImage>,
img: Option<backend::CaptureImage>, img: Option<backend::CaptureImage>,
handle: ZcosmicWorkspaceHandleV1, handle: ExtWorkspaceHandleV1,
outputs: HashSet<wl_output::WlOutput>, outputs: HashSet<wl_output::WlOutput>,
is_active: bool, is_active: bool,
} }
@ -170,13 +170,13 @@ struct App {
} }
impl App { impl App {
fn workspace_for_handle(&self, handle: &ZcosmicWorkspaceHandleV1) -> Option<&Workspace> { fn workspace_for_handle(&self, handle: &ExtWorkspaceHandleV1) -> Option<&Workspace> {
self.workspaces.iter().find(|i| &i.handle == handle) self.workspaces.iter().find(|i| &i.handle == handle)
} }
fn workspace_for_handle_mut( fn workspace_for_handle_mut(
&mut self, &mut self,
handle: &ZcosmicWorkspaceHandleV1, handle: &ExtWorkspaceHandleV1,
) -> Option<&mut Workspace> { ) -> Option<&mut Workspace> {
self.workspaces.iter_mut().find(|i| &i.handle == handle) self.workspaces.iter_mut().find(|i| &i.handle == handle)
} }
@ -377,13 +377,14 @@ impl Application for App {
backend::Event::CmdSender(sender) => { backend::Event::CmdSender(sender) => {
self.wayland_cmd_sender = Some(sender); self.wayland_cmd_sender = Some(sender);
} }
backend::Event::Workspaces(workspaces) => { backend::Event::Workspaces(mut workspaces) => {
workspaces.sort_by(|(_, w1), (_, w2)| w1.coordinates.cmp(&w2.coordinates));
let old_workspaces = mem::take(&mut self.workspaces); let old_workspaces = mem::take(&mut self.workspaces);
self.workspaces = Vec::new(); self.workspaces = Vec::new();
for (outputs, workspace) in workspaces { for (outputs, workspace) in workspaces {
let is_active = workspace.state.contains(&WEnum::Value( let is_active = workspace
zcosmic_workspace_handle_v1::State::Active, .state
)); .contains(ext_workspace_handle_v1::State::Active);
// XXX efficiency // XXX efficiency
#[allow(clippy::mutable_key_type)] #[allow(clippy::mutable_key_type)]

View file

@ -236,7 +236,7 @@ fn workspaces_sidebar<'a>(
workspaces: impl Iterator<Item = &'a Workspace>, workspaces: impl Iterator<Item = &'a Workspace>,
output: &'a wl_output::WlOutput, output: &'a wl_output::WlOutput,
layout: WorkspaceLayout, layout: WorkspaceLayout,
drop_target: Option<&backend::ZcosmicWorkspaceHandleV1>, drop_target: Option<&backend::ExtWorkspaceHandleV1>,
) -> cosmic::Element<'a, Msg> { ) -> cosmic::Element<'a, Msg> {
let sidebar_entries = workspaces let sidebar_entries = workspaces
.map(|w| workspace_sidebar_entry(w, output, drop_target == Some(&w.handle))) .map(|w| workspace_sidebar_entry(w, output, drop_target == Some(&w.handle)))