retrom_plugin_steam/
lib.rs

1use std::collections::HashSet;
2
3use notify::{EventKind, Watcher};
4use tauri::{
5    plugin::{Builder, TauriPlugin},
6    Emitter, Manager, Runtime,
7};
8
9mod commands;
10mod desktop;
11mod error;
12
13pub use error::{Error, Result};
14
15use desktop::Steam;
16use tracing::instrument;
17
18/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the steam APIs.
19pub trait SteamExt<R: Runtime> {
20    fn steam(&self) -> Option<&Steam<R>>;
21}
22
23impl<R: Runtime, T: Manager<R>> crate::SteamExt<R> for T {
24    fn steam(&self) -> Option<&Steam<R>> {
25        self.try_state::<Steam<R>>().map(|s| s.inner())
26    }
27}
28
29/// Initializes the plugin.
30#[instrument(skip_all)]
31pub fn init<R: Runtime>() -> TauriPlugin<R> {
32    Builder::new("steam")
33        .invoke_handler(tauri::generate_handler![])
34        .setup(|app, api| {
35            let steam = match desktop::init(app, api) {
36                Ok(steam) => steam,
37                Err(e) => {
38                    tracing::warn!(
39                        "Failed to initialize steam plugin, is steam installed: {:?}",
40                        e
41                    );
42                    return Ok(());
43                }
44            };
45
46            let handle = app.app_handle().clone();
47            std::thread::spawn(move || {
48                let steam = match steamlocate::SteamDir::locate() {
49                    Ok(steam) => steam,
50                    Err(e) => {
51                        tracing::debug!("Could not locate steam directory: {:?}", e);
52                        return;
53                    }
54                };
55
56                let libraries = steam.library_paths().unwrap_or_default();
57
58                let mut apps_installed = match steam.libraries() {
59                    Ok(libraries) => libraries
60                        .filter_map(std::result::Result::ok)
61                        .flat_map(|lib| lib.app_ids().to_vec())
62                        .collect::<HashSet<_>>(),
63
64                    Err(e) => {
65                        tracing::debug!("Could not get steam libraries: {:?}", e);
66                        return;
67                    }
68                };
69
70                let (tx, rx) = std::sync::mpsc::channel::<notify::Result<notify::Event>>();
71
72                let mut watchers = vec![];
73                for lib in libraries.iter() {
74                    let tx = tx.clone();
75                    let mut watcher = match notify::recommended_watcher(tx) {
76                        Ok(watcher) => watcher,
77                        Err(e) => {
78                            tracing::warn!("Error creating watcher: {:?}", e);
79                            continue;
80                        }
81                    };
82
83                    if let Err(why) = watcher.watch(lib.as_path(), notify::RecursiveMode::Recursive)
84                    {
85                        tracing::warn!("Error watching directory: {:?}", why);
86                        continue;
87                    }
88
89                    watchers.push(watcher);
90                }
91
92                for res in rx {
93                    match res {
94                        Ok(event) => match event.kind {
95                            EventKind::Create(_) | EventKind::Remove(_) => {
96                                let libraries = match steam.libraries() {
97                                    Ok(libraries) => libraries,
98                                    Err(e) => {
99                                        tracing::warn!("Could not get steam libraries: {:?}", e);
100                                        continue;
101                                    }
102                                };
103
104                                let app_ids = libraries
105                                    .filter_map(std::result::Result::ok)
106                                    .flat_map(|lib| lib.app_ids().to_vec())
107                                    .collect::<HashSet<_>>();
108
109                                let newly_installed = app_ids.difference(&apps_installed);
110                                let newly_uninstalled = apps_installed.difference(&app_ids);
111
112                                if newly_installed.count() > 0 {
113                                    if let Err(why) = handle.emit("steam-game-installed", ()) {
114                                        tracing::error!("Error emitting event: {:?}", why);
115                                    }
116                                }
117
118                                if newly_uninstalled.count() > 0 {
119                                    if let Err(why) = handle.emit("steam-game-uninstalled", ()) {
120                                        tracing::error!("Error emitting event: {:?}", why);
121                                    }
122                                }
123
124                                apps_installed = app_ids;
125                            }
126                            _ => {}
127                        },
128                        Err(e) => {
129                            tracing::error!("watch error: {:?}", e);
130                        }
131                    }
132                }
133            });
134
135            app.manage(steam);
136            Ok(())
137        })
138        .build()
139}