SamonsLol/minideck

Python

2

16 commits

updated Oct 9, 2026

See the code

See what people are saying

SourceMessageScoreDate

I built MiniDeck, a free open-source alternative to a Stream Deck that runs in your phone's browser (r/SideProject)

I wanted a customizable control deck for my computer without buying extra hardware, so I built MiniDeck. It turns your phone into a control surface for Windows, macOS, and Linux. There's no mobile app to install — just open the web interface in your phone's browser. **Features:** * Custom buttons…

1

Oct 9, 2026

README

MiniDeck

English · Español · 🌐 minideck.samons.co

Website CI License: AGPL v3 Python Platforms Buy Me A Coffee

Turn your phone into a Stream Deck for your PC or Mac. There's no app to install on the phone: it's a PWA you open in the browser. The server is Python (FastAPI + WebSocket) and the frontend is plain HTML/CSS/JS with no build step.

Issues and PRs are welcome in English or Spanish.

See it in action: the website minideck.samons.co is built like the app itself — tap the keys, toggle the stateful buttons and switch themes.

Tour: pages, folders, editor, language and themes

Home page with Now Playing, buttons and mixer OBS page with stateful record and live buttons

Media page LCD theme

Features

  • Buttons for keyboard shortcuts, apps and websites, shell commands, macros, webhooks (n8n, Home Assistant…), shutdown/lock, and more.
  • Long press: a second action per button (e.g. tap = cancel shutdown, hold = shut down in 60 s).
  • Folders: a button can open another page, and back returns.
  • Stateful buttons: icon, color and label change with live state (OBS recording, Discord muted, a Home Assistant light on…).
  • Live widgets: volume, per-app mixer (Windows), Now Playing with album art, Discord (mute/deafen and who's speaking), CPU/RAM, weather, Git, Docker, OBS Studio and Home Assistant.
  • Visual editor right on the phone: pages, icons (Lucide), colors, drag and drop, undo, and deck import/export.
  • Works offline on your LAN: icons are cached by the server after first use.
  • Spanish and English UI (tap ES/EN in the top bar).
  • Works without JavaScript: /basic is a server-rendered version with plain links and forms (use the deck, folders, long press, sliders, OBS, Home Assistant, and a full deck editor). Browsers without JS land there automatically; JavaScript only adds real-time updates, drag and drop and animations.
  • Plugins in Python + JS, plus custom widgets built in the Control Panel (/panel.html).
  • Secure QR pairing: nobody else on your network can control your computer.

Install

Option 1: download (no Python needed)

Grab the latest build from the website or from Releases:

  • Windows: MiniDeck-…-windows.zip → unzip → run MiniDeck.exe. A tray icon appears: right-click → Mostrar QR.
  • macOS: MiniDeck-…-macos.dmg → drag to Applications. The first time: right-click → Open (the app isn't notarized). See README_MAC.md for permissions.

Option 2: pipx

pipx install git+https://github.com/SamonsLol/minideck.git
minideck            # or: minideck --port 9000
minideck-tray       # Windows/Linux: tray icon instead of a console

Option 3: from source

git clone https://github.com/SamonsLol/minideck.git
cd minideck
python -m venv .venv
# Windows:        .venv\Scripts\activate
# macOS / Linux:  source .venv/bin/activate
pip install -r requirements.txt
cd server
python main.py

On Windows you can also double-click MiniDeck.bat (no console, tray icon).

Pair your phone

  1. On the computer, open http://localhost:8765/qr.
  2. Scan the QR code with your phone's camera. MiniDeck opens already paired.
  3. Use Share → Add to Home Screen to run it full screen.

The app installed on the home screen doesn't share storage with the browser (especially on iPhone), so the first time it opens it asks to pair again: tap 📷 Scan QR code and point at the computer's screen (over plain HTTP it takes a photo of the QR; with HTTPS it scans live), or type the code shown under the QR.

The phone and the computer must be on the same Wi-Fi network. On Windows, allow Python/MiniDeck on private networks when the firewall asks.

Security

MiniDeck can run commands on your computer, so:

  • Every request to /api and /ws requires a pairing token. It's generated on first run and stored in auth_token in the config folder.
  • The QR code and token are only shown on the computer itself (localhost).
  • Don't share the QR code or the pairing code. To revoke every paired device, delete auth_token and restart; a new one is generated.
  • Never expose port 8765 to the internet (no port forwarding). For remote access, use a VPN such as Tailscale or WireGuard.

More details and how to report vulnerabilities: SECURITY.md.

Configuring the deck

The deck lives in deck.json:

ModeLocation
From source (python main.py)server/config/deck.json
Packaged app or pipxWindows %APPDATA%\MiniDeck\ · macOS ~/Library/Application Support/MiniDeck/ · Linux ~/.config/minideck/

On first run it's created from the default deck for your OS (server/config/deck.default.<windows|macos|linux>.json). The easiest way to edit it is from the phone with the ✎ button. You can also edit the JSON by hand: when you save, every connected client updates automatically. Every save keeps a backup (the last 30, in backups/), so the editor's ↶ Undo can go back.

{
  "id": "rec",
  "label": "Record",
  "icon": "lucide:circle",
  "color": "#8a93a3",
  "action": "obs_record_toggle",
  "params": {},
  "longAction": "obs_replay_save",
  "longParams": {},
  "when": { "key": "obs.recording", "icon": "lucide:circle-stop",
            "color": "#f87171", "label": "Recording" }
}
FieldMeaning
action / paramsWhat a tap does.
longAction / longParamsOptional: what a long press (~0.5 s) does.
whenOptional: look while a live-state value is true. key is a path in the state (obs.recording, discord.mute, ha.entities.light.living.state), equals compares against a value; icon, color and label override the button.
"action": "page"Folder: {"page": "<page id>"} opens that page, {"page": "back"} goes back.
w / hWidth/height in grid cells ("w": "full" = whole row).

Built-in actions

ActionParamsPlatforms
hotkey{"keys": "ctrl+shift+m"}all
type_text{"text": "hello"}all
launch{"path": "..."} or {"app": "code"}all
command{"cmd": "...", "shell": "powershell"}all
website{"url": "https://..."}all
http_request{"url", "method", "body", "headers"}all
now_playing{"cmd": "play_pause" | "next" | "previous" | "seek", "position": 90}Win, Mac
volume_set / volume_change / volume_mute{"level": 50} / {"delta": 5} / {}Win, Mac
mixer_set / mixer_mute{"app": "chrome.exe", "level": 40}Win
discord_mute / discord_deafen{}all
obs_scene, obs_record_toggle, obs_stream_toggle, obs_mute_toggle, obs_replay_save…see OBS pluginall
ha_toggle, ha_scene, ha_servicesee Home Assistant pluginall
lock{}all
power{"mode": "shutdown" | "restart" | "sleep" | "cancel"}all
kill_process{"name": "app.exe"}all
mouse_click / mouse_move{"x": 100, "y": 200}all
macro{"steps": [{"action": ..., "params": ...}, {"delay_ms": 500}]}all
screenshot, show_desktop, shortcut, clipboard_copy, ocr_capture, audio_output…see sourceMac

Parameters are validated before running, so a mistake shows a clear message on the phone. Every action has a timeout (30 s by default), so a hung action can't leave the phone waiting. The editor fills in a parameter template when you pick an action. The exact list on your machine: GET /api/actions/schema.

Discord needs an app created in the Discord Developer Portal: see the instructions at the top of server/actions/discord_rpc.py and the template in server/config/discord.example.json.

Plugins

Add actions, live data, and widgets without touching MiniDeck's code:

%APPDATA%\MiniDeck\plugins\my_plugin\     (or ~/Library/Application Support/MiniDeck/plugins/…)
├── plugin.json   ← name, version, platforms, dependencies, settings
├── plugin.py     ← @action("my_plugin_do")
├── widget.js     ← MiniDeck.registerWidget("my_plugin", {...})
└── widget.css
from actions import action

@action("my_plugin_do", schema={"name": {"type": "str", "required": True}})
def do(params: dict):
    return {"message": f"Hi {params['name']}"}

Full guide (in Spanish): docs/PLUGINS.md · Template: examples/plugins/hello · Community index: docs/PLUGIN_INDEX.md

Server options

OptionDefaultPurpose
--port / MINIDECK_PORT8765Port
--host / MINIDECK_HOST0.0.0.0Interface (use 127.0.0.1 for local only)
MINIDECK_TOKENrandomFixed token (e.g. the same one on several machines)
MINIDECK_CONFIG_DIRsee aboveFolder for deck.json, backups, the token, and discord.json
MINIDECK_PLUGINS_DIR<data>/pluginsUser plugins folder
MINIDECK_LOG_DIR<data>/logsRotating log (minideck.log, ~3 MB max). Also at GET /api/logs
MINIDECK_ICON_CACHE<data>/icon-cacheDownloaded icons
MINIDECK_NO_AUTH=1—Disables the token. Not recommended.

Project layout

minideck/
├── server/
│   ├── main.py            # FastAPI + WebSocket + routes
│   ├── auth.py            # pairing token
│   ├── paths.py           # per-OS paths
│   ├── icons.py           # icon proxy with disk cache
│   ├── actions/           # built-in actions (one module per platform)
│   ├── plugins/           # bundled plugins (obs, homeassistant, sysmon, clock, indicators)
│   ├── config/            # default decks per OS and templates
│   ├── app_tray.py        # tray icon (Windows/Linux, entry point of the .exe)
│   ├── app_menubar.py     # menu bar app (macOS)
│   └── run.ps1            # tray launcher without pystray (Windows)
├── frontend/              # PWA (plain HTML/CSS/JS, no build; i18n.js = translations)
├── packaging/             # pip/pipx launcher
├── examples/plugins/      # plugin template
├── docs/                  # plugin guide and screenshots
├── build/                 # PyInstaller specs (Windows .exe, macOS .app), DMG, certificates
└── tests/                 # server, plugins, packaging and browser UI tests

Contributing

Welcome aboard! Read CONTRIBUTING.md. For development:

pip install -r requirements-dev.txt
python -m playwright install chromium   # for the browser UI tests
pytest
ruff check server tests examples packaging

To publish a release: git tag v1.1.0 && git push origin v1.1.0. GitHub Actions builds the Windows .zip, the macOS .dmg and the Python package, and attaches them to the release.

Support

MiniDeck is free and will stay free. If it saves you time, you can buy me a coffee ☕

License

Copyright © 2026 Samons

MiniDeck is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version (AGPL-3.0-or-later). It is distributed WITHOUT ANY WARRANTY. Full text in LICENSE.

In short: you're free to use, modify, and share it, but if you distribute a modified version or make it available to others over a network, you must publish its source code under the same license.

control
control-systems
deck
decks
macos-management
window-manager

SamonsLol/minideck

Python

2

16 commits

updated Oct 9, 2026

See the code

See what people are saying

SourceMessageScoreDate

I built MiniDeck, a free open-source alternative to a Stream Deck that runs in your phone's browser (r/SideProject)

I wanted a customizable control deck for my computer without buying extra hardware, so I built MiniDeck. It turns your phone into a control surface for Windows, macOS, and Linux. There's no mobile app to install — just open the web interface in your phone's browser. **Features:** * Custom buttons…

1

Oct 9, 2026

README

MiniDeck

English · Español · 🌐 minideck.samons.co

Website CI License: AGPL v3 Python Platforms Buy Me A Coffee

Turn your phone into a Stream Deck for your PC or Mac. There's no app to install on the phone: it's a PWA you open in the browser. The server is Python (FastAPI + WebSocket) and the frontend is plain HTML/CSS/JS with no build step.

Issues and PRs are welcome in English or Spanish.

See it in action: the website minideck.samons.co is built like the app itself — tap the keys, toggle the stateful buttons and switch themes.

Tour: pages, folders, editor, language and themes

Home page with Now Playing, buttons and mixer OBS page with stateful record and live buttons

Media page LCD theme

Features

  • Buttons for keyboard shortcuts, apps and websites, shell commands, macros, webhooks (n8n, Home Assistant…), shutdown/lock, and more.
  • Long press: a second action per button (e.g. tap = cancel shutdown, hold = shut down in 60 s).
  • Folders: a button can open another page, and back returns.
  • Stateful buttons: icon, color and label change with live state (OBS recording, Discord muted, a Home Assistant light on…).
  • Live widgets: volume, per-app mixer (Windows), Now Playing with album art, Discord (mute/deafen and who's speaking), CPU/RAM, weather, Git, Docker, OBS Studio and Home Assistant.
  • Visual editor right on the phone: pages, icons (Lucide), colors, drag and drop, undo, and deck import/export.
  • Works offline on your LAN: icons are cached by the server after first use.
  • Spanish and English UI (tap ES/EN in the top bar).
  • Works without JavaScript: /basic is a server-rendered version with plain links and forms (use the deck, folders, long press, sliders, OBS, Home Assistant, and a full deck editor). Browsers without JS land there automatically; JavaScript only adds real-time updates, drag and drop and animations.
  • Plugins in Python + JS, plus custom widgets built in the Control Panel (/panel.html).
  • Secure QR pairing: nobody else on your network can control your computer.

Install

Option 1: download (no Python needed)

Grab the latest build from the website or from Releases:

  • Windows: MiniDeck-…-windows.zip → unzip → run MiniDeck.exe. A tray icon appears: right-click → Mostrar QR.
  • macOS: MiniDeck-…-macos.dmg → drag to Applications. The first time: right-click → Open (the app isn't notarized). See README_MAC.md for permissions.

Option 2: pipx

pipx install git+https://github.com/SamonsLol/minideck.git
minideck            # or: minideck --port 9000
minideck-tray       # Windows/Linux: tray icon instead of a console

Option 3: from source

git clone https://github.com/SamonsLol/minideck.git
cd minideck
python -m venv .venv
# Windows:        .venv\Scripts\activate
# macOS / Linux:  source .venv/bin/activate
pip install -r requirements.txt
cd server
python main.py

On Windows you can also double-click MiniDeck.bat (no console, tray icon).

Pair your phone

  1. On the computer, open http://localhost:8765/qr.
  2. Scan the QR code with your phone's camera. MiniDeck opens already paired.
  3. Use Share → Add to Home Screen to run it full screen.

The app installed on the home screen doesn't share storage with the browser (especially on iPhone), so the first time it opens it asks to pair again: tap 📷 Scan QR code and point at the computer's screen (over plain HTTP it takes a photo of the QR; with HTTPS it scans live), or type the code shown under the QR.

The phone and the computer must be on the same Wi-Fi network. On Windows, allow Python/MiniDeck on private networks when the firewall asks.

Security

MiniDeck can run commands on your computer, so:

  • Every request to /api and /ws requires a pairing token. It's generated on first run and stored in auth_token in the config folder.
  • The QR code and token are only shown on the computer itself (localhost).
  • Don't share the QR code or the pairing code. To revoke every paired device, delete auth_token and restart; a new one is generated.
  • Never expose port 8765 to the internet (no port forwarding). For remote access, use a VPN such as Tailscale or WireGuard.

More details and how to report vulnerabilities: SECURITY.md.

Configuring the deck

The deck lives in deck.json:

ModeLocation
From source (python main.py)server/config/deck.json
Packaged app or pipxWindows %APPDATA%\MiniDeck\ · macOS ~/Library/Application Support/MiniDeck/ · Linux ~/.config/minideck/

On first run it's created from the default deck for your OS (server/config/deck.default.<windows|macos|linux>.json). The easiest way to edit it is from the phone with the ✎ button. You can also edit the JSON by hand: when you save, every connected client updates automatically. Every save keeps a backup (the last 30, in backups/), so the editor's ↶ Undo can go back.

{
  "id": "rec",
  "label": "Record",
  "icon": "lucide:circle",
  "color": "#8a93a3",
  "action": "obs_record_toggle",
  "params": {},
  "longAction": "obs_replay_save",
  "longParams": {},
  "when": { "key": "obs.recording", "icon": "lucide:circle-stop",
            "color": "#f87171", "label": "Recording" }
}
FieldMeaning
action / paramsWhat a tap does.
longAction / longParamsOptional: what a long press (~0.5 s) does.
whenOptional: look while a live-state value is true. key is a path in the state (obs.recording, discord.mute, ha.entities.light.living.state), equals compares against a value; icon, color and label override the button.
"action": "page"Folder: {"page": "<page id>"} opens that page, {"page": "back"} goes back.
w / hWidth/height in grid cells ("w": "full" = whole row).

Built-in actions

ActionParamsPlatforms
hotkey{"keys": "ctrl+shift+m"}all
type_text{"text": "hello"}all
launch{"path": "..."} or {"app": "code"}all
command{"cmd": "...", "shell": "powershell"}all
website{"url": "https://..."}all
http_request{"url", "method", "body", "headers"}all
now_playing{"cmd": "play_pause" | "next" | "previous" | "seek", "position": 90}Win, Mac
volume_set / volume_change / volume_mute{"level": 50} / {"delta": 5} / {}Win, Mac
mixer_set / mixer_mute{"app": "chrome.exe", "level": 40}Win
discord_mute / discord_deafen{}all
obs_scene, obs_record_toggle, obs_stream_toggle, obs_mute_toggle, obs_replay_save…see OBS pluginall
ha_toggle, ha_scene, ha_servicesee Home Assistant pluginall
lock{}all
power{"mode": "shutdown" | "restart" | "sleep" | "cancel"}all
kill_process{"name": "app.exe"}all
mouse_click / mouse_move{"x": 100, "y": 200}all
macro{"steps": [{"action": ..., "params": ...}, {"delay_ms": 500}]}all
screenshot, show_desktop, shortcut, clipboard_copy, ocr_capture, audio_output…see sourceMac

Parameters are validated before running, so a mistake shows a clear message on the phone. Every action has a timeout (30 s by default), so a hung action can't leave the phone waiting. The editor fills in a parameter template when you pick an action. The exact list on your machine: GET /api/actions/schema.

Discord needs an app created in the Discord Developer Portal: see the instructions at the top of server/actions/discord_rpc.py and the template in server/config/discord.example.json.

Plugins

Add actions, live data, and widgets without touching MiniDeck's code:

%APPDATA%\MiniDeck\plugins\my_plugin\     (or ~/Library/Application Support/MiniDeck/plugins/…)
├── plugin.json   ← name, version, platforms, dependencies, settings
├── plugin.py     ← @action("my_plugin_do")
├── widget.js     ← MiniDeck.registerWidget("my_plugin", {...})
└── widget.css
from actions import action

@action("my_plugin_do", schema={"name": {"type": "str", "required": True}})
def do(params: dict):
    return {"message": f"Hi {params['name']}"}

Full guide (in Spanish): docs/PLUGINS.md · Template: examples/plugins/hello · Community index: docs/PLUGIN_INDEX.md

Server options

OptionDefaultPurpose
--port / MINIDECK_PORT8765Port
--host / MINIDECK_HOST0.0.0.0Interface (use 127.0.0.1 for local only)
MINIDECK_TOKENrandomFixed token (e.g. the same one on several machines)
MINIDECK_CONFIG_DIRsee aboveFolder for deck.json, backups, the token, and discord.json
MINIDECK_PLUGINS_DIR<data>/pluginsUser plugins folder
MINIDECK_LOG_DIR<data>/logsRotating log (minideck.log, ~3 MB max). Also at GET /api/logs
MINIDECK_ICON_CACHE<data>/icon-cacheDownloaded icons
MINIDECK_NO_AUTH=1—Disables the token. Not recommended.

Project layout

minideck/
├── server/
│   ├── main.py            # FastAPI + WebSocket + routes
│   ├── auth.py            # pairing token
│   ├── paths.py           # per-OS paths
│   ├── icons.py           # icon proxy with disk cache
│   ├── actions/           # built-in actions (one module per platform)
│   ├── plugins/           # bundled plugins (obs, homeassistant, sysmon, clock, indicators)
│   ├── config/            # default decks per OS and templates
│   ├── app_tray.py        # tray icon (Windows/Linux, entry point of the .exe)
│   ├── app_menubar.py     # menu bar app (macOS)
│   └── run.ps1            # tray launcher without pystray (Windows)
├── frontend/              # PWA (plain HTML/CSS/JS, no build; i18n.js = translations)
├── packaging/             # pip/pipx launcher
├── examples/plugins/      # plugin template
├── docs/                  # plugin guide and screenshots
├── build/                 # PyInstaller specs (Windows .exe, macOS .app), DMG, certificates
└── tests/                 # server, plugins, packaging and browser UI tests

Contributing

Welcome aboard! Read CONTRIBUTING.md. For development:

pip install -r requirements-dev.txt
python -m playwright install chromium   # for the browser UI tests
pytest
ruff check server tests examples packaging

To publish a release: git tag v1.1.0 && git push origin v1.1.0. GitHub Actions builds the Windows .zip, the macOS .dmg and the Python package, and attaches them to the release.

Support

MiniDeck is free and will stay free. If it saves you time, you can buy me a coffee ☕

License

Copyright © 2026 Samons

MiniDeck is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version (AGPL-3.0-or-later). It is distributed WITHOUT ANY WARRANTY. Full text in LICENSE.

In short: you're free to use, modify, and share it, but if you distribute a modified version or make it available to others over a network, you must publish its source code under the same license.

control
control-systems
deck
decks
macos-management
window-manager