Remove mpris from old-panel
Similar functionality should already be implemented in the audio applet.
This commit is contained in:
parent
04ce88e4ce
commit
fb321a2e05
5 changed files with 0 additions and 425 deletions
|
|
@ -1,16 +0,0 @@
|
||||||
use std::str::FromStr;
|
|
||||||
use toml_edit::{Document, Table, Array, TomlError};
|
|
||||||
|
|
||||||
struct Buttons<'a>(&'a Table);
|
|
||||||
|
|
||||||
struct Config(Document);
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
fn new(s: &str) -> Result<Self, TomlError> {
|
|
||||||
Ok(Self(Document::from_str(s)?))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn buttons(&self) -> Option<Buttons> {
|
|
||||||
Some(Buttons(self.0.as_table().get("buttons")?.as_table()?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,8 +2,6 @@ use gtk4::{glib, prelude::*};
|
||||||
|
|
||||||
mod application;
|
mod application;
|
||||||
mod deref_cell;
|
mod deref_cell;
|
||||||
mod mpris;
|
|
||||||
mod mpris_player;
|
|
||||||
mod popover_container;
|
mod popover_container;
|
||||||
mod time_button;
|
mod time_button;
|
||||||
mod window;
|
mod window;
|
||||||
|
|
|
||||||
|
|
@ -1,134 +0,0 @@
|
||||||
use cascade::cascade;
|
|
||||||
use futures::stream::StreamExt;
|
|
||||||
use gtk4::{
|
|
||||||
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;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct MprisControlsInner {
|
|
||||||
listbox: DerefCell<gtk4::ListBox>,
|
|
||||||
dbus: OnceCell<DBusProxy<'static>>,
|
|
||||||
players: RefCell<HashMap<String, MprisPlayer>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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 listbox = cascade! {
|
|
||||||
gtk4::ListBox::new();
|
|
||||||
..set_parent(obj);
|
|
||||||
};
|
|
||||||
|
|
||||||
glib::MainContext::default().spawn_local(clone!(@strong obj => async move {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
match dbus.list_names().await {
|
|
||||||
Ok(names) => for name in names {
|
|
||||||
if name.starts_with("org.mpris.MediaPlayer2.") {
|
|
||||||
glib::MainContext::default().spawn_local(clone!(@strong obj => async move {
|
|
||||||
obj.player_added(&name).await;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => eprintln!("Failed to call 'ListNames: {}'", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = obj.inner().dbus.set(dbus);
|
|
||||||
}));
|
|
||||||
|
|
||||||
self.listbox.set(listbox);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dispose(&self, _obj: &MprisControls) {
|
|
||||||
self.listbox.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 inner(&self) -> &MprisControlsInner {
|
|
||||||
MprisControlsInner::from_instance(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn player_added(&self, name: &str) {
|
|
||||||
let player = match MprisPlayer::new(&name).await {
|
|
||||||
Ok(player) => player,
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("Failed to connect to '{}': {}", name, err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let row = cascade! {
|
|
||||||
gtk4::ListBoxRow::new();
|
|
||||||
..set_selectable(false);
|
|
||||||
..set_child(Some(&player));
|
|
||||||
};
|
|
||||||
self.inner().listbox.append(&row);
|
|
||||||
|
|
||||||
self.inner()
|
|
||||||
.players
|
|
||||||
.borrow_mut()
|
|
||||||
.insert(name.to_owned(), player.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn player_removed(&self, name: &str) {
|
|
||||||
self.inner().players.borrow_mut().remove(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,264 +0,0 @@
|
||||||
use cascade::cascade;
|
|
||||||
use futures::StreamExt;
|
|
||||||
use gtk4::{
|
|
||||||
gdk_pixbuf, gio,
|
|
||||||
glib::{self, clone},
|
|
||||||
pango,
|
|
||||||
prelude::*,
|
|
||||||
subclass::prelude::*,
|
|
||||||
};
|
|
||||||
use std::{cell::RefCell, collections::HashMap};
|
|
||||||
use zbus::dbus_proxy;
|
|
||||||
use zvariant::OwnedValue;
|
|
||||||
|
|
||||||
use crate::deref_cell::DerefCell;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct MprisPlayerInner {
|
|
||||||
box_: DerefCell<gtk4::Box>,
|
|
||||||
backward_button: DerefCell<gtk4::Button>,
|
|
||||||
play_pause_button: DerefCell<gtk4::Button>,
|
|
||||||
forward_button: DerefCell<gtk4::Button>,
|
|
||||||
player: DerefCell<PlayerProxy<'static>>,
|
|
||||||
image: DerefCell<gtk4::Image>,
|
|
||||||
image_uri: RefCell<Option<String>>,
|
|
||||||
title_label: DerefCell<gtk4::Label>,
|
|
||||||
artist_label: DerefCell<gtk4::Label>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[glib::object_subclass]
|
|
||||||
impl ObjectSubclass for MprisPlayerInner {
|
|
||||||
const NAME: &'static str = "S76MprisPlayer";
|
|
||||||
type ParentType = gtk4::Widget;
|
|
||||||
type Type = MprisPlayer;
|
|
||||||
|
|
||||||
fn class_init(klass: &mut Self::Class) {
|
|
||||||
klass.set_layout_manager_type::<gtk4::BinLayout>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ObjectImpl for MprisPlayerInner {
|
|
||||||
fn constructed(&self, obj: &MprisPlayer) {
|
|
||||||
let image = cascade! {
|
|
||||||
gtk4::Image::new();
|
|
||||||
..set_pixel_size(64);
|
|
||||||
};
|
|
||||||
|
|
||||||
let title_label = cascade! {
|
|
||||||
gtk4::Label::new(None);
|
|
||||||
..set_halign(gtk4::Align::Start);
|
|
||||||
..set_ellipsize(pango::EllipsizeMode::End);
|
|
||||||
..set_max_width_chars(20);
|
|
||||||
..set_attributes(Some(&cascade! {
|
|
||||||
pango::AttrList::new();
|
|
||||||
..insert(pango::AttrInt::new_weight(pango::Weight::Bold));
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
let artist_label = cascade! {
|
|
||||||
gtk4::Label::new(None);
|
|
||||||
..set_halign(gtk4::Align::Start);
|
|
||||||
..set_ellipsize(pango::EllipsizeMode::End);
|
|
||||||
..set_max_width_chars(20);
|
|
||||||
};
|
|
||||||
|
|
||||||
let backward_button = cascade! {
|
|
||||||
gtk4::Button::from_icon_name("media-skip-backward-symbolic");
|
|
||||||
..connect_clicked(clone!(@strong obj => move |_| obj.call("Previous")));
|
|
||||||
};
|
|
||||||
|
|
||||||
let play_pause_button = cascade! {
|
|
||||||
gtk4::Button::from_icon_name("media-playback-start-symbolic");
|
|
||||||
..connect_clicked(clone!(@strong obj => move |_| obj.call("PlayPause")));
|
|
||||||
};
|
|
||||||
|
|
||||||
let forward_button = cascade! {
|
|
||||||
gtk4::Button::from_icon_name("media-skip-forward-symbolic");
|
|
||||||
..connect_clicked(clone!(@strong obj => move |_| obj.call("Next")));
|
|
||||||
};
|
|
||||||
|
|
||||||
let box_ = cascade! {
|
|
||||||
gtk4::Box::new(gtk4::Orientation::Horizontal, 6);
|
|
||||||
..set_parent(obj);
|
|
||||||
..append(&image);
|
|
||||||
..append(&cascade! {
|
|
||||||
gtk4::Box::new(gtk4::Orientation::Vertical, 0);
|
|
||||||
..append(&title_label);
|
|
||||||
..append(&artist_label);
|
|
||||||
..append(&cascade! {
|
|
||||||
gtk4::Box::new(gtk4::Orientation::Horizontal, 0);
|
|
||||||
..set_valign(gtk4::Align::Start);
|
|
||||||
..append(&backward_button);
|
|
||||||
..append(&play_pause_button);
|
|
||||||
..append(&forward_button);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
self.box_.set(box_);
|
|
||||||
self.backward_button.set(backward_button);
|
|
||||||
self.play_pause_button.set(play_pause_button);
|
|
||||||
self.forward_button.set(forward_button);
|
|
||||||
self.image.set(image);
|
|
||||||
self.title_label.set(title_label);
|
|
||||||
self.artist_label.set(artist_label);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dispose(&self, _obj: &MprisPlayer) {
|
|
||||||
self.box_.unparent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WidgetImpl for MprisPlayerInner {}
|
|
||||||
|
|
||||||
glib::wrapper! {
|
|
||||||
pub struct MprisPlayer(ObjectSubclass<MprisPlayerInner>)
|
|
||||||
@extends gtk4::Widget;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MprisPlayer {
|
|
||||||
pub async fn new(name: &str) -> zbus::Result<Self> {
|
|
||||||
let obj = glib::Object::new::<Self>(&[]).unwrap();
|
|
||||||
|
|
||||||
let connection = zbus::Connection::session().await?;
|
|
||||||
let player = PlayerProxy::builder(&connection)
|
|
||||||
.destination(name.to_string())?
|
|
||||||
.build()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut status_stream = player.receive_playback_status_changed().await;
|
|
||||||
let mut metadata_stream = player.receive_metadata_changed().await;
|
|
||||||
|
|
||||||
obj.inner().player.set(player);
|
|
||||||
|
|
||||||
glib::MainContext::default().spawn_local(clone!(@strong obj => async move {
|
|
||||||
while status_stream.next().await.is_some() {
|
|
||||||
obj.update_status();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
glib::MainContext::default().spawn_local(clone!(@strong obj => async move {
|
|
||||||
while metadata_stream.next().await.is_some() {
|
|
||||||
obj.update_metadata();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
Ok(obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn inner(&self) -> &MprisPlayerInner {
|
|
||||||
MprisPlayerInner::from_instance(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
eprintln!("Failed to call '{}': {}", method, err);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn update_arturl(&self, arturl: Option<&str>) {
|
|
||||||
let mut image_uri = self.inner().image_uri.borrow_mut();
|
|
||||||
if image_uri.as_deref() == arturl {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
*image_uri = arturl.map(String::from);
|
|
||||||
drop(image_uri);
|
|
||||||
|
|
||||||
let pixbuf = async {
|
|
||||||
// TODO: Security?
|
|
||||||
let file = gio::File::for_uri(&arturl?);
|
|
||||||
let stream = file.read_future(glib::PRIORITY_DEFAULT).await.ok()?;
|
|
||||||
gdk_pixbuf::Pixbuf::from_stream_future(&stream).await.ok()
|
|
||||||
}
|
|
||||||
.await;
|
|
||||||
if let Some(pixbuf) = pixbuf {
|
|
||||||
self.inner().image.set_from_pixbuf(Some(&pixbuf));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_status(&self) {
|
|
||||||
let status = match self.inner().player.cached_playback_status() {
|
|
||||||
Ok(Some(status)) => status,
|
|
||||||
_ => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let play_pause_icon = if status == "Playing" {
|
|
||||||
"media-playback-pause-symbolic"
|
|
||||||
} else {
|
|
||||||
"media-playback-start-symbolic"
|
|
||||||
};
|
|
||||||
|
|
||||||
self.inner()
|
|
||||||
.play_pause_button
|
|
||||||
.set_icon_name(play_pause_icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_metadata(&self) {
|
|
||||||
let metadata = match self.inner().player.cached_metadata() {
|
|
||||||
Ok(Some(metadata)) => metadata,
|
|
||||||
_ => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let title = metadata.title().unwrap_or_else(|| String::new());
|
|
||||||
// XXX correct way to handle multiple?
|
|
||||||
let artist = metadata
|
|
||||||
.artist()
|
|
||||||
.and_then(|x| x.get(0).cloned())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let _album = metadata.album(); // TODO
|
|
||||||
|
|
||||||
let arturl = metadata.arturl();
|
|
||||||
glib::MainContext::default().spawn_local(clone!(@strong self as self_ => async move {
|
|
||||||
self_.update_arturl(arturl.as_deref()).await;
|
|
||||||
}));
|
|
||||||
|
|
||||||
self.inner().title_label.set_label(&title);
|
|
||||||
self.inner().artist_label.set_label(&artist);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Metadata(HashMap<String, OwnedValue>);
|
|
||||||
|
|
||||||
impl TryFrom<OwnedValue> for Metadata {
|
|
||||||
type Error = zbus::Error;
|
|
||||||
|
|
||||||
fn try_from(value: OwnedValue) -> zbus::Result<Self> {
|
|
||||||
Ok(Self(value.try_into()?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Metadata {
|
|
||||||
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> {
|
|
||||||
self.lookup("xesam:title")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn album(&self) -> Option<String> {
|
|
||||||
self.lookup("xesam:album")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn artist(&self) -> Option<Vec<String>> {
|
|
||||||
self.lookup("xesam:artist")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn arturl(&self) -> Option<String> {
|
|
||||||
self.lookup("mpris:artUrl")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[dbus_proxy(
|
|
||||||
interface = "org.mpris.MediaPlayer2.Player",
|
|
||||||
default_path = "/org/mpris/MediaPlayer2"
|
|
||||||
)]
|
|
||||||
trait Player {
|
|
||||||
#[dbus_proxy(property)]
|
|
||||||
fn metadata(&self) -> zbus::Result<Metadata>;
|
|
||||||
|
|
||||||
#[dbus_proxy(property)]
|
|
||||||
fn playback_status(&self) -> zbus::Result<String>;
|
|
||||||
}
|
|
||||||
|
|
@ -8,7 +8,6 @@ use gtk4::{
|
||||||
|
|
||||||
use crate::application::PanelApp;
|
use crate::application::PanelApp;
|
||||||
use crate::deref_cell::DerefCell;
|
use crate::deref_cell::DerefCell;
|
||||||
use crate::mpris::MprisControls;
|
|
||||||
use crate::popover_container::PopoverContainer;
|
use crate::popover_container::PopoverContainer;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -16,7 +15,6 @@ pub struct TimeButtonInner {
|
||||||
calendar: DerefCell<gtk4::Calendar>,
|
calendar: DerefCell<gtk4::Calendar>,
|
||||||
button: DerefCell<gtk4::ToggleButton>,
|
button: DerefCell<gtk4::ToggleButton>,
|
||||||
label: DerefCell<gtk4::Label>,
|
label: DerefCell<gtk4::Label>,
|
||||||
left_box: DerefCell<gtk4::Box>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
|
|
@ -50,17 +48,11 @@ impl ObjectImpl for TimeButtonInner {
|
||||||
..set_child(Some(&label));
|
..set_child(Some(&label));
|
||||||
};
|
};
|
||||||
|
|
||||||
let left_box = cascade! {
|
|
||||||
gtk4::Box::new(gtk4::Orientation::Vertical, 0);
|
|
||||||
..append(&MprisControls::new());
|
|
||||||
};
|
|
||||||
|
|
||||||
cascade! {
|
cascade! {
|
||||||
PopoverContainer::new(&button);
|
PopoverContainer::new(&button);
|
||||||
..set_parent(obj);
|
..set_parent(obj);
|
||||||
..popover().set_child(Some(&cascade! {
|
..popover().set_child(Some(&cascade! {
|
||||||
gtk4::Box::new(gtk4::Orientation::Horizontal, 0);
|
gtk4::Box::new(gtk4::Orientation::Horizontal, 0);
|
||||||
..append(&left_box);
|
|
||||||
..append(&calendar);
|
..append(&calendar);
|
||||||
}));
|
}));
|
||||||
..popover().connect_show(clone!(@strong obj => move |_| obj.opening()));
|
..popover().connect_show(clone!(@strong obj => move |_| obj.opening()));
|
||||||
|
|
@ -70,7 +62,6 @@ impl ObjectImpl for TimeButtonInner {
|
||||||
self.calendar.set(calendar);
|
self.calendar.set(calendar);
|
||||||
self.button.set(button);
|
self.button.set(button);
|
||||||
self.label.set(label);
|
self.label.set(label);
|
||||||
self.left_box.set(left_box);
|
|
||||||
|
|
||||||
// TODO: better way to do this?
|
// TODO: better way to do this?
|
||||||
glib::timeout_add_seconds_local(
|
glib::timeout_add_seconds_local(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue