Move status area code into an applet

This commit is contained in:
Ian Douglas Scott 2022-06-03 19:36:05 -07:00
parent 8df3019973
commit f50ed504b1
16 changed files with 313 additions and 11 deletions

19
Cargo.lock generated
View file

@ -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"

View file

@ -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"]

View file

@ -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"]}

View file

@ -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

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128px" height="128px" viewBox="0 0 128 128" version="1.1">
<defs>
<filter id="alpha" filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%">
<feColorMatrix type="matrix" in="SourceGraphic" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
</filter>
<mask id="mask0">
<g filter="url(#alpha)">
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
</g>
</mask>
<clipPath id="clip1">
<rect x="0" y="0" width="192" height="152"/>
</clipPath>
<g id="surface10632" clip-path="url(#clip1)">
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 123.503906 236 C 123.503906 268.863281 96.863281 295.503906 64 295.503906 C 31.136719 295.503906 4.496094 268.863281 4.496094 236 C 4.496094 203.136719 31.136719 176.496094 64 176.496094 C 96.863281 176.496094 123.503906 203.136719 123.503906 236 Z M 123.503906 236 " transform="matrix(1,0,0,1,8,-156)"/>
</g>
<mask id="mask1">
<g filter="url(#alpha)">
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
</g>
</mask>
<clipPath id="clip2">
<rect x="0" y="0" width="192" height="152"/>
</clipPath>
<g id="surface10635" clip-path="url(#clip2)">
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 29.195312 180.496094 L 98.804688 180.496094 C 103.609375 180.496094 107.503906 184.046875 107.503906 188.425781 L 107.503906 283.574219 C 107.503906 287.953125 103.609375 291.503906 98.804688 291.503906 L 29.195312 291.503906 C 24.390625 291.503906 20.496094 287.953125 20.496094 283.574219 L 20.496094 188.425781 C 20.496094 184.046875 24.390625 180.496094 29.195312 180.496094 Z M 29.195312 180.496094 " transform="matrix(1,0,0,1,8,-156)"/>
</g>
<mask id="mask2">
<g filter="url(#alpha)">
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
</g>
</mask>
<clipPath id="clip3">
<rect x="0" y="0" width="192" height="152"/>
</clipPath>
<g id="surface10638" clip-path="url(#clip3)">
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 20.417969 184.496094 L 107.582031 184.496094 C 111.957031 184.496094 115.503906 188.042969 115.503906 192.417969 L 115.503906 279.582031 C 115.503906 283.957031 111.957031 287.503906 107.582031 287.503906 L 20.417969 287.503906 C 16.042969 287.503906 12.496094 283.957031 12.496094 279.582031 L 12.496094 192.417969 C 12.496094 188.042969 16.042969 184.496094 20.417969 184.496094 Z M 20.417969 184.496094 " transform="matrix(1,0,0,1,8,-156)"/>
</g>
<mask id="mask3">
<g filter="url(#alpha)">
<rect x="0" y="0" width="128" height="128" style="fill:rgb(0%,0%,0%);fill-opacity:0.1;stroke:none;"/>
</g>
</mask>
<clipPath id="clip4">
<rect x="0" y="0" width="192" height="152"/>
</clipPath>
<g id="surface10641" clip-path="url(#clip4)">
<path style="fill:none;stroke-width:0.99;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.99,0.99;stroke-miterlimit:4;" d="M 16.425781 200.496094 L 111.574219 200.496094 C 115.953125 200.496094 119.503906 204.390625 119.503906 209.195312 L 119.503906 278.804688 C 119.503906 283.609375 115.953125 287.503906 111.574219 287.503906 L 16.425781 287.503906 C 12.046875 287.503906 8.496094 283.609375 8.496094 278.804688 L 8.496094 209.195312 C 8.496094 204.390625 12.046875 200.496094 16.425781 200.496094 Z M 16.425781 200.496094 " transform="matrix(1,0,0,1,8,-156)"/>
</g>
</defs>
<g id="surface10578">
<rect x="0" y="0" width="128" height="128" style="fill:rgb(94.117647%,94.117647%,94.117647%);fill-opacity:1;stroke:none;"/>
<use xlink:href="#surface10632" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask0)"/>
<use xlink:href="#surface10635" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask1)"/>
<use xlink:href="#surface10638" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask2)"/>
<use xlink:href="#surface10641" transform="matrix(1,0,0,1,-8,-16)" mask="url(#mask3)"/>
<path style="fill:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(38.431373%,62.7451%,91.764706%);stroke-opacity:1;stroke-miterlimit:4;" d="M 0 289 L 128 289 " transform="matrix(1,0,0,1,0,-172)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View file

@ -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<zbus::ConnectionBuilder<'static>>,
>(
well_known_name: &'static str,
serve_cb: F,
) -> zbus::Result<zbus::Connection> {
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)
}

View file

@ -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<T>(OnceCell<T>);
impl<T> DerefCell<T> {
#[track_caller]
pub fn set(&self, value: T) {
if self.0.set(value).is_err() {
panic!("Initialized twice");
}
}
}
impl<T> Default for DerefCell<T> {
fn default() -> Self {
Self(OnceCell::default())
}
}
impl<T> std::ops::Deref for DerefCell<T> {
type Target = T;
#[track_caller]
fn deref(&self) -> &T {
self.0.get().unwrap()
}
}

View file

@ -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();
}

View file

@ -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<gtk4::Widget>,
popover: DerefCell<gtk4::Popover>,
}
#[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(&gtk4::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<PopoverContainerInner>)
@extends gtk4::Widget;
}
impl PopoverContainer {
pub fn new<T: IsA<gtk4::Widget>>(child: &T) -> Self {
let obj = glib::Object::new::<Self>(&[]).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) -> &gtk4::Popover {
&self.inner().popover
}
pub fn popup(&self) {
self.popover().popup();
}
pub fn popdown(&self) {
self.popover().popdown();
}
}

View file

@ -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

View file

@ -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());
}
}

View file

@ -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;

View file

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