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