HTTP client, game cover art fetcher, game list image display, use smol::LocalExecutor for async runtime
This commit is contained in:
298
Cargo.lock
generated
298
Cargo.lock
generated
@@ -287,12 +287,6 @@ dependencies = [
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ascii"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
|
||||
|
||||
[[package]]
|
||||
name = "ash"
|
||||
version = "0.38.0+1.3.281"
|
||||
@@ -398,6 +392,18 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-native-tls"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9343dc5acf07e79ff82d0c37899f079db3534d99f189a1837c8e549c99405bec"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"native-tls",
|
||||
"thiserror 1.0.69",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-net"
|
||||
version = "2.0.0"
|
||||
@@ -795,12 +801,6 @@ version = "3.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
||||
|
||||
[[package]]
|
||||
name = "bytecount"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.24.0"
|
||||
@@ -1198,7 +1198,7 @@ dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation",
|
||||
"core-graphics-types",
|
||||
"foreign-types",
|
||||
"foreign-types 0.5.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -1483,17 +1483,20 @@ name = "dash-frontend"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"async-native-tls",
|
||||
"chrono",
|
||||
"gio 0.21.5",
|
||||
"glam",
|
||||
"gtk",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"keyvalues-parser",
|
||||
"log",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"steam_shortcuts_util",
|
||||
"smol",
|
||||
"smol-hyper",
|
||||
"wayvr-ipc",
|
||||
"wgui",
|
||||
"wlx-common",
|
||||
@@ -1985,6 +1988,15 @@ dependencies = [
|
||||
"ttf-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared 0.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.5.0"
|
||||
@@ -1992,7 +2004,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
|
||||
dependencies = [
|
||||
"foreign-types-macros",
|
||||
"foreign-types-shared",
|
||||
"foreign-types-shared 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2006,6 +2018,12 @@ dependencies = [
|
||||
"syn 2.0.111",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.3.1"
|
||||
@@ -2489,6 +2507,25 @@ dependencies = [
|
||||
"syn 2.0.111",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http",
|
||||
"indexmap 2.12.1",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.7.1"
|
||||
@@ -2603,12 +2640,73 @@ version = "3.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body-util"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.64"
|
||||
@@ -3378,6 +3476,23 @@ dependencies = [
|
||||
"pxfm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"openssl",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk"
|
||||
version = "0.9.0"
|
||||
@@ -3469,17 +3584,6 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom_locate"
|
||||
version = "4.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e3c83c053b0713da60c5b8de47fe8e494fe3ece5267b2f23090a07a053ba8f3"
|
||||
dependencies = [
|
||||
"bytecount",
|
||||
"memchr",
|
||||
"nom 7.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "noop_proc_macro"
|
||||
version = "0.3.0"
|
||||
@@ -3917,6 +4021,50 @@ version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"cfg-if",
|
||||
"foreign-types 0.3.2",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.111"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openxr"
|
||||
version = "0.19.0"
|
||||
@@ -4885,6 +5033,15 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
@@ -4916,6 +5073,29 @@ dependencies = [
|
||||
"tiny-skia",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "self_cell"
|
||||
version = "1.2.1"
|
||||
@@ -5272,6 +5452,36 @@ dependencies = [
|
||||
"xkeysym",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smol"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-executor",
|
||||
"async-fs",
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"async-net",
|
||||
"async-process",
|
||||
"blocking",
|
||||
"futures-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smol-hyper"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7428a49d323867702cd12b97b08a6b0104f39ec13b49117911f101271321bc1a"
|
||||
dependencies = [
|
||||
"async-executor",
|
||||
"async-io",
|
||||
"futures-io",
|
||||
"hyper",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smol_str"
|
||||
version = "0.2.2"
|
||||
@@ -5303,18 +5513,6 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[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 7.1.3",
|
||||
"nom_locate",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strict-num"
|
||||
version = "0.1.1"
|
||||
@@ -5903,6 +6101,12 @@ dependencies = [
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.25.1"
|
||||
@@ -6082,6 +6286,12 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.2.1"
|
||||
@@ -6170,6 +6380,15 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
|
||||
dependencies = [
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
@@ -7037,6 +7256,7 @@ dependencies = [
|
||||
"idmap-derive",
|
||||
"log",
|
||||
"serde",
|
||||
"smol",
|
||||
"wayvr-ipc",
|
||||
"xdg 3.0.0",
|
||||
]
|
||||
|
||||
@@ -16,6 +16,9 @@ serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
wlx-common = { path = "../wlx-common" }
|
||||
wayvr-ipc = { path = "../wayvr-ipc", default-features = false }
|
||||
base64 = "0.22.1"
|
||||
keyvalues-parser = { git = "https://github.com/CosmicHorrorDev/vdf-rs.git", rev = "fc6dcbea9eb13cacb98dea40063f6f56cde6e145" }
|
||||
steam_shortcuts_util = "1.1.8"
|
||||
smol = "2.0.2"
|
||||
hyper = { version = "1.8.1", features = ["client", "http1", "http2"] }
|
||||
http-body-util = "0.1.3"
|
||||
async-native-tls = "0.5.0"
|
||||
smol-hyper = "0.1.1"
|
||||
|
||||
@@ -25,6 +25,7 @@ use crate::{
|
||||
util::{
|
||||
popup_manager::{MountPopupParams, PopupManager, PopupManagerParams},
|
||||
toast_manager::ToastManager,
|
||||
various::AsyncExecutor,
|
||||
},
|
||||
views,
|
||||
};
|
||||
@@ -43,6 +44,9 @@ pub struct Frontend {
|
||||
pub settings: Box<dyn settings::SettingsIO>,
|
||||
pub interface: BoxDashInterface,
|
||||
|
||||
// async runtime executor
|
||||
pub executor: AsyncExecutor,
|
||||
|
||||
#[allow(dead_code)]
|
||||
state: ParserState,
|
||||
|
||||
@@ -146,6 +150,7 @@ impl Frontend {
|
||||
toast_manager,
|
||||
window_audio_settings: WguiWindow::default(),
|
||||
view_audio_settings: None,
|
||||
executor: Rc::new(smol::LocalExecutor::new()),
|
||||
};
|
||||
|
||||
// init some things first
|
||||
@@ -172,9 +177,13 @@ impl Frontend {
|
||||
tab.update(TabUpdateParams {
|
||||
layout: &mut layout,
|
||||
interface: &mut self.interface,
|
||||
executor: &mut self.executor,
|
||||
})?;
|
||||
}
|
||||
|
||||
// process async runtime tasks
|
||||
while self.executor.try_tick() {}
|
||||
|
||||
self.tick(width, height, timestep_alpha)?;
|
||||
self.ticks += 1;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ impl Tab for TabGames {
|
||||
}
|
||||
|
||||
fn update(&mut self, params: super::TabUpdateParams) -> anyhow::Result<()> {
|
||||
self.view_game_list.update(params.layout, params.interface)?;
|
||||
self.view_game_list.update(params.layout, params.executor)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ use wgui::{
|
||||
};
|
||||
use wlx_common::dash_interface::BoxDashInterface;
|
||||
|
||||
use crate::frontend::{FrontendTasks, RcFrontend};
|
||||
use crate::{
|
||||
frontend::{FrontendTasks, RcFrontend},
|
||||
util::various::AsyncExecutor,
|
||||
};
|
||||
|
||||
pub mod apps;
|
||||
pub mod games;
|
||||
@@ -35,6 +38,7 @@ pub struct TabParams<'a> {
|
||||
pub struct TabUpdateParams<'a> {
|
||||
pub layout: &'a mut Layout,
|
||||
pub interface: &'a mut BoxDashInterface,
|
||||
pub executor: &'a mut AsyncExecutor,
|
||||
}
|
||||
|
||||
pub trait Tab {
|
||||
|
||||
42
dash-frontend/src/util/cover_art_fetcher.rs
Normal file
42
dash-frontend/src/util/cover_art_fetcher.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use wlx_common::cache_dir;
|
||||
|
||||
use crate::util::{http_client, steam_utils::AppID, various::AsyncExecutor};
|
||||
|
||||
pub struct CoverArt {
|
||||
// can be empty in case if data couldn't be fetched (use a fallback image then)
|
||||
pub compressed_image_data: Vec<u8>,
|
||||
}
|
||||
|
||||
pub async fn request_image(executor: AsyncExecutor, app_id: AppID) -> anyhow::Result<CoverArt> {
|
||||
let cache_file_path = format!("cover_arts/{}.bin", app_id);
|
||||
|
||||
// check if file already exists in cache directory
|
||||
if let Some(data) = cache_dir::get_data(&cache_file_path).await {
|
||||
return Ok(CoverArt {
|
||||
compressed_image_data: data,
|
||||
});
|
||||
}
|
||||
|
||||
let url = format!(
|
||||
"https://shared.steamstatic.com/store_item_assets/steam/apps/{}/library_600x900.jpg",
|
||||
app_id
|
||||
);
|
||||
|
||||
match http_client::get(&executor, &url).await {
|
||||
Ok(response) => {
|
||||
log::info!("Success");
|
||||
cache_dir::set_data(&cache_file_path, &response.data).await?;
|
||||
Ok(CoverArt {
|
||||
compressed_image_data: response.data,
|
||||
})
|
||||
}
|
||||
Err(e) => {
|
||||
// fetch failed, write an empty file
|
||||
log::error!("CoverArtFetcher: failed fetch for AppID {}: {}", app_id, e);
|
||||
cache_dir::set_data(&cache_file_path, &[]).await?;
|
||||
Ok(CoverArt {
|
||||
compressed_image_data: Vec::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
134
dash-frontend/src/util/http_client.rs
Normal file
134
dash-frontend/src/util/http_client.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
//
|
||||
// example smol+hyper usage derived from
|
||||
// https://github.com/smol-rs/smol/blob/master/examples/hyper-client.rs
|
||||
// under Apache-2.0 + MIT license.
|
||||
// Repository URL: https://github.com/smol-rs/smol
|
||||
//
|
||||
|
||||
use anyhow::Context as _;
|
||||
use async_native_tls::TlsStream;
|
||||
use http_body_util::{BodyStream, Empty};
|
||||
use hyper::Request;
|
||||
use smol::{net::TcpStream, prelude::*};
|
||||
use std::convert::TryInto;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use crate::util::various::AsyncExecutor;
|
||||
pub struct HttpClientResponse {
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
pub async fn get(executor: &AsyncExecutor, url: &str) -> anyhow::Result<HttpClientResponse> {
|
||||
log::info!("fetching URL \"{}\"", url);
|
||||
|
||||
let url: hyper::Uri = url.try_into()?;
|
||||
let req = Request::builder()
|
||||
.header(
|
||||
hyper::header::HOST,
|
||||
url.authority().context("invalid authority")?.clone().as_str(),
|
||||
)
|
||||
.uri(url)
|
||||
.body(Empty::new())?;
|
||||
|
||||
let resp = fetch(executor, req).await?;
|
||||
|
||||
if !resp.status().is_success() {
|
||||
// non-200 HTTP response
|
||||
anyhow::bail!("non-200 HTTP response: {}", resp.status().as_str());
|
||||
}
|
||||
|
||||
let body = BodyStream::new(resp.into_body())
|
||||
.try_fold(Vec::new(), |mut body, chunk| {
|
||||
if let Some(chunk) = chunk.data_ref() {
|
||||
body.extend_from_slice(chunk);
|
||||
}
|
||||
Ok(body)
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(HttpClientResponse { data: body })
|
||||
}
|
||||
|
||||
async fn fetch(
|
||||
ex: &AsyncExecutor,
|
||||
req: hyper::Request<http_body_util::Empty<&'static [u8]>>,
|
||||
) -> anyhow::Result<hyper::Response<hyper::body::Incoming>> {
|
||||
let io = {
|
||||
let host = req.uri().host().context("cannot parse host")?;
|
||||
|
||||
match req.uri().scheme_str() {
|
||||
Some("http") => {
|
||||
let stream = {
|
||||
let port = req.uri().port_u16().unwrap_or(80);
|
||||
smol::net::TcpStream::connect((host, port)).await?
|
||||
};
|
||||
SmolStream::Plain(stream)
|
||||
}
|
||||
Some("https") => {
|
||||
// In case of HTTPS, establish a secure TLS connection first.
|
||||
let stream = {
|
||||
let port = req.uri().port_u16().unwrap_or(443);
|
||||
smol::net::TcpStream::connect((host, port)).await?
|
||||
};
|
||||
let stream = async_native_tls::connect(host, stream).await?;
|
||||
SmolStream::Tls(stream)
|
||||
}
|
||||
scheme => anyhow::bail!("unsupported scheme: {:?}", scheme),
|
||||
}
|
||||
};
|
||||
|
||||
// Spawn the HTTP/1 connection.
|
||||
let (mut sender, conn) = hyper::client::conn::http1::handshake(smol_hyper::rt::FuturesIo::new(io)).await?;
|
||||
ex.spawn(async move {
|
||||
if let Err(e) = conn.await {
|
||||
println!("Connection failed: {:?}", e);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
// Get the result
|
||||
let result = sender.send_request(req).await?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// A TCP or TCP+TLS connection.
|
||||
enum SmolStream {
|
||||
/// A plain TCP connection.
|
||||
Plain(TcpStream),
|
||||
|
||||
/// A TCP connection secured by TLS.
|
||||
Tls(TlsStream<TcpStream>),
|
||||
}
|
||||
|
||||
impl AsyncRead for SmolStream {
|
||||
fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll<smol::io::Result<usize>> {
|
||||
match &mut *self {
|
||||
SmolStream::Plain(stream) => Pin::new(stream).poll_read(cx, buf),
|
||||
SmolStream::Tls(stream) => Pin::new(stream).poll_read(cx, buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for SmolStream {
|
||||
fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll<smol::io::Result<usize>> {
|
||||
match &mut *self {
|
||||
SmolStream::Plain(stream) => Pin::new(stream).poll_write(cx, buf),
|
||||
SmolStream::Tls(stream) => Pin::new(stream).poll_write(cx, buf),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<smol::io::Result<()>> {
|
||||
match &mut *self {
|
||||
SmolStream::Plain(stream) => Pin::new(stream).poll_close(cx),
|
||||
SmolStream::Tls(stream) => Pin::new(stream).poll_close(cx),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<smol::io::Result<()>> {
|
||||
match &mut *self {
|
||||
SmolStream::Plain(stream) => Pin::new(stream).poll_flush(cx),
|
||||
SmolStream::Tls(stream) => Pin::new(stream).poll_flush(cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
pub mod cover_art_fetcher;
|
||||
pub mod desktop_finder;
|
||||
pub mod http_client;
|
||||
pub mod pactl_wrapper;
|
||||
pub mod popup_manager;
|
||||
pub mod steam_utils;
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
use keyvalues_parser::{Obj, Vdf};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use steam_shortcuts_util::parse_shortcuts;
|
||||
|
||||
pub struct SteamUtils {
|
||||
steam_root: PathBuf,
|
||||
@@ -19,12 +14,7 @@ fn get_steam_root() -> anyhow::Result<PathBuf> {
|
||||
".steam/debian-installation",
|
||||
".var/app/com.valvesoftware.Steam/data/Steam",
|
||||
];
|
||||
let Some(steam_path) = steam_paths
|
||||
.iter()
|
||||
.map(|path| home.join(path))
|
||||
.filter(|p| p.exists())
|
||||
.next()
|
||||
else {
|
||||
let Some(steam_path) = steam_paths.iter().map(|path| home.join(path)).find(|p| p.exists()) else {
|
||||
anyhow::bail!("Couldn't find Steam installation in search paths");
|
||||
};
|
||||
|
||||
@@ -38,7 +28,6 @@ pub struct AppManifest {
|
||||
pub app_id: AppID,
|
||||
pub run_game_id: AppID,
|
||||
pub name: String,
|
||||
pub cover_b64: Option<String>,
|
||||
pub raw_state_flags: u64, // documentation: https://github.com/lutris/lutris/blob/master/docs/steam.rst
|
||||
pub last_played: Option<u64>, // unix timestamp
|
||||
}
|
||||
@@ -119,7 +108,6 @@ fn vdf_parse_appstate<'a>(app_id: AppID, vdf_root: &'a Vdf<'a>) -> Option<AppMan
|
||||
Some(AppManifest {
|
||||
app_id: app_id.clone(),
|
||||
run_game_id: app_id,
|
||||
cover_b64: None,
|
||||
name: String::from(name),
|
||||
raw_state_flags,
|
||||
last_played,
|
||||
@@ -161,15 +149,6 @@ pub struct RunningGame {
|
||||
pub pid: i32,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Shortcut {
|
||||
name: String,
|
||||
exe: String,
|
||||
run_game_id: u64,
|
||||
app_id: u64,
|
||||
cover_b64: Option<String>,
|
||||
}
|
||||
|
||||
pub fn list_running_games() -> anyhow::Result<Vec<RunningGame>> {
|
||||
let mut res = Vec::<RunningGame>::new();
|
||||
|
||||
@@ -191,10 +170,7 @@ pub fn list_running_games() -> anyhow::Result<Vec<RunningGame>> {
|
||||
|
||||
let args: Vec<&str> = cmdline
|
||||
.split(|byte| *byte == 0x00)
|
||||
.filter_map(|arg| match std::str::from_utf8(arg) {
|
||||
Ok(arg) => Some(arg),
|
||||
Err(_) => None,
|
||||
})
|
||||
.filter_map(|arg| std::str::from_utf8(arg).ok())
|
||||
.collect();
|
||||
|
||||
let mut has_steam_launch = false;
|
||||
@@ -251,88 +227,7 @@ 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_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
|
||||
}
|
||||
|
||||
impl SteamUtils {
|
||||
fn convert_cover_to_base64(app_id: &u32, original_path: &Path) -> std::io::Result<Option<String>> {
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn list_shortcuts(&self) -> Result<Vec<Shortcut>, Box<dyn std::error::Error>> {
|
||||
let userdata_dir = self.steam_root.join("userdata");
|
||||
let user_dirs = fs::read_dir(userdata_dir)?;
|
||||
|
||||
let mut shortcuts: Vec<Shortcut> = Vec::new();
|
||||
|
||||
for user in user_dirs.flatten() {
|
||||
let config_path = user.path().join("config");
|
||||
let shortcut_path = config_path.join("shortcuts.vdf");
|
||||
|
||||
if !shortcut_path.exists() {
|
||||
continue;
|
||||
}
|
||||
|
||||
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_base64 = match SteamUtils::convert_cover_to_base64(&s.app_id, &config_path) {
|
||||
Ok(path) => path, // If successful, use the new path
|
||||
Err(e) => {
|
||||
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,
|
||||
app_id: s.app_id as u64,
|
||||
cover_b64: cover_base64,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(shortcuts)
|
||||
}
|
||||
|
||||
fn get_dir_steamapps(&self) -> PathBuf {
|
||||
self.steam_root.join("steamapps")
|
||||
}
|
||||
@@ -373,7 +268,11 @@ impl SteamUtils {
|
||||
let manifest = match self.get_app_manifest(app_entry) {
|
||||
Ok(manifest) => manifest,
|
||||
Err(e) => {
|
||||
log::error!("Failed to get app manifest for AppID {}: {}", app_entry.app_id, e);
|
||||
log::warn!(
|
||||
"Failed to get app manifest for AppID {}: {}. This entry won't show.",
|
||||
app_entry.app_id,
|
||||
e
|
||||
);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
@@ -381,16 +280,6 @@ impl SteamUtils {
|
||||
})
|
||||
.collect();
|
||||
|
||||
if let Ok(shortcuts) = self.list_shortcuts() {
|
||||
let mut fake_manifests = shortcuts
|
||||
.iter()
|
||||
.map(shortcut_to_fake_manifest)
|
||||
.collect::<Vec<AppManifest>>();
|
||||
games.append(&mut fake_manifests);
|
||||
} else {
|
||||
log::error!("Failed to read non-Steam shortcuts");
|
||||
}
|
||||
|
||||
match sort_method {
|
||||
GameSortMethod::NameAsc => {
|
||||
games.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
use std::{path::PathBuf, rc::Rc, str::FromStr};
|
||||
use wgui::{
|
||||
assets::{AssetPath, AssetPathOwned},
|
||||
globals::WguiGlobals,
|
||||
@@ -12,6 +12,8 @@ use wgui::{
|
||||
},
|
||||
};
|
||||
|
||||
pub type AsyncExecutor = Rc<smol::LocalExecutor<'static>>;
|
||||
|
||||
use crate::util::desktop_finder;
|
||||
|
||||
// the compiler wants to scream
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use std::{collections::HashMap, rc::Rc};
|
||||
|
||||
use wayvr_ipc::packet_server::{self, WvrWindowHandle};
|
||||
use wgui::{
|
||||
assets::AssetPath,
|
||||
components::{
|
||||
@@ -13,32 +12,36 @@ use wgui::{
|
||||
i18n::Translation,
|
||||
layout::{Layout, WidgetID, WidgetPair},
|
||||
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||
renderer_vk::text::custom_glyph::{CustomGlyphContent, CustomGlyphData},
|
||||
taffy::{
|
||||
self,
|
||||
prelude::{length, percent},
|
||||
},
|
||||
widget::{
|
||||
ConstructEssentials,
|
||||
div::WidgetDiv,
|
||||
image::{WidgetImage, WidgetImageParams},
|
||||
label::{WidgetLabel, WidgetLabelParams},
|
||||
rectangle,
|
||||
util::WLength,
|
||||
},
|
||||
};
|
||||
use wlx_common::dash_interface::BoxDashInterface;
|
||||
|
||||
use crate::{
|
||||
frontend::{FrontendTask, FrontendTasks},
|
||||
task::Tasks,
|
||||
util::{
|
||||
cover_art_fetcher::{self, CoverArt},
|
||||
popup_manager::MountPopupParams,
|
||||
steam_utils::{self, SteamUtils},
|
||||
steam_utils::{self, AppID, SteamUtils},
|
||||
various::AsyncExecutor,
|
||||
},
|
||||
views::window_options,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Task {
|
||||
AppManifestClicked(steam_utils::AppManifest),
|
||||
SetCoverArt((AppID, Rc<CoverArt>)),
|
||||
Refresh,
|
||||
}
|
||||
|
||||
@@ -49,6 +52,10 @@ pub struct Params<'a> {
|
||||
pub parent_id: WidgetID,
|
||||
}
|
||||
|
||||
struct Cell {
|
||||
image_parent: WidgetID,
|
||||
}
|
||||
|
||||
pub struct View {
|
||||
#[allow(dead_code)]
|
||||
pub parser_state: ParserState,
|
||||
@@ -57,6 +64,8 @@ pub struct View {
|
||||
globals: WguiGlobals,
|
||||
id_list_parent: WidgetID,
|
||||
steam_utils: steam_utils::SteamUtils,
|
||||
|
||||
cells: HashMap<AppID, Cell>,
|
||||
}
|
||||
|
||||
impl View {
|
||||
@@ -83,10 +92,11 @@ impl View {
|
||||
globals: params.globals.clone(),
|
||||
id_list_parent: list_parent.id,
|
||||
steam_utils,
|
||||
cells: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update(&mut self, layout: &mut Layout, interface: &mut BoxDashInterface) -> anyhow::Result<()> {
|
||||
pub fn update(&mut self, layout: &mut Layout, executor: &AsyncExecutor) -> anyhow::Result<()> {
|
||||
loop {
|
||||
let tasks = self.tasks.drain();
|
||||
if tasks.is_empty() {
|
||||
@@ -94,8 +104,9 @@ impl View {
|
||||
}
|
||||
for task in tasks {
|
||||
match task {
|
||||
Task::Refresh => self.refresh(layout, interface)?,
|
||||
Task::Refresh => self.refresh(layout, executor)?,
|
||||
Task::AppManifestClicked(manifest) => self.action_app_manifest_clicked(manifest)?,
|
||||
Task::SetCoverArt((app_id, cover_art)) => self.action_set_cover_art(layout, &app_id, cover_art)?,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,11 +125,25 @@ const BORDER_COLOR_HOVERED: drawing::Color = drawing::Color::new(1.0, 1.0, 1.0,
|
||||
const GAME_COVER_SIZE_X: f32 = 140.0;
|
||||
const GAME_COVER_SIZE_Y: f32 = 210.0;
|
||||
|
||||
pub fn construct_game_cover(
|
||||
async fn request_cover_image(executor: AsyncExecutor, manifest: steam_utils::AppManifest, tasks: Tasks<Task>) {
|
||||
let cover_art = match cover_art_fetcher::request_image(executor, manifest.app_id.clone()).await {
|
||||
Ok(cover_art) => cover_art,
|
||||
Err(e) => {
|
||||
log::error!("request_cover_image failed: {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
tasks.push(Task::SetCoverArt((manifest.app_id, Rc::from(cover_art))));
|
||||
}
|
||||
|
||||
fn construct_game_cover(
|
||||
ess: &mut ConstructEssentials,
|
||||
globals: &WguiGlobals,
|
||||
executor: &AsyncExecutor,
|
||||
tasks: &Tasks<Task>,
|
||||
_globals: &WguiGlobals,
|
||||
manifest: &steam_utils::AppManifest,
|
||||
) -> anyhow::Result<(WidgetPair, Rc<ComponentButton>)> {
|
||||
) -> anyhow::Result<(WidgetPair, Rc<ComponentButton>, Cell)> {
|
||||
let (widget_button, button) = components::button::construct(
|
||||
ess,
|
||||
components::button::Params {
|
||||
@@ -145,6 +170,20 @@ pub fn construct_game_cover(
|
||||
},
|
||||
)?;
|
||||
|
||||
let (image_parent, _) = ess.layout.add_child(
|
||||
widget_button.id,
|
||||
WidgetDiv::create(),
|
||||
taffy::Style {
|
||||
position: taffy::Position::Absolute,
|
||||
size: taffy::Size {
|
||||
width: percent(1.0),
|
||||
height: percent(1.0),
|
||||
},
|
||||
padding: taffy::Rect::length(2.0),
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let rect_gradient = |color: drawing::Color, color2: drawing::Color| {
|
||||
rectangle::WidgetRectangle::create(rectangle::WidgetRectangleParams {
|
||||
color,
|
||||
@@ -166,7 +205,7 @@ pub fn construct_game_cover(
|
||||
};
|
||||
|
||||
// top shine
|
||||
ess.layout.add_child(
|
||||
let (top_shine, _) = ess.layout.add_child(
|
||||
widget_button.id,
|
||||
rect_gradient(
|
||||
drawing::Color::new(1.0, 1.0, 1.0, 0.25),
|
||||
@@ -175,6 +214,9 @@ pub fn construct_game_cover(
|
||||
rect_gradient_style(taffy::AlignSelf::Baseline, 0.05),
|
||||
)?;
|
||||
|
||||
// not optimal, this forces us to create a new pass for every created cover art just to overlay various rectangles at the top of the image cover art
|
||||
top_shine.widget.state().flags.new_pass = true;
|
||||
|
||||
// top white gradient
|
||||
ess.layout.add_child(
|
||||
widget_button.id,
|
||||
@@ -190,7 +232,7 @@ pub fn construct_game_cover(
|
||||
widget_button.id,
|
||||
rect_gradient(
|
||||
drawing::Color::new(0.0, 0.0, 0.0, 0.0),
|
||||
drawing::Color::new(0.0, 0.0, 0.0, 0.2),
|
||||
drawing::Color::new(0.0, 0.0, 0.0, 0.25),
|
||||
),
|
||||
rect_gradient_style(taffy::AlignSelf::End, 0.5),
|
||||
)?;
|
||||
@@ -200,23 +242,35 @@ pub fn construct_game_cover(
|
||||
widget_button.id,
|
||||
rect_gradient(
|
||||
drawing::Color::new(0.0, 0.0, 0.0, 0.0),
|
||||
drawing::Color::new(0.0, 0.0, 0.0, 0.2),
|
||||
drawing::Color::new(0.0, 0.0, 0.0, 0.5),
|
||||
),
|
||||
rect_gradient_style(taffy::AlignSelf::End, 0.05),
|
||||
rect_gradient_style(taffy::AlignSelf::End, 0.1),
|
||||
)?;
|
||||
|
||||
Ok((widget_button, button))
|
||||
// request cover image data from the internet or disk cache
|
||||
executor
|
||||
.spawn(request_cover_image(executor.clone(), manifest.clone(), tasks.clone()))
|
||||
.detach();
|
||||
|
||||
Ok((
|
||||
widget_button,
|
||||
button,
|
||||
Cell {
|
||||
image_parent: image_parent.id,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn fill_game_list(
|
||||
globals: &WguiGlobals,
|
||||
ess: &mut ConstructEssentials,
|
||||
interface: &mut BoxDashInterface,
|
||||
executor: &AsyncExecutor,
|
||||
cells: &mut HashMap<AppID, Cell>,
|
||||
games: &Games,
|
||||
tasks: &Tasks<Task>,
|
||||
) -> anyhow::Result<()> {
|
||||
for manifest in &games.manifests {
|
||||
let (_, button) = construct_game_cover(ess, globals, manifest)?;
|
||||
let (_, button, cell) = construct_game_cover(ess, executor, tasks, globals, manifest)?;
|
||||
|
||||
button.on_click({
|
||||
let tasks = tasks.clone();
|
||||
@@ -226,6 +280,8 @@ fn fill_game_list(
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
|
||||
cells.insert(manifest.app_id.clone(), cell);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -240,8 +296,9 @@ impl View {
|
||||
Ok(Games { manifests })
|
||||
}
|
||||
|
||||
fn refresh(&mut self, layout: &mut Layout, interface: &mut BoxDashInterface) -> anyhow::Result<()> {
|
||||
fn refresh(&mut self, layout: &mut Layout, executor: &AsyncExecutor) -> anyhow::Result<()> {
|
||||
layout.remove_children(self.id_list_parent);
|
||||
self.cells.clear();
|
||||
|
||||
let mut text: Option<Translation> = None;
|
||||
match self.game_list() {
|
||||
@@ -255,7 +312,8 @@ impl View {
|
||||
layout,
|
||||
parent: self.id_list_parent,
|
||||
},
|
||||
interface,
|
||||
executor,
|
||||
&mut self.cells,
|
||||
&list,
|
||||
&self.tasks,
|
||||
)?
|
||||
@@ -285,11 +343,7 @@ impl View {
|
||||
self.frontend_tasks.push(FrontendTask::MountPopup(MountPopupParams {
|
||||
title: Translation::from_raw_text(&manifest.name),
|
||||
on_content: {
|
||||
let frontend_tasks = self.frontend_tasks.clone();
|
||||
let globals = self.globals.clone();
|
||||
let tasks = self.tasks.clone();
|
||||
|
||||
Rc::new(move |data| {
|
||||
Rc::new(move |_data| {
|
||||
// todo
|
||||
Ok(())
|
||||
})
|
||||
@@ -298,4 +352,53 @@ impl View {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn action_set_cover_art(
|
||||
&mut self,
|
||||
layout: &mut Layout,
|
||||
app_id: &AppID,
|
||||
cover_art: Rc<CoverArt>,
|
||||
) -> anyhow::Result<()> {
|
||||
if cover_art.compressed_image_data.is_empty() {
|
||||
return Ok(()); // do nothing
|
||||
}
|
||||
|
||||
let Some(cell) = self.cells.get(app_id) else {
|
||||
debug_assert!(false); // this shouldn't happen
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let glyph_content = match CustomGlyphContent::from_bin_raster(&cover_art.compressed_image_data) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
log::warn!(
|
||||
"failed to decode cover art image for AppID {} ({:?}), skipping",
|
||||
app_id,
|
||||
e
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let image = WidgetImage::create(WidgetImageParams {
|
||||
round: WLength::Units(12.0),
|
||||
glyph_data: Some(CustomGlyphData::new(glyph_content)),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let (a, _) = layout.add_child(
|
||||
cell.image_parent,
|
||||
image,
|
||||
taffy::Style {
|
||||
size: taffy::Size {
|
||||
width: percent(1.0),
|
||||
height: percent(1.0),
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
a.widget.state().flags.new_pass = true;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,3 +14,4 @@ wayvr-ipc = { path = "../wayvr-ipc", default-features = false }
|
||||
anyhow = { workspace = true }
|
||||
xdg = "3.0"
|
||||
log = { workspace = true }
|
||||
smol = "2.0.2"
|
||||
|
||||
@@ -17,19 +17,25 @@ fn get_cache_root() -> PathBuf {
|
||||
CACHE_ROOT_PATH.clone()
|
||||
}
|
||||
|
||||
fn ensure_dir(cache_root_path: &PathBuf) {
|
||||
let _ = std::fs::create_dir(cache_root_path);
|
||||
}
|
||||
|
||||
pub fn get_data(data_path: &str) -> Option<Vec<u8>> {
|
||||
let mut path = get_cache_root();
|
||||
ensure_dir(&path);
|
||||
path.push(data_path);
|
||||
std::fs::read(path).ok()
|
||||
}
|
||||
|
||||
pub fn set_data(data_path: &str, data: &[u8]) -> std::io::Result<()> {
|
||||
// todo: mutex
|
||||
pub async fn get_data(data_path: &str) -> Option<Vec<u8>> {
|
||||
let mut path = get_cache_root();
|
||||
path.push(data_path);
|
||||
std::fs::write(path, data)
|
||||
smol::fs::read(path).await.ok()
|
||||
}
|
||||
|
||||
// todo: mutex
|
||||
pub async fn set_data(data_path: &str, data: &[u8]) -> std::io::Result<()> {
|
||||
let mut path = get_cache_root();
|
||||
path.push(data_path);
|
||||
log::debug!(
|
||||
"Writing cache data ({} bytes) to path {}",
|
||||
data.len(),
|
||||
path.to_string_lossy()
|
||||
);
|
||||
|
||||
let mut dir_path = path.clone();
|
||||
dir_path.pop();
|
||||
smol::fs::create_dir_all(dir_path).await?; // make sure directory is available
|
||||
smol::fs::write(path, data).await
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user