Compare commits
22 Commits
00b2b1f64c
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
092f0eab76 | ||
|
|
55ade087de | ||
|
|
495210c229 | ||
|
|
3285353006 | ||
|
|
1ec5d1e30d | ||
|
|
0a3a93adf2 | ||
|
|
0f67f4ffcf | ||
|
|
63f247a0dc | ||
|
|
5ffed55e37 | ||
|
|
f88f3ba30d | ||
|
|
2fa7b4a55c | ||
|
|
0f19ff6213 | ||
|
|
61a4e136ed | ||
|
|
daf865423f | ||
|
|
e547fd5885 | ||
|
|
c758268beb | ||
|
|
541ce5ccb2 | ||
|
|
f46c2408a5 | ||
|
|
c3ad6f38cf | ||
|
|
11fb03427c | ||
|
|
cdfc5b8d36 | ||
|
|
de1f731e63 |
72
.gitea/workflows/build.yaml
Normal file
72
.gitea/workflows/build.yaml
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
name: Build Project
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build Linux
|
||||||
|
runs-on: ubuntu-26.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Print Os release
|
||||||
|
run: cat /etc/os-release
|
||||||
|
|
||||||
|
- name: Cache Cargo
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/bin/
|
||||||
|
~/.cargo/registry/index/
|
||||||
|
~/.cargo/registry/cache/
|
||||||
|
~/.cargo/git/db/
|
||||||
|
target/
|
||||||
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-cargo-
|
||||||
|
|
||||||
|
- name: Install system dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y \
|
||||||
|
build-essential \
|
||||||
|
pkg-config \
|
||||||
|
libgtk-4-dev \
|
||||||
|
libgtk4-layer-shell-dev \
|
||||||
|
libudev-dev \
|
||||||
|
libpulse-dev
|
||||||
|
|
||||||
|
- name: Install Rust
|
||||||
|
run: |
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
|
||||||
|
| sh -s -- -y --profile minimal --default-toolchain stable
|
||||||
|
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||||
|
|
||||||
|
- name: Install Rust components
|
||||||
|
run: |
|
||||||
|
rustup component add clippy
|
||||||
|
|
||||||
|
- name: Run Clippy
|
||||||
|
run: cargo clippy --all-targets --all-features -- -D warnings
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: cargo test --locked
|
||||||
|
|
||||||
|
- name: Build release binaries
|
||||||
|
run: cargo build --locked --release --bins
|
||||||
|
|
||||||
|
- name: Upload binaries
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: jmb-bar-linux-x86_64
|
||||||
|
path: |
|
||||||
|
target/release/jmbbar
|
||||||
|
target/release/jmbctl
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 14
|
||||||
3633
Cargo.lock
generated
3633
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -9,11 +9,11 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "jmbctl"
|
name = "jmbctl"
|
||||||
path = "src/ctl.rs"
|
path = "src/bin/ctl.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
grass = "0.13.4"
|
grass = "0.13.4"
|
||||||
gtk = { version = "0.11.5", package = "gtk4", features = ["v4_24"] }
|
gtk = { version = "0.11.5", package = "gtk4", features = ["v4_22"] }
|
||||||
gtk4-layer-shell = "0.8.1"
|
gtk4-layer-shell = "0.8.1"
|
||||||
hyprland = { git = "https://github.com/romanstingler/hyprland-rs-next", branch = "master" }
|
hyprland = { git = "https://github.com/romanstingler/hyprland-rs-next", branch = "master" }
|
||||||
notify = "8.2.0"
|
notify = "8.2.0"
|
||||||
@@ -26,7 +26,6 @@ serde_json = "1.0.154"
|
|||||||
crossbeam-channel = "0.5.17"
|
crossbeam-channel = "0.5.17"
|
||||||
jmb-logger-rs = { version = "0.2.2", git = "https://github.com/jmb05/jmb-logger-rs" }
|
jmb-logger-rs = { version = "0.2.2", git = "https://github.com/jmb05/jmb-logger-rs" }
|
||||||
sysinfo = "0.40.0"
|
sysinfo = "0.40.0"
|
||||||
all-smi = "0.28.1"
|
|
||||||
rustsni = "0.2.2"
|
rustsni = "0.2.2"
|
||||||
thiserror = "2.0.21"
|
thiserror = "2.0.21"
|
||||||
pulse = { package = "libpulse-binding", version = "2.30" }
|
pulse = { package = "libpulse-binding", version = "2.30" }
|
||||||
@@ -45,8 +44,10 @@ zbus = "5.19.0" # Keep at this version for now because other dependencies also h
|
|||||||
serde = { version = "1.0.229", features = ["derive"] }
|
serde = { version = "1.0.229", features = ["derive"] }
|
||||||
freedesktop-desktop-entry = "0.8.4"
|
freedesktop-desktop-entry = "0.8.4"
|
||||||
nucleo-matcher = "0.3.1"
|
nucleo-matcher = "0.3.1"
|
||||||
meval = "0.2.0"
|
meval = { version = "0.3.0", git = "https://github.com/UncleAndy/meval-rs" }
|
||||||
toml = "1.1.8"
|
toml = "1.1.8"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
glib-build-tools = "0.22.8"
|
glib-build-tools = "0.22.8"
|
||||||
|
grass = "0.13.4"
|
||||||
|
regex = "1.13.1"
|
||||||
|
|||||||
28
build.rs
28
build.rs
@@ -1,7 +1,35 @@
|
|||||||
|
use regex::Regex;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::{env, fs};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
glib_build_tools::compile_resources(
|
glib_build_tools::compile_resources(
|
||||||
&["resources"],
|
&["resources"],
|
||||||
"resources/resources.gresource.xml",
|
"resources/resources.gresource.xml",
|
||||||
"templates.gresource",
|
"templates.gresource",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let colors_path = "style/_colors.scss";
|
||||||
|
|
||||||
|
println!("cargo:rerun-if-changed={colors_path}");
|
||||||
|
|
||||||
|
let scss = fs::read_to_string(colors_path).expect("Failed to read SCSS colors");
|
||||||
|
|
||||||
|
let regex =
|
||||||
|
Regex::new(r"(?m)^\s*\$([a-zA-Z_][a-zA-Z0-9_-]*)\s*:\s*(#[0-9a-fA-F]{3,8})\s*;").unwrap();
|
||||||
|
|
||||||
|
let mut generated = String::from("// Generated by build.rs. Do not edit\n\n");
|
||||||
|
|
||||||
|
for captures in regex.captures_iter(&scss) {
|
||||||
|
let name = captures.get(1).unwrap().as_str();
|
||||||
|
let value = captures.get(2).unwrap().as_str();
|
||||||
|
|
||||||
|
let constant_name = name.replace('-', "_").to_uppercase();
|
||||||
|
|
||||||
|
generated.push_str(&format!("pub const {constant_name}: &str = \"{value}\";\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||||
|
|
||||||
|
fs::write(out_dir.join("theme.rs"), generated).expect("Failed to generate theme.rs");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
[idle]
|
||||||
idle_timeout = 20
|
idle_timeout = 20
|
||||||
dim_timeout = 60
|
dim_timeout = 60
|
||||||
lock_timeout = 300
|
lock_timeout = 300
|
||||||
sleep_timeout = 320
|
sleep_timeout = 320
|
||||||
|
lock_command = 'hyprlock'
|
||||||
|
sleep_command = 'systemctl sleep'
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
pub mod audio;
|
pub mod audio;
|
||||||
pub mod brightness_control;
|
pub mod brightness_control;
|
||||||
mod commands;
|
mod commands;
|
||||||
|
mod gpu_utilization;
|
||||||
pub(crate) mod idle;
|
pub(crate) mod idle;
|
||||||
pub(crate) mod json_socket;
|
pub(crate) mod json_socket;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
@@ -13,11 +14,12 @@ use crate::backend::brightness_control::{BrightnessManager, BrightnessMessage};
|
|||||||
use crate::backend::commands::ControlSocket;
|
use crate::backend::commands::ControlSocket;
|
||||||
use crate::backend::idle::{IdleDaemon, IdleMessage, IdleState};
|
use crate::backend::idle::{IdleDaemon, IdleMessage, IdleState};
|
||||||
use crate::backend::json_socket::{JsonSocketBackend, State};
|
use crate::backend::json_socket::{JsonSocketBackend, State};
|
||||||
use crate::backend::network::{NetworkBackendHandle, NetworkCommand, NetworkState};
|
use crate::backend::network::{NetworkBackend, NetworkCommand, NetworkState};
|
||||||
use crate::backend::perf_stats::{Stats, StatsBackend};
|
use crate::backend::perf_stats::{Stats, StatsBackend};
|
||||||
use crate::backend::window_manager_hypr::{HyprlandComms, WindowManagerEvent};
|
use crate::backend::window_manager_hypr::{HyprlandComms, WindowManagerEvent};
|
||||||
use crate::ctl::ControlCommand;
|
|
||||||
use crossbeam_channel::{Receiver, Sender};
|
use crossbeam_channel::{Receiver, Sender};
|
||||||
|
use jmb_bar::ControlCommand;
|
||||||
|
use jmb_bar::config::Config;
|
||||||
use jmb_logger_rs::*;
|
use jmb_logger_rs::*;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
@@ -27,8 +29,15 @@ pub(crate) type Registrator<M> = Sender<Sender<M>>;
|
|||||||
pub trait BackendThread<M: Debug + Clone, D> {
|
pub trait BackendThread<M: Debug + Clone, D> {
|
||||||
fn subscribe(registrator: Registrator<M>) -> (Receiver<M>, D);
|
fn subscribe(registrator: Registrator<M>) -> (Receiver<M>, D);
|
||||||
fn subscribers(&self) -> &Vec<Sender<M>>;
|
fn subscribers(&self) -> &Vec<Sender<M>>;
|
||||||
fn init(registration_rx: Receiver<Sender<M>>);
|
fn init(self, registration_rx: Receiver<Sender<M>>);
|
||||||
fn publish(&self, message: M) {
|
fn publish(&self, message: M) {
|
||||||
|
if self.subscribers().is_empty() {
|
||||||
|
warn!(
|
||||||
|
&LOGGER,
|
||||||
|
"Tried sending message: {message:?} to no subscribers"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
trace!(&LOGGER, "Sending: {message:?} to {:?}", self.subscribers());
|
trace!(&LOGGER, "Sending: {message:?} to {:?}", self.subscribers());
|
||||||
for tx in self.subscribers() {
|
for tx in self.subscribers() {
|
||||||
if let Err(e) = tx.send(message.clone()) {
|
if let Err(e) = tx.send(message.clone()) {
|
||||||
@@ -40,16 +49,19 @@ pub trait BackendThread<M: Debug + Clone, D> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn start() -> Registrator<M> {
|
fn start(self) -> Registrator<M>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
let (registration_tx, registration_rx) = crossbeam_channel::unbounded::<Sender<M>>();
|
let (registration_tx, registration_rx) = crossbeam_channel::unbounded::<Sender<M>>();
|
||||||
Self::init(registration_rx);
|
Self::init(self, registration_rx);
|
||||||
registration_tx
|
registration_tx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SimpleBackendThread<M: Debug + Clone>: BackendThread<M, ()> {
|
pub trait SimpleBackendThread<M: Debug + Clone>: BackendThread<M, ()> {
|
||||||
fn subscribers(&self) -> &Vec<Sender<M>>;
|
fn subscribers(&self) -> &Vec<Sender<M>>;
|
||||||
fn init(registration_rx: Receiver<Sender<M>>);
|
fn init(self, registration_rx: Receiver<Sender<M>>);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: Debug + Clone, S: SimpleBackendThread<M>> BackendThread<M, ()> for S {
|
impl<M: Debug + Clone, S: SimpleBackendThread<M>> BackendThread<M, ()> for S {
|
||||||
@@ -63,8 +75,8 @@ impl<M: Debug + Clone, S: SimpleBackendThread<M>> BackendThread<M, ()> for S {
|
|||||||
SimpleBackendThread::subscribers(self)
|
SimpleBackendThread::subscribers(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(registration_rx: Receiver<Sender<M>>) {
|
fn init(self, registration_rx: Receiver<Sender<M>>) {
|
||||||
<S as SimpleBackendThread<M>>::init(registration_rx)
|
<S as SimpleBackendThread<M>>::init(self, registration_rx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,6 +93,7 @@ pub struct Backend {
|
|||||||
control_registrator: Registrator<ControlCommand>,
|
control_registrator: Registrator<ControlCommand>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
pub enum ChannelType {
|
pub enum ChannelType {
|
||||||
Net,
|
Net,
|
||||||
Hypr,
|
Hypr,
|
||||||
@@ -94,17 +107,17 @@ pub enum ChannelType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Backend {
|
impl Backend {
|
||||||
pub fn start() -> Self {
|
pub fn start(config: &Config) -> Self {
|
||||||
debug!(&LOGGER, "Starting Backend threads...");
|
debug!(&LOGGER, "Starting Backend threads...");
|
||||||
let hypr_registrator = HyprlandComms::start();
|
let hypr_registrator = HyprlandComms::new().start();
|
||||||
let net_registrator = NetworkBackendHandle::start();
|
let net_registrator = NetworkBackend::new().start();
|
||||||
let br_registrator = BrightnessManager::start();
|
let br_registrator = BrightnessManager::new().start();
|
||||||
let stats_registrator = StatsBackend::start();
|
let stats_registrator = StatsBackend::new().start();
|
||||||
let audio_registrator = AudioBackend::start();
|
let audio_registrator = AudioBackend::new().start();
|
||||||
let media_registration = MediaBackend::start();
|
let media_registration = MediaBackend::new().start();
|
||||||
let netbird_registrator = JsonSocketBackend::start();
|
let netbird_registrator = JsonSocketBackend::new().start();
|
||||||
let idle_registrator = IdleDaemon::start();
|
let idle_registrator = IdleDaemon::new(config.idle.clone()).start();
|
||||||
let control_registrator = ControlSocket::start();
|
let control_registrator = ControlSocket::new().start();
|
||||||
debug!(&LOGGER, "Started Backend threads");
|
debug!(&LOGGER, "Started Backend threads");
|
||||||
Self {
|
Self {
|
||||||
hypr_registrator,
|
hypr_registrator,
|
||||||
@@ -124,7 +137,7 @@ impl Backend {
|
|||||||
for ch_type in channel_types {
|
for ch_type in channel_types {
|
||||||
match ch_type {
|
match ch_type {
|
||||||
ChannelType::Net => {
|
ChannelType::Net => {
|
||||||
let (rx, tx) = NetworkBackendHandle::subscribe(self.net_registrator.clone());
|
let (rx, tx) = NetworkBackend::subscribe(self.net_registrator.clone());
|
||||||
channels.net_tx = Some(tx);
|
channels.net_tx = Some(tx);
|
||||||
channels.net_rx = Some(rx);
|
channels.net_rx = Some(rx);
|
||||||
}
|
}
|
||||||
@@ -169,8 +182,8 @@ impl Backend {
|
|||||||
channels
|
channels
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn subscribe_all(&self) -> BackendChannels {
|
pub fn subscribe_except(&self, channel_types: &[ChannelType]) -> BackendChannels {
|
||||||
self.subscribe(&[
|
let mut types = vec![
|
||||||
ChannelType::Net,
|
ChannelType::Net,
|
||||||
ChannelType::Idle,
|
ChannelType::Idle,
|
||||||
ChannelType::Netbird,
|
ChannelType::Netbird,
|
||||||
@@ -180,7 +193,9 @@ impl Backend {
|
|||||||
ChannelType::Audio,
|
ChannelType::Audio,
|
||||||
ChannelType::Media,
|
ChannelType::Media,
|
||||||
ChannelType::Hypr,
|
ChannelType::Hypr,
|
||||||
])
|
];
|
||||||
|
types.retain(|i| !channel_types.contains(i));
|
||||||
|
self.subscribe(&types)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -266,7 +266,16 @@ pub enum AudioMessage {
|
|||||||
|
|
||||||
pub struct AudioBackend {
|
pub struct AudioBackend {
|
||||||
subscribers: Vec<Sender<AudioMessage>>,
|
subscribers: Vec<Sender<AudioMessage>>,
|
||||||
command_tx: Sender<AudioMessage>,
|
command_tx: Option<Sender<AudioMessage>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioBackend {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
subscribers: vec![],
|
||||||
|
command_tx: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BackendThread<AudioMessage, Sender<AudioMessage>> for AudioBackend {
|
impl BackendThread<AudioMessage, Sender<AudioMessage>> for AudioBackend {
|
||||||
@@ -288,12 +297,10 @@ impl BackendThread<AudioMessage, Sender<AudioMessage>> for AudioBackend {
|
|||||||
&self.subscribers
|
&self.subscribers
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(registration_rx: Receiver<Sender<AudioMessage>>) {
|
fn init(mut self, registration_rx: Receiver<Sender<AudioMessage>>) {
|
||||||
let (command_tx, command_rx) = crossbeam_channel::unbounded::<AudioMessage>();
|
let (command_tx, command_rx) = crossbeam_channel::unbounded::<AudioMessage>();
|
||||||
let instance = Arc::new(Mutex::new(Self {
|
self.command_tx = Some(command_tx);
|
||||||
subscribers: vec![],
|
let instance = Arc::new(Mutex::new(self));
|
||||||
command_tx,
|
|
||||||
}));
|
|
||||||
let (controller_tx, controller_rx) = crossbeam_channel::unbounded::<ControllerMsg>();
|
let (controller_tx, controller_rx) = crossbeam_channel::unbounded::<ControllerMsg>();
|
||||||
let notify = Arc::new(EventFD::new(0, EfdFlags::EFD_CLOEXEC).unwrap());
|
let notify = Arc::new(EventFD::new(0, EfdFlags::EFD_CLOEXEC).unwrap());
|
||||||
thread::spawn({
|
thread::spawn({
|
||||||
@@ -381,7 +388,8 @@ impl BackendThread<AudioMessage, Sender<AudioMessage>> for AudioBackend {
|
|||||||
.lock()
|
.lock()
|
||||||
.expect("Couldn't get lock")
|
.expect("Couldn't get lock")
|
||||||
.command_tx
|
.command_tx
|
||||||
.clone(),
|
.clone()
|
||||||
|
.unwrap(),
|
||||||
))
|
))
|
||||||
.is_ok()
|
.is_ok()
|
||||||
{
|
{
|
||||||
@@ -460,18 +468,14 @@ impl BackendThread<MediaMessage, Sender<MediaMessage>> for MediaBackend {
|
|||||||
&self.subscribers
|
&self.subscribers
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(registration_rx: Receiver<Sender<MediaMessage>>) {
|
fn init(self, registration_rx: Receiver<Sender<MediaMessage>>) {
|
||||||
let (tx, rx) = crossbeam_channel::unbounded::<MediaMessage>();
|
let (tx, rx) = crossbeam_channel::unbounded::<MediaMessage>();
|
||||||
let instance = Arc::new(Mutex::new(Self {
|
let instance = Arc::new(Mutex::new(self));
|
||||||
subscribers: vec![],
|
|
||||||
players: HashMap::new(),
|
|
||||||
active_player: None,
|
|
||||||
}));
|
|
||||||
thread::spawn({
|
thread::spawn({
|
||||||
let instance = instance.clone();
|
let instance = instance.clone();
|
||||||
move || {
|
move || {
|
||||||
while let Ok(sender) = registration_rx.recv() {
|
while let Ok(sender) = registration_rx.recv() {
|
||||||
if let Ok(_) = sender.send(MediaMessage::Init(tx.clone())) {
|
if sender.send(MediaMessage::Init(tx.clone())).is_ok() {
|
||||||
let mut instance_guard = instance.lock().unwrap();
|
let mut instance_guard = instance.lock().unwrap();
|
||||||
if let Some(name) = &instance_guard.active_player
|
if let Some(name) = &instance_guard.active_player
|
||||||
&& let Some(status) = instance_guard.players.get(name)
|
&& let Some(status) = instance_guard.players.get(name)
|
||||||
@@ -512,30 +516,32 @@ impl BackendThread<MediaMessage, Sender<MediaMessage>> for MediaBackend {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
while let Ok(msg) = rx.recv() {
|
while let Ok(msg) = rx.recv() {
|
||||||
let mut instance_guard = instance.lock().unwrap();
|
let Some(name) = &instance.lock().unwrap().active_player.clone() else {
|
||||||
if let Some(name) = &instance_guard.active_player {
|
continue;
|
||||||
if let Some(status) = instance_guard.players.get(name) {
|
};
|
||||||
match msg {
|
let Some(status) = instance.lock().unwrap().players.get(name).cloned()
|
||||||
MediaMessage::PlayerPlay => {
|
else {
|
||||||
if let PlayerStatus::Paused(_) = status {
|
continue;
|
||||||
instance_guard.active_player_play(&connection).await;
|
};
|
||||||
}
|
match msg {
|
||||||
}
|
MediaMessage::PlayerPlay => {
|
||||||
MediaMessage::PlayerPause => {
|
if let PlayerStatus::Paused(_) = status {
|
||||||
if let PlayerStatus::Playing(_) = status {
|
MediaBackend::player_play(name, &connection).await;
|
||||||
instance_guard.active_player_pause(&connection).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MediaMessage::PlayerStop => {
|
|
||||||
if let PlayerStatus::Playing(_) | PlayerStatus::Paused(_) =
|
|
||||||
status
|
|
||||||
{
|
|
||||||
instance_guard.active_player_stop(&connection).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
MediaMessage::PlayerPause => {
|
||||||
|
if let PlayerStatus::Playing(_) = status {
|
||||||
|
MediaBackend::player_pause(name, &connection).await;
|
||||||
|
instance.lock().unwrap().update_active_player();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MediaMessage::PlayerStop => {
|
||||||
|
if let PlayerStatus::Playing(_) | PlayerStatus::Paused(_) = status {
|
||||||
|
MediaBackend::player_stop(name, &connection).await;
|
||||||
|
instance.lock().unwrap().select_active_player(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -545,6 +551,13 @@ impl BackendThread<MediaMessage, Sender<MediaMessage>> for MediaBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MediaBackend {
|
impl MediaBackend {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
subscribers: vec![],
|
||||||
|
players: HashMap::new(),
|
||||||
|
active_player: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
async fn find_existing_players(
|
async fn find_existing_players(
|
||||||
connection: &zbus::Connection,
|
connection: &zbus::Connection,
|
||||||
instance: Arc<Mutex<Self>>,
|
instance: Arc<Mutex<Self>>,
|
||||||
@@ -564,7 +577,7 @@ impl MediaBackend {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for player in players {
|
for player in players {
|
||||||
Self::spawn_new_player_watcher(&connection, &instance, &player);
|
Self::spawn_new_player_watcher(connection, &instance, &player);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -669,11 +682,11 @@ impl MediaBackend {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(value) = args.changed_properties().get("PlaybackStatus") {
|
if let Some(value) = args.changed_properties().get("PlaybackStatus")
|
||||||
if let Ok(status) = <String>::try_from(value.clone()) {
|
&& let Ok(status) = <String>::try_from(value.clone())
|
||||||
debug!(&LOGGER, "Player ({name}) status has changed to: '{status}'");
|
{
|
||||||
Self::insert_or_update_player(&instance, &props, name.into(), status).await;
|
debug!(&LOGGER, "Player ({name}) status has changed to: '{status}'");
|
||||||
}
|
Self::insert_or_update_player(&instance, &props, name.into(), status).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
debug!(&LOGGER, "(2) Removing player: {name}");
|
debug!(&LOGGER, "(2) Removing player: {name}");
|
||||||
@@ -688,7 +701,7 @@ impl MediaBackend {
|
|||||||
player_name: String,
|
player_name: String,
|
||||||
player_status: String,
|
player_status: String,
|
||||||
) {
|
) {
|
||||||
match Self::get_player_status_from_string(&props, player_status).await {
|
match Self::get_player_status_from_string(props, player_status).await {
|
||||||
Ok(status) => {
|
Ok(status) => {
|
||||||
let mut instance_guard = instance.lock().unwrap();
|
let mut instance_guard = instance.lock().unwrap();
|
||||||
instance_guard.players.insert(player_name.clone(), status);
|
instance_guard.players.insert(player_name.clone(), status);
|
||||||
@@ -745,60 +758,35 @@ impl MediaBackend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn active_player_play(&self, connection: &zbus::Connection) {
|
async fn player_play(name: &str, connection: &zbus::Connection) {
|
||||||
if let Some(player_name) = &self.active_player {
|
if let Ok(proxy) =
|
||||||
if let Ok(proxy) = zbus::Proxy::new(
|
zbus::Proxy::new(connection, name, MPRIS_PATH, MPRIS_PLAYER_INTERFACE).await
|
||||||
&connection,
|
&& let Err(e) = proxy.call_method("Play", &()).await
|
||||||
player_name.as_str(),
|
{
|
||||||
MPRIS_PATH,
|
warn!(&LOGGER, "Error starting play for player: {name}, {e}");
|
||||||
MPRIS_PLAYER_INTERFACE,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
if let Err(e) = proxy.call_method("Play", &()).await {
|
|
||||||
warn!(
|
|
||||||
&LOGGER,
|
|
||||||
"Error starting play for player: {player_name}, {e}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn active_player_pause(&mut self, connection: &zbus::Connection) {
|
async fn player_pause(name: &str, connection: &zbus::Connection) {
|
||||||
if let Some(player_name) = &self.active_player {
|
if let Ok(proxy) =
|
||||||
if let Ok(proxy) = zbus::Proxy::new(
|
zbus::Proxy::new(connection, name, MPRIS_PATH, MPRIS_PLAYER_INTERFACE).await
|
||||||
&connection,
|
&& let Err(e) = proxy.call_method("Pause", &()).await
|
||||||
player_name.as_str(),
|
{
|
||||||
MPRIS_PATH,
|
warn!(&LOGGER, "Error pausing player: {name}, {e}");
|
||||||
MPRIS_PLAYER_INTERFACE,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
if let Err(e) = proxy.call_method("Pause", &()).await {
|
|
||||||
warn!(&LOGGER, "Error pausing player: {player_name}, {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.select_active_player(self.active_player.clone());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn active_player_stop(&mut self, connection: &zbus::Connection) {
|
async fn player_stop(name: &str, connection: &zbus::Connection) {
|
||||||
if let Some(player_name) = &self.active_player {
|
if let Ok(proxy) =
|
||||||
if let Ok(proxy) = zbus::Proxy::new(
|
zbus::Proxy::new(connection, name, MPRIS_PATH, MPRIS_PLAYER_INTERFACE).await
|
||||||
&connection,
|
&& let Err(e) = proxy.call_method("Stop", &()).await
|
||||||
player_name.as_str(),
|
{
|
||||||
MPRIS_PATH,
|
warn!(&LOGGER, "Error stopping player: {name}, {e}");
|
||||||
MPRIS_PLAYER_INTERFACE,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
if let Err(e) = proxy.call_method("Stop", &()).await {
|
|
||||||
warn!(&LOGGER, "Error stopping player: {player_name}, {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.select_active_player(None);
|
}
|
||||||
|
|
||||||
|
fn update_active_player(&mut self) {
|
||||||
|
self.select_active_player(self.active_player.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_active_player(&mut self, changed_player: Option<String>) {
|
fn select_active_player(&mut self, changed_player: Option<String>) {
|
||||||
|
|||||||
@@ -143,12 +143,9 @@ impl BackendThread<BrightnessMessage, Sender<BrightnessMessage>> for BrightnessM
|
|||||||
&self.subscribers
|
&self.subscribers
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(registration_rx: Receiver<Sender<BrightnessMessage>>) {
|
fn init(self, registration_rx: Receiver<Sender<BrightnessMessage>>) {
|
||||||
let (f2b_tx, f2b_rx) = crossbeam_channel::unbounded::<BrightnessMessage>();
|
let (f2b_tx, f2b_rx) = crossbeam_channel::unbounded::<BrightnessMessage>();
|
||||||
let instance = Arc::new(Mutex::new(Self {
|
let instance = Arc::new(Mutex::new(self));
|
||||||
display_cache: HashMap::new(),
|
|
||||||
subscribers: Vec::new(),
|
|
||||||
}));
|
|
||||||
let instance_clone = instance.clone();
|
let instance_clone = instance.clone();
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
while let Ok(tx) = registration_rx.recv() {
|
while let Ok(tx) = registration_rx.recv() {
|
||||||
@@ -246,6 +243,12 @@ enum Operation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BrightnessManager {
|
impl BrightnessManager {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
subscribers: vec![],
|
||||||
|
display_cache: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn set_brightness(&mut self, connector: String, percent: u8) {
|
pub fn set_brightness(&mut self, connector: String, percent: u8) {
|
||||||
if !self.display_cache.contains_key(&connector) {
|
if !self.display_cache.contains_key(&connector) {
|
||||||
self.register_display(connector.clone());
|
self.register_display(connector.clone());
|
||||||
|
|||||||
@@ -1,26 +1,33 @@
|
|||||||
use crate::LOGGER;
|
use crate::LOGGER;
|
||||||
use crate::backend::{BackendThread, SimpleBackendThread};
|
use crate::backend::{BackendThread, SimpleBackendThread};
|
||||||
use crate::ctl::{ControlCommand, SOCKET_PATH};
|
|
||||||
use crossbeam_channel::{Receiver, Sender};
|
use crossbeam_channel::{Receiver, Sender};
|
||||||
|
use jmb_bar::{ControlCommand, SOCKET_PATH};
|
||||||
use jmb_logger_rs::*;
|
use jmb_logger_rs::*;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::net::Shutdown;
|
use std::net::Shutdown;
|
||||||
use std::os::unix::net::UnixListener;
|
use std::os::unix::net::UnixListener;
|
||||||
|
use std::str::FromStr;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
pub struct ControlSocket {
|
pub struct ControlSocket {
|
||||||
subscribers: Vec<Sender<ControlCommand>>,
|
subscribers: Vec<Sender<ControlCommand>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ControlSocket {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
subscribers: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SimpleBackendThread<ControlCommand> for ControlSocket {
|
impl SimpleBackendThread<ControlCommand> for ControlSocket {
|
||||||
fn subscribers(&self) -> &Vec<Sender<ControlCommand>> {
|
fn subscribers(&self) -> &Vec<Sender<ControlCommand>> {
|
||||||
&self.subscribers
|
&self.subscribers
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(registration_rx: Receiver<Sender<ControlCommand>>) {
|
fn init(self, registration_rx: Receiver<Sender<ControlCommand>>) {
|
||||||
let instance = Arc::new(Mutex::new(Self {
|
let instance = Arc::new(Mutex::new(self));
|
||||||
subscribers: Vec::new(),
|
|
||||||
}));
|
|
||||||
let instance_clone = instance.clone();
|
let instance_clone = instance.clone();
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
while let Ok(sender) = registration_rx.recv() {
|
while let Ok(sender) = registration_rx.recv() {
|
||||||
@@ -28,11 +35,11 @@ impl SimpleBackendThread<ControlCommand> for ControlSocket {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
if std::fs::metadata(SOCKET_PATH).is_ok() {
|
if std::fs::metadata(SOCKET_PATH).is_ok()
|
||||||
if let Err(e) = std::fs::remove_file(SOCKET_PATH) {
|
&& let Err(e) = std::fs::remove_file(SOCKET_PATH)
|
||||||
error!(&LOGGER, "Error deleting old socket file: {e}");
|
{
|
||||||
return;
|
error!(&LOGGER, "Error deleting old socket file: {e}");
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
let socket = UnixListener::bind(SOCKET_PATH);
|
let socket = UnixListener::bind(SOCKET_PATH);
|
||||||
let Ok(socket) = socket else {
|
let Ok(socket) = socket else {
|
||||||
@@ -52,7 +59,7 @@ impl SimpleBackendThread<ControlCommand> for ControlSocket {
|
|||||||
let _ = stream.shutdown(Shutdown::Both);
|
let _ = stream.shutdown(Shutdown::Both);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(command) = ControlCommand::from_str(&read) {
|
if let Ok(command) = ControlCommand::from_str(&read) {
|
||||||
instance.lock().unwrap().publish(command);
|
instance.lock().unwrap().publish(command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
194
src/backend/gpu_utilization.rs
Normal file
194
src/backend/gpu_utilization.rs
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
|
||||||
|
use std::{fs, io, mem};
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Default)]
|
||||||
|
struct PerfEventAttr {
|
||||||
|
type_: u32,
|
||||||
|
size: u32,
|
||||||
|
config: u64,
|
||||||
|
sample_period: u64,
|
||||||
|
sample_type: u64,
|
||||||
|
read_format: u64,
|
||||||
|
flags: u64,
|
||||||
|
wakeup_events: u32,
|
||||||
|
bp_type: u32,
|
||||||
|
config1: u64,
|
||||||
|
config2: u64,
|
||||||
|
branch_sample_type: u64,
|
||||||
|
sample_regs_user: u64,
|
||||||
|
sample_stack_user: u32,
|
||||||
|
clockid: i32,
|
||||||
|
sample_regs_intr: u64,
|
||||||
|
aux_watermark: u32,
|
||||||
|
sample_max_stack: u16,
|
||||||
|
reserved2: u16,
|
||||||
|
aux_sample_size: u32,
|
||||||
|
reserved3: u32,
|
||||||
|
sig_data: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
const PERF_FORMAT_TOTAL_TIME_ENABLED: u64 = 1;
|
||||||
|
const PERF_FORMAT_TOTAL_TIME_RUNNING: u64 = 2;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct ReadFormat {
|
||||||
|
value: u64,
|
||||||
|
time_enabled: u64,
|
||||||
|
time_running: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReadFormat {
|
||||||
|
fn calculate_utilization(&self, previous: &ReadFormat) -> f64 {
|
||||||
|
let busy_delta = self.value.saturating_sub(previous.value);
|
||||||
|
let elapsed_delta = self.time_enabled.saturating_sub(previous.time_enabled);
|
||||||
|
|
||||||
|
if elapsed_delta == 0 {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
(busy_delta as f64 / elapsed_delta as f64 * 100.0).clamp(0.0, 100.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_number(path: &str) -> io::Result<u64> {
|
||||||
|
fs::read_to_string(path)?
|
||||||
|
.trim()
|
||||||
|
.parse()
|
||||||
|
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_config(path: &str) -> io::Result<u64> {
|
||||||
|
let contents = fs::read_to_string(path)?;
|
||||||
|
let value = contents
|
||||||
|
.trim()
|
||||||
|
.strip_prefix("config=")
|
||||||
|
.unwrap_or(contents.trim());
|
||||||
|
|
||||||
|
u64::from_str_radix(value.trim_start_matches("0x"), 16)
|
||||||
|
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
|
||||||
|
}
|
||||||
|
|
||||||
|
struct I915Engine {
|
||||||
|
name: String,
|
||||||
|
fd: OwnedFd,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for I915Engine {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.name.hash(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<Self> for I915Engine {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.name.eq(&other.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for I915Engine {}
|
||||||
|
|
||||||
|
impl I915Engine {
|
||||||
|
fn open(name: &str) -> io::Result<Self> {
|
||||||
|
let base = "/sys/bus/event_source/devices/i915";
|
||||||
|
|
||||||
|
let pmu_type = read_number(&format!("{base}/type"))? as u32;
|
||||||
|
let config = read_config(&format!("{base}/events/{name}"))?;
|
||||||
|
|
||||||
|
let attr = PerfEventAttr {
|
||||||
|
type_: pmu_type,
|
||||||
|
size: size_of::<PerfEventAttr>() as u32,
|
||||||
|
config,
|
||||||
|
read_format: PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// pid = -1 and cpu = -1: system-wide event, if permitted.
|
||||||
|
let fd = unsafe {
|
||||||
|
libc::syscall(
|
||||||
|
libc::SYS_perf_event_open,
|
||||||
|
&attr as *const PerfEventAttr,
|
||||||
|
-1i32, // all processes
|
||||||
|
0i32, // CPU 0
|
||||||
|
-1i32, // no event group
|
||||||
|
0u64,
|
||||||
|
) as i32
|
||||||
|
};
|
||||||
|
|
||||||
|
if fd < 0 {
|
||||||
|
return Err(io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
let fd = unsafe { OwnedFd::from_raw_fd(fd) };
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
name: name.to_owned(),
|
||||||
|
fd,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(&self) -> io::Result<ReadFormat> {
|
||||||
|
let mut result = ReadFormat {
|
||||||
|
value: 0,
|
||||||
|
time_enabled: 0,
|
||||||
|
time_running: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let size = mem::size_of::<ReadFormat>();
|
||||||
|
let read = unsafe {
|
||||||
|
libc::read(
|
||||||
|
self.fd.as_raw_fd(),
|
||||||
|
&mut result as *mut _ as *mut libc::c_void,
|
||||||
|
size,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if read != size as isize {
|
||||||
|
return Err(io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct I915UtilizationMonitor {
|
||||||
|
engines: HashMap<I915Engine, Option<ReadFormat>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl I915UtilizationMonitor {
|
||||||
|
pub fn new() -> Option<Self> {
|
||||||
|
let mut engines: HashMap<_, Option<ReadFormat>> = HashMap::new();
|
||||||
|
let Ok(dir) = fs::read_dir("/sys/bus/event_source/devices/i915/events") else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
for entry in dir {
|
||||||
|
let Ok(entry) = entry else { continue };
|
||||||
|
let Ok(name) = String::from_utf8(entry.file_name().into_encoded_bytes()) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if name.ends_with("-busy") {
|
||||||
|
let Ok(engine) = I915Engine::open(&name) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
engines.insert(engine, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Self { engines })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calc_utilization(&mut self) -> u8 {
|
||||||
|
let mut values: Vec<u8> = Vec::new();
|
||||||
|
for (engine, sample) in &mut self.engines {
|
||||||
|
let new_sample = engine.read().unwrap();
|
||||||
|
if let Some(last) = sample {
|
||||||
|
values.push(new_sample.calculate_utilization(last) as u8);
|
||||||
|
}
|
||||||
|
let _ = sample.insert(new_sample);
|
||||||
|
}
|
||||||
|
values
|
||||||
|
.iter()
|
||||||
|
.fold(0u8, |arg0: u8, other: &u8| arg0.max(*other))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::backend::{BackendThread, Registrator};
|
use crate::backend::{BackendThread, Registrator};
|
||||||
use crate::config::Config;
|
|
||||||
use crate::{APP_NAME, LOGGER};
|
use crate::{APP_NAME, LOGGER};
|
||||||
use crossbeam_channel::{Receiver, Sender};
|
use crossbeam_channel::{Receiver, Sender};
|
||||||
|
use jmb_bar::config::IdleConfig;
|
||||||
use jmb_logger_rs::*;
|
use jmb_logger_rs::*;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
@@ -100,6 +100,7 @@ pub enum IdleMessage {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct IdleDaemon {
|
pub struct IdleDaemon {
|
||||||
subscribers: Vec<Sender<IdleState>>,
|
subscribers: Vec<Sender<IdleState>>,
|
||||||
|
config: IdleConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BackendThread<IdleState, Sender<IdleMessage>> for IdleDaemon {
|
impl BackendThread<IdleState, Sender<IdleMessage>> for IdleDaemon {
|
||||||
@@ -121,12 +122,9 @@ impl BackendThread<IdleState, Sender<IdleMessage>> for IdleDaemon {
|
|||||||
&self.subscribers
|
&self.subscribers
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(registration_rx: Receiver<Sender<IdleState>>) {
|
fn init(self, registration_rx: Receiver<Sender<IdleState>>) {
|
||||||
let config = Config::load();
|
|
||||||
let (tx, rx) = crossbeam_channel::unbounded::<IdleMessage>();
|
let (tx, rx) = crossbeam_channel::unbounded::<IdleMessage>();
|
||||||
let instance = Arc::new(Mutex::new(Self {
|
let instance = Arc::new(Mutex::new(self));
|
||||||
subscribers: Vec::new(),
|
|
||||||
}));
|
|
||||||
std::thread::spawn({
|
std::thread::spawn({
|
||||||
let instance = instance.clone();
|
let instance = instance.clone();
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
@@ -146,9 +144,9 @@ impl BackendThread<IdleState, Sender<IdleMessage>> for IdleDaemon {
|
|||||||
let (wl_tx, wl_rx) = watch::channel(false);
|
let (wl_tx, wl_rx) = watch::channel(false);
|
||||||
let (dbus_tx, dbus_rx) = watch::channel(false);
|
let (dbus_tx, dbus_rx) = watch::channel(false);
|
||||||
std::thread::spawn({
|
std::thread::spawn({
|
||||||
let config = config.clone();
|
let instance = instance.clone();
|
||||||
move || {
|
move || {
|
||||||
if let Err(err) = Self::start_idle_notifier(wl_tx, &config) {
|
if let Err(err) = Self::start_idle_notifier(wl_tx, instance) {
|
||||||
error!(&LOGGER, "Idle Daemon unexpectedly quit, reason: {err}");
|
error!(&LOGGER, "Idle Daemon unexpectedly quit, reason: {err}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,7 +200,7 @@ impl BackendThread<IdleState, Sender<IdleMessage>> for IdleDaemon {
|
|||||||
let res = IdleDaemon::start_dbus(dbus_tx, instance_clone).await;
|
let res = IdleDaemon::start_dbus(dbus_tx, instance_clone).await;
|
||||||
match res {
|
match res {
|
||||||
Ok((_, _)) => {
|
Ok((_, _)) => {
|
||||||
Self::run_idle_timer(instance, wl_rx, dbus_rx, &config).await;
|
Self::run_idle_timer(instance, wl_rx, dbus_rx).await;
|
||||||
}
|
}
|
||||||
Err(err) => error!(&LOGGER, "Error running D-Bus Screensaver: {err}"),
|
Err(err) => error!(&LOGGER, "Error running D-Bus Screensaver: {err}"),
|
||||||
}
|
}
|
||||||
@@ -228,7 +226,7 @@ impl IdleStage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn timeout(self, cfg: &Config) -> Duration {
|
fn timeout(self, cfg: &IdleConfig) -> Duration {
|
||||||
match self {
|
match self {
|
||||||
Self::Dim => Duration::from_secs(cfg.dim_timeout_delta()),
|
Self::Dim => Duration::from_secs(cfg.dim_timeout_delta()),
|
||||||
Self::TurnOff => Duration::from_secs(cfg.lock_timeout_delta()),
|
Self::TurnOff => Duration::from_secs(cfg.lock_timeout_delta()),
|
||||||
@@ -238,11 +236,16 @@ impl IdleStage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl IdleDaemon {
|
impl IdleDaemon {
|
||||||
|
pub fn new(config: IdleConfig) -> Self {
|
||||||
|
Self {
|
||||||
|
subscribers: vec![],
|
||||||
|
config,
|
||||||
|
}
|
||||||
|
}
|
||||||
async fn run_idle_timer(
|
async fn run_idle_timer(
|
||||||
instance: Arc<Mutex<Self>>,
|
instance: Arc<Mutex<Self>>,
|
||||||
mut wl_rx: watch::Receiver<bool>,
|
mut wl_rx: watch::Receiver<bool>,
|
||||||
mut dbus_rx: watch::Receiver<bool>,
|
mut dbus_rx: watch::Receiver<bool>,
|
||||||
config: &Config,
|
|
||||||
) {
|
) {
|
||||||
loop {
|
loop {
|
||||||
if let Err(e) = wl_rx.changed().await {
|
if let Err(e) = wl_rx.changed().await {
|
||||||
@@ -254,12 +257,13 @@ impl IdleDaemon {
|
|||||||
if wl_idle && !dbus_state {
|
if wl_idle && !dbus_state {
|
||||||
let mut stage = IdleStage::Dim;
|
let mut stage = IdleStage::Dim;
|
||||||
loop {
|
loop {
|
||||||
|
let timeout = stage.timeout(&instance.lock().unwrap().config);
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = sleep(stage.timeout(config)) => {
|
_ = sleep(timeout) => {
|
||||||
match stage {
|
match stage {
|
||||||
IdleStage::Dim => instance.lock().unwrap().publish(IdleState::Dimmed),
|
IdleStage::Dim => instance.lock().unwrap().publish(IdleState::Dimmed),
|
||||||
IdleStage::TurnOff => _ = Command::new("hyprlock").spawn(),
|
IdleStage::TurnOff => _ = Command::new("bash").arg("-c").arg(format!("'{}'", instance.lock().unwrap().config.lock_command())).spawn(),
|
||||||
IdleStage::Sleep => _ = Command::new("systemctl").arg("sleep").spawn(),
|
IdleStage::Sleep => _ = Command::new("bash").arg("-c").arg(format!("'{}'", instance.lock().unwrap().config.sleep_command())).spawn(),
|
||||||
}
|
}
|
||||||
stage = stage.next();
|
stage = stage.next();
|
||||||
}
|
}
|
||||||
@@ -286,7 +290,10 @@ impl IdleDaemon {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_idle_notifier(tx: watch::Sender<bool>, config: &Config) -> Result<(), Box<dyn Error>> {
|
fn start_idle_notifier(
|
||||||
|
tx: watch::Sender<bool>,
|
||||||
|
instance: Arc<Mutex<IdleDaemon>>,
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
let conn = Connection::connect_to_env()?;
|
let conn = Connection::connect_to_env()?;
|
||||||
let (globals, mut event_queue) = registry_queue_init::<WlState>(&conn)?;
|
let (globals, mut event_queue) = registry_queue_init::<WlState>(&conn)?;
|
||||||
let queue_handle = event_queue.handle();
|
let queue_handle = event_queue.handle();
|
||||||
@@ -298,7 +305,7 @@ impl IdleDaemon {
|
|||||||
let mut state = WlState { idle: None, tx };
|
let mut state = WlState { idle: None, tx };
|
||||||
|
|
||||||
let _notification = notifier.get_idle_notification(
|
let _notification = notifier.get_idle_notification(
|
||||||
(config.idle_timeout() * 1000) as u32,
|
(instance.lock().unwrap().config.idle_timeout() * 1000) as u32,
|
||||||
&seat,
|
&seat,
|
||||||
&queue_handle,
|
&queue_handle,
|
||||||
(),
|
(),
|
||||||
|
|||||||
@@ -37,6 +37,13 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl JsonSocketBackend {
|
impl JsonSocketBackend {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
subscribers: vec![],
|
||||||
|
state: initial_state_tailscale(),
|
||||||
|
socket_address: String::from("/var/run/netbird-http.sock"),
|
||||||
|
}
|
||||||
|
}
|
||||||
async fn _watch(instance: Arc<Mutex<Self>>) -> Result<(), Box<dyn Error + Send + Sync>> {
|
async fn _watch(instance: Arc<Mutex<Self>>) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||||
let socket_address = instance
|
let socket_address = instance
|
||||||
.lock()
|
.lock()
|
||||||
@@ -197,14 +204,8 @@ impl BackendThread<State, ()> for JsonSocketBackend {
|
|||||||
&self.subscribers
|
&self.subscribers
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(registration_rx: Receiver<Sender<State>>) {
|
fn init(self, registration_rx: Receiver<Sender<State>>) {
|
||||||
let state = initial_state_tailscale();
|
let instance = Arc::new(Mutex::new(self));
|
||||||
|
|
||||||
let instance = Arc::new(Mutex::new(Self {
|
|
||||||
subscribers: vec![],
|
|
||||||
state,
|
|
||||||
socket_address: String::from("/var/run/netbird-http.sock"),
|
|
||||||
}));
|
|
||||||
|
|
||||||
let instance_clone = instance.clone();
|
let instance_clone = instance.clone();
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ use nmrs::raw::zvariant::OwnedObjectPath;
|
|||||||
use nmrs::{ActiveConnection, ConnectionError, NetworkEvent, NetworkManager, WifiSecurity};
|
use nmrs::{ActiveConnection, ConnectionError, NetworkEvent, NetworkManager, WifiSecurity};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
use tokio::time::{Instant, Sleep};
|
use tokio::time::{Instant, Sleep};
|
||||||
|
|
||||||
@@ -109,10 +111,10 @@ pub enum NetworkState {
|
|||||||
ConnectSuccess(String),
|
ConnectSuccess(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug)]
|
||||||
pub struct NetworkBackendHandle {
|
pub struct NetworkBackend {
|
||||||
subscribers: Vec<Sender<NetworkState>>,
|
subscribers: Vec<Sender<NetworkState>>,
|
||||||
command_tx: UnboundedSender<NetworkCommand>,
|
command_tx: Option<UnboundedSender<NetworkCommand>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EventDebouncer {
|
struct EventDebouncer {
|
||||||
@@ -121,7 +123,7 @@ struct EventDebouncer {
|
|||||||
sleep: Pin<Box<Sleep>>,
|
sleep: Pin<Box<Sleep>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BackendThread<NetworkState, UnboundedSender<NetworkCommand>> for NetworkBackendHandle {
|
impl BackendThread<NetworkState, UnboundedSender<NetworkCommand>> for NetworkBackend {
|
||||||
fn subscribe(
|
fn subscribe(
|
||||||
registrator: Sender<Sender<NetworkState>>,
|
registrator: Sender<Sender<NetworkState>>,
|
||||||
) -> (Receiver<NetworkState>, UnboundedSender<NetworkCommand>) {
|
) -> (Receiver<NetworkState>, UnboundedSender<NetworkCommand>) {
|
||||||
@@ -140,12 +142,10 @@ impl BackendThread<NetworkState, UnboundedSender<NetworkCommand>> for NetworkBac
|
|||||||
&self.subscribers
|
&self.subscribers
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(registration_rx: Receiver<Sender<NetworkState>>) {
|
fn init(mut self, registration_rx: Receiver<Sender<NetworkState>>) {
|
||||||
let (command_tx, mut command_rx) = tokio::sync::mpsc::unbounded_channel::<NetworkCommand>();
|
let (command_tx, mut command_rx) = tokio::sync::mpsc::unbounded_channel::<NetworkCommand>();
|
||||||
let mut instance = Self {
|
self.command_tx = Some(command_tx);
|
||||||
subscribers: vec![],
|
let instance = Arc::new(Mutex::new(self));
|
||||||
command_tx,
|
|
||||||
};
|
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||||
.enable_all()
|
.enable_all()
|
||||||
@@ -162,8 +162,8 @@ impl BackendThread<NetworkState, UnboundedSender<NetworkCommand>> for NetworkBac
|
|||||||
};
|
};
|
||||||
|
|
||||||
std::thread::spawn({
|
std::thread::spawn({
|
||||||
|
let instance = instance.clone();
|
||||||
let nm = nm.clone();
|
let nm = nm.clone();
|
||||||
let mut instance = instance.clone();
|
|
||||||
move || {
|
move || {
|
||||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||||
.enable_all()
|
.enable_all()
|
||||||
@@ -172,10 +172,12 @@ impl BackendThread<NetworkState, UnboundedSender<NetworkCommand>> for NetworkBac
|
|||||||
|
|
||||||
runtime.block_on(async move {
|
runtime.block_on(async move {
|
||||||
while let Ok(tx) = registration_rx.recv() {
|
while let Ok(tx) = registration_rx.recv() {
|
||||||
if tx.send(NetworkState::Init(instance.command_tx.clone())).is_ok() {
|
let mut instance_guard = instance.lock().await;
|
||||||
instance.subscribers.push(tx);
|
if tx.send(NetworkState::Init(instance_guard.command_tx.clone().unwrap())).is_ok() {
|
||||||
let _ = instance.send_all_networks(&nm).await;
|
instance_guard.subscribers.push(tx);
|
||||||
let _ = instance.send_active_connection(&nm).await;
|
println!("Added subscriber: {:?}", instance_guard.subscribers);
|
||||||
|
let _ = instance_guard.send_all_networks(&nm).await;
|
||||||
|
let _ = instance_guard.send_active_connection(&nm).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -205,15 +207,16 @@ impl BackendThread<NetworkState, UnboundedSender<NetworkCommand>> for NetworkBac
|
|||||||
// Run the command without stopping event processing
|
// Run the command without stopping event processing
|
||||||
// while a potentially slow connection operation runs.
|
// while a potentially slow connection operation runs.
|
||||||
let nm = nm.clone();
|
let nm = nm.clone();
|
||||||
let instance_clone = instance.clone();
|
let instance = instance.clone();
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(
|
||||||
|
async move {
|
||||||
match command {
|
match command {
|
||||||
NetworkCommand::ToggleVpn(profile) => instance_clone.toggle_vpn(profile, &nm).await,
|
NetworkCommand::ToggleVpn(profile) => instance.lock().await.toggle_vpn(profile, &nm).await,
|
||||||
NetworkCommand::Connect(ssid, auth) => {
|
NetworkCommand::Connect(ssid, auth) => {
|
||||||
if let Ok(saved) = nm.list_saved_connections().await {
|
if let Ok(saved) = nm.list_saved_connections().await {
|
||||||
if saved.iter().find(|c| c.id.eq(&ssid)).is_some() {
|
if saved.iter().find(|c| c.id.eq(&ssid)).is_some() {
|
||||||
instance_clone.connect(&nm, ssid, WifiSecurity::Open).await;
|
instance.lock().await.connect(&nm, ssid, WifiSecurity::Open).await;
|
||||||
} else {
|
} else {
|
||||||
match auth {
|
match auth {
|
||||||
Auth::Open => {
|
Auth::Open => {
|
||||||
@@ -221,19 +224,18 @@ impl BackendThread<NetworkState, UnboundedSender<NetworkCommand>> for NetworkBac
|
|||||||
&& let Some(ap) = networks.iter().find(|net| net.ssid.eq(&ssid))
|
&& let Some(ap) = networks.iter().find(|net| net.ssid.eq(&ssid))
|
||||||
{
|
{
|
||||||
if ap.security.is_open() {
|
if ap.security.is_open() {
|
||||||
instance_clone.connect(&nm, ssid, WifiSecurity::Open).await;
|
instance.lock().await.connect(&nm, ssid, WifiSecurity::Open).await;
|
||||||
} else {
|
} else {
|
||||||
instance_clone.publish(NetworkState::Auth(ssid, AuthType::Password));
|
instance.lock().await.publish(NetworkState::Auth(ssid, AuthType::Password));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Auth::Password(password) => instance_clone.connect(&nm, ssid, WifiSecurity::WpaPsk { psk: password }).await,
|
Auth::Password(password) => instance.lock().await.connect(&nm, ssid, WifiSecurity::WpaPsk { psk: password }).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = nm;
|
let _ = nm;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -241,7 +243,7 @@ impl BackendThread<NetworkState, UnboundedSender<NetworkCommand>> for NetworkBac
|
|||||||
match event {
|
match event {
|
||||||
Some(event_res) => {
|
Some(event_res) => {
|
||||||
match event_res {
|
match event_res {
|
||||||
Ok(event) => instance.handle_event(&nm, event, &mut debouncer).await,
|
Ok(event) => instance.lock().await.handle_event(&nm, event, &mut debouncer).await,
|
||||||
Err(err) => error!(&LOGGER, "Error: {err:?}"),
|
Err(err) => error!(&LOGGER, "Error: {err:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,7 +252,7 @@ impl BackendThread<NetworkState, UnboundedSender<NetworkCommand>> for NetworkBac
|
|||||||
}
|
}
|
||||||
_ = &mut debouncer.sleep, if debouncer.deadline.is_some() => {
|
_ = &mut debouncer.sleep, if debouncer.deadline.is_some() => {
|
||||||
debouncer.deadline = None;
|
debouncer.deadline = None;
|
||||||
let _ = instance.send_all_networks(&nm).await;
|
let _ = instance.lock().await.send_all_networks(&nm).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,7 +261,13 @@ impl BackendThread<NetworkState, UnboundedSender<NetworkCommand>> for NetworkBac
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NetworkBackendHandle {
|
impl NetworkBackend {
|
||||||
|
pub fn new() -> NetworkBackend {
|
||||||
|
Self {
|
||||||
|
subscribers: vec![],
|
||||||
|
command_tx: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
async fn connect(&self, nm: &NetworkManager, ssid: String, creds: WifiSecurity) {
|
async fn connect(&self, nm: &NetworkManager, ssid: String, creds: WifiSecurity) {
|
||||||
let previous_ssid = nm.current_ssid().await;
|
let previous_ssid = nm.current_ssid().await;
|
||||||
let res = nm.connect(&ssid, None, creds).await;
|
let res = nm.connect(&ssid, None, creds).await;
|
||||||
@@ -431,44 +439,58 @@ impl NetworkBackendHandle {
|
|||||||
async fn send_active_connection(&self, nm: &NetworkManager) {
|
async fn send_active_connection(&self, nm: &NetworkManager) {
|
||||||
trace!(&LOGGER, "Sending Active Connection");
|
trace!(&LOGGER, "Sending Active Connection");
|
||||||
if let Ok(connections) = nm.list_active_connections().await {
|
if let Ok(connections) = nm.list_active_connections().await {
|
||||||
let connection = if let Some(primary) = Self::primary_connection(nm).await {
|
let Some(connection) = (if let Some(primary) = Self::primary_connection(nm).await {
|
||||||
connections.iter().find(|c| match c {
|
connections
|
||||||
ActiveConnection::Wired(c) => c.uuid.eq(&primary),
|
.iter()
|
||||||
ActiveConnection::Wifi(c) => c.uuid.eq(&primary),
|
.find(|c| match c {
|
||||||
_ => false,
|
ActiveConnection::Wired(c) => c.uuid.eq(&primary),
|
||||||
})
|
ActiveConnection::Wifi(c) => c.uuid.eq(&primary),
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
} else {
|
} else {
|
||||||
connections.first()
|
let mut iter = connections.into_iter();
|
||||||
};
|
loop {
|
||||||
match connection {
|
let Some(conn) = iter.next() else {
|
||||||
Some(conn) => {
|
break None;
|
||||||
|
};
|
||||||
match conn {
|
match conn {
|
||||||
ActiveConnection::Wired(c) => {
|
ActiveConnection::Other(_) | ActiveConnection::Vpn(_) => continue,
|
||||||
let info = NetworkInfo {
|
other => {
|
||||||
name: c.id.clone(),
|
break Some(other);
|
||||||
network_type: NetworkType::Wired,
|
|
||||||
signal: 0,
|
|
||||||
secured: None,
|
|
||||||
known: true,
|
|
||||||
};
|
|
||||||
self.publish(NetworkState::ChangeNetwork(Some(info)));
|
|
||||||
}
|
}
|
||||||
ActiveConnection::Wifi(c) => {
|
|
||||||
let info = NetworkInfo {
|
|
||||||
name: c.ssid.clone(),
|
|
||||||
network_type: NetworkType::Wifi,
|
|
||||||
signal: c.strength.unwrap_or(0),
|
|
||||||
secured: None, // Not relevant here
|
|
||||||
known: true,
|
|
||||||
};
|
|
||||||
self.publish(NetworkState::ChangeNetwork(Some(info)));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
}) else {
|
||||||
self.publish(NetworkState::ChangeNetwork(None));
|
debug!(&LOGGER, "Disconnected from all Networks");
|
||||||
|
self.publish(NetworkState::ChangeNetwork(None));
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
debug!(&LOGGER, "Found active connection: {connection:#?}");
|
||||||
|
match connection {
|
||||||
|
ActiveConnection::Wired(c) => {
|
||||||
|
let info = NetworkInfo {
|
||||||
|
name: c.id.clone(),
|
||||||
|
network_type: NetworkType::Wired,
|
||||||
|
signal: 0,
|
||||||
|
secured: None,
|
||||||
|
known: true,
|
||||||
|
};
|
||||||
|
debug!(&LOGGER, "Connected to wired network");
|
||||||
|
self.publish(NetworkState::ChangeNetwork(Some(info)));
|
||||||
}
|
}
|
||||||
|
ActiveConnection::Wifi(c) => {
|
||||||
|
let info = NetworkInfo {
|
||||||
|
name: c.ssid.clone(),
|
||||||
|
network_type: NetworkType::Wifi,
|
||||||
|
signal: c.strength.unwrap_or(0),
|
||||||
|
secured: None, // Not relevant here
|
||||||
|
known: true,
|
||||||
|
};
|
||||||
|
debug!(&LOGGER, "Connected to wireless network");
|
||||||
|
self.publish(NetworkState::ChangeNetwork(Some(info)));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::LOGGER;
|
use crate::LOGGER;
|
||||||
|
use crate::backend::gpu_utilization::I915UtilizationMonitor;
|
||||||
use crate::backend::{BackendThread, Registrator};
|
use crate::backend::{BackendThread, Registrator};
|
||||||
use all_smi::AllSmi;
|
|
||||||
use battery::State;
|
use battery::State;
|
||||||
use battery::units::Time;
|
use battery::units::Time;
|
||||||
use battery::units::power::watt;
|
use battery::units::power::watt;
|
||||||
@@ -8,6 +8,7 @@ use battery::units::ratio::percent;
|
|||||||
use battery::units::time::millisecond;
|
use battery::units::time::millisecond;
|
||||||
use crossbeam_channel::{Receiver, Sender};
|
use crossbeam_channel::{Receiver, Sender};
|
||||||
use jmb_logger_rs::*;
|
use jmb_logger_rs::*;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use sysinfo::{Disks, System};
|
use sysinfo::{Disks, System};
|
||||||
@@ -19,7 +20,7 @@ pub struct Stats {
|
|||||||
pub cpu: Option<u8>,
|
pub cpu: Option<u8>,
|
||||||
pub ram: Option<u8>,
|
pub ram: Option<u8>,
|
||||||
pub disks: Vec<(String, u8)>,
|
pub disks: Vec<(String, u8)>,
|
||||||
pub gpus: Vec<(String, u8)>,
|
pub gpu: Option<u8>,
|
||||||
pub battery_charge: u8,
|
pub battery_charge: u8,
|
||||||
pub battery_state: State,
|
pub battery_state: State,
|
||||||
pub battery_draw: f32,
|
pub battery_draw: f32,
|
||||||
@@ -28,8 +29,14 @@ pub struct Stats {
|
|||||||
|
|
||||||
pub struct StatsBackend {
|
pub struct StatsBackend {
|
||||||
subscribers: Vec<Sender<Stats>>,
|
subscribers: Vec<Sender<Stats>>,
|
||||||
system: Option<System>,
|
}
|
||||||
disks: Option<Disks>,
|
|
||||||
|
impl StatsBackend {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
subscribers: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BackendThread<Stats, ()> for StatsBackend {
|
impl BackendThread<Stats, ()> for StatsBackend {
|
||||||
@@ -43,24 +50,26 @@ impl BackendThread<Stats, ()> for StatsBackend {
|
|||||||
&self.subscribers
|
&self.subscribers
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(registration_rx: Receiver<Sender<Stats>>) {
|
fn init(self, registration_rx: Receiver<Sender<Stats>>) {
|
||||||
thread::spawn(move || {
|
let instance = Arc::new(Mutex::new(self));
|
||||||
let mut instance = Self {
|
thread::spawn({
|
||||||
subscribers: Vec::new(),
|
let instance = instance.clone();
|
||||||
system: System::new().ok(),
|
move || {
|
||||||
disks: Disks::new().ok(),
|
while let Ok(tx) = registration_rx.recv() {
|
||||||
};
|
|
||||||
let manager = battery::Manager::new();
|
|
||||||
let smi = AllSmi::new();
|
|
||||||
loop {
|
|
||||||
while let Ok(tx) = registration_rx.try_recv() {
|
|
||||||
trace!(&LOGGER, "Added Stats Subscriber: {tx:?}");
|
trace!(&LOGGER, "Added Stats Subscriber: {tx:?}");
|
||||||
instance.subscribers.push(tx);
|
instance.lock().unwrap().subscribers.push(tx);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
thread::spawn(move || {
|
||||||
|
let manager = battery::Manager::new();
|
||||||
|
let mut system = System::new().ok();
|
||||||
|
let mut disks = Disks::new().ok();
|
||||||
|
let mut gpu_monitor = I915UtilizationMonitor::new();
|
||||||
|
loop {
|
||||||
let mut cpu = None;
|
let mut cpu = None;
|
||||||
let mut ram = None;
|
let mut ram = None;
|
||||||
if let Some(system) = instance.system.as_mut() {
|
if let Some(system) = system.as_mut() {
|
||||||
system.refresh_cpu_usage();
|
system.refresh_cpu_usage();
|
||||||
system.refresh_memory();
|
system.refresh_memory();
|
||||||
|
|
||||||
@@ -69,7 +78,7 @@ impl BackendThread<Stats, ()> for StatsBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut disk_stats = Vec::new();
|
let mut disk_stats = Vec::new();
|
||||||
if let Some(disks) = instance.disks.as_mut() {
|
if let Some(disks) = disks.as_mut() {
|
||||||
disks.refresh(true);
|
disks.refresh(true);
|
||||||
disk_stats = disks
|
disk_stats = disks
|
||||||
.iter()
|
.iter()
|
||||||
@@ -82,14 +91,7 @@ impl BackendThread<Stats, ()> for StatsBackend {
|
|||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut gpus: Vec<(String, u8)> = Vec::new();
|
let gpu = gpu_monitor.as_mut().map(|v| v.calc_utilization());
|
||||||
if let Ok(smi) = &smi {
|
|
||||||
gpus = smi
|
|
||||||
.get_gpu_info()
|
|
||||||
.iter()
|
|
||||||
.map(|info| (info.name.clone(), info.utilization as u8))
|
|
||||||
.collect();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut battery_charge: u8 = 255; // Use > 100 as invalid value
|
let mut battery_charge: u8 = 255; // Use > 100 as invalid value
|
||||||
let mut battery_state = State::Unknown;
|
let mut battery_state = State::Unknown;
|
||||||
@@ -128,13 +130,13 @@ impl BackendThread<Stats, ()> for StatsBackend {
|
|||||||
cpu,
|
cpu,
|
||||||
ram,
|
ram,
|
||||||
disks: disk_stats,
|
disks: disk_stats,
|
||||||
gpus,
|
gpu,
|
||||||
battery_charge,
|
battery_charge,
|
||||||
battery_state,
|
battery_state,
|
||||||
battery_draw,
|
battery_draw,
|
||||||
battery_time,
|
battery_time,
|
||||||
};
|
};
|
||||||
instance.publish(msg);
|
instance.lock().unwrap().publish(msg);
|
||||||
|
|
||||||
thread::sleep(Duration::from_millis(POLL_INTERVAL));
|
thread::sleep(Duration::from_millis(POLL_INTERVAL));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,11 +28,9 @@ impl SimpleBackendThread<WindowManagerEvent> for HyprlandComms {
|
|||||||
&self.subscribers
|
&self.subscribers
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(registration_rx: Receiver<Sender<WindowManagerEvent>>) {
|
fn init(self, registration_rx: Receiver<Sender<WindowManagerEvent>>) {
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let instance = Arc::new(Mutex::new(Self {
|
let instance = Arc::new(Mutex::new(self));
|
||||||
subscribers: vec![],
|
|
||||||
}));
|
|
||||||
let mut listener = EventListener::new();
|
let mut listener = EventListener::new();
|
||||||
|
|
||||||
let inst_clone = instance.clone();
|
let inst_clone = instance.clone();
|
||||||
@@ -153,6 +151,12 @@ impl SimpleBackendThread<WindowManagerEvent> for HyprlandComms {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl HyprlandComms {
|
impl HyprlandComms {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
subscribers: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn monitor_id_from_workspace_id(id: WorkspaceId) -> Option<MonitorId> {
|
fn monitor_id_from_workspace_id(id: WorkspaceId) -> Option<MonitorId> {
|
||||||
if let Ok(wss) = Workspaces::get()
|
if let Ok(wss) = Workspaces::get()
|
||||||
&& let Some(ws) = wss.to_vec().iter().find(|ws| ws.id().unwrap() == id)
|
&& let Some(ws) = wss.to_vec().iter().find(|ws| ws.id().unwrap() == id)
|
||||||
@@ -162,7 +166,7 @@ impl HyprlandComms {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn current_monitor() -> MonitorId {
|
pub fn _current_monitor() -> MonitorId {
|
||||||
if let Ok(monitor) = Monitor::get_active() {
|
if let Ok(monitor) = Monitor::get_active() {
|
||||||
return monitor.id;
|
return monitor.id;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,11 @@
|
|||||||
|
use jmb_bar::{ControlCommand, SOCKET_PATH};
|
||||||
use jmb_logger_rs::*;
|
use jmb_logger_rs::*;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::os::unix::net::UnixStream;
|
use std::os::unix::net::UnixStream;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
const CTL_LOGGER: Logger = Logger::create_with_custom_format(Level::DEBUG, String::new());
|
const CTL_LOGGER: Logger = Logger::create_with_custom_format(Level::DEBUG, String::new());
|
||||||
pub const SOCKET_PATH: &str = "/run/user/1000/jmb-bar.sock";
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum ControlCommand {
|
|
||||||
OpenRunMenu,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ControlCommand {
|
|
||||||
pub fn from_str(s: &str) -> Option<Self> {
|
|
||||||
match s {
|
|
||||||
"menu" => Some(ControlCommand::OpenRunMenu),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_string(self) -> String {
|
|
||||||
match self {
|
|
||||||
ControlCommand::OpenRunMenu => String::from("menu"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
info!(&CTL_LOGGER, "Hello, this is mission control");
|
info!(&CTL_LOGGER, "Hello, this is mission control");
|
||||||
@@ -43,7 +24,7 @@ fn main() {
|
|||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
|
|
||||||
match args.get(1) {
|
match args.get(1) {
|
||||||
Some(str) if let Some(cmd) = ControlCommand::from_str(str) => stream
|
Some(str) if let Ok(cmd) = ControlCommand::from_str(str) => stream
|
||||||
.write(cmd.to_string().as_bytes())
|
.write(cmd.to_string().as_bytes())
|
||||||
.expect("Error sending message"),
|
.expect("Error sending message"),
|
||||||
_ => 0usize,
|
_ => 0usize,
|
||||||
@@ -1,34 +1,44 @@
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use std::default::Default;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, Clone, Default)]
|
||||||
|
pub struct Config {
|
||||||
|
pub idle: IdleConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn load(file_path: &str) -> Option<Config> {
|
||||||
|
fs::read_to_string(file_path)
|
||||||
|
.ok()
|
||||||
|
.and_then(|s| toml::from_str::<Config>(&s).ok())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Clone)]
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
pub(crate) struct Config {
|
pub struct IdleConfig {
|
||||||
idle_timeout: u64,
|
idle_timeout: u64,
|
||||||
dim_timeout: u64,
|
dim_timeout: u64,
|
||||||
lock_timeout: u64,
|
lock_timeout: u64,
|
||||||
sleep_timeout: u64,
|
sleep_timeout: u64,
|
||||||
|
lock_command: String,
|
||||||
|
sleep_command: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for IdleConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
idle_timeout: 20,
|
idle_timeout: 20,
|
||||||
dim_timeout: 60,
|
dim_timeout: 60,
|
||||||
lock_timeout: 300,
|
lock_timeout: 300,
|
||||||
sleep_timeout: 320,
|
sleep_timeout: 320,
|
||||||
|
lock_command: String::default(),
|
||||||
|
sleep_command: String::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl IdleConfig {
|
||||||
pub fn load() -> Config {
|
|
||||||
fs::read_to_string("jmb-bar.conf")
|
|
||||||
.ok()
|
|
||||||
.map(|s| toml::from_str::<Config>(&s).ok())
|
|
||||||
.flatten()
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn idle_timeout(&self) -> u64 {
|
pub fn idle_timeout(&self) -> u64 {
|
||||||
self.idle_timeout
|
self.idle_timeout
|
||||||
}
|
}
|
||||||
@@ -56,4 +66,12 @@ impl Config {
|
|||||||
pub fn sleep_timeout_delta(&self) -> u64 {
|
pub fn sleep_timeout_delta(&self) -> u64 {
|
||||||
self.sleep_timeout - self.lock_timeout_delta()
|
self.sleep_timeout - self.lock_timeout_delta()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn lock_command(&self) -> &str {
|
||||||
|
&self.lock_command
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sleep_command(&self) -> &str {
|
||||||
|
&self.sleep_command
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
34
src/lib.rs
Normal file
34
src/lib.rs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
pub mod config;
|
||||||
|
pub mod theme;
|
||||||
|
|
||||||
|
pub const SOCKET_PATH: &str = "/run/user/1000/jmb-bar.sock";
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum ControlCommand {
|
||||||
|
OpenRunMenu,
|
||||||
|
OpenNetMenu,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ControlCommand {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ControlCommand::OpenRunMenu => f.write_str("menu"),
|
||||||
|
ControlCommand::OpenNetMenu => f.write_str("net"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for ControlCommand {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"menu" => Ok(ControlCommand::OpenRunMenu),
|
||||||
|
"net" => Ok(ControlCommand::OpenNetMenu),
|
||||||
|
other => Err(format!("Unknown command: {other}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/main.rs
16
src/main.rs
@@ -1,17 +1,15 @@
|
|||||||
use crate::backend::Backend;
|
use crate::backend::Backend;
|
||||||
mod backend;
|
mod backend;
|
||||||
mod config;
|
|
||||||
mod css;
|
mod css;
|
||||||
mod ctl;
|
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
use glib::clone;
|
use glib::clone;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Application, CssProvider, gdk, gio};
|
use gtk::{Application, CssProvider, gdk, gio};
|
||||||
|
use jmb_bar::config::Config;
|
||||||
use jmb_logger_rs::*;
|
use jmb_logger_rs::*;
|
||||||
use notify::RecommendedWatcher;
|
use notify::RecommendedWatcher;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::process::Command;
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
pub const LOGGER: Logger = Logger::create(Level::DEBUG);
|
pub const LOGGER: Logger = Logger::create(Level::DEBUG);
|
||||||
@@ -20,16 +18,18 @@ pub const APP_NAME: &str = "net.jmb19905.jmb-bar";
|
|||||||
fn main() {
|
fn main() {
|
||||||
info!(&LOGGER, "Starting jmb-bar");
|
info!(&LOGGER, "Starting jmb-bar");
|
||||||
|
|
||||||
gio::resources_register_include!("templates.gresource").expect("Failed to register resources");
|
let config = Config::load("jmb-bar.conf");
|
||||||
|
let config = config.unwrap_or_else(|| {
|
||||||
|
warn!(&LOGGER, "Couldn't load config file, using default");
|
||||||
|
Config::default()
|
||||||
|
});
|
||||||
|
let backend = Backend::start(&config);
|
||||||
|
|
||||||
|
gio::resources_register_include!("templates.gresource").expect("Failed to register resources");
|
||||||
let app = Application::builder().application_id(APP_NAME).build();
|
let app = Application::builder().application_id(APP_NAME).build();
|
||||||
|
|
||||||
let watcher_slot: Rc<RefCell<Option<RecommendedWatcher>>> = Rc::new(RefCell::new(None));
|
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();
|
|
||||||
|
|
||||||
app.connect_startup(move |_| {
|
app.connect_startup(move |_| {
|
||||||
debug!(&LOGGER, "Connect startup");
|
debug!(&LOGGER, "Connect startup");
|
||||||
let provider = CssProvider::new();
|
let provider = CssProvider::new();
|
||||||
|
|||||||
1
src/theme.rs
Normal file
1
src/theme.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
include!(concat!(env!("OUT_DIR"), "/theme.rs"));
|
||||||
31
src/ui.rs
31
src/ui.rs
@@ -2,7 +2,6 @@ use crate::LOGGER;
|
|||||||
use crate::backend::audio::MediaMessage;
|
use crate::backend::audio::MediaMessage;
|
||||||
use crate::backend::window_manager_hypr::{HyprlandComms, WindowManagerEvent};
|
use crate::backend::window_manager_hypr::{HyprlandComms, WindowManagerEvent};
|
||||||
use crate::backend::{Backend, BackendChannels, ChannelType};
|
use crate::backend::{Backend, BackendChannels, ChannelType};
|
||||||
use crate::ctl::ControlCommand;
|
|
||||||
use crate::ui::action_centre::ActionCentre;
|
use crate::ui::action_centre::ActionCentre;
|
||||||
use crate::ui::run_menu::RunMenu;
|
use crate::ui::run_menu::RunMenu;
|
||||||
use glib::ControlFlow;
|
use glib::ControlFlow;
|
||||||
@@ -11,6 +10,7 @@ use gtk::gdk::Display;
|
|||||||
use gtk::prelude::{BoxExt, ButtonExt, DisplayExt, GtkWindowExt, ListModelExt, WidgetExt};
|
use gtk::prelude::{BoxExt, ButtonExt, DisplayExt, GtkWindowExt, ListModelExt, WidgetExt};
|
||||||
use gtk::{Align, Application, ApplicationWindow, Button, Orientation, gdk};
|
use gtk::{Align, Application, ApplicationWindow, Button, Orientation, gdk};
|
||||||
use gtk4_layer_shell::{Edge, KeyboardMode, Layer, LayerShell};
|
use gtk4_layer_shell::{Edge, KeyboardMode, Layer, LayerShell};
|
||||||
|
use jmb_bar::ControlCommand;
|
||||||
use jmb_logger_rs::*;
|
use jmb_logger_rs::*;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
@@ -29,11 +29,11 @@ pub fn create_bars(app: &Application, backend: Backend) {
|
|||||||
let display = Display::default().expect("Couldn't find GDK Display");
|
let display = Display::default().expect("Couldn't find GDK Display");
|
||||||
let app_clone = app.clone();
|
let app_clone = app.clone();
|
||||||
let bars: Rc<Mutex<HashMap<u32, ApplicationWindow>>> = Rc::new(Mutex::new(HashMap::new()));
|
let bars: Rc<Mutex<HashMap<u32, ApplicationWindow>>> = Rc::new(Mutex::new(HashMap::new()));
|
||||||
let ac_channels = backend.subscribe_all();
|
let ac_channels = backend.subscribe(&[ChannelType::Net]);
|
||||||
let action_centre = ActionCentre::create_action_centre(app.clone(), ac_channels);
|
let action_centre = ActionCentre::create_action_centre(app.clone(), ac_channels);
|
||||||
for monitor in 0..display.monitors().n_items() {
|
for monitor in 0..display.monitors().n_items() {
|
||||||
debug!(&LOGGER, "Opening bar {monitor}");
|
debug!(&LOGGER, "Opening bar {monitor}");
|
||||||
let channels = backend.subscribe_all();
|
let channels = backend.subscribe_except(&[ChannelType::Control]);
|
||||||
bars.lock().expect("Couldn't get active bars").insert(
|
bars.lock().expect("Couldn't get active bars").insert(
|
||||||
monitor,
|
monitor,
|
||||||
create_and_show_bar(
|
create_and_show_bar(
|
||||||
@@ -46,9 +46,9 @@ pub fn create_bars(app: &Application, backend: Backend) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
let backend_clone = backend.clone();
|
let backend_clone = backend.clone();
|
||||||
display
|
display.monitors().connect_items_changed({
|
||||||
.monitors()
|
let action_centre = action_centre.clone();
|
||||||
.connect_items_changed(move |_list, pos, removed, added| {
|
move |_list, pos, removed, added| {
|
||||||
if removed == 1 {
|
if removed == 1 {
|
||||||
debug!(&LOGGER, "Detected Monitor disconnect {pos}");
|
debug!(&LOGGER, "Detected Monitor disconnect {pos}");
|
||||||
if let Some(bar) = bars.lock().expect("Couldn't get active bars").remove(&pos) {
|
if let Some(bar) = bars.lock().expect("Couldn't get active bars").remove(&pos) {
|
||||||
@@ -58,7 +58,7 @@ pub fn create_bars(app: &Application, backend: Backend) {
|
|||||||
debug!(&LOGGER, "Detected new monitor: {pos}");
|
debug!(&LOGGER, "Detected new monitor: {pos}");
|
||||||
let app_clone = app_clone.clone();
|
let app_clone = app_clone.clone();
|
||||||
let action_centre = action_centre.clone();
|
let action_centre = action_centre.clone();
|
||||||
let channels = backend_clone.subscribe_all();
|
let channels = backend_clone.subscribe_except(&[ChannelType::Control]);
|
||||||
let bars = bars.clone();
|
let bars = bars.clone();
|
||||||
glib::idle_add_local_once(move || {
|
glib::idle_add_local_once(move || {
|
||||||
let display = Display::default().expect("Couldn't find GDK Display");
|
let display = Display::default().expect("Couldn't find GDK Display");
|
||||||
@@ -68,21 +68,24 @@ pub fn create_bars(app: &Application, backend: Backend) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let mut ctl_channel = backend.subscribe(&[ChannelType::Control]);
|
let mut ctl_channel = backend.subscribe(&[ChannelType::Control]);
|
||||||
let ctl_rx = ctl_channel.take_ctl_rx().unwrap();
|
let ctl_rx = ctl_channel.take_ctl_rx().unwrap();
|
||||||
let app_clone = app.clone();
|
let app_clone = app.clone();
|
||||||
let mut menu = RunMenu::create(app_clone);
|
let mut menu = RunMenu::create(app_clone);
|
||||||
glib::timeout_add_local(Duration::from_millis(100), move || {
|
glib::timeout_add_local(Duration::from_millis(100), {
|
||||||
while let Ok(cmd) = ctl_rx.try_recv() {
|
let action_centre = action_centre.clone();
|
||||||
match cmd {
|
move || {
|
||||||
ControlCommand::OpenRunMenu => {
|
while let Ok(cmd) = ctl_rx.try_recv() {
|
||||||
menu.show();
|
match cmd {
|
||||||
|
ControlCommand::OpenRunMenu => menu.toggle(),
|
||||||
|
ControlCommand::OpenNetMenu => action_centre.toggle_active_monitor(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ControlFlow::Continue
|
||||||
}
|
}
|
||||||
ControlFlow::Continue
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use crate::backend::BackendChannels;
|
|||||||
use crate::backend::network::{
|
use crate::backend::network::{
|
||||||
Auth, AuthType, ConnectError, NetworkCommand, NetworkInfo, NetworkState,
|
Auth, AuthType, ConnectError, NetworkCommand, NetworkInfo, NetworkState,
|
||||||
};
|
};
|
||||||
|
use crate::backend::window_manager_hypr::HyprlandComms;
|
||||||
use crate::ui::widgets::{LabelEntry, WifiWidget};
|
use crate::ui::widgets::{LabelEntry, WifiWidget};
|
||||||
use glib::prelude::Cast;
|
use glib::prelude::Cast;
|
||||||
use glib::{ControlFlow, clone};
|
use glib::{ControlFlow, clone};
|
||||||
@@ -58,7 +59,7 @@ impl ActionCentre {
|
|||||||
window.set_margin(Edge::Top, 20);
|
window.set_margin(Edge::Top, 20);
|
||||||
window.set_margin(Edge::Right, 20);
|
window.set_margin(Edge::Right, 20);
|
||||||
window.set_margin(Edge::Bottom, 20);
|
window.set_margin(Edge::Bottom, 20);
|
||||||
window.set_keyboard_mode(KeyboardMode::OnDemand);
|
window.set_keyboard_mode(KeyboardMode::Exclusive);
|
||||||
|
|
||||||
let instance = Self {
|
let instance = Self {
|
||||||
window: window.clone(),
|
window: window.clone(),
|
||||||
@@ -116,7 +117,7 @@ impl ActionCentre {
|
|||||||
let instance = instance.clone();
|
let instance = instance.clone();
|
||||||
move |_, key, _, _| {
|
move |_, key, _, _| {
|
||||||
if key == gdk::Key::Escape {
|
if key == gdk::Key::Escape {
|
||||||
instance.hide_action_centre();
|
instance.hide();
|
||||||
glib::Propagation::Stop
|
glib::Propagation::Stop
|
||||||
} else {
|
} else {
|
||||||
glib::Propagation::Proceed
|
glib::Propagation::Proceed
|
||||||
@@ -318,7 +319,25 @@ impl ActionCentre {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_action_centre(&self, monitor_idx: u32, display: &Display) {
|
pub fn toggle_active_monitor(&self) {
|
||||||
|
if self.window.is_visible() {
|
||||||
|
self.hide();
|
||||||
|
} else {
|
||||||
|
self.show_active_monitor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show_active_monitor(&self) {
|
||||||
|
let Some(display) = Display::default() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(monitor) = HyprlandComms::cursor_monitor() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.show(monitor as u32, &display);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show(&self, monitor_idx: u32, display: &Display) {
|
||||||
let window = &self.window;
|
let window = &self.window;
|
||||||
let monitors = display.monitors();
|
let monitors = display.monitors();
|
||||||
let Some(monitor) = monitors.item(monitor_idx) else {
|
let Some(monitor) = monitors.item(monitor_idx) else {
|
||||||
@@ -334,7 +353,7 @@ impl ActionCentre {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hide_action_centre(&self) {
|
pub fn hide(&self) {
|
||||||
let clone = self.clone();
|
let clone = self.clone();
|
||||||
self.animate_margin(
|
self.animate_margin(
|
||||||
20,
|
20,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use gtk::prelude::{
|
|||||||
};
|
};
|
||||||
use gtk::{Align, Application, ApplicationWindow, Orientation, PolicyType, gdk};
|
use gtk::{Align, Application, ApplicationWindow, Orientation, PolicyType, gdk};
|
||||||
use gtk4_layer_shell::{Edge, KeyboardMode, Layer, LayerShell};
|
use gtk4_layer_shell::{Edge, KeyboardMode, Layer, LayerShell};
|
||||||
|
use jmb_bar::theme;
|
||||||
use jmb_logger_rs::*;
|
use jmb_logger_rs::*;
|
||||||
use nucleo_matcher::pattern::{CaseMatching, Normalization, Pattern};
|
use nucleo_matcher::pattern::{CaseMatching, Normalization, Pattern};
|
||||||
use nucleo_matcher::{Config, Matcher};
|
use nucleo_matcher::{Config, Matcher};
|
||||||
@@ -66,17 +67,14 @@ impl RunMenu {
|
|||||||
window.set_child(Some(&v_box));
|
window.set_child(Some(&v_box));
|
||||||
|
|
||||||
let display = Display::default().expect("Couldn't get display");
|
let display = Display::default().expect("Couldn't get display");
|
||||||
let monitor = display
|
let monitor = HyprlandComms::cursor_monitor()
|
||||||
.monitors()
|
.and_then(|id| display.monitors().item(id as u32))
|
||||||
.item(HyprlandComms::current_monitor() as u32)
|
|
||||||
.expect("Couldn't get monitor")
|
.expect("Couldn't get monitor")
|
||||||
.downcast::<Monitor>()
|
.downcast::<Monitor>()
|
||||||
.ok();
|
.ok();
|
||||||
window.set_monitor(monitor.as_ref());
|
window.set_monitor(monitor.as_ref());
|
||||||
if let Some(monitor) = monitor {
|
if let Some(monitor) = monitor {
|
||||||
let geometry = monitor.geometry();
|
let geometry = monitor.geometry();
|
||||||
let (_, natural_height, _, _) =
|
|
||||||
window.child().unwrap().measure(Orientation::Vertical, -1);
|
|
||||||
let top_margin = (geometry.height()) / 2;
|
let top_margin = (geometry.height()) / 2;
|
||||||
window.set_margin(Edge::Top, top_margin - 100);
|
window.set_margin(Edge::Top, top_margin - 100);
|
||||||
} else {
|
} else {
|
||||||
@@ -94,22 +92,42 @@ impl RunMenu {
|
|||||||
entry.connect_changed(clone!(
|
entry.connect_changed(clone!(
|
||||||
#[strong]
|
#[strong]
|
||||||
content_box,
|
content_box,
|
||||||
move |e| {
|
move |entry| {
|
||||||
while let Some(child) = content_box.first_child() {
|
while let Some(child) = content_box.first_child() {
|
||||||
content_box.remove(&child);
|
content_box.remove(&child);
|
||||||
}
|
}
|
||||||
if e.text_length() > 0 {
|
if entry.text_length() > 0 {
|
||||||
let text = e.text();
|
let text = entry.text();
|
||||||
if text.starts_with("=") {
|
if let Some(eval) = text.strip_prefix("=") {
|
||||||
if let Ok(eval) = meval::eval_str(&text[1..]) {
|
let new_eval = eval.replace("**", "^");
|
||||||
let label = gtk::Label::new(Some(&format!("{eval}")));
|
if new_eval != eval {
|
||||||
|
let cursor = entry.position() as usize;
|
||||||
|
let removed_before_cursor = text[..cursor].matches("**").count();
|
||||||
|
let new_cursor = cursor - removed_before_cursor;
|
||||||
|
|
||||||
|
glib::idle_add_local_once(clone!(
|
||||||
|
#[weak]
|
||||||
|
entry,
|
||||||
|
move || {
|
||||||
|
entry.set_text(&format!("={new_eval}"));
|
||||||
|
entry.set_position(new_cursor as i32);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if let Ok(result) = meval::eval_str(eval) {
|
||||||
|
let label = gtk::Label::new(Some(&format!("{}", result)));
|
||||||
label.add_css_class("exec-button");
|
label.add_css_class("exec-button");
|
||||||
label.set_halign(Align::Start);
|
label.set_halign(Align::Start);
|
||||||
label.set_hexpand(true);
|
label.set_hexpand(true);
|
||||||
content_box.append(&label);
|
content_box.append(&label);
|
||||||
|
instance_clone.window.set_default_size(500, -1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
instance_clone.list_matching(e, &content_box, &mut matcher_rc.borrow_mut());
|
instance_clone.list_matching(
|
||||||
|
entry,
|
||||||
|
&content_box,
|
||||||
|
&mut matcher_rc.borrow_mut(),
|
||||||
|
);
|
||||||
instance_clone.window.set_default_size(500, -1);
|
instance_clone.window.set_default_size(500, -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,10 +150,17 @@ impl RunMenu {
|
|||||||
#[strong]
|
#[strong]
|
||||||
content_box,
|
content_box,
|
||||||
move |_| {
|
move |_| {
|
||||||
if let Some(child) = content_box.first_child()
|
if let Some(child) = content_box.first_child() {
|
||||||
&& let Ok(button) = child.downcast::<gtk::Button>()
|
if let Ok(button) = child.clone().downcast::<gtk::Button>() {
|
||||||
{
|
button.activate();
|
||||||
button.activate();
|
} else if let Ok(label) = child.downcast::<gtk::Label>() {
|
||||||
|
display.clipboard().set_text(&label.text());
|
||||||
|
label.set_markup(&format!(
|
||||||
|
"{}<span foreground=\"{}\"><i> - Copied</i></span>",
|
||||||
|
label.text(),
|
||||||
|
theme::LIGHTGRAY
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
@@ -164,8 +189,16 @@ impl RunMenu {
|
|||||||
self.window.present();
|
self.window.present();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_entry(&self, content_box: >k::Box, entry: &String) -> gtk::Button {
|
pub fn toggle(&mut self) {
|
||||||
let entry_clone = entry.clone();
|
if self.window.is_visible() {
|
||||||
|
self.hide();
|
||||||
|
} else {
|
||||||
|
self.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_entry(&self, content_box: >k::Box, entry: &str) -> gtk::Button {
|
||||||
|
let entry_clone = entry.to_owned();
|
||||||
let button = gtk::Button::new();
|
let button = gtk::Button::new();
|
||||||
button.set_halign(Align::Fill);
|
button.set_halign(Align::Fill);
|
||||||
button.set_hexpand(true);
|
button.set_hexpand(true);
|
||||||
@@ -198,14 +231,14 @@ impl RunMenu {
|
|||||||
content_box: >k::Box,
|
content_box: >k::Box,
|
||||||
matcher: &mut Matcher,
|
matcher: &mut Matcher,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let keys: Vec<String> = self.entries.borrow().keys().map(|i| i.clone()).collect();
|
let keys: Vec<String> = self.entries.borrow().keys().cloned().collect();
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
let mut added = false;
|
let mut added = false;
|
||||||
for (entry, _) in Pattern::parse(&entry.text(), CaseMatching::Ignore, Normalization::Smart)
|
for (entry, _) in Pattern::parse(&entry.text(), CaseMatching::Ignore, Normalization::Smart)
|
||||||
.match_list(keys, matcher)
|
.match_list(keys, matcher)
|
||||||
.iter()
|
.iter()
|
||||||
{
|
{
|
||||||
let button = self.create_entry(content_box, &entry);
|
let button = self.create_entry(content_box, entry);
|
||||||
if first {
|
if first {
|
||||||
button.add_css_class("suggested");
|
button.add_css_class("suggested");
|
||||||
first = false;
|
first = false;
|
||||||
|
|||||||
@@ -10,28 +10,25 @@ pub fn _build() -> gtk::Box {
|
|||||||
let host = TrayHost::new();
|
let host = TrayHost::new();
|
||||||
|
|
||||||
if let Ok(mut host) = host {
|
if let Ok(mut host) = host {
|
||||||
glib::timeout_add_local(
|
glib::timeout_add_local(Duration::from_millis(1_000), move || {
|
||||||
Duration::from_millis(1_000),
|
for event in host.poll().unwrap() {
|
||||||
move || {
|
match event {
|
||||||
for event in host.poll().unwrap() {
|
TrayEvent::ItemAdded(id) | TrayEvent::ItemChanged(id) => {
|
||||||
match event {
|
if let Some(item) = host.items().get(&id) {
|
||||||
TrayEvent::ItemAdded(id) | TrayEvent::ItemChanged(id) => {
|
println!("tray: {} — {}", item.title, item.status);
|
||||||
if let Some(item) = host.items().get(&id) {
|
println!(" category: {}", item.category);
|
||||||
println!("tray: {} — {}", item.title, item.status);
|
println!(" has menu: {}", item.has_menu());
|
||||||
println!(" category: {}", item.category);
|
|
||||||
println!(" has menu: {}", item.has_menu());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
TrayEvent::ItemRemoved(id) => {
|
|
||||||
println!("item gone: {id}");
|
|
||||||
}
|
|
||||||
TrayEvent::HostShutdown => break,
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
TrayEvent::ItemRemoved(id) => {
|
||||||
|
println!("item gone: {id}");
|
||||||
|
}
|
||||||
|
TrayEvent::HostShutdown => break,
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
ControlFlow::Continue
|
|
||||||
}
|
}
|
||||||
);
|
ControlFlow::Continue
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
error!(&LOGGER, "Couldn't get SystemTray");
|
error!(&LOGGER, "Couldn't get SystemTray");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use crate::backend::{BackendChannels, json_socket};
|
|||||||
use crate::ui::action_centre::ActionCentre;
|
use crate::ui::action_centre::ActionCentre;
|
||||||
use crate::ui::widgets::{ExpandableBarItem, IconSlider};
|
use crate::ui::widgets::{ExpandableBarItem, IconSlider};
|
||||||
use battery::State;
|
use battery::State;
|
||||||
|
use crossbeam_channel::TryRecvError;
|
||||||
use glib::prelude::Cast;
|
use glib::prelude::Cast;
|
||||||
use glib::subclass::prelude::ObjectSubclassIsExt;
|
use glib::subclass::prelude::ObjectSubclassIsExt;
|
||||||
use glib::{ControlFlow, clone};
|
use glib::{ControlFlow, clone};
|
||||||
@@ -205,9 +206,9 @@ pub fn build(
|
|||||||
display,
|
display,
|
||||||
move |_| {
|
move |_| {
|
||||||
if action_centre.window.is_visible() {
|
if action_centre.window.is_visible() {
|
||||||
action_centre.hide_action_centre();
|
action_centre.hide();
|
||||||
} else {
|
} else {
|
||||||
action_centre.show_action_centre(monitor_idx, &display);
|
action_centre.show(monitor_idx, &display);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
@@ -390,43 +391,61 @@ pub fn build(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while let Ok(result) = net_rx.try_recv() {
|
loop {
|
||||||
trace!(&LOGGER, "NetworkResult: {:?}", result);
|
match net_rx.try_recv() {
|
||||||
match result {
|
Ok(result) => {
|
||||||
NetworkState::ChangeNetwork(cfg) => {
|
trace!(&LOGGER, "NetworkResult: {:?}", result);
|
||||||
match cfg {
|
match result {
|
||||||
Some(network) => {
|
NetworkState::ChangeNetwork(cfg) => {
|
||||||
match network.network_type {
|
println!("Change network: {cfg:?}");
|
||||||
NetworkType::Wifi => {
|
match cfg {
|
||||||
//let idx = ((20.0 / 100.0) * (WIFI_ICON_PATHS.len() - 1) as f32 + 0.5) as usize;
|
Some(network) => {
|
||||||
let idx = ((network.signal as f32 / 100.0)
|
match network.network_type {
|
||||||
* (WIFI_ICON_PATHS.len() - 1) as f32
|
NetworkType::Wifi => {
|
||||||
+ 0.5)
|
//let idx = ((20.0 / 100.0) * (WIFI_ICON_PATHS.len() - 1) as f32 + 0.5) as usize;
|
||||||
as usize;
|
let idx = ((network.signal as f32 / 100.0)
|
||||||
network_icon.set_from_file(Some(WIFI_ICON_PATHS[idx]));
|
* (WIFI_ICON_PATHS.len() - 1) as f32
|
||||||
network_label.set_label(&network.name);
|
+ 0.5)
|
||||||
network_label.set_visible(true);
|
as usize;
|
||||||
|
network_icon
|
||||||
|
.set_from_file(Some(WIFI_ICON_PATHS[idx]));
|
||||||
|
network_label.set_label(&network.name);
|
||||||
|
network_label.set_visible(true);
|
||||||
|
}
|
||||||
|
NetworkType::Wired => {
|
||||||
|
network_icon
|
||||||
|
.set_from_file(Some("icons/ethernet.svg"));
|
||||||
|
network_label.set_visible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
NetworkType::Wired => {
|
None => {
|
||||||
network_icon.set_from_file(Some("icons/ethernet.svg"));
|
network_icon.set_from_file(Some("icons/disconnected.svg"));
|
||||||
network_label.set_visible(false);
|
network_label.set_visible(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
NetworkState::VpnState { connected, profile }
|
||||||
network_icon.set_from_file(Some("icons/disconnected.svg"));
|
if profile.as_str() == "wg" =>
|
||||||
network_label.set_visible(false);
|
{
|
||||||
|
if connected {
|
||||||
|
wg_button.add_css_class("on");
|
||||||
|
} else {
|
||||||
|
wg_button.remove_css_class("on");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
match e {
|
||||||
|
TryRecvError::Empty => {}
|
||||||
|
TryRecvError::Disconnected => {
|
||||||
|
warn!(&LOGGER, "Network channel disconnected")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
NetworkState::VpnState { connected, profile } if profile.as_str() == "wg" => {
|
|
||||||
if connected {
|
|
||||||
wg_button.add_css_class("on");
|
|
||||||
} else {
|
|
||||||
wg_button.remove_css_class("on");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while let Ok(tailscale_state) = netbird_rx.try_recv() {
|
while let Ok(tailscale_state) = netbird_rx.try_recv() {
|
||||||
@@ -481,8 +500,8 @@ pub fn build(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(gpu) = stats.gpus.into_iter().next() {
|
if let Some(gpu) = stats.gpu {
|
||||||
gpu_label.set_label(&format!("{}%", gpu.1));
|
gpu_label.set_label(&format!("{}%", gpu));
|
||||||
}
|
}
|
||||||
|
|
||||||
set_battery_css_classes(&battery_widget, stats.battery_state, stats.battery_charge);
|
set_battery_css_classes(&battery_widget, stats.battery_state, stats.battery_charge);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// Changing colors here will not apply to the constants in theme.rs until the app is recompiled
|
||||||
$background: #232320;
|
$background: #232320;
|
||||||
$background1: #3c3836;
|
$background1: #3c3836;
|
||||||
|
|
||||||
Reference in New Issue
Block a user