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