Merge pull request #130 from an-anime-team/next

Release 3.5.0
This commit is contained in:
Observer KRypt0n_ 2023-04-16 18:16:07 +02:00 committed by GitHub
commit b756caba31
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 861 additions and 231 deletions

View file

@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- Added game sandboxing feature
- Added debugger to wine tools
### Changed
- Removed fractions displaying in components downloading progress bar
- Moved to upgraded launcher SDK
## [3.4.1] - 12.04.2023 ## [3.4.1] - 12.04.2023
### Fixed ### Fixed

25
Cargo.lock generated
View file

@ -40,12 +40,13 @@ dependencies = [
[[package]] [[package]]
name = "anime-game-core" name = "anime-game-core"
version = "1.6.2" version = "1.7.0"
source = "git+https://github.com/an-anime-team/anime-game-core?tag=1.6.2#f41c0edbf6d830662b20a220987531bb517cf8cb" source = "git+https://github.com/an-anime-team/anime-game-core?tag=1.7.0#290cc5ff9399f68799e8e00fd3f78fa26d076296"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bzip2", "bzip2",
"cached", "cached",
"dns-lookup",
"flate2", "flate2",
"fs_extra", "fs_extra",
"kinda-virtual-fs", "kinda-virtual-fs",
@ -86,8 +87,8 @@ dependencies = [
[[package]] [[package]]
name = "anime-launcher-sdk" name = "anime-launcher-sdk"
version = "0.5.17" version = "1.0.1"
source = "git+https://github.com/an-anime-team/anime-launcher-sdk?tag=0.5.17#5b4403956feaca7204f057a26f06e69b7fc1b285" source = "git+https://github.com/an-anime-team/anime-launcher-sdk?tag=1.0.1#1d56514b71da92b827ec4162975a12e4bf9b13fc"
dependencies = [ dependencies = [
"anime-game-core", "anime-game-core",
"anyhow", "anyhow",
@ -329,9 +330,9 @@ dependencies = [
[[package]] [[package]]
name = "cached" name = "cached"
version = "0.42.0" version = "0.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5877db5d1af7fae60d06b5db9430b68056a69b3582a0be8e3691e87654aeb6" checksum = "bc2fafddf188d13788e7099295a59b99e99b2148ab2195cae454e754cc099925"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"async_once", "async_once",
@ -631,6 +632,18 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "dns-lookup"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53ecafc952c4528d9b51a458d1a8904b81783feff9fde08ab6ed2545ff396872"
dependencies = [
"cfg-if",
"libc",
"socket2",
"winapi",
]
[[package]] [[package]]
name = "doc-comment" name = "doc-comment"
version = "0.3.3" version = "0.3.3"

View file

@ -17,7 +17,8 @@ glib-build-tools = "0.17"
[dependencies.anime-launcher-sdk] [dependencies.anime-launcher-sdk]
git = "https://github.com/an-anime-team/anime-launcher-sdk" git = "https://github.com/an-anime-team/anime-launcher-sdk"
tag = "0.5.17" tag = "1.0.1"
features = ["all", "genshin"]
# path = "../anime-launcher-sdk" # ! for dev purposes only # path = "../anime-launcher-sdk" # ! for dev purposes only
@ -29,14 +30,14 @@ adw = { package = "libadwaita", version = "0.3", features = ["v1_2"] }
rfd = { version = "0.11", features = ["xdg-portal"], default-features = false } rfd = { version = "0.11", features = ["xdg-portal"], default-features = false }
open = "4.0" open = "4.0"
serde_json = "1"
anyhow = "1.0"
lazy_static = "1.4.0"
cached = { version = "0.43", features = ["proc_macro"] }
md-5 = { version = "0.10", features = ["asm"] }
tracing = "0.1" tracing = "0.1"
tracing-subscriber = "0.3" tracing-subscriber = "0.3"
fluent-templates = "0.8" fluent-templates = "0.8"
unic-langid = "0.9" unic-langid = "0.9"
lazy_static = "1.4.0"
anyhow = "1.0"
cached = { version = "0.42", features = ["proc_macro"] }
serde_json = "1"
md-5 = { version = "0.10", features = ["asm"] }

View file

@ -69,6 +69,7 @@ registry-editor = Registry editor
explorer = Explorer explorer = Explorer
task-manager = Task manager task-manager = Task manager
configuration = Configuration configuration = Configuration
debugger = Debugger
dxvk-version = DXVK version dxvk-version = DXVK version
dxvk-selection-disabled = DXVK auswahl ist durch ihre Wine auswahl deaktiviert dxvk-selection-disabled = DXVK auswahl ist durch ihre Wine auswahl deaktiviert

View file

@ -0,0 +1,24 @@
sandbox = Sandbox
sandbox-description = Run the game in isolated environment, preventing it from accessing your personal data
enable-sandboxing = Enable sandboxing
enable-sandboxing-description = Run the game in read-only copy of your root filesystem
hide-home-directory = Hide home directory
hide-home-directory-description = Isolate your /home, /var/home/$USER, and $HOME folders from the game
hostname = Hostname
private-directories = Private directories
private-directories-description = These folders will be replaced by an empty virtual filesystem (tmpfs), and their original content will not be available to sandboxed game
path = Path
shared-directories = Shared directories
shared-directories-description = These directories will be symlinked to directories in your host system
original-path = Original path
new-path = New path
read-only = Read-only
read-only-description = Forbid game to write any data to this directory

View file

@ -69,6 +69,7 @@ registry-editor = Registry editor
explorer = Explorer explorer = Explorer
task-manager = Task manager task-manager = Task manager
configuration = Configuration configuration = Configuration
debugger = Debugger
dxvk-version = DXVK version dxvk-version = DXVK version
dxvk-selection-disabled = DXVK selection is disabled by your wine group preferences dxvk-selection-disabled = DXVK selection is disabled by your wine group preferences

View file

@ -0,0 +1,24 @@
sandbox = Sandbox
sandbox-description = Run the game in isolated environment, preventing it from accessing your personal data
enable-sandboxing = Enable sandboxing
enable-sandboxing-description = Run the game in read-only copy of your root filesystem
hide-home-directory = Hide home directory
hide-home-directory-description = Isolate your /home, /var/home/$USER, and $HOME folders from the game
hostname = Hostname
private-directories = Private directories
private-directories-description = These folders will be replaced by an empty virtual filesystem (tmpfs), and their original content will not be available to sandboxed game
path = Path
shared-directories = Shared directories
shared-directories-description = These directories will be symlinked to directories in your host system
original-path = Original path
new-path = New path
read-only = Read-only
read-only-description = Forbid game to write any data to this directory

View file

@ -69,6 +69,7 @@ registry-editor = Editor del Registro
explorer = Explorador explorer = Explorador
task-manager = Administrador de Tareas task-manager = Administrador de Tareas
configuration = Configuración configuration = Configuración
debugger = Debugger
dxvk-version = Versión de DXVK dxvk-version = Versión de DXVK
dxvk-selection-disabled = La selección de DXVK está deshabilitada por las preferencias de su grupo de vinos dxvk-selection-disabled = La selección de DXVK está deshabilitada por las preferencias de su grupo de vinos

View file

@ -0,0 +1,24 @@
sandbox = Sandbox
sandbox-description = Run the game in isolated environment, preventing it from accessing your personal data
enable-sandboxing = Enable sandboxing
enable-sandboxing-description = Run the game in read-only copy of your root filesystem
hide-home-directory = Hide home directory
hide-home-directory-description = Isolate your /home, /var/home/$USER, and $HOME folders from the game
hostname = Hostname
private-directories = Private directories
private-directories-description = These folders will be replaced by an empty virtual filesystem (tmpfs), and their original content will not be available to sandboxed game
path = Path
shared-directories = Shared directories
shared-directories-description = These directories will be symlinked to directories in your host system
original-path = Original path
new-path = New path
read-only = Read-only
read-only-description = Forbid game to write any data to this directory

View file

@ -69,6 +69,7 @@ registry-editor = Registry editor
explorer = Explorer explorer = Explorer
task-manager = Task manager task-manager = Task manager
configuration = Configuration configuration = Configuration
debugger = Debugger
dxvk-version = Version de DXVK dxvk-version = Version de DXVK
dxvk-selection-disabled = La sélection de versions DXVK est désactivé par vos préférences de groupe wine dxvk-selection-disabled = La sélection de versions DXVK est désactivé par vos préférences de groupe wine

View file

@ -0,0 +1,24 @@
sandbox = Sandbox
sandbox-description = Run the game in isolated environment, preventing it from accessing your personal data
enable-sandboxing = Enable sandboxing
enable-sandboxing-description = Run the game in read-only copy of your root filesystem
hide-home-directory = Hide home directory
hide-home-directory-description = Isolate your /home, /var/home/$USER, and $HOME folders from the game
hostname = Hostname
private-directories = Private directories
private-directories-description = These folders will be replaced by an empty virtual filesystem (tmpfs), and their original content will not be available to sandboxed game
path = Path
shared-directories = Shared directories
shared-directories-description = These directories will be symlinked to directories in your host system
original-path = Original path
new-path = New path
read-only = Read-only
read-only-description = Forbid game to write any data to this directory

View file

@ -69,6 +69,7 @@ registry-editor = Редактор реестра
explorer = Проводник explorer = Проводник
task-manager = Диспетчер задач task-manager = Диспетчер задач
configuration = Настройки configuration = Настройки
debugger = Отладчик
dxvk-version = Версия DXVK dxvk-version = Версия DXVK
dxvk-selection-disabled = Выбор версии DXVK отключен настройками выбранного вами Wine dxvk-selection-disabled = Выбор версии DXVK отключен настройками выбранного вами Wine

View file

@ -0,0 +1,24 @@
sandbox = Песочница
sandbox-description = Запускать игру в изолированном окружении предотвращая доступ к вашим персональным данным
enable-sandboxing = Использовать песочницу
enable-sandboxing-description = Запускать игру в копии корневой файловой системы компьютера без прав на изменение файлов
hide-home-directory = Скрыть домашнюю директорию
hide-home-directory-description = Изолировать ваши директории /home, /var/home/$USER, и $HOME от игры
hostname = Имя хоста
private-directories = Приватные директории
private-directories-description = Эти папки будут заменены пустой виртуальной файловой системой (tmpfs) и их изначальное содержимое не будет доступно игре
path = Путь
shared-directories = Общие директории
shared-directories-description = Эти директории будут доступны для игры
original-path = Изначальный путь
new-path = Путь в песочнице
read-only = Только для чтения
read-only-description = Запретить игре изменять содержимое этой директории

View file

@ -69,6 +69,7 @@ registry-editor = Registry editor
explorer = Explorer explorer = Explorer
task-manager = Task manager task-manager = Task manager
configuration = Configuration configuration = Configuration
debugger = Debugger
dxvk-version = DXVK versiyonu dxvk-version = DXVK versiyonu
dxvk-selection-disabled = DXVK özelliği Wine grup tercihleriniz yüzünden devre dışı dxvk-selection-disabled = DXVK özelliği Wine grup tercihleriniz yüzünden devre dışı

View file

@ -0,0 +1,24 @@
sandbox = Sandbox
sandbox-description = Run the game in isolated environment, preventing it from accessing your personal data
enable-sandboxing = Enable sandboxing
enable-sandboxing-description = Run the game in read-only copy of your root filesystem
hide-home-directory = Hide home directory
hide-home-directory-description = Isolate your /home, /var/home/$USER, and $HOME folders from the game
hostname = Hostname
private-directories = Private directories
private-directories-description = These folders will be replaced by an empty virtual filesystem (tmpfs), and their original content will not be available to sandboxed game
path = Path
shared-directories = Shared directories
shared-directories-description = These directories will be symlinked to directories in your host system
original-path = Original path
new-path = New path
read-only = Read-only
read-only-description = Forbid game to write any data to this directory

View file

@ -6,7 +6,7 @@ debug-file-opening-error = 打开调试文件失败
wish-url-search-failed = 找不到祈愿 URL wish-url-search-failed = 找不到祈愿 URL
wish-url-opening-error = 无法转到祈愿 URL wish-url-opening-error = 无法转到祈愿 URL
wine-run-error = Failed to run {$executable} executable using wine wine-run-error = 使用 wine 运行 {$executable} 失败
game-launching-failed = 启动游戏失败 game-launching-failed = 启动游戏失败
failed-get-selected-wine = 选择 Wine 版本失败 failed-get-selected-wine = 选择 Wine 版本失败

View file

@ -36,14 +36,14 @@ show-all-folders-subtitle = 显示额外的路径选项。按我说的做...
runners-folder = 运行程序文件夹 runners-folder = 运行程序文件夹
dxvks-folder = DXVK 文件夹 dxvks-folder = DXVK 文件夹
wine-prefix-folder = Wine prefix 文件夹 wine-prefix-folder = Wine prefix 文件夹
global-game-installation-folder = Global game version installation folder global-game-installation-folder = 国际服安装目录
chinese-game-installation-folder = Chinese game version installation folder chinese-game-installation-folder = 国服安装目录
fps-unlocker-folder = FPS Unlocker 文件夹 fps-unlocker-folder = FPS Unlocker 文件夹
components-index = 成分指数 components-index = 成分指数
patch-folder = 补丁文件夹 patch-folder = 补丁文件夹
temp-folder = 临时文件夹 temp-folder = 临时文件夹
migrate = Migrate migrate = 迁移
select-voice-packages = 选择语音包 select-voice-packages = 选择语音包

View file

@ -7,22 +7,22 @@ update-background-description = 下载官方启动器背景图片。你可以关
launcher-language = 启动器语言 launcher-language = 启动器语言
launcher-language-description = 重启后生效 launcher-language-description = 重启后生效
game-edition = Game edition game-edition = 游戏版本
global = Global global = 国际服
china = China china = 国服
game-environment = Game environment game-environment = 游戏环境
game-environment-description = Get specific features like additional payment methods game-environment-description = 获取特定功能,如其他付款方式
game-voiceovers = 游戏语音 game-voiceovers = 游戏语音
game-voiceovers-description = List of downloaded game voiceovers. You can select them in the game settings game-voiceovers-description = 已下载的游戏语音,可以在游戏设置中更换
english = 英语 english = 英语
japanese = 日语 japanese = 日语
korean = 韩语 korean = 韩语
chinese = 汉语 chinese = 汉语
migrate-installation = Migrate installation migrate-installation = 迁移安装
migrate-installation-description = Open special window where you can change your game installation folder migrate-installation-description = 打开此窗口以改变游戏安装文件夹
repair-game = 修复游戏 repair-game = 修复游戏
status = 状态 status = 状态
@ -63,12 +63,13 @@ recommended-only = 仅显示推荐版本
wine-version = Wine 版本 wine-version = Wine 版本
wine-recommended-description = 仅显示推荐的 Wine 版本 wine-recommended-description = 仅显示推荐的 Wine 版本
wine-tools = Wine tools wine-tools = Wine 工具
command-line = Command line command-line = 命令行
registry-editor = Registry editor registry-editor = 注册表编辑器
explorer = Explorer explorer = 资源管理器
task-manager = Task manager task-manager = 任务管理器
configuration = Configuration configuration = wine 设置
debugger = 调试器
dxvk-version = DXVK 版本 dxvk-version = DXVK 版本
dxvk-selection-disabled = 您的葡萄酒组首选项禁用 DXVK 选择 dxvk-selection-disabled = 您的葡萄酒组首选项禁用 DXVK 选择

View file

@ -40,17 +40,17 @@ downloading = 正在下载
unpacking = 正在解压缩 unpacking = 正在解压缩
verifying-files = 正在检验文件 verifying-files = 正在检验文件
repairing-files = 正在修复文件 repairing-files = 正在修复文件
migrating-folders = Migrating folders migrating-folders = 正在迁移目录
applying-hdiff = Applying hdiff patches applying-hdiff = 正在应用 hdiff 补丁
removing-outdated = Removing outdated files removing-outdated = 删除过期的文件
components-index-updated = 组件索引已更新 components-index-updated = 组件索引已更新
launch = 启动 launch = 启动
migrate-folders = Migrate folders migrate-folders = 迁移目录
migrate-folders-tooltip = Update game folders structure migrate-folders-tooltip = 更新游戏目录结构
apply-patch = 安装补丁 apply-patch = 安装补丁
download-wine = 下载 Wine download-wine = 下载 Wine
create-prefix = 创建 Wine prefix create-prefix = 创建 Wine prefix

View file

@ -0,0 +1,24 @@
sandbox = 沙盒
sandbox-description = 在隔离环境中运行游戏,阻止其对个人数据的访问
enable-sandboxing = 启用沙盒
enable-sandboxing-description = 在根文件系统的只读副本中运行游戏
hide-home-directory = 隐藏家目录
hide-home-directory-description = 将 /home、 /var/home/$USER 和 $HOME 目录与游戏隔离
hostname = 主机名
private-directories = 隐私目录
private-directories-description = 这些目录将会被空的虚拟文件系统tmpfs替代其中的原始内容不可被沙盒中的游戏访问
path = 路径
shared-directories = 共享目录
shared-directories-description = 这些目录将会被软链接到主机系统上的目录
original-path = 原路径
new-path = 新路径
read-only = 只读
read-only-description = 禁止游戏向此目录写入任何数据

View file

@ -1,8 +1,10 @@
use relm4::prelude::*; use relm4::prelude::*;
use anime_launcher_sdk::config; use anime_launcher_sdk::config::ConfigExt;
use anime_launcher_sdk::states::LauncherState; use anime_launcher_sdk::genshin::config::{Config, Schema};
use anime_launcher_sdk::consts::launcher_dir;
use anime_launcher_sdk::genshin::states::LauncherState;
use anime_launcher_sdk::genshin::consts::launcher_dir;
use anime_launcher_sdk::anime_game_core::prelude::*; use anime_launcher_sdk::anime_game_core::prelude::*;
use anime_launcher_sdk::anime_game_core::genshin::prelude::*; use anime_launcher_sdk::anime_game_core::genshin::prelude::*;
@ -34,9 +36,9 @@ pub fn is_ready() -> bool {
} }
lazy_static::lazy_static! { lazy_static::lazy_static! {
/// Config loaded on the app's start. Use `config::get()` to get up to date config instead. /// Config loaded on the app's start. Use `Config::get()` to get up to date config instead.
/// This one is used to prepare some launcher UI components on start /// This one is used to prepare some launcher UI components on start
pub static ref CONFIG: config::Config = config::get().expect("Failed to load config"); pub static ref CONFIG: Schema = Config::get().expect("Failed to load config");
pub static ref GAME: Game = Game::new(CONFIG.game.path.for_edition(CONFIG.launcher.edition)); pub static ref GAME: Game = Game::new(CONFIG.game.path.for_edition(CONFIG.launcher.edition));
@ -69,11 +71,11 @@ fn main() {
std::fs::write(FIRST_RUN_FILE.as_path(), "").expect("Failed to create .first-run file"); std::fs::write(FIRST_RUN_FILE.as_path(), "").expect("Failed to create .first-run file");
// Set initial launcher language based on system language // Set initial launcher language based on system language
let mut config = config::get().expect("Failed to get config"); let mut config = Config::get().expect("Failed to get config");
config.launcher.language = i18n::format_lang(&i18n::get_default_lang()); config.launcher.language = i18n::format_lang(&i18n::get_default_lang());
config::update_raw(config).expect("Failed to update config"); Config::update_raw(config).expect("Failed to update config");
} }
// Force debug output // Force debug output
@ -161,7 +163,7 @@ fn main() {
", BACKGROUND_FILE.to_string_lossy())); ", BACKGROUND_FILE.to_string_lossy()));
// Set game edition // Set game edition
GameEdition::from(CONFIG.launcher.edition).select(); CONFIG.launcher.edition.select();
// Set UI language // Set UI language
let lang = CONFIG.launcher.language.parse().expect("Wrong language format used in config"); let lang = CONFIG.launcher.language.parse().expect("Wrong language format used in config");
@ -187,7 +189,7 @@ fn main() {
match state { match state {
LauncherState::Launch => { LauncherState::Launch => {
anime_launcher_sdk::game::run().expect("Failed to run the game"); anime_launcher_sdk::genshin::game::run().expect("Failed to run the game");
return; return;
} }
@ -196,7 +198,7 @@ fn main() {
LauncherState::UnityPlayerPatchAvailable(UnityPlayerPatch { status: PatchStatus::NotAvailable, .. }) | LauncherState::UnityPlayerPatchAvailable(UnityPlayerPatch { status: PatchStatus::NotAvailable, .. }) |
LauncherState::XluaPatchAvailable(XluaPatch { status: PatchStatus::NotAvailable, .. }) => { LauncherState::XluaPatchAvailable(XluaPatch { status: PatchStatus::NotAvailable, .. }) => {
if just_run_game { if just_run_game {
anime_launcher_sdk::game::run().expect("Failed to run the game"); anime_launcher_sdk::genshin::game::run().expect("Failed to run the game");
return; return;
} }

View file

@ -46,6 +46,10 @@ impl SimpleComponent for AboutDialog {
"Nikita Podvirnyy https://github.com/krypt0nn" "Nikita Podvirnyy https://github.com/krypt0nn"
], ],
add_credit_section: (Some("Patch developer"), &[
"@Krock https://notabug.org/Krock/dawn"
]),
add_credit_section: (Some("An Anime Team"), &[ add_credit_section: (Some("An Anime Team"), &[
"@Marie https://github.com/Mar0xy", "@Marie https://github.com/Mar0xy",
"@lane https://github.com/laurinneff", "@lane https://github.com/laurinneff",
@ -78,17 +82,18 @@ impl SimpleComponent for AboutDialog {
set_release_notes_version: &APP_VERSION, set_release_notes_version: &APP_VERSION,
set_release_notes: &[ set_release_notes: &[
"<p>Fixed</p>", "<p>Added</p>",
"<ul>", "<ul>",
"<li>Fixed base game's hdiff patches applying errors caused by 3.6's voiceovers files migration</li>", "<li>Added game sandboxing feature</li>",
"<li>Fixed xlua patch applying</li>", "<li>Added debugger to wine tools</li>",
"</ul>", "</ul>",
"<p>Changed</p>", "<p>Changed</p>",
"<ul>", "<ul>",
"<li>Removed fractions displaying in repairer's progress bar</li>", "<li>Removed fractions displaying in components downloading progress bar</li>",
"<li>Moved to upgraded launcher SDK</li>",
"</ul>", "</ul>",
].join("\n"), ].join("\n"),

View file

@ -6,13 +6,15 @@ use adw::prelude::*;
use gtk::glib::clone; use gtk::glib::clone;
use super::progress_bar::ProgressBarMsg;
use anime_launcher_sdk::config;
use anime_launcher_sdk::anime_game_core::prelude::*; use anime_launcher_sdk::anime_game_core::prelude::*;
use anime_launcher_sdk::config::ConfigExt;
use anime_launcher_sdk::genshin::config::Config;
use std::path::PathBuf; use std::path::PathBuf;
use super::progress_bar::ProgressBarMsg;
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum VersionState { pub enum VersionState {
Downloaded, Downloaded,
@ -142,7 +144,7 @@ impl SimpleAsyncComponent for ComponentVersion {
} }
VersionState::NotDownloaded => { VersionState::NotDownloaded => {
if let Ok(config) = config::get() { if let Ok(config) = Config::get() {
// todo // todo
let mut installer = Installer::new(&self.download_uri) let mut installer = Installer::new(&self.download_uri)
.expect("Failed to create installer instance for this version"); .expect("Failed to create installer instance for this version");

View file

@ -3,8 +3,6 @@ use relm4::component::*;
use adw::prelude::*; use adw::prelude::*;
use anime_launcher_sdk::config;
use std::path::PathBuf; use std::path::PathBuf;
use crate::*; use crate::*;
@ -360,7 +358,7 @@ impl SimpleAsyncComponent for DefaultPathsApp {
#[allow(unused_must_use)] #[allow(unused_must_use)]
DefaultPathsAppMsg::Continue => { DefaultPathsAppMsg::Continue => {
let old_config = config::get().unwrap_or_else(|_| CONFIG.clone()); let old_config = Config::get().unwrap_or_else(|_| CONFIG.clone());
match self.update_config() { match self.update_config() {
Ok(_) => { Ok(_) => {
@ -431,7 +429,7 @@ impl SimpleAsyncComponent for DefaultPathsApp {
impl DefaultPathsApp { impl DefaultPathsApp {
pub fn update_config(&self) -> anyhow::Result<()> { pub fn update_config(&self) -> anyhow::Result<()> {
let mut config = config::get()?; let mut config = Config::get()?;
config.game.wine.builds = self.runners.clone(); config.game.wine.builds = self.runners.clone();
config.game.dxvk.builds = self.dxvks.clone(); config.game.dxvk.builds = self.dxvks.clone();
@ -444,6 +442,6 @@ impl DefaultPathsApp {
config.game.enhancements.fps_unlocker.path = self.fps_unlocker.clone(); config.game.enhancements.fps_unlocker.path = self.fps_unlocker.clone();
config::update_raw(config) Config::update_raw(config)
} }
} }

View file

@ -4,10 +4,13 @@ use relm4::component::*;
use adw::prelude::*; use adw::prelude::*;
use anime_launcher_sdk::anime_game_core::prelude::*; use anime_launcher_sdk::anime_game_core::prelude::*;
use anime_launcher_sdk::wincompatlib::prelude::*;
use anime_launcher_sdk::components::*; use anime_launcher_sdk::components::*;
use anime_launcher_sdk::components::wine::WincompatlibWine; use anime_launcher_sdk::components::wine::WincompatlibWine;
use anime_launcher_sdk::config;
use anime_launcher_sdk::wincompatlib::prelude::*; use anime_launcher_sdk::config::ConfigExt;
use anime_launcher_sdk::genshin::config::Config;
use std::path::PathBuf; use std::path::PathBuf;
@ -312,7 +315,7 @@ impl SimpleAsyncComponent for DownloadComponentsApp {
async fn update(&mut self, msg: Self::Input, sender: AsyncComponentSender<Self>) { async fn update(&mut self, msg: Self::Input, sender: AsyncComponentSender<Self>) {
match msg { match msg {
DownloadComponentsAppMsg::UpdateVersionsLists => { DownloadComponentsAppMsg::UpdateVersionsLists => {
let config = config::get().unwrap_or_else(|_| CONFIG.clone()); let config = Config::get().unwrap_or_else(|_| CONFIG.clone());
// 4 latest versions of 4 first available wine group // 4 latest versions of 4 first available wine group
self.wine_versions = wine::get_groups(&config.components.path).unwrap() self.wine_versions = wine::get_groups(&config.components.path).unwrap()
@ -331,7 +334,7 @@ impl SimpleAsyncComponent for DownloadComponentsApp {
#[allow(unused_must_use)] #[allow(unused_must_use)]
DownloadComponentsAppMsg::DownloadWine => { DownloadComponentsAppMsg::DownloadWine => {
let config = config::get().unwrap_or_else(|_| CONFIG.clone()); let config = Config::get().unwrap_or_else(|_| CONFIG.clone());
self.selected_wine = Some(self.wine_versions[self.wine_combo.selected() as usize].clone()); self.selected_wine = Some(self.wine_versions[self.wine_combo.selected() as usize].clone());
self.selected_dxvk = Some(self.dxvk_versions[self.dxvk_combo.selected() as usize].clone()); self.selected_dxvk = Some(self.dxvk_versions[self.dxvk_combo.selected() as usize].clone());
@ -350,11 +353,11 @@ impl SimpleAsyncComponent for DownloadComponentsApp {
if wine.is_downloaded_in(&config.game.wine.builds) { if wine.is_downloaded_in(&config.game.wine.builds) {
tracing::info!("Wine already installed: {}", wine.name); tracing::info!("Wine already installed: {}", wine.name);
let mut config = config::get().unwrap_or_else(|_| CONFIG.clone()); let mut config = Config::get().unwrap_or_else(|_| CONFIG.clone());
config.game.wine.selected = Some(wine.name); config.game.wine.selected = Some(wine.name);
if let Err(err) = config::update_raw(config) { if let Err(err) = Config::update_raw(config) {
tracing::error!("Failed to update config: {err}"); tracing::error!("Failed to update config: {err}");
sender.output(Self::Output::Toast { sender.output(Self::Output::Toast {
@ -402,11 +405,11 @@ impl SimpleAsyncComponent for DownloadComponentsApp {
// Create prefix // Create prefix
InstallerUpdate::UnpackingFinished => { InstallerUpdate::UnpackingFinished => {
let mut config = config::get().unwrap_or_else(|_| CONFIG.clone()); let mut config = Config::get().unwrap_or_else(|_| CONFIG.clone());
config.game.wine.selected = Some(wine.name.clone()); config.game.wine.selected = Some(wine.name.clone());
if let Err(err) = config::update_raw(config) { if let Err(err) = Config::update_raw(config) {
tracing::error!("Failed to update config: {err}"); tracing::error!("Failed to update config: {err}");
sender.output(Self::Output::Toast { sender.output(Self::Output::Toast {
@ -443,7 +446,7 @@ impl SimpleAsyncComponent for DownloadComponentsApp {
self.downloading_wine = Some(true); self.downloading_wine = Some(true);
self.creating_prefix = Some(false); self.creating_prefix = Some(false);
let config = config::get().unwrap_or_else(|_| CONFIG.clone()); let config = Config::get().unwrap_or_else(|_| CONFIG.clone());
tracing::info!("Creating wine prefix"); tracing::info!("Creating wine prefix");
@ -477,7 +480,7 @@ impl SimpleAsyncComponent for DownloadComponentsApp {
self.creating_prefix = Some(true); self.creating_prefix = Some(true);
self.downloading_dxvk = Some(false); self.downloading_dxvk = Some(false);
let config = config::get().unwrap_or_else(|_| CONFIG.clone()); let config = Config::get().unwrap_or_else(|_| CONFIG.clone());
let dxvk = self.selected_dxvk.clone().unwrap(); let dxvk = self.selected_dxvk.clone().unwrap();
let progress_bar_input = self.progress_bar.sender().clone(); let progress_bar_input = self.progress_bar.sender().clone();
@ -554,7 +557,7 @@ impl SimpleAsyncComponent for DownloadComponentsApp {
self.downloading_dxvk = Some(true); self.downloading_dxvk = Some(true);
self.applying_dxvk = Some(false); self.applying_dxvk = Some(false);
let config = config::get().unwrap_or_else(|_| CONFIG.clone()); let config = Config::get().unwrap_or_else(|_| CONFIG.clone());
tracing::info!("Applying DXVK"); tracing::info!("Applying DXVK");

View file

@ -219,7 +219,7 @@ impl SimpleComponent for FirstRunApp {
// Update components index // Update components index
sender.input(FirstRunAppMsg::SetLoadingStatus(Some(Some(tr("updating-components-index"))))); sender.input(FirstRunAppMsg::SetLoadingStatus(Some(Some(tr("updating-components-index")))));
let config = config::get().unwrap_or_else(|_| CONFIG.clone()); let config = Config::get().unwrap_or_else(|_| CONFIG.clone());
let components_sender = self.download_components.sender().clone(); let components_sender = self.download_components.sender().clone();
let components = ComponentsLoader::new(config.components.path); let components = ComponentsLoader::new(config.components.path);

View file

@ -3,7 +3,8 @@ use relm4::component::*;
use adw::prelude::*; use adw::prelude::*;
use anime_launcher_sdk::config; use anime_launcher_sdk::config::ConfigExt;
use anime_launcher_sdk::genshin::config::Config;
use crate::i18n::*; use crate::i18n::*;
use super::main::*; use super::main::*;
@ -153,7 +154,7 @@ impl SimpleAsyncComponent for SelectVoiceoversApp {
impl SelectVoiceoversApp { impl SelectVoiceoversApp {
pub fn update_config(&self) -> anyhow::Result<()> { pub fn update_config(&self) -> anyhow::Result<()> {
let mut config = config::get()?; let mut config = Config::get()?;
config.game.voices = Vec::new(); config.game.voices = Vec::new();
@ -173,6 +174,6 @@ impl SelectVoiceoversApp {
config.game.voices.push(String::from("zh-cn")); config.game.voices.push(String::from("zh-cn"));
} }
config::update_raw(config) Config::update_raw(config)
} }
} }

View file

@ -1,7 +1,5 @@
use relm4::prelude::*; use relm4::prelude::*;
use anime_launcher_sdk::config;
use crate::*; use crate::*;
use crate::i18n::*; use crate::i18n::*;
use super::{App, AppMsg}; use super::{App, AppMsg};
@ -16,7 +14,7 @@ pub fn apply_patch<T: PatchExt + Send + Sync + 'static>(sender: ComponentSender<
PatchStatus::Available { .. } => { PatchStatus::Available { .. } => {
sender.input(AppMsg::DisableButtons(true)); sender.input(AppMsg::DisableButtons(true));
let config = config::get().unwrap(); let config = Config::get().unwrap();
std::thread::spawn(move || { std::thread::spawn(move || {
let mut apply_patch_if_needed = true; let mut apply_patch_if_needed = true;

View file

@ -1,13 +1,15 @@
use relm4::prelude::*; use relm4::prelude::*;
use anime_launcher_sdk::config;
use anime_launcher_sdk::wincompatlib::prelude::*; use anime_launcher_sdk::wincompatlib::prelude::*;
use anime_launcher_sdk::config::ConfigExt;
use anime_launcher_sdk::genshin::config::Config;
use crate::i18n::*; use crate::i18n::*;
use super::{App, AppMsg}; use super::{App, AppMsg};
pub fn create_prefix(sender: ComponentSender<App>) { pub fn create_prefix(sender: ComponentSender<App>) {
let config = config::get().unwrap(); let config = Config::get().unwrap();
match config.get_selected_wine() { match config.get_selected_wine() {
Ok(Some(wine)) => { Ok(Some(wine)) => {

View file

@ -5,7 +5,6 @@ use relm4::{
use gtk::glib::clone; use gtk::glib::clone;
use anime_launcher_sdk::config;
use anime_launcher_sdk::anime_game_core::installer::diff::VersionDiff; use anime_launcher_sdk::anime_game_core::installer::diff::VersionDiff;
use crate::*; use crate::*;
@ -17,7 +16,7 @@ pub fn download_diff(sender: ComponentSender<App>, progress_bar_input: Sender<Pr
sender.input(AppMsg::SetDownloading(true)); sender.input(AppMsg::SetDownloading(true));
std::thread::spawn(move || { std::thread::spawn(move || {
let config = config::get().unwrap(); let config = Config::get().unwrap();
let game_path = config.game.path.for_edition(config.launcher.edition).to_path_buf(); let game_path = config.game.path.for_edition(config.launcher.edition).to_path_buf();
let result = diff.install_to_by(game_path, config.launcher.temp, clone!(@strong sender => move |state| { let result = diff.install_to_by(game_path, config.launcher.temp, clone!(@strong sender => move |state| {

View file

@ -5,7 +5,6 @@ use relm4::{
use gtk::glib::clone; use gtk::glib::clone;
use anime_launcher_sdk::config;
use anime_launcher_sdk::components::wine; use anime_launcher_sdk::components::wine;
use crate::*; use crate::*;
@ -14,7 +13,7 @@ use crate::ui::components::*;
use super::{App, AppMsg}; use super::{App, AppMsg};
pub fn download_wine(sender: ComponentSender<App>, progress_bar_input: Sender<ProgressBarMsg>) { pub fn download_wine(sender: ComponentSender<App>, progress_bar_input: Sender<ProgressBarMsg>) {
let mut config = config::get().unwrap(); let mut config = Config::get().unwrap();
match wine::get_downloaded(&CONFIG.components.path, &config.game.wine.builds) { match wine::get_downloaded(&CONFIG.components.path, &config.game.wine.builds) {
Ok(downloaded) => { Ok(downloaded) => {
@ -22,7 +21,7 @@ pub fn download_wine(sender: ComponentSender<App>, progress_bar_input: Sender<Pr
if !downloaded.is_empty() { if !downloaded.is_empty() {
config.game.wine.selected = Some(downloaded[0].versions[0].name.clone()); config.game.wine.selected = Some(downloaded[0].versions[0].name.clone());
config::update(config); Config::update(config);
sender.input(AppMsg::UpdateLauncherState { sender.input(AppMsg::UpdateLauncherState {
perform_on_download_needed: false, perform_on_download_needed: false,
@ -85,7 +84,7 @@ pub fn download_wine(sender: ComponentSender<App>, progress_bar_input: Sender<Pr
config.game.wine.selected = Some(wine.name.clone()); config.game.wine.selected = Some(wine.name.clone());
config::update(config); Config::update(config);
sender.input(AppMsg::SetDownloading(false)); sender.input(AppMsg::SetDownloading(false));
sender.input(AppMsg::UpdateLauncherState { sender.input(AppMsg::UpdateLauncherState {

View file

@ -7,7 +7,7 @@ pub fn launch(sender: ComponentSender<App>) {
sender.input(AppMsg::HideWindow); sender.input(AppMsg::HideWindow);
std::thread::spawn(move || { std::thread::spawn(move || {
if let Err(err) = anime_launcher_sdk::game::run() { if let Err(err) = anime_launcher_sdk::genshin::game::run() {
tracing::error!("Failed to launch game: {err}"); tracing::error!("Failed to launch game: {err}");
sender.input(AppMsg::Toast { sender.input(AppMsg::Toast {

View file

@ -18,10 +18,15 @@ mod download_diff;
mod migrate_folder; mod migrate_folder;
mod launch; mod launch;
use anime_launcher_sdk::config::launcher::LauncherStyle;
use anime_launcher_sdk::states::LauncherState;
use anime_launcher_sdk::components::loader::ComponentsLoader; use anime_launcher_sdk::components::loader::ComponentsLoader;
use anime_launcher_sdk::anime_game_core::genshin::consts::GameEdition;
use anime_launcher_sdk::config::ConfigExt;
use anime_launcher_sdk::genshin::config::Config;
use anime_launcher_sdk::genshin::config::schema::launcher::LauncherStyle;
use anime_launcher_sdk::genshin::states::*;
use anime_launcher_sdk::genshin::consts::*;
use crate::*; use crate::*;
use crate::i18n::*; use crate::i18n::*;
@ -328,7 +333,7 @@ impl SimpleComponent for App {
#[watch] #[watch]
set_sensitive: match model.state.as_ref() { set_sensitive: match model.state.as_ref() {
Some(LauncherState::PredownloadAvailable { game, voices }) => { Some(LauncherState::PredownloadAvailable { game, voices }) => {
let config = config::get().unwrap(); let config = Config::get().unwrap();
let temp = config.launcher.temp.unwrap_or_else(std::env::temp_dir); let temp = config.launcher.temp.unwrap_or_else(std::env::temp_dir);
let downloaded = temp.join(game.file_name().unwrap()).exists() && let downloaded = temp.join(game.file_name().unwrap()).exists() &&
@ -343,7 +348,7 @@ impl SimpleComponent for App {
#[watch] #[watch]
set_css_classes: match model.state.as_ref() { set_css_classes: match model.state.as_ref() {
Some(LauncherState::PredownloadAvailable { game, voices }) => { Some(LauncherState::PredownloadAvailable { game, voices }) => {
let config = config::get().unwrap(); let config = Config::get().unwrap();
let temp = config.launcher.temp.unwrap_or_else(std::env::temp_dir); let temp = config.launcher.temp.unwrap_or_else(std::env::temp_dir);
let downloaded = temp.join(game.file_name().unwrap()).exists() && let downloaded = temp.join(game.file_name().unwrap()).exists() &&
@ -481,7 +486,7 @@ impl SimpleComponent for App {
}, },
connect_close_request[sender] => move |_| { connect_close_request[sender] => move |_| {
if let Err(err) = config::flush() { if let Err(err) = Config::flush() {
sender.input(AppMsg::Toast { sender.input(AppMsg::Toast {
title: tr("config-update-error"), title: tr("config-update-error"),
description: Some(err.to_string()) description: Some(err.to_string())
@ -558,7 +563,7 @@ impl SimpleComponent for App {
}))); })));
group.add_action::<GameFolder>(&RelmAction::new_stateless(clone!(@strong sender => move |_| { group.add_action::<GameFolder>(&RelmAction::new_stateless(clone!(@strong sender => move |_| {
let path = match config::get() { let path = match Config::get() {
Ok(config) => config.game.path.for_edition(config.launcher.edition).to_path_buf(), Ok(config) => config.game.path.for_edition(config.launcher.edition).to_path_buf(),
Err(_) => CONFIG.game.path.for_edition(CONFIG.launcher.edition).to_path_buf(), Err(_) => CONFIG.game.path.for_edition(CONFIG.launcher.edition).to_path_buf(),
}; };
@ -574,7 +579,7 @@ impl SimpleComponent for App {
}))); })));
group.add_action::<ConfigFile>(&RelmAction::new_stateless(clone!(@strong sender => move |_| { group.add_action::<ConfigFile>(&RelmAction::new_stateless(clone!(@strong sender => move |_| {
if let Ok(file) = anime_launcher_sdk::consts::config_file() { if let Ok(file) = config_file() {
if let Err(err) = open::that(file) { if let Err(err) = open::that(file) {
sender.input(AppMsg::Toast { sender.input(AppMsg::Toast {
title: tr("config-file-opening-error"), title: tr("config-file-opening-error"),
@ -599,10 +604,10 @@ impl SimpleComponent for App {
group.add_action::<WishUrl>(&RelmAction::new_stateless(clone!(@strong sender => move |_| { group.add_action::<WishUrl>(&RelmAction::new_stateless(clone!(@strong sender => move |_| {
std::thread::spawn(clone!(@strong sender => move || { std::thread::spawn(clone!(@strong sender => move || {
let config = config::get().unwrap_or_else(|_| CONFIG.clone()); let config = Config::get().unwrap_or_else(|_| CONFIG.clone());
let web_cache = config.game.path.for_edition(config.launcher.edition) let web_cache = config.game.path.for_edition(config.launcher.edition)
.join(GameEdition::from(config.launcher.edition).data_folder()) .join(config.launcher.edition.data_folder())
.join("webCaches/Cache/Cache_Data/data_2"); .join("webCaches/Cache/Cache_Data/data_2");
if !web_cache.exists() { if !web_cache.exists() {
@ -860,8 +865,6 @@ impl SimpleComponent for App {
} }
let updater = clone!(@strong sender => move |state| { let updater = clone!(@strong sender => move |state| {
use anime_launcher_sdk::states::StateUpdating;
if show_status_page { if show_status_page {
match state { match state {
StateUpdating::Game => { StateUpdating::Game => {
@ -963,7 +966,7 @@ impl SimpleComponent for App {
#[allow(unused_must_use)] #[allow(unused_must_use)]
AppMsg::PredownloadUpdate => { AppMsg::PredownloadUpdate => {
if let Some(LauncherState::PredownloadAvailable { game, mut voices }) = self.state.clone() { if let Some(LauncherState::PredownloadAvailable { game, mut voices }) = self.state.clone() {
let tmp = config::get().unwrap().launcher.temp.unwrap_or_else(std::env::temp_dir); let tmp = Config::get().unwrap().launcher.temp.unwrap_or_else(std::env::temp_dir);
self.downloading = true; self.downloading = true;

View file

@ -7,8 +7,6 @@ use gtk::glib::clone;
use std::path::Path; use std::path::Path;
use anime_launcher_sdk::config;
use crate::*; use crate::*;
use crate::i18n::*; use crate::i18n::*;
use crate::ui::components::*; use crate::ui::components::*;
@ -16,7 +14,7 @@ use super::{App, AppMsg};
#[allow(unused_must_use)] #[allow(unused_must_use)]
pub fn repair_game(sender: ComponentSender<App>, progress_bar_input: Sender<ProgressBarMsg>) { pub fn repair_game(sender: ComponentSender<App>, progress_bar_input: Sender<ProgressBarMsg>) {
let config = config::get().unwrap(); let config = Config::get().unwrap();
progress_bar_input.send(ProgressBarMsg::UpdateCaption(Some(tr("verifying-files")))); progress_bar_input.send(ProgressBarMsg::UpdateCaption(Some(tr("verifying-files"))));
sender.input(AppMsg::SetDownloading(true)); sender.input(AppMsg::SetDownloading(true));

View file

@ -3,8 +3,11 @@ use relm4::component::*;
use adw::prelude::*; use adw::prelude::*;
use anime_launcher_sdk::config; use anime_launcher_sdk::config::ConfigExt;
use anime_launcher_sdk::config::prelude::*; use anime_launcher_sdk::genshin::config::Config;
use anime_launcher_sdk::config::schema_blanks::prelude::*;
use anime_launcher_sdk::is_available; use anime_launcher_sdk::is_available;
use crate::i18n::tr; use crate::i18n::tr;
@ -52,10 +55,10 @@ impl SimpleAsyncComponent for EnhancementsApp {
connect_selected_notify => |row| unsafe { connect_selected_notify => |row| unsafe {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.game.wine.sync = WineSync::from_ordinal_unsafe(row.selected() as i8); config.game.wine.sync = WineSync::from_ordinal_unsafe(row.selected() as i8);
config::update(config); Config::update(config);
} }
} }
} }
@ -84,10 +87,10 @@ impl SimpleAsyncComponent for EnhancementsApp {
connect_selected_notify => |row| unsafe { connect_selected_notify => |row| unsafe {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.game.wine.language = WineLang::from_ordinal_unsafe(row.selected() as i8); config.game.wine.language = WineLang::from_ordinal_unsafe(row.selected() as i8);
config::update(config); Config::update(config);
} }
} }
} }
@ -103,10 +106,10 @@ impl SimpleAsyncComponent for EnhancementsApp {
connect_state_notify => |switch| { connect_state_notify => |switch| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.game.wine.borderless = switch.state(); config.game.wine.borderless = switch.state();
config::update(config); Config::update(config);
} }
} }
} }
@ -130,13 +133,13 @@ impl SimpleAsyncComponent for EnhancementsApp {
connect_selected_notify => |row| { connect_selected_notify => |row| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
let (width, height) = Resolution::try_from(row.selected()).unwrap().get_pair(); let (width, height) = Resolution::try_from(row.selected()).unwrap().get_pair();
config.game.wine.virtual_desktop.width = width; config.game.wine.virtual_desktop.width = width;
config.game.wine.virtual_desktop.height = height; config.game.wine.virtual_desktop.height = height;
config::update(config); Config::update(config);
} }
} }
}, },
@ -148,10 +151,10 @@ impl SimpleAsyncComponent for EnhancementsApp {
connect_state_notify => |switch| { connect_state_notify => |switch| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.game.wine.virtual_desktop.enabled = switch.state(); config.game.wine.virtual_desktop.enabled = switch.state();
config::update(config); Config::update(config);
} }
} }
} }
@ -176,10 +179,10 @@ impl SimpleAsyncComponent for EnhancementsApp {
connect_selected_notify => |row| unsafe { connect_selected_notify => |row| unsafe {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.game.enhancements.hud = HUD::from_ordinal_unsafe(row.selected() as i8); config.game.enhancements.hud = HUD::from_ordinal_unsafe(row.selected() as i8);
config::update(config); Config::update(config);
} }
} }
} }
@ -209,10 +212,10 @@ impl SimpleAsyncComponent for EnhancementsApp {
connect_selected_notify => |row| { connect_selected_notify => |row| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.game.enhancements.fsr.strength = 5 - row.selected() as u64; config.game.enhancements.fsr.strength = 5 - row.selected() as u64;
config::update(config); Config::update(config);
} }
} }
}, },
@ -224,10 +227,10 @@ impl SimpleAsyncComponent for EnhancementsApp {
connect_state_notify => |switch| { connect_state_notify => |switch| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.game.enhancements.fsr.enabled = switch.state(); config.game.enhancements.fsr.enabled = switch.state();
config::update(config); Config::update(config);
} }
} }
} }
@ -247,10 +250,10 @@ impl SimpleAsyncComponent for EnhancementsApp {
connect_state_notify => |switch| { connect_state_notify => |switch| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.game.enhancements.gamemode = switch.state(); config.game.enhancements.gamemode = switch.state();
config::update(config); Config::update(config);
} }
} }
} }
@ -279,10 +282,10 @@ impl SimpleAsyncComponent for EnhancementsApp {
connect_state_notify => |switch| { connect_state_notify => |switch| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.game.enhancements.gamescope.enabled = switch.state(); config.game.enhancements.gamescope.enabled = switch.state();
config::update(config); Config::update(config);
} }
} }
} }
@ -303,10 +306,10 @@ impl SimpleAsyncComponent for EnhancementsApp {
connect_state_notify => |switch| { connect_state_notify => |switch| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.launcher.discord_rpc.enabled = switch.state(); config.launcher.discord_rpc.enabled = switch.state();
config::update(config); Config::update(config);
} }
} }
} }
@ -319,10 +322,10 @@ impl SimpleAsyncComponent for EnhancementsApp {
connect_changed: |row| { connect_changed: |row| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.launcher.discord_rpc.title = row.text().to_string(); config.launcher.discord_rpc.title = row.text().to_string();
config::update(config); Config::update(config);
} }
} }
} }
@ -334,10 +337,10 @@ impl SimpleAsyncComponent for EnhancementsApp {
connect_changed: |row| { connect_changed: |row| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.launcher.discord_rpc.subtitle = row.text().to_string(); config.launcher.discord_rpc.subtitle = row.text().to_string();
config::update(config); Config::update(config);
} }
} }
} }
@ -377,10 +380,10 @@ impl SimpleAsyncComponent for EnhancementsApp {
connect_selected_notify => |row| { connect_selected_notify => |row| {
if is_ready() && row.selected() < Fps::list().len() as u32 - 1 { if is_ready() && row.selected() < Fps::list().len() as u32 - 1 {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.game.enhancements.fps_unlocker.config.fps = Fps::list()[row.selected() as usize].to_num(); config.game.enhancements.fps_unlocker.config.fps = Fps::list()[row.selected() as usize].to_num();
config::update(config); Config::update(config);
} }
} }
}, },
@ -392,10 +395,10 @@ impl SimpleAsyncComponent for EnhancementsApp {
connect_state_notify => |switch| { connect_state_notify => |switch| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.game.enhancements.fps_unlocker.enabled = switch.state(); config.game.enhancements.fps_unlocker.enabled = switch.state();
config::update(config); Config::update(config);
} }
} }
} }
@ -413,10 +416,10 @@ impl SimpleAsyncComponent for EnhancementsApp {
connect_state_notify => |switch| { connect_state_notify => |switch| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.game.enhancements.fps_unlocker.config.power_saving = switch.state(); config.game.enhancements.fps_unlocker.config.power_saving = switch.state();
config::update(config); Config::update(config);
} }
} }
} }
@ -435,10 +438,10 @@ impl SimpleAsyncComponent for EnhancementsApp {
connect_changed => |row| { connect_changed => |row| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.game.enhancements.fps_unlocker.config.monitor = row.value() as u64; config.game.enhancements.fps_unlocker.config.monitor = row.value() as u64;
config::update(config); Config::update(config);
} }
} }
} }
@ -459,10 +462,10 @@ impl SimpleAsyncComponent for EnhancementsApp {
connect_selected_notify => |row| unsafe { connect_selected_notify => |row| unsafe {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.game.enhancements.fps_unlocker.config.window_mode = WindowMode::from_ordinal_unsafe(row.selected() as i8); config.game.enhancements.fps_unlocker.config.window_mode = WindowMode::from_ordinal_unsafe(row.selected() as i8);
config::update(config); Config::update(config);
} }
} }
} }
@ -486,10 +489,10 @@ impl SimpleAsyncComponent for EnhancementsApp {
connect_selected_notify => |row| { connect_selected_notify => |row| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.game.enhancements.fps_unlocker.config.priority = row.selected() as u64; config.game.enhancements.fps_unlocker.config.priority = row.selected() as u64;
config::update(config); Config::update(config);
} }
} }
} }

View file

@ -4,8 +4,6 @@ use relm4::factory::*;
use adw::prelude::*; use adw::prelude::*;
use anime_launcher_sdk::config;
use crate::i18n::tr; use crate::i18n::tr;
use crate::*; use crate::*;
@ -64,8 +62,8 @@ impl AsyncFactoryComponent for Variable {
pub struct EnvironmentApp { pub struct EnvironmentApp {
variables: AsyncFactoryVecDeque<Variable>, variables: AsyncFactoryVecDeque<Variable>,
name: gtk::Entry, name_entry: adw::EntryRow,
value: gtk::Entry value_entry: adw::EntryRow
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -94,16 +92,16 @@ impl SimpleAsyncComponent for EnvironmentApp {
set_text: CONFIG.game.command.as_ref().unwrap_or(&String::new()).trim(), set_text: CONFIG.game.command.as_ref().unwrap_or(&String::new()).trim(),
connect_changed => |entry| { connect_changed => |entry| {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
let command = entry.text().trim().to_string(); let command = entry.text().trim().to_string();
config.game.command = if command.is_empty() { config.game.command = if command.is_empty() {
None None
} else { } else {
Some(command) Some(command)
}; };
config::update(config); Config::update(config);
} }
} }
} }
@ -112,24 +110,19 @@ impl SimpleAsyncComponent for EnvironmentApp {
add = &adw::PreferencesGroup { add = &adw::PreferencesGroup {
set_title: &tr("new-variable"), set_title: &tr("new-variable"),
gtk::Box { #[local_ref]
set_orientation: gtk::Orientation::Horizontal, name_entry -> adw::EntryRow {
set_spacing: 8, set_title: &tr("name")
},
#[local_ref] #[local_ref]
name -> gtk::Entry { value_entry -> adw::EntryRow {
set_placeholder_text: Some(&tr("name")) set_title: &tr("value")
},
#[local_ref]
value -> gtk::Entry {
set_placeholder_text: Some(&tr("value")),
set_hexpand: true
}
}, },
gtk::Button { gtk::Button {
set_label: &tr("add"), set_label: &tr("add"),
add_css_class: "pill",
set_margin_top: 8, set_margin_top: 8,
set_halign: gtk::Align::Start, set_halign: gtk::Align::Start,
@ -153,8 +146,8 @@ impl SimpleAsyncComponent for EnvironmentApp {
let mut model = Self { let mut model = Self {
variables: AsyncFactoryVecDeque::new(adw::PreferencesGroup::new(), sender.input_sender()), variables: AsyncFactoryVecDeque::new(adw::PreferencesGroup::new(), sender.input_sender()),
name: gtk::Entry::new(), name_entry: adw::EntryRow::new(),
value: gtk::Entry::new() value_entry: adw::EntryRow::new()
}; };
for (name, value) in &CONFIG.game.environment { for (name, value) in &CONFIG.game.environment {
@ -163,8 +156,8 @@ impl SimpleAsyncComponent for EnvironmentApp {
let variables = model.variables.widget(); let variables = model.variables.widget();
let name = &model.name; let name_entry = &model.name_entry;
let value = &model.value; let value_entry = &model.value_entry;
let widgets = view_output!(); let widgets = view_output!();
@ -174,24 +167,26 @@ impl SimpleAsyncComponent for EnvironmentApp {
async fn update(&mut self, msg: Self::Input, _sender: AsyncComponentSender<Self>) { async fn update(&mut self, msg: Self::Input, _sender: AsyncComponentSender<Self>) {
match msg { match msg {
EnvironmentMsg::Add => { EnvironmentMsg::Add => {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
let name = self.name.text().trim().to_string(); let name = self.name_entry.text().trim().to_string();
let value = self.value.text().trim().to_string(); let value = self.value_entry.text().trim().to_string();
config.game.environment.insert(name.clone(), value.clone()); if !name.is_empty() && !value.is_empty() {
config.game.environment.insert(name.clone(), value.clone());
config::update(config); Config::update(config);
self.variables.guard().push_back((name, value)); self.variables.guard().push_back((name, value));
}
} }
} }
EnvironmentMsg::Remove(index) => { EnvironmentMsg::Remove(index) => {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
if let Some(var) = self.variables.guard().get(index.current_index()) { if let Some(var) = self.variables.guard().get(index.current_index()) {
config.game.environment.remove(&var.key); config.game.environment.remove(&var.key);
config::update(config); Config::update(config);
} }
self.variables.guard().remove(index.current_index()); self.variables.guard().remove(index.current_index());

View file

@ -3,8 +3,10 @@ use relm4::component::*;
use adw::prelude::*; use adw::prelude::*;
use anime_launcher_sdk::config; use anime_launcher_sdk::config::ConfigExt;
use anime_launcher_sdk::config::prelude::*; use anime_launcher_sdk::genshin::config::Config;
use anime_launcher_sdk::config::schema_blanks::prelude::*;
use crate::i18n::tr; use crate::i18n::tr;
use crate::*; use crate::*;
@ -43,10 +45,10 @@ impl SimpleAsyncComponent for GamescopeApp {
connect_changed => |row| { connect_changed => |row| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.game.enhancements.gamescope.game.width = row.text().parse().unwrap_or_default(); config.game.enhancements.gamescope.game.width = row.text().parse().unwrap_or_default();
config::update(config); Config::update(config);
} }
} }
} }
@ -64,10 +66,10 @@ impl SimpleAsyncComponent for GamescopeApp {
connect_changed => |row| { connect_changed => |row| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.game.enhancements.gamescope.game.height = row.text().parse().unwrap_or_default(); config.game.enhancements.gamescope.game.height = row.text().parse().unwrap_or_default();
config::update(config); Config::update(config);
} }
} }
} }
@ -89,10 +91,10 @@ impl SimpleAsyncComponent for GamescopeApp {
connect_changed => |row| { connect_changed => |row| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.game.enhancements.gamescope.gamescope.width = row.text().parse().unwrap_or_default(); config.game.enhancements.gamescope.gamescope.width = row.text().parse().unwrap_or_default();
config::update(config); Config::update(config);
} }
} }
} }
@ -110,10 +112,10 @@ impl SimpleAsyncComponent for GamescopeApp {
connect_changed => |row| { connect_changed => |row| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.game.enhancements.gamescope.gamescope.height = row.text().parse().unwrap_or_default(); config.game.enhancements.gamescope.gamescope.height = row.text().parse().unwrap_or_default();
config::update(config); Config::update(config);
} }
} }
} }
@ -133,10 +135,10 @@ impl SimpleAsyncComponent for GamescopeApp {
connect_state_notify => |switch| { connect_state_notify => |switch| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.game.enhancements.gamescope.integer_scaling = switch.state(); config.game.enhancements.gamescope.integer_scaling = switch.state();
config::update(config); Config::update(config);
} }
} }
} }
@ -153,10 +155,10 @@ impl SimpleAsyncComponent for GamescopeApp {
connect_state_notify => |switch| { connect_state_notify => |switch| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.game.enhancements.gamescope.fsr = switch.state(); config.game.enhancements.gamescope.fsr = switch.state();
config::update(config); Config::update(config);
} }
} }
} }
@ -173,10 +175,10 @@ impl SimpleAsyncComponent for GamescopeApp {
connect_state_notify => |switch| { connect_state_notify => |switch| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.game.enhancements.gamescope.nis = switch.state(); config.game.enhancements.gamescope.nis = switch.state();
config::update(config); Config::update(config);
} }
} }
} }
@ -201,10 +203,10 @@ impl SimpleAsyncComponent for GamescopeApp {
connect_changed => |row| { connect_changed => |row| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.game.enhancements.gamescope.framerate.focused = row.text().parse().unwrap_or_default(); config.game.enhancements.gamescope.framerate.focused = row.text().parse().unwrap_or_default();
config::update(config); Config::update(config);
} }
} }
} }
@ -222,10 +224,10 @@ impl SimpleAsyncComponent for GamescopeApp {
connect_changed => |row| { connect_changed => |row| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.game.enhancements.gamescope.framerate.unfocused = row.text().parse().unwrap_or_default(); config.game.enhancements.gamescope.framerate.unfocused = row.text().parse().unwrap_or_default();
config::update(config); Config::update(config);
} }
} }
} }
@ -244,10 +246,10 @@ impl SimpleAsyncComponent for GamescopeApp {
connect_selected_notify => |row| unsafe { connect_selected_notify => |row| unsafe {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.game.enhancements.gamescope.window_type = WindowType::from_ordinal_unsafe(row.selected() as i8); config.game.enhancements.gamescope.window_type = WindowType::from_ordinal_unsafe(row.selected() as i8);
config::update(config); Config::update(config);
} }
} }
} }

View file

@ -10,14 +10,18 @@ use gtk::prelude::*;
use adw::prelude::*; use adw::prelude::*;
use anime_launcher_sdk::anime_game_core::prelude::*; use anime_launcher_sdk::anime_game_core::prelude::*;
use anime_launcher_sdk::anime_game_core::genshin::consts::GameEdition;
use anime_launcher_sdk::wincompatlib::prelude::*; use anime_launcher_sdk::wincompatlib::prelude::*;
use anime_launcher_sdk::config;
use anime_launcher_sdk::config::launcher::LauncherStyle;
use anime_launcher_sdk::components::*; use anime_launcher_sdk::components::*;
use anime_launcher_sdk::components::wine::WincompatlibWine; use anime_launcher_sdk::components::wine::WincompatlibWine;
use anime_launcher_sdk::env_emulation::Environment;
use anime_launcher_sdk::config::launcher::GameEdition; use anime_launcher_sdk::config::ConfigExt;
use anime_launcher_sdk::anime_game_core::genshin::consts::GameEdition as CoreGameEdition; use anime_launcher_sdk::genshin::config::Config;
use anime_launcher_sdk::genshin::config::schema::launcher::LauncherStyle;
use anime_launcher_sdk::genshin::env_emulation::Environment;
use super::main::PreferencesAppMsg; use super::main::PreferencesAppMsg;
use crate::ui::migrate_installation::MigrateInstallationApp; use crate::ui::migrate_installation::MigrateInstallationApp;
@ -291,12 +295,12 @@ impl SimpleAsyncComponent for GeneralApp {
connect_selected_notify => |row| { connect_selected_notify => |row| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.launcher.language = crate::i18n::format_lang(SUPPORTED_LANGUAGES config.launcher.language = crate::i18n::format_lang(SUPPORTED_LANGUAGES
.get(row.selected() as usize) .get(row.selected() as usize)
.unwrap_or(&SUPPORTED_LANGUAGES[0])); .unwrap_or(&SUPPORTED_LANGUAGES[0]));
config::update(config); Config::update(config);
} }
} }
} }
@ -318,7 +322,7 @@ impl SimpleAsyncComponent for GeneralApp {
connect_selected_notify[sender] => move |row| { connect_selected_notify[sender] => move |row| {
if is_ready() { if is_ready() {
#[allow(unused_must_use)] #[allow(unused_must_use)]
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.launcher.edition = match row.selected() { config.launcher.edition = match row.selected() {
0 => GameEdition::Global, 0 => GameEdition::Global,
1 => GameEdition::China, 1 => GameEdition::China,
@ -327,9 +331,9 @@ impl SimpleAsyncComponent for GeneralApp {
}; };
// Select new game edition // Select new game edition
CoreGameEdition::from(config.launcher.edition).select(); config.launcher.edition.select();
config::update(config); Config::update(config);
sender.output(PreferencesAppMsg::UpdateLauncherState); sender.output(PreferencesAppMsg::UpdateLauncherState);
} }
@ -355,7 +359,7 @@ impl SimpleAsyncComponent for GeneralApp {
connect_selected_notify => |row| { connect_selected_notify => |row| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.launcher.environment = match row.selected() { config.launcher.environment = match row.selected() {
0 => Environment::PC, 0 => Environment::PC,
1 => Environment::Android, 1 => Environment::Android,
@ -363,7 +367,7 @@ impl SimpleAsyncComponent for GeneralApp {
_ => unreachable!() _ => unreachable!()
}; };
config::update(config); Config::update(config);
} }
} }
} }
@ -478,7 +482,7 @@ impl SimpleAsyncComponent for GeneralApp {
PatchStatus::Preparation { .. } | PatchStatus::Preparation { .. } |
PatchStatus::Testing { .. } => &["warning"], PatchStatus::Testing { .. } => &["warning"],
PatchStatus::Available { .. } => unsafe { PatchStatus::Available { .. } => unsafe {
let path = match config::get() { let path = match Config::get() {
Ok(config) => config.game.path.for_edition(config.launcher.edition).to_path_buf(), Ok(config) => config.game.path.for_edition(config.launcher.edition).to_path_buf(),
Err(_) => CONFIG.game.path.for_edition(CONFIG.launcher.edition).to_path_buf(), Err(_) => CONFIG.game.path.for_edition(CONFIG.launcher.edition).to_path_buf(),
}; };
@ -505,7 +509,7 @@ impl SimpleAsyncComponent for GeneralApp {
PatchStatus::Preparation { .. } => tr("patch-preparation-tooltip"), PatchStatus::Preparation { .. } => tr("patch-preparation-tooltip"),
PatchStatus::Testing { .. } => tr("patch-testing-tooltip"), PatchStatus::Testing { .. } => tr("patch-testing-tooltip"),
PatchStatus::Available { .. } => unsafe { PatchStatus::Available { .. } => unsafe {
let path = match config::get() { let path = match Config::get() {
Ok(config) => config.game.path.for_edition(config.launcher.edition).to_path_buf(), Ok(config) => config.game.path.for_edition(config.launcher.edition).to_path_buf(),
Err(_) => CONFIG.game.path.for_edition(CONFIG.launcher.edition).to_path_buf(), Err(_) => CONFIG.game.path.for_edition(CONFIG.launcher.edition).to_path_buf(),
}; };
@ -549,7 +553,7 @@ impl SimpleAsyncComponent for GeneralApp {
PatchStatus::Preparation { .. } | PatchStatus::Preparation { .. } |
PatchStatus::Testing { .. } => &["warning"], PatchStatus::Testing { .. } => &["warning"],
PatchStatus::Available { .. } => unsafe { PatchStatus::Available { .. } => unsafe {
let path = match config::get() { let path = match Config::get() {
Ok(config) => config.game.path.for_edition(config.launcher.edition).to_path_buf(), Ok(config) => config.game.path.for_edition(config.launcher.edition).to_path_buf(),
Err(_) => CONFIG.game.path.for_edition(CONFIG.launcher.edition).to_path_buf(), Err(_) => CONFIG.game.path.for_edition(CONFIG.launcher.edition).to_path_buf(),
}; };
@ -576,7 +580,7 @@ impl SimpleAsyncComponent for GeneralApp {
PatchStatus::Preparation { .. } => tr("patch-preparation-tooltip"), PatchStatus::Preparation { .. } => tr("patch-preparation-tooltip"),
PatchStatus::Testing { .. } => tr("patch-testing-tooltip"), PatchStatus::Testing { .. } => tr("patch-testing-tooltip"),
PatchStatus::Available { .. } => unsafe { PatchStatus::Available { .. } => unsafe {
let path = match config::get() { let path = match Config::get() {
Ok(config) => config.game.path.for_edition(config.launcher.edition).to_path_buf(), Ok(config) => config.game.path.for_edition(config.launcher.edition).to_path_buf(),
Err(_) => CONFIG.game.path.for_edition(CONFIG.launcher.edition).to_path_buf(), Err(_) => CONFIG.game.path.for_edition(CONFIG.launcher.edition).to_path_buf(),
}; };
@ -607,10 +611,10 @@ impl SimpleAsyncComponent for GeneralApp {
connect_state_notify[sender] => move |switch| { connect_state_notify[sender] => move |switch| {
if is_ready() { if is_ready() {
#[allow(unused_must_use)] #[allow(unused_must_use)]
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.patch.apply_xlua = switch.state(); config.patch.apply_xlua = switch.state();
config::update(config); Config::update(config);
sender.output(PreferencesAppMsg::UpdateLauncherState); sender.output(PreferencesAppMsg::UpdateLauncherState);
} }
@ -630,10 +634,10 @@ impl SimpleAsyncComponent for GeneralApp {
connect_state_notify => |switch| { connect_state_notify => |switch| {
if is_ready() { if is_ready() {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.patch.root = switch.state(); config.patch.root = switch.state();
config::update(config); Config::update(config);
} }
} }
} }
@ -701,11 +705,11 @@ impl SimpleAsyncComponent for GeneralApp {
add_row = &adw::ActionRow { add_row = &adw::ActionRow {
set_title: &tr("command-line"), set_title: &tr("command-line"),
set_subtitle: "start cmd", set_subtitle: "wineconsole",
set_activatable: true, set_activatable: true,
connect_activated => GeneralAppMsg::WineOpen(&["start", "cmd"]) connect_activated => GeneralAppMsg::WineOpen(&["wineconsole"])
}, },
add_row = &adw::ActionRow { add_row = &adw::ActionRow {
@ -742,6 +746,15 @@ impl SimpleAsyncComponent for GeneralApp {
set_activatable: true, set_activatable: true,
connect_activated => GeneralAppMsg::WineOpen(&["winecfg"]) connect_activated => GeneralAppMsg::WineOpen(&["winecfg"])
},
add_row = &adw::ActionRow {
set_title: &tr("debugger"),
set_subtitle: "start winedbg",
set_activatable: true,
connect_activated => GeneralAppMsg::WineOpen(&["start", "winedbg"])
} }
} }
}, },
@ -951,11 +964,11 @@ impl SimpleAsyncComponent for GeneralApp {
#[allow(unused_must_use)] #[allow(unused_must_use)]
GeneralAppMsg::AddVoicePackage(index) => { GeneralAppMsg::AddVoicePackage(index) => {
if let Some(package) = self.voice_packages.get(index.current_index()) { if let Some(package) = self.voice_packages.get(index.current_index()) {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
if !config.game.voices.iter().any(|voice| VoiceLocale::from_str(voice) == Some(package.locale)) { if !config.game.voices.iter().any(|voice| VoiceLocale::from_str(voice) == Some(package.locale)) {
config.game.voices.push(package.locale.to_code().to_string()); config.game.voices.push(package.locale.to_code().to_string());
config::update(config); Config::update(config);
sender.output(PreferencesAppMsg::UpdateLauncherState); sender.output(PreferencesAppMsg::UpdateLauncherState);
} }
@ -966,12 +979,12 @@ impl SimpleAsyncComponent for GeneralApp {
#[allow(unused_must_use)] #[allow(unused_must_use)]
GeneralAppMsg::RemoveVoicePackage(index) => { GeneralAppMsg::RemoveVoicePackage(index) => {
if let Some(package) = self.voice_packages.guard().get_mut(index.current_index()) { if let Some(package) = self.voice_packages.guard().get_mut(index.current_index()) {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
package.sensitive = false; package.sensitive = false;
config.game.voices.retain(|voice| VoiceLocale::from_str(voice) != Some(package.locale)); config.game.voices.retain(|voice| VoiceLocale::from_str(voice) != Some(package.locale));
config::update(config.clone()); Config::update(config.clone());
let package = VoicePackage::with_locale(package.locale).unwrap(); let package = VoicePackage::with_locale(package.locale).unwrap();
let game_path = config.game.path.for_edition(config.launcher.edition).to_path_buf(); let game_path = config.game.path.for_edition(config.launcher.edition).to_path_buf();
@ -1019,7 +1032,7 @@ impl SimpleAsyncComponent for GeneralApp {
} }
GeneralAppMsg::WineOpen(executable) => { GeneralAppMsg::WineOpen(executable) => {
let config = config::get().unwrap_or_else(|_| CONFIG.clone()); let config = Config::get().unwrap_or_else(|_| CONFIG.clone());
if let Ok(Some(wine)) = config.get_selected_wine() { if let Ok(Some(wine)) = config.get_selected_wine() {
let result = wine.to_wine(config.components.path, Some(config.game.wine.builds.join(&wine.name))) let result = wine.to_wine(config.components.path, Some(config.game.wine.builds.join(&wine.name)))
@ -1056,10 +1069,10 @@ impl SimpleAsyncComponent for GeneralApp {
} }
} }
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
config.launcher.style = style; config.launcher.style = style;
config::update(config); Config::update(config);
} }
self.style = style; self.style = style;
@ -1135,7 +1148,7 @@ impl SimpleAsyncComponent for GeneralApp {
} }
GeneralAppMsg::SelectWine(index) => { GeneralAppMsg::SelectWine(index) => {
if let Ok(mut config) = config::get() { if let Ok(mut config) = Config::get() {
if let Some((version, features)) = self.downloaded_wine_versions.get(index) { if let Some((version, features)) = self.downloaded_wine_versions.get(index) {
if config.game.wine.selected.as_ref() != Some(&version.title) { if config.game.wine.selected.as_ref() != Some(&version.title) {
self.selecting_wine_version = true; self.selecting_wine_version = true;
@ -1154,7 +1167,7 @@ impl SimpleAsyncComponent for GeneralApp {
Ok(_) => { Ok(_) => {
config.game.wine.selected = Some(wine_name); config.game.wine.selected = Some(wine_name);
config::update(config); Config::update(config);
} }
Err(err) => { Err(err) => {
@ -1178,7 +1191,7 @@ impl SimpleAsyncComponent for GeneralApp {
} }
GeneralAppMsg::SelectDxvk(index) => { GeneralAppMsg::SelectDxvk(index) => {
if let Ok(config) = config::get() { if let Ok(config) = Config::get() {
if let Some(version) = self.downloaded_dxvk_versions.get(index) { if let Some(version) = self.downloaded_dxvk_versions.get(index) {
if let Ok(selected) = config.get_selected_dxvk() { if let Ok(selected) = config.get_selected_dxvk() {
if selected.is_none() || selected.unwrap().name != version.name { if selected.is_none() || selected.unwrap().name != version.name {

View file

@ -6,12 +6,16 @@ use adw::prelude::*;
use anime_launcher_sdk::anime_game_core::prelude::*; use anime_launcher_sdk::anime_game_core::prelude::*;
use anime_launcher_sdk::anime_game_core::genshin::prelude::*; use anime_launcher_sdk::anime_game_core::genshin::prelude::*;
use anime_launcher_sdk::config::launcher::LauncherStyle;
use anime_launcher_sdk::config::ConfigExt;
use anime_launcher_sdk::genshin::config::Config;
use anime_launcher_sdk::genshin::config::schema::launcher::LauncherStyle;
use crate::i18n::tr; use crate::i18n::tr;
use super::general::*; use super::general::*;
use super::enhancements::*; use super::enhancements::*;
use super::sandbox::*;
use super::environment::*; use super::environment::*;
pub static mut PREFERENCES_WINDOW: Option<adw::PreferencesWindow> = None; pub static mut PREFERENCES_WINDOW: Option<adw::PreferencesWindow> = None;
@ -19,6 +23,7 @@ pub static mut PREFERENCES_WINDOW: Option<adw::PreferencesWindow> = None;
pub struct PreferencesApp { pub struct PreferencesApp {
general: AsyncController<GeneralApp>, general: AsyncController<GeneralApp>,
enhancements: AsyncController<EnhancementsApp>, enhancements: AsyncController<EnhancementsApp>,
sandbox: AsyncController<SandboxApp>,
environment: AsyncController<EnvironmentApp> environment: AsyncController<EnvironmentApp>
} }
@ -64,10 +69,11 @@ impl SimpleAsyncComponent for PreferencesApp {
add = model.general.widget(), add = model.general.widget(),
add = model.enhancements.widget(), add = model.enhancements.widget(),
add = model.sandbox.widget(),
add = model.environment.widget(), add = model.environment.widget(),
connect_close_request[sender] => move |_| { connect_close_request[sender] => move |_| {
if let Err(err) = anime_launcher_sdk::config::flush() { if let Err(err) = Config::flush() {
sender.input(PreferencesAppMsg::Toast { sender.input(PreferencesAppMsg::Toast {
title: tr("config-update-error"), title: tr("config-update-error"),
description: Some(err.to_string()) description: Some(err.to_string())
@ -95,6 +101,10 @@ impl SimpleAsyncComponent for PreferencesApp {
.launch(()) .launch(())
.detach(), .detach(),
sandbox: SandboxApp::builder()
.launch(())
.detach(),
environment: EnvironmentApp::builder() environment: EnvironmentApp::builder()
.launch(()) .launch(())
.detach() .detach()

View file

@ -1,5 +1,6 @@
pub mod main; pub mod main;
pub mod general; pub mod general;
pub mod enhancements; pub mod enhancements;
pub mod sandbox;
pub mod environment; pub mod environment;
pub mod gamescope; pub mod gamescope;

View file

@ -0,0 +1,395 @@
use relm4::prelude::*;
use relm4::component::*;
use relm4::factory::*;
use adw::prelude::*;
use anime_launcher_sdk::is_available;
use crate::i18n::tr;
use crate::*;
#[derive(Debug)]
struct PrivateDirectory {
path: String
}
#[relm4::factory(async)]
impl AsyncFactoryComponent for PrivateDirectory {
type Init = String;
type Input = SandboxAppMsg;
type Output = SandboxAppMsg;
type CommandOutput = ();
type ParentInput = SandboxAppMsg;
type ParentWidget = adw::PreferencesGroup;
view! {
root = adw::ActionRow {
set_title: &self.path,
add_suffix = &gtk::Button {
set_icon_name: "user-trash-symbolic",
add_css_class: "flat",
set_valign: gtk::Align::Center,
connect_clicked[sender, index] => move |_| {
sender.input(SandboxAppMsg::RemovePrivate(index.clone()));
}
}
}
}
fn output_to_parent_input(output: Self::Output) -> Option<Self::ParentInput> {
Some(output)
}
async fn init_model(
init: Self::Init,
_index: &DynamicIndex,
_sender: AsyncFactorySender<Self>,
) -> Self {
Self {
path: init
}
}
async fn update(&mut self, msg: Self::Input, sender: AsyncFactorySender<Self>) {
sender.output(msg);
}
}
#[derive(Debug)]
struct SharedDirectory {
mount_from: String,
mount_to: String
}
#[relm4::factory(async)]
impl AsyncFactoryComponent for SharedDirectory {
type Init = (String, String);
type Input = SandboxAppMsg;
type Output = SandboxAppMsg;
type CommandOutput = ();
type ParentInput = SandboxAppMsg;
type ParentWidget = adw::PreferencesGroup;
view! {
root = adw::ActionRow {
set_title: &self.mount_to,
set_subtitle: &self.mount_from,
add_suffix = &gtk::Button {
set_icon_name: "user-trash-symbolic",
add_css_class: "flat",
set_valign: gtk::Align::Center,
connect_clicked[sender, index] => move |_| {
sender.input(SandboxAppMsg::RemoveShared(index.clone()));
}
}
}
}
fn output_to_parent_input(output: Self::Output) -> Option<Self::ParentInput> {
Some(output)
}
async fn init_model(
init: Self::Init,
_index: &DynamicIndex,
_sender: AsyncFactorySender<Self>,
) -> Self {
Self {
mount_from: init.0,
mount_to: init.1
}
}
async fn update(&mut self, msg: Self::Input, sender: AsyncFactorySender<Self>) {
sender.output(msg);
}
}
pub struct SandboxApp {
private_paths: AsyncFactoryVecDeque<PrivateDirectory>,
shared_paths: AsyncFactoryVecDeque<SharedDirectory>,
private_path_entry: adw::EntryRow,
shared_path_from_entry: adw::EntryRow,
shared_path_to_entry: adw::EntryRow,
read_only_switch: gtk::Switch
}
#[derive(Debug, Clone)]
pub enum SandboxAppMsg {
AddPrivate,
RemovePrivate(DynamicIndex),
AddShared,
RemoveShared(DynamicIndex)
}
#[relm4::component(async, pub)]
impl SimpleAsyncComponent for SandboxApp {
type Init = ();
type Input = SandboxAppMsg;
type Output = ();
view! {
adw::PreferencesPage {
set_title: &tr("sandbox"),
set_icon_name: Some("folder-symbolic"),
set_sensitive: is_available("bwrap"),
add = &adw::PreferencesGroup {
set_title: &tr("sandbox"),
set_description: Some(&tr("sandbox-description")),
adw::ActionRow {
set_title: &tr("enable-sandboxing"),
set_subtitle: &tr("enable-sandboxing-description"),
add_suffix = &gtk::Switch {
set_valign: gtk::Align::Center,
set_state: CONFIG.sandbox.enabled,
connect_state_notify => |switch| {
if is_ready() {
if let Ok(mut config) = Config::get() {
config.sandbox.enabled = switch.state();
Config::update(config);
}
}
}
}
},
adw::ActionRow {
set_title: &tr("hide-home-directory"),
set_subtitle: &tr("hide-home-directory-description"),
add_suffix = &gtk::Switch {
set_valign: gtk::Align::Center,
set_state: CONFIG.sandbox.isolate_home,
connect_state_notify => |switch| {
if is_ready() {
if let Ok(mut config) = Config::get() {
config.sandbox.isolate_home = switch.state();
Config::update(config);
}
}
}
}
},
adw::EntryRow {
set_title: &tr("hostname"),
set_text: CONFIG.sandbox.hostname.as_ref().unwrap_or(&String::new()).trim(),
connect_changed => |entry| {
if let Ok(mut config) = Config::get() {
let command = entry.text().trim().to_string();
config.sandbox.hostname = if command.is_empty() {
None
} else {
Some(command)
};
Config::update(config);
}
}
}
},
add = &adw::PreferencesGroup {
set_title: &tr("private-directories"),
set_description: Some(&tr("private-directories-description")),
#[local_ref]
private_path_entry -> adw::EntryRow {
set_title: &tr("path")
},
gtk::Button {
set_label: &tr("add"),
add_css_class: "pill",
set_margin_top: 8,
set_halign: gtk::Align::Start,
connect_clicked => SandboxAppMsg::AddPrivate
}
},
#[local_ref]
add = private_paths -> adw::PreferencesGroup {},
add = &adw::PreferencesGroup {
set_title: &tr("shared-directories"),
set_description: Some(&tr("shared-directories-description")),
#[local_ref]
shared_path_from_entry -> adw::EntryRow {
set_title: &tr("original-path")
},
#[local_ref]
shared_path_to_entry -> adw::EntryRow {
set_title: &tr("new-path")
},
adw::ActionRow {
set_title: &tr("read-only"),
set_subtitle: &tr("read-only-description"),
#[local_ref]
add_suffix = read_only_switch -> gtk::Switch {
set_valign: gtk::Align::Center
}
},
gtk::Button {
set_label: &tr("add"),
add_css_class: "pill",
set_margin_top: 8,
set_halign: gtk::Align::Start,
connect_clicked => SandboxAppMsg::AddShared
}
},
#[local_ref]
add = shared_paths -> adw::PreferencesGroup {}
}
}
async fn init(
_init: Self::Init,
root: Self::Root,
sender: AsyncComponentSender<Self>,
) -> AsyncComponentParts<Self> {
tracing::info!("Initializing environment settings");
let mut model = Self {
private_paths: AsyncFactoryVecDeque::new(adw::PreferencesGroup::new(), sender.input_sender()),
shared_paths: AsyncFactoryVecDeque::new(adw::PreferencesGroup::new(), sender.input_sender()),
private_path_entry: adw::EntryRow::new(),
shared_path_from_entry: adw::EntryRow::new(),
shared_path_to_entry: adw::EntryRow::new(),
read_only_switch: gtk::Switch::new()
};
for path in &CONFIG.sandbox.private {
model.private_paths.guard().push_back(path.trim().to_string());
}
for (from, to) in &CONFIG.sandbox.mounts.read_only {
model.shared_paths.guard().push_back((
from.trim().to_string(),
format!("[read-only] {}", to.trim())
));
}
for (from, to) in &CONFIG.sandbox.mounts.bind {
model.shared_paths.guard().push_back((
from.trim().to_string(),
to.trim().to_string()
));
}
let private_paths = model.private_paths.widget();
let shared_paths = model.shared_paths.widget();
let private_path_entry = &model.private_path_entry;
let shared_path_from_entry = &model.shared_path_from_entry;
let shared_path_to_entry = &model.shared_path_to_entry;
let read_only_switch = &model.read_only_switch;
let widgets = view_output!();
AsyncComponentParts { model, widgets }
}
async fn update(&mut self, msg: Self::Input, _sender: AsyncComponentSender<Self>) {
match msg {
SandboxAppMsg::AddPrivate => {
if let Ok(mut config) = Config::get() {
let path = self.private_path_entry.text().trim().to_string();
if !path.is_empty() {
config.sandbox.private.push(path.clone());
Config::update(config);
self.private_paths.guard().push_back(path);
}
}
}
SandboxAppMsg::RemovePrivate(index) => {
if let Ok(mut config) = Config::get() {
if let Some(var) = self.private_paths.guard().get(index.current_index()) {
config.sandbox.private.retain(|item| item != &var.path);
Config::update(config);
}
self.private_paths.guard().remove(index.current_index());
}
},
SandboxAppMsg::AddShared => {
if let Ok(mut config) = Config::get() {
let from = self.shared_path_from_entry.text().trim().to_string();
let to = self.shared_path_to_entry.text().trim().to_string();
let read_only = self.read_only_switch.state();
if !from.is_empty() && !to.is_empty() {
if read_only {
config.sandbox.mounts.read_only.insert(from.clone(), to.clone());
} else {
config.sandbox.mounts.bind.insert(from.clone(), to.clone());
}
Config::update(config);
self.shared_paths.guard().push_back((
from,
if read_only {
format!("[read-only] {}", to)
} else {
to
}
));
}
}
}
SandboxAppMsg::RemoveShared(index) => {
if let Ok(mut config) = Config::get() {
if let Some(var) = self.shared_paths.guard().get(index.current_index()) {
config.sandbox.mounts.read_only.remove(&var.mount_from);
config.sandbox.mounts.bind.remove(&var.mount_from);
Config::update(config);
}
self.shared_paths.guard().remove(index.current_index());
}
}
}
}
}