mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-24 19:01:43 +02:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb44ec49f1 | ||
|
|
86636fb7d2 |
No files matched your search
@@ -2,6 +2,11 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Fix Return to Game Mode misidentifying full Plasma Desktop Mode when its VR
|
||||
overlay also runs gamescope (issue #142). Session targets now determine the
|
||||
mode; conflicting or incomplete evidence stays unknown. Use SteamOS's
|
||||
`steamosctl` switch when available, with older selector commands retained.
|
||||
|
||||
- Export read-only power and heat observations to an offline accessible HTML
|
||||
report with JSON/CSV downloads. `--html FILE` limits sessions to ten minutes;
|
||||
Ctrl+C saves received samples with an explicit incomplete state. Units,
|
||||
|
||||
+44
-31
@@ -12,15 +12,21 @@ with no sign-in files or keys in the archives. Restoring one game's saves on top
|
||||
of identical files wrote nothing and left nothing behind. The session check
|
||||
reported Game Mode, so **Return to Game Mode** changed nothing. Not tested on a
|
||||
headset: a restore after a real factory reset, a restore that replaces files,
|
||||
Android data restore, and the switch out of a real Desktop Mode session. Unit
|
||||
tests and the fake Frame cover those (see [Verification](#verification)).
|
||||
On 2026-10-24 a read-only session check on the same build again reported Game
|
||||
Mode (`gamescope=true`, `plasma=false`, `nested_desktop=false`) and the
|
||||
`steamos-session-select` command was present. Opening the nested desktop for a
|
||||
clipboard test did not enter full Desktop Mode. A full session switch was not
|
||||
attempted because it restarts the headset session and a reliable return path
|
||||
was not established; the **Return to Game Mode** branch for a real full
|
||||
Desktop Mode session remains unverified.
|
||||
Android data restore. Unit tests and the fake Frame cover those (see
|
||||
[Verification](#verification)).
|
||||
On 2026-10-24, the real-device reproduction in [issue #142](https://github.com/saphid/frame-control/issues/142)
|
||||
confirmed full Plasma Desktop Mode on the same build: the Plasma session target
|
||||
was active and the Game Mode target inactive. Desktop Mode also ran gamescope
|
||||
for `steamvr-plasma.service`, so the old process check incorrectly reported
|
||||
Game Mode. SteamOS's `steamosctl switch-to-game-mode` restored Game Mode and
|
||||
reversed the target states; battery stayed at 99%, and the rollback timer and
|
||||
shared lock were cleaned up. After #143 passed CI, the corrected code was
|
||||
tested in another full Plasma session under a three-minute rollback timer.
|
||||
`ui/frame_backup.py session` reported Desktop Mode despite gamescope, and
|
||||
`ui/frame_backup.py game-mode` invoked the same guarded Return function used by
|
||||
the button. Game Mode became active and a second call made no switch. Battery
|
||||
remained at 99%; the timer and lock were removed. The literal desktop button
|
||||
was not clicked in this test, and older SteamOS builds remain fixture-tested.
|
||||
|
||||
## Back up
|
||||
|
||||
@@ -118,29 +124,28 @@ normally runs *inside* it as one panel (`steamos-nested-desktop`, see
|
||||
[how-the-frame-works.md](how-the-frame-works.md#the-layer-cake)). If that panel
|
||||
is what you see, you're already in Game Mode: close it from the Steam dashboard.
|
||||
|
||||
Full Desktop Mode is a different session. SteamOS picks the session through
|
||||
SDDM's autologin file, `/etc/sddm.conf.d/zz-steamos-autologin.conf` (or the
|
||||
default `/etc/sddm.conf.d/steamos.conf`), and switches with
|
||||
`steamos-session-select` (`holo-session-select` on newer images). This is what
|
||||
Valve's own devkit tools on the Frame read and run
|
||||
([`steamos-get-status`](../frame/devkit-utils/steamos-get-status),
|
||||
[`steamos-delete`](../frame/devkit-utils/steamos-delete)).
|
||||
Full Desktop Mode is a different session. **Check** reads the main user's
|
||||
systemd session targets over SSH: `gamescope-session.target` and
|
||||
`plasma-workspace-wayland.target`. Game Mode requires the former active and the
|
||||
latter inactive; Desktop Mode requires the reverse. This takes precedence over
|
||||
process names: full Desktop Mode can run gamescope for its VR overlay, and
|
||||
Game Mode can run a nested Plasma panel. Check changes nothing.
|
||||
|
||||
**Check** reads that file and whether gamescope and Plasma are running; it
|
||||
changes nothing. On the Frame gamescope's process is `gamescope-wl`, started by
|
||||
`start-gamescope-session`, and either counts as Game Mode. Only when Plasma is
|
||||
running without gamescope (Desktop Mode) does **Return to Game Mode** run
|
||||
exactly `steamos-session-select gamescope` (or `holo-session-select
|
||||
gamescope`) over SSH, after asking. It closes the desktop and anything open in
|
||||
it. Frame Control never edits the SDDM files itself, and runs nothing when the
|
||||
headset is in Game Mode, when it can't tell which mode it's in, or when the
|
||||
image has neither command.
|
||||
Conflicting, transitional, failed or unreadable targets produce **unknown**.
|
||||
On older images where both targets are explicitly absent, the fallback accepts
|
||||
only gamescope without Plasma/nested desktop as Game Mode, or Plasma without
|
||||
gamescope/nested desktop as Desktop Mode. Mixed processes remain unknown.
|
||||
The SDDM autologin file is still reported as a login preference; it cannot
|
||||
establish which session is running.
|
||||
|
||||
**Uncertain:** whether the Frame offers a full Desktop Mode session at all
|
||||
(only the nested desktop has been seen on BUILD_ID 20260922.6101926 and
|
||||
20260925.6191901), and whether `steamos-session-select` there works without
|
||||
the Developer Mode password. If the switch is refused, Frame Control says so and
|
||||
leaves the headset as it was.
|
||||
After confirmation, **Return to Game Mode** runs exactly
|
||||
`steamosctl switch-to-game-mode` when available, or the older
|
||||
`holo-session-select gamescope` / `steamos-session-select gamescope` command.
|
||||
It closes the desktop and anything open in it. Frame Control never edits the
|
||||
SDDM files itself, and runs no switch when the headset is in Game Mode, when
|
||||
its mode is unknown, or when no supported switch command is installed. A
|
||||
refused command reports an error; a request being sent does not verify that
|
||||
the new session has started.
|
||||
|
||||
### Manual fallback
|
||||
|
||||
@@ -151,7 +156,9 @@ If the button can't help, or the headset isn't reachable:
|
||||
Desktop Mode session, so a restart brings Game Mode back. That the Frame
|
||||
behaves the same is **inferred**, not tested.
|
||||
2. **Over SSH** (Developer Mode on), run Valve's switch yourself:
|
||||
`ssh frame steamos-session-select gamescope`.
|
||||
`ssh frame steamosctl switch-to-game-mode` (verified on SteamOS 0.5.6).
|
||||
Older images may instead provide `steamos-session-select gamescope` or
|
||||
`holo-session-select gamescope`.
|
||||
3. **Boot menu:** hold Power 10 seconds, then power on while holding the AUX
|
||||
button on top of Power. Choose `Current`, or `Previous` to start the other
|
||||
SteamOS slot; both keep your data. Details and the further options
|
||||
@@ -172,3 +179,9 @@ restore replaces only its own files and keeps the old ones, unsafe archives
|
||||
names that try to leave the backups folder, and the exact Game Mode command.
|
||||
`tests/e2e/test_recovery_kit.py` backs up a Proton save from the fake Frame through
|
||||
the server, deletes it and restores it. Neither involves a real headset.
|
||||
|
||||
Isolated session tests in the same suite reproduce full Plasma plus gamescope,
|
||||
Game Mode with and without the nested desktop, conflicting/transitional targets,
|
||||
missing targets on older builds, failed/timed-out systemd reads, selector
|
||||
compatibility, and the confirmed Return action with SSH mocked. Run them with
|
||||
`python3 -m unittest discover -s tests -p test_backup.py -v`.
|
||||
+104
-18
@@ -5,6 +5,7 @@ import io
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -203,23 +204,106 @@ class Remote(unittest.TestCase):
|
||||
self.assertEqual(result['written'], 0)
|
||||
self.assertEqual(result['held'], {'refused': 1, 'reference': 1})
|
||||
|
||||
def test_session_reads_the_mode(self):
|
||||
conf = write(self.home, 'zz-steamos-autologin.conf', '[Autologin]\nSession=plasma-steamos-wayland-oneshot.desktop\n')
|
||||
running = set()
|
||||
with patch.object(remote, 'SDDM_CONFS', (str(conf),)), \
|
||||
patch.object(remote, '_running', side_effect=lambda n, full=False: n in running), \
|
||||
patch.object(remote.shutil, 'which', side_effect=lambda n: '/usr/bin/' + n if n == 'steamos-session-select' else None):
|
||||
state = remote.session()
|
||||
self.assertEqual((state['mode'], state['selector']), ('desktop', 'steamos-session-select'))
|
||||
self.assertEqual(state['configured'], 'plasma-steamos-wayland-oneshot.desktop')
|
||||
running.update({'gamescope', 'plasmashell'}) # the nested desktop panel
|
||||
self.assertEqual(remote.session()['mode'], 'game')
|
||||
running.clear()
|
||||
running.add('gamescope-wl') # gamescope's process name on the Frame, with no desktop panel open
|
||||
self.assertEqual(remote.session()['mode'], 'game')
|
||||
running.clear()
|
||||
running.add('start-gamescope-session')
|
||||
self.assertEqual(remote.session()['mode'], 'game')
|
||||
|
||||
class Session(unittest.TestCase):
|
||||
def state(self, targets, running=(), configured=None):
|
||||
with tempfile.TemporaryDirectory() as folder:
|
||||
conf = write(folder, 'sddm.conf', 'Session=%s\n' % (configured or 'gamescope.desktop'))
|
||||
with patch.object(remote, 'SDDM_CONFS', (str(conf),)), \
|
||||
patch.object(remote, '_session_target', side_effect=targets), \
|
||||
patch.object(remote, '_running', side_effect=lambda n, full=False: n in running), \
|
||||
patch.object(remote.shutil, 'which', return_value='/usr/bin/steamosctl'):
|
||||
return remote.session()
|
||||
|
||||
def test_full_plasma_with_vr_gamescope_is_desktop(self):
|
||||
# Issue #142, SteamOS 0.5.6: steamvr-plasma also starts nested scripts.
|
||||
state = self.state(['inactive', 'active'],
|
||||
{'gamescope-wl', 'plasmashell', 'steamos-nested-desktop'},
|
||||
configured='gamescope.desktop')
|
||||
self.assertEqual(state['mode'], 'desktop')
|
||||
self.assertTrue(state['gamescope'])
|
||||
self.assertTrue(state['nested_desktop'])
|
||||
self.assertEqual(state['selector'], 'steamosctl')
|
||||
self.assertEqual(state['session_targets']['plasma-workspace-wayland.target'], 'active')
|
||||
with patch.object(backup, 'session_status', return_value=state), patch.object(backup, '_ssh') as ssh:
|
||||
self.assertTrue(backup.return_to_game_mode(confirmed=True)['switched'])
|
||||
ssh.assert_called_once_with('steamosctl switch-to-game-mode', timeout=60)
|
||||
|
||||
def test_game_target_wins_with_or_without_nested_desktop(self):
|
||||
for running in ((), {'gamescope-wl'}, {'gamescope', 'plasmashell', 'steamos-nested-desktop'}):
|
||||
state = self.state(['active', 'inactive'], running, configured='plasma.desktop')
|
||||
self.assertEqual(state['mode'], 'game')
|
||||
with patch.object(backup, 'session_status', return_value=state), patch.object(backup, '_ssh') as ssh:
|
||||
self.assertFalse(backup.return_to_game_mode(confirmed=True)['switched'])
|
||||
ssh.assert_not_called()
|
||||
|
||||
def test_conflicting_incomplete_and_transitional_targets_block_return(self):
|
||||
for targets in (['active', 'active'], ['inactive', 'inactive'], ['active', 'unknown'],
|
||||
['not-found', 'active'], ['unknown', 'unknown'], ['inactive', 'activating'],
|
||||
['deactivating', 'active'], ['failed', 'active']):
|
||||
with self.subTest(targets=targets):
|
||||
state = self.state(targets, {'gamescope', 'plasmashell'}, 'plasma.desktop')
|
||||
self.assertEqual(state['mode'], 'unknown')
|
||||
with patch.object(backup, 'session_status', return_value=state), patch.object(backup, '_ssh') as ssh:
|
||||
with self.assertRaisesRegex(backup.BackupError, 'nothing was changed'):
|
||||
backup.return_to_game_mode(confirmed=True)
|
||||
ssh.assert_not_called()
|
||||
|
||||
def test_older_builds_use_only_unambiguous_running_processes(self):
|
||||
cases = [((), 'unknown'), ({'plasmashell'}, 'desktop'),
|
||||
({'gamescope'}, 'game'), ({'gamescope-wl'}, 'game'),
|
||||
({'start-gamescope-session'}, 'game'),
|
||||
({'gamescope', 'plasmashell'}, 'unknown'),
|
||||
({'gamescope', 'steamos-nested-desktop'}, 'unknown')]
|
||||
for running, expected in cases:
|
||||
with self.subTest(running=running):
|
||||
state = self.state(['not-found', 'not-found'], running, 'plasma.desktop')
|
||||
self.assertEqual(state['mode'], expected)
|
||||
self.assertEqual(state['mode_source'], 'legacy-processes')
|
||||
|
||||
def test_target_probe_parses_systemd_and_uses_main_user_bus(self):
|
||||
cases = [('LoadState=loaded\nActiveState=active\n', 0, 'active'),
|
||||
('ActiveState=inactive\nLoadState=loaded\n', 0, 'inactive'),
|
||||
('LoadState=not-found\nActiveState=inactive\n', 0, 'not-found'),
|
||||
('LoadState=error\nActiveState=active\n', 0, 'unknown'),
|
||||
('garbage', 0, 'unknown'), ('LoadState=loaded\nActiveState=active', 1, 'unknown')]
|
||||
for stdout, code, expected in cases:
|
||||
with self.subTest(stdout=stdout, code=code), \
|
||||
patch.object(remote.os, 'getuid', return_value=1000, create=True), \
|
||||
patch.object(remote.subprocess, 'run', return_value=subprocess.CompletedProcess([], code, stdout)) as run:
|
||||
self.assertEqual(remote._session_target('gamescope-session.target'), expected)
|
||||
self.assertEqual(run.call_args.args[0], ['systemctl', '--user', 'show',
|
||||
'gamescope-session.target', '--property=LoadState', '--property=ActiveState'])
|
||||
self.assertEqual(run.call_args.kwargs['timeout'], 5)
|
||||
self.assertEqual(run.call_args.kwargs['env']['DBUS_SESSION_BUS_ADDRESS'], 'unix:path=/run/user/1000/bus')
|
||||
self.assertEqual(run.call_args.kwargs['env']['XDG_RUNTIME_DIR'], '/run/user/1000')
|
||||
for error in (OSError('no systemctl'), subprocess.TimeoutExpired('systemctl', 5)):
|
||||
with patch.object(remote.os, 'getuid', return_value=1000, create=True), \
|
||||
patch.object(remote.subprocess, 'run', side_effect=error):
|
||||
self.assertEqual(remote._session_target('gamescope-session.target'), 'unknown')
|
||||
|
||||
def test_selector_preference_and_older_builds(self):
|
||||
for available, expected in [({'steamosctl', 'steamos-session-select'}, 'steamosctl'),
|
||||
({'holo-session-select', 'steamos-session-select'}, 'holo-session-select'),
|
||||
({'steamos-session-select'}, 'steamos-session-select'), (set(), None)]:
|
||||
with patch.object(remote, 'SDDM_CONFS', ()), \
|
||||
patch.object(remote, '_session_target', return_value='unknown'), \
|
||||
patch.object(remote, '_running', return_value=False), \
|
||||
patch.object(remote.shutil, 'which', side_effect=lambda n: '/usr/bin/' + n if n in available else None):
|
||||
self.assertEqual(remote.session()['selector'], expected)
|
||||
|
||||
def test_script_probe_does_not_match_names_in_transmitted_python_source(self):
|
||||
with patch.object(remote.subprocess, 'call', return_value=1) as call:
|
||||
self.assertFalse(remote._running('steamos-nested-desktop', full=True))
|
||||
pattern = call.call_args.args[0][-1]
|
||||
for command in ('/usr/bin/steamos-nested-desktop',
|
||||
'/bin/sh /usr/bin/steamos-nested-desktop',
|
||||
'bash steamos-nested-desktop --flag'):
|
||||
self.assertIsNotNone(re.search(pattern, command), command)
|
||||
for command in ('python3 -c ' + backup.REMOTE.read_text(),
|
||||
'/bin/sh -c pgrep -f steamos-nested-desktop',
|
||||
'/usr/bin/steamos-nested-desktop-other'):
|
||||
self.assertIsNone(re.search(pattern, command), command)
|
||||
|
||||
|
||||
def fake_ssh(home, os_release):
|
||||
@@ -393,9 +477,11 @@ class Ssh(unittest.TestCase):
|
||||
|
||||
class GameMode(unittest.TestCase):
|
||||
def test_command_is_only_ever_the_session_switch(self):
|
||||
self.assertEqual(backup.session_command('steamosctl'), 'steamosctl switch-to-game-mode')
|
||||
self.assertEqual(backup.session_command('steamos-session-select'), 'steamos-session-select gamescope')
|
||||
self.assertEqual(backup.session_command('holo-session-select'), 'holo-session-select gamescope')
|
||||
for bad in (None, '', 'rm -rf ~', '/tmp/steamos-session-select', 'steamos-session-select; reboot'):
|
||||
for bad in (None, '', 'rm -rf ~', '/tmp/steamos-session-select', 'steamos-session-select; reboot',
|
||||
'steamosctl switch-to-desktop-mode', 'steamosctl; reboot'):
|
||||
with self.assertRaises(backup.BackupError):
|
||||
backup.session_command(bad)
|
||||
|
||||
|
||||
+4
-5
@@ -30,9 +30,8 @@ ARCHIVE = re.compile(r'^(saves|settings|android)/[A-Za-z0-9._-]{1,200}\.tar\.gz$
|
||||
PART = re.compile(r'^(saves:(\d{1,10}|linux)|settings:(steam|system)|android:[A-Za-z][\w]*(\.[A-Za-z_][\w]*)+)$')
|
||||
KINDS = ('saves', 'settings', 'android')
|
||||
FORMAT = 1
|
||||
# The switch Valve's own devkit tools use (frame/devkit-utils/steamos-delete). Nothing else
|
||||
# is ever run as a session command.
|
||||
SELECTORS = ('holo-session-select', 'steamos-session-select')
|
||||
# Fixed SteamOS switches only; never execute a command supplied by remote status.
|
||||
SELECTORS = ('steamosctl', 'holo-session-select', 'steamos-session-select')
|
||||
REASONS = {
|
||||
'not_installed': 'Install the game first, then restore its saves',
|
||||
'no_prefix': 'Start the game once so Proton sets up its folders, then restore',
|
||||
@@ -344,10 +343,10 @@ def session_status():
|
||||
|
||||
|
||||
def session_command(selector):
|
||||
"""The switch to Game Mode, exactly as Valve's devkit tools run it."""
|
||||
"""Allow only fixed Game Mode commands, including older devkit selectors."""
|
||||
if selector not in SELECTORS:
|
||||
raise BackupError('this SteamOS build has no session switch command; use the steps in the guide')
|
||||
return shlex.join([selector, 'gamescope'])
|
||||
return shlex.join([selector, 'switch-to-game-mode' if selector == 'steamosctl' else 'gamescope'])
|
||||
|
||||
|
||||
def return_to_game_mode(confirmed=False):
|
||||
|
||||
@@ -373,15 +373,40 @@ def restore(part, stamp, source):
|
||||
# The Frame normally runs gamescope (Game Mode) with Plasma nested inside it as one panel
|
||||
# (steamos-nested-desktop). A full Desktop Mode session is SDDM autologin into Plasma
|
||||
# instead. Valve's own devkit tools (frame/devkit-utils: steamos-get-status, steamos-delete)
|
||||
# read the session from these files and switch with holo-session-select or
|
||||
# steamos-session-select, so that's what this reads and suggests.
|
||||
# read the login preference from these files. It is not the running session.
|
||||
|
||||
SDDM_CONFS = ('/etc/sddm.conf.d/zz-steamos-autologin.conf', '/etc/sddm.conf.d/steamos.conf')
|
||||
SESSION_TARGETS = ('gamescope-session.target', 'plasma-workspace-wayland.target')
|
||||
|
||||
|
||||
def _session_target(unit):
|
||||
"""Read the main user's manager, not a nested Plasma session bus over SSH."""
|
||||
runtime = '/run/user/%d' % os.getuid()
|
||||
env = dict(os.environ, XDG_RUNTIME_DIR=runtime,
|
||||
DBUS_SESSION_BUS_ADDRESS='unix:path=%s/bus' % runtime)
|
||||
try:
|
||||
result = subprocess.run(['systemctl', '--user', 'show', unit,
|
||||
'--property=LoadState', '--property=ActiveState'],
|
||||
capture_output=True, text=True, timeout=5, env=env)
|
||||
except (OSError, subprocess.TimeoutExpired):
|
||||
return 'unknown'
|
||||
if result.returncode:
|
||||
return 'unknown'
|
||||
properties = dict(line.split('=', 1) for line in result.stdout.splitlines() if '=' in line)
|
||||
if properties.get('LoadState') == 'not-found':
|
||||
return 'not-found'
|
||||
if properties.get('LoadState') == 'loaded':
|
||||
return properties.get('ActiveState', 'unknown')
|
||||
return 'unknown'
|
||||
|
||||
|
||||
def _running(name, full=False):
|
||||
# This module is sent as `python3 -c <source>`: unanchored pgrep -f would
|
||||
# match the script names inside our own source, even when no script runs.
|
||||
pattern = (r'^([^ ]*/)?((ba|da|z)?sh +([^ ]*/)?)?%s( |$)' % re.escape(name)
|
||||
if full else name)
|
||||
try:
|
||||
return subprocess.call(['pgrep', '-f' if full else '-x', name], stdout=subprocess.DEVNULL,
|
||||
return subprocess.call(['pgrep', '-f' if full else '-x', pattern], stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL) == 0
|
||||
except OSError:
|
||||
return False
|
||||
@@ -397,21 +422,32 @@ def session():
|
||||
m = re.search(r'^\s*Session\s*=\s*(\S+)', text, re.M)
|
||||
conf, configured = path, (m.group(1) if m else None)
|
||||
break
|
||||
selector = next((s for s in ('holo-session-select', 'steamos-session-select') if shutil.which(s)), None)
|
||||
selector = next((s for s in ('steamosctl', 'holo-session-select', 'steamos-session-select') if shutil.which(s)), None)
|
||||
# On the Frame gamescope's process is named gamescope-wl (seen on build 20261015.6141207),
|
||||
# under the start-gamescope-session script.
|
||||
gamescope = (_running('gamescope') or _running('gamescope-wl') or
|
||||
_running('start-gamescope-session', full=True))
|
||||
plasma = _running('plasmashell')
|
||||
nested = _running('steamos-nested-desktop', full=True) # a script: its process name is the shell's
|
||||
if gamescope:
|
||||
targets = {unit: _session_target(unit) for unit in SESSION_TARGETS}
|
||||
game, desktop = (targets[unit] for unit in SESSION_TARGETS)
|
||||
mode = 'unknown'
|
||||
source = 'session-targets'
|
||||
if (game, desktop) == ('active', 'inactive'):
|
||||
mode = 'game'
|
||||
elif plasma or (configured and 'plasma' in configured):
|
||||
elif (game, desktop) == ('inactive', 'active'):
|
||||
mode = 'desktop'
|
||||
else:
|
||||
mode = 'unknown'
|
||||
elif game == desktop == 'not-found':
|
||||
# Older images have neither target. A compositor plus Plasma is ambiguous:
|
||||
# Desktop Mode's steamvr-plasma.service also runs gamescope and nested scripts.
|
||||
source = 'legacy-processes'
|
||||
if gamescope and not plasma and not nested:
|
||||
mode = 'game'
|
||||
elif plasma and not gamescope and not nested:
|
||||
mode = 'desktop'
|
||||
return {'mode': mode, 'gamescope': gamescope, 'plasma': plasma, 'nested_desktop': nested,
|
||||
'configured': configured, 'config_file': conf, 'selector': selector}
|
||||
'configured': configured, 'config_file': conf, 'selector': selector,
|
||||
'session_targets': targets, 'mode_source': source}
|
||||
|
||||
|
||||
def main(argv):
|
||||
|
||||
Reference in new issue
Block a user