Compare commits
No commits in common. "3e42402e2bdeb665b555919cf57ad3258ef103e0" and "cc08b5457ca9545111141b0f7a27320d9bd3116d" have entirely different histories.
3e42402e2b
...
cc08b5457c
12
README
12
README
@ -40,7 +40,6 @@ figured out it would make sense:
|
|||||||
- hsm - system monitor
|
- hsm - system monitor
|
||||||
- hss - spreadsheets
|
- hss - spreadsheets
|
||||||
- htd - translation dictionary
|
- htd - translation dictionary
|
||||||
- ht - terminal emulator
|
|
||||||
- htk - GUI toolkit library
|
- htk - GUI toolkit library
|
||||||
|
|
||||||
See Projects for more information about the individual projects.
|
See Projects for more information about the individual projects.
|
||||||
@ -51,7 +50,7 @@ Identity
|
|||||||
--------
|
--------
|
||||||
The name merely hints at motivations and otherwise isn't of any practical
|
The name merely hints at motivations and otherwise isn't of any practical
|
||||||
significance other than that we need an identifier. This time I resisted using
|
significance other than that we need an identifier. This time I resisted using
|
||||||
something offensive for personal amusement.
|
something offensive.
|
||||||
|
|
||||||
A logo covering the entire project is not needed and it seems hard to figure out
|
A logo covering the entire project is not needed and it seems hard to figure out
|
||||||
anything meaninful anyway, though we might pick a specific font to use for the
|
anything meaninful anyway, though we might pick a specific font to use for the
|
||||||
@ -185,9 +184,6 @@ only the per-buffer input line is going to be desynchronized.
|
|||||||
|
|
||||||
https://godoc.org/github.com/gorilla/websocket
|
https://godoc.org/github.com/gorilla/websocket
|
||||||
|
|
||||||
The higher-level client-server API could be made rather generic to allow for
|
|
||||||
smooth integration with non-IRC "backends" such as Slack or Mattermost.
|
|
||||||
|
|
||||||
htd -- translation dictionary
|
htd -- translation dictionary
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
This specific kind of application doesn't need a lot of user interface either,
|
This specific kind of application doesn't need a lot of user interface either,
|
||||||
@ -225,8 +221,6 @@ In the second stage, support for the Language Server Protocol will be added so
|
|||||||
that the project can be edited using its own tools. Some scripting, perhaps
|
that the project can be edited using its own tools. Some scripting, perhaps
|
||||||
a tiny subset of VimL, might be desirable. Or other means of configuration.
|
a tiny subset of VimL, might be desirable. Or other means of configuration.
|
||||||
|
|
||||||
Visual block mode or the color column may still be implemented.
|
|
||||||
|
|
||||||
The real model for the editor is Qt Creator with FakeVIM, though this is not to
|
The real model for the editor is Qt Creator with FakeVIM, though this is not to
|
||||||
be a clone of it, e.g. the various "Output" lists could be just special buffers,
|
be a clone of it, e.g. the various "Output" lists could be just special buffers,
|
||||||
which may be have names starting on "// ".
|
which may be have names starting on "// ".
|
||||||
@ -255,10 +249,6 @@ Indexing and search may be based on a common database, no need to get all fancy:
|
|||||||
http://rachbelaid.com/postgres-full-text-search-is-good-enough/
|
http://rachbelaid.com/postgres-full-text-search-is-good-enough/
|
||||||
https://www.sqlite.org/fts3.html#full_text_index_queries (FTS4 seems better)
|
https://www.sqlite.org/fts3.html#full_text_index_queries (FTS4 seems better)
|
||||||
|
|
||||||
ht -- terminal emulator
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
Similar scope to st(1). Clever display of internal padding for better looks.
|
|
||||||
|
|
||||||
The rest
|
The rest
|
||||||
~~~~~~~~
|
~~~~~~~~
|
||||||
Currently there are no significant, specific plans about the other applications.
|
Currently there are no significant, specific plans about the other applications.
|
||||||
|
@ -129,7 +129,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if result.err != nil {
|
if result.err != nil {
|
||||||
log("stdin: %s", result.err)
|
log("%s: %s", "stdin", result.err)
|
||||||
fromUser = nil
|
fromUser = nil
|
||||||
if err := conn.CloseWrite(); err != nil {
|
if err := conn.CloseWrite(); err != nil {
|
||||||
log("remote: %s", err)
|
log("remote: %s", err)
|
||||||
|
@ -1,313 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"github.com/BurntSushi/xgb"
|
|
||||||
"github.com/BurntSushi/xgb/render"
|
|
||||||
"github.com/BurntSushi/xgb/shm"
|
|
||||||
"github.com/BurntSushi/xgb/xproto"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"time"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"image"
|
|
||||||
_ "image/gif"
|
|
||||||
_ "image/jpeg"
|
|
||||||
_ "image/png"
|
|
||||||
)
|
|
||||||
|
|
||||||
// #include <sys/ipc.h>
|
|
||||||
// #include <sys/shm.h>
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
func F64ToFixed(f float64) render.Fixed { return render.Fixed(f * 65536) }
|
|
||||||
func FixedToF64(f render.Fixed) float64 { return float64(f) / 65536 }
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
/*
|
|
||||||
pf, err := os.Create("pprof.out")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
pprof.StartCPUProfile(pf)
|
|
||||||
defer pprof.StopCPUProfile()
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Load a picture from the command line.
|
|
||||||
f, err := os.Open(os.Args[1])
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
img, name, err := image.Decode(f)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
log.Println("image type is", name)
|
|
||||||
|
|
||||||
// Miscellaneous X11 initialization.
|
|
||||||
X, err := xgb.NewConn()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := render.Init(X); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
setup := xproto.Setup(X)
|
|
||||||
screen := setup.DefaultScreen(X)
|
|
||||||
|
|
||||||
visual, depth := screen.RootVisual, screen.RootDepth
|
|
||||||
// TODO: We should check that we find it, though we don't /need/ alpha here,
|
|
||||||
// it's just a minor improvement--affects the backpixel value.
|
|
||||||
for _, i := range screen.AllowedDepths {
|
|
||||||
for _, v := range i.Visuals {
|
|
||||||
// TODO: Could/should check other parameters.
|
|
||||||
if i.Depth == 32 && v.Class == xproto.VisualClassTrueColor {
|
|
||||||
visual, depth = v.VisualId, i.Depth
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mid, err := xproto.NewColormapId(X)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = xproto.CreateColormap(
|
|
||||||
X, xproto.ColormapAllocNone, mid, screen.Root, visual)
|
|
||||||
|
|
||||||
wid, err := xproto.NewWindowId(X)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Border pixel and colormap are required when depth differs from parent.
|
|
||||||
_ = xproto.CreateWindow(X, depth, wid, screen.Root,
|
|
||||||
0, 0, 500, 500, 0, xproto.WindowClassInputOutput,
|
|
||||||
visual, xproto.CwBackPixel|xproto.CwBorderPixel|xproto.CwEventMask|
|
|
||||||
xproto.CwColormap, []uint32{0x80808080, 0,
|
|
||||||
xproto.EventMaskStructureNotify | xproto.EventMaskExposure,
|
|
||||||
uint32(mid)})
|
|
||||||
|
|
||||||
title := []byte("Image")
|
|
||||||
_ = xproto.ChangeProperty(X, xproto.PropModeReplace, wid, xproto.AtomWmName,
|
|
||||||
xproto.AtomString, 8, uint32(len(title)), title)
|
|
||||||
|
|
||||||
_ = xproto.MapWindow(X, wid)
|
|
||||||
|
|
||||||
pformats, err := render.QueryPictFormats(X).Reply()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Similar to XRenderFindVisualFormat.
|
|
||||||
// The DefaultScreen is almost certain to be zero.
|
|
||||||
var pformat render.Pictformat
|
|
||||||
for _, pd := range pformats.Screens[X.DefaultScreen].Depths {
|
|
||||||
// This check seems to be slightly extraneous.
|
|
||||||
if pd.Depth != depth {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, pv := range pd.Visuals {
|
|
||||||
if pv.Visual == visual {
|
|
||||||
pformat = pv.Format
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap the window's surface in a picture.
|
|
||||||
pid, err := render.NewPictureId(X)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
render.CreatePicture(X, pid, xproto.Drawable(wid), pformat, 0, []uint32{})
|
|
||||||
|
|
||||||
// setup.BitmapFormatScanline{Pad,Unit} and setup.BitmapFormatBitOrder
|
|
||||||
// don't interest us here since we're only using Z format pixmaps.
|
|
||||||
for _, pf := range setup.PixmapFormats {
|
|
||||||
if pf.Depth == 32 {
|
|
||||||
if pf.BitsPerPixel != 32 || pf.ScanlinePad != 32 {
|
|
||||||
log.Fatalln("unsuported X server")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pixid, err := xproto.NewPixmapId(X)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
_ = xproto.CreatePixmap(X, 32, pixid, xproto.Drawable(screen.Root),
|
|
||||||
uint16(img.Bounds().Dx()), uint16(img.Bounds().Dy()))
|
|
||||||
|
|
||||||
var bgraFormat render.Pictformat
|
|
||||||
wanted := render.Directformat{
|
|
||||||
RedShift: 16,
|
|
||||||
RedMask: 0xff,
|
|
||||||
GreenShift: 8,
|
|
||||||
GreenMask: 0xff,
|
|
||||||
BlueShift: 0,
|
|
||||||
BlueMask: 0xff,
|
|
||||||
AlphaShift: 24,
|
|
||||||
AlphaMask: 0xff,
|
|
||||||
}
|
|
||||||
for _, pf := range pformats.Formats {
|
|
||||||
if pf.Depth == 32 && pf.Direct == wanted {
|
|
||||||
bgraFormat = pf.Id
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if bgraFormat == 0 {
|
|
||||||
log.Fatalln("ARGB format not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// We could also look for the inverse pictformat.
|
|
||||||
var encoding binary.ByteOrder
|
|
||||||
if setup.ImageByteOrder == xproto.ImageOrderMSBFirst {
|
|
||||||
encoding = binary.BigEndian
|
|
||||||
} else {
|
|
||||||
encoding = binary.LittleEndian
|
|
||||||
}
|
|
||||||
|
|
||||||
pixpicid, err := render.NewPictureId(X)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
render.CreatePicture(X, pixpicid, xproto.Drawable(pixid), bgraFormat,
|
|
||||||
0, []uint32{})
|
|
||||||
|
|
||||||
// Do we really need this? :/
|
|
||||||
cid, err := xproto.NewGcontextId(X)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
_ = xproto.CreateGC(X, cid, xproto.Drawable(pixid),
|
|
||||||
xproto.GcGraphicsExposures, []uint32{0})
|
|
||||||
|
|
||||||
bounds := img.Bounds()
|
|
||||||
Lstart := time.Now()
|
|
||||||
|
|
||||||
if err := shm.Init(X); err != nil {
|
|
||||||
log.Println("MIT-SHM unavailable")
|
|
||||||
|
|
||||||
// We're being lazy and resolve the 1<<16 limit of requests by sending
|
|
||||||
// a row at a time. The encoding is also done inefficiently.
|
|
||||||
// Also see xgbutil/xgraphics/xsurface.go.
|
|
||||||
row := make([]byte, bounds.Dx()*4)
|
|
||||||
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
|
||||||
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
|
||||||
r, g, b, a := img.At(x, y).RGBA()
|
|
||||||
encoding.PutUint32(row[x*4:],
|
|
||||||
(a>>8)<<24|(r>>8)<<16|(g>>8)<<8|(b>>8))
|
|
||||||
}
|
|
||||||
_ = xproto.PutImage(X, xproto.ImageFormatZPixmap,
|
|
||||||
xproto.Drawable(pixid), cid, uint16(bounds.Dx()), 1,
|
|
||||||
0, int16(y),
|
|
||||||
0, 32, row)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rep, err := shm.QueryVersion(X).Reply()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
if rep.PixmapFormat != xproto.ImageFormatZPixmap ||
|
|
||||||
!rep.SharedPixmaps {
|
|
||||||
log.Fatalln("MIT-SHM configuration unfit")
|
|
||||||
}
|
|
||||||
|
|
||||||
shmSize := bounds.Dx() * bounds.Dy() * 4
|
|
||||||
|
|
||||||
// As a side note, to clean up unreferenced segments (orphans):
|
|
||||||
// ipcs -m | awk '$6 == "0" { print $2 }' | xargs ipcrm shm
|
|
||||||
shmID := int(C.shmget(C.IPC_PRIVATE,
|
|
||||||
C.size_t(shmSize), C.IPC_CREAT|0777))
|
|
||||||
if shmID == -1 {
|
|
||||||
// TODO: We should handle this case by falling back to PutImage,
|
|
||||||
// if only because the allocation may hit a system limit.
|
|
||||||
log.Fatalln("memory allocation failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
dataRaw := C.shmat(C.int(shmID), nil, 0)
|
|
||||||
defer C.shmdt(dataRaw)
|
|
||||||
defer C.shmctl(C.int(shmID), C.IPC_RMID, nil)
|
|
||||||
|
|
||||||
data := *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
|
|
||||||
Data: uintptr(dataRaw), Len: shmSize, Cap: shmSize}))
|
|
||||||
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
|
||||||
row := data[y*bounds.Dx()*4:]
|
|
||||||
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
|
||||||
r, g, b, a := img.At(x, y).RGBA()
|
|
||||||
encoding.PutUint32(row[x*4:],
|
|
||||||
(a>>8)<<24|(r>>8)<<16|(g>>8)<<8|(b>>8))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
segid, err := shm.NewSegId(X)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Need to have it attached on the server before we unload the segment.
|
|
||||||
c := shm.AttachChecked(X, segid, uint32(shmID), true /* RO */)
|
|
||||||
if err := c.Check(); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = shm.PutImage(X, xproto.Drawable(pixid), cid,
|
|
||||||
uint16(bounds.Dx()), uint16(bounds.Dy()), 0, 0,
|
|
||||||
uint16(bounds.Dx()), uint16(bounds.Dy()), 0, 0,
|
|
||||||
32, xproto.ImageFormatZPixmap,
|
|
||||||
0 /* SendEvent */, segid, 0 /* Offset */)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("uploading took", time.Now().Sub(Lstart))
|
|
||||||
|
|
||||||
var scale float64 = 1
|
|
||||||
for {
|
|
||||||
ev, xerr := X.WaitForEvent()
|
|
||||||
if xerr != nil {
|
|
||||||
log.Printf("Error: %s\n", xerr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ev == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Event: %s\n", ev)
|
|
||||||
switch e := ev.(type) {
|
|
||||||
case xproto.UnmapNotifyEvent:
|
|
||||||
return
|
|
||||||
|
|
||||||
case xproto.ConfigureNotifyEvent:
|
|
||||||
w, h := e.Width, e.Height
|
|
||||||
|
|
||||||
scaleX := float64(bounds.Dx()) / float64(w)
|
|
||||||
scaleY := float64(bounds.Dy()) / float64(h)
|
|
||||||
|
|
||||||
if scaleX < scaleY {
|
|
||||||
scale = scaleY
|
|
||||||
} else {
|
|
||||||
scale = scaleX
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = render.SetPictureTransform(X, pixpicid, render.Transform{
|
|
||||||
F64ToFixed(scale), F64ToFixed(0), F64ToFixed(0),
|
|
||||||
F64ToFixed(0), F64ToFixed(scale), F64ToFixed(0),
|
|
||||||
F64ToFixed(0), F64ToFixed(0), F64ToFixed(1),
|
|
||||||
})
|
|
||||||
_ = render.SetPictureFilter(X, pixpicid, 8, "bilinear", nil)
|
|
||||||
|
|
||||||
case xproto.ExposeEvent:
|
|
||||||
_ = render.Composite(X, render.PictOpSrc,
|
|
||||||
pixpicid, render.PictureNone, pid,
|
|
||||||
0, 0, 0, 0, 0 /* dst-x */, 0, /* dst-y */
|
|
||||||
uint16(float64(img.Bounds().Dx())/scale),
|
|
||||||
uint16(float64(img.Bounds().Dy())/scale))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,213 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/BurntSushi/xgb"
|
|
||||||
//"github.com/BurntSushi/xgb/xkb"
|
|
||||||
"github.com/BurntSushi/xgb/xproto"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
X, err := xgb.NewConn()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Use the extension if available, makes better use of state bits.
|
|
||||||
if err := xkb.Init(X); err == nil {
|
|
||||||
if _, err := xkb.UseExtension(X, 1, 0).Reply(); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
setup := xproto.Setup(X)
|
|
||||||
screen := setup.DefaultScreen(X)
|
|
||||||
|
|
||||||
visual, depth := screen.RootVisual, screen.RootDepth
|
|
||||||
// TODO: We should check that we find it, though we don't /need/ alpha here,
|
|
||||||
// it's just a minor improvement--affects the backpixel value.
|
|
||||||
for _, i := range screen.AllowedDepths {
|
|
||||||
for _, v := range i.Visuals {
|
|
||||||
// TODO: Could/should check other parameters.
|
|
||||||
if i.Depth == 32 && v.Class == xproto.VisualClassTrueColor {
|
|
||||||
visual, depth = v.VisualId, i.Depth
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mid, err := xproto.NewColormapId(X)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = xproto.CreateColormap(
|
|
||||||
X, xproto.ColormapAllocNone, mid, screen.Root, visual)
|
|
||||||
|
|
||||||
wid, err := xproto.NewWindowId(X)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Border pixel and colormap are required when depth differs from parent.
|
|
||||||
_ = xproto.CreateWindow(X, depth, wid, screen.Root,
|
|
||||||
0, 0, 500, 500, 0, xproto.WindowClassInputOutput,
|
|
||||||
visual, xproto.CwBackPixel|xproto.CwBorderPixel|xproto.CwEventMask|
|
|
||||||
xproto.CwColormap, []uint32{0x80808080, 0,
|
|
||||||
xproto.EventMaskStructureNotify | xproto.EventMaskKeyPress |
|
|
||||||
/* KeymapNotify */ xproto.EventMaskKeymapState, uint32(mid)})
|
|
||||||
|
|
||||||
title := []byte("Keys")
|
|
||||||
_ = xproto.ChangeProperty(X, xproto.PropModeReplace, wid, xproto.AtomWmName,
|
|
||||||
xproto.AtomString, 8, uint32(len(title)), title)
|
|
||||||
|
|
||||||
_ = xproto.MapWindow(X, wid)
|
|
||||||
|
|
||||||
mapping, err := xproto.GetKeyboardMapping(X, setup.MinKeycode,
|
|
||||||
byte(setup.MaxKeycode-setup.MinKeycode+1)).Reply()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The order is "Shift, Lock, Control, Mod1, Mod2, Mod3, Mod4, and Mod5."
|
|
||||||
mm, err := xproto.GetModifierMapping(X).Reply()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// XXX: This seems pointless, the key will just end up switching groups
|
|
||||||
// instead of levels without full XKB handling. Though perhaps it might
|
|
||||||
// at least work as intended when there's only one XKB group.
|
|
||||||
const MODE_SWITCH = 0xff7e
|
|
||||||
|
|
||||||
var modeSwitchMask uint16
|
|
||||||
for mod := 0; mod < 8; mod++ {
|
|
||||||
perMod := int(mm.KeycodesPerModifier)
|
|
||||||
for _, kc := range mm.Keycodes[mod*perMod : (mod+1)*perMod] {
|
|
||||||
if kc == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
perKc := int(mapping.KeysymsPerKeycode)
|
|
||||||
k := int(kc - setup.MinKeycode)
|
|
||||||
for _, ks := range mapping.Keysyms[k*perKc : (k+1)*perKc] {
|
|
||||||
if ks == MODE_SWITCH {
|
|
||||||
modeSwitchMask |= 1 << uint(mod)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
ev, xerr := X.WaitForEvent()
|
|
||||||
if xerr != nil {
|
|
||||||
log.Printf("Error: %s\n", xerr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ev == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Event: %s\n", ev)
|
|
||||||
switch e := ev.(type) {
|
|
||||||
case xproto.UnmapNotifyEvent:
|
|
||||||
return
|
|
||||||
|
|
||||||
case xproto.KeymapNotifyEvent:
|
|
||||||
// e.Keys is a 32 * 8 large bitmap indicating which keys are
|
|
||||||
// currently pressed down. This is sent "after every EnterNotify
|
|
||||||
// and FocusIn" but it also seems to fire when the keyboard layout
|
|
||||||
// changes. aixterm manual even speaks of that explicitly.
|
|
||||||
//
|
|
||||||
// But since changing the effective group involves no changes to
|
|
||||||
// the compatibility mapping, there's nothing for us to do.
|
|
||||||
|
|
||||||
case xproto.MappingNotifyEvent:
|
|
||||||
// e.FirstKeyCode .. e.Count changes have happened but rereading
|
|
||||||
// everything is the simpler thing to do.
|
|
||||||
mapping, err = xproto.GetKeyboardMapping(X, setup.MinKeycode,
|
|
||||||
byte(setup.MaxKeycode-setup.MinKeycode+1)).Reply()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: We should also repeat the search for MODE SWITCH.
|
|
||||||
|
|
||||||
case xproto.KeyPressEvent:
|
|
||||||
step := int(mapping.KeysymsPerKeycode)
|
|
||||||
from := int(e.Detail-setup.MinKeycode) * step
|
|
||||||
ks := mapping.Keysyms[from : from+step]
|
|
||||||
|
|
||||||
// Strip trailing NoSymbol entries.
|
|
||||||
for len(ks) > 0 && ks[len(ks)-1] == 0 {
|
|
||||||
ks = ks[:len(ks)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expand back to at least 4.
|
|
||||||
switch {
|
|
||||||
case len(ks) == 1:
|
|
||||||
ks = append(ks, 0, ks[0], 0)
|
|
||||||
case len(ks) == 2:
|
|
||||||
ks = append(ks, ks[0], ks[1])
|
|
||||||
case len(ks) == 3:
|
|
||||||
ks = append(ks, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Other silly expansion rules, only applied to basic ASCII.
|
|
||||||
if ks[1] == 0 {
|
|
||||||
ks[1] = ks[0]
|
|
||||||
if ks[0] >= 'A' && ks[0] <= 'Z' ||
|
|
||||||
ks[0] >= 'a' && ks[0] <= 'z' {
|
|
||||||
ks[0] = ks[0] | 32
|
|
||||||
ks[1] = ks[0] &^ 32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ks[3] == 0 {
|
|
||||||
ks[3] = ks[2]
|
|
||||||
if ks[2] >= 'A' && ks[2] <= 'Z' ||
|
|
||||||
ks[2] >= 'a' && ks[2] <= 'z' {
|
|
||||||
ks[2] = ks[2] | 32
|
|
||||||
ks[3] = ks[2] &^ 32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We only have enough information to switch between two groups.
|
|
||||||
offset := 0
|
|
||||||
if e.State&modeSwitchMask != 0 {
|
|
||||||
offset += 2
|
|
||||||
}
|
|
||||||
|
|
||||||
var result xproto.Keysym
|
|
||||||
|
|
||||||
shift := e.State&xproto.ModMaskShift != 0
|
|
||||||
lock := e.State&xproto.ModMaskLock != 0
|
|
||||||
switch {
|
|
||||||
case !shift && !lock:
|
|
||||||
result = ks[offset+0]
|
|
||||||
case !shift && lock:
|
|
||||||
if ks[offset+0] >= 'a' && ks[offset+0] <= 'z' {
|
|
||||||
result = ks[offset+1]
|
|
||||||
} else {
|
|
||||||
result = ks[offset+0]
|
|
||||||
}
|
|
||||||
case shift && lock:
|
|
||||||
if ks[offset+1] >= 'a' && ks[offset+1] <= 'z' {
|
|
||||||
result = ks[offset+1] &^ 32
|
|
||||||
} else {
|
|
||||||
result = ks[offset+1]
|
|
||||||
}
|
|
||||||
case shift:
|
|
||||||
result = ks[offset+1]
|
|
||||||
}
|
|
||||||
|
|
||||||
if result <= 0xff {
|
|
||||||
log.Printf("%c (Latin-1)\n", rune(result))
|
|
||||||
} else {
|
|
||||||
log.Println(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/BurntSushi/xgb"
|
|
||||||
"github.com/BurntSushi/xgb/xproto"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
// Needs a patched local version with xcb-proto 1.12 and this fix:
|
|
||||||
// -size := xgb.Pad((8 + (24 + xgb.Pad((int(NOutput) * 4)))))
|
|
||||||
// +size := xgb.Pad((8 + (24 + xgb.Pad((int(Monitorinfo.NOutput) * 4)))))
|
|
||||||
"github.com/BurntSushi/xgb/randr"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
X, err := xgb.NewConn()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := randr.Init(X); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
setup := xproto.Setup(X)
|
|
||||||
screen := setup.DefaultScreen(X)
|
|
||||||
|
|
||||||
ms, err := randr.GetMonitors(X, screen.Root, true /* GetActive */).Reply()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, m := range ms.Monitors {
|
|
||||||
reply, err := xproto.GetAtomName(X, m.Name).Reply()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Monitor %s %+v\n", reply.Name, m)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,538 +0,0 @@
|
|||||||
// This is an amalgamation of xgb-xrender.go and xgb-keys.go and more of a demo,
|
|
||||||
// some comments have been stripped.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/BurntSushi/xgb"
|
|
||||||
"github.com/BurntSushi/xgb/render"
|
|
||||||
"github.com/BurntSushi/xgb/xproto"
|
|
||||||
"github.com/golang/freetype"
|
|
||||||
"github.com/golang/freetype/truetype"
|
|
||||||
"golang.org/x/image/font"
|
|
||||||
"golang.org/x/image/font/gofont/goregular"
|
|
||||||
"golang.org/x/image/math/fixed"
|
|
||||||
"image"
|
|
||||||
"image/draw"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func glyphListBytes(buf []byte, runes []rune, size int) int {
|
|
||||||
b := 0
|
|
||||||
for _, r := range runes {
|
|
||||||
switch size {
|
|
||||||
default:
|
|
||||||
buf[b] = byte(r)
|
|
||||||
b += 1
|
|
||||||
case 2:
|
|
||||||
xgb.Put16(buf[b:], uint16(r))
|
|
||||||
b += 2
|
|
||||||
case 4:
|
|
||||||
xgb.Put32(buf[b:], uint32(r))
|
|
||||||
b += 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return xgb.Pad(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// When the len is 255, a GLYPHABLE follows, otherwise a list of CARD8/16/32.
|
|
||||||
func glyphEltHeaderBytes(buf []byte, len byte, deltaX, deltaY int16) int {
|
|
||||||
b := 0
|
|
||||||
buf[b] = len
|
|
||||||
b += 4
|
|
||||||
xgb.Put16(buf[b:], uint16(deltaX))
|
|
||||||
b += 2
|
|
||||||
xgb.Put16(buf[b:], uint16(deltaY))
|
|
||||||
b += 2
|
|
||||||
return xgb.Pad(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
type xgbCookie interface{ Check() error }
|
|
||||||
|
|
||||||
// compositeString makes an appropriate render.CompositeGlyphs request,
|
|
||||||
// assuming that glyphs equal Unicode codepoints.
|
|
||||||
func compositeString(c *xgb.Conn, op byte, src, dst render.Picture,
|
|
||||||
maskFormat render.Pictformat, glyphset render.Glyphset, srcX, srcY int16,
|
|
||||||
destX, destY int16, text string) xgbCookie {
|
|
||||||
runes := []rune(text)
|
|
||||||
|
|
||||||
var highest rune
|
|
||||||
for _, r := range runes {
|
|
||||||
if r > highest {
|
|
||||||
highest = r
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size := 1
|
|
||||||
switch {
|
|
||||||
case highest > 1<<16:
|
|
||||||
size = 4
|
|
||||||
case highest > 1<<8:
|
|
||||||
size = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
// They gave up on the XCB protocol API and we need to serialize explicitly.
|
|
||||||
|
|
||||||
// To spare us from caring about the padding, use the largest number lesser
|
|
||||||
// than 255 that is divisible by 4 (for size 2 and 4 the requirements are
|
|
||||||
// less strict but this works in the general case).
|
|
||||||
const maxPerChunk = 252
|
|
||||||
|
|
||||||
buf := make([]byte, (len(runes)+maxPerChunk-1)/maxPerChunk*8+len(runes)*size)
|
|
||||||
b := 0
|
|
||||||
|
|
||||||
for len(runes) > maxPerChunk {
|
|
||||||
b += glyphEltHeaderBytes(buf[b:], maxPerChunk, 0, 0)
|
|
||||||
b += glyphListBytes(buf[b:], runes[:maxPerChunk], size)
|
|
||||||
runes = runes[maxPerChunk:]
|
|
||||||
}
|
|
||||||
if len(runes) > 0 {
|
|
||||||
b += glyphEltHeaderBytes(buf[b:], byte(len(runes)), destX, destY)
|
|
||||||
b += glyphListBytes(buf[b:], runes, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch size {
|
|
||||||
default:
|
|
||||||
return render.CompositeGlyphs8(c, op, src, dst, maskFormat, glyphset,
|
|
||||||
srcX, srcY, buf)
|
|
||||||
case 2:
|
|
||||||
return render.CompositeGlyphs16(c, op, src, dst, maskFormat, glyphset,
|
|
||||||
srcX, srcY, buf)
|
|
||||||
case 4:
|
|
||||||
return render.CompositeGlyphs32(c, op, src, dst, maskFormat, glyphset,
|
|
||||||
srcX, srcY, buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type textRenderer struct {
|
|
||||||
f *truetype.Font
|
|
||||||
opts *truetype.Options
|
|
||||||
face font.Face
|
|
||||||
|
|
||||||
bounds fixed.Rectangle26_6 // outer bounds for all the font's glyph
|
|
||||||
buf *image.RGBA // rendering buffer
|
|
||||||
|
|
||||||
X *xgb.Conn
|
|
||||||
gsid render.Glyphset
|
|
||||||
loaded map[rune]bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTextRenderer(X *xgb.Conn, ttf []byte, opts *truetype.Options) (
|
|
||||||
*textRenderer, error) {
|
|
||||||
pformats, err := render.QueryPictFormats(X).Reply()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We use RGBA here just so that lines are padded to 32 bits.
|
|
||||||
// Since there's no subpixel antialiasing and alpha is premultiplied,
|
|
||||||
// it doesn't even mater that RGBA is interpreted as ARGB or BGRA.
|
|
||||||
var rgbFormat render.Pictformat
|
|
||||||
for _, pf := range pformats.Formats {
|
|
||||||
if pf.Depth == 32 && pf.Direct.AlphaMask != 0 {
|
|
||||||
rgbFormat = pf.Id
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tr := &textRenderer{opts: opts, X: X, loaded: make(map[rune]bool)}
|
|
||||||
if tr.f, err = freetype.ParseFont(goregular.TTF); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tr.face = truetype.NewFace(tr.f, opts)
|
|
||||||
tr.bounds = tr.f.Bounds(fixed.Int26_6(opts.Size * float64(opts.DPI) *
|
|
||||||
(64.0 / 72.0)))
|
|
||||||
|
|
||||||
if tr.gsid, err = render.NewGlyphsetId(X); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := render.CreateGlyphSetChecked(X, tr.gsid, rgbFormat).
|
|
||||||
Check(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tr.buf = image.NewRGBA(image.Rect(
|
|
||||||
+tr.bounds.Min.X.Floor(),
|
|
||||||
-tr.bounds.Min.Y.Floor(),
|
|
||||||
+tr.bounds.Max.X.Ceil(),
|
|
||||||
-tr.bounds.Max.Y.Ceil(),
|
|
||||||
))
|
|
||||||
return tr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *textRenderer) addRune(r rune) bool {
|
|
||||||
dr, mask, maskp, advance, ok := tr.face.Glyph(
|
|
||||||
fixed.P(0, 0) /* subpixel destination location */, r)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(tr.buf.Pix); i++ {
|
|
||||||
tr.buf.Pix[i] = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copying, since there are absolutely no guarantees.
|
|
||||||
draw.Draw(tr.buf, dr, mask, maskp, draw.Src)
|
|
||||||
|
|
||||||
_ = render.AddGlyphs(tr.X, tr.gsid, 1, []uint32{uint32(r)},
|
|
||||||
[]render.Glyphinfo{{
|
|
||||||
Width: uint16(tr.buf.Rect.Size().X),
|
|
||||||
Height: uint16(tr.buf.Rect.Size().Y),
|
|
||||||
X: int16(-tr.bounds.Min.X.Floor()),
|
|
||||||
Y: int16(+tr.bounds.Max.Y.Ceil()),
|
|
||||||
XOff: int16(advance.Ceil()),
|
|
||||||
YOff: int16(0),
|
|
||||||
}}, []byte(tr.buf.Pix))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *textRenderer) render(src, dst render.Picture,
|
|
||||||
srcX, srcY, destX, destY int16, text string) xgbCookie {
|
|
||||||
// XXX: You're really supposed to handle tabs differently from this.
|
|
||||||
text = strings.Replace(text, "\t", " ", -1)
|
|
||||||
|
|
||||||
for _, r := range text {
|
|
||||||
if !tr.loaded[r] {
|
|
||||||
tr.addRune(r)
|
|
||||||
tr.loaded[r] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return compositeString(tr.X, render.PictOpOver, src, dst,
|
|
||||||
0 /* TODO: mask Pictureformat? */, tr.gsid,
|
|
||||||
srcX, srcY, destX, destY, text)
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
ksEscape = 0xff1b
|
|
||||||
ksUp = 0xff52
|
|
||||||
ksDown = 0xff54
|
|
||||||
ksPageUp = 0xff55
|
|
||||||
ksPageDown = 0xff56
|
|
||||||
ksModeSwitch = 0xff7e
|
|
||||||
)
|
|
||||||
|
|
||||||
type keyMapper struct {
|
|
||||||
X *xgb.Conn
|
|
||||||
setup *xproto.SetupInfo
|
|
||||||
mapping *xproto.GetKeyboardMappingReply
|
|
||||||
|
|
||||||
modeSwitchMask uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
func newKeyMapper(X *xgb.Conn) (*keyMapper, error) {
|
|
||||||
m := &keyMapper{X: X, setup: xproto.Setup(X)}
|
|
||||||
if err := m.update(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (km *keyMapper) update() error {
|
|
||||||
var err error
|
|
||||||
km.mapping, err = xproto.GetKeyboardMapping(km.X, km.setup.MinKeycode,
|
|
||||||
byte(km.setup.MaxKeycode-km.setup.MinKeycode+1)).Reply()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
km.modeSwitchMask = 0
|
|
||||||
|
|
||||||
// The order is "Shift, Lock, Control, Mod1, Mod2, Mod3, Mod4, and Mod5."
|
|
||||||
mm, err := xproto.GetModifierMapping(km.X).Reply()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
perMod := int(mm.KeycodesPerModifier)
|
|
||||||
perKc := int(km.mapping.KeysymsPerKeycode)
|
|
||||||
for mod := 0; mod < 8; mod++ {
|
|
||||||
for _, kc := range mm.Keycodes[mod*perMod : (mod+1)*perMod] {
|
|
||||||
if kc == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
k := int(kc - km.setup.MinKeycode)
|
|
||||||
for _, ks := range km.mapping.Keysyms[k*perKc : (k+1)*perKc] {
|
|
||||||
if ks == ksModeSwitch {
|
|
||||||
km.modeSwitchMask |= 1 << uint(mod)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (km *keyMapper) decode(e xproto.KeyPressEvent) (result xproto.Keysym) {
|
|
||||||
step := int(km.mapping.KeysymsPerKeycode)
|
|
||||||
from := int(e.Detail-km.setup.MinKeycode) * step
|
|
||||||
ks := km.mapping.Keysyms[from : from+step]
|
|
||||||
|
|
||||||
// Strip trailing NoSymbol entries.
|
|
||||||
for len(ks) > 0 && ks[len(ks)-1] == 0 {
|
|
||||||
ks = ks[:len(ks)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expand back to at least 4.
|
|
||||||
switch {
|
|
||||||
case len(ks) == 1:
|
|
||||||
ks = append(ks, 0, ks[0], 0)
|
|
||||||
case len(ks) == 2:
|
|
||||||
ks = append(ks, ks[0], ks[1])
|
|
||||||
case len(ks) == 3:
|
|
||||||
ks = append(ks, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Other silly expansion rules, only applied to basic ASCII since we
|
|
||||||
// don't have translation tables to Unicode here for brevity.
|
|
||||||
if ks[1] == 0 {
|
|
||||||
ks[1] = ks[0]
|
|
||||||
if ks[0] >= 'A' && ks[0] <= 'Z' ||
|
|
||||||
ks[0] >= 'a' && ks[0] <= 'z' {
|
|
||||||
ks[0] = ks[0] | 32
|
|
||||||
ks[1] = ks[0] &^ 32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ks[3] == 0 {
|
|
||||||
ks[3] = ks[2]
|
|
||||||
if ks[2] >= 'A' && ks[2] <= 'Z' ||
|
|
||||||
ks[2] >= 'a' && ks[2] <= 'z' {
|
|
||||||
ks[2] = ks[2] | 32
|
|
||||||
ks[3] = ks[2] &^ 32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
offset := 0
|
|
||||||
if e.State&km.modeSwitchMask != 0 {
|
|
||||||
offset += 2
|
|
||||||
}
|
|
||||||
|
|
||||||
shift := e.State&xproto.ModMaskShift != 0
|
|
||||||
lock := e.State&xproto.ModMaskLock != 0
|
|
||||||
switch {
|
|
||||||
case !shift && !lock:
|
|
||||||
result = ks[offset+0]
|
|
||||||
case !shift && lock:
|
|
||||||
if ks[offset+0] >= 'a' && ks[offset+0] <= 'z' {
|
|
||||||
result = ks[offset+1]
|
|
||||||
} else {
|
|
||||||
result = ks[offset+0]
|
|
||||||
}
|
|
||||||
case shift && lock:
|
|
||||||
if ks[offset+1] >= 'a' && ks[offset+1] <= 'z' {
|
|
||||||
result = ks[offset+1] &^ 32
|
|
||||||
} else {
|
|
||||||
result = ks[offset+1]
|
|
||||||
}
|
|
||||||
case shift:
|
|
||||||
result = ks[offset+1]
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if len(os.Args) < 2 {
|
|
||||||
log.Fatalln("no filename given")
|
|
||||||
}
|
|
||||||
|
|
||||||
text, err := ioutil.ReadFile(os.Args[1])
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
lines := strings.Split(string(text), "\n")
|
|
||||||
|
|
||||||
X, err := xgb.NewConn()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
if err := render.Init(X); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
setup := xproto.Setup(X)
|
|
||||||
screen := setup.DefaultScreen(X)
|
|
||||||
|
|
||||||
visual, depth := screen.RootVisual, screen.RootDepth
|
|
||||||
// TODO: We should check that we find it, though we don't /need/ alpha here,
|
|
||||||
// it's just a minor improvement--affects the backpixel value.
|
|
||||||
for _, i := range screen.AllowedDepths {
|
|
||||||
// TODO: Could/should check other parameters.
|
|
||||||
for _, v := range i.Visuals {
|
|
||||||
if i.Depth == 32 && v.Class == xproto.VisualClassTrueColor {
|
|
||||||
visual, depth = v.VisualId, i.Depth
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mid, err := xproto.NewColormapId(X)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
_ = xproto.CreateColormap(
|
|
||||||
X, xproto.ColormapAllocNone, mid, screen.Root, visual)
|
|
||||||
|
|
||||||
wid, err := xproto.NewWindowId(X)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
// Border pixel and colormap are required when depth differs from parent.
|
|
||||||
_ = xproto.CreateWindow(X, depth, wid, screen.Root,
|
|
||||||
0, 0, 500, 500, 0, xproto.WindowClassInputOutput,
|
|
||||||
visual, xproto.CwBackPixel|xproto.CwBorderPixel|xproto.CwEventMask|
|
|
||||||
xproto.CwColormap, []uint32{0xf0f0f0f0, 0,
|
|
||||||
xproto.EventMaskStructureNotify | xproto.EventMaskKeyPress |
|
|
||||||
/* KeymapNotify */ xproto.EventMaskKeymapState |
|
|
||||||
xproto.EventMaskExposure | xproto.EventMaskButtonPress,
|
|
||||||
uint32(mid)})
|
|
||||||
|
|
||||||
title := []byte("Viewer")
|
|
||||||
_ = xproto.ChangeProperty(X, xproto.PropModeReplace, wid, xproto.AtomWmName,
|
|
||||||
xproto.AtomString, 8, uint32(len(title)), title)
|
|
||||||
|
|
||||||
_ = xproto.MapWindow(X, wid)
|
|
||||||
|
|
||||||
pformats, err := render.QueryPictFormats(X).Reply()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Similar to XRenderFindVisualFormat.
|
|
||||||
// The DefaultScreen is almost certain to be zero.
|
|
||||||
var pformat render.Pictformat
|
|
||||||
for _, pd := range pformats.Screens[X.DefaultScreen].Depths {
|
|
||||||
// This check seems to be slightly extraneous.
|
|
||||||
if pd.Depth != depth {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, pv := range pd.Visuals {
|
|
||||||
if pv.Visual == visual {
|
|
||||||
pformat = pv.Format
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pid, err := render.NewPictureId(X)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
render.CreatePicture(X, pid, xproto.Drawable(wid), pformat, 0, []uint32{})
|
|
||||||
|
|
||||||
blackid, err := render.NewPictureId(X)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
_ = render.CreateSolidFill(X, blackid, render.Color{Alpha: 0xffff})
|
|
||||||
|
|
||||||
tr, err := newTextRenderer(X, goregular.TTF, &truetype.Options{
|
|
||||||
Size: 10,
|
|
||||||
DPI: float64(screen.WidthInPixels) /
|
|
||||||
float64(screen.WidthInMillimeters) * 25.4,
|
|
||||||
Hinting: font.HintingFull,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
scroll := 0 // index of the top line
|
|
||||||
|
|
||||||
var w, h uint16
|
|
||||||
redraw := func() {
|
|
||||||
y, ascent, step := 5, tr.bounds.Max.Y.Ceil(),
|
|
||||||
tr.bounds.Max.Y.Ceil()-tr.bounds.Min.Y.Floor()
|
|
||||||
for _, line := range lines[scroll:] {
|
|
||||||
if uint16(y) >= h {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
_ = tr.render(blackid, pid, 0, 0, 5, int16(y+ascent), line)
|
|
||||||
y += step
|
|
||||||
}
|
|
||||||
|
|
||||||
vis := float64(h-10) / float64(step)
|
|
||||||
if vis < float64(len(lines)) {
|
|
||||||
length := float64(step) * (vis + 1) * vis / float64(len(lines))
|
|
||||||
start := float64(step) * float64(scroll) * vis / float64(len(lines))
|
|
||||||
|
|
||||||
_ = render.FillRectangles(X, render.PictOpSrc, pid,
|
|
||||||
render.Color{Alpha: 0xffff}, []xproto.Rectangle{{
|
|
||||||
X: int16(w - 15), Y: int16(start),
|
|
||||||
Width: 15, Height: uint16(length + 10)}})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
km, err := newKeyMapper(X)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
ev, xerr := X.WaitForEvent()
|
|
||||||
if xerr != nil {
|
|
||||||
log.Printf("Error: %s\n", xerr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ev == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch e := ev.(type) {
|
|
||||||
case xproto.UnmapNotifyEvent:
|
|
||||||
return
|
|
||||||
|
|
||||||
case xproto.ConfigureNotifyEvent:
|
|
||||||
w, h = e.Width, e.Height
|
|
||||||
|
|
||||||
case xproto.MappingNotifyEvent:
|
|
||||||
_ = km.update()
|
|
||||||
|
|
||||||
case xproto.KeyPressEvent:
|
|
||||||
_ = xproto.ClearArea(X, true /* ExposeEvent */, wid, 0, 0, w, h)
|
|
||||||
|
|
||||||
const pageJump = 40
|
|
||||||
switch km.decode(e) {
|
|
||||||
case ksEscape:
|
|
||||||
return
|
|
||||||
case ksUp:
|
|
||||||
if scroll >= 1 {
|
|
||||||
scroll--
|
|
||||||
}
|
|
||||||
case ksDown:
|
|
||||||
if scroll+1 < len(lines) {
|
|
||||||
scroll++
|
|
||||||
}
|
|
||||||
case ksPageUp:
|
|
||||||
if scroll >= pageJump {
|
|
||||||
scroll -= pageJump
|
|
||||||
}
|
|
||||||
case ksPageDown:
|
|
||||||
if scroll+pageJump < len(lines) {
|
|
||||||
scroll += pageJump
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case xproto.ButtonPressEvent:
|
|
||||||
_ = xproto.ClearArea(X, true /* ExposeEvent */, wid, 0, 0, w, h)
|
|
||||||
|
|
||||||
switch e.Detail {
|
|
||||||
case xproto.ButtonIndex4:
|
|
||||||
if scroll > 0 {
|
|
||||||
scroll--
|
|
||||||
}
|
|
||||||
case xproto.ButtonIndex5:
|
|
||||||
if scroll+1 < len(lines) {
|
|
||||||
scroll++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case xproto.ExposeEvent:
|
|
||||||
// FIXME: The window's context haven't necessarily been destroyed.
|
|
||||||
if e.Count == 0 {
|
|
||||||
redraw()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,156 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/BurntSushi/xgb"
|
|
||||||
"github.com/BurntSushi/xgb/xproto"
|
|
||||||
"log"
|
|
||||||
"math"
|
|
||||||
"math/rand"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
X, err := xgb.NewConn()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
setup := xproto.Setup(X)
|
|
||||||
screen := setup.DefaultScreen(X)
|
|
||||||
|
|
||||||
var visual xproto.Visualid
|
|
||||||
var depth byte
|
|
||||||
for _, i := range screen.AllowedDepths {
|
|
||||||
if i.Depth == 32 {
|
|
||||||
// TODO: Could/should check other parameters.
|
|
||||||
for _, v := range i.Visuals {
|
|
||||||
if v.Class == xproto.VisualClassTrueColor {
|
|
||||||
visual = v.VisualId
|
|
||||||
depth = i.Depth
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if visual == 0 {
|
|
||||||
log.Fatalln("cannot find an RGBA TrueColor visual")
|
|
||||||
}
|
|
||||||
|
|
||||||
mid, err := xproto.NewColormapId(X)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = xproto.CreateColormap(
|
|
||||||
X, xproto.ColormapAllocNone, mid, screen.Root, visual)
|
|
||||||
|
|
||||||
wid, err := xproto.NewWindowId(X)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Border pixel and colormap are required when depth differs from parent.
|
|
||||||
_ = xproto.CreateWindow(X, depth, wid, screen.Root,
|
|
||||||
0, 0, 500, 500, 0, xproto.WindowClassInputOutput,
|
|
||||||
visual, xproto.CwBorderPixel|xproto.CwColormap,
|
|
||||||
[]uint32{0, uint32(mid)})
|
|
||||||
|
|
||||||
// This could be included in CreateWindow parameters.
|
|
||||||
_ = xproto.ChangeWindowAttributes(X, wid,
|
|
||||||
xproto.CwBackPixel|xproto.CwEventMask, []uint32{0x80808080,
|
|
||||||
xproto.EventMaskStructureNotify | xproto.EventMaskKeyPress |
|
|
||||||
xproto.EventMaskExposure})
|
|
||||||
|
|
||||||
title := "Gradient"
|
|
||||||
_ = xproto.ChangeProperty(X, xproto.PropModeReplace, wid, xproto.AtomWmName,
|
|
||||||
xproto.AtomString, 8, uint32(len(title)), []byte(title))
|
|
||||||
|
|
||||||
_ = xproto.MapWindow(X, wid)
|
|
||||||
|
|
||||||
cid, err := xproto.NewGcontextId(X)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = xproto.CreateGC(X, cid, xproto.Drawable(wid),
|
|
||||||
xproto.GcGraphicsExposures, []uint32{0})
|
|
||||||
|
|
||||||
blend := func(a, b uint32, ratio, gamma float64) uint32 {
|
|
||||||
iratio := 1 - ratio
|
|
||||||
|
|
||||||
fa := math.Pow(float64(a)/255, gamma)
|
|
||||||
fb := math.Pow(float64(b)/255, gamma)
|
|
||||||
|
|
||||||
return uint32(math.Pow(ratio*fa+iratio*fb, 1/gamma)*255) & 0xff
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: We could show some text just like we intend to with xgb-xrender.go.
|
|
||||||
|
|
||||||
var w, h uint16
|
|
||||||
var start, end uint32 = 0xabcdef, 0x32ab54
|
|
||||||
gradient := func() {
|
|
||||||
ra, ga, ba := (start>>16)&0xff, (start>>8)&0xff, start&0xff
|
|
||||||
rb, gb, bb := (end>>16)&0xff, (end>>8)&0xff, end&0xff
|
|
||||||
|
|
||||||
var low, high uint16 = 50, h - 50
|
|
||||||
if high > h {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for y := low; y < high; y++ {
|
|
||||||
ratio := float64(y-low) / (float64(high) - float64(low))
|
|
||||||
|
|
||||||
rR := blend(ra, rb, ratio, 2.2)
|
|
||||||
gG := blend(ga, gb, ratio, 2.2)
|
|
||||||
bB := blend(ba, bb, ratio, 2.2)
|
|
||||||
|
|
||||||
_ = xproto.ChangeGC(X, cid, xproto.GcForeground,
|
|
||||||
[]uint32{0xff000000 | rR<<16 | gG<<8 | bB})
|
|
||||||
_ = xproto.PolyLine(X, xproto.CoordModeOrigin, xproto.Drawable(wid),
|
|
||||||
cid, []xproto.Point{
|
|
||||||
{X: 50, Y: int16(y)},
|
|
||||||
{X: int16(w / 2), Y: int16(y)},
|
|
||||||
})
|
|
||||||
|
|
||||||
rR = blend(ra, rb, ratio, 1)
|
|
||||||
gG = blend(ga, gb, ratio, 1)
|
|
||||||
bB = blend(ba, bb, ratio, 1)
|
|
||||||
|
|
||||||
_ = xproto.ChangeGC(X, cid, xproto.GcForeground,
|
|
||||||
[]uint32{0xff000000 | rR<<16 | gG<<8 | bB})
|
|
||||||
_ = xproto.PolyLine(X, xproto.CoordModeOrigin, xproto.Drawable(wid),
|
|
||||||
cid, []xproto.Point{
|
|
||||||
{X: int16(w / 2), Y: int16(y)},
|
|
||||||
{X: int16(w - 50), Y: int16(y)},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
ev, xerr := X.WaitForEvent()
|
|
||||||
if xerr != nil {
|
|
||||||
log.Printf("Error: %s\n", xerr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ev == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Event: %s\n", ev)
|
|
||||||
switch e := ev.(type) {
|
|
||||||
case xproto.UnmapNotifyEvent:
|
|
||||||
return
|
|
||||||
|
|
||||||
case xproto.ConfigureNotifyEvent:
|
|
||||||
w, h = e.Width, e.Height
|
|
||||||
|
|
||||||
case xproto.KeyPressEvent:
|
|
||||||
start = rand.Uint32() & 0xffffff
|
|
||||||
end = rand.Uint32() & 0xffffff
|
|
||||||
gradient()
|
|
||||||
|
|
||||||
case xproto.ExposeEvent:
|
|
||||||
gradient()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,397 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
// We could also easily use x/image/font/basicfont to load some glyphs into X,
|
|
||||||
// relying on the fact that it is a vertical array of A8 masks. Though it only
|
|
||||||
// supports ASCII and has but one size. Best just make a custom BDF loader,
|
|
||||||
// those fonts have larger coverages and we would be in control. Though again,
|
|
||||||
// they don't seem to be capable of antialiasing.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/BurntSushi/xgb"
|
|
||||||
"github.com/BurntSushi/xgb/render"
|
|
||||||
"github.com/BurntSushi/xgb/xproto"
|
|
||||||
// golang.org/x/image/font/opentype cannot render yet but the API is
|
|
||||||
// more or less the same.
|
|
||||||
"github.com/golang/freetype"
|
|
||||||
"github.com/golang/freetype/truetype"
|
|
||||||
"golang.org/x/image/font"
|
|
||||||
"golang.org/x/image/font/gofont/goregular"
|
|
||||||
"golang.org/x/image/math/fixed"
|
|
||||||
"image"
|
|
||||||
"image/draw"
|
|
||||||
"log"
|
|
||||||
"math/rand"
|
|
||||||
)
|
|
||||||
|
|
||||||
func F64ToFixed(f float64) render.Fixed { return render.Fixed(f * 65536) }
|
|
||||||
func FixedToF64(f render.Fixed) float64 { return float64(f) / 65536 }
|
|
||||||
|
|
||||||
func glyphListBytes(buf []byte, runes []rune, size int) int {
|
|
||||||
b := 0
|
|
||||||
for _, r := range runes {
|
|
||||||
switch size {
|
|
||||||
default:
|
|
||||||
buf[b] = byte(r)
|
|
||||||
b += 1
|
|
||||||
case 2:
|
|
||||||
xgb.Put16(buf[b:], uint16(r))
|
|
||||||
b += 2
|
|
||||||
case 4:
|
|
||||||
xgb.Put32(buf[b:], uint32(r))
|
|
||||||
b += 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return xgb.Pad(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// When the len is 255, a GLYPHABLE follows, otherwise a list of CARD8/16/32.
|
|
||||||
func glyphEltHeaderBytes(buf []byte, len byte, deltaX, deltaY int16) int {
|
|
||||||
b := 0
|
|
||||||
buf[b] = len
|
|
||||||
b += 4
|
|
||||||
xgb.Put16(buf[b:], uint16(deltaX))
|
|
||||||
b += 2
|
|
||||||
xgb.Put16(buf[b:], uint16(deltaY))
|
|
||||||
b += 2
|
|
||||||
return xgb.Pad(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
type xgbCookie interface{ Check() error }
|
|
||||||
|
|
||||||
// TODO: We actually need a higher-level function that also keeps track of
|
|
||||||
// and loads glyphs into the X server.
|
|
||||||
// TODO: We also need a way to use kerning tables with this, inserting/removing
|
|
||||||
// advance pixels between neighboring characters.
|
|
||||||
|
|
||||||
// compositeString makes an appropriate render.CompositeGlyphs request,
|
|
||||||
// assuming that glyphs equal Unicode codepoints.
|
|
||||||
func compositeString(c *xgb.Conn, op byte, src, dst render.Picture,
|
|
||||||
maskFormat render.Pictformat, glyphset render.Glyphset, srcX, srcY int16,
|
|
||||||
destX, destY int16, text string) xgbCookie {
|
|
||||||
runes := []rune(text)
|
|
||||||
|
|
||||||
var highest rune
|
|
||||||
for _, r := range runes {
|
|
||||||
if r > highest {
|
|
||||||
highest = r
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size := 1
|
|
||||||
switch {
|
|
||||||
case highest > 1<<16:
|
|
||||||
size = 4
|
|
||||||
case highest > 1<<8:
|
|
||||||
size = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
// They gave up on the XCB protocol API and we need to serialize explicitly.
|
|
||||||
|
|
||||||
// To spare us from caring about the padding, use the largest number lesser
|
|
||||||
// than 255 that is divisible by 4 (for size 2 and 4 the requirements are
|
|
||||||
// less strict but this works in the general case).
|
|
||||||
const maxPerChunk = 252
|
|
||||||
|
|
||||||
buf := make([]byte, (len(runes)+maxPerChunk-1)/maxPerChunk*8+len(runes)*size)
|
|
||||||
b := 0
|
|
||||||
|
|
||||||
for len(runes) > maxPerChunk {
|
|
||||||
b += glyphEltHeaderBytes(buf[b:], maxPerChunk, 0, 0)
|
|
||||||
b += glyphListBytes(buf[b:], runes[:maxPerChunk], size)
|
|
||||||
runes = runes[maxPerChunk:]
|
|
||||||
}
|
|
||||||
if len(runes) > 0 {
|
|
||||||
b += glyphEltHeaderBytes(buf[b:], byte(len(runes)), destX, destY)
|
|
||||||
b += glyphListBytes(buf[b:], runes, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch size {
|
|
||||||
default:
|
|
||||||
return render.CompositeGlyphs8(c, op, src, dst, maskFormat, glyphset,
|
|
||||||
srcX, srcY, buf)
|
|
||||||
case 2:
|
|
||||||
return render.CompositeGlyphs16(c, op, src, dst, maskFormat, glyphset,
|
|
||||||
srcX, srcY, buf)
|
|
||||||
case 4:
|
|
||||||
return render.CompositeGlyphs32(c, op, src, dst, maskFormat, glyphset,
|
|
||||||
srcX, srcY, buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
X, err := xgb.NewConn()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := render.Init(X); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
setup := xproto.Setup(X)
|
|
||||||
screen := setup.DefaultScreen(X)
|
|
||||||
|
|
||||||
var visual xproto.Visualid
|
|
||||||
var depth byte
|
|
||||||
for _, i := range screen.AllowedDepths {
|
|
||||||
if i.Depth == 32 {
|
|
||||||
// TODO: Could/should check other parameters.
|
|
||||||
for _, v := range i.Visuals {
|
|
||||||
if v.Class == xproto.VisualClassTrueColor {
|
|
||||||
visual = v.VisualId
|
|
||||||
depth = i.Depth
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if visual == 0 {
|
|
||||||
log.Fatalln("cannot find an RGBA TrueColor visual")
|
|
||||||
}
|
|
||||||
|
|
||||||
mid, err := xproto.NewColormapId(X)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = xproto.CreateColormap(
|
|
||||||
X, xproto.ColormapAllocNone, mid, screen.Root, visual)
|
|
||||||
|
|
||||||
wid, err := xproto.NewWindowId(X)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Border pixel and colormap are required when depth differs from parent.
|
|
||||||
_ = xproto.CreateWindow(X, depth, wid, screen.Root,
|
|
||||||
0, 0, 500, 500, 0, xproto.WindowClassInputOutput,
|
|
||||||
visual, xproto.CwBorderPixel|xproto.CwColormap,
|
|
||||||
[]uint32{0, uint32(mid)})
|
|
||||||
|
|
||||||
// This could be included in CreateWindow parameters.
|
|
||||||
_ = xproto.ChangeWindowAttributes(X, wid,
|
|
||||||
xproto.CwBackPixel|xproto.CwEventMask, []uint32{0x80808080,
|
|
||||||
xproto.EventMaskStructureNotify | xproto.EventMaskKeyPress |
|
|
||||||
xproto.EventMaskExposure})
|
|
||||||
|
|
||||||
title := []byte("Gradient")
|
|
||||||
_ = xproto.ChangeProperty(X, xproto.PropModeReplace, wid, xproto.AtomWmName,
|
|
||||||
xproto.AtomString, 8, uint32(len(title)), title)
|
|
||||||
|
|
||||||
_ = xproto.MapWindow(X, wid)
|
|
||||||
|
|
||||||
/*
|
|
||||||
rfilters, err := render.QueryFilters(X, xproto.Drawable(wid)).Reply()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
filters := []string{}
|
|
||||||
for _, f := range rfilters.Filters {
|
|
||||||
filters = append(filters, f.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("filters: %v\n", filters)
|
|
||||||
*/
|
|
||||||
|
|
||||||
pformats, err := render.QueryPictFormats(X).Reply()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
for _, pf := range pformats.Formats {
|
|
||||||
log.Printf("format %2d: depth %2d, RGBA %3x %3x %3x %3x\n",
|
|
||||||
pf.Id, pf.Depth,
|
|
||||||
pf.Direct.RedMask, pf.Direct.GreenMask, pf.Direct.BlueMask,
|
|
||||||
pf.Direct.AlphaMask)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Similar to XRenderFindVisualFormat.
|
|
||||||
// The DefaultScreen is almost certain to be zero.
|
|
||||||
var pformat render.Pictformat
|
|
||||||
for _, pd := range pformats.Screens[X.DefaultScreen].Depths {
|
|
||||||
// This check seems to be slightly extraneous.
|
|
||||||
if pd.Depth != depth {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, pv := range pd.Visuals {
|
|
||||||
if pv.Visual == visual {
|
|
||||||
pformat = pv.Format
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...or just scan through pformats.Formats and look for matches, which is
|
|
||||||
// what XRenderFindStandardFormat in Xlib does as well as exp/shiny.
|
|
||||||
|
|
||||||
f, err := freetype.ParseFont(goregular.TTF)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LCD subpixel rendering isn't supported. :(
|
|
||||||
opts := &truetype.Options{
|
|
||||||
Size: 10,
|
|
||||||
DPI: 96, // TODO: Take this from the screen or monitor.
|
|
||||||
Hinting: font.HintingFull,
|
|
||||||
}
|
|
||||||
face := truetype.NewFace(f, opts)
|
|
||||||
bounds := f.Bounds(fixed.Int26_6(opts.Size * float64(opts.DPI) *
|
|
||||||
(64.0 / 72.0)))
|
|
||||||
|
|
||||||
var rgbFormat render.Pictformat
|
|
||||||
for _, pf := range pformats.Formats {
|
|
||||||
// Hopefully. Might want to check byte order.
|
|
||||||
if pf.Depth == 32 && pf.Direct.AlphaMask != 0 {
|
|
||||||
rgbFormat = pf.Id
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gsid, err := render.NewGlyphsetId(X)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: A depth of 24 will not work, the server always rejects it.
|
|
||||||
// Composite alpha doesn't make sense since golang/freetype can't use it.
|
|
||||||
// We use RGBA here just so that lines are padded to 32 bits.
|
|
||||||
_ = render.CreateGlyphSet(X, gsid, rgbFormat)
|
|
||||||
|
|
||||||
// NOTE: We could do gamma post-correction in higher precision if we
|
|
||||||
// implemented our own clone of the image.Image implementation.
|
|
||||||
nrgb := image.NewRGBA(image.Rect(
|
|
||||||
+bounds.Min.X.Floor(),
|
|
||||||
-bounds.Min.Y.Floor(),
|
|
||||||
+bounds.Max.X.Ceil(),
|
|
||||||
-bounds.Max.Y.Ceil(),
|
|
||||||
))
|
|
||||||
|
|
||||||
for r := rune(32); r < 128; r++ {
|
|
||||||
dr, mask, maskp, advance, ok := face.Glyph(
|
|
||||||
fixed.P(0, 0) /* subpixel destination location */, r)
|
|
||||||
if !ok {
|
|
||||||
log.Println("skip")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(nrgb.Pix); i++ {
|
|
||||||
nrgb.Pix[i] = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
draw.Draw(nrgb, dr, mask, maskp, draw.Src)
|
|
||||||
|
|
||||||
_ = render.AddGlyphs(X, gsid, 1, []uint32{uint32(r)},
|
|
||||||
[]render.Glyphinfo{{
|
|
||||||
Width: uint16(nrgb.Rect.Size().X),
|
|
||||||
Height: uint16(nrgb.Rect.Size().Y),
|
|
||||||
X: int16(-bounds.Min.X.Floor()),
|
|
||||||
Y: int16(+bounds.Max.Y.Ceil()),
|
|
||||||
XOff: int16(advance.Ceil()),
|
|
||||||
YOff: int16(0),
|
|
||||||
}}, []byte(nrgb.Pix))
|
|
||||||
}
|
|
||||||
|
|
||||||
pid, err := render.NewPictureId(X)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dithering is not supported. :(
|
|
||||||
render.CreatePicture(X, pid, xproto.Drawable(wid), pformat, 0, []uint32{})
|
|
||||||
|
|
||||||
// Reserve an ID for the gradient.
|
|
||||||
gid, err := render.NewPictureId(X)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
whiteid, err := render.NewPictureId(X)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = render.CreateSolidFill(X, whiteid, render.Color{
|
|
||||||
Red: 0xffff,
|
|
||||||
Green: 0xffff,
|
|
||||||
Blue: 0xffff,
|
|
||||||
Alpha: 0xffff,
|
|
||||||
})
|
|
||||||
|
|
||||||
var from, to render.Color
|
|
||||||
var start, end uint32
|
|
||||||
recolor := func() {
|
|
||||||
start = rand.Uint32() & 0xffffff
|
|
||||||
from = render.Color{
|
|
||||||
Red: 0x101 * uint16((start>>16)&0xff),
|
|
||||||
Green: 0x101 * uint16((start>>8)&0xff),
|
|
||||||
Blue: 0x101 * uint16(start&0xff),
|
|
||||||
Alpha: 0xffff,
|
|
||||||
}
|
|
||||||
|
|
||||||
end = rand.Uint32() & 0xffffff
|
|
||||||
to = render.Color{
|
|
||||||
Red: 0x101 * uint16((end>>16)&0xff),
|
|
||||||
Green: 0x101 * uint16((end>>8)&0xff),
|
|
||||||
Blue: 0x101 * uint16(end&0xff),
|
|
||||||
Alpha: 0xffff,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var w, h uint16
|
|
||||||
gradient := func() {
|
|
||||||
if w < 100 || h < 100 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// We could also use a transformation matrix for changes in size.
|
|
||||||
_ = render.CreateLinearGradient(X, gid,
|
|
||||||
render.Pointfix{F64ToFixed(0), F64ToFixed(0)},
|
|
||||||
render.Pointfix{F64ToFixed(0), F64ToFixed(float64(h) - 100)},
|
|
||||||
2, []render.Fixed{F64ToFixed(0), F64ToFixed(1)},
|
|
||||||
[]render.Color{from, to})
|
|
||||||
|
|
||||||
_ = render.Composite(X, render.PictOpSrc, gid, render.PictureNone, pid,
|
|
||||||
0, 0, 0, 0, 50, 50, w-100, h-100)
|
|
||||||
|
|
||||||
_ = render.FreePicture(X, gid)
|
|
||||||
|
|
||||||
_ = compositeString(X, render.PictOpOver, whiteid, pid,
|
|
||||||
0 /* TODO: mask Pictureformat? */, gsid, 0, 0, 100, 100,
|
|
||||||
fmt.Sprintf("%#06x - %#06x", start, end))
|
|
||||||
_ = compositeString(X, render.PictOpOver, whiteid, pid,
|
|
||||||
0 /* TODO: mask Pictureformat? */, gsid, 0, 0, 100, 150,
|
|
||||||
"The quick brown fox jumps over the lazy dog.")
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
ev, xerr := X.WaitForEvent()
|
|
||||||
if xerr != nil {
|
|
||||||
log.Printf("Error: %s\n", xerr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ev == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Event: %s\n", ev)
|
|
||||||
switch e := ev.(type) {
|
|
||||||
case xproto.UnmapNotifyEvent:
|
|
||||||
return
|
|
||||||
|
|
||||||
case xproto.ConfigureNotifyEvent:
|
|
||||||
w, h = e.Width, e.Height
|
|
||||||
recolor()
|
|
||||||
|
|
||||||
case xproto.KeyPressEvent:
|
|
||||||
recolor()
|
|
||||||
gradient()
|
|
||||||
|
|
||||||
case xproto.ExposeEvent:
|
|
||||||
gradient()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user