use anyhow::Result;
use clap::Parser;
use crate::args::Args;
use crate::connection::HyprlandClient;
use crate::domain::{Direction, OwnedMonitor, OwnedWorkspace};
pub struct HyprCycle {
connection: Box<dyn HyprlandClient>,
}
impl HyprCycle {
pub fn parse_args() -> Args {
Args::parse()
}
pub fn new(connection: Box<dyn HyprlandClient>) -> HyprCycle {
HyprCycle { connection }
}
pub fn real() -> Result<HyprCycle> {
let conn = hyprrust::HyprlandConnection::current().map_err(anyhow::Error::new)?;
let client = crate::connection::RealHyprlandClient::new(conn);
Ok(HyprCycle::new(Box::new(client)))
}
pub fn get_focused_monitor(&self) -> Result<OwnedMonitor> {
let monitors = self.connection.get_monitors()?;
let monitor = monitors
.into_iter()
.find(|m| m.focused())
.ok_or_else(|| anyhow::anyhow!("No focused monitor found"))?;
Ok(monitor)
}
pub fn get_workspaces_for_monitor(
&self,
monitor: &OwnedMonitor,
) -> Result<Vec<OwnedWorkspace>> {
let workspaces = self.connection.get_workspaces()?;
let mut workspaces_for_monitor: Vec<OwnedWorkspace> = workspaces
.into_iter()
.filter(|w| w.monitor_name().eq(monitor.name()) && w.visible())
.collect();
if workspaces_for_monitor.is_empty() {
return Err(anyhow::anyhow!(
"No workspaces found for monitor: {}",
monitor.name()
));
}
workspaces_for_monitor.sort();
Ok(workspaces_for_monitor)
}
pub fn get_current_workspace(&self) -> Result<OwnedWorkspace> {
let focused_monitor = self.get_focused_monitor()?;
let active_workspace = focused_monitor.active_workspace();
Ok(active_workspace)
}
pub fn get_target_workspace(&self, direction: Direction) -> Result<OwnedWorkspace> {
let monitor = &self.get_focused_monitor()?;
let workspaces = &self.get_workspaces_for_monitor(monitor)?;
let current_ws = &self.get_current_workspace()?;
let idx = workspaces
.iter()
.position(|w| w == current_ws)
.ok_or_else(|| anyhow::anyhow!("Current workspace not found"))?;
let len = workspaces.len();
let next_idx = match direction {
Direction::Next => (idx + 1) % len,
Direction::Previous => (idx + len - 1) % len,
};
Ok(workspaces[next_idx].to_owned())
}
pub fn switch_to_workspace(&self, target: &OwnedWorkspace) -> Result<()> {
self.connection.go_to_workspace(target.id())?;
Ok(())
}
}
#[cfg(test)]
pub mod fixtures {
use crate::domain::{OwnedMonitor, OwnedWorkspace};
pub fn ws(id: i64, mon: &str) -> OwnedWorkspace {
OwnedWorkspace::new(id, mon.to_string())
}
pub fn mon(name: &str, id: i64, focused: bool, active_id: i64) -> OwnedMonitor {
OwnedMonitor::new(name.to_string(), id, focused, ws(active_id, name))
}
pub fn monitors() -> Vec<OwnedMonitor> {
vec![
mon("eDP-1", 1, true, 1), mon("HDMI-1", 2, false, 3),
]
}
pub fn workspaces() -> Vec<OwnedWorkspace> {
vec![
ws(-97, "eDP-1"), ws(1, "eDP-1"),
ws(2, "eDP-1"),
ws(3, "HDMI-1"),
]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::connection::MockHyprlandClient;
mod helpers {
use super::super::*;
use crate::connection::MockHyprlandClient;
use anyhow::Context;
pub fn mock_service_with(conn: MockHyprlandClient) -> HyprCycle {
HyprCycle::new(Box::new(conn))
}
pub fn mock_service() -> HyprCycle {
let mut conn = MockHyprlandClient::new();
conn.expect_get_monitors()
.returning(|| Ok(fixtures::monitors()));
conn.expect_get_workspaces()
.returning(|| Ok(fixtures::workspaces()));
mock_service_with(conn)
}
pub fn visible_for_monitor(
ws: Vec<OwnedWorkspace>,
monitor: &OwnedMonitor,
) -> Vec<OwnedWorkspace> {
ws.into_iter()
.filter(|w| w.visible() && w.monitor_name() == monitor.name())
.collect()
}
pub fn focused_monitor(monitors: Vec<OwnedMonitor>) -> Result<OwnedMonitor> {
monitors
.into_iter()
.find(|m| m.focused())
.context("No focused monitor found")
}
}
#[test]
fn test_get_focused_monitor() -> Result<()> {
let expected = helpers::focused_monitor(fixtures::monitors())?;
let returned = helpers::mock_service().get_focused_monitor()?;
assert_eq!(returned.name(), expected.name());
Ok(())
}
#[test]
fn test_get_workspaces_for_monitor() -> Result<()> {
let target_monitor = &fixtures::monitors()[0];
let returned_workspaces =
helpers::mock_service().get_workspaces_for_monitor(target_monitor)?;
assert!(returned_workspaces.iter().all(|w| w.visible()));
assert!(returned_workspaces
.iter()
.all(|w| w.monitor_name() == target_monitor.name()));
let expected_workspaces =
helpers::visible_for_monitor(fixtures::workspaces(), target_monitor);
assert_eq!(expected_workspaces, returned_workspaces);
Ok(())
}
#[test]
fn test_get_current_workspace() -> Result<()> {
let expected = helpers::focused_monitor(fixtures::monitors())?;
let returned = helpers::mock_service().get_current_workspace()?;
assert_eq!(returned.id(), expected.active_workspace().id());
Ok(())
}
#[test]
fn test_switch_to_workspace() -> Result<()> {
let mut conn = MockHyprlandClient::new();
conn.expect_go_to_workspace().times(1).returning(|_| Ok(()));
helpers::mock_service_with(conn).switch_to_workspace(&fixtures::workspaces()[0])
}
}