hyprshell_exec_lib/
util.rs

1use anyhow::Context;
2use core_lib::{Active, ClientId};
3use hyprland::ctl::{Color, notify, reload};
4use hyprland::data::{Client, Clients, Monitor, Monitors, Workspace};
5use hyprland::keyword::Keyword;
6use hyprland::prelude::*;
7use std::sync::{Mutex, OnceLock};
8use tracing::{debug, trace};
9
10pub fn get_clients() -> Vec<Client> {
11    Clients::get().map_or(vec![], |clients| clients.to_vec())
12}
13
14pub fn get_monitors() -> Vec<Monitor> {
15    Monitors::get().map_or(vec![], |monitors| monitors.to_vec())
16}
17
18pub fn get_current_monitor() -> Option<Monitor> {
19    Monitor::get_active().ok()
20}
21
22pub fn reload_hyprland_config() -> anyhow::Result<()> {
23    debug!("Reloading hyprland config");
24    reload::call().context("Failed to reload hyprland config")
25}
26
27pub fn toast(body: &str) {
28    let _ = notify::call(
29        notify::Icon::Warning,
30        std::time::Duration::from_secs(10),
31        Color::new(255, 0, 0, 255),
32        format!("hyprshell Error: {}", body),
33    );
34}
35
36/// trim 0x from hexadecimal (base-16) string and convert to id
37pub fn to_client_id(id: &hyprland::shared::Address) -> ClientId {
38    u64::from_str_radix(id.to_string().trim_start_matches("0x"), 16)
39        .expect("Failed to parse client id, this should never happen")
40}
41/// convert id to hexadecimal (base-16) string
42pub fn to_client_address(id: ClientId) -> hyprland::shared::Address {
43    hyprland::shared::Address::new(format!("{:x}", id))
44}
45
46fn get_prev_follow_mouse() -> &'static Mutex<Option<String>> {
47    static PREV_FOLLOW_MOUSE: OnceLock<Mutex<Option<String>>> = OnceLock::new();
48    PREV_FOLLOW_MOUSE.get_or_init(|| Mutex::new(None))
49}
50
51fn get_gestures_enabled() -> &'static Mutex<Option<bool>> {
52    static GESTURES_ENABLED: OnceLock<Mutex<Option<bool>>> = OnceLock::new();
53    GESTURES_ENABLED.get_or_init(|| Mutex::new(None))
54}
55
56pub fn set_remain_focused() -> anyhow::Result<()> {
57    let mut lock = get_prev_follow_mouse()
58        .lock()
59        .map_err(|e| anyhow::anyhow!("unable to lock get_prev_follow_mouse mutex: {}", e))?;
60    // only set once
61    if lock.is_none() {
62        let follow = Keyword::get("input:follow_mouse").context("keyword failed")?;
63        trace!("Storing previous follow_mouse value: {}", follow.value);
64        *lock = Some(follow.value.to_string());
65    }
66    Keyword::set("input:follow_mouse", "3").context("keyword failed")?;
67    trace!("Set follow_mouse to 3");
68
69    let mut lock = get_gestures_enabled()
70        .lock()
71        .map_err(|e| anyhow::anyhow!("unable to lock get_gestures_enabled mutex: {}", e))?;
72    if lock.is_none() {
73        let gestures_enabled =
74            Keyword::get("gestures:workspace_swipe").context("keyword failed")?;
75        trace!(
76            "Storing previous gestures_enabled value: {}",
77            gestures_enabled.value
78        );
79        *lock = Some(gestures_enabled.value.to_string() == "1");
80    }
81    Keyword::set("gestures:workspace_swipe", "0").context("keyword failed")?;
82    trace!("Set gestures:workspace_swipe to 0");
83    Ok(())
84}
85
86pub fn reset_remain_focused() -> anyhow::Result<()> {
87    let follow = get_prev_follow_mouse()
88        .lock()
89        .map_err(|e| anyhow::anyhow!("unable to lock get_prev_follow_mouse mutex: {}", e))?;
90    if let Some(follow) = follow.as_ref() {
91        Keyword::set("input:follow_mouse", follow.to_string()).context("keyword failed")?;
92        trace!("Restored previous follow_mouse value: {}", follow);
93    } else {
94        trace!("No previous follow_mouse value stored, skipping reset");
95    }
96
97    let gestures_enabled = get_gestures_enabled()
98        .lock()
99        .map_err(|e| anyhow::anyhow!("unable to lock get_gestures_enabled mutex: {}", e))?;
100    if let Some(enabled) = gestures_enabled.as_ref() {
101        Keyword::set("gestures:workspace_swipe", if *enabled { "1" } else { "0" })
102            .context("keyword failed")?;
103        trace!(
104            "Restored previous gestures:workspace_swipe value: {}",
105            enabled
106        );
107    } else {
108        trace!("No previous gestures:workspace_swipe value stored, skipping reset");
109    }
110    Ok(())
111}
112
113// pub fn activate_submap(submap_name: &str) -> anyhow::Result<()> {
114//     Dispatch::call(DispatchType::Custom("submap", submap_name)).context("dispatch failed")?;
115//     debug!("Activated submap: {}", submap_name);
116//     Ok(())
117// }
118//
119// pub fn reset_submap() -> anyhow::Result<()> {
120//     Dispatch::call(DispatchType::Custom("submap", "reset")).context("dispatch failed")?;
121//     debug!("reset submap");
122//     Ok(())
123// }
124
125pub fn get_initial_active() -> anyhow::Result<Active> {
126    let active_client = Client::get_active()?.map(|c| to_client_id(&c.address));
127    let active_ws = Workspace::get_active()?.id;
128    let active_monitor = Monitor::get_active()?.id;
129    Ok(Active {
130        client: active_client,
131        workspace: active_ws,
132        monitor: active_monitor,
133    })
134}
135
136pub fn get_version() -> anyhow::Result<String> {
137    let version = hyprland::data::Version::get()
138        .context("Failed to get version! (hyprland is probably outdated or too new??)")?;
139
140    trace!("hyprland {version:?}");
141
142    Ok(version
143        .version
144        .unwrap_or(version.tag.trim_start_matches('v').to_string()))
145}