From f50ed504b103b73c2a6ca4c045293a86eb7530ef Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 3 Jun 2022 19:36:05 -0700 Subject: [PATCH] Move status area code into an applet --- Cargo.lock | 19 +++- Cargo.toml | 2 +- applets/cosmic-applet-status-area/Cargo.toml | 16 ++++ ...om.system76.CosmicAppletStatusArea.desktop | 10 +++ .../com.system76.CosmicAppletStatusArea.svg | 60 +++++++++++++ .../src/dbus_service.rs | 53 +++++++++++ .../src/deref_cell.rs | 31 +++++++ applets/cosmic-applet-status-area/src/main.rs | 30 +++++++ .../src/popover_container.rs | 89 +++++++++++++++++++ .../src/status_area.rs | 0 .../src/status_menu.rs | 0 .../src/status_notifier_watcher.rs | 0 justfile | 6 ++ old-panel/src/application.rs | 2 - old-panel/src/main.rs | 3 - old-panel/src/window.rs | 3 - 16 files changed, 313 insertions(+), 11 deletions(-) create mode 100644 applets/cosmic-applet-status-area/Cargo.toml create mode 100644 applets/cosmic-applet-status-area/data/com.system76.CosmicAppletStatusArea.desktop create mode 100644 applets/cosmic-applet-status-area/data/icons/com.system76.CosmicAppletStatusArea.svg create mode 100644 applets/cosmic-applet-status-area/src/dbus_service.rs create mode 100644 applets/cosmic-applet-status-area/src/deref_cell.rs create mode 100644 applets/cosmic-applet-status-area/src/main.rs create mode 100644 applets/cosmic-applet-status-area/src/popover_container.rs rename {old-panel => applets/cosmic-applet-status-area}/src/status_area.rs (100%) rename {old-panel => applets/cosmic-applet-status-area}/src/status_menu.rs (100%) rename {old-panel => applets/cosmic-applet-status-area}/src/status_notifier_watcher.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 4022670e..1bb893cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -318,6 +318,21 @@ dependencies = [ "zbus", ] +[[package]] +name = "cosmic-applet-status-area" +version = "0.1.0" +dependencies = [ + "cascade", + "cosmic-panel-config", + "futures", + "gtk4", + "once_cell", + "serde", + "zbus", + "zbus_names", + "zvariant", +] + [[package]] name = "cosmic-dbus-networkmanager" version = "0.1.0" @@ -1499,9 +1514,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.10.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" [[package]] name = "ordered-stream" diff --git a/Cargo.toml b/Cargo.toml index 4be26bd7..f67e1075 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,2 @@ [workspace] -members = ["applets/cosmic-applet-audio", "applets/cosmic-applet-graphics", "applets/cosmic-applet-network", "applets/cosmic-applet-power", "old-panel"] +members = ["applets/cosmic-applet-audio", "applets/cosmic-applet-graphics", "applets/cosmic-applet-network", "applets/cosmic-applet-power", "applets/cosmic-applet-status-area", "old-panel"] diff --git a/applets/cosmic-applet-status-area/Cargo.toml b/applets/cosmic-applet-status-area/Cargo.toml new file mode 100644 index 00000000..5dada3c7 --- /dev/null +++ b/applets/cosmic-applet-status-area/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "cosmic-applet-status-area" +version = "0.1.0" +edition = "2021" +license = "GPL-3.0-or-later" + +[dependencies] +cascade = "1" +futures = "0.3" +gtk4 = "0.4.6" +once_cell = "1.12" +serde = "1" +zbus = "2.0.1" +zbus_names = "2" +zvariant = "3" +cosmic-panel-config = {git = "https://github.com/pop-os/cosmic-panel", features = ["gtk4"]} diff --git a/applets/cosmic-applet-status-area/data/com.system76.CosmicAppletStatusArea.desktop b/applets/cosmic-applet-status-area/data/com.system76.CosmicAppletStatusArea.desktop new file mode 100644 index 00000000..c60bc271 --- /dev/null +++ b/applets/cosmic-applet-status-area/data/com.system76.CosmicAppletStatusArea.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Name=Cosmic Applet Status Area +Type=Application +Exec=cosmic-applet-status-area +Terminal=false +Categories=GNOME;GTK; +Keywords=Gnome;GTK; +# Translators: Do NOT translate or transliterate this text (this is an icon file name)! +Icon=com.system76.CosmicAppletStatusArea +NoDisplay=true diff --git a/applets/cosmic-applet-status-area/data/icons/com.system76.CosmicAppletStatusArea.svg b/applets/cosmic-applet-status-area/data/icons/com.system76.CosmicAppletStatusArea.svg new file mode 100644 index 00000000..c2bd5b1b --- /dev/null +++ b/applets/cosmic-applet-status-area/data/icons/com.system76.CosmicAppletStatusArea.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/applets/cosmic-applet-status-area/src/dbus_service.rs b/applets/cosmic-applet-status-area/src/dbus_service.rs new file mode 100644 index 00000000..3c00b5bc --- /dev/null +++ b/applets/cosmic-applet-status-area/src/dbus_service.rs @@ -0,0 +1,53 @@ +use futures::prelude::*; +use gtk4::glib::{self, clone}; +use std::cell::Cell; +use zbus::fdo::{DBusProxy, RequestNameFlags, RequestNameReply}; +use zbus_names::WellKnownName; + +pub async fn create< + F: Fn(zbus::ConnectionBuilder<'static>) -> zbus::Result>, +>( + well_known_name: &'static str, + serve_cb: F, +) -> zbus::Result { + let well_known_name = WellKnownName::try_from(well_known_name)?; + + let connection = serve_cb(zbus::ConnectionBuilder::session()?)? + .build() + .await?; + let dbus_proxy = DBusProxy::new(&connection).await?; + let mut name_owner_changed_stream = dbus_proxy.receive_name_owner_changed().await?; + + let flags = RequestNameFlags::AllowReplacement.into(); + match dbus_proxy + .request_name(well_known_name.as_ref(), flags) + .await? + { + RequestNameReply::InQueue => { + eprintln!("Bus name '{}' already owned", well_known_name); + } + _ => {} + } + + glib::MainContext::default().spawn_local(clone!(@strong connection => async move { + let have_bus_name = Cell::new(false); + let unique_name = connection.unique_name().map(|x| x.as_ref()); + while let Some(evt) = name_owner_changed_stream.next().await { + let args = match evt.args() { + Ok(args) => args, + Err(_) => { continue; }, + }; + if args.name.as_ref() == well_known_name { + if args.new_owner.as_ref() == unique_name.as_ref() { + eprintln!("Acquired bus name: {}", well_known_name); + have_bus_name.set(true); + } else if have_bus_name.get() { + eprintln!("Lost bus name: {}", well_known_name); + have_bus_name.set(false); + } + } + } + })); + + Ok(connection) +} diff --git a/applets/cosmic-applet-status-area/src/deref_cell.rs b/applets/cosmic-applet-status-area/src/deref_cell.rs new file mode 100644 index 00000000..dfdd4936 --- /dev/null +++ b/applets/cosmic-applet-status-area/src/deref_cell.rs @@ -0,0 +1,31 @@ +use once_cell::unsync::OnceCell; + +/// Wrapper around `OnceCell` implementing `Deref`, and thus also panicking +/// when not set (or set twice). +/// +/// To be used in place of `gtk::TemplateChild`, but without xml. +pub struct DerefCell(OnceCell); + +impl DerefCell { + #[track_caller] + pub fn set(&self, value: T) { + if self.0.set(value).is_err() { + panic!("Initialized twice"); + } + } +} + +impl Default for DerefCell { + fn default() -> Self { + Self(OnceCell::default()) + } +} + +impl std::ops::Deref for DerefCell { + type Target = T; + + #[track_caller] + fn deref(&self) -> &T { + self.0.get().unwrap() + } +} diff --git a/applets/cosmic-applet-status-area/src/main.rs b/applets/cosmic-applet-status-area/src/main.rs new file mode 100644 index 00000000..e9d763bd --- /dev/null +++ b/applets/cosmic-applet-status-area/src/main.rs @@ -0,0 +1,30 @@ +use gtk4::{glib, prelude::*}; + +mod dbus_service; +mod deref_cell; +mod popover_container; +mod status_area; +mod status_menu; +mod status_notifier_watcher; + +use status_area::StatusArea; + +fn main() { + gtk4::init().unwrap(); + + // XXX Implement DBus service somewhere other than applet? + glib::MainContext::default().spawn_local(status_notifier_watcher::start()); + + let status_area = StatusArea::new(); + gtk4::Window::builder() + .decorated(false) + .child(&status_area) + .resizable(false) + .width_request(1) + .height_request(1) + .build() + .show(); + + let main_loop = glib::MainLoop::new(None, false); + main_loop.run(); +} diff --git a/applets/cosmic-applet-status-area/src/popover_container.rs b/applets/cosmic-applet-status-area/src/popover_container.rs new file mode 100644 index 00000000..2e551294 --- /dev/null +++ b/applets/cosmic-applet-status-area/src/popover_container.rs @@ -0,0 +1,89 @@ +use cascade::cascade; +use gtk4::{glib, prelude::*, subclass::prelude::*}; + +use crate::deref_cell::DerefCell; + +/// Unlike gtk4's `MenuButton`, this supports a custom child. +#[derive(Default)] +pub struct PopoverContainerInner { + child: DerefCell, + popover: DerefCell, +} + +#[glib::object_subclass] +impl ObjectSubclass for PopoverContainerInner { + const NAME: &'static str = "S76PopoverContainer"; + type ParentType = gtk4::Widget; + type Type = PopoverContainer; +} + +impl ObjectImpl for PopoverContainerInner { + fn constructed(&self, obj: &PopoverContainer) { + let popover = cascade! { + gtk4::Popover::new(); + ..set_parent(obj); + }; + + self.popover.set(popover); + } + + fn dispose(&self, _obj: &PopoverContainer) { + self.child.unparent(); + self.popover.unparent(); + } +} + +impl WidgetImpl for PopoverContainerInner { + fn measure( + &self, + _obj: &PopoverContainer, + orientation: gtk4::Orientation, + for_size: i32, + ) -> (i32, i32, i32, i32) { + self.child.measure(orientation, for_size) + } + + fn size_allocate(&self, _obj: &PopoverContainer, width: i32, height: i32, baseline: i32) { + self.child + .size_allocate(>k4::Allocation::new(0, 0, width, height), baseline); + self.popover.present(); + } + + fn focus(&self, _obj: &PopoverContainer, direction: gtk4::DirectionType) -> bool { + if self.popover.is_visible() { + self.popover.child_focus(direction) + } else { + self.child.child_focus(direction) + } + } +} + +glib::wrapper! { + pub struct PopoverContainer(ObjectSubclass) + @extends gtk4::Widget; +} + +impl PopoverContainer { + pub fn new>(child: &T) -> Self { + let obj = glib::Object::new::(&[]).unwrap(); + child.set_parent(&obj); + obj.inner().child.set(child.clone().upcast()); + obj + } + + fn inner(&self) -> &PopoverContainerInner { + PopoverContainerInner::from_instance(self) + } + + pub fn popover(&self) -> >k4::Popover { + &self.inner().popover + } + + pub fn popup(&self) { + self.popover().popup(); + } + + pub fn popdown(&self) { + self.popover().popdown(); + } +} diff --git a/old-panel/src/status_area.rs b/applets/cosmic-applet-status-area/src/status_area.rs similarity index 100% rename from old-panel/src/status_area.rs rename to applets/cosmic-applet-status-area/src/status_area.rs diff --git a/old-panel/src/status_menu.rs b/applets/cosmic-applet-status-area/src/status_menu.rs similarity index 100% rename from old-panel/src/status_menu.rs rename to applets/cosmic-applet-status-area/src/status_menu.rs diff --git a/old-panel/src/status_notifier_watcher.rs b/applets/cosmic-applet-status-area/src/status_notifier_watcher.rs similarity index 100% rename from old-panel/src/status_notifier_watcher.rs rename to applets/cosmic-applet-status-area/src/status_notifier_watcher.rs diff --git a/justfile b/justfile index 5afda439..b5bd750a 100644 --- a/justfile +++ b/justfile @@ -17,6 +17,7 @@ audio_id := 'com.system76.CosmicAppletAudio' graphics_id := 'com.system76.CosmicAppletGraphics' network_id := 'com.system76.CosmicAppletNetwork' power_id := 'com.system76.CosmicAppletPower' +status_area_id := 'com.system76.CosmicAppletStatusArea' all: _extract_vendor cargo build {{cargo_args}} @@ -43,6 +44,11 @@ install: install -Dm0644 applets/cosmic-applet-power/data/{{power_id}}.desktop {{sharedir}}/applications/{{power_id}}.desktop install -Dm04755 target/release/cosmic-applet-power {{bindir}}/cosmic-applet-power + # status area + install -Dm0644 applets/cosmic-applet-status-area/data/icons/{{status_area_id}}.svg {{iconsdir}}/{{status_area_id}}.svg + install -Dm0644 applets/cosmic-applet-status-area/data/{{status_area_id}}.desktop {{sharedir}}/applications/{{status_area_id}}.desktop + install -Dm04755 target/release/cosmic-applet-status-area {{bindir}}/cosmic-applet-status-area + # Extracts vendored dependencies if vendor=1 _extract_vendor: #!/usr/bin/env sh diff --git a/old-panel/src/application.rs b/old-panel/src/application.rs index a64f2d63..3bb83332 100644 --- a/old-panel/src/application.rs +++ b/old-panel/src/application.rs @@ -8,7 +8,6 @@ use std::cell::Cell; use crate::deref_cell::DerefCell; use crate::notifications::Notifications; -use crate::status_notifier_watcher; use crate::window; #[derive(Default)] @@ -30,7 +29,6 @@ impl ObjectImpl for PanelAppInner { self.parent_constructed(obj); - glib::MainContext::default().spawn_local(status_notifier_watcher::start()); self.notifications.set(Notifications::new()); } } diff --git a/old-panel/src/main.rs b/old-panel/src/main.rs index e7498ede..df717497 100644 --- a/old-panel/src/main.rs +++ b/old-panel/src/main.rs @@ -10,9 +10,6 @@ mod notification_popover; mod notification_widget; mod notifications; mod popover_container; -mod status_area; -mod status_menu; -mod status_notifier_watcher; mod time_button; mod window; diff --git a/old-panel/src/window.rs b/old-panel/src/window.rs index a5136649..171c9db2 100644 --- a/old-panel/src/window.rs +++ b/old-panel/src/window.rs @@ -6,7 +6,6 @@ use std::cell::Cell; use crate::application::PanelApp; use crate::deref_cell::DerefCell; -use crate::status_area::StatusArea; use crate::time_button::TimeButton; const BOTTOM: bool = false; @@ -57,7 +56,6 @@ fn window_box(app: &PanelApp) -> gtk4::Widget { ..append(&button("Applications")); })); ..set_center_widget(Some(&TimeButton::new(app))); - ..set_end_widget(Some(&StatusArea::new())); }; widget.upcast() } @@ -101,7 +99,6 @@ impl ObjectImpl for PanelWindowInner { ..append(&button("Workspaces")); ..append(&button("Applications")); })); - ..set_end_widget(Some(&StatusArea::new())); }; cascade! {