Another controller took over the keyboard.

About

⌨️

I'm th108 — a free companion app for the Epomaker TH108 V2 PRO keyboard. I run on your PC and drive its per-key RGB and LCD screen with effects the keyboard's own firmware can't do on its own:

Unofficial & not affiliated with Epomaker. See the Docs tab for a full feature guide, comparisons and legal notes.

Keyboard

not connected

While connected, click this page once so it has keyboard focus, then type — pressed keys flash orange.

Browser keydown only fires while this tab is focused. Reactive-in-any-app comes from the background daemon instead.

💡 Daemon running? Skip Connect for layer editing — edits push to it live and reactive keeps working in every app. Connect is only for the GIF→keys, onboard-effect, and LCD tools (it takes the keyboard from the daemon until you close this tab).

Background Daemon

Runs your layers always-on — reactive typing in any app — and takes over whenever this page is closed.

checking…

One process name per line or comma-separated — the name in Task Manager → Details (e.g. obs64, steam). While that app is in front, a lighting freeze clears in ~12 s instead of ~30 s. Can't stop the freeze, only speed the recovery.

Toolbox

Drag any card by its title bar to rearrange the grid — the layout is saved.

Log

Pick a Key

No key is selected.

Assign to Key

Connect to use all hotkey features
No key is selected.

Decorative Light Toggles

Click a light — your Spacebar becomes its cycle key and a focus overlay opens; Esc or ✕ restores the Spacebar.

Advanced Keys

Give one key a smarter behavior. Like everything on this tab it lives in the keyboard's keymap — works in every app, no software needed. Pick a key on the board, choose a behavior, Apply. Restore Default undoes it. (The official tool allows up to 40 advanced keys.)
⚠ Heads-up — not working on this board's firmware: these entries write correctly (the official Epomaker tool even reads them back and shows them identically), but the board's firmware doesn't execute the advanced-key types — and the official tool's own advanced keys don't fire either (verified 2026-06-30 against a full byte-for-byte capture of its connect + apply). So it's a firmware limitation, not a software gap; if a firmware update ever adds support, these bindings will just start working. Normal remaps + light functions on this tab are unaffected.

Profiles

Snapshot your whole lighting setup — layers, pattern settings, stacking order — as a named profile. Apply, rename, export, or import them; up to 10. Click a name to rename it.

Profile Data

The full Lighting-tab setup: all your layers with their pattern settings, plus the stacking order — the same data the background daemon runs. Key bindings live in the keyboard's own flash, so they stay put across profile switches. Exported files are daemon-compatible JSON.

Terminology — the jargon in tooltips, decoded

The daemon — a small background app that drives your keyboard's lighting when this page is closed or unfocused. It's what lets reactive lighting and effects work in every app, not just this browser tab.

Driver (page vs daemon) — only one thing paints the LEDs at a time: either this tab (over WebHID) or the daemon. "Who's driving" is just which of the two is currently in control.

Lease / yield · auto-yield — the handoff of the keyboard between the tab and the daemon. Click away and the tab yields to the daemon; click back and it reclaims. Auto-yield is this happening automatically on focus changes (you can turn it off with "Keep driving from this tab").

Reactive — a layer type where keys light up in response to keypresses. It only sees keys typed into the focused app of whoever's driving — the tab sees only this tab; the daemon sees the whole system.

Churn — rapid back-and-forth handoffs of the keyboard between tab and daemon (e.g. from quickly alt-tabbing). Excess churn can desync the keyboard's USB command stream.

Mute / wedge — when the keyboard stops accepting lighting updates (its USB command queue desyncs) so the LEDs freeze or go dark. It usually self-recovers in ~30–45s via a USB restart; toggling wired ↔ Bluetooth or replugging forces an instant fix.

Onboard effect — the keyboard's own built-in lighting (rainbow, etc.) stored in its firmware. It runs when nothing is driving from the host; the daemon or page paints over it.

What Connect actually does

It hands the keyboard from the background daemon to this page and starts your layers automatically — one click, no second step. You only need it for the GIF→keys player and the LCD uploader; plain layer editing pushes to the daemon live without ever connecting.

Reactive only sees this tab's keys after Connect

