Replace mpris crate with own implementation
This commit is contained in:
43
Cargo.lock
generated
43
Cargo.lock
generated
@@ -1170,17 +1170,6 @@ version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06"
|
||||
|
||||
[[package]]
|
||||
name = "dbus"
|
||||
version = "0.9.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libdbus-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ddc"
|
||||
version = "0.2.2"
|
||||
@@ -1493,17 +1482,6 @@ version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099"
|
||||
|
||||
[[package]]
|
||||
name = "enum-kinds"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e40a16955681d469ab3da85aaa6b42ff656b3c67b52e1d8d3dd36afe97fd462"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum_dispatch"
|
||||
version = "0.3.13"
|
||||
@@ -2777,7 +2755,6 @@ dependencies = [
|
||||
"libc",
|
||||
"libpulse-binding",
|
||||
"meval",
|
||||
"mpris",
|
||||
"nmrs",
|
||||
"notify",
|
||||
"nucleo-matcher",
|
||||
@@ -2946,15 +2923,6 @@ version = "0.2.191"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
|
||||
|
||||
[[package]]
|
||||
name = "libdbus-sys"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043"
|
||||
dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.9"
|
||||
@@ -3338,17 +3306,6 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mpris"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b72fbda9340cad2ca8181eabd8b96a2d8f4386e1ac9eefcf4045fc4e97d1ab7b"
|
||||
dependencies = [
|
||||
"dbus",
|
||||
"enum-kinds",
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multimap"
|
||||
version = "0.10.1"
|
||||
|
||||
@@ -18,7 +18,6 @@ gtk4-layer-shell = "0.8.1"
|
||||
hyprland = { git = "https://github.com/romanstingler/hyprland-rs-next", branch = "master" }
|
||||
notify = "8.2.0"
|
||||
glib = "0.22.10"
|
||||
mpris = "2.1.0"
|
||||
time-format = "1.2.4"
|
||||
nmrs = "3.5.7"
|
||||
tokio = { version = "1.53.3", features = ["rt", "time", "net", "sync", "rt-multi-thread", "io-util", "macros", "signal"] }
|
||||
@@ -42,13 +41,12 @@ battery = "0.7.8"
|
||||
ddc-hi = { git = "https://github.com/jmb05/ddc-hi-rs" }
|
||||
wayland-protocols = { version = "0.32.13", features = ["staging", "client"] }
|
||||
wayland-client = "0.31.15"
|
||||
zbus = "5.19.0"
|
||||
zbus = "5.19.0" # Keep at this version for now because other dependencies also have it
|
||||
serde = { version = "1.0.229", features = ["derive"] }
|
||||
freedesktop-desktop-entry = "0.8.4"
|
||||
nucleo-matcher = "0.3.1"
|
||||
meval = "0.2.0"
|
||||
toml = "1.1.8"
|
||||
#zbus = "6.1.0"
|
||||
|
||||
[build-dependencies]
|
||||
glib-build-tools = "0.22.8"
|
||||
|
||||
@@ -4,7 +4,8 @@ Additionally, it acts as an idle daemon. It is written in rust and uses Gtk 4 an
|
||||
gtk-layer-shell.
|
||||
**This is a hobby project and for my own personal use. It might be unoptimized in
|
||||
some places and naively implemented. It is also most definitely not very customizable
|
||||
and highly opinionated. Use at your own risk.**
|
||||
and highly opinionated. It is built with the specifics of my system in mind and
|
||||
will probably not work on your machine. Use at your own risk.**
|
||||
|
||||
### Building and Running
|
||||
To build the project you need the rust compiler and cargo installed.
|
||||
|
||||
@@ -8,7 +8,7 @@ pub mod perf_stats;
|
||||
pub mod window_manager_hypr;
|
||||
|
||||
use crate::LOGGER;
|
||||
use crate::backend::audio::{MediaBackend, MediaCommand};
|
||||
use crate::backend::audio::{AudioBackend, AudioMessage, MediaBackend, MediaMessage};
|
||||
use crate::backend::brightness_control::{BrightnessManager, BrightnessMessage};
|
||||
use crate::backend::commands::ControlSocket;
|
||||
use crate::backend::idle::{IdleDaemon, IdleMessage, IdleState};
|
||||
@@ -74,7 +74,8 @@ pub struct Backend {
|
||||
net_registrator: Registrator<NetworkState>,
|
||||
br_registrator: Registrator<BrightnessMessage>,
|
||||
stats_registrator: Registrator<Stats>,
|
||||
media_registrator: Registrator<MediaCommand>,
|
||||
audio_registrator: Registrator<AudioMessage>,
|
||||
media_registration: Registrator<MediaMessage>,
|
||||
netbird_registrator: Registrator<State>,
|
||||
idle_registrator: Registrator<IdleState>,
|
||||
control_registrator: Registrator<ControlCommand>,
|
||||
@@ -85,6 +86,7 @@ pub enum ChannelType {
|
||||
Hypr,
|
||||
Br,
|
||||
Stats,
|
||||
Audio,
|
||||
Media,
|
||||
Idle,
|
||||
Netbird,
|
||||
@@ -98,8 +100,9 @@ impl Backend {
|
||||
let net_registrator = NetworkBackendHandle::start();
|
||||
let br_registrator = BrightnessManager::start();
|
||||
let stats_registrator = StatsBackend::start();
|
||||
let media_registrator = MediaBackend::start();
|
||||
let tailscale_registrator = JsonSocketBackend::start();
|
||||
let audio_registrator = AudioBackend::start();
|
||||
let media_registration = MediaBackend::start();
|
||||
let netbird_registrator = JsonSocketBackend::start();
|
||||
let idle_registrator = IdleDaemon::start();
|
||||
let control_registrator = ControlSocket::start();
|
||||
debug!(&LOGGER, "Started Backend threads");
|
||||
@@ -108,8 +111,9 @@ impl Backend {
|
||||
net_registrator,
|
||||
br_registrator,
|
||||
stats_registrator,
|
||||
media_registrator,
|
||||
netbird_registrator: tailscale_registrator,
|
||||
audio_registrator,
|
||||
media_registration,
|
||||
netbird_registrator,
|
||||
idle_registrator,
|
||||
control_registrator,
|
||||
}
|
||||
@@ -124,21 +128,18 @@ impl Backend {
|
||||
channels.net_tx = Some(tx);
|
||||
channels.net_rx = Some(rx);
|
||||
}
|
||||
ChannelType::Hypr => {
|
||||
channels.hypr_rx =
|
||||
Some(HyprlandComms::subscribe(self.hypr_registrator.clone()).0);
|
||||
}
|
||||
ChannelType::Br => {
|
||||
let (rx, tx) = BrightnessManager::subscribe(self.br_registrator.clone());
|
||||
channels.br_rx = Some(rx);
|
||||
channels.br_tx = Some(tx);
|
||||
}
|
||||
ChannelType::Stats => {
|
||||
channels.stats_rx =
|
||||
Some(StatsBackend::subscribe(self.stats_registrator.clone()).0);
|
||||
ChannelType::Audio => {
|
||||
let (rx, tx) = AudioBackend::subscribe(self.audio_registrator.clone());
|
||||
channels.audio_rx = Some(rx);
|
||||
channels.audio_tx = Some(tx);
|
||||
}
|
||||
ChannelType::Media => {
|
||||
let (rx, tx) = MediaBackend::subscribe(self.media_registrator.clone());
|
||||
let (rx, tx) = MediaBackend::subscribe(self.media_registration.clone());
|
||||
channels.media_rx = Some(rx);
|
||||
channels.media_tx = Some(tx);
|
||||
}
|
||||
@@ -155,6 +156,14 @@ impl Backend {
|
||||
channels.ctl_rx =
|
||||
Some(ControlSocket::subscribe(self.control_registrator.clone()).0);
|
||||
}
|
||||
ChannelType::Stats => {
|
||||
channels.stats_rx =
|
||||
Some(StatsBackend::subscribe(self.stats_registrator.clone()).0);
|
||||
}
|
||||
ChannelType::Hypr => {
|
||||
channels.hypr_rx =
|
||||
Some(HyprlandComms::subscribe(self.hypr_registrator.clone()).0);
|
||||
}
|
||||
}
|
||||
}
|
||||
channels
|
||||
@@ -168,6 +177,7 @@ impl Backend {
|
||||
ChannelType::Br,
|
||||
ChannelType::Stats,
|
||||
ChannelType::Control,
|
||||
ChannelType::Audio,
|
||||
ChannelType::Media,
|
||||
ChannelType::Hypr,
|
||||
])
|
||||
@@ -182,8 +192,10 @@ pub struct BackendChannels {
|
||||
br_rx: Option<Receiver<BrightnessMessage>>,
|
||||
br_tx: Option<Sender<BrightnessMessage>>,
|
||||
stats_rx: Option<Receiver<Stats>>,
|
||||
media_rx: Option<Receiver<MediaCommand>>,
|
||||
media_tx: Option<Sender<MediaCommand>>,
|
||||
audio_rx: Option<Receiver<AudioMessage>>,
|
||||
audio_tx: Option<Sender<AudioMessage>>,
|
||||
media_rx: Option<Receiver<MediaMessage>>,
|
||||
media_tx: Option<Sender<MediaMessage>>,
|
||||
netbird_rx: Option<Receiver<State>>,
|
||||
idle_rx: Option<Receiver<IdleState>>,
|
||||
idle_tx: Option<Sender<IdleMessage>>,
|
||||
@@ -206,7 +218,10 @@ impl BackendChannels {
|
||||
pub fn take_netbird_rx(&mut self) -> Option<Receiver<State>> {
|
||||
self.netbird_rx.take()
|
||||
}
|
||||
pub fn take_media_rx(&mut self) -> Option<Receiver<MediaCommand>> {
|
||||
pub fn take_audio_rx(&mut self) -> Option<Receiver<AudioMessage>> {
|
||||
self.audio_rx.take()
|
||||
}
|
||||
pub fn take_media_rx(&mut self) -> Option<Receiver<MediaMessage>> {
|
||||
self.media_rx.take()
|
||||
}
|
||||
pub fn take_idle_rx(&mut self) -> Option<Receiver<IdleState>> {
|
||||
@@ -221,7 +236,10 @@ impl BackendChannels {
|
||||
pub fn br_tx(&self) -> Option<Sender<BrightnessMessage>> {
|
||||
self.br_tx.clone()
|
||||
}
|
||||
pub fn media_tx(&self) -> Option<Sender<MediaCommand>> {
|
||||
pub fn audio_tx(&self) -> Option<Sender<AudioMessage>> {
|
||||
self.audio_tx.clone()
|
||||
}
|
||||
pub fn media_tx(&self) -> Option<Sender<MediaMessage>> {
|
||||
self.media_tx.clone()
|
||||
}
|
||||
pub fn idle_tx(&self) -> Option<Sender<IdleMessage>> {
|
||||
|
||||
@@ -15,12 +15,19 @@ use pulse::volume::{ChannelVolumes, Volume};
|
||||
use pulse::{mainloop, operation};
|
||||
use std::boxed::Box;
|
||||
use std::cell::Cell;
|
||||
use std::collections::HashMap;
|
||||
use std::os::fd::{AsRawFd, RawFd};
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use zbus::export::ordered_stream::OrderedStreamExt;
|
||||
use zbus::fdo::{NameOwnerChanged, PropertiesProxy};
|
||||
use zbus::message::Type;
|
||||
use zbus::names::InterfaceName;
|
||||
use zbus::zvariant::OwnedValue;
|
||||
use zbus::{MatchRule, MessageStream};
|
||||
|
||||
pub struct AudioController {
|
||||
pub struct PulseController {
|
||||
pub mainloop: Mainloop,
|
||||
context: Context,
|
||||
device_index: Option<u32>,
|
||||
@@ -37,7 +44,7 @@ pub enum ControllerMsg {
|
||||
Set(u8, bool),
|
||||
}
|
||||
|
||||
impl AudioController {
|
||||
impl PulseController {
|
||||
pub fn new(tx: Sender<ControllerMsg>) -> Self {
|
||||
let mut mainloop = Mainloop::new().unwrap();
|
||||
let mut prop_list = Proplist::new().unwrap();
|
||||
@@ -95,7 +102,7 @@ impl AudioController {
|
||||
trace!(&LOGGER, "Subscribed to pulseaudio events: {success}");
|
||||
});
|
||||
|
||||
let mut controller = AudioController {
|
||||
let mut controller = PulseController {
|
||||
context,
|
||||
mainloop,
|
||||
device_index: None,
|
||||
@@ -251,147 +258,151 @@ impl AudioController {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum MediaCommand {
|
||||
Init(Sender<MediaCommand>),
|
||||
pub enum AudioMessage {
|
||||
Init(Sender<AudioMessage>),
|
||||
Request,
|
||||
Cmd(u8, bool, bool),
|
||||
}
|
||||
|
||||
pub struct MediaBackend {
|
||||
subscribers: Vec<Sender<MediaCommand>>,
|
||||
command_tx: Sender<MediaCommand>,
|
||||
pub struct AudioBackend {
|
||||
subscribers: Vec<Sender<AudioMessage>>,
|
||||
command_tx: Sender<AudioMessage>,
|
||||
}
|
||||
|
||||
impl BackendThread<MediaCommand, Sender<MediaCommand>> for MediaBackend {
|
||||
impl BackendThread<AudioMessage, Sender<AudioMessage>> for AudioBackend {
|
||||
fn subscribe(
|
||||
registrator: Registrator<MediaCommand>,
|
||||
) -> (Receiver<MediaCommand>, Sender<MediaCommand>) {
|
||||
let (result_tx, result_rx) = crossbeam_channel::unbounded::<MediaCommand>();
|
||||
registrator: Registrator<AudioMessage>,
|
||||
) -> (Receiver<AudioMessage>, Sender<AudioMessage>) {
|
||||
let (result_tx, result_rx) = crossbeam_channel::unbounded::<AudioMessage>();
|
||||
let _ = registrator.send(result_tx);
|
||||
while let Ok(state) = result_rx.recv() {
|
||||
return match state {
|
||||
MediaCommand::Init(sender) => (result_rx, sender),
|
||||
AudioMessage::Init(sender) => (result_rx, sender),
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
panic!("Channel closed unexpectedly, without returning anything")
|
||||
}
|
||||
|
||||
fn subscribers(&self) -> &Vec<Sender<MediaCommand>> {
|
||||
fn subscribers(&self) -> &Vec<Sender<AudioMessage>> {
|
||||
&self.subscribers
|
||||
}
|
||||
|
||||
fn init(registration_rx: Receiver<Sender<MediaCommand>>) {
|
||||
let (command_tx, command_rx) = crossbeam_channel::unbounded::<MediaCommand>();
|
||||
fn init(registration_rx: Receiver<Sender<AudioMessage>>) {
|
||||
let (command_tx, command_rx) = crossbeam_channel::unbounded::<AudioMessage>();
|
||||
let instance = Arc::new(Mutex::new(Self {
|
||||
subscribers: vec![],
|
||||
command_tx,
|
||||
}));
|
||||
let instance_clone = instance.clone();
|
||||
let (controller_tx, controller_rx) = crossbeam_channel::unbounded::<ControllerMsg>();
|
||||
let controller_tx_clone = controller_tx.clone();
|
||||
let notify = Arc::new(EventFD::new(0, EfdFlags::EFD_CLOEXEC).unwrap());
|
||||
let notify_clone = notify.clone();
|
||||
thread::spawn(move || {
|
||||
let mut controller = AudioController::new(controller_tx_clone);
|
||||
let _event = api::Mainloop::new_io_event(
|
||||
&mut controller.mainloop,
|
||||
notify_clone.as_raw_fd(),
|
||||
io::FlagSet::INPUT,
|
||||
Box::new(move |_, fd, _flags| match drain_eventfd(fd) {
|
||||
Ok(_) => trace!(&LOGGER, "Woke pulseaudio loop using EventFD"),
|
||||
Err(e) => warn!(&LOGGER, "Error draining EventFD {}", e.to_string()),
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
loop {
|
||||
while let Ok(msg) = controller_rx.try_recv() {
|
||||
match msg {
|
||||
Request => {
|
||||
instance_clone.lock().expect("Couldn't get lock").publish(
|
||||
MediaCommand::Cmd(
|
||||
controller.current_vol,
|
||||
controller.current_mute,
|
||||
controller.device_bt.unwrap_or(false),
|
||||
),
|
||||
);
|
||||
}
|
||||
ControllerMsg::NotifyServer => {
|
||||
controller.query_default_device_blocking();
|
||||
let _ = controller.query_state_blocking();
|
||||
instance_clone.lock().expect("Couldn't get lock").publish(
|
||||
MediaCommand::Cmd(
|
||||
controller.current_vol,
|
||||
controller.current_mute,
|
||||
controller.device_bt.unwrap_or(false),
|
||||
),
|
||||
);
|
||||
}
|
||||
ControllerMsg::NotifySink(idx) => {
|
||||
if controller.device_index.is_some_and(|i| i == idx) {
|
||||
let changed = controller.query_state_blocking();
|
||||
if changed {
|
||||
instance_clone.lock().expect("Couldn't get lock").publish(
|
||||
MediaCommand::Cmd(
|
||||
controller.current_vol,
|
||||
controller.current_mute,
|
||||
controller.device_bt.unwrap_or(false),
|
||||
),
|
||||
);
|
||||
trace!(
|
||||
&LOGGER,
|
||||
"Change: {} muted: {}",
|
||||
thread::spawn({
|
||||
let instance = instance.clone();
|
||||
let controller_tx = controller_tx.clone();
|
||||
let notify = notify.clone();
|
||||
move || {
|
||||
let mut controller = PulseController::new(controller_tx);
|
||||
let _event = api::Mainloop::new_io_event(
|
||||
&mut controller.mainloop,
|
||||
notify.as_raw_fd(),
|
||||
io::FlagSet::INPUT,
|
||||
Box::new(move |_, fd, _flags| match drain_eventfd(fd) {
|
||||
Ok(_) => trace!(&LOGGER, "Woke pulseaudio loop using EventFD"),
|
||||
Err(e) => warn!(&LOGGER, "Error draining EventFD {}", e.to_string()),
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
loop {
|
||||
while let Ok(msg) = controller_rx.try_recv() {
|
||||
match msg {
|
||||
Request => {
|
||||
instance.lock().expect("Couldn't get lock").publish(
|
||||
AudioMessage::Cmd(
|
||||
controller.current_vol,
|
||||
controller.current_mute
|
||||
);
|
||||
controller.current_mute,
|
||||
controller.device_bt.unwrap_or(false),
|
||||
),
|
||||
);
|
||||
}
|
||||
ControllerMsg::NotifyServer => {
|
||||
controller.query_default_device_blocking();
|
||||
let _ = controller.query_state_blocking();
|
||||
instance.lock().expect("Couldn't get lock").publish(
|
||||
AudioMessage::Cmd(
|
||||
controller.current_vol,
|
||||
controller.current_mute,
|
||||
controller.device_bt.unwrap_or(false),
|
||||
),
|
||||
);
|
||||
}
|
||||
ControllerMsg::NotifySink(idx) => {
|
||||
if controller.device_index.is_some_and(|i| i == idx) {
|
||||
let changed = controller.query_state_blocking();
|
||||
if changed {
|
||||
instance.lock().expect("Couldn't get lock").publish(
|
||||
AudioMessage::Cmd(
|
||||
controller.current_vol,
|
||||
controller.current_mute,
|
||||
controller.device_bt.unwrap_or(false),
|
||||
),
|
||||
);
|
||||
trace!(
|
||||
&LOGGER,
|
||||
"Change: {} muted: {}",
|
||||
controller.current_vol,
|
||||
controller.current_mute
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ControllerMsg::Set(volume, mute) => {
|
||||
controller.set_state(volume, mute);
|
||||
instance_clone.lock().expect("Couldn't get lock").publish(
|
||||
MediaCommand::Cmd(
|
||||
controller.current_vol,
|
||||
controller.current_mute,
|
||||
controller.device_bt.unwrap_or(false),
|
||||
),
|
||||
);
|
||||
ControllerMsg::Set(volume, mute) => {
|
||||
controller.set_state(volume, mute);
|
||||
instance.lock().expect("Couldn't get lock").publish(
|
||||
AudioMessage::Cmd(
|
||||
controller.current_vol,
|
||||
controller.current_mute,
|
||||
controller.device_bt.unwrap_or(false),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
controller.mainloop.iterate(true);
|
||||
}
|
||||
controller.mainloop.iterate(true);
|
||||
}
|
||||
});
|
||||
let instance_clone = instance.clone();
|
||||
thread::spawn(move || {
|
||||
while let Ok(sender) = registration_rx.recv() {
|
||||
if sender
|
||||
.send(MediaCommand::Init(
|
||||
instance_clone
|
||||
thread::spawn({
|
||||
let instance = instance.clone();
|
||||
move || {
|
||||
while let Ok(sender) = registration_rx.recv() {
|
||||
if sender
|
||||
.send(AudioMessage::Init(
|
||||
instance
|
||||
.lock()
|
||||
.expect("Couldn't get lock")
|
||||
.command_tx
|
||||
.clone(),
|
||||
))
|
||||
.is_ok()
|
||||
{
|
||||
let _ = sender.send(AudioMessage::Request);
|
||||
instance
|
||||
.lock()
|
||||
.expect("Couldn't get lock")
|
||||
.command_tx
|
||||
.clone(),
|
||||
))
|
||||
.is_ok()
|
||||
{
|
||||
let _ = sender.send(MediaCommand::Request);
|
||||
instance_clone
|
||||
.lock()
|
||||
.expect("Couldn't get subscribers")
|
||||
.subscribers
|
||||
.push(sender);
|
||||
.expect("Couldn't get subscribers")
|
||||
.subscribers
|
||||
.push(sender);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
thread::spawn(move || {
|
||||
while let Ok(command) = command_rx.recv() {
|
||||
match command {
|
||||
MediaCommand::Cmd(vol, mute, _) => {
|
||||
AudioMessage::Cmd(vol, mute, _) => {
|
||||
let _ = controller_tx.send(ControllerMsg::Set(vol, mute));
|
||||
notify.write(1).expect("Error writing notify");
|
||||
}
|
||||
MediaCommand::Request => {
|
||||
AudioMessage::Request => {
|
||||
let _ = controller_tx.send(Request);
|
||||
notify.write(1).expect("Error writing notify");
|
||||
}
|
||||
@@ -404,6 +415,438 @@ impl BackendThread<MediaCommand, Sender<MediaCommand>> for MediaBackend {
|
||||
}
|
||||
}
|
||||
|
||||
const MPRIS_PREFIX: &str = "org.mpris.MediaPlayer2";
|
||||
const MPRIS_PATH: &str = "/org/mpris/MediaPlayer2";
|
||||
const MPRIS_PLAYER_INTERFACE: &str = "org.mpris.MediaPlayer2.Player";
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PlayerStatus {
|
||||
Playing(String),
|
||||
Paused(String),
|
||||
Stopped,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum MediaMessage {
|
||||
Init(Sender<MediaMessage>),
|
||||
PlayerStatus(PlayerStatus),
|
||||
PlayerPlay,
|
||||
PlayerPause,
|
||||
PlayerStop,
|
||||
}
|
||||
|
||||
pub struct MediaBackend {
|
||||
subscribers: Vec<Sender<MediaMessage>>,
|
||||
players: HashMap<String, PlayerStatus>,
|
||||
active_player: Option<String>,
|
||||
}
|
||||
|
||||
impl BackendThread<MediaMessage, Sender<MediaMessage>> for MediaBackend {
|
||||
fn subscribe(
|
||||
registrator: Registrator<MediaMessage>,
|
||||
) -> (Receiver<MediaMessage>, Sender<MediaMessage>) {
|
||||
let (result_tx, result_rx) = crossbeam_channel::unbounded::<MediaMessage>();
|
||||
let _ = registrator.send(result_tx);
|
||||
while let Ok(state) = result_rx.recv() {
|
||||
return match state {
|
||||
MediaMessage::Init(sender) => (result_rx, sender),
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
panic!("Channel closed unexpectedly, without returning anything")
|
||||
}
|
||||
|
||||
fn subscribers(&self) -> &Vec<Sender<MediaMessage>> {
|
||||
&self.subscribers
|
||||
}
|
||||
|
||||
fn init(registration_rx: Receiver<Sender<MediaMessage>>) {
|
||||
let (tx, rx) = crossbeam_channel::unbounded::<MediaMessage>();
|
||||
let instance = Arc::new(Mutex::new(Self {
|
||||
subscribers: vec![],
|
||||
players: HashMap::new(),
|
||||
active_player: None,
|
||||
}));
|
||||
thread::spawn({
|
||||
let instance = instance.clone();
|
||||
move || {
|
||||
while let Ok(sender) = registration_rx.recv() {
|
||||
if let Ok(_) = sender.send(MediaMessage::Init(tx.clone())) {
|
||||
let mut instance_guard = instance.lock().unwrap();
|
||||
if let Some(name) = &instance_guard.active_player
|
||||
&& let Some(status) = instance_guard.players.get(name)
|
||||
{
|
||||
let _ = sender.send(MediaMessage::PlayerStatus(status.clone()));
|
||||
}
|
||||
instance_guard.subscribers.push(sender);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
thread::spawn({
|
||||
let instance = instance.clone();
|
||||
move || {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed to create Tokio runtime");
|
||||
|
||||
runtime.block_on(async move {
|
||||
if let Err(e) = Self::wait_for_players(instance).await {
|
||||
error!(&LOGGER, "Error waiting for players: {e}");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
thread::spawn(move || {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed to create Tokio runtime");
|
||||
|
||||
runtime.block_on({
|
||||
let instance = instance.clone();
|
||||
async move {
|
||||
let Ok(connection) = zbus::Connection::session().await else {
|
||||
error!(&LOGGER, "Couldn't create D-Bus connection");
|
||||
return;
|
||||
};
|
||||
while let Ok(msg) = rx.recv() {
|
||||
let mut instance_guard = instance.lock().unwrap();
|
||||
if let Some(name) = &instance_guard.active_player {
|
||||
if let Some(status) = instance_guard.players.get(name) {
|
||||
match msg {
|
||||
MediaMessage::PlayerPlay => {
|
||||
if let PlayerStatus::Paused(_) = status {
|
||||
instance_guard.active_player_play(&connection).await;
|
||||
}
|
||||
}
|
||||
MediaMessage::PlayerPause => {
|
||||
if let PlayerStatus::Playing(_) = status {
|
||||
instance_guard.active_player_pause(&connection).await;
|
||||
}
|
||||
}
|
||||
MediaMessage::PlayerStop => {
|
||||
if let PlayerStatus::Playing(_) | PlayerStatus::Paused(_) =
|
||||
status
|
||||
{
|
||||
instance_guard.active_player_stop(&connection).await;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl MediaBackend {
|
||||
async fn find_existing_players(
|
||||
connection: &zbus::Connection,
|
||||
instance: Arc<Mutex<Self>>,
|
||||
) -> zbus::fdo::Result<()> {
|
||||
let dbus = zbus::Proxy::new(
|
||||
connection,
|
||||
"org.freedesktop.DBus",
|
||||
"/org/freedesktop/DBus",
|
||||
"org.freedesktop.DBus",
|
||||
)
|
||||
.await?;
|
||||
|
||||
let names: Vec<String> = dbus.call("ListNames", &()).await?;
|
||||
let players: Vec<String> = names
|
||||
.into_iter()
|
||||
.filter(|name| name.starts_with(&format!("{MPRIS_PREFIX}.")))
|
||||
.collect();
|
||||
|
||||
for player in players {
|
||||
Self::spawn_new_player_watcher(&connection, &instance, &player);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn wait_for_players(instance: Arc<Mutex<Self>>) -> zbus::fdo::Result<()> {
|
||||
let connection = zbus::Connection::session().await?;
|
||||
|
||||
if let Err(e) = Self::find_existing_players(&connection, instance.clone()).await {
|
||||
warn!(&LOGGER, "Error finding existing Media Players: {e}");
|
||||
}
|
||||
|
||||
let rule = MatchRule::builder()
|
||||
.msg_type(Type::Signal)
|
||||
.sender("org.freedesktop.DBus")?
|
||||
.interface("org.freedesktop.DBus")?
|
||||
.member("NameOwnerChanged")?
|
||||
.build();
|
||||
|
||||
let mut stream = MessageStream::for_match_rule(rule, &connection, Some(32)).await?;
|
||||
|
||||
while let Some(Ok(message)) = stream.next().await {
|
||||
let Some(signal) = NameOwnerChanged::from_message(message) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Ok(args) = signal.args() else {
|
||||
continue;
|
||||
};
|
||||
let name = args.name.as_str();
|
||||
|
||||
if !name.starts_with(&format!("{MPRIS_PREFIX}.")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let old_owner = args.old_owner();
|
||||
let new_owner = args.new_owner();
|
||||
|
||||
let map = &mut instance.lock().unwrap().players;
|
||||
if old_owner.is_none() && new_owner.is_some() {
|
||||
Self::spawn_new_player_watcher(&connection, &instance, name);
|
||||
} else if old_owner.is_some() && new_owner.is_none() {
|
||||
map.remove(name);
|
||||
debug!(&LOGGER, "(1) Player removed: {name}");
|
||||
} else {
|
||||
debug!(&LOGGER, "Player owner changed: {name}");
|
||||
}
|
||||
}
|
||||
warn!(&LOGGER, "Player Finder ended prematurely");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn spawn_new_player_watcher(
|
||||
connection: &zbus::Connection,
|
||||
instance: &Arc<Mutex<Self>>,
|
||||
name: &str,
|
||||
) {
|
||||
tokio::spawn({
|
||||
let instance = instance.clone();
|
||||
let connection = connection.clone();
|
||||
let name = name.to_string();
|
||||
async move {
|
||||
let _ = Self::watch_player_status(&connection, instance, &name).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn watch_player_status(
|
||||
connection: &zbus::Connection,
|
||||
instance: Arc<Mutex<Self>>,
|
||||
name: &str,
|
||||
) -> zbus::fdo::Result<()> {
|
||||
let props = PropertiesProxy::builder(connection)
|
||||
.destination(name)?
|
||||
.path(MPRIS_PATH)?
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let mut changes = props.receive_properties_changed().await?;
|
||||
|
||||
let try_string: Result<String, _> = props
|
||||
.get(
|
||||
InterfaceName::from_static_str_unchecked(MPRIS_PLAYER_INTERFACE),
|
||||
"PlaybackStatus",
|
||||
)
|
||||
.await?
|
||||
.try_into();
|
||||
|
||||
let initial: String = if let Ok(s) = try_string {
|
||||
s
|
||||
} else {
|
||||
String::from("Stopped")
|
||||
};
|
||||
|
||||
debug!(&LOGGER, "Player added: {name} {initial}");
|
||||
let _ = Self::insert_or_update_player(&instance, &props, name.into(), initial).await;
|
||||
|
||||
while let Some(change) = changes.next().await {
|
||||
let args = change.args()?;
|
||||
|
||||
if !args.interface_name().eq(MPRIS_PLAYER_INTERFACE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(value) = args.changed_properties().get("PlaybackStatus") {
|
||||
if let Ok(status) = <String>::try_from(value.clone()) {
|
||||
debug!(&LOGGER, "Player ({name}) status has changed to: '{status}'");
|
||||
Self::insert_or_update_player(&instance, &props, name.into(), status).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
debug!(&LOGGER, "(2) Removing player: {name}");
|
||||
instance.lock().unwrap().players.remove(name);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn insert_or_update_player(
|
||||
instance: &Arc<Mutex<Self>>,
|
||||
props: &PropertiesProxy<'_>,
|
||||
player_name: String,
|
||||
player_status: String,
|
||||
) {
|
||||
match Self::get_player_status_from_string(&props, player_status).await {
|
||||
Ok(status) => {
|
||||
let mut instance_guard = instance.lock().unwrap();
|
||||
instance_guard.players.insert(player_name.clone(), status);
|
||||
instance_guard.select_active_player(Some(player_name));
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(&LOGGER, "Error parsing player status: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_title(props: &PropertiesProxy<'_>) -> zbus::fdo::Result<String> {
|
||||
let metadata = props
|
||||
.get(
|
||||
InterfaceName::from_static_str_unchecked(MPRIS_PLAYER_INTERFACE),
|
||||
"Metadata",
|
||||
)
|
||||
.await?;
|
||||
let metadata = <HashMap<String, OwnedValue>>::try_from(metadata);
|
||||
match metadata {
|
||||
Ok(metadata) => {
|
||||
if let Some(title) = metadata.get("xesam:title") {
|
||||
<String>::try_from(title.clone())
|
||||
.map_err(|e| zbus::fdo::Error::Failed(e.to_string()))
|
||||
} else {
|
||||
Err(zbus::fdo::Error::Failed("Couldn't parse title".into()))
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(&LOGGER, "Error parsing metadata {}", err);
|
||||
Err(zbus::fdo::Error::Failed("Couldn't parse metadata".into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_player_status_from_string(
|
||||
props: &PropertiesProxy<'_>,
|
||||
status: String,
|
||||
) -> zbus::fdo::Result<PlayerStatus> {
|
||||
match status.as_str() {
|
||||
"Playing" => match Self::get_title(props).await {
|
||||
Ok(title) => Ok(PlayerStatus::Playing(title)),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
"Paused" => match Self::get_title(props).await {
|
||||
Ok(title) => Ok(PlayerStatus::Paused(title)),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
"Stopped" => Ok(PlayerStatus::Stopped),
|
||||
other => Err(zbus::fdo::Error::Failed(format!(
|
||||
"Invalid status string: '{other}' {:?}",
|
||||
other.as_bytes()
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
async fn active_player_play(&self, connection: &zbus::Connection) {
|
||||
if let Some(player_name) = &self.active_player {
|
||||
if let Ok(proxy) = zbus::Proxy::new(
|
||||
&connection,
|
||||
player_name.as_str(),
|
||||
MPRIS_PATH,
|
||||
MPRIS_PLAYER_INTERFACE,
|
||||
)
|
||||
.await
|
||||
{
|
||||
if let Err(e) = proxy.call_method("Play", &()).await {
|
||||
warn!(
|
||||
&LOGGER,
|
||||
"Error starting play for player: {player_name}, {e}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn active_player_pause(&mut self, connection: &zbus::Connection) {
|
||||
if let Some(player_name) = &self.active_player {
|
||||
if let Ok(proxy) = zbus::Proxy::new(
|
||||
&connection,
|
||||
player_name.as_str(),
|
||||
MPRIS_PATH,
|
||||
MPRIS_PLAYER_INTERFACE,
|
||||
)
|
||||
.await
|
||||
{
|
||||
if let Err(e) = proxy.call_method("Pause", &()).await {
|
||||
warn!(&LOGGER, "Error pausing player: {player_name}, {e}");
|
||||
}
|
||||
}
|
||||
self.select_active_player(self.active_player.clone());
|
||||
}
|
||||
}
|
||||
|
||||
async fn active_player_stop(&mut self, connection: &zbus::Connection) {
|
||||
if let Some(player_name) = &self.active_player {
|
||||
if let Ok(proxy) = zbus::Proxy::new(
|
||||
&connection,
|
||||
player_name.as_str(),
|
||||
MPRIS_PATH,
|
||||
MPRIS_PLAYER_INTERFACE,
|
||||
)
|
||||
.await
|
||||
{
|
||||
if let Err(e) = proxy.call_method("Stop", &()).await {
|
||||
warn!(&LOGGER, "Error stopping player: {player_name}, {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
self.select_active_player(None);
|
||||
}
|
||||
|
||||
fn select_active_player(&mut self, changed_player: Option<String>) {
|
||||
let mut changed_also_playing = false;
|
||||
let mut new_active: Option<String> = None;
|
||||
let mut send_status = PlayerStatus::Stopped;
|
||||
if let Some(player) = changed_player
|
||||
&& let Some(status) = self.players.get(&player)
|
||||
{
|
||||
match status {
|
||||
PlayerStatus::Playing(_) => {
|
||||
new_active = Some(player);
|
||||
send_status = status.clone();
|
||||
changed_also_playing = true;
|
||||
}
|
||||
PlayerStatus::Paused(_) => {
|
||||
new_active = Some(player);
|
||||
send_status = status.clone();
|
||||
}
|
||||
PlayerStatus::Stopped => {} // Stopped players shouldn't be active
|
||||
}
|
||||
}
|
||||
|
||||
if !changed_also_playing {
|
||||
for (name, status) in &self.players {
|
||||
match status {
|
||||
PlayerStatus::Playing(_) => {
|
||||
// Found a player that is playing media -> prioritize this one
|
||||
new_active = Some(name.clone());
|
||||
send_status = status.clone();
|
||||
break;
|
||||
}
|
||||
PlayerStatus::Paused(_) => {
|
||||
if new_active.is_none() {
|
||||
new_active = Some(name.clone());
|
||||
send_status = status.clone();
|
||||
}
|
||||
}
|
||||
PlayerStatus::Stopped => {} // Stopped players shouldn't be active
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.active_player = new_active;
|
||||
self.publish(MediaMessage::PlayerStatus(send_status));
|
||||
}
|
||||
}
|
||||
|
||||
fn drain_eventfd(fd: RawFd) -> std::io::Result<u64> {
|
||||
let mut value: u64 = 0;
|
||||
|
||||
|
||||
49
src/ui.rs
49
src/ui.rs
@@ -1,10 +1,10 @@
|
||||
use crate::LOGGER;
|
||||
use crate::backend::audio::MediaMessage;
|
||||
use crate::backend::window_manager_hypr::{HyprlandComms, WindowManagerEvent};
|
||||
use crate::backend::{Backend, BackendChannels, ChannelType};
|
||||
use crate::ctl::ControlCommand;
|
||||
use crate::ui::action_centre::ActionCentre;
|
||||
use crate::ui::run_menu::RunMenu;
|
||||
use crossbeam_channel::TryRecvError;
|
||||
use glib::ControlFlow;
|
||||
use glib::prelude::Cast;
|
||||
use gtk::gdk::Display;
|
||||
@@ -12,7 +12,6 @@ use gtk::prelude::{BoxExt, ButtonExt, DisplayExt, GtkWindowExt, ListModelExt, Wi
|
||||
use gtk::{Align, Application, ApplicationWindow, Button, Orientation, gdk};
|
||||
use gtk4_layer_shell::{Edge, KeyboardMode, Layer, LayerShell};
|
||||
use jmb_logger_rs::*;
|
||||
use mpris::PlayerFinder;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Mutex;
|
||||
@@ -98,12 +97,10 @@ fn create_and_show_bar(
|
||||
let hypr_rx = channels
|
||||
.take_hypr_rx()
|
||||
.expect("Tried taking unavailable Hyprland Channel Receiver");
|
||||
|
||||
let player_opt = if let Ok(player_finder) = PlayerFinder::new() {
|
||||
player_finder.find_active().ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let media_rx = channels
|
||||
.take_media_rx()
|
||||
.expect("Tried taking unavailable Media Channel Receiver");
|
||||
let media_tx = channels.media_tx().unwrap();
|
||||
|
||||
let window = ApplicationWindow::builder()
|
||||
.application(&app)
|
||||
@@ -136,7 +133,7 @@ fn create_and_show_bar(
|
||||
);
|
||||
|
||||
let title_button: Option<Button>;
|
||||
let title = title_panel::build();
|
||||
let title = title_panel::build(media_tx);
|
||||
|
||||
let utilities = utilities_panel::build(&app, action_centre, channels, monitor_idx, &display);
|
||||
|
||||
@@ -149,34 +146,30 @@ fn create_and_show_bar(
|
||||
dock.append(&utilities);
|
||||
|
||||
title_button = Some(title);
|
||||
title_panel::manage_title(&title_button, &player_opt);
|
||||
title_panel::manage_title(&title_button, &None);
|
||||
|
||||
window.set_keyboard_mode(KeyboardMode::None);
|
||||
window.set_child(Some(&dock));
|
||||
window.present();
|
||||
|
||||
let window_clone = window.clone();
|
||||
|
||||
let mut status = None;
|
||||
glib::timeout_add_local(Duration::from_millis(50), move || {
|
||||
loop {
|
||||
match hypr_rx.try_recv() {
|
||||
Ok(event) => {
|
||||
handle_wm_event(
|
||||
&mut buttons,
|
||||
&submap,
|
||||
&title_button,
|
||||
event,
|
||||
window_clone.monitor(),
|
||||
);
|
||||
}
|
||||
Err(TryRecvError::Empty) => break,
|
||||
Err(TryRecvError::Disconnected) => {
|
||||
return ControlFlow::Break;
|
||||
}
|
||||
}
|
||||
while let Ok(event) = hypr_rx.try_recv() {
|
||||
handle_wm_event(
|
||||
&mut buttons,
|
||||
&submap,
|
||||
&title_button,
|
||||
event,
|
||||
window_clone.monitor(),
|
||||
);
|
||||
}
|
||||
|
||||
title_panel::manage_title(&title_button, &player_opt);
|
||||
while let Ok(MediaMessage::PlayerStatus(recv_status)) = media_rx.try_recv() {
|
||||
status = Some(recv_status);
|
||||
}
|
||||
|
||||
title_panel::manage_title(&title_button, &status);
|
||||
|
||||
ControlFlow::Continue
|
||||
});
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use crate::backend::audio::{MediaMessage, PlayerStatus};
|
||||
use crate::backend::window_manager_hypr::HyprlandComms;
|
||||
use crossbeam_channel::Sender;
|
||||
use glib::prelude::CastNone;
|
||||
use gtk::pango::EllipsizeMode;
|
||||
use gtk::prelude::{ButtonExt, GestureSingleExt, WidgetExt};
|
||||
use gtk::{Align, Button, GestureClick, Label};
|
||||
use mpris::{PlaybackStatus, Player, PlayerFinder};
|
||||
|
||||
pub fn build() -> Button {
|
||||
pub fn build(media_tx: Sender<MediaMessage>) -> Button {
|
||||
let title_label = Label::new(None);
|
||||
title_label.set_ellipsize(EllipsizeMode::End);
|
||||
title_label.set_max_width_chars(50);
|
||||
@@ -16,59 +17,56 @@ pub fn build() -> Button {
|
||||
title.set_child(Some(&title_label));
|
||||
title.set_visible(false);
|
||||
title.add_css_class("title");
|
||||
title.connect_clicked(|button| {
|
||||
if button.has_css_class("media")
|
||||
&& let Ok(player_finder) = PlayerFinder::new()
|
||||
&& let Ok(player) = player_finder.find_active()
|
||||
{
|
||||
let _ = player.play_pause();
|
||||
title.connect_clicked({
|
||||
let media_tx = media_tx.clone();
|
||||
move |button| {
|
||||
if button.has_css_class("media") {
|
||||
if button.has_css_class("playing") {
|
||||
let _ = media_tx.send(MediaMessage::PlayerPause);
|
||||
} else {
|
||||
let _ = media_tx.send(MediaMessage::PlayerPlay);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let right_click = GestureClick::new();
|
||||
right_click.set_button(3);
|
||||
right_click.connect_pressed(|_gesture, _n_press, _, _| {
|
||||
if let Ok(player_finder) = PlayerFinder::new()
|
||||
&& let Ok(player) = player_finder.find_active()
|
||||
{
|
||||
let _ = player.stop();
|
||||
}
|
||||
right_click.connect_pressed(move |_gesture, _n_press, _, _| {
|
||||
let _ = media_tx.send(MediaMessage::PlayerStop);
|
||||
});
|
||||
title.add_controller(right_click);
|
||||
title
|
||||
}
|
||||
|
||||
pub fn manage_title(button_opt: &Option<Button>, player_opt: &Option<Player>) {
|
||||
pub fn manage_title(button_opt: &Option<Button>, status: &Option<PlayerStatus>) {
|
||||
let mut handled = false;
|
||||
if let Some(button) = button_opt {
|
||||
if let Some(player) = player_opt
|
||||
&& let Ok(status) = player.get_playback_status()
|
||||
&& let Ok(metadata) = player.get_metadata()
|
||||
{
|
||||
if let Some(status) = status {
|
||||
match status {
|
||||
PlaybackStatus::Playing => {
|
||||
PlayerStatus::Playing(title) => {
|
||||
button.set_visible(true);
|
||||
button
|
||||
.child()
|
||||
.and_downcast::<Label>()
|
||||
.unwrap()
|
||||
.set_label(&format!(" {}", metadata.title().unwrap_or("")));
|
||||
.set_label(&format!(" {}", title));
|
||||
button.add_css_class("media");
|
||||
button.add_css_class("playing");
|
||||
handled = true;
|
||||
}
|
||||
PlaybackStatus::Paused => {
|
||||
PlayerStatus::Paused(title) => {
|
||||
button.set_visible(true);
|
||||
button
|
||||
.child()
|
||||
.and_downcast::<Label>()
|
||||
.unwrap()
|
||||
.set_label(&format!(" {}", metadata.title().unwrap_or("")));
|
||||
.set_label(&format!(" {}", title));
|
||||
button.add_css_class("media");
|
||||
button.remove_css_class("playing");
|
||||
handled = true;
|
||||
}
|
||||
PlaybackStatus::Stopped => {
|
||||
PlayerStatus::Stopped => {
|
||||
button.set_visible(true);
|
||||
button.remove_css_class("media");
|
||||
button.remove_css_class("playing");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::LOGGER;
|
||||
use crate::backend::audio::MediaCommand;
|
||||
use crate::backend::audio::AudioMessage;
|
||||
use crate::backend::brightness_control::BrightnessMessage;
|
||||
use crate::backend::idle::{IdleMessage, IdleState};
|
||||
use crate::backend::network::{NetworkCommand, NetworkState, NetworkType};
|
||||
@@ -76,13 +76,13 @@ pub fn build(
|
||||
volume.imp().second_icon.set_icon_name("bluetooth-symbolic");
|
||||
volume.imp().second_icon.add_child_css_class("bt-icon");
|
||||
|
||||
let media_tx = channels.media_tx().unwrap();
|
||||
let media_tx = channels.audio_tx().unwrap();
|
||||
let changed_progr = Rc::new(Cell::new(false));
|
||||
volume.connect_value_changed({
|
||||
let changed_progr = changed_progr.clone();
|
||||
move |slider, button, _| {
|
||||
if !changed_progr.get() {
|
||||
let _ = media_tx.send(MediaCommand::Cmd(
|
||||
let _ = media_tx.send(AudioMessage::Cmd(
|
||||
slider.value() as u8,
|
||||
button.is_active(),
|
||||
false,
|
||||
@@ -90,12 +90,12 @@ pub fn build(
|
||||
}
|
||||
}
|
||||
});
|
||||
let media_tx = channels.media_tx().unwrap();
|
||||
let media_tx = channels.audio_tx().unwrap();
|
||||
volume.connect_clicked(move |slider, button, _| {
|
||||
if button.is_active() {
|
||||
let _ = media_tx.send(MediaCommand::Cmd(slider.value() as u8, true, false));
|
||||
let _ = media_tx.send(AudioMessage::Cmd(slider.value() as u8, true, false));
|
||||
} else {
|
||||
let _ = media_tx.send(MediaCommand::Cmd(slider.value() as u8, false, false));
|
||||
let _ = media_tx.send(AudioMessage::Cmd(slider.value() as u8, false, false));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -315,7 +315,7 @@ pub fn build(
|
||||
});
|
||||
|
||||
glib::timeout_add_local(Duration::from_millis(100), {
|
||||
let media_rx = channels.take_media_rx().unwrap();
|
||||
let media_rx = channels.take_audio_rx().unwrap();
|
||||
let br_rx = channels.take_br_rx().unwrap();
|
||||
let net_rx = channels.take_net_rx().unwrap();
|
||||
let stats_rx = channels.take_stats_rx().unwrap();
|
||||
@@ -325,7 +325,7 @@ pub fn build(
|
||||
let br_tx = channels.br_tx().unwrap();
|
||||
move || {
|
||||
while let Ok(command) = media_rx.try_recv() {
|
||||
if let MediaCommand::Cmd(vol, mute, bt) = command {
|
||||
if let AudioMessage::Cmd(vol, mute, bt) = command {
|
||||
changed_progr.set(true);
|
||||
volume.set_value(vol as f64);
|
||||
changed_progr.set(false);
|
||||
|
||||
Reference in New Issue
Block a user