hyprshell_exec_lib/
switch.rs1use 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, warn};
11
12#[instrument(level = "debug", ret(level = "trace"))]
13pub fn switch_client(address: ClientId) -> anyhow::Result<()> {
14 match switch_client_lua(address) {
15 Err(e) => {
16 warn!("Failed to switch to client: {}, trying legacy syntax", e);
17 switch_client_legacy(address)
18 }
19 Ok(r) => Ok(r),
20 }
21}
22
23fn switch_client_legacy(address: ClientId) -> anyhow::Result<()> {
24 debug!("execute switch to client: {address}");
25 deactivate_special_workspace_if_needed().warn();
26 Dispatch::call(DispatchType::FocusWindow(WindowIdentifier::Address(
27 to_client_address(address),
28 )))
29 .context("failed to execute dispatch")?;
30
31 Dispatch::call(DispatchType::BringActiveToTop).context("failed to execute dispatch2")?;
32 Ok(())
33}
34
35fn switch_client_lua(address: ClientId) -> anyhow::Result<()> {
36 debug!("execute switch to client: {address}");
37 deactivate_special_workspace_if_needed().warn();
38 let disp = hyprland::dispatch_new::Dispatch::FocusWindow(
39 hyprland::dispatch_new::WindowIdentifier::Address(to_client_address(address)),
40 );
41 disp.apply().context("failed to execute dispatch")?;
42 let disp2 = hyprland::dispatch_new::Dispatch::WindowAlterZ(
43 hyprland::dispatch_new::ZOption::Top,
44 Some(hyprland::dispatch_new::WindowIdentifier::Address(
45 to_client_address(address),
46 )),
47 );
48 disp2.apply().context("failed to execute dispatch2")?;
49 Ok(())
50}
51
52#[instrument(level = "debug", ret(level = "trace"))]
53pub fn switch_client_by_initial_class(class: &str) -> anyhow::Result<()> {
54 match switch_client_by_initial_class_lua(class) {
55 Err(e) => {
56 warn!("Failed to switch to client: {}, trying legacy syntax", e);
57 switch_client_by_initial_class_legacy(class)
58 }
59 Ok(r) => Ok(r),
60 }
61}
62
63fn switch_client_by_initial_class_lua(class: &str) -> anyhow::Result<()> {
64 debug!("execute switch to client: {class} by initial_class");
65 deactivate_special_workspace_if_needed().warn();
66
67 let disp = hyprland::dispatch_new::Dispatch::FocusWindow(
68 hyprland::dispatch_new::WindowIdentifier::InitialClassRegularExpression(
69 class.to_ascii_lowercase(),
70 ),
71 );
72 disp.apply().context("failed to execute dispatch")?;
73 let disp2 = hyprland::dispatch_new::Dispatch::WindowAlterZ(
74 hyprland::dispatch_new::ZOption::Top,
75 Some(
76 hyprland::dispatch_new::WindowIdentifier::InitialClassRegularExpression(
77 class.to_ascii_lowercase(),
78 ),
79 ),
80 );
81 disp2.apply().context("failed to execute dispatch2")?;
82 Ok(())
83}
84
85fn switch_client_by_initial_class_legacy(class: &str) -> anyhow::Result<()> {
86 debug!("execute switch to client: {class} by initial_class");
87 deactivate_special_workspace_if_needed().warn();
88 Dispatch::call(DispatchType::FocusWindow(
89 WindowIdentifier::ClassRegularExpression(&format!(
90 "initialclass:{}",
91 class.to_ascii_lowercase()
92 )),
93 ))?;
94 Dispatch::call(DispatchType::BringActiveToTop)?;
95 Ok(())
96}
97
98#[instrument(level = "debug", ret(level = "trace"))]
99pub fn switch_workspace(workspace_id: WorkspaceId) -> anyhow::Result<()> {
100 deactivate_special_workspace_if_needed().warn();
101
102 let current_workspace = Workspace::get_active();
104 if let Ok(workspace) = current_workspace
105 && workspace_id == workspace.id
106 {
107 trace!("Already on workspace {}", workspace_id);
108 return Ok(());
109 }
110
111 if workspace_id < 0 {
112 switch_special_workspace(workspace_id).with_context(|| {
113 format!("Failed to execute switch special workspace with id {workspace_id}")
114 })?;
115 } else {
116 switch_normal_workspace(workspace_id).with_context(|| {
117 format!("Failed to execute switch workspace with id {workspace_id}")
118 })?;
119 }
120 Ok(())
121}
122
123#[instrument(level = "debug", ret(level = "trace"))]
124fn switch_special_workspace(workspace_id: WorkspaceId) -> anyhow::Result<()> {
125 match switch_special_workspace_lua(workspace_id) {
126 Err(e) => {
127 warn!(
128 "Failed to switch special workspace: {}, trying legacy syntax",
129 e
130 );
131 switch_special_workspace_legacy(workspace_id)
132 }
133 Ok(r) => Ok(r),
134 }
135}
136
137fn switch_special_workspace_legacy(workspace_id: WorkspaceId) -> anyhow::Result<()> {
138 let special = Monitors::get()?
139 .into_iter()
140 .find(|m| m.special_workspace.id == workspace_id);
141 if let Some(special) = special {
142 trace!("Special workspace already toggled: {special:?}");
143 return Ok(());
144 }
145 let ws = Workspaces::get()?
146 .into_iter()
147 .find(|w| w.id == workspace_id)
148 .context("workspace not found")?;
149
150 Dispatch::call(DispatchType::ToggleSpecialWorkspace(Some(
151 ws.name.trim_start_matches("special:").to_string(),
152 )))
153 .context("failed to execute dispatch")?;
154 Ok(())
155}
156
157fn switch_special_workspace_lua(workspace_id: WorkspaceId) -> anyhow::Result<()> {
158 let special = Monitors::get()?
159 .into_iter()
160 .find(|m| m.special_workspace.id == workspace_id);
161 if let Some(special) = special {
162 trace!("Special workspace already toggled: {special:?}");
163 return Ok(());
164 }
165 let ws = Workspaces::get()?
166 .into_iter()
167 .find(|w| w.id == workspace_id)
168 .context("workspace not found")?;
169
170 let disp = hyprland::dispatch_new::Dispatch::FocusWorkspace(
171 hyprland::dispatch_new::WorkspaceIdentifier::Special(Some(
172 ws.name.trim_start_matches("special:").to_string(),
173 )),
174 false,
175 );
176 disp.apply().context("failed to execute dispatch")?;
177 Ok(())
178}
179
180#[instrument(level = "debug", ret(level = "trace"))]
181fn switch_normal_workspace(workspace_id: WorkspaceId) -> anyhow::Result<()> {
182 match switch_normal_workspace_lua(workspace_id) {
183 Err(e) => {
184 warn!("Failed to switch workspace: {}, trying legacy syntax", e);
185 switch_normal_workspace_legacy(workspace_id)
186 }
187 Ok(r) => Ok(r),
188 }
189}
190
191fn switch_normal_workspace_lua(workspace_id: WorkspaceId) -> anyhow::Result<()> {
192 debug!("execute switch to workspace {workspace_id}");
193 let disp = hyprland::dispatch_new::Dispatch::FocusWorkspace(
194 hyprland::dispatch_new::WorkspaceIdentifier::Id(workspace_id),
195 false,
196 );
197 disp.apply().context("failed to execute dispatch")?;
198 Ok(())
199}
200
201fn switch_normal_workspace_legacy(workspace_id: WorkspaceId) -> anyhow::Result<()> {
202 debug!("execute switch to workspace {workspace_id}");
203 Dispatch::call(DispatchType::Workspace(WorkspaceIdentifierWithSpecial::Id(
204 workspace_id,
205 )))
206 .context("failed to execute dispatch")?;
207 Ok(())
208}
209
210#[instrument(level = "debug", ret(level = "trace"))]
214fn deactivate_special_workspace_if_needed() -> anyhow::Result<()> {
215 let active_ws = Workspace::get_active()
216 .map(|w| w.name)
217 .context("active workspace failed")?;
218 let active_ws = Client::get_active()
219 .context("active client failed")?
220 .map_or(active_ws, |a| a.workspace.name);
221 trace!("current workspace: {active_ws}");
222 if active_ws.starts_with("special:") {
223 debug!("current client is on special workspace, deactivating special workspace");
224 match deactivate_special_workspace_if_needed_lua(&active_ws) {
226 Err(e) => {
227 warn!(
228 "Failed to deactivate special workspace: {}, trying legacy syntax",
229 e
230 );
231 deactivate_special_workspace_if_needed_legacy(&active_ws)
232 }
233 Ok(r) => Ok(r),
234 }?;
235 }
236 Ok(())
237}
238
239fn deactivate_special_workspace_if_needed_lua(name: &str) -> anyhow::Result<()> {
240 debug!("execute switch to workspace {name}");
241 let disp = hyprland::dispatch_new::Dispatch::FocusWorkspace(
242 hyprland::dispatch_new::WorkspaceIdentifier::Special(Some(
243 name.trim_start_matches("special:").to_string(),
244 )),
245 false,
246 );
247 disp.apply().context("failed to execute dispatch")?;
248 Ok(())
249}
250
251fn deactivate_special_workspace_if_needed_legacy(name: &str) -> anyhow::Result<()> {
252 debug!("execute switch to workspace {name}");
253 Dispatch::call(DispatchType::ToggleSpecialWorkspace(Some(
254 name.trim_start_matches("special:").to_string(),
255 )))?;
256 Ok(())
257}