From 50d535fa797199d0553d76fcea65e9c2f8a6eee9 Mon Sep 17 00:00:00 2001 From: Sannidhya Sandheer <148160409+sannidhyas@users.noreply.github.com> Date: Sat, 10 Oct 2026 06:13:01 +0530 Subject: [PATCH 1/2] feat(plugins): add Game Mode Optimizer --- plugins/game-memory-mode/AGENTS.md | 84 +++++ plugins/game-memory-mode/LICENSE | 21 ++ plugins/game-memory-mode/README.md | 78 +++++ .../assets/preview-widget.png | Bin 0 -> 70061 bytes plugins/game-memory-mode/bin/game-memory | 291 ++++++++++++++++++ plugins/game-memory-mode/bin/game-memory-run | 13 + plugins/game-memory-mode/bin/test-controller | 88 ++++++ plugins/game-memory-mode/bin/test-live | 46 +++ plugins/game-memory-mode/bin/test-startup | 55 ++++ plugins/game-memory-mode/content/Panel.qml | 47 +++ plugins/game-memory-mode/content/Widget.qml | 23 ++ plugins/game-memory-mode/manifest.json | 70 +++++ .../game-memory-mode/product-manifest.json | 113 +++++++ plugins/game-memory-mode/service/Main.qml | 36 +++ plugins/registry.json | 29 ++ 15 files changed, 994 insertions(+) create mode 100644 plugins/game-memory-mode/AGENTS.md create mode 100644 plugins/game-memory-mode/LICENSE create mode 100644 plugins/game-memory-mode/README.md create mode 100644 plugins/game-memory-mode/assets/preview-widget.png create mode 100755 plugins/game-memory-mode/bin/game-memory create mode 100755 plugins/game-memory-mode/bin/game-memory-run create mode 100755 plugins/game-memory-mode/bin/test-controller create mode 100755 plugins/game-memory-mode/bin/test-live create mode 100755 plugins/game-memory-mode/bin/test-startup create mode 100644 plugins/game-memory-mode/content/Panel.qml create mode 100644 plugins/game-memory-mode/content/Widget.qml create mode 100644 plugins/game-memory-mode/manifest.json create mode 100644 plugins/game-memory-mode/product-manifest.json create mode 100644 plugins/game-memory-mode/service/Main.qml diff --git a/plugins/game-memory-mode/AGENTS.md b/plugins/game-memory-mode/AGENTS.md new file mode 100644 index 0000000..e853d34 --- /dev/null +++ b/plugins/game-memory-mode/AGENTS.md @@ -0,0 +1,84 @@ +# Agent guide for the game-memory-mode plugin + +This folder is a Ryoku shell plugin. If you are an AI agent editing it, follow +every rule below. They are the same rules `ryoku plugin validate` enforces, so +work that breaks one will not install. This file is self-contained: you do not +need any other document to work here. + +## The rules + +R1 Place. A plugin is one folder named after its id. Author it under +`$(xdg-user-dir DOCUMENTS)/ryoku-plugins//` (fallback `~/ryoku-plugins//`); +`ryoku plugin new ` creates it there. Never write into +`~/.local/share/ryoku/plugins/` (the store's install root, receipt-owned) or +`~/.config/quickshell/` (the shipped shell). + +R2 Shape. `manifest.json` at the root; `service/Main.qml` (logic, no UI); +`content/Widget.qml` (the one view); optional `content/Panel.qml` (a bar panel); +`README.md`; `LICENSE`; `assets/preview-widget.png` (a real capture); scripts +only under `bin/` with a shebang and the exec bit. Every extra file listed in +manifest `files`. + +R3 Id. Lowercase `[a-z0-9][a-z0-9-]*`, unique, not a built-in widget id. + +R4 Imports. Only `QtQuick*`, `Quickshell*`, `Ryoku.PluginKit`, +`Ryoku.PluginKit.Singletons`, and files inside the plugin folder. Never +`shell.*`, `Ryoku.Ui*` internals, or a relative import that climbs out of the +folder. + +R5 Settings. Declared in `metadata.settings` (types the bar renders: toggle, +choice, multi, int, text); read through `pluginApi.pluginSettings` behind a +default; written only through `pluginApi.saveSetting(key, value)`. Never edit +`shell.json` or `plugins.json` directly. + +R6 Commands. Every external program the plugin runs is either in its own `bin/` +or listed in `dependencies.commands`. No `sudo`, `doas`, `su`. A privileged +action is allowed only through `pkexec`, only on an explicit click, only when +listed in manifest `capabilities.privileged` (exact command strings) and +explained in README. + +R7 Network. Every host the plugin talks to is listed in +`capabilities.network`. No `curl … | sh`, no downloading and running code. + +R8 Files. Write only under `pluginApi.stateDir` +(`$XDG_STATE_HOME/ryoku/plugins/`), `$XDG_CACHE_HOME/ryoku/plugins/`, or +a temp dir. Never touch `~/.ssh`, `/etc`, shell rc files, or another plugin's +folder. + +R9 Shell. No `sh -c` with a string built from settings or program output +(injection). Pass argv arrays. + +R10 Secrets and binaries. No tokens, keys or credentials in the tree; no +compiled binaries (ELF/Mach-O/.so); scripts only. + +R11 Honesty. `official` is never true for a community plugin. `author` is +`Name `. README says what runs, what it reads, what it writes, and every +privileged/network capability. + +## The workflow + +1. `ryoku plugin new ` scaffolds this folder (already done). +2. Edit: `service/Main.qml` for logic, `content/Widget.qml` for the view, and + `content/Panel.qml` for the bar panel. Capture `assets/preview-widget.png`. +3. `ryoku plugin validate ` and fix every blocking finding (warnings are + advisory but read them). +4. `ryoku plugin add --bar --yes` to install it, then verify it on the bar + and under **QS Bar Settings > Community**. +5. `ryoku plugin share ` ONLY when the user asks to publish. Never share a + plugin unless asked. + +## What the host sets + +Every entry point receives, from the host: `pluginApi` (see below), `s` (a scale +factor), `active`, `density`, and `widthBudget`. Read them; never assign them. + +`pluginApi` exposes: + +- `mainInstance`: the live `service/Main.qml` instance every view shares. +- `pluginSettings`: the resolved settings object; read behind a default. +- `pluginDir`: this plugin's folder. +- `stateDir`: `$XDG_STATE_HOME/ryoku/plugins/`; the only place to write. +- `saveSetting(key, value)`: persist one setting. +- `panelOpen` (bool), `openPanel()`, `closePanel()`, `togglePanel()`: the bar + panel controls (present but inert off the bar). The sanctioned widget click is + `onClicked: pluginApi.togglePanel()`. diff --git a/plugins/game-memory-mode/LICENSE b/plugins/game-memory-mode/LICENSE new file mode 100644 index 0000000..ee37ec6 --- /dev/null +++ b/plugins/game-memory-mode/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Sannidhya Sandheer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/game-memory-mode/README.md b/plugins/game-memory-mode/README.md new file mode 100644 index 0000000..d181791 --- /dev/null +++ b/plugins/game-memory-mode/README.md @@ -0,0 +1,78 @@ +# Game Mode Optimizer + +Temporarily stop voice typing and replace animated wallpapers while gaming, then restore your desktop. + +![Game Mode Optimizer on the QS Bar](assets/preview-widget.png) + +## What it does + +The QS Bar controller adds two desktop optimizations to **Quick Settings → Gaming** (Super+Esc): stop active Voxtype and switch live wallpapers to a static frame. Existing Gaming performance and DND behavior stays with Ryoku. The independent manual control and launcher wrapper apply only these two actions. + +The previous wallpaper, audio settings and voice-typing state return after the last game/manual/Gaming hold ends. Already-off voice typing stays off. A static wallpaper stays unchanged. A wallpaper chosen during gaming is respected, as is a changed Voxtype service enablement setting. Overlapping games have independent holds; a killed wrapper retains its hold while its game process group survives. + +One tested NVIDIA desktop reclaimed 1825 MiB of GPU memory with these actions. Savings vary by wallpaper, voice model and hardware; this is game-mode optimization, not a graphics-driver fix. + +## Install + +Install **Game Mode Optimizer** from Ryostore → Plugins, or install a reviewed local copy with `ryoku plugin add --bar --yes`. Click the controller icon on the QS Bar for its panel. Preferences are also under **QS Bar Settings → Community**. + +Requires Python 3, systemd user services and Ryogami. FFmpeg is needed to extract a still frame from video; selecting an existing static image avoids extraction. Voxtype is optional: an absent or inactive service is left alone. No root access or separate service installation is required. The first client starts a helper; the bar monitors it. If the widget is unloaded and no game remains, the helper restores the desktop and exits after 15 seconds without requests. Existing service-managed deployments may still run `bin/game-memory daemon` directly. + +### Automatic game launches + +Use the installed `bin/game-memory-run` as a launcher wrapper. Launcher settings are configured by you; this plugin never edits Heroic or Steam configuration. + +**Heroic:** Settings → Advanced → Wrapper (the exact section can vary by version). Add the executable at `~/.local/share/ryoku/plugins/game-memory-mode/bin/game-memory-run`, using your full home-directory path. Leave wrapper arguments empty. Configure Game Defaults for new games, and existing games individually if they override defaults. Preserve any other wrappers already in use. + +**Steam:** Game → Properties → General → Launch Options. With the default XDG data location: + +```sh +"$HOME/.local/share/ryoku/plugins/game-memory-mode/bin/game-memory-run" %command% +``` + +Prefix existing launch options instead of replacing them. Steam requires this once per game; it has no universal per-game launch-option default. If you use a custom XDG data home, use the actual installed path. + +The wrapper waits for desktop preparation before starting the game. Turning off **Automatic on game launch** makes the wrapper run the game without acquiring a hold. Launch failure releases its own hold. Shell reloads do not interrupt an existing game hold. + +### Remove + +Close wrapped games, turn Gaming/manual mode off, and remove the wrapper from launcher settings before removing the plugin through QS Bar Settings or Ryostore. The ordinary helper restores and exits when the widget stops polling. To stop it immediately, run the installed `bin/game-memory shutdown`. For a pre-existing systemd-managed deployment, stop and disable your own helper unit first. Runtime state is kept in the plugin's own directory; the plugin does not delete it automatically. + +## How it plugs in + +- `service/Main.qml` polls the local helper and passes declared settings. The bar glyph only opens the panel; actions occur in the panel or follow the user's Gaming/launcher configuration. +- `bin/game-memory` runs an unprivileged Python helper and CLI. It executes `systemctl --user` for Voxtype and `ffmpeg` for a still frame. Games are passed as argv arrays to `bin/game-memory-run`; no shell strings are generated. +- Reads: published Ryoku `flags.json` Gaming state, the user's `/proc` process identities/groups, boot identity, Voxtype service status/enablement, selected wallpaper/image files, and Ryogami's local JSON-RPC socket. +- Writes: only `$XDG_STATE_HOME/ryoku/plugins/game-memory-mode` (private state, preferences, snapshots, still frames and controller socket). Restores wallpapers through the owning Ryogami API. Stops/starts only `voxtype.service`; never changes its enablement. No launcher configuration, shell stores, login scripts, user units or shipped Ryoku files are edited. +- No network access, downloads, privileged operations, NVIDIA dependency or compositor-specific API. Source code runs with the user's permissions and is not sandboxed by the store. + +Limitations: active wallpaper auto-rotation refuses activation; turn rotation off first. Video/static wallpapers work automatically; Wallpaper Engine needs a static image path. Failed preparation blocks a wrapped launch and reports an error. GPU memory cannot be released by merely pausing a video or suspending the voice process, so these are stopped/replaced. A game that changes its process group independently of its launcher may escape group tracking; automatic holds are intended for launchers that preserve the game process group. + +## Settings + +| Setting | Default | Effect | +| --- | --- | --- | +| Automatic on game launch | On | Wrapped launches acquire a game hold. | +| Follow the Gaming toggle | On | Existing Quick Settings Gaming adds these two actions. | +| Static image path | Blank | Use a frame of the video; supply an absolute image path for other live wallpaper types. | + +## Develop + +```text +manifest.json +service/Main.qml +content/Widget.qml +content/Panel.qml +bin/game-memory +bin/game-memory-run +bin/test-controller +bin/test-startup +bin/test-live +assets/preview-widget.png +``` + +Run `PYTHONDONTWRITEBYTECODE=1 bin/test-controller`, `bin/test-startup`, and `ryoku plugin validate .`. The startup test uses isolated temporary state and does not change your desktop. `bin/test-live` is an explicit live qualification: it temporarily changes wallpaper/voice state, launches short sleep processes, kills this helper to check recovery, and verifies restoration. Run it only with no active Gaming/manual/game hold and after reviewing it. It is never an install hook. Live tests require the wrapper/helper paths to be from the same installation. + +## Credits + +MIT © 2026 Sannidhya Sandheer. Built with Ryoku PluginKit and Ryogami. Community maintenance belongs to the contributor, not the Ryoku team. Reports and maintenance contact: [@sannidhyas](https://github.com/sannidhyas), through the [Ryostore issue tracker](https://github.com/Ryoku-dev/ryostore/issues). Store screening is not a security guarantee; review source and updates before enabling. diff --git a/plugins/game-memory-mode/assets/preview-widget.png b/plugins/game-memory-mode/assets/preview-widget.png new file mode 100644 index 0000000000000000000000000000000000000000..da1efca8ca5acbea8a80086e0fc2da4e6a07bc68 GIT binary patch literal 70061 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 z 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 literal 0 HcmV?d00001 diff --git a/plugins/game-memory-mode/bin/game-memory b/plugins/game-memory-mode/bin/game-memory new file mode 100755 index 0000000..1a17cca --- /dev/null +++ b/plugins/game-memory-mode/bin/game-memory @@ -0,0 +1,291 @@ +#!/usr/bin/python3 +"""Two-action gaming mode. State stays in this plugin's own state directory.""" +import fcntl +import hashlib +import json +import os +from pathlib import Path +import signal +import socket +import subprocess +import sys +import tempfile +import threading +import time +import uuid + +STATE = Path(os.environ.get('XDG_STATE_HOME', str(Path.home()/'.local/state')))/'ryoku/plugins/game-memory-mode' +RUNTIME = Path(os.environ.get('XDG_RUNTIME_DIR', f'/run/user/{os.getuid()}')) +SOCKET = STATE/'control.sock' +FLAGS = STATE.parent.parent/'flags.json' +BOOT = Path('/proc/sys/kernel/random/boot_id').read_text().strip() + +def read(path, default): + try: return json.loads(path.read_text()) + except (OSError, ValueError): return default + +def write(path, obj): + path.parent.mkdir(parents=True, exist_ok=True) + content = json.dumps(obj, indent=2)+'\n' + try: + if path.read_text() == content: return + except OSError: pass + # A private random temp file avoids following pre-existing links; rename + # replaces the destination atomically rather than opening it for writing. + with tempfile.NamedTemporaryFile(mode='w',dir=path.parent,prefix=path.name+'.',delete=False) as stream: + tmp=Path(stream.name) + stream.write(content) + try: tmp.replace(path) + finally: tmp.unlink(missing_ok=True) + +def rpc(path, method, params=None): + with socket.socket(socket.AF_UNIX) as sock: + sock.settimeout(30); sock.connect(str(path)) + sock.sendall((json.dumps({'id':1,'method':method,'params':params or {}})+'\n').encode()) + with sock.makefile('r') as reader: + for line in reader: + reply=json.loads(line) + if reply.get('id') == 1: + if reply.get('error'): raise RuntimeError(str(reply['error'])) + return reply.get('result', reply) + raise RuntimeError('No daemon reply') + +def wall(method, params=None): return rpc(RUNTIME/'ryogami.sock', method, params) + +def unit(verb): + p=subprocess.run(['systemctl','--user',verb,'voxtype.service'],capture_output=True,text=True,timeout=15) + if verb.startswith('is-'): return p.stdout.strip() + if p.returncode: raise RuntimeError(p.stderr.strip() or 'Voxtype '+verb+' failed') + return p.stdout.strip() + +def process(pid): + try: + fields=Path(f'/proc/{pid}/stat').read_text().rsplit(')',1)[1].split() + return {'start':fields[19],'group':int(fields[2]),'state':fields[0]} + except (OSError,ValueError,IndexError): return None + +def alive(lease, groups): + if lease.get('boot') != BOOT: return False + p=process(lease['pid']) + return bool(p and p['start']==lease['start'] and p['state']!='Z') or lease.get('group') in groups + +def groups_now(): + groups=set() + for p in Path('/proc').iterdir(): + if p.name.isdigit(): + try: + if p.stat().st_uid!=os.getuid(): continue + info=process(int(p.name)) + if info and info['state']!='Z': groups.add(info['group']) + except OSError: pass + return groups + +def static_frame(entry): + source=Path(entry.get('path','')) + if entry.get('type')=='static' and source.is_file(): return str(source) + if entry.get('type')!='video' or not source.is_file(): + raise RuntimeError('This wallpaper type needs a static image selected in Game Mode Optimizer settings') + key=hashlib.sha256((str(source)+str(source.stat().st_mtime_ns)).encode()).hexdigest()[:20] + target=STATE/'frames'/f'{key}.png' + if not target.exists(): + target.parent.mkdir(parents=True,exist_ok=True) + p=subprocess.run(['ffmpeg','-hide_banner','-loglevel','error','-ss','1','-i',str(source),'-frames:v','1','-update','1','-y',str(target)],capture_output=True,text=True,timeout=30) + if p.returncode or not target.is_file(): raise RuntimeError('Could not extract wallpaper still frame') + return str(target) + +def apply_entry(output, entry): + kind=entry.get('type') + params={'type':kind} + if output!='*': params['outputs']=[output] + if kind in ('static','video'): params['path']=entry['path'] + elif kind=='we': params.update({'we_id':entry['we_id'],'screens':[] if output=='*' else [output]}) + else: raise RuntimeError('Unsupported wallpaper type '+str(kind)) + if kind in ('video','we'): + params['outputs_audio']={output:entry.get('mute',True)} + params['outputs_volume']={output:entry.get('volume',80)} + wall('wall.apply',params) + +def client(method, params=None): + try: return rpc(SOCKET,method,params) + except (FileNotFoundError,ConnectionRefusedError): + # No installation hooks or user-unit writes: the first client starts an + # inspectable, unprivileged helper. flock arbitrates simultaneous starts. + STATE.mkdir(parents=True,exist_ok=True,mode=0o700) + subprocess.Popen(['python3',str(Path(__file__).resolve()),'daemon','--idle-timeout','15'], + stdin=subprocess.DEVNULL,stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL,start_new_session=True) + for _ in range(30): + try: return rpc(SOCKET,method,params) + except (FileNotFoundError,ConnectionRefusedError): time.sleep(.2) + raise RuntimeError('Game Mode Optimizer helper did not start') + +class Controller: + def __init__(self): + self.lock=threading.RLock() + self.data=read(STATE/'state.json',{'manual':False,'leases':{},'snapshot':None}) + self.config=read(STATE/'options.json',{'automatic':True,'followGaming':True,'staticImage':''}) + self.error=''; self.stopping=False + if self.data.get('boot')!=BOOT: + self.data['leases']={}; self.data['manual']=False + self.data['boot']=BOOT + def save(self): write(STATE/'state.json',self.data) + def status(self): + gaming=bool(self.config.get('followGaming',True) and read(FLAGS,{}).get('gameMode',False)) + return {'active':bool(self.data.get('snapshot')),'manual':self.data.get('manual',False),'gaming':gaming,'games':len(self.data['leases']),'error':self.error,'automatic':self.config.get('automatic',True),'followGaming':self.config.get('followGaming',True)} + def enter(self): + outputs=wall('wall.outputs')['outputs'] + rotation=wall('wall.rotation_status') + if rotation.get('active'): + raise RuntimeError('Turn off wallpaper auto-rotation before using Game Mode Optimizer mode') + replacement={} + image=self.config.get('staticImage','') + if image and not Path(image).is_file(): raise RuntimeError('Selected static wallpaper does not exist') + for key,entry in outputs.items(): + if entry.get('type')!='static': replacement[key]={'type':'static','path':image or static_frame(entry)} + snapshot={'boot':BOOT,'outputs':outputs,'replacement':replacement,'voiceActive':unit('is-active')=='active','voiceEnabled':unit('is-enabled'),'voiceStopped':False,'changed':[]} + self.data['snapshot']=snapshot; self.save() + try: + if snapshot['voiceActive']: + # Record intent first so a crash between stop and save is recoverable. + snapshot['voiceStopped']=True; self.save(); unit('stop') + for key,entry in replacement.items(): + snapshot['changed'].append(key); self.save(); apply_entry(key,entry) + except Exception: + self.leave(); raise + def leave(self): + snapshot=self.data.get('snapshot') + if not snapshot: return + current=wall('wall.outputs')['outputs'] + for key in list(snapshot['changed']): + actual=current.get(key,{}) + expected=snapshot['replacement'][key] + # Don't overwrite a wallpaper the user chose while gaming. + if actual.get('type')==expected['type'] and actual.get('path')==expected['path']: + apply_entry(key,snapshot['outputs'][key]) + snapshot['changed'].remove(key); self.save() + if snapshot['voiceStopped'] and snapshot.get('boot')==BOOT: + if unit('is-active')!='active' and unit('is-enabled')==snapshot['voiceEnabled']: + unit('start') + self.data['snapshot']=None; self.save() + def reconcile(self): + with self.lock: + groups=groups_now() if self.data['leases'] else set() + self.data['leases']={k:v for k,v in self.data['leases'].items() if alive(v,groups)} + try: + if self.data.get('snapshot') and self.data['snapshot'].get('boot') != BOOT: + self.leave() + st=self.status(); wanted=st['manual'] or st['gaming'] or bool(self.data['leases']) + if wanted and not st['active']: self.enter() + elif not wanted and st['active']: self.leave() + self.error='' + except Exception as exc: self.error=str(exc) + self.save(); write(STATE/'status.json',self.status()) + def handle(self,method,params): + with self.lock: + if method=='configure': + for key in ('automatic','followGaming'): + if key in params and not isinstance(params[key],bool): + raise RuntimeError(key+' must be true or false') + if 'staticImage' in params and not isinstance(params['staticImage'],str): + raise RuntimeError('staticImage must be an image path') + self.config.update({k:v for k,v in params.items() if k in ('automatic','followGaming','staticImage')}) + write(STATE/'options.json',self.config) + elif method=='manual': self.data['manual']=bool(params['enabled']) + elif method=='acquire': + if self.config.get('automatic',True): + p=process(params['pid']) + if not p: raise RuntimeError('Launch owner disappeared') + self.data['leases'][params['token']]={'pid':params['pid'],'start':p['start'],'boot':BOOT} + elif method=='attach': + if params['token'] in self.data['leases']: self.data['leases'][params['token']]['group']=params['group'] + elif method=='release': + # Keep a lease if a forked child is still alive in the game's group. + lease=self.data['leases'].get(params['token']) + if lease and lease.get('group') in groups_now(): + lease['pid']=0; lease['start']='' + else: self.data['leases'].pop(params['token'],None) + elif method=='shutdown': + if self.data['leases']: raise RuntimeError('Close wrapped games before stopping the helper') + self.data['manual']=False + self.leave(); self.stopping=True; self.save() + return self.status() + elif method!='status': raise RuntimeError('Unknown request') + self.reconcile() + if method=='acquire' and self.error: + self.data['leases'].pop(params['token'],None); self.reconcile() + raise RuntimeError('Could not prepare desktop for game') + return self.status() + +def daemon(idle_timeout=0): + STATE.mkdir(parents=True,exist_ok=True,mode=0o700) + descriptor=os.open(STATE/'daemon.lock',os.O_CREAT|os.O_RDWR|os.O_NOFOLLOW,0o600) + lock=os.fdopen(descriptor,'w'); fcntl.flock(lock,fcntl.LOCK_EX|fcntl.LOCK_NB) + ctl=Controller(); SOCKET.unlink(missing_ok=True) + last_request=time.monotonic() + server=socket.socket(socket.AF_UNIX);server.bind(str(SOCKET));SOCKET.chmod(0o600);server.listen(8);server.settimeout(1) + def stop(*_): ctl.stopping=True + signal.signal(signal.SIGTERM,stop); signal.signal(signal.SIGINT,stop) + def connection(conn): + nonlocal last_request + with conn: + conn.settimeout(35) + try: + with conn.makefile('r') as f: + line=f.readline(65537) + if len(line)>65536: raise RuntimeError('Request too large') + req=json.loads(line) + last_request=time.monotonic() + result=ctl.handle(req['method'],req.get('params',{}));reply={'id':1,'result':result} + except Exception as exc: reply={'id':1,'error':str(exc)} + try: conn.sendall((json.dumps(reply)+'\n').encode()) + except OSError: pass + while not ctl.stopping: + ctl.reconcile() + # A removed/unloaded widget has no polling client. Restore and exit, + # while surviving games retain their hold independently of Quickshell. + if idle_timeout and not ctl.data['leases'] and time.monotonic()-last_request>idle_timeout: + ctl.data['manual']=False; ctl.save(); break + try: + conn,_=server.accept();threading.Thread(target=connection,args=(conn,),daemon=True).start() + except socket.timeout: pass + try: ctl.leave() + except Exception as exc: print(str(exc),file=sys.stderr) + server.close(); SOCKET.unlink(missing_ok=True) + +def run(command): + if command and command[0]=='--': command=command[1:] + if not command: raise RuntimeError('No game command supplied') + token=uuid.uuid4().hex + client('acquire',{'pid':os.getpid(),'token':token}) + child=None + try: + child=subprocess.Popen(command,start_new_session=True) + client('attach',{'token':token,'group':child.pid}) + def forward(sig,_): + try: os.killpg(child.pid,sig) + except ProcessLookupError: pass + signal.signal(signal.SIGTERM,forward);signal.signal(signal.SIGINT,forward) + return child.wait() + finally: client('release',{'token':token}) + +def main(): + args=sys.argv[1:] + cmd=args[0] if args else 'status' + if cmd=='daemon': + idle_timeout=0 + if len(args)>1: + if len(args)!=3 or args[1]!='--idle-timeout': raise RuntimeError('Invalid daemon arguments') + idle_timeout=float(args[2]) + if not 0 0 + text: (root.status.gaming ? "Following Quick Settings → Gaming" : "") + (root.status.games > 0 ? " · " + root.status.games + " game session(s)" : "") + width: parent.width; wrapMode: Text.WordWrap; color: Theme.dim; font.pixelSize: 12*root.s + } + Rectangle { + width: parent.width; height: 36*root.s; radius: Theme.radius; color: Theme.accent + Text { anchors.centerIn: parent; text: root.status.manual ? "Turn manual mode off" : "Turn manual mode on"; color: Theme.cardBot; font.pixelSize: 13*root.s } + MouseArea { anchors.fill: parent; onClicked: if (root.service) root.service.toggleManual() } + } + Repeater { + model: [{key:"automatic",label:"Automatic on game launch"},{key:"followGaming",label:"Follow the Gaming toggle"}] + delegate: Rectangle { + required property var modelData + readonly property bool checked: root.pluginApi ? (root.pluginApi.pluginSettings[modelData.key] ?? true) : true + width: col.width; height: 34*root.s; radius: Theme.radius; color: Theme.cardBot + Text { anchors.left: parent.left; anchors.leftMargin: 8*root.s; anchors.verticalCenter: parent.verticalCenter; text: modelData.label; color: Theme.bright; font.pixelSize: 12*root.s } + Text { anchors.right: parent.right; anchors.rightMargin: 8*root.s; anchors.verticalCenter: parent.verticalCenter; text: parent.checked ? "On" : "Off"; color: Theme.accent; font.pixelSize: 12*root.s } + MouseArea { anchors.fill: parent; onClicked: root.pluginApi.saveSetting(parent.modelData.key,!parent.checked) } + } + } + Text { width: parent.width; wrapMode: Text.WordWrap; text: "Restores what was running before gaming. A video becomes a still frame of the same wallpaper."; color: Theme.dim; font.pixelSize: 12*root.s } + } +} diff --git a/plugins/game-memory-mode/content/Widget.qml b/plugins/game-memory-mode/content/Widget.qml new file mode 100644 index 0000000..04a42a4 --- /dev/null +++ b/plugins/game-memory-mode/content/Widget.qml @@ -0,0 +1,23 @@ +import QtQuick +import Ryoku.PluginKit.Singletons +Item { + id: root + property var pluginApi + property var screen + property bool active: false + property string density: "glyph" + property real s: 1 + property real widthBudget: 0 + readonly property var service: pluginApi ? pluginApi.mainInstance : null + implicitWidth: label.implicitWidth + 8*s + implicitHeight: 20*s + Text { + id: label + anchors.centerIn: parent + text: "🎮" + opacity: root.service?.enabled ? 1 : 0.65 + color: root.service?.enabled ? Theme.accent : Theme.dim + font.pixelSize: 16*root.s + } + MouseArea { anchors.fill: parent; onClicked: if (root.pluginApi) root.pluginApi.togglePanel() } +} diff --git a/plugins/game-memory-mode/manifest.json b/plugins/game-memory-mode/manifest.json new file mode 100644 index 0000000..1c555ae --- /dev/null +++ b/plugins/game-memory-mode/manifest.json @@ -0,0 +1,70 @@ +{ + "id": "game-memory-mode", + "name": "Game Mode Optimizer", + "version": "0.2.0", + "author": "Sannidhya Sandheer <148160409+sannidhyas@users.noreply.github.com>", + "description": "Temporarily stop voice typing and replace animated wallpapers while gaming; restore the desktop afterwards.", + "license": "MIT", + "official": false, + "hosts": [ + "topbarGlyph" + ], + "entryPoints": { + "main": "service/Main.qml", + "content": "content/Widget.qml", + "panel": "content/Panel.qml" + }, + "panel": { + "width": 320 + }, + "files": [ + "bin/game-memory", + "bin/game-memory-run", + "bin/test-controller", + "assets/preview-widget.png", + "bin/test-live", + "bin/test-startup" + ], + "capabilities": { + "densities": [ + "glyph" + ] + }, + "defaults": { + "host": "topbarGlyph", + "label": "Game Mode Optimizer" + }, + "dependencies": { + "commands": [ + "python3", + "systemctl", + "ffmpeg", + "sleep" + ] + }, + "metadata": { + "settings": [ + { + "key": "automatic", + "type": "toggle", + "label": "Automatic on game launch", + "group": "Gaming", + "default": true + }, + { + "key": "followGaming", + "type": "toggle", + "label": "Follow the Gaming toggle", + "group": "Gaming", + "default": true + }, + { + "key": "staticImage", + "type": "text", + "label": "Static image path (blank uses a video still)", + "group": "Gaming", + "default": "" + } + ] + } +} diff --git a/plugins/game-memory-mode/product-manifest.json b/plugins/game-memory-mode/product-manifest.json new file mode 100644 index 0000000..2fece2f --- /dev/null +++ b/plugins/game-memory-mode/product-manifest.json @@ -0,0 +1,113 @@ +{ + "schema": 1, + "id": "game-memory-mode", + "category": "plugins", + "version": "0.2.0", + "destination": "ryoku/plugins/game-memory-mode", + "files": [ + { + "source": "AGENTS.md", + "destination": "AGENTS.md", + "sha256": "a315bad498fc30c2f2778a081a0347bedfa393d1da074992d92bfe273ca3b0a2", + "mode": "0644", + "size": 3965, + "install": true + }, + { + "source": "LICENSE", + "destination": "LICENSE", + "sha256": "7b36e508a194efd6ec42caabdbcf1a2a7ae7960b5235c395a6b4170bdd039f35", + "mode": "0644", + "size": 1075, + "install": false + }, + { + "source": "README.md", + "destination": "README.md", + "sha256": "dad3a9b5bc2a10905c628a8364b8c46d203ba30ad36bfecbd82c20d57b4d76ea", + "mode": "0644", + "size": 6940, + "install": false + }, + { + "source": "assets/preview-widget.png", + "destination": "assets/preview-widget.png", + "sha256": "6db7330a195baa0fdbda6a207c11cfaf7a2552863f560b9a10deeae319542856", + "mode": "0644", + "size": 70061, + "install": false + }, + { + "source": "bin/game-memory", + "destination": "bin/game-memory", + "sha256": "6fe816ca5a51e96c1f17f7d06339482769239f231631e651fcf368543ce17a07", + "mode": "0755", + "size": 14761, + "install": true + }, + { + "source": "bin/game-memory-run", + "destination": "bin/game-memory-run", + "sha256": "6c196fc7cef5eb9abef657f177118c763a7284b5144461ff2cb72a25e6dcca72", + "mode": "0755", + "size": 398, + "install": true + }, + { + "source": "bin/test-controller", + "destination": "bin/test-controller", + "sha256": "c7557aece5d71585ca74e1d1c11502f14b69ac984d2429d9862884451e1af928", + "mode": "0755", + "size": 6012, + "install": true + }, + { + "source": "bin/test-live", + "destination": "bin/test-live", + "sha256": "0480b40407e94c71efc494fb2c44558775bc3e7301d073875733ec30b29f5d43", + "mode": "0755", + "size": 2266, + "install": true + }, + { + "source": "bin/test-startup", + "destination": "bin/test-startup", + "sha256": "0ccca326e94b0c8132b06d02d3699a648f02d97744c5f48d93885bd283a87404", + "mode": "0755", + "size": 2431, + "install": true + }, + { + "source": "content/Panel.qml", + "destination": "content/Panel.qml", + "sha256": "eeb4bc9dbd0b112c755e7acf3c9a7d88c9cf4b81ce005064706d422de78b3818", + "mode": "0644", + "size": 3043, + "install": true + }, + { + "source": "content/Widget.qml", + "destination": "content/Widget.qml", + "sha256": "90a8f019f0063fac80a78c5520e3b81acf0d6eca2811ceebebb38ada7da5107c", + "mode": "0644", + "size": 731, + "install": true + }, + { + "source": "manifest.json", + "destination": "manifest.json", + "sha256": "13524c74aee0f0c4f4a89fc9ab390cdf1042889317e58f2d601fbf953a7b49d9", + "mode": "0644", + "size": 1534, + "install": true + }, + { + "source": "service/Main.qml", + "destination": "service/Main.qml", + "sha256": "00a80611ce4590b7b466069e1c59a2ba1ed22ef63f3b85567bbb8793c8b15e57", + "mode": "0644", + "size": 1892, + "install": true + } + ] +} diff --git a/plugins/game-memory-mode/service/Main.qml b/plugins/game-memory-mode/service/Main.qml new file mode 100644 index 0000000..38a3c6b --- /dev/null +++ b/plugins/game-memory-mode/service/Main.qml @@ -0,0 +1,36 @@ +pragma ComponentBehavior: Bound +import QtQuick +import Quickshell.Io +Item { + id: svc + property var pluginApi + readonly property var settings: pluginApi ? pluginApi.pluginSettings : null + property var status: ({active: false, manual: false, gaming: false, games: 0, error: ""}) + property string configured: "" + readonly property bool enabled: status.active === true + function invoke(verb, args) { + if (!pluginApi || action.running) return; + action.command = [pluginApi.pluginDir + "/bin/game-memory", verb].concat(args || []); + action.running = true; + } + function toggleManual() { invoke(status.manual ? "off" : "on"); } + function refresh() { + if (!pluginApi || query.running || action.running) return; + var cfg = JSON.stringify({automatic: settings?.automatic ?? true, + followGaming: settings?.followGaming ?? true, staticImage: settings?.staticImage ?? ""}); + if (cfg !== configured) { configured = cfg; invoke("configure", [cfg]); } + else { query.command = [pluginApi.pluginDir + "/bin/game-memory", "status"]; query.running = true; } + } + Process { + id: query + stdout: StdioCollector { onStreamFinished: { try { svc.status = JSON.parse(text); } catch (e) {} } } + stderr: StdioCollector { onStreamFinished: if (text.trim()) svc.status = Object.assign({}, svc.status, {error: text.trim()}); } + } + Process { + id: action + stdout: StdioCollector { onStreamFinished: { try { svc.status = JSON.parse(text); } catch (e) {} } } + stderr: StdioCollector { onStreamFinished: if (text.trim()) svc.status = Object.assign({}, svc.status, {error: text.trim()}); } + onExited: (code, exitStatus) => { if (code !== 0) svc.configured = ""; } + } + Timer { interval: 2000; running: true; repeat: true; triggeredOnStart: true; onTriggered: svc.refresh() } +} diff --git a/plugins/registry.json b/plugins/registry.json index ee1011f..4b0ef7a 100644 --- a/plugins/registry.json +++ b/plugins/registry.json @@ -1651,6 +1651,35 @@ "topbarGlyph" ], "lastUpdated": "2026-10-07" + }, + { + "id": "game-memory-mode", + "name": "Game Mode Optimizer", + "path": "plugins/game-memory-mode", + "version": "0.2.0", + "author": "Sannidhya Sandheer", + "upstream": "https://github.com/Ryoku-dev/ryostore/tree/main/plugins/game-memory-mode", + "official": false, + "tagline": "A quieter desktop while you game.", + "description": "Temporarily stop voice typing and replace animated wallpapers while gaming; restore the desktop afterwards.", + "icon": "sports_esports", + "tags": [ + "bar-widget", + "gaming" + ], + "hosts": [ + "topbarGlyph" + ], + "preview": "assets/preview-widget.png", + "screenshots": [ + "assets/preview-widget.png" + ], + "lastUpdated": "2026-10-10", + "manifestSha256": "4bf686c0733bc109319979338ed09a58dd423f5cb6befc482b675be96710172e", + "summary": "Temporarily stop voice typing and replace animated wallpapers while gaming; restore the desktop afterwards.", + "accent": "#8f8378", + "surface": "#101010", + "manifest": "product-manifest.json" } ] } From 0742189d171a365e09e661a9635b2d2e26cafbde Mon Sep 17 00:00:00 2001 From: Sannidhya Sandheer <148160409+sannidhyas@users.noreply.github.com> Date: Sat, 10 Oct 2026 06:14:56 +0530 Subject: [PATCH 2/2] fix(game-mode): retain helper after slow desktop preparation --- plugins/game-memory-mode/bin/game-memory | 7 ++++++- plugins/game-memory-mode/product-manifest.json | 4 ++-- plugins/registry.json | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/plugins/game-memory-mode/bin/game-memory b/plugins/game-memory-mode/bin/game-memory index 1a17cca..a7c485e 100755 --- a/plugins/game-memory-mode/bin/game-memory +++ b/plugins/game-memory-mode/bin/game-memory @@ -235,7 +235,12 @@ def daemon(idle_timeout=0): if len(line)>65536: raise RuntimeError('Request too large') req=json.loads(line) last_request=time.monotonic() - result=ctl.handle(req['method'],req.get('params',{}));reply={'id':1,'result':result} + with ctl.lock: + result=ctl.handle(req['method'],req.get('params',{})) + # Extraction/preparation can outlast the idle timeout. Count + # inactivity from completion, before releasing the lock. + last_request=time.monotonic() + reply={'id':1,'result':result} except Exception as exc: reply={'id':1,'error':str(exc)} try: conn.sendall((json.dumps(reply)+'\n').encode()) except OSError: pass diff --git a/plugins/game-memory-mode/product-manifest.json b/plugins/game-memory-mode/product-manifest.json index 2fece2f..558b05d 100644 --- a/plugins/game-memory-mode/product-manifest.json +++ b/plugins/game-memory-mode/product-manifest.json @@ -40,9 +40,9 @@ { "source": "bin/game-memory", "destination": "bin/game-memory", - "sha256": "6fe816ca5a51e96c1f17f7d06339482769239f231631e651fcf368543ce17a07", + "sha256": "fee510853b619c8de53866b440c58ba7668dca14b4e0d23ad8e1fdbd74c35c38", "mode": "0755", - "size": 14761, + "size": 15020, "install": true }, { diff --git a/plugins/registry.json b/plugins/registry.json index 4b0ef7a..88757c0 100644 --- a/plugins/registry.json +++ b/plugins/registry.json @@ -1675,7 +1675,7 @@ "assets/preview-widget.png" ], "lastUpdated": "2026-10-10", - "manifestSha256": "4bf686c0733bc109319979338ed09a58dd423f5cb6befc482b675be96710172e", + "manifestSha256": "10817b4efd9e87ce49ad4caeee2491a5917ead72306a56a7075e5bf9650f1fde", "summary": "Temporarily stop voice typing and replace animated wallpapers while gaming; restore the desktop afterwards.", "accent": "#8f8378", "surface": "#101010",