Tailscale -> Netbird
Start Implement Idle Daemon with Dimming Display
This commit is contained in:
75
Cargo.lock
generated
75
Cargo.lock
generated
@@ -1361,6 +1361,12 @@ dependencies = [
|
||||
"litrs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||
|
||||
[[package]]
|
||||
name = "dunce"
|
||||
version = "1.0.5"
|
||||
@@ -2735,6 +2741,9 @@ dependencies = [
|
||||
"thiserror 2.0.21",
|
||||
"time-format",
|
||||
"tokio",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4249,6 +4258,15 @@ dependencies = [
|
||||
"pulldown-cmark",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.41.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn"
|
||||
version = "0.11.12"
|
||||
@@ -6108,6 +6126,63 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38a91b4eaddff87b1cd1074985e3713da4af2c49742d1b356b2c01670a67a078"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"downcast-rs",
|
||||
"rustix",
|
||||
"smallvec",
|
||||
"wayland-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-client"
|
||||
version = "0.31.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"rustix",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols"
|
||||
version = "0.32.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-scanner"
|
||||
version = "0.31.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quick-xml",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-sys"
|
||||
version = "0.31.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be"
|
||||
dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.106"
|
||||
|
||||
@@ -13,7 +13,7 @@ glib = "0.22.10"
|
||||
mpris = "2.1.0"
|
||||
time-format = "1.2.4"
|
||||
nmrs = "3.5.7"
|
||||
tokio = { version = "1.53.3", features = ["rt", "time", "net", "sync", "rt-multi-thread", "io-util", "macros"] }
|
||||
tokio = { version = "1.53.3", features = ["rt", "time", "net", "sync", "rt-multi-thread", "io-util", "macros", "signal"] }
|
||||
futures-util = "0.3"
|
||||
serde_json = "1.0.154"
|
||||
crossbeam-channel = "0.5.17"
|
||||
@@ -32,6 +32,10 @@ http-body-util = "0.1.5"
|
||||
brightness = { version = "0.8.0", features = ["blocking"] }
|
||||
battery = "0.7.8"
|
||||
ddc-hi = { git = "https://github.com/jmb05/ddc-hi-rs" }
|
||||
wayland-protocols = { version = "0.32.13", features = ["staging", "client"] }
|
||||
wayland-client = "0.31.15"
|
||||
zbus = "5.19.0"
|
||||
#zbus = "6.1.0"
|
||||
|
||||
[build-dependencies]
|
||||
glib-build-tools = "0.22.8"
|
||||
|
||||
5
icons/lan-symbolic.svg
Normal file
5
icons/lan-symbolic.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:gpa="https://www.gtk.org/grappa" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" gpa:rendering="symbolic" width="16" height="16" gpa:version="1" gpa:state="0" gpa:state-names="outline state1 empty"><path id="cables" fill="none" stroke="url("#gpa:foreground") rgb(0,0,0)" stroke-width="2" stroke-linecap="butt" stroke-linejoin="round" d="M 13 8 L 13 12 M 3 8 L 3 12 M 8 8 L 8 4 M 16 8 L 0 8" class="transparent-fill foreground-stroke" gpa:stroke="foreground" gpa:fill="none" gpa:states="outline state1" gpa:stroke-minwidth="25%" gpa:stroke-maxwidth="150%">
|
||||
</path><path id="outline" fill="none" stroke="url("#gpa:foreground") rgb(0,0,0)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M 1 13 L 1 14 C 1 14.5522852, 1.44771528 15, 2 15 L 4 15 C 4.55228472 15, 5 14.5522852, 5 14 L 5 13 C 5 12.4477148, 4.55228472 12, 4 12 L 2 12 C 1.44771528 12, 1 12.4477148, 1 13 Z M 6 2 L 6 3 C 6 3.55228472, 6.44771528 4, 7 4 L 9 4 C 9.55228519 4, 10 3.55228472, 10 3 L 10 2 C 10 1.44771528, 9.55228519 1, 9 1 L 7 1 C 6.44771528 1, 6 1.44771528, 6 2 Z M 11 13 L 11 14 C 11 14.5522852, 11.4477148 15, 12 15 L 14 15 C 14.5522852 15, 15 14.5522852, 15 14 L 15 13 C 15 12.4477148, 14.5522852 12, 14 12 L 12 12 C 11.4477148 12, 11 12.4477148, 11 13 Z" class="transparent-fill foreground-stroke" gpa:stroke="foreground" gpa:fill="none" gpa:states="outline" gpa:stroke-minwidth="25%" gpa:stroke-maxwidth="150%">
|
||||
</path><path visibility="hidden" id="fill" fill="url("#gpa:foreground") rgb(0,0,0)" fill-rule="nonzero" stroke="url("#gpa:foreground") rgb(0,0,0)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M 1 13 L 1 14 C 1 14.5522852, 1.44771528 15, 2 15 L 4 15 C 4.55228472 15, 5 14.5522852, 5 14 L 5 13 C 5 12.4477148, 4.55228472 12, 4 12 L 2 12 C 1.44771528 12, 1 12.4477148, 1 13 Z M 6 2 L 6 3 C 6 3.55228472, 6.44771528 4, 7 4 L 9 4 C 9.55228519 4, 10 3.55228472, 10 3 L 10 2 C 10 1.44771528, 9.55228519 1, 9 1 L 7 1 C 6.44771528 1, 6 1.44771528, 6 2 Z M 11 13 L 11 14 C 11 14.5522852, 11.4477148 15, 12 15 L 14 15 C 14.5522852 15, 15 14.5522852, 15 14 L 15 13 C 15 12.4477148, 14.5522852 12, 14 12 L 12 12 C 11.4477148 12, 11 12.4477148, 11 13 Z" class="foreground foreground-fill foreground-stroke" gpa:stroke="foreground" gpa:states="state1" gpa:fill="foreground" gpa:stroke-minwidth="25%" gpa:stroke-maxwidth="150%">
|
||||
</path><metadata><rdf:RDF><cc:Work><cc:license rdf:resource="https://creativecommons.org/publicdomain/zero/1.0/"/><dc:subject><rdf:Bag><rdf:li>network-wired lan ethernet nic</rdf:li></rdf:Bag></dc:subject><dc:creator><cc:Agent><dc:title>Jakub Steiner</dc:title></cc:Agent></dc:creator></cc:Work></rdf:RDF></metadata></svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
3
icons/plus-symbolic.svg
Normal file
3
icons/plus-symbolic.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:gpa="https://www.gtk.org/grappa" gpa:rendering="symbolic" gpa:version="1" gpa:state-names="outline empty" gpa:state="0" width="16" height="16"><metadata><rdf:RDF><cc:Work><cc:license rdf:resource="https://creativecommons.org/publicdomain/zero/1.0/"/><dc:creator><cc:Agent><dc:title>Jakub Steiner</dc:title></cc:Agent></dc:creator><dc:subject><rdf:Bag><rdf:li>list-add add plus create</rdf:li></rdf:Bag></dc:subject></cc:Work></rdf:RDF></metadata><path id="plus" fill="none" stroke="url(#gpa:foreground) rgb(0,0,0)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M 2 8 L 14 8 M 8 2 L 8 14" gpa:stroke-minwidth="25%" gpa:stroke-maxwidth="150%" class="transparent-fill foreground-stroke" gpa:stroke="foreground" gpa:states="outline">
|
||||
</path></svg>
|
||||
|
After Width: | Height: | Size: 1016 B |
@@ -1,22 +1,22 @@
|
||||
pub mod audio;
|
||||
pub mod brightness_control;
|
||||
pub(crate) mod idle;
|
||||
pub(crate) mod json_socket;
|
||||
pub mod network;
|
||||
pub mod perf_stats;
|
||||
pub(crate) mod tailscale;
|
||||
pub mod window_manager_hypr;
|
||||
|
||||
use crate::LOGGER;
|
||||
use crate::backend::audio::{MediaBackend, MediaCommand};
|
||||
use crate::backend::brightness_control::{BrightnessManager, BrightnessMessage};
|
||||
use crate::backend::idle::{IdleDaemon, IdleMessage, IdleState};
|
||||
use crate::backend::json_socket::{JsonSocketBackend, State};
|
||||
use crate::backend::network::{NetworkBackendHandle, NetworkCommand, NetworkState};
|
||||
use crate::backend::perf_stats::{Stats, StatsBackend};
|
||||
use crate::backend::tailscale::{TailscaleBackend, TailscaleState};
|
||||
use crate::backend::window_manager_hypr::{HyprlandComms, WindowManagerEvent};
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use jmb_logger_rs::*;
|
||||
use std::fmt::Debug;
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
|
||||
pub(crate) type Registrator<M> = Sender<Sender<M>>;
|
||||
@@ -44,6 +44,27 @@ pub trait BackendThread<M: Debug + Clone, D> {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SimpleBackendThread<M: Debug + Clone>: BackendThread<M, ()> {
|
||||
fn subscribers(&self) -> &Vec<Sender<M>>;
|
||||
fn init(registration_rx: Receiver<Sender<M>>);
|
||||
}
|
||||
|
||||
impl<M: Debug + Clone, S: SimpleBackendThread<M>> BackendThread<M, ()> for S {
|
||||
fn subscribe(registrator: Registrator<M>) -> (Receiver<M>, ()) {
|
||||
let (tx, rx) = crossbeam_channel::unbounded::<M>();
|
||||
let _ = registrator.send(tx);
|
||||
(rx, ())
|
||||
}
|
||||
|
||||
fn subscribers(&self) -> &Vec<Sender<M>> {
|
||||
SimpleBackendThread::subscribers(self)
|
||||
}
|
||||
|
||||
fn init(registration_rx: Receiver<Sender<M>>) {
|
||||
<S as SimpleBackendThread<M>>::init(registration_rx)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Backend {
|
||||
hypr_registrator: Registrator<WindowManagerEvent>,
|
||||
@@ -51,8 +72,8 @@ pub struct Backend {
|
||||
br_registrator: Registrator<BrightnessMessage>,
|
||||
stats_registrator: Registrator<Stats>,
|
||||
media_registrator: Registrator<MediaCommand>,
|
||||
tailscale_registrator: Registrator<TailscaleState>,
|
||||
hypridle: Arc<Mutex<Option<Child>>>,
|
||||
tailscale_registrator: Registrator<State>,
|
||||
idle_registrator: Registrator<IdleState>,
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
@@ -63,10 +84,8 @@ impl Backend {
|
||||
let br_registrator = BrightnessManager::start();
|
||||
let stats_registrator = StatsBackend::start();
|
||||
let media_registrator = MediaBackend::start();
|
||||
let tailscale_registrator = TailscaleBackend::start();
|
||||
let hypridle = Arc::new(Mutex::new(
|
||||
Command::new("hypridle").stdout(Stdio::null()).spawn().ok(),
|
||||
));
|
||||
let tailscale_registrator = JsonSocketBackend::start();
|
||||
let idle_registrator = IdleDaemon::start();
|
||||
debug!(&LOGGER, "Started Backend threads");
|
||||
Self {
|
||||
hypr_registrator,
|
||||
@@ -75,7 +94,7 @@ impl Backend {
|
||||
stats_registrator,
|
||||
media_registrator,
|
||||
tailscale_registrator,
|
||||
hypridle,
|
||||
idle_registrator,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +104,8 @@ impl Backend {
|
||||
let (br_rx, br_tx) = BrightnessManager::subscribe(self.br_registrator.clone());
|
||||
let (stats_rx, _) = StatsBackend::subscribe(self.stats_registrator.clone());
|
||||
let (media_rx, media_tx) = MediaBackend::subscribe(self.media_registrator.clone());
|
||||
let (tailscale_rx, _) = TailscaleBackend::subscribe(self.tailscale_registrator.clone());
|
||||
let (tailscale_rx, _) = JsonSocketBackend::subscribe(self.tailscale_registrator.clone());
|
||||
let (idle_rx, idle_tx) = IdleDaemon::subscribe(self.idle_registrator.clone());
|
||||
BackendChannels {
|
||||
net_rx: Some(net_rx),
|
||||
net_tx,
|
||||
@@ -96,13 +116,10 @@ impl Backend {
|
||||
media_rx: Some(media_rx),
|
||||
media_tx,
|
||||
tailscale_rx: Some(tailscale_rx),
|
||||
hypridle: self.hypridle.clone(),
|
||||
idle_rx: Some(idle_rx),
|
||||
idle_tx,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_drop_guard(&self) -> DropGuard {
|
||||
DropGuard(self.hypridle.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BackendChannels {
|
||||
@@ -114,8 +131,9 @@ pub struct BackendChannels {
|
||||
stats_rx: Option<Receiver<Stats>>,
|
||||
media_rx: Option<Receiver<MediaCommand>>,
|
||||
media_tx: Sender<MediaCommand>,
|
||||
tailscale_rx: Option<Receiver<TailscaleState>>,
|
||||
hypridle: Arc<Mutex<Option<Child>>>,
|
||||
tailscale_rx: Option<Receiver<State>>,
|
||||
idle_rx: Option<Receiver<IdleState>>,
|
||||
idle_tx: Sender<IdleMessage>,
|
||||
}
|
||||
|
||||
impl BackendChannels {
|
||||
@@ -131,12 +149,15 @@ impl BackendChannels {
|
||||
pub fn take_stats_rx(&mut self) -> Option<Receiver<Stats>> {
|
||||
self.stats_rx.take()
|
||||
}
|
||||
pub fn take_ts_rx(&mut self) -> Option<Receiver<TailscaleState>> {
|
||||
pub fn take_netbird_rx(&mut self) -> Option<Receiver<State>> {
|
||||
self.tailscale_rx.take()
|
||||
}
|
||||
pub fn take_media_rx(&mut self) -> Option<Receiver<MediaCommand>> {
|
||||
self.media_rx.take()
|
||||
}
|
||||
pub fn take_idle_rx(&mut self) -> Option<Receiver<IdleState>> {
|
||||
self.idle_rx.take()
|
||||
}
|
||||
pub fn net_tx(&self) -> UnboundedSender<NetworkCommand> {
|
||||
self.net_tx.clone()
|
||||
}
|
||||
@@ -146,21 +167,7 @@ impl BackendChannels {
|
||||
pub fn media_tx(&self) -> Sender<MediaCommand> {
|
||||
self.media_tx.clone()
|
||||
}
|
||||
pub fn hypridle(&self) -> Arc<Mutex<Option<Child>>> {
|
||||
self.hypridle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DropGuard(Arc<Mutex<Option<Child>>>);
|
||||
impl Drop for DropGuard {
|
||||
fn drop(&mut self) {
|
||||
let mut hypridle = self
|
||||
.0
|
||||
.lock()
|
||||
.expect("Couldn't get lock on hypridle child process handle");
|
||||
if hypridle.is_some() {
|
||||
info!(&LOGGER, "Killing hypridle");
|
||||
let _ = hypridle.as_mut().unwrap().kill();
|
||||
}
|
||||
pub fn idle_tx(&self) -> Sender<IdleMessage> {
|
||||
self.idle_tx.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,9 @@ use brightness::blocking::{Brightness, BrightnessDevice};
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use ddc_hi::Ddc;
|
||||
use jmb_logger_rs::*;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::error::Error;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::os::unix::prelude::MetadataExt;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
@@ -18,6 +19,20 @@ pub enum DisplayBackend {
|
||||
DdcCi(Box<ddc_hi::Display>),
|
||||
}
|
||||
|
||||
impl Debug for DisplayBackend {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
DisplayBackend::Sysfs(dev) => {
|
||||
return dev.fmt(f);
|
||||
}
|
||||
DisplayBackend::DdcCi(displ) => {
|
||||
f.write_str(&displ.info.id).expect("Error");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_sysfs_dev(id: &str) -> Option<BrightnessDevice> {
|
||||
brightness::blocking::brightness_devices()
|
||||
.find(|dev| {
|
||||
@@ -62,26 +77,45 @@ impl DisplayBackend {
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum BrightnessMessage {
|
||||
Init(Sender<BrightnessMessage>),
|
||||
Value(String, u8),
|
||||
Value(String, u8, bool),
|
||||
Revert(String),
|
||||
Request(String),
|
||||
}
|
||||
|
||||
struct DisplayData(DisplayBackend, u8);
|
||||
#[derive(Debug)]
|
||||
struct DisplayData {
|
||||
backend: DisplayBackend,
|
||||
value: u8,
|
||||
saved_value: Option<u8>,
|
||||
}
|
||||
|
||||
impl DisplayData {
|
||||
fn new(backend: DisplayBackend, value: u8) -> Self {
|
||||
Self {
|
||||
backend,
|
||||
value,
|
||||
saved_value: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn save(&mut self) -> Option<&u8> {
|
||||
self.saved_value = Some(self.value);
|
||||
self.saved_value.as_ref()
|
||||
}
|
||||
|
||||
fn update(&mut self) -> bool {
|
||||
if let Some(value) = self.0.get_brightness()
|
||||
&& self.1 != value
|
||||
if let Some(value) = self.backend.get_brightness()
|
||||
&& self.value != value
|
||||
{
|
||||
self.1 = value;
|
||||
self.value = value;
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn set_brightness(&mut self, percent: u8) {
|
||||
self.0.set_brightness(percent);
|
||||
self.1 = percent;
|
||||
self.backend.set_brightness(percent);
|
||||
self.value = percent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +153,7 @@ impl BackendThread<BrightnessMessage, Sender<BrightnessMessage>> for BrightnessM
|
||||
thread::spawn(move || {
|
||||
while let Ok(tx) = registration_rx.recv() {
|
||||
if tx.send(BrightnessMessage::Init(f2b_tx.clone())).is_ok() {
|
||||
trace!(&LOGGER, "Added Brightness Subscriber: {tx:?}");
|
||||
debug!(&LOGGER, "Added Brightness Subscriber: {tx:?}");
|
||||
instance_clone
|
||||
.lock()
|
||||
.expect("Couldn't get lock")
|
||||
@@ -130,36 +164,53 @@ impl BackendThread<BrightnessMessage, Sender<BrightnessMessage>> for BrightnessM
|
||||
});
|
||||
let instance_clone = instance.clone();
|
||||
thread::spawn(move || {
|
||||
while let Ok(mut msg) = f2b_rx.recv() {
|
||||
while let Ok(newer) = f2b_rx.try_recv() {
|
||||
msg = newer;
|
||||
}
|
||||
match msg {
|
||||
BrightnessMessage::Value(id, val) => {
|
||||
instance_clone
|
||||
.lock()
|
||||
.expect("Couldn't get lock")
|
||||
.set_brightness(id, val);
|
||||
loop {
|
||||
// Ensure all Messages are handled in the correct order
|
||||
let mut operations: HashMap<String, Vec<Operation>> = HashMap::new();
|
||||
let mut requests: HashSet<String> = HashSet::new();
|
||||
if let Ok(msg) = f2b_rx.recv() {
|
||||
Self::insert_message_operation(&mut operations, &mut requests, msg);
|
||||
while let Ok(msg) = f2b_rx.try_recv() {
|
||||
Self::insert_message_operation(&mut operations, &mut requests, msg);
|
||||
}
|
||||
BrightnessMessage::Request(id) => {
|
||||
instance_clone
|
||||
.lock()
|
||||
.expect("Couldn't get lock")
|
||||
.register_display(id.clone());
|
||||
if let Some(val) = instance_clone
|
||||
.lock()
|
||||
.expect("Couldn't get lock")
|
||||
.display_cache
|
||||
.get(&id)
|
||||
.map(|v| v.1)
|
||||
{
|
||||
instance_clone
|
||||
.lock()
|
||||
.expect("Couldn't get lock")
|
||||
.publish(BrightnessMessage::Value(id, val));
|
||||
}
|
||||
|
||||
let mut manager = instance_clone.lock().unwrap();
|
||||
for (id, operations) in operations {
|
||||
let mut last = manager
|
||||
.display_cache
|
||||
.get(&id)
|
||||
.and_then(|d| Some(d.value))
|
||||
.unwrap_or(60);
|
||||
let mut revert_flag = false;
|
||||
for operation in operations {
|
||||
match operation {
|
||||
Operation::Set(val, save) => {
|
||||
if save {
|
||||
manager.display_cache.get_mut(&id).and_then(|d| d.save());
|
||||
}
|
||||
last = val;
|
||||
revert_flag = false;
|
||||
}
|
||||
Operation::Revert => {
|
||||
last = manager
|
||||
.display_cache
|
||||
.get_mut(&id)
|
||||
.and_then(|d| d.saved_value.take())
|
||||
.unwrap_or(last);
|
||||
revert_flag = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
if revert_flag {
|
||||
manager.publish(BrightnessMessage::Value(id.clone(), last, false));
|
||||
}
|
||||
manager.set_brightness(id, last);
|
||||
}
|
||||
for id in requests {
|
||||
if let Some(val) = manager.get_brightness(id.clone()) {
|
||||
manager.publish(BrightnessMessage::Value(id, val, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -168,9 +219,11 @@ impl BackendThread<BrightnessMessage, Sender<BrightnessMessage>> for BrightnessM
|
||||
thread::sleep(Duration::from_millis(10_000));
|
||||
let mut messages = vec![];
|
||||
|
||||
for monitor in &mut instance.lock().expect("Couldn't get lock").display_cache {
|
||||
if monitor.1.update() {
|
||||
messages.push(BrightnessMessage::Value(monitor.0.clone(), monitor.1.1));
|
||||
for (monitor, data) in
|
||||
&mut instance.lock().expect("Couldn't get lock").display_cache
|
||||
{
|
||||
if data.update() {
|
||||
messages.push(BrightnessMessage::Value(monitor.clone(), data.value, false));
|
||||
}
|
||||
}
|
||||
for message in messages {
|
||||
@@ -181,6 +234,12 @@ impl BackendThread<BrightnessMessage, Sender<BrightnessMessage>> for BrightnessM
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Operation {
|
||||
Set(u8, bool),
|
||||
Revert,
|
||||
}
|
||||
|
||||
impl BrightnessManager {
|
||||
pub fn set_brightness(&mut self, connector: String, percent: u8) {
|
||||
if !self.display_cache.contains_key(&connector) {
|
||||
@@ -191,12 +250,12 @@ impl BrightnessManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn _get_brightness(&mut self, connector: String) -> Option<u8> {
|
||||
pub fn get_brightness(&mut self, connector: String) -> Option<u8> {
|
||||
if !self.display_cache.contains_key(&connector) {
|
||||
self.register_display(connector.clone());
|
||||
}
|
||||
if let Some(display) = self.display_cache.get_mut(&connector) {
|
||||
display.0.get_brightness()
|
||||
display.backend.get_brightness()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -220,7 +279,7 @@ impl BrightnessManager {
|
||||
.to_str()
|
||||
.is_some_and(|name| name.eq(&format!("card1-{connector}")))
|
||||
{
|
||||
println!("Found sysfs display {connector}");
|
||||
debug!(&LOGGER, "Found sysfs display {connector}");
|
||||
let path = bl_path.file_name();
|
||||
let Some(name) = path.to_str() else { continue };
|
||||
let Some(device) = get_sysfs_dev(name) else {
|
||||
@@ -229,7 +288,7 @@ impl BrightnessManager {
|
||||
let mut backend = DisplayBackend::Sysfs(device);
|
||||
let value = backend.get_brightness().unwrap_or(60);
|
||||
self.display_cache
|
||||
.insert(connector.clone(), DisplayData(backend, value));
|
||||
.insert(connector.clone(), DisplayData::new(backend, value));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -239,13 +298,13 @@ impl BrightnessManager {
|
||||
&& let Ok(data) = fs::metadata(path)
|
||||
{
|
||||
let id = data.rdev();
|
||||
println!("Found DDC/CI display {connector}");
|
||||
debug!(&LOGGER, "Found DDC/CI display {connector}");
|
||||
for display in ddc_hi::Display::enumerate() {
|
||||
if id.to_string().eq(&display.info.id) {
|
||||
let mut backend = DisplayBackend::DdcCi(Box::new(display));
|
||||
let value = backend.get_brightness().unwrap_or(60);
|
||||
self.display_cache
|
||||
.insert(connector.clone(), DisplayData(backend, value));
|
||||
.insert(connector.clone(), DisplayData::new(backend, value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -318,4 +377,35 @@ impl BrightnessManager {
|
||||
)
|
||||
.into())
|
||||
}
|
||||
|
||||
fn insert_message_operation(
|
||||
operations: &mut HashMap<String, Vec<Operation>>,
|
||||
requests: &mut HashSet<String>,
|
||||
msg: BrightnessMessage,
|
||||
) {
|
||||
match msg {
|
||||
BrightnessMessage::Value(id, val, save) => {
|
||||
if !operations.contains_key(&id) {
|
||||
operations.insert(id.clone(), Vec::new());
|
||||
}
|
||||
let ops = operations.get_mut(&id);
|
||||
if let Some(ops) = ops {
|
||||
ops.push(Operation::Set(val, save));
|
||||
}
|
||||
}
|
||||
BrightnessMessage::Revert(id) => {
|
||||
if !operations.contains_key(&id) {
|
||||
operations.insert(id.clone(), Vec::new());
|
||||
}
|
||||
let ops = operations.get_mut(&id);
|
||||
if let Some(ops) = ops {
|
||||
ops.push(Operation::Revert);
|
||||
}
|
||||
}
|
||||
BrightnessMessage::Request(id) => {
|
||||
requests.insert(id);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
454
src/backend/idle.rs
Normal file
454
src/backend/idle.rs
Normal file
@@ -0,0 +1,454 @@
|
||||
use crate::backend::{BackendThread, Registrator};
|
||||
use crate::{APP_NAME, LOGGER};
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use jmb_logger_rs::*;
|
||||
use nmrs::raw::zbus::interface;
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
use tokio::sync::watch;
|
||||
use tokio::time::sleep;
|
||||
use wayland_client::globals::registry_queue_init;
|
||||
use wayland_client::protocol::wl_registry::WlRegistry;
|
||||
use wayland_client::protocol::wl_seat::WlSeat;
|
||||
use wayland_client::{Connection, Dispatch, QueueHandle, delegate_noop};
|
||||
use wayland_protocols::ext::idle_notify::v1::client::ext_idle_notification_v1::{
|
||||
Event, ExtIdleNotificationV1,
|
||||
};
|
||||
use wayland_protocols::ext::idle_notify::v1::client::ext_idle_notifier_v1::ExtIdleNotifierV1;
|
||||
use zbus;
|
||||
use zbus::conn::Builder;
|
||||
use zbus::message::Header;
|
||||
|
||||
const SERVICE: &str = "org.freedesktop.ScreenSaver";
|
||||
const OBJECT_PATH: &str = "/org/freedesktop/ScreenSaver";
|
||||
const LEGACY_OBJECT_PATH: &str = "/ScreenSaver";
|
||||
|
||||
struct WlState {
|
||||
idle: Option<bool>,
|
||||
tx: watch::Sender<IdleStateInner>,
|
||||
}
|
||||
|
||||
delegate_noop!(WlState: ignore WlSeat);
|
||||
|
||||
impl Dispatch<WlRegistry, wayland_client::globals::GlobalListContents> for WlState {
|
||||
fn event(
|
||||
_: &mut Self,
|
||||
_: &WlRegistry,
|
||||
_: wayland_client::protocol::wl_registry::Event,
|
||||
_: &wayland_client::globals::GlobalListContents,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ExtIdleNotificationV1, ()> for WlState {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_: &ExtIdleNotificationV1,
|
||||
event: Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
match event {
|
||||
Event::Idled => state.idle = Some(true),
|
||||
Event::Resumed => state.idle = Some(false),
|
||||
_ => {}
|
||||
}
|
||||
if let Some(idle) = state.idle {
|
||||
state.tx.send_modify(|state| {
|
||||
state.wayland_idle = idle;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ExtIdleNotifierV1, (), WlState> for WlState {
|
||||
fn event(
|
||||
_: &mut WlState,
|
||||
_: &ExtIdleNotifierV1,
|
||||
event: <ExtIdleNotifierV1 as wayland_client::Proxy>::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WlState>,
|
||||
) {
|
||||
match event {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Inhibitor {
|
||||
owner: String,
|
||||
application: String,
|
||||
reason: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct IdleStateInner {
|
||||
dimmed: bool,
|
||||
wayland_idle: bool,
|
||||
dbus_inhibited: bool,
|
||||
caffeine: bool,
|
||||
}
|
||||
|
||||
impl IdleStateInner {
|
||||
fn should_dim(&self) -> bool {
|
||||
self.wayland_idle && !self.dbus_inhibited
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<IdleState> for IdleStateInner {
|
||||
fn into(self) -> IdleState {
|
||||
if self.should_dim() {
|
||||
IdleState::Dimmed
|
||||
} else if self.caffeine {
|
||||
IdleState::Caffeine
|
||||
} else if self.dbus_inhibited {
|
||||
IdleState::Inhibited
|
||||
} else {
|
||||
IdleState::Active
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum IdleState {
|
||||
Init(Sender<IdleMessage>),
|
||||
Dimmed,
|
||||
Inhibited,
|
||||
Caffeine,
|
||||
Active,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum IdleMessage {
|
||||
Inhibit,
|
||||
Release,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IdleDaemon {
|
||||
subscribers: Vec<Sender<IdleState>>,
|
||||
}
|
||||
|
||||
impl BackendThread<IdleState, Sender<IdleMessage>> for IdleDaemon {
|
||||
fn subscribe(
|
||||
registrator: Registrator<IdleState>,
|
||||
) -> (Receiver<IdleState>, Sender<IdleMessage>) {
|
||||
let (tx, rx) = crossbeam_channel::unbounded::<IdleState>();
|
||||
let _ = registrator.send(tx);
|
||||
while let Ok(message) = rx.recv() {
|
||||
return match message {
|
||||
IdleState::Init(sender) => (rx, sender),
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
panic!("Channel closed unexpectedly, without returning anything")
|
||||
}
|
||||
|
||||
fn subscribers(&self) -> &Vec<Sender<IdleState>> {
|
||||
&self.subscribers
|
||||
}
|
||||
|
||||
fn init(registration_rx: Receiver<Sender<IdleState>>) {
|
||||
let (tx, rx) = crossbeam_channel::unbounded::<IdleMessage>();
|
||||
let tx_clone = tx.clone();
|
||||
let instance = Arc::new(Mutex::new(Self {
|
||||
subscribers: Vec::new(),
|
||||
}));
|
||||
let instance_clone = instance.clone();
|
||||
std::thread::spawn(move || {
|
||||
while let Ok(sender) = registration_rx.recv() {
|
||||
if sender.send(IdleState::Init(tx_clone.clone())).is_ok() {
|
||||
trace!(&LOGGER, "Added IdleDaemon Subscriber");
|
||||
instance_clone
|
||||
.lock()
|
||||
.expect("Couldn't get lock")
|
||||
.subscribers
|
||||
.push(sender);
|
||||
}
|
||||
}
|
||||
});
|
||||
let (state_tx, state_rx) = watch::channel(IdleStateInner {
|
||||
dimmed: false,
|
||||
wayland_idle: false,
|
||||
dbus_inhibited: false,
|
||||
caffeine: false,
|
||||
});
|
||||
let tx_clone = state_tx.clone();
|
||||
std::thread::spawn(move || {
|
||||
if let Err(err) = Self::start_idle_notifier(tx_clone) {
|
||||
error!(&LOGGER, "Idle Daemon unexpectedly quit, reason: {err}");
|
||||
}
|
||||
});
|
||||
std::thread::spawn(move || {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed to create Tokio runtime");
|
||||
runtime.block_on(async move {
|
||||
let mut idle_inhibitor = None;
|
||||
while let Ok(msg) = rx.recv() {
|
||||
match msg {
|
||||
IdleMessage::Inhibit => {
|
||||
if idle_inhibitor.is_none()
|
||||
&& let Ok(ii) = IdleInhibitor::inhibit_idle().await
|
||||
{
|
||||
idle_inhibitor = Some(ii);
|
||||
}
|
||||
}
|
||||
IdleMessage::Release => {
|
||||
if let Some(ii) = idle_inhibitor.take()
|
||||
&& let Err(e) = ii.uninhibit_idle().await
|
||||
{
|
||||
warn!(&LOGGER, "Error uninhibiting {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
std::thread::spawn(move || {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed to create Tokio runtime");
|
||||
|
||||
runtime.block_on(async move {
|
||||
let res = IdleDaemon::start_dbus(state_tx).await;
|
||||
match res {
|
||||
Ok((_, _)) => {
|
||||
Self::run_idle_timer(instance, state_rx, Duration::from_secs(10)).await;
|
||||
}
|
||||
Err(err) => error!(&LOGGER, "Error running D-Bus Screensaver: {err}"),
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl IdleDaemon {
|
||||
async fn run_idle_timer(
|
||||
instance: Arc<Mutex<Self>>,
|
||||
mut state_rx: watch::Receiver<IdleStateInner>,
|
||||
timeout: Duration,
|
||||
) {
|
||||
loop {
|
||||
let mut state = *state_rx.borrow_and_update();
|
||||
instance.lock().unwrap().publish(state.into());
|
||||
|
||||
if !state.should_dim() {
|
||||
if state_rx.changed().await.is_err() {
|
||||
return;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
tokio::select! {
|
||||
_ = sleep(timeout) => {
|
||||
state.dimmed = true;
|
||||
instance.lock().unwrap().publish(state.into());
|
||||
}
|
||||
result = state_rx.changed() => {
|
||||
if result.is_err() {
|
||||
return;
|
||||
}
|
||||
state.dimmed = false;
|
||||
instance.lock().unwrap().publish(state.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn start_idle_notifier(tx: watch::Sender<IdleStateInner>) -> Result<(), Box<dyn Error>> {
|
||||
let conn = Connection::connect_to_env()?;
|
||||
let (globals, mut event_queue) = registry_queue_init::<WlState>(&conn)?;
|
||||
let queue_handle = event_queue.handle();
|
||||
|
||||
let seat = globals.bind::<WlSeat, _, _>(&queue_handle, 1..=9, ())?;
|
||||
|
||||
let notifier = globals.bind::<ExtIdleNotifierV1, _, _>(&queue_handle, 2..=2, ())?;
|
||||
|
||||
let mut state = WlState { idle: None, tx };
|
||||
|
||||
let _notification = notifier.get_idle_notification(10_000, &seat, &queue_handle, ());
|
||||
|
||||
loop {
|
||||
event_queue.blocking_dispatch(&mut state)?;
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_dbus(
|
||||
tx: watch::Sender<IdleStateInner>,
|
||||
) -> zbus::Result<(ScreenSaver, zbus::connection::Connection)> {
|
||||
let service = ScreenSaver::new(tx);
|
||||
|
||||
let connection = Builder::session()?
|
||||
.name(SERVICE)?
|
||||
.serve_at(OBJECT_PATH, service.clone())?
|
||||
.serve_at(LEGACY_OBJECT_PATH, service.clone())?
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
info!(&LOGGER, "Providing {SERVICE}");
|
||||
|
||||
Ok((service, connection))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IdleInhibitor {
|
||||
connection: zbus::Connection,
|
||||
cookie: u32,
|
||||
}
|
||||
|
||||
impl IdleInhibitor {
|
||||
async fn inhibit_idle() -> zbus::Result<IdleInhibitor> {
|
||||
let connection = zbus::Connection::session().await?;
|
||||
|
||||
let reply = connection
|
||||
.call_method(
|
||||
Some("org.freedesktop.ScreenSaver"),
|
||||
"/org/freedesktop/ScreenSaver",
|
||||
Some("org.freedesktop.ScreenSaver"),
|
||||
"Inhibit",
|
||||
&(APP_NAME, "Caffeine mode active"),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let cookie = reply.body().deserialize::<u32>()?;
|
||||
|
||||
Ok(Self { connection, cookie })
|
||||
}
|
||||
|
||||
async fn uninhibit_idle(self) -> zbus::Result<()> {
|
||||
self.connection
|
||||
.call_method(
|
||||
Some("org.freedesktop.ScreenSaver"),
|
||||
"/org/freedesktop/ScreenSaver",
|
||||
Some("org.freedesktop.ScreenSaver"),
|
||||
"UnInhibit",
|
||||
&(self.cookie,),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ScreenSaver {
|
||||
inner: Arc<Mutex<Inner>>,
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
next_cookie: u32,
|
||||
inhibitors: HashMap<u32, Inhibitor>,
|
||||
tx: watch::Sender<IdleStateInner>,
|
||||
}
|
||||
|
||||
impl ScreenSaver {
|
||||
fn new(tx: watch::Sender<IdleStateInner>) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(Mutex::new(Inner {
|
||||
next_cookie: 1,
|
||||
inhibitors: HashMap::new(),
|
||||
tx,
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[interface(name = "org.freedesktop.ScreenSaver")]
|
||||
impl ScreenSaver {
|
||||
#[zbus(name = "Inhibit")]
|
||||
fn inhibit(
|
||||
&self,
|
||||
#[zbus(header)] header: Header<'_>,
|
||||
application_name: String,
|
||||
reason_for_inhibit: String,
|
||||
) -> zbus::fdo::Result<u32> {
|
||||
let owner = header
|
||||
.sender()
|
||||
.ok_or_else(|| zbus::fdo::Error::Failed("Missing D-Bus sender".into()))?
|
||||
.to_string();
|
||||
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
|
||||
let start = inner.next_cookie;
|
||||
let cookie = loop {
|
||||
let candidate = inner.next_cookie;
|
||||
inner.next_cookie = inner.next_cookie.wrapping_add(1).max(1);
|
||||
|
||||
if !inner.inhibitors.contains_key(&candidate) {
|
||||
break candidate;
|
||||
}
|
||||
|
||||
if inner.next_cookie == start {
|
||||
return Err(zbus::fdo::Error::Failed(
|
||||
"No available inhibition cookies".into(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let caffeine_flag = application_name.eq(APP_NAME);
|
||||
|
||||
debug!(&LOGGER, "Inhibitor added: {application_name} {cookie}");
|
||||
inner.inhibitors.insert(
|
||||
cookie,
|
||||
Inhibitor {
|
||||
owner,
|
||||
application: application_name,
|
||||
reason: reason_for_inhibit,
|
||||
},
|
||||
);
|
||||
|
||||
inner.tx.send_modify(|state| {
|
||||
state.dbus_inhibited = !inner.inhibitors.is_empty();
|
||||
state.caffeine = caffeine_flag;
|
||||
});
|
||||
Ok(cookie)
|
||||
}
|
||||
|
||||
#[zbus(name = "UnInhibit")]
|
||||
fn uninhibit(&self, #[zbus(header)] header: Header<'_>, cookie: u32) -> zbus::fdo::Result<()> {
|
||||
let owner = header
|
||||
.sender()
|
||||
.ok_or_else(|| zbus::fdo::Error::Failed("Missing D-Bus sender".into()))?
|
||||
.to_string();
|
||||
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
|
||||
match inner.inhibitors.get(&cookie) {
|
||||
Some(inhibitor) if inhibitor.owner == owner => {
|
||||
if let Some(removed) = inner.inhibitors.remove(&cookie) {
|
||||
debug!(
|
||||
&LOGGER,
|
||||
"Inhibitor removed: {} {cookie}", removed.application
|
||||
);
|
||||
}
|
||||
let mut caffeine_flag = false;
|
||||
for inhibitor in inner.inhibitors.values() {
|
||||
if inhibitor.application.eq(APP_NAME) {
|
||||
caffeine_flag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
inner.tx.send_modify(|state| {
|
||||
state.dbus_inhibited = !inner.inhibitors.is_empty();
|
||||
state.caffeine = caffeine_flag;
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
Some(_) => Err(zbus::fdo::Error::AccessDenied(
|
||||
"Cookie belongs to another D-Bus client".into(),
|
||||
)),
|
||||
None => Err(zbus::fdo::Error::Failed("Unknown inhibition cookie".into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
265
src/backend/json_socket.rs
Normal file
265
src/backend/json_socket.rs
Normal file
@@ -0,0 +1,265 @@
|
||||
use crate::LOGGER;
|
||||
use crate::backend::{BackendThread, Registrator};
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use http::Request;
|
||||
use http_body_util::{BodyExt, Empty, Full};
|
||||
use hyper::body::{Bytes, Incoming};
|
||||
use hyper_util::rt::TokioIo;
|
||||
use jmb_logger_rs::*;
|
||||
use std::cmp::PartialEq;
|
||||
use std::error::Error;
|
||||
use std::process::Command;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio::net::UnixStream;
|
||||
|
||||
pub struct JsonSocketBackend {
|
||||
subscribers: Vec<Sender<State>>,
|
||||
state: State,
|
||||
socket_address: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum State {
|
||||
Connected,
|
||||
Connecting,
|
||||
Disconnected,
|
||||
NeedsLogin,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn try_from_value(value: i64) -> Option<State> {
|
||||
match value {
|
||||
5 => Some(State::Connecting),
|
||||
6 => Some(State::Connected),
|
||||
_ => Some(State::Disconnected),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonSocketBackend {
|
||||
async fn watch(instance: Arc<Mutex<Self>>) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let socket_address = instance
|
||||
.lock()
|
||||
.expect("Couldn't get lock")
|
||||
.socket_address
|
||||
.clone();
|
||||
let socket = UnixStream::connect(&socket_address).await?;
|
||||
let io = TokioIo::new(socket);
|
||||
|
||||
let (mut sender, connection) = hyper::client::conn::http1::handshake(io).await?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(err) = connection.await {
|
||||
warn!(&LOGGER, "Tailscale socket HTTP connection error: {err}");
|
||||
}
|
||||
});
|
||||
|
||||
let request = Request::builder()
|
||||
.method("GET")
|
||||
.uri("http://local-tailscaled.sock/localapi/v0/watch-ipn-bus")
|
||||
.header("Host", "local-tailscaled.sock")
|
||||
.header("Accept", "application/json")
|
||||
.body(Empty::<Bytes>::new())?;
|
||||
|
||||
let response = sender.send_request(request).await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(format!("Tailscale API returned {}", response.status()).into());
|
||||
}
|
||||
|
||||
let mut body: Incoming = response.into_body();
|
||||
let mut pending = Vec::<u8>::new();
|
||||
|
||||
while let Some(frame_result) = body.frame().await {
|
||||
let frame = frame_result?;
|
||||
|
||||
if let Ok(data) = frame.into_data() {
|
||||
pending.extend_from_slice(&data);
|
||||
|
||||
while let Some(newline) = pending.iter().position(|&b| b == b'\n') {
|
||||
let line: Vec<u8> = pending.drain(..=newline).collect();
|
||||
let line = &line[..line.len() - 1];
|
||||
|
||||
if line.iter().all(u8::is_ascii_whitespace) {
|
||||
continue;
|
||||
}
|
||||
|
||||
match serde_json::from_slice::<serde_json::Value>(line) {
|
||||
Ok(notification) => {
|
||||
if let Some(state) = notification.get("State")
|
||||
&& !state.is_null()
|
||||
{
|
||||
let state = State::try_from_value(state.as_i64().unwrap_or(4))
|
||||
.unwrap_or(State::Disconnected);
|
||||
instance.lock().expect("Couldn't get lock").publish(state);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(&LOGGER, "Invalid Tailscale notification: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn watch_netbird(instance: Arc<Mutex<Self>>) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let socket_address = instance
|
||||
.lock()
|
||||
.expect("Couldn't get lock")
|
||||
.socket_address
|
||||
.clone();
|
||||
|
||||
let socket = UnixStream::connect(&socket_address).await?;
|
||||
let io = TokioIo::new(socket);
|
||||
|
||||
let (mut sender, connection) = hyper::client::conn::http1::handshake(io).await?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(err) = connection.await {
|
||||
warn!(&LOGGER, "Netbird socket HTTP connection error: {err}");
|
||||
}
|
||||
});
|
||||
|
||||
let request = Request::builder()
|
||||
.method("POST")
|
||||
.uri("http://localhost/daemon.DaemonService/SubscribeStatus")
|
||||
.header("Host", "localhost")
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept", "application/json")
|
||||
.body(Full::new(Bytes::from_static(b"{}")))?;
|
||||
|
||||
let response = sender.send_request(request).await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(format!("NetBird API returned {}", response.status()).into());
|
||||
}
|
||||
|
||||
let mut body = response.into_body();
|
||||
let mut pending = Vec::<u8>::new();
|
||||
|
||||
while let Some(frame_result) = body.frame().await {
|
||||
let frame = frame_result?;
|
||||
|
||||
if let Ok(data) = frame.into_data() {
|
||||
pending.extend_from_slice(&data);
|
||||
|
||||
while let Some(newline) = pending.iter().position(|&b| b == b'\n') {
|
||||
let line: Vec<u8> = pending.drain(..=newline).collect();
|
||||
let line = &line[..line.len() - 1];
|
||||
|
||||
if line.iter().all(u8::is_ascii_whitespace) {
|
||||
continue;
|
||||
}
|
||||
|
||||
match serde_json::from_slice::<serde_json::Value>(line) {
|
||||
Ok(notification) => {
|
||||
let Some(status) = notification
|
||||
.get("result")
|
||||
.and_then(|v| v.get("status").and_then(|v| v.as_str()))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let state = match status {
|
||||
"Connected" => State::Connected,
|
||||
"Connecting" => State::Connecting,
|
||||
"Idle" => State::Disconnected,
|
||||
"NeedsLogin" => State::NeedsLogin,
|
||||
other => {
|
||||
warn!(&LOGGER, "Unknown NetBird status: {other}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
instance.lock().expect("Couldn't get lock").publish(state);
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(&LOGGER, "Invalid NetBird notification: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl BackendThread<State, ()> for JsonSocketBackend {
|
||||
fn subscribe(registrator: Registrator<State>) -> (Receiver<State>, ()) {
|
||||
let (tx, rx) = crossbeam_channel::unbounded::<State>();
|
||||
let _ = registrator.send(tx);
|
||||
(rx, ())
|
||||
}
|
||||
|
||||
fn subscribers(&self) -> &Vec<Sender<State>> {
|
||||
&self.subscribers
|
||||
}
|
||||
|
||||
fn init(registration_rx: Receiver<Sender<State>>) {
|
||||
let state = initial_state_tailscale();
|
||||
|
||||
let instance = Arc::new(Mutex::new(Self {
|
||||
subscribers: vec![],
|
||||
state,
|
||||
socket_address: String::from("/var/run/netbird-http.sock"),
|
||||
}));
|
||||
|
||||
let instance_clone = instance.clone();
|
||||
std::thread::spawn(move || {
|
||||
while let Ok(tx) = registration_rx.recv() {
|
||||
trace!(&LOGGER, "Added NetBird Subscriber: {tx:?}");
|
||||
|
||||
let _ = tx.send(
|
||||
instance_clone
|
||||
.lock()
|
||||
.expect("Couldn't get lock")
|
||||
.state
|
||||
.clone(),
|
||||
);
|
||||
|
||||
instance_clone
|
||||
.lock()
|
||||
.expect("Couldn't get lock")
|
||||
.subscribers
|
||||
.push(tx);
|
||||
}
|
||||
});
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed to create Tokio runtime");
|
||||
|
||||
runtime.block_on(async move {
|
||||
loop {
|
||||
if let Err(e) = Self::watch_netbird(instance.clone()).await {
|
||||
warn!(&LOGGER, "Error: {e:?}");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn initial_state_tailscale() -> State {
|
||||
if let Ok(output) = Command::new("tailscale")
|
||||
.arg("status")
|
||||
.arg("--json")
|
||||
.output()
|
||||
{
|
||||
let val: Result<serde_json::Value, _> = serde_json::from_slice(&output.stdout);
|
||||
if let Ok(val) = val {
|
||||
if let Some(res) = val.get("BackendState").and_then(|v| v.as_str())
|
||||
&& res.eq("Running")
|
||||
{
|
||||
return State::Connected;
|
||||
}
|
||||
} else {
|
||||
error!(&LOGGER, "{:?}", val.err());
|
||||
}
|
||||
}
|
||||
State::Connecting
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
use crate::LOGGER;
|
||||
use crate::backend::{BackendThread, Registrator};
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use http::Request;
|
||||
use http_body_util::BodyExt;
|
||||
use hyper::body::Incoming;
|
||||
use hyper_util::rt::TokioIo;
|
||||
use jmb_logger_rs::*;
|
||||
use std::error::Error;
|
||||
use std::process::Command;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio::net::UnixStream;
|
||||
|
||||
pub struct TailscaleBackend {
|
||||
subscribers: Vec<Sender<TailscaleState>>,
|
||||
state: TailscaleState,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TailscaleState {
|
||||
NoState = 0,
|
||||
InUseOtherUser = 1,
|
||||
NeedsLogin = 2,
|
||||
NeedsMachineAuth = 3,
|
||||
Stopped = 4,
|
||||
Starting = 5,
|
||||
Running = 6,
|
||||
}
|
||||
|
||||
impl TailscaleState {
|
||||
fn try_from_value(value: i64) -> Option<TailscaleState> {
|
||||
match value {
|
||||
0 => Some(TailscaleState::NoState),
|
||||
1 => Some(TailscaleState::InUseOtherUser),
|
||||
2 => Some(TailscaleState::NeedsLogin),
|
||||
3 => Some(TailscaleState::NeedsMachineAuth),
|
||||
4 => Some(TailscaleState::Stopped),
|
||||
5 => Some(TailscaleState::Starting),
|
||||
6 => Some(TailscaleState::Running),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TailscaleBackend {
|
||||
async fn watch_tailscale(
|
||||
instance: Arc<Mutex<Self>>,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let socket = UnixStream::connect("/var/run/tailscale/tailscaled.sock").await?;
|
||||
let io = TokioIo::new(socket);
|
||||
|
||||
let (mut sender, connection) = hyper::client::conn::http1::handshake(io).await?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(err) = connection.await {
|
||||
warn!(&LOGGER, "Tailscale socket HTTP connection error: {err}");
|
||||
}
|
||||
});
|
||||
|
||||
let request = Request::builder()
|
||||
.method("GET")
|
||||
.uri("http://local-tailscaled.sock/localapi/v0/watch-ipn-bus")
|
||||
.header("Host", "local-tailscaled.sock")
|
||||
.header("Accept", "application/json")
|
||||
.body(http_body_util::Empty::<hyper::body::Bytes>::new())?;
|
||||
|
||||
let response = sender.send_request(request).await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(format!("Tailscale API returned {}", response.status()).into());
|
||||
}
|
||||
|
||||
let mut body: Incoming = response.into_body();
|
||||
let mut pending = Vec::<u8>::new();
|
||||
|
||||
while let Some(frame_result) = body.frame().await {
|
||||
let frame = frame_result?;
|
||||
|
||||
if let Ok(data) = frame.into_data() {
|
||||
pending.extend_from_slice(&data);
|
||||
|
||||
while let Some(newline) = pending.iter().position(|&b| b == b'\n') {
|
||||
let line: Vec<u8> = pending.drain(..=newline).collect();
|
||||
let line = &line[..line.len() - 1];
|
||||
|
||||
if line.iter().all(u8::is_ascii_whitespace) {
|
||||
continue;
|
||||
}
|
||||
|
||||
match serde_json::from_slice::<serde_json::Value>(line) {
|
||||
Ok(notification) => {
|
||||
if let Some(state) = notification.get("State")
|
||||
&& !state.is_null()
|
||||
{
|
||||
let state =
|
||||
TailscaleState::try_from_value(state.as_i64().unwrap_or(4))
|
||||
.unwrap_or(TailscaleState::Stopped);
|
||||
instance.lock().expect("Couldn't get lock").publish(state);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Invalid Tailscale notification: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl BackendThread<TailscaleState, ()> for TailscaleBackend {
|
||||
fn subscribe(registrator: Registrator<TailscaleState>) -> (Receiver<TailscaleState>, ()) {
|
||||
let (tx, rx) = crossbeam_channel::unbounded::<TailscaleState>();
|
||||
let _ = registrator.send(tx);
|
||||
(rx, ())
|
||||
}
|
||||
|
||||
fn subscribers(&self) -> &Vec<Sender<TailscaleState>> {
|
||||
&self.subscribers
|
||||
}
|
||||
|
||||
fn init(registration_rx: Receiver<Sender<TailscaleState>>) {
|
||||
let state = initial_state();
|
||||
|
||||
let instance = Arc::new(Mutex::new(Self {
|
||||
subscribers: vec![],
|
||||
state,
|
||||
}));
|
||||
|
||||
let instance_clone = instance.clone();
|
||||
std::thread::spawn(move || {
|
||||
while let Ok(tx) = registration_rx.recv() {
|
||||
trace!(&LOGGER, "Added Tailscale Subscriber: {tx:?}");
|
||||
|
||||
let _ = tx.send(
|
||||
instance_clone
|
||||
.lock()
|
||||
.expect("Couldn't get lock")
|
||||
.state
|
||||
.clone(),
|
||||
);
|
||||
|
||||
instance_clone
|
||||
.lock()
|
||||
.expect("Couldn't get lock")
|
||||
.subscribers
|
||||
.push(tx);
|
||||
}
|
||||
});
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed to create Tokio runtime");
|
||||
|
||||
runtime.block_on(async move {
|
||||
loop {
|
||||
if let Err(e) = Self::watch_tailscale(instance.clone()).await {
|
||||
warn!(&LOGGER, "Error: {e:?}");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn initial_state() -> TailscaleState {
|
||||
if let Ok(output) = Command::new("tailscale")
|
||||
.arg("status")
|
||||
.arg("--json")
|
||||
.output()
|
||||
{
|
||||
let val: Result<serde_json::Value, _> = serde_json::from_slice(&output.stdout);
|
||||
if let Ok(val) = val {
|
||||
if let Some(res) = val.get("BackendState").and_then(|v| v.as_str())
|
||||
&& res.eq("Running")
|
||||
{
|
||||
return TailscaleState::Running;
|
||||
}
|
||||
} else {
|
||||
error!(&LOGGER, "{:?}", val.err());
|
||||
}
|
||||
}
|
||||
TailscaleState::Stopped
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::LOGGER;
|
||||
use crate::backend::BackendThread;
|
||||
use crate::backend::{BackendThread, SimpleBackendThread};
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use hyprland::data::{Client, Monitor, Workspaces};
|
||||
use hyprland::event_listener::EventListener;
|
||||
@@ -23,14 +23,7 @@ pub struct HyprlandComms {
|
||||
subscribers: Vec<Sender<WindowManagerEvent>>,
|
||||
}
|
||||
|
||||
impl BackendThread<WindowManagerEvent, ()> for HyprlandComms {
|
||||
fn subscribe(
|
||||
registrator: Sender<Sender<WindowManagerEvent>>,
|
||||
) -> (Receiver<WindowManagerEvent>, ()) {
|
||||
let (tx, rx) = crossbeam_channel::unbounded::<WindowManagerEvent>();
|
||||
let _ = registrator.send(tx);
|
||||
(rx, ())
|
||||
}
|
||||
impl SimpleBackendThread<WindowManagerEvent> for HyprlandComms {
|
||||
|
||||
fn subscribers(&self) -> &Vec<Sender<WindowManagerEvent>> {
|
||||
&self.subscribers
|
||||
|
||||
@@ -13,23 +13,20 @@ use std::process::Command;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub const LOGGER: Logger = Logger::create(Level::DEBUG);
|
||||
pub const APP_NAME: &str = "net.jmb19905.jmb-bar";
|
||||
|
||||
fn main() {
|
||||
info!(&LOGGER, "Starting jmb-bar");
|
||||
|
||||
gio::resources_register_include!("templates.gresource").expect("Failed to register resources");
|
||||
|
||||
let app = Application::builder()
|
||||
.application_id("net.jmb19905.jmb-bar")
|
||||
.build();
|
||||
let app = Application::builder().application_id(APP_NAME).build();
|
||||
|
||||
let watcher_slot: Rc<RefCell<Option<RecommendedWatcher>>> = Rc::new(RefCell::new(None));
|
||||
|
||||
let _ = Command::new("pkill hypridle").output(); // Kill existing hypridle processes
|
||||
|
||||
let backend = Backend::start();
|
||||
let _guard = backend.create_drop_guard(); // Make sure hypridle gets killed when the process exits
|
||||
trace!(&LOGGER, "Initialized DropGuard");
|
||||
|
||||
app.connect_startup(move |_| {
|
||||
let provider = CssProvider::new();
|
||||
|
||||
@@ -42,33 +42,41 @@ pub fn manage_title(button_opt: &Option<Button>, player_opt: &Option<Player>) {
|
||||
let mut handled = false;
|
||||
if let Some(button) = button_opt
|
||||
&& let Some(player) = player_opt
|
||||
&& let Ok(status) = player.get_playback_status()
|
||||
&& let Ok(metadata) = player.get_metadata()
|
||||
{
|
||||
match status {
|
||||
PlaybackStatus::Playing => {
|
||||
button.set_visible(true);
|
||||
button
|
||||
.child()
|
||||
.and_downcast::<Label>()
|
||||
.unwrap()
|
||||
.set_label(&format!(" {}", metadata.title().unwrap_or("")));
|
||||
button.add_css_class("media");
|
||||
button.add_css_class("playing");
|
||||
handled = true;
|
||||
//println!("{:?}", player.get_playback_status());
|
||||
if let Ok(status) = player.get_playback_status()
|
||||
&& let Ok(metadata) = player.get_metadata()
|
||||
{
|
||||
match status {
|
||||
PlaybackStatus::Playing => {
|
||||
button.set_visible(true);
|
||||
button
|
||||
.child()
|
||||
.and_downcast::<Label>()
|
||||
.unwrap()
|
||||
.set_label(&format!(" {}", metadata.title().unwrap_or("")));
|
||||
button.add_css_class("media");
|
||||
button.add_css_class("playing");
|
||||
handled = true;
|
||||
}
|
||||
PlaybackStatus::Paused => {
|
||||
button.set_visible(true);
|
||||
button
|
||||
.child()
|
||||
.and_downcast::<Label>()
|
||||
.unwrap()
|
||||
.set_label(&format!(" {}", metadata.title().unwrap_or("")));
|
||||
button.add_css_class("media");
|
||||
button.remove_css_class("playing");
|
||||
handled = true;
|
||||
}
|
||||
PlaybackStatus::Stopped => {
|
||||
button.set_visible(true);
|
||||
button.remove_css_class("media");
|
||||
button.remove_css_class("playing");
|
||||
set_title(&HyprlandComms::get_current_title(), button_opt);
|
||||
}
|
||||
}
|
||||
PlaybackStatus::Paused => {
|
||||
button.set_visible(true);
|
||||
button
|
||||
.child()
|
||||
.and_downcast::<Label>()
|
||||
.unwrap()
|
||||
.set_label(&format!(" {}", metadata.title().unwrap_or("")));
|
||||
button.add_css_class("media");
|
||||
button.remove_css_class("playing");
|
||||
handled = true;
|
||||
}
|
||||
PlaybackStatus::Stopped => {}
|
||||
}
|
||||
if !handled {
|
||||
button.set_visible(true);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::LOGGER;
|
||||
use crate::backend::BackendChannels;
|
||||
use crate::backend::audio::MediaCommand;
|
||||
use crate::backend::brightness_control::BrightnessMessage;
|
||||
use crate::backend::idle::{IdleMessage, IdleState};
|
||||
use crate::backend::network::{NetworkCommand, NetworkState, NetworkType};
|
||||
use crate::backend::tailscale::TailscaleState;
|
||||
use crate::backend::{BackendChannels, json_socket};
|
||||
use crate::ui::action_centre::ActionCentre;
|
||||
use crate::ui::widgets::{ExpandableBarItem, IconSlider};
|
||||
use battery::State;
|
||||
@@ -65,8 +65,7 @@ pub fn build(
|
||||
if monitor_valid {
|
||||
let _ = channels
|
||||
.br_tx()
|
||||
.send(BrightnessMessage::Request(monitor.clone()))
|
||||
.is_ok();
|
||||
.send(BrightnessMessage::Request(monitor.clone()));
|
||||
}
|
||||
|
||||
let volume_adjustment = Adjustment::new(60.0, 1.0, 100.0, 1.0, 5.0, 0.0);
|
||||
@@ -108,7 +107,11 @@ pub fn build(
|
||||
move |slider, _button, _| {
|
||||
if monitor_valid {
|
||||
let slider_val = slider.value().max(1.0);
|
||||
let _ = br_tx.send(BrightnessMessage::Value(monitor.clone(), slider_val as u8));
|
||||
let _ = br_tx.send(BrightnessMessage::Value(
|
||||
monitor.clone(),
|
||||
slider_val as u8,
|
||||
false,
|
||||
));
|
||||
let idx =
|
||||
((slider_val / 100.0) * (BRIGHTNESS_ICONS.len() - 1) as f64 + 0.5) as usize;
|
||||
slider.imp().main_icon.set_font_icon(BRIGHTNESS_ICONS[idx]);
|
||||
@@ -121,7 +124,7 @@ pub fn build(
|
||||
monitor,
|
||||
move |slider, _button, _| {
|
||||
if monitor_valid {
|
||||
let _ = br_tx.send(BrightnessMessage::Value(monitor.clone(), 60));
|
||||
let _ = br_tx.send(BrightnessMessage::Value(monitor.clone(), 60, false));
|
||||
slider.set_value(60.0);
|
||||
let idx = ((60.0 / 100.0) * (BRIGHTNESS_ICONS.len() - 1) as f64 + 0.5) as usize;
|
||||
slider.imp().main_icon.set_font_icon(BRIGHTNESS_ICONS[idx]);
|
||||
@@ -131,27 +134,12 @@ pub fn build(
|
||||
|
||||
let caffeine_button = Button::with_label(" ");
|
||||
caffeine_button.add_css_class("caffeine");
|
||||
let hypridle = channels.hypridle();
|
||||
let idle_tx = channels.idle_tx();
|
||||
caffeine_button.connect_clicked(move |button| {
|
||||
let mut child = hypridle.lock().expect("Couldn't get lock on hypridle");
|
||||
if child.is_none() {
|
||||
if let Ok(new_child) = Command::new("hypridle").spawn() {
|
||||
let _ = child.insert(new_child);
|
||||
button.remove_css_class("on")
|
||||
}
|
||||
} else if child.is_some()
|
||||
&& let Ok(exit) = child.as_mut().unwrap().try_wait()
|
||||
&& let Some(_) = exit
|
||||
{
|
||||
if let Ok(new_child) = Command::new("hypridle").spawn() {
|
||||
let _ = child.insert(new_child);
|
||||
button.remove_css_class("on")
|
||||
}
|
||||
if button.has_css_class("on") {
|
||||
let _ = idle_tx.send(IdleMessage::Release);
|
||||
} else {
|
||||
if child.as_mut().unwrap().kill().is_ok() {
|
||||
let _ = child.take();
|
||||
button.add_css_class("on");
|
||||
}
|
||||
let _ = idle_tx.send(IdleMessage::Inhibit);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -228,10 +216,10 @@ pub fn build(
|
||||
let _ = net_tx.send(NetworkCommand::ToggleVpn(String::from("wg")));
|
||||
});
|
||||
|
||||
let ts_button = Button::with_label("ts");
|
||||
ts_button.add_css_class("vpn");
|
||||
ts_button.connect_clicked(move |button| {
|
||||
let _ = Command::new("tailscale")
|
||||
let netbird_button = Button::with_label("nb");
|
||||
netbird_button.add_css_class("vpn");
|
||||
netbird_button.connect_clicked(move |button| {
|
||||
let _ = Command::new("netbird")
|
||||
.arg(if button.has_css_class("on") {
|
||||
"down"
|
||||
} else {
|
||||
@@ -249,7 +237,7 @@ pub fn build(
|
||||
vpn_box.set_valign(Align::Fill);
|
||||
vpn_box.set_vexpand(true);
|
||||
vpn_box.append(&wg_button);
|
||||
vpn_box.append(&ts_button);
|
||||
vpn_box.append(&netbird_button);
|
||||
//vpn_box.append(&tud_button);
|
||||
//vpn_box.append(&mvd_button);
|
||||
|
||||
@@ -327,8 +315,10 @@ pub fn build(
|
||||
let br_rx = channels.take_br_rx().unwrap();
|
||||
let net_rx = channels.take_net_rx().unwrap();
|
||||
let stats_rx = channels.take_stats_rx().unwrap();
|
||||
let ts_rx = channels.take_ts_rx().unwrap();
|
||||
let hypridle = channels.hypridle();
|
||||
let netbird_rx = channels.take_netbird_rx().unwrap();
|
||||
let idle_rx = channels.take_idle_rx().unwrap();
|
||||
let monitor_clone = monitor.clone();
|
||||
let br_tx = channels.br_tx();
|
||||
glib::timeout_add_local(Duration::from_millis(100), move || {
|
||||
while let Ok(command) = media_rx.try_recv() {
|
||||
if let MediaCommand::Cmd(vol, mute, bt) = command {
|
||||
@@ -355,7 +345,7 @@ pub fn build(
|
||||
}
|
||||
}
|
||||
while let Ok(message) = br_rx.try_recv() {
|
||||
if let BrightnessMessage::Value(mon, value) = message
|
||||
if let BrightnessMessage::Value(mon, value, _) = message
|
||||
&& monitor.eq(&mon)
|
||||
{
|
||||
brightness.set_value(value as f64);
|
||||
@@ -367,17 +357,33 @@ pub fn build(
|
||||
.set_font_icon(BRIGHTNESS_ICONS[idx]);
|
||||
}
|
||||
}
|
||||
if let Ok(mut hypridle) = hypridle.try_lock() {
|
||||
if hypridle.is_none() {
|
||||
caffeine_button.add_css_class("on");
|
||||
} else {
|
||||
if let Ok(exit) = hypridle.as_mut().unwrap().try_wait()
|
||||
&& exit.is_some()
|
||||
{
|
||||
caffeine_button.add_css_class("on");
|
||||
} else {
|
||||
while let Ok(state) = idle_rx.try_recv() {
|
||||
match state {
|
||||
IdleState::Dimmed => {
|
||||
if !caffeine_button.has_css_class("idle") && monitor_valid {
|
||||
let _ =
|
||||
br_tx.send(BrightnessMessage::Value(monitor_clone.clone(), 20, true));
|
||||
brightness.set_value(20.0);
|
||||
println!("Dimmed: {monitor_clone}");
|
||||
}
|
||||
caffeine_button.add_css_class("idle");
|
||||
caffeine_button.remove_css_class("inhibit");
|
||||
caffeine_button.remove_css_class("on");
|
||||
}
|
||||
other => {
|
||||
caffeine_button.remove_css_class("idle");
|
||||
caffeine_button.remove_css_class("inhibit");
|
||||
caffeine_button.remove_css_class("on");
|
||||
if monitor_valid {
|
||||
let _ = br_tx.send(BrightnessMessage::Revert(monitor_clone.clone()));
|
||||
}
|
||||
match other {
|
||||
IdleState::Caffeine => caffeine_button.add_css_class("on"),
|
||||
IdleState::Inhibited => caffeine_button.add_css_class("inhibit"),
|
||||
IdleState::Active => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
while let Ok(result) = net_rx.try_recv() {
|
||||
@@ -419,34 +425,43 @@ pub fn build(
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
while let Ok(tailscale_state) = ts_rx.try_recv() {
|
||||
while let Ok(tailscale_state) = netbird_rx.try_recv() {
|
||||
match tailscale_state {
|
||||
TailscaleState::Stopped => {
|
||||
ts_button.remove_css_class("on");
|
||||
ts_button.remove_css_class("starting");
|
||||
ts_button.remove_css_class("unknown");
|
||||
json_socket::State::Disconnected => {
|
||||
netbird_button.remove_css_class("on");
|
||||
netbird_button.remove_css_class("starting");
|
||||
netbird_button.remove_css_class("unknown");
|
||||
netbird_button.remove_css_class("login");
|
||||
}
|
||||
TailscaleState::Starting => {
|
||||
ts_button.remove_css_class("on");
|
||||
ts_button.add_css_class("starting");
|
||||
ts_button.remove_css_class("unknown");
|
||||
json_socket::State::Connecting => {
|
||||
netbird_button.remove_css_class("on");
|
||||
netbird_button.add_css_class("starting");
|
||||
netbird_button.remove_css_class("unknown");
|
||||
netbird_button.remove_css_class("login");
|
||||
}
|
||||
TailscaleState::Running => {
|
||||
ts_button.add_css_class("on");
|
||||
ts_button.remove_css_class("starting");
|
||||
ts_button.remove_css_class("unknown");
|
||||
json_socket::State::Connected => {
|
||||
netbird_button.add_css_class("on");
|
||||
netbird_button.remove_css_class("starting");
|
||||
netbird_button.remove_css_class("unknown");
|
||||
netbird_button.remove_css_class("login");
|
||||
}
|
||||
_ => {
|
||||
ts_button.remove_css_class("on");
|
||||
ts_button.remove_css_class("starting");
|
||||
ts_button.add_css_class("unknown");
|
||||
json_socket::State::NeedsLogin => {
|
||||
netbird_button.remove_css_class("on");
|
||||
netbird_button.remove_css_class("starting");
|
||||
netbird_button.remove_css_class("unknown");
|
||||
netbird_button.add_css_class("login");
|
||||
}
|
||||
}
|
||||
}
|
||||
if wg_button.has_css_class("on") || ts_button.has_css_class("on") {
|
||||
if wg_button.has_css_class("on") || netbird_button.has_css_class("on") {
|
||||
vpn_button.add_css_class("on");
|
||||
vpn_button.remove_css_class("login");
|
||||
} else if wg_button.has_css_class("login") || netbird_button.has_css_class("login") {
|
||||
vpn_button.add_css_class("login");
|
||||
vpn_button.remove_css_class("on");
|
||||
} else {
|
||||
vpn_button.remove_css_class("on");
|
||||
vpn_button.remove_css_class("login");
|
||||
}
|
||||
while let Ok(stats) = stats_rx.try_recv() {
|
||||
cpu_label.set_label(&format!("{}%", stats.cpu));
|
||||
|
||||
@@ -129,6 +129,14 @@ button {
|
||||
background: $red;
|
||||
}
|
||||
|
||||
.caffeine.idle {
|
||||
background: $yellow;
|
||||
}
|
||||
|
||||
.caffeine.inhibit {
|
||||
background: $green;
|
||||
}
|
||||
|
||||
.stats {
|
||||
padding: 0 6px;
|
||||
}
|
||||
@@ -176,6 +184,14 @@ button {
|
||||
color: $lightgreen;
|
||||
}
|
||||
|
||||
.vpn-icon.login {
|
||||
color: $yellow;
|
||||
}
|
||||
|
||||
.vpn-icon.login:hover {
|
||||
color: $yellow;
|
||||
}
|
||||
|
||||
.vpn {
|
||||
color: $background;
|
||||
background: $red;
|
||||
@@ -192,7 +208,7 @@ button {
|
||||
background: $green;
|
||||
}
|
||||
|
||||
.vpn.starting {
|
||||
.vpn.login {
|
||||
background: $yellow;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user