// The shapes example shows how to draw basic shapes into a window. // It can be considered the Go equivalent of // https://x.org/releases/X11R7.5/doc/libxcb/tutorial/#drawingprim // Four points, a single polyline, two line segments, // two rectangles and two arcs are drawn. // In addition to this, we will also write some text // and fill a rectangle. package main import ( "fmt" "unicode/utf16" "github.com/jezek/xgb" "github.com/jezek/xgb/xproto" ) func main() { X, err := xgb.NewConn() if err != nil { fmt.Println("error connecting to X:", err) return } defer X.Close() setup := xproto.Setup(X) screen := setup.DefaultScreen(X) wid, err := xproto.NewWindowId(X) if err != nil { fmt.Println("error creating window id:", err) return } draw := xproto.Drawable(wid) // for now, we simply draw into the window // Create the window xproto.CreateWindow(X, screen.RootDepth, wid, screen.Root, 0, 0, 180, 200, 8, // X, Y, width, height, *border width* xproto.WindowClassInputOutput, screen.RootVisual, xproto.CwBackPixel|xproto.CwEventMask, []uint32{screen.WhitePixel, xproto.EventMaskStructureNotify | xproto.EventMaskExposure}) // Map the window on the screen xproto.MapWindow(X, wid) // Up to here everything is the same as in the `create-window` example. // We opened a connection, created and mapped the window. // But this time we'll be drawing some basic shapes. // Note how this time the border width is set to 8 instead of 0. // // First of all we need to create a context to draw with. // The graphics context combines all properties (e.g. color, line width, font, fill style, ...) // that should be used to draw something. All available properties // // These properties can be set by or'ing their keys (xproto.Gc*) // and adding the value to the end of the values array. // The order in which the values have to be given corresponds to the order that they defined // mentioned in `xproto`. // // Here we create a new graphics context // which only has the foreground (color) value set to black: foreground, err := xproto.NewGcontextId(X) if err != nil { fmt.Println("error creating foreground context:", err) return } mask := uint32(xproto.GcForeground) values := []uint32{screen.BlackPixel} xproto.CreateGC(X, foreground, draw, mask, values) // It is possible to set the foreground value to something different. // In production, this should use xorg color maps instead for compatibility // but for demonstration setting the color directly also works. // For more information on color maps, see the xcb documentation: // https://x.org/releases/X11R7.5/doc/libxcb/tutorial/#usecolor red, err := xproto.NewGcontextId(X) if err != nil { fmt.Println("error creating red context:", err) return } mask = uint32(xproto.GcForeground) values = []uint32{0xff0000} xproto.CreateGC(X, red, draw, mask, values) // We'll create another graphics context that draws thick lines: thick, err := xproto.NewGcontextId(X) if err != nil { fmt.Println("error creating thick context:", err) return } mask = uint32(xproto.GcLineWidth) values = []uint32{10} xproto.CreateGC(X, thick, draw, mask, values) // It is even possible to set multiple properties at once. // Only remember to put the values in the same order as they're // defined in `xproto`: // Foreground is defined first, so we also set it's value first. // LineWidth comes second. blue, err := xproto.NewGcontextId(X) if err != nil { fmt.Println("error creating blue context:", err) return } mask = uint32(xproto.GcForeground | xproto.GcLineWidth) values = []uint32{0x0000ff, 4} xproto.CreateGC(X, blue, draw, mask, values) // Properties of an already created gc can also be changed // if the original values aren't needed anymore. // In this case, we will change the line width // and cap (line corner) style of our foreground context, // to smooth out the polyline: mask = uint32(xproto.GcLineWidth | xproto.GcCapStyle) values = []uint32{3, xproto.CapStyleRound} xproto.ChangeGC(X, foreground, mask, values) // Writing text needs a bit more setup -- we first have // to open the required font. font, err := xproto.NewFontId(X) if err != nil { fmt.Println("error creating font id:", err) return } // The font identifier that has to be passed to X for opening the font // sets all font properties: // publisher-family-weight-slant-width-adstyl-pxlsz-ptSz-resx-resy-spc-avgWidth-registry-encoding // For all available fonts, install and run xfontsel. // // To load any available font, set all fields to an asterisk. // To specify a font, set one or multiple fields. // This can also be seen in xfontsel -- initially every field is set to *, // however, the more fields are set, the fewer fonts match. // // Using a specific font (e.g. Gnu Unifont) can be as easy as // "-gnu-unifont-*-*-*-*-16-*-*-*-*-*-*-*" // // To load any font that is encoded for usage // with Unicode characters, one would use // fontname := "-*-*-*-*-*-*-14-*-*-*-*-*-iso10646-1" // // For now, we'll simply stick with the fixed font which is available // to every X session: fontname := "-*-fixed-*-*-*-*-14-*-*-*-*-*-*-*" err = xproto.OpenFontChecked(X, font, uint16(len(fontname)), fontname).Check() if err != nil { fmt.Println("failed opening the font:", err) return } // And create a context from it. We simply pass the font's ID to the GcFont property. textCtx, err := xproto.NewGcontextId(X) if err != nil { fmt.Println("error creating text context:", err) return } mask = uint32(xproto.GcForeground | xproto.GcBackground | xproto.GcFont) values = []uint32{screen.BlackPixel, screen.WhitePixel, uint32(font)} xproto.CreateGC(X, textCtx, draw, mask, values) text := convertStringToChar2b("Hellö World!") // Unicode capable! // Close the font handle: xproto.CloseFont(X, font) // After all, writing text is way more comfortable using Xft - it supports TrueType, // and overall better configuration. points := []xproto.Point{ {X: 10, Y: 10}, {X: 20, Y: 10}, {X: 30, Y: 10}, {X: 40, Y: 10}, } // A polyline is essentially a line with multiple points. // The first point is placed absolutely inside the window, // while every other point is placed relative to the one before it. polyline := []xproto.Point{ {X: 50, Y: 10}, {X: 5, Y: 20}, // move 5 to the right, 20 down {X: 25, Y: -20}, // move 25 to the right, 20 up - notice how this point is level again with the first point {X: 10, Y: 10}, // move 10 to the right, 10 down } segments := []xproto.Segment{ {X1: 100, Y1: 10, X2: 140, Y2: 30}, {X1: 110, Y1: 25, X2: 130, Y2: 60}, {X1: 0, Y1: 160, X2: 90, Y2: 100}, } // Rectangles have a start coordinate (upper left) and width and height. rectangles := []xproto.Rectangle{ {X: 10, Y: 50, Width: 40, Height: 20}, {X: 80, Y: 50, Width: 10, Height: 40}, } // This rectangle we will use to demonstrate filling a shape. rectangles2 := []xproto.Rectangle{ {X: 150, Y: 50, Width: 20, Height: 60}, } // Arcs are defined by a top left position (notice where the third line goes to) // their width and height, a starting and end angle. // Angles are defined in units of 1/64 of a single degree, // so we have to multiply the degrees by 64 (or left shift them by 6). arcs := []xproto.Arc{ {X: 10, Y: 100, Width: 60, Height: 40, Angle1: 0 << 6, Angle2: 90 << 6}, {X: 90, Y: 100, Width: 55, Height: 40, Angle1: 20 << 6, Angle2: 270 << 6}, } for { evt, err := X.WaitForEvent() if err != nil { fmt.Println("error reading event:", err) return } else if evt == nil { return } switch evt.(type) { case xproto.ExposeEvent: // Draw the four points we specified earlier. // Notice how we use the `foreground` context to draw them in black. // Also notice how even though we changed the line width to 3, // these still only appear as a single pixel. // To draw points that are bigger than a single pixel, // one has to either fill rectangles, circles or polygons. xproto.PolyPoint(X, xproto.CoordModeOrigin, draw, foreground, points) // Draw the polyline. This time we specified `xproto.CoordModePrevious`, // which means that every point is placed relatively to the previous. // If we were to use `xproto.CoordModeOrigin` instead, // we could specify each point absolutely on the screen. // It is also possible to use `xproto.CoordModePrevious` for drawing *points* // which means that each point would be specified relative to the previous one, // just as we did with the polyline. xproto.PolyLine(X, xproto.CoordModePrevious, draw, foreground, polyline) // Draw two lines in red. xproto.PolySegment(X, draw, red, segments) // Draw two thick rectangles. // The line width only specifies the width of the outline. // Notice how the second rectangle gets completely filled // due to the line width. xproto.PolyRectangle(X, draw, thick, rectangles) // Draw the circular arcs in blue. xproto.PolyArc(X, draw, blue, arcs) // There's also a fill variant for all drawing commands: xproto.PolyFillRectangle(X, draw, red, rectangles2) // Draw the text. Xorg currently knows two ways of specifying text: // a) the (extended) ASCII encoding using ImageText8(..., []byte) // b) UTF16 encoding using ImageText16(..., []Char2b) -- Char2b is // a structure consisting of two bytes. // At the bottom of this example, there are two utility functions that help // convert a go string into an array of Char2b's. xproto.ImageText16(X, byte(len(text)), draw, textCtx, 10, 160, text) case xproto.DestroyNotifyEvent: return } } } // Char2b is defined as // Byte1 byte // Byte2 byte // and is used as a utf16 character. // This function takes a string and converts each rune into a char2b. func convertStringToChar2b(s string) []xproto.Char2b { var chars []xproto.Char2b var p []uint16 for _, r := range []rune(s) { p = utf16.Encode([]rune{r}) if len(p) == 1 { chars = append(chars, convertUint16ToChar2b(p[0])) } else { // If the utf16 representation is larger than 2 bytes // we can not use it and insert a blank instead: chars = append(chars, xproto.Char2b{Byte1: 0, Byte2: 32}) } } return chars } // convertUint16ToChar2b converts a uint16 (which is basically two bytes) // into a Char2b by using the higher 8 bits of u as Byte1 // and the lower 8 bits of u as Byte2. func convertUint16ToChar2b(u uint16) xproto.Char2b { return xproto.Char2b{ Byte1: byte((u & 0xff00) >> 8), Byte2: byte((u & 0x00ff)), } }