- added gamescope support
- now gamemode option will not be clickabke if gamemode is not installed
- reworked enhancements page's events
- changed winesync env variables
- added `lib::is_available` function to check packages availability
This commit is contained in:
Observer KRypt0n_ 2022-08-03 21:38:01 +02:00
parent a5caf1a217
commit ea6094daf5
No known key found for this signature in database
GPG key ID: 844DA47BA25FE1E2
14 changed files with 631 additions and 140 deletions

2
Cargo.lock generated
View file

@ -51,7 +51,7 @@ dependencies = [
[[package]]
name = "anime-game-launcher"
version = "0.6.3"
version = "0.7.0"
dependencies = [
"anime-game-core",
"dirs",

View file

@ -1,6 +1,6 @@
[package]
name = "anime-game-launcher"
version = "0.6.3"
version = "0.7.0"
description = "Anime Game launcher"
authors = ["Nikita Podvirnyy <suimin.tu.mu.ga.mi@gmail.com>"]
license = "GPL-3.0"

View file

@ -19,6 +19,7 @@
<gresource prefix="/org/app/ui/preferences">
<file preprocess="xml-stripblanks" compressed="true" alias="general.ui">ui/.dist/preferences/general.ui</file>
<file preprocess="xml-stripblanks" compressed="true" alias="enhancements.ui">ui/.dist/preferences/enhancements.ui</file>
<file preprocess="xml-stripblanks" compressed="true" alias="gamescope.ui">ui/.dist/preferences/gamescope.ui</file>
<file preprocess="xml-stripblanks" compressed="true" alias="environment.ui">ui/.dist/preferences/environment.ui</file>
</gresource>
</gresources>

View file

@ -73,7 +73,7 @@ Adw.PreferencesPage page {
}
}
Adw.ActionRow {
Adw.ActionRow gamemode_row {
title: "Gamemode";
subtitle: "This prioritizes the game over the rest of the processes";
@ -81,5 +81,20 @@ Adw.PreferencesPage page {
valign: center;
}
}
Adw.ActionRow gamescope_row {
title: "Gamescope";
Gtk.Button gamescope_settings {
icon-name: "emblem-system-symbolic";
valign: center;
styles ["flat"]
}
Gtk.Switch gamescope_switcher {
valign: center;
}
}
}
}

View file

@ -0,0 +1,111 @@
using Gtk 4.0;
using Adw 1;
Adw.PreferencesWindow window {
title: "Gamescope";
modal: true;
hide-on-close: true;
Adw.PreferencesPage {
Adw.PreferencesGroup {
title: "Game resolution";
Adw.ActionRow {
title: "Width";
Gtk.Entry game_width {
placeholder-text: "0";
valign: center;
input-purpose: digits;
}
}
Adw.ActionRow {
title: "Height";
Gtk.Entry game_height {
placeholder-text: "0";
valign: center;
input-purpose: digits;
}
}
}
Adw.PreferencesGroup {
title: "Gamescope resolution";
Adw.ActionRow {
title: "Width";
Gtk.Entry gamescope_width {
placeholder-text: "0";
valign: center;
input-purpose: digits;
}
}
Adw.ActionRow {
title: "Height";
Gtk.Entry gamescope_height {
placeholder-text: "0";
valign: center;
input-purpose: digits;
}
}
}
Adw.PreferencesGroup {
title: "Other settings";
Adw.ActionRow {
title: "Framerate limit";
Gtk.Entry framerate_limit {
placeholder-text: "0";
valign: center;
input-purpose: digits;
}
}
Adw.ActionRow {
title: "Unfocused framerate limit";
Gtk.Entry framerate_unfocused_limit {
placeholder-text: "0";
valign: center;
input-purpose: digits;
}
}
Adw.ActionRow {
title: "Use integer scaling";
Gtk.Switch integer_scaling {
valign: center;
}
}
Adw.ActionRow {
title: "Window type";
Gtk.Box {
orientation: horizontal;
Gtk.ToggleButton borderless {
label: "Borderless";
valign: center;
}
Gtk.ToggleButton fullscreen {
label: "Fullscreen";
valign: center;
}
styles ["linked"]
}
}
}
}
}

