Skip to content

Commit f65839f

Browse files
committed
fix(Power Saver): check Gamescope input counter for mouse/keyboard inactivity
1 parent 72b5e56 commit f65839f

File tree

3 files changed

+72
-1
lines changed

3 files changed

+72
-1
lines changed

core/systems/power/power_saver.gd

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ class_name PowerSaver
77
var display := load("res://core/global/display_manager.tres") as DisplayManager
88
var settings := load("res://core/global/settings_manager.tres") as SettingsManager
99
var power_manager := load("res://core/systems/power/power_manager.tres") as UPowerInstance
10+
var gamescope := load("res://core/systems/gamescope/gamescope.tres") as GamescopeInstance
1011

1112
const MINUTE := 60
1213

@@ -22,16 +23,19 @@ const MINUTE := 60
2223

2324
@onready var dim_timer := $%DimTimer as Timer
2425
@onready var suspend_timer := $%SuspendTimer as Timer
26+
@onready var gamescope_timer := $%GamescopeCheckTimer as Timer
2527

2628
var dimmed := false
2729
var prev_brightness := {}
2830
var supports_brightness := display.supports_brightness()
2931
var has_battery := false
3032
var display_device := power_manager.get_display_device()
33+
var gamescope_input_counters: Dictionary[int, int] = {}
3134
var logger := Log.get_logger("PowerSaver")
3235

3336

3437
func _ready() -> void:
38+
gamescope_timer.timeout.connect(_on_gamescope_check_timeout)
3539
if display_device:
3640
has_battery = display_device.is_present
3741

@@ -84,7 +88,7 @@ func _on_suspend_timer_timeout() -> void:
8488
logger.warn("Failed to suspend: '" + output[0] + "'")
8589

8690

87-
func _input(event: InputEvent) -> void:
91+
func _input(_event: InputEvent) -> void:
8892
if dim_screen_enabled and supports_brightness:
8993
if dimmed:
9094
dimmed = false
@@ -98,3 +102,47 @@ func _input(event: InputEvent) -> void:
98102
dim_timer.start(dim_after_inactivity_mins * MINUTE)
99103
if auto_suspend_enabled:
100104
suspend_timer.start(suspend_after_inactivity_mins * MINUTE)
105+
106+
107+
## Called at a regular interval to check if gamescope input counters have changed.
108+
## Gamepad inputs will be routed to all running applications (including OpenGamepadUI)
109+
## which can be used to check for inactivity, but keyboard/mouse inputs will not.
110+
## To work around this, this method will check the input counter atom in gamescope
111+
## which will change whenever keyboard/mouse input is detected.
112+
func _on_gamescope_check_timeout() -> void:
113+
# Loop through each xwayland instance to see if any have received mouse or
114+
# keyboard inputs since the last check
115+
var detected_input := false
116+
for xwayland_type in [gamescope.XWAYLAND_TYPE_PRIMARY, gamescope.XWAYLAND_TYPE_OGUI]:
117+
if not _has_gamescope_input_counter_changed(xwayland_type):
118+
continue
119+
detected_input = true
120+
var xwayland := gamescope.get_xwayland(xwayland_type)
121+
if not xwayland:
122+
continue
123+
self.gamescope_input_counters[xwayland_type] = xwayland.get_input_counter()
124+
125+
if not detected_input:
126+
return
127+
128+
self._input(null)
129+
130+
131+
## Returns true if input counter for the given gamescope type is different
132+
## than the one recorded in `self.gamescope_input_counters`
133+
func _has_gamescope_input_counter_changed(xwayland_type: int) -> bool:
134+
var xwayland := gamescope.get_xwayland(xwayland_type)
135+
if not xwayland:
136+
return false
137+
var last := _get_last_input_counter_for(xwayland_type)
138+
var current := xwayland.get_input_counter()
139+
140+
return last != current
141+
142+
143+
## Returns the last set input counter for the given XWayland type.
144+
func _get_last_input_counter_for(xwayland_type: int) -> int:
145+
var counter := 0
146+
if xwayland_type in self.gamescope_input_counters:
147+
counter = self.gamescope_input_counters[xwayland_type]
148+
return counter

core/systems/power/power_saver.tscn

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,7 @@ one_shot = true
1212
[node name="SuspendTimer" type="Timer" parent="."]
1313
unique_name_in_owner = true
1414
one_shot = true
15+
16+
[node name="GamescopeCheckTimer" type="Timer" parent="."]
17+
unique_name_in_owner = true
18+
autostart = true

extensions/core/src/gamescope/x11_client.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,25 @@ impl GamescopeXWayland {
232232
}
233233
}
234234

235+
/// Returns the value of the Gamescope input counter. This is useful to detect
236+
/// whether or not Gamescope has been receiving mouse or keyboard inputs.
237+
#[func]
238+
pub fn get_input_counter(&self) -> u32 {
239+
let Ok(root_id) = self.xwayland.get_root_window_id() else {
240+
return 0;
241+
};
242+
let result = self
243+
.xwayland
244+
.get_one_xprop(root_id, GamescopeAtom::InputCounter);
245+
match result {
246+
Ok(counter) => counter.unwrap_or_default(),
247+
Err(e) => {
248+
log::trace!("No input counter found: {e}");
249+
0
250+
}
251+
}
252+
}
253+
235254
/// Returns the list of currently watched windows.
236255
#[func]
237256
pub fn get_watched_windows(&self) -> PackedInt64Array {

0 commit comments

Comments
 (0)