From 560ebc0bf58cff2d4757f4cdc0cf23271ba149c8 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 19 Jul 2022 11:33:19 -0400 Subject: [PATCH] wip: use toplevel protocols --- Cargo.lock | 54 +- applets/cosmic-app-list/Cargo.toml | 12 +- .../data/com.system76.CosmicAppList.desktop | 1 + .../cosmic-app-list/src/apps_container/mod.rs | 9 +- .../cosmic-app-list/src/apps_window/mod.rs | 6 +- applets/cosmic-app-list/src/config.rs | 31 ++ applets/cosmic-app-list/src/dock_item/imp.rs | 4 +- applets/cosmic-app-list/src/dock_item/mod.rs | 7 +- applets/cosmic-app-list/src/dock_list/imp.rs | 4 +- applets/cosmic-app-list/src/dock_list/mod.rs | 45 +- .../cosmic-app-list/src/dock_object/mod.rs | 2 +- .../cosmic-app-list/src/dock_popover/imp.rs | 4 +- .../cosmic-app-list/src/dock_popover/mod.rs | 36 +- applets/cosmic-app-list/src/main.rs | 368 +++++++------ applets/cosmic-app-list/src/utils.rs | 19 +- applets/cosmic-app-list/src/wayland.rs | 512 ++++++++++++++++++ applets/cosmic-app-list/src/wayland_source.rs | 219 ++++++++ applets/cosmic-applet-workspaces/Cargo.toml | 5 +- .../resources/ext-workspace-unstable-v1.xml | 306 ----------- .../cosmic-applet-workspaces/src/wayland.rs | 52 +- 20 files changed, 1073 insertions(+), 623 deletions(-) create mode 100644 applets/cosmic-app-list/src/config.rs create mode 100644 applets/cosmic-app-list/src/wayland.rs create mode 100644 applets/cosmic-app-list/src/wayland_source.rs delete mode 100644 applets/cosmic-applet-workspaces/data/resources/ext-workspace-unstable-v1.xml diff --git a/Cargo.lock b/Cargo.lock index 48655d91..54169c86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -277,8 +277,10 @@ name = "cosmic-app-list" version = "0.1.0" dependencies = [ "anyhow", + "calloop", "cascade", "cosmic-panel-config", + "cosmic-protocols", "futures", "futures-util", "gio 0.16.0", @@ -288,13 +290,18 @@ dependencies = [ "i18n-embed", "i18n-embed-fl", "libcosmic", + "log", + "nix 0.24.1", "once_cell", "pretty_env_logger", "relm4-macros", + "ron", "rust-embed", "serde", "serde_json", "tokio", + "wayland-backend", + "wayland-client 0.30.0-beta.8", "xdg", ] @@ -436,14 +443,13 @@ dependencies = [ "i18n-embed", "i18n-embed-fl", "log", - "nix 0.24.1", + "nix 0.22.3", "once_cell", "pretty_env_logger", "rust-embed", "tokio", "wayland-backend", - "wayland-client 0.30.0-beta.7", - "wayland-commons", + "wayland-client 0.30.0-beta.8", ] [[package]] @@ -493,13 +499,13 @@ dependencies = [ [[package]] name = "cosmic-protocols" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-protocols#1962ffdca3d9c914929eea358ebeab61ff2217a8" +source = "git+https://github.com/pop-os/cosmic-protocols#81d6a50bdc91af5968f87785fc19a16cf261c96b" dependencies = [ "bitflags", "wayland-backend", - "wayland-client 0.30.0-beta.7", - "wayland-protocols 0.30.0-beta.7", - "wayland-scanner 0.30.0-beta.7", + "wayland-client 0.30.0-beta.8", + "wayland-protocols 0.30.0-beta.8", + "wayland-scanner 0.30.0-beta.8", ] [[package]] @@ -2135,7 +2141,7 @@ checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "relm4" version = "0.5.0-beta.1" -source = "git+https://github.com/relm4/relm4?branch=next#746d244004e23764294b23519f6f8be1002c1ceb" +source = "git+https://github.com/Relm4/Relm4.git?branch=next#746d244004e23764294b23519f6f8be1002c1ceb" dependencies = [ "async-broadcast", "async-oneshot", @@ -2152,7 +2158,7 @@ dependencies = [ [[package]] name = "relm4-macros" version = "0.5.0-beta.1" -source = "git+https://github.com/relm4/relm4?branch=next#746d244004e23764294b23519f6f8be1002c1ceb" +source = "git+https://github.com/Relm4/Relm4.git?branch=next#746d244004e23764294b23519f6f8be1002c1ceb" dependencies = [ "proc-macro2", "quote", @@ -2768,17 +2774,16 @@ checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" [[package]] name = "wayland-backend" -version = "0.1.0-beta.7" +version = "0.1.0-beta.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a861eb7cd51f67de60f228a570f142396d94759babcb427f861071ffb0757c9e" +checksum = "0ee8e77c63b0cdc68bfc7b407b862b0fe2718949ce060b32d4f94ef1ea9607a4" dependencies = [ "cc", "downcast-rs", - "log", "nix 0.24.1", "scoped-tls", "smallvec", - "wayland-sys 0.30.0-beta.7", + "wayland-sys 0.30.0-beta.8", ] [[package]] @@ -2798,18 +2803,17 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.30.0-beta.7" +version = "0.30.0-beta.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dca5290499da69c21fcf64b4021886963511b888af056dbfb6bebfb7e1587e6" +checksum = "0f9e0d862c23f07b2c4b49de66b0680948af5dd1d2def17f1ddc16520352bf14" dependencies = [ "bitflags", "futures-channel", "futures-core", - "log", "nix 0.24.1", "thiserror", "wayland-backend", - "wayland-scanner 0.30.0-beta.7", + "wayland-scanner 0.30.0-beta.8", ] [[package]] @@ -2838,14 +2842,14 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.30.0-beta.7" +version = "0.30.0-beta.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d64adbf2e145b9da69ff0e9bb72fc513182978c826fc6f704c05f0f80b663a6d" +checksum = "e47c45a60d531d5a513601f47f51a4743901836778ddae208ae9124606be1719" dependencies = [ "bitflags", "wayland-backend", - "wayland-client 0.30.0-beta.7", - "wayland-scanner 0.30.0-beta.7", + "wayland-client 0.30.0-beta.8", + "wayland-scanner 0.30.0-beta.8", ] [[package]] @@ -2861,9 +2865,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.30.0-beta.7" +version = "0.30.0-beta.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3baff545c2f5a0c32d796595d0b3c8fafccf29e72e557ff1969fe552ff093d6" +checksum = "87933ccc3df4f6335cf240aca0647aa34319fdd693dda503f645ca4df4e10386" dependencies = [ "proc-macro2", "quote", @@ -2882,9 +2886,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.30.0-beta.7" +version = "0.30.0-beta.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f62b62672d36b6cf2f7d936f95c9f5894c0609190fa789c2ce46b73912baf239" +checksum = "beca223ed017df1b356ff181d4d6e7f2b135418c4888df5bb02df7a563f02ab0" dependencies = [ "dlib", "log", diff --git a/applets/cosmic-app-list/Cargo.toml b/applets/cosmic-app-list/Cargo.toml index 10dfc0d4..5b3cee7f 100644 --- a/applets/cosmic-app-list/Cargo.toml +++ b/applets/cosmic-app-list/Cargo.toml @@ -6,12 +6,12 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] cosmic-panel-config = {git = "https://github.com/pop-os/cosmic-panel", features = ["gtk4"] } +cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", default-features = false, features = ["client"] } cascade = "1.0.0" gtk4 = { git = "https://github.com/gtk-rs/gtk4-rs", features = ["v4_4"] } gio = { git = "https://github.com/gtk-rs/gtk-rs-core" } libcosmic = { git = "https://github.com/pop-os/libcosmic", branch = "relm4-next" } relm4-macros = { git = "https://github.com/Relm4/Relm4.git", branch = "next" } -serde = "1.0.136" serde_json = "1.0.78" tokio = { version = "1.16.1", features = ["sync"] } futures = "0.3.19" @@ -20,10 +20,18 @@ once_cell = "1.9.0" xdg = "2.4.0" gsk4 = { git = "https://github.com/gtk-rs/gtk4-rs" } pretty_env_logger = "0.4" -anyhow = "1.0.50" i18n-embed = { version = "0.13.4", features = ["fluent-system", "desktop-requester"] } i18n-embed-fl = "0.6.4" rust-embed = "6.3.0" +calloop = "0.10.1" +wayland-backend = { version = "0.1.0-beta.7" } +wayland-client = { version = "0.30.0-beta.7" } +nix = "0.24.1" +# config +anyhow = "1.0.53" +ron = "0.7.0" +serde = { version = "1.0.136", features = ["derive"] } +log = "0.4" [build-dependencies] glib-build-tools = { git = "https://github.com/gtk-rs/gtk-rs-core" } diff --git a/applets/cosmic-app-list/data/com.system76.CosmicAppList.desktop b/applets/cosmic-app-list/data/com.system76.CosmicAppList.desktop index 369c6c69..0e47f470 100644 --- a/applets/cosmic-app-list/data/com.system76.CosmicAppList.desktop +++ b/applets/cosmic-app-list/data/com.system76.CosmicAppList.desktop @@ -10,3 +10,4 @@ Keywords=Gnome;GTK; Icon=com.system76.CosmicAppList.svg StartupNotify=true NoDisplay=true +HostWaylandDisplay=true \ No newline at end of file diff --git a/applets/cosmic-app-list/src/apps_container/mod.rs b/applets/cosmic-app-list/src/apps_container/mod.rs index b60a0ac0..304bddb2 100644 --- a/applets/cosmic-app-list/src/apps_container/mod.rs +++ b/applets/cosmic-app-list/src/apps_container/mod.rs @@ -1,9 +1,10 @@ use std::env; +use crate::TX; // SPDX-License-Identifier: MPL-2.0-only use crate::dock_list::DockList; use crate::dock_list::DockListType; -use crate::utils::Event; +use crate::utils::AppListEvent; use cascade::cascade; use cosmic_panel_config::{PanelAnchor, CosmicPanelConfig}; use gtk4::prelude::*; @@ -21,7 +22,7 @@ glib::wrapper! { } impl AppsContainer { - pub fn new(tx: Sender) -> Self { + pub fn new() -> Self { let self_: Self = glib::Object::new(&[]).expect("Failed to create AppsContainer"); let imp = imp::AppsContainer::from_instance(&self_); @@ -34,7 +35,7 @@ impl AppsContainer { let config = CosmicPanelConfig::load_from_env().unwrap_or_default(); - let saved_app_list_view = DockList::new(DockListType::Saved, tx.clone(), config.clone()); + let saved_app_list_view = DockList::new(DockListType::Saved, config.clone()); self_.append(&saved_app_list_view); // let separator_container = cascade! { @@ -52,7 +53,7 @@ impl AppsContainer { // ..add_css_class("dock_separator"); // }; // separator_container.append(&separator); - let active_app_list_view = DockList::new(DockListType::Active, tx, config.clone()); + let active_app_list_view = DockList::new(DockListType::Active, config.clone()); self_.append(&active_app_list_view); // self_.connect_orientation_notify(glib::clone!(@weak separator => move |c| { // dbg!(c.orientation()); diff --git a/applets/cosmic-app-list/src/apps_window/mod.rs b/applets/cosmic-app-list/src/apps_window/mod.rs index 2f454905..bf72eb94 100644 --- a/applets/cosmic-app-list/src/apps_window/mod.rs +++ b/applets/cosmic-app-list/src/apps_window/mod.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MPL-2.0-only -use crate::{apps_container::AppsContainer, fl, Event}; +use crate::{apps_container::AppsContainer, fl, AppListEvent}; use cascade::cascade; use gtk4::{ gio, @@ -20,7 +20,7 @@ glib::wrapper! { } impl CosmicAppListWindow { - pub fn new(app: >k4::Application, tx: mpsc::Sender) -> Self { + pub fn new(app: >k4::Application) -> Self { let self_: Self = Object::new(&[("application", app)]).expect("Failed to create `CosmicAppListWindow`."); let imp = imp::CosmicAppListWindow::from_instance(&self_); @@ -34,7 +34,7 @@ impl CosmicAppListWindow { ..set_title(Some(&fl!("cosmic-app-list"))); ..add_css_class("transparent"); }; - let app_list = AppsContainer::new(tx); + let app_list = AppsContainer::new(); self_.set_child(Some(&app_list)); imp.inner.set(app_list).unwrap(); diff --git a/applets/cosmic-app-list/src/config.rs b/applets/cosmic-app-list/src/config.rs new file mode 100644 index 00000000..0d24f88b --- /dev/null +++ b/applets/cosmic-app-list/src/config.rs @@ -0,0 +1,31 @@ +use std::fmt::Debug; +use std::fs::File; +use anyhow::anyhow; +use serde::Deserialize; +use xdg::BaseDirectories; +use crate::ID; + +#[derive(Debug, Clone, Deserialize)] +pub enum TopLevelFilter { + ActiveWorkspace, + ConfiguredOutput, +} + +#[derive(Debug, Clone, Default, Deserialize)] +pub struct AppListConfig { + pub filter_top_levels: Option, +} + +impl AppListConfig { + /// load config with the provided name + pub fn load() -> anyhow::Result { + let file= match BaseDirectories::new().ok().and_then(|dirs| dirs.find_config_file(format!("{ID}/config.ron"))).and_then(|p| File::open(p).ok()) { + Some(path) => path, + _ => { + anyhow::bail!("Failed to load config"); + } + }; + + ron::de::from_reader::<_, AppListConfig>(file).map_err(|err| anyhow!("Failed to parse config file: {}", err)) + } +} \ No newline at end of file diff --git a/applets/cosmic-app-list/src/dock_item/imp.rs b/applets/cosmic-app-list/src/dock_item/imp.rs index cc8a7991..2fe3a9f2 100644 --- a/applets/cosmic-app-list/src/dock_item/imp.rs +++ b/applets/cosmic-app-list/src/dock_item/imp.rs @@ -11,7 +11,7 @@ use std::rc::Rc; use tokio::sync::mpsc::Sender; use crate::dock_popover::DockPopover; -use crate::utils::Event; +use crate::utils::AppListEvent; #[derive(Debug, Default)] pub struct DockItem { @@ -20,7 +20,7 @@ pub struct DockItem { pub item_box: Rc>, pub popover: Rc>, pub popover_menu: Rc>>, - pub tx: OnceCell>, + pub tx: OnceCell>, pub icon_size: Rc>, } diff --git a/applets/cosmic-app-list/src/dock_item/mod.rs b/applets/cosmic-app-list/src/dock_item/mod.rs index 046e6956..f65e4deb 100644 --- a/applets/cosmic-app-list/src/dock_item/mod.rs +++ b/applets/cosmic-app-list/src/dock_item/mod.rs @@ -3,7 +3,7 @@ use crate::dock_object::DockObject; use crate::dock_popover::DockPopover; use crate::utils::BoxedWindowList; -use crate::utils::Event; +use crate::utils::AppListEvent; use cascade::cascade; use cosmic_panel_config::PanelAnchor; use gtk4::glib; @@ -25,7 +25,7 @@ glib::wrapper! { } impl DockItem { - pub fn new(tx: Sender, icon_size: u32) -> Self { + pub fn new(icon_size: u32) -> Self { let self_: DockItem = glib::Object::new(&[]).expect("Failed to create DockItem"); let item_box = Box::new(Orientation::Vertical, 0); @@ -66,7 +66,7 @@ impl DockItem { }); let popover_menu = cascade! { - DockPopover::new(tx.clone()); + DockPopover::new(); ..add_css_class("popover_menu"); }; popover.set_child(Some(&popover_menu)); @@ -87,7 +87,6 @@ impl DockItem { imp.item_box.replace(item_box); imp.popover.replace(popover); imp.popover_menu.replace(Some(popover_menu)); - imp.tx.set(tx).unwrap(); self_ } diff --git a/applets/cosmic-app-list/src/dock_list/imp.rs b/applets/cosmic-app-list/src/dock_list/imp.rs index 7f8603e6..e9ad2b6e 100644 --- a/applets/cosmic-app-list/src/dock_list/imp.rs +++ b/applets/cosmic-app-list/src/dock_list/imp.rs @@ -10,7 +10,7 @@ use std::cell::{Cell, RefCell}; use std::rc::Rc; use tokio::sync::mpsc; -use crate::utils::Event; +use crate::utils::AppListEvent; #[derive(Debug, Default)] pub struct DockList { @@ -24,7 +24,7 @@ pub struct DockList { pub drag_cancel_signal: Rc>>, pub popover_menu_index: Rc>>, pub position: Rc>, - pub tx: OnceCell>, + pub tx: OnceCell>, pub config: OnceCell } diff --git a/applets/cosmic-app-list/src/dock_list/mod.rs b/applets/cosmic-app-list/src/dock_list/mod.rs index dda1d6e7..e3d4f1e0 100644 --- a/applets/cosmic-app-list/src/dock_list/mod.rs +++ b/applets/cosmic-app-list/src/dock_list/mod.rs @@ -1,9 +1,11 @@ // SPDX-License-Identifier: MPL-2.0-only +use crate::{TX, WAYLAND_TX}; use crate::dock_item::DockItem; use crate::dock_object::DockObject; use crate::utils::data_path; -use crate::utils::{BoxedWindowList, Event, Item}; +use crate::utils::{BoxedWindowList, AppListEvent}; +use crate::wayland::{Toplevel, ToplevelEvent}; use cascade::cascade; use gio::DesktopAppInfo; use gio::Icon; @@ -49,11 +51,10 @@ impl Default for DockListType { } impl DockList { - pub fn new(type_: DockListType, tx: Sender, config: CosmicPanelConfig) -> Self { + pub fn new(type_: DockListType, config: CosmicPanelConfig) -> Self { let self_: DockList = glib::Object::new(&[]).expect("Failed to create DockList"); let imp = imp::DockList::from_instance(&self_); imp.type_.set(type_).unwrap(); - imp.tx.set(tx).unwrap(); imp.config.set(config).unwrap(); self_.layout(); //dnd behavior is different for each type, as well as the data in the model @@ -219,7 +220,6 @@ impl DockList { let model = self.model(); let list_view = &imp.list_view.get().unwrap(); let popover_menu_index = &imp.popover_menu_index; - let tx = imp.tx.get().unwrap().clone(); controller.connect_released(glib::clone!(@weak model, @weak list_view, @weak popover_menu_index => move |self_, _, x, y| { let max_x = list_view.allocated_width(); let max_y = list_view.allocated_height(); @@ -238,13 +238,10 @@ impl DockList { // dbg!(click_modifier); // Launch the application when an item of the list is activated - let tx = tx.clone(); - let focus_window = move |first_focused_item: &Item| { - let entity = first_focused_item.entity; - let tx = tx.clone(); - glib::MainContext::default().spawn_local(async move { - let _ = tx.clone().send(Event::Activate(entity)).await; - }); + let focus_window = move |first_focused_item: &Toplevel| { + let toplevel_handle = first_focused_item.toplevel_handle.clone(); + let tx = WAYLAND_TX.get().unwrap().clone(); + let _ = tx.clone().send(ToplevelEvent::Activate(toplevel_handle)); }; let old_index = popover_menu_index.get(); if let Some(old_index) = old_index { @@ -322,7 +319,6 @@ impl DockList { let list_view = &imp.list_view.get().unwrap(); let drag_end = &imp.drag_end_signal; let drag_source = &imp.drag_source.get().unwrap(); - let tx = imp.tx.get().unwrap().clone(); drop_controller.connect_drop( glib::clone!(@weak model, @weak list_view, @weak drag_end, @weak drag_source => @default-return true, move |_self, drop_value, x, y| { //calculate insertion location @@ -388,10 +384,8 @@ impl DockList { // dbg!("rejecting drop"); _self.reject(); } - let tx = tx.clone(); - glib::MainContext::default().spawn_local(async move { - let _ = tx.send(Event::RefreshFromCache).await; - }); + let tx = TX.get().unwrap().clone(); + let _ = tx.send(AppListEvent::Refresh); true }), ); @@ -419,7 +413,6 @@ impl DockList { let drag_end = &imp.drag_end_signal; let drag_cancel = &imp.drag_cancel_signal; let type_ = *type_; - let tx = imp.tx.get().unwrap().clone(); list_view.add_controller(&drag_source); drag_source.connect_prepare(glib::clone!(@weak model, @weak list_view, @weak drag_end, @weak drag_cancel => @default-return None, move |self_, x, _y| { let max_x = list_view.allocated_width(); @@ -430,30 +423,25 @@ impl DockList { let index = (x * n_buckets as f64 / (max_x as f64 + 0.1)) as u32; if let Some(item) = model.item(index) { if type_ == DockListType::Saved { - let tx1 = tx.clone(); if let Some(old_handle) = drag_end.replace(Some(self_.connect_drag_end( glib::clone!(@weak model => move |_self, _drag, _delete_data| { if _delete_data { model.remove(index); - let tx = tx1.clone(); - glib::MainContext::default().spawn_local(async move { - let _ = tx.send(Event::RefreshFromCache).await; - }); + let tx = TX.get().unwrap().clone(); + let _ = tx.send(AppListEvent::Refresh); + }; }), ))) { glib::signal_handler_disconnect(self_, old_handle); } - let tx = tx.clone(); if let Some(old_handle) = drag_cancel.replace(Some(self_.connect_drag_cancel( glib::clone!(@weak model => @default-return false, move |_self, _drag, cancel_reason| { if cancel_reason != gdk::DragCancelReason::UserCancelled { model.remove(index); - let tx = tx.clone(); - glib::MainContext::default().spawn_local(async move { - let _ = tx.send(Event::RefreshFromCache).await; - }); + let tx = TX.get().unwrap().clone(); + let _ = tx.send(AppListEvent::Refresh); true } else { false @@ -515,11 +503,10 @@ impl DockList { let popover_menu_index = &imp.popover_menu_index; let factory = SignalListItemFactory::new(); let model = imp.model.get().expect("Failed to get saved app model."); - let tx = imp.tx.get().unwrap().clone(); let icon_size = imp.config.get().unwrap().get_applet_icon_size(); factory.connect_setup( glib::clone!(@weak popover_menu_index, @weak model => move |_, list_item| { - let dock_item = DockItem::new(tx.clone(), icon_size); + let dock_item = DockItem::new(icon_size); dock_item .connect_local("popover-closed", false, move |_| { if let Some(old_index) = popover_menu_index.replace(None) { diff --git a/applets/cosmic-app-list/src/dock_object/mod.rs b/applets/cosmic-app-list/src/dock_object/mod.rs index f484477d..d27df1e8 100644 --- a/applets/cosmic-app-list/src/dock_object/mod.rs +++ b/applets/cosmic-app-list/src/dock_object/mod.rs @@ -95,7 +95,7 @@ impl DockObject { if let Some(path) = path.to_str() { if let Some(app_info) = gio::DesktopAppInfo::new(path) { if app_info.should_show() - && first.description.as_str() == app_info.name().as_str() + && first.name.as_str() == app_info.name().as_str() { return Some(app_info); } diff --git a/applets/cosmic-app-list/src/dock_popover/imp.rs b/applets/cosmic-app-list/src/dock_popover/imp.rs index 08ee90f3..5b586620 100644 --- a/applets/cosmic-app-list/src/dock_popover/imp.rs +++ b/applets/cosmic-app-list/src/dock_popover/imp.rs @@ -13,7 +13,7 @@ use once_cell::sync::OnceCell; use tokio::sync::mpsc::Sender; use crate::dock_object::DockObject; -use crate::utils::Event; +use crate::utils::AppListEvent; #[derive(Debug, Default)] pub struct DockPopover { @@ -26,7 +26,7 @@ pub struct DockPopover { pub quit_all_item: Rc>, //TODO figure out how to use lifetimes with glib::wrapper! macro pub dock_object: Rc>>, - pub tx: OnceCell>, + pub tx: OnceCell>, } #[glib::object_subclass] diff --git a/applets/cosmic-app-list/src/dock_popover/mod.rs b/applets/cosmic-app-list/src/dock_popover/mod.rs index a10d51fa..d3019677 100644 --- a/applets/cosmic-app-list/src/dock_popover/mod.rs +++ b/applets/cosmic-app-list/src/dock_popover/mod.rs @@ -9,9 +9,11 @@ use gtk4::{prelude::*, Label}; use gtk4::{Box, Button, Image, ListBox, Orientation}; use tokio::sync::mpsc::Sender; +use crate::wayland::ToplevelEvent; +use crate::{TX, WAYLAND_TX}; use crate::dock_object::DockObject; use crate::utils::BoxedWindowList; -use crate::utils::Event; +use crate::utils::AppListEvent; mod imp; @@ -22,10 +24,9 @@ glib::wrapper! { } impl DockPopover { - pub fn new(tx: Sender) -> Self { + pub fn new() -> Self { let self_: DockPopover = glib::Object::new(&[]).expect("Failed to create DockList"); let imp = imp::DockPopover::from_instance(&self_); - imp.tx.set(tx).unwrap(); self_.layout(); //dnd behavior is different for each type, as well as the data in the model self_ @@ -192,30 +193,24 @@ impl DockPopover { self_.emit_hide(); })); - let tx = imp.tx.get().unwrap().clone(); let self_ = self.clone(); quit_all_item.connect_clicked(glib::clone!(@weak dock_object => move |_| { let active = dock_object.property::("active").0; for w in active { - let entity = w.entity; - let tx = tx.clone(); - glib::MainContext::default().spawn_local(async move { - let _ = tx.clone().send(Event::Close(entity)).await; - }); + let t = w.toplevel_handle.clone(); + let tx = WAYLAND_TX.get().unwrap().clone(); + let _ = tx.clone().send(ToplevelEvent::Close(t)); } self_.emit_hide(); })); - let tx = imp.tx.get().unwrap().clone(); let self_ = self.clone(); favorite_item.connect_clicked(glib::clone!(@weak dock_object => move |_| { let saved = dock_object.property::("saved"); - let tx = tx.clone(); - glib::MainContext::default().spawn_local(async move { - if let Some(name) = dock_object.get_name() { - let _ = tx.clone().send(Event::Favorite((name, !saved))).await; - } - }); + if let Some(name) = dock_object.get_name() { + let tx = TX.get().unwrap().clone(); + let _ = tx.clone().send(AppListEvent::Favorite((name, !saved))); + } self_.emit_hide(); })); @@ -227,16 +222,13 @@ impl DockPopover { // }), // ); - let tx = imp.tx.get().unwrap().clone(); let self_ = self.clone(); window_listbox.connect_row_activated( glib::clone!(@weak dock_object => move |_, item| { let active = dock_object.property::("active").0; - let entity = active[usize::try_from(item.index()).unwrap()].entity; - let tx = tx.clone(); - glib::MainContext::default().spawn_local(async move { - let _ = tx.send(Event::Activate(entity)).await; - }); + let t = active[usize::try_from(item.index()).unwrap()].toplevel_handle.clone(); + let tx = WAYLAND_TX.get().unwrap().clone(); + let _ = tx.send(ToplevelEvent::Activate(t)); self_.emit_hide(); }), ); diff --git a/applets/cosmic-app-list/src/main.rs b/applets/cosmic-app-list/src/main.rs index 3da8b078..beb5d0dd 100644 --- a/applets/cosmic-app-list/src/main.rs +++ b/applets/cosmic-app-list/src/main.rs @@ -1,17 +1,19 @@ // SPDX-License-Identifier: MPL-2.0-only use apps_window::CosmicAppListWindow; +use calloop::channel::SyncSender; use dock_list::DockListType; use dock_object::DockObject; use gio::{ApplicationFlags, DesktopAppInfo}; use gtk4::gdk::Display; use gtk4::{glib, prelude::*, CssProvider, StyleContext}; use once_cell::sync::OnceCell; +use wayland::{ToplevelEvent, Toplevel}; use std::collections::BTreeMap; use std::sync::{Arc, Mutex}; use std::time::Duration; use tokio::sync::mpsc; -use utils::{block_on, BoxedWindowList, Event, Item, DEST, PATH}; +use utils::{block_on, BoxedWindowList, AppListEvent, DEST, PATH}; mod apps_container; mod apps_window; @@ -21,9 +23,13 @@ mod dock_object; mod dock_popover; mod localize; mod utils; +mod wayland; +mod wayland_source; +mod config; const ID: &str = "com.system76.CosmicAppList"; -static TX: OnceCell> = OnceCell::new(); +static TX: OnceCell> = OnceCell::new(); +static WAYLAND_TX: OnceCell> = OnceCell::new(); pub fn localize() { let localizer = crate::localize::localizer(); @@ -57,202 +63,206 @@ fn main() { app.connect_activate(|app| { load_css(); - let (tx, mut rx) = mpsc::channel(100); + let (tx, rx) = glib::MainContext::channel(glib::Priority::default()); - let window = CosmicAppListWindow::new(app, tx.clone()); + let window = CosmicAppListWindow::new(app); + let apps_container = apps_container::AppsContainer::new(); + let wayland_tx = wayland::spawn_toplevels(); - let apps_container = apps_container::AppsContainer::new(tx.clone()); - let cached_results = Arc::new(Mutex::new(Vec::new())); + WAYLAND_TX.set(wayland_tx).unwrap(); + + + + let mut cached_results = Vec::new(); // let zbus_conn = spawn_zbus(tx.clone(), Arc::clone(&cached_results)); TX.set(tx.clone()).unwrap(); - let _ = glib::MainContext::default().spawn_local(async move { - while let Some(event) = rx.recv().await { - match event { - Event::Activate(_) => { - // let _activate_window = zbus_conn - // .call_method(Some(DEST), PATH, Some(DEST), "WindowFocus", &((e,))) - // .await - // .expect("Failed to focus selected window"); - } - Event::Close(_) => { - // let _activate_window = zbus_conn - // .call_method(Some(DEST), PATH, Some(DEST), "WindowQuit", &((e,))) - // .await - // .expect("Failed to close selected window"); - } - Event::Favorite((name, should_favorite)) => { - let saved_app_model = apps_container.model(DockListType::Saved); - let active_app_model = apps_container.model(DockListType::Active); - if should_favorite { - let mut cur: u32 = 0; - let mut index: Option = None; - while let Some(item) = active_app_model.item(cur) { - if let Ok(cur_dock_object) = item.downcast::() { - if cur_dock_object.get_path() == Some(name.clone()) { - cur_dock_object.set_saved(true); - index = Some(cur); - } + rx.attach(None, glib::clone!(@weak window => @default-return glib::prelude::Continue(true), move |event| { + match event { + AppListEvent::Activate(_) => { + // let _activate_window = zbus_conn + // .call_method(Some(DEST), PATH, Some(DEST), "WindowFocus", &((e,))) + // .await + // .expect("Failed to focus selected window"); + } + AppListEvent::Close(_) => { + // let _activate_window = zbus_conn + // .call_method(Some(DEST), PATH, Some(DEST), "WindowQuit", &((e,))) + // .await + // .expect("Failed to close selected window"); + } + AppListEvent::Favorite((name, should_favorite)) => { + let saved_app_model = apps_container.model(DockListType::Saved); + let active_app_model = apps_container.model(DockListType::Active); + if should_favorite { + let mut cur: u32 = 0; + let mut index: Option = None; + while let Some(item) = active_app_model.item(cur) { + if let Ok(cur_dock_object) = item.downcast::() { + if cur_dock_object.get_path() == Some(name.clone()) { + cur_dock_object.set_saved(true); + index = Some(cur); } - cur += 1; - } - if let Some(index) = index { - let object = active_app_model.item(index).unwrap(); - active_app_model.remove(index); - saved_app_model.append(&object); - } - } else { - let mut cur: u32 = 0; - let mut index: Option = None; - while let Some(item) = saved_app_model.item(cur) { - if let Ok(cur_dock_object) = item.downcast::() { - if cur_dock_object.get_path() == Some(name.clone()) { - cur_dock_object.set_saved(false); - index = Some(cur); - } - } - cur += 1; - } - if let Some(index) = index { - let object = saved_app_model.item(index).unwrap(); - saved_app_model.remove(index); - active_app_model.append(&object); } + cur += 1; } - let _ = tx.send(Event::RefreshFromCache).await; - } - Event::RefreshFromCache => { - // println!("refreshing model from cache"); - let cached_results = cached_results.as_ref().lock().unwrap(); - let stack_active = cached_results.iter().fold( - BTreeMap::new(), - |mut acc: BTreeMap, elem: &Item| { - if let Some(v) = acc.get_mut(&elem.description) { - v.0.push(elem.clone()); - } else { - acc.insert( - elem.description.clone(), - BoxedWindowList(vec![elem.clone()]), - ); + if let Some(index) = index { + let object = active_app_model.item(index).unwrap(); + active_app_model.remove(index); + saved_app_model.append(&object); + } + } else { + let mut cur: u32 = 0; + let mut index: Option = None; + while let Some(item) = saved_app_model.item(cur) { + if let Ok(cur_dock_object) = item.downcast::() { + if cur_dock_object.get_path() == Some(name.clone()) { + cur_dock_object.set_saved(false); + index = Some(cur); } - acc - }, - ); - let mut stack_active: Vec = - stack_active.into_values().collect(); + } + cur += 1; + } + if let Some(index) = index { + let object = saved_app_model.item(index).unwrap(); + saved_app_model.remove(index); + active_app_model.append(&object); + } + } + let _ = tx.send(AppListEvent::Refresh); + } + AppListEvent::Refresh => { + // println!("refreshing model from cache"); + let stack_active = cached_results.iter().fold( + BTreeMap::new(), + |mut acc: BTreeMap, elem: &Toplevel| { + if let Some(v) = acc.get_mut(&elem.name) { + v.0.push(elem.clone()); + } else { + acc.insert( + elem.name.clone(), + BoxedWindowList(vec![elem.clone()]), + ); + } + acc + }, + ); + let mut stack_active: Vec = + stack_active.into_values().collect(); - // update active app stacks for saved apps into the saved app model - // then put the rest in the active app model (which doesn't include saved apps) - let saved_app_model = apps_container.model(DockListType::Saved); + // update active app stacks for saved apps into the saved app model + // then put the rest in the active app model (which doesn't include saved apps) + let saved_app_model = apps_container.model(DockListType::Saved); - let mut saved_i: u32 = 0; - while let Some(item) = saved_app_model.item(saved_i) { - if let Ok(dock_obj) = item.downcast::() { - if let Some(cur_app_info) = - dock_obj.property::>("appinfo") + let mut saved_i: u32 = 0; + while let Some(item) = saved_app_model.item(saved_i) { + if let Ok(dock_obj) = item.downcast::() { + if let Some(cur_app_info) = + dock_obj.property::>("appinfo") + { + if let Some((i, _s)) = stack_active + .iter() + .enumerate() + .find(|(_i, s)| s.0[0].name == cur_app_info.name()) { - if let Some((i, _s)) = stack_active - .iter() - .enumerate() - .find(|(_i, s)| s.0[0].description == cur_app_info.name()) - { - // println!( - // "found active saved app {} at {}", - // _s.0[0].name, i - // ); - let active = stack_active.remove(i); - dock_obj.set_property("active", active.to_value()); - saved_app_model.items_changed(saved_i, 0, 0); - } else if cached_results - .iter() - .any(|s| s.description == cur_app_info.name()) - { - dock_obj.set_property( - "active", - BoxedWindowList(Vec::new()).to_value(), - ); - saved_app_model.items_changed(saved_i, 0, 0); - } - } - } - saved_i += 1; - } - - let active_app_model = apps_container.model(DockListType::Active); - let model_len = active_app_model.n_items(); - let new_results: Vec = stack_active - .into_iter() - .map(|v| DockObject::from_search_results(v).upcast()) - .collect(); - active_app_model.splice(0, model_len, &new_results[..]); - } - Event::WindowList => { - // sort to make comparison with cache easier - let results = cached_results.as_ref().lock().unwrap(); - - // build active app stacks for each app - let stack_active = results.iter().fold( - BTreeMap::new(), - |mut acc: BTreeMap, elem| { - if let Some(v) = acc.get_mut(&elem.description) { - v.0.push(elem.clone()); - } else { - acc.insert( - elem.description.clone(), - BoxedWindowList(vec![elem.clone()]), - ); - } - acc - }, - ); - let mut stack_active: Vec = - stack_active.into_values().collect(); - - // update active app stacks for saved apps into the saved app model - // then put the rest in the active app model (which doesn't include saved apps) - let saved_app_model = apps_container.model(DockListType::Saved); - - let mut saved_i: u32 = 0; - while let Some(item) = saved_app_model.item(saved_i) { - if let Ok(dock_obj) = item.downcast::() { - if let Some(cur_app_info) = - dock_obj.property::>("appinfo") + // println!( + // "found active saved app {} at {}", + // _s.0[0].name, i + // ); + let active = stack_active.remove(i); + dock_obj.set_property("active", active.to_value()); + saved_app_model.items_changed(saved_i, 0, 0); + } else if cached_results + .iter() + .any(|s| s.name == cur_app_info.name()) { - if let Some((i, _s)) = stack_active - .iter() - .enumerate() - .find(|(_i, s)| s.0[0].description == cur_app_info.name()) - { - // println!("found active saved app {} at {}", s.0[0].name, i); - let active = stack_active.remove(i); - dock_obj.set_property("active", active.to_value()); - saved_app_model.items_changed(saved_i, 0, 0); - } else if results - .iter() - .any(|s| s.description == cur_app_info.name()) - { - dock_obj.set_property( - "active", - BoxedWindowList(Vec::new()).to_value(), - ); - saved_app_model.items_changed(saved_i, 0, 0); - } + dock_obj.set_property( + "active", + BoxedWindowList(Vec::new()).to_value(), + ); + saved_app_model.items_changed(saved_i, 0, 0); } } - saved_i += 1; } - - let active_app_model = apps_container.model(DockListType::Active); - let model_len = active_app_model.n_items(); - let new_results: Vec = stack_active - .into_iter() - .map(|v| DockObject::from_search_results(v).upcast()) - .collect(); - active_app_model.splice(0, model_len, &new_results[..]); + saved_i += 1; } + + let active_app_model = apps_container.model(DockListType::Active); + let model_len = active_app_model.n_items(); + let new_results: Vec = stack_active + .into_iter() + .map(|v| DockObject::from_search_results(v).upcast()) + .collect(); + active_app_model.splice(0, model_len, &new_results[..]); + } + AppListEvent::WindowList(results) => { + // sort to make comparison with cache easier + cached_results = results.clone(); + + // build active app stacks for each app + let stack_active = results.iter().fold( + BTreeMap::new(), + |mut acc: BTreeMap, elem| { + if let Some(v) = acc.get_mut(&elem.name) { + v.0.push(elem.clone()); + } else { + acc.insert( + elem.name.clone(), + BoxedWindowList(vec![elem.clone()]), + ); + } + acc + }, + ); + let mut stack_active: Vec = + stack_active.into_values().collect(); + + // update active app stacks for saved apps into the saved app model + // then put the rest in the active app model (which doesn't include saved apps) + let saved_app_model = apps_container.model(DockListType::Saved); + + let mut saved_i: u32 = 0; + while let Some(item) = saved_app_model.item(saved_i) { + if let Ok(dock_obj) = item.downcast::() { + if let Some(cur_app_info) = + dock_obj.property::>("appinfo") + { + if let Some((i, _s)) = stack_active + .iter() + .enumerate() + .find(|(_i, s)| s.0[0].name == cur_app_info.name()) + { + // println!("found active saved app {} at {}", s.0[0].name, i); + let active = stack_active.remove(i); + dock_obj.set_property("active", active.to_value()); + saved_app_model.items_changed(saved_i, 0, 0); + } else if results + .iter() + .any(|s| s.name == cur_app_info.name()) + { + dock_obj.set_property( + "active", + BoxedWindowList(Vec::new()).to_value(), + ); + saved_app_model.items_changed(saved_i, 0, 0); + } + } + } + saved_i += 1; + } + + let active_app_model = apps_container.model(DockListType::Active); + let model_len = active_app_model.n_items(); + let new_results: Vec = stack_active + .into_iter() + .map(|v| DockObject::from_search_results(v).upcast()) + .collect(); + active_app_model.splice(0, model_len, &new_results[..]); } } - }); + glib::prelude::Continue(true) + })); + window.show(); }); app.run(); diff --git a/applets/cosmic-app-list/src/utils.rs b/applets/cosmic-app-list/src/utils.rs index 868ccc37..0a3d0e8a 100644 --- a/applets/cosmic-app-list/src/utils.rs +++ b/applets/cosmic-app-list/src/utils.rs @@ -3,32 +3,25 @@ use std::path::PathBuf; use gtk4::glib; -use serde::{Deserialize, Serialize}; use std::future::Future; +use crate::wayland::Toplevel; + pub const DEST: &str = "com.System76.PopShell"; pub const PATH: &str = "/com/System76/PopShell"; #[derive(Debug)] -pub enum Event { - WindowList, +pub enum AppListEvent { + WindowList(Vec), Activate((u32, u32)), Close((u32, u32)), Favorite((String, bool)), - RefreshFromCache, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Item { - pub(crate) entity: (u32, u32), - pub(crate) name: String, - pub(crate) description: String, - pub(crate) desktop_entry: String, + Refresh, } #[derive(Clone, Debug, Default, glib::Boxed)] #[boxed_type(name = "BoxedWindowList")] -pub struct BoxedWindowList(pub Vec); +pub struct BoxedWindowList(pub Vec); pub fn data_path() -> PathBuf { let mut path = glib::user_data_dir(); diff --git a/applets/cosmic-app-list/src/wayland.rs b/applets/cosmic-app-list/src/wayland.rs new file mode 100644 index 00000000..c0be9ff3 --- /dev/null +++ b/applets/cosmic-app-list/src/wayland.rs @@ -0,0 +1,512 @@ +use crate::{ + wayland_source::WaylandSource, config::TopLevelFilter, TX, utils::AppListEvent, +}; +use cosmic_panel_config::CosmicPanelConfig; +use gtk4::glib; +use std::{ +env, os::unix::net::UnixStream, path::PathBuf,time::Duration, +}; +use wayland_client::{ + event_created_child, + protocol::{ + wl_output::{self, WlOutput}, + wl_registry, + }, + ConnectError, Proxy, +}; +use cosmic_protocols::{workspace::v1::client::{ + zcosmic_workspace_manager_v1::{self, ZcosmicWorkspaceManagerV1}, + zcosmic_workspace_group_handle_v1::{self, ZcosmicWorkspaceGroupHandleV1}, + zcosmic_workspace_handle_v1::{self, ZcosmicWorkspaceHandleV1}, +}, toplevel_info::v1::client::{zcosmic_toplevel_info_v1::{ZcosmicToplevelInfoV1, self}, zcosmic_toplevel_handle_v1::{ZcosmicToplevelHandleV1, self}}, toplevel_management::v1::client::zcosmic_toplevel_manager_v1::{self, ZcosmicToplevelManagerV1}}; +use wayland_client::{Connection, Dispatch, QueueHandle}; +use calloop::channel::*; +use crate::config::AppListConfig; + +#[derive(Debug, Clone)] +pub enum ToplevelEvent { + Activate(ZcosmicToplevelHandleV1), + Close(ZcosmicToplevelHandleV1), +} + +pub fn spawn_toplevels() -> SyncSender { + let config = AppListConfig::load().unwrap_or_default(); + + let (workspaces_tx, workspaces_rx) = calloop::channel::sync_channel(100); + + if let Ok(Ok(conn)) = std::env::var("WAYLAND_DISPLAY") + .map_err(anyhow::Error::msg) + .map(|display_str| { + let mut socket_path = env::var_os("XDG_RUNTIME_DIR") + .map(Into::::into) + .ok_or(ConnectError::NoCompositor)?; + socket_path.push(display_str); + + Ok(UnixStream::connect(socket_path).map_err(|_| ConnectError::NoCompositor)?) + }) + .and_then(|s| s.map(|s| Connection::from_socket(s).map_err(anyhow::Error::msg))) + { + std::thread::spawn(move || { + let output = match config.filter_top_levels { + Some(TopLevelFilter::ConfiguredOutput) => CosmicPanelConfig::load_from_env() + .ok().map(|c| c.output), + _ => None, + }; + let mut event_loop = calloop::EventLoop::::try_new().unwrap(); + let loop_handle = event_loop.handle(); + let event_queue = conn.new_event_queue::(); + let qhandle = event_queue.handle(); + + WaylandSource::new(event_queue) + .expect("Failed to create wayland source") + .insert(loop_handle) + .unwrap(); + + let display = conn.display(); + display.get_registry(&qhandle, ()).unwrap(); + + let mut state = State { + workspace_manager: None, + workspace_groups: Vec::new(), + toplevel_info: None, + toplevel_manager: None, + config, + configured_output: output, + expected_output: None, + running: true, + toplevels: vec![] + }; + let loop_handle = event_loop.handle(); + loop_handle + .insert_source(workspaces_rx, |e, _, state| match e { + Event::Msg(ToplevelEvent::Activate(_t)) => { + todo!() + } + Event::Msg(ToplevelEvent::Close(_t)) => { + todo!() + } + Event::Closed => { + if let Some(workspace_manager) = &mut state.workspace_manager { + for g in &mut state.workspace_groups { + g.workspace_group_handle.destroy(); + } + workspace_manager.stop(); + } + if let Some(toplevel_manager) = &mut state.toplevel_manager { + toplevel_manager.destroy(); + } + if let Some(toplevel_info) = &mut state.toplevel_info { + for toplevel in &state.toplevels { + toplevel.toplevel_handle.destroy(); + } + toplevel_info.stop(); + } + } + }) + .unwrap(); + while state.running { + event_loop + .dispatch(Duration::from_millis(16), &mut state) + .unwrap(); + } + }); + } else { + eprintln!("ENV variable WAYLAND_DISPLAY is missing. Exiting..."); + std::process::exit(1); + } + + workspaces_tx +} + +#[derive(Debug, Clone)] +pub struct State { + running: bool, + config: AppListConfig, + configured_output: Option, + expected_output: Option, + workspace_manager: Option, + workspace_groups: Vec, + toplevel_info: Option, + toplevel_manager: Option, + toplevels: Vec, +} + +impl State { + pub fn workspace_list(&self) -> impl Iterator + '_ { + self.workspace_groups + .iter() + .filter_map(|g| { + if g.output == self.expected_output { + Some(g.workspaces.iter().map(|w| (w.name.clone(), match &w.states { + x if x.contains(&zcosmic_workspace_handle_v1::State::Active) => 0, + x if x.contains(&zcosmic_workspace_handle_v1::State::Urgent) => 1, + x if x.contains(&zcosmic_workspace_handle_v1::State::Hidden) => 2, + _ => 3, + }))) + } else { + None + } + }) + .flatten() + } +} + +#[derive(Debug, Clone)] +pub struct Toplevel { + pub name: String, + pub app_id: String, + pub toplevel_handle: ZcosmicToplevelHandleV1, + pub states: Vec, + pub output: Option, + pub workspace: Option, +} + +#[derive(Debug, Clone)] +struct WorkspaceGroup { + workspace_group_handle: ZcosmicWorkspaceGroupHandleV1, + output: Option, + workspaces: Vec, +} + +#[derive(Debug, Clone)] +struct Workspace { + workspace_handle: ZcosmicWorkspaceHandleV1, + name: String, + coordinates: Vec, + states: Vec, +} + +impl Dispatch for State { + fn event( + state: &mut Self, + registry: &wl_registry::WlRegistry, + event: wl_registry::Event, + _: &(), + _: &Connection, + qh: &QueueHandle, + ) { + if let wl_registry::Event::Global { + name, + interface, + version, + } = event + { + match &interface[..] { + "zcosmic_workspace_info_v1" => { + let ti = registry + .bind::( + name, + 1, + qh, + (), + ) + .unwrap(); + state.toplevel_info = Some(ti); + } + "zcosmic_toplevel_manager_v1" => { + let tm = registry + .bind::( + name, + 1, + qh, + (), + ) + .unwrap(); + state.toplevel_manager = Some(tm); + } + "zcosmic_toplevel_manager_v1" => { + let workspace_manager = registry + .bind::( + name, + 1, + qh, + (), + ) + .unwrap(); + state.workspace_manager = Some(workspace_manager); + } + "wl_output" => { + registry.bind::(name, 1, qh, ()).unwrap(); + } + _ => {} + } + } + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + _: &ZcosmicToplevelInfoV1, + event: ::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + dbg!(&event); + match event { + zcosmic_toplevel_info_v1::Event::Toplevel { toplevel } => { + state.toplevels.push(Toplevel { name: "".into(), app_id: "".into(), toplevel_handle: toplevel, states: vec![], output: None, workspace: None }); + }, + zcosmic_toplevel_info_v1::Event::Finished => { + dbg!(&state.toplevels); + let tx = TX.get().unwrap().clone(); + + let _ = tx.send(AppListEvent::WindowList(state.toplevels.iter().filter(|t| { + match state.config.filter_top_levels { + Some(TopLevelFilter::ActiveWorkspace) => true, + _ => false, + } + }).cloned().collect())); + }, + _ => {}, + } + } +} + + +impl Dispatch for State { + fn event( + _: &mut Self, + _: &ZcosmicToplevelManagerV1, + event: ::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + match event { + zcosmic_toplevel_manager_v1::Event::Capabilities { .. } => { + // TODO capabilities affect what is shown to user in applet + }, + _ => {}, + } + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + p: &ZcosmicToplevelHandleV1, + event: ::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + dbg!(&event); + match event { + zcosmic_toplevel_handle_v1::Event::Closed => { + if let Some(i) = state.toplevels.iter().position(|t| &t.toplevel_handle == p) { + state.toplevels.remove(i); + } + }, + zcosmic_toplevel_handle_v1::Event::Done => {}, + zcosmic_toplevel_handle_v1::Event::Title { title } => { + if let Some(i) = state.toplevels.iter_mut().find(|t| &t.toplevel_handle == p) { + i.name = title; + } + }, + zcosmic_toplevel_handle_v1::Event::AppId { app_id } => { + if let Some(i) = state.toplevels.iter_mut().find(|t| &t.toplevel_handle == p) { + i.app_id = app_id; + } + }, + zcosmic_toplevel_handle_v1::Event::OutputEnter { output } => { + if let Some(i) = state.toplevels.iter_mut().find(|t| &t.toplevel_handle == p) { + i.output.replace(output); + } + }, + zcosmic_toplevel_handle_v1::Event::OutputLeave { output } => { + if let Some(i) = state.toplevels.iter_mut().find(|t| &t.toplevel_handle == p && t.output.as_ref() == Some(&output)) { + i.output.take(); + } + }, + zcosmic_toplevel_handle_v1::Event::WorkspaceEnter { workspace } => { + if let Some(i) = state.toplevels.iter_mut().find(|t| &t.toplevel_handle == p) { + i.workspace.replace(workspace); + } + }, + zcosmic_toplevel_handle_v1::Event::WorkspaceLeave { workspace } => { + if let Some(i) = state.toplevels.iter_mut().find(|t| &t.toplevel_handle == p && t.workspace.as_ref() == Some(&workspace)) { + i.workspace.take(); + } + }, + zcosmic_toplevel_handle_v1::Event::State { state: t_state } => { + if let Some(i) = state.toplevels.iter_mut().find(|t| &t.toplevel_handle == p) { + i.states = t_state.chunks(4).map(|chunk| zcosmic_toplevel_handle_v1::State::try_from(u32::from_ne_bytes(chunk.try_into().unwrap())).unwrap()).collect(); + } + }, + _ => todo!(), + } + } +} + + +impl Dispatch for State { + fn event( + state: &mut Self, + _: &ZcosmicWorkspaceManagerV1, + event: zcosmic_workspace_manager_v1::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + match event { + zcosmic_workspace_manager_v1::Event::WorkspaceGroup { workspace_group } => { + state.workspace_groups.push(WorkspaceGroup { + workspace_group_handle: workspace_group, + output: None, + workspaces: Vec::new(), + }); + } + zcosmic_workspace_manager_v1::Event::Done => { + for group in &mut state.workspace_groups { + group.workspaces.sort_by(|w1, w2| { + w1.coordinates.iter().zip(w2.coordinates.iter()) + .skip_while(|(coord1, coord2)| coord1 == coord2) + .next() + .map(|(coord1, coord2)| coord1.cmp(coord2)) + .unwrap_or(std::cmp::Ordering::Equal) + }); + } + } + zcosmic_workspace_manager_v1::Event::Finished => { + state.workspace_manager.take(); + } + _ => {} + } + } + + event_created_child!(State, ZcosmicWorkspaceManagerV1, [ + 0 => (ZcosmicWorkspaceGroupHandleV1, ()) + ]); +} + +impl Dispatch for State { + fn event( + state: &mut Self, + group: &ZcosmicWorkspaceGroupHandleV1, + event: zcosmic_workspace_group_handle_v1::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + match event { + zcosmic_workspace_group_handle_v1::Event::OutputEnter { output } => { + if let Some(group) = state + .workspace_groups + .iter_mut() + .find(|g| &g.workspace_group_handle == group) + { + group.output = Some(output); + } + } + zcosmic_workspace_group_handle_v1::Event::OutputLeave { output } => { + if let Some(group) = state.workspace_groups.iter_mut().find(|g| { + &g.workspace_group_handle == group && g.output.as_ref() == Some(&output) + }) { + group.output = None; + } + } + zcosmic_workspace_group_handle_v1::Event::Workspace { workspace } => { + if let Some(group) = state + .workspace_groups + .iter_mut() + .find(|g| &g.workspace_group_handle == group) + { + group.workspaces.push(Workspace { + workspace_handle: workspace, + name: String::new(), + coordinates: Vec::new(), + states: Vec::new(), + }) + } + } + zcosmic_workspace_group_handle_v1::Event::Remove => { + if let Some(group) = state + .workspace_groups + .iter() + .position(|g| &g.workspace_group_handle == group) + { + state.workspace_groups.remove(group); + } + } + _ => {} + } + } + + event_created_child!(State, ZcosmicWorkspaceGroupHandleV1, [ + 3 => (ZcosmicWorkspaceHandleV1, ()) + ]); +} + +impl Dispatch for State { + fn event( + state: &mut Self, + workspace: &ZcosmicWorkspaceHandleV1, + event: zcosmic_workspace_handle_v1::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + match event { + zcosmic_workspace_handle_v1::Event::Name { name } => { + if let Some(w) = state.workspace_groups.iter_mut().find_map(|g| { + g.workspaces + .iter_mut() + .find(|w| &w.workspace_handle == workspace) + }) { + w.name = name; + } + } + zcosmic_workspace_handle_v1::Event::Coordinates { coordinates } => { + if let Some(w) = state.workspace_groups.iter_mut().find_map(|g| { + g.workspaces + .iter_mut() + .find(|w| &w.workspace_handle == workspace) + }) { + // wayland is host byte order + w.coordinates = coordinates.chunks(4).map(|chunk| u32::from_ne_bytes(chunk.try_into().unwrap())).collect(); + } + } + zcosmic_workspace_handle_v1::Event::State { state: workspace_state } => { + if let Some(w) = state.workspace_groups.iter_mut().find_map(|g| { + g.workspaces + .iter_mut() + .find(|w| &w.workspace_handle == workspace) + }) { + // wayland is host byte order + w.states = workspace_state.chunks(4).map(|chunk| zcosmic_workspace_handle_v1::State::try_from(u32::from_ne_bytes(chunk.try_into().unwrap())).unwrap()).collect(); + } + } + zcosmic_workspace_handle_v1::Event::Remove => { + if let Some((g, w_i)) = state.workspace_groups.iter_mut().find_map(|g| { + g.workspaces + .iter_mut() + .position(|w| &w.workspace_handle == workspace) + .map(|p| (g, p)) + }) { + g.workspaces.remove(w_i); + } + } + _ => {} + } + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + o: &WlOutput, + e: wl_output::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + match e { + wl_output::Event::Name { name } if Some(&name) == state.configured_output.as_ref() => { + state.expected_output.replace(o.clone()); + } + _ => {} // ignored + } + } +} diff --git a/applets/cosmic-app-list/src/wayland_source.rs b/applets/cosmic-app-list/src/wayland_source.rs new file mode 100644 index 00000000..028c9e56 --- /dev/null +++ b/applets/cosmic-app-list/src/wayland_source.rs @@ -0,0 +1,219 @@ +//! Utilities for using an [`EventQueue`] from wayland-client with an event loop that performs polling with +//! [`calloop`](https://crates.io/crates/calloop). + +use std::{io, os::unix::prelude::RawFd}; + +use calloop::{ + generic::Generic, EventSource, InsertError, Interest, LoopHandle, Mode, Poll, PostAction, + Readiness, RegistrationToken, Token, TokenFactory, +}; +use nix::errno::Errno; +use wayland_backend::client::{ReadEventsGuard, WaylandError}; +use wayland_client::{DispatchError, EventQueue}; + +/// An adapter to insert an [`EventQueue`] into a calloop [`EventLoop`](calloop::EventLoop). +/// +/// This type implements [`EventSource`] which generates an event whenever events on the display need to be +/// dispatched. The event queue available in the callback calloop registers may be used to dispatch pending +/// events using [`EventQueue::dispatch_pending`]. +/// +/// [`WaylandSource::insert`] can be used to insert this source into an event loop and automatically dispatch +/// pending events on the display. +#[derive(Debug)] +pub struct WaylandSource { + queue: EventQueue, + fd: Generic, + read_guard: Option, +} + +impl WaylandSource { + /// Wrap an [`EventQueue`] as a [`WaylandSource`]. + pub fn new(queue: EventQueue) -> Result, WaylandError> { + let guard = queue.prepare_read()?; + let fd = Generic::new(guard.connection_fd(), Interest::READ, Mode::Level); + drop(guard); + + Ok(WaylandSource { + queue, + fd, + read_guard: None, + }) + } + + /// Access the underlying event queue + /// + /// Note that you should be careful when interacting with it if you invoke methods that + /// interact with the wayland socket (such as `dispatch()` or `prepare_read()`). These may + /// interfere with the proper waking up of this event source in the event loop. + pub fn queue(&mut self) -> &mut EventQueue { + &mut self.queue + } + + /// Insert this source into the given event loop. + /// + /// This adapter will pass the event loop's shared data as the `D` type for the event loop. + pub fn insert(self, handle: LoopHandle) -> Result> + where + D: 'static, + { + handle.insert_source(self, |_, queue, data| queue.dispatch_pending(data)) + } +} + +impl EventSource for WaylandSource { + type Event = (); + + /// The underlying event queue. + /// + /// You should call [`EventQueue::dispatch_pending`] inside your callback using this queue. + type Metadata = EventQueue; + type Ret = Result; + type Error = calloop::Error; + + fn process_events( + &mut self, + readiness: Readiness, + token: Token, + mut callback: F, + ) -> Result + where + F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret, + { + let queue = &mut self.queue; + let read_guard = &mut self.read_guard; + + let action = self.fd.process_events(readiness, token, |_, _| { + // 1. read events from the socket if any are available + if let Some(guard) = read_guard.take() { + // might be None if some other thread read events before us, concurrently + if let Err(WaylandError::Io(err)) = guard.read() { + if err.kind() != io::ErrorKind::WouldBlock { + return Err(err); + } + } + } + + // 2. dispatch any pending events in the queue + // This is done to ensure we are not waiting for messages that are already in the buffer. + Self::loop_callback_pending(queue, &mut callback)?; + *read_guard = Some(Self::prepare_read(queue)?); + + // 3. Once dispatching is finished, flush the responses to the compositor + if let Err(WaylandError::Io(e)) = queue.flush() { + if e.kind() != io::ErrorKind::WouldBlock { + // in case of error, forward it and fast-exit + return Err(e); + } + // WouldBlock error means the compositor could not process all our messages + // quickly. Either it is slowed down or we are a spammer. + // Should not really happen, if it does we do nothing and will flush again later + } + + Ok(PostAction::Continue) + })?; + + Ok(action) + } + + fn register( + &mut self, + poll: &mut Poll, + token_factory: &mut TokenFactory, + ) -> calloop::Result<()> { + self.fd.register(poll, token_factory) + } + + fn reregister( + &mut self, + poll: &mut Poll, + token_factory: &mut TokenFactory, + ) -> calloop::Result<()> { + self.fd.reregister(poll, token_factory) + } + + fn unregister(&mut self, poll: &mut Poll) -> calloop::Result<()> { + self.fd.unregister(poll) + } + + fn pre_run(&mut self, mut callback: F) -> calloop::Result<()> + where + F: FnMut((), &mut Self::Metadata) -> Self::Ret, + { + debug_assert!(self.read_guard.is_none()); + + // flush the display before starting to poll + if let Err(WaylandError::Io(err)) = self.queue.flush() { + if err.kind() != io::ErrorKind::WouldBlock { + // in case of error, don't prepare a read, if the error is persistent, it'll trigger in other + // wayland methods anyway + log::error!("Error trying to flush the wayland display: {}", err); + return Err(err.into()); + } + } + + // ensure we are not waiting for messages that are already in the buffer. + Self::loop_callback_pending(&mut self.queue, &mut callback)?; + self.read_guard = Some(Self::prepare_read(&mut self.queue)?); + + Ok(()) + } + + fn post_run(&mut self, _: F) -> calloop::Result<()> + where + F: FnMut((), &mut Self::Metadata) -> Self::Ret, + { + // Drop implementation of ReadEventsGuard will do cleanup + self.read_guard.take(); + Ok(()) + } +} + +impl WaylandSource { + /// Loop over the callback until all pending messages have been dispatched. + fn loop_callback_pending(queue: &mut EventQueue, callback: &mut F) -> io::Result<()> + where + F: FnMut((), &mut EventQueue) -> Result, + { + // Loop on the callback until no pending events are left. + loop { + match callback((), queue) { + // No more pending events. + Ok(0) => break Ok(()), + + Ok(_) => continue, + + Err(DispatchError::Backend(WaylandError::Io(err))) => { + return Err(err); + } + + Err(DispatchError::Backend(WaylandError::Protocol(err))) => { + log::error!("Protocol error received on display: {}", err); + + break Err(Errno::EPROTO.into()); + } + + Err(DispatchError::BadMessage { msg, interface }) => { + log::error!( + "Bad message on interface \"{}\": (opcode: {}, args: {:?})", + interface, + msg.opcode, + msg.args, + ); + + break Err(Errno::EPROTO.into()); + } + } + } + } + + fn prepare_read(queue: &mut EventQueue) -> io::Result { + queue.prepare_read().map_err(|err| match err { + WaylandError::Io(err) => err, + + WaylandError::Protocol(err) => { + log::error!("Protocol error received on display: {}", err); + Errno::EPROTO.into() + } + }) + } +} diff --git a/applets/cosmic-applet-workspaces/Cargo.toml b/applets/cosmic-applet-workspaces/Cargo.toml index f45b6c3d..d4d00bb7 100644 --- a/applets/cosmic-applet-workspaces/Cargo.toml +++ b/applets/cosmic-applet-workspaces/Cargo.toml @@ -18,9 +18,8 @@ i18n-embed = { version = "0.13.4", features = ["fluent-system", "desktop-request i18n-embed-fl = "0.6.4" rust-embed = "6.3.0" tokio = { version = "1.16.1", features = ["sync"] } -wayland-commons = "0.29.4" -wayland-backend = { version = "0.1.0-beta.7" } -wayland-client = { version = "0.30.0-beta.7" } +wayland-backend = { version = "0.1.0-beta.8" } +wayland-client = { version = "0.30.0-beta.8" } calloop = "*" nix = "*" log = "0.4" diff --git a/applets/cosmic-applet-workspaces/data/resources/ext-workspace-unstable-v1.xml b/applets/cosmic-applet-workspaces/data/resources/ext-workspace-unstable-v1.xml deleted file mode 100644 index 24410b62..00000000 --- a/applets/cosmic-applet-workspaces/data/resources/ext-workspace-unstable-v1.xml +++ /dev/null @@ -1,306 +0,0 @@ - - - - Copyright © 2019 Christopher Billington - Copyright © 2020 Ilia Bozhinov - - Permission to use, copy, modify, distribute, and sell this - software and its documentation for any purpose is hereby granted - without fee, provided that the above copyright notice appear in - all copies and that both that copyright notice and this permission - notice appear in supporting documentation, and that the name of - the copyright holders not be used in advertising or publicity - pertaining to distribution of the software without specific, - written prior permission. The copyright holders make no - representations about the suitability of this software for any - purpose. It is provided "as is" without express or implied - warranty. - - THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS - SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND - FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY - SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, - ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF - THIS SOFTWARE. - - - - - Workspaces, also called virtual desktops, are groups of surfaces. A - compositor with a concept of workspaces may only show some such groups of - surfaces (those of 'active' workspaces) at a time. 'Activating' a - workspace is a request for the compositor to display that workspace's - surfaces as normal, whereas the compositor may hide or otherwise - de-emphasise surfaces that are associated only with 'inactive' workspaces. - Workspaces are grouped by which sets of outputs they correspond to, and - may contain surfaces only from those outputs. In this way, it is possible - for each output to have its own set of workspaces, or for all outputs (or - any other arbitrary grouping) to share workspaces. Compositors may - optionally conceptually arrange each group of workspaces in an - N-dimensional grid. - - The purpose of this protocol is to enable the creation of taskbars and - docks by providing them with a list of workspaces and their properties, - and allowing them to activate and deactivate workspaces. - - After a client binds the zext_workspace_manager_v1, each workspace will be - sent via the workspace event. - - - - - This event is emitted whenever a new workspace group has been created. - - All initial details of the workspace group (workspaces, outputs) will be - sent immediately after this event via the corresponding events in - zext_workspace_group_handle_v1. - - - - - - - The client must send this request after it has finished sending other - requests. The compositor must process a series of requests preceding a - commit request atomically. - - This allows changes to the workspace properties to be seen as atomic, - even if they happen via multiple events, and even if they involve - multiple zext_workspace_handle_v1 objects, for example, deactivating one - workspace and activating another. - - - - - - This event is sent after all changes in all workspace groups have been - sent. - - This allows changes to one or more zext_workspace_group_handle_v1 - properties to be seen as atomic, even if they happen via multiple - events. In particular, an output moving from one workspace group to - another sends an output_enter event and an output_leave event to the two - zext_workspace_group_handle_v1 objects in question. The compositor sends - the done event only after updating the output information in both - workspace groups. - - - - - - This event indicates that the compositor is done sending events to the - zext_workspace_manager_v1. The server will destroy the object - immediately after sending this request, so it will become invalid and - the client should free any resources associated with it. - - - - - - Indicates the client no longer wishes to receive events for new - workspace groups. However the compositor may emit further workspace - events, until the finished event is emitted. - - The client must not send any more requests after this one. - - - - - - - A zext_workspace_group_handle_v1 object represents a a workspace group - that is assigned a set of outputs and contains a number of workspaces. - - The set of outputs assigned to the workspace group is conveyed to the client via - output_enter and output_leave events, and its workspaces are conveyed with - workspace events. - - For example, a compositor which has a set of workspaces for each output may - advertise a workspace group (and its workspaces) per output, whereas a compositor - where a workspace spans all outputs may advertise a single workspace group for all - outputs. - - - - - This event is emitted whenever an output is assigned to the workspace - group. - - - - - - - This event is emitted whenever an output is removed from the workspace - group. - - - - - - - This event is emitted whenever a new workspace has been created. - - All initial details of the workspace (name, coordinates, state) will - be sent immediately after this event via the corresponding events in - zext_workspace_handle_v1. - - - - - - - This event means the zext_workspace_group_handle_v1 has been destroyed. - It is guaranteed there won't be any more events for this - zext_workspace_group_handle_v1. The zext_workspace_group_handle_v1 becomes - inert so any requests will be ignored except the destroy request. - - The compositor must remove all workspaces belonging to a workspace group - before removing the workspace group. - - - - - - Request that the compositor create a new workspace with the given name. - - There is no guarantee that the compositor will create a new workspace, - or that the created workspace will have the provided name. - - - - - - - Destroys the zext_workspace_handle_v1 object. - - This request should be called either when the client does not want to - use the workspace object any more or after the remove event to finalize - the destruction of the object. - - - - - - - A zext_workspace_handle_v1 object represents a a workspace that handles a - group of surfaces. - - Each workspace has a name, conveyed to the client with the name event; a - list of states, conveyed to the client with the state event; and - optionally a set of coordinates, conveyed to the client with the - coordinates event. The client may request that the compositor activate or - deactivate the workspace. - - Each workspace can belong to only a single workspace group. - Depepending on the compositor policy, there might be workspaces with - the same name in different workspace groups, but these workspaces are still - separate (e.g. one of them might be active while the other is not). - - - - - This event is emitted immediately after the zext_workspace_handle_v1 is - created and whenever the name of the workspace changes. - - - - - - - This event is used to organize workspaces into an N-dimensional grid - within a workspace group, and if supported, is emitted immediately after - the zext_workspace_handle_v1 is created and whenever the coordinates of - the workspace change. Compositors may not send this event if they do not - conceptually arrange workspaces in this way. If compositors simply - number workspaces, without any geometric interpretation, they may send - 1D coordinates, which clients should not interpret as implying any - geometry. Sending an empty array means that the compositor no longer - orders the workspace geometrically. - - Coordinates have an arbitrary number of dimensions N with an uint32 - position along each dimension. By convention if N > 1, the first - dimension is X, the second Y, the third Z, and so on. The compositor may - chose to utilize these events for a more novel workspace layout - convention, however. No guarantee is made about the grid being filled or - bounded; there may be a workspace at coordinate 1 and another at - coordinate 1000 and none in between. Within a workspace group, however, - workspaces must have unique coordinates of equal dimensionality. - - - - - - - This event is emitted immediately after the zext_workspace_handle_v1 is - created and each time the workspace state changes, either because of a - compositor action or because of a request in this protocol. - - - - - - - The different states that a workspace can have. - - - - - - - The workspace is not visible in its workspace group, and clients - attempting to visualize the compositor workspace state should not - display such workspaces. - - - - - - - This event means the zext_workspace_handle_v1 has been destroyed. It is - guaranteed there won't be any more events for this - zext_workspace_handle_v1. The zext_workspace_handle_v1 becomes inert so - any requests will be ignored except the destroy request. - - - - - - Destroys the zext_workspace_handle_v1 object. - - This request should be called either when the client does not want to - use the workspace object any more or after the remove event to finalize - the destruction of the object. - - - - - - Request that this workspace be activated. - - There is no guarantee the workspace will be actually activated, and - behaviour may be compositor-dependent. For example, activating a - workspace may or may not deactivate all other workspaces in the same - group. - - - - - - Request that this workspace be deactivated. - - There is no guarantee the workspace will be actually deactivated. - - - - - - Request that this workspace be removed. - - There is no guarantee the workspace will be actually removed. - - - - diff --git a/applets/cosmic-applet-workspaces/src/wayland.rs b/applets/cosmic-applet-workspaces/src/wayland.rs index 2e7d7401..f2ccf191 100644 --- a/applets/cosmic-applet-workspaces/src/wayland.rs +++ b/applets/cosmic-applet-workspaces/src/wayland.rs @@ -27,9 +27,9 @@ use wayland_client::{Connection, Dispatch, QueueHandle}; use calloop::channel::*; pub fn spawn_workspaces(tx: glib::Sender) -> SyncSender { - let (workspaces_tx, mut workspaces_rx) = calloop::channel::sync_channel(100); + let (workspaces_tx, workspaces_rx) = calloop::channel::sync_channel(100); - if let Ok(Ok(conn)) = std::env::var("HOST_WAYLAND_DISPLAY") + if let Ok(Ok(conn)) = std::env::var("WAYLAND_DISPLAY") .map_err(anyhow::Error::msg) .map(|display_str| { let mut socket_path = env::var_os("XDG_RUNTIME_DIR") @@ -130,7 +130,7 @@ pub fn spawn_workspaces(tx: glib::Sender) -> SyncSender { } }); } else { - eprintln!("ENV variable HOST_WAYLAND_DISPLAY is missing. Exiting..."); + eprintln!("ENV variable WAYLAND_DISPLAY is missing. Exiting..."); std::process::exit(1); } @@ -185,7 +185,7 @@ struct Workspace { impl Dispatch for State { fn event( - &mut self, + state: &mut Self, registry: &wl_registry::WlRegistry, event: wl_registry::Event, _: &(), @@ -208,7 +208,7 @@ impl Dispatch for State { (), ) .unwrap(); - self.workspace_manager = Some(workspace_manager); + state.workspace_manager = Some(workspace_manager); } "wl_output" => { registry.bind::(name, 1, qh, ()).unwrap(); @@ -221,7 +221,7 @@ impl Dispatch for State { impl Dispatch for State { fn event( - &mut self, + state: &mut Self, _: &ZcosmicWorkspaceManagerV1, event: zcosmic_workspace_manager_v1::Event, _: &(), @@ -230,14 +230,14 @@ impl Dispatch for State { ) { match event { zcosmic_workspace_manager_v1::Event::WorkspaceGroup { workspace_group } => { - self.workspace_groups.push(WorkspaceGroup { + state.workspace_groups.push(WorkspaceGroup { workspace_group_handle: workspace_group, output: None, workspaces: Vec::new(), }); } zcosmic_workspace_manager_v1::Event::Done => { - for group in &mut self.workspace_groups { + for group in &mut state.workspace_groups { group.workspaces.sort_by(|w1, w2| { w1.coordinates.iter().zip(w2.coordinates.iter()) .skip_while(|(coord1, coord2)| coord1 == coord2) @@ -246,10 +246,10 @@ impl Dispatch for State { .unwrap_or(std::cmp::Ordering::Equal) }); } - let _ = self.tx.send(self.clone()); + let _ = state.tx.send(state.clone()); } zcosmic_workspace_manager_v1::Event::Finished => { - self.workspace_manager.take(); + state.workspace_manager.take(); } _ => {} } @@ -262,7 +262,7 @@ impl Dispatch for State { impl Dispatch for State { fn event( - &mut self, + state: &mut Self, group: &ZcosmicWorkspaceGroupHandleV1, event: zcosmic_workspace_group_handle_v1::Event, _: &(), @@ -271,7 +271,7 @@ impl Dispatch for State { ) { match event { zcosmic_workspace_group_handle_v1::Event::OutputEnter { output } => { - if let Some(group) = self + if let Some(group) = state .workspace_groups .iter_mut() .find(|g| &g.workspace_group_handle == group) @@ -280,14 +280,14 @@ impl Dispatch for State { } } zcosmic_workspace_group_handle_v1::Event::OutputLeave { output } => { - if let Some(group) = self.workspace_groups.iter_mut().find(|g| { + if let Some(group) = state.workspace_groups.iter_mut().find(|g| { &g.workspace_group_handle == group && g.output.as_ref() == Some(&output) }) { group.output = None; } } zcosmic_workspace_group_handle_v1::Event::Workspace { workspace } => { - if let Some(group) = self + if let Some(group) = state .workspace_groups .iter_mut() .find(|g| &g.workspace_group_handle == group) @@ -301,12 +301,12 @@ impl Dispatch for State { } } zcosmic_workspace_group_handle_v1::Event::Remove => { - if let Some(group) = self + if let Some(group) = state .workspace_groups .iter() .position(|g| &g.workspace_group_handle == group) { - self.workspace_groups.remove(group); + state.workspace_groups.remove(group); } } _ => {} @@ -320,7 +320,7 @@ impl Dispatch for State { impl Dispatch for State { fn event( - &mut self, + state: &mut Self, workspace: &ZcosmicWorkspaceHandleV1, event: zcosmic_workspace_handle_v1::Event, _: &(), @@ -329,7 +329,7 @@ impl Dispatch for State { ) { match event { zcosmic_workspace_handle_v1::Event::Name { name } => { - if let Some(w) = self.workspace_groups.iter_mut().find_map(|g| { + if let Some(w) = state.workspace_groups.iter_mut().find_map(|g| { g.workspaces .iter_mut() .find(|w| &w.workspace_handle == workspace) @@ -338,7 +338,7 @@ impl Dispatch for State { } } zcosmic_workspace_handle_v1::Event::Coordinates { coordinates } => { - if let Some(w) = self.workspace_groups.iter_mut().find_map(|g| { + if let Some(w) = state.workspace_groups.iter_mut().find_map(|g| { g.workspaces .iter_mut() .find(|w| &w.workspace_handle == workspace) @@ -347,18 +347,18 @@ impl Dispatch for State { w.coordinates = coordinates.chunks(4).map(|chunk| u32::from_ne_bytes(chunk.try_into().unwrap())).collect(); } } - zcosmic_workspace_handle_v1::Event::State { state } => { - if let Some(w) = self.workspace_groups.iter_mut().find_map(|g| { + zcosmic_workspace_handle_v1::Event::State { state: workspace_state } => { + if let Some(w) = state.workspace_groups.iter_mut().find_map(|g| { g.workspaces .iter_mut() .find(|w| &w.workspace_handle == workspace) }) { // wayland is host byte order - w.states = state.chunks(4).map(|chunk| zcosmic_workspace_handle_v1::State::try_from(u32::from_ne_bytes(chunk.try_into().unwrap())).unwrap()).collect(); + w.states = workspace_state.chunks(4).map(|chunk| zcosmic_workspace_handle_v1::State::try_from(u32::from_ne_bytes(chunk.try_into().unwrap())).unwrap()).collect(); } } zcosmic_workspace_handle_v1::Event::Remove => { - if let Some((g, w_i)) = self.workspace_groups.iter_mut().find_map(|g| { + if let Some((g, w_i)) = state.workspace_groups.iter_mut().find_map(|g| { g.workspaces .iter_mut() .position(|w| &w.workspace_handle == workspace) @@ -374,7 +374,7 @@ impl Dispatch for State { impl Dispatch for State { fn event( - &mut self, + state: &mut Self, o: &WlOutput, e: wl_output::Event, _: &(), @@ -382,8 +382,8 @@ impl Dispatch for State { _: &QueueHandle, ) { match e { - wl_output::Event::Name { name } if name == self.configured_output => { - self.expected_output.replace(o.clone()); + wl_output::Event::Name { name } if name == state.configured_output => { + state.expected_output.replace(o.clone()); } _ => {} // ignored }