Handle global workspaces

This commit is contained in:
Ian Douglas Scott 2023-03-22 10:03:18 -07:00
parent 69a877894a
commit 4e2ef433fd
7 changed files with 87 additions and 40 deletions

4
Cargo.lock generated
View file

@ -365,7 +365,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=e491d91#e491d91d10cc0a6af725d733bada77ae413e459a" source = "git+https://github.com/pop-os/cosmic-protocols#62986ea6ab3b6517f45395e7912f9bddc8dc94e4"
dependencies = [ dependencies = [
"cosmic-protocols", "cosmic-protocols",
"smithay-client-toolkit", "smithay-client-toolkit",
@ -375,7 +375,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=e491d91#e491d91d10cc0a6af725d733bada77ae413e459a" source = "git+https://github.com/pop-os/cosmic-protocols#62986ea6ab3b6517f45395e7912f9bddc8dc94e4"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"wayland-backend", "wayland-backend",

View file

@ -5,7 +5,7 @@ edition = "2021"
[dependencies] [dependencies]
calloop = "0.10.5" calloop = "0.10.5"
cctk = { package = "cosmic-client-toolkit", git = "https://github.com/pop-os/cosmic-protocols", rev = "e491d91" } cctk = { package = "cosmic-client-toolkit", git = "https://github.com/pop-os/cosmic-protocols" }
futures-channel = "0.3.25" futures-channel = "0.3.25"
libcosmic = { git = "https://github.com/pop-os/libcosmic", default-features = false, features = ["tokio", "wayland"] } libcosmic = { git = "https://github.com/pop-os/libcosmic", default-features = false, features = ["tokio", "wayland"] }
tokio = "1.23.0" tokio = "1.23.0"

View file

@ -53,9 +53,9 @@ enum Msg {
#[derive(Debug)] #[derive(Debug)]
struct Workspace { struct Workspace {
name: String, name: String,
img: Option<iced::widget::image::Handle>, img_for_output: HashMap<String, iced::widget::image::Handle>,
handle: zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1, handle: zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1,
output_name: String, output_names: Vec<String>,
is_active: bool, is_active: bool,
} }
@ -63,6 +63,7 @@ struct Workspace {
struct Toplevel { struct Toplevel {
handle: zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, handle: zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1,
info: ToplevelInfo, info: ToplevelInfo,
output_name: Option<String>,
img: Option<iced::widget::image::Handle>, img: Option<iced::widget::image::Handle>,
} }
@ -300,39 +301,42 @@ impl Application for App {
wayland::Event::Workspaces(workspaces) => { wayland::Event::Workspaces(workspaces) => {
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 (output_name, workspace) in workspaces { for (output_names, workspace) in workspaces {
let is_active = workspace.state.contains(&WEnum::Value( let is_active = workspace.state.contains(&WEnum::Value(
zcosmic_workspace_handle_v1::State::Active, zcosmic_workspace_handle_v1::State::Active,
)); ));
// XXX efficiency // XXX efficiency
let img = old_workspaces let img_for_output = old_workspaces
.iter() .iter()
.find(|i| i.handle == workspace.handle) .find(|i| i.handle == workspace.handle)
.and_then(|i| i.img.clone()); .map(|i| i.img_for_output.clone())
.unwrap_or_default();
self.workspaces.push(Workspace { self.workspaces.push(Workspace {
name: workspace.name, name: workspace.name,
handle: workspace.handle, handle: workspace.handle,
output_name, output_names,
img, img_for_output,
is_active, is_active,
}); });
} }
self.update_capture_filter(); self.update_capture_filter();
} }
wayland::Event::NewToplevel(handle, info) => { wayland::Event::NewToplevel(handle, output_name, info) => {
println!("New toplevel: {info:?}"); println!("New toplevel: {info:?}");
self.toplevels.push(Toplevel { self.toplevels.push(Toplevel {
handle, handle,
output_name,
info, info,
img: None, img: None,
}); });
} }
wayland::Event::UpdateToplevel(handle, info) => { wayland::Event::UpdateToplevel(handle, output_name, info) => {
if let Some(toplevel) = if let Some(toplevel) =
self.toplevels.iter_mut().find(|x| x.handle == handle) self.toplevels.iter_mut().find(|x| x.handle == handle)
{ {
toplevel.output_name = output_name;
toplevel.info = info; toplevel.info = info;
} }
} }
@ -341,9 +345,9 @@ impl Application for App {
self.toplevels.remove(idx); self.toplevels.remove(idx);
} }
} }
wayland::Event::WorkspaceCapture(handle, image) => { wayland::Event::WorkspaceCapture(handle, output_name, image) => {
if let Some(workspace) = self.workspace_for_handle_mut(&handle) { if let Some(workspace) = self.workspace_for_handle_mut(&handle) {
workspace.img = Some(image); workspace.img_for_output.insert(output_name, image);
} }
} }
wayland::Event::ToplevelCapture(handle, image) => { wayland::Event::ToplevelCapture(handle, image) => {
@ -435,17 +439,21 @@ fn layer_surface<'a>(app: &'a App, surface: &'a LayerSurface) -> cosmic::Element
workspaces_sidebar( workspaces_sidebar(
app.workspaces app.workspaces
.iter() .iter()
.filter(|i| i.output_name == surface.output_name), .filter(|i| i.output_names.contains(&surface.output_name)),
&surface.output_name
), ),
toplevel_previews(app.toplevels.iter().filter(|i| { toplevel_previews(app.toplevels.iter().filter(|i| {
if i.output_name.as_ref() != Some(&surface.output_name) {
return false;
}
if let Some(workspace) = &i.info.workspace { if let Some(workspace) = &i.info.workspace {
app.workspace_for_handle(workspace).map_or(false, |x| { app.workspace_for_handle(workspace)
x.is_active && x.output_name == surface.output_name .map_or(false, |x| x.is_active)
})
} else { } else {
false false
} }
})), }))
] ]
.spacing(12) .spacing(12)
.height(iced::Length::Fill) .height(iced::Length::Fill)
@ -460,7 +468,10 @@ fn close_button(on_press: Msg) -> cosmic::Element<'static, Msg> {
.into() .into()
} }
fn workspace_sidebar_entry(workspace: &Workspace) -> cosmic::Element<Msg> { fn workspace_sidebar_entry<'a>(
workspace: &'a Workspace,
output_name: &'a str,
) -> cosmic::Element<'a, Msg> {
// TODO style // TODO style
let theme = if workspace.is_active { let theme = if workspace.is_active {
cosmic::theme::Button::Primary cosmic::theme::Button::Primary
@ -472,8 +483,9 @@ fn workspace_sidebar_entry(workspace: &Workspace) -> cosmic::Element<Msg> {
widget::button(widget::column![ widget::button(widget::column![
widget::Image::new( widget::Image::new(
workspace workspace
.img .img_for_output
.clone() .get(output_name)
.cloned()
.unwrap_or_else(|| widget::image::Handle::from_pixels( .unwrap_or_else(|| widget::image::Handle::from_pixels(
1, 1,
1, 1,
@ -491,11 +503,16 @@ fn workspace_sidebar_entry(workspace: &Workspace) -> cosmic::Element<Msg> {
fn workspaces_sidebar<'a>( fn workspaces_sidebar<'a>(
workspaces: impl Iterator<Item = &'a Workspace>, workspaces: impl Iterator<Item = &'a Workspace>,
output_name: &'a str,
) -> cosmic::Element<'a, Msg> { ) -> cosmic::Element<'a, Msg> {
widget::column(workspaces.map(workspace_sidebar_entry).collect()) widget::column(
.width(iced::Length::Fill) workspaces
.height(iced::Length::Fill) .map(|w| workspace_sidebar_entry(w, output_name))
.into() .collect(),
)
.width(iced::Length::Fill)
.height(iced::Length::Fill)
.into()
// New workspace // New workspace
} }

View file

@ -57,17 +57,20 @@ pub enum Event {
ToplevelManager(zcosmic_toplevel_manager_v1::ZcosmicToplevelManagerV1), ToplevelManager(zcosmic_toplevel_manager_v1::ZcosmicToplevelManagerV1),
WorkspaceManager(zcosmic_workspace_manager_v1::ZcosmicWorkspaceManagerV1), WorkspaceManager(zcosmic_workspace_manager_v1::ZcosmicWorkspaceManagerV1),
// XXX Output name rather than `WlOutput` // XXX Output name rather than `WlOutput`
Workspaces(Vec<(String, cctk::workspace::Workspace)>), Workspaces(Vec<(Vec<String>, cctk::workspace::Workspace)>),
WorkspaceCapture( WorkspaceCapture(
zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1, zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1,
String,
image::Handle, image::Handle,
), ),
NewToplevel( NewToplevel(
zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1,
Option<String>,
ToplevelInfo, ToplevelInfo,
), ),
UpdateToplevel( UpdateToplevel(
zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1,
Option<String>,
ToplevelInfo, ToplevelInfo,
), ),
CloseToplevel(zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1), CloseToplevel(zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1),

View file

@ -1,7 +1,7 @@
use cctk::{ use cctk::{
cosmic_protocols::screencopy::v1::client::zcosmic_screencopy_session_v1, cosmic_protocols::screencopy::v1::client::zcosmic_screencopy_session_v1,
screencopy::{BufferInfo, ScreencopyHandler, ScreencopyState}, screencopy::{BufferInfo, ScreencopyHandler, ScreencopyState},
wayland_client::{protocol::wl_shm, Connection, QueueHandle, WEnum}, wayland_client::{protocol::wl_shm, Connection, Proxy, QueueHandle, WEnum},
}; };
use super::{AppData, Buffer, Capture, CaptureSource, Event}; use super::{AppData, Buffer, Capture, CaptureSource, Event};
@ -71,13 +71,20 @@ impl ScreencopyHandler for AppData {
let mut buffer = capture.buffer.lock().unwrap(); let mut buffer = capture.buffer.lock().unwrap();
let image = unsafe { buffer.as_mut().unwrap().to_image() }; let image = unsafe { buffer.as_mut().unwrap().to_image() };
let event = match &capture.source { match &capture.source {
CaptureSource::Toplevel(toplevel) => Event::ToplevelCapture(toplevel.clone(), image), CaptureSource::Toplevel(toplevel) => {
CaptureSource::Workspace(workspace, _) => { self.send_event(Event::ToplevelCapture(toplevel.clone(), image))
Event::WorkspaceCapture(workspace.clone(), image) }
CaptureSource::Workspace(workspace, output) => {
if let Some(Some(output_name)) = self.output_names.get(&output.id()) {
self.send_event(Event::WorkspaceCapture(
workspace.clone(),
output_name.clone(),
image,
));
}
} }
}; };
self.send_event(event);
session.destroy(); session.destroy();
// Capture again on damage // Capture again on damage

View file

@ -5,7 +5,7 @@ use cctk::{
}, },
toplevel_info::{ToplevelInfoHandler, ToplevelInfoState}, toplevel_info::{ToplevelInfoHandler, ToplevelInfoState},
toplevel_management::{ToplevelManagerHandler, ToplevelManagerState}, toplevel_management::{ToplevelManagerHandler, ToplevelManagerState},
wayland_client::{Connection, QueueHandle, WEnum}, wayland_client::{Connection, Proxy, QueueHandle, WEnum},
}; };
use super::{AppData, CaptureSource, Event}; use super::{AppData, CaptureSource, Event};
@ -23,7 +23,15 @@ impl ToplevelInfoHandler for AppData {
toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1,
) { ) {
let info = self.toplevel_info_state.info(toplevel).unwrap(); let info = self.toplevel_info_state.info(toplevel).unwrap();
self.send_event(Event::NewToplevel(toplevel.clone(), info.clone())); let output_name = info
.output
.as_ref()
.and_then(|o| self.output_names.get(&o.id()).cloned()?);
self.send_event(Event::NewToplevel(
toplevel.clone(),
output_name,
info.clone(),
));
self.add_capture_source(CaptureSource::Toplevel(toplevel.clone())); self.add_capture_source(CaptureSource::Toplevel(toplevel.clone()));
} }
@ -35,7 +43,15 @@ impl ToplevelInfoHandler for AppData {
toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1, toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1,
) { ) {
let info = self.toplevel_info_state.info(toplevel).unwrap(); let info = self.toplevel_info_state.info(toplevel).unwrap();
self.send_event(Event::UpdateToplevel(toplevel.clone(), info.clone())); let output_name = info
.output
.as_ref()
.and_then(|o| self.output_names.get(&o.id()).cloned()?);
self.send_event(Event::UpdateToplevel(
toplevel.clone(),
output_name,
info.clone(),
));
} }
fn toplevel_closed( fn toplevel_closed(

View file

@ -18,11 +18,15 @@ impl WorkspaceHandler for AppData {
for group in self.workspace_state.workspace_groups() { for group in self.workspace_state.workspace_groups() {
for workspace in &group.workspaces { for workspace in &group.workspaces {
if let Some(output) = group.output.as_ref() { let output_names: Vec<_> = group
if let Some(output_name) = self.output_names.get(&output.id()).unwrap().clone() .outputs
{ .iter()
workspaces.push((output_name, workspace.clone())); .filter_map(|output| self.output_names.get(&output.id()).cloned()?)
.collect();
if !output_names.is_empty() {
workspaces.push((output_names, workspace.clone()));
for output in &group.outputs {
self.add_capture_source(CaptureSource::Workspace( self.add_capture_source(CaptureSource::Workspace(
workspace.handle.clone(), workspace.handle.clone(),
output.clone(), output.clone(),