hyprshell_exec_lib/
util.rs

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