Start implementing media controls
This commit is contained in:
parent
6b64b2e998
commit
4917422f21
6 changed files with 246 additions and 1 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -763,6 +763,7 @@ dependencies = [
|
|||
"chrono",
|
||||
"gdk4-x11",
|
||||
"gtk4",
|
||||
"once_cell",
|
||||
"toml",
|
||||
"x11",
|
||||
"zbus",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ cascade = "1"
|
|||
chrono = "0.4"
|
||||
gdk4-x11 = "0.2"
|
||||
gtk4 = "0.2"
|
||||
once_cell = "1"
|
||||
toml = "0.5"
|
||||
x11 = { version = "2", features = ["xlib"] }
|
||||
zbus = "1"
|
||||
|
|
|
|||
31
src/deref_cell.rs
Normal file
31
src/deref_cell.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
use gtk4::{gdk, glib, prelude::*};
|
||||
|
||||
mod deref_cell;
|
||||
mod mpris;
|
||||
mod window;
|
||||
mod x;
|
||||
|
||||
|
|
|
|||
205
src/mpris.rs
Normal file
205
src/mpris.rs
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
use cascade::cascade;
|
||||
use gtk4::{
|
||||
gio,
|
||||
glib::{self, clone},
|
||||
prelude::*,
|
||||
subclass::prelude::*,
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
|
||||
use crate::deref_cell::DerefCell;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MprisControlsInner {
|
||||
box_: DerefCell<gtk4::Box>,
|
||||
backward_button: DerefCell<gtk4::Button>,
|
||||
play_pause_button: DerefCell<gtk4::Button>,
|
||||
forward_button: DerefCell<gtk4::Button>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for MprisControlsInner {
|
||||
const NAME: &'static str = "S76MprisControls";
|
||||
type ParentType = gtk4::Widget;
|
||||
type Type = MprisControls;
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
klass.set_layout_manager_type::<gtk4::BinLayout>();
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for MprisControlsInner {
|
||||
fn constructed(&self, obj: &MprisControls) {
|
||||
let backward_button = cascade! {
|
||||
gtk4::Button::from_icon_name(Some("media-skip-backward-symbolic"));
|
||||
..connect_clicked(|_| {});
|
||||
};
|
||||
|
||||
let play_pause_button = cascade! {
|
||||
gtk4::Button::from_icon_name(Some("media-playback-start-symbolic"));
|
||||
..connect_clicked(|_| {});
|
||||
};
|
||||
|
||||
let forward_button = cascade! {
|
||||
gtk4::Button::from_icon_name(Some("media-skip-forward-symbolic"));
|
||||
..connect_clicked(|_| {});
|
||||
};
|
||||
|
||||
let box_ = cascade! {
|
||||
gtk4::Box::new(gtk4::Orientation::Horizontal, 0);
|
||||
..set_parent(obj);
|
||||
..append(&backward_button);
|
||||
..append(&play_pause_button);
|
||||
..append(&forward_button);
|
||||
};
|
||||
|
||||
/*
|
||||
glib::MainContext::default().spawn_local(clone!(@strong obj => async move {
|
||||
// XXX unwrap
|
||||
let dbus = DBus::new().await.unwrap();
|
||||
dbus.connect_name_owner_changed(|a, b, c| {
|
||||
println!("{:?}", (a, b, c));
|
||||
});
|
||||
for name in dbus.list_names().await.unwrap() {
|
||||
if !name.starts_with("org.mpris.MediaPlayer2.") {
|
||||
continue;
|
||||
}
|
||||
let player = Player::new(&name).await.unwrap();
|
||||
|
||||
println!("{}", name);
|
||||
let status = player.playback_status();
|
||||
let metadata = player.metadata().unwrap();
|
||||
let title = metadata.title();
|
||||
let album = metadata.album();
|
||||
let artist = metadata.artist();
|
||||
let art = metadata.arturl();
|
||||
println!("{:?}", (title, art));
|
||||
}
|
||||
std::mem::forget(dbus);
|
||||
}));
|
||||
*/
|
||||
|
||||
self.box_.set(box_);
|
||||
self.backward_button.set(backward_button);
|
||||
self.play_pause_button.set(play_pause_button);
|
||||
self.forward_button.set(forward_button);
|
||||
}
|
||||
|
||||
fn dispose(&self, obj: &MprisControls) {
|
||||
self.box_.unparent();
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for MprisControlsInner {}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct MprisControls(ObjectSubclass<MprisControlsInner>)
|
||||
@extends gtk4::Widget;
|
||||
}
|
||||
|
||||
impl MprisControls {
|
||||
pub fn new() -> Self {
|
||||
glib::Object::new(&[]).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn dict_lookup<T: glib::FromVariant>(dict: &glib::VariantDict, key: &str) -> Option<T> {
|
||||
dict.lookup_value(key, None)?.get()
|
||||
}
|
||||
|
||||
fn property<T: glib::FromVariant>(proxy: &gio::DBusProxy, prop: &str) -> Option<T> {
|
||||
proxy.cached_property(prop)?.get()
|
||||
}
|
||||
|
||||
struct Metadata(glib::VariantDict);
|
||||
|
||||
impl Metadata {
|
||||
fn title(&self) -> Option<String> {
|
||||
dict_lookup(&self.0, "xesam:title")
|
||||
}
|
||||
|
||||
fn album(&self) -> Option<String> {
|
||||
dict_lookup(&self.0, "xesam:album")
|
||||
}
|
||||
|
||||
fn artist(&self) -> Option<Vec<String>> {
|
||||
dict_lookup(&self.0, "xesam:artist")
|
||||
}
|
||||
|
||||
fn arturl(&self) -> Option<String> {
|
||||
dict_lookup(&self.0, "mpris:artUrl")
|
||||
}
|
||||
}
|
||||
|
||||
struct Player(gio::DBusProxy);
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
fn connect_properties_changed<F: Fn() + 'static>(&self, f: F) {
|
||||
self.0
|
||||
.connect_local("g-properties-changed", false, move |_| {
|
||||
f();
|
||||
None
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn playback_status(&self) -> Option<String> {
|
||||
property(&self.0, "PlayBackStatus")
|
||||
}
|
||||
|
||||
fn metadata(&self) -> Option<Metadata> {
|
||||
Some(Metadata(property(&self.0, "Metadata")?))
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ use cascade::cascade;
|
|||
use glib::clone;
|
||||
use gtk4::{gdk, glib, prelude::*};
|
||||
|
||||
use crate::mpris::MprisControls;
|
||||
use crate::x;
|
||||
|
||||
pub fn window(monitor: gdk::Monitor) -> gtk4::Window {
|
||||
|
|
@ -11,7 +12,11 @@ pub fn window(monitor: gdk::Monitor) -> gtk4::Window {
|
|||
..set_popover(Some(&cascade! {
|
||||
gtk4::Popover::new();
|
||||
..set_child(Some(&cascade! {
|
||||
gtk4::Calendar::new();
|
||||
gtk4::Box::new(gtk4::Orientation::Horizontal, 0);
|
||||
..append(&MprisControls::new());
|
||||
..append(&cascade! {
|
||||
gtk4::Calendar::new();
|
||||
});
|
||||
}));
|
||||
}));
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue