Add folder support: keys that open sub-pages with a back key

A key with `folder: NAME` swaps the deck to the keys defined under
`folders.NAME`. Every folder shows a back key (slot 0 by default, drawn
with a built-in chevron) that pops back to the parent; folders nest.

The daemon now runs one page at a time: per-key setup moved into
setupKey(), openPage() draws a page under its own context, and
page.stop() tears it down before the next page is drawn (and on exit,
so device death no longer waits on uncancelled goroutines).

streamdeck-ctl gains `key set -folder`, reports kind "folder", lists
every folder in status, and refuses to `key press` a navigation-only
key. The Omarchy grid shows a folder glyph.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ARLa4moGb154fzc9TevgV
This commit is contained in:
Levi Woodard
2026-10-01 18:39:24 -06:00
parent c143db6b68
commit 4158165410
10 changed files with 568 additions and 153 deletions

View File

@@ -42,6 +42,34 @@ type KeyConfig struct {
Module string `yaml:"module"`
Function string `yaml:"function"`
Params map[string]string `yaml:"params"`
// Folder key: pressing it swaps the deck to the named folder's keys
// (defined under the top-level `folders:` block). Folders nest — a folder's
// key may itself have a folder — and every folder shows a back key.
Folder string `yaml:"folder"`
}
// BackConfig customises the back key every folder shows. All fields are
// optional: the default is key 0 drawn with a built-in arrow and "Back" label.
type BackConfig struct {
Key *int `yaml:"key"` // slot index for the back key (default 0)
Icon string `yaml:"icon"` // icon filename relative to icons_dir (default: built-in arrow)
Text string `yaml:"text"` // label (default "Back")
TextColor string `yaml:"text_color"` // same values as KeyConfig.TextColor
}
// FolderConfig is a sub-page of keys shown while a folder is open.
type FolderConfig struct {
Back BackConfig `yaml:"back"`
Keys map[int]KeyConfig `yaml:"keys"`
}
// BackKey returns the slot index the folder's back key occupies.
func (f FolderConfig) BackKey() int {
if f.Back.Key != nil {
return *f.Back.Key
}
return 0
}
// DefaultConfigPath returns the default config file path, respecting XDG_CONFIG_HOME.
@@ -65,6 +93,9 @@ type Config struct {
Brightness int `yaml:"brightness"`
Device DeviceConfig `yaml:"device"`
Keys map[int]KeyConfig `yaml:"keys"`
// Folders are sub-pages opened by keys with a `folder:` field. The root
// page is Keys; a folder's keys replace it until its back key is pressed.
Folders map[string]FolderConfig `yaml:"folders"`
}
// DeviceConfig allows overriding USB IDs (defaults work for Stream Deck XL v2).