feat: Initial commit

This commit is contained in:
ptrcnull 2022-01-02 05:28:31 +01:00
commit 9698a95f16
6 changed files with 375 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
watchface

23
draw.go Normal file
View file

@ -0,0 +1,23 @@
package main
import (
"image"
"image/color"
"image/draw"
)
func Fill(img draw.Image, rect image.Rectangle, c color.Color) {
for x := rect.Min.X; x < rect.Max.X; x++ {
for y := rect.Min.Y; y < rect.Max.Y; y++ {
img.Set(x, y, c)
}
}
}
func Copy(dst, src draw.Image, rect image.Rectangle) {
for x := rect.Min.X; x < rect.Max.X; x++ {
for y := rect.Min.Y; y < rect.Max.Y; y++ {
dst.Set(x, y, src.At(x, y))
}
}
}

201
framebuffer/fb.go Normal file
View file

@ -0,0 +1,201 @@
package framebuffer
import (
"golang.org/x/sys/unix"
"image"
"image/color"
"image/draw"
"os"
"syscall"
"unsafe"
)
const (
FBIOGET_VSCREENINFO = 0x4600
FBIOGET_FSCREENINFO = 0x4602
FBIOBLANK = 0x4611
)
var _ draw.Image = (*SimpleRGBA)(nil)
// Open expects a framebuffer device as its argument (such as "/dev/fb0"). The
// device will be memory-mapped to a local buffer. Writing to the device changes
// the screen output.
// The returned Device implements the draw.Image interface. This means that you
// can use it to copy to and from other images.
// After you are done using the Device, call Close on it to unmap the memory and
// close the framebuffer file.
func Open(device string) (*Device, error) {
file, err := os.OpenFile(device, os.O_RDWR, os.ModeDevice)
if err != nil {
return nil, err
}
_ = unix.IoctlSetInt(int(file.Fd()), FBIOBLANK, 0)
_ = unix.IoctlSetInt(int(file.Fd()), FBIOBLANK, 4)
_ = unix.IoctlSetInt(int(file.Fd()), FBIOBLANK, 0)
fixInfo, _ := getFixScreenInfo(file.Fd())
varInfo, _ := getVarScreenInfo(file.Fd())
pixels, err := syscall.Mmap(
int(file.Fd()),
0,
int(fixInfo.smemLen),
//int(varInfo.Xres*varInfo.Yres*varInfo.bitsPerPixel/8),
syscall.PROT_READ|syscall.PROT_WRITE,
syscall.MAP_SHARED,
)
if err != nil {
file.Close()
return nil, err
}
return &Device{
file: file,
SimpleRGBA: &SimpleRGBA{
Pixels: pixels,
Stride: int(fixInfo.lineLength),
Xres: int(varInfo.xres),
Yres: int(varInfo.yres),
},
}, nil
}
// Device represents the frame buffer. It implements the draw.Image interface.
type Device struct {
*SimpleRGBA
file *os.File
}
type SimpleRGBA struct {
Pixels []byte
Stride int
Xres int
Yres int
}
func (s *SimpleRGBA) ColorModel() color.Model {
return color.RGBAModel
}
func (s *SimpleRGBA) Bounds() image.Rectangle {
return image.Rect(0, 0, s.Xres, s.Yres)
}
func (s *SimpleRGBA) At(x, y int) color.Color {
if x < 0 || x > s.Xres || y < 0 || y > s.Yres {
return color.RGBA{}
}
i := y*s.Stride + x*4
n := s.Pixels[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
return color.RGBA{R: n[0], G: n[1], B: n[2], A: n[3]}
}
func (s *SimpleRGBA) Black(rect image.Rectangle) {
start := rect.Min.Y*s.Stride + rect.Min.X*4
end := rect.Max.Y*s.Stride + rect.Max.X*4 + 4
for i := start; i < end; i++ {
s.Pixels[i] = 0
}
}
func (s *SimpleRGBA) Set(x, y int, c color.Color) {
r, g, b, a := c.RGBA()
i := y*s.Stride + x*4
n := s.Pixels[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
n[0] = byte(r)
n[1] = byte(g)
n[2] = byte(b)
n[3] = byte(a)
}
// Close unmaps the framebuffer memory and closes the device file. Call this
// function when you are done using the frame buffer.
func (d *Device) Close() {
syscall.Munmap(d.Pixels)
d.file.Close()
}
func ioctlPtr(fd uintptr, req uint, arg unsafe.Pointer) error {
_, _, err := unix.Syscall(unix.SYS_IOCTL, fd, uintptr(req), uintptr(arg))
if err != 0 {
return err
}
return nil
}
func getFixScreenInfo(fd uintptr) (*fixScreenInfo, error) {
var value fixScreenInfo
err := ioctlPtr(fd, FBIOGET_FSCREENINFO, unsafe.Pointer(&value))
return &value, err
}
func getVarScreenInfo(fd uintptr) (*varScreenInfo, error) {
var value varScreenInfo
err := ioctlPtr(fd, FBIOGET_VSCREENINFO, unsafe.Pointer(&value))
return &value, err
}
type fixScreenInfo struct {
id [16]byte
smemStart uint32
smemLen uint32
fbType uint32
typeAux uint32
visual uint32
xPanStep uint16
yPanStep uint16
yWrapStep uint16
lineLength uint32
mmioStart uint32
mmioLen uint32
accel uint32
capabilities uint16
reserved [2]uint16
}
type bitField struct {
offset uint32
length uint32
msbRight uint32
}
type varScreenInfo struct {
xres uint32
yres uint32
xresVirtual uint32
yresVirtual uint32
xoffset uint32
yoffset uint32
bitsPerPixel uint32
grayscale uint32
red bitField
green bitField
blue bitField
transp bitField
nonstd uint32
activate uint32
height uint32
width uint32
accelFlags uint32
pixclock uint32
leftMargin uint32
rightMargin uint32
upperMargin uint32
lowerMargin uint32
hsyncLen uint32
vsyncLen uint32
sync uint32
vmode uint32
rotate uint32
colorspace uint32
reserved [4]uint32
}

10
go.mod Normal file
View file

@ -0,0 +1,10 @@
module git.ddd.rip/ptrcnull/watchface
go 1.17
require (
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/gonutz/framebuffer v1.0.0
golang.org/x/image v0.0.0-20211028202545-6944b10bf410
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e
)

10
go.sum Normal file
View file

@ -0,0 +1,10 @@
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/gonutz/framebuffer v1.0.0 h1:wWFTPqT2+AQ2DllFTOhLWKaxGxUmXmMsMh2wWXgX0LQ=
github.com/gonutz/framebuffer v1.0.0/go.mod h1:wbfYEFSpBxkC4CWzipKZDlKisTkAWors57aJ99aqqhQ=
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ=
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

130
main.go Normal file
View file

@ -0,0 +1,130 @@
package main
import (
"image"
"image/color"
"io/ioutil"
"strings"
"time"
"golang.org/x/image/draw"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
"git.ddd.rip/ptrcnull/watchface/framebuffer"
)
var Gray = color.RGBA{R: 60, G: 60, B: 60, A: 60}
var LightGray = color.RGBA{R: 150, G: 150, B: 150, A: 150}
var White = color.RGBA{R: 255, G: 255, B: 255, A: 255}
func addLabel(img draw.Image, face font.Face, rect image.Rectangle, label string) {
point := fixed.Point26_6{X: fixed.Int26_6(rect.Min.X * 64), Y: fixed.Int26_6(rect.Max.Y * 64)}
d := &font.Drawer{
Dst: img,
Src: image.NewUniform(LightGray),
Face: face,
Dot: point,
}
d.DrawString(label)
}
var SecondClock = image.Rect(62, 162, 298, 198)
var MinuteClock = image.Rect(108, 162, 253, 198)
var Battery = image.Rect(90, 240, 150, 240+32)
var notoSans *truetype.Font
func sized(size float64) font.Face {
return truetype.NewFace(notoSans, &truetype.Options{
Size: size,
Hinting: font.HintingFull,
DPI: 0,
})
}
func main() {
fontData, _ := ioutil.ReadFile("/usr/share/fonts/noto/NotoSansMono-Regular.ttf")
notoSans, _ = freetype.ParseFont(fontData)
fb, _ := framebuffer.Open("/dev/fb0")
Fill(fb, fb.Bounds(), color.RGBA{})
face := Face{
//tmp: &framebuffer.SimpleRGBA{
// Pixels: make([]uint8, fb.Xres*fb.Yres*4),
// Stride: fb.Yres * 4,
// Xres: fb.Xres,
// Yres: fb.Yres,
//},
tmp: image.NewRGBA(fb.Bounds()),
fb: fb.SimpleRGBA,
}
simple := true
go face.MinuteClock(time.Now())
go face.Battery()
ticker := time.NewTicker(time.Second)
for {
t := time.Now()
if simple {
if t.Second() == 0 {
go face.MinuteClock(t)
go face.Battery()
}
} else {
go face.SecondClock(t)
if t.Second() == 0 {
go face.Battery()
}
}
<-ticker.C
}
}
type Face struct {
tmp draw.Image
fb draw.Image
}
func (f *Face) Battery() {
Fill(f.tmp, Battery, color.RGBA{})
addLabel(f.tmp, sized(32), Battery, getBattery())
Copy(f.fb, f.tmp, Battery)
}
func (f *Face) SecondClock(t time.Time) {
Fill(f.tmp, SecondClock, color.RGBA{})
addLabel(f.tmp, sized(48), SecondClock, t.Format("15:04:05"))
Copy(f.fb, f.tmp, SecondClock)
}
func (f *Face) MinuteClock(t time.Time) {
Fill(f.tmp, MinuteClock, color.RGBA{})
addLabel(f.tmp, sized(48), MinuteClock, t.Format("15:04"))
Copy(f.fb, f.tmp, MinuteClock)
}
//func Loop(duration time.Duration, handler func()) {
// for {
// go handler()
// time.Sleep(duration)
// }
//}
func getBattery() string {
res, _ := ioutil.ReadFile("/sys/class/power_supply/battery/capacity")
value := strings.Trim(string(res), "\n")
if len(value) == 1 {
value = "0" + value
}
if value == "100" {
return "uwu"
}
return value + "%"
}