Skip to content

[ryoku] gamebar: add the overlay and the Discord Voice widget - #771

Open
dodo986 wants to merge 3 commits into
Ryoku-dev:unstable-devfrom
dodo986:ryo-gamebar
Open

dodo986 wants to merge 3 commits into
Ryoku-dev:unstable-devfrom
dodo986:ryo-gamebar

Conversation

@dodo986

@dodo986 dodo986 commented Nov 9, 2026

Copy link
Copy Markdown
Member

What changed

Adds the Game Bar, like Windows, a passive overlay per output raised with Super+Shift+G
over the focused monitor. The strip carries eight widget buttons plus a
snapping toggle, clock and battery. The cards are capture, performance,
resources, tasks, media, voice, audio and gallery. Cards drag from their frame,
snap to screen edges, centre lines and each other with a guide grid, and keep
their position and size across restarts. A pinned card stays after the bar
closes and never takes pointer or keyboard focus; a pinned Voice card shrinks
to a content-sized panel in the corner its full card occupied.

Discord Voice reads the official client over native RPC, or Vesktop and Equibop
through a bundled Vencord companion that publishes the call over a local Unix
socket. The card offers to build and install that companion, points the
client's state.json at the built dist, and keeps a backup. The bridge runs
only while the widget is on screen.

Ryoku Hub gains a Game Bar page under Desktop for the master switch, dim, scale,
radius, fade, snapping and guides, settle spring, and the voice overlay. Every
key lives under gamebar in shell.json.

Docs: new docs/gamebar.md, linked from docs/structure.md. CHANGELOG.md
carries the New and Fixed entries.

Area

ryoku

How it was tested

  • Super+Shift+G over a real fullscreen game on a
    1920x1080 output. All eight cards rendered and were working as they were expected; the bar closed cleanly.

  • Official Discord over native RPC: authorization, channel and participant
    list, speaking state, mute and deafen.

  • Vesktop companion: install_companion.sh --client vesktop cloned Vencord,
    installed with pnpm install --frozen-lockfile and built with pnpm build.
    The plugin is present in dist/renderer.js and dist/vencordDesktopMain.js,
    state.json points vencordDir at dist, and a backup
    state.json.pre-ryoku-voice exists. After restarting Vesktop, the Voice card
    showed the channel Genel, 1 person in voice, backend Vesktop and
    participant dodo985(me); ss -x showed an established connection to
    /run/user/1000/ryoku-discord-voice.sock.

  • luac -p on the Hyprland bind module, bash -n on the installer,

    python3 -m py_compile on the bridge, and bin/ryoku-dev-lint-qml on the
    shell and Hub roots. The layout, tasks and units JS tests pass. Commit hooks
    pass without --no-verify.

Checklist

  • One logical change, with a clear [area] scope: summary commit subject.
  • Matching CHANGELOG.md updated in the area I touched.
  • The git hooks pass locally; I did not use --no-verify.
  • Lua parses (luac -p), shell scripts pass bash -n, and QML passes
    qmllint where applicable.
  • No duplicated config, no dead code, no commented-out code, no em-dash.
  • Docs updated if behavior or layout changed.

A passive overlay per output, raised with SUPER + SHIFT + G over a
fullscreen game: a strip of widget buttons, draggable and resizable
cards, edge and grid snapping with guides, and a settle curve. The
cards are capture, performance, resources, tasks, media, voice, audio
and gallery, and each reads only while it is on screen.

Discord Voice reads the official client over native RPC, or Vesktop
through a Vencord companion that publishes the call on a local socket.
A pinned voice card shrinks to a content-sized overlay in the corner
the card was placed, stays passive when the bar is closed, and stops
the bridge when Voice is not shown.

The Hub gains a Game Bar page for the master switch, placement, motion
and the voice overlay, every key stored under gamebar in shell.json.
The keybind is registered in every window manager.

Note: New: the Game Bar overlay (SUPER + SHIFT + G) with widgets and a pinned Discord Voice overlay
Note: Fixed: Hub step rows now keep to the schema range
@neur0map

neur0map commented Nov 9, 2026 •

Copy link
Copy Markdown
Member

🦊 mika's review

Incremental review: new commits since the last checkpoint.

Summary

  • Add a configurable Game Bar with eight draggable, pinnable widgets.
  • Integrate Discord voice through native RPC and a Vencord companion.
  • Persist overlay layouts and expose settings and compositor shortcuts.
  • Fix Hub step controls to respect schema bounds.

Change diagram

flowchart LR
  Bind["Compositor shortcut / shell IPC"] --> Overlay["Game Bar overlay"]
  Hub["Hub settings"] --> Config["Settings daemon"]
  Config --> Overlay
  Overlay --> Store["Persisted layout"]
  Overlay --> Cards["Capture, system, media and audio cards"]
  Overlay --> Voice["Discord voice service"]
  Voice --> Bridge["Python bridge"]
  Bridge --> RPC["Discord RPC"]
  Bridge --> Companion["Vencord companion socket"]
Loading

Estimated review effort: 3/5 · risk: medium

📁 Walkthrough details

ryoku/shell/quickshell/shell/modules/gamebar/GameBar.qml

  • Implements the per-output overlay, widget placement, snapping and passive pinned cards.
  • Hides the surface during captures and restores it afterward.
    • Adds eight-card catalogue, input masks, animations and capture integration.

ryoku/shell/quickshell/shell/modules/gamebar/GameBarCard.qml

  • Adds the shared draggable card frame, pin and close controls, and resizing support.
    • Provides a minimal presentation for pinned Voice.

ryoku/shell/quickshell/shell/modules/gamebar/GameBarStrip.qml

  • Adds widget toggles, snapping control, clock and battery display.
    • Routes widget and snapping actions to the overlay.

ryoku/shell/quickshell/shell/modules/gamebar/cards/VoiceCard.qml

  • Displays Discord channel, participants, speaking status and voice controls.
  • Provides authorization, retry and companion installation actions.
    • Adds a resizable card and compact passive pinned view.

ryoku/shell/quickshell/shell/modules/gamebar/cards/CaptureCard.qml

  • Connects screenshot targets and recording controls to existing capture services.
    • Suppresses the overlay while initiating captures.

ryoku/shell/quickshell/shell/modules/gamebar/cards/PerformanceCard.qml

  • Displays system utilization, temperature, display resolution and refresh rate.
    • Uses shared samplers and distinguishes refresh rate from game FPS.

ryoku/shell/quickshell/shell/modules/gamebar/cards/ResourcesCard.qml

  • Adds CPU, GPU and memory tabs with utilization graphs.
    • Persists the selected tab and samples GPU and memory histories.

ryoku/shell/quickshell/shell/modules/gamebar/cards/TasksCard.qml

  • Lists the busiest processes and offers confirmed termination of unprotected tasks.
    • Refreshes process statistics and sends SIGTERM on confirmation.

ryoku/shell/quickshell/shell/modules/gamebar/cards/AudioCard.qml

  • Reuses shell audio controls for output, input and application streams.
    • Adds volume and mute controls backed by the shared audio service.

ryoku/shell/quickshell/shell/modules/gamebar/cards/MediaCard.qml

  • Displays the shared now-playing selection with artwork and transport controls.
    • Supports previous, play or pause, and next actions.

ryoku/shell/quickshell/shell/modules/gamebar/cards/GalleryCard.qml

  • Shows recent screenshots and recordings from the existing capture folders.
    • Opens individual files or their containing folder.

ryoku/shell/quickshell/shell/services/GameBarStore.qml

  • Persists monitor selection, visible cards, pins, positions, sizes and resource tab.
    • Validates restored state and debounces writes to gamebar.json.

ryoku/shell/quickshell/shell/services/DiscordVoice.qml

  • Manages the voice bridge, participant model, reconnects and companion installer.
    • Starts the bridge only while Voice is visible or pinned.
    • Exposes authorization, mute and deafen actions.

ryoku/shell/quickshell/shell/modules/gamebar/discordVoice/bridge.py

  • Bridges official Discord RPC or a companion socket to the QML voice service.
    • Handles authorization, voice events, reconnection and control commands.
    • Stores cached Discord tokens with restrictive permissions.

ryoku/shell/quickshell/shell/modules/gamebar/discordVoice/companion/ryokuDiscordVoice/desktop.ts

  • Hosts the local companion socket and forwards call updates and control commands.
    • Restricts socket permissions and bounds incoming message buffering.

ryoku/shell/quickshell/shell/modules/gamebar/discordVoice/companion/ryokuDiscordVoice/index.tsx

  • Publishes call participants and speaking state from Vencord clients.
    • Forwards mute and deafen commands to Discord's local stores.

ryoku/shell/quickshell/shell/modules/gamebar/discordVoice/install_companion.sh

  • Builds and installs the companion for Vesktop or Equibop.
    • Checks prerequisites and client state, backs up configuration and updates the client path.

ryoku/shell/ipc/settings.go

  • Adds persisted Game Bar settings, defaults and validation to the settings daemon.
    • Covers overlay appearance, placement, motion and Voice options.

ryoku/hub/quickshell/schema/GameBarPage.js

  • Defines Hub controls for Game Bar behavior, placement, motion and Voice.
    • Supplies ranges, dependencies and search keywords for settings rows.

ryoku/hub/quickshell/SettingsSheet.qml

  • Fixes step controls to clamp values using schema lo and hi.
    • Replaces the unrelated from and to bounds.

Findings

Severity Category Fix effort File Issue
🟡 minor correctness low ryoku/shell/quickshell/shell/modules/gamebar/GameBar.qml Possible Bug — exitTimer is not stopped when the surface becomes active again. If the bar is reopened before its closing fade finishes, the timer fires while wanted is true and leaves stripUp unchanged; more importantly, a pendin…
🟡 minor correctness low ryoku/shell/quickshell/shell/modules/gamebar/GameBarTaskRow.qml Bug — Limit the task name to the space before the CPU column. Currently, long names overlap both the CPU and memory values.
🟡 minor correctness medium ryoku/shell/quickshell/shell/modules/gamebar/cards/AudioCard.qml Possible Bug — The app list is limited to the first three streams with no scrolling or selection. When more than three applications are playing audio, users cannot adjust the remaining application volumes from the Game Bar. Make the li…
🟡 minor correctness low ryoku/shell/quickshell/shell/modules/gamebar/cards/GalleryCard.qml Bug — model.get() does not establish a QML binding dependency on model changes, so the gallery can remain empty after the folder loads or stale after captures are added or removed. Set the model's sort order to newest first…
🟡 minor correctness low ryoku/shell/quickshell/shell/modules/gamebar/cards/PerformanceCard.qml Bug — The state read runs only when the card is created; if monitorName is assigned or changes afterward, the refresh rate and resolution can remain associated with the wrong output. Restart the read when monitorName chang…
🟡 minor correctness low ryoku/shell/quickshell/shell/modules/gamebar/discordVoice/bridge.py Bug — Escape non-ASCII characters when emitting JSON. The bridge runs with a pipe on stdout, whose encoding can be ASCII under some locales; a Discord display name containing non-ASCII characters would otherwise terminate the…
🟡 minor correctness low ryoku/shell/quickshell/shell/modules/gamebar/discordVoice/bridge.py Bug — Bound the handshake read so an RPC socket that accepts connections but never sends READY cannot block the input loop indefinitely. Catch the timeout alongside the existing connection failures so the next candidate is t…
🟡 minor correctness low ryoku/shell/quickshell/shell/modules/gamebar/discordVoice/companion/native.ts Bug — An old socket can close after a replacement starts connecting; its handler then clears the replacement's connecting flag and input buffer. Reset connection state only when the closing socket is still current.
🟡 minor correctness low ryoku/shell/quickshell/shell/services/DiscordVoice.qml Bug — Discord CDN avatar sizes must be powers of two; the default 96 produces an invalid image URL. Use 128 as the default so participant avatars load.

⚡ Quick fixes by effort

Low effort (apply from the diff):

  • ryoku/shell/quickshell/shell/modules/gamebar/cards/GalleryCard.qml — Refresh gallery items when folders change
  • ryoku/shell/quickshell/shell/modules/gamebar/cards/PerformanceCard.qml — Refresh display mode when output changes
  • ryoku/shell/quickshell/shell/modules/gamebar/GameBarTaskRow.qml — Prevent task names overlapping metrics
  • ryoku/shell/quickshell/shell/modules/gamebar/discordVoice/bridge.py — Escape Unicode in bridge output
  • ryoku/shell/quickshell/shell/services/DiscordVoice.qml — Use a valid Discord avatar size
  • ryoku/shell/quickshell/shell/modules/gamebar/discordVoice/bridge.py — Bound the RPC handshake wait
  • ryoku/shell/quickshell/shell/modules/gamebar/discordVoice/companion/native.ts — Preserve replacement socket state
  • ryoku/shell/quickshell/shell/modules/gamebar/cards/GalleryCard.qml:44 — Bug — model.get() does not establish a QML binding dependency on model changes, so the gallery can remain empty after the folder loads or stale after captures are added or removed. Set the model's sort order to newest first…
  • ryoku/shell/quickshell/shell/modules/gamebar/cards/PerformanceCard.qml:32 — Bug — The state read runs only when the card is created; if monitorName is assigned or changes afterward, the refresh rate and resolution can remain associated with the wrong output. Restart the read when monitorName chang…
  • ryoku/shell/quickshell/shell/modules/gamebar/GameBarTaskRow.qml:29 — Bug — Limit the task name to the space before the CPU column. Currently, long names overlap both the CPU and memory values.
  • ryoku/shell/quickshell/shell/modules/gamebar/GameBar.qml:384 — Possible Bug — exitTimer is not stopped when the surface becomes active again. If the bar is reopened before its closing fade finishes, the timer fires while wanted is true and leaves stripUp unchanged; more importantly, a pendin…
  • ryoku/shell/quickshell/shell/modules/gamebar/discordVoice/bridge.py:34 — Bug — Escape non-ASCII characters when emitting JSON. The bridge runs with a pipe on stdout, whose encoding can be ASCII under some locales; a Discord display name containing non-ASCII characters would otherwise terminate the…
  • ryoku/shell/quickshell/shell/services/DiscordVoice.qml:86 — Bug — Discord CDN avatar sizes must be powers of two; the default 96 produces an invalid image URL. Use 128 as the default so participant avatars load.
  • ryoku/shell/quickshell/shell/modules/gamebar/discordVoice/bridge.py:125 — Bug — Bound the handshake read so an RPC socket that accepts connections but never sends READY cannot block the input loop indefinitely. Catch the timeout alongside the existing connection failures so the next candidate is t…
  • ryoku/shell/quickshell/shell/modules/gamebar/discordVoice/companion/native.ts:67 — Bug — An old socket can close after a replacement starts connecting; its handler then clears the replacement's connecting flag and input buffer. Reset connection state only when the closing socket is still current.

Medium effort (a function-level change):

  • ryoku/shell/quickshell/shell/modules/gamebar/cards/AudioCard.qml:70 — Possible Bug — The app list is limited to the first three streams with no scrolling or selection. When more than three applications are playing audio, users cannot adjust the remaining application volumes from the Game Bar. Make the li…
🤖 Prompt for AI agents (click, copy, paste into Claude/Codex)
Fix the following issues found by mika-agent on Ryoku-dev/ryoku#771 (branch ryo-gamebar):

- ryoku/shell/quickshell/shell/modules/gamebar/cards/GalleryCard.qml:44-46 — Bug (minor severity, low effort): `model.get()` does not establish a QML binding dependency on model changes, so the gallery can remain empty after the folder loads or stale after captures are added or removed. Set the model's sort order to newest first…
  suggested fix:

readonly property var shots: {
shotFiles.count;
return card.newest(shotFiles, 6, "shot");
}
readonly property var clips: {
clipFiles.count;
return card.newest(clipFiles, 6, "clip");
}
readonly property var items: card.mode === "clips" ? card.clips : card.shots

- ryoku/shell/quickshell/shell/modules/gamebar/cards/PerformanceCard.qml:32-36 — Bug (minor severity, low effort): The state read runs only when the card is created; if `monitorName` is assigned or changes afterward, the refresh rate and resolution can remain associated with the wrong output. Restart the read when `monitorName` chang…
suggested fix:

Component.onCompleted: {
Sysinfo.setActive(card, true);
StatsFeed.setActive(card, true);
if (!modeRead.running)
modeRead.running = true;
}
onMonitorNameChanged: {
card.displayMode = "";
if (modeRead.running)
modeRead.running = false;
modeRead.running = true;
}

- ryoku/shell/quickshell/shell/modules/gamebar/GameBarTaskRow.qml:29-38 — Bug (minor severity, low effort): Limit the task name to the space before the CPU column. Currently, long names overlap both the CPU and memory values.
suggested fix:

Text {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
width: Math.max(0, cpu.x - 10 * root.s)
text: root.task ? root.task.name : ""
color: GameBarLook.fg
font.family: GameBarLook.face
font.pixelSize: GameBarLook.small * root.s
elide: Text.ElideRight
}

- ryoku/shell/quickshell/shell/modules/gamebar/GameBar.qml:384-390 — Possible Bug (minor severity, low effort): `exitTimer` is not stopped when the surface becomes active again. If the bar is reopened before its closing fade finishes, the timer fires while `wanted` is true and leaves `stripUp` unchanged; more importantly, a pendin…
- ryoku/shell/quickshell/shell/modules/gamebar/cards/AudioCard.qml:70-71 — Possible Bug (minor severity, medium effort): The app list is limited to the first three streams with no scrolling or selection. When more than three applications are playing audio, users cannot adjust the remaining application volumes from the Game Bar. Make the li…
- ryoku/shell/quickshell/shell/modules/gamebar/discordVoice/bridge.py:34-35 — Bug (minor severity, low effort): Escape non-ASCII characters when emitting JSON. The bridge runs with a pipe on stdout, whose encoding can be ASCII under some locales; a Discord display name containing non-ASCII characters would otherwise terminate the…
suggested fix:

def emit(kind: str, **fields: Any) -> None:
print(json.dumps({"type": kind, **fields}, separators=(",", ":"), ensure_ascii=True), flush=True)

- ryoku/shell/quickshell/shell/services/DiscordVoice.qml:86-86 — Bug (minor severity, low effort): Discord CDN avatar sizes must be powers of two; the default `96` produces an invalid image URL. Use `128` as the default so participant avatars load.
suggested fix:
  • ".png?size=" + (size || 128);
- ryoku/shell/quickshell/shell/modules/gamebar/discordVoice/bridge.py:125-128 — Bug (minor severity, low effort): Bound the handshake read so an RPC socket that accepts connections but never sends `READY` cannot block the input loop indefinitely. Catch the timeout alongside the existing connection failures so the next candidate is t…
suggested fix:

reader, writer = await asyncio.open_unix_connection(path)
self.reader, self.writer, self.rpc_path = reader, writer, path
await self.send_frame({"v": 1, "client_id": APP_ID}, HANDSHAKE)
try:
opcode, ready = await asyncio.wait_for(self.receive(reader), AUTHORIZE_TIMEOUT)
except asyncio.TimeoutError as error:
raise ConnectionError("the RPC handshake timed out") from error

- ryoku/shell/quickshell/shell/modules/gamebar/discordVoice/companion/native.ts:67-71 — Bug (minor severity, low effort): An old socket can close after a replacement starts connecting; its handler then clears the replacement's `connecting` flag and input buffer. Reset connection state only when the closing socket is still current.
suggested fix:

candidate.on("close", () => {
if (socket === candidate) {
reset();
socket = undefined;
}
});


For each: verify it is real in the current code, apply the smallest correct fix, and run the repo's tests. Skip anything already resolved and say why.
🛠 Deterministic checks run
  • golangci-lint: not installed (skip)
  • shellcheck: not installed (skip)

🚦 Merge recommendation: needs_review


🦊 Generated by Mika — the Ryoku org's self-hosted AI code reviewer. Machine findings: verify before merging.

@dodo986

dodo986 commented Nov 9, 2026

Copy link
Copy Markdown
Member Author
image

Only the active output fills its cards, card loaders wait for the surface to
be live, the exit timer follows the fade, and capture suppression follows the
selection overlay instead of polling.

Gallery gains a screenshots/recordings switch with play tiles and file names.
Voice delegates bind the participant explicitly. The bridge ignores non-object
companion messages, the companion clears queued lines on disconnect, the
installer cannot swallow a failed build, and the tasks card reads cputimes as
seconds.

Note: Fixed: the Game Bar hides for captures, lists recordings, shows Voice participants and reports real CPU use
SelectedChannelStore may return no channel, and the companion build accepts
the undefined case at runtime. Say so in the signature so tsc --noEmit passes.

@neur0map neur0map left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Changes requested — 0 critical and 1 major issue(s): see the inline comments and the walkthrough for fixes and an agent-ready prompt.

Comment on lines +657 to +665
case "audio": return audioComp;
case "gallery": return galleryComp;
}
return null;
}

// The bar must not be in its own capture: unmap the surface, run the
// existing shot path, and restore after the selection overlay finishes.
function shoot(mode) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 minor · Bug · fix: low effort

A pending restoreDelay from a previous capture can fire during a new selection and remap the overlay into the screenshot. Stop it before starting the new capture.

When a screenshot is cancelled before Capture.selecting ever becomes nonempty, no restore timer is scheduled and suppressed remains true. Add a fallback that restores the overlay if selection never starts.

Suggested change

Suggested change
case "audio": return audioComp;
case "gallery": return galleryComp;
}
return null;
}
// The bar must not be in its own capture: unmap the surface, run the
// existing shot path, and restore after the selection overlay finishes.
function shoot(mode) {
function shoot(mode) {
if (Capture.selecting !== "")
return;
restoreDelay.stop();
win.sawSelecting = false;
win.suppressed = true;
Capture.shoot(mode);
if (mode === "all" || Capture.selecting === "")
win.restoreAfter(Capture.delay * 1000 + 800);
}

Comment on lines +675 to +678
function startRecording() {
win.suppressed = true;
Recorder.startAfter(Recorder.recordArgs(), 1);
win.restoreAfter(1900);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 major · Possible Bug · fix: medium effort

Recording restores the overlay after 1.9 seconds even while recording continues. A screen or monitor recording therefore includes the Game Bar whenever it remains open or pinned; keep it suppressed until recording stops.

implicitHeight: 32 * root.s
implicitWidth: 276 * root.s

onTaskChanged: confirming = false

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 minor · Possible Bug · fix: medium effort

Each process refresh replaces the row's task, resetting confirming every two seconds. A user who takes longer than the refresh interval to confirm an End action loses the confirmation; retain it while the PID remains the same.

Comment on lines +29 to +33
Text {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
width: Math.max(0, actions.x - 12 * root.s)
text: root.task ? root.task.name : ""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 minor · Bug · fix: low effort

The process name extends into the CPU and memory columns because its width is based on actions.x. Limit it to the start of the CPU column so long names cannot obscure the metrics.

Suggested change

Suggested change
Text {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
width: Math.max(0, actions.x - 12 * root.s)
text: root.task ? root.task.name : ""
Text {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
width: Math.max(0, cpu.x - 12 * root.s)
text: root.task ? root.task.name : ""

Comment on lines +45 to +47
readonly property var shots: card.newest(shotFiles, 6, "shot")
readonly property var clips: card.newest(clipFiles, 6, "clip")
readonly property var items: card.mode === "clips" ? card.clips : card.shots

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 minor · Bug · fix: low effort

newest() reads model entries through get(), which does not establish a binding dependency on model changes. Depend on each model's count so the gallery refreshes when captures are added or removed.

Suggested change

Suggested change
readonly property var shots: card.newest(shotFiles, 6, "shot")
readonly property var clips: card.newest(clipFiles, 6, "clip")
readonly property var items: card.mode === "clips" ? card.clips : card.shots
readonly property var shots: {
shotFiles.count;
return card.newest(shotFiles, 6, "shot");
}
readonly property var clips: {
clipFiles.count;
return card.newest(clipFiles, 6, "clip");
}
readonly property var items: card.mode === "clips" ? card.clips : card.shots

Comment on lines +86 to +90
protectedName: Tasks.isProtected(modelData.name)
onEndRequested: pid => {
killer.command = ["kill", "-TERM", String(pid)];
killer.running = true;
settle.restart();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 minor · Possible bug · fix: low effort

ps lists processes owned by other users, and kill inherits the shell's privileges. Prevent ending another user's processes by limiting the displayed process list to the current user.

confidence: 60/100

Suggested change

Suggested change
protectedName: Tasks.isProtected(modelData.name)
onEndRequested: pid => {
killer.command = ["kill", "-TERM", String(pid)];
killer.running = true;
settle.restart();
onEndRequested: pid => {
killer.command = ["kill", "-TERM", String(pid)];
killer.running = true;
settle.restart();
}

@neur0map neur0map left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 mika reviewed this change; details in the walkthrough comment.

Comment on lines +384 to +390
onActiveChanged: {
if (win.active) {
win.live = true;
win.shownCards = win.cards;
} else {
exitTimer.restart();
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 minor · Possible Bug · fix: low effort

exitTimer is not stopped when the surface becomes active again. If the bar is reopened before its closing fade finishes, the timer fires while wanted is true and leaves stripUp unchanged; more importantly, a pending timer from a monitor change can clear shownCards on the old surface while state is changing. Cancel the timer on reactivation and test rapid toggles and monitor changes.

confidence: 65/100

Comment on lines +70 to +71
Repeater {
model: card.streams.slice(0, 3)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 minor · Possible Bug · fix: medium effort

The app list is limited to the first three streams with no scrolling or selection. When more than three applications are playing audio, users cannot adjust the remaining application volumes from the Game Bar. Make the list scrollable or otherwise expose all streams.

Comment on lines +32 to +36
Component.onCompleted: {
Sysinfo.setActive(card, true);
StatsFeed.setActive(card, true);
modeRead.running = true;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 minor · Bug · fix: low effort

The state read runs only when the card is created; if monitorName is assigned or changes afterward, the refresh rate and resolution can remain associated with the wrong output. Restart the read when monitorName changes, clearing the previous mode first.

Suggested change

Suggested change
Component.onCompleted: {
Sysinfo.setActive(card, true);
StatsFeed.setActive(card, true);
modeRead.running = true;
}
Component.onCompleted: {
Sysinfo.setActive(card, true);
StatsFeed.setActive(card, true);
if (!modeRead.running)
modeRead.running = true;
}
onMonitorNameChanged: {
card.displayMode = "";
if (modeRead.running)
modeRead.running = false;
modeRead.running = true;
}

Comment on lines +34 to +35
def emit(kind: str, **fields: Any) -> None:
print(json.dumps({"type": kind, **fields}, separators=(",", ":")), flush=True)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 minor · Bug · fix: low effort

Escape non-ASCII characters when emitting JSON. The bridge runs with a pipe on stdout, whose encoding can be ASCII under some locales; a Discord display name containing non-ASCII characters would otherwise terminate the process.

Suggested change

Suggested change
def emit(kind: str, **fields: Any) -> None:
print(json.dumps({"type": kind, **fields}, separators=(",", ":")), flush=True)
def emit(kind: str, **fields: Any) -> None:
print(json.dumps({"type": kind, **fields}, separators=(",", ":"), ensure_ascii=True), flush=True)

Comment on lines +125 to +128
reader, writer = await asyncio.open_unix_connection(path)
self.reader, self.writer, self.rpc_path = reader, writer, path
await self.send_frame({"v": 1, "client_id": APP_ID}, HANDSHAKE)
opcode, ready = await self.receive(reader)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 minor · Bug · fix: low effort

Bound the handshake read so an RPC socket that accepts connections but never sends READY cannot block the input loop indefinitely. Catch the timeout alongside the existing connection failures so the next candidate is tried.

Suggested change

Suggested change
reader, writer = await asyncio.open_unix_connection(path)
self.reader, self.writer, self.rpc_path = reader, writer, path
await self.send_frame({"v": 1, "client_id": APP_ID}, HANDSHAKE)
opcode, ready = await self.receive(reader)
reader, writer = await asyncio.open_unix_connection(path)
self.reader, self.writer, self.rpc_path = reader, writer, path
await self.send_frame({"v": 1, "client_id": APP_ID}, HANDSHAKE)
try:
opcode, ready = await asyncio.wait_for(self.receive(reader), AUTHORIZE_TIMEOUT)
except asyncio.TimeoutError as error:
raise ConnectionError("the RPC handshake timed out") from error

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants