package x import "os" import "golang.org/x/image/font" import "golang.org/x/image/font/opentype" import "golang.org/x/image/font/basicfont" import "github.com/jezek/xgbutil" import "github.com/jezek/xgb/xproto" import "github.com/jezek/xgbutil/icccm" import "github.com/jezek/xgbutil/xevent" import "github.com/jezek/xgbutil/xwindow" import "github.com/jezek/xgbutil/keybind" import "github.com/jezek/xgbutil/xgraphics" import "git.tebibyte.media/sashakoshka/stone" import "github.com/flopp/go-findfont" // factory instantiates an X backend. func factory ( application *stone.Application, callbackManager *stone.CallbackManager, ) ( output stone.Backend, err error, ) { backend := &Backend { application: application, config: application.Config(), callbackManager: callbackManager, } // load font backend.font.face = findAndLoadFont ( backend.config.FontName(), float64(backend.config.FontSize())) if backend.font.face == nil { backend.font.face = basicfont.Face7x13 } // 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), } } // 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 } // get keyboard mapping information keybind.Initialize(backend.connection) backend.modifierMasks.capsLock = backend.keysymToMask(0xFFE5) backend.modifierMasks.shiftLock = backend.keysymToMask(0xFFE6) backend.modifierMasks.numLock = backend.keysymToMask(0xFF7F) backend.modifierMasks.modeSwitch = backend.keysymToMask(0xFF7E) backend.modifierMasks.hyper = backend.keysymToMask(0xffed) backend.modifierMasks.super = backend.keysymToMask(0xffeb) backend.modifierMasks.meta = backend.keysymToMask(0xffe7) backend.modifierMasks.alt = backend.keysymToMask(0xffe9) // create the window backend.window.Create ( backend.connection.RootWin(), 0, 0, backend.metrics.windowWidth, backend.metrics.windowHeight, 0) backend.window.Map() err = backend.window.Listen ( xproto.EventMaskStructureNotify, xproto.EventMaskPointerMotion, xproto.EventMaskKeyPress, xproto.EventMaskKeyRelease, xproto.EventMaskButtonPress, xproto.EventMaskButtonRelease) backend.SetTitle(application.Title()) backend.SetIcon(application.Icon()) if err != nil { return } // set minimum dimensions minWidth := backend.metrics.cellWidth + backend.metrics.padding * 2 minHeight := backend.metrics.cellHeight + backend.metrics.padding * 2 err = icccm.WmNormalHintsSet ( backend.connection, backend.window.Id, &icccm.NormalHints { Flags: icccm.SizeHintPMinSize, MinWidth: uint(minWidth), MinHeight: uint(minHeight), }) if err != nil { return } // create a canvas backend.reallocateCanvas() // attatch graceful close handler backend.window.WMGracefulClose (func (window *xwindow.Window) { backend.window.Destroy() xevent.Quit(backend.connection) }) // attatch event handlers xevent.ConfigureNotifyFun(backend.handleConfigureNotify). Connect(backend.connection, backend.window.Id) xevent.ButtonPressFun(backend.handleButtonPress). Connect(backend.connection, backend.window.Id) xevent.ButtonReleaseFun(backend.handleButtonRelease). Connect(backend.connection, backend.window.Id) xevent.MotionNotifyFun(backend.handleMotionNotify). Connect(backend.connection, backend.window.Id) xevent.KeyPressFun(backend.handleKeyPress). Connect(backend.connection, backend.window.Id) xevent.KeyReleaseFun(backend.handleKeyRelease). Connect(backend.connection, backend.window.Id) // uncomment these to draw debug bounds // backend.drawCellBounds = true // backend.drawBufferBounds = true output = backend return } func findAndLoadFont (name string, size float64) (face font.Face) { if name == "" { return } fontPath, err := findfont.Find(name) if err != nil { return } fontFile, err := os.Open(fontPath) if err != nil { return } fontObject, err := opentype.ParseReaderAt(fontFile) if err != nil { return } face, err = opentype.NewFace (fontObject, &opentype.FaceOptions { Size: size, DPI: 96, Hinting: font.HintingFull, }) if err != nil { face = nil } return } func (backend *Backend) keysymToKeycode ( symbol xproto.Keysym, ) ( code xproto.Keycode, ) { mapping := keybind.KeyMapGet(backend.connection) for index, testSymbol := range mapping.Keysyms { if testSymbol == symbol { code = xproto.Keycode ( index / int(mapping.KeysymsPerKeycode) + int(backend.connection.Setup().MinKeycode)) break } } return } func (backend *Backend) keysymToMask ( symbol xproto.Keysym, ) ( mask uint16, ) { mask = keybind.ModGet ( backend.connection, backend.keysymToKeycode(symbol)) return } // init registers this backend when the program starts. func init () { stone.RegisterBackend(factory) }