Skip to content

[WIP] Add Linux support#742

Closed
MiahNelah wants to merge 8 commits intoWhiteMagic:developfrom
MiahNelah:feature/cross-platform
Closed

[WIP] Add Linux support#742
MiahNelah wants to merge 8 commits intoWhiteMagic:developfrom
MiahNelah:feature/cross-platform

Conversation

@MiahNelah
Copy link
Copy Markdown

@MiahNelah MiahNelah commented Mar 22, 2026

Context

As a long-time user of Joystick Gremlin — a truly fantastic tool — I've been particularly frustrated since wanting to migrate to Linux, as the native solutions, while functional, are far less advanced. I therefore took on the somewhat ambitious challenge of trying to port JG to Linux! Since this work is starting to bear fruit, I thought it would be a good idea to open a PR (even though there are still quite a few issues to resolve) in order to start the conversation.

In practice, I imagine that Joystick Gremlin is a project that is far from easy to develop and maintain, and that it has always been very strongly tied to Windows. I understand that someone showing up out of nowhere to propose Linux support could represent a lot of potential headaches! So in all honesty, I would completely understand if this PR were rejected for being too large, too complex to maintain over time, or simply not aligned with the project's desired philosophy. That said, I still think it's worth opening the discussion, as more and more users are migrating to Linux.

I want to be clear that my main goals throughout the development of this PR were:

  • to always guarantee that the Windows version continues to work exactly as before
  • to minimize changes to Joystick Gremlin's core code
  • to introduce no regression in test coverage

If I got some free time soon, and if you have some interest in, I'll try to add a demo !

Overview of Changes

(I know, there's a lot of changes ! And I messed up my commits at some point so it's not easy to read ! Sorry for that...)

Adding Linux support to Joystick Gremlin is not extremely complicated on paper: there are only two Windows-specific dependencies to replace, and the equivalent building blocks exist on Linux and are relatively straightforward to work with. The bulk of the work therefore consists of teaching Joystick Gremlin to use these building blocks the same way it would use the Windows DLLs.

The dependencies to replace are:

  • DILL.dll, to be replaced by evdev for reading inputs from all physical devices
  • VJoyInterface.dll, to be replaced by uinput for creating and simulating virtual devices

Joystick Gremlin's code is already very well organized in the sense that the DILL and VJoy wrappers are already identified and isolated. I therefore simply had to develop a Platform Abstraction Layer (PAL) to transparently manage the Windows and Linux dependencies.

There are 3 PALs in total:

  • a PAL for DILL.dll
  • a PAL for VJoyInterface.dll
  • a PAL specific to Gremlin that abstracts other OS-specific elements (process monitoring, system directory management, admin rights handling, ...)

Most of the changes made to Joystick Gremlin's core only aim to replace direct Windows system calls with calls to these abstraction layers.

Within the PALs, the Windows-specific code has not been rewritten: the original code has simply been moved and reorganized to fit into this new architecture.

Beyond that, the vast majority of the new additions concern the Linux-specific implementation.

Supported Features

This section is a work in progress: Joystick Gremlin has many features, and it's not easy to quickly go through all of them and validate everything!

