2022-11-09 13:52:49 -07:00
|
|
|
package x
|
|
|
|
|
|
|
|
import "image"
|
2022-11-10 10:38:02 -07:00
|
|
|
import "golang.org/x/image/font"
|
|
|
|
import "golang.org/x/image/font/basicfont"
|
2022-11-09 13:52:49 -07:00
|
|
|
|
2022-11-09 20:33:18 -07:00
|
|
|
import "github.com/jezek/xgb"
|
2022-11-09 13:52:49 -07:00
|
|
|
import "github.com/jezek/xgbutil"
|
2022-11-09 20:33:18 -07:00
|
|
|
import "github.com/jezek/xgb/xproto"
|
2022-11-10 00:02:08 -07:00
|
|
|
import "github.com/jezek/xgbutil/ewmh"
|
2022-11-09 16:53:14 -07:00
|
|
|
import "github.com/jezek/xgbutil/xevent"
|
2022-11-09 13:52:49 -07:00
|
|
|
import "github.com/jezek/xgbutil/xwindow"
|
|
|
|
import "github.com/jezek/xgbutil/xgraphics"
|
|
|
|
|
2022-11-10 10:38:02 -07:00
|
|
|
|
2022-11-09 13:52:49 -07:00
|
|
|
import "git.tebibyte.media/sashakoshka/stone"
|
|
|
|
|
|
|
|
type Backend struct {
|
2022-11-09 16:53:14 -07:00
|
|
|
application *stone.Application
|
|
|
|
config *stone.Config
|
|
|
|
connection *xgbutil.XUtil
|
|
|
|
window *xwindow.Window
|
|
|
|
canvas *xgraphics.Image
|
|
|
|
channel chan(stone.Event)
|
2022-11-09 13:52:49 -07:00
|
|
|
|
2022-11-09 16:53:14 -07:00
|
|
|
ping struct {
|
|
|
|
before chan(struct { })
|
|
|
|
after chan(struct { })
|
|
|
|
quit chan(struct { })
|
|
|
|
}
|
2022-11-10 10:38:02 -07:00
|
|
|
|
|
|
|
font struct {
|
|
|
|
face font.Face
|
|
|
|
}
|
2022-11-10 18:43:27 -07:00
|
|
|
|
|
|
|
colors [4]xgraphics.BGRA
|
2022-11-09 16:53:14 -07:00
|
|
|
|
|
|
|
metrics struct {
|
2022-11-09 20:33:18 -07:00
|
|
|
windowWidth int
|
|
|
|
windowHeight int
|
|
|
|
cellWidth int
|
|
|
|
cellHeight int
|
|
|
|
padding int
|
|
|
|
paddingX int
|
|
|
|
paddingY int
|
|
|
|
descent int
|
2022-11-09 16:53:14 -07:00
|
|
|
}
|
2022-11-09 13:52:49 -07:00
|
|
|
}
|
|
|
|
|
2022-11-09 16:53:14 -07:00
|
|
|
func (backend *Backend) Run (channel chan(stone.Event)) {
|
|
|
|
backend.channel = channel
|
2022-11-09 13:52:49 -07:00
|
|
|
|
2022-11-09 16:53:14 -07:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <- backend.ping.before:
|
2022-11-09 20:33:18 -07:00
|
|
|
// if the queue is empty, don't dequeue anything because
|
|
|
|
// it would cause a fucking segfault lmao (???)
|
|
|
|
if !xevent.Empty(backend.connection) {
|
|
|
|
event, err := xevent.Dequeue(backend.connection)
|
|
|
|
if err != nil {
|
|
|
|
// TODO: do something with err
|
|
|
|
}
|
|
|
|
|
|
|
|
if event != nil {
|
|
|
|
backend.handleXEvent(event)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-09 16:53:14 -07:00
|
|
|
<- backend.ping.after
|
|
|
|
|
|
|
|
case <- backend.ping.quit:
|
|
|
|
backend.shutDown()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2022-11-09 13:52:49 -07:00
|
|
|
}
|
|
|
|
|
2022-11-10 00:02:08 -07:00
|
|
|
func (backend *Backend) SetTitle (title string) (err error) {
|
|
|
|
err = ewmh.WmNameSet(backend.connection, backend.window.Id, title)
|
|
|
|
return
|
2022-11-09 13:52:49 -07:00
|
|
|
}
|
|
|
|
|
2022-11-10 00:02:08 -07:00
|
|
|
func (backend *Backend) SetIcon (icons []image.Image) (err error) {
|
|
|
|
wmIcons := []ewmh.WmIcon { }
|
2022-11-09 13:52:49 -07:00
|
|
|
|
2022-11-10 00:02:08 -07:00
|
|
|
for _, icon := range icons {
|
|
|
|
width := icon.Bounds().Max.X
|
|
|
|
height := icon.Bounds().Max.Y
|
|
|
|
wmIcon := ewmh.WmIcon {
|
|
|
|
Width: uint(width),
|
|
|
|
Height: uint(height),
|
|
|
|
Data: make ([]uint, width * height),
|
|
|
|
}
|
|
|
|
|
|
|
|
// manually convert image data beacuse of course we have to do
|
|
|
|
// this
|
|
|
|
index := 0
|
|
|
|
for y := 0; y < height; y ++ {
|
|
|
|
for x := 0; x < width; x ++ {
|
|
|
|
r, g, b, a := icon.At(x, y).RGBA()
|
|
|
|
r >>= 8
|
|
|
|
g >>= 8
|
|
|
|
b >>= 8
|
|
|
|
a >>= 8
|
|
|
|
wmIcon.Data[index] =
|
|
|
|
(uint(a) << 24) |
|
|
|
|
(uint(r) << 16) |
|
|
|
|
(uint(g) << 8) |
|
|
|
|
(uint(b) << 0)
|
|
|
|
index ++
|
|
|
|
}}
|
|
|
|
|
|
|
|
wmIcons = append(wmIcons, wmIcon)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = ewmh.WmIconSet(backend.connection, backend.window.Id, wmIcons)
|
|
|
|
return
|
2022-11-09 13:52:49 -07:00
|
|
|
}
|
2022-11-09 16:53:14 -07:00
|
|
|
|
2022-11-09 20:33:18 -07:00
|
|
|
func (backend *Backend) handleXEvent (event xgb.Event) {
|
|
|
|
switch event.(type) {
|
|
|
|
case xproto.ConfigureNotifyEvent:
|
|
|
|
configureEvent := event.(xproto.ConfigureNotifyEvent)
|
|
|
|
|
|
|
|
newWidth := int(configureEvent.Width)
|
|
|
|
newHeight := int(configureEvent.Height)
|
|
|
|
sizeChanged :=
|
|
|
|
backend.metrics.windowWidth != newWidth ||
|
|
|
|
backend.metrics.windowHeight != newHeight
|
|
|
|
backend.metrics.windowWidth = newWidth
|
|
|
|
backend.metrics.windowHeight = newHeight
|
|
|
|
|
|
|
|
if sizeChanged {
|
2022-11-09 23:00:47 -07:00
|
|
|
// compress events
|
|
|
|
configureEvent =
|
|
|
|
backend.compressConfigureNotify(configureEvent)
|
|
|
|
|
|
|
|
// resize and rebind canvas
|
2022-11-10 18:43:27 -07:00
|
|
|
backend.reallocateCanvas()
|
2022-11-09 20:33:18 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-09 23:00:47 -07:00
|
|
|
func (backend *Backend) compressConfigureNotify (
|
|
|
|
firstEvent xproto.ConfigureNotifyEvent,
|
|
|
|
) (
|
|
|
|
lastEvent xproto.ConfigureNotifyEvent,
|
|
|
|
) {
|
|
|
|
backend.connection.Sync()
|
|
|
|
xevent.Read(backend.connection, false)
|
|
|
|
lastEvent = firstEvent
|
|
|
|
|
|
|
|
for index, untypedEvent := range xevent.Peek(backend.connection) {
|
|
|
|
if untypedEvent.Err != nil { continue }
|
|
|
|
|
|
|
|
typedEvent, ok := untypedEvent.Event.(xproto.ConfigureNotifyEvent)
|
|
|
|
if !ok { continue }
|
|
|
|
|
|
|
|
lastEvent = typedEvent
|
|
|
|
defer func (index int) {
|
|
|
|
xevent.DequeueAt(backend.connection, index)
|
|
|
|
} (index)
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-11-09 16:53:14 -07:00
|
|
|
func (backend *Backend) shutDown () {
|
|
|
|
backend.channel <- stone.EventQuit { }
|
|
|
|
}
|
|
|
|
|
|
|
|
// calculateWindowSize calculates window bounds based on the internal buffer
|
|
|
|
// size.
|
|
|
|
func (backend *Backend) calculateWindowSize () (x, y int) {
|
|
|
|
width, height := backend.application.Size()
|
|
|
|
x =
|
|
|
|
width * backend.metrics.cellWidth +
|
|
|
|
backend.metrics.padding * 2
|
|
|
|
y =
|
|
|
|
height * backend.metrics.cellHeight +
|
|
|
|
backend.metrics.padding * 2
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-11-10 18:43:27 -07:00
|
|
|
func (backend *Backend) reallocateCanvas () {
|
|
|
|
if backend.canvas != nil {
|
|
|
|
backend.canvas.Destroy()
|
|
|
|
}
|
|
|
|
backend.canvas = xgraphics.New (
|
|
|
|
backend.connection,
|
|
|
|
image.Rect (
|
|
|
|
0, 0,
|
|
|
|
backend.metrics.windowWidth,
|
|
|
|
backend.metrics.windowHeight))
|
|
|
|
backend.canvas.For (func (x, y int) xgraphics.BGRA {
|
|
|
|
return backend.colors[stone.ColorApplication]
|
|
|
|
})
|
|
|
|
// FIXME (?): this doesn't work. if it were to work, it
|
|
|
|
// would possibly be a cleaner way to resize the canvas.
|
|
|
|
// backend.canvas.Scale (
|
|
|
|
// backend.metrics.windowWidth,
|
|
|
|
// backend.metrics.windowHeight)
|
|
|
|
backend.drawRune(8, 16, 'X')
|
2022-11-09 17:07:40 -07:00
|
|
|
backend.canvas.XSurfaceSet(backend.window.Id)
|
|
|
|
backend.canvas.XDraw()
|
|
|
|
backend.canvas.XPaint(backend.window.Id)
|
|
|
|
}
|
2022-11-09 16:53:14 -07:00
|
|
|
|
2022-11-10 10:38:02 -07:00
|
|
|
func (backend *Backend) drawRune (x, y int, character rune) {
|
2022-11-10 18:43:27 -07:00
|
|
|
// bounds, image, point, _, _ :=
|
2022-11-10 10:38:02 -07:00
|
|
|
}
|
|
|
|
|
2022-11-09 16:53:14 -07:00
|
|
|
// factory instantiates an X backend.
|
|
|
|
func factory (application *stone.Application) (output stone.Backend, err error) {
|
|
|
|
backend := &Backend {
|
|
|
|
application: application,
|
|
|
|
config: application.Config(),
|
|
|
|
}
|
|
|
|
|
2022-11-10 10:38:02 -07:00
|
|
|
// load font
|
|
|
|
// TODO: load this from a file
|
|
|
|
backend.font.face = basicfont.Face7x13
|
|
|
|
|
2022-11-10 18:43:27 -07:00
|
|
|
// pre-calculate colors
|
|
|
|
for index := 0; index < len(backend.colors); index ++ {
|
|
|
|
color := backend.config.Color(stone.Color(index))
|
|
|
|
r, g, b, a := color.RGBA()
|
|
|
|
r >>= 8
|
|
|
|
g >>= 8
|
|
|
|
b >>= 8
|
|
|
|
a >>= 8
|
|
|
|
backend.colors[index] = xgraphics.BGRA {
|
|
|
|
R: uint8(r),
|
|
|
|
G: uint8(g),
|
|
|
|
B: uint8(b),
|
|
|
|
A: uint8(a),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-09 16:53:14 -07:00
|
|
|
// calculate metrics
|
2022-11-10 10:38:02 -07:00
|
|
|
metrics := backend.font.face.Metrics()
|
|
|
|
glyphAdvance, _ := backend.font.face.GlyphAdvance('M')
|
|
|
|
backend.metrics.cellWidth = glyphAdvance.Round()
|
|
|
|
backend.metrics.cellHeight = metrics.Height.Round()
|
|
|
|
backend.metrics.descent = metrics.Descent.Round()
|
2022-11-09 16:53:14 -07:00
|
|
|
backend.metrics.padding =
|
|
|
|
backend.config.Padding() *
|
|
|
|
backend.metrics.cellHeight
|
|
|
|
backend.metrics.paddingX = backend.metrics.padding
|
|
|
|
backend.metrics.paddingY = backend.metrics.padding
|
2022-11-09 20:33:18 -07:00
|
|
|
backend.metrics.windowWidth,
|
|
|
|
backend.metrics.windowHeight = backend.calculateWindowSize()
|
2022-11-09 16:53:14 -07:00
|
|
|
|
|
|
|
// connect to X
|
|
|
|
backend.connection, err = xgbutil.NewConn()
|
|
|
|
if err != nil { return }
|
|
|
|
backend.window, err = xwindow.Generate(backend.connection)
|
|
|
|
if err != nil { return }
|
|
|
|
|
|
|
|
// create the window
|
|
|
|
backend.window.Create (
|
|
|
|
backend.connection.RootWin(),
|
2022-11-09 20:33:18 -07:00
|
|
|
0, 0,
|
|
|
|
backend.metrics.windowWidth, backend.metrics.windowHeight,
|
2022-11-09 16:53:14 -07:00
|
|
|
0)
|
|
|
|
backend.window.Map()
|
2022-11-09 20:33:18 -07:00
|
|
|
backend.window.Listen(xproto.EventMaskStructureNotify)
|
2022-11-10 00:02:08 -07:00
|
|
|
backend.SetTitle(application.Title())
|
|
|
|
backend.SetIcon(application.Icon())
|
2022-11-09 16:53:14 -07:00
|
|
|
|
2022-11-09 17:07:40 -07:00
|
|
|
// create a canvas
|
2022-11-10 18:43:27 -07:00
|
|
|
backend.reallocateCanvas()
|
2022-11-09 17:07:40 -07:00
|
|
|
|
2022-11-09 16:53:14 -07:00
|
|
|
// attatch graceful close handler
|
|
|
|
backend.window.WMGracefulClose (func (window *xwindow.Window) {
|
|
|
|
backend.window.Destroy()
|
|
|
|
backend.shutDown()
|
|
|
|
})
|
|
|
|
|
|
|
|
// start event loop
|
|
|
|
backend.ping.before,
|
|
|
|
backend.ping.after,
|
|
|
|
backend.ping.quit = xevent.MainPing(backend.connection)
|
|
|
|
|
|
|
|
output = backend
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// init registers this backend when the program starts.
|
|
|
|
func init () {
|
|
|
|
stone.RegisterBackend(factory)
|
|
|
|
}
|