mirror of
https://github.com/an-anime-team/sleepy-launcher.git
synced 2025-04-02 23:13:28 +03:00
I don't wanna die, I don't wanna die
This commit is contained in:
parent
744f234acd
commit
0f6ebfff55
16 changed files with 982 additions and 188 deletions
265
assets/ui/first_run.blp
Normal file
265
assets/ui/first_run.blp
Normal file
|
@ -0,0 +1,265 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
using Adw 1;
|
||||||
|
|
||||||
|
Adw.ApplicationWindow window {
|
||||||
|
default-width: 780;
|
||||||
|
default-height: 560;
|
||||||
|
|
||||||
|
content: Gtk.Box {
|
||||||
|
orientation: vertical;
|
||||||
|
|
||||||
|
Adw.HeaderBar {
|
||||||
|
title-widget: Adw.WindowTitle {
|
||||||
|
title: "An Anime Game Launcher";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Adw.Carousel carousel {
|
||||||
|
allow-mouse-drag: false;
|
||||||
|
|
||||||
|
// First page (welcome message)
|
||||||
|
|
||||||
|
Gtk.Box first_page {
|
||||||
|
orientation: vertical;
|
||||||
|
hexpand: true;
|
||||||
|
|
||||||
|
Adw.PreferencesPage {
|
||||||
|
Adw.PreferencesGroup {
|
||||||
|
Gtk.Image {
|
||||||
|
resource: "/org/app/assets/images/icon.png";
|
||||||
|
|
||||||
|
vexpand: true;
|
||||||
|
margin-top: 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Label {
|
||||||
|
label: "An Anime Game Launcher";
|
||||||
|
margin-top: 32;
|
||||||
|
|
||||||
|
styles ["title-1"]
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Label {
|
||||||
|
label: "Hi there! Welcome to the An Anime Game Launcher. We need to prepare some stuff and download default components before you could run the game";
|
||||||
|
|
||||||
|
wrap: true;
|
||||||
|
justify: center;
|
||||||
|
margin-top: 32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Adw.PreferencesGroup {
|
||||||
|
vexpand: true;
|
||||||
|
valign: center;
|
||||||
|
|
||||||
|
Gtk.Box {
|
||||||
|
orientation: horizontal;
|
||||||
|
spacing: 8;
|
||||||
|
halign: center;
|
||||||
|
|
||||||
|
Gtk.Button first_page_continue {
|
||||||
|
label: "Continue";
|
||||||
|
|
||||||
|
styles ["suggested-action"]
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Button {
|
||||||
|
label: "Advanced";
|
||||||
|
sensitive: false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second page (warning message)
|
||||||
|
|
||||||
|
Gtk.Box second_page {
|
||||||
|
orientation: vertical;
|
||||||
|
hexpand: true;
|
||||||
|
|
||||||
|
Adw.PreferencesPage {
|
||||||
|
Adw.PreferencesGroup {
|
||||||
|
Gtk.Label {
|
||||||
|
label: "ToS violation warning";
|
||||||
|
margin-top: 8;
|
||||||
|
|
||||||
|
styles ["title-1"]
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Box {
|
||||||
|
orientation: vertical;
|
||||||
|
margin-top: 32;
|
||||||
|
spacing: 12;
|
||||||
|
|
||||||
|
Gtk.Label {
|
||||||
|
label: "This launcher is an unofficial tool, in no way related to miHoYo nor COGNOSPHERE.";
|
||||||
|
|
||||||
|
wrap: true;
|
||||||
|
halign: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Label {
|
||||||
|
label: "This tool is designed to facilitate playing Genshin Impact on Linux, and was built with the sole purpose of installing and running the game with less hassle.";
|
||||||
|
|
||||||
|
wrap: true;
|
||||||
|
halign: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Label {
|
||||||
|
label: "It does so by using existing components and making the experience simple for the user.";
|
||||||
|
|
||||||
|
wrap: true;
|
||||||
|
halign: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Label {
|
||||||
|
label: "However, some components used here likely break the miHoYo Terms of Service for Genshin Impact.";
|
||||||
|
|
||||||
|
wrap: true;
|
||||||
|
halign: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Label {
|
||||||
|
label: "If you are using this launcher, your player account could become identified as TOS-non-compliant by miHoYo/COGNOSPHERE.";
|
||||||
|
|
||||||
|
wrap: true;
|
||||||
|
halign: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Label {
|
||||||
|
label: "If this happens, as your account would be disobeying TOS, miHoYo/COGNOSPHERE are free to do what they want. Including banning.";
|
||||||
|
|
||||||
|
wrap: true;
|
||||||
|
halign: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Label {
|
||||||
|
label: "If you understand the risk of trying to play the game in an unofficial capacity, press OK and let's go researching the world of Teyvat!";
|
||||||
|
|
||||||
|
wrap: true;
|
||||||
|
halign: start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Adw.PreferencesGroup {
|
||||||
|
vexpand: true;
|
||||||
|
valign: center;
|
||||||
|
|
||||||
|
Gtk.Box {
|
||||||
|
orientation: horizontal;
|
||||||
|
spacing: 8;
|
||||||
|
halign: center;
|
||||||
|
|
||||||
|
Gtk.Button second_page_continue {
|
||||||
|
label: "Continue";
|
||||||
|
|
||||||
|
styles ["suggested-action"]
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Button second_page_exit {
|
||||||
|
label: "Exit";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Third page (downloading components)
|
||||||
|
|
||||||
|
Gtk.Box third_page {
|
||||||
|
orientation: vertical;
|
||||||
|
hexpand: true;
|
||||||
|
|
||||||
|
Adw.PreferencesPage {
|
||||||
|
Adw.PreferencesGroup {
|
||||||
|
Gtk.Label {
|
||||||
|
label: "Download default components";
|
||||||
|
margin-top: 16;
|
||||||
|
|
||||||
|
styles ["title-1"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Adw.PreferencesGroup {
|
||||||
|
vexpand: true;
|
||||||
|
valign: center;
|
||||||
|
|
||||||
|
Adw.ComboRow third_page_wine_version {
|
||||||
|
title: "Wine version";
|
||||||
|
|
||||||
|
model: Gtk.StringList {
|
||||||
|
strings [
|
||||||
|
"Wine-GE-Proton 7-22"
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Adw.ComboRow third_page_dxvk_version {
|
||||||
|
title: "DXVK version";
|
||||||
|
|
||||||
|
model: Gtk.StringList {
|
||||||
|
strings [
|
||||||
|
"dxvk-1.10.2"
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Adw.PreferencesGroup third_page_buttons_group {
|
||||||
|
vexpand: true;
|
||||||
|
valign: center;
|
||||||
|
|
||||||
|
Gtk.Box {
|
||||||
|
orientation: horizontal;
|
||||||
|
spacing: 8;
|
||||||
|
halign: center;
|
||||||
|
|
||||||
|
Gtk.Button third_page_download {
|
||||||
|
label: "Download";
|
||||||
|
|
||||||
|
styles ["suggested-action"]
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Button third_page_exit {
|
||||||
|
label: "Exit";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Adw.PreferencesGroup third_page_progress_bar_group {
|
||||||
|
vexpand: true;
|
||||||
|
valign: center;
|
||||||
|
visible: false;
|
||||||
|
|
||||||
|
Gtk.Box {
|
||||||
|
halign: center;
|
||||||
|
margin-top: 64;
|
||||||
|
spacing: 20;
|
||||||
|
|
||||||
|
Gtk.ProgressBar third_page_progress_bar {
|
||||||
|
text: "Downloading: 37% (3.7 of 10 GB)";
|
||||||
|
show-text: true;
|
||||||
|
|
||||||
|
width-request: 260;
|
||||||
|
fraction: 0.37;
|
||||||
|
valign: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Button {
|
||||||
|
label: "Pause";
|
||||||
|
sensitive: false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Adw.CarouselIndicatorDots {
|
||||||
|
carousel: carousel;
|
||||||
|
height-request: 32;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -102,6 +102,7 @@ Adw.ApplicationWindow window {
|
||||||
|
|
||||||
Gtk.Button {
|
Gtk.Button {
|
||||||
label: "Pause";
|
label: "Pause";
|
||||||
|
sensitive: false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
34
src/lib/launcher/executors/create_prefix.rs
Normal file
34
src/lib/launcher/executors/create_prefix.rs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
use std::io::Error;
|
||||||
|
|
||||||
|
use wait_not_await::Await;
|
||||||
|
|
||||||
|
use crate::ui::components::progress_bar::ProgressBar;
|
||||||
|
|
||||||
|
use crate::lib::wine_prefix::WinePrefix;
|
||||||
|
use crate::lib::config::Config;
|
||||||
|
|
||||||
|
pub fn create_prefix(config: Config, progress_bar: ProgressBar) -> Await<Result<(), (String, Error)>> {
|
||||||
|
Await::new(move || {
|
||||||
|
// Create prefix if needed
|
||||||
|
let prefix = WinePrefix::new(&config.game.wine.prefix);
|
||||||
|
|
||||||
|
if !prefix.exists() {
|
||||||
|
progress_bar.update(0.0, Some("Creating prefix..."));
|
||||||
|
|
||||||
|
match config.try_get_selected_wine_info() {
|
||||||
|
Some(wine_version) => match prefix.update(&config.game.wine.builds, wine_version) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(err) => Err((String::from("Failed to create prefix"), err))
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
// TODO: download default wine
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
64
src/lib/launcher/executors/download_diff.rs
Normal file
64
src/lib/launcher/executors/download_diff.rs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
use std::io::Error;
|
||||||
|
|
||||||
|
use anime_game_core::prelude::*;
|
||||||
|
use wait_not_await::Await;
|
||||||
|
|
||||||
|
use crate::ui::components::progress_bar::ProgressBar;
|
||||||
|
use crate::lib::prettify_bytes::prettify_bytes;
|
||||||
|
|
||||||
|
/*pub fn download_diff(diff: &VersionDiff, progress_bar: ProgressBar, suffix: Option<String>) -> Await<Result<(), (String, Error)>> {
|
||||||
|
let (send, recv) = std::sync::mpsc::channel();
|
||||||
|
|
||||||
|
diff.install(move |state| {
|
||||||
|
match state {
|
||||||
|
InstallerUpdate::DownloadingStarted(_) => progress_bar.update(0.0, Some("Downloading...")),
|
||||||
|
|
||||||
|
InstallerUpdate::DownloadingProgress(curr, total) => {
|
||||||
|
// To reduce amount of action requests
|
||||||
|
// if curr % 10000 < 200 {
|
||||||
|
let progress = curr as f64 / total as f64;
|
||||||
|
|
||||||
|
progress_bar.update(progress, Some(&format!(
|
||||||
|
"Downloading{}: {:.2}% ({} of {})",
|
||||||
|
if let Some(suffix) = suffix { format!(" {}", suffix) } else { String::new() },
|
||||||
|
progress * 100.0,
|
||||||
|
prettify_bytes(curr),
|
||||||
|
prettify_bytes(total)
|
||||||
|
)));
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
InstallerUpdate::UnpackingStarted(_) => progress_bar.update(0.0, Some("Unpacking...")),
|
||||||
|
|
||||||
|
InstallerUpdate::UnpackingProgress(curr, total) => {
|
||||||
|
let progress = curr as f64 / total as f64;
|
||||||
|
|
||||||
|
progress_bar.update(progress, Some(&format!(
|
||||||
|
"Unpacking{}: {:.2}% ({} of {})",
|
||||||
|
if let Some(suffix) = suffix { format!(" {}", suffix) } else { String::new() },
|
||||||
|
progress * 100.0,
|
||||||
|
prettify_bytes(curr),
|
||||||
|
prettify_bytes(total)
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
InstallerUpdate::DownloadingFinished => (),
|
||||||
|
|
||||||
|
InstallerUpdate::UnpackingFinished => {
|
||||||
|
send.send(Ok(()));
|
||||||
|
},
|
||||||
|
|
||||||
|
InstallerUpdate::DownloadingError(err) => {
|
||||||
|
send.send(Err((String::from("Failed to download"), err.into())));
|
||||||
|
}
|
||||||
|
|
||||||
|
InstallerUpdate::UnpackingError => {
|
||||||
|
send.send(Err((String::from("Failed to unpack"), Error::last_os_error())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Await::new(move || {
|
||||||
|
recv.recv().unwrap()
|
||||||
|
})
|
||||||
|
}*/
|
32
src/lib/launcher/executors/mod.rs
Normal file
32
src/lib/launcher/executors/mod.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
mod create_prefix;
|
||||||
|
mod download_diff;
|
||||||
|
|
||||||
|
pub use create_prefix::*;
|
||||||
|
pub use download_diff::*;
|
||||||
|
|
||||||
|
use crate::lib::config;
|
||||||
|
use crate::lib::wine_prefix::WinePrefix;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum Component {
|
||||||
|
Wine,
|
||||||
|
DXVK,
|
||||||
|
Prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ComponentsChain {
|
||||||
|
pub chain: Vec<Component>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentsChain {
|
||||||
|
pub fn get() -> std::io::Result<Self> {
|
||||||
|
let config = config::get()?;
|
||||||
|
|
||||||
|
let wine_prefix = WinePrefix::new(&config.game.wine.prefix);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1,2 @@
|
||||||
pub mod states;
|
pub mod states;
|
||||||
|
pub mod executors;
|
||||||
|
|
|
@ -4,8 +4,11 @@ use libadwaita::{self as adw, prelude::*};
|
||||||
use std::io::{Error, ErrorKind};
|
use std::io::{Error, ErrorKind};
|
||||||
|
|
||||||
use anime_game_core::prelude::*;
|
use anime_game_core::prelude::*;
|
||||||
|
use wait_not_await::Await;
|
||||||
|
|
||||||
|
use crate::ui::components::progress_bar::ProgressBar;
|
||||||
use crate::lib::config;
|
use crate::lib::config;
|
||||||
|
use crate::lib::prettify_bytes::prettify_bytes;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum LauncherState {
|
pub enum LauncherState {
|
||||||
|
@ -102,4 +105,171 @@ impl LauncherState {
|
||||||
VersionDiff::NotInstalled { .. } => Self::GameNotInstalled(diff)
|
VersionDiff::NotInstalled { .. } => Self::GameNotInstalled(diff)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*pub fn execute(&self, progress_bar: &ProgressBar) -> Await<Result<(), (&str, Error)>> {
|
||||||
|
match self {
|
||||||
|
Self::Launch => {
|
||||||
|
// Display toast message if the game is failed to run
|
||||||
|
/*if let Err(err) = game::run(false) {
|
||||||
|
this.toast_error("Failed to run game", err);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
todo!();
|
||||||
|
},
|
||||||
|
|
||||||
|
Self::PatchAvailable(_) => todo!(),
|
||||||
|
|
||||||
|
Self::VoiceUpdateAvailable(diff) |
|
||||||
|
Self::VoiceNotInstalled(diff) |
|
||||||
|
Self::GameUpdateAvailable(diff) |
|
||||||
|
Self::GameNotInstalled(diff) => {
|
||||||
|
// this.update(Actions::DownloadDiff(Rc::new(diff))).unwrap();
|
||||||
|
|
||||||
|
// Download wine version if not installed
|
||||||
|
match WineVersion::latest() {
|
||||||
|
Ok(wine) => match Installer::new(wine.uri) {
|
||||||
|
Ok(mut installer) => {
|
||||||
|
let (send, recv) = std::sync::mpsc::channel();
|
||||||
|
let wine_title = wine.title.clone();
|
||||||
|
|
||||||
|
installer.install(&config.game.wine.builds, clone!(@strong this => move |state| {
|
||||||
|
match state {
|
||||||
|
InstallerUpdate::UnpackingFinished => {
|
||||||
|
send.send(true).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
InstallerUpdate::DownloadingError(_) |
|
||||||
|
InstallerUpdate::UnpackingError => {
|
||||||
|
send.send(false).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.update(Actions::UpdateProgressByState(Rc::new((state, Some(wine_title.clone()))))).unwrap();
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Block thread until downloading finished
|
||||||
|
if recv.recv().unwrap() {
|
||||||
|
config.game.wine.selected = Some(wine.name);
|
||||||
|
|
||||||
|
config::update(config.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
println!("I'm tired, Boss!");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
toast_error(&this, "Failed to init wine version installer", err.into());
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
toast_error(&this, "Failed to load wine versions list", err.into());
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create prefix if needed
|
||||||
|
let prefix = WinePrefix::new(&config.game.wine.prefix);
|
||||||
|
|
||||||
|
if !prefix.exists() {
|
||||||
|
this.update(Actions::UpdateProgress {
|
||||||
|
fraction: Rc::new(0.0),
|
||||||
|
title: Rc::new(String::from("Creating prefix..."))
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
match config.try_get_selected_wine_info() {
|
||||||
|
Some(wine_version) => {
|
||||||
|
if let Err(err) = prefix.update(&config.game.wine.builds, wine_version) {
|
||||||
|
toast_error(&this, "Failed to create wineprefix", err);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download and apply DXVK if not installed
|
||||||
|
match DxvkVersion::latest() {
|
||||||
|
Ok(dxvk) => match Installer::new(&dxvk.uri) {
|
||||||
|
Ok(mut installer) => {
|
||||||
|
let (send, recv) = std::sync::mpsc::channel();
|
||||||
|
let dxvk_title = dxvk.name.clone();
|
||||||
|
|
||||||
|
installer.install(&config.game.dxvk.builds, clone!(@strong this => move |state| {
|
||||||
|
match state {
|
||||||
|
InstallerUpdate::UnpackingFinished => {
|
||||||
|
send.send(true).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
InstallerUpdate::DownloadingError(_) |
|
||||||
|
InstallerUpdate::UnpackingError => {
|
||||||
|
send.send(false).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.update(Actions::UpdateProgressByState(Rc::new((state, Some(dxvk_title.clone()))))).unwrap();
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Block thread until downloading finished
|
||||||
|
if recv.recv().unwrap() {
|
||||||
|
config.game.dxvk.selected = Some(dxvk.name.clone());
|
||||||
|
|
||||||
|
config::update(config.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply DXVK
|
||||||
|
this.update(Actions::UpdateProgress {
|
||||||
|
fraction: Rc::new(100.0),
|
||||||
|
title: Rc::new(String::from("Applying DXVK..."))
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
match dxvk.apply(&config.game.dxvk.builds, &config.game.wine.prefix) {
|
||||||
|
Ok(_) => {
|
||||||
|
config.game.dxvk.selected = Some(dxvk.name);
|
||||||
|
|
||||||
|
config::update(config.clone());
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
toast_error(&this, "Failed to apply DXVK", err);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
toast_error(&this, "Failed to init wine version installer", err.into());
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
toast_error(&this, "Failed to load wine versions list", err.into());
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
todo!();
|
||||||
|
},
|
||||||
|
|
||||||
|
Self::GameOutdated(_) => (),
|
||||||
|
Self::VoiceOutdated(_) => ()
|
||||||
|
}
|
||||||
|
|
||||||
|
todo!();
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,3 +6,4 @@ pub mod dxvk;
|
||||||
pub mod wine;
|
pub mod wine;
|
||||||
pub mod wine_prefix;
|
pub mod wine_prefix;
|
||||||
pub mod launcher;
|
pub mod launcher;
|
||||||
|
pub mod prettify_bytes;
|
||||||
|
|
17
src/lib/prettify_bytes.rs
Normal file
17
src/lib/prettify_bytes.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
pub fn prettify_bytes(bytes: u64) -> String {
|
||||||
|
if bytes > 1024 * 1024 * 1024 {
|
||||||
|
format!("{:.2} GB", bytes as f64 / 1024.0 / 1024.0 / 1024.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
else if bytes > 1024 * 1024 {
|
||||||
|
format!("{:.2} MB", bytes as f64 / 1024.0 / 1024.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
else if bytes > 1024 {
|
||||||
|
format!("{:.2} KB", bytes as f64 / 1024.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
format!("{:.2} B", bytes)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct WinePrefix {
|
pub struct WinePrefix {
|
||||||
pub path: String
|
pub path: String
|
||||||
}
|
}
|
||||||
|
@ -17,8 +18,8 @@ impl WinePrefix {
|
||||||
fn wineboot<T: ToString>(&self, runners_folder: T, runner: super::wine::Version, command: &str) -> std::io::Result<()> {
|
fn wineboot<T: ToString>(&self, runners_folder: T, runner: super::wine::Version, command: &str) -> std::io::Result<()> {
|
||||||
let runners_folder = runners_folder.to_string();
|
let runners_folder = runners_folder.to_string();
|
||||||
|
|
||||||
let wineboot = format!("{}/{}", &runners_folder, runner.files.wineboot);
|
let wineboot = format!("{}/{}/{}", &runners_folder, runner.name, runner.files.wineboot);
|
||||||
let wineserver = format!("{}/{}", &runners_folder, runner.files.wineserver);
|
let wineserver = format!("{}/{}/{}", &runners_folder, runner.name, runner.files.wineserver);
|
||||||
|
|
||||||
Command::new(wineboot)
|
Command::new(wineboot)
|
||||||
.env("WINESERVER", wineserver)
|
.env("WINESERVER", wineserver)
|
||||||
|
|
30
src/main.rs
30
src/main.rs
|
@ -5,6 +5,9 @@ use gtk::{CssProvider, StyleContext, STYLE_PROVIDER_PRIORITY_APPLICATION};
|
||||||
use gtk::gdk::Display;
|
use gtk::gdk::Display;
|
||||||
use gtk::glib::set_application_name;
|
use gtk::glib::set_application_name;
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
pub mod lib;
|
pub mod lib;
|
||||||
|
|
||||||
|
@ -27,13 +30,6 @@ async fn main() {
|
||||||
// FIXME: doesn't work?
|
// FIXME: doesn't work?
|
||||||
set_application_name("An Anime Game Launcher");
|
set_application_name("An Anime Game Launcher");
|
||||||
|
|
||||||
// Create default launcher folder if needed
|
|
||||||
let launcher_dir = lib::consts::launcher_dir().unwrap();
|
|
||||||
|
|
||||||
if !std::path::Path::new(&launcher_dir).exists() {
|
|
||||||
std::fs::create_dir_all(launcher_dir).expect("Failed to create default launcher dir");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create app
|
// Create app
|
||||||
let application = gtk::Application::new(
|
let application = gtk::Application::new(
|
||||||
Some(APP_ID),
|
Some(APP_ID),
|
||||||
|
@ -53,10 +49,24 @@ async fn main() {
|
||||||
STYLE_PROVIDER_PRIORITY_APPLICATION
|
STYLE_PROVIDER_PRIORITY_APPLICATION
|
||||||
);
|
);
|
||||||
|
|
||||||
// Load main window and show it
|
// Create default launcher folder if needed
|
||||||
let main = MainApp::new(app).expect("Failed to init MainApp");
|
let launcher_dir = lib::consts::launcher_dir().unwrap();
|
||||||
|
|
||||||
main.show();
|
if !Path::new(&launcher_dir).exists() || Path::new(&format!("{}/.first-run", launcher_dir)).exists() {
|
||||||
|
fs::create_dir_all(&launcher_dir).expect("Failed to create default launcher dir");
|
||||||
|
fs::write(format!("{}/.first-run", launcher_dir), "").expect("Failed to create .first-run file");
|
||||||
|
|
||||||
|
let first_run = FirstRunApp::new(app).expect("Failed to init FirstRunApp");
|
||||||
|
|
||||||
|
first_run.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load main window and show it
|
||||||
|
else {
|
||||||
|
let main = MainApp::new(app).expect("Failed to init MainApp");
|
||||||
|
|
||||||
|
main.show();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Flush config from the memory to the file before closing the app
|
// Flush config from the memory to the file before closing the app
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
pub mod dxvk_row;
|
pub mod dxvk_row;
|
||||||
pub mod wine_group;
|
pub mod wine_group;
|
||||||
pub mod wine_row;
|
pub mod wine_row;
|
||||||
|
pub mod progress_bar;
|
||||||
|
|
82
src/ui/components/progress_bar.rs
Normal file
82
src/ui/components/progress_bar.rs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
use gtk4::{self as gtk, prelude::*};
|
||||||
|
use libadwaita::{self as adw, prelude::*};
|
||||||
|
|
||||||
|
use gtk::glib;
|
||||||
|
|
||||||
|
use std::io::Error;
|
||||||
|
|
||||||
|
use anime_game_core::prelude::*;
|
||||||
|
use wait_not_await::Await;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ProgressUpdateResult {
|
||||||
|
Updated,
|
||||||
|
Error(String, std::io::Error),
|
||||||
|
Finished
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, glib::Downgrade)]
|
||||||
|
pub struct ProgressBar {
|
||||||
|
pub progress_bar: gtk::ProgressBar,
|
||||||
|
pub default_group: adw::PreferencesGroup,
|
||||||
|
pub progress_bar_group: adw::PreferencesGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProgressBar {
|
||||||
|
pub fn new(progress_bar: gtk::ProgressBar, default_group: adw::PreferencesGroup, progress_bar_group: adw::PreferencesGroup) -> Self {
|
||||||
|
Self {
|
||||||
|
progress_bar,
|
||||||
|
default_group,
|
||||||
|
progress_bar_group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show(&self) {
|
||||||
|
self.progress_bar.set_text(None);
|
||||||
|
self.progress_bar.set_fraction(0.0);
|
||||||
|
|
||||||
|
self.default_group.hide();
|
||||||
|
self.progress_bar_group.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hide(&self) {
|
||||||
|
self.default_group.show();
|
||||||
|
self.progress_bar_group.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&self, fraction: f64, text: Option<&str>) {
|
||||||
|
self.progress_bar.set_fraction(fraction);
|
||||||
|
self.progress_bar.set_text(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_from_state(&self, state: InstallerUpdate) -> ProgressUpdateResult {
|
||||||
|
match state {
|
||||||
|
InstallerUpdate::DownloadingStarted(_) => self.show(),
|
||||||
|
|
||||||
|
InstallerUpdate::DownloadingProgress(curr, total) => {
|
||||||
|
let progress = curr as f64 / total as f64;
|
||||||
|
|
||||||
|
self.update(progress, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
InstallerUpdate::UnpackingProgress(curr, total) => {
|
||||||
|
let progress = curr as f64 / total as f64;
|
||||||
|
|
||||||
|
self.update(progress, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
InstallerUpdate::DownloadingFinished => (),
|
||||||
|
InstallerUpdate::UnpackingStarted(_) => (),
|
||||||
|
|
||||||
|
InstallerUpdate::DownloadingError(err) => return ProgressUpdateResult::Error(String::from("Failed to download"), err.into()),
|
||||||
|
InstallerUpdate::UnpackingError => return ProgressUpdateResult::Error(String::from("Failed to unpack"), Error::last_os_error()),
|
||||||
|
|
||||||
|
InstallerUpdate::UnpackingFinished => return ProgressUpdateResult::Finished
|
||||||
|
}
|
||||||
|
|
||||||
|
ProgressUpdateResult::Updated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for ProgressBar {}
|
||||||
|
unsafe impl Sync for ProgressBar {}
|
275
src/ui/first_run.rs
Normal file
275
src/ui/first_run.rs
Normal file
|
@ -0,0 +1,275 @@
|
||||||
|
use gtk4::{self as gtk, prelude::*};
|
||||||
|
use libadwaita::{self as adw, prelude::*};
|
||||||
|
|
||||||
|
use gtk::glib;
|
||||||
|
use gtk::glib::clone;
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::cell::Cell;
|
||||||
|
|
||||||
|
use anime_game_core::prelude::*;
|
||||||
|
|
||||||
|
use crate::ui::*;
|
||||||
|
use crate::ui::components::progress_bar::*;
|
||||||
|
|
||||||
|
use crate::lib::wine::Version as WineVersion;
|
||||||
|
use crate::lib::dxvk::Version as DxvkVersion;
|
||||||
|
use crate::lib::wine_prefix::WinePrefix;
|
||||||
|
use crate::lib::config;
|
||||||
|
|
||||||
|
/// This structure is used to describe widgets used in application
|
||||||
|
///
|
||||||
|
/// `AppWidgets::default` function loads UI file from `.assets/ui/.dist` folder and returns structure with references to its widgets
|
||||||
|
///
|
||||||
|
/// This function does not implement events
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AppWidgets {
|
||||||
|
pub window: adw::ApplicationWindow,
|
||||||
|
pub carousel: adw::Carousel,
|
||||||
|
|
||||||
|
// First page
|
||||||
|
pub first_page: gtk::Box,
|
||||||
|
pub first_page_continue: gtk::Button,
|
||||||
|
|
||||||
|
// Second page
|
||||||
|
pub second_page: gtk::Box,
|
||||||
|
pub second_page_continue: gtk::Button,
|
||||||
|
pub second_page_exit: gtk::Button,
|
||||||
|
|
||||||
|
// Third page
|
||||||
|
pub third_page: gtk::Box,
|
||||||
|
|
||||||
|
pub third_page_wine_version: adw::ComboRow,
|
||||||
|
pub third_page_dxvk_version: adw::ComboRow,
|
||||||
|
|
||||||
|
pub third_page_download: gtk::Button,
|
||||||
|
pub third_page_exit: gtk::Button,
|
||||||
|
|
||||||
|
pub third_page_progress_bar: ProgressBar
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppWidgets {
|
||||||
|
pub fn try_get() -> Result<Self, String> {
|
||||||
|
let builder = gtk::Builder::from_string(include_str!("../../assets/ui/.dist/first_run.ui"));
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
window: get_object(&builder, "window")?,
|
||||||
|
carousel: get_object(&builder, "carousel")?,
|
||||||
|
|
||||||
|
// First page
|
||||||
|
first_page: get_object(&builder, "first_page")?,
|
||||||
|
first_page_continue: get_object(&builder, "first_page_continue")?,
|
||||||
|
|
||||||
|
// Second page
|
||||||
|
second_page: get_object(&builder, "second_page")?,
|
||||||
|
second_page_continue: get_object(&builder, "second_page_continue")?,
|
||||||
|
second_page_exit: get_object(&builder, "second_page_exit")?,
|
||||||
|
|
||||||
|
// Third page
|
||||||
|
third_page: get_object(&builder, "third_page")?,
|
||||||
|
|
||||||
|
third_page_wine_version: get_object(&builder, "third_page_wine_version")?,
|
||||||
|
third_page_dxvk_version: get_object(&builder, "third_page_dxvk_version")?,
|
||||||
|
|
||||||
|
third_page_download: get_object(&builder, "third_page_download")?,
|
||||||
|
third_page_exit: get_object(&builder, "third_page_exit")?,
|
||||||
|
|
||||||
|
third_page_progress_bar: ProgressBar::new(
|
||||||
|
get_object(&builder, "third_page_progress_bar")?,
|
||||||
|
get_object(&builder, "third_page_buttons_group")?,
|
||||||
|
get_object(&builder, "third_page_progress_bar_group")?
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This enum is used to describe an action inside of this application
|
||||||
|
///
|
||||||
|
/// It may be helpful if you want to add the same event for several widgets, or call an action inside of another action
|
||||||
|
///
|
||||||
|
/// Has to implement glib::Downgrade` trait
|
||||||
|
#[derive(Debug, glib::Downgrade)]
|
||||||
|
pub enum Actions {
|
||||||
|
FirstPageContinue,
|
||||||
|
SecondPageContinue,
|
||||||
|
ThirdPageDownload,
|
||||||
|
Exit
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Actions {
|
||||||
|
pub fn into_fn<T: gtk::glib::IsA<gtk::Widget>>(&self, app: &App) -> Box<dyn Fn(&T)> {
|
||||||
|
Box::new(clone!(@weak self as action, @strong app => move |_| {
|
||||||
|
app.update(action).unwrap();
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This enum is used to store some of this application data
|
||||||
|
///
|
||||||
|
/// In this example we store a counter here to know what should we increment or decrement
|
||||||
|
///
|
||||||
|
/// This must implement `Default` trait
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Values;
|
||||||
|
|
||||||
|
/// The main application structure
|
||||||
|
///
|
||||||
|
/// `Default` macro automatically calls `AppWidgets::default`, i.e. loads UI file and reference its widgets
|
||||||
|
///
|
||||||
|
/// `Rc<Cell<Values>>` means this:
|
||||||
|
/// - `Rc` addeds ability to reference the same value from various clones of the structure.
|
||||||
|
/// This will guarantee us that inner `Cell<Values>` is the same for all the `App::clone()` values
|
||||||
|
/// - `Cell` addeds inner mutability to its value, so we can mutate it even without mutable reference.
|
||||||
|
///
|
||||||
|
/// So we have a shared reference to some value that can be changed without mutable reference.
|
||||||
|
/// That's what we need and what we use in `App::update` method
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct App {
|
||||||
|
widgets: AppWidgets,
|
||||||
|
values: Rc<Cell<Values>>,
|
||||||
|
actions: Rc<Cell<Option<glib::Sender<Actions>>>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
/// Create new application
|
||||||
|
pub fn new(app: >k::Application) -> Result<Self, String> {
|
||||||
|
// Get default widgets from ui file and add events to them
|
||||||
|
let result = Self {
|
||||||
|
widgets: AppWidgets::try_get()?,
|
||||||
|
values: Default::default(),
|
||||||
|
actions: Default::default()
|
||||||
|
}.init_events().init_actions();
|
||||||
|
|
||||||
|
// Bind app to the window
|
||||||
|
result.widgets.window.set_application(Some(app));
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add default events and values to the widgets
|
||||||
|
fn init_events(self) -> Self {
|
||||||
|
self.widgets.first_page_continue.connect_clicked(Actions::FirstPageContinue.into_fn(&self));
|
||||||
|
self.widgets.second_page_continue.connect_clicked(Actions::SecondPageContinue.into_fn(&self));
|
||||||
|
self.widgets.third_page_download.connect_clicked(Actions::ThirdPageDownload.into_fn(&self));
|
||||||
|
|
||||||
|
self.widgets.second_page_exit.connect_clicked(Actions::Exit.into_fn(&self));
|
||||||
|
self.widgets.third_page_exit.connect_clicked(Actions::Exit.into_fn(&self));
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add actions processors
|
||||||
|
///
|
||||||
|
/// Changes will happen in the main thread so you can call `update` method from separate thread
|
||||||
|
pub fn init_actions(self) -> Self {
|
||||||
|
let (sender, receiver) = glib::MainContext::channel::<Actions>(glib::PRIORITY_DEFAULT);
|
||||||
|
|
||||||
|
let this = self.clone();
|
||||||
|
|
||||||
|
receiver.attach(None, move |action| {
|
||||||
|
// Some debug output
|
||||||
|
println!("[update] action: {:?}", &action);
|
||||||
|
|
||||||
|
match action {
|
||||||
|
Actions::FirstPageContinue => {
|
||||||
|
this.widgets.carousel.scroll_to(&this.widgets.second_page, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Actions::SecondPageContinue => {
|
||||||
|
this.widgets.carousel.scroll_to(&this.widgets.third_page, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Actions::ThirdPageDownload => {
|
||||||
|
this.widgets.third_page_wine_version.set_sensitive(false);
|
||||||
|
this.widgets.third_page_dxvk_version.set_sensitive(false);
|
||||||
|
|
||||||
|
this.widgets.third_page_progress_bar.show();
|
||||||
|
|
||||||
|
let this = this.clone();
|
||||||
|
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let config = config::get().unwrap();
|
||||||
|
|
||||||
|
let wine_version = WineVersion::latest().unwrap();
|
||||||
|
let dxvk_version = DxvkVersion::latest().unwrap();
|
||||||
|
|
||||||
|
let mut wine_version_installer = Installer::new(&wine_version.uri).unwrap();
|
||||||
|
|
||||||
|
let progress_bar = this.widgets.third_page_progress_bar.clone();
|
||||||
|
|
||||||
|
wine_version_installer.install(&config.game.wine.builds, move |state| {
|
||||||
|
match progress_bar.update_from_state(state) {
|
||||||
|
ProgressUpdateResult::Updated => (),
|
||||||
|
ProgressUpdateResult::Error(_, _) => todo!(),
|
||||||
|
|
||||||
|
ProgressUpdateResult::Finished => {
|
||||||
|
let mut config = config::get().unwrap();
|
||||||
|
let prefix = WinePrefix::new(&config.game.wine.prefix);
|
||||||
|
|
||||||
|
config.game.wine.selected = Some(wine_version.name.clone());
|
||||||
|
|
||||||
|
config::update_raw(config.clone()).unwrap();
|
||||||
|
|
||||||
|
prefix.update(&config.game.wine.builds, wine_version.clone()).unwrap();
|
||||||
|
|
||||||
|
let mut dxvk_version_installer = Installer::new(&dxvk_version.uri).unwrap();
|
||||||
|
|
||||||
|
let dxvk_version = dxvk_version.clone();
|
||||||
|
let progress_bar = progress_bar.clone();
|
||||||
|
|
||||||
|
dxvk_version_installer.install(&config.game.dxvk.builds, move |state| {
|
||||||
|
match progress_bar.update_from_state(state) {
|
||||||
|
ProgressUpdateResult::Updated => (),
|
||||||
|
ProgressUpdateResult::Error(_, _) => todo!(),
|
||||||
|
|
||||||
|
ProgressUpdateResult::Finished => {
|
||||||
|
let mut config = config::get().unwrap();
|
||||||
|
|
||||||
|
config.game.dxvk.selected = Some(dxvk_version.name.clone());
|
||||||
|
|
||||||
|
config::update_raw(config.clone()).unwrap();
|
||||||
|
|
||||||
|
println!("Done!!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Actions::Exit => {
|
||||||
|
this.widgets.window.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glib::Continue(true)
|
||||||
|
});
|
||||||
|
|
||||||
|
self.actions.set(Some(sender));
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update widgets state by calling some action
|
||||||
|
pub fn update(&self, action: Actions) -> Result<(), std::sync::mpsc::SendError<Actions>> {
|
||||||
|
let actions = self.actions.take();
|
||||||
|
|
||||||
|
let result = match &actions {
|
||||||
|
Some(sender) => Ok(sender.send(action)?),
|
||||||
|
None => Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
self.actions.set(actions);
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show application window
|
||||||
|
pub fn show(&self) {
|
||||||
|
self.widgets.window.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for App {}
|
185
src/ui/main.rs
185
src/ui/main.rs
|
@ -14,6 +14,7 @@ use crate::ui::*;
|
||||||
|
|
||||||
use super::preferences::PreferencesStack;
|
use super::preferences::PreferencesStack;
|
||||||
use super::traits::toast_error::ToastError;
|
use super::traits::toast_error::ToastError;
|
||||||
|
use super::components::progress_bar::ProgressBar;
|
||||||
|
|
||||||
use crate::lib::config;
|
use crate::lib::config;
|
||||||
use crate::lib::game;
|
use crate::lib::game;
|
||||||
|
@ -22,24 +23,7 @@ use crate::lib::launcher::states::LauncherState;
|
||||||
use crate::lib::wine_prefix::WinePrefix;
|
use crate::lib::wine_prefix::WinePrefix;
|
||||||
use crate::lib::wine::Version as WineVersion;
|
use crate::lib::wine::Version as WineVersion;
|
||||||
use crate::lib::dxvk::Version as DxvkVersion;
|
use crate::lib::dxvk::Version as DxvkVersion;
|
||||||
|
use crate::lib::prettify_bytes::prettify_bytes;
|
||||||
fn prettify_bytes(bytes: u64) -> String {
|
|
||||||
if bytes > 1024 * 1024 * 1024 {
|
|
||||||
format!("{:.2} GB", bytes as f64 / 1024.0 / 1024.0 / 1024.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
else if bytes > 1024 * 1024 {
|
|
||||||
format!("{:.2} MB", bytes as f64 / 1024.0 / 1024.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
else if bytes > 1024 {
|
|
||||||
format!("{:.2} KB", bytes as f64 / 1024.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
format!("{:.2} B", bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toast_error(app: &App, msg: &str, err: Error) {
|
fn toast_error(app: &App, msg: &str, err: Error) {
|
||||||
app.update(Actions::ToastError(Rc::new((
|
app.update(Actions::ToastError(Rc::new((
|
||||||
|
@ -70,15 +54,13 @@ pub struct AppWidgets {
|
||||||
pub launch_game: gtk::Button,
|
pub launch_game: gtk::Button,
|
||||||
pub open_preferences: gtk::Button,
|
pub open_preferences: gtk::Button,
|
||||||
|
|
||||||
pub launch_game_group: adw::PreferencesGroup,
|
pub progress_bar: ProgressBar,
|
||||||
pub progress_bar_group: adw::PreferencesGroup,
|
|
||||||
pub progress_bar: gtk::ProgressBar,
|
|
||||||
|
|
||||||
pub preferences_stack: PreferencesStack
|
pub preferences_stack: PreferencesStack
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppWidgets {
|
impl AppWidgets {
|
||||||
fn try_get() -> Result<Self, String> {
|
pub fn try_get() -> Result<Self, String> {
|
||||||
let builder = gtk::Builder::from_string(include_str!("../../assets/ui/.dist/main.ui"));
|
let builder = gtk::Builder::from_string(include_str!("../../assets/ui/.dist/main.ui"));
|
||||||
|
|
||||||
let window = get_object::<adw::ApplicationWindow>(&builder, "window")?;
|
let window = get_object::<adw::ApplicationWindow>(&builder, "window")?;
|
||||||
|
@ -98,9 +80,11 @@ impl AppWidgets {
|
||||||
launch_game: get_object(&builder, "launch_game")?,
|
launch_game: get_object(&builder, "launch_game")?,
|
||||||
open_preferences: get_object(&builder, "open_preferences")?,
|
open_preferences: get_object(&builder, "open_preferences")?,
|
||||||
|
|
||||||
launch_game_group: get_object(&builder, "launch_game_group")?,
|
progress_bar: ProgressBar::new(
|
||||||
progress_bar_group: get_object(&builder, "progress_bar_group")?,
|
get_object(&builder, "progress_bar")?,
|
||||||
progress_bar: get_object(&builder, "progress_bar")?,
|
get_object(&builder, "launch_game_group")?,
|
||||||
|
get_object(&builder, "progress_bar_group")?
|
||||||
|
),
|
||||||
|
|
||||||
preferences_stack: PreferencesStack::new(window, toast_overlay)?
|
preferences_stack: PreferencesStack::new(window, toast_overlay)?
|
||||||
};
|
};
|
||||||
|
@ -311,144 +295,6 @@ impl App {
|
||||||
|
|
||||||
this.update(Actions::ShowProgressBar).unwrap();
|
this.update(Actions::ShowProgressBar).unwrap();
|
||||||
|
|
||||||
// Download wine version if not installed
|
|
||||||
match WineVersion::latest() {
|
|
||||||
Ok(wine) => match Installer::new(wine.uri) {
|
|
||||||
Ok(mut installer) => {
|
|
||||||
let (send, recv) = std::sync::mpsc::channel();
|
|
||||||
let wine_title = wine.title.clone();
|
|
||||||
|
|
||||||
installer.install(&config.game.wine.builds, clone!(@strong this => move |state| {
|
|
||||||
match state {
|
|
||||||
InstallerUpdate::UnpackingFinished => {
|
|
||||||
send.send(true).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
InstallerUpdate::DownloadingError(_) |
|
|
||||||
InstallerUpdate::UnpackingError => {
|
|
||||||
send.send(false).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => ()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.update(Actions::UpdateProgressByState(Rc::new((state, Some(wine_title.clone()))))).unwrap();
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Block thread until downloading finished
|
|
||||||
if recv.recv().unwrap() {
|
|
||||||
config.game.wine.selected = Some(wine.name);
|
|
||||||
|
|
||||||
config::update(config.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
println!("I'm tired, Boss!");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(err) => {
|
|
||||||
toast_error(&this, "Failed to init wine version installer", err.into());
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(err) => {
|
|
||||||
toast_error(&this, "Failed to load wine versions list", err.into());
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create prefix if needed
|
|
||||||
let prefix = WinePrefix::new(&config.game.wine.prefix);
|
|
||||||
|
|
||||||
if !prefix.exists() {
|
|
||||||
this.update(Actions::UpdateProgress {
|
|
||||||
fraction: Rc::new(0.0),
|
|
||||||
title: Rc::new(String::from("Creating prefix..."))
|
|
||||||
}).unwrap();
|
|
||||||
|
|
||||||
match config.try_get_selected_wine_info() {
|
|
||||||
Some(wine_version) => {
|
|
||||||
if let Err(err) = prefix.update(&config.game.wine.builds, wine_version) {
|
|
||||||
toast_error(&this, "Failed to create wineprefix", err);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download and apply DXVK if not installed
|
|
||||||
match DxvkVersion::latest() {
|
|
||||||
Ok(dxvk) => match Installer::new(&dxvk.uri) {
|
|
||||||
Ok(mut installer) => {
|
|
||||||
let (send, recv) = std::sync::mpsc::channel();
|
|
||||||
let dxvk_title = dxvk.name.clone();
|
|
||||||
|
|
||||||
installer.install(&config.game.dxvk.builds, clone!(@strong this => move |state| {
|
|
||||||
match state {
|
|
||||||
InstallerUpdate::UnpackingFinished => {
|
|
||||||
send.send(true).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
InstallerUpdate::DownloadingError(_) |
|
|
||||||
InstallerUpdate::UnpackingError => {
|
|
||||||
send.send(false).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => ()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.update(Actions::UpdateProgressByState(Rc::new((state, Some(dxvk_title.clone()))))).unwrap();
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Block thread until downloading finished
|
|
||||||
if recv.recv().unwrap() {
|
|
||||||
config.game.dxvk.selected = Some(dxvk.name.clone());
|
|
||||||
|
|
||||||
config::update(config.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply DXVK
|
|
||||||
this.update(Actions::UpdateProgress {
|
|
||||||
fraction: Rc::new(100.0),
|
|
||||||
title: Rc::new(String::from("Applying DXVK..."))
|
|
||||||
}).unwrap();
|
|
||||||
|
|
||||||
match dxvk.apply(&config.game.dxvk.builds, &config.game.wine.prefix) {
|
|
||||||
Ok(_) => {
|
|
||||||
config.game.dxvk.selected = Some(dxvk.name);
|
|
||||||
|
|
||||||
config::update(config.clone());
|
|
||||||
},
|
|
||||||
Err(err) => {
|
|
||||||
toast_error(&this, "Failed to apply DXVK", err);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(err) => {
|
|
||||||
toast_error(&this, "Failed to init wine version installer", err.into());
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(err) => {
|
|
||||||
toast_error(&this, "Failed to load wine versions list", err.into());
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download diff
|
// Download diff
|
||||||
diff.install_to_by(config.game.path, config.launcher.temp, move |state| {
|
diff.install_to_by(config.game.path, config.launcher.temp, move |state| {
|
||||||
this.update(Actions::UpdateProgressByState(Rc::new((state, None)))).unwrap();
|
this.update(Actions::UpdateProgressByState(Rc::new((state, None)))).unwrap();
|
||||||
|
@ -464,6 +310,7 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
Actions::UpdateProgressByState(state) => {
|
Actions::UpdateProgressByState(state) => {
|
||||||
|
todo!();
|
||||||
// let (state, suffix) = (&*state).clone();
|
// let (state, suffix) = (&*state).clone();
|
||||||
|
|
||||||
match &state.0 {
|
match &state.0 {
|
||||||
|
@ -532,21 +379,15 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
Actions::ShowProgressBar => {
|
Actions::ShowProgressBar => {
|
||||||
this.widgets.progress_bar.set_text(None);
|
this.widgets.progress_bar.show();
|
||||||
this.widgets.progress_bar.set_fraction(0.0);
|
|
||||||
|
|
||||||
this.widgets.launch_game_group.hide();
|
|
||||||
this.widgets.progress_bar_group.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Actions::UpdateProgress { fraction, title } => {
|
Actions::UpdateProgress { fraction, title } => {
|
||||||
this.widgets.progress_bar.set_text(Some(title.as_str()));
|
this.widgets.progress_bar.update(*fraction, Some(title.as_str()));
|
||||||
this.widgets.progress_bar.set_fraction(*fraction);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Actions::HideProgressBar => {
|
Actions::HideProgressBar => {
|
||||||
this.widgets.launch_game_group.show();
|
this.widgets.progress_bar.hide();
|
||||||
this.widgets.progress_bar_group.hide();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Actions::ToastError(toast) => {
|
Actions::ToastError(toast) => {
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
use gtk4::{self as gtk, prelude::*};
|
use gtk4::{self as gtk, prelude::*};
|
||||||
use libadwaita::{self as adw, prelude::*};
|
use libadwaita::{self as adw, prelude::*};
|
||||||
|
|
||||||
|
mod first_run;
|
||||||
mod main;
|
mod main;
|
||||||
mod preferences;
|
mod preferences;
|
||||||
mod traits;
|
mod traits;
|
||||||
|
|
||||||
pub mod components;
|
pub mod components;
|
||||||
|
|
||||||
pub use main::{
|
pub use first_run::App as FirstRunApp;
|
||||||
App as MainApp,
|
pub use main::App as MainApp;
|
||||||
// AppState as MainAppState,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// This function loads object from builder or panics if it doesn't exist
|
/// This function loads object from builder or panics if it doesn't exist
|
||||||
pub fn get_object<T: IsA<gtk::glib::Object>>(builder: >k::Builder, name: &str) -> Result<T, String> {
|
pub fn get_object<T: IsA<gtk::glib::Object>>(builder: >k::Builder, name: &str) -> Result<T, String> {
|
||||||
|
|
Loading…
Add table
Reference in a new issue