Skip to main content

hyprshell_hyprland/
ctl.rs

1use derive_more::{Constructor, Display as MDisplay};
2use std::fmt::Display as FDisplay;
3
4use crate::default_instance;
5use crate::instance::Instance;
6use crate::shared::*;
7
8/// Reload hyprland config
9pub mod reload {
10    use super::*;
11
12    /// Reload hyprland config
13    pub fn call() -> crate::Result<()> {
14        instance_call(default_instance()?)
15    }
16
17    /// Reload hyprland config
18    pub fn instance_call(instance: &Instance) -> crate::Result<()> {
19        instance.write_to_socket(command!(Empty, "reload"))?;
20        Ok(())
21    }
22
23    /// Reload hyprland config (async)
24    #[cfg(any(feature = "async-lite", feature = "tokio"))]
25    pub async fn call_async() -> crate::Result<()> {
26        instance_call_async(default_instance()?).await
27    }
28
29    /// Reload hyprland config (async)
30    #[cfg(any(feature = "async-lite", feature = "tokio"))]
31    pub async fn instance_call_async(instance: &Instance) -> crate::Result<()> {
32        instance
33            .write_to_socket_async(command!(Empty, "reload"))
34            .await?;
35        Ok(())
36    }
37}
38/// Enter kill mode (similar to xkill)
39pub mod kill {
40    use super::*;
41
42    /// Enter kill mode (similar to xkill)
43    pub fn call() -> crate::Result<()> {
44        instance_call(default_instance()?)
45    }
46
47    /// Enter kill mode (similar to xkill)
48    pub fn instance_call(instance: &Instance) -> crate::Result<()> {
49        instance.write_to_socket(command!(Empty, "kill"))?;
50        Ok(())
51    }
52
53    /// Enter kill mode (similar to xkill) (async)
54    #[cfg(any(feature = "async-lite", feature = "tokio"))]
55    pub async fn call_async() -> crate::Result<()> {
56        instance_call_async(default_instance()?).await
57    }
58
59    /// Enter kill mode (similar to xkill) (async)
60    #[cfg(any(feature = "async-lite", feature = "tokio"))]
61    pub async fn instance_call_async(instance: &Instance) -> crate::Result<()> {
62        instance
63            .write_to_socket_async(command!(Empty, "kill"))
64            .await?;
65        Ok(())
66    }
67}
68
69/// Set the cursor theme
70pub mod set_cursor {
71    use super::*;
72
73    /// Set the cursor theme
74    pub fn call<Str: FDisplay>(theme: Str, size: u16) -> crate::Result<()> {
75        instance_call(default_instance()?, theme, size)
76    }
77
78    /// Set the cursor theme
79    pub fn instance_call<Str: FDisplay>(
80        instance: &Instance,
81        theme: Str,
82        size: u16,
83    ) -> crate::Result<()> {
84        instance.write_to_socket(command!(Empty, "setcursor {theme} {size}"))?;
85        Ok(())
86    }
87
88    /// Set the cursor theme (async)
89    #[cfg(any(feature = "async-lite", feature = "tokio"))]
90    pub async fn call_async<Str: FDisplay>(theme: Str, size: u16) -> crate::Result<()> {
91        instance_call_async(default_instance()?, theme, size).await
92    }
93
94    /// Set the cursor theme (async)
95    #[cfg(any(feature = "async-lite", feature = "tokio"))]
96    pub async fn instance_call_async<Str: FDisplay>(
97        instance: &Instance,
98        theme: Str,
99        size: u16,
100    ) -> crate::Result<()> {
101        instance
102            .write_to_socket_async(command!(Empty, "setcursor {theme} {size}"))
103            .await?;
104        Ok(())
105    }
106}
107
108/// Stuff related to managing virtual outputs/displays
109pub mod output {
110    use super::*;
111
112    /// Output backend types
113    #[derive(Debug, MDisplay, Clone, Copy, PartialEq, Eq)]
114    pub enum OutputBackends {
115        /// The wayland output backend
116        #[display("wayland")]
117        Wayland,
118        /// The x11 output backend
119        #[display("x11")]
120        X11,
121        /// The headless output backend
122        #[display("headless")]
123        Headless,
124        /// Let Hyprland decide the backend type
125        #[display("auto")]
126        Auto,
127    }
128
129    /// Create virtual displays
130    pub fn create(backend: OutputBackends, name: Option<&str>) -> crate::Result<()> {
131        instance_create(default_instance()?, backend, name)
132    }
133
134    /// Remove virtual displays
135    pub fn remove<Str: FDisplay>(name: Str) -> crate::Result<()> {
136        instance_remove(default_instance()?, name)
137    }
138
139    /// Create virtual displays
140    pub fn instance_create(
141        instance: &Instance,
142        backend: OutputBackends,
143        name: Option<&str>,
144    ) -> crate::Result<()> {
145        let name = name.unwrap_or_default();
146        instance.write_to_socket(command!(Empty, "output create {backend} {name}"))?;
147        Ok(())
148    }
149
150    /// Remove virtual displays
151    pub fn instance_remove<Str: FDisplay>(instance: &Instance, name: Str) -> crate::Result<()> {
152        instance.write_to_socket(command!(Empty, "output remove {name}"))?;
153        Ok(())
154    }
155
156    /// Create virtual displays
157    #[cfg(any(feature = "async-lite", feature = "tokio"))]
158    pub async fn create_async(backend: OutputBackends, name: Option<&str>) -> crate::Result<()> {
159        instance_create_async(default_instance()?, backend, name).await
160    }
161
162    /// Create virtual displays
163    #[cfg(any(feature = "async-lite", feature = "tokio"))]
164    pub async fn instance_create_async(
165        instance: &Instance,
166        backend: OutputBackends,
167        name: Option<&str>,
168    ) -> crate::Result<()> {
169        let name = name.unwrap_or_default();
170        instance
171            .write_to_socket_async(command!(Empty, "output create {backend} {name}"))
172            .await?;
173        Ok(())
174    }
175
176    /// Remove virtual displays
177    #[cfg(any(feature = "async-lite", feature = "tokio"))]
178    pub async fn remove_async<Str: FDisplay>(name: Str) -> crate::Result<()> {
179        instance_remove_async(default_instance()?, name).await
180    }
181
182    /// Remove virtual displays
183    #[cfg(any(feature = "async-lite", feature = "tokio"))]
184    pub async fn instance_remove_async<Str: FDisplay>(
185        instance: &Instance,
186        name: Str,
187    ) -> crate::Result<()> {
188        instance
189            .write_to_socket_async(command!(Empty, "output remove {name}"))
190            .await?;
191        Ok(())
192    }
193}
194
195/// Switch the xkb layout index for a keyboard
196pub mod switch_xkb_layout {
197    use super::*;
198
199    /// The types of Cmds used by [switch_xkb_layout]
200    #[derive(Debug, MDisplay, Clone, Copy, PartialEq, Eq)]
201    pub enum SwitchXKBLayoutCmdTypes {
202        /// Next input
203        #[display("next")]
204        Next,
205        /// Previous inout
206        #[display("prev")]
207        Previous,
208        /// Set to a specific input id
209        #[display("{_0}")]
210        Id(u8),
211    }
212
213    /// Switch the xkb layout index for a keyboard
214    pub fn call<Str: FDisplay>(device: Str, cmd: SwitchXKBLayoutCmdTypes) -> crate::Result<()> {
215        instance_call(default_instance()?, device, cmd)
216    }
217
218    /// Switch the xkb layout index for a keyboard
219    pub fn instance_call<Str: FDisplay>(
220        instance: &Instance,
221        device: Str,
222        cmd: SwitchXKBLayoutCmdTypes,
223    ) -> crate::Result<()> {
224        instance.write_to_socket(command!(Empty, "switchxkblayout {device} {cmd}"))?;
225        Ok(())
226    }
227
228    /// Switch the xkb layout index for a keyboard
229    #[cfg(any(feature = "async-lite", feature = "tokio"))]
230    pub async fn call_async<Str: FDisplay>(
231        instance: &Instance,
232        device: Str,
233        cmd: SwitchXKBLayoutCmdTypes,
234    ) -> crate::Result<()> {
235        instance_call_async(instance, device, cmd).await
236    }
237
238    /// Switch the xkb layout index for a keyboard
239    #[cfg(any(feature = "async-lite", feature = "tokio"))]
240    pub async fn instance_call_async<Str: FDisplay>(
241        instance: &Instance,
242        device: Str,
243        cmd: SwitchXKBLayoutCmdTypes,
244    ) -> crate::Result<()> {
245        instance
246            .write_to_socket_async(command!(Empty, "switchxkblayout {device} {cmd}"))
247            .await?;
248        Ok(())
249    }
250}
251
252/// Creates a error that Hyprland will display
253pub mod set_error {
254    use super::*;
255
256    /// Creates a error that Hyprland will display
257    pub fn call(color: Color, msg: String) -> crate::Result<()> {
258        instance_call(default_instance()?, color, msg)
259    }
260
261    /// Creates a error that Hyprland will display
262    pub fn instance_call(instance: &Instance, color: Color, msg: String) -> crate::Result<()> {
263        instance.write_to_socket(command!(Empty, "seterror {color} {msg}"))?;
264        Ok(())
265    }
266
267    /// Creates a error that Hyprland will display (async)
268    #[cfg(any(feature = "async-lite", feature = "tokio"))]
269    pub async fn call_async(color: Color, msg: String) -> crate::Result<()> {
270        instance_call_async(default_instance()?, color, msg).await
271    }
272
273    /// Creates a error that Hyprland will display (async)
274    #[cfg(any(feature = "async-lite", feature = "tokio"))]
275    pub async fn instance_call_async(
276        instance: &Instance,
277        color: Color,
278        msg: String,
279    ) -> crate::Result<()> {
280        instance
281            .write_to_socket_async(command!(Empty, "seterror {color} {msg}"))
282            .await?;
283        Ok(())
284    }
285}
286
287/// Creates a notification with Hyprland
288pub mod notify {
289    use super::*;
290
291    #[allow(missing_docs)]
292    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
293    #[repr(i8)]
294    pub enum Icon {
295        NoIcon = -1,
296        Warning = 0,
297        Info = 1,
298        Hint = 2,
299        Error = 3,
300        Confused = 4,
301        Ok = 5,
302    }
303
304    /// Creates a notification with Hyprland
305    pub fn call(
306        icon: Icon,
307        time: std::time::Duration,
308        color: Color,
309        msg: String,
310    ) -> crate::Result<()> {
311        instance_call(default_instance()?, icon, time, color, msg)
312    }
313
314    /// Creates a notification with Hyprland
315    pub fn instance_call(
316        instance: &Instance,
317        icon: Icon,
318        time: std::time::Duration,
319        color: Color,
320        msg: String,
321    ) -> crate::Result<()> {
322        instance.write_to_socket(command!(
323            Empty,
324            "notify {} {} {color} {msg}",
325            icon as i8,
326            time.as_millis()
327        ))?;
328        Ok(())
329    }
330
331    /// Creates a error that Hyprland will display (async)
332    #[cfg(any(feature = "async-lite", feature = "tokio"))]
333    pub async fn call_async(
334        icon: Icon,
335        time: std::time::Duration,
336        color: Color,
337        msg: String,
338    ) -> crate::Result<()> {
339        instance_call_async(default_instance()?, icon, time, color, msg).await
340    }
341
342    /// Creates a error that Hyprland will display (async)
343    #[cfg(any(feature = "async-lite", feature = "tokio"))]
344    pub async fn instance_call_async(
345        instance: &Instance,
346        icon: Icon,
347        time: std::time::Duration,
348        color: Color,
349        msg: String,
350    ) -> crate::Result<()> {
351        instance
352            .write_to_socket_async(command!(
353                Empty,
354                "notify {} {} {color} {msg}",
355                icon as i8,
356                time.as_millis()
357            ))
358            .await?;
359        Ok(())
360    }
361}
362/// Dismisses all or up to a specified amount of notifications with Hyprland
363pub mod dismissnotify {
364    use super::*;
365
366    /// Dismisses notifications with Hyprland
367    ///
368    /// If `amount` is [None] then will dismiss ALL notifications
369    pub fn call(amount: Option<std::num::NonZeroU8>) -> crate::Result<()> {
370        instance_call(default_instance()?, amount)
371    }
372
373    /// Dismisses notifications with Hyprland
374    ///
375    /// If `amount` is [None] then will dismiss ALL notifications
376    pub fn instance_call(
377        instance: &Instance,
378        amount: Option<std::num::NonZeroU8>,
379    ) -> crate::Result<()> {
380        instance.write_to_socket(command!(
381            Empty,
382            "dismissnotify {}",
383            if let Some(amount) = amount {
384                amount.to_string()
385            } else {
386                (-1).to_string()
387            }
388        ))?;
389        Ok(())
390    }
391
392    /// Dismisses notifications with Hyprland (async)
393    ///
394    /// If `amount` is [None] then will dismiss ALL notifications
395    #[cfg(any(feature = "async-lite", feature = "tokio"))]
396    pub async fn call_async(amount: Option<std::num::NonZeroU8>) -> crate::Result<()> {
397        instance_call_async(default_instance()?, amount).await
398    }
399
400    /// Dismisses notifications with Hyprland (async)
401    ///
402    /// If `amount` is [None] then will dismiss ALL notifications
403    #[cfg(any(feature = "async-lite", feature = "tokio"))]
404    pub async fn instance_call_async(
405        instance: &Instance,
406        amount: Option<std::num::NonZeroU8>,
407    ) -> crate::Result<()> {
408        instance
409            .write_to_socket_async(command!(
410                Empty,
411                "dismissnotify {}",
412                if let Some(amount) = amount {
413                    amount.to_string()
414                } else {
415                    (-1).to_string()
416                }
417            ))
418            .await?;
419        Ok(())
420    }
421}
422
423/// A 8-bit color with a alpha channel
424#[derive(Debug, Copy, Clone, MDisplay, Constructor, PartialEq, Eq)]
425#[display("rgba({_0:02x}{_1:02x}{_2:02x}{_3:02x})")]
426pub struct Color(u8, u8, u8, u8);
427
428/// Provides things to setting props
429pub mod set_prop {
430    use super::*;
431
432    fn l(b: bool) -> &'static str {
433        if b { "lock" } else { "" }
434    }
435
436    /// Type that represents a prop
437    #[derive(MDisplay, Clone, PartialEq)]
438    pub enum PropType {
439        /// The animation style
440        #[display("animationstyle {_0}")]
441        AnimationStyle(String),
442        /// The roundness
443        #[display("rounding {_0} {}", l(*_1))]
444        Rounding(
445            i64,
446            /// locked
447            bool,
448        ),
449        /// Force no blur
450        #[display("forcenoblur {} {}", *_0 as u8, l(*_1))]
451        ForceNoBlur(
452            bool,
453            /// locked
454            bool,
455        ),
456        /// Force opaque
457        #[display("forceopaque {} {}", *_0 as u8, l(*_1))]
458        ForceOpaque(
459            bool,
460            /// locked
461            bool,
462        ),
463        /// Force opaque overriden
464        #[display("forceopaqueoverriden {} {}", *_0 as u8, l(*_1))]
465        ForceOpaqueOverriden(
466            bool,
467            /// locked
468            bool,
469        ),
470        /// Force allow input
471        #[display("forceallowsinput {} {}", *_0 as u8, l(*_1))]
472        ForceAllowsInput(
473            bool,
474            /// locked
475            bool,
476        ),
477        /// Force no animations
478        #[display("forcenoanims {} {}", *_0 as u8, l(*_1))]
479        ForceNoAnims(
480            bool,
481            /// locked
482            bool,
483        ),
484        /// Force no border
485        #[display("forcenoborder {} {}", *_0 as u8, l(*_1))]
486        ForceNoBorder(
487            bool,
488            /// locked
489            bool,
490        ),
491        /// Force no shadow
492        #[display("forcenoshadow {} {}", *_0 as u8, l(*_1))]
493        ForceNoShadow(
494            bool,
495            /// locked
496            bool,
497        ),
498        /// Allow for windoe dancing?
499        #[display("windowdancecompat {} {}", *_0 as u8, l(*_1))]
500        WindowDanceCompat(
501            bool,
502            /// locked
503            bool,
504        ),
505        /// Allow for overstepping max size
506        #[display("nomaxsize {} {}", *_0 as u8, l(*_1))]
507        NoMaxSize(
508            bool,
509            /// locked
510            bool,
511        ),
512        /// Dim around?
513        #[display("dimaround {} {}", *_0 as u8, l(*_1))]
514        DimAround(
515            bool,
516            /// locked
517            bool,
518        ),
519        /// Makes the next setting be override instead of multiply
520        #[display("alphaoverride {} {}", *_0 as u8, l(*_1))]
521        AlphaOverride(
522            bool,
523            /// locked
524            bool,
525        ),
526        /// The alpha
527        #[display("alpha {_0} {}", l(*_1))]
528        Alpha(
529            f32,
530            /// locked
531            bool,
532        ),
533        /// Makes the next setting be override instead of multiply
534        #[display("alphainactiveoverride {} {}", *_0 as u8, l(*_1))]
535        AlphaInactiveOverride(
536            bool,
537            /// locked
538            bool,
539        ),
540        /// The alpha for inactive
541        #[display("alphainactive {_0} {}", l(*_1))]
542        AlphaInactive(
543            f32,
544            /// locked
545            bool,
546        ),
547        /// The active border color
548        #[display("alphabordercolor {_0} {}", l(*_1))]
549        ActiveBorderColor(
550            Color,
551            /// locked
552            bool,
553        ),
554        /// The inactive border color
555        #[display("inalphabordercolor {_0} {}", l(*_1))]
556        InactiveBorderColor(
557            Color,
558            /// locked
559            bool,
560        ),
561    }
562
563    /// Sets a window prob
564    pub fn call(ident: String, prop: PropType, lock: bool) -> crate::Result<()> {
565        instance_call(default_instance()?, ident, prop, lock)
566    }
567
568    /// Sets a window prob
569    pub fn instance_call(
570        instance: &Instance,
571        ident: String,
572        prop: PropType,
573        lock: bool,
574    ) -> crate::Result<()> {
575        instance.write_to_socket(command!(
576            Empty,
577            "setprop {ident} {prop} {}",
578            if lock { "lock" } else { "" }
579        ))?;
580        Ok(())
581    }
582
583    /// Sets a window prob (async)
584    #[cfg(any(feature = "async-lite", feature = "tokio"))]
585    pub async fn call_async(ident: String, prop: PropType, lock: bool) -> crate::Result<()> {
586        instance_call_async(default_instance()?, ident, prop, lock).await
587    }
588
589    /// Sets a window prob (async)
590    #[cfg(any(feature = "async-lite", feature = "tokio"))]
591    pub async fn instance_call_async(
592        instance: &Instance,
593        ident: String,
594        prop: PropType,
595        lock: bool,
596    ) -> crate::Result<()> {
597        instance
598            .write_to_socket_async(command!(
599                Empty,
600                "setprop {ident} {prop} {}",
601                if lock { "lock" } else { "" }
602            ))
603            .await?;
604        Ok(())
605    }
606}
607
608/// Provides functions for communication with plugin system
609pub mod plugin {
610    use super::*;
611    use crate::error::HyprError;
612    use std::path::Path;
613
614    /// This struct represents a loaded plugin
615    #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
616    pub struct Plugin {
617        /// plugin name
618        pub name: String,
619        /// plugin author
620        pub author: String,
621        /// handle to plugin
622        pub handle: String,
623        /// plugin version
624        pub version: String,
625        /// plugin description
626        pub description: String,
627    }
628
629    /// Returns a list of all plugins
630    pub fn list() -> crate::Result<Vec<Plugin>> {
631        instance_list(default_instance()?)
632    }
633
634    /// Returns a list of all plugins
635    pub fn instance_list(instance: &Instance) -> crate::Result<Vec<Plugin>> {
636        let data = instance.write_to_socket(command!(JSON, "plugin list"))?;
637        let deserialized: Vec<Plugin> = serde_json::from_str(&data)?;
638        Ok(deserialized)
639    }
640
641    /// Returns a list of all plugins (async)
642    #[cfg(any(feature = "async-lite", feature = "tokio"))]
643    pub async fn list_async() -> crate::Result<Vec<Plugin>> {
644        instance_list_async(default_instance()?).await
645    }
646
647    /// Returns a list of all plugins (async)
648    #[cfg(any(feature = "async-lite", feature = "tokio"))]
649    pub async fn instance_list_async(instance: &Instance) -> crate::Result<Vec<Plugin>> {
650        let data = instance
651            .write_to_socket_async(command!(JSON, "plugin list"))
652            .await?;
653        let deserialized: Vec<Plugin> = serde_json::from_str(&data)?;
654        Ok(deserialized)
655    }
656
657    /// Loads a plugin, by absolute path
658    pub fn load(path: &Path) -> crate::Result<()> {
659        instance_load(default_instance()?, path)
660    }
661
662    /// Loads a plugin, by absolute path
663    pub fn instance_load(instance: &Instance, path: &Path) -> crate::Result<()> {
664        let str = instance.write_to_socket(command!(Empty, "plugin load {}", path.display()))?;
665        if str.contains("could not be loaded") {
666            Err(HyprError::Internal(str))
667        } else {
668            Ok(())
669        }
670    }
671
672    /// Loads a plugin, by absolute path (async)
673    #[cfg(any(feature = "async-lite", feature = "tokio"))]
674    pub async fn load_async(path: &Path) -> crate::Result<()> {
675        instance_load_async(default_instance()?, path).await
676    }
677
678    /// Loads a plugin, by absolute path (async)
679    #[cfg(any(feature = "async-lite", feature = "tokio"))]
680    pub async fn instance_load_async(instance: &Instance, path: &Path) -> crate::Result<()> {
681        let str = instance
682            .write_to_socket_async(command!(Empty, "plugin load {}", path.display()))
683            .await?;
684        if str.contains("could not be loaded") {
685            Err(HyprError::Internal(str))
686        } else {
687            Ok(())
688        }
689    }
690
691    /// Unloads a plugin, by absolute path.
692    pub fn unload(path: &Path) -> crate::Result<()> {
693        instance_unload(default_instance()?, path)
694    }
695
696    /// Unloads a plugin, by absolute path.
697    pub fn instance_unload(instance: &Instance, path: &Path) -> crate::Result<()> {
698        let str = instance.write_to_socket(command!(Empty, "plugin unload {}", path.display()))?;
699        if str.contains("plugin not loaded") {
700            Err(HyprError::Internal(str))
701        } else {
702            Ok(())
703        }
704    }
705
706    /// Unloads a plugin, by absolute path (async)
707    #[cfg(any(feature = "async-lite", feature = "tokio"))]
708    pub async fn unload_async(path: &Path) -> crate::Result<()> {
709        instance_unload_async(default_instance()?, path).await
710    }
711
712    /// Unloads a plugin, by absolute path (async)
713    #[cfg(any(feature = "async-lite", feature = "tokio"))]
714    pub async fn instance_unload_async(instance: &Instance, path: &Path) -> crate::Result<()> {
715        let str = instance
716            .write_to_socket_async(command!(Empty, "plugin unload {}", path.display()))
717            .await?;
718        if str.contains("plugin not loaded") {
719            Err(HyprError::Internal(str))
720        } else {
721            Ok(())
722        }
723    }
724}
725
726/// This module allows listing running hyprland instances
727pub mod instance {
728    use crate::shared::get_hypr_path;
729    use std::fs::{DirEntry, File};
730    use std::io::Read;
731    use std::path::Path;
732
733    /// This struct represents a running Hyprland instance
734    #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
735    pub struct Instance {
736        /// instance name (9958d29...) in /run/user/$UID/hypr/$instance
737        pub instance: String,
738        /// ???
739        pub time: u64,
740        /// pid of hyprland process
741        pub pid: u32,
742        /// name of wayland socket in /run/user/$UID/$wl_socket
743        pub wl_socket: String,
744    }
745
746    /// Returns a list of running instances
747    pub fn instance_list() -> crate::Result<Vec<Instance>> {
748        let buf = get_hypr_path()?;
749        let entries = std::fs::read_dir(buf)?;
750        let mut instances = Vec::new();
751        for entry in entries.flatten() {
752            if let Some(instance) = parse_instance_entry(entry) {
753                instances.push(instance);
754            }
755        }
756        instances.retain(|el| Path::new(&format!("/proc/{}", el.pid)).exists());
757        Ok(instances)
758    }
759
760    fn parse_instance_entry(entry: DirEntry) -> Option<Instance> {
761        let file_name = entry.file_name().to_string_lossy().to_string();
762        let first = file_name.find('_')?;
763        let last = file_name.rfind('_')?;
764        if last <= first {
765            return None;
766        }
767        let time = file_name[first + 1..last].parse::<u64>().ok()?;
768
769        let lock_path = entry.path().join("hyprland.lock");
770        let mut file = File::open(&lock_path).ok()?;
771        if file.metadata().ok()?.len() == 0 {
772            return None; // Empty lock file, skip this instance
773        }
774        let mut content = String::new();
775        file.read_to_string(&mut content).ok()?;
776        let data = content
777            .lines()
778            .map(|line| line.trim().to_string())
779            .collect::<Vec<_>>();
780        if data.len() != 2 {
781            return None;
782        }
783
784        let pid = data.first().and_then(|s| s.parse::<u32>().ok())?;
785        let wl_socket = data.get(1).cloned().unwrap_or_default();
786
787        Some(Instance {
788            instance: file_name,
789            time,
790            pid,
791            wl_socket,
792        })
793    }
794}