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! {