I don't wanna die, I don't wanna die

This commit is contained in:
Observer KRypt0n_ 2022-07-27 17:37:52 +02:00
parent 744f234acd
commit 0f6ebfff55
No known key found for this signature in database
GPG key ID: 844DA47BA25FE1E2
16 changed files with 982 additions and 188 deletions

265
assets/ui/first_run.blp Normal file
View 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;
}
};
}

View file

@ -102,6 +102,7 @@ Adw.ApplicationWindow window {
Gtk.Button {
label: "Pause";
sensitive: false;
}
}
}

View 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(())
}
})
}

View 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()
})
}*/

View 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!();
}
}

View file

@ -1 +1,2 @@
pub mod states;
pub mod executors;

View file

@ -4,8 +4,11 @@ use libadwaita::{self as adw, prelude::*};
use std::io::{Error, ErrorKind};
use anime_game_core::prelude::*;
use wait_not_await::Await;
use crate::ui::components::progress_bar::ProgressBar;
use crate::lib::config;
use crate::lib::prettify_bytes::prettify_bytes;
#[derive(Debug, Clone)]
pub enum LauncherState {
@ -102,4 +105,171 @@ impl LauncherState {
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!();
}*/
}

View file

@ -6,3 +6,4 @@ pub mod dxvk;
pub mod wine;
pub mod wine_prefix;
pub mod launcher;
pub mod prettify_bytes;

17
src/lib/prettify_bytes.rs Normal file
View 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)
}
}

View file

@ -1,6 +1,7 @@
use std::path::Path;
use std::process::Command;
#[derive(Debug, Clone)]
pub struct WinePrefix {
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<()> {
let runners_folder = runners_folder.to_string();
let wineboot = format!("{}/{}", &runners_folder, runner.files.wineboot);
let wineserver = format!("{}/{}", &runners_folder, runner.files.wineserver);
let wineboot = format!("{}/{}/{}", &runners_folder, runner.name, runner.files.wineboot);
let wineserver = format!("{}/{}/{}", &runners_folder, runner.name, runner.files.wineserver);
Command::new(wineboot)
.env("WINESERVER", wineserver)

View file

@ -5,6 +5,9 @@ use gtk::{CssProvider, StyleContext, STYLE_PROVIDER_PRIORITY_APPLICATION};
use gtk::gdk::Display;
use gtk::glib::set_application_name;
use std::path::Path;
use std::fs;
pub mod ui;
pub mod lib;
@ -27,13 +30,6 @@ async fn main() {
// FIXME: doesn't work?
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
let application = gtk::Application::new(
Some(APP_ID),
@ -53,10 +49,24 @@ async fn main() {
STYLE_PROVIDER_PRIORITY_APPLICATION
);
// Load main window and show it
let main = MainApp::new(app).expect("Failed to init MainApp");
// Create default launcher folder if needed
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

View file

@ -1,3 +1,4 @@
pub mod dxvk_row;
pub mod wine_group;
pub mod wine_row;
pub mod progress_bar;

View 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
View 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: &gtk::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 {}

View file

@ -14,6 +14,7 @@ use crate::ui::*;
use super::preferences::PreferencesStack;
use super::traits::toast_error::ToastError;
use super::components::progress_bar::ProgressBar;
use crate::lib::config;
use crate::lib::game;
@ -22,24 +23,7 @@ use crate::lib::launcher::states::LauncherState;
use crate::lib::wine_prefix::WinePrefix;
use crate::lib::wine::Version as WineVersion;
use crate::lib::dxvk::Version as DxvkVersion;
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)
}
}
use crate::lib::prettify_bytes::prettify_bytes;
fn toast_error(app: &App, msg: &str, err: Error) {
app.update(Actions::ToastError(Rc::new((
@ -70,15 +54,13 @@ pub struct AppWidgets {
pub launch_game: gtk::Button,
pub open_preferences: gtk::Button,
pub launch_game_group: adw::PreferencesGroup,
pub progress_bar_group: adw::PreferencesGroup,
pub progress_bar: gtk::ProgressBar,
pub progress_bar: ProgressBar,
pub preferences_stack: PreferencesStack
}
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 window = get_object::<adw::ApplicationWindow>(&builder, "window")?;
@ -98,9 +80,11 @@ impl AppWidgets {
launch_game: get_object(&builder, "launch_game")?,
open_preferences: get_object(&builder, "open_preferences")?,
launch_game_group: get_object(&builder, "launch_game_group")?,
progress_bar_group: get_object(&builder, "progress_bar_group")?,
progress_bar: get_object(&builder, "progress_bar")?,
progress_bar: ProgressBar::new(
get_object(&builder, "progress_bar")?,
get_object(&builder, "launch_game_group")?,
get_object(&builder, "progress_bar_group")?
),
preferences_stack: PreferencesStack::new(window, toast_overlay)?
};
@ -311,144 +295,6 @@ impl App {
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
diff.install_to_by(config.game.path, config.launcher.temp, move |state| {
this.update(Actions::UpdateProgressByState(Rc::new((state, None)))).unwrap();
@ -464,6 +310,7 @@ impl App {
}
Actions::UpdateProgressByState(state) => {
todo!();
// let (state, suffix) = (&*state).clone();
match &state.0 {
@ -532,21 +379,15 @@ impl App {
}
Actions::ShowProgressBar => {
this.widgets.progress_bar.set_text(None);
this.widgets.progress_bar.set_fraction(0.0);
this.widgets.launch_game_group.hide();
this.widgets.progress_bar_group.show();
this.widgets.progress_bar.show();
}
Actions::UpdateProgress { fraction, title } => {
this.widgets.progress_bar.set_text(Some(title.as_str()));
this.widgets.progress_bar.set_fraction(*fraction);
this.widgets.progress_bar.update(*fraction, Some(title.as_str()));
}
Actions::HideProgressBar => {
this.widgets.launch_game_group.show();
this.widgets.progress_bar_group.hide();
this.widgets.progress_bar.hide();
}
Actions::ToastError(toast) => {

View file

@ -1,16 +1,15 @@
use gtk4::{self as gtk, prelude::*};
use libadwaita::{self as adw, prelude::*};
mod first_run;
mod main;
mod preferences;
mod traits;
pub mod components;
pub use main::{
App as MainApp,
// AppState as MainAppState,
};
pub use first_run::App as FirstRunApp;
pub use main::App as MainApp;
/// This function loads object from builder or panics if it doesn't exist
pub fn get_object<T: IsA<gtk::glib::Object>>(builder: &gtk::Builder, name: &str) -> Result<T, String> {