1 /* © 2015 David Given.
2 * WordGrinder is licensed under the MIT open source license. See the COPYING
3 * file in this distribution for the full text.
4 */
5
6 #include "globals.h"
7 #include <string.h>
8 #include <wctype.h>
9 #include <sys/time.h>
10 #include <time.h>
11 #include <ctype.h>
12 #include <poll.h>
13 #include "x11.h"
14
15 #define VKM_SHIFT 0x02000000
16 #define VKM_CTRL 0x04000000
17 #define VKM_CTRLASCII 0x08000000
18 #define VKM__MASK 0x0e000000
19 #define VK_RESIZE 0x10000000
20 #define VK_TIMEOUT 0x20000000
21 #define VK_REDRAW 0x30000000
22
23 struct gg
24 {
25 unsigned c : 24;
26 unsigned attr : 8;
27 };
28
29 enum
30 {
31 REGULAR = 0,
32 ITALIC = (1<<0),
33 BOLD = (1<<1),
34 };
35
36 Display* display;
37 Window window;
38 XftColor colours[NUM_COLOURS];
39 int fontwidth, fontheight, fontascent;
40
41 static XIC xic;
42 static XIM xim;
43 static XftDraw* draw;
44 static GC gc;
45
46 static int screenwidth, screenheight;
47 static int cursorx, cursory;
48 static unsigned int* frontbuffer = NULL;
49 static unsigned int* backbuffer = NULL;
50 static int defaultattr = 0;
51
52 static uni_t queued[4];
53 static int numqueued = 0;
54
55 static void redraw(void);
56
dequeue(void)57 static uni_t dequeue(void)
58 {
59 uni_t c = queued[0];
60 queued[0] = queued[1];
61 queued[1] = queued[2];
62 queued[2] = queued[3];
63 numqueued--;
64 return c;
65 }
66
push_key(uni_t c)67 void push_key(uni_t c)
68 {
69 if (numqueued >= (sizeof(queued)/sizeof(*queued)))
70 return;
71
72 queued[numqueued] = c;
73 numqueued++;
74 }
75
sput(unsigned int * screen,int x,int y,unsigned int id)76 static void sput(unsigned int* screen, int x, int y, unsigned int id)
77 {
78 if (!screen)
79 return;
80 if ((x < 0) || (x >= screenwidth))
81 return;
82 if ((y < 0) || (y >= screenheight))
83 return;
84
85 screen[y*screenwidth + x] = id;
86 }
87
dpy_init(const char * argv[])88 void dpy_init(const char* argv[])
89 {
90 }
91
load_colour(const char * name,const char * fallback)92 static XftColor load_colour(const char* name, const char* fallback)
93 {
94 lua_getglobal(L, name);
95 const char* value = lua_tostring(L, -1);
96 if (!value)
97 value = fallback;
98
99 XftColor colour;
100 if (!XftColorAllocName(display,
101 DefaultVisual(display, DefaultScreen(display)),
102 DefaultColormap(display, DefaultScreen(display)),
103 value, &colour))
104 {
105 fprintf(stderr, "Error: can't parse colour '%s'.\n", value);
106 exit(1);
107 }
108
109 return colour;
110 }
111
dpy_start(void)112 void dpy_start(void)
113 {
114 display = XOpenDisplay(NULL);
115 if (!display)
116 {
117 fprintf(stderr, "Error: can't open display. Is DISPLAY set?\n");
118 exit(1);
119 }
120
121 window = XCreateSimpleWindow(display, RootWindow(display, 0),
122 0, 0, 800, 600, 0, 0, BlackPixel(display, 0));
123 XStoreName(display, window, "WordGrinder " VERSION);
124 XSetClassHint(display, window,
125 &((XClassHint) { "WordGrinder", "WordGrinder" }));
126 XSelectInput(display, window,
127 StructureNotifyMask | ExposureMask | KeyPressMask | KeymapStateMask);
128 XMapWindow(display, window);
129
130 glyphcache_init();
131
132 colours[COLOUR_BLACK] = load_colour("X11_BLACK_COLOUR", "#000000");
133 colours[COLOUR_DIM] = load_colour("X11_DIM_COLOUR", "#555555");
134 colours[COLOUR_NORMAL] = load_colour("X11_NORMAL_COLOUR", "#888888");
135 colours[COLOUR_BRIGHT] = load_colour("X11_BRIGHT_COLOUR", "#ffffff");
136
137 draw = XftDrawCreate(display, window,
138 DefaultVisual(display, DefaultScreen(display)),
139 DefaultColormap(display, DefaultScreen(display)));
140
141 xim = XOpenIM(display, NULL, NULL, NULL);
142 if (xim)
143 xic = XCreateIC(xim, XNInputStyle,
144 XIMPreeditNothing | XIMStatusNothing, XNClientWindow, window, NULL);
145 if (!xim || !xic)
146 {
147 fprintf(stderr, "Error: couldn't set up input methods\n");
148 exit(1);
149 }
150 XSetICFocus(xic);
151
152 {
153 XGCValues gcv =
154 {
155 .graphics_exposures = false
156 };
157
158 gc = XCreateGC(display, window, GCGraphicsExposures, &gcv);
159 }
160
161 screenwidth = screenheight = 0;
162 cursorx = cursory = 0;
163 }
164
dpy_shutdown(void)165 void dpy_shutdown(void)
166 {
167 }
168
dpy_clearscreen(void)169 void dpy_clearscreen(void)
170 {
171 dpy_cleararea(0, 0, screenwidth-1, screenheight-1);
172 }
173
dpy_getscreensize(int * x,int * y)174 void dpy_getscreensize(int* x, int* y)
175 {
176 *x = screenwidth;
177 *y = screenheight;
178 }
179
dpy_sync(void)180 void dpy_sync(void)
181 {
182 if (!frontbuffer)
183 frontbuffer = calloc(screenwidth * screenheight, sizeof(unsigned int));
184 redraw();
185 }
186
dpy_setcursor(int x,int y)187 void dpy_setcursor(int x, int y)
188 {
189 if (frontbuffer)
190 {
191 for (int xx=(cursorx-1); xx<=(cursorx+1); xx++)
192 for (int yy=(cursory-1); yy<=(cursory+1); yy++)
193 sput(frontbuffer, xx, yy, 0);
194 }
195
196 cursorx = x;
197 cursory = y;
198 }
199
dpy_setattr(int andmask,int ormask)200 void dpy_setattr(int andmask, int ormask)
201 {
202 defaultattr &= andmask;
203 defaultattr |= ormask;
204 }
205
dpy_writechar(int x,int y,uni_t c)206 void dpy_writechar(int x, int y, uni_t c)
207 {
208 unsigned int id = glyphcache_id(c, defaultattr);
209 sput(backbuffer, x, y, id);
210 if (emu_wcwidth(c) == 2)
211 sput(backbuffer, x+1, y, 0);
212 }
213
dpy_cleararea(int x1,int y1,int x2,int y2)214 void dpy_cleararea(int x1, int y1, int x2, int y2)
215 {
216 for (int y=y1; y<=y2; y++)
217 {
218 unsigned int* p = &backbuffer[y * screenwidth];
219 for (int x=x1; x<=x2; x++)
220 p[x] = glyphcache_id(' ' , defaultattr);
221 }
222 }
223
render_glyph(unsigned int id,int x,int y)224 static void render_glyph(unsigned int id, int x, int y)
225 {
226 struct glyph* glyph = glyphcache_getglyph(id);
227 if (glyph && glyph->pixmap)
228 XCopyArea(display, glyph->pixmap, window, gc,
229 0, 0, glyph->width, fontheight,
230 x*fontwidth, y*fontheight);
231 }
232
redraw(void)233 static void redraw(void)
234 {
235 if (!frontbuffer || !backbuffer)
236 return;
237
238 for (int y = 0; y<screenheight; y++)
239 {
240 unsigned int* frontp = &frontbuffer[y * screenwidth];
241 unsigned int* backp = &backbuffer[y * screenwidth];
242 for (int x = 0; x<screenwidth; x++)
243 {
244 if (frontp[x] != backp[x])
245 {
246 frontp[x] = backp[x];
247 render_glyph(frontp[x], x, y);
248 }
249 }
250 }
251
252 /* Draw a caret where the cursor should be. */
253
254 int x = cursorx*fontwidth - 1;
255 if (x < 0)
256 x = 0;
257 int y = cursory*fontheight;
258 int h = fontheight;
259 XftColor* c = &colours[COLOUR_BRIGHT];
260
261 XftDrawRect(draw, c, x, y, 1, h);
262 XftDrawRect(draw, c, x-1, y-1, 1, 1);
263 XftDrawRect(draw, c, x+1, y-1, 1, 1);
264 XftDrawRect(draw, c, x-1, y+h, 1, 1);
265 XftDrawRect(draw, c, x+1, y+h, 1, 1);
266 }
267
dpy_getchar(int timeout)268 uni_t dpy_getchar(int timeout)
269 {
270 while (numqueued == 0)
271 {
272 /* If a timeout was asked for, wait that long for an event. */
273
274 if ((timeout != -1) && !XPending(display))
275 {
276 struct pollfd pfd =
277 {
278 .fd = ConnectionNumber(display),
279 .events = POLLIN,
280 .revents = 0
281 };
282
283 poll(&pfd, 1, timeout*1000);
284 if (!pfd.revents)
285 return -VK_TIMEOUT;
286 }
287
288 XEvent e;
289 XNextEvent(display, &e);
290
291 if (XFilterEvent(&e, window))
292 continue;
293
294 switch (e.type)
295 {
296 case MapNotify:
297 break;
298
299 case Expose:
300 {
301 /* Mark some of the screen as needing redrawing. */
302
303 if (frontbuffer)
304 {
305 for (int y=0; y<screenheight; y++)
306 {
307 unsigned int* p = &frontbuffer[y * screenwidth];
308 for (int x=0; x<screenwidth; x++)
309 p[x] = 0;
310 }
311 }
312 redraw();
313 break;
314 }
315
316 case ConfigureNotify:
317 {
318 XConfigureEvent* xce = &e.xconfigure;
319 int w = xce->width / fontwidth;
320 int h = xce->height / fontheight;
321
322 if ((w != screenwidth) || (h != screenheight))
323 {
324 screenwidth = w;
325 screenheight = h;
326
327 if (frontbuffer)
328 free(frontbuffer);
329 frontbuffer = NULL;
330 if (backbuffer)
331 free(backbuffer);
332 backbuffer = calloc(screenwidth * screenheight, sizeof(unsigned int));
333 push_key(-VK_RESIZE);
334 }
335
336 break;
337 }
338
339 case MappingNotify:
340 case KeymapNotify:
341 XRefreshKeyboardMapping(&e.xmapping);
342 break;
343
344 case KeyPress:
345 {
346 XKeyPressedEvent* xke = &e.xkey;
347 KeySym keysym;
348 char buffer[32];
349 Status status = 0;
350 int charcount = Xutf8LookupString(xic, xke,
351 buffer, sizeof(buffer)-1, &keysym, &status);
352
353 int mods = 0;
354 if (xke->state & ShiftMask)
355 mods |= VKM_SHIFT;
356 if (xke->state & ControlMask)
357 mods |= VKM_CTRL;
358
359 if ((keysym & 0xffffff00) == 0xff00)
360 {
361 /* Special function key. */
362 if (!IsModifierKey(keysym))
363 push_key(-(keysym | mods));
364 }
365 else
366 {
367 const char* p = buffer;
368
369 while ((p-buffer) < charcount)
370 {
371 uni_t c = readu8(&p);
372
373 if (c < 32)
374 {
375 /* Ctrl + letter key */
376 push_key(-(VKM_CTRLASCII | c | mods));
377 }
378 else
379 {
380 if (xke->state & Mod1Mask)
381 push_key(-XK_Escape);
382 push_key(c);
383 }
384 }
385 }
386 break;
387 }
388 }
389 }
390
391 return dequeue();
392 }
393
dpy_getkeyname(uni_t k)394 const char* dpy_getkeyname(uni_t k)
395 {
396 static char buffer[32];
397
398 switch (-k)
399 {
400 case VK_RESIZE: return "KEY_RESIZE";
401 case VK_TIMEOUT: return "KEY_TIMEOUT";
402 case VK_REDRAW: return "KEY_REDRAW";
403 }
404
405 int key = -k & ~VKM__MASK;
406 int mods = -k & VKM__MASK;
407
408 if (mods & VKM_CTRLASCII)
409 {
410 sprintf(buffer, "KEY_%s^%c",
411 (mods & VKM_SHIFT) ? "S" : "",
412 key + 64);
413 return buffer;
414 }
415
416 const char* template = NULL;
417 switch (key)
418 {
419 case XK_KP_Down:
420 case XK_Down: template = "DOWN"; break;
421 case XK_KP_Up:
422 case XK_Up: template = "UP"; break;
423 case XK_KP_Left:
424 case XK_Left: template = "LEFT"; break;
425 case XK_KP_Right:
426 case XK_Right: template = "RIGHT"; break;
427 case XK_KP_Home:
428 case XK_Home: template = "HOME"; break;
429 case XK_KP_End:
430 case XK_End: template = "END"; break;
431 case XK_KP_Delete:
432 case XK_Delete: template = "DELETE"; break;
433 case XK_KP_Insert:
434 case XK_Insert: template = "INSERT"; break;
435 case XK_KP_Page_Down:
436 case XK_Page_Down: template = "PGDN"; break;
437 case XK_KP_Page_Up:
438 case XK_Page_Up: template = "PGUP"; break;
439 case XK_KP_Enter:
440 case XK_Return: template = "RETURN"; break;
441 case XK_Tab: template = "TAB"; break;
442 case XK_Escape: template = "ESCAPE"; break;
443 case XK_BackSpace: template = "BACKSPACE"; break;
444 }
445
446 if (template)
447 {
448 sprintf(buffer, "KEY_%s%s%s",
449 (mods & VKM_SHIFT) ? "S" : "",
450 (mods & VKM_CTRL) ? "^" : "",
451 template);
452 return buffer;
453 }
454
455 if ((key >= XK_F1) && (key <= XK_F35))
456 {
457 sprintf(buffer, "KEY_%s%sF%d",
458 (mods & VKM_SHIFT) ? "S" : "",
459 (mods & VKM_CTRL) ? "^" : "",
460 key - XK_F1 + 1);
461 return buffer;
462 }
463
464 sprintf(buffer, "KEY_UNKNOWN_%d", -k);
465 return buffer;
466 }
467
468