v / vlib / sokol / sapp / sapp_x11_linux.v
2033 lines · 1936 sloc · 61.96 KB · ddd4ac7880f392ad6c39719af05f01d29eb0ad3a
Raw
1module sapp
2
3// X11 backend implementation for sokol_app.
4// Ports the C _sapp_x11_* functions from sokol_app.h to V.
5
6const x11_max_keycodes = 256
7
8// Keysym-to-unicode lookup table (from GLFW's xkb_unicode.c)
9struct X11Codepair {
10 keysym u16
11 ucs u16
12}
13
14const x11_keysymtab = [
15 X11Codepair{0x01a1, 0x0104},
16 X11Codepair{0x01a2, 0x02d8},
17 X11Codepair{0x01a3, 0x0141},
18 X11Codepair{0x01a5, 0x013d},
19 X11Codepair{0x01a6, 0x015a},
20 X11Codepair{0x01a9, 0x0160},
21 X11Codepair{0x01aa, 0x015e},
22 X11Codepair{0x01ab, 0x0164},
23 X11Codepair{0x01ac, 0x0179},
24 X11Codepair{0x01ae, 0x017d},
25 X11Codepair{0x01af, 0x017b},
26 X11Codepair{0x01b1, 0x0105},
27 X11Codepair{0x01b2, 0x02db},
28 X11Codepair{0x01b3, 0x0142},
29 X11Codepair{0x01b5, 0x013e},
30 X11Codepair{0x01b6, 0x015b},
31 X11Codepair{0x01b7, 0x02c7},
32 X11Codepair{0x01b9, 0x0161},
33 X11Codepair{0x01ba, 0x015f},
34 X11Codepair{0x01bb, 0x0165},
35 X11Codepair{0x01bc, 0x017a},
36 X11Codepair{0x01bd, 0x02dd},
37 X11Codepair{0x01be, 0x017e},
38 X11Codepair{0x01bf, 0x017c},
39 X11Codepair{0x01c0, 0x0154},
40 X11Codepair{0x01c3, 0x0102},
41 X11Codepair{0x01c5, 0x0139},
42 X11Codepair{0x01c6, 0x0106},
43 X11Codepair{0x01c8, 0x010c},
44 X11Codepair{0x01ca, 0x0118},
45 X11Codepair{0x01cc, 0x011a},
46 X11Codepair{0x01cf, 0x010e},
47 X11Codepair{0x01d0, 0x0110},
48 X11Codepair{0x01d1, 0x0143},
49 X11Codepair{0x01d2, 0x0147},
50 X11Codepair{0x01d5, 0x0150},
51 X11Codepair{0x01d8, 0x0158},
52 X11Codepair{0x01d9, 0x016e},
53 X11Codepair{0x01db, 0x0170},
54 X11Codepair{0x01de, 0x0162},
55 X11Codepair{0x01e0, 0x0155},
56 X11Codepair{0x01e3, 0x0103},
57 X11Codepair{0x01e5, 0x013a},
58 X11Codepair{0x01e6, 0x0107},
59 X11Codepair{0x01e8, 0x010d},
60 X11Codepair{0x01ea, 0x0119},
61 X11Codepair{0x01ec, 0x011b},
62 X11Codepair{0x01ef, 0x010f},
63 X11Codepair{0x01f0, 0x0111},
64 X11Codepair{0x01f1, 0x0144},
65 X11Codepair{0x01f2, 0x0148},
66 X11Codepair{0x01f5, 0x0151},
67 X11Codepair{0x01f8, 0x0159},
68 X11Codepair{0x01f9, 0x016f},
69 X11Codepair{0x01fb, 0x0171},
70 X11Codepair{0x01fe, 0x0163},
71 X11Codepair{0x01ff, 0x02d9},
72 X11Codepair{0x02a1, 0x0126},
73 X11Codepair{0x02a6, 0x0124},
74 X11Codepair{0x02a9, 0x0130},
75 X11Codepair{0x02ab, 0x011e},
76 X11Codepair{0x02ac, 0x0134},
77 X11Codepair{0x02b1, 0x0127},
78 X11Codepair{0x02b6, 0x0125},
79 X11Codepair{0x02b9, 0x0131},
80 X11Codepair{0x02bb, 0x011f},
81 X11Codepair{0x02bc, 0x0135},
82 X11Codepair{0x02c5, 0x010a},
83 X11Codepair{0x02c6, 0x0108},
84 X11Codepair{0x02d5, 0x0120},
85 X11Codepair{0x02d8, 0x011c},
86 X11Codepair{0x02dd, 0x016c},
87 X11Codepair{0x02de, 0x015c},
88 X11Codepair{0x02e5, 0x010b},
89 X11Codepair{0x02e6, 0x0109},
90 X11Codepair{0x02f5, 0x0121},
91 X11Codepair{0x02f8, 0x011d},
92 X11Codepair{0x02fd, 0x016d},
93 X11Codepair{0x02fe, 0x015d},
94 X11Codepair{0x03a2, 0x0138},
95 X11Codepair{0x03a3, 0x0156},
96 X11Codepair{0x03a5, 0x0128},
97 X11Codepair{0x03a6, 0x013b},
98 X11Codepair{0x03aa, 0x0112},
99 X11Codepair{0x03ab, 0x0122},
100 X11Codepair{0x03ac, 0x0166},
101 X11Codepair{0x03b3, 0x0157},
102 X11Codepair{0x03b5, 0x0129},
103 X11Codepair{0x03b6, 0x013c},
104 X11Codepair{0x03ba, 0x0113},
105 X11Codepair{0x03bb, 0x0123},
106 X11Codepair{0x03bc, 0x0167},
107 X11Codepair{0x03bd, 0x014a},
108 X11Codepair{0x03bf, 0x014b},
109 X11Codepair{0x03c0, 0x0100},
110 X11Codepair{0x03c7, 0x012e},
111 X11Codepair{0x03cc, 0x0116},
112 X11Codepair{0x03cf, 0x012a},
113 X11Codepair{0x03d1, 0x0145},
114 X11Codepair{0x03d2, 0x014c},
115 X11Codepair{0x03d3, 0x0136},
116 X11Codepair{0x03d9, 0x0172},
117 X11Codepair{0x03dd, 0x0168},
118 X11Codepair{0x03de, 0x016a},
119 X11Codepair{0x03e0, 0x0101},
120 X11Codepair{0x03e7, 0x012f},
121 X11Codepair{0x03ec, 0x0117},
122 X11Codepair{0x03ef, 0x012b},
123 X11Codepair{0x03f1, 0x0146},
124 X11Codepair{0x03f2, 0x014d},
125 X11Codepair{0x03f3, 0x0137},
126 X11Codepair{0x03f9, 0x0173},
127 X11Codepair{0x03fd, 0x0169},
128 X11Codepair{0x03fe, 0x016b},
129 X11Codepair{0x047e, 0x203e},
130 X11Codepair{0x04a1, 0x3002},
131 X11Codepair{0x04a2, 0x300c},
132 X11Codepair{0x04a3, 0x300d},
133 X11Codepair{0x04a4, 0x3001},
134 X11Codepair{0x04a5, 0x30fb},
135 X11Codepair{0x04a6, 0x30f2},
136 X11Codepair{0x04a7, 0x30a1},
137 X11Codepair{0x04a8, 0x30a3},
138 X11Codepair{0x04a9, 0x30a5},
139 X11Codepair{0x04aa, 0x30a7},
140 X11Codepair{0x04ab, 0x30a9},
141 X11Codepair{0x04ac, 0x30e3},
142 X11Codepair{0x04ad, 0x30e5},
143 X11Codepair{0x04ae, 0x30e7},
144 X11Codepair{0x04af, 0x30c3},
145 X11Codepair{0x04b0, 0x30fc},
146 X11Codepair{0x04b1, 0x30a2},
147 X11Codepair{0x04b2, 0x30a4},
148 X11Codepair{0x04b3, 0x30a6},
149 X11Codepair{0x04b4, 0x30a8},
150 X11Codepair{0x04b5, 0x30aa},
151 X11Codepair{0x04b6, 0x30ab},
152 X11Codepair{0x04b7, 0x30ad},
153 X11Codepair{0x04b8, 0x30af},
154 X11Codepair{0x04b9, 0x30b1},
155 X11Codepair{0x04ba, 0x30b3},
156 X11Codepair{0x04bb, 0x30b5},
157 X11Codepair{0x04bc, 0x30b7},
158 X11Codepair{0x04bd, 0x30b9},
159 X11Codepair{0x04be, 0x30bb},
160 X11Codepair{0x04bf, 0x30bd},
161 X11Codepair{0x04c0, 0x30bf},
162 X11Codepair{0x04c1, 0x30c1},
163 X11Codepair{0x04c2, 0x30c4},
164 X11Codepair{0x04c3, 0x30c6},
165 X11Codepair{0x04c4, 0x30c8},
166 X11Codepair{0x04c5, 0x30ca},
167 X11Codepair{0x04c6, 0x30cb},
168 X11Codepair{0x04c7, 0x30cc},
169 X11Codepair{0x04c8, 0x30cd},
170 X11Codepair{0x04c9, 0x30ce},
171 X11Codepair{0x04ca, 0x30cf},
172 X11Codepair{0x04cb, 0x30d2},
173 X11Codepair{0x04cc, 0x30d5},
174 X11Codepair{0x04cd, 0x30d8},
175 X11Codepair{0x04ce, 0x30db},
176 X11Codepair{0x04cf, 0x30de},
177 X11Codepair{0x04d0, 0x30df},
178 X11Codepair{0x04d1, 0x30e0},
179 X11Codepair{0x04d2, 0x30e1},
180 X11Codepair{0x04d3, 0x30e2},
181 X11Codepair{0x04d4, 0x30e4},
182 X11Codepair{0x04d5, 0x30e6},
183 X11Codepair{0x04d6, 0x30e8},
184 X11Codepair{0x04d7, 0x30e9},
185 X11Codepair{0x04d8, 0x30ea},
186 X11Codepair{0x04d9, 0x30eb},
187 X11Codepair{0x04da, 0x30ec},
188 X11Codepair{0x04db, 0x30ed},
189 X11Codepair{0x04dc, 0x30ef},
190 X11Codepair{0x04dd, 0x30f3},
191 X11Codepair{0x04de, 0x309b},
192 X11Codepair{0x04df, 0x309c},
193 X11Codepair{0x05ac, 0x060c},
194 X11Codepair{0x05bb, 0x061b},
195 X11Codepair{0x05bf, 0x061f},
196 X11Codepair{0x05c1, 0x0621},
197 X11Codepair{0x05c2, 0x0622},
198 X11Codepair{0x05c3, 0x0623},
199 X11Codepair{0x05c4, 0x0624},
200 X11Codepair{0x05c5, 0x0625},
201 X11Codepair{0x05c6, 0x0626},
202 X11Codepair{0x05c7, 0x0627},
203 X11Codepair{0x05c8, 0x0628},
204 X11Codepair{0x05c9, 0x0629},
205 X11Codepair{0x05ca, 0x062a},
206 X11Codepair{0x05cb, 0x062b},
207 X11Codepair{0x05cc, 0x062c},
208 X11Codepair{0x05cd, 0x062d},
209 X11Codepair{0x05ce, 0x062e},
210 X11Codepair{0x05cf, 0x062f},
211 X11Codepair{0x05d0, 0x0630},
212 X11Codepair{0x05d1, 0x0631},
213 X11Codepair{0x05d2, 0x0632},
214 X11Codepair{0x05d3, 0x0633},
215 X11Codepair{0x05d4, 0x0634},
216 X11Codepair{0x05d5, 0x0635},
217 X11Codepair{0x05d6, 0x0636},
218 X11Codepair{0x05d7, 0x0637},
219 X11Codepair{0x05d8, 0x0638},
220 X11Codepair{0x05d9, 0x0639},
221 X11Codepair{0x05da, 0x063a},
222 X11Codepair{0x05e0, 0x0640},
223 X11Codepair{0x05e1, 0x0641},
224 X11Codepair{0x05e2, 0x0642},
225 X11Codepair{0x05e3, 0x0643},
226 X11Codepair{0x05e4, 0x0644},
227 X11Codepair{0x05e5, 0x0645},
228 X11Codepair{0x05e6, 0x0646},
229 X11Codepair{0x05e7, 0x0647},
230 X11Codepair{0x05e8, 0x0648},
231 X11Codepair{0x05e9, 0x0649},
232 X11Codepair{0x05ea, 0x064a},
233 X11Codepair{0x05eb, 0x064b},
234 X11Codepair{0x05ec, 0x064c},
235 X11Codepair{0x05ed, 0x064d},
236 X11Codepair{0x05ee, 0x064e},
237 X11Codepair{0x05ef, 0x064f},
238 X11Codepair{0x05f0, 0x0650},
239 X11Codepair{0x05f1, 0x0651},
240 X11Codepair{0x05f2, 0x0652},
241 X11Codepair{0x06a1, 0x0452},
242 X11Codepair{0x06a2, 0x0453},
243 X11Codepair{0x06a3, 0x0451},
244 X11Codepair{0x06a4, 0x0454},
245 X11Codepair{0x06a5, 0x0455},
246 X11Codepair{0x06a6, 0x0456},
247 X11Codepair{0x06a7, 0x0457},
248 X11Codepair{0x06a8, 0x0458},
249 X11Codepair{0x06a9, 0x0459},
250 X11Codepair{0x06aa, 0x045a},
251 X11Codepair{0x06ab, 0x045b},
252 X11Codepair{0x06ac, 0x045c},
253 X11Codepair{0x06ae, 0x045e},
254 X11Codepair{0x06af, 0x045f},
255 X11Codepair{0x06b0, 0x2116},
256 X11Codepair{0x06b1, 0x0402},
257 X11Codepair{0x06b2, 0x0403},
258 X11Codepair{0x06b3, 0x0401},
259 X11Codepair{0x06b4, 0x0404},
260 X11Codepair{0x06b5, 0x0405},
261 X11Codepair{0x06b6, 0x0406},
262 X11Codepair{0x06b7, 0x0407},
263 X11Codepair{0x06b8, 0x0408},
264 X11Codepair{0x06b9, 0x0409},
265 X11Codepair{0x06ba, 0x040a},
266 X11Codepair{0x06bb, 0x040b},
267 X11Codepair{0x06bc, 0x040c},
268 X11Codepair{0x06be, 0x040e},
269 X11Codepair{0x06bf, 0x040f},
270 X11Codepair{0x06c0, 0x044e},
271 X11Codepair{0x06c1, 0x0430},
272 X11Codepair{0x06c2, 0x0431},
273 X11Codepair{0x06c3, 0x0446},
274 X11Codepair{0x06c4, 0x0434},
275 X11Codepair{0x06c5, 0x0435},
276 X11Codepair{0x06c6, 0x0444},
277 X11Codepair{0x06c7, 0x0433},
278 X11Codepair{0x06c8, 0x0445},
279 X11Codepair{0x06c9, 0x0438},
280 X11Codepair{0x06ca, 0x0439},
281 X11Codepair{0x06cb, 0x043a},
282 X11Codepair{0x06cc, 0x043b},
283 X11Codepair{0x06cd, 0x043c},
284 X11Codepair{0x06ce, 0x043d},
285 X11Codepair{0x06cf, 0x043e},
286 X11Codepair{0x06d0, 0x043f},
287 X11Codepair{0x06d1, 0x044f},
288 X11Codepair{0x06d2, 0x0440},
289 X11Codepair{0x06d3, 0x0441},
290 X11Codepair{0x06d4, 0x0442},
291 X11Codepair{0x06d5, 0x0443},
292 X11Codepair{0x06d6, 0x0436},
293 X11Codepair{0x06d7, 0x0432},
294 X11Codepair{0x06d8, 0x044c},
295 X11Codepair{0x06d9, 0x044b},
296 X11Codepair{0x06da, 0x0437},
297 X11Codepair{0x06db, 0x0448},
298 X11Codepair{0x06dc, 0x044d},
299 X11Codepair{0x06dd, 0x0449},
300 X11Codepair{0x06de, 0x0447},
301 X11Codepair{0x06df, 0x044a},
302 X11Codepair{0x06e0, 0x042e},
303 X11Codepair{0x06e1, 0x0410},
304 X11Codepair{0x06e2, 0x0411},
305 X11Codepair{0x06e3, 0x0426},
306 X11Codepair{0x06e4, 0x0414},
307 X11Codepair{0x06e5, 0x0415},
308 X11Codepair{0x06e6, 0x0424},
309 X11Codepair{0x06e7, 0x0413},
310 X11Codepair{0x06e8, 0x0425},
311 X11Codepair{0x06e9, 0x0418},
312 X11Codepair{0x06ea, 0x0419},
313 X11Codepair{0x06eb, 0x041a},
314 X11Codepair{0x06ec, 0x041b},
315 X11Codepair{0x06ed, 0x041c},
316 X11Codepair{0x06ee, 0x041d},
317 X11Codepair{0x06ef, 0x041e},
318 X11Codepair{0x06f0, 0x041f},
319 X11Codepair{0x06f1, 0x042f},
320 X11Codepair{0x06f2, 0x0420},
321 X11Codepair{0x06f3, 0x0421},
322 X11Codepair{0x06f4, 0x0422},
323 X11Codepair{0x06f5, 0x0423},
324 X11Codepair{0x06f6, 0x0416},
325 X11Codepair{0x06f7, 0x0412},
326 X11Codepair{0x06f8, 0x042c},
327 X11Codepair{0x06f9, 0x042b},
328 X11Codepair{0x06fa, 0x0417},
329 X11Codepair{0x06fb, 0x0428},
330 X11Codepair{0x06fc, 0x042d},
331 X11Codepair{0x06fd, 0x0429},
332 X11Codepair{0x06fe, 0x0427},
333 X11Codepair{0x06ff, 0x042a},
334 X11Codepair{0x07a1, 0x0386},
335 X11Codepair{0x07a2, 0x0388},
336 X11Codepair{0x07a3, 0x0389},
337 X11Codepair{0x07a4, 0x038a},
338 X11Codepair{0x07a5, 0x03aa},
339 X11Codepair{0x07a7, 0x038c},
340 X11Codepair{0x07a8, 0x038e},
341 X11Codepair{0x07a9, 0x03ab},
342 X11Codepair{0x07ab, 0x038f},
343 X11Codepair{0x07ae, 0x0385},
344 X11Codepair{0x07af, 0x2015},
345 X11Codepair{0x07b1, 0x03ac},
346 X11Codepair{0x07b2, 0x03ad},
347 X11Codepair{0x07b3, 0x03ae},
348 X11Codepair{0x07b4, 0x03af},
349 X11Codepair{0x07b5, 0x03ca},
350 X11Codepair{0x07b6, 0x0390},
351 X11Codepair{0x07b7, 0x03cc},
352 X11Codepair{0x07b8, 0x03cd},
353 X11Codepair{0x07b9, 0x03cb},
354 X11Codepair{0x07ba, 0x03b0},
355 X11Codepair{0x07bb, 0x03ce},
356 X11Codepair{0x07c1, 0x0391},
357 X11Codepair{0x07c2, 0x0392},
358 X11Codepair{0x07c3, 0x0393},
359 X11Codepair{0x07c4, 0x0394},
360 X11Codepair{0x07c5, 0x0395},
361 X11Codepair{0x07c6, 0x0396},
362 X11Codepair{0x07c7, 0x0397},
363 X11Codepair{0x07c8, 0x0398},
364 X11Codepair{0x07c9, 0x0399},
365 X11Codepair{0x07ca, 0x039a},
366 X11Codepair{0x07cb, 0x039b},
367 X11Codepair{0x07cc, 0x039c},
368 X11Codepair{0x07cd, 0x039d},
369 X11Codepair{0x07ce, 0x039e},
370 X11Codepair{0x07cf, 0x039f},
371 X11Codepair{0x07d0, 0x03a0},
372 X11Codepair{0x07d1, 0x03a1},
373 X11Codepair{0x07d2, 0x03a3},
374 X11Codepair{0x07d4, 0x03a4},
375 X11Codepair{0x07d5, 0x03a5},
376 X11Codepair{0x07d6, 0x03a6},
377 X11Codepair{0x07d7, 0x03a7},
378 X11Codepair{0x07d8, 0x03a8},
379 X11Codepair{0x07d9, 0x03a9},
380 X11Codepair{0x07e1, 0x03b1},
381 X11Codepair{0x07e2, 0x03b2},
382 X11Codepair{0x07e3, 0x03b3},
383 X11Codepair{0x07e4, 0x03b4},
384 X11Codepair{0x07e5, 0x03b5},
385 X11Codepair{0x07e6, 0x03b6},
386 X11Codepair{0x07e7, 0x03b7},
387 X11Codepair{0x07e8, 0x03b8},
388 X11Codepair{0x07e9, 0x03b9},
389 X11Codepair{0x07ea, 0x03ba},
390 X11Codepair{0x07eb, 0x03bb},
391 X11Codepair{0x07ec, 0x03bc},
392 X11Codepair{0x07ed, 0x03bd},
393 X11Codepair{0x07ee, 0x03be},
394 X11Codepair{0x07ef, 0x03bf},
395 X11Codepair{0x07f0, 0x03c0},
396 X11Codepair{0x07f1, 0x03c1},
397 X11Codepair{0x07f2, 0x03c3},
398 X11Codepair{0x07f3, 0x03c2},
399 X11Codepair{0x07f4, 0x03c4},
400 X11Codepair{0x07f5, 0x03c5},
401 X11Codepair{0x07f6, 0x03c6},
402 X11Codepair{0x07f7, 0x03c7},
403 X11Codepair{0x07f8, 0x03c8},
404 X11Codepair{0x07f9, 0x03c9},
405 X11Codepair{0x0cdf, 0x2017},
406 X11Codepair{0x0ce0, 0x05d0},
407 X11Codepair{0x0ce1, 0x05d1},
408 X11Codepair{0x0ce2, 0x05d2},
409 X11Codepair{0x0ce3, 0x05d3},
410 X11Codepair{0x0ce4, 0x05d4},
411 X11Codepair{0x0ce5, 0x05d5},
412 X11Codepair{0x0ce6, 0x05d6},
413 X11Codepair{0x0ce7, 0x05d7},
414 X11Codepair{0x0ce8, 0x05d8},
415 X11Codepair{0x0ce9, 0x05d9},
416 X11Codepair{0x0cea, 0x05da},
417 X11Codepair{0x0ceb, 0x05db},
418 X11Codepair{0x0cec, 0x05dc},
419 X11Codepair{0x0ced, 0x05dd},
420 X11Codepair{0x0cee, 0x05de},
421 X11Codepair{0x0cef, 0x05df},
422 X11Codepair{0x0cf0, 0x05e0},
423 X11Codepair{0x0cf1, 0x05e1},
424 X11Codepair{0x0cf2, 0x05e2},
425 X11Codepair{0x0cf3, 0x05e3},
426 X11Codepair{0x0cf4, 0x05e4},
427 X11Codepair{0x0cf5, 0x05e5},
428 X11Codepair{0x0cf6, 0x05e6},
429 X11Codepair{0x0cf7, 0x05e7},
430 X11Codepair{0x0cf8, 0x05e8},
431 X11Codepair{0x0cf9, 0x05e9},
432 X11Codepair{0x0cfa, 0x05ea},
433 X11Codepair{0x13a4, 0x20ac},
434 X11Codepair{0x13bc, 0x0152},
435 X11Codepair{0x13bd, 0x0153},
436 X11Codepair{0x13be, 0x0178},
437 X11Codepair{0x20ac, 0x20ac},
438 X11Codepair{0xfe50, 0x0060},
439 X11Codepair{0xfe51, 0x00b4},
440 X11Codepair{0xfe52, 0x005e},
441 X11Codepair{0xfe53, 0x007e},
442 X11Codepair{0xfe54, 0x00af},
443 X11Codepair{0xfe55, 0x02d8},
444 X11Codepair{0xfe56, 0x02d9},
445 X11Codepair{0xfe57, 0x00a8},
446 X11Codepair{0xfe58, 0x02da},
447 X11Codepair{0xfe59, 0x02dd},
448 X11Codepair{0xfe5a, 0x02c7},
449 X11Codepair{0xfe5b, 0x00b8},
450 X11Codepair{0xfe5c, 0x02db},
451 X11Codepair{0xfe5d, 0x037a},
452 X11Codepair{0xfe5e, 0x309b},
453 X11Codepair{0xfe5f, 0x309c},
454 X11Codepair{0xfe63, 0x002f},
455 X11Codepair{0xfe64, 0x02bc},
456 X11Codepair{0xfe65, 0x02bd},
457 X11Codepair{0xfe66, 0x02f5},
458 X11Codepair{0xfe67, 0x02f3},
459 X11Codepair{0xfe68, 0x02cd},
460 X11Codepair{0xfe69, 0xa788},
461 X11Codepair{0xfe6a, 0x02f7},
462 X11Codepair{0xfe6e, 0x002c},
463 X11Codepair{0xfe6f, 0x00a4},
464 X11Codepair{0xfe80, 0x0061},
465 X11Codepair{0xfe81, 0x0041},
466 X11Codepair{0xfe82, 0x0065},
467 X11Codepair{0xfe83, 0x0045},
468 X11Codepair{0xfe84, 0x0069},
469 X11Codepair{0xfe85, 0x0049},
470 X11Codepair{0xfe86, 0x006f},
471 X11Codepair{0xfe87, 0x004f},
472 X11Codepair{0xfe88, 0x0075},
473 X11Codepair{0xfe89, 0x0055},
474 X11Codepair{0xfe8a, 0x0259},
475 X11Codepair{0xfe8b, 0x018f},
476 X11Codepair{0xfe8c, 0x00b5},
477 X11Codepair{0xfe90, 0x005f},
478 X11Codepair{0xfe91, 0x02c8},
479 X11Codepair{0xfe92, 0x02cc},
480 X11Codepair{0xff80, 0x0020},
481 X11Codepair{0xff95, 0x0037},
482 X11Codepair{0xff96, 0x0034},
483 X11Codepair{0xff97, 0x0038},
484 X11Codepair{0xff98, 0x0036},
485 X11Codepair{0xff99, 0x0032},
486 X11Codepair{0xff9a, 0x0039},
487 X11Codepair{0xff9b, 0x0033},
488 X11Codepair{0xff9c, 0x0031},
489 X11Codepair{0xff9d, 0x0035},
490 X11Codepair{0xff9e, 0x0030},
491 X11Codepair{0xffaa, 0x002a},
492 X11Codepair{0xffab, 0x002b},
493 X11Codepair{0xffac, 0x002c},
494 X11Codepair{0xffad, 0x002d},
495 X11Codepair{0xffae, 0x002e},
496 X11Codepair{0xffaf, 0x002f},
497 X11Codepair{0xffb0, 0x0030},
498 X11Codepair{0xffb1, 0x0031},
499 X11Codepair{0xffb2, 0x0032},
500 X11Codepair{0xffb3, 0x0033},
501 X11Codepair{0xffb4, 0x0034},
502 X11Codepair{0xffb5, 0x0035},
503 X11Codepair{0xffb6, 0x0036},
504 X11Codepair{0xffb7, 0x0037},
505 X11Codepair{0xffb8, 0x0038},
506 X11Codepair{0xffb9, 0x0039},
507 X11Codepair{0xffbd, 0x003d},
508]!
509
510// === Error handling ===
511
512fn x11_grab_error_handler() {
513 g_sapp_state.x11.error_code = 0 // Success
514 C.XSetErrorHandler(voidptr(x11_error_handler))
515}
516
517fn x11_release_error_handler() {
518 C.XSync(g_sapp_state.x11.display, x_false)
519 C.XSetErrorHandler(unsafe { nil })
520}
521
522fn x11_error_handler(_display &C.Display, _event voidptr) int {
523 // XErrorEvent.error_code is at a known offset
524 // For simplicity, just set a non-zero error code
525 g_sapp_state.x11.error_code = 1
526 return 0
527}
528
529// === Atom initialization ===
530
531fn x11_init_extensions() {
532 d := g_sapp_state.x11.display
533 g_sapp_state.x11.utf8_string = C.XInternAtom(d, c'UTF8_STRING', x_false)
534 g_sapp_state.x11.wm_protocols = C.XInternAtom(d, c'WM_PROTOCOLS', x_false)
535 g_sapp_state.x11.wm_delete_window = C.XInternAtom(d, c'WM_DELETE_WINDOW', x_false)
536 g_sapp_state.x11.wm_state = C.XInternAtom(d, c'WM_STATE', x_false)
537 g_sapp_state.x11.net_wm_name = C.XInternAtom(d, c'_NET_WM_NAME', x_false)
538 g_sapp_state.x11.net_wm_icon_name = C.XInternAtom(d, c'_NET_WM_ICON_NAME', x_false)
539 g_sapp_state.x11.net_wm_icon = C.XInternAtom(d, c'_NET_WM_ICON', x_false)
540 g_sapp_state.x11.net_wm_state = C.XInternAtom(d, c'_NET_WM_STATE', x_false)
541 g_sapp_state.x11.net_wm_state_fullscreen = C.XInternAtom(d, c'_NET_WM_STATE_FULLSCREEN',
542 x_false)
543 g_sapp_state.x11.clipboard_atom = C.XInternAtom(d, c'CLIPBOARD', x_false)
544 g_sapp_state.x11.targets = C.XInternAtom(d, c'TARGETS', x_false)
545 if g_sapp_state.drop.enabled {
546 g_sapp_state.x11.xdnd.xdnd_aware = C.XInternAtom(d, c'XdndAware', x_false)
547 g_sapp_state.x11.xdnd.xdnd_enter = C.XInternAtom(d, c'XdndEnter', x_false)
548 g_sapp_state.x11.xdnd.xdnd_position = C.XInternAtom(d, c'XdndPosition', x_false)
549 g_sapp_state.x11.xdnd.xdnd_status = C.XInternAtom(d, c'XdndStatus', x_false)
550 g_sapp_state.x11.xdnd.xdnd_action_copy = C.XInternAtom(d, c'XdndActionCopy', x_false)
551 g_sapp_state.x11.xdnd.xdnd_drop = C.XInternAtom(d, c'XdndDrop', x_false)
552 g_sapp_state.x11.xdnd.xdnd_finished = C.XInternAtom(d, c'XdndFinished', x_false)
553 g_sapp_state.x11.xdnd.xdnd_selection = C.XInternAtom(d, c'XdndSelection', x_false)
554 g_sapp_state.x11.xdnd.xdnd_type_list = C.XInternAtom(d, c'XdndTypeList', x_false)
555 g_sapp_state.x11.xdnd.text_uri_list = C.XInternAtom(d, c'text/uri-list', x_false)
556 }
557 // Check Xi extension for raw mouse input
558 if C.XQueryExtension(d, c'XInputExtension', &g_sapp_state.x11.xi.major_opcode,
559 &g_sapp_state.x11.xi.event_base, &g_sapp_state.x11.xi.error_base) != 0 {
560 g_sapp_state.x11.xi.major = 2
561 g_sapp_state.x11.xi.minor = 0
562 if C.XIQueryVersion(d, &g_sapp_state.x11.xi.major, &g_sapp_state.x11.xi.minor) == 0 {
563 g_sapp_state.x11.xi.available = true
564 }
565 }
566}
567
568// === Key translation ===
569
570fn x11_translate_keysyms(keysyms &KeySym, width int) KeyCode {
571 if width > 1 {
572 unsafe {
573 match keysyms[1] {
574 xk_kp_0 { return .kp_0 }
575 xk_kp_1 { return .kp_1 }
576 xk_kp_2 { return .kp_2 }
577 xk_kp_3 { return .kp_3 }
578 xk_kp_4 { return .kp_4 }
579 xk_kp_5 { return .kp_5 }
580 xk_kp_6 { return .kp_6 }
581 xk_kp_7 { return .kp_7 }
582 xk_kp_8 { return .kp_8 }
583 xk_kp_9 { return .kp_9 }
584 xk_kp_separator, xk_kp_decimal { return .kp_decimal }
585 xk_kp_equal { return .kp_equal }
586 xk_kp_enter { return .kp_enter }
587 else {}
588 }
589 }
590 }
591 unsafe {
592 return match keysyms[0] {
593 xk_escape { KeyCode.escape }
594 xk_tab { KeyCode.tab }
595 xk_shift_l { KeyCode.left_shift }
596 xk_shift_r { KeyCode.right_shift }
597 xk_control_l { KeyCode.left_control }
598 xk_control_r { KeyCode.right_control }
599 xk_meta_l, xk_alt_l { KeyCode.left_alt }
600 xk_mode_switch, xk_iso_level3_shift, xk_meta_r, xk_alt_r { KeyCode.right_alt }
601 xk_super_l { KeyCode.left_super }
602 xk_super_r { KeyCode.right_super }
603 xk_menu { KeyCode.menu }
604 xk_num_lock { KeyCode.num_lock }
605 xk_caps_lock { KeyCode.caps_lock }
606 xk_print { KeyCode.print_screen }
607 xk_scroll_lock { KeyCode.scroll_lock }
608 xk_pause { KeyCode.pause }
609 xk_delete { KeyCode.delete }
610 xk_backspace { KeyCode.backspace }
611 xk_return { KeyCode.enter }
612 xk_home { KeyCode.home }
613 xk_end { KeyCode.end }
614 xk_page_up { KeyCode.page_up }
615 xk_page_down { KeyCode.page_down }
616 xk_insert { KeyCode.insert }
617 xk_left { KeyCode.left }
618 xk_right { KeyCode.right }
619 xk_down { KeyCode.down }
620 xk_up { KeyCode.up }
621 xk_f1 { KeyCode.f1 }
622 xk_f2 { KeyCode.f2 }
623 xk_f3 { KeyCode.f3 }
624 xk_f4 { KeyCode.f4 }
625 xk_f5 { KeyCode.f5 }
626 xk_f6 { KeyCode.f6 }
627 xk_f7 { KeyCode.f7 }
628 xk_f8 { KeyCode.f8 }
629 xk_f9 { KeyCode.f9 }
630 xk_f10 { KeyCode.f10 }
631 xk_f11 { KeyCode.f11 }
632 xk_f12 { KeyCode.f12 }
633 xk_f13 { KeyCode.f13 }
634 xk_f14 { KeyCode.f14 }
635 xk_f15 { KeyCode.f15 }
636 xk_f16 { KeyCode.f16 }
637 xk_f17 { KeyCode.f17 }
638 xk_f18 { KeyCode.f18 }
639 xk_f19 { KeyCode.f19 }
640 xk_f20 { KeyCode.f20 }
641 xk_f21 { KeyCode.f21 }
642 xk_f22 { KeyCode.f22 }
643 xk_f23 { KeyCode.f23 }
644 xk_f24 { KeyCode.f24 }
645 xk_f25 { KeyCode.f25 }
646 xk_kp_divide { KeyCode.kp_divide }
647 xk_kp_multiply { KeyCode.kp_multiply }
648 xk_kp_subtract { KeyCode.kp_subtract }
649 xk_kp_add { KeyCode.kp_add }
650 xk_kp_insert { KeyCode.kp_0 }
651 xk_kp_end { KeyCode.kp_1 }
652 xk_kp_down { KeyCode.kp_2 }
653 xk_kp_page_down { KeyCode.kp_3 }
654 xk_kp_left { KeyCode.kp_4 }
655 xk_kp_right { KeyCode.kp_6 }
656 xk_kp_home { KeyCode.kp_7 }
657 xk_kp_up { KeyCode.kp_8 }
658 xk_kp_page_up { KeyCode.kp_9 }
659 xk_kp_delete { KeyCode.kp_decimal }
660 xk_kp_equal { KeyCode.kp_equal }
661 xk_kp_enter { KeyCode.kp_enter }
662 xk_a { KeyCode.a }
663 xk_b { KeyCode.b }
664 xk_c { KeyCode.c }
665 xk_d { KeyCode.d }
666 xk_e { KeyCode.e }
667 xk_f { KeyCode.f }
668 xk_g { KeyCode.g }
669 xk_h { KeyCode.h }
670 xk_i { KeyCode.i }
671 xk_j { KeyCode.j }
672 xk_k { KeyCode.k }
673 xk_l { KeyCode.l }
674 xk_m { KeyCode.m }
675 xk_n { KeyCode.n }
676 xk_o { KeyCode.o }
677 xk_p { KeyCode.p }
678 xk_q { KeyCode.q }
679 xk_r { KeyCode.r }
680 xk_s { KeyCode.s }
681 xk_t { KeyCode.t }
682 xk_u { KeyCode.u }
683 xk_v { KeyCode.v }
684 xk_w { KeyCode.w }
685 xk_x { KeyCode.x }
686 xk_y { KeyCode.y }
687 xk_z { KeyCode.z }
688 xk_1 { KeyCode._1 }
689 xk_2 { KeyCode._2 }
690 xk_3 { KeyCode._3 }
691 xk_4 { KeyCode._4 }
692 xk_5 { KeyCode._5 }
693 xk_6 { KeyCode._6 }
694 xk_7 { KeyCode._7 }
695 xk_8 { KeyCode._8 }
696 xk_9 { KeyCode._9 }
697 xk_0 { KeyCode._0 }
698 xk_space { KeyCode.space }
699 xk_minus { KeyCode.minus }
700 xk_equal { KeyCode.equal }
701 xk_bracketleft { KeyCode.left_bracket }
702 xk_bracketright { KeyCode.right_bracket }
703 xk_backslash { KeyCode.backslash }
704 xk_semicolon { KeyCode.semicolon }
705 xk_apostrophe { KeyCode.apostrophe }
706 xk_grave { KeyCode.grave_accent }
707 xk_comma { KeyCode.comma }
708 xk_period { KeyCode.period }
709 xk_slash { KeyCode.slash }
710 xk_less { KeyCode.world_1 }
711 else { KeyCode.invalid }
712 }
713 }
714}
715
716fn x11_lookup_keysym(event &C.XEvent) KeySym {
717 mut keysym := KeySym(0)
718 unsafe {
719 C.XLookupString(&event.xkey, nil, 0, &keysym, nil)
720 }
721 return keysym
722}
723
724fn x11_translate_event_key(scancode int, keysym KeySym) KeyCode {
725 key := linux_translate_navigation_or_keypad_keysym(u32(keysym))
726 if key != .invalid {
727 return key
728 }
729 return x11_translate_key(scancode)
730}
731
732// XKB key name to keycode mapping entry
733struct X11KeymapEntry {
734mut:
735 key KeyCode
736 name [4]u8
737}
738
739fn make_keymap_entry(key KeyCode, name string) X11KeymapEntry {
740 mut e := X11KeymapEntry{
741 key: key
742 }
743 for i := 0; i < 4 && i < name.len; i++ {
744 unsafe {
745 e.name[i] = name[i]
746 }
747 }
748 return e
749}
750
751fn x11_init_keytable() {
752 for i in 0 .. max_keycodes {
753 g_sapp_state.keycodes[i] = .invalid
754 }
755 // Use XKB to determine physical key locations
756 desc := C.XkbGetMap(g_sapp_state.x11.display, 0, xkb_use_core_kbd)
757 if desc == unsafe { nil } {
758 return
759 }
760 C.XkbGetNames(g_sapp_state.x11.display, xkb_key_names_mask | xkb_key_aliases_mask, desc)
761
762 scancode_min := int(desc.min_key_code)
763 scancode_max := int(desc.max_key_code)
764
765 keymap := [
766 make_keymap_entry(.grave_accent, 'TLDE'),
767 make_keymap_entry(._1, 'AE01'),
768 make_keymap_entry(._2, 'AE02'),
769 make_keymap_entry(._3, 'AE03'),
770 make_keymap_entry(._4, 'AE04'),
771 make_keymap_entry(._5, 'AE05'),
772 make_keymap_entry(._6, 'AE06'),
773 make_keymap_entry(._7, 'AE07'),
774 make_keymap_entry(._8, 'AE08'),
775 make_keymap_entry(._9, 'AE09'),
776 make_keymap_entry(._0, 'AE10'),
777 make_keymap_entry(.minus, 'AE11'),
778 make_keymap_entry(.equal, 'AE12'),
779 make_keymap_entry(.q, 'AD01'),
780 make_keymap_entry(.w, 'AD02'),
781 make_keymap_entry(.e, 'AD03'),
782 make_keymap_entry(.r, 'AD04'),
783 make_keymap_entry(.t, 'AD05'),
784 make_keymap_entry(.y, 'AD06'),
785 make_keymap_entry(.u, 'AD07'),
786 make_keymap_entry(.i, 'AD08'),
787 make_keymap_entry(.o, 'AD09'),
788 make_keymap_entry(.p, 'AD10'),
789 make_keymap_entry(.left_bracket, 'AD11'),
790 make_keymap_entry(.right_bracket, 'AD12'),
791 make_keymap_entry(.a, 'AC01'),
792 make_keymap_entry(.s, 'AC02'),
793 make_keymap_entry(.d, 'AC03'),
794 make_keymap_entry(.f, 'AC04'),
795 make_keymap_entry(.g, 'AC05'),
796 make_keymap_entry(.h, 'AC06'),
797 make_keymap_entry(.j, 'AC07'),
798 make_keymap_entry(.k, 'AC08'),
799 make_keymap_entry(.l, 'AC09'),
800 make_keymap_entry(.semicolon, 'AC10'),
801 make_keymap_entry(.apostrophe, 'AC11'),
802 make_keymap_entry(.z, 'AB01'),
803 make_keymap_entry(.x, 'AB02'),
804 make_keymap_entry(.c, 'AB03'),
805 make_keymap_entry(.v, 'AB04'),
806 make_keymap_entry(.b, 'AB05'),
807 make_keymap_entry(.n, 'AB06'),
808 make_keymap_entry(.m, 'AB07'),
809 make_keymap_entry(.comma, 'AB08'),
810 make_keymap_entry(.period, 'AB09'),
811 make_keymap_entry(.slash, 'AB10'),
812 make_keymap_entry(.backslash, 'BKSL'),
813 make_keymap_entry(.world_1, 'LSGT'),
814 make_keymap_entry(.space, 'SPCE'),
815 make_keymap_entry(.escape, 'ESC\x00'),
816 make_keymap_entry(.enter, 'RTRN'),
817 make_keymap_entry(.tab, 'TAB\x00'),
818 make_keymap_entry(.backspace, 'BKSP'),
819 make_keymap_entry(.insert, 'INS\x00'),
820 make_keymap_entry(.delete, 'DELE'),
821 make_keymap_entry(.right, 'RGHT'),
822 make_keymap_entry(.left, 'LEFT'),
823 make_keymap_entry(.down, 'DOWN'),
824 make_keymap_entry(.up, 'UP\x00\x00'),
825 make_keymap_entry(.page_up, 'PGUP'),
826 make_keymap_entry(.page_down, 'PGDN'),
827 make_keymap_entry(.home, 'HOME'),
828 make_keymap_entry(.end, 'END\x00'),
829 make_keymap_entry(.caps_lock, 'CAPS'),
830 make_keymap_entry(.scroll_lock, 'SCLK'),
831 make_keymap_entry(.num_lock, 'NMLK'),
832 make_keymap_entry(.print_screen, 'PRSC'),
833 make_keymap_entry(.pause, 'PAUS'),
834 make_keymap_entry(.f1, 'FK01'),
835 make_keymap_entry(.f2, 'FK02'),
836 make_keymap_entry(.f3, 'FK03'),
837 make_keymap_entry(.f4, 'FK04'),
838 make_keymap_entry(.f5, 'FK05'),
839 make_keymap_entry(.f6, 'FK06'),
840 make_keymap_entry(.f7, 'FK07'),
841 make_keymap_entry(.f8, 'FK08'),
842 make_keymap_entry(.f9, 'FK09'),
843 make_keymap_entry(.f10, 'FK10'),
844 make_keymap_entry(.f11, 'FK11'),
845 make_keymap_entry(.f12, 'FK12'),
846 make_keymap_entry(.f13, 'FK13'),
847 make_keymap_entry(.f14, 'FK14'),
848 make_keymap_entry(.f15, 'FK15'),
849 make_keymap_entry(.f16, 'FK16'),
850 make_keymap_entry(.f17, 'FK17'),
851 make_keymap_entry(.f18, 'FK18'),
852 make_keymap_entry(.f19, 'FK19'),
853 make_keymap_entry(.f20, 'FK20'),
854 make_keymap_entry(.f21, 'FK21'),
855 make_keymap_entry(.f22, 'FK22'),
856 make_keymap_entry(.f23, 'FK23'),
857 make_keymap_entry(.f24, 'FK24'),
858 make_keymap_entry(.f25, 'FK25'),
859 make_keymap_entry(.kp_0, 'KP0\x00'),
860 make_keymap_entry(.kp_1, 'KP1\x00'),
861 make_keymap_entry(.kp_2, 'KP2\x00'),
862 make_keymap_entry(.kp_3, 'KP3\x00'),
863 make_keymap_entry(.kp_4, 'KP4\x00'),
864 make_keymap_entry(.kp_5, 'KP5\x00'),
865 make_keymap_entry(.kp_6, 'KP6\x00'),
866 make_keymap_entry(.kp_7, 'KP7\x00'),
867 make_keymap_entry(.kp_8, 'KP8\x00'),
868 make_keymap_entry(.kp_9, 'KP9\x00'),
869 make_keymap_entry(.kp_decimal, 'KPDL'),
870 make_keymap_entry(.kp_divide, 'KPDV'),
871 make_keymap_entry(.kp_multiply, 'KPMU'),
872 make_keymap_entry(.kp_subtract, 'KPSU'),
873 make_keymap_entry(.kp_add, 'KPAD'),
874 make_keymap_entry(.kp_enter, 'KPEN'),
875 make_keymap_entry(.kp_equal, 'KPEQ'),
876 make_keymap_entry(.left_shift, 'LFSH'),
877 make_keymap_entry(.left_control, 'LCTL'),
878 make_keymap_entry(.left_alt, 'LALT'),
879 make_keymap_entry(.left_super, 'LWIN'),
880 make_keymap_entry(.right_shift, 'RTSH'),
881 make_keymap_entry(.right_control, 'RCTL'),
882 make_keymap_entry(.right_alt, 'RALT'),
883 make_keymap_entry(.right_alt, 'LVL3'),
884 make_keymap_entry(.right_alt, 'MDSW'),
885 make_keymap_entry(.right_super, 'RWIN'),
886 make_keymap_entry(.menu, 'MENU'),
887 ]
888
889 // Find X11 keycode to sokol-app key code mapping
890 for scancode in scancode_min .. scancode_max + 1 {
891 mut key := KeyCode.invalid
892 for km in keymap {
893 if unsafe {
894 C.strncmp(&char(&desc.names.keys[scancode].name[0]), &char(&km.name[0]),
895 usize(xkb_key_name_length))
896 } == 0 {
897 key = km.key
898 break
899 }
900 }
901 // Fall back to key aliases
902 if key == .invalid && desc.names.key_aliases != unsafe { nil } {
903 for i in 0 .. int(desc.names.num_key_aliases) {
904 if unsafe {
905 C.strncmp(&char(&desc.names.key_aliases[i].real[0]),
906 &char(&desc.names.keys[scancode].name[0]), usize(xkb_key_name_length))
907 } != 0 {
908 continue
909 }
910 for km in keymap {
911 if unsafe {
912 C.strncmp(&char(&desc.names.key_aliases[i].alias[0]), &char(&km.name[0]),
913 usize(xkb_key_name_length))
914 } == 0 {
915 key = km.key
916 break
917 }
918 }
919 if key != .invalid {
920 break
921 }
922 }
923 }
924 g_sapp_state.keycodes[scancode] = key
925 }
926 C.XkbFreeNames(desc, xkb_key_names_mask, x_true)
927 C.XkbFreeKeyboard(desc, 0, x_true)
928
929 // Fall back using traditional keysym lookup
930 mut syms_per_code := 0
931 keysyms := C.XGetKeyboardMapping(g_sapp_state.x11.display, u8(scancode_min), scancode_max -
932 scancode_min + 1, &syms_per_code)
933 for scancode in scancode_min .. scancode_max + 1 {
934 if g_sapp_state.keycodes[scancode] == .invalid {
935 base := usize((scancode - scancode_min) * syms_per_code)
936 g_sapp_state.keycodes[scancode] = x11_translate_keysyms(unsafe { keysyms + base },
937 syms_per_code)
938 }
939 }
940 C.XFree(keysyms)
941}
942
943// === DPI ===
944
945fn x11_query_system_dpi() {
946 mut dpi_ok := false
947 rms := C.XResourceManagerString(g_sapp_state.x11.display)
948 if rms != unsafe { nil } {
949 db := C.XrmGetStringDatabase(rms)
950 if db != unsafe { nil } {
951 mut value := C.XrmValue{}
952 mut @type := &char(unsafe { nil })
953 if C.XrmGetResource(db, c'Xft.dpi', c'Xft.Dpi', &@type, &value) != 0 {
954 if @type != unsafe { nil } && C.strcmp(@type, c'String') == 0 {
955 g_sapp_state.x11.dpi = f32(C.atof(value.addr))
956 dpi_ok = true
957 }
958 }
959 C.XrmDestroyDatabase(db)
960 }
961 }
962 if !dpi_ok {
963 g_sapp_state.x11.dpi = 96.0
964 }
965}
966
967// === Keysym to unicode ===
968
969fn x11_keysym_to_unicode(keysym KeySym) i32 {
970 // Latin-1 characters (1:1 mapping)
971 if (keysym >= 0x0020 && keysym <= 0x007e) || (keysym >= 0x00a0 && keysym <= 0x00ff) {
972 return i32(keysym)
973 }
974 // Directly encoded 24-bit UCS characters
975 if (keysym & 0xff000000) == 0x01000000 {
976 return i32(keysym & 0x00ffffff)
977 }
978 // Binary search in table
979 mut min := 0
980 mut max := x11_keysymtab.len - 1
981 for max >= min {
982 mid := (min + max) / 2
983 if u64(x11_keysymtab[mid].keysym) < keysym {
984 min = mid + 1
985 } else if u64(x11_keysymtab[mid].keysym) > keysym {
986 max = mid - 1
987 } else {
988 return i32(x11_keysymtab[mid].ucs)
989 }
990 }
991 return -1
992}
993
994// === Window management ===
995
996fn x11_send_event(msg_type Atom, a i64, b i64, c_ i64, d i64, e i64) {
997 mut event := C.XEvent{}
998 unsafe {
999 event.@type = x_client_message
1000 event.xclient.window = g_sapp_state.x11.window
1001 event.xclient.format = 32
1002 event.xclient.message_type = msg_type
1003 event.xclient.data.l[0] = a
1004 event.xclient.data.l[1] = b
1005 event.xclient.data.l[2] = c_
1006 event.xclient.data.l[3] = d
1007 event.xclient.data.l[4] = e
1008 }
1009 C.XSendEvent(g_sapp_state.x11.display, g_sapp_state.x11.root, x_false,
1010 substructure_notify_mask | substructure_redirect_mask, &event)
1011}
1012
1013fn x11_wait_for_event(event_type int, timeout_sec f64, out_event &C.XEvent) bool {
1014 // Simple polling loop
1015 for {
1016 if C.XCheckTypedWindowEvent(g_sapp_state.x11.display, g_sapp_state.x11.window, event_type,
1017 out_event) != 0 {
1018 return true
1019 }
1020 mut fd := C.pollfd{
1021 fd: C.ConnectionNumber(g_sapp_state.x11.display)
1022 events: pollin
1023 }
1024 C.poll(&fd, 1, int(timeout_sec * 1000))
1025 // Simplified timeout - just try once
1026 return C.XCheckTypedWindowEvent(g_sapp_state.x11.display, g_sapp_state.x11.window,
1027 event_type, out_event) != 0
1028 }
1029 return false
1030}
1031
1032fn x11_app_event(event_type EventType) {
1033 if g_sapp_state.init_called && !g_sapp_state.cleanup_called {
1034 init_sapp_event(event_type)
1035 call_sapp_event(&g_sapp_state.event)
1036 }
1037}
1038
1039fn x11_roundf_gzero(v f32) int {
1040 if v >= 0.5 {
1041 return int(v + 0.5)
1042 } else {
1043 return int(v)
1044 }
1045}
1046
1047fn x11_update_dimensions(x11_window_width int, x11_window_height int) {
1048 window_scale := g_sapp_state.x11.dpi / 96.0
1049 g_sapp_state.window_width = x11_roundf_gzero(f32(x11_window_width) / window_scale)
1050 g_sapp_state.window_height = x11_roundf_gzero(f32(x11_window_height) / window_scale)
1051 cur_fb_width := g_sapp_state.framebuffer_width
1052 cur_fb_height := g_sapp_state.framebuffer_height
1053 g_sapp_state.framebuffer_width =
1054 x11_roundf_gzero(f32(g_sapp_state.window_width) * g_sapp_state.dpi_scale)
1055 g_sapp_state.framebuffer_height =
1056 x11_roundf_gzero(f32(g_sapp_state.window_height) * g_sapp_state.dpi_scale)
1057 dim_changed := g_sapp_state.framebuffer_width != cur_fb_width
1058 || g_sapp_state.framebuffer_height != cur_fb_height
1059 if dim_changed && !g_sapp_state.first_frame {
1060 x11_app_event(.resized)
1061 }
1062}
1063
1064fn x11_update_dimensions_from_window_size() {
1065 mut attribs := C.XWindowAttributes{}
1066 C.XGetWindowAttributes(g_sapp_state.x11.display, g_sapp_state.x11.window, &attribs)
1067 x11_update_dimensions(attribs.width, attribs.height)
1068}
1069
1070fn x11_set_fullscreen(enable bool) {
1071 if g_sapp_state.x11.net_wm_state != x_none && g_sapp_state.x11.net_wm_state_fullscreen != x_none {
1072 if enable {
1073 x11_send_event(g_sapp_state.x11.net_wm_state, 1, // _NET_WM_STATE_ADD
1074 i64(g_sapp_state.x11.net_wm_state_fullscreen), 0, 1, 0)
1075 } else {
1076 x11_send_event(g_sapp_state.x11.net_wm_state, 0, // _NET_WM_STATE_REMOVE
1077 i64(g_sapp_state.x11.net_wm_state_fullscreen), 0, 1, 0)
1078 }
1079 }
1080 C.XFlush(g_sapp_state.x11.display)
1081}
1082
1083// set_resizable toggles whether the current Linux window can be resized by the window manager.
1084pub fn set_resizable(resizable bool) {
1085 $if sokol_wayland ? {
1086 wl_set_resizable(resizable)
1087 } $else {
1088 x11_set_resizable(resizable)
1089 }
1090}
1091
1092fn x11_set_resizable(resizable bool) {
1093 if g_sapp_state.x11.display == unsafe { nil } || g_sapp_state.x11.window == 0 {
1094 return
1095 }
1096 mut hints := C.XAllocSizeHints()
1097 if hints == unsafe { nil } {
1098 return
1099 }
1100 hints.flags = pw_gravity
1101 hints.win_gravity = center_gravity
1102 if !resizable {
1103 mut attribs := C.XWindowAttributes{}
1104 C.XGetWindowAttributes(g_sapp_state.x11.display, g_sapp_state.x11.window, &attribs)
1105 hints.flags |= i64(1 << 4) | i64(1 << 5) // PMinSize | PMaxSize
1106 hints.min_width = attribs.width
1107 hints.min_height = attribs.height
1108 hints.max_width = attribs.width
1109 hints.max_height = attribs.height
1110 }
1111 C.XSetWMNormalHints(g_sapp_state.x11.display, g_sapp_state.x11.window, hints)
1112 C.XFree(hints)
1113 C.XFlush(g_sapp_state.x11.display)
1114}
1115
1116fn x11_create_window(visual_ &C.Visual, depth int) {
1117 mut vis := unsafe { visual_ }
1118 mut dep := depth
1119 if vis == unsafe { nil } {
1120 vis = C.XDefaultVisual(g_sapp_state.x11.display, g_sapp_state.x11.screen)
1121 dep = C.XDefaultDepth(g_sapp_state.x11.display, g_sapp_state.x11.screen)
1122 }
1123 g_sapp_state.x11.colormap = C.XCreateColormap(g_sapp_state.x11.display, g_sapp_state.x11.root,
1124 vis, alloc_none)
1125 mut wa := C.XSetWindowAttributes{}
1126 wa.colormap = g_sapp_state.x11.colormap
1127 wa.border_pixel = 0
1128 wa.event_mask = u64(structure_notify_mask | key_press_mask | key_release_mask | pointer_motion_mask | button_press_mask | button_release_mask | exposure_mask | focus_change_mask | visibility_change_mask | enter_window_mask | leave_window_mask | property_change_mask)
1129
1130 display_width := C.XDisplayWidth(g_sapp_state.x11.display, g_sapp_state.x11.screen)
1131 display_height := C.XDisplayHeight(g_sapp_state.x11.display, g_sapp_state.x11.screen)
1132 window_scale := g_sapp_state.x11.dpi / 96.0
1133 mut x11_window_width := x11_roundf_gzero(f32(g_sapp_state.window_width) * window_scale)
1134 mut x11_window_height := x11_roundf_gzero(f32(g_sapp_state.window_height) * window_scale)
1135 if g_sapp_state.window_width == 0 {
1136 x11_window_width = (display_width * 4) / 5
1137 }
1138 if g_sapp_state.window_height == 0 {
1139 x11_window_height = (display_height * 4) / 5
1140 }
1141 // wamask = CWBorderPixel | CWColormap | CWEventMask
1142 wamask := u64(0x800 | 0x2000 | 0x800)
1143 x11_grab_error_handler()
1144 g_sapp_state.x11.window = C.XCreateWindow(g_sapp_state.x11.display, g_sapp_state.x11.root, 0,
1145 0, u32(x11_window_width), u32(x11_window_height), 0, dep, input_output, vis, wamask, &wa)
1146 x11_release_error_handler()
1147 if g_sapp_state.x11.window == 0 {
1148 eprintln('sokol_app: X11: failed to create window')
1149 return
1150 }
1151 mut protocols := [1]Atom{}
1152 protocols[0] = g_sapp_state.x11.wm_delete_window
1153 C.XSetWMProtocols(g_sapp_state.x11.display, g_sapp_state.x11.window, &protocols[0], 1)
1154 mut hints := C.XAllocSizeHints()
1155 hints.flags = pw_gravity
1156 hints.win_gravity = center_gravity
1157 C.XSetWMNormalHints(g_sapp_state.x11.display, g_sapp_state.x11.window, hints)
1158 C.XFree(hints)
1159 // Announce support for drag'n'drop
1160 if g_sapp_state.drop.enabled {
1161 version := Atom(x11_xdnd_version)
1162 C.XChangeProperty(g_sapp_state.x11.display, g_sapp_state.x11.window,
1163 g_sapp_state.x11.xdnd.xdnd_aware, xa_atom, 32, prop_mode_replace,
1164 unsafe { &u8(&version) }, 1)
1165 }
1166 x11_update_window_title()
1167 x11_update_dimensions_from_window_size()
1168}
1169
1170fn x11_destroy_window() {
1171 if g_sapp_state.x11.window != 0 {
1172 C.XUnmapWindow(g_sapp_state.x11.display, g_sapp_state.x11.window)
1173 C.XDestroyWindow(g_sapp_state.x11.display, g_sapp_state.x11.window)
1174 g_sapp_state.x11.window = 0
1175 }
1176 if g_sapp_state.x11.colormap != 0 {
1177 C.XFreeColormap(g_sapp_state.x11.display, g_sapp_state.x11.colormap)
1178 g_sapp_state.x11.colormap = 0
1179 }
1180 C.XFlush(g_sapp_state.x11.display)
1181}
1182
1183fn x11_window_visible() bool {
1184 mut wa := C.XWindowAttributes{}
1185 C.XGetWindowAttributes(g_sapp_state.x11.display, g_sapp_state.x11.window, &wa)
1186 return wa.map_state == is_viewable
1187}
1188
1189fn x11_show_window() {
1190 if !x11_window_visible() {
1191 C.XMapWindow(g_sapp_state.x11.display, g_sapp_state.x11.window)
1192 mut dummy := C.XEvent{}
1193 x11_wait_for_event(visibility_notify, 0.1, &dummy)
1194 C.XRaiseWindow(g_sapp_state.x11.display, g_sapp_state.x11.window)
1195 C.XFlush(g_sapp_state.x11.display)
1196 }
1197}
1198
1199fn x11_update_window_title() {
1200 title := &char(&g_sapp_state.window_title[0])
1201 C.Xutf8SetWMProperties(g_sapp_state.x11.display, g_sapp_state.x11.window, title, title,
1202 unsafe { nil }, 0, unsafe { nil }, unsafe { nil }, unsafe { nil })
1203 C.XChangeProperty(g_sapp_state.x11.display, g_sapp_state.x11.window,
1204 g_sapp_state.x11.net_wm_name, g_sapp_state.x11.utf8_string, 8, prop_mode_replace,
1205 &g_sapp_state.window_title[0], int(C.strlen(title)))
1206 C.XChangeProperty(g_sapp_state.x11.display, g_sapp_state.x11.window,
1207 g_sapp_state.x11.net_wm_icon_name, g_sapp_state.x11.utf8_string, 8, prop_mode_replace,
1208 &g_sapp_state.window_title[0], int(C.strlen(title)))
1209 C.XFlush(g_sapp_state.x11.display)
1210}
1211
1212// === Cursors ===
1213
1214fn x11_create_hidden_cursor() {
1215 mut img := C.XcursorImageCreate(16, 16)
1216 if img == unsafe { nil } {
1217 return
1218 }
1219 img.xhot = 0
1220 img.yhot = 0
1221 unsafe { C.memset(img.pixels, 0, usize(16 * 16 * 4)) }
1222 g_sapp_state.x11.hidden_cursor = C.XcursorImageLoadCursor(g_sapp_state.x11.display, img)
1223 C.XcursorImageDestroy(img)
1224}
1225
1226fn x11_create_standard_cursor(cursor MouseCursor, name &char, theme &char, size int, fallback_native u32) {
1227 idx := int(cursor)
1228 if theme != unsafe { nil } {
1229 img := C.XcursorLibraryLoadImage(name, theme, size)
1230 if img != unsafe { nil } {
1231 g_sapp_state.x11.standard_cursors[idx] =
1232 C.XcursorImageLoadCursor(g_sapp_state.x11.display, img)
1233 C.XcursorImageDestroy(img)
1234 }
1235 }
1236 if g_sapp_state.x11.standard_cursors[idx] == 0 && fallback_native != 0 {
1237 g_sapp_state.x11.standard_cursors[idx] = C.XCreateFontCursor(g_sapp_state.x11.display,
1238 fallback_native)
1239 }
1240}
1241
1242fn x11_create_standard_cursors() {
1243 cursor_theme := C.XcursorGetTheme(g_sapp_state.x11.display)
1244 size := C.XcursorGetDefaultSize(g_sapp_state.x11.display)
1245 x11_create_standard_cursor(.arrow, c'default', cursor_theme, size, xc_left_ptr)
1246 x11_create_standard_cursor(.ibeam, c'text', cursor_theme, size, xc_xterm)
1247 x11_create_standard_cursor(.crosshair, c'crosshair', cursor_theme, size, xc_crosshair)
1248 x11_create_standard_cursor(.pointing_hand, c'pointer', cursor_theme, size, xc_hand2)
1249 x11_create_standard_cursor(.resize_ew, c'ew-resize', cursor_theme, size, xc_sb_h_double_arrow)
1250 x11_create_standard_cursor(.resize_ns, c'ns-resize', cursor_theme, size, xc_sb_v_double_arrow)
1251 x11_create_standard_cursor(.resize_nwse, c'nwse-resize', cursor_theme, size, 0)
1252 x11_create_standard_cursor(.resize_nesw, c'nesw-resize', cursor_theme, size, 0)
1253 x11_create_standard_cursor(.resize_all, c'all-scroll', cursor_theme, size, xc_fleur)
1254 x11_create_standard_cursor(.not_allowed, c'no-allowed', cursor_theme, size, 0)
1255 x11_create_hidden_cursor()
1256}
1257
1258fn x11_destroy_standard_cursors() {
1259 if g_sapp_state.x11.hidden_cursor != 0 {
1260 C.XFreeCursor(g_sapp_state.x11.display, g_sapp_state.x11.hidden_cursor)
1261 g_sapp_state.x11.hidden_cursor = 0
1262 }
1263 for i in 0 .. mousecursor_num {
1264 if g_sapp_state.x11.standard_cursors[i] != 0 {
1265 C.XFreeCursor(g_sapp_state.x11.display, g_sapp_state.x11.standard_cursors[i])
1266 g_sapp_state.x11.standard_cursors[i] = 0
1267 }
1268 }
1269}
1270
1271fn x11_toggle_fullscreen() {
1272 g_sapp_state.fullscreen = !g_sapp_state.fullscreen
1273 x11_set_fullscreen(g_sapp_state.fullscreen)
1274 x11_update_dimensions_from_window_size()
1275}
1276
1277fn x11_update_cursor(cursor MouseCursor, shown bool) {
1278 idx := int(cursor)
1279 if shown {
1280 if g_sapp_state.custom_cursor_bound[idx] {
1281 xcursor := g_sapp_state.x11.custom_cursors[idx]
1282 if xcursor != 0 {
1283 C.XDefineCursor(g_sapp_state.x11.display, g_sapp_state.x11.window, xcursor)
1284 }
1285 } else if g_sapp_state.x11.standard_cursors[idx] != 0 {
1286 C.XDefineCursor(g_sapp_state.x11.display, g_sapp_state.x11.window,
1287 g_sapp_state.x11.standard_cursors[idx])
1288 } else {
1289 C.XUndefineCursor(g_sapp_state.x11.display, g_sapp_state.x11.window)
1290 }
1291 } else {
1292 C.XDefineCursor(g_sapp_state.x11.display, g_sapp_state.x11.window,
1293 g_sapp_state.x11.hidden_cursor)
1294 }
1295 C.XFlush(g_sapp_state.x11.display)
1296}
1297
1298// === Mouse lock ===
1299
1300fn x11_lock_mouse(do_lock bool) {
1301 if do_lock == g_sapp_state.mouse.locked {
1302 return
1303 }
1304 g_sapp_state.mouse.dx = 0.0
1305 g_sapp_state.mouse.dy = 0.0
1306 g_sapp_state.mouse.locked = do_lock
1307 if g_sapp_state.mouse.locked {
1308 if g_sapp_state.x11.xi.available {
1309 mut em := C.XIEventMask{}
1310 mut mask := [4]u8{}
1311 em.deviceid = xi_all_master_devices
1312 em.mask_len = int(sizeof(mask))
1313 em.mask = unsafe { &mask[0] }
1314 xi_set_mask(unsafe { &mask[0] }, xi_raw_motion)
1315 C.XISelectEvents(g_sapp_state.x11.display, g_sapp_state.x11.root, &em, 1)
1316 }
1317 C.XGrabPointer(g_sapp_state.x11.display, g_sapp_state.x11.window, x_true,
1318 u32(button_press_mask | button_release_mask | pointer_motion_mask), grab_mode_async,
1319 grab_mode_async, g_sapp_state.x11.window, g_sapp_state.x11.hidden_cursor, current_time)
1320 } else {
1321 if g_sapp_state.x11.xi.available {
1322 mut em := C.XIEventMask{}
1323 mut mask := [1]u8{}
1324 em.deviceid = xi_all_master_devices
1325 em.mask_len = int(sizeof(mask))
1326 em.mask = unsafe { &mask[0] }
1327 C.XISelectEvents(g_sapp_state.x11.display, g_sapp_state.x11.root, &em, 1)
1328 }
1329 C.XWarpPointer(g_sapp_state.x11.display, Window(0), g_sapp_state.x11.window, 0, 0, 0, 0,
1330 int(g_sapp_state.mouse.x), int(g_sapp_state.mouse.y))
1331 C.XUngrabPointer(g_sapp_state.x11.display, current_time)
1332 }
1333 C.XFlush(g_sapp_state.x11.display)
1334}
1335
1336// === Clipboard ===
1337
1338fn x11_set_clipboard_string(_str &char) {
1339 if !g_sapp_state.clipboard.enabled || g_sapp_state.clipboard.buffer == unsafe { nil } {
1340 return
1341 }
1342 C.XSetSelectionOwner(g_sapp_state.x11.display, g_sapp_state.x11.clipboard_atom,
1343 g_sapp_state.x11.window, current_time)
1344}
1345
1346fn x11_get_clipboard_string() &char {
1347 if !g_sapp_state.clipboard.enabled || g_sapp_state.clipboard.buffer == unsafe { nil } {
1348 return unsafe { nil }
1349 }
1350 // If we own the selection, just return our buffer
1351 if C.XGetSelectionOwner(g_sapp_state.x11.display, g_sapp_state.x11.clipboard_atom) == g_sapp_state.x11.window {
1352 return g_sapp_state.clipboard.buffer
1353 }
1354 sapp_selection := C.XInternAtom(g_sapp_state.x11.display, c'SAPP_SELECTION', x_false)
1355 C.XConvertSelection(g_sapp_state.x11.display, g_sapp_state.x11.clipboard_atom,
1356 g_sapp_state.x11.utf8_string, sapp_selection, g_sapp_state.x11.window, current_time)
1357 mut event := C.XEvent{}
1358 if !x11_wait_for_event(x_selection_notify, 0.1, &event) {
1359 return unsafe { nil }
1360 }
1361 unsafe {
1362 if event.xselection.property == x_none {
1363 return nil
1364 }
1365 mut data := &u8(nil)
1366 mut actual_type := Atom(0)
1367 mut actual_format := 0
1368 mut item_count := u64(0)
1369 mut bytes_after := u64(0)
1370 C.XGetWindowProperty(g_sapp_state.x11.display, event.xselection.requestor,
1371 event.xselection.property, 0, 0x7fffffff, x_true, g_sapp_state.x11.utf8_string,
1372 &actual_type, &actual_format, &item_count, &bytes_after, &&u8(&data))
1373 if data == nil {
1374 return nil
1375 }
1376 if item_count >= usize(g_sapp_state.clipboard.buf_size) {
1377 C.XFree(data)
1378 return nil
1379 }
1380 C.memcpy(g_sapp_state.clipboard.buffer, data, usize(item_count))
1381 g_sapp_state.clipboard.buffer[item_count] = 0
1382 C.XFree(data)
1383 return g_sapp_state.clipboard.buffer
1384 }
1385}
1386
1387// === Modifier helpers ===
1388
1389fn x11_key_modifier_bit(key KeyCode) u32 {
1390 return match key {
1391 .left_shift, .right_shift { u32(Modifier.shift) }
1392 .left_control, .right_control { u32(Modifier.ctrl) }
1393 .left_alt, .right_alt { u32(Modifier.alt) }
1394 .left_super, .right_super { u32(Modifier.super) }
1395 else { u32(0) }
1396 }
1397}
1398
1399fn x11_button_modifier_bit(btn MouseButton) u32 {
1400 return match btn {
1401 .left { u32(Modifier.lmb) }
1402 .right { u32(Modifier.rmb) }
1403 .middle { u32(Modifier.mmb) }
1404 else { u32(0) }
1405 }
1406}
1407
1408fn x11_mods(x11_mods_ u32) u32 {
1409 mut mods := u32(0)
1410 if x11_mods_ & shift_mask != 0 {
1411 mods |= u32(Modifier.shift)
1412 }
1413 if x11_mods_ & control_mask != 0 {
1414 mods |= u32(Modifier.ctrl)
1415 }
1416 if x11_mods_ & mod1_mask != 0 {
1417 mods |= u32(Modifier.alt)
1418 }
1419 if x11_mods_ & mod4_mask != 0 {
1420 mods |= u32(Modifier.super)
1421 }
1422 if x11_mods_ & button1_mask != 0 {
1423 mods |= u32(Modifier.lmb)
1424 }
1425 if x11_mods_ & button2_mask != 0 {
1426 mods |= u32(Modifier.mmb)
1427 }
1428 if x11_mods_ & button3_mask != 0 {
1429 mods |= u32(Modifier.rmb)
1430 }
1431 return mods
1432}
1433
1434fn x11_translate_button(event &C.XEvent) MouseButton {
1435 return match unsafe { event.xbutton.button } {
1436 x_button1 { MouseButton.left }
1437 x_button2 { MouseButton.middle }
1438 x_button3 { MouseButton.right }
1439 else { MouseButton.invalid }
1440 }
1441}
1442
1443// === Mouse/key event helpers ===
1444
1445fn x11_mouse_update(x int, y int, clear_dxdy bool) {
1446 if !g_sapp_state.mouse.locked {
1447 new_x := f32(x)
1448 new_y := f32(y)
1449 if clear_dxdy {
1450 g_sapp_state.mouse.dx = 0.0
1451 g_sapp_state.mouse.dy = 0.0
1452 } else if g_sapp_state.mouse.pos_valid {
1453 g_sapp_state.mouse.dx = new_x - g_sapp_state.mouse.x
1454 g_sapp_state.mouse.dy = new_y - g_sapp_state.mouse.y
1455 }
1456 g_sapp_state.mouse.x = new_x
1457 g_sapp_state.mouse.y = new_y
1458 g_sapp_state.mouse.pos_valid = true
1459 }
1460}
1461
1462fn x11_mouse_event(event_type EventType, btn MouseButton, mods u32) {
1463 if g_sapp_state.init_called && !g_sapp_state.cleanup_called {
1464 init_sapp_event(event_type)
1465 g_sapp_state.event.mouse_button = btn
1466 g_sapp_state.event.modifiers = mods
1467 call_sapp_event(&g_sapp_state.event)
1468 }
1469}
1470
1471fn x11_scroll_event(x f32, y f32, mods u32) {
1472 if g_sapp_state.init_called && !g_sapp_state.cleanup_called {
1473 init_sapp_event(.mouse_scroll)
1474 g_sapp_state.event.modifiers = mods
1475 g_sapp_state.event.scroll_x = x
1476 g_sapp_state.event.scroll_y = y
1477 call_sapp_event(&g_sapp_state.event)
1478 }
1479}
1480
1481fn x11_key_event(event_type EventType, key KeyCode, repeat bool, mods u32) {
1482 if g_sapp_state.init_called && !g_sapp_state.cleanup_called {
1483 init_sapp_event(event_type)
1484 g_sapp_state.event.key_code = key
1485 g_sapp_state.event.key_repeat = repeat
1486 g_sapp_state.event.modifiers = mods
1487 call_sapp_event(&g_sapp_state.event)
1488 // Check if a CLIPBOARD_PASTED event must be sent too
1489 if g_sapp_state.clipboard.enabled && event_type == .key_down
1490 && g_sapp_state.event.modifiers == u32(Modifier.ctrl)
1491 && g_sapp_state.event.key_code == .v {
1492 init_sapp_event(.clipboard_pasted)
1493 call_sapp_event(&g_sapp_state.event)
1494 }
1495 }
1496}
1497
1498fn x11_char_event(chr u32, repeat bool, mods u32) {
1499 if g_sapp_state.init_called && !g_sapp_state.cleanup_called {
1500 init_sapp_event(.char)
1501 g_sapp_state.event.char_code = chr
1502 g_sapp_state.event.key_repeat = repeat
1503 g_sapp_state.event.modifiers = mods
1504 call_sapp_event(&g_sapp_state.event)
1505 }
1506}
1507
1508fn x11_translate_key(scancode int) KeyCode {
1509 if scancode >= 0 && scancode < x11_max_keycodes {
1510 return g_sapp_state.keycodes[scancode]
1511 }
1512 return .invalid
1513}
1514
1515fn x11_keypress_repeat(keycode int) bool {
1516 mut repeat := false
1517 if keycode >= 0 && keycode < x11_max_keycodes {
1518 repeat = g_sapp_state.x11.key_repeat[keycode]
1519 g_sapp_state.x11.key_repeat[keycode] = true
1520 }
1521 return repeat
1522}
1523
1524fn x11_keyrelease_repeat(keycode int) {
1525 if keycode >= 0 && keycode < x11_max_keycodes {
1526 g_sapp_state.x11.key_repeat[keycode] = false
1527 }
1528}
1529
1530// === Window property helpers ===
1531
1532fn x11_get_window_property(window Window, property Atom, req_type Atom, value &&u8) u64 {
1533 mut actual_type := Atom(0)
1534 mut actual_format := 0
1535 mut item_count := u64(0)
1536 mut bytes_after := u64(0)
1537 C.XGetWindowProperty(g_sapp_state.x11.display, window, property, 0, 0x7fffffff, x_false,
1538 req_type, &actual_type, &actual_format, &item_count, &bytes_after, unsafe { &&&u8(value) })
1539 return item_count
1540}
1541
1542fn x11_get_window_state() int {
1543 mut result := withdrawn_state
1544 mut state := &u8(unsafe { nil })
1545 if x11_get_window_property(g_sapp_state.x11.window, g_sapp_state.x11.wm_state,
1546 g_sapp_state.x11.wm_state, &&u8(&state)) >= 2 {
1547 unsafe {
1548 result = int(*(&u32(state)))
1549 }
1550 }
1551 if state != unsafe { nil } {
1552 C.XFree(state)
1553 }
1554 return result
1555}
1556
1557// === Dropped files ===
1558
1559fn x11_parse_dropped_files_list(src &char) bool {
1560 if src == unsafe { nil } || g_sapp_state.drop.buffer == unsafe { nil } {
1561 return false
1562 }
1563 // Clear drop buffer
1564 unsafe { C.memset(g_sapp_state.drop.buffer, 0, usize(g_sapp_state.drop.buf_size)) }
1565 g_sapp_state.drop.num_files = 0
1566
1567 mut err := false
1568 mut src_count := 0
1569 mut dst_ptr := g_sapp_state.drop.buffer
1570 dst_end_ptr := unsafe { g_sapp_state.drop.buffer + (g_sapp_state.drop.max_path_length - 1) }
1571 mut p := unsafe { src }
1572 for {
1573 src_chr := unsafe { *p }
1574 if src_chr == 0 {
1575 break
1576 }
1577 unsafe { p++ }
1578 src_count++
1579 mut dst_chr := u8(0)
1580 // Check leading 'file://'
1581 if src_count <= 7 {
1582 valid := (src_count == 1 && src_chr == `f`)
1583 || (src_count == 2 && src_chr == `i`)
1584 || (src_count == 3 && src_chr == `l`)
1585 || (src_count == 4 && src_chr == `e`)
1586 || (src_count == 5 && src_chr == `:`)
1587 || (src_count == 6 && src_chr == `/`)
1588 || (src_count == 7 && src_chr == `/`)
1589 if !valid {
1590 err = true
1591 break
1592 }
1593 } else if src_chr == `\r` {
1594 // skip
1595 } else if src_chr == `\n` {
1596 src_count = 0
1597 g_sapp_state.drop.num_files++
1598 if g_sapp_state.drop.num_files >= g_sapp_state.drop.max_files {
1599 break
1600 }
1601 dst_ptr = unsafe {
1602 g_sapp_state.drop.buffer +
1603 g_sapp_state.drop.num_files * g_sapp_state.drop.max_path_length
1604 }
1605 } else if src_chr == `%` {
1606 unsafe {
1607 if *p != 0 && *(p + 1) != 0 {
1608 mut digits := [3]u8{}
1609 digits[0] = u8(*p)
1610 digits[1] = u8(*(p + 1))
1611 digits[2] = 0
1612 p = &char(usize(p) + 2)
1613 dst_chr = u8(C.strtol(&char(&digits[0]), &char(0), 16))
1614 }
1615 }
1616 } else {
1617 dst_chr = u8(src_chr)
1618 }
1619 if dst_chr != 0 {
1620 if usize(dst_ptr) < usize(dst_end_ptr) {
1621 unsafe {
1622 *dst_ptr = char(dst_chr)
1623 dst_ptr++
1624 }
1625 } else {
1626 err = true
1627 break
1628 }
1629 }
1630 }
1631 if err {
1632 unsafe { C.memset(g_sapp_state.drop.buffer, 0, usize(g_sapp_state.drop.buf_size)) }
1633 g_sapp_state.drop.num_files = 0
1634 return false
1635 }
1636 return true
1637}
1638
1639// === Event handlers ===
1640
1641fn x11_on_genericevent(event &C.XEvent) {
1642 if g_sapp_state.mouse.locked && g_sapp_state.x11.xi.available {
1643 unsafe {
1644 if event.xcookie.extension == g_sapp_state.x11.xi.major_opcode {
1645 if C.XGetEventData(g_sapp_state.x11.display, &event.xcookie) != 0 {
1646 if event.xcookie.evtype == xi_raw_motion {
1647 re := &C.XIRawEvent(event.xcookie.data)
1648 if re.valuators.mask_len > 0 {
1649 mut values := re.raw_values
1650 if xi_mask_is_set(re.valuators.mask, 0) {
1651 g_sapp_state.mouse.dx = f32(*values)
1652 values = values + 1
1653 }
1654 if xi_mask_is_set(re.valuators.mask, 1) {
1655 g_sapp_state.mouse.dy = f32(*values)
1656 }
1657 x11_mouse_event(.mouse_move, .invalid, x11_mods(event.xmotion.state))
1658 }
1659 }
1660 C.XFreeEventData(g_sapp_state.x11.display, &event.xcookie)
1661 }
1662 }
1663 }
1664 }
1665}
1666
1667fn x11_on_focusin(event &C.XEvent) {
1668 unsafe {
1669 if event.xfocus.mode != notify_grab && event.xfocus.mode != notify_ungrab {
1670 x11_app_event(.focused)
1671 }
1672 }
1673}
1674
1675fn x11_on_focusout(event &C.XEvent) {
1676 if g_sapp_state.mouse.locked {
1677 x11_lock_mouse(false)
1678 }
1679 unsafe {
1680 if event.xfocus.mode != notify_grab && event.xfocus.mode != notify_ungrab {
1681 x11_app_event(.unfocused)
1682 }
1683 }
1684}
1685
1686fn x11_on_keypress(event &C.XEvent) {
1687 unsafe {
1688 keycode := int(event.xkey.keycode)
1689 keysym := x11_lookup_keysym(event)
1690 key := x11_translate_event_key(keycode, keysym)
1691 repeat := x11_keypress_repeat(keycode)
1692 mut mods := x11_mods(event.xkey.state)
1693 // X11 doesn't set modifier bit on key down, so emulate that
1694 mods |= x11_key_modifier_bit(key)
1695 if key != .invalid {
1696 x11_key_event(.key_down, key, repeat, mods)
1697 }
1698 chr := x11_keysym_to_unicode(keysym)
1699 if chr > 0 {
1700 x11_char_event(u32(chr), repeat, mods)
1701 }
1702 }
1703}
1704
1705fn x11_on_keyrelease(event &C.XEvent) {
1706 unsafe {
1707 keycode := int(event.xkey.keycode)
1708 keysym := x11_lookup_keysym(event)
1709 key := x11_translate_event_key(keycode, keysym)
1710 x11_keyrelease_repeat(keycode)
1711 if key != .invalid {
1712 mut mods := x11_mods(event.xkey.state)
1713 // X11 doesn't clear modifier bit on key up, so emulate that
1714 mods &= ~x11_key_modifier_bit(key)
1715 x11_key_event(.key_up, key, false, mods)
1716 }
1717 }
1718}
1719
1720fn x11_on_buttonpress(event &C.XEvent) {
1721 unsafe {
1722 x11_mouse_update(event.xbutton.x, event.xbutton.y, false)
1723 btn := x11_translate_button(event)
1724 mut mods := x11_mods(event.xbutton.state)
1725 mods |= x11_button_modifier_bit(btn)
1726 if btn != .invalid {
1727 x11_mouse_event(.mouse_down, btn, mods)
1728 g_sapp_state.x11.mouse_buttons |= u8(1 << int(btn))
1729 } else {
1730 // Might be a scroll event
1731 match event.xbutton.button {
1732 4 { x11_scroll_event(0.0, 1.0, mods) }
1733 5 { x11_scroll_event(0.0, -1.0, mods) }
1734 6 { x11_scroll_event(1.0, 0.0, mods) }
1735 7 { x11_scroll_event(-1.0, 0.0, mods) }
1736 else {}
1737 }
1738 }
1739 }
1740}
1741
1742fn x11_on_buttonrelease(event &C.XEvent) {
1743 unsafe {
1744 x11_mouse_update(event.xbutton.x, event.xbutton.y, false)
1745 btn := x11_translate_button(event)
1746 if btn != .invalid {
1747 mut mods := x11_mods(event.xbutton.state)
1748 mods &= ~x11_button_modifier_bit(btn)
1749 x11_mouse_event(.mouse_up, btn, mods)
1750 g_sapp_state.x11.mouse_buttons &= ~u8(1 << int(btn))
1751 }
1752 }
1753}
1754
1755fn x11_on_enternotify(event &C.XEvent) {
1756 if g_sapp_state.x11.mouse_buttons == 0 {
1757 unsafe {
1758 x11_mouse_update(event.xcrossing.x, event.xcrossing.y, true)
1759 x11_mouse_event(.mouse_enter, .invalid, x11_mods(event.xcrossing.state))
1760 }
1761 }
1762}
1763
1764fn x11_on_leavenotify(event &C.XEvent) {
1765 if g_sapp_state.x11.mouse_buttons == 0 {
1766 unsafe {
1767 x11_mouse_update(event.xcrossing.x, event.xcrossing.y, true)
1768 x11_mouse_event(.mouse_leave, .invalid, x11_mods(event.xcrossing.state))
1769 }
1770 }
1771}
1772
1773fn x11_on_motionnotify(event &C.XEvent) {
1774 if !g_sapp_state.mouse.locked {
1775 unsafe {
1776 x11_mouse_update(event.xmotion.x, event.xmotion.y, false)
1777 x11_mouse_event(.mouse_move, .invalid, x11_mods(event.xmotion.state))
1778 }
1779 }
1780}
1781
1782fn x11_on_propertynotify(event &C.XEvent) {
1783 unsafe {
1784 if event.xproperty.state == property_new_value {
1785 if event.xproperty.atom == g_sapp_state.x11.wm_state {
1786 state := x11_get_window_state()
1787 if state != g_sapp_state.x11.window_state {
1788 g_sapp_state.x11.window_state = state
1789 if state == iconic_state {
1790 x11_app_event(.iconified)
1791 } else if state == normal_state {
1792 x11_app_event(.restored)
1793 }
1794 }
1795 }
1796 }
1797 }
1798}
1799
1800fn x11_on_selectionnotify(event &C.XEvent) {
1801 unsafe {
1802 if event.xselection.property == g_sapp_state.x11.xdnd.xdnd_selection {
1803 mut data := &u8(nil)
1804 result := x11_get_window_property(event.xselection.requestor,
1805 event.xselection.property, event.xselection.target, &&u8(&data))
1806 if g_sapp_state.drop.enabled && result > 0 {
1807 if x11_parse_dropped_files_list(&char(data)) {
1808 g_sapp_state.mouse.dx = 0.0
1809 g_sapp_state.mouse.dy = 0.0
1810 if g_sapp_state.init_called && !g_sapp_state.cleanup_called {
1811 init_sapp_event(.files_dropped)
1812 call_sapp_event(&g_sapp_state.event)
1813 }
1814 }
1815 }
1816 if g_sapp_state.x11.xdnd.version >= 2 {
1817 mut reply := C.XEvent{}
1818 reply.@type = x_client_message
1819 reply.xclient.window = g_sapp_state.x11.xdnd.source
1820 reply.xclient.message_type = g_sapp_state.x11.xdnd.xdnd_finished
1821 reply.xclient.format = 32
1822 reply.xclient.data.l[0] = i64(g_sapp_state.x11.window)
1823 reply.xclient.data.l[1] = i64(result)
1824 reply.xclient.data.l[2] = i64(g_sapp_state.x11.xdnd.xdnd_action_copy)
1825 C.XSendEvent(g_sapp_state.x11.display, g_sapp_state.x11.xdnd.source, x_false,
1826 no_event_mask, &reply)
1827 C.XFlush(g_sapp_state.x11.display)
1828 }
1829 if data != nil {
1830 C.XFree(data)
1831 }
1832 }
1833 }
1834}
1835
1836fn x11_on_selectionrequest(event &C.XEvent) {
1837 unsafe {
1838 req := &event.xselectionrequest
1839 if req.selection != g_sapp_state.x11.clipboard_atom {
1840 return
1841 }
1842 if !g_sapp_state.clipboard.enabled {
1843 return
1844 }
1845 mut reply := C.XEvent{}
1846 reply.@type = x_selection_notify
1847 reply.xselection.requestor = req.requestor
1848 reply.xselection.property = req.property
1849 reply.xselection.target = req.target
1850
1851 if req.target == g_sapp_state.x11.utf8_string {
1852 C.XChangeProperty(g_sapp_state.x11.display, req.requestor, req.property,
1853 g_sapp_state.x11.utf8_string, 8, prop_mode_replace,
1854 &u8(g_sapp_state.clipboard.buffer), int(C.strlen(g_sapp_state.clipboard.buffer)))
1855 } else if req.target == g_sapp_state.x11.targets {
1856 C.XChangeProperty(g_sapp_state.x11.display, req.requestor, req.property, xa_atom, 32,
1857 prop_mode_replace, &u8(&g_sapp_state.x11.utf8_string), 1)
1858 } else {
1859 reply.xselection.property = x_none
1860 }
1861 C.XSendEvent(g_sapp_state.x11.display, req.requestor, x_false, 0, &reply)
1862 }
1863}
1864
1865fn x11_on_clientmessage(event &C.XEvent) {
1866 if C.XFilterEvent(event, Window(0)) != 0 {
1867 return
1868 }
1869 unsafe {
1870 if event.xclient.message_type == g_sapp_state.x11.wm_protocols {
1871 protocol := Atom(u64(event.xclient.data.l[0]))
1872 if protocol == g_sapp_state.x11.wm_delete_window {
1873 g_sapp_state.quit_requested = true
1874 }
1875 } else if event.xclient.message_type == g_sapp_state.x11.xdnd.xdnd_enter {
1876 is_list := (event.xclient.data.l[1] & 1) != 0
1877 g_sapp_state.x11.xdnd.source = Window(u64(event.xclient.data.l[0]))
1878 g_sapp_state.x11.xdnd.version = event.xclient.data.l[1] >> 24
1879 g_sapp_state.x11.xdnd.format = x_none
1880 if g_sapp_state.x11.xdnd.version > x11_xdnd_version {
1881 return
1882 }
1883 mut count := u64(0)
1884 mut formats := &Atom(nil)
1885 if is_list {
1886 count = x11_get_window_property(g_sapp_state.x11.xdnd.source,
1887 g_sapp_state.x11.xdnd.xdnd_type_list, xa_atom, &&u8(&formats))
1888 } else {
1889 count = 3
1890 formats = &Atom(voidptr(&event.xclient.data.l[2]))
1891 }
1892 for i in 0 .. count {
1893 if formats[i] == g_sapp_state.x11.xdnd.text_uri_list {
1894 g_sapp_state.x11.xdnd.format = g_sapp_state.x11.xdnd.text_uri_list
1895 break
1896 }
1897 }
1898 if is_list && formats != nil {
1899 C.XFree(formats)
1900 }
1901 } else if event.xclient.message_type == g_sapp_state.x11.xdnd.xdnd_drop {
1902 if g_sapp_state.x11.xdnd.version > x11_xdnd_version {
1903 return
1904 }
1905 if g_sapp_state.x11.xdnd.format != x_none {
1906 mut time := current_time
1907 if g_sapp_state.x11.xdnd.version >= 1 {
1908 time = Time(u64(event.xclient.data.l[2]))
1909 }
1910 C.XConvertSelection(g_sapp_state.x11.display, g_sapp_state.x11.xdnd.xdnd_selection,
1911 g_sapp_state.x11.xdnd.format, g_sapp_state.x11.xdnd.xdnd_selection,
1912 g_sapp_state.x11.window, time)
1913 } else if g_sapp_state.x11.xdnd.version >= 2 {
1914 mut reply := C.XEvent{}
1915 reply.@type = x_client_message
1916 reply.xclient.window = g_sapp_state.x11.xdnd.source
1917 reply.xclient.message_type = g_sapp_state.x11.xdnd.xdnd_finished
1918 reply.xclient.format = 32
1919 reply.xclient.data.l[0] = i64(g_sapp_state.x11.window)
1920 reply.xclient.data.l[1] = 0
1921 reply.xclient.data.l[2] = i64(x_none)
1922 C.XSendEvent(g_sapp_state.x11.display, g_sapp_state.x11.xdnd.source, x_false,
1923 no_event_mask, &reply)
1924 C.XFlush(g_sapp_state.x11.display)
1925 }
1926 } else if event.xclient.message_type == g_sapp_state.x11.xdnd.xdnd_position {
1927 if g_sapp_state.x11.xdnd.version > x11_xdnd_version {
1928 return
1929 }
1930 mut reply := C.XEvent{}
1931 reply.@type = x_client_message
1932 reply.xclient.window = g_sapp_state.x11.xdnd.source
1933 reply.xclient.message_type = g_sapp_state.x11.xdnd.xdnd_status
1934 reply.xclient.format = 32
1935 reply.xclient.data.l[0] = i64(g_sapp_state.x11.window)
1936 if g_sapp_state.x11.xdnd.format != x_none {
1937 reply.xclient.data.l[1] = 1
1938 if g_sapp_state.x11.xdnd.version >= 2 {
1939 reply.xclient.data.l[4] = i64(g_sapp_state.x11.xdnd.xdnd_action_copy)
1940 }
1941 }
1942 C.XSendEvent(g_sapp_state.x11.display, g_sapp_state.x11.xdnd.source, x_false,
1943 no_event_mask, &reply)
1944 C.XFlush(g_sapp_state.x11.display)
1945 }
1946 }
1947}
1948
1949// === Main event dispatcher ===
1950
1951fn x11_process_event(event &C.XEvent) {
1952 unsafe {
1953 match event.@type {
1954 x_generic_event { x11_on_genericevent(event) }
1955 x_focus_in { x11_on_focusin(event) }
1956 x_focus_out { x11_on_focusout(event) }
1957 x_key_press { x11_on_keypress(event) }
1958 x_key_release { x11_on_keyrelease(event) }
1959 x_button_press { x11_on_buttonpress(event) }
1960 x_button_release { x11_on_buttonrelease(event) }
1961 x_enter_notify { x11_on_enternotify(event) }
1962 x_leave_notify { x11_on_leavenotify(event) }
1963 x_motion_notify { x11_on_motionnotify(event) }
1964 x_property_notify { x11_on_propertynotify(event) }
1965 x_selection_notify { x11_on_selectionnotify(event) }
1966 x_selection_request { x11_on_selectionrequest(event) }
1967 x_destroy_notify {}
1968 x_client_message { x11_on_clientmessage(event) }
1969 else {}
1970 }
1971 }
1972}
1973
1974// === Frame and run functions ===
1975
1976fn x11_linux_frame() {
1977 x11_update_dimensions_from_window_size()
1978 sapp_do_frame()
1979 C.eglSwapBuffers(g_sapp_state.egl.display, g_sapp_state.egl.surface)
1980}
1981
1982fn x11_run() {
1983 C.XInitThreads()
1984 C.XrmInitialize()
1985 g_sapp_state.x11.display = C.XOpenDisplay(unsafe { nil })
1986 if g_sapp_state.x11.display == unsafe { nil } {
1987 eprintln('sokol_app: X11: failed to open display')
1988 return
1989 }
1990 g_sapp_state.x11.screen = C.XDefaultScreen(g_sapp_state.x11.display)
1991 g_sapp_state.x11.root = C.XDefaultRootWindow(g_sapp_state.x11.display)
1992 g_sapp_state.x11.window_state = normal_state
1993 x11_query_system_dpi()
1994 g_sapp_state.dpi_scale = g_sapp_state.x11.dpi / 96.0
1995 x11_init_extensions()
1996 x11_create_standard_cursors()
1997 C.XkbSetDetectableAutoRepeat(g_sapp_state.x11.display, x_true, unsafe { nil })
1998 x11_init_keytable()
1999
2000 // EGL init (creates window internally)
2001 sapp_egl_init_x11()
2002
2003 g_sapp_state.valid = true
2004 x11_show_window()
2005 if g_sapp_state.fullscreen {
2006 x11_set_fullscreen(true)
2007 }
2008 C.XFlush(g_sapp_state.x11.display)
2009
2010 for !g_sapp_state.quit_ordered {
2011 count := C.XPending(g_sapp_state.x11.display)
2012 for _ in 0 .. count {
2013 mut event := C.XEvent{}
2014 C.XNextEvent(g_sapp_state.x11.display, &event)
2015 x11_process_event(&event)
2016 }
2017 x11_linux_frame()
2018 C.XFlush(g_sapp_state.x11.display)
2019 // Handle quit-requested
2020 if g_sapp_state.quit_requested && !g_sapp_state.quit_ordered {
2021 x11_app_event(.quit_requested)
2022 if g_sapp_state.quit_requested {
2023 g_sapp_state.quit_ordered = true
2024 }
2025 }
2026 }
2027 sapp_call_cleanup()
2028 sapp_egl_destroy()
2029 x11_destroy_window()
2030 x11_destroy_standard_cursors()
2031 C.XCloseDisplay(g_sapp_state.x11.display)
2032 sapp_discard_state()
2033}
2034