Skip to content

Add Docky plugin - #1102

Open
datbird wants to merge 8 commits into
SteamDeckHomebrew:mainfrom
datbird:add/docky
Open

datbird wants to merge 8 commits into
SteamDeckHomebrew:mainfrom
datbird:add/docky

Conversation

@datbird

@datbird datbird commented Aug 7, 2026 •

Copy link
Copy Markdown

Add Docky to Plugin Store

Docky is a Steam Deck automation plugin on a Task → Action → Mode model, run manually or by triggers (dock/undock, AC power, controller connect, resume, startup). It ships built-in dock fixes (docked-audio sink switch, controller order, gamescope composition/stretch, TDP cap), a temp→RPM fan-control engine with named profiles, and full Sunshine game-streaming control.

How it differs from decky-sunshine: decky-sunshine only starts/stops Sunshine. Docky is a broader automation framework whose Sunshine support is one integrated Action — it can drive Sunshine directly or defer to decky-sunshine — and adds self-healing mDNS discovery, capture-health recovery, and Sunshine⇄Desktop GPU coexistence not offered elsewhere.

Root disclosure: Docky declares flags:["root"]. For Sunshine KMS capture, it creates a setuid-root copy of the system's own bwrap under /var/lib/docky (root-owned chain; /tmp and /run are nosuid on SteamOS). Rationale and the security hardening (TOCTOU guards, clean_env, subprocess timeouts) are in docs/design-notes.md and docs/decky-integration.md. It bundles no third-party binaries — the backend is pure Python, the frontend builds from source.

Repo: https://github.com/datbird/docky (v1.4.8)

Task Checklist

Developer

  • I am the original author or an authorized maintainer of this plugin.
  • I have abided by the licenses of the libraries I am utilizing, including attaching license notices where appropriate.
  • Generative AI was NOT used to write a majority of the code I am submitting.

Plugin

  • I have verified that my plugin works properly on the Stable and Beta update channels of SteamOS.
  • I have verified my plugin is unique or provides more/alternative functionality to a plugin already on the store.

Backend

  • No: I am using a custom backend other than Python.
  • No: I am using a tool or software from a 3rd party FOSS project that does not have it's dependencies statically linked.
  • No: I am using a custom binary that has all of it's dependencies statically linked.

Community

  • I have tested and left feedback on two other pull requests for new or updating plugins.
  • I have commented links to my testing report in this PR.

Testing

  • Tested by a third party on SteamOS Stable or Beta update channel.

@datbird
datbird requested a review from a team as a code owner August 7, 2026 03:23
@github-actions github-actions Bot added the plugin-addition Adding a plugin to the Plugin Store label Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

Issues Found

No issues with your PR description were found.

Next Steps

  1. If we found any issues above, please edit your pull request description to resolve them and leave a comment saying you've done so.
  2. For the quickest review, please see the Community section of the pull request template for how you can help other developers.
  3. Once your description is correct, a maintainer will review your pull request as soon as possible.

Thank you for your contribution! If you need any help, please reach out on our Discord server. ❤️

@datbird datbird changed the title Add/docky Add Docky plugin Aug 7, 2026
@datbird

datbird commented Aug 7, 2026 •

Copy link
Copy Markdown
Author

Completed the community testing requirements, now three plugins. All were built from source at each PR's pinned commit and tested on a Steam Deck OLED running SteamOS 3.8.16 Stable. The first two on Decky Loader v3.2.6, the third on v3.2.8.

Testing reports:

All three work as advertised. I've reported what I found in each, including a few backend issues worth a look, and left all three installed so I can re-test if the authors push changes.

Still looking for a third party to test Docky on SteamOS Stable or Beta. Happy to help however is useful in the meantime.

@github-actions

Copy link
Copy Markdown

This pull request is stale as no updates or testing reports have been posted within the last 7 days. Please ensure you are actively recruiting testers or resolving the issues discussed. If you do not remove the stale label or comment, this will be closed in 7 days. Please close this pull request if you need more time to resolve issues so we can keep our repo healthy.

@datbird

datbird commented Aug 15, 2026

Copy link
Copy Markdown
Author

Continued efforts to try and get plugin tested and approved have been fruitless on the Decky plugin Discord. I am continuing to stay engaged on the Discord and recruit testers. Please keep the PR open until I get someone to review.

@datbird

datbird commented Aug 21, 2026

Copy link
Copy Markdown
Author

On this plugin's authorship history

I would rather state this up front than have a reviewer find it on their own.

In mid July I posted Docky in the Decky Discord looking for testers. AAGaming
looked at it and said it could not be accepted because it was mostly LLM code.
That was a fair read of the repo as it stood. AAGaming and NGnius then clarified
the actual policy for me: LLM assistance is fine for boilerplate and normal
tooling, but you should not write a plugin through an agent, because it produces
code that is hard to review, hard to maintain, and that the author cannot fix
when it breaks. I asked directly whether the plugin would get equal
consideration if I went through it myself, rewrote it, and published my own
commits. I was told yes.

That is what I did. The git history shows it plainly rather than hiding it.

Everything up to commit 4e58fdc (2026-07-15) carries a Co-Authored-By: Claude
trailer. That is 75 of the repo's 95 commits. I am not rewriting history to
erase them.

Every commit after that point is mine, with no trailer. That is 17 commits from
2026-07-16 to 2026-08-06, covering 36 files, 3,209 insertions and 2,767
deletions. It rewrote every backend module (main.py, docky.py, sunshine.py,
deckops.py, padswap.py, mdns.py, sysenv.py) and every frontend component.
It was not a cosmetic pass. I went through the plugin file by file, rewrote the
parts I did not want to own, fixed real bugs I found on the way, and documented
the platform behaviour I had to learn in order to do it.

"I refactored it" is easy to say and hard to verify, so here is what actually
came out of that work:

  • Removed a force HDR toggle built on a wrong premise. Latching gamescope's
    HDR atom does not gate a client's ability to request HDR. HDR is negotiated in
    the stream protocol from the encoder. The toggle enabled nothing and broke every
    SDR client. I removed it and added a migration that unlatches the atom on
    upgrade, so nobody is left stuck in HDR with no UI to undo it.
  • Tracked down a Moonlight black screen to the encoder, not to capture. The
    Deck's hevc_vaapi emits an IDR the bitstream parser cannot read on Mesa 26.0.6
    and Van Gogh. Sunshine advertises HEVC from an encoder open probe, which proves
    only that the encoder starts, not that its output decodes. That is now a
    documented toggle with a warning instead of a silent black screen.
  • Found that Steam's on screen keyboard swallows the click on any Decky panel
    button behind it.
    pointerdown reaches the button, but the following click
    is retargeted to the element underneath, so React's onClick never runs. I
    proved it over CDP on the device rather than guessing, then fixed it with a
    pointerdown handler alongside onClick behind a timestamp guard. This is not
    specific to Docky and will affect any plugin with a text field above a submit
    button.
  • Found that decky-frontend-lib's types describe intent, not runtime. DFL
    finds Steam's components by webpack sniffing, so the hand written types
    describe what it expects, not what the injected component honours. TextField
    silently drops onKeyDown. SliderField is not exposed and crashes the panel
    with React Add AutoFlatpaks #130. A green tsc proves nothing there, so I verify props on
    device and catch events on a wrapper in the capture phase.
  • Removed api_version: 1 from plugin.json after it broke every panel to
    backend call. Docky uses DFL's legacy callPluginMethod, and the loader
    rejects legacy methods above api_version 0. The store template ships
    api_version: 1 because it uses @decky/ui, which is why I got it wrong in the
    first place.

I am not claiming the plugin is flawless. I am claiming I understand it well
enough to maintain it, which was the concern actually raised, and the items above
are the evidence for that rather than an assertion.

If the history still disqualifies it, I would rather hear that directly than keep
the PR open on a false hope.

@github-actions

Copy link
Copy Markdown

This pull request is stale as no updates or testing reports have been posted within the last 7 days. Please ensure you are actively recruiting testers or resolving the issues discussed. If you do not remove the stale label or comment, this will be closed in 7 days. Please close this pull request if you need more time to resolve issues so we can keep our repo healthy.

@datbird

datbird commented Aug 29, 2026

Copy link
Copy Markdown
Author

This is not stale and I remain actively attempting to recruit testers for this plugin.

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

This pull request is stale as no updates or testing reports have been posted within the last 7 days. Please ensure you are actively recruiting testers or resolving the issues discussed. If you do not remove the stale label or comment, this will be closed in 7 days. Please close this pull request if you need more time to resolve issues so we can keep our repo healthy.

@datbird

datbird commented Sep 12, 2026

Copy link
Copy Markdown
Author

This is not stale and I remain actively attempting to recruit testers for this plugin.

@zarif98

zarif98 commented Sep 15, 2026

Copy link
Copy Markdown

This is actually a really cool plugin. I don't have a steam dock, unfortunately, but I really liked the fan controls that were implemented. The TDP cap that stays enforced per profile is also something I can't believe hasn't been implemented yet. I would use it all of the time!

The only issue that I saw was privilege escalation where the code chowns its config to the "user" so it looks like anything running as deck could edit the config and get a command run as root on the next dock. Also, is it running the legacy plugin API?

@datbird

datbird commented Sep 15, 2026 •

Copy link
Copy Markdown
Author

Thanks for the review, and glad you liked the fan control and the enforced TDP profiles!

You are right about the config. The backend runs as root and a task can run any command, so a config the deck user can write is a free path to root. Fixed for 1.4.9: config.json and state.json move to /var/lib/docky, root owned and world readable. Root owning just the file was not enough, since ~/.config belongs to deck and a rename only needs write permission on the parent directory. An existing config is imported once on first load and the old file is renamed. Editing by hand now needs sudo. The panel does not, since it writes through the root backend.

Yes, it is the legacy decky-frontend-lib API with api_version absent. That was the choice when I started the plugin and I have not moved to @decky/ui. It is on my list for someday.

I will re-pin the submodule once 1.4.9 is tagged.

@datbird

datbird commented Sep 16, 2026

Copy link
Copy Markdown
Author

v1.4.11 is up and the submodule is re-pinned to it. Both fixes from the review above are in, and I tested both on my own Deck (OLED, SteamOS 3.8.16 Stable, Decky 3.2.8).

The config move: config.json and state.json now live in /var/lib/docky, root owned. An existing config in ~/.config/docky is imported once on first load and the old file is renamed. Verified on my Deck, the migrated file was byte identical to the original.

While I was in there I fixed a second problem the same review made me look at. The plugin was being SIGKILLed on every unload, so its fan hand-back never ran. The cause is that an await inside _unload never resumes, because the loop stops ticking once Decky asks the plugin to stop. Doing the work synchronously fixed it. Measured on device, with jupiter-fan-control stopped first:

Plugin Docky (v1.4.10) still alive 5 seconds after stop request! Sending SIGKILL!
Plugin Docky (v1.4.10) has been stopped in 5.1s
Loaded Docky (v1.4.11)
Docky: handed the fan back to SteamOS
Plugin Docky (v1.4.11) has been stopped in 0.1s

Sunshine and its bwrap child are still left running on purpose, so a loader restart never interrupts a live stream.

This is ready for someone else to test. Since the build here needs maintainer approval before the testing store can serve it, the easiest route is Decky's developer mode, Install Plugin from URL:

https://github.com/datbird/docky/releases/latest/download/Docky.zip

Happy to answer anything or fix what a tester finds.

@AAGaming00 AAGaming00 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.

code is fine but i think this plugin encroaches on the functionality of a lot of other plugins and those functionalities should instead integrate with those plugins or be contributions to them?

not going to fully block for that but please think about it. maybe we can chat about this in #plugin-dev
i'll let it into testing at least

@datbird

datbird commented Sep 21, 2026

Copy link
Copy Markdown
Author

code is fine but i think this plugin encroaches on the functionality of a lot of other plugins and those functionalities should instead integrate with those plugins or be contributions to them?

not going to fully block for that but please think about it. maybe we can chat about this in #plugin-dev i'll let it into testing at least

Thanks for the approval, and for the honest note.

You are right that there is duplication on the surface. The heart of Docky is a trigger engine with one UI in front of it. Detect something in the environment, then run what the user configured. Dock and undock, AC power, controller connect, resume from sleep, startup.

Most of the duplication sits on the action side, and the reason is that a plugin cannot call another plugin's backend. I cannot ask Fantastic to apply a fan curve when I dock, so Docky has to own the fan. Same story for TDP and PowerTools. If there is an inter-plugin path I missed, I would rather delegate than duplicate, and I will build the integration.

Sunshine is a slightly different case. decky-sunshine covers the common path well, but the install and config hooks I needed were hard to reach from outside it. I kept my own option and left the choice to the user, and I am happy to keep both.

I did look before I built. Of the 110 plugins in the live store feed, none is a general trigger engine. Bash Shortcuts comes closest with its Hooks, but those fire on Steam software events (log in, game start, sleep, shutdown), not on hardware state, and its upstream repo is archived. AutoSuspend, PowerTools and Steamback each do one fixed rule.

Happy to talk it through in #plugin-dev. If I did overlook one, point me at it and I will integrate instead.

@zarif98

zarif98 commented Sep 25, 2026

Copy link
Copy Markdown

Already had a chance to test it earlier but wanted to test once more to try it out but works great! The built-in actions are something I wish Steam would've originally put in.

This branch is waiting to be deployed

1 waiting deployment
testing_env — ba6663d6 Waiting Sep 22, 2026 by datbird via Build plugin (Docky, true) #4027
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

plugin-addition Adding a plugin to the Plugin Store

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants