hyprshell_core_lib/
util.rs1use anyhow::Context;
2use semver::Version;
3use std::fs::DirEntry;
4use std::os::unix::net::UnixStream;
5use std::path::PathBuf;
6use std::{env, fmt};
7use tracing::{debug, trace, warn};
8
9pub const MIN_VERSION: Version = Version::new(0, 42, 0);
10
11pub const OVERVIEW_NAMESPACE: &str = "hyprshell_overview";
12pub const LAUNCHER_NAMESPACE: &str = "hyprshell_launcher";
13
14pub trait Warn<A> {
15 fn warn(self, msg: &str) -> Option<A>;
16}
17
18impl<A> Warn<A> for Option<A> {
19 fn warn(self, msg: &str) -> Option<A> {
20 match self {
21 Some(o) => Some(o),
22 None => {
23 warn!("{}", msg);
24 None
25 }
26 }
27 }
28}
29
30impl<A, E: fmt::Display> Warn<A> for Result<A, E> {
31 fn warn(self, msg: &str) -> Option<A> {
32 match self {
33 Ok(o) => Some(o),
34 Err(e) => {
35 warn!("{}: {}", msg, e);
36 None
37 }
38 }
39 }
40}
41
42pub const TERMINALS: [&str; 29] = [
44 "alacritty",
45 "kitty",
46 "x-terminal-emulator",
47 "mate-terminal",
48 "gnome-terminal",
49 "terminator",
50 "xfce4-terminal",
51 "urxvt",
52 "rxvt",
53 "termit",
54 "Eterm",
55 "aterm",
56 "uxterm",
57 "xterm",
58 "roxterm",
59 "termite",
60 "lxterminal",
61 "terminology",
62 "st",
63 "qterminal",
64 "lilyterm",
65 "tilix",
66 "terminix",
67 "konsole",
68 "guake",
69 "tilda",
70 "hyper",
71 "wezterm",
72 "rio",
73];
74
75pub fn get_daemon_socket_path_buff() -> PathBuf {
76 let mut buf = if let Ok(runtime_path) = env::var("XDG_RUNTIME_DIR") {
77 std::path::PathBuf::from(runtime_path)
78 } else if let Ok(uid) = env::var("UID") {
79 std::path::PathBuf::from("/run/user/".to_owned() + &uid)
80 } else {
81 std::path::PathBuf::from("/tmp")
82 };
83 #[cfg(debug_assertions)]
84 buf.push("hyprshell.debug.sock");
85 #[cfg(not(debug_assertions))]
86 buf.push("hyprshell.sock");
87 buf
88}
89
90pub fn daemon_running() -> bool {
91 let buf = get_daemon_socket_path_buff();
93 if buf.exists() {
94 debug!("Checking if daemon is running");
95 UnixStream::connect(buf).is_ok()
96 } else {
97 debug!("Daemon not running");
98 false
99 }
100}
101
102pub fn check_version(version: anyhow::Result<String>) -> anyhow::Result<()> {
103 if let Ok(version) = version {
104 let parsed_version =
105 semver::Version::parse(&version).context("Unable to parse hyprland Version")?;
106
107 if parsed_version.lt(&MIN_VERSION) {
108 Err(anyhow::anyhow!(
109 "hyprland version {} is too old or unknown, please update to at least {}",
110 parsed_version,
111 MIN_VERSION
112 ))
113 } else {
114 Ok(())
115 }
116 } else {
117 Err(anyhow::anyhow!("Unable to get hyprland version"))
118 }
119}
120
121pub fn collect_desktop_files() -> Vec<DirEntry> {
122 let mut res = Vec::new();
123 for dir in find_application_dirs() {
124 if !dir.exists() {
125 continue;
126 }
127 match dir.read_dir() {
128 Ok(dir) => {
129 for entry in dir.flatten() {
130 let path = entry.path();
131 if path.is_file() && path.extension().is_some_and(|e| e == "desktop") {
132 res.push(entry);
133 }
134 }
135 }
136 Err(e) => {
137 warn!("Failed to read dir {dir:?}: {e}");
138 continue;
139 }
140 }
141 }
142 debug!("found {} desktop files", res.len());
143 res
144}
145
146fn find_application_dirs() -> Vec<PathBuf> {
147 let mut dirs = env::var_os("XDG_DATA_DIRS")
148 .map(|val| env::split_paths(&val).collect())
149 .unwrap_or_else(|| {
150 vec![
151 PathBuf::from("/usr/local/share"),
152 PathBuf::from("/usr/share"),
153 ]
154 });
155
156 if let Some(data_home) = env::var_os("XDG_DATA_HOME")
157 .map(PathBuf::from)
158 .map_or_else(
159 || {
160 env::var_os("HOME")
161 .map(|p| PathBuf::from(p).join(".local/share"))
162 .or_else(|| {
163 warn!("No XDG_DATA_HOME and HOME environment variable found");
164 None
165 })
166 },
167 Some,
168 ) { dirs.push(data_home) }
169
170 let dirs = dirs
171 .into_iter()
172 .map(|dir| dir.join("applications"))
173 .collect();
174 trace!("searching for icons in dirs: {:?}", dirs);
175 dirs
176}