Implement some daemons
This commit is contained in:
parent
4a04b840a7
commit
a4ce780084
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
*/target
|
||||
target/
|
||||
result
|
||||
|
351
Cargo.lock
generated
Normal file
351
Cargo.lock
generated
Normal file
@ -0,0 +1,351 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
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 = "battery"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8775be4956c98c9ac7c11cc383d632636935d3a688dabddb71ac83ba00a3a72f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"core-foundation",
|
||||
"lazycell",
|
||||
"libc",
|
||||
"mach",
|
||||
"num-traits",
|
||||
"uom",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "blurz"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1bc2df4d0f1d373b324672d9966aca3f5ba1f03d69edad6240144774539ea59"
|
||||
dependencies = [
|
||||
"dbus",
|
||||
"hex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[package]]
|
||||
name = "configparser"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe1d7dcda7d1da79e444bdfba1465f2f849a58b07774e1df473ee77030cb47a7"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
|
||||
|
||||
[[package]]
|
||||
name = "dbus"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48b5f0f36f1eebe901b0e6bee369a77ed3396334bf3f09abd46454a576f71819"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libdbus-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gdk-pixbuf"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16160d212ae91abe9f3324c3fb233929ba322dde63585d15cda3336f8c529ed1"
|
||||
dependencies = [
|
||||
"gdk-pixbuf-sys",
|
||||
"glib",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gdk-pixbuf-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "798f97101eea8180da363d0e80e07ec7ec6d1809306601c0100c1de5bc8b4f52"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"gio-sys",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gio-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a303bbf7a5e75ab3b627117ff10e495d1b9e97e1d68966285ac2b1f6270091bc"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glib"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9b0452824cc63066940f01adc721804919f0b76cdba3cfab977b00b87f16d4a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glib-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9693049613ff52b93013cc3d2590366d8e530366d288438724b73f6c7dc4be8"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gobject-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60d507c87a71b1143c66ed21a969be9b99a76df234b342d733e787e6c9c7d7c2"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"glib-sys",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "lazycell"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235"
|
||||
|
||||
[[package]]
|
||||
name = "libdbus-sys"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc12a3bc971424edbbf7edaf6e5740483444db63aa8e23d3751ff12a30f306f0"
|
||||
dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libnotify"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10506a4f8bc6f8f7ccc6fde3a8290378d7aed3d1a26dca606a73e2ffe140cc2d"
|
||||
dependencies = [
|
||||
"gdk-pixbuf",
|
||||
"gdk-pixbuf-sys",
|
||||
"glib",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"libnotify-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libnotify-sys"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0a716b9b7d24ed10f1eb431e1527fa13c9a4bf2d4fa68bb3e54da1d0747383c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"gdk-pixbuf-sys",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libpulse-binding"
|
||||
version = "2.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1795efad67f34ae95740ded62e3c346e707b0caeb0f6088d75958e97316b5538"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libpulse-sys",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libpulse-sys"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f53857071d564f864755a6945d316cc8fe62c36ed20bd4231c8ab6b62141786"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mach"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33"
|
||||
|
||||
[[package]]
|
||||
name = "simple-osd-battery"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"battery",
|
||||
"simple-osd-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simple-osd-bluetooth"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"blurz",
|
||||
"simple-osd-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simple-osd-brightness"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"backlight",
|
||||
"simple-osd-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simple-osd-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"configparser",
|
||||
"libnotify",
|
||||
"xdg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simple-osd-pulseaudio"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libpulse-binding",
|
||||
"simple-osd-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
|
||||
|
||||
[[package]]
|
||||
name = "uom"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8bb593f5252356bfb829112f8fca2d0982d48588d2d6bb5a92553b0dfc4c9aba"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "xdg"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57"
|
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"common",
|
||||
|
||||
"battery",
|
||||
"bluetooth",
|
||||
"brightness",
|
||||
"pulseaudio"
|
||||
]
|
23
README.md
Normal file
23
README.md
Normal file
@ -0,0 +1,23 @@
|
||||
# Simple OSD daemons
|
||||
|
||||
A collection of simple yet configurable OSD daemons that use Freedesktop notifications to display various information about your system.
|
||||
|
||||
## Installation
|
||||
|
||||
### Nix
|
||||
|
||||
These commands will allow you to try the daemons without installing anything globally; Likely, you want to somehow install this on your system after this. How you do it depends on your setup and preferences.
|
||||
|
||||
`nix shell git+https://code.balsoft.ru/balsoft/simple-osd-daemons` to get all daemons. Add `#$DAEMON` to only get `$DAEMON`. If you don't use nix 3 yet, try `nix-shell https://code.balsoft.ru/balsoft/simple-osd-daemons/archive/master.tar.gz -A defaultPackage.x86_64-linux`
|
||||
|
||||
### Cargo
|
||||
|
||||
`cargo build`
|
||||
|
||||
## Usage
|
||||
|
||||
Run the daemons you need. At this moment, none of them accept any arguments.
|
||||
|
||||
### Configuration
|
||||
|
||||
`simple-osd-daemons` follows XDG Basedir specification: configuration will be written to `$XDG_CONFIG_HOME/simple-osd/`, typically `~/.config/simple-osd/`. Each daemon has a separate configuration file in INI format, and there is also a `common` configuration file. On startup, the daemons will create their configuration files and populate them with default values if they don't exist.
|
11
battery/Cargo.toml
Normal file
11
battery/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "simple-osd-battery"
|
||||
version = "0.1.0"
|
||||
authors = ["Alexander Bantyev <balsoft@balsoft.ru>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
battery = "0.7.6"
|
||||
simple-osd-common = { version = "0.1", path = "../common" }
|
122
battery/src/main.rs
Normal file
122
battery/src/main.rs
Normal file
@ -0,0 +1,122 @@
|
||||
extern crate battery;
|
||||
extern crate simple_osd_common as osd;
|
||||
|
||||
use std::io;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use osd::notify::{OSD, OSDContents, OSDProgressText, Urgency};
|
||||
use osd::config::Config;
|
||||
|
||||
use battery::units::Time;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Threshold {
|
||||
Percentage(i32),
|
||||
Minutes(i32)
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
enum State {
|
||||
Low,
|
||||
Critical,
|
||||
Charging,
|
||||
Normal
|
||||
}
|
||||
|
||||
fn parse_threshold(thresh: String) -> Option<Threshold> {
|
||||
let mut s = thresh.clone();
|
||||
|
||||
let last = s.pop();
|
||||
|
||||
match last {
|
||||
Some('%') => s.parse().map(Threshold::Percentage).ok(),
|
||||
Some('m') => s.parse().map(Threshold::Minutes).ok(),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> battery::Result<()> {
|
||||
let mut config = Config::new("battery");
|
||||
|
||||
let mut low_threshold_str = config.get_default("threshold", "low", String::from("30m"));
|
||||
let mut 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 refresh_interval = config.get_default("default", "refresh interval", 30);
|
||||
|
||||
println!("{:?}, {:?}", low_threshold, critical_threshold);
|
||||
|
||||
let mut osd = OSD::new();
|
||||
osd.icon = Some(String::from("battery"));
|
||||
|
||||
let manager = battery::Manager::new()?;
|
||||
let mut battery = match manager.batteries()?.next() {
|
||||
Some(Ok(battery)) => battery,
|
||||
Some(Err(e)) => {
|
||||
eprintln!("Unable to access battery information");
|
||||
return Err(e);
|
||||
}
|
||||
None => {
|
||||
eprintln!("Unable to find any batteries");
|
||||
return Err(io::Error::from(io::ErrorKind::NotFound).into());
|
||||
}
|
||||
};
|
||||
|
||||
let mut state: State;
|
||||
let mut last_state: State = State::Normal;
|
||||
|
||||
loop {
|
||||
state = match battery.state() {
|
||||
battery::State::Charging => State::Charging,
|
||||
battery::State::Full => State::Normal,
|
||||
_ => {
|
||||
let soc = (battery.state_of_charge().value * 100.) as i32;
|
||||
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 }
|
||||
};
|
||||
match critical_threshold {
|
||||
Threshold::Percentage(p) => if soc <= p { State::Critical } else { low },
|
||||
Threshold::Minutes(m) => if tte <= m { State::Critical } else { low }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if state != last_state {
|
||||
match state {
|
||||
State::Charging => {
|
||||
battery.time_to_full().map(|ttf| {
|
||||
osd.title = Some(format!("Charging, {:?} until full", ttf));
|
||||
osd.urgency = Urgency::Low;
|
||||
osd.update();
|
||||
});
|
||||
}
|
||||
State::Low => {
|
||||
battery.time_to_empty().map(|tte| {
|
||||
osd.title = Some(format!("Low battery, {:?} remaining", tte));
|
||||
osd.urgency = Urgency::Normal;
|
||||
osd.update();
|
||||
});
|
||||
},
|
||||
State::Normal | State::Critical => { }
|
||||
}
|
||||
}
|
||||
|
||||
if state == State::Critical {
|
||||
battery.time_to_empty().map(|tte| {
|
||||
osd.title = Some(format!("Critically low battery, {:?} remaining", tte));
|
||||
osd.urgency = Urgency::Critical;
|
||||
osd.update();
|
||||
});
|
||||
}
|
||||
|
||||
thread::sleep(Duration::from_secs(refresh_interval));
|
||||
manager.refresh(&mut battery)?;
|
||||
last_state = state;
|
||||
}
|
||||
}
|
11
bluetooth/Cargo.toml
Normal file
11
bluetooth/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "simple-osd-bluetooth"
|
||||
version = "0.1.0"
|
||||
authors = ["Alexander Bantyev <balsoft@balsoft.ru>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
simple-osd-common = { version = "0.1", path = "../common" }
|
||||
blurz = "0.4.0"
|
43
bluetooth/src/main.rs
Normal file
43
bluetooth/src/main.rs
Normal file
@ -0,0 +1,43 @@
|
||||
extern crate blurz;
|
||||
extern crate simple_osd_common as osd;
|
||||
|
||||
use blurz::bluetooth_session::BluetoothSession;
|
||||
use blurz::bluetooth_adapter::BluetoothAdapter;
|
||||
|
||||
use osd::config::Config;
|
||||
use osd::notify::{OSD, Urgency};
|
||||
|
||||
fn main() {
|
||||
|
||||
let mut config = Config::new("bluetooth");
|
||||
|
||||
let refresh_interval = config.get_default("default", "refresh interval", 15);
|
||||
|
||||
let path: Option<String> = config.get("session", "path");
|
||||
|
||||
let session = BluetoothSession::create_session(path.as_deref()).unwrap();
|
||||
|
||||
let adapter = BluetoothAdapter::init(&session).unwrap();
|
||||
|
||||
let mut osd = OSD::new();
|
||||
osd.icon = Some(String::from("bluetooth"));
|
||||
osd.urgency = Urgency::Low;
|
||||
|
||||
let mut device_name: String;
|
||||
let mut last_device_name: String = String::new();
|
||||
|
||||
loop {
|
||||
let device = adapter.get_first_device().unwrap();
|
||||
|
||||
device_name = device.get_name().unwrap_or(String::from(""));
|
||||
|
||||
if device_name != last_device_name {
|
||||
osd.title = Some(format!("Bluetooth: connected to {}", device_name));
|
||||
osd.update();
|
||||
}
|
||||
|
||||
last_device_name = device_name;
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_secs(refresh_interval))
|
||||
}
|
||||
}
|
11
brightness/Cargo.toml
Normal file
11
brightness/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "simple-osd-brightness"
|
||||
version = "0.1.0"
|
||||
authors = ["Alexander Bantyev <balsoft@balsoft.ru>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
simple-osd-common = { version = "0.1", path = "../common" }
|
||||
backlight = "0.1.1"
|
35
brightness/src/main.rs
Normal file
35
brightness/src/main.rs
Normal file
@ -0,0 +1,35 @@
|
||||
extern crate backlight;
|
||||
extern crate simple_osd_common as osd;
|
||||
|
||||
use osd::config::Config;
|
||||
use osd::notify::{OSD, OSDContents, OSDProgressText};
|
||||
|
||||
use backlight::Brightness;
|
||||
|
||||
fn main() {
|
||||
let mut config = Config::new("brightness");
|
||||
|
||||
let refresh_interval = config.get_default("default", "refresh interval", 1);
|
||||
|
||||
let brightness = Brightness::default();
|
||||
|
||||
let m = brightness.get_max_brightness().unwrap() as f32;
|
||||
|
||||
let mut osd = OSD::new();
|
||||
osd.title = Some(String::from("Screen brightness"));
|
||||
|
||||
let mut b : f32;
|
||||
|
||||
let mut last_b : f32 = 0.;
|
||||
|
||||
loop {
|
||||
b = brightness.get_brightness().unwrap() as f32;
|
||||
|
||||
if b != last_b {
|
||||
osd.contents = OSDContents::Progress(b/m, OSDProgressText::Percentage);
|
||||
osd.update();
|
||||
}
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_secs(refresh_interval))
|
||||
}
|
||||
}
|
12
common/Cargo.toml
Normal file
12
common/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "simple-osd-common"
|
||||
version = "0.1.0"
|
||||
authors = ["Alexander Bantyev <balsoft@balsoft.ru>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
libnotify = "1.0.3"
|
||||
configparser = "1.0.0"
|
||||
xdg = "2.1"
|
179
common/src/lib.rs
Normal file
179
common/src/lib.rs
Normal file
@ -0,0 +1,179 @@
|
||||
extern crate libnotify;
|
||||
extern crate xdg;
|
||||
extern crate configparser;
|
||||
|
||||
pub static APPNAME: &str = "simple-osd";
|
||||
|
||||
pub mod config {
|
||||
use configparser::ini::Ini;
|
||||
use xdg::BaseDirectories;
|
||||
use std::fs::{File, metadata};
|
||||
|
||||
pub struct Config {
|
||||
config_path: Option<String>,
|
||||
config: Ini
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
||||
pub fn new(name: &'static str) -> Config {
|
||||
let mut config = Ini::new();
|
||||
|
||||
let xdg_dirs = BaseDirectories::with_prefix(crate::APPNAME).unwrap();
|
||||
|
||||
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) {
|
||||
config.load(config_path_buf.to_str().unwrap());
|
||||
} else {
|
||||
File::create(config_path_buf);
|
||||
}
|
||||
}
|
||||
|
||||
let config_path = config_path_option.map(|p| p.to_str().unwrap().to_string());
|
||||
|
||||
Config { config, config_path }
|
||||
}
|
||||
|
||||
pub fn get<T: std::str::FromStr>(&mut self, section: &str, key: &str) -> Option<T>
|
||||
where
|
||||
T: std::str::FromStr,
|
||||
<T as std::str::FromStr>::Err: std::fmt::Debug
|
||||
{
|
||||
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()));
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_default<T>(&mut self, section: &str, key: &str, default: T) -> T
|
||||
where
|
||||
T: std::str::FromStr,
|
||||
T: std::fmt::Display,
|
||||
<T as std::str::FromStr>::Err: std::fmt::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()));
|
||||
default
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
pub mod notify {
|
||||
use libnotify::Notification;
|
||||
pub use libnotify::Urgency;
|
||||
use crate::config::Config;
|
||||
|
||||
fn init_if_not_already() {
|
||||
if ! libnotify::is_initted() {
|
||||
println!("Initializing libnotify");
|
||||
libnotify::init(crate::APPNAME).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum OSDProgressText {
|
||||
Percentage,
|
||||
Text(Option<String>)
|
||||
}
|
||||
|
||||
pub enum OSDContents {
|
||||
Simple(Option<String>),
|
||||
Progress(f32, OSDProgressText)
|
||||
}
|
||||
|
||||
pub struct OSD {
|
||||
pub title: Option<String>,
|
||||
|
||||
pub icon: Option<String>,
|
||||
|
||||
pub contents: OSDContents,
|
||||
|
||||
pub urgency: Urgency,
|
||||
|
||||
// Progress bar stuff
|
||||
length: i32,
|
||||
|
||||
full: String,
|
||||
empty: String,
|
||||
|
||||
start: String,
|
||||
end: String,
|
||||
|
||||
// Internal notification
|
||||
notification: Notification
|
||||
}
|
||||
|
||||
impl OSD {
|
||||
pub fn new() -> OSD {
|
||||
init_if_not_already();
|
||||
|
||||
let mut config = Config::new("common");
|
||||
|
||||
let length = config.get_default("progressbar", "length", 20);
|
||||
|
||||
let full = config.get_default("progressbar", "full", String::from("█"));
|
||||
let empty = config.get_default("progressbar", "empty", String::from("░"));
|
||||
|
||||
let start = config.get_default("progressbar", "start", String::new());
|
||||
let end = config.get_default("progressbar", "end", String::new());
|
||||
|
||||
let notification = Notification::new("", None, None);
|
||||
|
||||
return OSD {
|
||||
title: None, icon: None,
|
||||
contents: OSDContents::Simple(None),
|
||||
urgency: Urgency::Normal,
|
||||
length, full, empty, start, end,
|
||||
notification
|
||||
};
|
||||
}
|
||||
|
||||
fn get_full_text(&self) -> Option<String> {
|
||||
match &self.contents {
|
||||
OSDContents::Simple(text) => text.clone(),
|
||||
OSDContents::Progress(value, text) => {
|
||||
let mut s = String::new();
|
||||
|
||||
s.push_str(self.start.as_str());
|
||||
|
||||
for _ in 0..(value * self.length as f32) as i32 {
|
||||
s.push_str(self.full.as_str())
|
||||
}
|
||||
|
||||
for _ in (value * self.length as f32) as i32..self.length {
|
||||
s.push_str(self.empty.as_str())
|
||||
}
|
||||
|
||||
s.push_str(self.end.as_str());
|
||||
|
||||
s.push_str(" ");
|
||||
|
||||
match text {
|
||||
OSDProgressText::Percentage => {
|
||||
s.push_str(((value * 100.) as i32).to_string().as_str());
|
||||
|
||||
s.push_str("%");
|
||||
},
|
||||
OSDProgressText::Text(text) => {
|
||||
text.as_ref().map(|text| s.push_str(text.as_str()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self) {
|
||||
self.notification.update(self.title.as_deref().unwrap_or(""), self.get_full_text().as_deref(), self.icon.as_deref()).unwrap();
|
||||
self.notification.set_urgency(self.urgency);
|
||||
self.notification.show().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
3
default.nix
Normal file
3
default.nix
Normal file
@ -0,0 +1,3 @@
|
||||
(import (fetchTarball https://github.com/edolstra/flake-compat/archive/master.tar.gz) {
|
||||
src = builtins.fetchGit ./.;
|
||||
}).defaultNix
|
26
flake.lock
Normal file
26
flake.lock
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1599773960,
|
||||
"narHash": "sha256-5bL52aaUOOyOBjgKh9/6jQlFbeE+WfVX7dpvjohmD+w=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "5916b9637048446755629c84ae6f13361f623d13",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
57
flake.nix
Normal file
57
flake.nix
Normal file
@ -0,0 +1,57 @@
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs";
|
||||
};
|
||||
|
||||
description = "A collection of simple on-screen-display daemons";
|
||||
|
||||
outputs = { self, nixpkgs }:
|
||||
let forAllSystems = f: builtins.mapAttrs (_: f) nixpkgs.legacyPackages;
|
||||
in {
|
||||
packages = forAllSystems (pkgs:
|
||||
let
|
||||
commonDeps = with pkgs; [ libnotify gdk_pixbuf glib ];
|
||||
|
||||
project = import ./Cargo.nix {
|
||||
inherit nixpkgs pkgs;
|
||||
defaultCrateOverrides = pkgs.defaultCrateOverrides // {
|
||||
simple-osd-battery = oa: {
|
||||
buildInputs = commonDeps;
|
||||
};
|
||||
simple-osd-bluetooth = oa: {
|
||||
buildInputs = commonDeps ++ [ pkgs.dbus_tools.lib pkgs.dbus_tools.dev ];
|
||||
nativeBuildInputs = [ pkgs.pkg-config ];
|
||||
};
|
||||
simple-osd-brightness = oa: {
|
||||
buildInputs = commonDeps;
|
||||
};
|
||||
# See https://github.com/kolloch/crate2nix/issues/149
|
||||
libpulse-binding = oa: {
|
||||
preBuild = "sed s/pulse::libpulse.so.0/pulse/ -i target/*link*";
|
||||
};
|
||||
simple-osd-pulseaudio = oa: {
|
||||
buildInputs = commonDeps ++ [ pkgs.libpulseaudio ];
|
||||
nativeBuildInputs = [ pkgs.pkg-config ];
|
||||
preBuild = "sed s/pulse::libpulse.so.0/pulse/ -i target/*link*";
|
||||
};
|
||||
};
|
||||
};
|
||||
membersList = builtins.attrValues (builtins.mapAttrs (name: member: { name = pkgs.lib.removePrefix "simple-osd-" name; value = member.build; }) project.workspaceMembers);
|
||||
in builtins.listToAttrs membersList);
|
||||
apps = builtins.mapAttrs (_:
|
||||
builtins.mapAttrs (_: pkg: {
|
||||
type = "app";
|
||||
program = "${pkg}/bin/${pkg.pname}";
|
||||
})) self.packages;
|
||||
|
||||
defaultPackage = builtins.mapAttrs (system: pkgs:
|
||||
pkgs.buildEnv {
|
||||
name = "simple-osd-daemons";
|
||||
paths = builtins.attrValues self.packages.${system};
|
||||
}) nixpkgs.legacyPackages;
|
||||
|
||||
devShell = builtins.mapAttrs (system: pkgs: pkgs.mkShell {
|
||||
inputsFrom = builtins.attrValues self.packages.${system};
|
||||
}) nixpkgs.legacyPackages;
|
||||
};
|
||||
}
|
11
pulseaudio/Cargo.toml
Normal file
11
pulseaudio/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "simple-osd-pulseaudio"
|
||||
version = "0.1.0"
|
||||
authors = ["Alexander Bantyev <balsoft@balsoft.ru>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
libpulse-binding = "2.16.2"
|
||||
simple-osd-common = { version = "0.1", path = "../common" }
|
84
pulseaudio/src/main.rs
Normal file
84
pulseaudio/src/main.rs
Normal file
@ -0,0 +1,84 @@
|
||||
extern crate libpulse_binding as pulse;
|
||||
|
||||
extern crate simple_osd_common as osd;
|
||||
|
||||
use pulse::mainloop::standard::Mainloop;
|
||||
use pulse::context::Context;
|
||||
use osd::config::Config;
|
||||
use osd::notify::{OSD, OSDContents, OSDProgressText};
|
||||
|
||||
use pulse::context::subscribe::{subscription_masks, Operation, Facility};
|
||||
|
||||
use pulse::callbacks::ListResult;
|
||||
use pulse::context::introspect::SinkInfo;
|
||||
|
||||
fn main() {
|
||||
let mut mainloop = Mainloop::new().expect("Failed to create mainloop");
|
||||
|
||||
let mut config = Config::new("pulseaudio");
|
||||
|
||||
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)
|
||||
.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 => {
|
||||
eprintln!("Context state failed/terminated, quitting...");
|
||||
return;
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!("connected");
|
||||
|
||||
context.subscribe(subscription_masks::SINK, |success| {
|
||||
if ! success {
|
||||
eprintln!("failed to subscribe to events");
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
let introspector = context.introspect();
|
||||
|
||||
|
||||
// Explanation for the unsafe:
|
||||
// Both subscribe_callback and sink_info_handler shall not outlive mainloop, but the borrow checker can't know that.
|
||||
// Thus, it moves osd into the subscribe_callback and then tries to move it into the sink_info_handler, but that's impossible.
|
||||
// In reality, both closures will be destroyed when mainloop quits, and osd's lifetime is the same as mainloop's.
|
||||
unsafe {
|
||||
let osd: *mut OSD = &mut OSD::new();
|
||||
(*osd).icon = Some(String::from("multimedia-volume-control"));
|
||||
|
||||
let sink_info_handler = move |results: ListResult<&SinkInfo>| {
|
||||
if let ListResult::Item(i) = results {
|
||||
let volume = i.volume.avg();
|
||||
let sink_name = i.description.as_deref().unwrap_or("Unnamed sink");
|
||||
let muted_message = if i.mute { " [MUTED]" } else { "" };
|
||||
(*osd).title = Some(format!("Volume on {}{}", sink_name, muted_message));
|
||||
(*osd).contents = OSDContents::Progress(volume.0 as f32 / 65536., OSDProgressText::Percentage);
|
||||
(*osd).update();
|
||||
}
|
||||
};
|
||||
|
||||
let subscribe_callback = move |facility, operation, index| {
|
||||
if facility == Some(Facility::Sink) && operation == Some(Operation::Changed) {
|
||||
introspector.get_sink_info_by_index(index, sink_info_handler);
|
||||
}
|
||||
};
|
||||
|
||||
context.set_subscribe_callback(Some(Box::new(subscribe_callback)));
|
||||
|
||||
// We need to run mainloop here for reasons I don't understand. It crashes otherwise.
|
||||
mainloop.run().unwrap();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user