hyprshell_core_lib/
util.rs1use crate::find_application_dirs;
2use anyhow::Context;
3use semver::Version;
4use std::fs::DirEntry;
5use std::os::unix::net::UnixStream;
6use std::path::PathBuf;
7use std::{env, fmt};
8use tracing::{debug, info, warn};
9
10pub const MIN_VERSION: Version = Version::new(0, 42, 0);
11
12pub const OVERVIEW_NAMESPACE: &str = "hyprshell_overview";
13pub const LAUNCHER_NAMESPACE: &str = "hyprshell_launcher";
14
15pub trait Warn<A> {
16 fn warn(self, msg: &str) -> Option<A>;
17}
18
19impl<A> Warn<A> for Option<A> {
20 fn warn(self, msg: &str) -> Option<A> {
21 match self {
22 Some(o) => Some(o),
23 None => {
24 warn!("{}", msg);
25 None
26 }
27 }
28 }
29}
30
31impl<A, E: fmt::Debug + fmt::Display> Warn<A> for Result<A, E> {
32 fn warn(self, msg: &str) -> Option<A> {
33 match self {
34 Ok(o) => Some(o),
35 Err(e) => {
36 warn!("{}: {}", msg, e);
37 debug!("{e:?}");
38 None
39 }
40 }
41 }
42}
43
44pub const TERMINALS: [&str; 9] = [
47 "alacritty",
48 "kitty",
49 "wezterm",
50 "foot",
51 "qterminal",
52 "lilyterm",
53 "tilix",
54 "terminix",
55 "konsole",
56];
57
58pub fn get_daemon_socket_path_buff() -> PathBuf {
59 let mut buf = if let Some(runtime_path) = env::var_os("XDG_RUNTIME_DIR") {
60 std::path::PathBuf::from(runtime_path)
61 } else if let Ok(uid) = env::var("UID") {
62 std::path::PathBuf::from("/run/user/".to_owned() + &uid)
63 } else {
64 std::path::PathBuf::from("/tmp")
65 };
66 #[cfg(debug_assertions)]
67 buf.push("hyprshell.debug.sock");
68 #[cfg(not(debug_assertions))]
69 buf.push("hyprshell.sock");
70 buf
71}
72
73pub fn daemon_running() -> bool {
74 let buf = get_daemon_socket_path_buff();
76 if buf.exists() {
77 debug!("Checking if daemon is running");
78 UnixStream::connect(buf).is_ok()
79 } else {
80 debug!("Daemon not running");
81 false
82 }
83}
84
85pub fn check_version(version: anyhow::Result<String>) -> anyhow::Result<()> {
86 if let Ok(version) = version {
87 info!(
88 "Starting hyprshell {} in {} mode on hyprland {}",
89 env!("CARGO_PKG_VERSION"),
90 if cfg!(debug_assertions) {
91 "debug"
92 } else {
93 "release"
94 },
95 version,
96 );
97 let parsed_version =
98 Version::parse(&version).context("Unable to parse hyprland Version")?;
99 if parsed_version.lt(&MIN_VERSION) {
100 Err(anyhow::anyhow!(
101 "hyprland version {} is too old or unknown, please update to at least {}",
102 parsed_version,
103 MIN_VERSION
104 ))
105 } else {
106 Ok(())
107 }
108 } else {
109 Err(anyhow::anyhow!("Unable to get hyprland version"))
110 }
111}
112
113pub fn collect_desktop_files() -> Vec<DirEntry> {
114 let mut res = Vec::new();
115 for dir in find_application_dirs() {
116 if !dir.exists() {
117 continue;
118 }
119 match dir.read_dir() {
120 Ok(dir) => {
121 for entry in dir.flatten() {
122 let path = entry.path();
123 if path.is_file() && path.extension().is_some_and(|e| e == "desktop") {
124 res.push(entry);
125 }
126 }
127 }
128 Err(e) => {
129 warn!("Failed to read dir {dir:?}: {e}");
130 continue;
131 }
132 }
133 }
134 debug!("found {} desktop files", res.len());
135 res
136}
137
138pub fn get_hyprshell_path() -> String {
139 env::current_exe()
140 .expect("Current executable not found")
141 .display()
142 .to_string()
143 .replace("(deleted)", "")
144}
145
146pub fn get_hyprctl_path() -> String {
147 env::var("PATH")
148 .unwrap_or_else(|_| String::from("/usr/bin:/bin:/usr/local/bin"))
149 .split(':')
150 .find_map(|dir| {
151 let path = PathBuf::from(dir).join("hyprctl");
152 if path.exists() {
153 Some(path.display().to_string())
154 } else {
155 None
156 }
157 })
158 .unwrap_or_else(|| String::from("hyprctl"))
159}
160
161pub fn generate_socat(echo: &str) -> String {
162 format!(r#"{} socat '{}'"#, get_hyprshell_path(), echo)
163}
164
165pub fn generate_socat_and_activate_submap(echo: &str, submap: &str) -> String {
166 format!(
167 r#"{} dispatch submap {} && {} socat '{}'"#,
168 get_hyprctl_path(),
169 submap,
170 get_hyprshell_path(),
171 echo
172 )
173}
174
175#[derive(Debug, Clone)]
176pub enum ExecType {
177 Flatpak(Box<str>, Box<str>),
178 PWA(Box<str>, Box<str>),
179 FlatpakPWA(Box<str>, Box<str>),
180 Absolute(Box<str>, Box<str>),
181 AppImage(Box<str>, Box<str>),
182 Relative(Box<str>),
183}
184
185const UNKNOWN_EXEC: &str = "unknown";
186
187pub fn analyse_exec(exec: &str) -> ExecType {
188 let exec_trim = exec.replace("'", "").replace("\"", "");
189 if exec.contains("--app-id=") && exec.contains("--profile-directory=") {
191 if exec.contains("flatpak run") || exec.contains("flatpak 'run'") {
193 let browser_exec_in_flatpak = exec_trim
194 .split_whitespace()
195 .find(|s| s.contains("--command="))
196 .and_then(|s| {
197 s.split('=')
198 .next_back()
199 .and_then(|s| s.split('/').next_back())
200 })
201 .unwrap_or(UNKNOWN_EXEC);
202 let flatpak_identifier = exec_trim
203 .split_whitespace()
204 .skip(2)
205 .find(|arg| !arg.starts_with("--"))
206 .unwrap_or(UNKNOWN_EXEC);
207 ExecType::FlatpakPWA(
208 Box::from(flatpak_identifier),
209 Box::from(browser_exec_in_flatpak),
210 )
211 } else {
212 let browser_exec = exec
214 .split_whitespace()
215 .next()
216 .and_then(|s| s.split('/').next_back())
217 .unwrap_or(UNKNOWN_EXEC);
218 let browser_full_exec = exec.split_whitespace().next().unwrap_or(UNKNOWN_EXEC);
219 ExecType::PWA(Box::from(browser_exec), Box::from(browser_full_exec))
220 }
221 } else if exec.contains("flatpak run") || exec.contains("flatpak 'run'") {
223 let command_in_flatpak = exec_trim
224 .split_whitespace()
225 .find(|s| s.contains("--command="))
226 .and_then(|s| {
227 s.split('=')
228 .next_back()
229 .and_then(|s| s.split('/').next_back())
230 })
231 .unwrap_or(UNKNOWN_EXEC);
232 let flatpak_identifier = exec_trim
233 .split_whitespace()
234 .skip(2)
235 .find(|arg| !arg.starts_with("--"))
236 .unwrap_or(UNKNOWN_EXEC);
237 ExecType::Flatpak(Box::from(flatpak_identifier), Box::from(command_in_flatpak))
238 } else if exec_trim.contains(".AppImage") {
239 let appimage_name = exec_trim
241 .split_whitespace()
242 .next()
243 .and_then(|s| s.split('/').next_back())
244 .and_then(|s| s.split('_').next())
245 .unwrap_or(UNKNOWN_EXEC);
246 ExecType::AppImage(Box::from(appimage_name), Box::from(exec))
247 } else if exec_trim.starts_with("/") {
248 let exec_name = exec_trim
249 .split_whitespace()
250 .next()
251 .and_then(|s| s.split('/').next_back())
252 .unwrap_or(UNKNOWN_EXEC);
253 ExecType::Absolute(Box::from(exec_name), Box::from(exec))
254 } else {
255 ExecType::Relative(Box::from(exec_trim))
256 }
257}