switched from fbink to gtk2
This commit is contained in:
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,2 +1,8 @@
|
||||
kindle_fbink_go
|
||||
test-go
|
||||
test-go
|
||||
docker
|
||||
*.sh
|
||||
Dockerfile
|
||||
.shims
|
||||
.pcstub
|
||||
nomi
|
||||
@@ -1,41 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
CONTAINER_NAME=kindle_go_builder
|
||||
WORKDIR=/src
|
||||
OUTFILE=kindle_fbink_go
|
||||
|
||||
if ! docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}\$"; then
|
||||
echo "[sh] Creating container"
|
||||
docker run -dit --name ${CONTAINER_NAME} debian:bookworm sleep infinity
|
||||
fi
|
||||
|
||||
echo "[sh] Installing toolchain inside container"
|
||||
docker exec ${CONTAINER_NAME} bash -c "
|
||||
set -e
|
||||
apt-get update
|
||||
apt-get install -y golang-go curl xz-utils build-essential gcc-arm-linux-gnueabi g++-arm-linux-gnueabi
|
||||
if [ ! -d /usr/local/arm-linux-musleabi-cross ]; then
|
||||
echo '[sh] Downloading musl cross toolchain...'
|
||||
cd /usr/local
|
||||
curl -L https://musl.cc/arm-linux-musleabi-cross.tgz | tar xz
|
||||
fi
|
||||
"
|
||||
|
||||
echo "[sh] Copying project into container"
|
||||
docker exec ${CONTAINER_NAME} rm -rf ${WORKDIR}
|
||||
docker cp ./go ${CONTAINER_NAME}:${WORKDIR}
|
||||
|
||||
echo "[sh] Building for ARMv7 soft-float (musl static)"
|
||||
docker exec ${CONTAINER_NAME} bash -c "
|
||||
set -e
|
||||
export PATH=/usr/local/arm-linux-musleabi-cross/bin:\$PATH
|
||||
cd ${WORKDIR}
|
||||
export GOOS=linux GOARCH=arm GOARM=5 CGO_ENABLED=1 CC=arm-linux-gnueabi-gcc
|
||||
go build -ldflags '-linkmode external -extldflags "-static"' -o kindle_fbink_go .
|
||||
"
|
||||
|
||||
echo "[sh] Copying binary back to hsot"
|
||||
docker cp ${CONTAINER_NAME}:${WORKDIR}/${OUTFILE} ./${OUTFILE}
|
||||
|
||||
echo "[sh] Build complete: ${OUTFILE}"
|
||||
10
go.mod
Normal file
10
go.mod
Normal file
@@ -0,0 +1,10 @@
|
||||
module main
|
||||
|
||||
go 1.25.2
|
||||
|
||||
require github.com/mattn/go-gtk v0.0.0-20240119050609-48574e312fac
|
||||
|
||||
require (
|
||||
github.com/mattn/go-pointer v0.0.1 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
)
|
||||
12
go.sum
Normal file
12
go.sum
Normal file
@@ -0,0 +1,12 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/mattn/go-gtk v0.0.0-20240119050609-48574e312fac h1:tNm7zRceQAOg9D8vQFq0K9hy49j39+9+7rSjML4YREI=
|
||||
github.com/mattn/go-gtk v0.0.0-20240119050609-48574e312fac/go.mod h1:PwzwfeB5syFHXORC3MtPylVcjIoTDT/9cvkKpEndGVI=
|
||||
github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0=
|
||||
github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
48
main.go
Normal file
48
main.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package main
|
||||
|
||||
// #cgo pkg-config: gtk+-2.0
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"github.com/mattn/go-gtk/gtk"
|
||||
)
|
||||
|
||||
func main() {
|
||||
gtk.Init(nil)
|
||||
|
||||
// Main window
|
||||
win := gtk.NewWindow(gtk.WINDOW_TOPLEVEL)
|
||||
win.SetTitle("L:A_N:application_PC:TS_ID:com.lab126.store")
|
||||
win.SetDefaultSize(400, 200)
|
||||
win.Connect("destroy", gtk.MainQuit)
|
||||
|
||||
// Vertical layout container
|
||||
vbox := gtk.NewVBox(false, 10)
|
||||
win.Add(vbox)
|
||||
|
||||
// Label
|
||||
label := gtk.NewLabel("Hello Kindle!")
|
||||
vbox.PackStart(label, false, false, 10)
|
||||
|
||||
// Horizontal box for buttons
|
||||
hbox := gtk.NewHBox(true, 5)
|
||||
vbox.PackStart(hbox, false, false, 10)
|
||||
|
||||
// Show button
|
||||
btnShow := gtk.NewButtonWithLabel("Show Text")
|
||||
btnShow.Clicked(func() {
|
||||
label.Show()
|
||||
})
|
||||
|
||||
// Hide button
|
||||
btnHide := gtk.NewButtonWithLabel("Hide Text")
|
||||
btnHide.Clicked(func() {
|
||||
label.Hide()
|
||||
})
|
||||
|
||||
hbox.PackStart(btnShow, true, true, 5)
|
||||
hbox.PackStart(btnHide, true, true, 5)
|
||||
|
||||
win.ShowAll()
|
||||
gtk.Main()
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type DisplayController struct {
|
||||
brightness BrightnessInfo
|
||||
}
|
||||
|
||||
type BrightnessInfo struct {
|
||||
maxBrightness int
|
||||
currentBrightness int
|
||||
brightnessPath string
|
||||
}
|
||||
|
||||
const BACKLIGHT_DIR = "/sys/class/backlight/"
|
||||
|
||||
func (a *App) initBrightness() {
|
||||
entries, err := os.ReadDir(BACKLIGHT_DIR)
|
||||
if err != nil || len(entries) != 1 {
|
||||
return
|
||||
}
|
||||
|
||||
entry := entries[0].Name()
|
||||
brightnessPath := fmt.Sprintf("%s%s/brightness", BACKLIGHT_DIR, entry)
|
||||
maxPath := fmt.Sprintf("%s%s/max_brightness", BACKLIGHT_DIR, entry)
|
||||
|
||||
if err := os.WriteFile(brightnessPath, []byte("256\n"), 0644); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
maxBytes, err := os.ReadFile(maxPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
maxVal, err := strconv.Atoi(strings.TrimSpace(string(maxBytes)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
controller := DisplayController{
|
||||
brightness: BrightnessInfo{
|
||||
maxBrightness: maxVal,
|
||||
currentBrightness: 256,
|
||||
brightnessPath: brightnessPath,
|
||||
},
|
||||
}
|
||||
fmt.Println("display controller assembled")
|
||||
a.dispCtrl = controller
|
||||
}
|
||||
|
||||
func (a *App) setBrightness(newBrightness int) {
|
||||
filepath := a.dispCtrl.brightness.brightnessPath
|
||||
if filepath == "" {
|
||||
fmt.Println("no filepath to change brightness")
|
||||
return
|
||||
}
|
||||
if newBrightness > a.dispCtrl.brightness.maxBrightness {
|
||||
newBrightness = a.dispCtrl.brightness.maxBrightness
|
||||
}
|
||||
if newBrightness < 0 {
|
||||
newBrightness = 0
|
||||
}
|
||||
|
||||
os.WriteFile(filepath, []byte(strconv.Itoa(newBrightness)+"\n"), 0644)
|
||||
a.dispCtrl.brightness.currentBrightness = newBrightness
|
||||
|
||||
}
|
||||
236
src/draw.go
236
src/draw.go
@@ -1,236 +0,0 @@
|
||||
package main
|
||||
|
||||
import "math"
|
||||
|
||||
type Vec3 struct{ x, y, z float64 }
|
||||
|
||||
func project(v Vec3, w, h int, scale float64) (int, int) {
|
||||
return int(v.x*scale) + w/2, int(v.y*scale) + h/2
|
||||
}
|
||||
func rotate(v Vec3, ax, ay, az float64) Vec3 {
|
||||
cx, sx := math.Cos(ax), math.Sin(ax)
|
||||
cy, sy := math.Cos(ay), math.Sin(ay)
|
||||
cz, sz := math.Cos(az), math.Sin(az)
|
||||
x := v.x*cy*cz + v.y*(sx*sy*cz-cx*sz) + v.z*(cx*sy*cz+sx*sz)
|
||||
y := v.x*cy*sz + v.y*(sx*sy*sz+cx*cz) + v.z*(cx*sy*sz-sx*cz)
|
||||
z := v.x*-sy + v.y*sx*cy + v.z*cx*cy
|
||||
return Vec3{x, y, z}
|
||||
}
|
||||
|
||||
func setPixel(buf []byte, stride, x, y int, color byte) {
|
||||
if x < 0 || y < 0 || x >= stride {
|
||||
return
|
||||
}
|
||||
idx := y*stride + x
|
||||
if idx < 0 || idx >= len(buf) {
|
||||
return
|
||||
}
|
||||
buf[idx] = color
|
||||
}
|
||||
|
||||
func drawHSpan(buf []byte, stride, x0, x1, y int, color byte) {
|
||||
if x0 > x1 {
|
||||
return
|
||||
}
|
||||
if y < 0 || y*stride >= len(buf) {
|
||||
return
|
||||
}
|
||||
if x0 < 0 {
|
||||
x0 = 0
|
||||
}
|
||||
if x1 >= stride {
|
||||
x1 = stride - 1
|
||||
}
|
||||
row := y * stride
|
||||
if row < 0 || row >= len(buf) {
|
||||
return
|
||||
}
|
||||
for x := x0; x <= x1; x++ {
|
||||
idx := row + x
|
||||
if idx >= 0 && idx < len(buf) {
|
||||
buf[idx] = color
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func drawLine(buf []byte, stride, x0, y0, x1, y1 int, color byte) {
|
||||
dx := int(math.Abs(float64(x1 - x0)))
|
||||
sx := -1
|
||||
if x0 < x1 {
|
||||
sx = 1
|
||||
}
|
||||
dy := -int(math.Abs(float64(y1 - y0)))
|
||||
sy := -1
|
||||
if y0 < y1 {
|
||||
sy = 1
|
||||
}
|
||||
e := dx + dy
|
||||
for {
|
||||
if x0 >= 0 && x0 < stride && y0 >= 0 && y0*stride+x0 < len(buf) {
|
||||
buf[y0*stride+x0] = color
|
||||
}
|
||||
if x0 == x1 && y0 == y1 {
|
||||
break
|
||||
}
|
||||
e2 := 2 * e
|
||||
if e2 >= dy {
|
||||
e += dy
|
||||
x0 += sx
|
||||
}
|
||||
if e2 <= dx {
|
||||
e += dx
|
||||
y0 += sy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func drawRect(buf []byte, stride, x, y, w, h int, color byte) {
|
||||
for j := y; j < y+h; j++ {
|
||||
if j < 0 || j*stride >= len(buf) {
|
||||
continue
|
||||
}
|
||||
row := j * stride
|
||||
for i := x; i < x+w; i++ {
|
||||
if i >= 0 && i < stride && row+i < len(buf) {
|
||||
buf[row+i] = color
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func strokeRect(buf []byte, stride, x, y, w, h, thickness int, color byte) {
|
||||
if thickness <= 0 || w <= 0 || h <= 0 {
|
||||
return
|
||||
}
|
||||
if thickness > w/2 {
|
||||
thickness = w / 2
|
||||
}
|
||||
if thickness > h/2 {
|
||||
thickness = h / 2
|
||||
}
|
||||
drawRect(buf, stride, x, y, w, thickness, color)
|
||||
drawRect(buf, stride, x, y+h-thickness, w, thickness, color)
|
||||
drawRect(buf, stride, x, y, thickness, h, color)
|
||||
drawRect(buf, stride, x+w-thickness, y, thickness, h, color)
|
||||
}
|
||||
|
||||
func fillRoundedRect(buf []byte, stride, x, y, w, h, radius int, color byte) {
|
||||
if w <= 0 || h <= 0 {
|
||||
return
|
||||
}
|
||||
if radius <= 0 {
|
||||
drawRect(buf, stride, x, y, w, h, color)
|
||||
return
|
||||
}
|
||||
maxRadius := w
|
||||
if h < maxRadius {
|
||||
maxRadius = h
|
||||
}
|
||||
maxRadius /= 2
|
||||
if radius > maxRadius {
|
||||
radius = maxRadius
|
||||
}
|
||||
if radius <= 0 {
|
||||
drawRect(buf, stride, x, y, w, h, color)
|
||||
return
|
||||
}
|
||||
innerHeight := h - 2*radius
|
||||
if innerHeight > 0 {
|
||||
drawRect(buf, stride, x, y+radius, w, innerHeight, color)
|
||||
}
|
||||
floatR := float64(radius)
|
||||
for oy := 0; oy < radius; oy++ {
|
||||
dy := floatR - (float64(oy) + 0.5)
|
||||
delta := floatR*floatR - dy*dy
|
||||
if delta < 0 {
|
||||
continue
|
||||
}
|
||||
span := int(math.Sqrt(delta))
|
||||
left := x + radius - span
|
||||
right := x + w - radius + span - 1
|
||||
drawHSpan(buf, stride, left, right, y+oy, color)
|
||||
drawHSpan(buf, stride, left, right, y+h-1-oy, color)
|
||||
}
|
||||
}
|
||||
|
||||
func strokeRoundedRect(buf []byte, stride, x, y, w, h, thickness, radius int, color byte) {
|
||||
if thickness <= 0 || w <= 0 || h <= 0 {
|
||||
return
|
||||
}
|
||||
if thickness > w/2 {
|
||||
thickness = w / 2
|
||||
}
|
||||
if thickness > h/2 {
|
||||
thickness = h / 2
|
||||
}
|
||||
if radius <= 0 {
|
||||
strokeRect(buf, stride, x, y, w, h, thickness, color)
|
||||
return
|
||||
}
|
||||
maxRadius := w
|
||||
if h < maxRadius {
|
||||
maxRadius = h
|
||||
}
|
||||
maxRadius /= 2
|
||||
if radius > maxRadius {
|
||||
radius = maxRadius
|
||||
}
|
||||
if radius <= 0 {
|
||||
strokeRect(buf, stride, x, y, w, h, thickness, color)
|
||||
return
|
||||
}
|
||||
innerRadius := radius - thickness
|
||||
if innerRadius < 0 {
|
||||
innerRadius = 0
|
||||
}
|
||||
topWidth := w - 2*radius
|
||||
if topWidth < 0 {
|
||||
topWidth = 0
|
||||
}
|
||||
if topWidth > 0 {
|
||||
drawRect(buf, stride, x+radius, y, topWidth, thickness, color)
|
||||
drawRect(buf, stride, x+radius, y+h-thickness, topWidth, thickness, color)
|
||||
}
|
||||
sideHeight := h - 2*radius
|
||||
if sideHeight < 0 {
|
||||
sideHeight = 0
|
||||
}
|
||||
if sideHeight > 0 {
|
||||
drawRect(buf, stride, x, y+radius, thickness, sideHeight, color)
|
||||
drawRect(buf, stride, x+w-thickness, y+radius, thickness, sideHeight, color)
|
||||
}
|
||||
floatOuter := float64(radius)
|
||||
floatInner := float64(innerRadius)
|
||||
outerSq := floatOuter * floatOuter
|
||||
innerSq := floatInner * floatInner
|
||||
for oy := 0; oy < radius; oy++ {
|
||||
pyTop := y + oy
|
||||
pyBottom := y + h - 1 - oy
|
||||
dy := floatOuter - (float64(oy) + 0.5)
|
||||
outerDelta := outerSq - dy*dy
|
||||
if outerDelta < 0 {
|
||||
continue
|
||||
}
|
||||
outerSpan := int(math.Sqrt(outerDelta))
|
||||
outerLeft := x + radius - outerSpan
|
||||
outerRight := x + w - radius + outerSpan - 1
|
||||
if innerRadius <= 0 {
|
||||
drawHSpan(buf, stride, outerLeft, outerRight, pyTop, color)
|
||||
drawHSpan(buf, stride, outerLeft, outerRight, pyBottom, color)
|
||||
continue
|
||||
}
|
||||
innerDelta := innerSq - dy*dy
|
||||
if innerDelta <= 0 {
|
||||
drawHSpan(buf, stride, outerLeft, outerRight, pyTop, color)
|
||||
drawHSpan(buf, stride, outerLeft, outerRight, pyBottom, color)
|
||||
continue
|
||||
}
|
||||
innerSpan := int(math.Sqrt(innerDelta))
|
||||
innerLeft := x + radius - innerSpan
|
||||
innerRight := x + w - radius + innerSpan - 1
|
||||
drawHSpan(buf, stride, outerLeft, innerLeft-1, pyTop, color)
|
||||
drawHSpan(buf, stride, innerRight+1, outerRight, pyTop, color)
|
||||
drawHSpan(buf, stride, outerLeft, innerLeft-1, pyBottom, color)
|
||||
drawHSpan(buf, stride, innerRight+1, outerRight, pyBottom, color)
|
||||
}
|
||||
}
|
||||
1804
src/fbink/fbink.h
1804
src/fbink/fbink.h
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1,76 +0,0 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -I./fbink
|
||||
#cgo LDFLAGS: -L./fbink -lfbink -lm
|
||||
#include <stdlib.h>
|
||||
#include "fbink.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type FB struct {
|
||||
file *os.File
|
||||
fd C.int
|
||||
cfg C.FBInkConfig
|
||||
screenWidth int
|
||||
screenHeight int
|
||||
}
|
||||
|
||||
func InitFBInk(width, height int) (*FB, error) {
|
||||
fb, err := os.OpenFile("/dev/fb0", os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open fb0: %w", err)
|
||||
}
|
||||
f := &FB{file: fb, fd: C.int(fb.Fd()), screenWidth: width, screenHeight: height}
|
||||
C.fbink_init(f.fd, &f.cfg)
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (f *FB) Close() { _ = f.file.Close() }
|
||||
|
||||
func (f *FB) WriteBuffer(buf []byte) error {
|
||||
_, err := f.file.WriteAt(buf, 0)
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *FB) Clear(doRefresh bool) {
|
||||
var r C.FBInkRect
|
||||
C.fbink_cls(f.fd, &f.cfg, &r, C._Bool(doRefresh))
|
||||
if doRefresh {
|
||||
C.fbink_wait_for_complete(f.fd, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FB) PrintAtPixel(x, y int, text string, fontmult int) {
|
||||
cstr := C.CString(text)
|
||||
defer C.free(unsafe.Pointer(cstr))
|
||||
f.cfg.hoffset = C.short(x)
|
||||
f.cfg.voffset = C.short(y)
|
||||
f.cfg.fontmult = C.uint8_t(fontmult)
|
||||
C.fbink_print(f.fd, cstr, &f.cfg)
|
||||
}
|
||||
|
||||
func (f *FB) RefreshRect(top, left, w, h int) {
|
||||
if w <= 0 || h <= 0 {
|
||||
return
|
||||
}
|
||||
C.fbink_refresh(
|
||||
f.fd,
|
||||
C.uint32_t(top),
|
||||
C.uint32_t(left),
|
||||
C.uint32_t(w),
|
||||
C.uint32_t(h),
|
||||
&f.cfg,
|
||||
)
|
||||
C.fbink_wait_for_complete(f.fd, 0)
|
||||
}
|
||||
|
||||
func (f *FB) RefreshFull() {
|
||||
f.RefreshRect(0, 0, f.screenWidth, f.screenHeight)
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
module kindle_fbink_go
|
||||
|
||||
go 1.19
|
||||
185
src/main.go
185
src/main.go
@@ -1,185 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("startup")
|
||||
const (
|
||||
width = 1072
|
||||
height = 1448
|
||||
)
|
||||
|
||||
fb, err := InitFBInk(width, height)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer fb.Close()
|
||||
|
||||
var evfd *os.File
|
||||
var (
|
||||
touchAxisX AxisRange
|
||||
touchAxisY AxisRange
|
||||
haveAxis bool
|
||||
)
|
||||
|
||||
if path, err := findTouchEventNode(); err == nil {
|
||||
if f, e := openNonblock(path); e == nil {
|
||||
evfd = f
|
||||
fmt.Println("touch input:", path)
|
||||
defer evfd.Close()
|
||||
if xr, yr, err := ReadTouchAxisRanges(path); err == nil {
|
||||
fmt.Printf("touch axis from sysfs X:[%d,%d] Y:[%d,%d]\n", xr.Min, xr.Max, yr.Min, yr.Max)
|
||||
touchAxisX = xr
|
||||
touchAxisY = yr
|
||||
haveAxis = true
|
||||
} else {
|
||||
fmt.Println("warning: touch axis range:", err)
|
||||
}
|
||||
} else {
|
||||
fmt.Println("warning: open touch:", e)
|
||||
}
|
||||
} else {
|
||||
fmt.Println("warning:", err)
|
||||
}
|
||||
|
||||
app := NewApp(fb, evfd, width, height)
|
||||
if os.Getenv("KINDLE_UI_TOUCH_DEBUG") != "" {
|
||||
app.EnableTouchDebug(true)
|
||||
}
|
||||
if haveAxis {
|
||||
app.SetTouchAxisRange(touchAxisX, touchAxisY)
|
||||
} else {
|
||||
fmt.Println("Using default touch axis range")
|
||||
app.SetTouchAxisRange(
|
||||
AxisRange{Min: 0, Max: 4095},
|
||||
AxisRange{Min: 0, Max: 4095},
|
||||
)
|
||||
}
|
||||
|
||||
var message = "Welcome"
|
||||
|
||||
styles := map[string]*ButtonStyle{
|
||||
"primary": {
|
||||
BorderRadius: 20,
|
||||
FillColor: bytePtr(0xE0),
|
||||
StrokeColor: bytePtr(0x30),
|
||||
StrokeThickness: 2,
|
||||
FontMultiplier: 2,
|
||||
},
|
||||
"secondary": {
|
||||
BorderRadius: 20,
|
||||
FillColor: bytePtr(0xF4),
|
||||
StrokeColor: bytePtr(0x40),
|
||||
StrokeThickness: 2,
|
||||
FontMultiplier: 2,
|
||||
},
|
||||
"ghost": {
|
||||
BorderRadius: 20,
|
||||
StrokeColor: bytePtr(0x30),
|
||||
StrokeThickness: 2,
|
||||
FontMultiplier: 2,
|
||||
},
|
||||
}
|
||||
|
||||
homePage := &Page{
|
||||
Name: "home",
|
||||
Background: 0xFF,
|
||||
OnDraw: func(a *App) {
|
||||
a.DrawText(40, 80, "test go ui", 3)
|
||||
a.DrawText(40, 140, message, 2)
|
||||
},
|
||||
Buttons: []*Button{
|
||||
{
|
||||
Rect: Rect{X: width/2 - 220, Y: 260, W: 440, H: 120},
|
||||
Label: "Say Hello",
|
||||
Style: styles["primary"],
|
||||
OnTap: func(a *App) {
|
||||
message = "Hello from your Kindle!"
|
||||
fmt.Println("Button pressed: Say Hello")
|
||||
a.RequestRender()
|
||||
},
|
||||
},
|
||||
{
|
||||
Rect: Rect{X: width/2 - 220, Y: 420, W: 440, H: 120},
|
||||
Label: "Go To Counter",
|
||||
Style: styles["primary"],
|
||||
OnTap: func(a *App) {
|
||||
a.Navigate("counter")
|
||||
},
|
||||
},
|
||||
{
|
||||
Rect: Rect{X: width/2 - 220, Y: 580, W: 440, H: 120},
|
||||
Label: "Log To Console",
|
||||
Style: styles["secondary"],
|
||||
OnTap: func(a *App) {
|
||||
fmt.Println("console button tapped at", time.Now().Format(time.RFC3339))
|
||||
message = "Logged a message to the console."
|
||||
a.RequestRender()
|
||||
},
|
||||
},
|
||||
{
|
||||
Rect: Rect{X: width/2 - 220, Y: 740, W: 440, H: 120},
|
||||
Label: "Higher brightness",
|
||||
Style: styles["ghost"],
|
||||
OnTap: func(a *App) {
|
||||
fmt.Println("brightness change")
|
||||
if a.dispCtrl.brightness.currentBrightness > 0 {
|
||||
a.setBrightness(0)
|
||||
} else {
|
||||
a.setBrightness(256)
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
counter := 0
|
||||
|
||||
counterPage := &Page{
|
||||
Name: "counter",
|
||||
Background: 0xFF,
|
||||
OnDraw: func(a *App) {
|
||||
a.DrawText(40, 80, "basically react start example", 3)
|
||||
a.DrawText(40, 140, fmt.Sprintf("Current value: %d", counter), 2)
|
||||
},
|
||||
Buttons: []*Button{
|
||||
{
|
||||
Rect: Rect{X: width/2 - 220, Y: 260, W: 440, H: 120},
|
||||
Label: "Increment",
|
||||
Style: styles["primary"],
|
||||
OnTapCount: func(a *App, count int) {
|
||||
counter += count
|
||||
a.RequestRender()
|
||||
},
|
||||
},
|
||||
{
|
||||
Rect: Rect{X: width/2 - 220, Y: 420, W: 440, H: 120},
|
||||
Label: "Reset",
|
||||
Style: styles["secondary"],
|
||||
OnTap: func(a *App) {
|
||||
counter = 0
|
||||
a.RequestRender()
|
||||
},
|
||||
},
|
||||
{
|
||||
Rect: Rect{X: width/2 - 220, Y: 580, W: 440, H: 120},
|
||||
Label: "Back",
|
||||
Style: styles["ghost"],
|
||||
OnTap: func(a *App) {
|
||||
a.Navigate("home")
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app.AddPage(homePage)
|
||||
app.AddPage(counterPage)
|
||||
app.Navigate("home")
|
||||
|
||||
app.Run()
|
||||
}
|
||||
279
src/touch.go
279
src/touch.go
@@ -1,279 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
// Event type categories (high-level groupings)
|
||||
EV_SYN = 0x00 // Synchronization: marks end of a full event packet (a "frame" of updates)
|
||||
EV_KEY = 0x01 // Key/button events: pressed/released toggles (also used for touch "down/up")
|
||||
EV_ABS = 0x03 // Absolute axis events: reports positional or pressure data, not deltas
|
||||
|
||||
// EV_SYN codes
|
||||
SYN_REPORT = 0 // Sent after a batch of ABS/KEY updates; tells you "this frame is complete"
|
||||
|
||||
// EV_KEY codes (pretend the touchscreen is a keyboard with one key)
|
||||
BTN_TOUCH = 0x14a // Touch contact active (1 = finger down, 0 = finger up)
|
||||
BTN_TOOL_FINGER = 0x145 // "A finger tool is in range" - capacitive proximity (optional)
|
||||
|
||||
// EV_ABS axis codes: continuous values like coordinates and pressure
|
||||
ABS_X = 0x00 // Generic absolute X position (used on single-touch panels)
|
||||
ABS_Y = 0x01 // Generic absolute Y position
|
||||
ABS_MT_POSITION_X = 0x35 // Multi-touch X position for a specific contact slot
|
||||
ABS_MT_POSITION_Y = 0x36 // Multi-touch Y position
|
||||
ABS_PRESSURE = 0x18 // Pressure value (force of touch, if hardware supports it)
|
||||
ABS_MT_TRACKING_ID = 0x39 // Unique ID for a finger contact (changes when finger lifts/reappears)
|
||||
ABS_MT_SLOT = 0x2f // Selects which multi-touch "slot" subsequent MT events apply to
|
||||
)
|
||||
|
||||
type inputEvent struct {
|
||||
Sec int32
|
||||
USec int32
|
||||
Type uint16
|
||||
Code uint16
|
||||
Value int32
|
||||
}
|
||||
|
||||
type AxisRange struct {
|
||||
Min int
|
||||
Max int
|
||||
}
|
||||
|
||||
func findTouchEventNode() (string, error) {
|
||||
candidates, _ := filepath.Glob("/dev/input/event*")
|
||||
for _, ev := range candidates {
|
||||
namePath := fmt.Sprintf("/sys/class/input/%s/device/name", filepath.Base(ev))
|
||||
if b, err := os.ReadFile(namePath); err == nil {
|
||||
name := strings.ToLower(strings.TrimSpace(string(b)))
|
||||
if strings.Contains(name, "touch") || strings.Contains(name, "synaptics") ||
|
||||
strings.Contains(name, "atmel") || strings.Contains(name, "elan") ||
|
||||
strings.Contains(name, "ft") || strings.Contains(name, "mxt") {
|
||||
return ev, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, ev := range []string{"/dev/input/event1", "/dev/input/event0"} {
|
||||
if _, err := os.Stat(ev); err == nil {
|
||||
return ev, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("no input event node found")
|
||||
}
|
||||
|
||||
func openNonblock(path string) (*os.File, error) {
|
||||
return os.OpenFile(path, os.O_RDONLY|syscall.O_NONBLOCK, 0)
|
||||
}
|
||||
|
||||
func ReadTouchAxisRanges(eventPath string) (AxisRange, AxisRange, error) {
|
||||
base := filepath.Base(eventPath)
|
||||
baseDir := filepath.Join("/sys/class/input", base)
|
||||
searchRoots := []string{
|
||||
filepath.Join(baseDir, "device"),
|
||||
filepath.Join(baseDir, "device", "device"),
|
||||
}
|
||||
var (
|
||||
data []byte
|
||||
err error
|
||||
tried []string
|
||||
addPath = func(p string) {
|
||||
if p == "" {
|
||||
return
|
||||
}
|
||||
tried = append(tried, p)
|
||||
}
|
||||
)
|
||||
for _, root := range searchRoots {
|
||||
direct := filepath.Join(root, "abs")
|
||||
addPath(direct)
|
||||
patterns := []string{
|
||||
filepath.Join(root, "*", "abs"),
|
||||
filepath.Join(root, "*", "*", "abs"),
|
||||
}
|
||||
for _, pattern := range patterns {
|
||||
if matches, e := filepath.Glob(pattern); e == nil {
|
||||
for _, m := range matches {
|
||||
addPath(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(tried) == 0 {
|
||||
tried = append(tried,
|
||||
filepath.Join(baseDir, "device", "abs"),
|
||||
filepath.Join(baseDir, "device", "device", "abs"),
|
||||
)
|
||||
}
|
||||
for _, p := range tried {
|
||||
if b, e := os.ReadFile(p); e == nil {
|
||||
data = b
|
||||
err = nil
|
||||
break
|
||||
} else {
|
||||
err = e
|
||||
}
|
||||
}
|
||||
if data == nil {
|
||||
return AxisRange{}, AxisRange{}, fmt.Errorf("read axis ranges: %w", err)
|
||||
}
|
||||
ranges := parseAxisRanges(data)
|
||||
var xr, yr AxisRange
|
||||
var ok bool
|
||||
if xr, ok = ranges[ABS_MT_POSITION_X]; !ok {
|
||||
xr = ranges[ABS_X]
|
||||
}
|
||||
if yr, ok = ranges[ABS_MT_POSITION_Y]; !ok {
|
||||
yr = ranges[ABS_Y]
|
||||
}
|
||||
if xr.Max <= xr.Min {
|
||||
xr = AxisRange{Min: 0, Max: touchMaxX - 1}
|
||||
}
|
||||
if yr.Max <= yr.Min {
|
||||
yr = AxisRange{Min: 0, Max: touchMaxY - 1}
|
||||
}
|
||||
return xr, yr, nil
|
||||
}
|
||||
|
||||
func parseAxisRanges(data []byte) map[uint16]AxisRange {
|
||||
result := make(map[uint16]AxisRange)
|
||||
scanner := bufio.NewScanner(bytes.NewReader(data))
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
line = strings.ReplaceAll(line, ":", "")
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 3 {
|
||||
continue
|
||||
}
|
||||
codeVal, err := strconv.ParseInt(fields[0], 0, 32)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
minVal, err := strconv.ParseInt(fields[1], 0, 32)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
maxVal, err := strconv.ParseInt(fields[2], 0, 32)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
result[uint16(codeVal)] = AxisRange{Min: int(minVal), Max: int(maxVal)}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
var touchDebugEnabled bool
|
||||
|
||||
func SetTouchDebugEnabled(enabled bool) {
|
||||
touchDebugEnabled = enabled
|
||||
}
|
||||
|
||||
// PollTouch drains up to maxReads events. Returns last known x,y, whether a touch is down, and whether we have valid coordinates.
|
||||
func PollTouch(evfd *os.File, maxReads int) (x, y int, pressed, haveXY bool) {
|
||||
x = lastTouchX
|
||||
y = lastTouchY
|
||||
pressed = lastTouchPressed
|
||||
haveXY = lastTouchHave
|
||||
|
||||
currentX := x
|
||||
currentY := y
|
||||
currentPressed := pressed
|
||||
currentHave := haveXY
|
||||
trackingActive := pressed
|
||||
|
||||
updated := false
|
||||
debug := touchDebugEnabled
|
||||
currentSlot := -1
|
||||
for i := 0; i < maxReads; i++ {
|
||||
var ev inputEvent
|
||||
if err := binary.Read(evfd, binary.LittleEndian, &ev); err != nil {
|
||||
if errors.Is(err, syscall.EAGAIN) || errors.Is(err, syscall.EWOULDBLOCK) {
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
if debug {
|
||||
fmt.Printf("event type=0x%X code=0x%X value=%d\n", ev.Type, ev.Code, ev.Value)
|
||||
}
|
||||
switch ev.Type {
|
||||
case EV_ABS:
|
||||
switch ev.Code {
|
||||
case ABS_X, ABS_MT_POSITION_X:
|
||||
if trackingActive {
|
||||
currentX = int(ev.Value)
|
||||
currentHave = true
|
||||
}
|
||||
case ABS_Y, ABS_MT_POSITION_Y:
|
||||
if trackingActive {
|
||||
currentY = int(ev.Value)
|
||||
currentHave = true
|
||||
}
|
||||
case ABS_MT_TRACKING_ID:
|
||||
if ev.Value >= 0 {
|
||||
currentPressed = true
|
||||
trackingActive = true
|
||||
} else {
|
||||
currentPressed = false
|
||||
trackingActive = false
|
||||
}
|
||||
case ABS_PRESSURE:
|
||||
if ev.Value > 0 {
|
||||
currentPressed = true
|
||||
} else {
|
||||
currentPressed = false
|
||||
}
|
||||
case ABS_MT_SLOT:
|
||||
currentSlot = int(ev.Value)
|
||||
if debug {
|
||||
fmt.Printf("event slot=%d\n", currentSlot)
|
||||
}
|
||||
}
|
||||
case EV_KEY:
|
||||
if ev.Code == BTN_TOUCH || ev.Code == BTN_TOOL_FINGER {
|
||||
currentPressed = ev.Value != 0
|
||||
}
|
||||
case EV_SYN:
|
||||
if ev.Code == SYN_REPORT {
|
||||
x = currentX
|
||||
y = currentY
|
||||
pressed = currentPressed
|
||||
haveXY = currentHave
|
||||
updated = true
|
||||
// Return after a single report so callers observe each touch frame.
|
||||
}
|
||||
}
|
||||
if updated {
|
||||
break
|
||||
}
|
||||
}
|
||||
if updated {
|
||||
lastTouchX = x
|
||||
lastTouchY = y
|
||||
lastTouchPressed = pressed
|
||||
lastTouchHave = haveXY
|
||||
} else {
|
||||
x = lastTouchX
|
||||
y = lastTouchY
|
||||
pressed = lastTouchPressed
|
||||
haveXY = lastTouchHave
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
lastTouchX int
|
||||
lastTouchY int
|
||||
lastTouchPressed bool
|
||||
lastTouchHave bool
|
||||
)
|
||||
Reference in New Issue
Block a user