Compare commits

...

1 Commits

Author SHA1 Message Date
ffd4d9a2c2
rustfmt and stuff 2021-01-17 11:19:53 +03:00
9 changed files with 397 additions and 182 deletions

64
Cargo.lock generated
View File

@ -33,12 +33,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "backlight"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1dd1f0fb2c34052222f8937bd7ab412c664e8436e2c775d1b64836974889f21"
[[package]]
name = "backtrace"
version = "0.3.50"
@ -279,9 +273,9 @@ version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
dependencies = [
"proc-macro2 1.0.21",
"proc-macro2 1.0.24",
"quote 1.0.7",
"syn 1.0.41",
"syn 1.0.58",
"synstructure",
]
@ -476,6 +470,12 @@ dependencies = [
"autocfg",
]
[[package]]
name = "numtoa"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e521b6adefa0b2c1fa5d2abdf9a5216288686fe6146249215d884c0e5ab320b0"
[[package]]
name = "objc"
version = "0.2.7"
@ -537,9 +537,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.21"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36e28516df94f3dd551a587da5357459d9b36d945a7c37c3557928c1c2ff2a2c"
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
dependencies = [
"unicode-xid 0.2.1",
]
@ -574,7 +574,7 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
dependencies = [
"proc-macro2 1.0.21",
"proc-macro2 1.0.24",
]
[[package]]
@ -632,8 +632,9 @@ dependencies = [
name = "simple-osd-brightness"
version = "0.1.0"
dependencies = [
"backlight",
"simple-osd-common",
"sysfs-class",
"thiserror",
]
[[package]]
@ -714,11 +715,11 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.41"
version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6690e3e9f692504b941dc6c3b188fd28df054f7fb8469ab40680df52fdcc842b"
checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5"
dependencies = [
"proc-macro2 1.0.21",
"proc-macro2 1.0.24",
"quote 1.0.7",
"unicode-xid 0.2.1",
]
@ -738,12 +739,41 @@ version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
dependencies = [
"proc-macro2 1.0.21",
"proc-macro2 1.0.24",
"quote 1.0.7",
"syn 1.0.41",
"syn 1.0.58",
"unicode-xid 0.2.1",
]
[[package]]
name = "sysfs-class"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e1bbcf869732c45a77898f7f61ed6d411dfc37613517e444842f58d428856d1"
dependencies = [
"numtoa",
]
[[package]]
name = "thiserror"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1"
dependencies = [
"proc-macro2 1.0.24",
"quote 1.0.7",
"syn 1.0.58",
]
[[package]]
name = "time"
version = "0.1.44"

View File

@ -8,13 +8,13 @@ use std::io;
use std::thread;
use std::time::Duration;
use osd::notify::{OSD, Urgency};
use osd::config::Config;
use osd::notify::{Urgency, OSD};
#[derive(Debug, Eq, PartialEq)]
enum Threshold {
Percentage(i32),
Minutes(i32)
Minutes(i32),
}
#[derive(Debug, Eq, PartialEq)]
@ -22,17 +22,21 @@ enum State {
Low,
Critical,
Charging,
Normal
Normal,
}
fn threshold_sane(thresh: Threshold) -> Option<Threshold> {
match thresh {
Threshold::Percentage(p) => {
if p < 0 || p > 100 { return None; }
if p < 0 || p > 100 {
return None;
}
Some(thresh)
},
}
Threshold::Minutes(m) => {
if m < 0 { return None; }
if m < 0 {
return None;
}
Some(thresh)
}
}
@ -46,9 +50,12 @@ fn parse_threshold(thresh: String) -> Option<Threshold> {
let parsed = s.parse();
match last {
Some('%') => parsed.map(Threshold::Percentage).ok().and_then(threshold_sane),
Some('%') => parsed
.map(Threshold::Percentage)
.ok()
.and_then(threshold_sane),
Some('m') => parsed.map(Threshold::Minutes).ok().and_then(threshold_sane),
_ => None
_ => None,
}
}
@ -58,11 +65,17 @@ mod parse_threshold_tests {
use super::Threshold;
#[test]
fn parses_percentage() {
assert_eq!(parse_threshold("15%".to_string()), Some(Threshold::Percentage(15)));
assert_eq!(
parse_threshold("15%".to_string()),
Some(Threshold::Percentage(15))
);
}
#[test]
fn parses_minutes() {
assert_eq!(parse_threshold("10m".to_string()), Some(Threshold::Minutes(10)));
assert_eq!(
parse_threshold("10m".to_string()),
Some(Threshold::Minutes(10))
);
}
#[test]
fn fails_on_incorrect_percentage() {
@ -100,16 +113,19 @@ fn format_duration(duration: f32) -> String {
let minutes = (d % 3600) / 60;
let seconds = d % 60;
if hours > 0 {
s.push_str(&format!("{}h", hours));
}
if minutes > 0 {
if hours > 0 { s.push(' '); }
if hours > 0 {
s.push(' ');
}
s.push_str(&format!("{}m", minutes));
}
if seconds > 0 {
if hours > 0 || minutes > 0 { s.push(' '); }
if hours > 0 || minutes > 0 {
s.push(' ');
}
s.push_str(&format!("{}s", seconds));
}
s
@ -158,8 +174,10 @@ fn main() -> battery::Result<()> {
let low_threshold_str = config.get_default("threshold", "low", String::from("30m"));
let critical_threshold_str = config.get_default("threshold", "critical", String::from("10m"));
let low_threshold = parse_threshold(low_threshold_str).expect("Low threshold is incorrect: must be either a percentage or minutes");
let critical_threshold = parse_threshold(critical_threshold_str).expect("Critical threshold is incorrect: must be either a percentage or minutes");
let low_threshold = parse_threshold(low_threshold_str)
.expect("Low threshold is incorrect: must be either a percentage or minutes");
let critical_threshold = parse_threshold(critical_threshold_str)
.expect("Critical threshold is incorrect: must be either a percentage or minutes");
let refresh_interval = config.get_default("default", "refresh interval", 30);
@ -182,7 +200,6 @@ fn main() -> battery::Result<()> {
let mut state: State;
let mut last_state: State = State::Normal;
loop {
state = match battery.state() {
battery::State::Charging => State::Charging,
@ -192,12 +209,36 @@ fn main() -> battery::Result<()> {
let tte = battery.time_to_empty().map(|q| q.value).unwrap_or(0.) as i32 / 60;
println!("{:?}, {:?}", soc, tte);
let low = match low_threshold {
Threshold::Percentage(p) => if soc <= p { State::Low } else { State::Normal },
Threshold::Minutes(m) => if tte <= m { State::Low } else { State::Normal }
Threshold::Percentage(p) => {
if soc <= p {
State::Low
} else {
State::Normal
}
}
Threshold::Minutes(m) => {
if tte <= m {
State::Low
} else {
State::Normal
}
}
};
match critical_threshold {
Threshold::Percentage(p) => if soc <= p { State::Critical } else { low },
Threshold::Minutes(m) => if tte <= m { State::Critical } else { low }
Threshold::Percentage(p) => {
if soc <= p {
State::Critical
} else {
low
}
}
Threshold::Minutes(m) => {
if tte <= m {
State::Critical
} else {
low
}
}
}
}
};
@ -206,25 +247,34 @@ fn main() -> battery::Result<()> {
match state {
State::Charging => {
if let Some(ttf) = battery.time_to_full() {
osd.title = Some(format!("Charging, {} until full", format_duration(ttf.value)));
osd.title = Some(format!(
"Charging, {} until full",
format_duration(ttf.value)
));
osd.urgency = Urgency::Low;
osd.update().unwrap();
};
}
State::Low => {
if let Some(tte) = battery.time_to_empty() {
osd.title = Some(format!("Low battery, {} remaining", format_duration(tte.value)));
osd.title = Some(format!(
"Low battery, {} remaining",
format_duration(tte.value)
));
osd.urgency = Urgency::Normal;
osd.update().unwrap();
};
},
}
State::Normal | State::Critical => {}
}
}
if state == State::Critical {
if let Some(tte) = battery.time_to_empty() {
osd.title = Some(format!("Critically low battery, {} remaining", format_duration(tte.value)));
osd.title = Some(format!(
"Critically low battery, {} remaining",
format_duration(tte.value)
));
osd.urgency = Urgency::Critical;
osd.update().unwrap();
};

View File

@ -4,14 +4,13 @@
extern crate blurz;
extern crate simple_osd_common as osd;
use blurz::bluetooth_session::BluetoothSession;
use blurz::bluetooth_adapter::BluetoothAdapter;
use blurz::bluetooth_session::BluetoothSession;
use osd::config::Config;
use osd::notify::{OSD, Urgency};
use osd::notify::{Urgency, OSD};
fn main() {
let mut config = Config::new("bluetooth");
let refresh_interval = config.get_default("default", "refresh interval", 15);

View File

@ -8,4 +8,5 @@ edition = "2018"
[dependencies]
simple-osd-common = { version = "0.1", path = "../common" }
backlight = "0.1.1"
sysfs-class = "0.1.2"
thiserror = "1.0"

View File

@ -1,22 +1,44 @@
// This is free and unencumbered software released into the public domain.
// balsoft 2020
extern crate backlight;
extern crate simple_osd_common as osd;
extern crate sysfs_class;
use osd::config::Config;
use osd::notify::{OSD, OSDContents, OSDProgressText};
use osd::notify::{OSDContents, OSDProgressText, OSD};
use std::path::PathBuf;
use log;
use sysfs_class::{Backlight, Brightness, SysClass};
use thiserror::Error;
use backlight::Brightness;
#[derive(Debug, Error)]
pub enum BrightnessError {
#[error("Failed to initialite backlight (possibly invalid backend): {0:?}")]
BacklightInitError(std::io::Error),
#[error("Failed to get maximum brightness: {0:?}")]
BacklightMaxBrightnessError(std::io::Error),
#[error("Failed to get brightness: {0:?}")]
BacklightBrightnessError(std::io::Error),
}
fn main() {
fn brightness_daemon() -> Result<(), BrightnessError> {
let mut config = Config::new("brightness");
let refresh_interval = config.get_default("default", "refresh interval", 1);
let brightness = Brightness::default();
let backend = config.get_default(
"default",
"backlight backend",
String::from("intel_backlight"),
);
let m = brightness.get_max_brightness().unwrap() as f32;
let brightness: Backlight = SysClass::from_path(&PathBuf::from(backend))
.map_err(BrightnessError::BacklightInitError)?;
let m = brightness
.max_brightness()
.map(|b| b as f32)
.map_err(BrightnessError::BacklightMaxBrightnessError)?;
let mut osd = OSD::new();
osd.title = Some(String::from("Screen brightness"));
@ -26,7 +48,10 @@ fn main() {
let mut last_b: f32 = 0.;
loop {
b = brightness.get_brightness().unwrap() as f32;
b = brightness
.brightness()
.map(|b| b as f32)
.map_err(BrightnessError::BacklightBrightnessError)?;
if (b - last_b).abs() > 0.1 {
osd.contents = OSDContents::Progress(b / m, OSDProgressText::Percentage);
@ -38,3 +63,13 @@ fn main() {
std::thread::sleep(std::time::Duration::from_secs(refresh_interval))
}
}
fn main() {
match brightness_daemon() {
Ok(_) => { },
Err(err) => {
error!(err);
std::process::exit(1);
}
}
}

View File

@ -1,9 +1,9 @@
extern crate simple_osd_common as osd;
use osd::notify::{OSD, OSDContents, OSDProgressText, Urgency};
use osd::config::Config;
use std::time::Duration;
use osd::notify::{OSDContents, OSDProgressText, Urgency, OSD};
use std::thread::sleep;
use std::time::Duration;
fn main() {
let mut config = Config::new("simple-example");
@ -12,9 +12,13 @@ fn main() {
println!("Value of foo is {}", foo);
let example_no_default = config.get::<i32>("example section", "example variable with no default");
let example_no_default =
config.get::<i32>("example section", "example variable with no default");
println!("Value of example variable with no default is {:?}", example_no_default);
println!(
"Value of example variable with no default is {:?}",
example_no_default
);
let refresh_interval = config.get_default("default", "refresh interval", 1);
@ -26,13 +30,15 @@ fn main() {
let mut percentage = 0.;
let mut osd_progress_bar_percentage = OSD::new();
osd_progress_bar_percentage.title = Some("A progress bar showing important percentage!".to_string());
osd_progress_bar_percentage.title =
Some("A progress bar showing important percentage!".to_string());
let eta = 15.;
let mut elapsed = 0.;
let mut osd_progress_bar_text = OSD::new();
osd_progress_bar_text.title = Some("Nuclear warhead launch in progress, time left:".to_string());
osd_progress_bar_text.title =
Some("Nuclear warhead launch in progress, time left:".to_string());
osd_progress_bar_text.urgency = Urgency::Low;
loop {
@ -40,9 +46,13 @@ fn main() {
elapsed = (elapsed + refresh_interval as f32) % eta;
osd_progress_bar_percentage.contents = OSDContents::Progress(percentage, OSDProgressText::Percentage);
osd_progress_bar_percentage.contents =
OSDContents::Progress(percentage, OSDProgressText::Percentage);
osd_progress_bar_text.contents = OSDContents::Progress(elapsed / eta, OSDProgressText::Text(Some(format!("{}s / {}s", elapsed, eta))));
osd_progress_bar_text.contents = OSDContents::Progress(
elapsed / eta,
OSDProgressText::Text(Some(format!("{}s / {}s", elapsed, eta))),
);
osd_simple.update();
osd_progress_bar_percentage.update();
@ -50,5 +60,4 @@ fn main() {
sleep(Duration::from_secs(refresh_interval));
}
}

View File

@ -1,30 +1,29 @@
// This is free and unencumbered software released into the public domain.
// balsoft 2020
extern crate notify_rust;
extern crate xdg;
extern crate configparser;
extern crate dbus;
extern crate notify_rust;
extern crate xdg;
pub static APPNAME: &str = "simple-osd";
pub mod config {
use configparser::ini::Ini;
use std::fs::{metadata, File};
use xdg::BaseDirectories;
use std::fs::{File, metadata};
use std::str::FromStr;
use std::default::Default;
use std::fmt::Debug;
use std::fmt::Display;
use std::default::Default;
use std::str::FromStr;
pub struct Config {
config_path: Option<String>,
config: Ini
config: Ini,
}
impl Config {
pub fn new(name: &'static str) -> Config {
let mut config = Ini::new();
@ -33,7 +32,10 @@ pub mod config {
let config_path_option = xdg_dirs.place_config_file(name).ok();
if let Some(config_path_buf) = config_path_option.clone() {
if metadata(config_path_buf.clone()).map(|m| m.is_file()).unwrap_or(false) {
if metadata(config_path_buf.clone())
.map(|m| m.is_file())
.unwrap_or(false)
{
let _ = config.load(config_path_buf.to_str().unwrap());
} else {
let _ = File::create(config_path_buf);
@ -42,17 +44,25 @@ pub mod config {
let config_path = config_path_option.map(|p| p.to_str().unwrap().to_string());
Config { config, config_path }
Config {
config,
config_path,
}
}
pub fn get<T>(&mut self, section: &str, key: &str) -> Option<T>
where
T: FromStr,
<T as FromStr>::Err: Debug
<T as FromStr>::Err: Debug,
{
self.config.get(section, key).map(|s: String| { s.parse().unwrap() }).or_else(|| {
self.config
.get(section, key)
.map(|s: String| s.parse().unwrap())
.or_else(|| {
self.config.set(section, key, None);
self.config_path.as_ref().map(|path| self.config.write(path.as_str()));
self.config_path
.as_ref()
.map(|path| self.config.write(path.as_str()));
None
})
}
@ -61,13 +71,15 @@ pub mod config {
where
T: FromStr,
T: Display,
<T as FromStr>::Err: Debug
<T as FromStr>::Err: Debug,
{
let val: Option<T> = self.get(section, key);
val.unwrap_or_else(|| {
self.config.set(section, key, Some(format!("{}", default)));
self.config_path.as_ref().map(|path| self.config.write(path.as_str()));
self.config_path
.as_ref()
.map(|path| self.config.write(path.as_str()));
default
})
}
@ -77,28 +89,28 @@ pub mod config {
T: FromStr,
T: Display,
T: Default,
<T as FromStr>::Err: Debug
<T as FromStr>::Err: Debug,
{
self.get_default(section, key, T::default())
}
}
}
pub mod notify {
use notify_rust::{Notification, NotificationHandle};
use dbus::ffidisp::{Connection, BusType};
use std::thread;
pub use notify_rust::Urgency;
use crate::config::Config;
use dbus::ffidisp::{BusType, Connection};
pub use notify_rust::Urgency;
use notify_rust::{Notification, NotificationHandle};
use std::default::Default;
use std::thread;
pub enum OSDProgressText {
Percentage,
Text(Option<String>)
Text(Option<String>),
}
pub enum OSDContents {
Simple(Option<String>),
Progress(f32, OSDProgressText)
Progress(f32, OSDProgressText),
}
impl Default for OSDContents {
@ -110,7 +122,7 @@ pub mod notify {
struct CustomHandle {
pub id: u32,
pub connection: Connection,
pub notification: Notification
pub notification: Notification,
}
pub struct OSD {
@ -135,7 +147,7 @@ pub mod notify {
// Internal notification
notification: Notification,
id: Option<u32>
id: Option<u32>,
}
impl OSD {
@ -155,20 +167,27 @@ pub mod notify {
let notification = Notification::new();
OSD {
title: None, icon: None,
title: None,
icon: None,
contents: OSDContents::default(),
urgency: Urgency::Normal, id: None,
urgency: Urgency::Normal,
id: None,
timeout,
length, full, empty, start, end,
notification
length,
full,
empty,
start,
end,
notification,
}
}
fn construct_fake_handle(id: u32, notification: Notification) -> NotificationHandle {
let h = CustomHandle
{ id,
let h = CustomHandle {
id,
connection: Connection::get_private(BusType::Session).unwrap(),
notification };
notification,
};
unsafe {
let handle: NotificationHandle = std::mem::transmute(h);
handle
@ -204,19 +223,21 @@ pub mod notify {
s.push_str(((value * 100.) as i32).to_string().as_str());
s.push('%');
},
}
OSDProgressText::Text(text) => {
if let Some(text) = text.as_ref() { s.push_str(text.as_str()) };
if let Some(text) = text.as_ref() {
s.push_str(text.as_str())
};
}
}
Some(s)
}
};
self.id.map(|i| self.notification.id(i));
let handle = self.notification
let handle = self
.notification
.summary(self.title.as_deref().unwrap_or(""))
.body(&text.unwrap_or_else(String::new))
.icon(self.icon.as_deref().unwrap_or(""))
@ -228,8 +249,10 @@ pub mod notify {
Ok(())
}
pub fn on_close<F: 'static>(&mut self, callback: F) where F: std::ops::FnOnce() + Send {
pub fn on_close<F: 'static>(&mut self, callback: F)
where
F: std::ops::FnOnce() + Send,
{
if let Some(id) = self.id {
let notification = self.notification.clone();

View File

@ -1,13 +1,13 @@
pub extern crate simple_osd_common as osd;
pub extern crate mpris;
pub extern crate simple_osd_common as osd;
pub use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicBool, Ordering};
pub use std::sync::{Arc, Mutex};
use std::ops::{Deref};
use std::ops::Deref;
pub use osd::notify::{OSD, OSDContents, OSDProgressText};
pub use osd::config::Config;
pub use osd::notify::{OSDContents, OSDProgressText, OSD};
use mpris::{PlaybackStatus, PlayerFinder};
@ -44,7 +44,7 @@ fn format_artists(artists: Vec<&str>) -> Option<String> {
v.reverse();
if v.len() < 2 {
return Some(v.pop()?.to_string())
return Some(v.pop()?.to_string());
}
let mut s = String::new();
@ -72,16 +72,24 @@ mod format_artists_test {
}
#[test]
fn one() {
assert_eq!(format_artists(["John Doe"].to_vec()), Some("John Doe".to_string()));
assert_eq!(
format_artists(["John Doe"].to_vec()),
Some("John Doe".to_string())
);
}
#[test]
fn two() {
assert_eq!(format_artists(["John Doe", "Jane Doe"].to_vec()), Some("John Doe & Jane Doe".to_string()));
assert_eq!(
format_artists(["John Doe", "Jane Doe"].to_vec()),
Some("John Doe & Jane Doe".to_string())
);
}
#[test]
fn many() {
assert_eq!(format_artists(["John Doe", "Jane Doe", "Chris P. Bacon", "Seymore Clevarge"].to_vec()),
Some("John Doe, Jane Doe, Chris P. Bacon & Seymore Clevarge".to_string()));
assert_eq!(
format_artists(["John Doe", "Jane Doe", "Chris P. Bacon", "Seymore Clevarge"].to_vec()),
Some("John Doe, Jane Doe, Chris P. Bacon & Seymore Clevarge".to_string())
);
}
}
@ -91,51 +99,71 @@ mod volume_changes {
use super::*;
use pulse::mainloop::standard::Mainloop;
use pulse::mainloop::standard::IterateResult;
use pulse::context::subscribe::{subscription_masks, Facility, Operation};
use pulse::context::Context;
use pulse::context::subscribe::{subscription_masks, Operation, Facility};
use pulse::mainloop::standard::IterateResult;
use pulse::mainloop::standard::Mainloop;
pub(super) struct VolumeMonitor {
mainloop: Arc<Mutex<Mainloop>>,
#[allow(dead_code)]
context: Arc<Mutex<Context>>
context: Arc<Mutex<Context>>,
}
impl VolumeMonitor {
pub fn new(config: Arc<Mutex<Config>>, trigger: Arc<Mutex<SystemTime>>, dismissed: Arc<Mutex<AtomicBool>>) -> VolumeMonitor {
let mainloop = Arc::new(Mutex::new
(Mainloop::new().expect("Failed to create mainloop")));
pub fn new(
config: Arc<Mutex<Config>>,
trigger: Arc<Mutex<SystemTime>>,
dismissed: Arc<Mutex<AtomicBool>>,
) -> VolumeMonitor {
let mainloop = Arc::new(Mutex::new(
Mainloop::new().expect("Failed to create mainloop"),
));
let context = Arc::new(Mutex::new(Context::new(
mainloop.lock().unwrap().deref(), osd::APPNAME
).expect("Failed to create new context")));
let context = Arc::new(Mutex::new(
Context::new(mainloop.lock().unwrap().deref(), osd::APPNAME)
.expect("Failed to create new context"),
));
context.lock().unwrap().connect(config.lock().unwrap().get::<String>("pulseaudio", "server").as_deref(), 0, None)
context
.lock()
.unwrap()
.connect(
config
.lock()
.unwrap()
.get::<String>("pulseaudio", "server")
.as_deref(),
0,
None,
)
.expect("Failed to connect context");
// Wait for context to be ready
loop {
match mainloop.lock().unwrap().iterate(false) {
IterateResult::Quit(_) |
IterateResult::Err(_) => {
IterateResult::Quit(_) | IterateResult::Err(_) => {
panic!("Iterate state was not success, quitting...");
},
IterateResult::Success(_) => {},
}
IterateResult::Success(_) => {}
}
match context.lock().unwrap().get_state() {
pulse::context::State::Ready => { break; },
pulse::context::State::Failed |
pulse::context::State::Unconnected |
pulse::context::State::Terminated => {
pulse::context::State::Ready => {
break;
}
pulse::context::State::Failed
| pulse::context::State::Unconnected
| pulse::context::State::Terminated => {
panic!("Context state failed/terminated, quitting...");
},
}
_ => {}
}
}
context.lock().unwrap().subscribe(subscription_masks::SINK, |success| {
context
.lock()
.unwrap()
.subscribe(subscription_masks::SINK, |success| {
if !success {
eprintln!("failed to subscribe to events");
return;
@ -149,17 +177,19 @@ mod volume_changes {
}
};
context.lock().unwrap().set_subscribe_callback(Some(Box::new(subscribe_callback)));
context
.lock()
.unwrap()
.set_subscribe_callback(Some(Box::new(subscribe_callback)));
VolumeMonitor { mainloop, context }
}
pub fn tick(&self) {
match self.mainloop.lock().unwrap().iterate(false) {
IterateResult::Quit(_) |
IterateResult::Err(_) => {
IterateResult::Quit(_) | IterateResult::Err(_) => {
panic!("Iterate state was not success, quitting...");
},
IterateResult::Success(_) => { },
}
IterateResult::Success(_) => {}
}
}
}
@ -175,15 +205,28 @@ fn main() {
let mut progress_tracker = player.track_progress(100).unwrap();
let update_on_volume_change = config.lock().unwrap().get_default("default", "update on volume change", true);
let timeout = config.lock().unwrap().get_default("default", "notification display time", 5);
let update_on_volume_change =
config
.lock()
.unwrap()
.get_default("default", "update on volume change", true);
let timeout = config
.lock()
.unwrap()
.get_default("default", "notification display time", 5);
let trigger = Arc::new(Mutex::new(SystemTime::now()));
#[cfg(feature = "display_on_volume_changes")]
let vc = if update_on_volume_change {
Some(volume_changes::VolumeMonitor::new(config.clone(), trigger.clone(), dismissed.clone()))
} else { None };
Some(volume_changes::VolumeMonitor::new(
config.clone(),
trigger.clone(),
dismissed.clone(),
))
} else {
None
};
drop(config);
@ -203,30 +246,49 @@ fn main() {
dismissed.lock().unwrap().store(false, Ordering::Relaxed);
}
let elapsed = trigger.lock().unwrap().elapsed().unwrap_or_else(|_| Duration::from_secs(timeout + 1));
let elapsed = trigger
.lock()
.unwrap()
.elapsed()
.unwrap_or_else(|_| Duration::from_secs(timeout + 1));
if elapsed.as_secs() < timeout && playback_status != PlaybackStatus::Stopped && ! dismissed.lock().unwrap().load(Ordering::Relaxed) {
if elapsed.as_secs() < timeout
&& playback_status != PlaybackStatus::Stopped
&& !dismissed.lock().unwrap().load(Ordering::Relaxed)
{
let metadata = progress.metadata();
let artists = metadata.artists().and_then(format_artists).unwrap_or_else(|| "Unknown".to_string());
let artists = metadata
.artists()
.and_then(format_artists)
.unwrap_or_else(|| "Unknown".to_string());
let position = progress.position();
let length = progress.length().unwrap_or_else(|| Duration::from_secs(100000000));
let length = progress
.length()
.unwrap_or_else(|| Duration::from_secs(100000000));
osd.title = Some(format!("{:?}: {} - {}", playback_status, title, artists));
let ratio = position.as_secs_f32() / length.as_secs_f32();
let text = format!("{} / {}", format_duration(position), format_duration(length));
let text = format!(
"{} / {}",
format_duration(position),
format_duration(length)
);
osd.contents = OSDContents::Progress(ratio, OSDProgressText::Text(Some(text)));
osd.timeout = 1;
osd.icon = match playback_status {
PlaybackStatus::Playing => Some("media-playback-start".to_string()),
PlaybackStatus::Paused => Some("media-playback-pause".to_string()),
_ => None
_ => None,
};
osd.update().unwrap();
if !waiting_on_close {
waiting_on_close = true;
let dismissed_clone = dismissed.clone();
osd.on_close(move || {
dismissed_clone.lock().unwrap().store(true, Ordering::Relaxed);
dismissed_clone
.lock()
.unwrap()
.store(true, Ordering::Relaxed);
});
}
} else {
@ -234,11 +296,12 @@ fn main() {
osd.close()
}
old_title = title;
old_playback_status = playback_status;
#[cfg(feature = "display_on_volume_changes")]
if let Some(v) = vc.as_ref() { v.tick() };
if let Some(v) = vc.as_ref() {
v.tick()
};
}
}

View File

@ -5,15 +5,15 @@ extern crate libpulse_binding as pulse;
extern crate simple_osd_common as osd;
use std::rc::Rc;
use std::cell::RefCell;
use std::rc::Rc;
use pulse::mainloop::standard::Mainloop;
use pulse::context::Context;
use osd::config::Config;
use osd::notify::{OSD, OSDContents, OSDProgressText};
use osd::notify::{OSDContents, OSDProgressText, OSD};
use pulse::context::Context;
use pulse::mainloop::standard::Mainloop;
use pulse::context::subscribe::{subscription_masks, Operation, Facility};
use pulse::context::subscribe::{subscription_masks, Facility, Operation};
use pulse::callbacks::ListResult;
use pulse::context::introspect::SinkInfo;
@ -23,24 +23,29 @@ fn main() {
let mut config = Config::new("pulseaudio");
let mut context = Context::new(
&mainloop, osd::APPNAME
).expect("Failed to create new context");
let mut context = Context::new(&mainloop, osd::APPNAME).expect("Failed to create new context");
context.connect(config.get::<String>("default", "server").as_deref(), 0, None)
context
.connect(
config.get::<String>("default", "server").as_deref(),
0,
None,
)
.expect("Failed to connect context");
// Wait for context to be ready
loop {
mainloop.iterate(false);
match context.get_state() {
pulse::context::State::Ready => { break; },
pulse::context::State::Failed |
pulse::context::State::Unconnected |
pulse::context::State::Terminated => {
pulse::context::State::Ready => {
break;
}
pulse::context::State::Failed
| pulse::context::State::Unconnected
| pulse::context::State::Terminated => {
eprintln!("Context state failed/terminated, quitting...");
return;
},
}
_ => {}
}
}
@ -56,7 +61,6 @@ fn main() {
let introspector = context.introspect();
let osd = Rc::new(RefCell::new(OSD::new()));
osd.borrow_mut().icon = Some(String::from("multimedia-volume-control"));
@ -66,7 +70,8 @@ fn main() {
let sink_name = i.description.as_deref().unwrap_or("Unnamed sink");
let muted_message = if i.mute { " [MUTED]" } else { "" };
osd.borrow_mut().title = Some(format!("Volume on {}{}", sink_name, muted_message));
osd.borrow_mut().contents = OSDContents::Progress(volume.0 as f32 / 65536., OSDProgressText::Percentage);
osd.borrow_mut().contents =
OSDContents::Progress(volume.0 as f32 / 65536., OSDProgressText::Percentage);
osd.borrow_mut().update().unwrap();
}
};