This only concerns layers whose type is Reactive (whichever level they sit at — other layer types aren't affected). Once this page owns the keyboard (after Connect), it can only see keystrokes typed into this tab — a browser limit. Reactive-in-every-app comes from the background daemon: close this tab (or never Connect) and it takes over within a second.

Use a key to toggle mic lighting or cycle profiles

Host Actions (Hotkeys → Assign to Key → Host Actions tab) bind a key to a background-app action — Mic/Music lighting on-off, or Profile Next/Prev. They run through the daemon, so they work with this page closed. Click the action, then press the key you want to use — it captures whatever that key sends, so it works even on a firmware-remapped key (and the key still does its normal job too).

Want a key to do only the host action and not its normal job — e.g. the Menu key, which otherwise opens the right-click menu? First firmware-remap that key to a do-nothing key: Pause/Break is ideal — it does nothing in Windows and has no indicator light. Then bind the host action by pressing that key (it'll show up as Pause, which is correct — that's what the key now sends). Avoid Scroll Lock and the other lock keys: the firmware forces their indicator LED on (white), which you can't paint over.

Make the Agent layer follow the focused session

The Agent layer can light up for one specific Claude Code session instead of all of them. Tick 🎯 Follow focused session (Agent layer → Session) and it automatically tracks whichever Claude session's VSCode window is in front — bring a different window forward and the lighting follows it.

It works on its own, no extra tools: the background app watches the Windows foreground window and matches it to a session by project folder (the window title carries it). Leave all VSCode windows — switch to a browser, say — and after a few seconds it reverts to All Sessions. Windows only; on other platforms Follow-focus simply stays on All Sessions.

Granularity — it follows by PROJECT, not by tab. Focus is matched from the VSCode window, which only identifies the project folder. So switching between Claude tabs/sessions inside the same project won't change what's followed (and won't re-flash) — if several live sessions share a project, it follows the busiest one. Per-tab following isn't possible from the window alone: two sessions in one window share it, and the Claude hooks give the daemon only a session id + folder, no per-session title to tell tabs apart.

Advanced: anything can also drive it directly by POSTing { session_id } to http://localhost:8123/agent/focus (null clears it) — but you don't need to.

Where are Advanced Keys (SOCD / Mod-Tap / Toggle / Combination)?

They're hidden because this keyboard's firmware doesn't run them — not a software issue on our end. Advanced keys are stored in the keyboard's keymap, and we write them correctly: the official Epomaker tool even reads our bindings back and displays them identically, which confirms the encoding is exactly right. But the firmware never executes those entry types, so the key just goes dead.

We verified this against the official driver byte-for-byte (2026-06-30): its setup sends no special "enable" command, its writes are identical to ours, and its own advanced keys don't work on this board either — a Combination set in the official tool doesn't fire in its "Test Your Bindings." So it's a firmware limitation shared by both tools. Normal key remaps and the light-function binds on the Hotkeys tab work fine — only these four advanced types are affected. If a firmware update ever adds support, the feature can be switched back on and the existing encoding will just work.

Keep the numpad working in Blender/CAD while still killing the NumLock LED (power-user)

The one-click Numpad → Digits fix on the Hotkeys tab kills the white NumLock LED by remapping the numpad to top-row digits in firmware — but that's global, so apps that read the real numpad (Blender's camera on Numpad 1/3/7, some CAD/DAW shortcuts) stop working. Firmware can't be per-app; a small host-side script can. It keeps the numpad native and turns it into digits everywhere except the apps you list.

You'll need AutoHotkey v1.1 (free). Then: 1) on the Hotkeys tab click Revert Numpad (back to native). 2) Turn NumLock off (LED off). 3) Save the script below as numpad-apps.ahk and double-click it — a green H appears in your tray (check the hidden-icons ^ arrow). To run it every login, drop a shortcut to it in your shell:startup folder.

#NoEnv
#SingleInstance Force
; Numpad types DIGITS everywhere EXCEPT the apps listed below, where it passes
; through native (so Blender camera / CAD numpad shortcuts still work).
#If !(WinActive("ahk_exe blender.exe"))
NumpadEnd::SendInput 1
NumpadDown::SendInput 2
NumpadPgDn::SendInput 3
NumpadLeft::SendInput 4
NumpadClear::SendInput 5
NumpadRight::SendInput 6
NumpadHome::SendInput 7
NumpadUp::SendInput 8
NumpadPgUp::SendInput 9
NumpadIns::SendInput 0
NumpadDel::SendInput {.}
#If

To cover more apps, extend the WinActive(...) line, e.g. #If !(WinActive("ahk_exe blender.exe") or WinActive("ahk_exe Fusion360.exe")). It assumes the app reads the numpad with NumLock off (Blender does by default; if a key misbehaves, turn on that app's own "emulate numpad" option). It's a normal AutoHotkey process — kill it anytime from its tray icon, and it's fully independent of the lighting daemon.

One caveat — this keeps Blender's numpad native, which is what makes the camera (Numpad 1/3/7) work, but it means typing digits into a value field (bone angle, a transform amount) still needs NumLock on. No per-app trick can make the same keys do camera and digit-entry automatically. The simplest fix is to just flip NumLock on for your Blender session — nav and field digits both work natively — and off when you're done; the white LED is lit only while you're in Blender.

Troubleshooting

Numpad → Digits doesn't type digits in PowerShell / the terminal

After the one-click Numpad → Digits remap, the numpad types numbers in normal apps but in PowerShell, Command Prompt, and Windows Terminal the numpad keys act as if NumLock is off — you get Home / End / PgUp / PgDn / arrows instead of digits. That's a Windows console quirk, not a bug in the remap: the console reads numpad keys by their physical scancode and NumLock state at a lower level than the firmware remap reaches, so it ignores the remap and applies its own NumLock-off navigation.

Fixes, easiest first: 1) use the top number row when you're in a terminal; 2) turn NumLock on just for terminal work (the numpad types digits again — but the white NumLock LED comes back while it's on); or 3) use the AutoHotkey method in the card above — its SendInput digits are accepted by the console, so with NumLock off the numpad types numbers in PowerShell too, with no white light. (The daemon can't fix this: it paints lighting, it doesn't inject keystrokes.)

You don't need Connect to edit layers.

With the daemon running, every layer edit pushes to it live (~½ s) and reactive keeps working everywhere. Connect is only for the GIF→keys player, onboard effects, and the LCD uploader.

Troubleshooting

Lighting froze or went static on its own

The board sometimes goes mute — it silently stops accepting host lighting (typing still works) and falls back to its saved firmware effect, which usually looks static. USB power events are the main trigger: sleep, or other devices power-cycling on the same bus. Rapidly handing the board back and forth between this tab and the background daemon — lots of Connect/Disconnect, replug, or tab-switching in quick succession — can also wedge it: every handoff briefly risks two writers on the same board, and its firmware doesn't love the churn. If you're toggling a lot, give it a second to settle between switches.

It can also go mute on its own during normal use — no sleep, no replug, nothing you did. Streaming per-frame lighting for a long stretch (heavy reactive typing, or an audio/media layer running for hours) can make the firmware occasionally choke on its own write queue and stop acknowledging frames. This is a known hardware quirk of the board, not a fault in the app — which is why the daemon watches for it and recovers automatically (a USB restart), usually within a minute, without you touching anything. If your lighting "fixed itself," that's what happened.

Cure ladder: wait ~30 s (automatic USB restart; some mutes self-recover) → press Ctrl+Alt+End anywhere (the manual recovery hotkey, same restart) → flip the mode switch to BT and back to wired⟳ Factory Reset Keyboard (Home tab; the reset command gets through even when lighting is dead — then Restore Keymap on Hotkeys brings your binds back). Note the board runs on battery, so unplugging the cable alone does NOT reboot it — deep wedges sail straight through replugs and USB restarts. To make mutes rarer in the first place, see the "Stop Windows from suspending the keyboard" card.

Troubleshooting

Stop Windows from suspending the keyboard

Out of the box, Windows is allowed to power-manage the keyboard's USB interfaces — and a USB suspend is exactly what puts the board into its mute state. A one-time fix removes that permission.

Right-click th108-daemon\disable-usb-powersave.ps1Run with PowerShell (it asks for admin), then replug the keyboard once. It clears the per-interface "allow the computer to turn off this device" flags and turns off USB selective suspend in your power plan. Run it again with -Enable to undo both.

Troubleshooting

"Online" but the keyboard shows not connected

Three indicators, three different things. The header Online tag = the background daemon process is running. The Background Daemon card says whether it currently holds the keyboard. The Keyboard card's status is whether this page holds it (Connect). They can legitimately disagree — e.g. daemon Online and driving the keyboard while the page shows "not connected" is the normal resting state.

Troubleshooting

Play/Pause controls my browser instead of Spotify

That's Windows, not this suite. The media keys go to the most-recently-active media session, and Chrome/Edge grab it aggressively — so your keyboard's ⏯ hits a browser tab instead of Spotify. The clean fix is to stop the browser from claiming the keys:

Chrome: chrome://flags/#hardware-media-key-handlingDisabled → Relaunch. Edge: edge://flags/#hardware-media-key-handlingDisabled → Relaunch. Firefox: about:configmedia.hardwaremediakeys.enabledfalse.

After that the media keys go to Spotify (or whatever non-browser app is playing). Trade-off: that browser then ignores the media keys entirely — which is the point.

Where to find the official driver

