Merge pull request #22 from pop-os/app-list-toplevel-dev
App list toplevel dev
This commit is contained in:
commit
9b97404fa6
22 changed files with 1314 additions and 743 deletions
49
Cargo.lock
generated
49
Cargo.lock
generated
|
|
@ -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,17 @@ 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",
|
||||
]
|
||||
|
||||
|
|
@ -442,8 +448,7 @@ dependencies = [
|
|||
"rust-embed",
|
||||
"tokio",
|
||||
"wayland-backend",
|
||||
"wayland-client 0.30.0-beta.7",
|
||||
"wayland-commons",
|
||||
"wayland-client 0.30.0-beta.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -493,13 +498,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]]
|
||||
|
|
@ -2768,17 +2773,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 +2802,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 +2841,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 +2864,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 +2885,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",
|
||||
|
|
|
|||
|
|
@ -6,24 +6,31 @@ 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"
|
||||
futures-util = "0.3.19"
|
||||
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" }
|
||||
|
|
|
|||
|
|
@ -10,3 +10,4 @@ Keywords=Gnome;GTK;
|
|||
Icon=com.system76.CosmicAppList.svg
|
||||
StartupNotify=true
|
||||
NoDisplay=true
|
||||
HostWaylandDisplay=true
|
||||
|
|
@ -1,16 +1,13 @@
|
|||
use std::env;
|
||||
|
||||
// SPDX-License-Identifier: MPL-2.0-only
|
||||
use crate::dock_list::DockList;
|
||||
use crate::dock_list::DockListType;
|
||||
use crate::utils::Event;
|
||||
use cascade::cascade;
|
||||
use cosmic_panel_config::{PanelAnchor, CosmicPanelConfig};
|
||||
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;
|
||||
|
||||
mod imp;
|
||||
|
||||
|
|
@ -21,7 +18,7 @@ glib::wrapper! {
|
|||
}
|
||||
|
||||
impl AppsContainer {
|
||||
pub fn new(tx: Sender<Event>) -> Self {
|
||||
pub fn new() -> Self {
|
||||
let self_: Self = glib::Object::new(&[]).expect("Failed to create AppsContainer");
|
||||
let imp = imp::AppsContainer::from_instance(&self_);
|
||||
|
||||
|
|
@ -34,25 +31,25 @@ 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! {
|
||||
// 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, tx, config.clone());
|
||||
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| {
|
||||
// dbg!(c.orientation());
|
||||
|
|
@ -67,13 +64,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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -8,7 +8,6 @@ use gtk4::{
|
|||
prelude::*,
|
||||
subclass::prelude::*,
|
||||
};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
mod imp;
|
||||
|
||||
|
|
@ -20,7 +19,7 @@ glib::wrapper! {
|
|||
}
|
||||
|
||||
impl CosmicAppListWindow {
|
||||
pub fn new(app: >k4::Application, tx: mpsc::Sender<Event>) -> 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 +33,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();
|
||||
|
||||
|
|
@ -43,6 +42,13 @@ 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::<gtk4::Window>();
|
||||
let action_quit = gio::SimpleAction::new("quit", None);
|
||||
|
|
|
|||
36
applets/cosmic-app-list/src/config.rs
Normal file
36
applets/cosmic-app-list/src/config.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
use crate::ID;
|
||||
use anyhow::anyhow;
|
||||
use serde::Deserialize;
|
||||
use std::fmt::Debug;
|
||||
use std::fs::File;
|
||||
use xdg::BaseDirectories;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub enum TopLevelFilter {
|
||||
ActiveWorkspace,
|
||||
ConfiguredOutput,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
pub struct AppListConfig {
|
||||
pub filter_top_levels: Option<TopLevelFilter>,
|
||||
}
|
||||
|
||||
impl AppListConfig {
|
||||
/// load config with the provided name
|
||||
pub fn load() -> anyhow::Result<AppListConfig> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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::Event;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DockItem {
|
||||
|
|
@ -20,7 +18,6 @@ pub struct DockItem {
|
|||
pub item_box: Rc<RefCell<gtk4::Box>>,
|
||||
pub popover: Rc<RefCell<gtk4::Popover>>,
|
||||
pub popover_menu: Rc<RefCell<Option<DockPopover>>>,
|
||||
pub tx: OnceCell<Sender<Event>>,
|
||||
pub icon_size: Rc<Cell<u32>>,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
use crate::dock_object::DockObject;
|
||||
use crate::dock_popover::DockPopover;
|
||||
use crate::utils::AppListEvent;
|
||||
use crate::utils::BoxedWindowList;
|
||||
use crate::utils::Event;
|
||||
use cascade::cascade;
|
||||
use cosmic_panel_config::PanelAnchor;
|
||||
use gtk4::glib;
|
||||
|
|
@ -14,7 +14,6 @@ use gtk4::Image;
|
|||
use gtk4::Orientation;
|
||||
use gtk4::Popover;
|
||||
use gtk4::{Align, PositionType};
|
||||
use tokio::sync::mpsc::Sender;
|
||||
|
||||
mod imp;
|
||||
|
||||
|
|
@ -25,7 +24,7 @@ glib::wrapper! {
|
|||
}
|
||||
|
||||
impl DockItem {
|
||||
pub fn new(tx: Sender<Event>, 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);
|
||||
|
|
@ -50,7 +49,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! {
|
||||
|
|
@ -66,7 +65,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 +86,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_
|
||||
}
|
||||
|
||||
|
|
@ -112,6 +110,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);
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
@ -8,9 +8,8 @@ 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::Event;
|
||||
use crate::utils::AppListEvent;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DockList {
|
||||
|
|
@ -24,8 +23,7 @@ pub struct DockList {
|
|||
pub drag_cancel_signal: Rc<RefCell<Option<SignalHandlerId>>>,
|
||||
pub popover_menu_index: Rc<Cell<Option<u32>>>,
|
||||
pub position: Rc<Cell<PanelAnchor>>,
|
||||
pub tx: OnceCell<mpsc::Sender<Event>>,
|
||||
pub config: OnceCell<CosmicPanelConfig>
|
||||
pub config: OnceCell<CosmicPanelConfig>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
|
|
|||
|
|
@ -1,32 +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::{BoxedWindowList, Event, Item};
|
||||
use crate::{
|
||||
dock_item::DockItem,
|
||||
utils::{AppListEvent, BoxedWindowList, data_path},
|
||||
wayland::{Toplevel, ToplevelEvent},
|
||||
{TX, WAYLAND_TX}, dock_object::DockObject,
|
||||
};
|
||||
use cascade::cascade;
|
||||
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 cosmic_panel_config::{CosmicPanelConfig, PanelAnchor};
|
||||
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;
|
||||
|
||||
|
|
@ -49,11 +43,10 @@ impl Default for DockListType {
|
|||
}
|
||||
|
||||
impl DockList {
|
||||
pub fn new(type_: DockListType, tx: Sender<Event>, 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
|
||||
|
|
@ -84,6 +77,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<String>>(file) {
|
||||
// dbg!(&data);
|
||||
|
|
@ -219,7 +213,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,14 +231,14 @@ 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;
|
||||
});
|
||||
// TODO use seat eventually
|
||||
// let wl_seat = self_.device().map(|d| d.seat().downcast::<WaylandSeat>().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) {
|
||||
|
|
@ -322,7 +315,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 +380,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 +409,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 +419,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 +499,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) {
|
||||
|
|
|
|||
|
|
@ -78,9 +78,9 @@ impl DockObject {
|
|||
imp.saved.replace(is_saved);
|
||||
}
|
||||
|
||||
pub fn from_search_results(results: BoxedWindowList) -> Self {
|
||||
let appinfo = if let Some(first) = results.0.get(0) {
|
||||
xdg::BaseDirectories::new()
|
||||
pub fn from_window_list(results: BoxedWindowList) -> Option<Self> {
|
||||
if let Some(first) = results.0.get(0) {
|
||||
return xdg::BaseDirectories::new()
|
||||
.expect("could not access XDG Base directory")
|
||||
.get_data_dirs()
|
||||
.iter_mut()
|
||||
|
|
@ -95,9 +95,23 @@ 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()
|
||||
&& 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);
|
||||
return Some(
|
||||
Object::new(&[
|
||||
("appinfo", &app_info),
|
||||
("active", &results),
|
||||
])
|
||||
.expect("Failed to create `DockObject`."),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -105,13 +119,9 @@ impl DockObject {
|
|||
}
|
||||
None
|
||||
})
|
||||
.next()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// dbg!(&appinfo);
|
||||
Object::new(&[("appinfo", &appinfo), ("active", &results)])
|
||||
.expect("Failed to create `DockObject`.")
|
||||
.next();
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn set_popover(&self, b: bool) {
|
||||
|
|
|
|||
|
|
@ -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::Event;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DockPopover {
|
||||
|
|
@ -26,7 +23,6 @@ pub struct DockPopover {
|
|||
pub quit_all_item: Rc<RefCell<Button>>,
|
||||
//TODO figure out how to use lifetimes with glib::wrapper! macro
|
||||
pub dock_object: Rc<RefCell<Option<DockObject>>>,
|
||||
pub tx: OnceCell<Sender<Event>>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
|
|
|||
|
|
@ -7,11 +7,12 @@ 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;
|
||||
use crate::utils::BoxedWindowList;
|
||||
use crate::utils::Event;
|
||||
use crate::wayland::ToplevelEvent;
|
||||
use crate::{TX, WAYLAND_TX};
|
||||
|
||||
mod imp;
|
||||
|
||||
|
|
@ -22,10 +23,9 @@ glib::wrapper! {
|
|||
}
|
||||
|
||||
impl DockPopover {
|
||||
pub fn new(tx: Sender<Event>) -> 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_
|
||||
|
|
@ -85,12 +85,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);
|
||||
|
|
@ -192,30 +191,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::<BoxedWindowList>("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::<bool>("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 +220,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::<BoxedWindowList>("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();
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// 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};
|
||||
|
|
@ -8,22 +9,24 @@ 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, BoxedWindowList, Event, Item, 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;
|
||||
mod dock_popover;
|
||||
mod localize;
|
||||
mod utils;
|
||||
mod wayland;
|
||||
mod wayland_source;
|
||||
|
||||
const ID: &str = "com.system76.CosmicAppList";
|
||||
static TX: OnceCell<mpsc::Sender<Event>> = OnceCell::new();
|
||||
static TX: OnceCell<glib::Sender<AppListEvent>> = OnceCell::new();
|
||||
static WAYLAND_TX: OnceCell<SyncSender<ToplevelEvent>> = OnceCell::new();
|
||||
|
||||
pub fn localize() {
|
||||
let localizer = crate::localize::localizer();
|
||||
|
|
@ -57,202 +60,222 @@ 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 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<u32> = None;
|
||||
while let Some(item) = active_app_model.item(cur) {
|
||||
if let Ok(cur_dock_object) = item.downcast::<DockObject>() {
|
||||
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| {
|
||||
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);
|
||||
let active_app_model = apps_container.model(DockListType::Active);
|
||||
if should_favorite {
|
||||
let mut cur: u32 = 0;
|
||||
let mut index: Option<u32> = None;
|
||||
while let Some(item) = active_app_model.item(cur) {
|
||||
if let Ok(cur_dock_object) = item.downcast::<DockObject>() {
|
||||
if cur_dock_object.get_name() == 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<u32> = None;
|
||||
while let Some(item) = saved_app_model.item(cur) {
|
||||
if let Ok(cur_dock_object) = item.downcast::<DockObject>() {
|
||||
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<String, BoxedWindowList>, 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()]),
|
||||
);
|
||||
}
|
||||
acc
|
||||
},
|
||||
);
|
||||
let mut stack_active: Vec<BoxedWindowList> =
|
||||
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::<DockObject>() {
|
||||
if let Some(cur_app_info) =
|
||||
dock_obj.property::<Option<DesktopAppInfo>>("appinfo")
|
||||
{
|
||||
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);
|
||||
}
|
||||
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<u32> = None;
|
||||
while let Some(item) = saved_app_model.item(cur) {
|
||||
if let Ok(cur_dock_object) = item.downcast::<DockObject>() {
|
||||
if cur_dock_object.get_name() == Some(name.clone()) {
|
||||
cur_dock_object.set_saved(false);
|
||||
index = Some(cur);
|
||||
}
|
||||
}
|
||||
saved_i += 1;
|
||||
cur += 1;
|
||||
}
|
||||
|
||||
let active_app_model = apps_container.model(DockListType::Active);
|
||||
let model_len = active_app_model.n_items();
|
||||
let new_results: Vec<glib::Object> = 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<String, BoxedWindowList>, 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<BoxedWindowList> =
|
||||
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::<DockObject>() {
|
||||
if let Some(cur_app_info) =
|
||||
dock_obj.property::<Option<DesktopAppInfo>>("appinfo")
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
saved_i += 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 active_app_model = apps_container.model(DockListType::Active);
|
||||
let model_len = active_app_model.n_items();
|
||||
let new_results: Vec<glib::Object> = stack_active
|
||||
.into_iter()
|
||||
.map(|v| DockObject::from_search_results(v).upcast())
|
||||
.collect();
|
||||
active_app_model.splice(0, model_len, &new_results[..]);
|
||||
}
|
||||
let _ = tx.send(AppListEvent::Refresh);
|
||||
false
|
||||
}
|
||||
AppListEvent::Refresh => {
|
||||
// println!("refreshing model from cache");
|
||||
let stack_active = cached_results.iter().fold(
|
||||
BTreeMap::new(),
|
||||
|mut acc: BTreeMap<String, BoxedWindowList>, elem: &Toplevel| {
|
||||
if let Some(v) = acc.get_mut(&elem.app_id) {
|
||||
v.0.push(elem.clone());
|
||||
} else {
|
||||
acc.insert(
|
||||
elem.app_id.clone(),
|
||||
BoxedWindowList(vec![elem.clone()]),
|
||||
);
|
||||
}
|
||||
acc
|
||||
},
|
||||
);
|
||||
let mut stack_active: Vec<BoxedWindowList> =
|
||||
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::<DockObject>() {
|
||||
if let Some(cur_app_info) =
|
||||
dock_obj.property::<Option<DesktopAppInfo>>("appinfo")
|
||||
{
|
||||
if let Some((i, _s)) = stack_active
|
||||
.iter()
|
||||
.enumerate()
|
||||
.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);
|
||||
dock_obj.set_property("active", active.to_value());
|
||||
saved_app_model.items_changed(saved_i, 0, 0);
|
||||
} else if cached_results
|
||||
.iter()
|
||||
.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",
|
||||
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<glib::Object> = stack_active
|
||||
.into_iter()
|
||||
.filter_map(|v| DockObject::from_window_list(v).map(|o| o.upcast()))
|
||||
.collect();
|
||||
active_app_model.splice(0, model_len, &new_results[..]);
|
||||
true
|
||||
}
|
||||
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
|
||||
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
|
||||
}
|
||||
};
|
||||
if should_apply_changes {
|
||||
// dbg!(&cached_results);
|
||||
// build active app stacks for each app
|
||||
let stack_active = cached_results.iter().fold(
|
||||
BTreeMap::new(),
|
||||
|mut acc: BTreeMap<String, BoxedWindowList>, elem| {
|
||||
if let Some(v) = acc.get_mut(&elem.app_id) {
|
||||
v.0.push(elem.clone());
|
||||
} else {
|
||||
acc.insert(
|
||||
elem.app_id.clone(),
|
||||
BoxedWindowList(vec![elem.clone()]),
|
||||
);
|
||||
}
|
||||
acc
|
||||
},
|
||||
);
|
||||
let mut stack_active: Vec<BoxedWindowList> =
|
||||
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::<DockObject>() {
|
||||
if let Some(cur_app_info) =
|
||||
dock_obj.property::<Option<DesktopAppInfo>>("appinfo")
|
||||
{
|
||||
if let Some((i, _s)) = stack_active
|
||||
.iter()
|
||||
.enumerate()
|
||||
.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);
|
||||
dock_obj.set_property("active", active.to_value());
|
||||
saved_app_model.items_changed(saved_i, 0, 0);
|
||||
} else if cached_results
|
||||
.iter()
|
||||
.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",
|
||||
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<glib::Object> = stack_active
|
||||
.into_iter()
|
||||
.filter_map(|v| DockObject::from_window_list(v).map(|o| o.upcast()))
|
||||
.collect();
|
||||
|
||||
active_app_model.splice(0, model_len, &new_results[..]);
|
||||
}
|
||||
});
|
||||
glib::prelude::Continue(true)
|
||||
}));
|
||||
|
||||
window.show();
|
||||
});
|
||||
app.run();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -57,6 +61,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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
Activate((u32, u32)),
|
||||
Close((u32, u32)),
|
||||
pub enum AppListEvent {
|
||||
WindowList(Vec<Toplevel>),
|
||||
Add(Toplevel),
|
||||
Remove(Toplevel),
|
||||
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<Item>);
|
||||
pub struct BoxedWindowList(pub Vec<Toplevel>);
|
||||
|
||||
pub fn data_path() -> PathBuf {
|
||||
let mut path = glib::user_data_dir();
|
||||
|
|
|
|||
599
applets/cosmic-app-list/src/wayland.rs
Normal file
599
applets/cosmic-app-list/src/wayland.rs
Normal file
|
|
@ -0,0 +1,599 @@
|
|||
use crate::config::AppListConfig;
|
||||
use crate::{config::TopLevelFilter, utils::AppListEvent, wayland_source::WaylandSource, TX};
|
||||
use calloop::channel::*;
|
||||
use cosmic_panel_config::CosmicPanelConfig;
|
||||
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 wayland_client::protocol::wl_seat::{WlSeat, self};
|
||||
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 wayland_client::{Connection, Dispatch, QueueHandle};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ToplevelEvent {
|
||||
Activate(ZcosmicToplevelHandleV1),
|
||||
Close(ZcosmicToplevelHandleV1),
|
||||
}
|
||||
|
||||
pub fn spawn_toplevels() -> SyncSender<ToplevelEvent> {
|
||||
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::<PathBuf>::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::<State>::try_new().unwrap();
|
||||
let loop_handle = event_loop.handle();
|
||||
let event_queue = conn.new_event_queue::<State>();
|
||||
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![],
|
||||
seats: vec![],
|
||||
};
|
||||
let loop_handle = event_loop.handle();
|
||||
loop_handle
|
||||
.insert_source(workspaces_rx, |e, _, state| match e {
|
||||
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)) => {
|
||||
if let Some(manager) = &state.toplevel_manager {
|
||||
manager.close(&t);
|
||||
}
|
||||
}
|
||||
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<String>,
|
||||
expected_output: Option<WlOutput>,
|
||||
workspace_manager: Option<ZcosmicWorkspaceManagerV1>,
|
||||
workspace_groups: Vec<WorkspaceGroup>,
|
||||
toplevel_info: Option<ZcosmicToplevelInfoV1>,
|
||||
toplevel_manager: Option<ZcosmicToplevelManagerV1>,
|
||||
toplevels: Vec<Toplevel>,
|
||||
seats: Vec<WlSeat>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn workspace_list(&self) -> impl Iterator<Item = (String, u32)> + '_ {
|
||||
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<zcosmic_toplevel_handle_v1::State>,
|
||||
pub output: Option<WlOutput>,
|
||||
pub workspace: Option<ZcosmicWorkspaceHandleV1>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct WorkspaceGroup {
|
||||
workspace_group_handle: ZcosmicWorkspaceGroupHandleV1,
|
||||
output: Option<WlOutput>,
|
||||
workspaces: Vec<Workspace>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Workspace {
|
||||
workspace_handle: ZcosmicWorkspaceHandleV1,
|
||||
name: String,
|
||||
coordinates: Vec<u32>,
|
||||
states: Vec<zcosmic_workspace_handle_v1::State>,
|
||||
}
|
||||
|
||||
impl Dispatch<wl_registry::WlRegistry, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
registry: &wl_registry::WlRegistry,
|
||||
event: wl_registry::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
qh: &QueueHandle<Self>,
|
||||
) {
|
||||
if let wl_registry::Event::Global {
|
||||
name,
|
||||
interface,
|
||||
version,
|
||||
} = event
|
||||
{
|
||||
match &interface[..] {
|
||||
"zcosmic_toplevel_info_v1" => {
|
||||
let ti = registry
|
||||
.bind::<ZcosmicToplevelInfoV1, _, _>(name, 1, qh, ())
|
||||
.unwrap();
|
||||
state.toplevel_info = Some(ti);
|
||||
}
|
||||
"zcosmic_toplevel_manager_v1" => {
|
||||
let tm = registry
|
||||
.bind::<ZcosmicToplevelManagerV1, _, _>(name, 1, qh, ())
|
||||
.unwrap();
|
||||
state.toplevel_manager = Some(tm);
|
||||
}
|
||||
"zcosmic_workspace_manager_v1" => {
|
||||
let workspace_manager = registry
|
||||
.bind::<ZcosmicWorkspaceManagerV1, _, _>(name, 1, qh, ())
|
||||
.unwrap();
|
||||
state.workspace_manager = Some(workspace_manager);
|
||||
}
|
||||
"wl_seat" => {
|
||||
registry.bind::<WlSeat, _, _>(name, 1, qh, ()).unwrap();
|
||||
}
|
||||
"wl_output" => {
|
||||
registry.bind::<WlOutput, _, _>(name, 1, qh, ()).unwrap();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZcosmicToplevelInfoV1, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_: &ZcosmicToplevelInfoV1,
|
||||
event: <ZcosmicToplevelInfoV1 as Proxy>::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
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 => {
|
||||
todo!()
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
event_created_child!(State, ZcosmicWorkspaceManagerV1, [
|
||||
0 => (ZcosmicToplevelHandleV1, ())
|
||||
]);
|
||||
}
|
||||
|
||||
impl Dispatch<ZcosmicToplevelManagerV1, ()> for State {
|
||||
fn event(
|
||||
_: &mut Self,
|
||||
_: &ZcosmicToplevelManagerV1,
|
||||
event: <ZcosmicToplevelManagerV1 as Proxy>::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
match event {
|
||||
zcosmic_toplevel_manager_v1::Event::Capabilities { .. } => {
|
||||
// TODO capabilities affect what is shown to user in applet
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZcosmicToplevelHandleV1, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
p: &ZcosmicToplevelHandleV1,
|
||||
event: <ZcosmicToplevelHandleV1 as Proxy>::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
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 => {
|
||||
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;
|
||||
}
|
||||
}
|
||||
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<ZcosmicWorkspaceManagerV1, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_: &ZcosmicWorkspaceManagerV1,
|
||||
event: zcosmic_workspace_manager_v1::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
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<ZcosmicWorkspaceGroupHandleV1, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
group: &ZcosmicWorkspaceGroupHandleV1,
|
||||
event: zcosmic_workspace_group_handle_v1::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
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<ZcosmicWorkspaceHandleV1, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
workspace: &ZcosmicWorkspaceHandleV1,
|
||||
event: zcosmic_workspace_handle_v1::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
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();
|
||||
// TODO if workspace active status changes while configured to only show active workspace, clear the list
|
||||
}
|
||||
}
|
||||
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<WlOutput, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
o: &WlOutput,
|
||||
e: wl_output::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
match e {
|
||||
wl_output::Event::Name { name } if Some(&name) == state.configured_output.as_ref() => {
|
||||
state.expected_output.replace(o.clone());
|
||||
}
|
||||
_ => {} // ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Dispatch<WlSeat, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
seat: &WlSeat,
|
||||
_: wl_seat::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
if state.seats.iter().find(|s| s == &seat).is_none() {
|
||||
state.seats.push(seat.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
219
applets/cosmic-app-list/src/wayland_source.rs
Normal file
219
applets/cosmic-app-list/src/wayland_source.rs
Normal file
|
|
@ -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<D> {
|
||||
queue: EventQueue<D>,
|
||||
fd: Generic<RawFd>,
|
||||
read_guard: Option<ReadEventsGuard>,
|
||||
}
|
||||
|
||||
impl<D> WaylandSource<D> {
|
||||
/// Wrap an [`EventQueue`] as a [`WaylandSource`].
|
||||
pub fn new(queue: EventQueue<D>) -> Result<WaylandSource<D>, 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<D> {
|
||||
&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<D>) -> Result<RegistrationToken, InsertError<Self>>
|
||||
where
|
||||
D: 'static,
|
||||
{
|
||||
handle.insert_source(self, |_, queue, data| queue.dispatch_pending(data))
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> EventSource for WaylandSource<D> {
|
||||
type Event = ();
|
||||
|
||||
/// The underlying event queue.
|
||||
///
|
||||
/// You should call [`EventQueue::dispatch_pending`] inside your callback using this queue.
|
||||
type Metadata = EventQueue<D>;
|
||||
type Ret = Result<usize, DispatchError>;
|
||||
type Error = calloop::Error;
|
||||
|
||||
fn process_events<F>(
|
||||
&mut self,
|
||||
readiness: Readiness,
|
||||
token: Token,
|
||||
mut callback: F,
|
||||
) -> Result<PostAction, Self::Error>
|
||||
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<F>(&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<F>(&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<D> WaylandSource<D> {
|
||||
/// Loop over the callback until all pending messages have been dispatched.
|
||||
fn loop_callback_pending<F>(queue: &mut EventQueue<D>, callback: &mut F) -> io::Result<()>
|
||||
where
|
||||
F: FnMut((), &mut EventQueue<D>) -> Result<usize, DispatchError>,
|
||||
{
|
||||
// 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<D>) -> io::Result<ReadEventsGuard> {
|
||||
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()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -1,306 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="ext_workspace_unstable_v1">
|
||||
<copyright>
|
||||
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.
|
||||
</copyright>
|
||||
|
||||
<interface name="zext_workspace_manager_v1" version="1">
|
||||
<description summary="list and control workspaces">
|
||||
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.
|
||||
</description>
|
||||
|
||||
<event name="workspace_group">
|
||||
<description summary="a workspace group has been created">
|
||||
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.
|
||||
</description>
|
||||
<arg name="workspace_group" type="new_id" interface="zext_workspace_group_handle_v1"/>
|
||||
</event>
|
||||
|
||||
<request name="commit">
|
||||
<description summary="all requests about the workspaces have been sent">
|
||||
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.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="done">
|
||||
<description summary="all information about the workspace groups has been sent">
|
||||
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.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<event name="finished">
|
||||
<description summary="the compositor has finished with the workspace_manager">
|
||||
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.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<request name="stop">
|
||||
<description summary="stop sending events">
|
||||
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.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zext_workspace_group_handle_v1" version="1">
|
||||
<description summary="a workspace group assigned to a set of outputs">
|
||||
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.
|
||||
</description>
|
||||
|
||||
<event name="output_enter">
|
||||
<description summary="output assigned to workspace group">
|
||||
This event is emitted whenever an output is assigned to the workspace
|
||||
group.
|
||||
</description>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
</event>
|
||||
|
||||
<event name="output_leave">
|
||||
<description summary="output removed from workspace group">
|
||||
This event is emitted whenever an output is removed from the workspace
|
||||
group.
|
||||
</description>
|
||||
<arg name="output" type="object" interface="wl_output"/>
|
||||
</event>
|
||||
|
||||
<event name="workspace">
|
||||
<description summary="workspace added to 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.
|
||||
</description>
|
||||
<arg name="workspace" type="new_id" interface="zext_workspace_handle_v1"/>
|
||||
</event>
|
||||
|
||||
<event name="remove">
|
||||
<description summary="this workspace group has been destroyed">
|
||||
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.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<request name="create_workspace">
|
||||
<description summary="create a new workspace">
|
||||
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.
|
||||
</description>
|
||||
<arg name="workspace" type="string"/>
|
||||
</request>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the zext_workspace_handle_v1 object">
|
||||
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.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zext_workspace_handle_v1" version="1">
|
||||
<description summary="a workspace handing a group of surfaces">
|
||||
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).
|
||||
</description>
|
||||
|
||||
<event name="name">
|
||||
<description summary="workspace name changed">
|
||||
This event is emitted immediately after the zext_workspace_handle_v1 is
|
||||
created and whenever the name of the workspace changes.
|
||||
</description>
|
||||
<arg name="name" type="string"/>
|
||||
</event>
|
||||
|
||||
<event name="coordinates">
|
||||
<description summary="workspace coordinates changed">
|
||||
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.
|
||||
</description>
|
||||
<arg name="coordinates" type="array"/>
|
||||
</event>
|
||||
|
||||
<event name="state">
|
||||
<description summary="the state of the workspace changed">
|
||||
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.
|
||||
</description>
|
||||
<arg name="state" type="array"/>
|
||||
</event>
|
||||
|
||||
<enum name="state">
|
||||
<description summary="types of states on the workspace">
|
||||
The different states that a workspace can have.
|
||||
</description>
|
||||
|
||||
<entry name="active" value="0" summary="the workspace is active"/>
|
||||
<entry name="urgent" value="1" summary="the workspace requests attention"/>
|
||||
<entry name="hidden" value="2">
|
||||
<description summary="the workspace is not visible">
|
||||
The workspace is not visible in its workspace group, and clients
|
||||
attempting to visualize the compositor workspace state should not
|
||||
display such workspaces.
|
||||
</description>
|
||||
</entry>
|
||||
</enum>
|
||||
|
||||
<event name="remove">
|
||||
<description summary="this workspace has been destroyed">
|
||||
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.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the zext_workspace_handle_v1 object">
|
||||
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.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="activate">
|
||||
<description summary="activate the workspace">
|
||||
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.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="deactivate">
|
||||
<description summary="activate the workspace">
|
||||
Request that this workspace be deactivated.
|
||||
|
||||
There is no guarantee the workspace will be actually deactivated.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="remove">
|
||||
<description summary="remove the workspace">
|
||||
Request that this workspace be removed.
|
||||
|
||||
There is no guarantee the workspace will be actually removed.
|
||||
</description>
|
||||
</request>
|
||||
</interface>
|
||||
</protocol>
|
||||
|
|
@ -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,18 +24,12 @@ 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<State>) -> SyncSender<WorkspaceEvent> {
|
||||
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")
|
||||
|
|
@ -80,19 +80,18 @@ pub fn spawn_workspaces(tx: glib::Sender<State>) -> SyncSender<WorkspaceEvent> {
|
|||
}
|
||||
}
|
||||
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 {
|
||||
|
|
@ -130,7 +129,7 @@ pub fn spawn_workspaces(tx: glib::Sender<State>) -> SyncSender<WorkspaceEvent> {
|
|||
}
|
||||
});
|
||||
} else {
|
||||
eprintln!("ENV variable HOST_WAYLAND_DISPLAY is missing. Exiting...");
|
||||
eprintln!("ENV variable WAYLAND_DISPLAY is missing. Exiting...");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -185,7 +189,7 @@ struct Workspace {
|
|||
|
||||
impl Dispatch<wl_registry::WlRegistry, ()> for State {
|
||||
fn event(
|
||||
&mut self,
|
||||
state: &mut Self,
|
||||
registry: &wl_registry::WlRegistry,
|
||||
event: wl_registry::Event,
|
||||
_: &(),
|
||||
|
|
@ -201,14 +205,9 @@ impl Dispatch<wl_registry::WlRegistry, ()> for State {
|
|||
match &interface[..] {
|
||||
"zcosmic_workspace_manager_v1" => {
|
||||
let workspace_manager = registry
|
||||
.bind::<ZcosmicWorkspaceManagerV1, _, _>(
|
||||
name,
|
||||
1,
|
||||
qh,
|
||||
(),
|
||||
)
|
||||
.bind::<ZcosmicWorkspaceManagerV1, _, _>(name, 1, qh, ())
|
||||
.unwrap();
|
||||
self.workspace_manager = Some(workspace_manager);
|
||||
state.workspace_manager = Some(workspace_manager);
|
||||
}
|
||||
"wl_output" => {
|
||||
registry.bind::<WlOutput, _, _>(name, 1, qh, ()).unwrap();
|
||||
|
|
@ -221,7 +220,7 @@ impl Dispatch<wl_registry::WlRegistry, ()> for State {
|
|||
|
||||
impl Dispatch<ZcosmicWorkspaceManagerV1, ()> for State {
|
||||
fn event(
|
||||
&mut self,
|
||||
state: &mut Self,
|
||||
_: &ZcosmicWorkspaceManagerV1,
|
||||
event: zcosmic_workspace_manager_v1::Event,
|
||||
_: &(),
|
||||
|
|
@ -230,26 +229,28 @@ impl Dispatch<ZcosmicWorkspaceManagerV1, ()> 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())
|
||||
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)
|
||||
});
|
||||
}
|
||||
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 +263,7 @@ impl Dispatch<ZcosmicWorkspaceManagerV1, ()> for State {
|
|||
|
||||
impl Dispatch<ZcosmicWorkspaceGroupHandleV1, ()> for State {
|
||||
fn event(
|
||||
&mut self,
|
||||
state: &mut Self,
|
||||
group: &ZcosmicWorkspaceGroupHandleV1,
|
||||
event: zcosmic_workspace_group_handle_v1::Event,
|
||||
_: &(),
|
||||
|
|
@ -271,7 +272,7 @@ impl Dispatch<ZcosmicWorkspaceGroupHandleV1, ()> 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 +281,14 @@ impl Dispatch<ZcosmicWorkspaceGroupHandleV1, ()> 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 +302,12 @@ impl Dispatch<ZcosmicWorkspaceGroupHandleV1, ()> 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 +321,7 @@ impl Dispatch<ZcosmicWorkspaceGroupHandleV1, ()> for State {
|
|||
|
||||
impl Dispatch<ZcosmicWorkspaceHandleV1, ()> for State {
|
||||
fn event(
|
||||
&mut self,
|
||||
state: &mut Self,
|
||||
workspace: &ZcosmicWorkspaceHandleV1,
|
||||
event: zcosmic_workspace_handle_v1::Event,
|
||||
_: &(),
|
||||
|
|
@ -329,7 +330,7 @@ impl Dispatch<ZcosmicWorkspaceHandleV1, ()> 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,27 +339,40 @@ impl Dispatch<ZcosmicWorkspaceHandleV1, ()> 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)
|
||||
}) {
|
||||
// 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 } => {
|
||||
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 +388,7 @@ impl Dispatch<ZcosmicWorkspaceHandleV1, ()> for State {
|
|||
|
||||
impl Dispatch<WlOutput, ()> for State {
|
||||
fn event(
|
||||
&mut self,
|
||||
state: &mut Self,
|
||||
o: &WlOutput,
|
||||
e: wl_output::Event,
|
||||
_: &(),
|
||||
|
|
@ -382,8 +396,8 @@ impl Dispatch<WlOutput, ()> for State {
|
|||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue