Compare commits

...
Author SHA1 Message Date
xXJsonDeruloXx 39b0b32198 fix: let Steam draw at the session's full size above 1080p 2026-10-07 07:26:37 -04:00
2 changed files with 103 additions and 2 deletions

No files matched your search

@@ -1,5 +1,10 @@
#!/usr/bin/python3
"""Initialize Steam's own Gamescope UI scale once; subsequent choices belong to Steam."""
"""Initialize Steam's own Gamescope display settings once; subsequent choices belong to Steam.
The UI scale, and native resolution: the client treats our gamescope as an external display and,
unless "Enable Native Resolution on External Displays" is on, draws itself at 1920x1080 on any
larger output - a 2340x1080 session came up as 16:9 with bars. The session's size is already the
user's Resolution choice, so the client should use all of it."""
import math
import os
from pathlib import Path
@@ -9,6 +14,7 @@ import tempfile
DISPLAY = "External: gamescope|||Windowed"
NATIVE_RESOLUTION = "EnableSteamNativeExternalResolution"
TOKEN = re.compile(r'\s+|//[^\n]*|"(?:\\.|[^"\\])*"|[{}]')
@@ -102,6 +108,25 @@ def seed_text(text, scale):
return text[:at] + insertion + text[at:], True
def seed_native_resolution(text):
root = parse(text).get('InstallConfigStore')
if not isinstance(root, Block):
raise ValueError("missing InstallConfigStore")
gamescope = root.get('Gamescope')
if gamescope is not None and not isinstance(gamescope, Block):
raise ValueError("invalid Gamescope settings")
if gamescope is not None and gamescope.get(NATIVE_RESOLUTION) is not None:
return text, False
entry = f'"{NATIVE_RESOLUTION}" "1"'
if gamescope is not None:
at, indent = gamescope.end, 2
else:
entry = '"Gamescope"\n{\n' + entry + '\n}'
at, indent = root.end, 1
insertion = '\n' + '\n'.join('\t' * indent + line for line in entry.splitlines()) + '\n'
return text[:at] + insertion + text[at:], True
def atomic_write(path, text):
path.parent.mkdir(parents=True, exist_ok=True)
fd, temporary = tempfile.mkstemp(prefix='.' + path.name, dir=path.parent)
@@ -128,9 +153,30 @@ def initialize(steam_root, width, height):
return changed
def initialize_native_resolution(steam_root):
config = Path(steam_root) / 'config/config.vdf'
marker = config.with_name('droiddeck-native-resolution-initialized')
if marker.exists():
return False
text = config.read_text() if config.exists() else '"InstallConfigStore"\n{\n}\n'
updated, changed = seed_native_resolution(text)
if changed:
atomic_write(config, updated)
print('== steam: native resolution on the session display')
atomic_write(marker, '1\n')
return changed
if __name__ == '__main__':
status = 0
try:
initialize(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]))
except (OSError, ValueError, RecursionError) as error:
print('== steam: left UI scale unchanged: ' + str(error), file=sys.stderr)
sys.exit(1)
status = 1
try:
initialize_native_resolution(sys.argv[1])
except (OSError, ValueError, RecursionError) as error:
print('== steam: left native resolution unchanged: ' + str(error), file=sys.stderr)
status = 1
sys.exit(status)
+55
View File
@@ -11,6 +11,9 @@ seed_text = MODULE['seed_text']
parse = MODULE['parse']
initial_scale = MODULE['initial_scale']
DISPLAY = MODULE['DISPLAY']
seed_native_resolution = MODULE['seed_native_resolution']
initialize_native_resolution = MODULE['initialize_native_resolution']
NATIVE_RESOLUTION = MODULE['NATIVE_RESOLUTION']
class SteamUiScaleTest(unittest.TestCase):
@@ -96,3 +99,55 @@ class SteamUiScaleTest(unittest.TestCase):
initialize(self.root, 0, 720)
self.assertFalse(self.config.exists())
self.assertFalse(self.marker.exists())
class SteamNativeResolutionTest(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.root = Path(self.temp.name)
self.config = self.root / 'config/config.vdf'
self.config.parent.mkdir()
self.marker = self.config.with_name('droiddeck-native-resolution-initialized')
def native(self, text):
return parse(text).get('InstallConfigStore').get('Gamescope').get(NATIVE_RESOLUTION)
def test_fresh_client_renders_at_the_session_size(self):
self.assertTrue(initialize_native_resolution(self.root))
self.assertEqual('1', self.native(self.config.read_text()))
self.assertTrue(self.marker.exists())
def test_existing_client_gets_it_once_beside_its_settings(self):
text = '"InstallConfigStore"\n{\n\t"Gamescope"\n\t{\n\t\t"HDREnabled"\t\t"1"\n\t}\n}\n'
self.config.write_text(text)
self.assertTrue(initialize_native_resolution(self.root))
updated = self.config.read_text()
self.assertEqual('1', self.native(updated))
self.assertEqual('1', parse(updated).get('InstallConfigStore').get('Gamescope').get('HDREnabled'))
def test_users_choice_is_kept(self):
text = f'"InstallConfigStore" {{ "Gamescope" {{ "{NATIVE_RESOLUTION}" "0" }} }}'
self.assertEqual((text, False), seed_native_resolution(text))
def test_turning_it_off_after_initialization_survives_restart(self):
initialize_native_resolution(self.root)
text = self.config.read_text().replace('"1"', '"0"')
self.config.write_text(text)
self.assertFalse(initialize_native_resolution(self.root))
self.assertEqual(text, self.config.read_text())
def test_ui_scale_and_native_resolution_seed_the_same_config(self):
initialize(self.root, 2340, 1080)
initialize_native_resolution(self.root)
text = self.config.read_text()
self.assertEqual('1', self.native(text))
self.assertIsNotNone(parse(text).get('InstallConfigStore').get('UI').get('display').get(DISPLAY))
def test_invalid_config_is_never_rewritten_or_marked_initialized(self):
for text in ('"InstallConfigStore" {', '"InstallConfigStore" { "Gamescope" "bad" }'):
self.config.write_text(text)
with self.assertRaises(ValueError):
initialize_native_resolution(self.root)
self.assertEqual(text, self.config.read_text())
self.assertFalse(self.marker.exists())