Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

desktop: Add gamemode support #17940

Merged
merged 6 commits into from
Oct 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ unknown-git = "deny"
# github.com organizations to allow git sources for
github = [
"ruffle-rs",
# TODO: Remove once a release with https://github.com/bilelmoussaoui/ashpd/pull/234 in it is out.
"bilelmoussaoui",
]

[advisories]
Expand Down
1 change: 1 addition & 0 deletions desktop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ thiserror.workspace = true

[target.'cfg(target_os = "linux")'.dependencies]
zbus = "4.4.0"
ashpd = { git = "https://github.com/bilelmoussaoui/ashpd.git", rev = "34c0ab8f83cd16c3190ecc4cc51021daa531d89b" }

[target.'cfg(windows)'.dependencies]
winapi = "0.3.9"
Expand Down
2 changes: 1 addition & 1 deletion desktop/assets/texts/en-US/common.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ cancel = Cancel
remove = Remove

enable = Enable
disable = Disable
disable = Disable
8 changes: 8 additions & 0 deletions desktop/assets/texts/en-US/preferences_dialog.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,11 @@ theme = Theme
theme-system = System Default
theme-light = Light
theme-dark = Dark

# See for context https://github.com/FeralInteractive/gamemode
gamemode = GameMode
gamemode-tooltip =
GameMode temporarily applies a set of optimizations to your computer and/or Ruffle.
Ruffle requests GameMode only when a movie is being played.
gamemode-default = Default
gamemode-default-tooltip = GameMode will be enabled only when power preference is set to high.
44 changes: 44 additions & 0 deletions desktop/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use ruffle_core::{LoadBehavior, PlayerRuntime, StageAlign, StageScaleMode};
use ruffle_render::quality::StageQuality;
use ruffle_render_wgpu::clap::{GraphicsBackend, PowerPreference};
use std::path::Path;
use std::str::FromStr;
use std::time::Duration;
use url::Url;

Expand Down Expand Up @@ -62,6 +63,19 @@ pub struct Opt {
#[clap(long, short)]
pub power: Option<PowerPreference>,

/// GameMode preference.
///
/// This allows enabling or disabling GameMode manually.
/// When enabled, GameMode will be requested only when a movie is loaded.
///
/// The default preference enables GameMode when power preference is set to high.
/// This option temporarily overrides any stored preference.
///
/// See <https://github.com/FeralInteractive/gamemode>.
#[clap(long)]
#[cfg_attr(not(target_os = "linux"), clap(hide = true))]
pub gamemode: Option<GameModePreference>,

/// Type of storage backend to use. This determines where local storage data is saved (e.g. shared objects).
///
/// This option temporarily overrides any stored preference.
Expand Down Expand Up @@ -298,6 +312,36 @@ impl Opt {
}
}

#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, clap::ValueEnum)]
pub enum GameModePreference {
#[default]
Default,
On,
Off,
}

impl GameModePreference {
pub fn as_str(&self) -> Option<&'static str> {
match self {
GameModePreference::Default => None,
GameModePreference::On => Some("on"),
GameModePreference::Off => Some("off"),
}
}
}

impl FromStr for GameModePreference {
type Err = ();

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"on" => Ok(GameModePreference::On),
"off" => Ok(GameModePreference::Off),
_ => Err(()),
}
}
}

// TODO The following enum exists in order to preserve
// the behavior of mapping gamepad buttons,
// We should probably do something smarter here.
Expand Down
56 changes: 56 additions & 0 deletions desktop/src/dbus.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#![cfg(target_os = "linux")]
//! Types and methods utilized for communicating with D-Bus

use std::mem;
use std::sync::{Arc, Mutex};

use futures::StreamExt;
use zbus::export::futures_core::Stream;
use zbus::zvariant::{OwnedValue, Value};
Expand Down Expand Up @@ -80,3 +83,56 @@ impl<'p> FreedesktopSettings<'p> {
}
}
}

pub struct GameModeSession {
_guard: Arc<Mutex<Option<GameModeGuard>>>,
}

impl GameModeSession {
pub fn new(enabled: bool) -> Self {
let guard = Arc::new(Mutex::new(None));
let guard2 = guard.clone();
tokio::spawn(async move {
let game_mode_guard = GameModeGuard::new(enabled).await;
*guard2.lock().expect("Non-poisoned gamemode guard") = Some(game_mode_guard);
});
Self { _guard: guard }
}
}

struct GameModeGuard {
gamemode: Option<ashpd::desktop::game_mode::GameMode<'static>>,
}

impl GameModeGuard {
async fn new(enabled: bool) -> Self {
if !enabled {
return Self { gamemode: None };
}

let gamemode = ashpd::desktop::game_mode::GameMode::new()
.await
.inspect_err(|err| tracing::warn!("Failed to initialize gamemode controller: {}", err))
.ok();

if let Some(gamemode) = &gamemode {
if let Err(err) = gamemode.register(std::process::id()).await {
tracing::warn!("Failed to register a game with gamemode: {}", err)
}
}

Self { gamemode }
}
}

