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