1 // The shapes example shows how to draw basic shapes into a window. 2 // It can be considered the Go equivalent of 3 // https://x.org/releases/X11R7.5/doc/libxcb/tutorial/#drawingprim 4 // Four points, a single polyline, two line segments, 5 // two rectangles and two arcs are drawn. 6 // In addition to this, we will also write some text 7 // and fill a rectangle. 8 package main 9 10 import ( 11 "fmt" 12 "unicode/utf16" 13 14 "github.com/jezek/xgb" 15 "github.com/jezek/xgb/xproto" 16 ) 17 18 func main() { 19 X, err := xgb.NewConn() 20 if err != nil { 21 fmt.Println("error connecting to X:", err) 22 return 23 } 24 defer X.Close() 25 26 setup := xproto.Setup(X) 27 screen := setup.DefaultScreen(X) 28 wid, err := xproto.NewWindowId(X) 29 if err != nil { 30 fmt.Println("error creating window id:", err) 31 return 32 } 33 34 draw := xproto.Drawable(wid) // for now, we simply draw into the window 35 36 // Create the window 37 xproto.CreateWindow(X, screen.RootDepth, wid, screen.Root, 38 0, 0, 180, 200, 8, // X, Y, width, height, *border width* 39 xproto.WindowClassInputOutput, screen.RootVisual, 40 xproto.CwBackPixel|xproto.CwEventMask, 41 []uint32{screen.WhitePixel, xproto.EventMaskStructureNotify | xproto.EventMaskExposure}) 42 43 // Map the window on the screen 44 xproto.MapWindow(X, wid) 45 46 // Up to here everything is the same as in the `create-window` example. 47 // We opened a connection, created and mapped the window. 48 // But this time we'll be drawing some basic shapes. 49 // Note how this time the border width is set to 8 instead of 0. 50 // 51 // First of all we need to create a context to draw with. 52 // The graphics context combines all properties (e.g. color, line width, font, fill style, ...) 53 // that should be used to draw something. All available properties 54 // 55 // These properties can be set by or'ing their keys (xproto.Gc*) 56 // and adding the value to the end of the values array. 57 // The order in which the values have to be given corresponds to the order that they defined 58 // mentioned in `xproto`. 59 // 60 // Here we create a new graphics context 61 // which only has the foreground (color) value set to black: 62 foreground, err := xproto.NewGcontextId(X) 63 if err != nil { 64 fmt.Println("error creating foreground context:", err) 65 return 66 } 67 68 mask := uint32(xproto.GcForeground) 69 values := []uint32{screen.BlackPixel} 70 xproto.CreateGC(X, foreground, draw, mask, values) 71 72 // It is possible to set the foreground value to something different. 73 // In production, this should use xorg color maps instead for compatibility 74 // but for demonstration setting the color directly also works. 75 // For more information on color maps, see the xcb documentation: 76 // https://x.org/releases/X11R7.5/doc/libxcb/tutorial/#usecolor 77 red, err := xproto.NewGcontextId(X) 78 if err != nil { 79 fmt.Println("error creating red context:", err) 80 return 81 } 82 83 mask = uint32(xproto.GcForeground) 84 values = []uint32{0xff0000} 85 xproto.CreateGC(X, red, draw, mask, values) 86 87 // We'll create another graphics context that draws thick lines: 88 thick, err := xproto.NewGcontextId(X) 89 if err != nil { 90 fmt.Println("error creating thick context:", err) 91 return 92 } 93 94 mask = uint32(xproto.GcLineWidth) 95 values = []uint32{10} 96 xproto.CreateGC(X, thick, draw, mask, values) 97 98 // It is even possible to set multiple properties at once. 99 // Only remember to put the values in the same order as they're 100 // defined in `xproto`: 101 // Foreground is defined first, so we also set it's value first. 102 // LineWidth comes second. 103 blue, err := xproto.NewGcontextId(X) 104 if err != nil { 105 fmt.Println("error creating blue context:", err) 106 return 107 } 108 109 mask = uint32(xproto.GcForeground | xproto.GcLineWidth) 110 values = []uint32{0x0000ff, 4} 111 xproto.CreateGC(X, blue, draw, mask, values) 112 113 // Properties of an already created gc can also be changed 114 // if the original values aren't needed anymore. 115 // In this case, we will change the line width 116 // and cap (line corner) style of our foreground context, 117 // to smooth out the polyline: 118 mask = uint32(xproto.GcLineWidth | xproto.GcCapStyle) 119 values = []uint32{3, xproto.CapStyleRound} 120 xproto.ChangeGC(X, foreground, mask, values) 121 122 // Writing text needs a bit more setup -- we first have 123 // to open the required font. 124 font, err := xproto.NewFontId(X) 125 if err != nil { 126 fmt.Println("error creating font id:", err) 127 return 128 } 129 130 // The font identifier that has to be passed to X for opening the font 131 // sets all font properties: 132 // publisher-family-weight-slant-width-adstyl-pxlsz-ptSz-resx-resy-spc-avgWidth-registry-encoding 133 // For all available fonts, install and run xfontsel. 134 // 135 // To load any available font, set all fields to an asterisk. 136 // To specify a font, set one or multiple fields. 137 // This can also be seen in xfontsel -- initially every field is set to *, 138 // however, the more fields are set, the fewer fonts match. 139 // 140 // Using a specific font (e.g. Gnu Unifont) can be as easy as 141 // "-gnu-unifont-*-*-*-*-16-*-*-*-*-*-*-*" 142 // 143 // To load any font that is encoded for usage 144 // with Unicode characters, one would use 145 // fontname := "-*-*-*-*-*-*-14-*-*-*-*-*-iso10646-1" 146 // 147 // For now, we'll simply stick with the fixed font which is available 148 // to every X session: 149 fontname := "-*-fixed-*-*-*-*-14-*-*-*-*-*-*-*" 150 err = xproto.OpenFontChecked(X, font, uint16(len(fontname)), fontname).Check() 151 if err != nil { 152 fmt.Println("failed opening the font:", err) 153 return 154 } 155 156 // And create a context from it. We simply pass the font's ID to the GcFont property. 157 textCtx, err := xproto.NewGcontextId(X) 158 if err != nil { 159 fmt.Println("error creating text context:", err) 160 return 161 } 162 163 mask = uint32(xproto.GcForeground | xproto.GcBackground | xproto.GcFont) 164 values = []uint32{screen.BlackPixel, screen.WhitePixel, uint32(font)} 165 xproto.CreateGC(X, textCtx, draw, mask, values) 166 text := convertStringToChar2b("Hellö World!") // Unicode capable! 167 168 // Close the font handle: 169 xproto.CloseFont(X, font) 170 171 // After all, writing text is way more comfortable using Xft - it supports TrueType, 172 // and overall better configuration. 173 174 points := []xproto.Point{ 175 {X: 10, Y: 10}, 176 {X: 20, Y: 10}, 177 {X: 30, Y: 10}, 178 {X: 40, Y: 10}, 179 } 180 181 // A polyline is essentially a line with multiple points. 182 // The first point is placed absolutely inside the window, 183 // while every other point is placed relative to the one before it. 184 polyline := []xproto.Point{ 185 {X: 50, Y: 10}, 186 {X: 5, Y: 20}, // move 5 to the right, 20 down 187 {X: 25, Y: -20}, // move 25 to the right, 20 up - notice how this point is level again with the first point 188 {X: 10, Y: 10}, // move 10 to the right, 10 down 189 } 190 191 segments := []xproto.Segment{ 192 {X1: 100, Y1: 10, X2: 140, Y2: 30}, 193 {X1: 110, Y1: 25, X2: 130, Y2: 60}, 194 {X1: 0, Y1: 160, X2: 90, Y2: 100}, 195 } 196 197 // Rectangles have a start coordinate (upper left) and width and height. 198 rectangles := []xproto.Rectangle{ 199 {X: 10, Y: 50, Width: 40, Height: 20}, 200 {X: 80, Y: 50, Width: 10, Height: 40}, 201 } 202 203 // This rectangle we will use to demonstrate filling a shape. 204 rectangles2 := []xproto.Rectangle{ 205 {X: 150, Y: 50, Width: 20, Height: 60}, 206 } 207 208 // Arcs are defined by a top left position (notice where the third line goes to) 209 // their width and height, a starting and end angle. 210 // Angles are defined in units of 1/64 of a single degree, 211 // so we have to multiply the degrees by 64 (or left shift them by 6). 212 arcs := []xproto.Arc{ 213 {X: 10, Y: 100, Width: 60, Height: 40, Angle1: 0 << 6, Angle2: 90 << 6}, 214 {X: 90, Y: 100, Width: 55, Height: 40, Angle1: 20 << 6, Angle2: 270 << 6}, 215 } 216 217 for { 218 evt, err := X.WaitForEvent() 219 220 if err != nil { 221 fmt.Println("error reading event:", err) 222 return 223 } else if evt == nil { 224 return 225 } 226 227 switch evt.(type) { 228 case xproto.ExposeEvent: 229 // Draw the four points we specified earlier. 230 // Notice how we use the `foreground` context to draw them in black. 231 // Also notice how even though we changed the line width to 3, 232 // these still only appear as a single pixel. 233 // To draw points that are bigger than a single pixel, 234 // one has to either fill rectangles, circles or polygons. 235 xproto.PolyPoint(X, xproto.CoordModeOrigin, draw, foreground, points) 236 237 // Draw the polyline. This time we specified `xproto.CoordModePrevious`, 238 // which means that every point is placed relatively to the previous. 239 // If we were to use `xproto.CoordModeOrigin` instead, 240 // we could specify each point absolutely on the screen. 241 // It is also possible to use `xproto.CoordModePrevious` for drawing *points* 242 // which means that each point would be specified relative to the previous one, 243 // just as we did with the polyline. 244 xproto.PolyLine(X, xproto.CoordModePrevious, draw, foreground, polyline) 245 246 // Draw two lines in red. 247 xproto.PolySegment(X, draw, red, segments) 248 249 // Draw two thick rectangles. 250 // The line width only specifies the width of the outline. 251 // Notice how the second rectangle gets completely filled 252 // due to the line width. 253 xproto.PolyRectangle(X, draw, thick, rectangles) 254 255 // Draw the circular arcs in blue. 256 xproto.PolyArc(X, draw, blue, arcs) 257 258 // There's also a fill variant for all drawing commands: 259 xproto.PolyFillRectangle(X, draw, red, rectangles2) 260 261 // Draw the text. Xorg currently knows two ways of specifying text: 262 // a) the (extended) ASCII encoding using ImageText8(..., []byte) 263 // b) UTF16 encoding using ImageText16(..., []Char2b) -- Char2b is 264 // a structure consisting of two bytes. 265 // At the bottom of this example, there are two utility functions that help 266 // convert a go string into an array of Char2b's. 267 xproto.ImageText16(X, byte(len(text)), draw, textCtx, 10, 160, text) 268 269 case xproto.DestroyNotifyEvent: 270 return 271 } 272 } 273 } 274 275 // Char2b is defined as 276 // Byte1 byte 277 // Byte2 byte 278 // and is used as a utf16 character. 279 // This function takes a string and converts each rune into a char2b. 280 func convertStringToChar2b(s string) []xproto.Char2b { 281 var chars []xproto.Char2b 282 var p []uint16 283 284 for _, r := range []rune(s) { 285 p = utf16.Encode([]rune{r}) 286 if len(p) == 1 { 287 chars = append(chars, convertUint16ToChar2b(p[0])) 288 } else { 289 // If the utf16 representation is larger than 2 bytes 290 // we can not use it and insert a blank instead: 291 chars = append(chars, xproto.Char2b{Byte1: 0, Byte2: 32}) 292 } 293 } 294 295 return chars 296 } 297 298 // convertUint16ToChar2b converts a uint16 (which is basically two bytes) 299 // into a Char2b by using the higher 8 bits of u as Byte1 300 // and the lower 8 bits of u as Byte2. 301 func convertUint16ToChar2b(u uint16) xproto.Char2b { 302 return xproto.Char2b{ 303 Byte1: byte((u & 0xff00) >> 8), 304 Byte2: byte((u & 0x00ff)), 305 } 306 } 307