From 560ebc0bf58cff2d4757f4cdc0cf23271ba149c8 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 19 Jul 2022 11:33:19 -0400 Subject: [PATCH 01/10] 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 } From dc13ca6e46419c9337b7c96c45018ccd5299563b Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 19 Jul 2022 11:40:04 -0400 Subject: [PATCH 02/10] fix typo --- Cargo.lock | 4 ++-- applets/cosmic-app-list/src/wayland.rs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 54169c86..28482658 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2141,7 +2141,7 @@ checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "relm4" version = "0.5.0-beta.1" -source = "git+https://github.com/Relm4/Relm4.git?branch=next#746d244004e23764294b23519f6f8be1002c1ceb" +source = "git+https://github.com/relm4/relm4?branch=next#746d244004e23764294b23519f6f8be1002c1ceb" dependencies = [ "async-broadcast", "async-oneshot", @@ -2158,7 +2158,7 @@ dependencies = [ [[package]] name = "relm4-macros" version = "0.5.0-beta.1" -source = "git+https://github.com/Relm4/Relm4.git?branch=next#746d244004e23764294b23519f6f8be1002c1ceb" +source = "git+https://github.com/relm4/relm4?branch=next#746d244004e23764294b23519f6f8be1002c1ceb" dependencies = [ "proc-macro2", "quote", diff --git a/applets/cosmic-app-list/src/wayland.rs b/applets/cosmic-app-list/src/wayland.rs index c0be9ff3..47580279 100644 --- a/applets/cosmic-app-list/src/wayland.rs +++ b/applets/cosmic-app-list/src/wayland.rs @@ -214,7 +214,7 @@ impl Dispatch for State { .unwrap(); state.toplevel_manager = Some(tm); } - "zcosmic_toplevel_manager_v1" => { + "zcosmic_workspace_manager_v1" => { let workspace_manager = registry .bind::( name, @@ -274,6 +274,7 @@ impl Dispatch for State { _: &Connection, _: &QueueHandle, ) { + match event { zcosmic_toplevel_manager_v1::Event::Capabilities { .. } => { // TODO capabilities affect what is shown to user in applet From 8a855cb3d524a37d021dc89e3560333b6dafc585 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 19 Jul 2022 12:32:27 -0400 Subject: [PATCH 03/10] fix: typo --- applets/cosmic-app-list/src/wayland.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applets/cosmic-app-list/src/wayland.rs b/applets/cosmic-app-list/src/wayland.rs index 47580279..a12524a2 100644 --- a/applets/cosmic-app-list/src/wayland.rs +++ b/applets/cosmic-app-list/src/wayland.rs @@ -192,7 +192,7 @@ impl Dispatch for State { } = event { match &interface[..] { - "zcosmic_workspace_info_v1" => { + "zcosmic_toplevel_info_v1" => { let ti = registry .bind::( name, @@ -274,7 +274,7 @@ impl Dispatch for State { _: &Connection, _: &QueueHandle, ) { - + dbg!(&event); match event { zcosmic_toplevel_manager_v1::Event::Capabilities { .. } => { // TODO capabilities affect what is shown to user in applet From e7f9e95440430fd52b87448673a6901d9e7ab3e0 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 19 Jul 2022 15:19:05 -0400 Subject: [PATCH 04/10] wip: toplevels --- applets/cosmic-app-list/src/main.rs | 47 ++++++++++++------------ applets/cosmic-app-list/src/utils.rs | 4 +-- applets/cosmic-app-list/src/wayland.rs | 50 +++++++++++++++++++------- 3 files changed, 64 insertions(+), 37 deletions(-) diff --git a/applets/cosmic-app-list/src/main.rs b/applets/cosmic-app-list/src/main.rs index beb5d0dd..1f7e78cd 100644 --- a/applets/cosmic-app-list/src/main.rs +++ b/applets/cosmic-app-list/src/main.rs @@ -78,19 +78,7 @@ fn main() { TX.set(tx.clone()).unwrap(); 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"); - } + let should_apply_changes = match event { AppListEvent::Favorite((name, should_favorite)) => { let saved_app_model = apps_container.model(DockListType::Saved); let active_app_model = apps_container.model(DockListType::Active); @@ -130,6 +118,7 @@ fn main() { } } let _ = tx.send(AppListEvent::Refresh); + false } AppListEvent::Refresh => { // println!("refreshing model from cache"); @@ -194,13 +183,28 @@ fn main() { .map(|v| DockObject::from_search_results(v).upcast()) .collect(); active_app_model.splice(0, model_len, &new_results[..]); + true } - AppListEvent::WindowList(results) => { + AppListEvent::WindowList(toplevels) => { + cached_results = toplevels; + true + } + AppListEvent::Remove(top_level) => { + if let Some(i) = cached_results.iter().position(|t| t.toplevel_handle == top_level.toplevel_handle) { + cached_results.swap_remove(i); + } + true + } + AppListEvent::Add(top_level) => { // sort to make comparison with cache easier - cached_results = results.clone(); - + cached_results.push(top_level); + true + } + }; + if should_apply_changes { + dbg!(&cached_results); // build active app stacks for each app - let stack_active = results.iter().fold( + let stack_active = cached_results.iter().fold( BTreeMap::new(), |mut acc: BTreeMap, elem| { if let Some(v) = acc.get_mut(&elem.name) { @@ -230,15 +234,15 @@ fn main() { if let Some((i, _s)) = stack_active .iter() .enumerate() - .find(|(_i, s)| s.0[0].name == cur_app_info.name()) + .find(|(_i, s)| s.0[0].app_id == 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 + } else if cached_results .iter() - .any(|s| s.name == cur_app_info.name()) + .any(|s| s.app_id == cur_app_info.name()) { dock_obj.set_property( "active", @@ -257,8 +261,7 @@ fn main() { .into_iter() .map(|v| DockObject::from_search_results(v).upcast()) .collect(); - active_app_model.splice(0, model_len, &new_results[..]); - } + active_app_model.splice(0, model_len, &new_results[..]); } glib::prelude::Continue(true) })); diff --git a/applets/cosmic-app-list/src/utils.rs b/applets/cosmic-app-list/src/utils.rs index 0a3d0e8a..7a6268c8 100644 --- a/applets/cosmic-app-list/src/utils.rs +++ b/applets/cosmic-app-list/src/utils.rs @@ -13,8 +13,8 @@ pub const PATH: &str = "/com/System76/PopShell"; #[derive(Debug)] pub enum AppListEvent { WindowList(Vec), - Activate((u32, u32)), - Close((u32, u32)), + Add(Toplevel), + Remove(Toplevel), Favorite((String, bool)), Refresh, } diff --git a/applets/cosmic-app-list/src/wayland.rs b/applets/cosmic-app-list/src/wayland.rs index a12524a2..4e57a752 100644 --- a/applets/cosmic-app-list/src/wayland.rs +++ b/applets/cosmic-app-list/src/wayland.rs @@ -2,7 +2,6 @@ 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, }; @@ -249,19 +248,15 @@ impl Dispatch for State { 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())); + todo!() }, _ => {}, } } + + event_created_child!(State, ZcosmicWorkspaceManagerV1, [ + 0 => (ZcosmicToplevelHandleV1, ()) + ]); } @@ -274,7 +269,6 @@ impl Dispatch for State { _: &Connection, _: &QueueHandle, ) { - dbg!(&event); match event { zcosmic_toplevel_manager_v1::Event::Capabilities { .. } => { // TODO capabilities affect what is shown to user in applet @@ -293,14 +287,42 @@ impl Dispatch for State { _: &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::Done => { + let to_send = match state.config.filter_top_levels { + Some(TopLevelFilter::ActiveWorkspace) => { + state.toplevels.iter_mut().find(|t| { + if &t.toplevel_handle == p { + state + .workspace_groups + .iter() + .find(|g| { + g.workspaces + .iter() + .find(|w| w.states.contains(&zcosmic_workspace_handle_v1::State::Active) && Some(&w.workspace_handle) == t.workspace.as_ref()).is_some() + }).is_some() + } else { + false + } + }) + }, + Some(TopLevelFilter::ConfiguredOutput) => + state.toplevels.iter_mut().find(|t| &t.toplevel_handle == p && state.expected_output == t.output), + _ => state.toplevels.iter_mut().find(|t| &t.toplevel_handle == p), + }; + + if let Some(toplevel) = to_send.cloned() { + let tx = TX.get().unwrap().clone(); + + let _ = tx.send(AppListEvent::Add(toplevel)); + } + + }, zcosmic_toplevel_handle_v1::Event::Title { title } => { if let Some(i) = state.toplevels.iter_mut().find(|t| &t.toplevel_handle == p) { i.name = title; @@ -477,6 +499,8 @@ impl Dispatch for State { }) { // 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(); + // TODO if workspace active status changes while configured to only show active workspace, clear the list + } } zcosmic_workspace_handle_v1::Event::Remove => { From 223c7855cf60ee401a1cbc61f7112cb63d7248a2 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 19 Jul 2022 23:12:37 -0400 Subject: [PATCH 05/10] wip fix: use app id for grouping window lists & replace top level if it already exists & show separator for active and saved app lists --- Cargo.lock | 4 +-- .../cosmic-app-list/src/apps_container/mod.rs | 31 ++++++++++--------- applets/cosmic-app-list/src/dock_list/mod.rs | 1 + .../cosmic-app-list/src/dock_object/mod.rs | 4 +-- applets/cosmic-app-list/src/main.rs | 28 ++++++++++------- 5 files changed, 38 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 28482658..54169c86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2141,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", @@ -2158,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", diff --git a/applets/cosmic-app-list/src/apps_container/mod.rs b/applets/cosmic-app-list/src/apps_container/mod.rs index 304bddb2..05ab1ce0 100644 --- a/applets/cosmic-app-list/src/apps_container/mod.rs +++ b/applets/cosmic-app-list/src/apps_container/mod.rs @@ -7,6 +7,7 @@ use crate::dock_list::DockListType; use crate::utils::AppListEvent; use cascade::cascade; use cosmic_panel_config::{PanelAnchor, CosmicPanelConfig}; +use gtk4::Separator; use gtk4::prelude::*; use gtk4::subclass::prelude::*; use gtk4::Orientation; @@ -38,21 +39,21 @@ impl AppsContainer { let saved_app_list_view = DockList::new(DockListType::Saved, config.clone()); self_.append(&saved_app_list_view); - // let separator_container = cascade! { - // gtk4::Box::new(Orientation::Vertical, 0); - // ..set_margin_top(8); - // ..set_margin_bottom(8); - // ..set_vexpand(true); - // }; - // self_.append(&separator_container); - // let separator = cascade! { - // Separator::new(Orientation::Vertical); - // ..set_margin_start(8); - // ..set_margin_end(8); - // ..set_vexpand(true); - // ..add_css_class("dock_separator"); - // }; - // separator_container.append(&separator); + let separator_container = cascade! { + gtk4::Box::new(Orientation::Vertical, 0); + ..set_margin_top(8); + ..set_margin_bottom(8); + ..set_vexpand(true); + }; + self_.append(&separator_container); + let separator = cascade! { + Separator::new(Orientation::Vertical); + ..set_margin_start(8); + ..set_margin_end(8); + ..set_vexpand(true); + ..add_css_class("dock_separator"); + }; + separator_container.append(&separator); 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| { diff --git a/applets/cosmic-app-list/src/dock_list/mod.rs b/applets/cosmic-app-list/src/dock_list/mod.rs index e3d4f1e0..ff4110f7 100644 --- a/applets/cosmic-app-list/src/dock_list/mod.rs +++ b/applets/cosmic-app-list/src/dock_list/mod.rs @@ -85,6 +85,7 @@ impl DockList { } fn restore_data(&self) { + // TODO use IDs instead of names if let Ok(file) = File::open(data_path()) { if let Ok(data) = serde_json::from_reader::<_, Vec>(file) { // dbg!(&data); diff --git a/applets/cosmic-app-list/src/dock_object/mod.rs b/applets/cosmic-app-list/src/dock_object/mod.rs index d27df1e8..936b75a7 100644 --- a/applets/cosmic-app-list/src/dock_object/mod.rs +++ b/applets/cosmic-app-list/src/dock_object/mod.rs @@ -78,7 +78,7 @@ impl DockObject { imp.saved.replace(is_saved); } - pub fn from_search_results(results: BoxedWindowList) -> Self { + pub fn from_window_list(results: BoxedWindowList) -> Self { let appinfo = if let Some(first) = results.0.get(0) { xdg::BaseDirectories::new() .expect("could not access XDG Base directory") @@ -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.name.as_str() == app_info.name().as_str() + && Some(&first.app_id) == app_info.id().map(|s| s.to_string()).as_ref() { return Some(app_info); } diff --git a/applets/cosmic-app-list/src/main.rs b/applets/cosmic-app-list/src/main.rs index 1f7e78cd..fc4a402d 100644 --- a/applets/cosmic-app-list/src/main.rs +++ b/applets/cosmic-app-list/src/main.rs @@ -125,11 +125,11 @@ fn main() { let stack_active = cached_results.iter().fold( BTreeMap::new(), |mut acc: BTreeMap, elem: &Toplevel| { - if let Some(v) = acc.get_mut(&elem.name) { + if let Some(v) = acc.get_mut(&elem.app_id) { v.0.push(elem.clone()); } else { acc.insert( - elem.name.clone(), + elem.app_id.clone(), BoxedWindowList(vec![elem.clone()]), ); } @@ -152,7 +152,7 @@ fn main() { if let Some((i, _s)) = stack_active .iter() .enumerate() - .find(|(_i, s)| s.0[0].name == cur_app_info.name()) + .find(|(_i, s)| Some(&s.0[0].app_id) == cur_app_info.id().map(|s| s.to_string()).as_ref()) { // println!( // "found active saved app {} at {}", @@ -163,7 +163,7 @@ fn main() { saved_app_model.items_changed(saved_i, 0, 0); } else if cached_results .iter() - .any(|s| s.name == cur_app_info.name()) + .any(|s| Some(&s.app_id) == cur_app_info.id().map(|s| s.to_string()).as_ref()) { dock_obj.set_property( "active", @@ -180,7 +180,7 @@ fn main() { let model_len = active_app_model.n_items(); let new_results: Vec = stack_active .into_iter() - .map(|v| DockObject::from_search_results(v).upcast()) + .map(|v| DockObject::from_window_list(v).upcast()) .collect(); active_app_model.splice(0, model_len, &new_results[..]); true @@ -197,7 +197,11 @@ fn main() { } AppListEvent::Add(top_level) => { // sort to make comparison with cache easier - cached_results.push(top_level); + if let Some(i) = cached_results.iter().position(|t| t.toplevel_handle == top_level.toplevel_handle) { + cached_results[i] = top_level; + } else { + cached_results.push(top_level); + } true } }; @@ -207,11 +211,11 @@ fn main() { let stack_active = cached_results.iter().fold( BTreeMap::new(), |mut acc: BTreeMap, elem| { - if let Some(v) = acc.get_mut(&elem.name) { + if let Some(v) = acc.get_mut(&elem.app_id) { v.0.push(elem.clone()); } else { acc.insert( - elem.name.clone(), + elem.app_id.clone(), BoxedWindowList(vec![elem.clone()]), ); } @@ -234,7 +238,7 @@ fn main() { if let Some((i, _s)) = stack_active .iter() .enumerate() - .find(|(_i, s)| s.0[0].app_id == cur_app_info.name()) + .find(|(_i, s)| Some(&s.0[0].app_id) == cur_app_info.id().map(|s| s.to_string()).as_ref()) { // println!("found active saved app {} at {}", s.0[0].name, i); let active = stack_active.remove(i); @@ -242,7 +246,7 @@ fn main() { saved_app_model.items_changed(saved_i, 0, 0); } else if cached_results .iter() - .any(|s| s.app_id == cur_app_info.name()) + .any(|s| Some(&s.app_id) == cur_app_info.id().map(|s| s.to_string()).as_ref()) { dock_obj.set_property( "active", @@ -259,8 +263,10 @@ fn main() { let model_len = active_app_model.n_items(); let new_results: Vec = stack_active .into_iter() - .map(|v| DockObject::from_search_results(v).upcast()) + .map(|v| DockObject::from_window_list(v).upcast()) .collect(); + dbg!(&new_results); + active_app_model.splice(0, model_len, &new_results[..]); } glib::prelude::Continue(true) From adc02df64fbe457a3a900993eed666980e76d455 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 19 Jul 2022 23:39:19 -0400 Subject: [PATCH 06/10] wip: use desktop file stem for app id --- Cargo.lock | 4 +- .../cosmic-app-list/src/apps_container/mod.rs | 7 +- applets/cosmic-app-list/src/config.rs | 17 +- applets/cosmic-app-list/src/dock_item/mod.rs | 2 +- applets/cosmic-app-list/src/dock_list/imp.rs | 4 +- applets/cosmic-app-list/src/dock_list/mod.rs | 6 +- .../cosmic-app-list/src/dock_object/mod.rs | 7 +- .../cosmic-app-list/src/dock_popover/mod.rs | 6 +- applets/cosmic-app-list/src/main.rs | 22 +- applets/cosmic-app-list/src/wayland.rs | 194 +++++++++++------- applets/cosmic-applet-graphics/src/main.rs | 2 - .../cosmic-applet-workspaces/src/wayland.rs | 84 ++++---- 12 files changed, 209 insertions(+), 146 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 54169c86..28482658 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2141,7 +2141,7 @@ checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "relm4" version = "0.5.0-beta.1" -source = "git+https://github.com/Relm4/Relm4.git?branch=next#746d244004e23764294b23519f6f8be1002c1ceb" +source = "git+https://github.com/relm4/relm4?branch=next#746d244004e23764294b23519f6f8be1002c1ceb" dependencies = [ "async-broadcast", "async-oneshot", @@ -2158,7 +2158,7 @@ dependencies = [ [[package]] name = "relm4-macros" version = "0.5.0-beta.1" -source = "git+https://github.com/Relm4/Relm4.git?branch=next#746d244004e23764294b23519f6f8be1002c1ceb" +source = "git+https://github.com/relm4/relm4?branch=next#746d244004e23764294b23519f6f8be1002c1ceb" dependencies = [ "proc-macro2", "quote", diff --git a/applets/cosmic-app-list/src/apps_container/mod.rs b/applets/cosmic-app-list/src/apps_container/mod.rs index 05ab1ce0..0d27c662 100644 --- a/applets/cosmic-app-list/src/apps_container/mod.rs +++ b/applets/cosmic-app-list/src/apps_container/mod.rs @@ -6,11 +6,11 @@ use crate::dock_list::DockList; use crate::dock_list::DockListType; use crate::utils::AppListEvent; use cascade::cascade; -use cosmic_panel_config::{PanelAnchor, CosmicPanelConfig}; -use gtk4::Separator; +use cosmic_panel_config::{CosmicPanelConfig, PanelAnchor}; use gtk4::prelude::*; use gtk4::subclass::prelude::*; use gtk4::Orientation; +use gtk4::Separator; use gtk4::{gio, glib}; use tokio::sync::mpsc::Sender; @@ -69,13 +69,12 @@ impl AppsContainer { // Setup self_.setup_callbacks(); self_.set_position(config.anchor); - Self::setup_callbacks(&self_); self_ } - + pub fn model(&self, type_: DockListType) -> &gio::ListStore { // Get state let imp = imp::AppsContainer::from_instance(self); diff --git a/applets/cosmic-app-list/src/config.rs b/applets/cosmic-app-list/src/config.rs index 0d24f88b..f255159d 100644 --- a/applets/cosmic-app-list/src/config.rs +++ b/applets/cosmic-app-list/src/config.rs @@ -1,9 +1,9 @@ -use std::fmt::Debug; -use std::fs::File; +use crate::ID; use anyhow::anyhow; use serde::Deserialize; +use std::fmt::Debug; +use std::fs::File; use xdg::BaseDirectories; -use crate::ID; #[derive(Debug, Clone, Deserialize)] pub enum TopLevelFilter { @@ -19,13 +19,18 @@ pub struct AppListConfig { 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()) { + 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)) + 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/mod.rs b/applets/cosmic-app-list/src/dock_item/mod.rs index f65e4deb..e7404495 100644 --- a/applets/cosmic-app-list/src/dock_item/mod.rs +++ b/applets/cosmic-app-list/src/dock_item/mod.rs @@ -2,8 +2,8 @@ use crate::dock_object::DockObject; use crate::dock_popover::DockPopover; -use crate::utils::BoxedWindowList; use crate::utils::AppListEvent; +use crate::utils::BoxedWindowList; use cascade::cascade; use cosmic_panel_config::PanelAnchor; use gtk4::glib; diff --git a/applets/cosmic-app-list/src/dock_list/imp.rs b/applets/cosmic-app-list/src/dock_list/imp.rs index e9ad2b6e..831def6a 100644 --- a/applets/cosmic-app-list/src/dock_list/imp.rs +++ b/applets/cosmic-app-list/src/dock_list/imp.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MPL-2.0-only -use cosmic_panel_config::{PanelAnchor, CosmicPanelConfig}; +use cosmic_panel_config::{CosmicPanelConfig, PanelAnchor}; use glib::SignalHandlerId; use gtk4::subclass::prelude::*; use gtk4::{gio, glib}; @@ -25,7 +25,7 @@ pub struct DockList { pub popover_menu_index: Rc>>, pub position: Rc>, pub tx: OnceCell>, - pub config: OnceCell + pub config: OnceCell, } #[glib::object_subclass] diff --git a/applets/cosmic-app-list/src/dock_list/mod.rs b/applets/cosmic-app-list/src/dock_list/mod.rs index ff4110f7..ab9db088 100644 --- a/applets/cosmic-app-list/src/dock_list/mod.rs +++ b/applets/cosmic-app-list/src/dock_list/mod.rs @@ -1,12 +1,13 @@ // 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, AppListEvent}; +use crate::utils::{AppListEvent, BoxedWindowList}; use crate::wayland::{Toplevel, ToplevelEvent}; +use crate::{TX, WAYLAND_TX}; use cascade::cascade; +use cosmic_panel_config::{CosmicPanelConfig, PanelAnchor}; use gio::DesktopAppInfo; use gio::Icon; use glib::Object; @@ -27,7 +28,6 @@ use gtk4::SignalListItemFactory; use gtk4::{DragSource, GestureClick}; use std::fs::File; use std::path::Path; -use cosmic_panel_config::{CosmicPanelConfig, PanelAnchor}; use tokio::sync::mpsc::Sender; mod imp; diff --git a/applets/cosmic-app-list/src/dock_object/mod.rs b/applets/cosmic-app-list/src/dock_object/mod.rs index 936b75a7..33975b51 100644 --- a/applets/cosmic-app-list/src/dock_object/mod.rs +++ b/applets/cosmic-app-list/src/dock_object/mod.rs @@ -95,7 +95,10 @@ impl DockObject { if let Some(path) = path.to_str() { if let Some(app_info) = gio::DesktopAppInfo::new(path) { if app_info.should_show() - && Some(&first.app_id) == app_info.id().map(|s| s.to_string()).as_ref() + && Some(&first.app_id) + == app_info.filename().and_then(|p| p + .file_stem() + .and_then(|s| s.to_str().map(|s| s.to_string()))).as_ref() { return Some(app_info); } @@ -109,7 +112,7 @@ impl DockObject { } else { None }; - // dbg!(&appinfo); + Object::new(&[("appinfo", &appinfo), ("active", &results)]) .expect("Failed to create `DockObject`.") } diff --git a/applets/cosmic-app-list/src/dock_popover/mod.rs b/applets/cosmic-app-list/src/dock_popover/mod.rs index d3019677..9aa8a0ca 100644 --- a/applets/cosmic-app-list/src/dock_popover/mod.rs +++ b/applets/cosmic-app-list/src/dock_popover/mod.rs @@ -9,11 +9,11 @@ use gtk4::{prelude::*, Label}; use gtk4::{Box, Button, Image, ListBox, Orientation}; use tokio::sync::mpsc::Sender; +use crate::dock_object::DockObject; +use crate::utils::AppListEvent; +use crate::utils::BoxedWindowList; use crate::wayland::ToplevelEvent; use crate::{TX, WAYLAND_TX}; -use crate::dock_object::DockObject; -use crate::utils::BoxedWindowList; -use crate::utils::AppListEvent; mod imp; diff --git a/applets/cosmic-app-list/src/main.rs b/applets/cosmic-app-list/src/main.rs index fc4a402d..8dc79a65 100644 --- a/applets/cosmic-app-list/src/main.rs +++ b/applets/cosmic-app-list/src/main.rs @@ -8,15 +8,16 @@ 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, AppListEvent, DEST, PATH}; +use utils::{block_on, AppListEvent, BoxedWindowList, DEST, PATH}; +use wayland::{Toplevel, ToplevelEvent}; mod apps_container; mod apps_window; +mod config; mod dock_item; mod dock_list; mod dock_object; @@ -25,7 +26,6 @@ mod localize; mod utils; mod wayland; mod wayland_source; -mod config; const ID: &str = "com.system76.CosmicAppList"; static TX: OnceCell> = OnceCell::new(); @@ -152,7 +152,9 @@ fn main() { if let Some((i, _s)) = stack_active .iter() .enumerate() - .find(|(_i, s)| Some(&s.0[0].app_id) == cur_app_info.id().map(|s| s.to_string()).as_ref()) + .find(|(_i, s)| Some(&s.0[0].app_id) == cur_app_info.filename().and_then(|p| p + .file_stem() + .and_then(|s| s.to_str().map(|s| s.to_string()))).as_ref()) { // println!( // "found active saved app {} at {}", @@ -163,7 +165,9 @@ fn main() { saved_app_model.items_changed(saved_i, 0, 0); } else if cached_results .iter() - .any(|s| Some(&s.app_id) == cur_app_info.id().map(|s| s.to_string()).as_ref()) + .any(|s| Some(&s.app_id) == cur_app_info.filename().and_then(|p| p + .file_stem() + .and_then(|s| s.to_str().map(|s| s.to_string()))).as_ref()) { dock_obj.set_property( "active", @@ -238,7 +242,9 @@ fn main() { if let Some((i, _s)) = stack_active .iter() .enumerate() - .find(|(_i, s)| Some(&s.0[0].app_id) == cur_app_info.id().map(|s| s.to_string()).as_ref()) + .find(|(_i, s)| Some(&s.0[0].app_id) == cur_app_info.filename().and_then(|p| p + .file_stem() + .and_then(|s| s.to_str().map(|s| s.to_string()))).as_ref()) { // println!("found active saved app {} at {}", s.0[0].name, i); let active = stack_active.remove(i); @@ -246,7 +252,9 @@ fn main() { saved_app_model.items_changed(saved_i, 0, 0); } else if cached_results .iter() - .any(|s| Some(&s.app_id) == cur_app_info.id().map(|s| s.to_string()).as_ref()) + .any(|s| Some(&s.app_id) == cur_app_info.filename().and_then(|p| p + .file_stem() + .and_then(|s| s.to_str().map(|s| s.to_string()))).as_ref()) { dock_obj.set_property( "active", diff --git a/applets/cosmic-app-list/src/wayland.rs b/applets/cosmic-app-list/src/wayland.rs index 4e57a752..096e0db1 100644 --- a/applets/cosmic-app-list/src/wayland.rs +++ b/applets/cosmic-app-list/src/wayland.rs @@ -1,10 +1,22 @@ -use crate::{ - wayland_source::WaylandSource, config::TopLevelFilter, TX, utils::AppListEvent, -}; +use crate::config::AppListConfig; +use crate::{config::TopLevelFilter, utils::AppListEvent, wayland_source::WaylandSource, TX}; +use calloop::channel::*; use cosmic_panel_config::CosmicPanelConfig; -use std::{ -env, os::unix::net::UnixStream, path::PathBuf,time::Duration, +use cosmic_protocols::{ + toplevel_info::v1::client::{ + zcosmic_toplevel_handle_v1::{self, ZcosmicToplevelHandleV1}, + zcosmic_toplevel_info_v1::{self, ZcosmicToplevelInfoV1}, + }, + toplevel_management::v1::client::zcosmic_toplevel_manager_v1::{ + self, ZcosmicToplevelManagerV1, + }, + workspace::v1::client::{ + zcosmic_workspace_group_handle_v1::{self, ZcosmicWorkspaceGroupHandleV1}, + zcosmic_workspace_handle_v1::{self, ZcosmicWorkspaceHandleV1}, + zcosmic_workspace_manager_v1::{self, ZcosmicWorkspaceManagerV1}, + }, }; +use std::{env, os::unix::net::UnixStream, path::PathBuf, time::Duration}; use wayland_client::{ event_created_child, protocol::{ @@ -13,14 +25,7 @@ use wayland_client::{ }, 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 { @@ -47,8 +52,9 @@ pub fn spawn_toplevels() -> SyncSender { { std::thread::spawn(move || { let output = match config.filter_top_levels { - Some(TopLevelFilter::ConfiguredOutput) => CosmicPanelConfig::load_from_env() - .ok().map(|c| c.output), + Some(TopLevelFilter::ConfiguredOutput) => { + CosmicPanelConfig::load_from_env().ok().map(|c| c.output) + } _ => None, }; let mut event_loop = calloop::EventLoop::::try_new().unwrap(); @@ -73,14 +79,14 @@ pub fn spawn_toplevels() -> SyncSender { configured_output: output, expected_output: None, running: true, - toplevels: vec![] + 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!() } @@ -136,12 +142,17 @@ impl State { .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, - }))) + 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 } @@ -193,34 +204,19 @@ impl Dispatch for State { match &interface[..] { "zcosmic_toplevel_info_v1" => { let ti = registry - .bind::( - name, - 1, - qh, - (), - ) + .bind::(name, 1, qh, ()) .unwrap(); state.toplevel_info = Some(ti); } "zcosmic_toplevel_manager_v1" => { let tm = registry - .bind::( - name, - 1, - qh, - (), - ) + .bind::(name, 1, qh, ()) .unwrap(); state.toplevel_manager = Some(tm); } "zcosmic_workspace_manager_v1" => { let workspace_manager = registry - .bind::( - name, - 1, - qh, - (), - ) + .bind::(name, 1, qh, ()) .unwrap(); state.workspace_manager = Some(workspace_manager); } @@ -245,12 +241,19 @@ impl Dispatch for State { 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 }); - }, + state.toplevels.push(Toplevel { + name: "".into(), + app_id: "".into(), + toplevel_handle: toplevel, + states: vec![], + output: None, + workspace: None, + }); + } zcosmic_toplevel_info_v1::Event::Finished => { todo!() - }, - _ => {}, + } + _ => {} } } @@ -259,7 +262,6 @@ impl Dispatch for State { ]); } - impl Dispatch for State { fn event( _: &mut Self, @@ -272,8 +274,8 @@ impl Dispatch for State { match event { zcosmic_toplevel_manager_v1::Event::Capabilities { .. } => { // TODO capabilities affect what is shown to user in applet - }, - _ => {}, + } + _ => {} } } } @@ -292,27 +294,33 @@ impl Dispatch for State { if let Some(i) = state.toplevels.iter().position(|t| &t.toplevel_handle == p) { state.toplevels.remove(i); } - }, + } zcosmic_toplevel_handle_v1::Event::Done => { let to_send = match state.config.filter_top_levels { - Some(TopLevelFilter::ActiveWorkspace) => { - state.toplevels.iter_mut().find(|t| { - if &t.toplevel_handle == p { - state + Some(TopLevelFilter::ActiveWorkspace) => state.toplevels.iter_mut().find(|t| { + if &t.toplevel_handle == p { + state .workspace_groups .iter() .find(|g| { g.workspaces .iter() - .find(|w| w.states.contains(&zcosmic_workspace_handle_v1::State::Active) && Some(&w.workspace_handle) == t.workspace.as_ref()).is_some() - }).is_some() - } else { - false - } - }) - }, - Some(TopLevelFilter::ConfiguredOutput) => - state.toplevels.iter_mut().find(|t| &t.toplevel_handle == p && state.expected_output == t.output), + .find(|w| { + w.states.contains( + &zcosmic_workspace_handle_v1::State::Active, + ) && Some(&w.workspace_handle) == t.workspace.as_ref() + }) + .is_some() + }) + .is_some() + } else { + false + } + }), + Some(TopLevelFilter::ConfiguredOutput) => state + .toplevels + .iter_mut() + .find(|t| &t.toplevel_handle == p && state.expected_output == t.output), _ => state.toplevels.iter_mut().find(|t| &t.toplevel_handle == p), }; @@ -321,49 +329,63 @@ impl Dispatch for State { let _ = tx.send(AppListEvent::Add(toplevel)); } - - }, + } 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)) { + 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)) { + 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(); + 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, @@ -384,7 +406,9 @@ impl Dispatch for State { 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()) + w1.coordinates + .iter() + .zip(w2.coordinates.iter()) .skip_while(|(coord1, coord2)| coord1 == coord2) .next() .map(|(coord1, coord2)| coord1.cmp(coord2)) @@ -488,19 +512,31 @@ impl Dispatch for State { .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(); + 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 } => { + 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(); + 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(); // TODO if workspace active status changes while configured to only show active workspace, clear the list - } } zcosmic_workspace_handle_v1::Event::Remove => { diff --git a/applets/cosmic-applet-graphics/src/main.rs b/applets/cosmic-applet-graphics/src/main.rs index 97f4327b..f29ce638 100644 --- a/applets/cosmic-applet-graphics/src/main.rs +++ b/applets/cosmic-applet-graphics/src/main.rs @@ -51,8 +51,6 @@ fn main() { gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION, ); - - let current_graphics = RT .block_on(get_current_graphics()) .expect("failed to connect to system76-power"); diff --git a/applets/cosmic-applet-workspaces/src/wayland.rs b/applets/cosmic-applet-workspaces/src/wayland.rs index f2ccf191..3d696ac0 100644 --- a/applets/cosmic-applet-workspaces/src/wayland.rs +++ b/applets/cosmic-applet-workspaces/src/wayland.rs @@ -2,7 +2,13 @@ use crate::{ utils::{Activate, WorkspaceEvent}, wayland_source::WaylandSource, }; +use calloop::channel::*; use cosmic_panel_config::CosmicPanelConfig; +use cosmic_protocols::workspace::v1::client::{ + zcosmic_workspace_group_handle_v1::{self, ZcosmicWorkspaceGroupHandleV1}, + zcosmic_workspace_handle_v1::{self, ZcosmicWorkspaceHandleV1}, + zcosmic_workspace_manager_v1::{self, ZcosmicWorkspaceManagerV1}, +}; use gtk4::glib; use std::{ collections::HashMap, env, hash::Hash, mem, os::unix::net::UnixStream, path::PathBuf, @@ -18,13 +24,7 @@ use wayland_client::{ }, 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}, -}; use wayland_client::{Connection, Dispatch, QueueHandle}; -use calloop::channel::*; pub fn spawn_workspaces(tx: glib::Sender) -> SyncSender { let (workspaces_tx, workspaces_rx) = calloop::channel::sync_channel(100); @@ -80,19 +80,18 @@ pub fn spawn_workspaces(tx: glib::Sender) -> SyncSender { } } Event::Msg(WorkspaceEvent::Scroll(v)) => { - if let Some((w_g, w_i)) = state - .workspace_groups - .iter() - .find_map(|g| { - if g.output != state.expected_output { - return None; - } - g.workspaces - .iter() - .position(|w| w.states.contains(&zcosmic_workspace_handle_v1::State::Active)) - .map(|w_i| (g, w_i)) - }) - { + if let Some((w_g, w_i)) = state.workspace_groups.iter().find_map(|g| { + if g.output != state.expected_output { + return None; + } + g.workspaces + .iter() + .position(|w| { + w.states + .contains(&zcosmic_workspace_handle_v1::State::Active) + }) + .map(|w_i| (g, w_i)) + }) { let max_w = w_g.workspaces.len().wrapping_sub(1); let d_i = if v > 0.0 { if w_i == max_w { @@ -154,12 +153,17 @@ impl State { .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, - }))) + 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 } @@ -201,12 +205,7 @@ impl Dispatch for State { match &interface[..] { "zcosmic_workspace_manager_v1" => { let workspace_manager = registry - .bind::( - name, - 1, - qh, - (), - ) + .bind::(name, 1, qh, ()) .unwrap(); state.workspace_manager = Some(workspace_manager); } @@ -239,7 +238,9 @@ impl Dispatch for State { 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()) + w1.coordinates + .iter() + .zip(w2.coordinates.iter()) .skip_while(|(coord1, coord2)| coord1 == coord2) .next() .map(|(coord1, coord2)| coord1.cmp(coord2)) @@ -344,17 +345,30 @@ impl Dispatch for State { .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(); + 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 } => { + 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(); + 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 => { From 83fc8893ec59dd0b3169b2038f83deb394286b81 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 20 Jul 2022 10:21:40 -0400 Subject: [PATCH 07/10] fix: use app container created by window & only create dock object from window list if a matching app info is found --- Cargo.lock | 4 ++-- .../cosmic-app-list/src/apps_window/mod.rs | 5 ++++- .../cosmic-app-list/src/dock_object/mod.rs | 19 ++++++++----------- applets/cosmic-app-list/src/main.rs | 9 ++++----- applets/cosmic-app-list/src/wayland.rs | 1 - 5 files changed, 18 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 28482658..54169c86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2141,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", @@ -2158,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", diff --git a/applets/cosmic-app-list/src/apps_window/mod.rs b/applets/cosmic-app-list/src/apps_window/mod.rs index bf72eb94..52f00804 100644 --- a/applets/cosmic-app-list/src/apps_window/mod.rs +++ b/applets/cosmic-app-list/src/apps_window/mod.rs @@ -8,7 +8,6 @@ use gtk4::{ prelude::*, subclass::prelude::*, }; -use tokio::sync::mpsc; mod imp; @@ -43,6 +42,10 @@ impl CosmicAppListWindow { self_ } + pub fn apps_container(&self) -> &AppsContainer { + imp::CosmicAppListWindow::from_instance(&self).inner.get().unwrap() + } + fn setup_shortcuts(&self) { let window = self.clone().upcast::(); let action_quit = gio::SimpleAction::new("quit", None); diff --git a/applets/cosmic-app-list/src/dock_object/mod.rs b/applets/cosmic-app-list/src/dock_object/mod.rs index 33975b51..63ac9400 100644 --- a/applets/cosmic-app-list/src/dock_object/mod.rs +++ b/applets/cosmic-app-list/src/dock_object/mod.rs @@ -78,9 +78,9 @@ impl DockObject { imp.saved.replace(is_saved); } - pub fn from_window_list(results: BoxedWindowList) -> Self { - let appinfo = if let Some(first) = results.0.get(0) { - xdg::BaseDirectories::new() + pub fn from_window_list(results: BoxedWindowList) -> Option { + if let Some(first) = results.0.get(0) { + return xdg::BaseDirectories::new() .expect("could not access XDG Base directory") .get_data_dirs() .iter_mut() @@ -100,7 +100,8 @@ impl DockObject { .file_stem() .and_then(|s| s.to_str().map(|s| s.to_string()))).as_ref() { - return Some(app_info); + return Some(Object::new(&[("appinfo", &app_info), ("active", &results)]) + .expect("Failed to create `DockObject`.")); } } } @@ -108,13 +109,9 @@ impl DockObject { } None }) - .next() - } else { - None - }; - - Object::new(&[("appinfo", &appinfo), ("active", &results)]) - .expect("Failed to create `DockObject`.") + .next(); + } + None } pub fn set_popover(&self, b: bool) { diff --git a/applets/cosmic-app-list/src/main.rs b/applets/cosmic-app-list/src/main.rs index 8dc79a65..a2ea264d 100644 --- a/applets/cosmic-app-list/src/main.rs +++ b/applets/cosmic-app-list/src/main.rs @@ -66,7 +66,6 @@ fn main() { let (tx, rx) = glib::MainContext::channel(glib::Priority::default()); let window = CosmicAppListWindow::new(app); - let apps_container = apps_container::AppsContainer::new(); let wayland_tx = wayland::spawn_toplevels(); WAYLAND_TX.set(wayland_tx).unwrap(); @@ -78,6 +77,7 @@ fn main() { TX.set(tx.clone()).unwrap(); rx.attach(None, glib::clone!(@weak window => @default-return glib::prelude::Continue(true), move |event| { + let apps_container = window.apps_container(); let should_apply_changes = match event { AppListEvent::Favorite((name, should_favorite)) => { let saved_app_model = apps_container.model(DockListType::Saved); @@ -184,7 +184,7 @@ fn main() { let model_len = active_app_model.n_items(); let new_results: Vec = stack_active .into_iter() - .map(|v| DockObject::from_window_list(v).upcast()) + .filter_map(|v| DockObject::from_window_list(v).map(|o| o.upcast())) .collect(); active_app_model.splice(0, model_len, &new_results[..]); true @@ -210,7 +210,7 @@ fn main() { } }; if should_apply_changes { - dbg!(&cached_results); + // dbg!(&cached_results); // build active app stacks for each app let stack_active = cached_results.iter().fold( BTreeMap::new(), @@ -271,9 +271,8 @@ fn main() { let model_len = active_app_model.n_items(); let new_results: Vec = stack_active .into_iter() - .map(|v| DockObject::from_window_list(v).upcast()) + .filter_map(|v| DockObject::from_window_list(v).map(|o| o.upcast())) .collect(); - dbg!(&new_results); active_app_model.splice(0, model_len, &new_results[..]); } diff --git a/applets/cosmic-app-list/src/wayland.rs b/applets/cosmic-app-list/src/wayland.rs index 096e0db1..4171b130 100644 --- a/applets/cosmic-app-list/src/wayland.rs +++ b/applets/cosmic-app-list/src/wayland.rs @@ -238,7 +238,6 @@ impl Dispatch for State { _: &Connection, _: &QueueHandle, ) { - dbg!(&event); match event { zcosmic_toplevel_info_v1::Event::Toplevel { toplevel } => { state.toplevels.push(Toplevel { From 29e029d626140919069e505337957cf01cf24e30 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 20 Jul 2022 11:48:51 -0400 Subject: [PATCH 08/10] fix: show active dots --- Cargo.lock | 4 ++-- applets/cosmic-app-list/src/dock_item/mod.rs | 3 ++- applets/cosmic-app-list/src/style.css | 6 ++++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 54169c86..28482658 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2141,7 +2141,7 @@ checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "relm4" version = "0.5.0-beta.1" -source = "git+https://github.com/Relm4/Relm4.git?branch=next#746d244004e23764294b23519f6f8be1002c1ceb" +source = "git+https://github.com/relm4/relm4?branch=next#746d244004e23764294b23519f6f8be1002c1ceb" dependencies = [ "async-broadcast", "async-oneshot", @@ -2158,7 +2158,7 @@ dependencies = [ [[package]] name = "relm4-macros" version = "0.5.0-beta.1" -source = "git+https://github.com/Relm4/Relm4.git?branch=next#746d244004e23764294b23519f6f8be1002c1ceb" +source = "git+https://github.com/relm4/relm4?branch=next#746d244004e23764294b23519f6f8be1002c1ceb" dependencies = [ "proc-macro2", "quote", diff --git a/applets/cosmic-app-list/src/dock_item/mod.rs b/applets/cosmic-app-list/src/dock_item/mod.rs index e7404495..433066ca 100644 --- a/applets/cosmic-app-list/src/dock_item/mod.rs +++ b/applets/cosmic-app-list/src/dock_item/mod.rs @@ -50,7 +50,7 @@ impl DockItem { ..set_valign(Align::Center); ..add_css_class("transparent"); }; - // TODO dots inverse color of parent with gsk blend modes? + item_box.append(&image); item_box.append(&dots); let popover = cascade! { @@ -111,6 +111,7 @@ impl DockItem { while let Some(c) = dots.first_child() { dots.remove(&c); } + for _ in active.0 { dots.append(&cascade! { Box::new(Orientation::Horizontal, 0); diff --git a/applets/cosmic-app-list/src/style.css b/applets/cosmic-app-list/src/style.css index e4a9c1a7..af349394 100644 --- a/applets/cosmic-app-list/src/style.css +++ b/applets/cosmic-app-list/src/style.css @@ -57,6 +57,12 @@ button.dock_item:hover { background: rgba(255, 255, 255, 0.1); } +box.dock_dots { + background: white; + padding: 2px; + border-radius: 4px; +} + *.transparent { border-color: transparent; background: transparent; From 2a8497fb2a4310cf66b7857cd53ed16d559f54ac Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 20 Jul 2022 12:09:16 -0400 Subject: [PATCH 09/10] fix: style color fixes --- applets/cosmic-app-list/src/dock_popover/mod.rs | 11 +++++------ applets/cosmic-app-list/src/style.css | 4 ++++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/applets/cosmic-app-list/src/dock_popover/mod.rs b/applets/cosmic-app-list/src/dock_popover/mod.rs index 9aa8a0ca..38e30e00 100644 --- a/applets/cosmic-app-list/src/dock_popover/mod.rs +++ b/applets/cosmic-app-list/src/dock_popover/mod.rs @@ -86,12 +86,11 @@ impl DockPopover { ..add_css_class("title-4"); ..add_css_class("dock_popover_title"); }; - - let window_image = cascade! { - //TODO fill with image of window - Image::from_pixbuf(None); - }; - window_box.append(&window_image); + //TODO fill with image of window + // let window_image = cascade! { + // Image::from_pixbuf(None); + // }; + // window_box.append(&window_image); window_box.append(&window_title); } // imp.all_windows_item_revealer.replace(window_list_revealer); diff --git a/applets/cosmic-app-list/src/style.css b/applets/cosmic-app-list/src/style.css index af349394..3a2fbac3 100644 --- a/applets/cosmic-app-list/src/style.css +++ b/applets/cosmic-app-list/src/style.css @@ -48,6 +48,10 @@ button.dock_item { outline-color: transparent; } +label.dock_popover_title { + color: black; +} + button.dock_item:hover { border-radius: 12px; transition: 100ms; From ab851fceef75fb56295a70dcad899d01b5a22d10 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 20 Jul 2022 16:17:43 -0400 Subject: [PATCH 10/10] feat: app list is working fairly well again --- Cargo.lock | 3 +- applets/cosmic-app-list/Cargo.toml | 1 - .../cosmic-app-list/src/apps_container/mod.rs | 5 -- .../cosmic-app-list/src/apps_window/mod.rs | 5 +- applets/cosmic-app-list/src/dock_item/imp.rs | 13 ++--- applets/cosmic-app-list/src/dock_item/mod.rs | 1 - applets/cosmic-app-list/src/dock_list/imp.rs | 2 - applets/cosmic-app-list/src/dock_list/mod.rs | 49 +++++++++---------- .../cosmic-app-list/src/dock_object/mod.rs | 20 ++++++-- .../cosmic-app-list/src/dock_popover/imp.rs | 4 -- .../cosmic-app-list/src/dock_popover/mod.rs | 1 - applets/cosmic-app-list/src/main.rs | 7 +-- applets/cosmic-app-list/src/wayland.rs | 35 +++++++++++-- 13 files changed, 80 insertions(+), 66 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 28482658..13723c03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -299,7 +299,6 @@ dependencies = [ "rust-embed", "serde", "serde_json", - "tokio", "wayland-backend", "wayland-client 0.30.0-beta.8", "xdg", @@ -443,7 +442,7 @@ dependencies = [ "i18n-embed", "i18n-embed-fl", "log", - "nix 0.22.3", + "nix 0.24.1", "once_cell", "pretty_env_logger", "rust-embed", diff --git a/applets/cosmic-app-list/Cargo.toml b/applets/cosmic-app-list/Cargo.toml index 5b3cee7f..80ef2052 100644 --- a/applets/cosmic-app-list/Cargo.toml +++ b/applets/cosmic-app-list/Cargo.toml @@ -13,7 +13,6 @@ 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_json = "1.0.78" -tokio = { version = "1.16.1", features = ["sync"] } futures = "0.3.19" futures-util = "0.3.19" once_cell = "1.9.0" diff --git a/applets/cosmic-app-list/src/apps_container/mod.rs b/applets/cosmic-app-list/src/apps_container/mod.rs index 0d27c662..64625077 100644 --- a/applets/cosmic-app-list/src/apps_container/mod.rs +++ b/applets/cosmic-app-list/src/apps_container/mod.rs @@ -1,10 +1,6 @@ -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::AppListEvent; use cascade::cascade; use cosmic_panel_config::{CosmicPanelConfig, PanelAnchor}; use gtk4::prelude::*; @@ -12,7 +8,6 @@ use gtk4::subclass::prelude::*; use gtk4::Orientation; use gtk4::Separator; use gtk4::{gio, glib}; -use tokio::sync::mpsc::Sender; mod imp; diff --git a/applets/cosmic-app-list/src/apps_window/mod.rs b/applets/cosmic-app-list/src/apps_window/mod.rs index 52f00804..0db1afe0 100644 --- a/applets/cosmic-app-list/src/apps_window/mod.rs +++ b/applets/cosmic-app-list/src/apps_window/mod.rs @@ -43,7 +43,10 @@ impl CosmicAppListWindow { } pub fn apps_container(&self) -> &AppsContainer { - imp::CosmicAppListWindow::from_instance(&self).inner.get().unwrap() + imp::CosmicAppListWindow::from_instance(&self) + .inner + .get() + .unwrap() } fn setup_shortcuts(&self) { diff --git a/applets/cosmic-app-list/src/dock_item/imp.rs b/applets/cosmic-app-list/src/dock_item/imp.rs index 2fe3a9f2..13d44855 100644 --- a/applets/cosmic-app-list/src/dock_item/imp.rs +++ b/applets/cosmic-app-list/src/dock_item/imp.rs @@ -1,17 +1,15 @@ // SPDX-License-Identifier: MPL-2.0-only -use glib::subclass::Signal; -use gtk4::glib; -use gtk4::prelude::*; -use gtk4::subclass::prelude::*; +use gtk4::{ + glib::{self, subclass::Signal}, + prelude::*, + subclass::prelude::*, +}; use once_cell::sync::Lazy; -use once_cell::sync::OnceCell; use std::cell::{Cell, RefCell}; use std::rc::Rc; -use tokio::sync::mpsc::Sender; use crate::dock_popover::DockPopover; -use crate::utils::AppListEvent; #[derive(Debug, Default)] pub struct DockItem { @@ -20,7 +18,6 @@ pub struct DockItem { pub item_box: Rc>, pub popover: Rc>, pub popover_menu: Rc>>, - 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 433066ca..a4c66b82 100644 --- a/applets/cosmic-app-list/src/dock_item/mod.rs +++ b/applets/cosmic-app-list/src/dock_item/mod.rs @@ -14,7 +14,6 @@ use gtk4::Image; use gtk4::Orientation; use gtk4::Popover; use gtk4::{Align, PositionType}; -use tokio::sync::mpsc::Sender; mod imp; diff --git a/applets/cosmic-app-list/src/dock_list/imp.rs b/applets/cosmic-app-list/src/dock_list/imp.rs index 831def6a..d11e6dce 100644 --- a/applets/cosmic-app-list/src/dock_list/imp.rs +++ b/applets/cosmic-app-list/src/dock_list/imp.rs @@ -8,7 +8,6 @@ use gtk4::{Box, DragSource, DropTarget, GestureClick, ListView}; use once_cell::sync::OnceCell; use std::cell::{Cell, RefCell}; use std::rc::Rc; -use tokio::sync::mpsc; use crate::utils::AppListEvent; @@ -24,7 +23,6 @@ pub struct DockList { pub drag_cancel_signal: Rc>>, pub popover_menu_index: Rc>>, pub position: Rc>, - 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 ab9db088..2348b598 100644 --- a/applets/cosmic-app-list/src/dock_list/mod.rs +++ b/applets/cosmic-app-list/src/dock_list/mod.rs @@ -1,34 +1,26 @@ // SPDX-License-Identifier: MPL-2.0-only -use crate::dock_item::DockItem; -use crate::dock_object::DockObject; -use crate::utils::data_path; -use crate::utils::{AppListEvent, BoxedWindowList}; -use crate::wayland::{Toplevel, ToplevelEvent}; -use crate::{TX, WAYLAND_TX}; +use crate::{ + dock_item::DockItem, + utils::{AppListEvent, BoxedWindowList, data_path}, + wayland::{Toplevel, ToplevelEvent}, + {TX, WAYLAND_TX}, dock_object::DockObject, +}; use cascade::cascade; use cosmic_panel_config::{CosmicPanelConfig, PanelAnchor}; -use gio::DesktopAppInfo; -use gio::Icon; -use glib::Object; -use glib::Type; -use gtk4::gdk; -use gtk4::gdk::ContentProvider; -use gtk4::gdk::Display; -use gtk4::gdk::ModifierType; -use gtk4::glib; -use gtk4::prelude::ListModelExt; -use gtk4::prelude::*; -use gtk4::subclass::prelude::*; -use gtk4::DropTarget; -use gtk4::IconTheme; -use gtk4::ListView; -use gtk4::Orientation; -use gtk4::SignalListItemFactory; -use gtk4::{DragSource, GestureClick}; -use std::fs::File; -use std::path::Path; -use tokio::sync::mpsc::Sender; + +use gio::traits::AppLaunchContextExt; +use gtk4::{ + gdk::{self, ContentProvider, Display, ModifierType}, + gio::{self, DesktopAppInfo, Icon}, + glib::{self, Object, Type}, + prelude::ListModelExt, + prelude::*, + subclass::prelude::*, + DropTarget, IconTheme, ListView, Orientation, SignalListItemFactory, + {DragSource, GestureClick}, +}; +use std::{fs::File, path::Path}; mod imp; @@ -239,11 +231,14 @@ impl DockList { // dbg!(click_modifier); // Launch the application when an item of the list is activated + // TODO use seat eventually + // let wl_seat = self_.device().map(|d| d.seat().downcast::().unwrap().wl_seat()).unwrap(); 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 { if let Some(old_item) = model.item(old_index) { diff --git a/applets/cosmic-app-list/src/dock_object/mod.rs b/applets/cosmic-app-list/src/dock_object/mod.rs index 63ac9400..df0974ed 100644 --- a/applets/cosmic-app-list/src/dock_object/mod.rs +++ b/applets/cosmic-app-list/src/dock_object/mod.rs @@ -96,12 +96,22 @@ impl DockObject { if let Some(app_info) = gio::DesktopAppInfo::new(path) { if app_info.should_show() && Some(&first.app_id) - == app_info.filename().and_then(|p| p - .file_stem() - .and_then(|s| s.to_str().map(|s| s.to_string()))).as_ref() + == app_info + .filename() + .and_then(|p| { + p.file_stem().and_then(|s| { + s.to_str().map(|s| s.to_string()) + }) + }) + .as_ref() { - return Some(Object::new(&[("appinfo", &app_info), ("active", &results)]) - .expect("Failed to create `DockObject`.")); + return Some( + Object::new(&[ + ("appinfo", &app_info), + ("active", &results), + ]) + .expect("Failed to create `DockObject`."), + ); } } } diff --git a/applets/cosmic-app-list/src/dock_popover/imp.rs b/applets/cosmic-app-list/src/dock_popover/imp.rs index 5b586620..212963d3 100644 --- a/applets/cosmic-app-list/src/dock_popover/imp.rs +++ b/applets/cosmic-app-list/src/dock_popover/imp.rs @@ -9,11 +9,8 @@ use gtk4::prelude::*; use gtk4::subclass::prelude::*; use gtk4::{Box, Button, ListBox, Revealer}; use once_cell::sync::Lazy; -use once_cell::sync::OnceCell; -use tokio::sync::mpsc::Sender; use crate::dock_object::DockObject; -use crate::utils::AppListEvent; #[derive(Debug, Default)] pub struct DockPopover { @@ -26,7 +23,6 @@ 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>, } #[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 38e30e00..8f0b73be 100644 --- a/applets/cosmic-app-list/src/dock_popover/mod.rs +++ b/applets/cosmic-app-list/src/dock_popover/mod.rs @@ -7,7 +7,6 @@ use gtk4::subclass::prelude::*; use gtk4::{gdk, gio, glib}; use gtk4::{prelude::*, Label}; use gtk4::{Box, Button, Image, ListBox, Orientation}; -use tokio::sync::mpsc::Sender; use crate::dock_object::DockObject; use crate::utils::AppListEvent; diff --git a/applets/cosmic-app-list/src/main.rs b/applets/cosmic-app-list/src/main.rs index a2ea264d..db4e5818 100644 --- a/applets/cosmic-app-list/src/main.rs +++ b/applets/cosmic-app-list/src/main.rs @@ -9,9 +9,6 @@ use gtk4::gdk::Display; use gtk4::{glib, prelude::*, CssProvider, StyleContext}; use once_cell::sync::OnceCell; use std::collections::BTreeMap; -use std::sync::{Arc, Mutex}; -use std::time::Duration; -use tokio::sync::mpsc; use utils::{block_on, AppListEvent, BoxedWindowList, DEST, PATH}; use wayland::{Toplevel, ToplevelEvent}; @@ -87,7 +84,7 @@ fn main() { 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()) { + if cur_dock_object.get_name() == Some(name.clone()) { cur_dock_object.set_saved(true); index = Some(cur); } @@ -104,7 +101,7 @@ fn main() { 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()) { + if cur_dock_object.get_name() == Some(name.clone()) { cur_dock_object.set_saved(false); index = Some(cur); } diff --git a/applets/cosmic-app-list/src/wayland.rs b/applets/cosmic-app-list/src/wayland.rs index 4171b130..88b5d3e2 100644 --- a/applets/cosmic-app-list/src/wayland.rs +++ b/applets/cosmic-app-list/src/wayland.rs @@ -16,6 +16,7 @@ use cosmic_protocols::{ zcosmic_workspace_manager_v1::{self, ZcosmicWorkspaceManagerV1}, }, }; +use wayland_client::protocol::wl_seat::{WlSeat, self}; use std::{env, os::unix::net::UnixStream, path::PathBuf, time::Duration}; use wayland_client::{ event_created_child, @@ -80,15 +81,21 @@ pub fn spawn_toplevels() -> SyncSender { expected_output: None, running: true, toplevels: vec![], + seats: 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::Activate(toplevel)) => { + if let Some(manager) = &state.toplevel_manager { + for seat in &state.seats { + manager.activate(&toplevel,seat) + } } } - Event::Msg(ToplevelEvent::Close(_t)) => { - todo!() + Event::Msg(ToplevelEvent::Close(t)) => { + if let Some(manager) = &state.toplevel_manager { + manager.close(&t); + } } Event::Closed => { if let Some(workspace_manager) = &mut state.workspace_manager { @@ -134,6 +141,7 @@ pub struct State { toplevel_info: Option, toplevel_manager: Option, toplevels: Vec, + seats: Vec, } impl State { @@ -220,6 +228,9 @@ impl Dispatch for State { .unwrap(); state.workspace_manager = Some(workspace_manager); } + "wl_seat" => { + registry.bind::(name, 1, qh, ()).unwrap(); + } "wl_output" => { registry.bind::(name, 1, qh, ()).unwrap(); } @@ -570,3 +581,19 @@ impl Dispatch for State { } } } + + +impl Dispatch for State { + fn event( + state: &mut Self, + seat: &WlSeat, + _: wl_seat::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + if state.seats.iter().find(|s| s == &seat).is_none() { + state.seats.push(seat.clone()); + } + } +}