impl Drop for GameModeGuard {
fn drop(&mut self) {
if let Some(gamemode) = mem::take(&mut self.gamemode) {
tokio::spawn(async move {
if let Err(err) = gamemode.unregister(std::process::id()).await {
tracing::warn!("Failed to unregister a game with gamemode: {}", err)
}
});
}
}
}
77 changes: 77 additions & 0 deletions desktop/src/gui/dialogs/preferences_dialog.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::cli::GameModePreference;
use crate::gui::{available_languages, optional_text, text, ThemePreference};
use crate::log::FilenamePattern;
use crate::preferences::{storage::StorageBackend, GlobalPreferences};
Expand All @@ -19,6 +20,10 @@ pub struct PreferencesDialog {
power_preference_readonly: bool,
power_preference_changed: bool,

gamemode_preference: GameModePreference,
gamemode_preference_readonly: bool,
gamemode_preference_changed: bool,

language: LanguageIdentifier,
language_changed: bool,

Expand Down Expand Up @@ -68,6 +73,10 @@ impl PreferencesDialog {
power_preference_readonly: preferences.cli.power.is_some(),
power_preference_changed: false,

gamemode_preference: preferences.gamemode_preference(),
gamemode_preference_readonly: preferences.cli.gamemode.is_some(),
gamemode_preference_changed: false,

language: preferences.language(),
language_changed: false,

Expand Down Expand Up @@ -114,6 +123,10 @@ impl PreferencesDialog {
.show(ui, |ui| {
self.show_graphics_preferences(locale, &locked_text, ui);

if cfg!(target_os = "linux") {
self.show_gamemode_preferences(locale, &locked_text, ui);
}

self.show_language_preferences(locale, ui);

self.show_theme_preferences(locale, ui);
Expand Down Expand Up @@ -289,6 +302,46 @@ impl PreferencesDialog {
ui.end_row();
}

fn show_gamemode_preferences(
&mut self,
locale: &LanguageIdentifier,
locked_text: &str,
ui: &mut Ui,
) {
ui.label(text(locale, "gamemode"))
.on_hover_text_at_pointer(text(locale, "gamemode-tooltip"));
if self.gamemode_preference_readonly {
ui.label(gamemode_preference_name(locale, self.gamemode_preference))
.on_hover_text(locked_text);
} else {
let previous = self.gamemode_preference;
ComboBox::from_id_salt("gamemode")
.selected_text(gamemode_preference_name(locale, self.gamemode_preference))
.show_ui(ui, |ui| {
let values = [
GameModePreference::Default,
GameModePreference::On,
GameModePreference::Off,
];
for value in values {
let response = ui.selectable_value(
&mut self.gamemode_preference,
value,
gamemode_preference_name(locale, value),
);

if let Some(tooltip) = gamemode_preference_tooltip(locale, value) {
response.on_hover_text_at_pointer(tooltip);
}
}
});
if self.gamemode_preference != previous {
self.gamemode_preference_changed = true;
}
}
ui.end_row();
}

fn show_audio_preferences(&mut self, locale: &LanguageIdentifier, ui: &mut Ui) {
ui.label(text(locale, "audio-output-device"));

Expand Down Expand Up @@ -459,6 +512,9 @@ impl PreferencesDialog {
if self.theme_preference_changed {
preferences.set_theme_preference(self.theme_preference);
}
if self.gamemode_preference_changed {
preferences.set_gamemode_preference(self.gamemode_preference);
}
}) {
// [NA] TODO: Better error handling... everywhere in desktop, really
tracing::error!("Could not save preferences: {e}");
Expand Down Expand Up @@ -500,6 +556,27 @@ fn theme_preference_name(
}
}

fn gamemode_preference_name(
locale: &LanguageIdentifier,
gamemode_preference: GameModePreference,
) -> Cow<str> {
match gamemode_preference {
GameModePreference::Default => text(locale, "gamemode-default"),
GameModePreference::On => text(locale, "enable"),
GameModePreference::Off => text(locale, "disable"),
}
}

fn gamemode_preference_tooltip(
locale: &LanguageIdentifier,
gamemode_preference: GameModePreference,
) -> Option<Cow<str>> {
Some(match gamemode_preference {
GameModePreference::Default => text(locale, "gamemode-default-tooltip"),
_ => return None,
})
}

fn filename_pattern_name(locale: &LanguageIdentifier, pattern: FilenamePattern) -> Cow<str> {
match pattern {
FilenamePattern::SingleFile => text(locale, "log-filename-pattern-single-file"),
Expand Down
Loading