mirror of
https://github.com/an-anime-team/sleepy-launcher.git
synced 2024-11-22 04:40:06 +03:00
0.7.0
- 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:
parent
a5caf1a217
commit
ea6094daf5
14 changed files with 631 additions and 140 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -51,7 +51,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anime-game-launcher"
|
||||
version = "0.6.3"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"anime-game-core",
|
||||
"dirs",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
111
assets/ui/preferences/gamescope.blp
Normal file
111
assets/ui/preferences/gamescope.blp
Normal 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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
235
src/ui/preferences/gamescope.rs
Normal file
235
src/ui/preferences/gamescope.rs
Normal 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: >k::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 {}
|
|
@ -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()?
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue