Port all dbus server/client to to zbus
Seems to generally be working well. May still need a few fixes.
This commit is contained in:
parent
8b5e1a7f12
commit
8b2a9c6359
13 changed files with 592 additions and 821 deletions
137
Cargo.lock
generated
137
Cargo.lock
generated
|
|
@ -452,37 +452,16 @@ version = "1.6.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83c8d82922337cd23a15f88b70d8e4ef5f11da38dd7cdb55e84dd5de99695da0"
|
||||
dependencies = [
|
||||
"enumflags2_derive 0.6.4",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a25c90b056b3f84111cf183cbeddef0d3a0bbe9a674f057e1a1533c315f24def"
|
||||
dependencies = [
|
||||
"enumflags2_derive 0.7.3",
|
||||
"enumflags2_derive",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2_derive"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "946ee94e3dbf58fdd324f9ce245c7b238d46a66f00e86a020b71996349e46cce"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2_derive"
|
||||
version = "0.7.3"
|
||||
|
|
@ -847,7 +826,7 @@ checksum = "2aad66361f66796bfc73f530c51ef123970eb895ffba991a234fcf7bea89e518"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"proc-macro-crate 1.1.0",
|
||||
"proc-macro-crate",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -962,7 +941,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"heck",
|
||||
"itertools",
|
||||
"proc-macro-crate 1.1.0",
|
||||
"proc-macro-crate",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -1080,9 +1059,9 @@ dependencies = [
|
|||
"x11",
|
||||
"x11rb",
|
||||
"xdg",
|
||||
"zbus 2.0.0",
|
||||
"zvariant 3.0.0",
|
||||
"zvariant_derive 3.0.0",
|
||||
"zbus",
|
||||
"zvariant",
|
||||
"zvariant_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1119,16 +1098,6 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nb-connect"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1bb540dc6ef51cfe1916ec038ce7a620daf3a111e2502d745197cd53d6bca15"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"socket2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.20.2"
|
||||
|
|
@ -1338,15 +1307,21 @@ dependencies = [
|
|||
"cascade",
|
||||
"chrono",
|
||||
"derivative",
|
||||
"enumflags2",
|
||||
"futures",
|
||||
"futures-channel",
|
||||
"gdk4-wayland",
|
||||
"gdk4-x11",
|
||||
"gobject-sys",
|
||||
"gtk4",
|
||||
"libcosmic",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"toml",
|
||||
"x11",
|
||||
"zbus 1.9.2",
|
||||
"zbus",
|
||||
"zbus_names",
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1415,15 +1390,6 @@ version = "0.2.16"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785"
|
||||
dependencies = [
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "1.1.0"
|
||||
|
|
@ -2113,29 +2079,6 @@ version = "0.8.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
|
||||
|
||||
[[package]]
|
||||
name = "zbus"
|
||||
version = "1.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5983c3d035549ab80db67c844ec83ed271f7c1f2546fd9577c594d34c1b6c85"
|
||||
dependencies = [
|
||||
"async-io",
|
||||
"byteorder",
|
||||
"derivative",
|
||||
"enumflags2 0.6.4",
|
||||
"fastrand",
|
||||
"futures",
|
||||
"nb-connect",
|
||||
"nix 0.20.2",
|
||||
"once_cell",
|
||||
"polling",
|
||||
"scoped-tls",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"zbus_macros 1.9.2",
|
||||
"zvariant 2.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus"
|
||||
version = "2.0.0"
|
||||
|
|
@ -2152,7 +2095,7 @@ dependencies = [
|
|||
"async-trait",
|
||||
"byteorder",
|
||||
"derivative",
|
||||
"enumflags2 0.7.3",
|
||||
"enumflags2",
|
||||
"event-listener",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
|
|
@ -2166,21 +2109,9 @@ dependencies = [
|
|||
"serde_repr",
|
||||
"sha1",
|
||||
"static_assertions",
|
||||
"zbus_macros 2.0.0",
|
||||
"zbus_macros",
|
||||
"zbus_names",
|
||||
"zvariant 3.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_macros"
|
||||
version = "1.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bce54ac7b2150a2fa21ad5842a7470ce2288158d7da1f9bfda8ad455a1c59a97"
|
||||
dependencies = [
|
||||
"proc-macro-crate 0.1.5",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2189,7 +2120,7 @@ version = "2.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd2ea67f43e8abd245eabc480e597990340d9870b585d40bf4350d742acb2219"
|
||||
dependencies = [
|
||||
"proc-macro-crate 1.1.0",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
|
|
@ -2204,21 +2135,7 @@ checksum = "ae1f142d242d6854815a8c5c2aea83d9508f72f5757d0a137c21ef4b07bfee66"
|
|||
dependencies = [
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"zvariant 3.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a68c7b55f2074489b7e8e07d2d0a6ee6b4f233867a653c664d8020ba53692525"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"enumflags2 0.6.4",
|
||||
"libc",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"zvariant_derive 2.10.0",
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2228,23 +2145,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "4a946c049b2eac1a253f98e9267a8ce7a3d93be274ea146e6dd7a0965232a911"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"enumflags2 0.7.3",
|
||||
"enumflags2",
|
||||
"libc",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"zvariant_derive 3.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_derive"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4ca5e22593eb4212382d60d26350065bf2a02c34b85bc850474a74b589a3de9"
|
||||
dependencies = [
|
||||
"proc-macro-crate 1.1.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"zvariant_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2253,7 +2158,7 @@ version = "3.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28fce5afb8d639bff79b1e8cdb258a3ca22d458f4603b23d794b4cb4e878c990"
|
||||
dependencies = [
|
||||
"proc-macro-crate 1.1.0",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
|
|
|
|||
10
Cargo.toml
10
Cargo.toml
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "pop-cosmic-panel"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
license = "LGPL-3.0-or-later"
|
||||
|
||||
[dependencies]
|
||||
|
|
@ -9,15 +9,21 @@ cascade = "1"
|
|||
chrono = "0.4"
|
||||
byte_string = "1"
|
||||
derivative = "2"
|
||||
enumflags2 = "0.7"
|
||||
futures = "0.3"
|
||||
futures-channel = "0.3"
|
||||
gdk4-x11 = "0.3"
|
||||
gdk4-wayland = { version = "0.3", optional = true }
|
||||
gtk4 = "0.3"
|
||||
gobject-sys = "0.14.0"
|
||||
libcosmic = { git = "https://github.com/pop-os/libcosmic" }
|
||||
once_cell = "1"
|
||||
serde = "1"
|
||||
toml = "0.5"
|
||||
x11 = { version = "2", features = ["xlib"] }
|
||||
zbus = "1"
|
||||
zbus = "2"
|
||||
zbus_names = "2"
|
||||
zvariant = "3"
|
||||
|
||||
[features]
|
||||
layer-shell = ["gdk4-wayland", "libcosmic/layer-shell"]
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ impl ObjectImpl for PanelAppInner {
|
|||
|
||||
self.parent_constructed(obj);
|
||||
|
||||
status_notifier_watcher::start();
|
||||
glib::MainContext::default().spawn_local(status_notifier_watcher::start());
|
||||
self.notifications.set(Notifications::new());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
53
src/dbus_service.rs
Normal file
53
src/dbus_service.rs
Normal 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)
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
use gtk4::prelude::*;
|
||||
use gtk4::{glib, prelude::*};
|
||||
|
||||
mod application;
|
||||
mod dbus_service;
|
||||
mod deref_cell;
|
||||
mod mpris;
|
||||
mod mpris_player;
|
||||
|
|
@ -18,5 +19,5 @@ mod window;
|
|||
use application::PanelApp;
|
||||
|
||||
fn main() {
|
||||
PanelApp::new().run();
|
||||
glib::MainContext::default().with_thread_default(|| PanelApp::new().run());
|
||||
}
|
||||
|
|
|
|||
78
src/mpris.rs
78
src/mpris.rs
|
|
@ -1,12 +1,13 @@
|
|||
use cascade::cascade;
|
||||
use futures::stream::StreamExt;
|
||||
use gtk4::{
|
||||
gio,
|
||||
glib::{self, clone},
|
||||
prelude::*,
|
||||
subclass::prelude::*,
|
||||
};
|
||||
use once_cell::unsync::OnceCell;
|
||||
use std::{cell::RefCell, collections::HashMap};
|
||||
use zbus::fdo::DBusProxy;
|
||||
|
||||
use crate::deref_cell::DerefCell;
|
||||
use crate::mpris_player::MprisPlayer;
|
||||
|
|
@ -14,7 +15,7 @@ use crate::mpris_player::MprisPlayer;
|
|||
#[derive(Default)]
|
||||
pub struct MprisControlsInner {
|
||||
listbox: DerefCell<gtk4::ListBox>,
|
||||
dbus: OnceCell<DBus>,
|
||||
dbus: OnceCell<DBusProxy<'static>>,
|
||||
players: RefCell<HashMap<String, MprisPlayer>>,
|
||||
}
|
||||
|
||||
|
|
@ -37,23 +38,32 @@ impl ObjectImpl for MprisControlsInner {
|
|||
};
|
||||
|
||||
glib::MainContext::default().spawn_local(clone!(@strong obj => async move {
|
||||
let dbus = match DBus::new().await {
|
||||
Ok(dbus) => dbus,
|
||||
let (dbus, mut name_owner_changed_stream) = match async {
|
||||
let connection = zbus::Connection::session().await?;
|
||||
let dbus = DBusProxy::new(&connection).await?;
|
||||
let stream = dbus.receive_name_owner_changed().await?;
|
||||
Ok::<_, zbus::Error>((dbus, stream))
|
||||
}.await {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
eprintln!("Failed to connect to 'org.freedesktop.DBus': {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
dbus.connect_name_owner_changed(clone!(@strong obj => move |name, old, new| {
|
||||
if name.starts_with("org.mpris.MediaPlayer2.") {
|
||||
if !old.is_empty() {
|
||||
obj.player_removed(&name);
|
||||
}
|
||||
if !new.is_empty() {
|
||||
glib::MainContext::default().spawn_local(clone!(@strong obj => async move {
|
||||
obj.player_added(&name).await;
|
||||
}));
|
||||
glib::MainContext::default().spawn_local(clone!(@strong obj => async move {
|
||||
while let Some(evt) = name_owner_changed_stream.next().await {
|
||||
let args = match evt.args() {
|
||||
Ok(args) => args,
|
||||
Err(_) => { continue; },
|
||||
};
|
||||
if args.name.starts_with("org.mpris.MediaPlayer2.") {
|
||||
if !args.old_owner.is_none() {
|
||||
obj.player_removed(&args.name);
|
||||
}
|
||||
if !args.new_owner.is_none() {
|
||||
obj.player_added(&args.name).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
|
@ -122,45 +132,3 @@ impl MprisControls {
|
|||
self.inner().players.borrow_mut().remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
struct DBus(gio::DBusProxy);
|
||||
|
||||
impl DBus {
|
||||
async fn new() -> Result<Self, glib::Error> {
|
||||
let proxy = gio::DBusProxy::for_bus_future(
|
||||
gio::BusType::Session,
|
||||
gio::DBusProxyFlags::NONE,
|
||||
None,
|
||||
"org.freedesktop.DBus",
|
||||
"/org/freedesktop/DBus",
|
||||
"org.freedesktop.DBus",
|
||||
)
|
||||
.await?;
|
||||
Ok(Self(proxy))
|
||||
}
|
||||
|
||||
async fn list_names(&self) -> Result<impl Iterator<Item = String>, glib::Error> {
|
||||
Ok(self
|
||||
.0
|
||||
.call_future("ListNames", None, gio::DBusCallFlags::NONE, 1000)
|
||||
.await?
|
||||
.child_value(0)
|
||||
.iter()
|
||||
.filter_map(|x| x.get::<String>()))
|
||||
}
|
||||
|
||||
fn connect_name_owner_changed<F: Fn(String, String, String) + 'static>(
|
||||
&self,
|
||||
f: F,
|
||||
) -> glib::SignalHandlerId {
|
||||
self.0
|
||||
.connect_local("g-signal", false, move |args| {
|
||||
if &args[2].get::<String>().unwrap() == "NameOwnerChanged" {
|
||||
let (name, old, new) = args[3].get::<glib::Variant>().unwrap().get().unwrap();
|
||||
f(name, old, new);
|
||||
}
|
||||
None
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use cascade::cascade;
|
||||
use futures::StreamExt;
|
||||
use gtk4::{
|
||||
gdk_pixbuf, gio,
|
||||
glib::{self, clone},
|
||||
|
|
@ -6,7 +7,9 @@ use gtk4::{
|
|||
prelude::*,
|
||||
subclass::prelude::*,
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
use std::{cell::RefCell, collections::HashMap};
|
||||
use zbus::dbus_proxy;
|
||||
use zvariant::OwnedValue;
|
||||
|
||||
use crate::deref_cell::DerefCell;
|
||||
|
||||
|
|
@ -16,7 +19,7 @@ pub struct MprisPlayerInner {
|
|||
backward_button: DerefCell<gtk4::Button>,
|
||||
play_pause_button: DerefCell<gtk4::Button>,
|
||||
forward_button: DerefCell<gtk4::Button>,
|
||||
player: DerefCell<Player>,
|
||||
player: DerefCell<PlayerProxy<'static>>,
|
||||
image: DerefCell<gtk4::Image>,
|
||||
image_uri: RefCell<Option<String>>,
|
||||
title_label: DerefCell<gtk4::Label>,
|
||||
|
|
@ -114,14 +117,29 @@ glib::wrapper! {
|
|||
}
|
||||
|
||||
impl MprisPlayer {
|
||||
pub async fn new(name: &str) -> Result<Self, glib::Error> {
|
||||
pub async fn new(name: &str) -> zbus::Result<Self> {
|
||||
let obj = glib::Object::new::<Self>(&[]).unwrap();
|
||||
|
||||
let player = Player::new(name).await?;
|
||||
player.connect_properties_changed(clone!(@weak obj => move |_player| {
|
||||
obj.update();
|
||||
}));
|
||||
let connection = zbus::Connection::session().await?;
|
||||
let player = PlayerProxy::builder(&connection)
|
||||
.destination(name.to_string())?
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let metadata_stream = player.receive_metadata_changed().await;
|
||||
let playback_status_stream = player.receive_playback_status_changed().await;
|
||||
let mut stream = futures::stream_select!(
|
||||
metadata_stream.map(|_| ()),
|
||||
playback_status_stream.map(|_| ())
|
||||
);
|
||||
|
||||
obj.inner().player.set(player);
|
||||
|
||||
glib::MainContext::default().spawn_local(clone!(@strong obj => async move {
|
||||
if stream.next().await.is_some() {
|
||||
obj.update();
|
||||
}
|
||||
}));
|
||||
obj.update();
|
||||
|
||||
Ok(obj)
|
||||
|
|
@ -133,7 +151,7 @@ impl MprisPlayer {
|
|||
|
||||
fn call(&self, method: &'static str) {
|
||||
glib::MainContext::default().spawn_local(clone!(@strong self as self_ => async move {
|
||||
if let Err(err) = self_.inner().player.call(method).await {
|
||||
if let Err(err) = self_.inner().player.call::<_, _, ()>(method, &()).await {
|
||||
eprintln!("Failed to call '{}': {}", method, err);
|
||||
}
|
||||
}));
|
||||
|
|
@ -164,8 +182,8 @@ impl MprisPlayer {
|
|||
let player = &self.inner().player;
|
||||
|
||||
// XXX status
|
||||
let (status, metadata) = match (player.playback_status(), player.metadata()) {
|
||||
(Some(status), Some(metadata)) => (status, metadata),
|
||||
let (status, metadata) = match (player.cached_playback_status(), player.cached_metadata()) {
|
||||
(Ok(Some(status)), Ok(Some(metadata))) => (status, Metadata(metadata)),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
|
|
@ -197,11 +215,11 @@ impl MprisPlayer {
|
|||
}
|
||||
}
|
||||
|
||||
struct Metadata(glib::VariantDict);
|
||||
struct Metadata(HashMap<String, OwnedValue>);
|
||||
|
||||
impl Metadata {
|
||||
fn lookup<T: glib::FromVariant>(&self, key: &str) -> Option<T> {
|
||||
self.0.lookup_value(key, None)?.get()
|
||||
fn lookup<'a, T: TryFrom<OwnedValue>>(&self, key: &str) -> Option<T> {
|
||||
T::try_from(self.0.get(key)?.clone()).ok()
|
||||
}
|
||||
|
||||
fn title(&self) -> Option<String> {
|
||||
|
|
@ -221,53 +239,14 @@ impl Metadata {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Player(gio::DBusProxy);
|
||||
#[dbus_proxy(
|
||||
interface = "org.mpris.MediaPlayer2.Player",
|
||||
default_path = "/org/mpris/MediaPlayer2"
|
||||
)]
|
||||
trait Player {
|
||||
#[dbus_proxy(property)]
|
||||
fn metadata(&self) -> zbus::Result<HashMap<String, OwnedValue>>;
|
||||
|
||||
impl Player {
|
||||
async fn new(name: &str) -> Result<Self, glib::Error> {
|
||||
let proxy = gio::DBusProxy::for_bus_future(
|
||||
gio::BusType::Session,
|
||||
gio::DBusProxyFlags::NONE,
|
||||
None,
|
||||
name,
|
||||
"/org/mpris/MediaPlayer2",
|
||||
"org.mpris.MediaPlayer2.Player",
|
||||
)
|
||||
.await?;
|
||||
Ok(Self(proxy))
|
||||
}
|
||||
|
||||
async fn call(&self, method: &str) -> Result<(), glib::Error> {
|
||||
self.0
|
||||
.call_future(method, None, gio::DBusCallFlags::NONE, 1000)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn property<T: glib::FromVariant>(&self, prop: &str) -> Option<T> {
|
||||
self.0.cached_property(prop)?.get()
|
||||
}
|
||||
|
||||
fn connect_properties_changed<F: Fn(Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
||||
let proxy = &self.0;
|
||||
self.0
|
||||
.connect_local(
|
||||
"g-properties-changed",
|
||||
false,
|
||||
clone!(@weak proxy => @default-panic, move |_| {
|
||||
f(Self(proxy));
|
||||
None
|
||||
}),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn playback_status(&self) -> Option<String> {
|
||||
self.property("PlaybackStatus")
|
||||
}
|
||||
|
||||
fn metadata(&self) -> Option<Metadata> {
|
||||
Some(Metadata(self.property("Metadata")?))
|
||||
}
|
||||
#[dbus_proxy(property)]
|
||||
fn playback_status(&self) -> zbus::Result<String>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,10 @@ impl ObjectImpl for NotificationListInner {
|
|||
..set_parent(obj);
|
||||
..connect_row_activated(clone!(@weak obj => move |_, row| {
|
||||
if let Some(id) = obj.id_for_row(row) {
|
||||
obj.inner().notifications.invoke_action(id, "default");
|
||||
let notifications = obj.inner().notifications.clone();
|
||||
glib::MainContext::default().spawn_local(async move {
|
||||
notifications.invoke_action(id, "default").await;
|
||||
});
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
|
@ -66,7 +69,7 @@ impl NotificationList {
|
|||
|
||||
obj.inner().notifications.set(notifications.clone());
|
||||
*obj.inner().ids.borrow_mut() = vec![
|
||||
notifications.connect_notification_recieved(clone!(@weak obj => move |notification| {
|
||||
notifications.connect_notification_received(clone!(@weak obj => move |notification| {
|
||||
obj.handle_notification(¬ification);
|
||||
})),
|
||||
notifications.connect_notification_closed(clone!(@weak obj => move |id| {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,10 @@ impl ObjectImpl for NotificationPopoverInner {
|
|||
return;
|
||||
}
|
||||
if let Some(id) = obj.id() {
|
||||
obj.inner().notifications.invoke_action(id, "default");
|
||||
let notifications = obj.inner().notifications.clone();
|
||||
glib::MainContext::default().spawn_local(async move {
|
||||
notifications.invoke_action(id, "default").await;
|
||||
});
|
||||
}
|
||||
obj.popdown();
|
||||
}));
|
||||
|
|
@ -83,7 +86,7 @@ impl NotificationPopover {
|
|||
|
||||
obj.inner().notifications.set(notifications.clone());
|
||||
*obj.inner().ids.borrow_mut() = vec![
|
||||
notifications.connect_notification_recieved(clone!(@weak obj => move |notification| {
|
||||
notifications.connect_notification_received(clone!(@weak obj => move |notification| {
|
||||
obj.handle_notification(¬ification);
|
||||
})),
|
||||
notifications.connect_notification_closed(clone!(@weak obj => move |id| {
|
||||
|
|
|
|||
|
|
@ -1,69 +1,161 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
use futures::stream::StreamExt;
|
||||
use futures_channel::mpsc;
|
||||
use gtk4::{
|
||||
gio,
|
||||
glib::{self, clone, subclass::Signal, SignalHandlerId},
|
||||
prelude::*,
|
||||
subclass::prelude::*,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use once_cell::unsync::OnceCell;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cell::{Cell, RefCell},
|
||||
collections::HashMap,
|
||||
convert::TryFrom,
|
||||
fmt,
|
||||
num::NonZeroU32,
|
||||
rc::Rc,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use zbus::{dbus_interface, Result, SignalContext};
|
||||
use zvariant::OwnedValue;
|
||||
|
||||
use crate::dbus_service;
|
||||
use crate::deref_cell::DerefCell;
|
||||
|
||||
static PATH: &str = "/org/freedesktop/Notifications";
|
||||
static INTERFACE: &str = "org.freedesktop.Notifications";
|
||||
static NOTIFICATIONS_XML: &str = "
|
||||
<node name='/org/freedesktop/Notifications'>
|
||||
<interface name='org.freedesktop.Notifications'>
|
||||
<method name='Notify'>
|
||||
<arg type='s' name='app_name' direction='in'/>
|
||||
<arg type='u' name='replaces_id' direction='in'/>
|
||||
<arg type='s' name='app_icon' direction='in'/>
|
||||
<arg type='s' name='summary' direction='in'/>
|
||||
<arg type='s' name='body' direction='in'/>
|
||||
<arg type='as' name='actions' direction='in'/>
|
||||
<arg type='a{sv}' name='hints' direction='in'/>
|
||||
<arg type='i' name='expire_timeout' direction='in'/>
|
||||
<arg type='u' name='id' direction='out'/>
|
||||
</method>
|
||||
|
||||
<method name='CloseNotification'>
|
||||
<arg type='u' name='id' direction='in'/>
|
||||
</method>
|
||||
enum Event {
|
||||
NotificationReceived(NotificationId),
|
||||
CloseNotification(NotificationId),
|
||||
}
|
||||
|
||||
<method name='GetCapabilities'>
|
||||
<arg type='as' direction='out'/>
|
||||
</method>
|
||||
pub struct NotificationsInterfaceInner {
|
||||
next_id: Mutex<NotificationId>,
|
||||
notifications: Mutex<HashMap<NotificationId, Arc<Notification>>>,
|
||||
sender: mpsc::UnboundedSender<Event>,
|
||||
}
|
||||
|
||||
<method name='GetServerInformation'>
|
||||
<arg type='s' name='name' direction='out'/>
|
||||
<arg type='s' name='vendor' direction='out'/>
|
||||
<arg type='s' name='version' direction='out'/>
|
||||
<arg type='s' name='spec_version' direction='out'/>
|
||||
</method>
|
||||
#[derive(Clone)]
|
||||
pub struct NotificationsInterface(Arc<NotificationsInterfaceInner>);
|
||||
|
||||
<signal name='NotificationClosed'>
|
||||
<arg type='u' name='id'/>
|
||||
<arg type='u' name='reason'/>
|
||||
</signal>
|
||||
impl NotificationsInterface {
|
||||
fn new() -> (Self, mpsc::UnboundedReceiver<Event>) {
|
||||
let (sender, receiver) = mpsc::unbounded();
|
||||
(
|
||||
Self(Arc::new(NotificationsInterfaceInner {
|
||||
next_id: Default::default(),
|
||||
notifications: Default::default(),
|
||||
sender,
|
||||
})),
|
||||
receiver,
|
||||
)
|
||||
}
|
||||
|
||||
<signal name='ActionInvoked'>
|
||||
<arg type='u' name='id'/>
|
||||
<arg type='s' name='action_key'/>
|
||||
</signal>
|
||||
</interface>
|
||||
</node>
|
||||
";
|
||||
fn next_id(&self) -> NotificationId {
|
||||
let mut next_id = self.0.next_id.lock().unwrap();
|
||||
let id = *next_id;
|
||||
*next_id = NotificationId::new(u32::from(id).wrapping_add(1)).unwrap_or_default();
|
||||
id
|
||||
}
|
||||
|
||||
fn handle_notify(
|
||||
&self,
|
||||
app_name: String,
|
||||
replaces_id: Option<NotificationId>,
|
||||
app_icon: String,
|
||||
summary: String,
|
||||
body: String,
|
||||
actions: Vec<String>,
|
||||
hints: Hints,
|
||||
_expire_timeout: i32,
|
||||
) -> NotificationId {
|
||||
// Ignores `expire-timeout`, like Gnome Shell
|
||||
|
||||
let id = replaces_id.unwrap_or_else(|| self.next_id());
|
||||
|
||||
let notification = Arc::new(Notification {
|
||||
id,
|
||||
app_name,
|
||||
app_icon,
|
||||
summary,
|
||||
body,
|
||||
actions,
|
||||
hints,
|
||||
});
|
||||
|
||||
self.0
|
||||
.notifications
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(id, notification);
|
||||
|
||||
self.0
|
||||
.sender
|
||||
.unbounded_send(Event::NotificationReceived(id))
|
||||
.unwrap();
|
||||
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: return value variable names in introspection data?
|
||||
|
||||
#[dbus_interface(name = "org.freedesktop.Notifications")]
|
||||
impl NotificationsInterface {
|
||||
fn Notify(
|
||||
&self,
|
||||
app_name: String,
|
||||
replaces_id: u32,
|
||||
app_icon: String,
|
||||
summary: String,
|
||||
body: String,
|
||||
actions: Vec<String>,
|
||||
hints: HashMap<String, OwnedValue>,
|
||||
expire_timeout: i32,
|
||||
) -> u32 {
|
||||
u32::from(self.handle_notify(
|
||||
app_name,
|
||||
NotificationId::new(replaces_id),
|
||||
app_icon,
|
||||
summary,
|
||||
body,
|
||||
actions,
|
||||
Hints(hints),
|
||||
expire_timeout,
|
||||
))
|
||||
}
|
||||
|
||||
async fn CloseNotification(&self, id: u32) {
|
||||
if let Some(id) = NotificationId::new(id) {
|
||||
self.0
|
||||
.sender
|
||||
.unbounded_send(Event::CloseNotification(id))
|
||||
.unwrap();
|
||||
}
|
||||
// TODO error?
|
||||
}
|
||||
|
||||
fn GetCapabilities(&self) -> Vec<&'static str> {
|
||||
// TODO: body-markup, sound
|
||||
vec!["actions", "body", "icon-static", "persistence"]
|
||||
}
|
||||
|
||||
fn GetServerInformation(&self) -> (&'static str, &'static str, &'static str, &'static str) {
|
||||
("cosmic-panel", "system76", env!("CARGO_PKG_VERSION"), "1.2")
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn NotificationClosed(ctxt: &SignalContext<'_>, id: u32, reason: u32) -> Result<()>;
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn ActionInvoked(ctxt: &SignalContext<'_>, id: u32, action_key: &str) -> Result<()>;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct NotificationsInner {
|
||||
next_id: Cell<NotificationId>,
|
||||
notifications: RefCell<HashMap<NotificationId, Rc<Notification>>>,
|
||||
connection: RefCell<Option<gio::DBusConnection>>,
|
||||
interface: DerefCell<NotificationsInterface>,
|
||||
connection: OnceCell<zbus::Connection>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
|
@ -99,16 +191,12 @@ glib::wrapper! {
|
|||
pub struct Notifications(ObjectSubclass<NotificationsInner>);
|
||||
}
|
||||
|
||||
// XXX hack: https://github.com/gtk-rs/gtk-rs-core/issues/263
|
||||
unsafe impl Send for Notifications {}
|
||||
unsafe impl Sync for Notifications {}
|
||||
|
||||
struct Hints(HashMap<String, glib::Variant>);
|
||||
struct Hints(HashMap<String, OwnedValue>);
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Hints {
|
||||
fn prop<T: glib::FromVariant>(&self, name: &str) -> Option<T> {
|
||||
self.0.get(name)?.get()
|
||||
fn prop<T: TryFrom<OwnedValue>>(&self, name: &str) -> Option<T> {
|
||||
T::try_from(self.0.get(name)?.clone()).ok()
|
||||
}
|
||||
|
||||
fn actions_icon(&self) -> bool {
|
||||
|
|
@ -162,13 +250,13 @@ impl fmt::Debug for Hints {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut s = f.debug_struct("Hints");
|
||||
for (k, v) in &self.0 {
|
||||
if let Some(v) = v.get::<String>() {
|
||||
if let Ok(v) = <&str>::try_from(v) {
|
||||
s.field(k, &v);
|
||||
} else if let Some(v) = v.get::<i32>() {
|
||||
} else if let Ok(v) = i32::try_from(v) {
|
||||
s.field(k, &v);
|
||||
} else if let Some(v) = v.get::<bool>() {
|
||||
} else if let Ok(v) = bool::try_from(v) {
|
||||
s.field(k, &v);
|
||||
} else if let Some(v) = v.get::<u8>() {
|
||||
} else if let Ok(v) = u8::try_from(v) {
|
||||
s.field(k, &v);
|
||||
} else {
|
||||
s.field(k, v);
|
||||
|
|
@ -178,18 +266,6 @@ impl fmt::Debug for Hints {
|
|||
}
|
||||
}
|
||||
|
||||
impl glib::StaticVariantType for Hints {
|
||||
fn static_variant_type() -> Cow<'static, glib::VariantTy> {
|
||||
glib::VariantTy::new("a{sv}").unwrap().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl glib::FromVariant for Hints {
|
||||
fn from_variant(variant: &glib::Variant) -> Option<Self> {
|
||||
variant.get().map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Clone, Copy, Hash, glib::GBoxed, PartialEq, Eq)]
|
||||
#[gboxed(type_name = "S76NotificationId")]
|
||||
|
|
@ -207,12 +283,6 @@ impl From<NotificationId> for u32 {
|
|||
}
|
||||
}
|
||||
|
||||
impl ToVariant for NotificationId {
|
||||
fn to_variant(&self) -> glib::Variant {
|
||||
self.0.get().to_variant()
|
||||
}
|
||||
}
|
||||
|
||||
impl NotificationId {
|
||||
fn new(value: u32) -> Option<Self> {
|
||||
NonZeroU32::new(value).map(Self)
|
||||
|
|
@ -244,14 +314,30 @@ impl Notifications {
|
|||
pub fn new() -> Self {
|
||||
let notifications = glib::Object::new::<Self>(&[]).unwrap();
|
||||
|
||||
gio::bus_own_name(
|
||||
gio::BusType::Session,
|
||||
INTERFACE,
|
||||
gio::BusNameOwnerFlags::NONE,
|
||||
clone!(@strong notifications => move |connection, name| notifications.bus_acquired(connection, name)),
|
||||
clone!(@strong notifications => move |connection, name| notifications.name_acquired(connection, name)),
|
||||
clone!(@strong notifications => move |connection, name| notifications.name_lost(connection, name)),
|
||||
);
|
||||
let (interface, mut receiver) = NotificationsInterface::new();
|
||||
notifications.inner().interface.set(interface);
|
||||
|
||||
glib::MainContext::default().spawn_local(clone!(@strong notifications => async move {
|
||||
let connection = match dbus_service::create(INTERFACE, |builder| builder.serve_at(PATH, notifications.inner().interface.clone())).await {
|
||||
Ok(connection) => connection,
|
||||
Err(err) => {
|
||||
eprintln!("Failed to start `Notifications` service: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let _ = notifications.inner().connection.set(connection.clone());
|
||||
|
||||
if let Some(event) = receiver.next().await {
|
||||
match event {
|
||||
Event::NotificationReceived(id) => {
|
||||
notifications.emit_by_name("notification-received", &[&id]).unwrap();
|
||||
}
|
||||
Event::CloseNotification(id) => {
|
||||
notifications.close_notification(id, CloseReason::Call).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
notifications
|
||||
}
|
||||
|
|
@ -260,155 +346,49 @@ impl Notifications {
|
|||
NotificationsInner::from_instance(self)
|
||||
}
|
||||
|
||||
fn next_id(&self) -> NotificationId {
|
||||
let next_id = &self.inner().next_id;
|
||||
let id = next_id.get();
|
||||
next_id.set(NotificationId::new(u32::from(id).wrapping_add(1)).unwrap_or_default());
|
||||
id
|
||||
}
|
||||
|
||||
fn handle_notify(
|
||||
&self,
|
||||
app_name: String,
|
||||
replaces_id: Option<NotificationId>,
|
||||
app_icon: String,
|
||||
summary: String,
|
||||
body: String,
|
||||
actions: Vec<String>,
|
||||
hints: Hints,
|
||||
_expire_timeout: i32,
|
||||
) -> NotificationId {
|
||||
// Ignores `expire-timeout`, like Gnome Shell
|
||||
|
||||
let id = replaces_id.unwrap_or_else(|| self.next_id());
|
||||
|
||||
let notification = Rc::new(Notification {
|
||||
id,
|
||||
app_name,
|
||||
app_icon,
|
||||
summary,
|
||||
body,
|
||||
actions,
|
||||
hints,
|
||||
});
|
||||
|
||||
async fn close_notification(&self, id: NotificationId, reason: CloseReason) {
|
||||
self.inner()
|
||||
.interface
|
||||
.0
|
||||
.notifications
|
||||
.borrow_mut()
|
||||
.insert(id, notification);
|
||||
|
||||
self.emit_by_name("notification-received", &[&id]).unwrap();
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
fn close_notification(&self, id: NotificationId, reason: CloseReason) {
|
||||
self.inner().notifications.borrow_mut().remove(&id);
|
||||
.lock()
|
||||
.unwrap()
|
||||
.remove(&id);
|
||||
|
||||
self.emit_by_name("notification-closed", &[&id]).unwrap();
|
||||
|
||||
if let Some(connection) = self.inner().connection.borrow().as_ref() {
|
||||
connection
|
||||
.emit_signal(
|
||||
None,
|
||||
PATH,
|
||||
INTERFACE,
|
||||
"CloseNotification",
|
||||
Some(&(id, &(reason as u32)).to_variant()),
|
||||
)
|
||||
.unwrap();
|
||||
if let Some(connection) = self.inner().connection.get() {
|
||||
let ctxt = SignalContext::new(connection, PATH).unwrap(); // XXX unwrap?
|
||||
let _ =
|
||||
NotificationsInterface::NotificationClosed(&ctxt, id.into(), reason as u32).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dismiss(&self, id: NotificationId) {
|
||||
self.close_notification(id, CloseReason::Dismiss);
|
||||
glib::MainContext::default().spawn_local(clone!(@strong self as self_ => async move {
|
||||
self_.close_notification(id, CloseReason::Dismiss).await
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn invoke_action(&self, id: NotificationId, action_key: &str) {
|
||||
if let Some(connection) = self.inner().connection.borrow().as_ref() {
|
||||
connection
|
||||
.emit_signal(
|
||||
None,
|
||||
PATH,
|
||||
INTERFACE,
|
||||
"ActionInvoked",
|
||||
Some(&(&(id, action_key),).to_variant()),
|
||||
)
|
||||
.unwrap();
|
||||
pub async fn invoke_action(&self, id: NotificationId, action_key: &str) {
|
||||
if let Some(connection) = self.inner().connection.get() {
|
||||
let ctxt = SignalContext::new(connection, PATH).unwrap(); // XXX unwrap?
|
||||
let _ = NotificationsInterface::ActionInvoked(&ctxt, id.into(), action_key).await;
|
||||
}
|
||||
}
|
||||
|
||||
fn bus_acquired(&self, connection: gio::DBusConnection, _name: &str) {
|
||||
*self.inner().connection.borrow_mut() = Some(connection);
|
||||
pub fn get(&self, id: NotificationId) -> Option<Arc<Notification>> {
|
||||
self.inner()
|
||||
.interface
|
||||
.0
|
||||
.notifications
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(&id)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn name_acquired(&self, connection: gio::DBusConnection, _name: &str) {
|
||||
let introspection_data = gio::DBusNodeInfo::for_xml(NOTIFICATIONS_XML).unwrap();
|
||||
let interface_info = introspection_data.lookup_interface(INTERFACE).unwrap();
|
||||
let method_call = clone!(@strong self as self_ => move |_connection: gio::DBusConnection,
|
||||
_sender: &str,
|
||||
_path: &str,
|
||||
_interface: &str,
|
||||
method: &str,
|
||||
args: glib::Variant,
|
||||
invocation: gio::DBusMethodInvocation| {
|
||||
match method {
|
||||
"Notify" => {
|
||||
let (app_name, replaces_id, app_icon, summary, body, actions, hints, expire_timeout) = args.get().unwrap();
|
||||
let replaces_id = NotificationId::new(replaces_id);
|
||||
let res = self_.handle_notify(app_name, replaces_id, app_icon, summary, body, actions, hints, expire_timeout);
|
||||
invocation.return_value(Some(&(u32::from(res),).to_variant()));
|
||||
// TODO error?
|
||||
}
|
||||
"CloseNotification" => {
|
||||
let (id,) = args.get::<(u32,)>().unwrap();
|
||||
if let Some(id) = NotificationId::new(id) {
|
||||
self_.close_notification(id, CloseReason::Call);
|
||||
}
|
||||
invocation.return_value(None);
|
||||
// TODO error?
|
||||
}
|
||||
"GetCapabilities" => {
|
||||
// TODO: body-markup, sound
|
||||
let capabilities = vec!["actions", "body", "icon-static", "persistence"];
|
||||
invocation.return_value(Some(&(capabilities,).to_variant()));
|
||||
}
|
||||
"GetServerInformation" => {
|
||||
let information = ("cosmic-panel", "system76", env!("CARGO_PKG_VERSION"), "1.2");
|
||||
invocation.return_value(Some(&information.to_variant()));
|
||||
}
|
||||
_ => unreachable!()
|
||||
}
|
||||
});
|
||||
let get_property = |_: gio::DBusConnection,
|
||||
_sender: &str,
|
||||
_path: &str,
|
||||
_interface: &str,
|
||||
_prop: &str| { unreachable!() };
|
||||
let set_property = |_: gio::DBusConnection,
|
||||
_sender: &str,
|
||||
_path: &str,
|
||||
_interface: &str,
|
||||
_prop: &str,
|
||||
_value: glib::Variant| { unreachable!() };
|
||||
if let Err(err) = connection.register_object(
|
||||
PATH,
|
||||
&interface_info,
|
||||
method_call,
|
||||
get_property,
|
||||
set_property,
|
||||
) {
|
||||
eprintln!("Failed to register object: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
fn name_lost(&self, _connection: Option<gio::DBusConnection>, _name: &str) {}
|
||||
|
||||
pub fn get(&self, id: NotificationId) -> Option<Rc<Notification>> {
|
||||
self.inner().notifications.borrow().get(&id).cloned()
|
||||
}
|
||||
|
||||
pub fn connect_notification_recieved<F: Fn(Rc<Notification>) + 'static>(
|
||||
pub fn connect_notification_received<F: Fn(Arc<Notification>) + 'static>(
|
||||
&self,
|
||||
cb: F,
|
||||
) -> SignalHandlerId {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
use cascade::cascade;
|
||||
use futures::stream::StreamExt;
|
||||
use gtk4::{
|
||||
gio,
|
||||
glib::{self, clone},
|
||||
prelude::*,
|
||||
subclass::prelude::*,
|
||||
};
|
||||
use once_cell::unsync::OnceCell;
|
||||
use std::{cell::RefCell, collections::HashMap};
|
||||
use zbus::dbus_proxy;
|
||||
|
||||
use crate::deref_cell::DerefCell;
|
||||
use crate::status_menu::StatusMenu;
|
||||
|
|
@ -14,7 +15,7 @@ use crate::status_menu::StatusMenu;
|
|||
#[derive(Default)]
|
||||
pub struct StatusAreaInner {
|
||||
box_: DerefCell<gtk4::Box>,
|
||||
watcher: OnceCell<StatusNotifierWatcher>,
|
||||
watcher: OnceCell<StatusNotifierWatcherProxy<'static>>,
|
||||
icons: RefCell<HashMap<String, StatusMenu>>,
|
||||
}
|
||||
|
||||
|
|
@ -39,35 +40,46 @@ impl ObjectImpl for StatusAreaInner {
|
|||
self.box_.set(box_);
|
||||
|
||||
glib::MainContext::default().spawn_local(clone!(@strong obj => async move {
|
||||
let watcher = match StatusNotifierWatcher::new().await {
|
||||
Ok(watcher) => watcher,
|
||||
Err(err) => {
|
||||
eprintln!("Failed to connect to 'org.kde.StatusNotifierWatcher': {}", err);
|
||||
return;
|
||||
async {
|
||||
let connection = zbus::Connection::session().await?;
|
||||
let watcher = StatusNotifierWatcherProxy::new(&connection).await?;
|
||||
|
||||
let name = connection.unique_name().unwrap().as_str();
|
||||
if let Err(err) = watcher.register_status_notifier_host(name).await {
|
||||
eprintln!("Failed to register status notifier host: {}", err);
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = watcher.register_host().await {
|
||||
eprintln!("Failed to register status notifier host: {}", err);
|
||||
}
|
||||
let mut registered_stream = watcher.receive_status_notifier_item_registered().await?;
|
||||
let mut unregistered_stream = watcher.receive_status_notifier_item_unregistered().await?;
|
||||
|
||||
for name in watcher.registered_items().await {
|
||||
glib::MainContext::default().spawn_local(clone!(@strong obj => async move {
|
||||
obj.item_registered(&name).await;
|
||||
}));
|
||||
}
|
||||
|
||||
watcher.connect_item_registered_unregistered(clone!(@strong obj => move |name, registered| {
|
||||
if registered {
|
||||
for name in watcher.registered_status_notifier_items().await? {
|
||||
glib::MainContext::default().spawn_local(clone!(@strong obj => async move {
|
||||
obj.item_registered(&name).await;
|
||||
}));
|
||||
} else {
|
||||
obj.item_unregistered(&name);
|
||||
}
|
||||
}));
|
||||
|
||||
let _ = obj.inner().watcher.set(watcher);
|
||||
glib::MainContext::default().spawn_local(clone!(@strong obj => async move {
|
||||
if let Some(evt) = registered_stream.next().await {
|
||||
if let Ok(args) = evt.args() {
|
||||
obj.item_registered(&args.name).await;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
glib::MainContext::default().spawn_local(clone!(@strong obj => async move {
|
||||
if let Some(evt) = unregistered_stream.next().await {
|
||||
if let Ok(args) = evt.args() {
|
||||
obj.item_unregistered(&args.name);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
let _ = obj.inner().watcher.set(watcher);
|
||||
|
||||
Ok::<_, zbus::Error>(())
|
||||
}.await.unwrap_or_else(|err| {
|
||||
eprintln!("Failed to connect to 'org.kde.StatusNotifierWatcher': {}", err);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
@ -114,62 +126,20 @@ impl StatusArea {
|
|||
}
|
||||
}
|
||||
|
||||
struct StatusNotifierWatcher(gio::DBusProxy);
|
||||
#[dbus_proxy(
|
||||
interface = "org.kde.StatusNotifierWatcher",
|
||||
default_service = "org.kde.StatusNotifierWatcher",
|
||||
default_path = "/StatusNotifierWatcher"
|
||||
)]
|
||||
trait StatusNotifierWatcher {
|
||||
fn register_status_notifier_host(&self, name: &str) -> zbus::Result<()>;
|
||||
|
||||
impl StatusNotifierWatcher {
|
||||
async fn new() -> Result<Self, glib::Error> {
|
||||
let proxy = gio::DBusProxy::for_bus_future(
|
||||
gio::BusType::Session,
|
||||
gio::DBusProxyFlags::NONE,
|
||||
None,
|
||||
"org.kde.StatusNotifierWatcher",
|
||||
"/StatusNotifierWatcher",
|
||||
"org.kde.StatusNotifierWatcher",
|
||||
)
|
||||
.await?;
|
||||
Ok(Self(proxy))
|
||||
}
|
||||
#[dbus_proxy(property)]
|
||||
fn registered_status_notifier_items(&self) -> zbus::Result<Vec<String>>;
|
||||
|
||||
fn property<T: glib::FromVariant>(&self, prop: &str) -> Option<T> {
|
||||
self.0.cached_property(prop)?.get()
|
||||
}
|
||||
#[dbus_proxy(signal)]
|
||||
fn status_notifier_item_registered(&self, name: &str) -> zbus::Result<()>;
|
||||
|
||||
async fn registered_items(&self) -> Vec<String> {
|
||||
self.property::<Vec<String>>("RegisteredStatusNotifierItems")
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn connect_item_registered_unregistered<F: Fn(String, bool) + 'static>(
|
||||
&self,
|
||||
f: F,
|
||||
) -> glib::SignalHandlerId {
|
||||
self.0
|
||||
.connect_local("g-signal", false, move |args| {
|
||||
let signal_args = args[3].get::<glib::Variant>().unwrap();
|
||||
match args[2].get::<String>().unwrap().as_str() {
|
||||
"StatusNotifierItemRegistered" => {
|
||||
f(signal_args.get::<(String,)>().unwrap().0, true);
|
||||
}
|
||||
"StatusNotifierItemUnregistered" => {
|
||||
f(signal_args.get::<(String,)>().unwrap().0, false);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
None
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn register_host(&self) -> Result<(), glib::Error> {
|
||||
let service = self.0.connection().unique_name().unwrap();
|
||||
self.0
|
||||
.call_future(
|
||||
"RegisterStatusNotifierHost",
|
||||
Some(&(service.as_str(),).to_variant()),
|
||||
gio::DBusCallFlags::NONE,
|
||||
1000,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
#[dbus_proxy(signal)]
|
||||
fn status_notifier_item_unregistered(&self, name: &str) -> zbus::Result<()>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
use byte_string::ByteStr;
|
||||
use cascade::cascade;
|
||||
use futures::StreamExt;
|
||||
use gtk4::{
|
||||
gdk_pixbuf, gio,
|
||||
gdk_pixbuf,
|
||||
glib::{self, clone},
|
||||
prelude::*,
|
||||
subclass::prelude::*,
|
||||
};
|
||||
use std::{borrow::Cow, cell::RefCell, collections::HashMap, fmt, io};
|
||||
use std::{cell::RefCell, collections::HashMap, io};
|
||||
use zbus::dbus_proxy;
|
||||
use zvariant::OwnedValue;
|
||||
|
||||
use crate::deref_cell::DerefCell;
|
||||
use crate::popover_container::PopoverContainer;
|
||||
|
|
@ -21,8 +23,8 @@ pub struct StatusMenuInner {
|
|||
button: DerefCell<gtk4::ToggleButton>,
|
||||
popover_container: DerefCell<PopoverContainer>,
|
||||
vbox: DerefCell<gtk4::Box>,
|
||||
item: DerefCell<StatusNotifierItem>,
|
||||
dbus_menu: DerefCell<DBusMenu>,
|
||||
item: DerefCell<StatusNotifierItemProxy<'static>>,
|
||||
dbus_menu: DerefCell<DBusMenuProxy<'static>>,
|
||||
menus: RefCell<HashMap<i32, Menu>>,
|
||||
}
|
||||
|
||||
|
|
@ -73,18 +75,38 @@ glib::wrapper! {
|
|||
}
|
||||
|
||||
impl StatusMenu {
|
||||
pub async fn new(name: &str) -> Result<Self, glib::Error> {
|
||||
let item = StatusNotifierItem::new(name).await?;
|
||||
let obj = glib::Object::new::<Self>(&[]).unwrap();
|
||||
if let Some(icon_name) = item.icon_name().as_deref() {
|
||||
obj.inner().button.set_icon_name(&icon_name);
|
||||
}
|
||||
pub async fn new(name: &str) -> zbus::Result<Self> {
|
||||
let idx = name.find('/').unwrap();
|
||||
let dest = &name[..idx];
|
||||
let path = &name[idx..];
|
||||
|
||||
let menu = item.menu().unwrap(); // XXX unwrap?
|
||||
let connection = zbus::Connection::session().await?;
|
||||
let item = StatusNotifierItemProxy::builder(&connection)
|
||||
.destination(dest.to_string())?
|
||||
.path(path.to_string())?
|
||||
.build()
|
||||
.await?;
|
||||
let obj = glib::Object::new::<Self>(&[]).unwrap();
|
||||
let icon_name = item.icon_name().await?;
|
||||
obj.inner().button.set_icon_name(&icon_name);
|
||||
|
||||
let menu = item.menu().await?;
|
||||
let menu = DBusMenuProxy::builder(&connection)
|
||||
.destination(dest.to_string())?
|
||||
.path(menu)?
|
||||
.build()
|
||||
.await?;
|
||||
let layout = menu.get_layout(0, -1, &[]).await?.1;
|
||||
|
||||
menu.connect_layout_updated(clone!(@weak obj => move |revision, parent| {
|
||||
obj.layout_updated(revision, parent);
|
||||
let mut layout_updated_stream = menu.receive_layout_updated().await?;
|
||||
glib::MainContext::default().spawn_local(clone!(@strong obj => async move {
|
||||
while let Some(evt) = layout_updated_stream.next().await {
|
||||
let args = match evt.args() {
|
||||
Ok(args) => args,
|
||||
Err(_) => { continue; },
|
||||
};
|
||||
obj.layout_updated(args.revision, args.parent);
|
||||
}
|
||||
}));
|
||||
|
||||
obj.inner().item.set(item);
|
||||
|
|
@ -140,7 +162,8 @@ impl StatusMenu {
|
|||
..set_visible(i.visible());
|
||||
};
|
||||
box_.append(&separator);
|
||||
} else if let Some(mut label) = i.label() {
|
||||
} else if let Some(label) = i.label() {
|
||||
let mut label = label.to_string();
|
||||
if let Some(toggle_state) = i.toggle_state() {
|
||||
if toggle_state != 0 {
|
||||
label = format!("✓ {}", label);
|
||||
|
|
@ -160,7 +183,7 @@ impl StatusMenu {
|
|||
};
|
||||
|
||||
if let Some(icon_data) = i.icon_data() {
|
||||
let icon_data = io::Cursor::new(icon_data);
|
||||
let icon_data = io::Cursor::new(icon_data.to_vec());
|
||||
let pixbuf = gdk_pixbuf::Pixbuf::from_read(icon_data).unwrap(); // XXX unwrap
|
||||
let image = cascade! {
|
||||
gtk4::Image::from_pixbuf(Some(&pixbuf));
|
||||
|
|
@ -182,7 +205,9 @@ impl StatusMenu {
|
|||
if close_on_click {
|
||||
self_.inner().popover_container.popdown();
|
||||
}
|
||||
self_.inner().dbus_menu.event(id, "clicked", &0.to_variant(), 0);
|
||||
glib::MainContext::default().spawn_local(clone!(@strong self_ => async move {
|
||||
let _ = self_.inner().dbus_menu.event(id, "clicked", &0.into(), 0).await;
|
||||
}))
|
||||
}));
|
||||
};
|
||||
box_.append(&button);
|
||||
|
|
@ -218,197 +243,111 @@ impl StatusMenu {
|
|||
}
|
||||
}
|
||||
|
||||
struct Layout(i32, HashMap<String, glib::Variant>, Vec<Layout>);
|
||||
#[dbus_proxy(interface = "org.kde.StatusNotifierItem")]
|
||||
trait StatusNotifierItem {
|
||||
#[dbus_proxy(property)]
|
||||
fn icon_name(&self) -> zbus::Result<String>;
|
||||
|
||||
impl fmt::Debug for Layout {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut s = f.debug_struct("Layout");
|
||||
s.field("id", &self.0);
|
||||
for (k, v) in &self.1 {
|
||||
if let Some(v) = v.get::<String>() {
|
||||
s.field(k, &v);
|
||||
} else if let Some(v) = v.get::<i32>() {
|
||||
s.field(k, &v);
|
||||
} else if let Some(v) = v.get::<bool>() {
|
||||
s.field(k, &v);
|
||||
} else if let Some(v) = v.get::<Vec<u8>>() {
|
||||
s.field(k, &ByteStr::new(&v));
|
||||
} else {
|
||||
s.field(k, v);
|
||||
}
|
||||
}
|
||||
s.field("children", &self.2);
|
||||
s.finish()
|
||||
#[dbus_proxy(property)]
|
||||
fn menu(&self) -> zbus::Result<zvariant::OwnedObjectPath>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Layout(i32, LayoutProps, Vec<Layout>);
|
||||
|
||||
impl<'a> serde::Deserialize<'a> for Layout {
|
||||
fn deserialize<D: serde::Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let (id, props, children) =
|
||||
<(i32, LayoutProps, Vec<(zvariant::Signature<'_>, Self)>)>::deserialize(deserializer)
|
||||
.unwrap();
|
||||
Ok(Self(id, props, children.into_iter().map(|x| x.1).collect()))
|
||||
}
|
||||
}
|
||||
|
||||
impl zvariant::Type for Layout {
|
||||
fn signature() -> zvariant::Signature<'static> {
|
||||
zvariant::Signature::try_from("(ia{sv}av)").unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, zvariant::DeserializeDict, zvariant::Type)]
|
||||
pub struct LayoutProps {
|
||||
#[zvariant(rename = "accessible-desc")]
|
||||
accessible_desc: Option<String>,
|
||||
#[zvariant(rename = "children-display")]
|
||||
children_display: Option<String>,
|
||||
label: Option<String>,
|
||||
enabled: Option<bool>,
|
||||
visible: Option<bool>,
|
||||
#[zvariant(rename = "type")]
|
||||
type_: Option<String>,
|
||||
#[zvariant(rename = "toggle-type")]
|
||||
toggle_type: Option<String>,
|
||||
#[zvariant(rename = "toggle-state")]
|
||||
toggle_state: Option<i32>,
|
||||
#[zvariant(rename = "icon-data")]
|
||||
icon_data: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Layout {
|
||||
fn prop<T: glib::FromVariant>(&self, name: &str) -> Option<T> {
|
||||
self.1.get(name)?.get()
|
||||
}
|
||||
|
||||
fn id(&self) -> i32 {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn accessible_desc(&self) -> Option<String> {
|
||||
self.prop("accessible-desc")
|
||||
}
|
||||
|
||||
fn children_display(&self) -> Option<String> {
|
||||
self.prop("children-display")
|
||||
}
|
||||
|
||||
fn label(&self) -> Option<String> {
|
||||
self.prop("label")
|
||||
}
|
||||
|
||||
fn enabled(&self) -> bool {
|
||||
self.prop("enabled").unwrap_or(true)
|
||||
}
|
||||
|
||||
fn visible(&self) -> bool {
|
||||
self.prop("visible").unwrap_or(true)
|
||||
}
|
||||
|
||||
fn type_(&self) -> Option<String> {
|
||||
self.prop("type")
|
||||
}
|
||||
|
||||
fn toggle_type(&self) -> Option<String> {
|
||||
self.prop("toggle-type")
|
||||
}
|
||||
|
||||
fn toggle_state(&self) -> Option<i32> {
|
||||
self.prop("toggle-state")
|
||||
}
|
||||
|
||||
fn icon_data(&self) -> Option<Vec<u8>> {
|
||||
self.prop("icon-data")
|
||||
}
|
||||
|
||||
fn children(&self) -> &[Self] {
|
||||
&self.2
|
||||
}
|
||||
}
|
||||
|
||||
impl glib::StaticVariantType for Layout {
|
||||
fn static_variant_type() -> Cow<'static, glib::VariantTy> {
|
||||
glib::VariantTy::new("(ia{sv}av)").unwrap().into()
|
||||
fn accessible_desc(&self) -> Option<&str> {
|
||||
self.1.accessible_desc.as_deref()
|
||||
}
|
||||
|
||||
fn children_display(&self) -> Option<&str> {
|
||||
self.1.children_display.as_deref()
|
||||
}
|
||||
|
||||
fn label(&self) -> Option<&str> {
|
||||
self.1.label.as_deref()
|
||||
}
|
||||
|
||||
fn enabled(&self) -> bool {
|
||||
self.1.enabled.unwrap_or(true)
|
||||
}
|
||||
|
||||
fn visible(&self) -> bool {
|
||||
self.1.visible.unwrap_or(true)
|
||||
}
|
||||
|
||||
fn type_(&self) -> Option<&str> {
|
||||
self.1.type_.as_deref()
|
||||
}
|
||||
|
||||
fn toggle_type(&self) -> Option<&str> {
|
||||
self.1.toggle_type.as_deref()
|
||||
}
|
||||
|
||||
fn toggle_state(&self) -> Option<i32> {
|
||||
self.1.toggle_state
|
||||
}
|
||||
|
||||
fn icon_data(&self) -> Option<&[u8]> {
|
||||
self.1.icon_data.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl glib::FromVariant for Layout {
|
||||
fn from_variant(variant: &glib::Variant) -> Option<Self> {
|
||||
let (id, props, children) = variant.get::<(_, _, Vec<glib::Variant>)>()?;
|
||||
let children = children.iter().filter_map(Self::from_variant).collect();
|
||||
Some(Self(id, props, children))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct DBusMenu(gio::DBusProxy);
|
||||
|
||||
impl DBusMenu {
|
||||
async fn new(dest: &str, path: &str) -> Result<Self, glib::Error> {
|
||||
let proxy = gio::DBusProxy::for_bus_future(
|
||||
gio::BusType::Session,
|
||||
gio::DBusProxyFlags::NONE,
|
||||
None,
|
||||
dest,
|
||||
path,
|
||||
"com.canonical.dbusmenu",
|
||||
)
|
||||
.await?;
|
||||
Ok(Self(proxy))
|
||||
}
|
||||
|
||||
async fn get_layout(
|
||||
#[dbus_proxy(interface = "com.canonical.dbusmenu")]
|
||||
trait DBusMenu {
|
||||
fn get_layout(
|
||||
&self,
|
||||
parent: i32,
|
||||
depth: i32,
|
||||
properties: &[&str],
|
||||
) -> Result<(u32, Layout), glib::Error> {
|
||||
// XXX unwrap
|
||||
Ok(self
|
||||
.0
|
||||
.call_future(
|
||||
"GetLayout",
|
||||
Some(&(parent, depth, properties).to_variant()),
|
||||
gio::DBusCallFlags::NONE,
|
||||
1000,
|
||||
)
|
||||
.await?
|
||||
.get()
|
||||
.unwrap())
|
||||
}
|
||||
parent_id: i32,
|
||||
recursion_depth: i32,
|
||||
property_names: &[&str],
|
||||
) -> zbus::Result<(u32, Layout)>;
|
||||
|
||||
fn event(&self, id: i32, eventid: &str, data: &glib::Variant, timestamp: u32) {
|
||||
let res = self.0.call_future(
|
||||
"Event",
|
||||
Some(&(id, eventid, data, timestamp).to_variant()),
|
||||
gio::DBusCallFlags::NONE,
|
||||
1000,
|
||||
);
|
||||
glib::MainContext::default().spawn_local(async move {
|
||||
if let Err(err) = res.await {
|
||||
eprintln!("Failed to call `Event`: {}", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
fn event(&self, id: i32, event_id: &str, data: &OwnedValue, timestamp: u32)
|
||||
-> zbus::Result<()>;
|
||||
|
||||
fn connect_layout_updated<F: Fn(u32, i32) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
||||
self.0
|
||||
.connect_local("g-signal", false, move |args| {
|
||||
if &args[2].get::<String>().unwrap() == "LayoutUpdated" {
|
||||
// XXX unwrap
|
||||
let (revision, parent) = args[3].get::<glib::Variant>().unwrap().get().unwrap();
|
||||
f(revision, parent);
|
||||
}
|
||||
None
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
struct StatusNotifierItem(gio::DBusProxy, Option<DBusMenu>);
|
||||
|
||||
impl StatusNotifierItem {
|
||||
async fn new(name: &str) -> Result<Self, glib::Error> {
|
||||
let idx = name.find('/').unwrap();
|
||||
let dest = &name[..idx];
|
||||
let path = &name[idx..];
|
||||
let proxy = gio::DBusProxy::for_bus_future(
|
||||
gio::BusType::Session,
|
||||
gio::DBusProxyFlags::NONE,
|
||||
None,
|
||||
dest,
|
||||
path,
|
||||
"org.kde.StatusNotifierItem",
|
||||
)
|
||||
.await?;
|
||||
let menu_path = proxy
|
||||
.cached_property("Menu")
|
||||
.and_then(|x| x.get::<String>());
|
||||
let menu = if let Some(menu_path) = menu_path {
|
||||
Some(DBusMenu::new(dest, &menu_path).await?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(Self(proxy, menu))
|
||||
}
|
||||
|
||||
fn property<T: glib::FromVariant>(&self, prop: &str) -> Option<T> {
|
||||
self.0.cached_property(prop)?.get()
|
||||
}
|
||||
|
||||
fn icon_name(&self) -> Option<String> {
|
||||
// TODO: IconThemePath? AttentionIconName?
|
||||
self.property("IconName")
|
||||
}
|
||||
|
||||
fn menu(&self) -> Option<DBusMenu> {
|
||||
self.1.clone()
|
||||
}
|
||||
#[dbus_proxy(signal)]
|
||||
fn layout_updated(&self, revision: u32, parent: i32) -> zbus::Result<()>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,106 +1,70 @@
|
|||
use gtk4::{
|
||||
gio,
|
||||
glib::{self, clone},
|
||||
prelude::*,
|
||||
};
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
use zbus::{dbus_interface, MessageHeader, Result, SignalContext};
|
||||
|
||||
static STATUS_NOTIFIER_XML: &str = "
|
||||
<node name='/StatusNotifierWatcher'>
|
||||
<interface name='org.kde.StatusNotifierWatcher'>
|
||||
<method name='RegisterStatusNotifierItem'>
|
||||
<arg name='service' type='s' direction='in' />
|
||||
</method>
|
||||
use crate::dbus_service;
|
||||
|
||||
<method name='RegisterStatusNotifierHost'>
|
||||
<arg name='service' type='s' direction='in' />
|
||||
</method>
|
||||
|
||||
<property name='RegisteredStatusNotifierItems' type='as' access='read' />
|
||||
<property name='IsStatusNotifierHostRegistered' type='b' access='read' />
|
||||
<property name='ProtocolVersion' type='i' access='read' />
|
||||
|
||||
<signal name='StatusNotifierItemRegistered'>
|
||||
<arg type='s' name='service' direction='out' />
|
||||
</signal>
|
||||
|
||||
<signal name='StatusNotifierItemUnregistered'>
|
||||
<arg type='s' name='service' direction='out' />
|
||||
</signal>
|
||||
|
||||
<signal name='StatusNotifierHostRegistered' />
|
||||
|
||||
<signal name='StatusNotifierHostUnregistered' />
|
||||
</interface>
|
||||
</node>
|
||||
";
|
||||
|
||||
pub fn start() {
|
||||
gio::bus_own_name(
|
||||
gio::BusType::Session,
|
||||
"org.kde.StatusNotifierWatcher",
|
||||
gio::BusNameOwnerFlags::NONE,
|
||||
bus_acquired,
|
||||
name_acquired,
|
||||
name_lost,
|
||||
);
|
||||
#[derive(Default)]
|
||||
struct StatusNotifierWatcher {
|
||||
items: Arc<Mutex<Vec<String>>>,
|
||||
}
|
||||
|
||||
fn bus_acquired(_connection: gio::DBusConnection, _name: &str) {}
|
||||
|
||||
fn name_acquired(connection: gio::DBusConnection, _name: &str) {
|
||||
let introspection_data = gio::DBusNodeInfo::for_xml(STATUS_NOTIFIER_XML).unwrap();
|
||||
let interface_info = introspection_data
|
||||
.lookup_interface("org.kde.StatusNotifierWatcher")
|
||||
.unwrap();
|
||||
let items = Arc::new(Mutex::new(Vec::<String>::new()));
|
||||
let method_call = clone!(@strong items => move |connection: gio::DBusConnection,
|
||||
sender: &str,
|
||||
path: &str,
|
||||
interface: &str,
|
||||
method: &str,
|
||||
args: glib::Variant,
|
||||
invocation: gio::DBusMethodInvocation| {
|
||||
match method {
|
||||
"RegisterStatusNotifierItem" => {
|
||||
let (service,) = args.get::<(String,)>().unwrap();
|
||||
let service = format!("{}{}", sender, service);
|
||||
connection.emit_signal(None, path, interface, "StatusNotifierItemRegistered", Some(&(&service,).to_variant())).unwrap();
|
||||
// XXX emit unreigstered
|
||||
items.lock().unwrap().push(service);
|
||||
invocation.return_value(None);
|
||||
}
|
||||
"RegisterStatusNotifierHost" => {
|
||||
let (_service,) = args.get::<(String,)>().unwrap();
|
||||
// XXX emit registed/unregistered
|
||||
invocation.return_value(None);
|
||||
}
|
||||
_ => unreachable!()
|
||||
}
|
||||
});
|
||||
let get_property = clone!(@strong items => move |_: gio::DBusConnection, _sender: &str, _path: &str, _interface: &str, prop: &str| {
|
||||
match prop {
|
||||
"RegisteredStatusNotifierItems" => items.lock().unwrap().to_variant(),
|
||||
"IsStatusNotifierHostRegistered" => true.to_variant(),
|
||||
"ProtocolVersion" => 0i32.to_variant(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
});
|
||||
let set_property = |_: gio::DBusConnection,
|
||||
_sender: &str,
|
||||
_path: &str,
|
||||
_interface: &str,
|
||||
_prop: &str,
|
||||
_value: glib::Variant| { unreachable!() };
|
||||
if let Err(err) = connection.register_object(
|
||||
"/StatusNotifierWatcher",
|
||||
&interface_info,
|
||||
method_call,
|
||||
get_property,
|
||||
set_property,
|
||||
#[dbus_interface(name = "org.kde.StatusNotifierWatcher")]
|
||||
impl StatusNotifierWatcher {
|
||||
async fn RegisterStatusNotifierItem(
|
||||
&self,
|
||||
service: &str,
|
||||
#[zbus(header)] hdr: MessageHeader<'_>,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
) {
|
||||
eprintln!("Failed to register object: {}", err);
|
||||
let service = format!("{}{}", hdr.sender().unwrap().unwrap(), service);
|
||||
Self::StatusNotifierItemRegistered(&ctxt, &service)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// XXX emit unreigstered
|
||||
self.items.lock().unwrap().push(service);
|
||||
}
|
||||
|
||||
fn RegisterStatusNotifierHost(&self, _service: &str) {
|
||||
// XXX emit registed/unregistered
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
fn RegisteredStatusNotifierItems(&self) -> Vec<String> {
|
||||
self.items.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
fn IsStatusNotifierHostRegistered(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
fn ProtocolVersion(&self) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn StatusNotifierItemRegistered(ctxt: &SignalContext<'_>, service: &str) -> Result<()>;
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn StatusNotifierItemUnregistered(ctxt: &SignalContext<'_>, service: &str) -> Result<()>;
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn StatusNotifierHostRegistered(ctxt: &SignalContext<'_>) -> Result<()>;
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
async fn StatusNotifierHostUnregistered(ctxt: &SignalContext<'_>) -> Result<()>;
|
||||
}
|
||||
|
||||
pub async fn start() {
|
||||
if let Err(err) = dbus_service::create("org.kde.StatusNotifierWatcher", |builder| {
|
||||
builder.serve_at("/StatusNotifierWatcher", StatusNotifierWatcher::default())
|
||||
})
|
||||
.await
|
||||
{
|
||||
eprintln!("Failed to start `StatusNotifierWatcher` service: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
fn name_lost(_connection: Option<gio::DBusConnection>, _name: &str) {}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue