package x import "image" import "golang.org/x/image/font" import "golang.org/x/image/font/basicfont" import "github.com/jezek/xgb" import "github.com/jezek/xgbutil" import "github.com/jezek/xgb/xproto" import "github.com/jezek/xgbutil/ewmh" import "github.com/jezek/xgbutil/xevent" import "github.com/jezek/xgbutil/xwindow" import "github.com/jezek/xgbutil/xgraphics" import "git.tebibyte.media/sashakoshka/stone" type Backend struct { application *stone.Application config *stone.Config connection *xgbutil.XUtil window *xwindow.Window canvas *xgraphics.Image channel chan(stone.Event) ping struct { before chan(struct { }) after chan(struct { }) quit chan(struct { }) } font struct { face font.Face } metrics struct { windowWidth int windowHeight int cellWidth int cellHeight int padding int paddingX int paddingY int descent int } } func (backend *Backend) Run (channel chan(stone.Event)) { backend.channel = channel for { select { case <- backend.ping.before: // 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) } } <- backend.ping.after case <- backend.ping.quit: backend.shutDown() return } } } func (backend *Backend) SetTitle (title string) (err error) { err = ewmh.WmNameSet(backend.connection, backend.window.Id, title) return } func (backend *Backend) SetIcon (icons []image.Image) (err error) { wmIcons := []ewmh.WmIcon { } 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 } 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 { // compress events configureEvent = backend.compressConfigureNotify(configureEvent) // resize and rebind canvas backend.canvas.Destroy() backend.canvas = xgraphics.New ( backend.connection, image.Rect ( 0, 0, backend.metrics.windowWidth, backend.metrics.windowHeight)) // 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') backend.bindCanvas() } } } 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 } 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 } func (backend *Backend) bindCanvas () { backend.canvas.XSurfaceSet(backend.window.Id) backend.canvas.XDraw() backend.canvas.XPaint(backend.window.Id) } func (backend *Backend) drawRune (x, y int, character rune) { } // factory instantiates an X backend. func factory (application *stone.Application) (output stone.Backend, err error) { backend := &Backend { application: application, config: application.Config(), } // load font // TODO: load this from a file backend.font.face = basicfont.Face7x13 // calculate metrics 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() backend.metrics.padding = backend.config.Padding() * backend.metrics.cellHeight backend.metrics.paddingX = backend.metrics.padding backend.metrics.paddingY = backend.metrics.padding backend.metrics.windowWidth, backend.metrics.windowHeight = backend.calculateWindowSize() // 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(), 0, 0, backend.metrics.windowWidth, backend.metrics.windowHeight, 0) backend.window.Map() backend.window.Listen(xproto.EventMaskStructureNotify) backend.SetTitle(application.Title()) backend.SetIcon(application.Icon()) // create a canvas backend.canvas = xgraphics.New ( backend.connection, image.Rect ( 0, 0, backend.metrics.windowWidth, backend.metrics.windowHeight)) backend.bindCanvas() // 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) }