Compare commits
1 Commits
master
...
better-log
Author | SHA1 | Date | |
---|---|---|---|
ffd4d9a2c2 |
64
Cargo.lock
generated
64
Cargo.lock
generated
@ -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"
|
||||
|
@ -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();
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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"
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user