hyprshell_exec_lib/
util.rs1use anyhow::{Context, anyhow};
2use core_lib::{Active, ClientId, notify_warn};
3use hyprland::ctl::reload;
4use hyprland::data::{Client, Monitor, Workspace};
5use hyprland::keyword::Keyword;
6use hyprland::prelude::*;
7use semver::Version;
8use std::sync::{Mutex, OnceLock};
9use std::thread;
10use std::time::Duration;
11use tracing::{debug, info, trace, warn};
12
13#[must_use]
18pub fn to_client_id(id: &hyprland::shared::Address) -> ClientId {
19 u64::from_str_radix(id.to_string().trim_start_matches("0x"), 16)
20 .expect("Failed to parse client id, this should never happen")
21}
22
23#[must_use]
25pub fn to_client_address(id: ClientId) -> hyprland::shared::Address {
26 hyprland::shared::Address::new(format!("{id:x}"))
27}
28
29pub fn reload_hyprland_config() -> anyhow::Result<()> {
30 debug!("Reloading hyprland config");
31 reload::call().context("Failed to reload hyprland config")
32}
33
34fn get_prev_follow_mouse() -> &'static Mutex<Option<String>> {
35 static PREV_FOLLOW_MOUSE: OnceLock<Mutex<Option<String>>> = OnceLock::new();
36 PREV_FOLLOW_MOUSE.get_or_init(|| Mutex::new(None))
37}
38
39pub fn set_no_follow_mouse() -> anyhow::Result<()> {
40 let raw = hyprland::EvalRaw::new("hl.config({ input = { follow_mouse = 3} })");
41 raw.eval().context("Failed to set follow_mouse to 3")?;
42 trace!("Set follow_mouse to 3");
43 Ok(())
44}
45
46pub fn reset_no_follow_mouse() -> anyhow::Result<()> {
47 let follow = get_prev_follow_mouse()
48 .lock()
49 .map_err(|e| anyhow::anyhow!("unable to lock get_prev_follow_mouse mutex: {e:?}"))?;
50 if let Some(follow) = follow.as_ref() {
51 let raw = hyprland::EvalRaw::new(format!(
52 "hl.config({{ input = {{ follow_mouse = {} }} }})",
53 follow
54 ));
55 raw.eval()
56 .context("Failed to set follow_mouse to default")?;
57 trace!("Restored previous follow_mouse value: {follow}");
58 } else {
59 trace!("No previous follow_mouse value stored, skipping reset");
60 }
61 drop(follow);
62 Ok(())
63}
64
65pub fn set_follow_mouse_default() -> anyhow::Result<()> {
66 let mut lock = get_prev_follow_mouse()
67 .lock()
68 .map_err(|e| anyhow::anyhow!("unable to lock get_prev_follow_mouse mutex: {e:?}"))?;
69 let follow = Keyword::get("input:follow_mouse").context("keyword failed")?;
70 trace!("Storing previous follow_mouse value: {}", follow.value);
71 *lock = Some(follow.value.to_string());
72 drop(lock);
73 Ok(())
74}
75
76pub fn get_initial_active() -> anyhow::Result<Active> {
81 let mut tries = 0;
82 loop {
83 match internal_get_initial_active() {
84 Ok(a) => break Ok(a),
85 Err(e) => {
86 if tries > 40 {
87 break Err(e);
88 }
89 warn!("waiting for correct initial active state from hyprland: {e:?}");
90 thread::sleep(Duration::from_millis(500));
91 }
92 }
93 tries += 1;
94 }
95}
96
97fn internal_get_initial_active() -> anyhow::Result<Active> {
98 let active_client = Client::get_active()
99 .ok()
100 .flatten()
101 .map(|c| to_client_id(&c.address));
102 let active_ws = Workspace::get_active()
103 .context("unable to get initial workspace")?
104 .id;
105 let active_monitor = Monitor::get_active()
106 .context("unable to get initial monitor")?
107 .id;
108
109 Ok(Active {
110 client: active_client,
111 workspace: active_ws,
112 monitor: active_monitor,
113 })
114}
115
116pub fn check_version() -> anyhow::Result<()> {
117 pub const MIN_VERSION: Version = Version::new(0, 55, 0);
118
119 let version = get_version()
120 .context("Failed to get version! (hyprland is probably outdated or too new??)")?;
121 trace!("hyprland {version:?}");
122
123 let version = version
124 .version
125 .unwrap_or_else(|| version.tag.trim_start_matches('v').to_string());
126 info!(
127 "Starting hyprshell {} in {} mode on hyprland {version}",
128 env!("CARGO_PKG_VERSION"),
129 if cfg!(debug_assertions) {
130 "debug"
131 } else {
132 "release"
133 },
134 );
135 let parsed_version = Version::parse(&version).context("Unable to parse hyprland Version")?;
136 if parsed_version.lt(&MIN_VERSION) {
137 notify_warn(&format!(
138 "hyprland version {parsed_version} is too old or unknown, please update to at least {MIN_VERSION}",
139 ));
140 }
141 Ok(())
142}
143
144pub(crate) fn get_version() -> anyhow::Result<hyprland::data::Version> {
145 let mut tries = 0;
146 loop {
147 match hyprland::data::Version::get() {
148 Ok(a) => break Ok(a),
149 Err(e) => {
150 if tries > 40 {
151 break Err(anyhow!(e));
152 }
153 warn!("waiting for correct version from hyprland: {e:?}");
154 thread::sleep(Duration::from_millis(500));
155 }
156 }
157 tries += 1;
158 }
159}