forked from BurntSushi/xgb
/
main.go
306 lines (264 loc) · 10.4 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
// 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)),
}
}