From f4750ca9b7ed1c2282407d4b8bef6699c190e922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20DIDIER?= Date: Wed, 28 May 2025 20:34:01 +0200 Subject: [PATCH 1/5] added support for non steam game with cover art support --- .gitignore | 4 +- libsteamium/Cargo.lock | 67 +++++++++++++++ libsteamium/Cargo.toml | 3 + libsteamium/cli/Cargo.lock | 67 +++++++++++++++ libsteamium/src/lib.rs | 148 +++++++++++++++++++++++++++++++--- src-tauri/Cargo.lock | 57 +++++++++++++ src-tauri/Cargo.toml | 1 + src-tauri/src/frontend_ipc.rs | 35 ++++++-- src-tauri/src/lib.rs | 1 + src/gui/gui.tsx | 96 +++++++++++++++------- src/ipc.ts | 20 +++-- src/panel/games.tsx | 40 +++++---- src/utils.ts | 2 +- 13 files changed, 469 insertions(+), 72 deletions(-) diff --git a/.gitignore b/.gitignore index 1d184ac..3edd8d7 100644 --- a/.gitignore +++ b/.gitignore @@ -24,5 +24,7 @@ dist-ssr *.sw? libsteamium/target - +libsteamium/cli/target +src-tauri/ressources/ temp +public/covers/ \ No newline at end of file diff --git a/libsteamium/Cargo.lock b/libsteamium/Cargo.lock index 457b0b5..dcb0778 100644 --- a/libsteamium/Cargo.lock +++ b/libsteamium/Cargo.lock @@ -8,6 +8,33 @@ version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + +[[package]] +name = "bytecount" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "itoa" version = "1.0.15" @@ -28,6 +55,7 @@ dependencies = [ "log", "serde", "serde_json", + "steam_shortcuts_util", ] [[package]] @@ -42,6 +70,33 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom_locate" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e3c83c053b0713da60c5b8de47fe8e494fe3ece5267b2f23090a07a053ba8f3" +dependencies = [ + "bytecount", + "memchr", + "nom", +] + [[package]] name = "proc-macro2" version = "1.0.94" @@ -98,6 +153,18 @@ dependencies = [ "serde", ] +[[package]] +name = "steam_shortcuts_util" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0543ebdb23a93b196aceebc53f70cc5a573bb74248a974b3f5fa3883e6a89b6" +dependencies = [ + "ascii", + "crc32fast", + "nom", + "nom_locate", +] + [[package]] name = "syn" version = "2.0.100" diff --git a/libsteamium/Cargo.toml b/libsteamium/Cargo.toml index 07d3d37..4613422 100644 --- a/libsteamium/Cargo.toml +++ b/libsteamium/Cargo.toml @@ -9,3 +9,6 @@ keyvalues-parser = { git = "https://github.com/CosmicHorrorDev/vdf-rs.git", rev log = "0.4.27" serde = { version = "1.0.218", features = ["derive"] } serde_json = "1.0.140" +steam_shortcuts_util = "1.1.7" +base64 = "0.22.1" +env = "1.0.1" \ No newline at end of file diff --git a/libsteamium/cli/Cargo.lock b/libsteamium/cli/Cargo.lock index 6b59465..4210b55 100644 --- a/libsteamium/cli/Cargo.lock +++ b/libsteamium/cli/Cargo.lock @@ -67,6 +67,24 @@ version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + +[[package]] +name = "bytecount" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clap" version = "4.5.35" @@ -113,6 +131,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "env_filter" version = "0.1.3" @@ -192,6 +219,7 @@ dependencies = [ "log", "serde", "serde_json", + "steam_shortcuts_util", ] [[package]] @@ -206,6 +234,33 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom_locate" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e3c83c053b0713da60c5b8de47fe8e494fe3ece5267b2f23090a07a053ba8f3" +dependencies = [ + "bytecount", + "memchr", + "nom", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -312,6 +367,18 @@ dependencies = [ "serde", ] +[[package]] +name = "steam_shortcuts_util" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0543ebdb23a93b196aceebc53f70cc5a573bb74248a974b3f5fa3883e6a89b6" +dependencies = [ + "ascii", + "crc32fast", + "nom", + "nom_locate", +] + [[package]] name = "steamium_cli" version = "0.1.0" diff --git a/libsteamium/src/lib.rs b/libsteamium/src/lib.rs index 90e8b24..1a4e182 100644 --- a/libsteamium/src/lib.rs +++ b/libsteamium/src/lib.rs @@ -1,6 +1,10 @@ use keyvalues_parser::{Obj, Vdf}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; +use steam_shortcuts_util::parse_shortcuts; +use std::path::Path; +use std::env; +use std::fs; pub struct Steamium { steam_root: PathBuf, @@ -25,12 +29,14 @@ fn get_steam_root() -> anyhow::Result { Ok(steam_path) } -pub type AppID = u64; +pub type AppID = String; #[derive(Debug, Serialize, Deserialize)] pub struct AppManifest { app_id: AppID, + run_game_id: AppID, name: String, + cover : String, raw_state_flags: u64, // documentation: https://github.com/lutris/lutris/blob/master/docs/steam.rst last_played: Option, // unix timestamp } @@ -81,7 +87,7 @@ fn vdf_parse_libraryfolders<'a>(vdf_root: &'a Vdf<'a>) -> Option> .iter() .filter_map(|item| item.0.parse::().ok()) .map(|app_id| AppEntry { - app_id, + app_id : app_id.to_string(), root_path: String::from(path), }), ); @@ -110,7 +116,9 @@ fn vdf_parse_appstate<'a>(app_id: AppID, vdf_root: &'a Vdf<'a>) -> Option anyhow::Result<()> { pub fn launch(app_id: AppID) -> anyhow::Result<()> { log::info!("Launching Steam game with AppID {}", app_id); - call_steam(&format!("steam://run/{}", app_id))?; + call_steam(&format!("steam://rungameid/{}", app_id))?; Ok(()) } #[derive(Serialize)] pub struct RunningGame { pub app_id: AppID, - pub pid: i32, + pub pid: u64, } +#[derive(Serialize)] +struct Shortcut { + name: String, + exe: String, + run_game_id: u64, + app_id : u64, + cover : String +} + + pub fn list_running_games() -> anyhow::Result> { let mut res = Vec::::new(); @@ -167,7 +185,7 @@ pub fn list_running_games() -> anyhow::Result> { continue; }; - let Ok(pid) = pid.parse::() else { + let Ok(pid) = pid.parse::() else { continue; }; @@ -212,8 +230,8 @@ pub fn list_running_games() -> anyhow::Result> { // AppID found. Add it to the list res.push(RunningGame { - app_id: app_id_num as AppID, - pid, + app_id: app_id_num.to_string(), + pid : pid as u64 }); break; @@ -224,6 +242,7 @@ pub fn list_running_games() -> anyhow::Result> { } fn call_steam(arg: &str) -> anyhow::Result<()> { + println!("{}", arg); match std::process::Command::new("xdg-open").arg(arg).spawn() { Ok(_) => Ok(()), Err(_) => { @@ -233,7 +252,100 @@ fn call_steam(arg: &str) -> anyhow::Result<()> { } } +fn shortcut_to_fake_manifest(shortcut: &Shortcut) -> AppManifest { + + AppManifest { + app_id : shortcut.app_id.to_string(), + run_game_id: shortcut.run_game_id.to_string(), + name: shortcut.name.clone(), + cover : shortcut.cover.clone(), + raw_state_flags: 0, // Pas applicable, 0 par défaut + last_played: None, // Steam ne stocke pas ça pour les shortcuts + } +} + +fn compute_rungameid(app_id: u32) -> u64 { + (app_id as u64) << 32 | 0x02000000 +} + + impl Steamium { + + pub fn get_cover_file_path(app_id: &u32) -> String { + + let filename = format!("{}.png", app_id); + let relative = PathBuf::from("../../ressources").join("covers").join(filename); + + if let Ok(current_dir) = env::current_dir() { + let absolute = current_dir.join(relative); + absolute.to_string_lossy().into_owned() + } else { + String::new() + } + } + + fn copy_cover_to_ressources(app_id: &u32, original_path: &Path) -> std::io::Result { + let filename = format!("{}.png", app_id); + let relative_path = format!("/covers/{}", filename); // This is what frontend will use + let dest_path = Path::new("../../ressources").join("covers").join(filename); + fs::create_dir_all(dest_path.parent().unwrap())?; + fs::copy(original_path, dest_path.clone())?; + println!("{}/{}",std::env::current_dir()?.display(), dest_path.display()); + Ok(relative_path) + } + + pub fn copy_cover_to_front(app_id:&u32, destination :&Path ) -> std::io::Result<()>{ + let original_path = Steamium::get_cover_file_path(&app_id); + fs::create_dir_all(destination.parent().unwrap())?; + fs::copy(original_path, destination)?; + Ok(()) + } + + + fn list_shortcuts(&self) -> Result, Box> { + let userdata_dir = self.steam_root.join("userdata"); + let user_dirs = fs::read_dir(userdata_dir)?; + + let mut shortcuts: Vec = Vec::new(); + + for user in user_dirs.flatten() { + let path = user.path().join("config").join("shortcuts.vdf"); + + if !path.exists() { + continue; + } + + let content = std::fs::read(&path)?; + let shortcuts_data =parse_shortcuts(content.as_slice())?; + + for s in shortcuts_data { + let run_game_id = compute_rungameid(s.app_id); + let cover_path = user.path().join("config").join("grid").join(format!("{}p.png", s.app_id)); + let local_cover_path = match Steamium::copy_cover_to_ressources(&s.app_id, &cover_path){ + Ok(path) => path, // If successful, use the new path + Err(e) => { + eprintln!("Error copying cover for app {}: {}", s.app_id, e); + String::from("") // Return an empty string if there was an error + } + }; + + println!("Local Cover Path : {}",local_cover_path); + + + shortcuts.push(Shortcut { + name: s.app_name.to_string(), + exe: s.exe.to_string(), + run_game_id: run_game_id, + app_id : s.app_id as u64, + cover : local_cover_path + }); + } + } + + + Ok(shortcuts) + } + fn get_dir_steamapps(&self) -> PathBuf { self.steam_root.join("steamapps") } @@ -251,7 +363,7 @@ impl Steamium { let vdf_data = std::fs::read_to_string(manifest_path)?; let vdf_root = keyvalues_parser::Vdf::parse(&vdf_data)?; - let Some(manifest) = vdf_parse_appstate(app_entry.app_id, &vdf_root) else { + let Some(manifest) = vdf_parse_appstate(app_entry.app_id.clone(), &vdf_root) else { anyhow::bail!("Failed to parse AppState"); }; @@ -287,7 +399,18 @@ impl Steamium { }; Some(manifest) }) - .collect(); + .collect(); + + if let Ok(shortcuts) = self.list_shortcuts() { + let mut fake_manifests = shortcuts + .iter() + .map(shortcut_to_fake_manifest) + .collect::>(); + games.append(&mut fake_manifests); + } else { + println!("Failed to read non-Steam shortcuts"); + } + match sort_method { GameSortMethod::NameAsc => { @@ -301,6 +424,11 @@ impl Steamium { } } + + + Ok(games) } + + } diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 313c085..28530cd 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -62,6 +62,12 @@ version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + [[package]] name = "atk" version = "0.18.2" @@ -216,6 +222,12 @@ version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +[[package]] +name = "bytecount" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" + [[package]] name = "bytemuck" version = "1.22.0" @@ -844,6 +856,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc95de49ad098572c02d3fbf368c9a020bfff5ae78483685b77f51d8a7e9486d" +dependencies = [ + "num_threads", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -2078,10 +2099,13 @@ name = "libsteamium" version = "0.1.0" dependencies = [ "anyhow", + "base64 0.22.1", + "env", "keyvalues-parser", "log", "serde", "serde_json", + "steam_shortcuts_util", ] [[package]] @@ -2280,6 +2304,17 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nom_locate" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e3c83c053b0713da60c5b8de47fe8e494fe3ece5267b2f23090a07a053ba8f3" +dependencies = [ + "bytecount", + "memchr", + "nom", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2326,6 +2361,15 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "objc-sys" version = "0.3.5" @@ -3736,6 +3780,18 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "steam_shortcuts_util" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0543ebdb23a93b196aceebc53f70cc5a573bb74248a974b3f5fa3883e6a89b6" +dependencies = [ + "ascii", + "crc32fast", + "nom", + "nom_locate", +] + [[package]] name = "string_cache" version = "0.8.9" @@ -4874,6 +4930,7 @@ version = "0.3.4" dependencies = [ "anyhow", "bytes", + "env", "gio 0.20.9", "glib 0.20.9", "gtk", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 31bdc2f..5540346 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -58,3 +58,4 @@ tracing = "0.1.41" gio = "0.20.9" glib = "0.20.9" gtk = "0.18.2" +env = "1.0.1" diff --git a/src-tauri/src/frontend_ipc.rs b/src-tauri/src/frontend_ipc.rs index 0a93e9f..81602a6 100644 --- a/src-tauri/src/frontend_ipc.rs +++ b/src-tauri/src/frontend_ipc.rs @@ -9,7 +9,8 @@ use wayvr_ipc::{ }; use crate::util::{self, pactl_wrapper}; - +use std::env; +use std::path::PathBuf; type AppStateType = Mutex; #[derive(Debug, Serialize)] @@ -73,14 +74,38 @@ pub async fn game_list(state: tauri::State<'_, AppStateType>) -> Result Result<(), String> { + // Parse the app ID string to u32 + let app_id = app_id_str + .parse::() + .map_err(|e| format!("Invalid app ID: {}", e))?; + + + // Create the relative path to "public/covers" + let relative = PathBuf::from("public").join("covers"); + + // Resolve absolute path from current directory + let current_dir = env::current_dir() + .map_err(|e| format!("Failed to get current directory: {}", e))?; + + let absolute = current_dir.join("../../../").join(&relative).join(format!("{}.png",app_id_str)); + + let _ = libsteamium::Steamium::copy_cover_to_front(&app_id,&absolute); + Ok(()) +} + + #[tauri::command] -pub fn game_launch(app_id: i32) -> Result<(), String> { - handle_result("launch a game", libsteamium::launch(app_id as u64)) +pub fn game_launch(app_id: String) -> Result<(), String> { + println!("app id received {}", app_id); + handle_result("launch a game", libsteamium::launch(app_id)) } #[tauri::command] -pub fn game_stop(app_id: i32, force: bool) -> Result<(), String> { - handle_result("stop a game", libsteamium::stop(app_id as u64, force)) +pub fn game_stop(app_id: String, force: bool) -> Result<(), String> { + handle_result("stop a game", libsteamium::stop(app_id, force)) } #[tauri::command] diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 75e7651..f3a4648 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -19,6 +19,7 @@ pub async fn run(params: AppParams) { }) .invoke_handler(tauri::generate_handler![ frontend_ipc::is_nvidia, + frontend_ipc::copy_png_to_frontend_public, frontend_ipc::desktop_file_list, frontend_ipc::game_list, frontend_ipc::game_launch, diff --git a/src/gui/gui.tsx b/src/gui/gui.tsx index 49bf576..b8352eb 100644 --- a/src/gui/gui.tsx +++ b/src/gui/gui.tsx @@ -257,7 +257,7 @@ export function TooltipSimple({ children, title, extend }: { children: any, titl } class FailedCovers { - covers = new Array; + covers = new Array; } export function failed_covers_clear() { @@ -277,41 +277,75 @@ function failed_covers_set(covers: FailedCovers) { localStorage.setItem("failed_covers", JSON.stringify(covers)); } -function get_alt_cover(manifest: ipc.AppManifest) { - return <> - - {manifest.name} - ; +async function get_alt_cover(manifest: ipc.AppManifest) { + + if(manifest.cover!= ""){ + await ipc.copy_png_to_frontend_public(manifest.app_id); + return <> + + ; + } + else{ + return <> + + {manifest.name} + ; + } + } export function GameCover({ manifest, big, on_click }: { manifest: ipc.AppManifest, big?: boolean, on_click?: () => void }) { const [content, setContent] = useState(<>); useEffect(() => { - const failed_covers = failed_covers_get(); - - const ret = failed_covers.covers.find((val) => { return val == manifest.app_id }); - if (ret === undefined) { - const url = "https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/" + manifest.app_id + "/library_600x900.jpg"; - setContent( { - console.log("Alt cover " + manifest.app_id + " failed to load, remembering it to prevent unnecessary requests to Steam API "); - const covers = failed_covers_get(); - covers.covers.push(manifest.app_id); - failed_covers_set(covers); - setContent(get_alt_cover(manifest)); - }} />); - } - else { - console.log("using previously failed cover"); - setContent(get_alt_cover(manifest)); - } - }, []); + const run = async () => { + try { + const failed_covers = failed_covers_get(); + const already_failed = failed_covers.covers.includes(manifest.app_id); + + if (!already_failed) { + const url = `https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/${manifest.app_id}/library_600x900.jpg`; + setContent( + { + try { + console.log("Alt cover " + manifest.app_id + " failed to load, remembering it to prevent unnecessary requests to Steam API "); + const updated_covers = failed_covers_get(); + updated_covers.covers.push(manifest.app_id); + failed_covers_set(updated_covers); + setContent(await get_alt_cover(manifest)); + } catch (err) { + console.error("Failed to load alt cover:", err); + } + }} + /> + ); + } else { + console.log("using previously failed cover"); + setContent(await get_alt_cover(manifest)); + } + } catch (err) { + console.error("Unhandled error in GameCover useEffect:", err); + } + }; - return
- {content} -
-
+ run(); + }, [manifest]); + + return ( +
+ {content} +
+
+ ); } export function ApplicationCover({ big, application, on_click }: { big?: boolean, application: ipc.DesktopFile, on_click?: () => void }) { @@ -565,7 +599,7 @@ function ManifestView({ globals, manifest }: { globals: Globals, manifest: ipc.A
{manifest.name}
{details} { - ipc.game_launch(manifest.app_id); + ipc.game_launch(manifest.run_game_id); globals.toast_manager.push("Game launched. This might take a while"); }} /> @@ -720,4 +754,4 @@ export function Popup({ children, on_close, ref_element }: { children: any, on_c }}> {children}
-} \ No newline at end of file +} diff --git a/src/ipc.ts b/src/ipc.ts index f7f0d34..1e4bcf5 100644 --- a/src/ipc.ts +++ b/src/ipc.ts @@ -24,10 +24,12 @@ export namespace ipc { } export interface AppManifest { - app_id: number; + app_id: string; + run_game_id : string; name: string; raw_state_flags: number; - last_played?: number + last_played?: number; + cover : string } export interface Games { @@ -108,23 +110,27 @@ export namespace ipc { } export interface SteamiumRunningGame { - app_id: number; - pid: number; + app_id: string; + pid: string; } export async function desktop_file_list(): Promise> { return await invoke("desktop_file_list"); } + export async function copy_png_to_frontend_public(app_id: string) : Promise { + return await invoke("copy_png_to_frontend_public",{appIdStr : app_id}); + } + export async function game_list(): Promise { return await invoke("game_list"); } - export async function game_launch(app_id: number): Promise { + export async function game_launch(app_id: string): Promise { return await invoke("game_launch", { appId: app_id }) } - export async function game_stop(app_id: number, force: boolean): Promise { + export async function game_stop(app_id: string, force: boolean): Promise { return await invoke("game_stop", { appId: app_id, force: force }); } @@ -323,7 +329,7 @@ export namespace ipc { } export interface ProcessHandle { - idx: number; + idx: bigint; generation: number; } diff --git a/src/panel/games.tsx b/src/panel/games.tsx index 8dcf40e..e3efcd8 100644 --- a/src/panel/games.tsx +++ b/src/panel/games.tsx @@ -1,12 +1,12 @@ import { BoxDown, BoxRight, Button, Container, createWindowManifest, GameCover, Icon, Separator, Title, TooltipSimple } from "../gui/gui" import style from "../app.module.scss" -import { useEffect, useMemo, useState } from "preact/hooks"; +import { useEffect, useState } from "preact/hooks"; import { ipc } from "../ipc"; import { Globals } from "../globals"; import { get_external_url } from "@/utils"; interface GameIcon { - app_id: number; + app_id: string; icon_path?: string; } @@ -27,9 +27,10 @@ async function game_icon_list(): Promise { } out.push({ - app_id: parseInt(appid_str), + app_id: appid_str, icon_path: cell.icon }); + } return out; @@ -144,20 +145,25 @@ export function PanelGames({ globals }: { globals: Globals }) { const [games, setGames] = useState(undefined); - useMemo(async () => { - const games = await ipc.game_list(); - setGames(games); - - const arr = games.manifests.map((manifest) => { - return { - createWindowManifest(globals, manifest); - }} manifest={manifest} /> - }); - - setList(<> - {arr} - ); - }, []) + useEffect(() => { + const fetchGames = async () => { + const games = await ipc.game_list(); + console.log("Fetched games:", games); // 👈 Print all data + setGames(games); + + const arr = games.manifests.map((manifest) => ( + createWindowManifest(globals, manifest)} + manifest={manifest} + /> + )); + + setList(<>{arr}); + }; + + fetchGames(); + }, []); return <> {games ? : undefined} diff --git a/src/utils.ts b/src/utils.ts index 7f0c6d7..aa538dc 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -23,7 +23,7 @@ class AppDetailItem { data!: AppDetails; } -export async function get_app_details_json(app_id: number) { +export async function get_app_details_json(app_id: string) { const key = "app_details_" + app_id; const storage = window.localStorage; From 66b2768e7888dedf4c10d061e31b0d6304e8b932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20DIDIER?= Date: Thu, 29 May 2025 14:00:54 +0200 Subject: [PATCH 2/5] removed copy to replace it by a base64 load --- libsteamium/Cargo.lock | 32 ++++++++++++++++++++++++ libsteamium/cli/Cargo.lock | 32 ++++++++++++++++++++++++ libsteamium/src/lib.rs | 46 ++++++++++++++++++----------------- src-tauri/src/frontend_ipc.rs | 24 ------------------ src-tauri/src/lib.rs | 1 - src-tauri/tauri.conf.json | 2 +- src/gui/gui.tsx | 2 +- src/ipc.ts | 3 --- wayvr-dashboard.desktop | 1 + 9 files changed, 91 insertions(+), 52 deletions(-) mode change 100644 => 100755 wayvr-dashboard.desktop diff --git a/libsteamium/Cargo.lock b/libsteamium/Cargo.lock index dcb0778..5da5f46 100644 --- a/libsteamium/Cargo.lock +++ b/libsteamium/Cargo.lock @@ -14,6 +14,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bytecount" version = "0.6.8" @@ -35,6 +41,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc95de49ad098572c02d3fbf368c9a020bfff5ae78483685b77f51d8a7e9486d" +dependencies = [ + "num_threads", +] + [[package]] name = "itoa" version = "1.0.15" @@ -46,11 +61,19 @@ name = "keyvalues-parser" version = "0.2.0" source = "git+https://github.com/CosmicHorrorDev/vdf-rs.git?rev=2aff7147c5ba2bf2551566269dc167099fe7d527#2aff7147c5ba2bf2551566269dc167099fe7d527" +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + [[package]] name = "libsteamium" version = "0.1.0" dependencies = [ "anyhow", + "base64", + "env", "keyvalues-parser", "log", "serde", @@ -97,6 +120,15 @@ dependencies = [ "nom", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "proc-macro2" version = "1.0.94" diff --git a/libsteamium/cli/Cargo.lock b/libsteamium/cli/Cargo.lock index 4210b55..3719786 100644 --- a/libsteamium/cli/Cargo.lock +++ b/libsteamium/cli/Cargo.lock @@ -73,6 +73,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bytecount" version = "0.6.8" @@ -140,6 +146,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc95de49ad098572c02d3fbf368c9a020bfff5ae78483685b77f51d8a7e9486d" +dependencies = [ + "num_threads", +] + [[package]] name = "env_filter" version = "0.1.3" @@ -210,11 +225,19 @@ name = "keyvalues-parser" version = "0.2.0" source = "git+https://github.com/CosmicHorrorDev/vdf-rs.git?rev=2aff7147c5ba2bf2551566269dc167099fe7d527#2aff7147c5ba2bf2551566269dc167099fe7d527" +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + [[package]] name = "libsteamium" version = "0.1.0" dependencies = [ "anyhow", + "base64", + "env", "keyvalues-parser", "log", "serde", @@ -261,6 +284,15 @@ dependencies = [ "nom", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "once_cell" version = "1.21.3" diff --git a/libsteamium/src/lib.rs b/libsteamium/src/lib.rs index 1a4e182..6840ee2 100644 --- a/libsteamium/src/lib.rs +++ b/libsteamium/src/lib.rs @@ -5,6 +5,8 @@ use steam_shortcuts_util::parse_shortcuts; use std::path::Path; use std::env; use std::fs; +use std::io::Read; +use base64::{engine::general_purpose, Engine as _}; pub struct Steamium { steam_root: PathBuf, @@ -284,23 +286,23 @@ impl Steamium { } } - fn copy_cover_to_ressources(app_id: &u32, original_path: &Path) -> std::io::Result { - let filename = format!("{}.png", app_id); - let relative_path = format!("/covers/{}", filename); // This is what frontend will use - let dest_path = Path::new("../../ressources").join("covers").join(filename); - fs::create_dir_all(dest_path.parent().unwrap())?; - fs::copy(original_path, dest_path.clone())?; - println!("{}/{}",std::env::current_dir()?.display(), dest_path.display()); - Ok(relative_path) - } + fn convert_cover_to_base64(app_id: &u32, original_path: &Path) -> std::io::Result { - pub fn copy_cover_to_front(app_id:&u32, destination :&Path ) -> std::io::Result<()>{ - let original_path = Steamium::get_cover_file_path(&app_id); - fs::create_dir_all(destination.parent().unwrap())?; - fs::copy(original_path, destination)?; - Ok(()) - } + let filepath = original_path.join("grid").join(format!("{}p.png", app_id)); + println!("steam cover art location {}",filepath.display()); + // Read the image file as bytes + let mut file = fs::File::open(filepath)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + // Convert bytes to Base64 string + let base64_string = general_purpose::STANDARD.encode(&buffer); + + // Add data URI prefix so it can be used directly in + let data_uri = format!("data:image/png;base64,{}", base64_string); + + Ok(data_uri) + } fn list_shortcuts(&self) -> Result, Box> { let userdata_dir = self.steam_root.join("userdata"); @@ -309,19 +311,19 @@ impl Steamium { let mut shortcuts: Vec = Vec::new(); for user in user_dirs.flatten() { - let path = user.path().join("config").join("shortcuts.vdf"); + let config_path = user.path().join("config"); + let shortcut_path = config_path.join("shortcuts.vdf"); - if !path.exists() { + if !shortcut_path.exists() { continue; } - let content = std::fs::read(&path)?; + let content = std::fs::read(&shortcut_path)?; let shortcuts_data =parse_shortcuts(content.as_slice())?; for s in shortcuts_data { let run_game_id = compute_rungameid(s.app_id); - let cover_path = user.path().join("config").join("grid").join(format!("{}p.png", s.app_id)); - let local_cover_path = match Steamium::copy_cover_to_ressources(&s.app_id, &cover_path){ + let cover_base64 = match Steamium::convert_cover_to_base64(&s.app_id, &config_path){ Ok(path) => path, // If successful, use the new path Err(e) => { eprintln!("Error copying cover for app {}: {}", s.app_id, e); @@ -329,7 +331,7 @@ impl Steamium { } }; - println!("Local Cover Path : {}",local_cover_path); + println!("Local Cover Path : {}",cover_base64); shortcuts.push(Shortcut { @@ -337,7 +339,7 @@ impl Steamium { exe: s.exe.to_string(), run_game_id: run_game_id, app_id : s.app_id as u64, - cover : local_cover_path + cover : cover_base64 }); } } diff --git a/src-tauri/src/frontend_ipc.rs b/src-tauri/src/frontend_ipc.rs index 81602a6..7ef56cb 100644 --- a/src-tauri/src/frontend_ipc.rs +++ b/src-tauri/src/frontend_ipc.rs @@ -9,8 +9,6 @@ use wayvr_ipc::{ }; use crate::util::{self, pactl_wrapper}; -use std::env; -use std::path::PathBuf; type AppStateType = Mutex; #[derive(Debug, Serialize)] @@ -75,28 +73,6 @@ pub async fn game_list(state: tauri::State<'_, AppStateType>) -> Result Result<(), String> { - // Parse the app ID string to u32 - let app_id = app_id_str - .parse::() - .map_err(|e| format!("Invalid app ID: {}", e))?; - - - // Create the relative path to "public/covers" - let relative = PathBuf::from("public").join("covers"); - - // Resolve absolute path from current directory - let current_dir = env::current_dir() - .map_err(|e| format!("Failed to get current directory: {}", e))?; - - let absolute = current_dir.join("../../../").join(&relative).join(format!("{}.png",app_id_str)); - - let _ = libsteamium::Steamium::copy_cover_to_front(&app_id,&absolute); - Ok(()) -} - - #[tauri::command] pub fn game_launch(app_id: String) -> Result<(), String> { println!("app id received {}", app_id); diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index f3a4648..75e7651 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -19,7 +19,6 @@ pub async fn run(params: AppParams) { }) .invoke_handler(tauri::generate_handler![ frontend_ipc::is_nvidia, - frontend_ipc::copy_png_to_frontend_public, frontend_ipc::desktop_file_list, frontend_ipc::game_list, frontend_ipc::game_launch, diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index ebac716..e7cb275 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -21,7 +21,7 @@ } ], "security": { - "csp": "default-src 'self'; img-src 'self' asset: http://asset.localhost https://shared.cloudflare.steamstatic.com; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'", + "csp": "default-src 'self'; img-src 'self' data: asset: http://asset.localhost https://shared.cloudflare.steamstatic.com; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'", "assetProtocol": { "enable": true, "scope": { diff --git a/src/gui/gui.tsx b/src/gui/gui.tsx index b8352eb..4cec74f 100644 --- a/src/gui/gui.tsx +++ b/src/gui/gui.tsx @@ -280,7 +280,7 @@ function failed_covers_set(covers: FailedCovers) { async function get_alt_cover(manifest: ipc.AppManifest) { if(manifest.cover!= ""){ - await ipc.copy_png_to_frontend_public(manifest.app_id); + console.log("Runnning copy png to frontend") return <> ; diff --git a/src/ipc.ts b/src/ipc.ts index 1e4bcf5..c8ea297 100644 --- a/src/ipc.ts +++ b/src/ipc.ts @@ -118,9 +118,6 @@ export namespace ipc { return await invoke("desktop_file_list"); } - export async function copy_png_to_frontend_public(app_id: string) : Promise { - return await invoke("copy_png_to_frontend_public",{appIdStr : app_id}); - } export async function game_list(): Promise { return await invoke("game_list"); diff --git a/wayvr-dashboard.desktop b/wayvr-dashboard.desktop old mode 100644 new mode 100755 index 29d56e0..59b1df8 --- a/wayvr-dashboard.desktop +++ b/wayvr-dashboard.desktop @@ -1,3 +1,4 @@ +#!/usr/bin/env xdg-open [Desktop Entry] Type=Application Name=WayVR Dashboard From 08069e5697d327dc146c0ca7abdd7403385e4766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20DIDIER?= Date: Thu, 29 May 2025 17:16:01 +0200 Subject: [PATCH 3/5] added compatibility for every steamgriddb image types --- libsteamium/src/lib.rs | 52 +++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/libsteamium/src/lib.rs b/libsteamium/src/lib.rs index 6840ee2..dcdcea4 100644 --- a/libsteamium/src/lib.rs +++ b/libsteamium/src/lib.rs @@ -244,7 +244,6 @@ pub fn list_running_games() -> anyhow::Result> { } fn call_steam(arg: &str) -> anyhow::Result<()> { - println!("{}", arg); match std::process::Command::new("xdg-open").arg(arg).spawn() { Ok(_) => Ok(()), Err(_) => { @@ -286,24 +285,41 @@ impl Steamium { } } - fn convert_cover_to_base64(app_id: &u32, original_path: &Path) -> std::io::Result { - - let filepath = original_path.join("grid").join(format!("{}p.png", app_id)); - println!("steam cover art location {}",filepath.display()); - // Read the image file as bytes - let mut file = fs::File::open(filepath)?; - let mut buffer = Vec::new(); - file.read_to_end(&mut buffer)?; - - // Convert bytes to Base64 string - let base64_string = general_purpose::STANDARD.encode(&buffer); - // Add data URI prefix so it can be used directly in - let data_uri = format!("data:image/png;base64,{}", base64_string); + fn convert_cover_to_base64(app_id: &u32, original_path: &Path) -> std::io::Result { + + // List of supported extensions with their MIME types + let extensions = [ + ("png", "image/png"), + ("jpg", "image/jpeg"), + ("jpeg", "image/jpeg"), + ("webp", "image/webp"), + ("bmp", "image/bmp"), + ("gif", "image/gif"), + ]; + + for (ext, mime) in extensions.iter() { + let filepath = original_path + .join("grid") + .join(format!("{}p.{}", app_id, ext)); + if filepath.exists() { + let mut file = fs::File::open(&filepath)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + + let base64_string = general_purpose::STANDARD.encode(&buffer); + let data_uri = format!("data:{};base64,{}", mime, base64_string); + return Ok(data_uri); + } + } - Ok(data_uri) + Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + "", + )) } + fn list_shortcuts(&self) -> Result, Box> { let userdata_dir = self.steam_root.join("userdata"); let user_dirs = fs::read_dir(userdata_dir)?; @@ -326,13 +342,11 @@ impl Steamium { let cover_base64 = match Steamium::convert_cover_to_base64(&s.app_id, &config_path){ Ok(path) => path, // If successful, use the new path Err(e) => { - eprintln!("Error copying cover for app {}: {}", s.app_id, e); + eprintln!("Error converting cover for app {}: {}", s.app_id, e); String::from("") // Return an empty string if there was an error } }; - - println!("Local Cover Path : {}",cover_base64); - + shortcuts.push(Shortcut { name: s.app_name.to_string(), From 68395f33cc08f622c54743d35645a87ce5ff0019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20DIDIER?= Date: Fri, 30 May 2025 21:23:51 +0200 Subject: [PATCH 4/5] fixed issues found by olekolek1000, replaced empty string by option for the cover_b64 --- .gitignore | 4 +- libsteamium/src/lib.rs | 154 ++++++++++++++-------------------- src-tauri/src/frontend_ipc.rs | 2 - src/gui/gui.tsx | 6 +- src/ipc.ts | 2 +- src/panel/games.tsx | 1 - 6 files changed, 66 insertions(+), 103 deletions(-) diff --git a/.gitignore b/.gitignore index 3edd8d7..1c25476 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,4 @@ dist-ssr libsteamium/target libsteamium/cli/target -src-tauri/ressources/ -temp -public/covers/ \ No newline at end of file +temp \ No newline at end of file diff --git a/libsteamium/src/lib.rs b/libsteamium/src/lib.rs index dcdcea4..8502a7c 100644 --- a/libsteamium/src/lib.rs +++ b/libsteamium/src/lib.rs @@ -1,12 +1,11 @@ +use base64::{engine::general_purpose, Engine as _}; use keyvalues_parser::{Obj, Vdf}; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; -use steam_shortcuts_util::parse_shortcuts; -use std::path::Path; -use std::env; use std::fs; use std::io::Read; -use base64::{engine::general_purpose, Engine as _}; +use std::path::Path; +use std::path::PathBuf; +use steam_shortcuts_util::parse_shortcuts; pub struct Steamium { steam_root: PathBuf, @@ -24,9 +23,10 @@ fn get_steam_root() -> anyhow::Result { .iter() .map(|path| home.join(path)) .filter(|p| p.exists()) - .next() else { - anyhow::bail!("Couldn't find Steam installation in search paths"); - }; + .next() + else { + anyhow::bail!("Couldn't find Steam installation in search paths"); + }; Ok(steam_path) } @@ -38,7 +38,7 @@ pub struct AppManifest { app_id: AppID, run_game_id: AppID, name: String, - cover : String, + cover_b64: Option, raw_state_flags: u64, // documentation: https://github.com/lutris/lutris/blob/master/docs/steam.rst last_played: Option, // unix timestamp } @@ -89,7 +89,7 @@ fn vdf_parse_libraryfolders<'a>(vdf_root: &'a Vdf<'a>) -> Option> .iter() .filter_map(|item| item.0.parse::().ok()) .map(|app_id| AppEntry { - app_id : app_id.to_string(), + app_id: app_id.to_string(), root_path: String::from(path), }), ); @@ -118,9 +118,9 @@ fn vdf_parse_appstate<'a>(app_id: AppID, vdf_root: &'a Vdf<'a>) -> Option anyhow::Result<()> { #[derive(Serialize)] pub struct RunningGame { pub app_id: AppID, - pub pid: u64, + pub pid: i32, } #[derive(Serialize)] struct Shortcut { - name: String, - exe: String, + name: String, + exe: String, run_game_id: u64, - app_id : u64, - cover : String + app_id: u64, + cover_b64: Option, } - pub fn list_running_games() -> anyhow::Result> { let mut res = Vec::::new(); @@ -187,7 +186,7 @@ pub fn list_running_games() -> anyhow::Result> { continue; }; - let Ok(pid) = pid.parse::() else { + let Ok(pid) = pid.parse::() else { continue; }; @@ -233,7 +232,7 @@ pub fn list_running_games() -> anyhow::Result> { // AppID found. Add it to the list res.push(RunningGame { app_id: app_id_num.to_string(), - pid : pid as u64 + pid: pid, }); break; @@ -254,72 +253,50 @@ fn call_steam(arg: &str) -> anyhow::Result<()> { } fn shortcut_to_fake_manifest(shortcut: &Shortcut) -> AppManifest { - AppManifest { - app_id : shortcut.app_id.to_string(), + app_id: shortcut.app_id.to_string(), run_game_id: shortcut.run_game_id.to_string(), - name: shortcut.name.clone(), - cover : shortcut.cover.clone(), - raw_state_flags: 0, // Pas applicable, 0 par défaut - last_played: None, // Steam ne stocke pas ça pour les shortcuts - } + name: shortcut.name.clone(), + cover_b64: shortcut.cover_b64.clone(), + raw_state_flags: 0, // Not applicable for shortcuts, 0 by default + last_played: None, // Steam does not use this for shortcuts + } } fn compute_rungameid(app_id: u32) -> u64 { - (app_id as u64) << 32 | 0x02000000 + (app_id as u64) << 32 | 0x02000000 } - impl Steamium { - - pub fn get_cover_file_path(app_id: &u32) -> String { - - let filename = format!("{}.png", app_id); - let relative = PathBuf::from("../../ressources").join("covers").join(filename); - - if let Ok(current_dir) = env::current_dir() { - let absolute = current_dir.join(relative); - absolute.to_string_lossy().into_owned() - } else { - String::new() + fn convert_cover_to_base64(app_id: &u32,original_path: &Path,) -> std::io::Result> { + // List of supported extensions with their MIME types + let extensions = [ + ("png", "image/png"), + ("jpg", "image/jpeg"), + ("jpeg", "image/jpeg"), + ("webp", "image/webp"), + ("bmp", "image/bmp"), + ("gif", "image/gif"), + ]; + + for (ext, mime) in extensions.iter() { + let filepath = original_path + .join("grid") + .join(format!("{}p.{}", app_id, ext)); + if filepath.exists() { + let mut file = fs::File::open(&filepath)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + + let base64_string = general_purpose::STANDARD.encode(&buffer); + let data_uri = format!("data:{};base64,{}", mime, base64_string); + return Ok(Some(data_uri)); + } } - } - - fn convert_cover_to_base64(app_id: &u32, original_path: &Path) -> std::io::Result { - - // List of supported extensions with their MIME types - let extensions = [ - ("png", "image/png"), - ("jpg", "image/jpeg"), - ("jpeg", "image/jpeg"), - ("webp", "image/webp"), - ("bmp", "image/bmp"), - ("gif", "image/gif"), - ]; - - for (ext, mime) in extensions.iter() { - let filepath = original_path - .join("grid") - .join(format!("{}p.{}", app_id, ext)); - if filepath.exists() { - let mut file = fs::File::open(&filepath)?; - let mut buffer = Vec::new(); - file.read_to_end(&mut buffer)?; - - let base64_string = general_purpose::STANDARD.encode(&buffer); - let data_uri = format!("data:{};base64,{}", mime, base64_string); - return Ok(data_uri); - } - } - - Err(std::io::Error::new( - std::io::ErrorKind::NotFound, - "", - )) + Ok(None) } - fn list_shortcuts(&self) -> Result, Box> { let userdata_dir = self.steam_root.join("userdata"); let user_dirs = fs::read_dir(userdata_dir)?; @@ -333,32 +310,29 @@ impl Steamium { if !shortcut_path.exists() { continue; } - + let content = std::fs::read(&shortcut_path)?; - let shortcuts_data =parse_shortcuts(content.as_slice())?; + let shortcuts_data = parse_shortcuts(content.as_slice())?; for s in shortcuts_data { let run_game_id = compute_rungameid(s.app_id); - let cover_base64 = match Steamium::convert_cover_to_base64(&s.app_id, &config_path){ + let cover_base64 = match Steamium::convert_cover_to_base64(&s.app_id, &config_path) { Ok(path) => path, // If successful, use the new path Err(e) => { - eprintln!("Error converting cover for app {}: {}", s.app_id, e); - String::from("") // Return an empty string if there was an error + log::error!("Error converting cover for app {}: {}", s.app_id, e); + None } }; - - shortcuts.push(Shortcut { name: s.app_name.to_string(), exe: s.exe.to_string(), run_game_id: run_game_id, - app_id : s.app_id as u64, - cover : cover_base64 + app_id: s.app_id as u64, + cover_b64: cover_base64, }); } } - Ok(shortcuts) } @@ -415,7 +389,7 @@ impl Steamium { }; Some(manifest) }) - .collect(); + .collect(); if let Ok(shortcuts) = self.list_shortcuts() { let mut fake_manifests = shortcuts @@ -424,9 +398,8 @@ impl Steamium { .collect::>(); games.append(&mut fake_manifests); } else { - println!("Failed to read non-Steam shortcuts"); + log::error!("Failed to read non-Steam shortcuts"); } - match sort_method { GameSortMethod::NameAsc => { @@ -440,11 +413,6 @@ impl Steamium { } } - - - Ok(games) } - - } diff --git a/src-tauri/src/frontend_ipc.rs b/src-tauri/src/frontend_ipc.rs index 7ef56cb..c56dda3 100644 --- a/src-tauri/src/frontend_ipc.rs +++ b/src-tauri/src/frontend_ipc.rs @@ -72,10 +72,8 @@ pub async fn game_list(state: tauri::State<'_, AppStateType>) -> Result Result<(), String> { - println!("app id received {}", app_id); handle_result("launch a game", libsteamium::launch(app_id)) } diff --git a/src/gui/gui.tsx b/src/gui/gui.tsx index 4cec74f..c2589e1 100644 --- a/src/gui/gui.tsx +++ b/src/gui/gui.tsx @@ -278,11 +278,11 @@ function failed_covers_set(covers: FailedCovers) { } async function get_alt_cover(manifest: ipc.AppManifest) { - - if(manifest.cover!= ""){ + console.log(manifest.cover_b64); + if(manifest.cover_b64!= undefined){ console.log("Runnning copy png to frontend") return <> - + ; } else{ diff --git a/src/ipc.ts b/src/ipc.ts index c8ea297..34561be 100644 --- a/src/ipc.ts +++ b/src/ipc.ts @@ -29,7 +29,7 @@ export namespace ipc { name: string; raw_state_flags: number; last_played?: number; - cover : string + cover_b64? : string } export interface Games { diff --git a/src/panel/games.tsx b/src/panel/games.tsx index e3efcd8..c7bfe51 100644 --- a/src/panel/games.tsx +++ b/src/panel/games.tsx @@ -148,7 +148,6 @@ export function PanelGames({ globals }: { globals: Globals }) { useEffect(() => { const fetchGames = async () => { const games = await ipc.game_list(); - console.log("Fetched games:", games); // 👈 Print all data setGames(games); const arr = games.manifests.map((manifest) => ( From b4f30916a9dc679f48eceafc327671ba394a90e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20DIDIER?= Date: Sun, 1 Jun 2025 22:27:52 +0200 Subject: [PATCH 5/5] added check to avoid steam API call for non steam game --- src/gui/gui.tsx | 51 ++++++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/src/gui/gui.tsx b/src/gui/gui.tsx index c2589e1..f8e0dfe 100644 --- a/src/gui/gui.tsx +++ b/src/gui/gui.tsx @@ -303,29 +303,36 @@ export function GameCover({ manifest, big, on_click }: { manifest: ipc.AppManife const failed_covers = failed_covers_get(); const already_failed = failed_covers.covers.includes(manifest.app_id); - if (!already_failed) { - const url = `https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/${manifest.app_id}/library_600x900.jpg`; - setContent( - { - try { - console.log("Alt cover " + manifest.app_id + " failed to load, remembering it to prevent unnecessary requests to Steam API "); - const updated_covers = failed_covers_get(); - updated_covers.covers.push(manifest.app_id); - failed_covers_set(updated_covers); - setContent(await get_alt_cover(manifest)); - } catch (err) { - console.error("Failed to load alt cover:", err); - } - }} - /> - ); - } else { - console.log("using previously failed cover"); - setContent(await get_alt_cover(manifest)); + if(manifest.run_game_id.length<10){ + if (!already_failed) { + const url = `https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/${manifest.app_id}/library_600x900.jpg`; + setContent( + { + try { + console.log("Alt cover " + manifest.app_id + " failed to load, remembering it to prevent unnecessary requests to Steam API "); + const updated_covers = failed_covers_get(); + updated_covers.covers.push(manifest.app_id); + failed_covers_set(updated_covers); + setContent(await get_alt_cover(manifest)); + } catch (err) { + console.error("Failed to load alt cover:", err); + } + }} + /> + ); + } else { + console.log("using previously failed cover"); + setContent(await get_alt_cover(manifest)); + } } + else{ + console.log("handling non Steam Game"); + setContent(await get_alt_cover(manifest)); + + } } catch (err) { console.error("Unhandled error in GameCover useEffect:", err); }