mirror of
https://github.com/an-anime-team/sleepy-launcher.git
synced 2024-11-22 12:49:02 +03:00
Reworked work with config file
- now missing fields will be automatically filled; excess fields - removed. thanks to new code structure I can easily create new fields or rename old ones - improved `WineLang` enum; now launcher loads languages list dynamically from this enum so I can easily add support for new languages
This commit is contained in:
parent
82691f70f9
commit
bf31327532
22 changed files with 902 additions and 393 deletions
|
@ -22,21 +22,6 @@ Adw.PreferencesPage page {
|
||||||
Adw.ComboRow wine_lang {
|
Adw.ComboRow wine_lang {
|
||||||
title: "Language";
|
title: "Language";
|
||||||
subtitle: "Choose the language to use in wine environment. Can fix keyboard layout detection in-game";
|
subtitle: "Choose the language to use in wine environment. Can fix keyboard layout detection in-game";
|
||||||
|
|
||||||
model: Gtk.StringList {
|
|
||||||
strings [
|
|
||||||
"System",
|
|
||||||
"English",
|
|
||||||
"German",
|
|
||||||
"Russian",
|
|
||||||
"Portuguese",
|
|
||||||
"French",
|
|
||||||
"Chinese",
|
|
||||||
"Spanish",
|
|
||||||
"Japanese",
|
|
||||||
"Korean"
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
48
src/lib/config/game/dxvk.rs
Normal file
48
src/lib/config/game/dxvk.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
use crate::lib::consts::launcher_dir;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Dxvk {
|
||||||
|
pub builds: String,
|
||||||
|
pub selected: Option<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Dxvk {
|
||||||
|
fn default() -> Self {
|
||||||
|
let launcher_dir = launcher_dir().expect("Failed to get launcher dir");
|
||||||
|
|
||||||
|
Self {
|
||||||
|
builds: format!("{launcher_dir}/dxvks"),
|
||||||
|
selected: None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&JsonValue> for Dxvk {
|
||||||
|
fn from(value: &JsonValue) -> Self {
|
||||||
|
let default = Self::default();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
builds: match value.get("builds") {
|
||||||
|
Some(value) => value.as_str().unwrap_or(&default.builds).to_string(),
|
||||||
|
None => default.builds
|
||||||
|
},
|
||||||
|
|
||||||
|
selected: match value.get("selected") {
|
||||||
|
Some(value) => {
|
||||||
|
if value.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
match value.as_str() {
|
||||||
|
Some(value) => Some(value.to_string()),
|
||||||
|
None => default.selected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => default.selected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
src/lib/config/game/enhancements/fsr.rs
Normal file
53
src/lib/config/game/enhancements/fsr.rs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
pub struct Fsr {
|
||||||
|
pub strength: u64,
|
||||||
|
pub enabled: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Fsr {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
strength: 2,
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&JsonValue> for Fsr {
|
||||||
|
fn from(value: &JsonValue) -> Self {
|
||||||
|
let default = Self::default();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
strength: match value.get("strength") {
|
||||||
|
Some(value) => value.as_u64().unwrap_or(default.strength),
|
||||||
|
None => default.strength
|
||||||
|
},
|
||||||
|
|
||||||
|
enabled: match value.get("enabled") {
|
||||||
|
Some(value) => value.as_bool().unwrap_or(default.enabled),
|
||||||
|
None => default.enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fsr {
|
||||||
|
/// Get environment variables corresponding to used amd fsr options
|
||||||
|
pub fn get_env_vars(&self) -> HashMap<&str, String> {
|
||||||
|
if self.enabled {
|
||||||
|
HashMap::from([
|
||||||
|
("WINE_FULLSCREEN_FSR", String::from("1")),
|
||||||
|
("WINE_FULLSCREEN_FSR_STRENGTH", self.strength.to_string())
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
HashMap::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
src/lib/config/game/enhancements/gamescope/framerate.rs
Normal file
26
src/lib/config/game/enhancements/gamescope/framerate.rs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
|
||||||
|
pub struct Framerate {
|
||||||
|
pub focused: u64,
|
||||||
|
pub unfocused: u64
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&JsonValue> for Framerate {
|
||||||
|
fn from(value: &JsonValue) -> Self {
|
||||||
|
let default = Self::default();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
focused: match value.get("focused") {
|
||||||
|
Some(value) => value.as_u64().unwrap_or(default.focused),
|
||||||
|
None => default.focused
|
||||||
|
},
|
||||||
|
|
||||||
|
unfocused: match value.get("unfocused") {
|
||||||
|
Some(value) => value.as_u64().unwrap_or(default.unfocused),
|
||||||
|
None => default.unfocused
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
149
src/lib/config/game/enhancements/gamescope/mod.rs
Normal file
149
src/lib/config/game/enhancements/gamescope/mod.rs
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
pub mod size;
|
||||||
|
pub mod framerate;
|
||||||
|
pub mod window_type;
|
||||||
|
|
||||||
|
pub mod prelude {
|
||||||
|
pub use super::Gamescope;
|
||||||
|
pub use super::size::Size;
|
||||||
|
pub use super::framerate::Framerate;
|
||||||
|
pub use super::window_type::WindowType;
|
||||||
|
}
|
||||||
|
|
||||||
|
use prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
pub struct Gamescope {
|
||||||
|
pub enabled: bool,
|
||||||
|
pub game: Size,
|
||||||
|
pub gamescope: Size,
|
||||||
|
pub framerate: Framerate,
|
||||||
|
pub integer_scaling: bool,
|
||||||
|
pub nvidia_image_scaling: bool,
|
||||||
|
pub window_type: WindowType
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Gamescope {
|
||||||
|
pub fn get_command(&self, fsr_enabled: bool) -> Option<String> {
|
||||||
|
// https://github.com/bottlesdevs/Bottles/blob/b908311348ed1184ead23dd76f9d8af41ff24082/src/backend/wine/winecommand.py#L478
|
||||||
|
if self.enabled {
|
||||||
|
let mut gamescope = String::from("gamescope");
|
||||||
|
|
||||||
|
// Set window type
|
||||||
|
match self.window_type {
|
||||||
|
WindowType::Borderless => gamescope += " -b",
|
||||||
|
WindowType::Fullscreen => gamescope += " -f"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set game width
|
||||||
|
if self.game.width > 0 {
|
||||||
|
gamescope += &format!(" -w {}", self.game.width);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set game height
|
||||||
|
if self.game.height > 0 {
|
||||||
|
gamescope += &format!(" -h {}", self.game.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set gamescope width
|
||||||
|
if self.gamescope.width > 0 {
|
||||||
|
gamescope += &format!(" -W {}", self.gamescope.width);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set gamescope height
|
||||||
|
if self.gamescope.height > 0 {
|
||||||
|
gamescope += &format!(" -H {}", self.gamescope.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set focused framerate limit
|
||||||
|
if self.framerate.focused > 0 {
|
||||||
|
gamescope += &format!(" -r {}", self.framerate.focused);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set unfocused framerate limit
|
||||||
|
if self.framerate.unfocused > 0 {
|
||||||
|
gamescope += &format!(" -o {}", self.framerate.unfocused);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set integer scaling
|
||||||
|
if self.integer_scaling {
|
||||||
|
gamescope += " -n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set NIS (Nvidia Image Scaling) support
|
||||||
|
if self.nvidia_image_scaling {
|
||||||
|
gamescope += " -Y";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set FSR support (only if NIS is not enabled)
|
||||||
|
else if fsr_enabled {
|
||||||
|
gamescope += " -U";
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(gamescope)
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Gamescope {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
enabled: false,
|
||||||
|
game: Size::default(),
|
||||||
|
gamescope: Size::default(),
|
||||||
|
framerate: Framerate::default(),
|
||||||
|
integer_scaling: true,
|
||||||
|
nvidia_image_scaling: false,
|
||||||
|
window_type: WindowType::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&JsonValue> for Gamescope {
|
||||||
|
fn from(value: &JsonValue) -> Self {
|
||||||
|
let default = Self::default();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
enabled: match value.get("enabled") {
|
||||||
|
Some(value) => value.as_bool().unwrap_or(default.enabled),
|
||||||
|
None => default.enabled
|
||||||
|
},
|
||||||
|
|
||||||
|
game: match value.get("game") {
|
||||||
|
Some(value) => Size::from(value),
|
||||||
|
None => default.game
|
||||||
|
},
|
||||||
|
|
||||||
|
gamescope: match value.get("gamescope") {
|
||||||
|
Some(value) => Size::from(value),
|
||||||
|
None => default.gamescope
|
||||||
|
},
|
||||||
|
|
||||||
|
framerate: match value.get("framerate") {
|
||||||
|
Some(value) => Framerate::from(value),
|
||||||
|
None => default.framerate
|
||||||
|
},
|
||||||
|
|
||||||
|
integer_scaling: match value.get("integer_scaling") {
|
||||||
|
Some(value) => value.as_bool().unwrap_or(default.integer_scaling),
|
||||||
|
None => default.integer_scaling
|
||||||
|
},
|
||||||
|
|
||||||
|
nvidia_image_scaling: match value.get("nvidia_image_scaling") {
|
||||||
|
Some(value) => value.as_bool().unwrap_or(default.nvidia_image_scaling),
|
||||||
|
None => default.nvidia_image_scaling
|
||||||
|
},
|
||||||
|
|
||||||
|
window_type: match value.get("window_type") {
|
||||||
|
Some(value) => WindowType::from(value),
|
||||||
|
None => default.window_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
src/lib/config/game/enhancements/gamescope/size.rs
Normal file
26
src/lib/config/game/enhancements/gamescope/size.rs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
|
||||||
|
pub struct Size {
|
||||||
|
pub width: u64,
|
||||||
|
pub height: u64
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&JsonValue> for Size {
|
||||||
|
fn from(value: &JsonValue) -> Self {
|
||||||
|
let default = Self::default();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
width: match value.get("width") {
|
||||||
|
Some(value) => value.as_u64().unwrap_or(default.width),
|
||||||
|
None => default.width
|
||||||
|
},
|
||||||
|
|
||||||
|
height: match value.get("height") {
|
||||||
|
Some(value) => value.as_u64().unwrap_or(default.height),
|
||||||
|
None => default.height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
src/lib/config/game/enhancements/gamescope/window_type.rs
Normal file
20
src/lib/config/game/enhancements/gamescope/window_type.rs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
pub enum WindowType {
|
||||||
|
Borderless,
|
||||||
|
Fullscreen
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for WindowType {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Borderless
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&JsonValue> for WindowType {
|
||||||
|
fn from(value: &JsonValue) -> Self {
|
||||||
|
serde_json::from_value(value.clone()).unwrap_or(Self::default())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,9 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
use super::Config;
|
use crate::lib::config::Config;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
pub enum HUD {
|
pub enum HUD {
|
||||||
|
@ -17,6 +18,12 @@ impl Default for HUD {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&JsonValue> for HUD {
|
||||||
|
fn from(value: &JsonValue) -> Self {
|
||||||
|
serde_json::from_value(value.clone()).unwrap_or(Self::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<u32> for HUD {
|
impl TryFrom<u32> for HUD {
|
||||||
type Error = String;
|
type Error = String;
|
||||||
|
|
52
src/lib/config/game/enhancements/mod.rs
Normal file
52
src/lib/config/game/enhancements/mod.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
pub mod fsr;
|
||||||
|
pub mod hud;
|
||||||
|
pub mod gamescope;
|
||||||
|
|
||||||
|
pub mod prelude {
|
||||||
|
pub use super::gamescope::prelude::*;
|
||||||
|
|
||||||
|
pub use super::Enhancements;
|
||||||
|
pub use super::fsr::Fsr;
|
||||||
|
pub use super::hud::HUD;
|
||||||
|
}
|
||||||
|
|
||||||
|
use prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
|
||||||
|
pub struct Enhancements {
|
||||||
|
pub fsr: Fsr,
|
||||||
|
pub gamemode: bool,
|
||||||
|
pub hud: HUD,
|
||||||
|
pub gamescope: Gamescope
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&JsonValue> for Enhancements {
|
||||||
|
fn from(value: &JsonValue) -> Self {
|
||||||
|
let default = Self::default();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
fsr: match value.get("fsr") {
|
||||||
|
Some(value) => Fsr::from(value),
|
||||||
|
None => default.fsr
|
||||||
|
},
|
||||||
|
|
||||||
|
gamemode: match value.get("gamemode") {
|
||||||
|
Some(value) => value.as_bool().unwrap_or(default.gamemode),
|
||||||
|
None => default.gamemode
|
||||||
|
},
|
||||||
|
|
||||||
|
hud: match value.get("hud") {
|
||||||
|
Some(value) => HUD::from(value),
|
||||||
|
None => default.hud
|
||||||
|
},
|
||||||
|
|
||||||
|
gamescope: match value.get("gamescope") {
|
||||||
|
Some(value) => Gamescope::from(value),
|
||||||
|
None => default.gamescope
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
127
src/lib/config/game/mod.rs
Normal file
127
src/lib/config/game/mod.rs
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
use crate::lib::consts::launcher_dir;
|
||||||
|
|
||||||
|
pub mod wine;
|
||||||
|
pub mod dxvk;
|
||||||
|
pub mod enhancements;
|
||||||
|
|
||||||
|
pub mod prelude {
|
||||||
|
pub use super::enhancements::prelude::*;
|
||||||
|
pub use super::wine::prelude::*;
|
||||||
|
|
||||||
|
pub use super::Game;
|
||||||
|
pub use super::dxvk::Dxvk;
|
||||||
|
}
|
||||||
|
|
||||||
|
use prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Game {
|
||||||
|
pub path: String,
|
||||||
|
pub voices: Vec<String>,
|
||||||
|
pub wine: prelude::Wine,
|
||||||
|
pub dxvk: prelude::Dxvk,
|
||||||
|
pub enhancements: prelude::Enhancements,
|
||||||
|
pub environment: HashMap<String, String>,
|
||||||
|
pub command: Option<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Game {
|
||||||
|
fn default() -> Self {
|
||||||
|
let launcher_dir = launcher_dir().expect("Failed to get launcher dir");
|
||||||
|
|
||||||
|
Self {
|
||||||
|
path: format!("{launcher_dir}/game/drive_c/Program Files/Genshin Impact"),
|
||||||
|
voices: vec![
|
||||||
|
String::from("en-us")
|
||||||
|
],
|
||||||
|
wine: Wine::default(),
|
||||||
|
dxvk: Dxvk::default(),
|
||||||
|
enhancements: Enhancements::default(),
|
||||||
|
environment: HashMap::new(),
|
||||||
|
command: None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&JsonValue> for Game {
|
||||||
|
fn from(value: &JsonValue) -> Self {
|
||||||
|
let default = Self::default();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
path: match value.get("path") {
|
||||||
|
Some(value) => value.as_str().unwrap_or(&default.path).to_string(),
|
||||||
|
None => default.path
|
||||||
|
},
|
||||||
|
|
||||||
|
voices: match value.get("voices") {
|
||||||
|
Some(value) => match value.as_array() {
|
||||||
|
Some(values) => {
|
||||||
|
let mut voices = Vec::new();
|
||||||
|
|
||||||
|
for value in values {
|
||||||
|
if let Some(voice) = value.as_str() {
|
||||||
|
voices.push(voice.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
voices
|
||||||
|
},
|
||||||
|
None => default.voices
|
||||||
|
},
|
||||||
|
None => default.voices
|
||||||
|
},
|
||||||
|
|
||||||
|
wine: match value.get("wine") {
|
||||||
|
Some(value) => Wine::from(value),
|
||||||
|
None => default.wine
|
||||||
|
},
|
||||||
|
|
||||||
|
dxvk: match value.get("dxvk") {
|
||||||
|
Some(value) => Dxvk::from(value),
|
||||||
|
None => default.dxvk
|
||||||
|
},
|
||||||
|
|
||||||
|
enhancements: match value.get("enhancements") {
|
||||||
|
Some(value) => Enhancements::from(value),
|
||||||
|
None => default.enhancements
|
||||||
|
},
|
||||||
|
|
||||||
|
environment: match value.get("environment") {
|
||||||
|
Some(value) => match value.as_object() {
|
||||||
|
Some(values) => {
|
||||||
|
let mut vars = HashMap::new();
|
||||||
|
|
||||||
|
for (name, value) in values {
|
||||||
|
if let Some(value) = value.as_str() {
|
||||||
|
vars.insert(name.clone(), value.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vars
|
||||||
|
},
|
||||||
|
None => default.environment
|
||||||
|
},
|
||||||
|
None => default.environment
|
||||||
|
},
|
||||||
|
|
||||||
|
command: match value.get("command") {
|
||||||
|
Some(value) => {
|
||||||
|
if value.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
match value.as_str() {
|
||||||
|
Some(value) => Some(value.to_string()),
|
||||||
|
None => default.command
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => default.command
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
80
src/lib/config/game/wine/mod.rs
Normal file
80
src/lib/config/game/wine/mod.rs
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
use crate::lib::consts::launcher_dir;
|
||||||
|
|
||||||
|
pub mod wine_sync;
|
||||||
|
pub mod wine_lang;
|
||||||
|
|
||||||
|
pub mod prelude {
|
||||||
|
pub use super::Wine;
|
||||||
|
pub use super::wine_sync::WineSync;
|
||||||
|
pub use super::wine_lang::WineLang;
|
||||||
|
}
|
||||||
|
|
||||||
|
use prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Wine {
|
||||||
|
pub prefix: String,
|
||||||
|
pub builds: String,
|
||||||
|
pub selected: Option<String>,
|
||||||
|
pub sync: WineSync,
|
||||||
|
pub language: WineLang
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Wine {
|
||||||
|
fn default() -> Self {
|
||||||
|
let launcher_dir = launcher_dir().expect("Failed to get launcher dir");
|
||||||
|
|
||||||
|
Self {
|
||||||
|
prefix: format!("{launcher_dir}/game"),
|
||||||
|
builds: format!("{launcher_dir}/runners"),
|
||||||
|
selected: None,
|
||||||
|
sync: WineSync::default(),
|
||||||
|
language: WineLang::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&JsonValue> for Wine {
|
||||||
|
fn from(value: &JsonValue) -> Self {
|
||||||
|
let default = Self::default();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
prefix: match value.get("prefix") {
|
||||||
|
Some(value) => value.as_str().unwrap_or(&default.prefix).to_string(),
|
||||||
|
None => default.prefix
|
||||||
|
},
|
||||||
|
|
||||||
|
builds: match value.get("builds") {
|
||||||
|
Some(value) => value.as_str().unwrap_or(&default.builds).to_string(),
|
||||||
|
None => default.builds
|
||||||
|
},
|
||||||
|
|
||||||
|
selected: match value.get("selected") {
|
||||||
|
Some(value) => {
|
||||||
|
if value.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
match value.as_str() {
|
||||||
|
Some(value) => Some(value.to_string()),
|
||||||
|
None => default.selected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => default.selected
|
||||||
|
},
|
||||||
|
|
||||||
|
sync: match value.get("sync") {
|
||||||
|
Some(value) => WineSync::from(value),
|
||||||
|
None => default.sync
|
||||||
|
},
|
||||||
|
|
||||||
|
language: match value.get("language") {
|
||||||
|
Some(value) => WineLang::from(value),
|
||||||
|
None => default.language
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
85
src/lib/config/game/wine/wine_lang.rs
Normal file
85
src/lib/config/game/wine/wine_lang.rs
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum WineLang {
|
||||||
|
System,
|
||||||
|
English,
|
||||||
|
Russian,
|
||||||
|
German,
|
||||||
|
Portuguese,
|
||||||
|
Polish,
|
||||||
|
French,
|
||||||
|
Spanish,
|
||||||
|
Chinese,
|
||||||
|
Japanese,
|
||||||
|
Korean
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for WineLang {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::System
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&JsonValue> for WineLang {
|
||||||
|
fn from(value: &JsonValue) -> Self {
|
||||||
|
serde_json::from_value(value.clone()).unwrap_or(Self::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<String> for WineLang {
|
||||||
|
fn into(self) -> String {
|
||||||
|
format!("{:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<u32> for WineLang {
|
||||||
|
fn into(self) -> u32 {
|
||||||
|
for (i, lang) in Self::list().into_iter().enumerate() {
|
||||||
|
if lang == self {
|
||||||
|
return i as u32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WineLang {
|
||||||
|
pub fn list() -> Vec<Self> {
|
||||||
|
vec![
|
||||||
|
Self::System,
|
||||||
|
Self::English,
|
||||||
|
Self::Russian,
|
||||||
|
Self::German,
|
||||||
|
Self::Portuguese,
|
||||||
|
Self::Polish,
|
||||||
|
Self::French,
|
||||||
|
Self::Spanish,
|
||||||
|
Self::Chinese,
|
||||||
|
Self::Japanese,
|
||||||
|
Self::Korean
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get environment variables corresponding to used wine language
|
||||||
|
pub fn get_env_vars(&self) -> HashMap<&str, &str> {
|
||||||
|
HashMap::from([("LANG", match self {
|
||||||
|
Self::System => return HashMap::new(),
|
||||||
|
|
||||||
|
Self::English => "en_US.UTF8",
|
||||||
|
Self::Russian => "ru_RU.UTF8",
|
||||||
|
Self::German => "de_DE.UTF8",
|
||||||
|
Self::Portuguese => "pt_PT.UTF8",
|
||||||
|
Self::Polish => "pl-PL.UTF8",
|
||||||
|
Self::French => "fr_FR.UTF8",
|
||||||
|
Self::Spanish => "es_ES.UTF8",
|
||||||
|
Self::Chinese => "zh_CN.UTF8",
|
||||||
|
Self::Japanese => "ja_JP.UTF8",
|
||||||
|
Self::Korean => "ko_KR.UTF8"
|
||||||
|
})])
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
pub enum WineSync {
|
pub enum WineSync {
|
||||||
|
@ -16,6 +17,12 @@ impl Default for WineSync {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&JsonValue> for WineSync {
|
||||||
|
fn from(value: &JsonValue) -> Self {
|
||||||
|
serde_json::from_value(value.clone()).unwrap_or(Self::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<u32> for WineSync {
|
impl TryFrom<u32> for WineSync {
|
||||||
type Error = String;
|
type Error = String;
|
||||||
|
|
62
src/lib/config/launcher/mod.rs
Normal file
62
src/lib/config/launcher/mod.rs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
use crate::lib::consts::launcher_dir;
|
||||||
|
|
||||||
|
pub mod repairer;
|
||||||
|
|
||||||
|
pub mod prelude {
|
||||||
|
pub use super::Launcher;
|
||||||
|
pub use super::repairer::Repairer;
|
||||||
|
}
|
||||||
|
|
||||||
|
use prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Launcher {
|
||||||
|
pub language: String,
|
||||||
|
pub temp: Option<String>,
|
||||||
|
pub repairer: Repairer
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Launcher {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
language: String::from("en-us"),
|
||||||
|
temp: launcher_dir(),
|
||||||
|
repairer: Repairer::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&JsonValue> for Launcher {
|
||||||
|
fn from(value: &JsonValue) -> Self {
|
||||||
|
let default = Self::default();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
language: match value.get("language") {
|
||||||
|
Some(value) => value.as_str().unwrap_or(&default.language).to_string(),
|
||||||
|
None => default.language
|
||||||
|
},
|
||||||
|
|
||||||
|
temp: match value.get("temp") {
|
||||||
|
Some(value) => {
|
||||||
|
if value.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
match value.as_str() {
|
||||||
|
Some(value) => Some(value.to_string()),
|
||||||
|
None => default.temp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => default.temp
|
||||||
|
},
|
||||||
|
|
||||||
|
repairer: match value.get("repairer") {
|
||||||
|
Some(value) => Repairer::from(value),
|
||||||
|
None => default.repairer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
src/lib/config/launcher/repairer.rs
Normal file
35
src/lib/config/launcher/repairer.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Repairer {
|
||||||
|
pub threads: u64,
|
||||||
|
pub fast: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Repairer {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
threads: 4,
|
||||||
|
fast: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&JsonValue> for Repairer {
|
||||||
|
fn from(value: &JsonValue) -> Self {
|
||||||
|
let default = Self::default();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
threads: match value.get("threads") {
|
||||||
|
Some(value) => value.as_u64().unwrap_or(default.threads),
|
||||||
|
None => default.threads
|
||||||
|
},
|
||||||
|
|
||||||
|
fast: match value.get("fast") {
|
||||||
|
Some(value) => value.as_bool().unwrap_or(default.fast),
|
||||||
|
None => default.fast
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::io::{Error, ErrorKind, Write};
|
use std::io::{Error, ErrorKind, Write};
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
use crate::lib;
|
use crate::lib;
|
||||||
use super::consts::*;
|
use super::consts::*;
|
||||||
|
@ -13,13 +13,18 @@ use super::wine::{
|
||||||
List as WineList
|
List as WineList
|
||||||
};
|
};
|
||||||
|
|
||||||
mod hud;
|
pub mod launcher;
|
||||||
mod wine_sync;
|
pub mod game;
|
||||||
mod wine_lang;
|
pub mod patch;
|
||||||
|
|
||||||
pub use hud::HUD;
|
pub mod prelude {
|
||||||
pub use wine_sync::WineSync;
|
pub use super::launcher::prelude::*;
|
||||||
pub use wine_lang::WineLang;
|
pub use super::game::prelude::*;
|
||||||
|
|
||||||
|
pub use super::patch::Patch;
|
||||||
|
}
|
||||||
|
|
||||||
|
use prelude::*;
|
||||||
|
|
||||||
static mut CONFIG: Option<Config> = None;
|
static mut CONFIG: Option<Config> = None;
|
||||||
|
|
||||||
|
@ -50,8 +55,10 @@ pub fn get_raw() -> Result<Config, Error> {
|
||||||
|
|
||||||
file.read_to_string(&mut json)?;
|
file.read_to_string(&mut json)?;
|
||||||
|
|
||||||
match serde_json::from_str::<Config>(&json) {
|
match serde_json::from_str(&json) {
|
||||||
Ok(config) => {
|
Ok(config) => {
|
||||||
|
let config = Config::from(&config);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
CONFIG = Some(config.clone());
|
CONFIG = Some(config.clone());
|
||||||
}
|
}
|
||||||
|
@ -179,288 +186,27 @@ impl Config {
|
||||||
None => None
|
None => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_gamescope_command(&self) -> Option<String> {
|
|
||||||
// https://github.com/bottlesdevs/Bottles/blob/b908311348ed1184ead23dd76f9d8af41ff24082/src/backend/wine/winecommand.py#L478
|
|
||||||
if self.game.enhancements.gamescope.enabled {
|
|
||||||
let mut gamescope = String::from("gamescope");
|
|
||||||
|
|
||||||
// Set window type
|
|
||||||
match self.game.enhancements.gamescope.window_type {
|
|
||||||
WindowType::Borderless => gamescope += " -b",
|
|
||||||
WindowType::Fullscreen => gamescope += " -f"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set game width
|
|
||||||
if self.game.enhancements.gamescope.game.width > 0 {
|
|
||||||
gamescope += &format!(" -w {}", self.game.enhancements.gamescope.game.width);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set game height
|
|
||||||
if self.game.enhancements.gamescope.game.height > 0 {
|
|
||||||
gamescope += &format!(" -h {}", self.game.enhancements.gamescope.game.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set gamescope width
|
|
||||||
if self.game.enhancements.gamescope.gamescope.width > 0 {
|
|
||||||
gamescope += &format!(" -W {}", self.game.enhancements.gamescope.gamescope.width);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set gamescope height
|
|
||||||
if self.game.enhancements.gamescope.gamescope.height > 0 {
|
|
||||||
gamescope += &format!(" -H {}", self.game.enhancements.gamescope.gamescope.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set focused framerate limit
|
|
||||||
if self.game.enhancements.gamescope.framerate.focused > 0 {
|
|
||||||
gamescope += &format!(" -r {}", self.game.enhancements.gamescope.framerate.focused);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set unfocused framerate limit
|
|
||||||
if self.game.enhancements.gamescope.framerate.unfocused > 0 {
|
|
||||||
gamescope += &format!(" -o {}", self.game.enhancements.gamescope.framerate.unfocused);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set integer scaling
|
|
||||||
if self.game.enhancements.gamescope.integer_scaling {
|
|
||||||
gamescope += " -n";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set NIS (Nvidia Image Scaling) support
|
|
||||||
if self.game.enhancements.gamescope.nvidia_image_scaling {
|
|
||||||
gamescope += " -Y";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set FSR support (only if NIS is not enabled)
|
|
||||||
else if self.game.enhancements.fsr.enabled {
|
|
||||||
gamescope += " -U";
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(gamescope)
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
impl From<&JsonValue> for Config {
|
||||||
pub struct Launcher {
|
fn from(value: &JsonValue) -> Self {
|
||||||
pub language: String,
|
let default = Self::default();
|
||||||
pub temp: Option<String>,
|
|
||||||
pub repairer: Repairer
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Launcher {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
language: String::from("en-us"),
|
launcher: match value.get("launcher") {
|
||||||
temp: launcher_dir(),
|
Some(value) => Launcher::from(value),
|
||||||
repairer: Repairer::default()
|
None => default.launcher
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct Repairer {
|
|
||||||
pub threads: u8,
|
|
||||||
pub fast: bool
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Repairer {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
threads: 4,
|
|
||||||
fast: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct Patch {
|
|
||||||
pub path: String,
|
|
||||||
pub servers: Vec<String>,
|
|
||||||
pub root: bool
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Patch {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
path: match launcher_dir() {
|
|
||||||
Some(dir) => format!("{}/patch", dir),
|
|
||||||
None => String::new()
|
|
||||||
},
|
},
|
||||||
servers: vec![
|
|
||||||
"https://notabug.org/Krock/dawn".to_string(),
|
|
||||||
"https://dev.kaifa.ch/Maroxy/dawn".to_string()
|
|
||||||
],
|
|
||||||
|
|
||||||
// Disable root requirement for patching if we're running launcher in flatpak
|
game: match value.get("game") {
|
||||||
root: !Path::new("/.flatpak-info").exists()
|
Some(value) => Game::from(value),
|
||||||
}
|
None => default.game
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct Game {
|
|
||||||
pub path: String,
|
|
||||||
pub voices: Vec<String>,
|
|
||||||
pub wine: Wine,
|
|
||||||
pub dxvk: Dxvk,
|
|
||||||
pub enhancements: Enhancements,
|
|
||||||
pub environment: HashMap<String, String>,
|
|
||||||
pub command: Option<String>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Game {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
path: match launcher_dir() {
|
|
||||||
Some(dir) => format!("{}/game/drive_c/Program Files/Genshin Impact", dir),
|
|
||||||
None => String::new()
|
|
||||||
},
|
},
|
||||||
voices: vec![
|
|
||||||
String::from("en-us")
|
patch: match value.get("patch") {
|
||||||
],
|
Some(value) => Patch::from(value),
|
||||||
wine: Wine::default(),
|
None => default.patch
|
||||||
dxvk: Dxvk::default(),
|
}
|
||||||
enhancements: Enhancements::default(),
|
|
||||||
environment: HashMap::new(),
|
|
||||||
command: None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct Wine {
|
|
||||||
pub prefix: String,
|
|
||||||
pub builds: String,
|
|
||||||
pub selected: Option<String>,
|
|
||||||
pub sync: WineSync,
|
|
||||||
pub language: WineLang
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Wine {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
prefix: match launcher_dir() {
|
|
||||||
Some(dir) => format!("{}/game", dir),
|
|
||||||
None => String::new()
|
|
||||||
},
|
|
||||||
builds: match launcher_dir() {
|
|
||||||
Some(dir) => format!("{}/runners", dir),
|
|
||||||
None => String::new()
|
|
||||||
},
|
|
||||||
selected: None,
|
|
||||||
sync: WineSync::default(),
|
|
||||||
language: WineLang::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct Dxvk {
|
|
||||||
pub builds: String,
|
|
||||||
pub selected: Option<String>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Dxvk {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
builds: match launcher_dir() {
|
|
||||||
Some(dir) => format!("{}/dxvks", dir),
|
|
||||||
None => String::new()
|
|
||||||
},
|
|
||||||
selected: None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
|
|
||||||
pub struct Enhancements {
|
|
||||||
pub fsr: Fsr,
|
|
||||||
pub gamemode: bool,
|
|
||||||
pub hud: HUD,
|
|
||||||
pub gamescope: Gamescope
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
|
||||||
pub struct Fsr {
|
|
||||||
pub strength: u32,
|
|
||||||
pub enabled: bool
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Fsr {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
strength: 2,
|
|
||||||
enabled: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Fsr {
|
|
||||||
/// Get environment variables corresponding to used amd fsr options
|
|
||||||
pub fn get_env_vars(&self) -> HashMap<&str, String> {
|
|
||||||
if self.enabled {
|
|
||||||
HashMap::from([
|
|
||||||
("WINE_FULLSCREEN_FSR", String::from("1")),
|
|
||||||
("WINE_FULLSCREEN_FSR_STRENGTH", self.strength.to_string())
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
HashMap::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
|
||||||
pub struct Gamescope {
|
|
||||||
pub enabled: bool,
|
|
||||||
pub game: Size,
|
|
||||||
pub gamescope: Size,
|
|
||||||
pub framerate: Framerate,
|
|
||||||
pub integer_scaling: bool,
|
|
||||||
pub nvidia_image_scaling: bool,
|
|
||||||
pub window_type: WindowType
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Gamescope {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
enabled: false,
|
|
||||||
game: Size::default(),
|
|
||||||
gamescope: Size::default(),
|
|
||||||
framerate: Framerate::default(),
|
|
||||||
integer_scaling: true,
|
|
||||||
nvidia_image_scaling: false,
|
|
||||||
window_type: WindowType::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
|
|
||||||
pub struct Size {
|
|
||||||
pub width: u16,
|
|
||||||
pub height: u16
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
|
|
||||||
pub struct Framerate {
|
|
||||||
pub focused: u16,
|
|
||||||
pub unfocused: u16
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
|
||||||
pub enum WindowType {
|
|
||||||
Borderless,
|
|
||||||
Fullscreen
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for WindowType {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Borderless
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
66
src/lib/config/patch.rs
Normal file
66
src/lib/config/patch.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
use crate::lib::consts::launcher_dir;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Patch {
|
||||||
|
pub path: String,
|
||||||
|
pub servers: Vec<String>,
|
||||||
|
pub root: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Patch {
|
||||||
|
fn default() -> Self {
|
||||||
|
let launcher_dir = launcher_dir().expect("Failed to get launcher dir");
|
||||||
|
|
||||||
|
Self {
|
||||||
|
path: format!("{launcher_dir}/patch"),
|
||||||
|
servers: vec![
|
||||||
|
"https://notabug.org/Krock/dawn".to_string(),
|
||||||
|
"https://dev.kaifa.ch/Maroxy/dawn".to_string()
|
||||||
|
],
|
||||||
|
|
||||||
|
// Disable root requirement for patching if we're running launcher in flatpak
|
||||||
|
root: !Path::new("/.flatpak-info").exists()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&JsonValue> for Patch {
|
||||||
|
fn from(value: &JsonValue) -> Self {
|
||||||
|
let default = Self::default();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
path: match value.get("path") {
|
||||||
|
Some(value) => value.as_str().unwrap_or(&default.path).to_string(),
|
||||||
|
None => default.path
|
||||||
|
},
|
||||||
|
|
||||||
|
servers: match value.get("servers") {
|
||||||
|
Some(value) => match value.as_array() {
|
||||||
|
Some(values) => {
|
||||||
|
let mut servers = Vec::new();
|
||||||
|
|
||||||
|
for value in values {
|
||||||
|
if let Some(server) = value.as_str() {
|
||||||
|
servers.push(server.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
servers
|
||||||
|
},
|
||||||
|
None => default.servers
|
||||||
|
},
|
||||||
|
None => default.servers
|
||||||
|
},
|
||||||
|
|
||||||
|
root: match value.get("root") {
|
||||||
|
Some(value) => value.as_bool().unwrap_or(default.root),
|
||||||
|
None => default.root
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,79 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
|
||||||
pub enum WineLang {
|
|
||||||
System,
|
|
||||||
English,
|
|
||||||
German,
|
|
||||||
Russian,
|
|
||||||
Portuguese,
|
|
||||||
French,
|
|
||||||
Chinese,
|
|
||||||
Spanish,
|
|
||||||
Japanese,
|
|
||||||
Korean
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for WineLang {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::System
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<u32> for WineLang {
|
|
||||||
type Error = String;
|
|
||||||
|
|
||||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
|
||||||
match value {
|
|
||||||
0 => Ok(Self::System),
|
|
||||||
1 => Ok(Self::English),
|
|
||||||
2 => Ok(Self::German),
|
|
||||||
3 => Ok(Self::Russian),
|
|
||||||
4 => Ok(Self::Portuguese),
|
|
||||||
5 => Ok(Self::French),
|
|
||||||
6 => Ok(Self::Chinese),
|
|
||||||
7 => Ok(Self::Spanish),
|
|
||||||
8 => Ok(Self::Japanese),
|
|
||||||
9 => Ok(Self::Korean),
|
|
||||||
_ => Err(String::from("Failed to convert number to WineLang enum"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<u32> for WineLang {
|
|
||||||
fn into(self) -> u32 {
|
|
||||||
match self {
|
|
||||||
WineLang::System => 0,
|
|
||||||
WineLang::English => 1,
|
|
||||||
WineLang::German => 2,
|
|
||||||
WineLang::Russian => 3,
|
|
||||||
WineLang::Portuguese => 4,
|
|
||||||
WineLang::French => 5,
|
|
||||||
WineLang::Chinese => 6,
|
|
||||||
WineLang::Spanish => 7,
|
|
||||||
WineLang::Japanese => 8,
|
|
||||||
WineLang::Korean => 9
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WineLang {
|
|
||||||
/// Get environment variables corresponding to used wine language
|
|
||||||
pub fn get_env_vars(&self) -> HashMap<&str, &str> {
|
|
||||||
HashMap::from([("LANG", match self {
|
|
||||||
WineLang::System => return HashMap::new(),
|
|
||||||
|
|
||||||
WineLang::English => "en_US.UTF8",
|
|
||||||
WineLang::German => "de_DE.UTF8",
|
|
||||||
WineLang::Russian => "ru_RU.UTF8",
|
|
||||||
WineLang::Portuguese => "pt_PT.UTF8",
|
|
||||||
WineLang::French => "fr_FR.UTF8",
|
|
||||||
WineLang::Chinese => "zh_CN.UTF8",
|
|
||||||
WineLang::Spanish => "es_ES.UTF8",
|
|
||||||
WineLang::Japanese => "ja_JP.UTF8",
|
|
||||||
WineLang::Korean => "ko_KR.UTF8"
|
|
||||||
})])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -99,7 +99,7 @@ pub fn run(debug: bool) -> std::io::Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// gamescope <params> -- <command to run>
|
// gamescope <params> -- <command to run>
|
||||||
if let Some(gamescope) = config.get_gamescope_command() {
|
if let Some(gamescope) = config.game.enhancements.gamescope.get_command(config.game.enhancements.fsr.enabled) {
|
||||||
bash_chain = format!("{gamescope} -- {bash_chain}");
|
bash_chain = format!("{gamescope} -- {bash_chain}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ fn main() {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create default launcher folder if needed
|
// Create default launcher folder if needed
|
||||||
let launcher_dir = lib::consts::launcher_dir().unwrap();
|
let launcher_dir = lib::consts::launcher_dir().expect("Failed to get launcher dir");
|
||||||
|
|
||||||
if !Path::new(&launcher_dir).exists() || Path::new(&format!("{}/.first-run", launcher_dir)).exists() {
|
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::create_dir_all(&launcher_dir).expect("Failed to create default launcher dir");
|
||||||
|
|
|
@ -6,6 +6,7 @@ use gtk::glib::clone;
|
||||||
|
|
||||||
use crate::lib;
|
use crate::lib;
|
||||||
use crate::lib::config;
|
use crate::lib::config;
|
||||||
|
use crate::lib::config::prelude::*;
|
||||||
|
|
||||||
use crate::ui::*;
|
use crate::ui::*;
|
||||||
|
|
||||||
|
@ -61,6 +62,17 @@ impl AppWidgets {
|
||||||
gamescope_app: GamescopeApp::new(window)?
|
gamescope_app: GamescopeApp::new(window)?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Set availale wine languages
|
||||||
|
let model = gtk::StringList::new(&[]);
|
||||||
|
|
||||||
|
for lang in WineLang::list() {
|
||||||
|
let lang: String = lang.into();
|
||||||
|
|
||||||
|
model.append(&lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.wine_lang.set_model(Some(&model));
|
||||||
|
|
||||||
// Disable gamemode row if it's not available
|
// Disable gamemode row if it's not available
|
||||||
if !lib::is_available("gamemoderun") {
|
if !lib::is_available("gamemoderun") {
|
||||||
result.gamemode_row.set_sensitive(false);
|
result.gamemode_row.set_sensitive(false);
|
||||||
|
@ -108,7 +120,7 @@ impl App {
|
||||||
// Wine sync selection
|
// Wine sync selection
|
||||||
self.widgets.sync_combo.connect_selected_notify(move |row| {
|
self.widgets.sync_combo.connect_selected_notify(move |row| {
|
||||||
if let Ok(mut config) = config::get() {
|
if let Ok(mut config) = config::get() {
|
||||||
config.game.wine.sync = config::WineSync::try_from(row.selected()).unwrap();
|
config.game.wine.sync = WineSync::try_from(row.selected()).unwrap();
|
||||||
|
|
||||||
config::update(config);
|
config::update(config);
|
||||||
}
|
}
|
||||||
|
@ -117,7 +129,7 @@ impl App {
|
||||||
// Wine language selection
|
// Wine language selection
|
||||||
self.widgets.wine_lang.connect_selected_notify(move |row| {
|
self.widgets.wine_lang.connect_selected_notify(move |row| {
|
||||||
if let Ok(mut config) = config::get() {
|
if let Ok(mut config) = config::get() {
|
||||||
config.game.wine.language = config::WineLang::try_from(row.selected()).unwrap();
|
config.game.wine.language = WineLang::list()[row.selected() as usize];
|
||||||
|
|
||||||
config::update(config);
|
config::update(config);
|
||||||
}
|
}
|
||||||
|
@ -126,7 +138,7 @@ impl App {
|
||||||
// HUD selection
|
// HUD selection
|
||||||
self.widgets.hud_combo.connect_selected_notify(move |row| {
|
self.widgets.hud_combo.connect_selected_notify(move |row| {
|
||||||
if let Ok(mut config) = config::get() {
|
if let Ok(mut config) = config::get() {
|
||||||
config.game.enhancements.hud = config::HUD::try_from(row.selected()).unwrap();
|
config.game.enhancements.hud = HUD::try_from(row.selected()).unwrap();
|
||||||
|
|
||||||
config::update(config);
|
config::update(config);
|
||||||
}
|
}
|
||||||
|
@ -142,7 +154,7 @@ impl App {
|
||||||
// Source: Bottles (https://github.com/bottlesdevs/Bottles/blob/22fa3573a13f4e9b9c429e4cdfe4ca29787a2832/src/ui/details-preferences.ui#L88)
|
// Source: Bottles (https://github.com/bottlesdevs/Bottles/blob/22fa3573a13f4e9b9c429e4cdfe4ca29787a2832/src/ui/details-preferences.ui#L88)
|
||||||
self.widgets.fsr_combo.connect_selected_notify(move |row| {
|
self.widgets.fsr_combo.connect_selected_notify(move |row| {
|
||||||
if let Ok(mut config) = config::get() {
|
if let Ok(mut config) = config::get() {
|
||||||
config.game.enhancements.fsr.strength = 5 - row.selected();
|
config.game.enhancements.fsr.strength = 5 - row.selected() as u64;
|
||||||
|
|
||||||
config::update(config);
|
config::update(config);
|
||||||
}
|
}
|
||||||
|
@ -207,7 +219,7 @@ impl App {
|
||||||
self.widgets.hud_combo.set_selected(config.game.enhancements.hud.into());
|
self.widgets.hud_combo.set_selected(config.game.enhancements.hud.into());
|
||||||
|
|
||||||
// FSR strength selection
|
// FSR strength selection
|
||||||
self.widgets.fsr_combo.set_selected(5 - config.game.enhancements.fsr.strength);
|
self.widgets.fsr_combo.set_selected(5 - config.game.enhancements.fsr.strength as u32);
|
||||||
|
|
||||||
// FSR switching
|
// FSR switching
|
||||||
self.widgets.fsr_switcher.set_state(config.game.enhancements.fsr.enabled);
|
self.widgets.fsr_switcher.set_state(config.game.enhancements.fsr.enabled);
|
||||||
|
|
|
@ -3,8 +3,10 @@ use libadwaita::{self as adw, prelude::*};
|
||||||
|
|
||||||
use gtk::glib;
|
use gtk::glib;
|
||||||
|
|
||||||
use crate::ui::get_object;
|
|
||||||
use crate::lib::config;
|
use crate::lib::config;
|
||||||
|
use crate::lib::config::prelude::*;
|
||||||
|
|
||||||
|
use crate::ui::*;
|
||||||
|
|
||||||
/// This structure is used to describe widgets used in application
|
/// This structure is used to describe widgets used in application
|
||||||
///
|
///
|
||||||
|
@ -174,9 +176,9 @@ impl App {
|
||||||
|
|
||||||
if let Ok(mut config) = config::get() {
|
if let Ok(mut config) = config::get() {
|
||||||
config.game.enhancements.gamescope.window_type = if button.is_active() {
|
config.game.enhancements.gamescope.window_type = if button.is_active() {
|
||||||
config::WindowType::Borderless
|
WindowType::Borderless
|
||||||
} else {
|
} else {
|
||||||
config::WindowType::Fullscreen
|
WindowType::Fullscreen
|
||||||
};
|
};
|
||||||
|
|
||||||
config::update(config);
|
config::update(config);
|
||||||
|
@ -195,9 +197,9 @@ impl App {
|
||||||
|
|
||||||
if let Ok(mut config) = config::get() {
|
if let Ok(mut config) = config::get() {
|
||||||
config.game.enhancements.gamescope.window_type = if button.is_active() {
|
config.game.enhancements.gamescope.window_type = if button.is_active() {
|
||||||
config::WindowType::Fullscreen
|
WindowType::Fullscreen
|
||||||
} else {
|
} else {
|
||||||
config::WindowType::Borderless
|
WindowType::Borderless
|
||||||
};
|
};
|
||||||
|
|
||||||
config::update(config);
|
config::update(config);
|
||||||
|
@ -214,7 +216,7 @@ impl App {
|
||||||
|
|
||||||
status_page.set_description(Some("Loading gamescope..."));
|
status_page.set_description(Some("Loading gamescope..."));
|
||||||
|
|
||||||
fn set_text(widget: >k::Entry, value: u16) {
|
fn set_text(widget: >k::Entry, value: u64) {
|
||||||
widget.set_text(&if value == 0 { String::new() } else { value.to_string() });
|
widget.set_text(&if value == 0 { String::new() } else { value.to_string() });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,8 +233,8 @@ impl App {
|
||||||
self.widgets.nvidia_image_scaling.set_state(config.game.enhancements.gamescope.nvidia_image_scaling);
|
self.widgets.nvidia_image_scaling.set_state(config.game.enhancements.gamescope.nvidia_image_scaling);
|
||||||
|
|
||||||
match config.game.enhancements.gamescope.window_type {
|
match config.game.enhancements.gamescope.window_type {
|
||||||
config::WindowType::Borderless => self.widgets.borderless.set_active(true),
|
WindowType::Borderless => self.widgets.borderless.set_active(true),
|
||||||
config::WindowType::Fullscreen => self.widgets.fullscreen.set_active(true)
|
WindowType::Fullscreen => self.widgets.fullscreen.set_active(true)
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Reference in a new issue