Overall, I would say that the core features are working:

  • The input viewer is fully functional: it correctly captures all inputs from all physical devices
  • Action simulation via keyboard, mouse, or virtual devices works
  • The application automatically creates 4 "VJoy" devices by default (configurable) with full feature support (including the maximum number of buttons allocated by VJoy), and simulation is functional
  • All "pure Python" features of Joystick Gremlin work identically: rule management, configuration, response curves, modes, profiles, etc.
  • TTS suppport thanks to espeak-ng (even if there is no TTS action in R14's actions list yet)
  • Active window for dynamic profile switching should work (Wayland only)

Known Bugs

It is not easy to distinguish existing bugs in R14 from bugs introduced by this PR, but I'm working on it! This list is therefore not exhaustive:

  • An error occurs when saving a profile to disk

Transparency About AI Usage

I know this is a debated topic, so I prefer to be upfront about it. If this is a major concern, I fully understand and have no objection. Everyone is free to act as they see fit.

Although I was fully aware of the major changes needed to make the project Linux-compatible, I used Claude Code to handle the bulk of the code writing, as the scope of the work was very significant. My original intention was more to produce a proof of concept than anything else. I personally oversaw all design decisions and made sure the AI did not touch anything fundamental in Joystick Gremlin. I also used AI to generate commit descriptions that are admittedly verbose, but thorough and relevant.

MiahNelah and others added 8 commits March 22, 2026 19:15
… guard

Axis.value setter computed the raw vJoy position as:
  half_range + half_range * value
which is correct only when axis_min == 0.  The guard that enforced that
assumption (raise VJoyError if min != 0) makes it impossible to use an
axis whose DLL-reported minimum is non-zero.

Fix both issues together:
  - Remove the guard: a non-zero minimum is a valid hardware configuration.
  - Add self._min_value to the formula so the result is always in
    [axis_min, axis_max] regardless of the reported minimum.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs in the original code:
1. The hook was always installed unconditionally (the second bare assignment
   outside the if-block overwrote the conditional one, making the guard a
   no-op).
2. The guard relied on the executable being named "joystick_gremlin.exe",
   which is Windows-only and would break any future non-Windows build.

Replace both with a single check on sys.frozen, which PyInstaller sets to
True on all platforms when the application is bundled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduce three PAL trees – dill/platform/, gremlin/platform/, vjoy/platform/ –
and the central gremlin/app_platform.py entry point.

Each tree contains:
  - base/  : abstract base classes (ABC) that define the interface
  - windows/: full Win32 implementations (moved from original modules)
  - linux/ : stub implementations that compile and import on Linux

app_platform.py selects the right backend at import time via sys.platform.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
keyboard.py, sendinput.py, windows_event_hook.py, tts.py, process_monitor.py
now contain only a short module docstring and platform-dispatch imports.
All Win32 implementation code has been moved to gremlin/platform/windows/.
Equivalent Linux stubs live in gremlin/platform/linux/.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
dill/__init__.py
  - Remove inline _GUID ctypes struct definition (moved to dill/platform/base/types.py)
  - Import portable struct types and AbstractDillBackend from the PAL base layer
  - Use app_backend for device enumeration; keeps public API identical

vjoy/vjoy_interface.py
  - Replace direct ctypes DLL loading with AbstractVJoyBackend facade
  - Select WindowsVJoyBackend or LinuxVJoyBackend at import time via sys.platform
  - VJoyInterface class becomes a thin dispatcher over the backend

vjoy/vjoy.py
  - Use the new VJoyInterface API (GetVJDAxisMin/Max now return values directly)
  - Fix axis value calculation: add self._min_value offset (was missing, causing
    off-by-min errors when axis min != 0)
  - Allow VJoyState.Missing when acquiring a device (Linux creates vJoy devices
    on first acquire)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
gremlin/util.py
  - is_user_admin(): delegate to app_backend instead of ctypes.windll.shell32
  - userprofile_path(): delegate to app_backend.user_data_dir() (cross-platform)

gremlin/profile.py
  - Use app_backend.temp_dir() instead of Windows-only %TEMP% env variable

gremlin/ui/backend.py
  - Add executableFileFilter Qt property (returns "*.exe" on Windows, "All files"
    on Linux) — consumed by QML file dialogs
  - Keep a stable _default_identifier on UIState to prevent the transient
    InputIdentifier from being garbage-collected before QML finishes using it

joystick_gremlin.py
  - Replace SetCurrentProcessExplicitAppUserModelID ctypes call with
    app_backend.configure_app()
  - Use app_backend.font_family() so the UI font is chosen per platform
  - Fix double sys.excepthook assignment (dead code); use sys.frozen check
    instead of Windows-only .exe name comparison

qml/OptionProfileAutoLoading.qml
  - Use backend.executableFileFilter instead of hardcoded "*.exe"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pyproject.toml
  - Make pywin32 Windows-only (platform marker: sys_platform == 'win32')
  - Add evdev >=1.7 as a Linux-only dependency (used by Linux input backend)

gremlin/repeater.py
  - Fix stale PyQt5 import; replace with PySide6 (matches the rest of the project)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both fixes are prerequisites for the cross-platform branch:
- fix/vjoy-axis: axis formula must handle non-zero min_value (required on Linux)
- fix/excepthook: sys.frozen check works on all platforms; .exe name check does not

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@MiahNelah MiahNelah force-pushed the feature/cross-platform branch 2 times, most recently from 3cb8382 to 60d4bf8 Compare March 23, 2026 12:25
@WhiteMagic
Copy link
Copy Markdown
Owner

WhiteMagic commented Mar 23, 2026

Modifying the code base such that it can run on linux is something that can be done. However, that would have to happen in a significantly different way. There would have to be an actual discussion on the approach, as there is a significant design space and whatever an LLM comes up with is likely short-sighted.

Using LLMs is not bad per se, but they are problematic if used to whole sale modify large chunks of code in one fell swoop as done here. Also even if this entire PR was written by a human it would not be mergable either, as the amount of change is too big and too spread out.

There are also changes made that do not relate to the linux side or are plain dangerous. For example, the modification of the vJoy axis value clearly shows the LLM did not understand the code and the supervising person also failed to realize the intent. The check is mandatory as it safeguards an implied assumption that should hold by DirectInput definition but, should it not hold, the code should fail as it's an exceptional circumstance.

The way to integrate linux support into Gremlin is something along the following lines:

  1. Discussion and outline of an architecture design of the following components which are independent of each other but designed with the end goal in mind
    • Generalize user input framework (directinput, xinput, keyboard, ...)
    • Generalized virtual device output framework (vJoy, vigem, uinput wrapper, ...)
    • Design and architecture to support multiple operating systems in Gremlin
  2. Extensive testing coverage, especially end-to-end evaluation, to ensure subtle aspects don't silently get broken
  3. Many focused PRs implementing singular aspects slowly building towards the goal

Without a good generalizable foundation there is a strong limitation how the Windows version can evolve and there is a significant risk of the entire software becoming brittle due to local hacks and patches if the support is not architected properly.

@MiahNelah
Copy link
Copy Markdown
Author

Thank you for your very insightful feedback. From the way you bring up the subject, I gather that you consider the abstraction of I/O and virtual device management to be insufficient, and that a more comprehensive design is needed to incorporate fully functional cross-platform management. I admit I’d be curious to hear your perspective, but for now I imagine your priorities are currently fully focused on R14.

As I mentioned earlier, my goal was to assess the feasibility of such a project. It turns out that while this is a feasible challenge, it does present many subtle issues that require a thorough understanding of how I/O works. In that sense, LLMs are indeed a practical and quick tool for producing a first draft, but I’ve noticed that they still struggle to fully grasp the intricacies of Gremlin.

For now, I'm going to close this PR to spare you unnecessary clutter and I hope I won't had make you loose too much time.

@MiahNelah MiahNelah closed this Mar 23, 2026
@WhiteMagic
Copy link
Copy Markdown
Owner

WhiteMagic commented Mar 23, 2026

The issue with the abstraction is that it replicates the existing interface which is far from ideal. Replicating it also makes adding new inputs or outputs a significant burden as new platform interface and then two or more concrete implementations need to be written, when this may make no sense. For example on Windows vJoy and vigem are sensible output options, but on linux there is no such thing as xinput so replicating a "vigem for linux" bit would be pointless. So it would be better to have several output options that work with their corresponding actions. So vjoy, vigem, uinput, etc. Those would be able to decide if they exist etc. The map to vjoy action, for example, checks if vJoy devices exist and if they don't it doesn't show up as available.

In that sense the entire input and output side would ideally be generic enough to allow many different "providers" to exist which then may happen to be os specific.

Though all of the above is my current thinking and likely incomplete and has short comings, which is why the change to the profile from R13 to R14 took probably four or five refinements as I encountered new things it couldn't do or things that would be better for long-term usability if it changed. That's where the discussion comes in to find what flaws exist or constraints are overlooked etc.

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