hyprshell_core_lib/
util.rs

1use 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
42// from https://github.com/i3/i3/blob/next/i3-sensible-terminal
43pub 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    // check if socket exists and socket is open
92    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}