fix: correct keyUp handler for plugin actions - #53
Merged
Conversation
config is the full button object { title, iconDataUrl, bgColor, action: {...} }.
The action type/pluginUUID/hotkey live at config.action, not at config root.
- config?.type?.includes('.') → config?.action?.type?.includes('.')
- config.type in context → config.action.type
- config.pluginUUID in context → config.action.pluginUUID
- { ...config } as settings → { ...config.action }
Impact: Push-to-Talk and Push-to-Mute keys were never released because the
condition was always false (config.type === undefined). The key got stuck
held down. Toggle-based actions (Mute, Deafen, Video, etc.) were unaffected
since they only need keyDown.
The electron-builder 'files' array only packaged dist/, electron/, and package.json. public/bridge/sdpi-bridge.js was never copied into the app bundle. In the packaged app (app.isPackaged === true), main.cjs looks for the bridge at process.resourcesPath/bridge/sdpi-bridge.js. Since the file was absent, net.fetch() returned ERR_FILE_NOT_FOUND, sdpi-bridge.js failed to load, and window.sdpi was undefined in the inspector iframe — causing 'ReferenceError: sdpi is not defined' when clicking Save. Fix: add extraResources entry so electron-builder copies the bridge into resources/bridge/sdpi-bridge.js during packaging.
xdotool key without --window sends events to the currently focused window. When a physical Stream Deck button is pressed, the Electron app or another window may be focused, so Discord never receives the synthetic key event. Add getDiscordWindowId() which searches by window class 'discord' (with title fallback) and passes --window <id> to all xdotool calls. Also add --clearmodifiers to prevent stale modifier state from interfering with the key combination. Falls back to sending to the focused window if Discord is not found.
xdotool key --window uses XSendEvent which sets send_event=True in the X11 event. Discord (and Chromium/Electron in general) rejects these as synthetic events - they are ignored by both the global hotkey listener and the Electron renderer's isTrusted check. Switch to: focus Discord's window first, then send the key via XTestFakeKeyEvent (no --window). XTest events have send_event=False so Chromium treats them as real hardware input (isTrusted=true), and they are dispatched to Discord's focused-window keyboard handler which triggers the mute/deafen/PTT action. Focus is saved before and restored after each call so the user's original window regains focus immediately.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The keyUp handler for plugin button dispatches read from
config(the full button object) instead ofconfig.action(the action sub-object). Sinceconfig.typeis alwaysundefined, the guard condition was never true — keyUp was never sent to any plugin process.For Push-to-Talk and Push-to-Mute actions this meant the key was held down indefinitely after pressing the Stream Deck button.
Root cause
buttonConfigsRef.current[index]returns{ title, iconDataUrl, bgColor, action: { type, pluginUUID, hotkey, ... } }.The code incorrectly accessed
config.type,config.pluginUUID, and spread{ ...config }instead of the action-level equivalents.The correct pattern was already present 10 lines above in the keyDown handler.
Fix
Four references corrected:
config?.type?.includes('.')config?.action?.type?.includes('.')actionUUID: config.typeactionUUID: config.action.typepluginUUID: config.pluginUUIDpluginUUID: config.action.pluginUUID{ ...config }{ ...config.action }Tests
All 115 existing tests pass (
npx vitest run).