Implement some daemons

This commit is contained in:
Alexander Bantyev 2020-09-13 20:26:46 +03:00
parent 4a04b840a7
commit a4ce780084
Signed by: balsoft
GPG Key ID: E081FF12ADCB4AD5
19 changed files with 2562 additions and 0 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
*/target
target/
result

351
Cargo.lock generated Normal file
View 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"

1570
Cargo.nix Normal file

File diff suppressed because it is too large Load Diff

9
Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[workspace]
members = [
"common",
"battery",
"bluetooth",
"brightness",
"pulseaudio"
]

23
README.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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();
}
}

3
shell.nix Normal file
View File

@ -0,0 +1,3 @@
(import (fetchTarball https://github.com/edolstra/flake-compat/archive/master.tar.gz) {
src = builtins.fetchGit ./.;
}).shellNix