From f268d519b8aeb35ac8434743b1bbd7329dad0454 Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Wed, 11 Feb 2026 22:16:20 -0800 Subject: [PATCH] Fix image headers, add Stream Deck+ color mixer with coarse/fine encoders --- cmd/deckcolor/main.go | 118 +++++++++++++++++++++++++++++++++++ lib/streamdeck/streamdeck.go | 40 ++++++------ lib/streamdeck/text.go | 21 +++++-- 3 files changed, 156 insertions(+), 23 deletions(-) create mode 100644 cmd/deckcolor/main.go diff --git a/cmd/deckcolor/main.go b/cmd/deckcolor/main.go new file mode 100644 index 0000000..20cecf1 --- /dev/null +++ b/cmd/deckcolor/main.go @@ -0,0 +1,118 @@ +package main + +import ( + "fmt" + "image" + "image/color" + "image/draw" + "os" + "os/signal" + "syscall" + + "qrun/lib/streamdeck" +) + +func clamp(v int) int { + if v < 0 { + return 0 + } + if v > 255 { + return 255 + } + return v +} + +func main() { + dev, err := streamdeck.OpenModel(&streamdeck.ModelPlus) + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + defer dev.Close() + + dev.SetBrightness(80) + + rgb := [3]int{0, 0, 0} + fine := [3]bool{false, false, false} + labels := [3]string{"R", "G", "B"} + labelColors := [3]color.RGBA{ + {255, 0, 0, 255}, + {0, 255, 0, 255}, + {0, 100, 255, 255}, + } + + updateLCD := func() { + c := color.RGBA{uint8(rgb[0]), uint8(rgb[1]), uint8(rgb[2]), 255} + dev.SetLCDColor(0, 0, dev.Model().LCDWidth, dev.Model().LCDHeight, c) + } + + updateKey := func(i int) { + bg := color.RGBA{labelColors[i].R / 4, labelColors[i].G / 4, labelColors[i].B / 4, 255} + sz := dev.Model().KeySize + txt := streamdeck.TextImageWithFaceSized(streamdeck.MonoBoldSmall, sz, bg, labelColors[i], labels[i], fmt.Sprintf("%d", rgb[i])) + if fine[i] { + img := image.NewRGBA(image.Rect(0, 0, sz, sz)) + draw.Draw(img, img.Bounds(), txt, image.Point{}, draw.Src) + border := labelColors[i] + b := 4 + for y := 0; y < sz; y++ { + for x := 0; x < sz; x++ { + if x < b || x >= sz-b || y < b || y >= sz-b { + img.Set(x, y, border) + } + } + } + dev.SetKeyImage(i, img) + } else { + dev.SetKeyImage(i, txt) + } + } + + updateAllKeys := func() { + for i := 0; i < 3; i++ { + updateKey(i) + } + } + + updateLCD() + updateAllKeys() + + for i := 3; i < dev.Model().Keys; i++ { + dev.ClearKey(i) + } + + input := make(chan streamdeck.InputEvent, 64) + go func() { + if err := dev.ReadInput(input); err != nil { + fmt.Fprintf(os.Stderr, "Read error: %v\n", err) + } + }() + + sig := make(chan os.Signal, 1) + signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) + + for { + select { + case ev := <-input: + if ev.Encoder != nil && ev.Encoder.Encoder < 3 { + i := ev.Encoder.Encoder + if ev.Encoder.Delta != 0 { + delta := ev.Encoder.Delta + if !fine[i] { + delta *= 10 + } + rgb[i] = clamp(rgb[i] + delta) + updateLCD() + updateKey(i) + fmt.Printf("R=%d G=%d B=%d\n", rgb[0], rgb[1], rgb[2]) + } else if ev.Encoder.Pressed { + fine[i] = !fine[i] + updateKey(i) + } + } + case <-sig: + fmt.Println() + return + } + } +} diff --git a/lib/streamdeck/streamdeck.go b/lib/streamdeck/streamdeck.go index 0dc9bf4..94fd55b 100644 --- a/lib/streamdeck/streamdeck.go +++ b/lib/streamdeck/streamdeck.go @@ -181,10 +181,10 @@ func (d *Device) ClearAllKeys() error { func (d *Device) sendKeyImage(key byte, imgData []byte) error { reportLen := d.dev.GetOutputReportLength() - hdrLen := uint16(8) + hdrLen := uint16(7) payloadLen := reportLen - hdrLen - var page uint16 + var page byte for start := uint16(0); start < uint16(len(imgData)); page++ { end := start + payloadLen last := byte(0) @@ -195,14 +195,13 @@ func (d *Device) sendKeyImage(key byte, imgData []byte) error { chunk := imgData[start:end] hdr := []byte{ - 0x02, 0x07, key, last, byte(len(chunk)), byte(len(chunk) >> 8), - byte(page), - byte(page >> 8), + page, + 0, } payload := append(hdr, chunk...) @@ -241,10 +240,10 @@ func (d *Device) SetLCDColor(x, y, w, h int, c color.Color) error { func (d *Device) sendLCDImage(x, y, w, h uint16, imgData []byte) error { reportLen := d.dev.GetOutputReportLength() - hdrLen := uint16(16) + hdrLen := uint16(15) payloadLen := reportLen - hdrLen - var page uint16 + var page byte for start := uint16(0); start < uint16(len(imgData)); page++ { end := start + payloadLen last := byte(0) @@ -254,17 +253,22 @@ func (d *Device) sendLCDImage(x, y, w, h uint16, imgData []byte) error { } chunk := imgData[start:end] - hdr := make([]byte, 16) - hdr[0] = 0x02 - hdr[1] = 0x0C - binary.LittleEndian.PutUint16(hdr[2:], x) - binary.LittleEndian.PutUint16(hdr[4:], y) - binary.LittleEndian.PutUint16(hdr[6:], w) - binary.LittleEndian.PutUint16(hdr[8:], h) - hdr[10] = last - binary.LittleEndian.PutUint16(hdr[11:], page) - binary.LittleEndian.PutUint16(hdr[13:], uint16(len(chunk))) - hdr[15] = 0 + hdr := make([]byte, 15) + hdr[0] = 0x0C + hdr[1] = byte(x) + hdr[2] = byte(x >> 8) + hdr[3] = byte(y) + hdr[4] = byte(y >> 8) + hdr[5] = byte(w) + hdr[6] = byte(w >> 8) + hdr[7] = byte(h) + hdr[8] = byte(h >> 8) + hdr[9] = last + hdr[10] = page + hdr[11] = 0 + hdr[12] = byte(len(chunk)) + hdr[13] = byte(len(chunk) >> 8) + hdr[14] = 0 payload := append(hdr, chunk...) padding := make([]byte, reportLen-uint16(len(payload))) diff --git a/lib/streamdeck/text.go b/lib/streamdeck/text.go index cd6a298..988a2fb 100644 --- a/lib/streamdeck/text.go +++ b/lib/streamdeck/text.go @@ -17,17 +17,23 @@ import ( var fontFS embed.FS var ( - MonoRegular font.Face - MonoMedium font.Face - MonoBold font.Face - Regular font.Face - Bold font.Face + MonoRegular font.Face + MonoMedium font.Face + MonoBold font.Face + MonoRegularSmall font.Face + MonoMediumSmall font.Face + MonoBoldSmall font.Face + Regular font.Face + Bold font.Face ) func init() { MonoRegular = loadFace("fonts/AtkinsonHyperlegibleMono-Regular.ttf", 72) MonoMedium = loadFace("fonts/AtkinsonHyperlegibleMono-Medium.ttf", 72) MonoBold = loadFace("fonts/AtkinsonHyperlegibleMono-Bold.ttf", 72) + MonoRegularSmall = loadFace("fonts/AtkinsonHyperlegibleMono-Regular.ttf", 40) + MonoMediumSmall = loadFace("fonts/AtkinsonHyperlegibleMono-Medium.ttf", 40) + MonoBoldSmall = loadFace("fonts/AtkinsonHyperlegibleMono-Bold.ttf", 40) Regular = loadFace("fonts/AtkinsonHyperlegible-Regular.ttf", 16) Bold = loadFace("fonts/AtkinsonHyperlegible-Bold.ttf", 16) } @@ -93,6 +99,11 @@ func (d *Device) SetKeyText(key int, bg color.Color, fg color.Color, text string return d.SetKeyImage(key, TextImageSized(d.model.KeySize, bg, fg, lines...)) } +func (d *Device) SetKeyTextWithFace(key int, face font.Face, bg color.Color, fg color.Color, text string) error { + lines := strings.Split(text, "\n") + return d.SetKeyImage(key, TextImageWithFaceSized(face, d.model.KeySize, bg, fg, lines...)) +} + func (d *Device) SetKeyBoldText(key int, bg color.Color, fg color.Color, text string) error { lines := strings.Split(text, "\n") return d.SetKeyImage(key, TextImageWithFaceSized(MonoBold, d.model.KeySize, bg, fg, lines...))