Epomaker's official web driver lives at epomaker.driveall.cn — useful for the firmware features this suite doesn't cover yet. Two warnings: quit the background daemon first (Home tab) — only one controller can hold the keyboard, otherwise the official site errors out on connect — and it has been seen scrambling keymaps when changing bindings, so take a Backup Keymap on the Hotkeys tab before using it.

What the two reset buttons clear

Both buttons live in the Toolbox card on the Home page.

↺ Reset All Settings resets this page: layers, per-pattern values, LCD calibration, and panel layout. Kept: your media Library, saved Profiles (delete those individually on the Profiles tab), and the keymap backup + remapped-key marks — those mirror the keyboard itself, which this reset never touches.

⟳ Factory Reset Keyboard is the opposite: it resets the keyboard's hardware — onboard config and keymap, including your key bindings — and changes nothing on this page.

The "Media" layer vs. the GIF → Keys card

Both put a GIF on your keys, but they work differently. A Media layer plays the GIF inside the Layer Compositor, so it blends with your other layers — opacity, blend mode and the global Adjust all apply — and it runs through the background app, so it keeps going with this tab closed. The GIF → Keyboard Lighting card on the Lighting tab plays a GIF on its own, taking over the whole board, and needs this page connected. Use the Media layer to mix a GIF into a stack; use the card for a quick full-board GIF.

Feature Guide

Everything th108 adds on top of your TH108 V2 PRO, by area. Most of it is host-driven — your PC computes the lighting frame-by-frame and streams it to the board, so effects can react to things the keyboard can't see on its own (what you type, what's playing, what's on screen).

Layer Compositor (Lighting tab)

  • Stack up to eight lighting layers (one per effect type) and blend them top-to-bottom — each with its own opacity, blend mode (Normal, Add, Screen, Multiply, etc.) and frame rate.
  • Layer types: Background (solid or animated base color), Reactive (lights keys as you press them), Gradient (two-color, angled, animatable), Pattern (built-in animations — rainbow, spectrum, fire, ripple and more), Individual (paint each key by hand), Audio (spectrum bars from your sound), Media (play a GIF or video inside the stack so it blends with the rest), and Agent (a status / twinkle overlay).
  • Global Adjust across the whole stack: brightness, saturation, contrast, gamma and hue-rotate.
  • Isometric View button — a draggable 3D visualizer that shows the layer stack as glowing key-planes (rotate, zoom, focus a layer, toggle layers) so you can see how the composite is built.

Reactive typing in any app

A Reactive layer lights each key as you press it — system-wide, in any program — because the always-on background app sees your keystrokes. (A browser tab can only see keys typed into itself, so this comes from the daemon, not the page.)

Audio-reactive lighting

  • System / App audio — the background app captures your output device and drives spectrum bars with the page closed.
  • Specific browser tab / microphone — captured inside this page (browser privacy limits it to here), so it only reaches the keys while this tab is driving the board.

GIF → Keys (Lighting tab)

Map any GIF onto the per-key grid and play it across the keyboard. Requires Connect (this page drives the board for the GIF player).

Onboard effects

Write the keyboard's built-in firmware effects from the page — useful as a clean baseline, or to overwrite a corrupt onboard config.

LCD Screen (LCD Screen tab)

Upload images and GIFs to the keyboard's little display, plus a Now Playing readout. (Uploads are chunked and rate-limited for hardware safety.)

Hotkeys & Host Actions (Hotkeys tab)

  • Remap keys to other keys or to light functions, written to the keyboard's keymap.
  • Host Actions — bind a key (or chord / multi-tap / hold) to a PC action: toggle mic or music lighting, cycle profiles, launch or switch to a program, minimise/maximise/restore a window, or run a macro. They run through the background app, so they work with this page closed — and the key still does its normal job too.

Profiles (Profiles tab)

Save and switch whole setups (layers + lighting + binds). Export/Import to JSON to back up or move between machines.

Background Daemon (Home tab)

The always-on engine: keeps your layers and reactive typing running in every app and takes over whenever this page is closed. Options: auto-start at login, auto-fix lighting wedge (USB restart), and lights-off when the monitor is off.

Apps that grab the keyboard (Home tab)

Some programs — screen recorders and game launchers/overlays like OBS or Steam — probe the keyboard in a way that can briefly freeze the lighting (a "wedge"). List their process names in the Apps that grab the keyboard box on the daemon panel, and while one of them is the active window the background app clears a wedge fast (~12s) instead of waiting out the normal typing-safe delay (~30s). It can't stop the freeze — that's the other app touching the hardware — only recover from it sooner, and it never restarts the USB mid-keystroke. Find process names in Task Manager → Details.

