feat: Initial commit
This commit is contained in:
commit
9698a95f16
6 changed files with 375 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
watchface
|
23
draw.go
Normal file
23
draw.go
Normal 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
201
framebuffer/fb.go
Normal 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
10
go.mod
Normal 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
10
go.sum
Normal 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
130
main.go
Normal 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 + "%"
|
||||||
|
}
|
Loading…
Reference in a new issue