Skip to content

Create App Overlays

PolyKybd can draw a shortcut icon on each keycap OLED for whichever app is focused — see Context-Aware Overlays for the user-facing side. This HowTo builds a new overlay set for an application by hand, the same way the shipped overlays were made.

An overlay is just an image you paint in an editor like GIMP, plus a one-line entry telling the host which app it belongs to. The editable source files for every built-in overlay live in the host repo under polyhost/res/overlays/src/ — a great starting point (and template.xcf there is a blank grid). The authoritative format spec is polyhost/res/overlay_specification.md.

How an overlay image is laid out

The host loads one or two PNGs per app. Each PNG is a 720×360 image = a 10×9 grid of 72×40 keycap cells, in row-major key order, and — this is the clever part — each colour channel of a cell is a different modifier variant of that key:

FileRedGreenBlueAlpha
*.mods.png (primary)CtrlAltShiftno modifier
*.combo.mods.png (combo)Ctrl+ShiftCtrl+AltAlt+ShiftGUI
*.extra.mods.png (extra)Ctrl+Alt+ShiftGUI+ShiftGUI+AltGUI+Ctrl
*.gui.mods.png (GUI combos)GUI+Ctrl+ShiftGUI+Alt+ShiftGUI+Ctrl+AltGUI+Ctrl+Alt+Shift
*.png (plain)grayscale → no modifier only

So one RGBA image holds four layers of the same keyboard at once — you can see it in GIMP’s Channels panel, each of Red / Green / Blue / Alpha carrying the icons for a different modifier:

GIMP editing an overlay: icons placed in the keycap grid on the left, the Red/Green/Blue/Alpha channels on the right each holding a different modifier's icons

White pixels are drawn (lit) on the OLED; everything else is ignored. Encode as straight (non-premultiplied) RGBA — a transparent pixel must still carry its R/G/B bytes, because those channels are the Ctrl/Alt/Shift variants, independent of alpha (see the export step below).

A few hard limits baked into the firmware — don’t fight them:

  • All sixteen modifier combinations work — every mix of Ctrl, Shift, Alt and GUI. Until recently GUI could not be combined with another modifier: GUI+Shift, GUI+Alt and so on all drew the plain GUI icon instead of their own. That mattered most on macOS, where Cmd+Shift chords are everywhere (Cmd+Shift+P opens the command palette in a lot of editors). If you skipped those shortcuts before, you can draw them now — see the extra and GUI combos rows above.
  • Repeating the same icon across several variants is free: the host spots byte-identical images and stores them once on the keyboard, so only genuinely different artwork costs space.
  • Cells are 72×40, 1-bit monochrome on the OLED — draw simple, high-contrast icons.
  • Only the 90 mapped keys carry a cell (AZ, 09, F1F12, punctuation, the nav cluster, modifiers). Keypad and media keys have none.
  • Put the icon in the bottom-right of the cell so it never covers the firmware-drawn key letter in the top-left.

Build it by hand

  1. Start from the template. Copy polyhost/res/overlays/src/template.xcf (the blank 720×360 grid) — or an existing app’s …_template-RGBA.xcf that’s close to what you want — and open it in GIMP. Working from a shipped source is the quickest way to see how the channels and cell grid are organised.

  2. Look up the app’s real shortcuts. From the app’s own menus, help, or shortcut reference. Note the modifier each one uses — every combination of Ctrl, Shift, Alt and GUI can be drawn, so nothing has to be dropped for lack of a channel.

  3. Draw an icon per shortcut. For each shortcut, place a small, clear icon into that key’s cell, on the channel for its modifier (Ctrl → red, Alt → green, Shift → blue, no-modifier → alpha). Keep it bottom-right. Reuse of the app’s own iconography reads best; simple monochrome art survives the 1-bit OLED far better than a detailed logo.

  4. Export the PNG(s). Export the image to polyhost/res/overlays/<app>_template.mods.png as 720×360 RGBA. Add each further tier only if you drew for it<app>_template.combo.mods.png for the combo layers (Ctrl+Shift / Ctrl+Alt / Alt+Shift / GUI), <app>_template.extra.mods.png for Ctrl+Alt+Shift and the two-modifier GUI chords, and <app>_template.gui.mods.png for the three- and four-modifier GUI chords. Most apps never need that last one. If you only need plain, unmodified icons, a single grayscale <app>_template.png is enough.

  5. Tell the host which app it’s for. Add an entry to polyhost/res/overlay-mapping.poly.yaml, keyed on the app’s window class / process name (comma-separate aliases), pointing overlay: at your PNG(s):

    myapp,myapp.exe:
    overlay: [myapp_template.mods.png, myapp_template.combo.mods.png]

    When one process serves many contexts (a browser hosting several web apps, an IDE), narrow it by window title with title: (a regex) or the titles-startswith / titles-contains / titles-endswith maps — see the existing Chrome and JetBrains entries for the pattern.

    If the app’s shortcuts differ per platform, give it an os: branch. Sublime Text does exactly this — Cmd on macOS, Ctrl elsewhere:

    sublime_text:
    overlay: [sublime_template.mods.png, sublime_template.combo.mods.png] # the default
    os:
    macos:
    overlay: [sublime_mac_template.mods.png, sublime_mac_template.combo.mods.png,
    sublime_mac_template.extra.mods.png, sublime_mac_template.gui.mods.png]

    Accepted keys are windows, macos, linux, plus linux_gnome / linux_kde when a desktop environment needs its own artwork (a plain linux: branch still covers those two). Anything the host can’t identify falls back to the entry’s own overlay:, so the default is never skipped.

  6. Test it. Focus the app with the keyboard connected — the overlay loads automatically. Run PolyKybdHost with --dev 2 to watch the overlay HID traffic if a key doesn’t show.

See Contributing for how to open the pull request, and Display Graphics & Fonts for how overlays are rendered on the device.