Skip to main content

hyprshell_exec_lib/
switch.rs

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