View file

@ -3,10 +3,10 @@ use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::io::{Error, ErrorKind, Write};
use std::process::{Command, Stdio};
use serde::{Serialize, Deserialize};
use crate::lib;
use super::consts::*;
use super::wine::{
Version as WineVersion,
@ -148,9 +148,12 @@ impl Config {
// ????
None
},
None => match Command::new("wine").stdout(Stdio::null()).stderr(Stdio::null()).output() {
Ok(output) => if output.status.success() { Some(String::from("wine")) } else { None },
Err(_) => None
None => {
if lib::is_available("wine") {
Some(String::from("wine"))
} else {
None
}
}
}
}
@ -176,6 +179,65 @@ impl Config {
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 FSR support
if self.game.enhancements.fsr.enabled {
gamescope += " -U";
}
Some(gamescope)
}
else {
None
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -314,7 +376,8 @@ impl Default for Dxvk {
pub struct Enhancements {
pub fsr: Fsr,
pub gamemode: bool,
pub hud: HUD
pub hud: HUD,
pub gamescope: Gamescope
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
@ -347,3 +410,50 @@ impl Fsr {
}
}
}
#[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 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,
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
}
}

View file

@ -44,20 +44,12 @@ impl Into<u32> for WineSync {
impl WineSync {
/// Get environment variables corresponding to used wine sync
pub fn get_env_vars(&self) -> HashMap<&str, &str> {
match self {
Self::None => HashMap::new(),
Self::ESync => HashMap::from([
("WINEESYNC", "1")
]),
Self::FSync => HashMap::from([
("WINEESYNC", "1"),
("WINEFSYNC", "1")
]),
Self::Futex2 => HashMap::from([
("WINEESYNC", "1"),
("WINEFSYNC", "1"),
("WINEFSYNC_FUTEX2", "1")
])
}
HashMap::from([(match self {
Self::None => return HashMap::new(),
Self::ESync => "WINEESYNC",
Self::FSync => "WINEFSYNC",
Self::Futex2 => "WINEFSYNC_FUTEX2"
}, "1")])
}
}

View file

@ -7,7 +7,7 @@ use anime_game_core::telemetry;
use super::consts;
use super::config;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
/*#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Terminal {
GnomeTerminal,
Konsole,
@ -59,7 +59,7 @@ pub fn try_get_terminal() -> Option<Terminal> {
}
None
}
}*/
/// Try to run the game
///
@ -90,26 +90,17 @@ pub fn run(debug: bool) -> std::io::Result<()> {
bash_chain += "gamemoderun ";
}
bash_chain += &format!("'{}' ", wine_executable);
bash_chain += &format!("'{wine_executable}' ");
if debug {
// Is not supported now because new spawned terminal needs
// to have cwd and env variables specified directly
// which is kinda difficult
todo!();
/*match try_get_terminal() {
Some(terminal) => {
command = Command::new(terminal.get_command());
command.args(terminal.get_args("launcher.bat"));
},
None => return Err(Error::new(ErrorKind::Other, "Couldn't find terminal application"))
}*/
} else {
bash_chain += "launcher.bat";
}
else {
bash_chain += "launcher.bat";
// gamescope <params> -- <command to run>
if let Some(gamescope) = config.get_gamescope_command() {
bash_chain = format!("{gamescope} -- {bash_chain}");
}
let bash_chain = match config.game.command {

View file

@ -6,3 +6,24 @@ pub mod wine;
pub mod wine_prefix;
pub mod launcher;
pub mod prettify_bytes;
use std::process::{Command, Stdio};
/// Check if specified binary is available
///
/// ```
/// use crate::lib;
///
/// assert!(lib::is_available("bash"));
/// ```
#[allow(unused_must_use)]
pub fn is_available(binary: &str) -> bool {
match Command::new(binary).stdout(Stdio::null()).stderr(Stdio::null()).spawn() {
Ok(mut child) => {
child.kill();
true
},
Err(_) => false
}
}

View file

@ -6,7 +6,7 @@ use gtk::glib::clone;
use std::rc::Rc;
use std::cell::Cell;
use std::process::{Command, Stdio};
use std::process::Command;
use anime_game_core::prelude::*;
@ -20,6 +20,7 @@ mod page_6;
use crate::ui::*;
use crate::ui::components::progress_bar::*;
use crate::lib;
use crate::lib::wine_prefix::WinePrefix;
use crate::lib::config;
@ -100,14 +101,6 @@ impl Actions {
}
}
/// This enum is used to store some of this application data
///
/// In this example we store a counter here to know what should we increment or decrement
///
/// This must implement `Default` trait
#[derive(Debug, Default)]
pub struct Values;
/// The main application structure
///
/// `Default` macro automatically calls `AppWidgets::default`, i.e. loads UI file and reference its widgets
@ -122,7 +115,6 @@ pub struct Values;
#[derive(Clone)]
pub struct App {
widgets: AppWidgets,
// values: Rc<Cell<Values>>,
actions: Rc<Cell<Option<glib::Sender<Actions>>>>
}
@ -132,7 +124,6 @@ impl App {
// Get default widgets from ui file and add events to them
let result = Self {
widgets: AppWidgets::try_get()?,
// values: Default::default(),
actions: Default::default()
}.init_events().init_actions();
@ -176,20 +167,18 @@ impl App {
match action {
Actions::FirstPageContinue => {
match Command::new("git").stdout(Stdio::null()).spawn() {
Ok(_) => match Command::new("xdelta3").stdout(Stdio::null()).spawn() {
Ok(_) => this.widgets.carousel.scroll_to(&this.widgets.page_3.page, true),
Err(_) => this.widgets.carousel.scroll_to(&this.widgets.page_2.page, true)
},
Err(_) => this.widgets.carousel.scroll_to(&this.widgets.page_2.page, true)
}
this.widgets.carousel.scroll_to({
if lib::is_available("git") && lib::is_available("xdelta3") {
&this.widgets.page_3.page
} else {
&this.widgets.page_2.page
}
}, true);
}
Actions::SecondPageCheck => {
if let Ok(_) = Command::new("git").stdout(Stdio::null()).spawn() {
if let Ok(_) = Command::new("xdelta3").stdout(Stdio::null()).spawn() {
this.widgets.carousel.scroll_to(&this.widgets.page_3.page, true);
}
if lib::is_available("git") && lib::is_available("xdelta3") {
this.widgets.carousel.scroll_to(&this.widgets.page_3.page, true);
}
}

View file

@ -80,7 +80,7 @@ impl AppWidgets {
get_object(&builder, "progress_bar_group")?
),
preferences_stack: PreferencesStack::new()?
preferences_stack: PreferencesStack::new(&window)?
};
// Set devel style to ApplicationWindow if it's debug mode

View file

@ -1,16 +1,16 @@
use gtk4 as gtk;
use libadwaita::{self as adw, prelude::*};
use gtk4::glib;
use gtk4::glib::clone;
use gtk::glib;
use gtk::glib::clone;
use std::rc::Rc;
use std::cell::Cell;
use std::io::Error;
use crate::ui::get_object;
use crate::lib;
use crate::lib::config;
use crate::ui::*;
use super::gamescope::App as GamescopeApp;
/// This structure is used to describe widgets used in application
///
/// `AppWidgets::try_get` function loads UI file from `.assets/ui/.dist` folder and returns structure with references to its widgets
@ -26,11 +26,19 @@ pub struct AppWidgets {
pub hud_combo: adw::ComboRow,
pub fsr_combo: adw::ComboRow,
pub fsr_switcher: gtk::Switch,
pub gamemode_switcher: gtk::Switch
pub gamemode_row: adw::ActionRow,
pub gamemode_switcher: gtk::Switch,
pub gamescope_row: adw::ActionRow,
pub gamescope_settings: gtk::Button,
pub gamescope_switcher: gtk::Switch,
pub gamescope_app: GamescopeApp
}
impl AppWidgets {
fn try_get() -> Result<Self, String> {
fn try_get(window: &adw::ApplicationWindow) -> Result<Self, String> {
let builder = gtk::Builder::from_resource("/org/app/ui/preferences/enhancements.ui");
let result = Self {
@ -42,29 +50,33 @@ impl AppWidgets {
hud_combo: get_object(&builder, "hud_combo")?,
fsr_combo: get_object(&builder, "fsr_combo")?,
fsr_switcher: get_object(&builder, "fsr_switcher")?,
gamemode_switcher: get_object(&builder, "gamemode_switcher")?
gamemode_row: get_object(&builder, "gamemode_row")?,
gamemode_switcher: get_object(&builder, "gamemode_switcher")?,
gamescope_row: get_object(&builder, "gamescope_row")?,
gamescope_settings: get_object(&builder, "gamescope_settings")?,
gamescope_switcher: get_object(&builder, "gamescope_switcher")?,
gamescope_app: GamescopeApp::new(window)?
};
// Disable gamemode row if it's not available
if !lib::is_available("gamemoderun") {
result.gamemode_row.set_sensitive(false);
result.gamemode_row.set_tooltip_text(Some("Gamemode is not installed"));
}
// Disable gamescope row if it's not available
if !lib::is_available("gamescope") {
result.gamescope_row.set_sensitive(false);
result.gamescope_row.set_tooltip_text(Some("Gamescope is not installed"));
}
Ok(result)
}
}
/// This enum is used to describe an action inside of this application
///
/// It may be helpful if you want to add the same event for several widgets, or call an action inside of another action
#[derive(Debug)]
pub enum Actions<T> {
OptionSelection(fn(crate::lib::config::Config, T) -> crate::lib::config::Config, T)
}
/// This enum is used to store some of this application data
///
/// In this example we store a counter here to know what should we increment or decrement
///
/// This must implement `Default` trait
#[derive(Debug, Default, glib::Downgrade)]
pub struct Values;
/// The main application structure
///
/// `Default` macro automatically calls `AppWidgets::default`, i.e. loads UI file and reference its widgets
@ -78,16 +90,14 @@ pub struct Values;
/// That's what we need and what we use in `App::update` method
#[derive(Clone, glib::Downgrade)]
pub struct App {
widgets: AppWidgets,
values: Rc<Cell<Values>>
widgets: AppWidgets
}
impl App {
/// Create new application
pub fn new() -> Result<Self, String> {
pub fn new(window: &adw::ApplicationWindow) -> Result<Self, String> {
let result = Self {
widgets: AppWidgets::try_get()?,
values: Default::default()
widgets: AppWidgets::try_get(window)?
}.init_events();
Ok(result)
@ -96,25 +106,31 @@ impl App {
/// Add default events and values to the widgets
fn init_events(self) -> Self {
// Wine sync selection
self.widgets.sync_combo.connect_selected_notify(clone!(@weak self as this => move |hud| {
this.update(Actions::OptionSelection(|mut config, value| {
config.game.wine.sync = value; config
}, config::WineSync::try_from(hud.selected()).unwrap()));
}));
self.widgets.sync_combo.connect_selected_notify(move |row| {
if let Ok(mut config) = config::get() {
config.game.wine.sync = config::WineSync::try_from(row.selected()).unwrap();
config::update(config);
}
});
// Wine language selection
self.widgets.wine_lang.connect_selected_notify(clone!(@weak self as this => move |hud| {
this.update(Actions::OptionSelection(|mut config, value| {
config.game.wine.language = value; config
}, config::WineLang::try_from(hud.selected()).unwrap()));
}));
self.widgets.wine_lang.connect_selected_notify(move |row| {
if let Ok(mut config) = config::get() {
config.game.wine.language = config::WineLang::try_from(row.selected()).unwrap();
config::update(config);
}
});
// HUD selection
self.widgets.hud_combo.connect_selected_notify(clone!(@weak self as this => move |hud| {
this.update(Actions::OptionSelection(|mut config, value| {
config.game.enhancements.hud = value; config
}, config::HUD::try_from(hud.selected()).unwrap()));
}));
self.widgets.hud_combo.connect_selected_notify(move |row| {
if let Ok(mut config) = config::get() {
config.game.enhancements.hud = config::HUD::try_from(row.selected()).unwrap();
config::update(config);
}
});
// FSR strength selection
//
@ -124,42 +140,47 @@ impl App {
// Performance = 2
//
// Source: Bottles (https://github.com/bottlesdevs/Bottles/blob/22fa3573a13f4e9b9c429e4cdfe4ca29787a2832/src/ui/details-preferences.ui#L88)
self.widgets.fsr_combo.connect_selected_notify(clone!(@weak self as this => move |hud| {
this.update(Actions::OptionSelection(|mut config, value| {
config.game.enhancements.fsr.strength = value; config
}, 5 - hud.selected()));
}));
self.widgets.fsr_combo.connect_selected_notify(move |row| {
if let Ok(mut config) = config::get() {
config.game.enhancements.fsr.strength = 5 - row.selected();
config::update(config);
}
});
// FSR switching
self.widgets.fsr_switcher.connect_state_notify(clone!(@weak self as this => move |switcher| {
this.update(Actions::OptionSelection(|mut config, value| {
config.game.enhancements.fsr.enabled = value; config
}, switcher.state()));
}));
self.widgets.fsr_switcher.connect_state_notify(move |switch| {
if let Ok(mut config) = config::get() {
config.game.enhancements.fsr.enabled = switch.state();
config::update(config);
}
});
// Gamemode switching
self.widgets.gamemode_switcher.connect_state_notify(clone!(@weak self as this => move |switcher| {
this.update(Actions::OptionSelection(|mut config, value| {
config.game.enhancements.gamemode = value; config
}, switcher.state()));
self.widgets.gamemode_switcher.connect_state_notify(move |switch| {
if let Ok(mut config) = config::get() {
config.game.enhancements.gamemode = switch.state();
config::update(config);
}
});
// Gamescope settings app
self.widgets.gamescope_settings.connect_clicked(clone!(@weak self as this => move |_| {
this.widgets.gamescope_app.show();
}));
self
}
// Gamescope swithing
self.widgets.gamescope_switcher.connect_state_notify(move |switch| {
if let Ok(mut config) = config::get() {
config.game.enhancements.gamescope.enabled = switch.state();
/// Update widgets state by calling some action
pub fn update<T>(&self, action: Actions<T>) {
let values = self.values.take();
match action {
Actions::OptionSelection(update, value) => {
if let Ok(config) = config::get() {
config::update((update)(config, value));
}
config::update(config);
}
}
});
self.values.set(values);
self
}
pub fn title() -> String {
@ -171,10 +192,10 @@ impl App {
}
/// This method is being called by the `PreferencesStack::update`
pub fn prepare(&self, status_page: &adw::StatusPage) -> Result<(), Error> {
pub fn prepare(&self, status_page: &adw::StatusPage) -> std::io::Result<()> {
let config = config::get()?;
status_page.set_description(Some("Loading preferences..."));
status_page.set_description(Some("Loading enhancements..."));
// Update Wine sync
self.widgets.sync_combo.set_selected(config.game.wine.sync.into());
@ -194,6 +215,9 @@ impl App {
// Gamemode switching
self.widgets.gamemode_switcher.set_state(config.game.enhancements.gamemode);
// Prepare gamescope settings app
self.widgets.gamescope_app.prepare(status_page)?;
Ok(())
}
}

View file

@ -0,0 +1,235 @@
use gtk4 as gtk;
use libadwaita::{self as adw, prelude::*};
use gtk::glib;
use crate::ui::get_object;
use crate::lib::config;
/// This structure is used to describe widgets used in application
///
/// `AppWidgets::try_get` function loads UI file from `.assets/ui/.dist` folder and returns structure with references to its widgets
///
/// This function does not implement events
#[derive(Clone, glib::Downgrade)]
pub struct AppWidgets {
pub window: adw::PreferencesWindow,
pub game_width: gtk::Entry,
pub game_height: gtk::Entry,
pub gamescope_width: gtk::Entry,
pub gamescope_height: gtk::Entry,
pub framerate_limit: gtk::Entry,
pub framerate_unfocused_limit: gtk::Entry,
pub integer_scaling: gtk::Switch,
pub borderless: gtk::ToggleButton,
pub fullscreen: gtk::ToggleButton
}
impl AppWidgets {
fn try_get(window: &adw::ApplicationWindow) -> Result<Self, String> {
let builder = gtk::Builder::from_resource("/org/app/ui/preferences/gamescope.ui");
let result = Self {
window: get_object(&builder, "window")?,
game_width: get_object(&builder, "game_width")?,
game_height: get_object(&builder, "game_height")?,
gamescope_width: get_object(&builder, "gamescope_width")?,
gamescope_height: get_object(&builder, "gamescope_height")?,
framerate_limit: get_object(&builder, "framerate_limit")?,
framerate_unfocused_limit: get_object(&builder, "framerate_unfocused_limit")?,
integer_scaling: get_object(&builder, "integer_scaling")?,
borderless: get_object(&builder, "borderless")?,
fullscreen: get_object(&builder, "fullscreen")?
};
result.window.set_transient_for(Some(window));
Ok(result)
}
}
/// The main application structure
///
/// `Default` macro automatically calls `AppWidgets::default`, i.e. loads UI file and reference its widgets
///
/// `Rc<Cell<Values>>` means this:
/// - `Rc` addeds ability to reference the same value from various clones of the structure.
/// This will guarantee us that inner `Cell<Values>` is the same for all the `App::clone()` values
/// - `Cell` addeds inner mutability to its value, so we can mutate it even without mutable reference.
///
/// So we have a shared reference to some value that can be changed without mutable reference.
/// That's what we need and what we use in `App::update` method
#[derive(Clone, glib::Downgrade)]
pub struct App {
widgets: AppWidgets
}
impl App {
/// Create new application
pub fn new(window: &adw::ApplicationWindow) -> Result<Self, String> {
let result = Self {
widgets: AppWidgets::try_get(window)?
}.init_events();
Ok(result)
}
/// Add default events and values to the widgets
fn init_events(self) -> Self {
// Game width
self.widgets.game_width.connect_changed(move |entry| {
if let Ok(mut config) = config::get() {
config.game.enhancements.gamescope.game.width = entry.text().parse().unwrap_or(0);
config::update(config);
}
});
// Game height
self.widgets.game_height.connect_changed(move |entry| {
if let Ok(mut config) = config::get() {
config.game.enhancements.gamescope.game.height = entry.text().parse().unwrap_or(0);
config::update(config);
}
});
// Gamescope width
self.widgets.gamescope_width.connect_changed(move |entry| {
if let Ok(mut config) = config::get() {
config.game.enhancements.gamescope.gamescope.width = entry.text().parse().unwrap_or(0);
config::update(config);
}
});
// Gamescope height
self.widgets.gamescope_height.connect_changed(move |entry| {
if let Ok(mut config) = config::get() {
config.game.enhancements.gamescope.gamescope.height = entry.text().parse().unwrap_or(0);
config::update(config);
}
});
// Framerate focused
self.widgets.framerate_limit.connect_changed(move |entry| {
if let Ok(mut config) = config::get() {
config.game.enhancements.gamescope.framerate.focused = entry.text().parse().unwrap_or(0);
config::update(config);
}
});
// Framerate unfocused
self.widgets.framerate_unfocused_limit.connect_changed(move |entry| {
if let Ok(mut config) = config::get() {
config.game.enhancements.gamescope.framerate.unfocused = entry.text().parse().unwrap_or(0);
config::update(config);
}
});
// Use integer scaling
self.widgets.integer_scaling.connect_state_notify(move |switch| {
if let Ok(mut config) = config::get() {
config.game.enhancements.gamescope.integer_scaling = switch.state();
config::update(config);
}
});
// Window type
let borderless = self.widgets.borderless.clone();
let fullscreen = self.widgets.fullscreen.clone();
// Window type (Borderless)
self.widgets.borderless.connect_clicked(move |button| {
if !button.is_active() {
button.activate();
}
else {
fullscreen.set_active(false);
if let Ok(mut config) = config::get() {
config.game.enhancements.gamescope.window_type = if button.is_active() {
config::WindowType::Borderless
} else {
config::WindowType::Fullscreen
};
config::update(config);
}
}
});
// Window type (Fullscreen)
self.widgets.fullscreen.connect_clicked(move |button| {
if !button.is_active() {
button.activate();
}
else {
borderless.set_active(false);
if let Ok(mut config) = config::get() {
config.game.enhancements.gamescope.window_type = if button.is_active() {
config::WindowType::Fullscreen
} else {
config::WindowType::Borderless
};
config::update(config);
}
}
});
self
}
/// This method is being called by the `EnhancementsPage::prepare`
pub fn prepare(&self, status_page: &adw::StatusPage) -> std::io::Result<()> {
let config = config::get()?;
status_page.set_description(Some("Loading gamescope..."));
fn set_text(widget: &gtk::Entry, value: u16) {
widget.set_text(&if value == 0 { String::new() } else { value.to_string() });
}
set_text(&self.widgets.game_width, config.game.enhancements.gamescope.game.width);
set_text(&self.widgets.game_height, config.game.enhancements.gamescope.game.height);
set_text(&self.widgets.gamescope_width, config.game.enhancements.gamescope.gamescope.width);
set_text(&self.widgets.gamescope_height, config.game.enhancements.gamescope.gamescope.height);
set_text(&self.widgets.framerate_limit, config.game.enhancements.gamescope.framerate.focused);
set_text(&self.widgets.framerate_unfocused_limit, config.game.enhancements.gamescope.framerate.unfocused);
self.widgets.integer_scaling.set_state(config.game.enhancements.gamescope.integer_scaling);
match config.game.enhancements.gamescope.window_type {
config::WindowType::Borderless => self.widgets.borderless.set_active(true),
config::WindowType::Fullscreen => self.widgets.fullscreen.set_active(true)
};
Ok(())
}
pub fn show(&self) {
self.widgets.window.show();
}
}
unsafe impl Send for App {}
unsafe impl Sync for App {}

View file

@ -14,6 +14,8 @@ mod general;
mod enhancements;
mod environment;
pub mod gamescope;
pub mod pages {
pub use super::general::App as GeneralPage;
pub use super::enhancements::App as EnhancementsPage;
@ -38,7 +40,7 @@ pub struct PreferencesStack {
}
impl PreferencesStack {
pub fn new() -> Result<Self, String> {
pub fn new(window: &adw::ApplicationWindow) -> Result<Self, String> {
let builder = gtk::Builder::from_resource("/org/app/ui/preferences.ui");
let result = Self {
@ -53,7 +55,7 @@ impl PreferencesStack {
stack: get_object(&builder, "stack")?,
general_page: pages::GeneralPage::new()?,
enhancements_page: pages::EnhancementsPage::new()?,
enhancements_page: pages::EnhancementsPage::new(window)?,
environment_page: pages::EnvironmentPage::new()?
};