Skip to main content

hyprshell_hyprland/
instance.rs

1use crate::error::hypr_err;
2use crate::shared::{CommandContent, get_hypr_path};
3use std::path::{Path, PathBuf};
4
5/// This is the sync version of the Hyprland Instance.
6/// It holds the event streams connected to the sockets of one running Hyprland instance.
7#[derive(Debug, Clone)]
8pub struct Instance {
9    instance: String,
10    /// .socket.sock
11    stream: Box<Path>,
12    /// .hyprpaper.sock
13    #[cfg(feature = "hyprpaper")]
14    hyprpaper_stream: Box<Path>,
15    /// .socket2.sock
16    #[cfg(feature = "listener")]
17    event_socket_path: Box<Path>,
18}
19
20impl PartialEq<Self> for Instance {
21    fn eq(&self, other: &Self) -> bool {
22        self.instance == other.instance
23    }
24}
25
26impl Instance {
27    /// uses the $HYPRLAND_INSTANCE_SIGNATURE env variable
28    pub fn from_current_env() -> crate::Result<Self> {
29        let mut path = get_hypr_path()?;
30        let name = get_env_name()?;
31        path.push(&name);
32        Self::from_base_socket_path(path)
33    }
34
35    /// Uses the name to determine the sockets to use
36    ///
37    /// Example name: `9958d297641b5c84dcff93f9039d80a5ad37ab00_1752788564_214680212`
38    pub fn from_instance(name: String) -> crate::Result<Self> {
39        let mut path = get_hypr_path()?;
40        path.push(&name);
41        Self::from_base_socket_path(path)
42    }
43
44    /// Uses the path to determine the sockets to use
45    ///
46    /// Example path: `/run/user/1000/hypr/9958d297641b5c84dcff93f9039d80a5ad37ab00_1752788564_21468021`
47    pub fn from_base_socket_path(path: PathBuf) -> crate::Result<Self> {
48        let Some(name) = path.file_name().map(|n| n.to_string_lossy().to_string()) else {
49            hypr_err!("Could not get instance name from path: {}", path.display());
50        };
51        if !path.exists() {
52            hypr_err!("Hyprland instance path does not exist: {}", path.display());
53        }
54        Ok(Self {
55            instance: name,
56            stream: path.join(".socket.sock").into_boxed_path(),
57            #[cfg(feature = "listener")]
58            event_socket_path: path.join(".socket2.sock").into_boxed_path(),
59            #[cfg(feature = "hyprpaper")]
60            hyprpaper_stream: path.join(".hyprpaper.sock").into_boxed_path(),
61        })
62    }
63}
64
65impl Instance {
66    pub(crate) fn write_to_socket(&self, content: CommandContent) -> crate::Result<String> {
67        use std::io::{Read, Write};
68        let mut stream = std::os::unix::net::UnixStream::connect(&self.stream)?;
69        #[cfg(feature = "trace")]
70        tracing::trace!("Sending command: {}", content.data);
71        stream.write_all(&content.as_bytes())?;
72        let mut response = Vec::new();
73        stream.read_to_end(&mut response)?;
74        Ok(String::from_utf8_lossy(&response).to_string())
75    }
76
77    #[cfg(any(feature = "async-lite", feature = "tokio"))]
78    pub(crate) async fn write_to_socket_async(
79        &self,
80        content: CommandContent,
81    ) -> crate::Result<String> {
82        use crate::async_import::{AsyncReadExt, AsyncWriteExt};
83        let mut stream = crate::async_import::UnixStream::connect(&self.stream).await?;
84        #[cfg(feature = "trace")]
85        tracing::trace!("Sending command: {}", content.data);
86        stream.write_all(&content.as_bytes()).await?;
87        let mut response = Vec::new();
88        stream.read_to_end(&mut response).await?;
89        Ok(String::from_utf8_lossy(&response).to_string())
90    }
91
92    #[cfg(feature = "hyprpaper")]
93    pub(crate) fn write_to_hyprpaper_socket(
94        &self,
95        content: CommandContent,
96    ) -> crate::Result<String> {
97        use std::io::{Read, Write};
98        let mut stream = std::os::unix::net::UnixStream::connect(&self.hyprpaper_stream)?;
99        #[cfg(feature = "trace")]
100        tracing::trace!("Sending command: {}", content.data);
101        stream.write_all(content.data.as_bytes())?;
102
103        let mut response = Vec::new();
104        const BUFFER_SIZE: usize = 4096;
105        let mut buf = [0u8; BUFFER_SIZE];
106        loop {
107            let n = stream.read(&mut buf[..])?;
108            response.extend_from_slice(&buf[..n]);
109            if n < BUFFER_SIZE {
110                break;
111            }
112        }
113        Ok(String::from_utf8_lossy(&response).to_string())
114    }
115
116    #[cfg(all(feature = "hyprpaper", any(feature = "async-lite", feature = "tokio")))]
117    pub(crate) async fn write_to_hyprpaper_socket_async(
118        &self,
119        content: CommandContent,
120    ) -> crate::Result<String> {
121        use crate::async_import::{AsyncReadExt, AsyncWriteExt};
122        let mut stream = crate::async_import::UnixStream::connect(&self.hyprpaper_stream).await?;
123        #[cfg(feature = "trace")]
124        tracing::trace!("Sending command: {}", content.data);
125        stream.write_all(content.data.as_bytes()).await?;
126
127        let mut response = Vec::new();
128        const BUFFER_SIZE: usize = 4096;
129        let mut buf = [0u8; BUFFER_SIZE];
130        loop {
131            let n = stream.read(&mut buf[..]).await?;
132            response.extend_from_slice(&buf[..n]);
133            if n < BUFFER_SIZE {
134                break;
135            }
136        }
137        Ok(String::from_utf8_lossy(&response).to_string())
138    }
139
140    #[cfg(feature = "listener")]
141    pub(crate) fn get_event_stream(&self) -> crate::Result<std::os::unix::net::UnixStream> {
142        let stream = std::os::unix::net::UnixStream::connect(&self.event_socket_path)?;
143        Ok(stream)
144    }
145
146    #[cfg(all(feature = "listener", any(feature = "async-lite", feature = "tokio")))]
147    pub(crate) async fn get_event_stream_async(
148        &self,
149    ) -> crate::Result<crate::async_import::UnixStream> {
150        let stream = crate::async_import::UnixStream::connect(&self.event_socket_path).await?;
151        Ok(stream)
152    }
153}
154
155fn get_env_name() -> crate::Result<String> {
156    let instance = match std::env::var("HYPRLAND_INSTANCE_SIGNATURE") {
157        Ok(var) => var,
158        Err(std::env::VarError::NotPresent) => {
159            hypr_err!("Could not get socket path! (Is Hyprland running??)")
160        }
161        Err(std::env::VarError::NotUnicode(_)) => {
162            hypr_err!("Corrupted Hyprland socket variable: Invalid unicode!")
163        }
164    };
165    Ok(instance)
166}