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:
Observer KRypt0n_ 2022-08-04 11:47:57 +02:00
parent 82691f70f9
commit bf31327532
No known key found for this signature in database
GPG key ID: 844DA47BA25FE1E2
22 changed files with 902 additions and 393 deletions

View file

@ -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"
]
};
} }
} }

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

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

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

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

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

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

View file

@ -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;

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

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

View 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"
})])
}
}

View file

@ -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;

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

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

View file

@ -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
View 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
}
}
}
}

View file

@ -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"
})])
}
}

View file

@ -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}");
} }

View file

@ -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");

View file

@ -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);

View file

@ -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: &gtk::Entry, value: u16) { fn set_text(widget: &gtk::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(())