Multi-action keys with tap/long-press/double-tap/hold, gesture timing, display output colors, global font, and display output JSON/color parsing
- Multi-action system: each key supports multiple actions with different triggers (tap, long_press, double_tap, hold_start, hold_end) - GestureEngine: timing-based gesture detection with configurable thresholds - New config types: KeyAction, TimingConfig, DisplayOutput - Display output now supports JSON format (text/background/text_color) and first-line-as-hex-color format for dynamic key backgrounds - Display background colors are reflected in the web UI on the grid - renderUnicodeText/renderUnicodeTextOnImage accept custom text color (fg) - Global font setting (medium/regular) in Settings UI - PageManager.defaultFont used as fallback for per-key font - Alpine.js bundled locally (no CDN dependency) - Web UI: action tabs (Tap/Long Press/Double Tap/Hold), unified More section with appearance + display settings, gesture timing sliders in Settings - Grid: subtle action type icons in top-left corner matching display indicator style - Backward compatible: old config 'action' field auto-migrated to 'actions' array - M+ 1m font files in assets/ for reference - Updated config.example.json and README with all new features
This commit is contained in:
@@ -56,6 +56,7 @@ func Run(ctx context.Context, cfg *config.Config, opts RunOptions) error {
|
||||
pageMgrs := make([]*PageManager, len(decks))
|
||||
for i, d := range decks {
|
||||
pageMgrs[i] = NewPageManager(d)
|
||||
pageMgrs[i].defaultFont = cfg.Font
|
||||
pageMgrs[i].LoadPages(cfg.Pages)
|
||||
}
|
||||
primaryPM := pageMgrs[0]
|
||||
@@ -99,6 +100,21 @@ func Run(ctx context.Context, cfg *config.Config, opts RunOptions) error {
|
||||
|
||||
ssCtrl := NewScreensaver(&cfg.Screensaver)
|
||||
|
||||
ge := NewGestureEngine(cfg.Timing.LongPressMs, cfg.Timing.DoubleTapMs, func(a *config.Action) {
|
||||
if a == nil {
|
||||
return
|
||||
}
|
||||
if err := ExecuteAction(a, primaryDeck, primaryPM); err != nil {
|
||||
slog.Error("execute action", "error", err)
|
||||
}
|
||||
if a.Type == "page" {
|
||||
asm.NotifyManualPage(a.Page)
|
||||
for _, pm := range pageMgrs[1:] {
|
||||
pm.ActivatePage(a.Page)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
reconnectTicker := time.NewTicker(5 * time.Second)
|
||||
defer reconnectTicker.Stop()
|
||||
|
||||
@@ -146,38 +162,27 @@ func Run(ctx context.Context, cfg *config.Config, opts RunOptions) error {
|
||||
}
|
||||
}
|
||||
|
||||
for idx, output := range savedOutputs {
|
||||
primaryPM.ReRenderDisplayKey(idx, output)
|
||||
}
|
||||
}
|
||||
|
||||
page := primaryPM.ActivePage()
|
||||
if page == nil {
|
||||
continue
|
||||
}
|
||||
for _, k := range page.Keys {
|
||||
if k.Index == evt.Index && k.Action != nil {
|
||||
slog.Debug("key pressed", "index", evt.Index, "action", k.Action.Type)
|
||||
|
||||
if k.Action.Type == "page" {
|
||||
asm.NotifyManualPage(k.Action.Page)
|
||||
for idx, dout := range savedOutputs {
|
||||
if dout != nil {
|
||||
primaryPM.ReRenderDisplayKey(idx, dout.Text)
|
||||
}
|
||||
|
||||
go func(a *config.Action) {
|
||||
if err := ExecuteAction(a, primaryDeck, primaryPM); err != nil {
|
||||
slog.Error("execute action", "error", err)
|
||||
}
|
||||
if a.Type == "page" {
|
||||
for _, pm := range pageMgrs[1:] {
|
||||
pm.ActivatePage(a.Page)
|
||||
}
|
||||
}
|
||||
}(k.Action)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
page := primaryPM.ActivePage()
|
||||
if page == nil {
|
||||
continue
|
||||
}
|
||||
for _, k := range page.Keys {
|
||||
if k.Index == evt.Index {
|
||||
if len(k.Actions) > 0 {
|
||||
ge.HandleEvent(evt, k.Actions)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
case win := <-windowCh:
|
||||
if page, ok := asm.Evaluate(win, primaryPM.ActivePageName()); ok {
|
||||
for _, pm := range pageMgrs {
|
||||
@@ -209,10 +214,12 @@ func Run(ctx context.Context, cfg *config.Config, opts RunOptions) error {
|
||||
for _, d := range decks {
|
||||
d.SetBrightness(deviceBrightness(cfg, d.Serial()))
|
||||
}
|
||||
for _, pm := range pageMgrs {
|
||||
pm.stopPeriodicKeys()
|
||||
pm.LoadPages(cfg.Pages)
|
||||
}
|
||||
for _, pm := range pageMgrs {
|
||||
pm.stopPeriodicKeys()
|
||||
pm.defaultFont = cfg.Font
|
||||
pm.LoadPages(cfg.Pages)
|
||||
}
|
||||
ge.ReloadTiming(cfg.Timing.LongPressMs, cfg.Timing.DoubleTapMs)
|
||||
asm.Reload(cfg.AutoSwitch)
|
||||
if len(cfg.AutoSwitch) > 0 && detector == nil {
|
||||
detector = NewWindowDetector()
|
||||
@@ -285,6 +292,7 @@ func reconnectDeck(ctx context.Context, cfg *config.Config, pm **PageManager, as
|
||||
newDeck, err := OpenDeck("")
|
||||
if err == nil {
|
||||
*pm = NewPageManager(newDeck)
|
||||
(*pm).defaultFont = cfg.Font
|
||||
(*pm).LoadPages(cfg.Pages)
|
||||
(*pm).ActivatePage(cfg.DefaultPage)
|
||||
(*pm).startPeriodicKeys()
|
||||
|
||||
Reference in New Issue
Block a user