hyprshell_exec_lib/
switch.rs

1use anyhow::Context;
2use core_lib::Warn;
3use hyprland::data::{Client, Monitors, Workspace, Workspaces};
4use hyprland::dispatch::{
5    Dispatch, DispatchType, WindowIdentifier, WorkspaceIdentifierWithSpecial,
6};
7use hyprland::prelude::*;
8use hyprland::shared::{Address, WorkspaceId};
9use tracing::{debug, trace};
10
11pub fn switch_client(address: Address) -> anyhow::Result<()> {
12    debug!("execute switch to client: {address}");
13    deactivate_special_workspace_if_needed().warn();
14    Dispatch::call(DispatchType::FocusWindow(WindowIdentifier::Address(
15        address,
16    )))?;
17    Dispatch::call(DispatchType::BringActiveToTop)?;
18    Ok(())
19}
20
21pub fn switch_client_by_initial_class(class: &str) -> anyhow::Result<()> {
22    debug!("execute switch to client: {class} by initial_class");
23    deactivate_special_workspace_if_needed().warn();
24    Dispatch::call(DispatchType::FocusWindow(
25        WindowIdentifier::ClassRegularExpression(&format!(
26            "initialclass:{}",
27            class.to_ascii_lowercase()
28        )),
29    ))?;
30    Dispatch::call(DispatchType::BringActiveToTop)?;
31    Ok(())
32}
33
34pub fn switch_workspace(workspace_id: WorkspaceId) -> anyhow::Result<()> {
35    deactivate_special_workspace_if_needed().warn();
36
37    // check if already on workspace (if so, don't switch because it throws an error `Previous workspace doesn't exist`)
38    let current_workspace = Workspace::get_active();
39    if let Ok(workspace) = current_workspace {
40        if workspace_id == workspace.id {
41            trace!("Already on workspace {}", workspace_id);
42            return Ok(());
43        }
44    }
45
46    if workspace_id < 0 {
47        switch_special_workspace(workspace_id).with_context(|| {
48            format!("Failed to execute switch special workspace with id {workspace_id}")
49        })?;
50    } else {
51        switch_normal_workspace(workspace_id).with_context(|| {
52            format!("Failed to execute switch workspace with id {workspace_id}")
53        })?;
54    }
55    Ok(())
56}
57
58fn switch_special_workspace(workspace_id: WorkspaceId) -> anyhow::Result<()> {
59    let special = Monitors::get()?
60        .into_iter()
61        .find(|m| m.special_workspace.id == workspace_id);
62    if let Some(special) = special {
63        trace!("Special workspace already toggled: {special:?}");
64        return Ok(());
65    }
66    let ws = Workspaces::get()?
67        .into_iter()
68        .find(|w| w.id == workspace_id)
69        .context("workspace not found")?;
70    Dispatch::call(DispatchType::ToggleSpecialWorkspace(Some(
71        ws.name.trim_start_matches("special:").to_string(),
72    )))
73    .context("failed to execute toggle special workspace")
74}
75
76fn switch_normal_workspace(workspace_id: WorkspaceId) -> anyhow::Result<()> {
77    debug!("execute switch to workspace {workspace_id}");
78    Dispatch::call(DispatchType::Workspace(WorkspaceIdentifierWithSpecial::Id(
79        workspace_id,
80    )))?;
81    Ok(())
82}
83
84/// always run when changing client or workspace
85///
86/// if client on special workspace is opened the workspace is activated
87fn deactivate_special_workspace_if_needed() -> anyhow::Result<()> {
88    let active_ws = Workspace::get_active()
89        .map(|w| w.name)
90        .context("active workspace failed")?;
91    let active_ws = Client::get_active()
92        .context("active client failed")?
93        .map_or(active_ws, |a| a.workspace.name);
94    trace!("current workspace: {active_ws}");
95    if active_ws.starts_with("special:") {
96        debug!("current client is on special workspace, deactivating special workspace");
97        // current client is on special workspace
98        Dispatch::call(DispatchType::ToggleSpecialWorkspace(Some(
99            active_ws.trim_start_matches("special:").to_string(),
100        )))?;
101    }
102    Ok(())
103}