Inline some clones to make code more readable, add config, implement dimming -> locking -> sleeping into idle daemon, fix waiting on network thread, implement run menu with application launching and math evaluation,
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
use crate::backend::{BackendThread, Registrator};
|
||||
use crate::config::Config;
|
||||
use crate::{APP_NAME, LOGGER};
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use jmb_logger_rs::*;
|
||||
use nmrs::raw::zbus::interface;
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
use std::process::Command;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
use std::time::Duration;
|
||||
use tokio::sync::watch;
|
||||
use tokio::time::sleep;
|
||||
use wayland_client::globals::registry_queue_init;
|
||||
@@ -17,12 +18,11 @@ use wayland_protocols::ext::idle_notify::v1::client::ext_idle_notification_v1::{
|
||||
Event, ExtIdleNotificationV1,
|
||||
};
|
||||
use wayland_protocols::ext::idle_notify::v1::client::ext_idle_notifier_v1::ExtIdleNotifierV1;
|
||||
use zbus::conn::Builder;
|
||||
use zbus::interface;
|
||||
use zbus::message::Header;
|
||||
|
||||
const SERVICE: &str = "org.freedesktop.ScreenSaver";
|
||||
const OBJECT_PATH: &str = "/org/freedesktop/ScreenSaver";
|
||||
const LEGACY_OBJECT_PATH: &str = "/ScreenSaver";
|
||||
|
||||
struct WlState {
|
||||
idle: Option<bool>,
|
||||
@@ -53,14 +53,8 @@ impl Dispatch<ExtIdleNotificationV1, ()> for WlState {
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
match event {
|
||||
Event::Idled => {
|
||||
println!("Idled: {:?}", Instant::now());
|
||||
state.idle = Some(true);
|
||||
}
|
||||
Event::Resumed => {
|
||||
println!("Resumed: {:?}", Instant::now());
|
||||
state.idle = Some(false);
|
||||
}
|
||||
Event::Idled => state.idle = Some(true),
|
||||
Event::Resumed => state.idle = Some(false),
|
||||
_ => {}
|
||||
}
|
||||
if let Some(idle) = state.idle {
|
||||
@@ -85,15 +79,15 @@ impl Dispatch<ExtIdleNotifierV1, (), WlState> for WlState {
|
||||
struct Inhibitor {
|
||||
owner: String,
|
||||
application: String,
|
||||
_reason: String,
|
||||
reason: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum IdleState {
|
||||
Init(Sender<IdleMessage>),
|
||||
Dimmed,
|
||||
Inhibited,
|
||||
Caffeine,
|
||||
Inhibited(bool),
|
||||
Caffeine(bool),
|
||||
Active,
|
||||
}
|
||||
|
||||
@@ -128,73 +122,92 @@ impl BackendThread<IdleState, Sender<IdleMessage>> for IdleDaemon {
|
||||
}
|
||||
|
||||
fn init(registration_rx: Receiver<Sender<IdleState>>) {
|
||||
let config = Config::load();
|
||||
let (tx, rx) = crossbeam_channel::unbounded::<IdleMessage>();
|
||||
let tx_clone = tx.clone();
|
||||
let instance = Arc::new(Mutex::new(Self {
|
||||
subscribers: Vec::new(),
|
||||
}));
|
||||
let instance_clone = instance.clone();
|
||||
std::thread::spawn(move || {
|
||||
while let Ok(sender) = registration_rx.recv() {
|
||||
if sender.send(IdleState::Init(tx_clone.clone())).is_ok() {
|
||||
trace!(&LOGGER, "Added IdleDaemon Subscriber");
|
||||
instance_clone
|
||||
.lock()
|
||||
.expect("Couldn't get lock")
|
||||
.subscribers
|
||||
.push(sender);
|
||||
std::thread::spawn({
|
||||
let instance = instance.clone();
|
||||
let tx = tx.clone();
|
||||
move || {
|
||||
while let Ok(sender) = registration_rx.recv() {
|
||||
if sender.send(IdleState::Init(tx.clone())).is_ok() {
|
||||
trace!(&LOGGER, "Added IdleDaemon Subscriber");
|
||||
instance
|
||||
.lock()
|
||||
.expect("Couldn't get lock")
|
||||
.subscribers
|
||||
.push(sender);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
let (wl_tx, wl_rx) = watch::channel(false);
|
||||
let (dbus_tx, dbus_rx) = watch::channel((false, false));
|
||||
std::thread::spawn(move || {
|
||||
if let Err(err) = Self::start_idle_notifier(wl_tx) {
|
||||
error!(&LOGGER, "Idle Daemon unexpectedly quit, reason: {err}");
|
||||
let (dbus_tx, dbus_rx) = watch::channel(false);
|
||||
std::thread::spawn({
|
||||
let config = config.clone();
|
||||
move || {
|
||||
if let Err(err) = Self::start_idle_notifier(wl_tx, &config) {
|
||||
error!(&LOGGER, "Idle Daemon unexpectedly quit, reason: {err}");
|
||||
}
|
||||
}
|
||||
});
|
||||
std::thread::spawn(move || {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed to create Tokio runtime");
|
||||
runtime.block_on(async move {
|
||||
let mut idle_inhibitor = None;
|
||||
while let Ok(msg) = rx.recv() {
|
||||
match msg {
|
||||
IdleMessage::Inhibit => {
|
||||
if idle_inhibitor.is_none()
|
||||
&& let Ok(ii) = IdleInhibitor::inhibit_idle().await
|
||||
{
|
||||
idle_inhibitor = Some(ii);
|
||||
std::thread::spawn({
|
||||
let instance = instance.clone();
|
||||
move || {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed to create Tokio runtime");
|
||||
runtime.block_on(async move {
|
||||
let mut idle_inhibitor = None;
|
||||
while let Ok(msg) = rx.recv() {
|
||||
match msg {
|
||||
IdleMessage::Inhibit => {
|
||||
if idle_inhibitor.is_none()
|
||||
&& let Ok(ii) = IdleInhibitor::inhibit_idle().await
|
||||
{
|
||||
instance.lock().unwrap().publish(IdleState::Caffeine(true));
|
||||
idle_inhibitor = Some(ii);
|
||||
}
|
||||
}
|
||||
}
|
||||
IdleMessage::Release => {
|
||||
if let Some(ii) = idle_inhibitor.take()
|
||||
&& let Err(e) = ii.uninhibit_idle().await
|
||||
{
|
||||
warn!(&LOGGER, "Error uninhibiting {e}");
|
||||
IdleMessage::Release => {
|
||||
if let Some(ii) = idle_inhibitor.take() {
|
||||
match ii.uninhibit_idle().await {
|
||||
Ok(_) => {
|
||||
instance
|
||||
.lock()
|
||||
.unwrap()
|
||||
.publish(IdleState::Caffeine(false));
|
||||
}
|
||||
Err(e) => warn!(&LOGGER, "Error uninhibiting {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
std::thread::spawn(move || {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed to create Tokio runtime");
|
||||
std::thread::spawn({
|
||||
let instance_clone = instance.clone();
|
||||
move || {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed to create Tokio runtime");
|
||||
|
||||
runtime.block_on(async move {
|
||||
let res = IdleDaemon::start_dbus(dbus_tx).await;
|
||||
match res {
|
||||
Ok((_, _)) => {
|
||||
Self::run_idle_timer(instance, wl_rx, dbus_rx).await;
|
||||
runtime.block_on(async move {
|
||||
let res = IdleDaemon::start_dbus(dbus_tx, instance_clone).await;
|
||||
match res {
|
||||
Ok((_, _)) => {
|
||||
Self::run_idle_timer(instance, wl_rx, dbus_rx, &config).await;
|
||||
}
|
||||
Err(err) => error!(&LOGGER, "Error running D-Bus Screensaver: {err}"),
|
||||
}
|
||||
Err(err) => error!(&LOGGER, "Error running D-Bus Screensaver: {err}"),
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -215,20 +228,21 @@ impl IdleStage {
|
||||
}
|
||||
}
|
||||
|
||||
fn timeout(self) -> Duration {
|
||||
fn timeout(self, cfg: &Config) -> Duration {
|
||||
match self {
|
||||
Self::Dim => Duration::from_secs(20),
|
||||
Self::TurnOff => Duration::from_secs(20),
|
||||
Self::Sleep => Duration::from_secs(20),
|
||||
Self::Dim => Duration::from_secs(cfg.dim_timeout_delta()),
|
||||
Self::TurnOff => Duration::from_secs(cfg.lock_timeout_delta()),
|
||||
Self::Sleep => Duration::from_secs(cfg.sleep_timeout_delta()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IdleDaemon {
|
||||
async fn run_idle_timer(
|
||||
_instance: Arc<Mutex<Self>>,
|
||||
instance: Arc<Mutex<Self>>,
|
||||
mut wl_rx: watch::Receiver<bool>,
|
||||
mut dbus_rx: watch::Receiver<(bool, bool)>,
|
||||
mut dbus_rx: watch::Receiver<bool>,
|
||||
config: &Config,
|
||||
) {
|
||||
loop {
|
||||
if let Err(e) = wl_rx.changed().await {
|
||||
@@ -237,22 +251,26 @@ impl IdleDaemon {
|
||||
}
|
||||
let wl_idle = *wl_rx.borrow_and_update();
|
||||
let dbus_state = *dbus_rx.borrow_and_update();
|
||||
println!("Wayland idle state changed {wl_idle} (DBus state: {dbus_state:?})");
|
||||
if wl_idle && !dbus_state.0 {
|
||||
println!("Going into idle cycle");
|
||||
if wl_idle && !dbus_state {
|
||||
let mut stage = IdleStage::Dim;
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = sleep(stage.timeout()) => {
|
||||
_ = sleep(stage.timeout(config)) => {
|
||||
match stage {
|
||||
IdleStage::Dim => instance.lock().unwrap().publish(IdleState::Dimmed),
|
||||
IdleStage::TurnOff => _ = Command::new("hyprlock").spawn(),
|
||||
IdleStage::Sleep => _ = Command::new("systemctl").arg("sleep").spawn(),
|
||||
}
|
||||
stage = stage.next();
|
||||
println!("Reached next stage {stage:?}");
|
||||
}
|
||||
res = wl_rx.changed() => {
|
||||
if let Err(e) = res {
|
||||
warn!(&LOGGER, "Error getting Wayland idle state {e}");
|
||||
return;
|
||||
}
|
||||
println!("WL state change");
|
||||
if !*wl_rx.borrow_and_update() {
|
||||
instance.lock().unwrap().publish(IdleState::Active);
|
||||
}
|
||||
break;
|
||||
}
|
||||
res = dbus_rx.changed() => {
|
||||
@@ -260,7 +278,6 @@ impl IdleDaemon {
|
||||
warn!(&LOGGER, "Error getting D-Bus idle state {e}");
|
||||
return;
|
||||
}
|
||||
println!("D-Bus state change");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -269,7 +286,7 @@ impl IdleDaemon {
|
||||
}
|
||||
}
|
||||
|
||||
fn start_idle_notifier(tx: watch::Sender<bool>) -> Result<(), Box<dyn Error>> {
|
||||
fn start_idle_notifier(tx: watch::Sender<bool>, config: &Config) -> Result<(), Box<dyn Error>> {
|
||||
let conn = Connection::connect_to_env()?;
|
||||
let (globals, mut event_queue) = registry_queue_init::<WlState>(&conn)?;
|
||||
let queue_handle = event_queue.handle();
|
||||
@@ -280,7 +297,12 @@ impl IdleDaemon {
|
||||
|
||||
let mut state = WlState { idle: None, tx };
|
||||
|
||||
let _notification = notifier.get_idle_notification(10_000, &seat, &queue_handle, ());
|
||||
let _notification = notifier.get_idle_notification(
|
||||
(config.idle_timeout() * 1000) as u32,
|
||||
&seat,
|
||||
&queue_handle,
|
||||
(),
|
||||
);
|
||||
|
||||
loop {
|
||||
event_queue.blocking_dispatch(&mut state)?;
|
||||
@@ -288,16 +310,18 @@ impl IdleDaemon {
|
||||
}
|
||||
|
||||
async fn start_dbus(
|
||||
tx: watch::Sender<(bool, bool)>,
|
||||
tx: watch::Sender<bool>,
|
||||
daemon: Arc<Mutex<IdleDaemon>>,
|
||||
) -> zbus::Result<(ScreenSaver, zbus::connection::Connection)> {
|
||||
let service = ScreenSaver::new(tx);
|
||||
let service = ScreenSaver::new(tx, daemon);
|
||||
|
||||
let connection = Builder::session()?
|
||||
.name(SERVICE)?
|
||||
.serve_at(OBJECT_PATH, service.clone())?
|
||||
.serve_at(LEGACY_OBJECT_PATH, service.clone())?
|
||||
.build()
|
||||
let connection = zbus::Connection::session().await?;
|
||||
|
||||
connection
|
||||
.object_server()
|
||||
.at(OBJECT_PATH, service.clone())
|
||||
.await?;
|
||||
connection.request_name(SERVICE).await?;
|
||||
|
||||
info!(&LOGGER, "Providing {SERVICE}");
|
||||
|
||||
@@ -347,22 +371,24 @@ impl IdleInhibitor {
|
||||
#[derive(Clone)]
|
||||
struct ScreenSaver {
|
||||
inner: Arc<Mutex<Inner>>,
|
||||
daemon: Arc<Mutex<IdleDaemon>>,
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
next_cookie: u32,
|
||||
inhibitors: HashMap<u32, Inhibitor>,
|
||||
tx: watch::Sender<(bool, bool)>,
|
||||
tx: watch::Sender<bool>,
|
||||
}
|
||||
|
||||
impl ScreenSaver {
|
||||
fn new(tx: watch::Sender<(bool, bool)>) -> Self {
|
||||
fn new(tx: watch::Sender<bool>, daemon: Arc<Mutex<IdleDaemon>>) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(Mutex::new(Inner {
|
||||
next_cookie: 1,
|
||||
inhibitors: HashMap::new(),
|
||||
tx,
|
||||
})),
|
||||
daemon,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -399,21 +425,26 @@ impl ScreenSaver {
|
||||
}
|
||||
};
|
||||
|
||||
let caffeine_flag = application_name.eq(APP_NAME);
|
||||
|
||||
debug!(&LOGGER, "Inhibitor added: {application_name} {cookie}");
|
||||
debug!(
|
||||
&LOGGER,
|
||||
"Inhibitor added - Name: '{application_name}' Reason: '{reason_for_inhibit}' Value: {cookie}"
|
||||
);
|
||||
inner.inhibitors.insert(
|
||||
cookie,
|
||||
Inhibitor {
|
||||
owner,
|
||||
application: application_name,
|
||||
_reason: reason_for_inhibit,
|
||||
reason: reason_for_inhibit,
|
||||
},
|
||||
);
|
||||
|
||||
self.daemon
|
||||
.lock()
|
||||
.unwrap()
|
||||
.publish(IdleState::Inhibited(true));
|
||||
|
||||
inner.tx.send_modify(|state| {
|
||||
state.0 = !inner.inhibitors.is_empty();
|
||||
state.1 = caffeine_flag;
|
||||
*state = !inner.inhibitors.is_empty();
|
||||
});
|
||||
Ok(cookie)
|
||||
}
|
||||
@@ -432,25 +463,25 @@ impl ScreenSaver {
|
||||
if let Some(removed) = inner.inhibitors.remove(&cookie) {
|
||||
debug!(
|
||||
&LOGGER,
|
||||
"Inhibitor removed: {} {cookie}", removed.application
|
||||
"Inhibitor removed - Name: '{}' Reason: '{}' Value: {cookie}",
|
||||
removed.application,
|
||||
removed.reason
|
||||
);
|
||||
}
|
||||
let mut caffeine_flag = false;
|
||||
for inhibitor in inner.inhibitors.values() {
|
||||
if inhibitor.application.eq(APP_NAME) {
|
||||
caffeine_flag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
self.daemon
|
||||
.lock()
|
||||
.unwrap()
|
||||
.publish(IdleState::Inhibited(!inner.inhibitors.is_empty()));
|
||||
|
||||
inner.tx.send_modify(|state| {
|
||||
state.0 = !inner.inhibitors.is_empty();
|
||||
state.1 = caffeine_flag;
|
||||
*state = !inner.inhibitors.is_empty();
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
Some(_) => Err(zbus::fdo::Error::AccessDenied(
|
||||
"Cookie belongs to another D-Bus client".into(),
|
||||
)),
|
||||
Some(_) => Err(zbus::fdo::Error::AccessDenied(format!(
|
||||
"Cookie {cookie} belongs to another D-Bus client"
|
||||
))),
|
||||
None => Err(zbus::fdo::Error::Failed("Unknown inhibition cookie".into())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,6 +161,26 @@ impl BackendThread<NetworkState, UnboundedSender<NetworkCommand>> for NetworkBac
|
||||
}
|
||||
};
|
||||
|
||||
std::thread::spawn({
|
||||
let nm = nm.clone();
|
||||
let mut instance = instance.clone();
|
||||
move || {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed to create Tokio runtime");
|
||||
|
||||
runtime.block_on(async move {
|
||||
while let Ok(tx) = registration_rx.recv() {
|
||||
if tx.send(NetworkState::Init(instance.command_tx.clone())).is_ok() {
|
||||
instance.subscribers.push(tx);
|
||||
let _ = instance.send_all_networks(&nm).await;
|
||||
let _ = instance.send_active_connection(&nm).await;
|
||||
}
|
||||
}
|
||||
});
|
||||
}});
|
||||
|
||||
let mut events = match nm.network_events().await {
|
||||
Ok(events) => events,
|
||||
Err(err) => {
|
||||
@@ -169,7 +189,6 @@ impl BackendThread<NetworkState, UnboundedSender<NetworkCommand>> for NetworkBac
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let mut debouncer = EventDebouncer {
|
||||
duration: Duration::from_millis(200),
|
||||
deadline: None,
|
||||
@@ -177,13 +196,6 @@ impl BackendThread<NetworkState, UnboundedSender<NetworkCommand>> for NetworkBac
|
||||
};
|
||||
debouncer.sleep.as_mut().reset(Instant::now() + Duration::from_secs(86400));
|
||||
loop {
|
||||
while let Ok(tx) = registration_rx.try_recv() {
|
||||
if tx.send(NetworkState::Init(instance.command_tx.clone())).is_ok() {
|
||||
instance.subscribers.push(tx);
|
||||
let _ = instance.send_all_networks(&nm).await;
|
||||
let _ = instance.send_active_connection(&nm).await;
|
||||
}
|
||||
}
|
||||
tokio::select! {
|
||||
command = command_rx.recv() => {
|
||||
let Some(command) = command else {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::LOGGER;
|
||||
use crate::backend::{BackendThread, SimpleBackendThread};
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use hyprland::data::{Client, Monitor, Workspaces};
|
||||
use hyprland::data::{Client, CursorPosition, Monitor, Monitors, Workspaces};
|
||||
use hyprland::event_listener::EventListener;
|
||||
use hyprland::prelude::*;
|
||||
use hyprland::shared::{MonitorId, WorkspaceId, WorkspaceType};
|
||||
use hyprland::shared::{MonitorId, WorkspaceId};
|
||||
use jmb_logger_rs::*;
|
||||
use std::io;
|
||||
use std::process::{Command, Output};
|
||||
@@ -24,7 +24,6 @@ pub struct HyprlandComms {
|
||||
}
|
||||
|
||||
impl SimpleBackendThread<WindowManagerEvent> for HyprlandComms {
|
||||
|
||||
fn subscribers(&self) -> &Vec<Sender<WindowManagerEvent>> {
|
||||
&self.subscribers
|
||||
}
|
||||
@@ -38,9 +37,7 @@ impl SimpleBackendThread<WindowManagerEvent> for HyprlandComms {
|
||||
|
||||
let inst_clone = instance.clone();
|
||||
listener.add_workspace_changed_handler(move |workspace| {
|
||||
if let WorkspaceType::Regular(name) = workspace.name
|
||||
&& let Ok(id) = name.parse::<i32>()
|
||||
{
|
||||
if let Ok(id) = workspace.name.parse::<i32>() {
|
||||
inst_clone
|
||||
.lock()
|
||||
.expect("Couldn't get Subscribers")
|
||||
@@ -50,26 +47,24 @@ impl SimpleBackendThread<WindowManagerEvent> for HyprlandComms {
|
||||
|
||||
let inst_clone = instance.clone();
|
||||
listener.add_workspace_added_handler(move |workspace| {
|
||||
if let WorkspaceType::Regular(name) = workspace.name {
|
||||
//unpacks the name of the workspace
|
||||
if let Ok(wss) = Workspaces::get() {
|
||||
//gets all workspaces
|
||||
if let Some(ws) = wss
|
||||
.to_vec()
|
||||
.iter()
|
||||
.find(|ws| ws.id().unwrap() == workspace.id)
|
||||
{
|
||||
//find the current workspace Object from its id
|
||||
if let Ok(id) = name.parse::<i32>() {
|
||||
// parse the id as an int
|
||||
inst_clone
|
||||
.lock()
|
||||
.expect("Couldn't get Subscribers")
|
||||
.publish(WindowManagerEvent::WorkspaceAdded(
|
||||
id,
|
||||
ws.monitor_id.unwrap(),
|
||||
));
|
||||
}
|
||||
//unpacks the name of the workspace
|
||||
if let Ok(wss) = Workspaces::get() {
|
||||
//gets all workspaces
|
||||
if let Some(ws) = wss
|
||||
.to_vec()
|
||||
.iter()
|
||||
.find(|ws| ws.id().unwrap() == workspace.id().unwrap())
|
||||
{
|
||||
//find the current workspace Object from its id
|
||||
if let Ok(id) = workspace.name.parse::<i32>() {
|
||||
// parse the id as an int
|
||||
inst_clone
|
||||
.lock()
|
||||
.expect("Couldn't get Subscribers")
|
||||
.publish(WindowManagerEvent::WorkspaceAdded(
|
||||
id,
|
||||
ws.monitor_id.unwrap(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,9 +72,7 @@ impl SimpleBackendThread<WindowManagerEvent> for HyprlandComms {
|
||||
|
||||
let inst_clone = instance.clone();
|
||||
listener.add_workspace_deleted_handler(move |workspace| {
|
||||
if let WorkspaceType::Regular(name) = workspace.name
|
||||
&& let Ok(id) = name.parse::<i32>()
|
||||
{
|
||||
if let Ok(id) = workspace.name.parse::<i32>() {
|
||||
inst_clone
|
||||
.lock()
|
||||
.expect("Couldn't get Subscribers")
|
||||
@@ -92,7 +85,9 @@ impl SimpleBackendThread<WindowManagerEvent> for HyprlandComms {
|
||||
inst_clone
|
||||
.lock()
|
||||
.expect("Couldn't get Subscribers")
|
||||
.publish(WindowManagerEvent::WorkspaceChanged(evt.workspace_id));
|
||||
.publish(WindowManagerEvent::WorkspaceChanged(
|
||||
evt.workspace.id().unwrap(),
|
||||
));
|
||||
});
|
||||
|
||||
let inst_clone = instance.clone();
|
||||
@@ -106,15 +101,22 @@ impl SimpleBackendThread<WindowManagerEvent> for HyprlandComms {
|
||||
|
||||
let inst_clone = instance.clone();
|
||||
listener.add_workspace_moved_handler(move |event| {
|
||||
if let Some(monitor_id) = Self::monitor_id_from_workspace_id(event.id) {
|
||||
if let Some(monitor_id) =
|
||||
Self::monitor_id_from_workspace_id(event.workspace.id().unwrap())
|
||||
{
|
||||
inst_clone
|
||||
.lock()
|
||||
.expect("Couldn't get Subscribers")
|
||||
.publish(WindowManagerEvent::WorkspaceRemoved(event.id));
|
||||
.publish(WindowManagerEvent::WorkspaceRemoved(
|
||||
event.workspace.id().unwrap(),
|
||||
));
|
||||
inst_clone
|
||||
.lock()
|
||||
.expect("Couldn't get Subscribers")
|
||||
.publish(WindowManagerEvent::WorkspaceAdded(event.id, monitor_id));
|
||||
.publish(WindowManagerEvent::WorkspaceAdded(
|
||||
event.workspace.id().unwrap(),
|
||||
monitor_id,
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -160,7 +162,7 @@ impl HyprlandComms {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn _current_monitor() -> MonitorId {
|
||||
pub fn current_monitor() -> MonitorId {
|
||||
if let Ok(monitor) = Monitor::get_active() {
|
||||
return monitor.id;
|
||||
}
|
||||
@@ -197,4 +199,22 @@ impl HyprlandComms {
|
||||
.arg(format!("hyprctl dispatch \"{dispatcher}\""))
|
||||
.output()
|
||||
}
|
||||
|
||||
pub fn cursor_monitor() -> Option<MonitorId> {
|
||||
if let Ok(pos) = CursorPosition::get()
|
||||
&& let Ok(monitors) = Monitors::get()
|
||||
{
|
||||
for monitor in monitors {
|
||||
if pos.x > monitor.x as i64
|
||||
&& pos.x < (monitor.x as i64 + monitor.width as i64)
|
||||
&& pos.y > monitor.y as i64
|
||||
&& pos.y < (monitor.y as i64 + monitor.height as i64)
|
||||
{
|
||||
return Some(monitor.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
59
src/config.rs
Normal file
59
src/config.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use serde::Deserialize;
|
||||
use std::fs;
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub(crate) struct Config {
|
||||
idle_timeout: u64,
|
||||
dim_timeout: u64,
|
||||
lock_timeout: u64,
|
||||
sleep_timeout: u64,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
idle_timeout: 20,
|
||||
dim_timeout: 60,
|
||||
lock_timeout: 300,
|
||||
sleep_timeout: 320,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
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 {
|
||||
self.idle_timeout
|
||||
}
|
||||
|
||||
pub fn dim_timeout(&self) -> u64 {
|
||||
self.dim_timeout
|
||||
}
|
||||
|
||||
pub fn lock_timeout(&self) -> u64 {
|
||||
self.lock_timeout
|
||||
}
|
||||
|
||||
pub fn sleep_timeout(&self) -> u64 {
|
||||
self.sleep_timeout
|
||||
}
|
||||
|
||||
pub fn dim_timeout_delta(&self) -> u64 {
|
||||
self.dim_timeout - self.idle_timeout
|
||||
}
|
||||
|
||||
pub fn lock_timeout_delta(&self) -> u64 {
|
||||
self.lock_timeout - self.dim_timeout_delta()
|
||||
}
|
||||
|
||||
pub fn sleep_timeout_delta(&self) -> u64 {
|
||||
self.sleep_timeout - self.lock_timeout_delta()
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
use crate::backend::Backend;
|
||||
mod backend;
|
||||
mod config;
|
||||
mod css;
|
||||
mod ctl;
|
||||
mod ui;
|
||||
|
||||
use crate::config::Config;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk::{Application, CssProvider, gdk, gio};
|
||||
|
||||
12
src/ui.rs
12
src/ui.rs
@@ -39,7 +39,7 @@ pub fn create_bars(app: &Application, backend: Backend) {
|
||||
monitor,
|
||||
create_and_show_bar(
|
||||
app_clone.clone(),
|
||||
&display,
|
||||
display.clone(),
|
||||
monitor,
|
||||
action_centre.clone(),
|
||||
channels,
|
||||
@@ -61,11 +61,11 @@ pub fn create_bars(app: &Application, backend: Backend) {
|
||||
let action_centre = action_centre.clone();
|
||||
let channels = backend_clone.subscribe_all();
|
||||
let bars = bars.clone();
|
||||
let display = display.clone();
|
||||
glib::idle_add_local_once(move || {
|
||||
let display = Display::default().expect("Couldn't find GDK Display");
|
||||
bars.lock().expect("Couldn't get active bars").insert(
|
||||
pos,
|
||||
create_and_show_bar(app_clone, &display, pos, action_centre, channels),
|
||||
create_and_show_bar(app_clone, display, pos, action_centre, channels),
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -74,7 +74,7 @@ pub fn create_bars(app: &Application, backend: Backend) {
|
||||
let mut ctl_channel = backend.subscribe(&[ChannelType::Control]);
|
||||
let ctl_rx = ctl_channel.take_ctl_rx().unwrap();
|
||||
let app_clone = app.clone();
|
||||
let menu = RunMenu::create(app_clone);
|
||||
let mut menu = RunMenu::create(app_clone);
|
||||
glib::timeout_add_local(Duration::from_millis(100), move || {
|
||||
while let Ok(cmd) = ctl_rx.try_recv() {
|
||||
match cmd {
|
||||
@@ -89,7 +89,7 @@ pub fn create_bars(app: &Application, backend: Backend) {
|
||||
|
||||
fn create_and_show_bar(
|
||||
app: Application,
|
||||
display: &Display,
|
||||
display: Display,
|
||||
monitor_idx: u32,
|
||||
action_centre: ActionCentre,
|
||||
mut channels: BackendChannels,
|
||||
@@ -138,7 +138,7 @@ fn create_and_show_bar(
|
||||
let title_button: Option<Button>;
|
||||
let title = title_panel::build();
|
||||
|
||||
let utilities = utilities_panel::build(&app, action_centre, channels, monitor_idx, display);
|
||||
let utilities = utilities_panel::build(&app, action_centre, channels, monitor_idx, &display);
|
||||
|
||||
let dock = gtk::Box::new(Orientation::Horizontal, 10);
|
||||
dock.add_css_class("dock");
|
||||
|
||||
@@ -111,106 +111,111 @@ impl ActionCentre {
|
||||
|
||||
window.set_child(Some(&scrolled));
|
||||
|
||||
let instance_clone = instance.clone();
|
||||
let key_controller = gtk::EventControllerKey::new();
|
||||
key_controller.connect_key_pressed(move |_, key, _, _| {
|
||||
if key == gdk::Key::Escape {
|
||||
instance_clone.hide_action_centre();
|
||||
glib::Propagation::Stop
|
||||
} else {
|
||||
glib::Propagation::Proceed
|
||||
key_controller.connect_key_pressed({
|
||||
let instance = instance.clone();
|
||||
move |_, key, _, _| {
|
||||
if key == gdk::Key::Escape {
|
||||
instance.hide_action_centre();
|
||||
glib::Propagation::Stop
|
||||
} else {
|
||||
glib::Propagation::Proceed
|
||||
}
|
||||
}
|
||||
});
|
||||
window.add_controller(key_controller);
|
||||
|
||||
let wifi_networks: Rc<RefCell<HashMap<String, WifiWidget>>> =
|
||||
Rc::new(RefCell::new(HashMap::new()));
|
||||
let wifi_networks_clone = wifi_networks.clone();
|
||||
window.connect_focus_widget_notify(move |window| {
|
||||
Self::focus_callback(&wifi_networks_clone, window)
|
||||
window.connect_focus_widget_notify({
|
||||
let wifi_networks = wifi_networks.clone();
|
||||
move |window| Self::focus_callback(&wifi_networks, window)
|
||||
});
|
||||
|
||||
let net_rx = channels.take_net_rx().unwrap();
|
||||
let window_clone = window.clone();
|
||||
let wifi_networks_clone = wifi_networks.clone();
|
||||
glib::timeout_add_local(Duration::from_millis(100), move || {
|
||||
while let Ok(message) = net_rx.try_recv() {
|
||||
match message {
|
||||
NetworkState::ChangeNetwork(Some(info)) => {
|
||||
for (name, widget) in wifi_networks_clone.borrow().iter() {
|
||||
if name.eq(&info.name) {
|
||||
widget.add_css_class("connected");
|
||||
} else {
|
||||
widget.remove_css_class("connected");
|
||||
glib::timeout_add_local(Duration::from_millis(100), {
|
||||
let window = window.clone();
|
||||
let wifi_networks = wifi_networks.clone();
|
||||
let net_rx = channels.take_net_rx().unwrap();
|
||||
move || {
|
||||
while let Ok(message) = net_rx.try_recv() {
|
||||
match message {
|
||||
NetworkState::ChangeNetwork(Some(info)) => {
|
||||
for (name, widget) in wifi_networks.borrow().iter() {
|
||||
if name.eq(&info.name) {
|
||||
widget.add_css_class("connected");
|
||||
} else {
|
||||
widget.remove_css_class("connected");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
NetworkState::Networks(networks) => {
|
||||
for network in &networks {
|
||||
let borrowed_wifi_networks_clone = wifi_networks_clone.borrow_mut();
|
||||
let widget = borrowed_wifi_networks_clone.get(&network.name);
|
||||
if let Some(widget) = widget {
|
||||
let idx = ((network.signal as f32 / 100.0)
|
||||
* (WIFI_ICONS.len() - 1) as f32
|
||||
+ 0.5) as usize;
|
||||
widget.set_wifi_icon_from_name(WIFI_ICONS[idx]);
|
||||
} else {
|
||||
let widget = Self::create_wifi_widget(
|
||||
&channels,
|
||||
network,
|
||||
borrowed_wifi_networks_clone,
|
||||
);
|
||||
content.append(&widget);
|
||||
NetworkState::Networks(networks) => {
|
||||
for network in &networks {
|
||||
let borrowed_wifi_networks = wifi_networks.borrow_mut();
|
||||
let widget = borrowed_wifi_networks.get(&network.name);
|
||||
if let Some(widget) = widget {
|
||||
let idx = ((network.signal as f32 / 100.0)
|
||||
* (WIFI_ICONS.len() - 1) as f32
|
||||
+ 0.5)
|
||||
as usize;
|
||||
widget.set_wifi_icon_from_name(WIFI_ICONS[idx]);
|
||||
} else {
|
||||
let widget = Self::create_wifi_widget(
|
||||
&channels,
|
||||
network,
|
||||
borrowed_wifi_networks,
|
||||
);
|
||||
content.append(&widget);
|
||||
}
|
||||
}
|
||||
let mut removal: Vec<String> = Vec::new();
|
||||
for (ssid, widget) in wifi_networks.borrow().iter() {
|
||||
let network = &networks.iter().find(|n| n.name.eq(ssid));
|
||||
if network.is_none() {
|
||||
content.remove(widget);
|
||||
removal.push(ssid.clone());
|
||||
}
|
||||
}
|
||||
for ssid in removal {
|
||||
wifi_networks.borrow_mut().remove(&ssid);
|
||||
}
|
||||
}
|
||||
let mut removal: Vec<String> = Vec::new();
|
||||
for (ssid, widget) in wifi_networks_clone.borrow().iter() {
|
||||
let network = &networks.iter().find(|n| n.name.eq(ssid));
|
||||
if network.is_none() {
|
||||
content.remove(widget);
|
||||
removal.push(ssid.clone());
|
||||
}
|
||||
}
|
||||
for ssid in removal {
|
||||
wifi_networks_clone.borrow_mut().remove(&ssid);
|
||||
}
|
||||
}
|
||||
NetworkState::Auth(ssid, auth) => {
|
||||
if let Some(widget) = Self::find_focused_widget(window_clone.as_ref()) {
|
||||
match auth {
|
||||
AuthType::Password => {
|
||||
if widget.ssid().eq(&ssid) {
|
||||
widget.show_password_entry();
|
||||
widget.stop_spinner();
|
||||
widget.grab_focus_password_entry();
|
||||
NetworkState::Auth(ssid, auth) => {
|
||||
if let Some(widget) = Self::find_focused_widget(window.as_ref()) {
|
||||
match auth {
|
||||
AuthType::Password => {
|
||||
if widget.ssid().eq(&ssid) {
|
||||
widget.show_password_entry();
|
||||
widget.stop_spinner();
|
||||
widget.grab_focus_password_entry();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
NetworkState::ConnectFail(ssid, err) => {
|
||||
let mut networks = wifi_networks_clone.borrow_mut();
|
||||
if let Some(widget) = networks.get(&ssid) {
|
||||
widget.stop_spinner();
|
||||
match err {
|
||||
ConnectError::AuthFailed => widget.show_password_entry(),
|
||||
ConnectError::NotFound => {
|
||||
content.remove(widget);
|
||||
networks.remove(&ssid);
|
||||
NetworkState::ConnectFail(ssid, err) => {
|
||||
let mut networks = wifi_networks.borrow_mut();
|
||||
if let Some(widget) = networks.get(&ssid) {
|
||||
widget.stop_spinner();
|
||||
match err {
|
||||
ConnectError::AuthFailed => widget.show_password_entry(),
|
||||
ConnectError::NotFound => {
|
||||
content.remove(widget);
|
||||
networks.remove(&ssid);
|
||||
}
|
||||
ConnectError::Unknown => {}
|
||||
}
|
||||
ConnectError::Unknown => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
NetworkState::ConnectSuccess(ssid) => {
|
||||
if let Some(widget) = wifi_networks_clone.borrow().get(&ssid) {
|
||||
widget.stop_spinner();
|
||||
NetworkState::ConnectSuccess(ssid) => {
|
||||
if let Some(widget) = wifi_networks.borrow().get(&ssid) {
|
||||
widget.stop_spinner();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
ControlFlow::Continue
|
||||
}
|
||||
ControlFlow::Continue
|
||||
});
|
||||
|
||||
instance
|
||||
@@ -219,7 +224,7 @@ impl ActionCentre {
|
||||
fn create_wifi_widget(
|
||||
channels: &BackendChannels,
|
||||
network: &NetworkInfo,
|
||||
mut borrowed_wifi_networks_clone: RefMut<HashMap<String, WifiWidget>>,
|
||||
mut borrowed_wifi_networks: RefMut<HashMap<String, WifiWidget>>,
|
||||
) -> WifiWidget {
|
||||
let ssid = network.name.clone();
|
||||
let widget = WifiWidget::new();
|
||||
@@ -229,7 +234,7 @@ impl ActionCentre {
|
||||
widget.set_known(network.known);
|
||||
let idx = ((network.signal as f32 / 100.0) * (WIFI_ICONS.len() - 1) as f32 + 0.5) as usize;
|
||||
widget.set_wifi_icon_from_name(WIFI_ICONS[idx]);
|
||||
borrowed_wifi_networks_clone.insert(ssid.clone(), widget.clone());
|
||||
borrowed_wifi_networks.insert(ssid.clone(), widget.clone());
|
||||
let net_tx = channels.net_tx().unwrap();
|
||||
widget.connect_clicked(clone!(
|
||||
#[strong]
|
||||
|
||||
@@ -1,27 +1,44 @@
|
||||
use gtk::prelude::{BoxExt, GtkWindowExt, WidgetExt};
|
||||
use crate::LOGGER;
|
||||
use crate::backend::window_manager_hypr::HyprlandComms;
|
||||
use freedesktop_desktop_entry::{default_paths, get_languages_from_env};
|
||||
use glib::clone;
|
||||
use glib::prelude::Cast;
|
||||
use gtk::gdk::{Display, Monitor};
|
||||
use gtk::prelude::{
|
||||
BoxExt, ButtonExt, DisplayExt, EditableExt, EntryExt, GtkWindowExt, ListModelExt, MonitorExt,
|
||||
WidgetExt,
|
||||
};
|
||||
use gtk::{Align, Application, ApplicationWindow, Orientation, PolicyType, gdk};
|
||||
use gtk4_layer_shell::{KeyboardMode, Layer, LayerShell};
|
||||
use gtk4_layer_shell::{Edge, KeyboardMode, Layer, LayerShell};
|
||||
use jmb_logger_rs::*;
|
||||
use nucleo_matcher::pattern::{CaseMatching, Normalization, Pattern};
|
||||
use nucleo_matcher::{Config, Matcher};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::process::Command;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RunMenu {
|
||||
window: ApplicationWindow,
|
||||
entry: gtk::Entry,
|
||||
entries: Rc<RefCell<HashMap<String, Vec<String>>>>,
|
||||
}
|
||||
|
||||
impl RunMenu {
|
||||
pub fn create(application: Application) -> Self {
|
||||
let window = gtk::ApplicationWindow::builder()
|
||||
let window = ApplicationWindow::builder()
|
||||
.application(&application)
|
||||
.title("JmbRunMenu")
|
||||
.default_width(500)
|
||||
.default_height(300)
|
||||
.css_classes(["run-menu"])
|
||||
.build();
|
||||
|
||||
window.init_layer_shell();
|
||||
window.set_layer(Layer::Overlay);
|
||||
window.set_halign(Align::Center);
|
||||
window.set_valign(Align::Center);
|
||||
window.set_size_request(500, 300);
|
||||
window.set_width_request(500);
|
||||
window.set_anchor(Edge::Top, true);
|
||||
window.set_keyboard_mode(KeyboardMode::Exclusive);
|
||||
|
||||
let v_box = gtk::Box::new(Orientation::Vertical, 0);
|
||||
@@ -31,28 +48,109 @@ impl RunMenu {
|
||||
|
||||
let scrolled = gtk::ScrolledWindow::new();
|
||||
scrolled.set_policy(PolicyType::Never, PolicyType::Automatic);
|
||||
scrolled.set_vexpand(true);
|
||||
scrolled.set_valign(Align::Fill);
|
||||
scrolled.set_vexpand(false);
|
||||
scrolled.set_propagate_natural_height(true);
|
||||
scrolled.set_min_content_height(0);
|
||||
scrolled.set_max_content_height(300);
|
||||
|
||||
let content_box = gtk::Box::new(Orientation::Vertical, 0);
|
||||
content_box.add_css_class("content-box");
|
||||
|
||||
for _ in 0..10 {
|
||||
let label = gtk::Label::new(Some("Test"));
|
||||
content_box.append(&label);
|
||||
}
|
||||
|
||||
content_box.set_hexpand(true);
|
||||
scrolled.set_child(Some(&content_box));
|
||||
|
||||
v_box.set_vexpand(false);
|
||||
v_box.set_valign(Align::Start);
|
||||
v_box.append(&entry);
|
||||
v_box.append(&scrolled);
|
||||
|
||||
window.set_child(Some(&v_box));
|
||||
|
||||
let display = Display::default().expect("Couldn't get display");
|
||||
let monitor = display
|
||||
.monitors()
|
||||
.item(HyprlandComms::current_monitor() as u32)
|
||||
.expect("Couldn't get monitor")
|
||||
.downcast::<Monitor>()
|
||||
.ok();
|
||||
window.set_monitor(monitor.as_ref());
|
||||
if let Some(monitor) = monitor {
|
||||
let geometry = monitor.geometry();
|
||||
let (_, natural_height, _, _) =
|
||||
window.child().unwrap().measure(Orientation::Vertical, -1);
|
||||
println!("Monitor height: {}", geometry.height());
|
||||
let top_margin = (geometry.height()) / 2;
|
||||
window.set_margin(Edge::Top, top_margin - 100);
|
||||
} else {
|
||||
window.set_valign(Align::Center);
|
||||
}
|
||||
|
||||
content_box
|
||||
.observe_children()
|
||||
.connect_items_changed(move |model, _, _, _| {
|
||||
if model.n_items() == 0 {
|
||||
println!("Hide");
|
||||
} else {
|
||||
println!("Reveal");
|
||||
}
|
||||
});
|
||||
|
||||
let window_clone = window.clone();
|
||||
let instance = Self {
|
||||
window: window_clone,
|
||||
entry: entry.clone(),
|
||||
entries: Rc::new(RefCell::new(HashMap::new())),
|
||||
};
|
||||
let instance_clone = instance.clone();
|
||||
let matcher_rc = Rc::new(RefCell::new(Matcher::new(Config::DEFAULT)));
|
||||
entry.connect_changed(clone!(
|
||||
#[strong]
|
||||
content_box,
|
||||
move |e| {
|
||||
while let Some(child) = content_box.first_child() {
|
||||
content_box.remove(&child);
|
||||
}
|
||||
if e.text_length() > 0 {
|
||||
let text = e.text();
|
||||
if text.starts_with("=") {
|
||||
if let Ok(eval) = meval::eval_str(&text[1..]) {
|
||||
let label = gtk::Label::new(Some(&format!("{eval}")));
|
||||
label.add_css_class("exec-button");
|
||||
label.set_halign(Align::Start);
|
||||
label.set_hexpand(true);
|
||||
content_box.append(&label);
|
||||
}
|
||||
} else {
|
||||
instance_clone.list_matching(e, &content_box, &mut matcher_rc.borrow_mut());
|
||||
instance_clone.window.set_default_size(500, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
));
|
||||
entry.connect_has_focus_notify(clone!(
|
||||
#[strong]
|
||||
content_box,
|
||||
move |e| {
|
||||
if let Some(child) = content_box.first_child() {
|
||||
if e.has_focus() {
|
||||
child.add_css_class("suggested");
|
||||
} else {
|
||||
child.remove_css_class("suggested");
|
||||
}
|
||||
}
|
||||
}
|
||||
));
|
||||
entry.connect_activate(clone!(
|
||||
#[strong]
|
||||
content_box,
|
||||
move |_| {
|
||||
if let Some(child) = content_box.first_child()
|
||||
&& let Ok(button) = child.downcast::<gtk::Button>()
|
||||
{
|
||||
button.activate();
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
let instance_clone = instance.clone();
|
||||
let key_controller = gtk::EventControllerKey::new();
|
||||
key_controller.connect_key_pressed(move |_, key, _, _| {
|
||||
@@ -69,9 +167,82 @@ impl RunMenu {
|
||||
|
||||
pub fn hide(&self) {
|
||||
self.window.set_visible(false);
|
||||
self.entry.set_text("");
|
||||
}
|
||||
|
||||
pub fn show(&self) {
|
||||
pub fn show(&mut self) {
|
||||
self.load_desktop_files();
|
||||
self.window.present();
|
||||
}
|
||||
|
||||
fn create_entry(&self, content_box: >k::Box, entry: &String) -> gtk::Button {
|
||||
let entry_clone = entry.clone();
|
||||
let button = gtk::Button::new();
|
||||
button.set_halign(Align::Fill);
|
||||
button.set_hexpand(true);
|
||||
|
||||
let label = gtk::Label::new(Some(entry));
|
||||
label.set_halign(Align::Start);
|
||||
button.set_child(Some(&label));
|
||||
|
||||
button.add_css_class("exec-button");
|
||||
let entry_map = self.entries.clone();
|
||||
let instance = self.clone();
|
||||
button.connect_clicked(move |_| {
|
||||
let map = entry_map.borrow();
|
||||
let exec = map.get(&entry_clone);
|
||||
if let Some(exec) = exec {
|
||||
let executable = &exec[0];
|
||||
if let Err(err) = Command::new(executable).args(&exec[1..]).spawn() {
|
||||
warn!(&LOGGER, "Couldn't execute app: {err}");
|
||||
}
|
||||
instance.hide();
|
||||
}
|
||||
});
|
||||
content_box.append(&button);
|
||||
button
|
||||
}
|
||||
|
||||
fn list_matching(
|
||||
&self,
|
||||
entry: >k::Entry,
|
||||
content_box: >k::Box,
|
||||
matcher: &mut Matcher,
|
||||
) -> bool {
|
||||
let keys: Vec<String> = self.entries.borrow().keys().map(|i| i.clone()).collect();
|
||||
let mut first = true;
|
||||
let mut added = false;
|
||||
for (entry, _) in Pattern::parse(&entry.text(), CaseMatching::Ignore, Normalization::Smart)
|
||||
.match_list(keys, matcher)
|
||||
.iter()
|
||||
{
|
||||
let button = self.create_entry(content_box, &entry);
|
||||
if first {
|
||||
button.add_css_class("suggested");
|
||||
first = false;
|
||||
}
|
||||
added = true;
|
||||
}
|
||||
added
|
||||
}
|
||||
|
||||
fn load_desktop_files(&mut self) {
|
||||
let locales = get_languages_from_env();
|
||||
|
||||
let entries = freedesktop_desktop_entry::Iter::new(default_paths())
|
||||
.entries(Some(&locales))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut entry_map = self.entries.borrow_mut();
|
||||
entry_map.clear();
|
||||
|
||||
for entry in entries {
|
||||
if let Ok(exec) = entry.parse_exec() {
|
||||
let name = entry.full_name(&locales);
|
||||
if let Some(name) = name {
|
||||
entry_map.insert(name.to_string(), exec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,14 +78,16 @@ pub fn build(
|
||||
|
||||
let media_tx = channels.media_tx().unwrap();
|
||||
let changed_progr = Rc::new(Cell::new(false));
|
||||
let changed_progr_clone = changed_progr.clone();
|
||||
volume.connect_value_changed(move |slider, button, _| {
|
||||
if !changed_progr_clone.get() {
|
||||
let _ = media_tx.send(MediaCommand::Cmd(
|
||||
slider.value() as u8,
|
||||
button.is_active(),
|
||||
false,
|
||||
));
|
||||
volume.connect_value_changed({
|
||||
let changed_progr = changed_progr.clone();
|
||||
move |slider, button, _| {
|
||||
if !changed_progr.get() {
|
||||
let _ = media_tx.send(MediaCommand::Cmd(
|
||||
slider.value() as u8,
|
||||
button.is_active(),
|
||||
false,
|
||||
));
|
||||
}
|
||||
}
|
||||
});
|
||||
let media_tx = channels.media_tx().unwrap();
|
||||
@@ -189,7 +191,7 @@ pub fn build(
|
||||
let network_button = Button::new();
|
||||
network_button.add_css_class("network");
|
||||
let network_button_content = gtk::Box::new(Orientation::Horizontal, 0);
|
||||
let network_icon = Image::from_file("../../icons/disconnected.svg");
|
||||
let network_icon = Image::from_file("icons/disconnected.svg");
|
||||
network_icon.add_css_class("network-icon");
|
||||
network_button_content.append(&network_icon);
|
||||
let network_label = Label::new(None);
|
||||
@@ -312,184 +314,188 @@ pub fn build(
|
||||
ControlFlow::Continue
|
||||
});
|
||||
|
||||
let media_rx = channels.take_media_rx().unwrap();
|
||||
let br_rx = channels.take_br_rx().unwrap();
|
||||
let net_rx = channels.take_net_rx().unwrap();
|
||||
let stats_rx = channels.take_stats_rx().unwrap();
|
||||
let netbird_rx = channels.take_netbird_rx().unwrap();
|
||||
let idle_rx = channels.take_idle_rx().unwrap();
|
||||
let monitor_clone = monitor.clone();
|
||||
let br_tx = channels.br_tx().unwrap();
|
||||
glib::timeout_add_local(Duration::from_millis(100), move || {
|
||||
while let Ok(command) = media_rx.try_recv() {
|
||||
if let MediaCommand::Cmd(vol, mute, bt) = command {
|
||||
changed_progr.set(true);
|
||||
volume.set_value(vol as f64);
|
||||
changed_progr.set(false);
|
||||
volume.show_scale(!mute);
|
||||
volume.imp().button.set_active(mute);
|
||||
if mute {
|
||||
volume.imp().button.add_css_class("muted");
|
||||
volume.imp().second_icon.add_child_css_class("muted");
|
||||
volume
|
||||
glib::timeout_add_local(Duration::from_millis(100), {
|
||||
let media_rx = channels.take_media_rx().unwrap();
|
||||
let br_rx = channels.take_br_rx().unwrap();
|
||||
let net_rx = channels.take_net_rx().unwrap();
|
||||
let stats_rx = channels.take_stats_rx().unwrap();
|
||||
let netbird_rx = channels.take_netbird_rx().unwrap();
|
||||
let idle_rx = channels.take_idle_rx().unwrap();
|
||||
let monitor = monitor.clone();
|
||||
let br_tx = channels.br_tx().unwrap();
|
||||
move || {
|
||||
while let Ok(command) = media_rx.try_recv() {
|
||||
if let MediaCommand::Cmd(vol, mute, bt) = command {
|
||||
changed_progr.set(true);
|
||||
volume.set_value(vol as f64);
|
||||
changed_progr.set(false);
|
||||
volume.show_scale(!mute);
|
||||
volume.imp().button.set_active(mute);
|
||||
if mute {
|
||||
volume.imp().button.add_css_class("muted");
|
||||
volume.imp().second_icon.add_child_css_class("muted");
|
||||
volume
|
||||
.imp()
|
||||
.main_icon
|
||||
.set_icon_path("icons/volume-mute.svg");
|
||||
} else {
|
||||
volume.imp().button.remove_css_class("muted");
|
||||
volume.imp().second_icon.remove_child_css_class("muted");
|
||||
let idx = ((volume.value() / 100.0) * (VOLUME_ICON_PATHS.len() - 1) as f64
|
||||
+ 0.5) as usize;
|
||||
volume.imp().main_icon.set_icon_path(VOLUME_ICON_PATHS[idx]);
|
||||
}
|
||||
volume.imp().second_icon.set_visible(bt);
|
||||
}
|
||||
}
|
||||
while let Ok(message) = br_rx.try_recv() {
|
||||
if let BrightnessMessage::Value(mon, value, _) = message
|
||||
&& monitor.eq(&mon)
|
||||
{
|
||||
brightness.set_value(value as f64);
|
||||
let idx = ((value as f32 / 100.0) * (BRIGHTNESS_ICONS.len() - 1) as f32 + 0.5)
|
||||
as usize;
|
||||
brightness
|
||||
.imp()
|
||||
.main_icon
|
||||
.set_icon_path("icons/volume-mute.svg");
|
||||
} else {
|
||||
volume.imp().button.remove_css_class("muted");
|
||||
volume.imp().second_icon.remove_child_css_class("muted");
|
||||
let idx = ((volume.value() / 100.0) * (VOLUME_ICON_PATHS.len() - 1) as f64
|
||||
+ 0.5) as usize;
|
||||
volume.imp().main_icon.set_icon_path(VOLUME_ICON_PATHS[idx]);
|
||||
.set_font_icon(BRIGHTNESS_ICONS[idx]);
|
||||
}
|
||||
volume.imp().second_icon.set_visible(bt);
|
||||
}
|
||||
}
|
||||
while let Ok(message) = br_rx.try_recv() {
|
||||
if let BrightnessMessage::Value(mon, value, _) = message
|
||||
&& monitor.eq(&mon)
|
||||
{
|
||||
brightness.set_value(value as f64);
|
||||
let idx =
|
||||
((value as f32 / 100.0) * (BRIGHTNESS_ICONS.len() - 1) as f32 + 0.5) as usize;
|
||||
brightness
|
||||
.imp()
|
||||
.main_icon
|
||||
.set_font_icon(BRIGHTNESS_ICONS[idx]);
|
||||
}
|
||||
}
|
||||
while let Ok(state) = idle_rx.try_recv() {
|
||||
match state {
|
||||
IdleState::Dimmed => {
|
||||
if !caffeine_button.has_css_class("idle") && monitor_valid {
|
||||
let _ =
|
||||
br_tx.send(BrightnessMessage::Value(monitor_clone.clone(), 20, true));
|
||||
brightness.set_value(20.0);
|
||||
while let Ok(state) = idle_rx.try_recv() {
|
||||
match state {
|
||||
IdleState::Dimmed => {
|
||||
if !caffeine_button.has_css_class("idle") && monitor_valid {
|
||||
let _ = br_tx.send(BrightnessMessage::Value(monitor.clone(), 20, true));
|
||||
brightness.set_value(20.0);
|
||||
}
|
||||
caffeine_button.add_css_class("idle");
|
||||
caffeine_button.remove_css_class("inhibit");
|
||||
caffeine_button.remove_css_class("on");
|
||||
}
|
||||
caffeine_button.add_css_class("idle");
|
||||
caffeine_button.remove_css_class("inhibit");
|
||||
caffeine_button.remove_css_class("on");
|
||||
}
|
||||
other => {
|
||||
caffeine_button.remove_css_class("idle");
|
||||
caffeine_button.remove_css_class("inhibit");
|
||||
caffeine_button.remove_css_class("on");
|
||||
if monitor_valid {
|
||||
let _ = br_tx.send(BrightnessMessage::Revert(monitor_clone.clone()));
|
||||
}
|
||||
match other {
|
||||
IdleState::Caffeine => caffeine_button.add_css_class("on"),
|
||||
IdleState::Inhibited => caffeine_button.add_css_class("inhibit"),
|
||||
IdleState::Active => {}
|
||||
_ => {}
|
||||
other => {
|
||||
caffeine_button.remove_css_class("idle");
|
||||
if monitor_valid {
|
||||
let _ = br_tx.send(BrightnessMessage::Revert(monitor.clone()));
|
||||
}
|
||||
match other {
|
||||
IdleState::Caffeine(true) => caffeine_button.add_css_class("on"),
|
||||
IdleState::Inhibited(true) => caffeine_button.add_css_class("inhibit"),
|
||||
IdleState::Caffeine(false) => caffeine_button.remove_css_class("on"),
|
||||
IdleState::Inhibited(false) => {
|
||||
caffeine_button.remove_css_class("inhibit")
|
||||
}
|
||||
IdleState::Active => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
while let Ok(result) = net_rx.try_recv() {
|
||||
trace!(&LOGGER, "NetworkResult: {:?}", result);
|
||||
match result {
|
||||
NetworkState::ChangeNetwork(cfg) => {
|
||||
match cfg {
|
||||
Some(network) => {
|
||||
match network.network_type {
|
||||
NetworkType::Wifi => {
|
||||
//let idx = ((20.0 / 100.0) * (WIFI_ICON_PATHS.len() - 1) as f32 + 0.5) as usize;
|
||||
let idx = ((network.signal as f32 / 100.0)
|
||||
* (WIFI_ICON_PATHS.len() - 1) as f32
|
||||
+ 0.5)
|
||||
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);
|
||||
while let Ok(result) = net_rx.try_recv() {
|
||||
trace!(&LOGGER, "NetworkResult: {:?}", result);
|
||||
match result {
|
||||
NetworkState::ChangeNetwork(cfg) => {
|
||||
match cfg {
|
||||
Some(network) => {
|
||||
match network.network_type {
|
||||
NetworkType::Wifi => {
|
||||
//let idx = ((20.0 / 100.0) * (WIFI_ICON_PATHS.len() - 1) as f32 + 0.5) as usize;
|
||||
let idx = ((network.signal as f32 / 100.0)
|
||||
* (WIFI_ICON_PATHS.len() - 1) as f32
|
||||
+ 0.5)
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
network_icon.set_from_file(Some("icons/disconnected.svg"));
|
||||
network_label.set_visible(false);
|
||||
None => {
|
||||
network_icon.set_from_file(Some("icons/disconnected.svg"));
|
||||
network_label.set_visible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
NetworkState::VpnState { connected, profile } if profile.as_str() == "wg" => {
|
||||
if connected {
|
||||
wg_button.add_css_class("on");
|
||||
} else {
|
||||
wg_button.remove_css_class("on");
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
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() {
|
||||
match tailscale_state {
|
||||
json_socket::State::Disconnected => {
|
||||
netbird_button.remove_css_class("on");
|
||||
netbird_button.remove_css_class("starting");
|
||||
netbird_button.remove_css_class("unknown");
|
||||
netbird_button.remove_css_class("login");
|
||||
}
|
||||
json_socket::State::Connecting => {
|
||||
netbird_button.remove_css_class("on");
|
||||
netbird_button.add_css_class("starting");
|
||||
netbird_button.remove_css_class("unknown");
|
||||
netbird_button.remove_css_class("login");
|
||||
}
|
||||
json_socket::State::Connected => {
|
||||
netbird_button.add_css_class("on");
|
||||
netbird_button.remove_css_class("starting");
|
||||
netbird_button.remove_css_class("unknown");
|
||||
netbird_button.remove_css_class("login");
|
||||
}
|
||||
json_socket::State::NeedsLogin => {
|
||||
netbird_button.remove_css_class("on");
|
||||
netbird_button.remove_css_class("starting");
|
||||
netbird_button.remove_css_class("unknown");
|
||||
netbird_button.add_css_class("login");
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
while let Ok(tailscale_state) = netbird_rx.try_recv() {
|
||||
match tailscale_state {
|
||||
json_socket::State::Disconnected => {
|
||||
netbird_button.remove_css_class("on");
|
||||
netbird_button.remove_css_class("starting");
|
||||
netbird_button.remove_css_class("unknown");
|
||||
netbird_button.remove_css_class("login");
|
||||
}
|
||||
json_socket::State::Connecting => {
|
||||
netbird_button.remove_css_class("on");
|
||||
netbird_button.add_css_class("starting");
|
||||
netbird_button.remove_css_class("unknown");
|
||||
netbird_button.remove_css_class("login");
|
||||
}
|
||||
json_socket::State::Connected => {
|
||||
netbird_button.add_css_class("on");
|
||||
netbird_button.remove_css_class("starting");
|
||||
netbird_button.remove_css_class("unknown");
|
||||
netbird_button.remove_css_class("login");
|
||||
}
|
||||
json_socket::State::NeedsLogin => {
|
||||
netbird_button.remove_css_class("on");
|
||||
netbird_button.remove_css_class("starting");
|
||||
netbird_button.remove_css_class("unknown");
|
||||
netbird_button.add_css_class("login");
|
||||
}
|
||||
if wg_button.has_css_class("on") || netbird_button.has_css_class("on") {
|
||||
vpn_button.add_css_class("on");
|
||||
vpn_button.remove_css_class("login");
|
||||
} else if wg_button.has_css_class("login") || netbird_button.has_css_class("login") {
|
||||
vpn_button.add_css_class("login");
|
||||
vpn_button.remove_css_class("on");
|
||||
} else {
|
||||
vpn_button.remove_css_class("on");
|
||||
vpn_button.remove_css_class("login");
|
||||
}
|
||||
}
|
||||
if wg_button.has_css_class("on") || netbird_button.has_css_class("on") {
|
||||
vpn_button.add_css_class("on");
|
||||
vpn_button.remove_css_class("login");
|
||||
} else if wg_button.has_css_class("login") || netbird_button.has_css_class("login") {
|
||||
vpn_button.add_css_class("login");
|
||||
vpn_button.remove_css_class("on");
|
||||
} else {
|
||||
vpn_button.remove_css_class("on");
|
||||
vpn_button.remove_css_class("login");
|
||||
}
|
||||
while let Ok(stats) = stats_rx.try_recv() {
|
||||
if let Some(cpu) = stats.cpu {
|
||||
cpu_label.set_label(&format!("{}%", cpu));
|
||||
}
|
||||
if let Some(ram) = stats.ram {
|
||||
ram_label.set_label(&format!("{}%", ram));
|
||||
}
|
||||
for disk in stats.disks {
|
||||
if disk.0.eq("/") {
|
||||
disk_label.set_label(&format!("{}%", disk.1));
|
||||
break;
|
||||
while let Ok(stats) = stats_rx.try_recv() {
|
||||
if let Some(cpu) = stats.cpu {
|
||||
cpu_label.set_label(&format!("{}%", cpu));
|
||||
}
|
||||
if let Some(ram) = stats.ram {
|
||||
ram_label.set_label(&format!("{}%", ram));
|
||||
}
|
||||
for disk in stats.disks {
|
||||
if disk.0.eq("/") {
|
||||
disk_label.set_label(&format!("{}%", disk.1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(gpu) = stats.gpus.into_iter().next() {
|
||||
gpu_label.set_label(&format!("{}%", gpu.1));
|
||||
}
|
||||
if let Some(gpu) = stats.gpus.into_iter().next() {
|
||||
gpu_label.set_label(&format!("{}%", gpu.1));
|
||||
}
|
||||
|
||||
set_battery_css_classes(&battery_widget, stats.battery_state, stats.battery_charge);
|
||||
let idx = ((stats.battery_charge as f32 / 100.0) * (BATTERY_ICONS.len() - 1) as f32
|
||||
+ 0.5) as usize;
|
||||
let idx = idx.min(BATTERY_ICONS.len() - 1);
|
||||
battery_widget.set_title(&format!("{} {}%", BATTERY_ICONS[idx], stats.battery_charge));
|
||||
wattage.set_label(&format!(" {:.1}W", stats.battery_draw));
|
||||
remaining_time.set_label(&format!(" {}h", format_remaining(stats.battery_time)));
|
||||
set_battery_css_classes(&battery_widget, stats.battery_state, stats.battery_charge);
|
||||
let idx = ((stats.battery_charge as f32 / 100.0) * (BATTERY_ICONS.len() - 1) as f32
|
||||
+ 0.5) as usize;
|
||||
let idx = idx.min(BATTERY_ICONS.len() - 1);
|
||||
battery_widget
|
||||
.set_title(&format!("{} {}%", BATTERY_ICONS[idx], stats.battery_charge));
|
||||
wattage.set_label(&format!(" {:.1}W", stats.battery_draw));
|
||||
remaining_time.set_label(&format!(" {}h", format_remaining(stats.battery_time)));
|
||||
}
|
||||
ControlFlow::Continue
|
||||
}
|
||||
ControlFlow::Continue
|
||||
});
|
||||
utilities
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user