Language (top bar)

The dropdown in the top bar switches the interface between 18 languages, including right-to-left layouts for Arabic. Your choice is remembered on this device.

th108 vs. Epomaker's WebHID tool

Epomaker ships an official browser configurator (the WebHID page) for the TH108. It writes settings into the keyboard's onboard memory, then your PC steps out of the way. th108 is different in kind: it's a host-driven suite — your PC keeps computing and streaming the lighting, which is what makes the reactive / audio / live-composite effects possible. The two can't drive the board at the same time.

CapabilityEpomaker WebHIDth108
Where effects runOn the keyboard (onboard)On your PC, streamed live
Works after the tab closesYes (onboard)Yes — via the background daemon
Multi-layer live compositingOne onboard effect (Musical Rhythm layers 2, fixed)Up to 8 freely-blended layers
Reactive typing in every appOnboard reactive onlyYes (daemon sees system-wide keys)
Audio-reactive lightingOnboard “Musical Rhythm” onlySystem / app / tab / mic — as a blendable layer
GIF → keysNoYes
Bind keys to PC actionsNoHost Actions (apps, windows, macros…)
LCD image / GIF uploadYesYes + Now Playing
Onboard effect editingYesYes
Profiles / export-importOnboard slotsHost profiles + JSON
Always-on PC background appNot needed / noneYes (optional, auto-start)

Comparison covers the official tool's per-key lighting configurator and its onboard “Musical Rhythm” audio effect; Epomaker may add features over time. Use whichever fits — onboard effects from either tool survive a reboot; host-driven effects need th108 running.

Get the daemon

The web page alone drives your keyboard over WebHID while this tab is open — great for a quick try. The optional companion daemon unlocks the rest: always-on lighting (keeps running with the page closed), the Audio and Agent layers, Now-Playing on the LCD, and Host Actions.

  1. Install Node.js LTS — one-time; the daemon runs on it.
  2. Download the th108 suite (green “Code” ▸ Download ZIP, then unzip anywhere).
  3. Run setup.cmd from the top of that unzipped folder — it installs dependencies, enables start-at-login (per-user, no admin), and opens the controller at http://localhost:8123/.

Windows only for now. The daemon is a small local Node process — nothing is uploaded anywhere. Once it’s running you open http://localhost:8123/ and this prompt no longer appears.

Changelog

Notable additions. Newer entries land on top as the suite updates.

Legal & Disclaimer

Not affiliated with Epomaker

th108 is an independent, unofficial, free tool. It is not affiliated with, endorsed by, sponsored by, or supported by Epomaker. "Epomaker" and "TH108" are trademarks of their respective owners and are used here only to identify the compatible hardware (nominative use).

No warranty

Provided "as is", without warranty of any kind. It communicates with hardware over standard HID lighting and screen commands; you use it at your own risk. The authors are not liable for any damage, data loss, or device malfunction. It does not flash or modify keyboard firmware.

Privacy

Everything runs locally on your machine. There is no telemetry and no data collection. Audio, tab, and microphone capture used for reactive effects stay on your PC and are never uploaded.

Third-party

Onboard-effect tooling adapts open-source work (e.g. OpenRGB protocol notes). Respect the licences of any such components when redistributing.

Layer Compositor

Onboard Effects

Firmware effects — run on the keyboard itself, persist without a PC. Setting one stops the live layers/GIF.

♪ Now Playing

Daemon off

The background daemon shows the current song on the LCD with album art (Spotify only by default). ⚠ Each update writes the LCD flash, which can occasionally softbrick this board. To minimize that: it updates only on a track change (never play/pause/resume), waits ≥20 s between writes, and caps at 30/hour. Still a real risk — leave it off if you can't afford a possible factory reset. While this page holds the keyboard, a new song queues and lands when the daemon takes over.

⌨ Heads-up: during each LCD upload the key lighting pauses and the board can briefly drop input (a fraction of a second) — not recommended during gaming or competitive play.

⚠ More impact than the bar alone: each song update flashes the keyboard and briefly drops input. The bar below is lighting-only and has no such cost.

Sets the keyboard's onboard effect to black, so when the board briefly falls back during an update it shows a dark blink instead of the rainbow startup. Best-effort — a true hardware re-init can still flash; A/B it on vs off. Writes a firmware setting + cycles the keyboard once when toggled.

Keys 1-0 light up to show how far through the song you are — lighting only, no LCD writes, so it can't softbrick. Needs the background daemon running (it reads Spotify's playback position). Works whether or not the LCD now-playing on the left is on.

Brightness 60%
Fade the bar out after idle for 3s

GIF Screen