2023-08-24 21:18:07 -05:00
|
|
|
use super::{Visibility, Workspace, WorkspaceClient, WorkspaceUpdate};
|
2023-12-17 23:51:43 +00:00
|
|
|
use crate::{arc_mut, lock, send, spawn_blocking};
|
2023-01-30 21:56:41 +00:00
|
|
|
use color_eyre::Result;
|
2023-01-27 20:08:14 +00:00
|
|
|
use hyprland::data::{Workspace as HWorkspace, Workspaces};
|
|
|
|
use hyprland::dispatch::{Dispatch, DispatchType, WorkspaceIdentifierWithSpecial};
|
2023-08-11 21:15:45 +01:00
|
|
|
use hyprland::event_listener::EventListener;
|
2023-01-27 20:08:14 +00:00
|
|
|
use hyprland::prelude::*;
|
2023-10-19 21:11:56 +01:00
|
|
|
use hyprland::shared::{HyprDataVec, WorkspaceType};
|
2023-01-27 20:08:14 +00:00
|
|
|
use tokio::sync::broadcast::{channel, Receiver, Sender};
|
2023-01-30 18:49:30 +00:00
|
|
|
use tracing::{debug, error, info};
|
2023-01-27 20:08:14 +00:00
|
|
|
|
2024-01-07 23:50:10 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Client {
|
2023-01-27 20:08:14 +00:00
|
|
|
workspace_tx: Sender<WorkspaceUpdate>,
|
|
|
|
_workspace_rx: Receiver<WorkspaceUpdate>,
|
|
|
|
}
|
|
|
|
|
2024-01-07 23:50:10 +00:00
|
|
|
impl Client {
|
|
|
|
pub(crate) fn new() -> Self {
|
2023-01-27 20:08:14 +00:00
|
|
|
let (workspace_tx, workspace_rx) = channel(16);
|
|
|
|
|
2024-01-07 23:50:10 +00:00
|
|
|
let instance = Self {
|
2023-01-27 20:08:14 +00:00
|
|
|
workspace_tx,
|
|
|
|
_workspace_rx: workspace_rx,
|
2024-01-07 23:50:10 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
instance.listen_workspace_events();
|
|
|
|
instance
|
2023-01-27 20:08:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
2023-01-30 21:56:41 +00:00
|
|
|
// we need a lock to ensure events don't run at the same time
|
2023-06-29 23:16:31 +01:00
|
|
|
let lock = arc_mut!(());
|
2023-01-30 21:56:41 +00:00
|
|
|
|
|
|
|
// 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");
|
2023-06-29 23:16:31 +01:00
|
|
|
let active = arc_mut!(Some(active));
|
2023-01-30 21:56:41 +00:00
|
|
|
|
2023-01-27 20:08:14 +00:00
|
|
|
{
|
|
|
|
let tx = tx.clone();
|
2023-01-30 21:56:41 +00:00
|
|
|
let lock = lock.clone();
|
|
|
|
let active = active.clone();
|
2023-01-27 20:08:14 +00:00
|
|
|
|
2023-08-11 21:15:45 +01:00
|
|
|
event_listener.add_workspace_added_handler(move |workspace_type| {
|
2023-01-30 21:56:41 +00:00
|
|
|
let _lock = lock!(lock);
|
2023-01-30 18:49:30 +00:00
|
|
|
debug!("Added workspace: {workspace_type:?}");
|
|
|
|
|
2023-01-30 21:56:41 +00:00
|
|
|
let workspace_name = get_workspace_name(workspace_type);
|
|
|
|
let prev_workspace = lock!(active);
|
2023-01-27 20:08:14 +00:00
|
|
|
|
2023-08-24 21:18:07 -05:00
|
|
|
let workspace = Self::get_workspace(&workspace_name, prev_workspace.as_ref());
|
2023-01-30 21:56:41 +00:00
|
|
|
|
|
|
|
if let Some(workspace) = workspace {
|
|
|
|
send!(tx, WorkspaceUpdate::Add(workspace));
|
|
|
|
}
|
2023-01-27 20:08:14 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
let tx = tx.clone();
|
2023-01-30 21:56:41 +00:00
|
|
|
let lock = lock.clone();
|
|
|
|
let active = active.clone();
|
2023-01-27 20:08:14 +00:00
|
|
|
|
2023-08-11 21:15:45 +01:00
|
|
|
event_listener.add_workspace_change_handler(move |workspace_type| {
|
2023-01-30 21:56:41 +00:00
|
|
|
let _lock = lock!(lock);
|
2023-01-27 20:08:14 +00:00
|
|
|
|
2023-01-30 21:56:41 +00:00
|
|
|
let mut prev_workspace = lock!(active);
|
2023-01-27 20:08:14 +00:00
|
|
|
|
2023-01-30 21:56:41 +00:00
|
|
|
debug!(
|
|
|
|
"Received workspace change: {:?} -> {workspace_type:?}",
|
|
|
|
prev_workspace.as_ref().map(|w| &w.id)
|
|
|
|
);
|
2023-01-27 20:08:14 +00:00
|
|
|
|
2023-01-30 21:56:41 +00:00
|
|
|
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
|
|
|
|
2023-01-27 20:08:14 +00:00
|
|
|
workspace.map_or_else(
|
2023-01-30 21:56:41 +00:00
|
|
|
|| {
|
|
|
|
error!("Unable to locate workspace");
|
|
|
|
},
|
2023-01-27 20:08:14 +00:00
|
|
|
|workspace| {
|
2023-01-30 21:56:41 +00:00
|
|
|
// 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() {
|
2023-01-30 21:56:41 +00:00
|
|
|
Self::send_focus_change(&mut prev_workspace, workspace, &tx);
|
|
|
|
}
|
2023-01-27 20:08:14 +00:00
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
let tx = tx.clone();
|
2023-01-30 21:56:41 +00:00
|
|
|
let lock = lock.clone();
|
|
|
|
let active = active.clone();
|
2023-01-27 20:08:14 +00:00
|
|
|
|
2023-08-11 21:15:45 +01:00
|
|
|
event_listener.add_active_monitor_change_handler(move |event_data| {
|
2023-01-30 21:56:41 +00:00
|
|
|
let _lock = lock!(lock);
|
2023-08-11 21:15:45 +01:00
|
|
|
let workspace_type = event_data.workspace;
|
2023-01-27 20:08:14 +00:00
|
|
|
|
2023-01-30 21:56:41 +00:00
|
|
|
let mut prev_workspace = lock!(active);
|
2023-01-27 20:08:14 +00:00
|
|
|
|
2023-01-30 21:56:41 +00:00
|
|
|
debug!(
|
|
|
|
"Received active monitor change: {:?} -> {workspace_type:?}",
|
|
|
|
prev_workspace.as_ref().map(|w| &w.name)
|
2023-01-27 20:08:14 +00:00
|
|
|
);
|
2023-01-30 21:56:41 +00:00
|
|
|
|
|
|
|
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 21:56:41 +00:00
|
|
|
|
2023-08-24 21:18:07 -05:00
|
|
|
if let Some((false, workspace)) =
|
|
|
|
workspace.map(|w| (w.visibility.is_focused(), w))
|
|
|
|
{
|
2023-01-30 21:56:41 +00:00
|
|
|
Self::send_focus_change(&mut prev_workspace, workspace, &tx);
|
|
|
|
} else {
|
|
|
|
error!("Unable to locate workspace");
|
|
|
|
}
|
2023-01-27 20:08:14 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
2023-01-30 21:56:41 +00:00
|
|
|
let tx = tx.clone();
|
|
|
|
let lock = lock.clone();
|
2023-01-27 20:08:14 +00:00
|
|
|
|
2023-08-11 21:15:45 +01:00
|
|
|
event_listener.add_workspace_moved_handler(move |event_data| {
|
2023-01-30 21:56:41 +00:00
|
|
|
let _lock = lock!(lock);
|
2023-08-11 21:15:45 +01:00
|
|
|
let workspace_type = event_data.workspace;
|
2023-01-30 21:56:41 +00:00
|
|
|
debug!("Received workspace move: {workspace_type:?}");
|
2023-01-27 20:08:14 +00:00
|
|
|
|
2023-01-30 21:56:41 +00:00
|
|
|
let mut prev_workspace = lock!(active);
|
2023-01-27 20:08:14 +00:00
|
|
|
|
2023-01-30 21:56:41 +00:00
|
|
|
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-27 20:08:14 +00:00
|
|
|
|
2023-01-30 21:56:41 +00:00
|
|
|
if let Some(workspace) = workspace {
|
|
|
|
send!(tx, WorkspaceUpdate::Move(workspace.clone()));
|
2023-01-27 20:08:14 +00:00
|
|
|
|
2023-08-24 21:18:07 -05:00
|
|
|
if !workspace.visibility.is_focused() {
|
2023-01-30 21:56:41 +00:00
|
|
|
Self::send_focus_change(&mut prev_workspace, workspace, &tx);
|
2023-01-27 20:08:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-01-30 21:56:41 +00:00
|
|
|
{
|
2023-08-11 21:15:45 +01:00
|
|
|
event_listener.add_workspace_destroy_handler(move |workspace_type| {
|
2023-01-30 21:56:41 +00:00
|
|
|
let _lock = lock!(lock);
|
|
|
|
debug!("Received workspace destroy: {workspace_type:?}");
|
|
|
|
|
|
|
|
let name = get_workspace_name(workspace_type);
|
|
|
|
send!(tx, WorkspaceUpdate::Remove(name));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-01-27 20:08:14 +00:00
|
|
|
event_listener
|
|
|
|
.start_listener()
|
|
|
|
.expect("Failed to start listener");
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-01-30 21:56:41 +00:00
|
|
|
/// 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(),
|
2023-01-30 21:56:41 +00:00
|
|
|
}
|
|
|
|
);
|
2023-01-27 20:08:14 +00:00
|
|
|
|
2023-01-30 21:56:41 +00:00
|
|
|
prev_workspace.replace(workspace);
|
2023-01-27 20:08:14 +00:00
|
|
|
}
|
|
|
|
|
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> {
|
2023-01-30 21:56:41 +00:00
|
|
|
Workspaces::get()
|
|
|
|
.expect("Failed to get workspaces")
|
2024-05-03 15:39:09 -05:00
|
|
|
.into_iter()
|
2023-01-30 21:56:41 +00:00
|
|
|
.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)))
|
2023-01-30 21:56:41 +00:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
2023-01-27 20:08:14 +00:00
|
|
|
}
|
|
|
|
|
2023-01-30 21:56:41 +00:00
|
|
|
/// 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)))?;
|
2023-01-30 21:56:41 +00:00
|
|
|
Ok(w)
|
2023-01-27 20:08:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-07 23:50:10 +00:00
|
|
|
impl WorkspaceClient for Client {
|
2023-01-30 21:56:41 +00:00
|
|
|
fn focus(&self, id: String) -> Result<()> {
|
2024-02-01 22:39:43 +00:00
|
|
|
let identifier = id.parse::<i32>().map_or_else(
|
|
|
|
|_| WorkspaceIdentifierWithSpecial::Name(&id),
|
|
|
|
WorkspaceIdentifierWithSpecial::Id,
|
|
|
|
);
|
2023-08-11 21:15:45 +01:00
|
|
|
|
|
|
|
Dispatch::call(DispatchType::Workspace(identifier))?;
|
2023-01-27 20:08:14 +00:00
|
|
|
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();
|
2023-01-27 20:08:14 +00:00
|
|
|
|
2023-01-30 21:56:41 +00:00
|
|
|
let workspaces = Workspaces::get()
|
|
|
|
.expect("Failed to get workspaces")
|
2024-05-03 15:39:09 -05:00
|
|
|
.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))
|
|
|
|
})
|
2023-01-30 21:56:41 +00:00
|
|
|
.collect();
|
2023-01-27 20:08:14 +00:00
|
|
|
|
2023-01-30 21:56:41 +00:00
|
|
|
send!(tx, WorkspaceUpdate::Init(workspaces));
|
2023-01-27 20:08:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
rx
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-30 21:56:41 +00:00
|
|
|
fn get_workspace_name(name: WorkspaceType) -> String {
|
|
|
|
match name {
|
2023-01-28 15:41:52 +00:00
|
|
|
WorkspaceType::Regular(name) => name,
|
2023-01-27 20:08:14 +00:00
|
|
|
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 {
|
2023-01-27 20:08:14 +00:00
|
|
|
Self {
|
2023-01-28 15:41:52 +00:00
|
|
|
id: workspace.id.to_string(),
|
2023-01-27 20:08:14 +00:00
|
|
|
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
|
2023-01-27 20:08:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|