refactor: restructure to standard Go project layout (cmd/ + internal/)
- Move entry point to cmd/streamdeck-lets-go/ - Split package main into internal packages: - internal/deck: hardware control (Deck, events, brightness) - internal/render: key rendering, icons, fonts, PageManager - internal/web: HTTP API + embedded SPA - internal/daemon: event loop, autoswitch, screensaver, actions - Add ConfigDir() to internal/config - Export cross-package API (Deck, PageManager, WebServer, etc.) - Move dist/arch/ → packaging/ with updated PKGBUILD/.SRCINFO - Add Makefile (build, test, vet, fmt, install) - Update README with new build commands and project structure - Delete unused media.go stub
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"streamdeck-lets-go/internal/config"
|
||||
"streamdeck-lets-go/internal/deck"
|
||||
)
|
||||
|
||||
type ActionCallback func(idx int, a *config.Action)
|
||||
|
||||
type gestureKeyState struct {
|
||||
mu sync.Mutex
|
||||
pressedAt time.Time
|
||||
lastTapAt time.Time
|
||||
holdTimer *time.Timer
|
||||
holdActive bool
|
||||
pendingTap *time.Timer
|
||||
}
|
||||
|
||||
type GestureEngine struct {
|
||||
mu sync.Mutex
|
||||
states map[int]*gestureKeyState
|
||||
longMs time.Duration
|
||||
doubleMs time.Duration
|
||||
onAction ActionCallback
|
||||
}
|
||||
|
||||
func NewGestureEngine(longMs, doubleMs int, cb ActionCallback) *GestureEngine {
|
||||
if longMs <= 0 {
|
||||
longMs = config.DefaultLongPressMs
|
||||
}
|
||||
if doubleMs <= 0 {
|
||||
doubleMs = config.DefaultDoubleTapMs
|
||||
}
|
||||
return &GestureEngine{
|
||||
states: make(map[int]*gestureKeyState),
|
||||
longMs: time.Duration(longMs) * time.Millisecond,
|
||||
doubleMs: time.Duration(doubleMs) * time.Millisecond,
|
||||
onAction: cb,
|
||||
}
|
||||
}
|
||||
|
||||
func (ge *GestureEngine) ReloadTiming(longMs, doubleMs int) {
|
||||
ge.mu.Lock()
|
||||
defer ge.mu.Unlock()
|
||||
if longMs > 0 {
|
||||
ge.longMs = time.Duration(longMs) * time.Millisecond
|
||||
}
|
||||
if doubleMs > 0 {
|
||||
ge.doubleMs = time.Duration(doubleMs) * time.Millisecond
|
||||
}
|
||||
}
|
||||
|
||||
func (ge *GestureEngine) Reset() {
|
||||
ge.mu.Lock()
|
||||
defer ge.mu.Unlock()
|
||||
for _, s := range ge.states {
|
||||
s.mu.Lock()
|
||||
s.cancelTimers()
|
||||
s.holdActive = false
|
||||
s.lastTapAt = time.Time{}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *gestureKeyState) cancelTimers() {
|
||||
if s.holdTimer != nil {
|
||||
s.holdTimer.Stop()
|
||||
s.holdTimer = nil
|
||||
}
|
||||
if s.pendingTap != nil {
|
||||
s.pendingTap.Stop()
|
||||
s.pendingTap = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (ge *GestureEngine) getState(index int) *gestureKeyState {
|
||||
ge.mu.Lock()
|
||||
defer ge.mu.Unlock()
|
||||
s, ok := ge.states[index]
|
||||
if !ok {
|
||||
s = &gestureKeyState{}
|
||||
ge.states[index] = s
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func findAction(actions []config.KeyAction, trigger string) *config.Action {
|
||||
for _, a := range actions {
|
||||
if a.Trigger == trigger {
|
||||
return &a.Action
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ge *GestureEngine) HandleEvent(evt deck.Event, actions []config.KeyAction) {
|
||||
state := ge.getState(evt.Index)
|
||||
|
||||
if len(actions) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
switch evt.Kind {
|
||||
case deck.EventKeyPressed:
|
||||
ge.handleKeyPress(state, evt, actions)
|
||||
case deck.EventKeyReleased:
|
||||
ge.handleKeyRelease(state, evt, actions)
|
||||
}
|
||||
}
|
||||
|
||||
func (ge *GestureEngine) handleKeyPress(state *gestureKeyState, evt deck.Event, actions []config.KeyAction) {
|
||||
state.mu.Lock()
|
||||
state.cancelTimers()
|
||||
state.holdActive = false
|
||||
state.pressedAt = evt.At
|
||||
state.holdTimer = time.AfterFunc(ge.longMs, func() {
|
||||
state.mu.Lock()
|
||||
state.holdActive = true
|
||||
state.holdTimer = nil
|
||||
state.mu.Unlock()
|
||||
|
||||
if a := findAction(actions, "hold_start"); a != nil {
|
||||
ge.onAction(evt.Index, a)
|
||||
} else if a := findAction(actions, "long_press"); a != nil {
|
||||
ge.onAction(evt.Index, a)
|
||||
}
|
||||
})
|
||||
state.mu.Unlock()
|
||||
}
|
||||
|
||||
func (ge *GestureEngine) handleKeyRelease(state *gestureKeyState, evt deck.Event, actions []config.KeyAction) {
|
||||
state.mu.Lock()
|
||||
state.cancelTimers()
|
||||
|
||||
if state.holdActive {
|
||||
state.holdActive = false
|
||||
state.mu.Unlock()
|
||||
if a := findAction(actions, "hold_end"); a != nil {
|
||||
ge.onAction(evt.Index, a)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !state.lastTapAt.IsZero() && evt.At.Sub(state.lastTapAt) <= ge.doubleMs {
|
||||
state.lastTapAt = time.Time{}
|
||||
state.mu.Unlock()
|
||||
if a := findAction(actions, "double_tap"); a != nil {
|
||||
ge.onAction(evt.Index, a)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
lastTap := evt.At
|
||||
state.lastTapAt = lastTap
|
||||
|
||||
state.pendingTap = time.AfterFunc(ge.doubleMs, func() {
|
||||
state.mu.Lock()
|
||||
if state.lastTapAt.Equal(lastTap) {
|
||||
state.lastTapAt = time.Time{}
|
||||
state.pendingTap = nil
|
||||
state.mu.Unlock()
|
||||
if a := findAction(actions, "tap"); a != nil {
|
||||
ge.onAction(evt.Index, a)
|
||||
}
|
||||
return
|
||||
}
|
||||
state.mu.Unlock()
|
||||
})
|
||||
state.mu.Unlock()
|
||||
}
|
||||
Reference in New Issue
Block a user