Skip to content

Linux/Wine support: ydotool input dispatcher + IFileOpenDialog picker#7

Open
Alexconquer wants to merge 2 commits into
Jarex985:mainfrom
Alexconquer:feat/wine-ydotool-dispatcher
Open

Linux/Wine support: ydotool input dispatcher + IFileOpenDialog picker#7
Alexconquer wants to merge 2 commits into
Jarex985:mainfrom
Alexconquer:feat/wine-ydotool-dispatcher

Conversation

@Alexconquer
Copy link
Copy Markdown

Why

OpenDeck (the open-source Stream Deck alternative) can spawn this plugin
under Wine on Linux when the manifest's OS array includes "linux",
but Win32 SendInput inside the Wine process produces synthetic input
messages that stay inside the Wine prefix and never reach native Linux
applications like Star Citizen via Proton. This PR makes the plugin
detect Wine at runtime and route its keyboard/mouse output through
ydotool (a uinput-based Linux utility) when it sees Wine, so deck
button presses actually reach the game.

It also fixes the Property Inspector file pickers (audio click sounds
and Data.p4k overrides), which were returning only filenames under
OpenDeck because the Tauri WebView doesn't grant the dialog plugin to
plugin-served pages and <input type="file"> is privacy-restricted.
The plugin now opens its own IFileOpenDialog (Common Item Dialog COM
API) on request — Wine renders it via its mature
dlls/comdlg32/itemdlg.c implementation and Windows users get the
standard shell-style dialog.

Tested live on CachyOS / Wayland with OpenDeck 7.1 + ydotool 1.0.4

  • Wine + .NET 8: ~700 existing button bindings deliver keystrokes to
    Star Citizen, the AdaptiveKey audio click sounds play (WAV + MP3 once
    gst-plugins-good is installed for ID3 demuxing), and Data.p4k
    channel overrides apply correctly through the new file picker.

The patch is purely additive on Windows: WineDetector.IsWine
returns false on native Windows and the existing SendInput path
runs unchanged. The file picker also switches from
<input type="file"> to IFileOpenDialog on Windows — same shell
dialog the user already expects, returned path is identical.

Commits

1️⃣ Add Wine runtime detection + ydotool input dispatcher

Adds the Wine-aware input path and the cross-process file-picker
plumbing:

  • WineDetector.cs — one-shot ntdll!wine_get_version probe,
    cached in a Lazy<bool>.
  • LinuxInputBridge.cs — receives an INPUT[] array, batches
    keyboard events into a single ydotool key A:1 B:1 B:0 A:0 call,
    dispatches mouse clicks / wheel via ydotool click and
    ydotool mousemove --wheel. Resolves the daemon socket from
    $XDG_RUNTIME_DIR/.ydotool_socket with /tmp/.ydotool_socket as
    fallback.
  • Vk2Evdev.cs — VK_* and PS/2 Set 1 scan code → Linux evdev
    keycode tables (alpha-num, function row, modifiers L/R, numpad,
    navigation cluster, OEM punctuation).
  • WineFileDialog.cs — Win32 file picker on an STA thread. Backs
    the new requestFilePicker PI ↔ plugin round-trip.
  • WindowsInputMessageDispatcher.cs — 3-line branch that
    delegates to LinuxInputBridge when WineDetector.IsWine.
  • ControlPanelKey.cs / SCActionBase.cs — handle
    requestFilePicker for the four Data.p4k pickers and the
    AdaptiveKey/ToggleKey audio pickers respectively.
  • sc-file-picker.js — clicking the picker button now sends
    requestFilePicker to the plugin and listens for filePickerResult
    instead of relying on <input type="file">.
  • manifest.json — appends {"Platform":"linux","MinimumVersion":"5.0"}
    to the OS array.

Cross-build accommodations (no-op on Windows CI):

  • PluginCore.csproj adds
    <EnableWindowsTargeting>true</EnableWindowsTargeting> and
    swaps \ to / in the PiAssetsBuilder pre-build <Exec> command.
  • Tests.csproj carries the same EnableWindowsTargeting.
  • DirectInputDisplayMapper.cs substitutes the ['+']
    collection-expression to string.Split with a static readonly
    char[] field (sidesteps a CS0121 ambiguity on some .NET 8 SDK
    builds, also satisfies CA1861).

2️⃣ Use IFileOpenDialog COM API for the file picker

Replaces the initial GetOpenFileNameW P/Invoke in WineFileDialog
with CLSID_FileOpenDialog / IFileOpenDialog (the Vista-era
Common Item Dialog) — same public API on the C# side, modern
shell-style dialog on the user side, equally well supported under
Wine.

docs/linux-wine.md is updated to match and adds two prerequisite
sections that surfaced during testing:

  • winetricks dotnet8 / dotnetdesktop8 inside the Wine prefix
    that spawns the plugin (Wine-Mono alone wasn't enough on
    CachyOS).
  • GStreamer host plugins for click-sound playback (Wine's
    winegstreamer delegates to the host's GStreamer; in particular
    gst-plugins-good provides id3demux which MP3 files require —
    without it the click sound silently fails with
    Missing decoder: application/x-id3 in the Wine debug log).

Test plan

Reviewer-side checks recommended on a Windows machine (CI already
covers dotnet build SCStreamDeck.sln -c Release):

  • Build the solution — no analyzer/CS0121/CA1861 regressions
  • Drop an AdaptiveKey button on the deck, click "FILE", confirm a
    modern shell-style Windows file dialog appears and the picked
    path is stored in the button's clickSoundPath setting
  • Press the button → SC action fires + click sound plays as
    before
  • Verify on a Wine-less Windows install that
    WineDetector.IsWine returns false (debugger or temporary
    log assertion); the SendInput path should run unchanged

Notes

  • WindowsInput.csproj keeps its legacy MSBuild format — only three
    additive <Compile Include> lines are added. Linux contributors
    building locally need to convert it to SDK-style
    (net8.0-windows) themselves; this is documented in
    docs/linux-wine.md and intentionally not part of the upstream
    diff to keep the project file changes minimal for the Windows
    maintainer.

Detect Wine at startup via ntdll!wine_get_version and route
IInputMessageDispatcher through ydotool (uinput) when detected, so
synthetic keystrokes reach native Linux apps (e.g. Star Citizen under
Proton) instead of staying inside the Wine prefix. Bridge the Property
Inspector picker through Win32 GetOpenFileNameW (rendered by Wine as a
native GTK dialog) so PI settings receive real file paths.

Zero behaviour change on Windows native — WineDetector returns false
and the existing SendInput path runs unchanged. See docs/linux-wine.md
for architecture, prerequisites, and build notes.
Replace the legacy comdlg32!GetOpenFileNameW P/Invoke with the
modern Common Item Dialog (CLSID_FileOpenDialog / IFileOpenDialog,
introduced in Vista). Public API of WineFileDialog is unchanged so
callers don't move. Windows native users now get the shell-style
file picker instead of the XP-era dialog; Wine renders the same COM
dialog via its dlls/comdlg32/itemdlg.c implementation.

docs/linux-wine.md is updated to match (describes the COM flow,
drops the "future migration" note) and gains two new prerequisite
sections:

  - winetricks dotnet8 / dotnetdesktop8 inside the Wine prefix that
    spawns the plugin.
  - GStreamer plugins on the Linux host. gst-plugins-good in
    particular is required for MP3 click sounds (provides id3demux);
    without it winegstreamer logs "Missing decoder: application/x-id3"
    and the sound never plays.
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.

1 participant