mirror of
https://github.com/an-anime-team/sleepy-launcher.git
synced 2024-11-22 04:40:06 +03:00
Added support for FPS unlocker
This commit is contained in:
parent
e7c0228fdd
commit
a7b1345302
13 changed files with 430 additions and 15 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -60,6 +60,7 @@ dependencies = [
|
|||
"gtk4",
|
||||
"lazy_static",
|
||||
"libadwaita",
|
||||
"md5",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
|
@ -29,3 +29,4 @@ wait_not_await = "0.2.1"
|
|||
regex = "1.6.0"
|
||||
lazy_static = "1.4.0"
|
||||
anyhow = "1.0"
|
||||
md5 = "0.7"
|
||||
|
|
|
@ -99,4 +99,26 @@ Adw.PreferencesPage page {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Adw.PreferencesGroup {
|
||||
title: "FPS Unlocker";
|
||||
|
||||
Adw.ComboRow fps_unlocker_combo {
|
||||
title: "Enabled";
|
||||
subtitle: "Remove frames rendering limitation modifying the game's memory. Can be detected by the anti-cheat";
|
||||
|
||||
Gtk.Switch fps_unlocker_switcher {
|
||||
valign: center;
|
||||
}
|
||||
}
|
||||
|
||||
Adw.ActionRow {
|
||||
title: "Power saving";
|
||||
subtitle: "Automatically sets the fps limit to 10 and low process priority upon losing focus to the game (e.g. tabbing out of the game)";
|
||||
|
||||
Gtk.Switch fps_unlocker_power_saving_switcher {
|
||||
valign: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
67
src/lib/config/game/enhancements/fps_unlocker/config/fps.rs
Normal file
67
src/lib/config/game/enhancements/fps_unlocker/config/fps.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
use gtk4 as gtk;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Fps {
|
||||
/// 90
|
||||
Ninety,
|
||||
|
||||
/// 100
|
||||
Hundred,
|
||||
|
||||
/// 120
|
||||
HundredTwenty,
|
||||
|
||||
/// 144
|
||||
HundredFourtyFour,
|
||||
|
||||
/// 200
|
||||
TwoHundred,
|
||||
|
||||
Custom(u64)
|
||||
}
|
||||
|
||||
impl Fps {
|
||||
pub fn list() -> Vec<Self> {
|
||||
vec![
|
||||
Self::Ninety,
|
||||
Self::Hundred,
|
||||
Self::HundredTwenty,
|
||||
Self::HundredFourtyFour,
|
||||
Self::TwoHundred
|
||||
]
|
||||
}
|
||||
|
||||
pub fn get_model() -> gtk::StringList {
|
||||
let model = gtk::StringList::new(&[]);
|
||||
|
||||
model.append("Custom");
|
||||
|
||||
for res in Self::list() {
|
||||
model.append(&res.to_num().to_string());
|
||||
}
|
||||
|
||||
model
|
||||
}
|
||||
|
||||
pub fn from_num(fps: u64) -> Self {
|
||||
match fps {
|
||||
90 => Self::Ninety,
|
||||
100 => Self::Hundred,
|
||||
120 => Self::HundredTwenty,
|
||||
144 => Self::HundredFourtyFour,
|
||||
200 => Self::TwoHundred,
|
||||
num => Self::Custom(num)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_num(&self) -> u64 {
|
||||
match self {
|
||||
Fps::Ninety => 90,
|
||||
Fps::Hundred => 100,
|
||||
Fps::HundredTwenty => 120,
|
||||
Fps::HundredFourtyFour => 144,
|
||||
Fps::TwoHundred => 200,
|
||||
Fps::Custom(num) => *num
|
||||
}
|
||||
}
|
||||
}
|
41
src/lib/config/game/enhancements/fps_unlocker/config/mod.rs
Normal file
41
src/lib/config/game/enhancements/fps_unlocker/config/mod.rs
Normal file
|
@ -0,0 +1,41 @@
|
|||
use serde::{Serialize, Deserialize};
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
pub mod fps;
|
||||
|
||||
pub mod prelude {
|
||||
pub use super::fps::Fps;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
pub fps: u64,
|
||||
pub power_saving: bool
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
fps: 120,
|
||||
power_saving: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&JsonValue> for Config {
|
||||
fn from(value: &JsonValue) -> Self {
|
||||
let default = Self::default();
|
||||
|
||||
Self {
|
||||
fps: match value.get("fps") {
|
||||
Some(value) => value.as_u64().unwrap_or(default.fps),
|
||||
None => default.fps
|
||||
},
|
||||
|
||||
power_saving: match value.get("power_saving") {
|
||||
Some(value) => value.as_bool().unwrap_or(default.power_saving),
|
||||
None => default.power_saving
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
56
src/lib/config/game/enhancements/fps_unlocker/mod.rs
Normal file
56
src/lib/config/game/enhancements/fps_unlocker/mod.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
use serde::{Serialize, Deserialize};
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
use crate::lib::consts::launcher_dir;
|
||||
|
||||
pub mod config;
|
||||
|
||||
pub mod prelude {
|
||||
pub use super::config::Config;
|
||||
|
||||
pub use super::config::prelude::*;
|
||||
}
|
||||
|
||||
use prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FpsUnlocker {
|
||||
pub path: String,
|
||||
pub enabled: bool,
|
||||
pub config: Config
|
||||
}
|
||||
|
||||
impl Default for FpsUnlocker {
|
||||
fn default() -> Self {
|
||||
let launcher_dir = launcher_dir().expect("Failed to get launcher dir");
|
||||
|
||||
Self {
|
||||
path: format!("{launcher_dir}/fps-unlocker"),
|
||||
enabled: false,
|
||||
config: Config::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&JsonValue> for FpsUnlocker {
|
||||
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
|
||||
},
|
||||
|
||||
enabled: match value.get("enabled") {
|
||||
Some(value) => value.as_bool().unwrap_or(default.enabled),
|
||||
None => default.enabled
|
||||
},
|
||||
|
||||
config: match value.get("config") {
|
||||
Some(value) => Config::from(value),
|
||||
None => default.config
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,23 +3,27 @@ use serde_json::Value as JsonValue;
|
|||
|
||||
pub mod fsr;
|
||||
pub mod hud;
|
||||
pub mod fps_unlocker;
|
||||
pub mod gamescope;
|
||||
|
||||
pub mod prelude {
|
||||
pub use super::gamescope::prelude::*;
|
||||
pub use super::fps_unlocker::prelude::*;
|
||||
|
||||
pub use super::Enhancements;
|
||||
pub use super::fsr::Fsr;
|
||||
pub use super::hud::HUD;
|
||||
pub use super::fps_unlocker::FpsUnlocker;
|
||||
}
|
||||
|
||||
use prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct Enhancements {
|
||||
pub fsr: Fsr,
|
||||
pub gamemode: bool,
|
||||
pub hud: HUD,
|
||||
pub fps_unlocker: FpsUnlocker,
|
||||
pub gamescope: Gamescope
|
||||
}
|
||||
|
||||
|
@ -43,6 +47,11 @@ impl From<&JsonValue> for Enhancements {
|
|||
None => default.hud
|
||||
},
|
||||
|
||||
fps_unlocker: match value.get("fps_unlocker") {
|
||||
Some(value) => FpsUnlocker::from(value),
|
||||
None => default.fps_unlocker
|
||||
},
|
||||
|
||||
gamescope: match value.get("gamescope") {
|
||||
Some(value) => Gamescope::from(value),
|
||||
None => default.gamescope
|
||||
|
|
64
src/lib/fps_unlocker/config_schema.rs
Normal file
64
src/lib/fps_unlocker/config_schema.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use super::FpsUnlockerConfig;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[allow(non_snake_case)]
|
||||
pub struct ConfigSchema {
|
||||
pub DllList: Vec<String>,
|
||||
pub Priority: u64,
|
||||
pub MonitorNum: u64,
|
||||
pub CustomResY: u64,
|
||||
pub CustomResX: u64,
|
||||
pub FPSTarget: u64,
|
||||
pub UsePowerSave: bool,
|
||||
pub StartMinimized: bool,
|
||||
pub IsExclusiveFullscreen: bool,
|
||||
pub UseCustomRes: bool,
|
||||
pub Fullscreen: bool,
|
||||
pub PopupWindow: bool,
|
||||
pub AutoClose: bool,
|
||||
pub AutoDisableVSync: bool,
|
||||
pub AutoStart: bool,
|
||||
pub GamePath: Option<String>
|
||||
}
|
||||
|
||||
impl Default for ConfigSchema {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
DllList: vec![],
|
||||
Priority: 3,
|
||||
MonitorNum: 1,
|
||||
CustomResY: 1080,
|
||||
CustomResX: 1920,
|
||||
FPSTarget: 120,
|
||||
UsePowerSave: false,
|
||||
IsExclusiveFullscreen: false,
|
||||
UseCustomRes: false,
|
||||
Fullscreen: false,
|
||||
PopupWindow: false,
|
||||
AutoDisableVSync: true,
|
||||
GamePath: None,
|
||||
|
||||
// Launcher-specific settings
|
||||
AutoStart: true,
|
||||
AutoClose: true,
|
||||
StartMinimized: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigSchema {
|
||||
pub fn from_config(config: FpsUnlockerConfig) -> Self {
|
||||
Self {
|
||||
FPSTarget: config.fps,
|
||||
UsePowerSave: config.power_saving,
|
||||
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn json(&self) -> serde_json::Result<String> {
|
||||
serde_json::to_string(self)
|
||||
}
|
||||
}
|
67
src/lib/fps_unlocker/mod.rs
Normal file
67
src/lib/fps_unlocker/mod.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
use anime_game_core::installer::downloader::Downloader;
|
||||
|
||||
use crate::lib::config::game::enhancements::fps_unlocker::config::Config as FpsUnlockerConfig;
|
||||
|
||||
pub mod config_schema;
|
||||
|
||||
const LATEST_INFO: (&str, &str) = (
|
||||
"6040a6f0be5dbf4d55d6b129cad47b5b",
|
||||
"https://github.com/34736384/genshin-fps-unlock/releases/download/v2.0.0/unlockfps_clr.exe"
|
||||
);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct FpsUnlocker {
|
||||
dir: String
|
||||
}
|
||||
|
||||
impl FpsUnlocker {
|
||||
/// Get FpsUnlocker from its containment directory
|
||||
///
|
||||
/// Returns
|
||||
/// - `Err(..)` if failed to read `unlocker.exe` file
|
||||
/// - `Ok(None)` if version is not latest
|
||||
/// - `Ok(..)` if version is latest
|
||||
pub fn from_dir<T: ToString>(dir: T) -> anyhow::Result<Option<Self>> {
|
||||
let hash = format!("{:x}", md5::compute(std::fs::read(format!("{}/unlocker.exe", dir.to_string()))?));
|
||||
|
||||
if hash == LATEST_INFO.0 {
|
||||
Ok(Some(Self { dir: dir.to_string() }))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Download FPS unlocker to specified directory
|
||||
pub fn download<T: ToString>(dir: T) -> anyhow::Result<Self> {
|
||||
let mut downloader = Downloader::new(LATEST_INFO.1)?;
|
||||
|
||||
match downloader.download_to(format!("{}/unlocker.exe", dir.to_string()), |_, _| {}) {
|
||||
Ok(_) => Ok(Self {
|
||||
dir: dir.to_string()
|
||||
}),
|
||||
Err(err) => {
|
||||
let err: std::io::Error = err.into();
|
||||
|
||||
Err(err.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_binary(&self) -> String {
|
||||
format!("{}/unlocker.exe", self.dir)
|
||||
}
|
||||
|
||||
pub fn dir(&self) -> &str {
|
||||
self.dir.as_str()
|
||||
}
|
||||
|
||||
/// Generate and save FPS unlocker config file to the game's directory
|
||||
pub fn update_config(&self, config: FpsUnlockerConfig) -> anyhow::Result<()> {
|
||||
let config = config_schema::ConfigSchema::from_config(config);
|
||||
|
||||
Ok(std::fs::write(
|
||||
format!("{}/fps_config.json", self.dir),
|
||||
config.json()?
|
||||
)?)
|
||||
}
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
use std::io::{Error, ErrorKind};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
|
@ -6,6 +5,7 @@ use anime_game_core::genshin::telemetry;
|
|||
|
||||
use super::consts;
|
||||
use super::config;
|
||||
use super::fps_unlocker::FpsUnlocker;
|
||||
|
||||
/*#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Terminal {
|
||||
|
@ -64,22 +64,51 @@ pub fn try_get_terminal() -> Option<Terminal> {
|
|||
/// Try to run the game
|
||||
///
|
||||
/// If `debug = true`, then the game will be run in the new terminal window
|
||||
pub fn run(debug: bool) -> std::io::Result<()> {
|
||||
pub fn run() -> anyhow::Result<()> {
|
||||
let config = config::get()?;
|
||||
|
||||
if !Path::new(&config.game.path).exists() {
|
||||
return Err(Error::new(ErrorKind::Other, "Game is not installed"));
|
||||
return Err(anyhow::anyhow!("Game is not installed"));
|
||||
}
|
||||
|
||||
let wine_executable = match config.try_get_wine_executable() {
|
||||
Some(path) => path,
|
||||
None => return Err(Error::new(ErrorKind::Other, "Couldn't find wine executable"))
|
||||
None => return Err(anyhow::anyhow!("Couldn't find wine executable"))
|
||||
};
|
||||
|
||||
// Check telemetry servers
|
||||
|
||||
if let Some(server) = telemetry::is_disabled(consts::TELEMETRY_CHECK_TIMEOUT) {
|
||||
return Err(Error::new(ErrorKind::Other, format!("Telemetry server is not disabled: {server}")));
|
||||
return Err(anyhow::anyhow!("Telemetry server is not disabled: {server}"));
|
||||
}
|
||||
|
||||
// Prepare fps unlocker
|
||||
// 1) Download if needed
|
||||
// 2) Generate config file
|
||||
// 3) Generate fpsunlocker.bat from launcher.bat
|
||||
|
||||
if config.game.enhancements.fps_unlocker.enabled {
|
||||
let unlocker = match FpsUnlocker::from_dir(&config.game.enhancements.fps_unlocker.path) {
|
||||
Ok(Some(unlocker)) => unlocker,
|
||||
Ok(None) => match FpsUnlocker::download(&config.game.enhancements.fps_unlocker.path) {
|
||||
Ok(unlocker) => unlocker,
|
||||
Err(err) => return Err(anyhow::anyhow!("Failed to download FPS unlocker: {err}"))
|
||||
},
|
||||
Err(err) => return Err(anyhow::anyhow!("Failed to load FPS unlocker: {err}"))
|
||||
};
|
||||
|
||||
// Generate FPS unlocker config file
|
||||
if let Err(err) = unlocker.update_config(config.game.enhancements.fps_unlocker.config.clone()) {
|
||||
return Err(anyhow::anyhow!("Failed to update FPS unlocker config: {err}"));
|
||||
}
|
||||
|
||||
let bat_path = format!("{}/fpsunlocker.bat", config.game.path);
|
||||
let original_bat_path = format!("{}/launcher.bat", config.game.path);
|
||||
|
||||
// Generate fpsunlocker.bat from launcher.bat
|
||||
std::fs::write(bat_path, std::fs::read_to_string(original_bat_path)?
|
||||
.replace("start GenshinImpact.exe %*", &format!("start GenshinImpact.exe %*\n\nZ:\ncd \"{}\"\nstart unlocker.exe", unlocker.dir()))
|
||||
.replace("start YuanShen.exe %*", &format!("start YuanShen.exe %*\n\nZ:\ncd \"{}\"\nstart unlocker.exe", unlocker.dir())))?;
|
||||
}
|
||||
|
||||
// Prepare bash -c '<command>'
|
||||
|
@ -96,11 +125,7 @@ pub fn run(debug: bool) -> std::io::Result<()> {
|
|||
bash_chain += &format!("{virtual_desktop} ");
|
||||
}
|
||||
|
||||
if debug {
|
||||
todo!();
|
||||
} else {
|
||||
bash_chain += "launcher.bat ";
|
||||
}
|
||||
bash_chain += if config.game.enhancements.fps_unlocker.enabled { "fpsunlocker.bat " } else { "launcher.bat " };
|
||||
|
||||
if config.game.wine.borderless {
|
||||
bash_chain += "-screen-fullscreen 0 -popupwindow ";
|
||||
|
@ -150,6 +175,6 @@ pub fn run(debug: bool) -> std::io::Result<()> {
|
|||
println!("Running command: bash -c \"{}\"", bash_chain);
|
||||
|
||||
command.current_dir(config.game.path).spawn()?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ pub mod wine;
|
|||
pub mod wine_prefix;
|
||||
pub mod launcher;
|
||||
pub mod prettify_bytes;
|
||||
pub mod fps_unlocker;
|
||||
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
|
|
|
@ -318,7 +318,7 @@ impl App {
|
|||
|
||||
std::thread::spawn(move || {
|
||||
// Display toast message if the game is failed to run
|
||||
if let Err(err) = game::run(false) {
|
||||
if let Err(err) = game::run() {
|
||||
this.widgets.window.show();
|
||||
|
||||
this.update(Actions::Toast(Rc::new((
|
||||
|
|
|
@ -38,7 +38,11 @@ pub struct AppWidgets {
|
|||
pub gamescope_settings: gtk::Button,
|
||||
pub gamescope_switcher: gtk::Switch,
|
||||
|
||||
pub gamescope_app: GamescopeApp
|
||||
pub gamescope_app: GamescopeApp,
|
||||
|
||||
pub fps_unlocker_combo: adw::ComboRow,
|
||||
pub fps_unlocker_switcher: gtk::Switch,
|
||||
pub fps_unlocker_power_saving_switcher: gtk::Switch
|
||||
}
|
||||
|
||||
impl AppWidgets {
|
||||
|
@ -65,7 +69,11 @@ impl AppWidgets {
|
|||
gamescope_settings: get_object(&builder, "gamescope_settings")?,
|
||||
gamescope_switcher: get_object(&builder, "gamescope_switcher")?,
|
||||
|
||||
gamescope_app: GamescopeApp::new(window)?
|
||||
gamescope_app: GamescopeApp::new(window)?,
|
||||
|
||||
fps_unlocker_combo: get_object(&builder, "fps_unlocker_combo")?,
|
||||
fps_unlocker_switcher: get_object(&builder, "fps_unlocker_switcher")?,
|
||||
fps_unlocker_power_saving_switcher: get_object(&builder, "fps_unlocker_power_saving_switcher")?
|
||||
};
|
||||
|
||||
// Set availale wine languages
|
||||
|
@ -74,6 +82,9 @@ impl AppWidgets {
|
|||
// Set availale virtual desktop resolutions
|
||||
result.virtual_desktop_row.set_model(Some(&Resolution::get_model()));
|
||||
|
||||
// Set availale fps unlocker limits
|
||||
result.fps_unlocker_combo.set_model(Some(&Fps::get_model()));
|
||||
|
||||
// Disable gamemode row if it's not available
|
||||
if !lib::is_available("gamemoderun") {
|
||||
result.gamemode_row.set_sensitive(false);
|
||||
|
@ -227,6 +238,35 @@ impl App {
|
|||
}
|
||||
});
|
||||
|
||||
// FPS unlocker swithing
|
||||
self.widgets.fps_unlocker_switcher.connect_state_notify(move |switch| {
|
||||
if let Ok(mut config) = config::get() {
|
||||
config.game.enhancements.fps_unlocker.enabled = switch.state();
|
||||
|
||||
config::update(config);
|
||||
}
|
||||
});
|
||||
|
||||
// FPS unlocker -> fps limit combo
|
||||
self.widgets.fps_unlocker_combo.connect_selected_notify(move |row| {
|
||||
if let Ok(mut config) = config::get() {
|
||||
if row.selected() > 0 {
|
||||
config.game.enhancements.fps_unlocker.config.fps = Fps::list()[row.selected() as usize - 1].to_num();
|
||||
|
||||
config::update(config);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// FPS unlocker -> power saving swithing
|
||||
self.widgets.fps_unlocker_power_saving_switcher.connect_state_notify(move |switch| {
|
||||
if let Ok(mut config) = config::get() {
|
||||
config.game.enhancements.fps_unlocker.config.power_saving = switch.state();
|
||||
|
||||
config::update(config);
|
||||
}
|
||||
});
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -288,6 +328,27 @@ impl App {
|
|||
// Switch gamescope option
|
||||
self.widgets.gamescope_switcher.set_state(config.game.enhancements.gamescope.enabled);
|
||||
|
||||
// Switch FPS unlocker
|
||||
self.widgets.fps_unlocker_switcher.set_state(config.game.enhancements.fps_unlocker.enabled);
|
||||
|
||||
// Select FPS limit
|
||||
let fps = Fps::from_num(config.game.enhancements.fps_unlocker.config.fps);
|
||||
|
||||
if let Fps::Custom(_) = fps {
|
||||
self.widgets.fps_unlocker_combo.set_selected(0);
|
||||
}
|
||||
|
||||
else {
|
||||
for (i, value) in Fps::list().into_iter().enumerate() {
|
||||
if value == fps {
|
||||
self.widgets.fps_unlocker_combo.set_selected(i as u32 + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Switch FPS unlocker -> power saving
|
||||
self.widgets.fps_unlocker_power_saving_switcher.set_state(config.game.enhancements.fps_unlocker.config.power_saving);
|
||||
|
||||
// Prepare gamescope settings app
|
||||
self.widgets.gamescope_app.prepare(status_page)?;
|
||||
|
||||
|
|
Loading…
Reference in a new issue