stone/backends/x/factory.go

217 lines
5.9 KiB
Go

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.normal = findAndLoadFont (
backend.config.FontNameNormal(),
float64(backend.config.FontSize()))
backend.font.bold = findAndLoadFont (
backend.config.FontNameBold(),
float64(backend.config.FontSize()))
backend.font.italic = findAndLoadFont (
backend.config.FontNameItalic(),
float64(backend.config.FontSize()))
backend.font.boldItalic = findAndLoadFont (
backend.config.FontNameBoldItalic(),
float64(backend.config.FontSize()))
if backend.font.normal == nil {
backend.font.normal = 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.normal.Metrics()
glyphAdvance, _ := backend.font.normal.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)
}