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
138fn 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 generate_socat(echo: &str) -> String {
147 format!(r#"{} socat '{}'"#, get_hyprshell_path(), echo)
148}
149
150#[derive(Debug, Clone)]
151pub enum ExecType {
152 Flatpak(Box<str>, Box<str>),
153 PWA(Box<str>, Box<str>),
154 FlatpakPWA(Box<str>, Box<str>),
155 Absolute(Box<str>, Box<str>),
156 AppImage(Box<str>, Box<str>),
157 Relative(Box<str>),
158}
159
160const UNKNOWN_EXEC: &str = "unknown";
161
162pub fn analyse_exec(exec: &str) -> ExecType {
163 let exec_trim = exec.replace("'", "").replace("\"", "");
164 if exec.contains("--app-id=") && exec.contains("--profile-directory=") {
166 if exec.contains("flatpak run") || exec.contains("flatpak 'run'") {
168 let browser_exec_in_flatpak = exec_trim
169 .split_whitespace()
170 .find(|s| s.contains("--command="))
171 .and_then(|s| {
172 s.split('=')
173 .next_back()
174 .and_then(|s| s.split('/').next_back())
175 })
176 .unwrap_or(UNKNOWN_EXEC);
177 let flatpak_identifier = exec_trim
178 .split_whitespace()
179 .skip(2)
180 .find(|arg| !arg.starts_with("--"))
181 .unwrap_or(UNKNOWN_EXEC);
182 ExecType::FlatpakPWA(
183 Box::from(flatpak_identifier),
184 Box::from(browser_exec_in_flatpak),
185 )
186 } else {
187 let browser_exec = exec
189 .split_whitespace()
190 .next()
191 .and_then(|s| s.split('/').next_back())
192 .unwrap_or(UNKNOWN_EXEC);
193 let browser_full_exec = exec.split_whitespace().next().unwrap_or(UNKNOWN_EXEC);
194 ExecType::PWA(Box::from(browser_exec), Box::from(browser_full_exec))
195 }
196 } else if exec.contains("flatpak run") || exec.contains("flatpak 'run'") {
198 let command_in_flatpak = exec_trim
199 .split_whitespace()
200 .find(|s| s.contains("--command="))
201 .and_then(|s| {
202 s.split('=')
203 .next_back()
204 .and_then(|s| s.split('/').next_back())
205 })
206 .unwrap_or(UNKNOWN_EXEC);
207 let flatpak_identifier = exec_trim
208 .split_whitespace()
209 .skip(2)
210 .find(|arg| !arg.starts_with("--"))
211 .unwrap_or(UNKNOWN_EXEC);
212 ExecType::Flatpak(Box::from(flatpak_identifier), Box::from(command_in_flatpak))
213 } else if exec_trim. contains(".AppImage"){
214 let appimage_name = exec_trim
216 .split_whitespace()
217 .next()
218 .and_then(|s| s.split('/').next_back())
219 .and_then(|s| s.split('_').next())
220 .unwrap_or(UNKNOWN_EXEC);
221 ExecType::AppImage(Box::from(appimage_name), Box::from(exec))
222 } else if exec_trim.starts_with("/") {
223 let exec_name = exec_trim
224 .split_whitespace()
225 .next()
226 .and_then(|s| s.split('/').next_back())
227 .unwrap_or(UNKNOWN_EXEC);
228 ExecType::Absolute(Box::from(exec_name), Box::from(exec))
229 } else {
230 ExecType::Relative(Box::from(exec_trim))
231 }
232}