1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2025-04-19 19:34:24 +02:00
ironbar/src/clients/compositor/hyprland.rs

290 lines
9.8 KiB
Rust
Raw Normal View History

2023-08-24 21:18:07 -05:00
use super::{Visibility, Workspace, WorkspaceClient, WorkspaceUpdate};
use crate::{arc_mut, lock, send, spawn_blocking};
use color_eyre::Result;
use hyprland::data::{Workspace as HWorkspace, Workspaces};
use hyprland::dispatch::{Dispatch, DispatchType, WorkspaceIdentifierWithSpecial};
use hyprland::event_listener::EventListener;
use hyprland::prelude::*;
2023-10-19 21:11:56 +01:00
use hyprland::shared::{HyprDataVec, WorkspaceType};
use tokio::sync::broadcast::{channel, Receiver, Sender};
2023-01-30 18:49:30 +00:00
use tracing::{debug, error, info};
#[derive(Debug)]
pub struct Client {
workspace_tx: Sender<WorkspaceUpdate>,
_workspace_rx: Receiver<WorkspaceUpdate>,
}
impl Client {
pub(crate) fn new() -> Self {
let (workspace_tx, workspace_rx) = channel(16);
let instance = Self {
workspace_tx,
_workspace_rx: workspace_rx,
};
instance.listen_workspace_events();
instance
}
fn listen_workspace_events(&self) {
info!("Starting Hyprland event listener");
let tx = self.workspace_tx.clone();
spawn_blocking(move || {
let mut event_listener = EventListener::new();
// we need a lock to ensure events don't run at the same time
let lock = arc_mut!(());
// cache the active workspace since Hyprland doesn't give us the prev active
let active = Self::get_active_workspace().expect("Failed to get active workspace");
let active = arc_mut!(Some(active));
{
let tx = tx.clone();
let lock = lock.clone();
let active = active.clone();
event_listener.add_workspace_added_handler(move |workspace_type| {
let _lock = lock!(lock);
2023-01-30 18:49:30 +00:00
debug!("Added workspace: {workspace_type:?}");
let workspace_name = get_workspace_name(workspace_type);
let prev_workspace = lock!(active);
2023-08-24 21:18:07 -05:00
let workspace = Self::get_workspace(&workspace_name, prev_workspace.as_ref());
if let Some(workspace) = workspace {
send!(tx, WorkspaceUpdate::Add(workspace));
}
});
}
{
let tx = tx.clone();
let lock = lock.clone();
let active = active.clone();
event_listener.add_workspace_change_handler(move |workspace_type| {
let _lock = lock!(lock);
let mut prev_workspace = lock!(active);
debug!(
"Received workspace change: {:?} -> {workspace_type:?}",
prev_workspace.as_ref().map(|w| &w.id)
);
let workspace_name = get_workspace_name(workspace_type);
2023-08-24 21:18:07 -05:00
let workspace = Self::get_workspace(&workspace_name, prev_workspace.as_ref());
2023-01-30 18:49:30 +00:00
workspace.map_or_else(
|| {
error!("Unable to locate workspace");
},
|workspace| {
// there may be another type of update so dispatch that regardless of focus change
2023-08-24 21:18:07 -05:00
if !workspace.visibility.is_focused() {
Self::send_focus_change(&mut prev_workspace, workspace, &tx);
}
},
);
});
}
{
let tx = tx.clone();
let lock = lock.clone();
let active = active.clone();
event_listener.add_active_monitor_change_handler(move |event_data| {
let _lock = lock!(lock);
let workspace_type = event_data.workspace;
let mut prev_workspace = lock!(active);
debug!(
"Received active monitor change: {:?} -> {workspace_type:?}",
prev_workspace.as_ref().map(|w| &w.name)
);
let workspace_name = get_workspace_name(workspace_type);
2023-08-24 21:18:07 -05:00
let workspace = Self::get_workspace(&workspace_name, prev_workspace.as_ref());
2023-08-24 21:18:07 -05:00
if let Some((false, workspace)) =
workspace.map(|w| (w.visibility.is_focused(), w))
{
Self::send_focus_change(&mut prev_workspace, workspace, &tx);
} else {
error!("Unable to locate workspace");
}
});
}
{
let tx = tx.clone();
let lock = lock.clone();
event_listener.add_workspace_moved_handler(move |event_data| {
let _lock = lock!(lock);
let workspace_type = event_data.workspace;
debug!("Received workspace move: {workspace_type:?}");
let mut prev_workspace = lock!(active);
let workspace_name = get_workspace_name(workspace_type);
2023-08-24 21:18:07 -05:00
let workspace = Self::get_workspace(&workspace_name, prev_workspace.as_ref());
if let Some(workspace) = workspace {
send!(tx, WorkspaceUpdate::Move(workspace.clone()));
2023-08-24 21:18:07 -05:00
if !workspace.visibility.is_focused() {
Self::send_focus_change(&mut prev_workspace, workspace, &tx);
}
}
});
}
{
event_listener.add_workspace_destroy_handler(move |workspace_type| {
let _lock = lock!(lock);
debug!("Received workspace destroy: {workspace_type:?}");
let name = get_workspace_name(workspace_type);
send!(tx, WorkspaceUpdate::Remove(name));
});
}
event_listener
.start_listener()
.expect("Failed to start listener");
});
}
/// Sends a `WorkspaceUpdate::Focus` event
/// and updates the active workspace cache.
fn send_focus_change(
prev_workspace: &mut Option<Workspace>,
workspace: Workspace,
tx: &Sender<WorkspaceUpdate>,
) {
send!(
tx,
WorkspaceUpdate::Focus {
2023-08-24 21:18:07 -05:00
old: prev_workspace.take(),
new: workspace.clone(),
}
);
prev_workspace.replace(workspace);
}
2023-08-24 21:18:07 -05:00
/// Gets a workspace by name from the server, given the active workspace if known.
fn get_workspace(name: &str, active: Option<&Workspace>) -> Option<Workspace> {
Workspaces::get()
.expect("Failed to get workspaces")
.into_iter()
.find_map(|w| {
if w.name == name {
2023-08-24 21:18:07 -05:00
let vis = Visibility::from((&w, active.map(|w| w.name.as_ref()), &|w| {
create_is_visible()(w)
}));
Some(Workspace::from((vis, w)))
} else {
None
}
})
}
/// Gets the active workspace from the server.
fn get_active_workspace() -> Result<Workspace> {
2023-08-24 21:18:07 -05:00
let w = HWorkspace::get_active().map(|w| Workspace::from((Visibility::focused(), w)))?;
Ok(w)
}
}
impl WorkspaceClient for Client {
fn focus(&self, id: String) -> Result<()> {
let identifier = id.parse::<i32>().map_or_else(
|_| WorkspaceIdentifierWithSpecial::Name(&id),
WorkspaceIdentifierWithSpecial::Id,
);
Dispatch::call(DispatchType::Workspace(identifier))?;
Ok(())
}
fn subscribe_workspace_change(&self) -> Receiver<WorkspaceUpdate> {
let rx = self.workspace_tx.subscribe();
{
let tx = self.workspace_tx.clone();
2023-08-24 21:18:07 -05:00
let active_id = HWorkspace::get_active().ok().map(|active| active.name);
let is_visible = create_is_visible();
let workspaces = Workspaces::get()
.expect("Failed to get workspaces")
.into_iter()
2023-08-24 21:18:07 -05:00
.map(|w| {
let vis = Visibility::from((&w, active_id.as_deref(), &is_visible));
Workspace::from((vis, w))
})
.collect();
send!(tx, WorkspaceUpdate::Init(workspaces));
}
rx
}
}
fn get_workspace_name(name: WorkspaceType) -> String {
match name {
2023-01-28 15:41:52 +00:00
WorkspaceType::Regular(name) => name,
WorkspaceType::Special(name) => name.unwrap_or_default(),
}
}
2023-10-19 21:11:56 +01:00
/// Creates a function which determines if a workspace is visible.
///
/// This function makes a Hyprland call that allocates so it should be cached when possible,
/// but it is only valid so long as workspaces do not change so it should not be stored long term
2023-08-24 21:18:07 -05:00
fn create_is_visible() -> impl Fn(&HWorkspace) -> bool {
2023-10-19 21:11:56 +01:00
let monitors = hyprland::data::Monitors::get().map_or(Vec::new(), HyprDataVec::to_vec);
2023-08-24 21:18:07 -05:00
move |w| monitors.iter().any(|m| m.active_workspace.id == w.id)
}
impl From<(Visibility, HWorkspace)> for Workspace {
fn from((visibility, workspace): (Visibility, HWorkspace)) -> Self {
Self {
2023-01-28 15:41:52 +00:00
id: workspace.id.to_string(),
name: workspace.name,
monitor: workspace.monitor,
2023-08-24 21:18:07 -05:00
visibility,
}
}
}
impl<'a, 'f, F> From<(&'a HWorkspace, Option<&str>, F)> for Visibility
where
F: FnOnce(&'f HWorkspace) -> bool,
'a: 'f,
{
fn from((workspace, active_name, is_visible): (&'a HWorkspace, Option<&str>, F)) -> Self {
if Some(workspace.name.as_str()) == active_name {
Self::focused()
} else if is_visible(workspace) {
Self::visible()
} else {
Self::Hidden
}
}
}