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