x_events.c 35.9 KB
Newer Older
deuce's avatar
deuce committed
1
2
3
4
5
/*
 * This file contains ONLY the functions that are called from the
 * event thread.
 */
 
Deucе's avatar
Deucе committed
6
#include <math.h>
deuce's avatar
deuce committed
7
#include <unistd.h>
deuce's avatar
deuce committed
8
#include <stdbool.h>
deuce's avatar
deuce committed
9
10
11

#include <fcntl.h>
#include <limits.h>
deuce's avatar
deuce committed
12
#include <locale.h>
deuce's avatar
deuce committed
13
14
15
16
17
18
#include <stdio.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <X11/Xatom.h>
19
#include <X11/cursorfont.h>
deuce's avatar
deuce committed
20
21
22
23
24
25
26

#include <threadwrap.h>
#include <genwrap.h>
#include <dirwrap.h>

#include "vidmodes.h"

27
#include "ciolib.h"
28
#define BITMAP_CIOLIB_DRIVER
deuce's avatar
deuce committed
29
30
#include "bitmap_con.h"
#include "link_list.h"
Deucе's avatar
Deucе committed
31
#include "scale.h"
deuce's avatar
deuce committed
32
33
#include "x_events.h"
#include "x_cio.h"
34
#include "utf8_codepages.h"
deuce's avatar
deuce committed
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

/*
 * Exported variables 
 */

int local_pipe[2];			/* Used for passing local events */
int key_pipe[2];			/* Used for passing keyboard events */

struct x11 x11;

char 	*copybuf;
pthread_mutex_t	copybuf_mutex;
char 	*pastebuf;
sem_t	pastebuf_set;
sem_t	pastebuf_used;
sem_t	init_complete;
sem_t	mode_set;
int x11_window_xpos;
int x11_window_ypos;
int x11_window_width;
int x11_window_height;
56
int x11_initialized=0;
57
58
sem_t	event_thread_complete;
int	terminate = 0;
59
60
61
Atom	copybuf_format;
Atom	pastebuf_format;

deuce's avatar
deuce committed
62
63
64
65
66
67
/*
 * Local variables
 */

/* Sets the atom to be used for copy/paste operations */
#define CONSOLE_CLIPBOARD	XA_PRIMARY
deuce's avatar
deuce committed
68
static Atom WM_DELETE_WINDOW=0;
deuce's avatar
deuce committed
69
70
71

static Display *dpy=NULL;
static Window win;
72
static Cursor curs = None;
deuce's avatar
deuce committed
73
static Visual visual;
74
static bool VisualIsRGB8 = false;
deuce's avatar
deuce committed
75
static XImage *xim;
deuce's avatar
deuce committed
76
77
static XIM im;
static XIC ic;
deuce's avatar
deuce committed
78
79
80
81
static unsigned int depth=0;
static int xfd;
static unsigned long black;
static unsigned long white;
82
83
static int bitmap_width=0;
static int bitmap_height=0;
84
static int old_scaling = 0;
85
struct video_stats x_cvstat;
86
87
88
89
static unsigned long base_pixel;
static int r_shift;
static int g_shift;
static int b_shift;
Deucе's avatar
Deucе committed
90
static struct graphics_buffer *last = NULL;
91

deuce's avatar
deuce committed
92
/* Array of Graphics Contexts */
93
static GC gc;
deuce's avatar
deuce committed
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

static WORD Ascii2Scan[] = {
 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
 0x000e, 0x000f, 0xffff, 0xffff, 0xffff, 0x001c, 0xffff, 0xffff,
 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
 0xffff, 0xffff, 0xffff, 0x0001, 0xffff, 0xffff, 0xffff, 0xffff,
 0x0039, 0x0102, 0x0128, 0x0104, 0x0105, 0x0106, 0x0108, 0x0028,
 0x010a, 0x010b, 0x0109, 0x010d, 0x0033, 0x000c, 0x0034, 0x0035,
 0x000b, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008,
 0x0009, 0x000a, 0x0127, 0x0027, 0x0133, 0x000d, 0x0134, 0x0135,
 0x0103, 0x011e, 0x0130, 0x012e, 0x0120, 0x0112, 0x0121, 0x0122,
 0x0123, 0x0117, 0x0124, 0x0125, 0x0126, 0x0132, 0x0131, 0x0118,
 0x0119, 0x0110, 0x0113, 0x011f, 0x0114, 0x0116, 0x012f, 0x0111,
 0x012d, 0x0115, 0x012c, 0x001a, 0x002b, 0x001b, 0x0107, 0x010c,
 0x0029, 0x001e, 0x0030, 0x002e, 0x0020, 0x0012, 0x0021, 0x0022,
 0x0023, 0x0017, 0x0024, 0x0025, 0x0026, 0x0032, 0x0031, 0x0018,
 0x0019, 0x0010, 0x0013, 0x001f, 0x0014, 0x0016, 0x002f, 0x0011,
 0x002d, 0x0015, 0x002c, 0x011a, 0x012b, 0x011b, 0x0129, 0xffff,
};

static struct {
    WORD	base;
    WORD	shift;
    WORD	ctrl;
    WORD	alt;
} ScanCodes[] = {
    {	0xffff, 0xffff, 0xffff, 0xffff }, /* key  0 */
    {	0x011b, 0x011b, 0x011b, 0xffff }, /* key  1 - Escape key */
    {	0x0231, 0x0221, 0xffff, 0x7800 }, /* key  2 - '1' */
    {	0x0332, 0x0340, 0x0300, 0x7900 }, /* key  3 - '2' - special handling */
    {	0x0433, 0x0423, 0xffff, 0x7a00 }, /* key  4 - '3' */
    {	0x0534, 0x0524, 0xffff, 0x7b00 }, /* key  5 - '4' */
    {	0x0635, 0x0625, 0xffff, 0x7c00 }, /* key  6 - '5' */
    {	0x0736, 0x075e, 0x071e, 0x7d00 }, /* key  7 - '6' */
    {	0x0837, 0x0826, 0xffff, 0x7e00 }, /* key  8 - '7' */
    {	0x0938, 0x092a, 0xffff, 0x7f00 }, /* key  9 - '8' */
    {	0x0a39, 0x0a28, 0xffff, 0x8000 }, /* key 10 - '9' */
    {	0x0b30, 0x0b29, 0xffff, 0x8100 }, /* key 11 - '0' */
    {	0x0c2d, 0x0c5f, 0x0c1f, 0x8200 }, /* key 12 - '-' */
    {	0x0d3d, 0x0d2b, 0xffff, 0x8300 }, /* key 13 - '=' */
    {	0x0e08, 0x0e08, 0x0e7f, 0xffff }, /* key 14 - backspace */
    {	0x0f09, 0x0f00, 0xffff, 0xffff }, /* key 15 - tab */
    {	0x1071, 0x1051, 0x1011, 0x1000 }, /* key 16 - 'Q' */
    {	0x1177, 0x1157, 0x1117, 0x1100 }, /* key 17 - 'W' */
    {	0x1265, 0x1245, 0x1205, 0x1200 }, /* key 18 - 'E' */
    {	0x1372, 0x1352, 0x1312, 0x1300 }, /* key 19 - 'R' */
    {	0x1474, 0x1454, 0x1414, 0x1400 }, /* key 20 - 'T' */
    {	0x1579, 0x1559, 0x1519, 0x1500 }, /* key 21 - 'Y' */
    {	0x1675, 0x1655, 0x1615, 0x1600 }, /* key 22 - 'U' */
    {	0x1769, 0x1749, 0x1709, 0x1700 }, /* key 23 - 'I' */
    {	0x186f, 0x184f, 0x180f, 0x1800 }, /* key 24 - 'O' */
    {	0x1970, 0x1950, 0x1910, 0x1900 }, /* key 25 - 'P' */
    {	0x1a5b, 0x1a7b, 0x1a1b, 0xffff }, /* key 26 - '[' */
    {	0x1b5d, 0x1b7d, 0x1b1d, 0xffff }, /* key 27 - ']' */
    {	0x1c0d, 0x1c0d, 0x1c0a, 0xffff }, /* key 28 - CR */
    {	0xffff, 0xffff, 0xffff, 0xffff }, /* key 29 - control */
    {	0x1e61, 0x1e41, 0x1e01, 0x1e00 }, /* key 30 - 'A' */
    {	0x1f73, 0x1f53, 0x1f13, 0x1f00 }, /* key 31 - 'S' */
    {	0x2064, 0x2044, 0x2004, 0x2000 }, /* key 32 - 'D' */
    {	0x2166, 0x2146, 0x2106, 0x2100 }, /* key 33 - 'F' */
    {	0x2267, 0x2247, 0x2207, 0x2200 }, /* key 34 - 'G' */
    {	0x2368, 0x2348, 0x2308, 0x2300 }, /* key 35 - 'H' */
    {	0x246a, 0x244a, 0x240a, 0x2400 }, /* key 36 - 'J' */
    {	0x256b, 0x254b, 0x250b, 0x2500 }, /* key 37 - 'K' */
    {	0x266c, 0x264c, 0x260c, 0x2600 }, /* key 38 - 'L' */
    {	0x273b, 0x273a, 0xffff, 0xffff }, /* key 39 - ';' */
    {	0x2827, 0x2822, 0xffff, 0xffff }, /* key 40 - ''' */
    {	0x2960, 0x297e, 0xffff, 0xffff }, /* key 41 - '`' */
    {	0xffff, 0xffff, 0xffff, 0xffff }, /* key 42 - left shift */
    {	0x2b5c, 0x2b7c, 0x2b1c, 0xffff }, /* key 43 - '' */
    {	0x2c7a, 0x2c5a, 0x2c1a, 0x2c00 }, /* key 44 - 'Z' */
    {	0x2d78, 0x2d58, 0x2d18, 0x2d00 }, /* key 45 - 'X' */
    {	0x2e63, 0x2e43, 0x2e03, 0x2e00 }, /* key 46 - 'C' */
    {	0x2f76, 0x2f56, 0x2f16, 0x2f00 }, /* key 47 - 'V' */
    {	0x3062, 0x3042, 0x3002, 0x3000 }, /* key 48 - 'B' */
    {	0x316e, 0x314e, 0x310e, 0x3100 }, /* key 49 - 'N' */
    {	0x326d, 0x324d, 0x320d, 0x3200 }, /* key 50 - 'M' */
    {	0x332c, 0x333c, 0xffff, 0xffff }, /* key 51 - ',' */
    {	0x342e, 0x343e, 0xffff, 0xffff }, /* key 52 - '.' */
    {	0x352f, 0x353f, 0xffff, 0xffff }, /* key 53 - '/' */
    {	0xffff, 0xffff, 0xffff, 0xffff }, /* key 54 - right shift - */
    {	0x372a, 0xffff, 0x3772, 0xffff }, /* key 55 - prt-scr - */
    {	0xffff, 0xffff, 0xffff, 0xffff }, /* key 56 - Alt - */
    {	0x3920, 0x3920, 0x3920, 0x3920 }, /* key 57 - space bar */
    {	0xffff, 0xffff, 0xffff, 0xffff }, /* key 58 - caps-lock -  */
    {	0x3b00, 0x5400, 0x5e00, 0x6800 }, /* key 59 - F1 */
    {	0x3c00, 0x5500, 0x5f00, 0x6900 }, /* key 60 - F2 */
    {	0x3d00, 0x5600, 0x6000, 0x6a00 }, /* key 61 - F3 */
    {	0x3e00, 0x5700, 0x6100, 0x6b00 }, /* key 62 - F4 */
    {	0x3f00, 0x5800, 0x6200, 0x6c00 }, /* key 63 - F5 */
    {	0x4000, 0x5900, 0x6300, 0x6d00 }, /* key 64 - F6 */
    {	0x4100, 0x5a00, 0x6400, 0x6e00 }, /* key 65 - F7 */
    {	0x4200, 0x5b00, 0x6500, 0x6f00 }, /* key 66 - F8 */
    {	0x4300, 0x5c00, 0x6600, 0x7000 }, /* key 67 - F9 */
    {	0x4400, 0x5d00, 0x6700, 0x7100 }, /* key 68 - F10 */
    {	0xffff, 0xffff, 0xffff, 0xffff }, /* key 69 - num-lock - */
    {	0xffff, 0xffff, 0xffff, 0xffff }, /* key 70 - scroll-lock -  */
    {	0x4700, 0x4737, 0x7700, 0xffff }, /* key 71 - home */
    {	0x4800, 0x4838, 0x8d00, 0x9800 }, /* key 72 - cursor up */
    {	0x4900, 0x4939, 0x8400, 0xffff }, /* key 73 - page up */
    {	0x4a2d, 0x4a2d, 0xffff, 0xffff }, /* key 74 - minus sign */
    {	0x4b00, 0x4b34, 0x7300, 0xffff }, /* key 75 - cursor left */
    {	0xffff, 0x4c35, 0xffff, 0xffff }, /* key 76 - center key */
    {	0x4d00, 0x4d36, 0x7400, 0xffff }, /* key 77 - cursor right */
    {	0x4e2b, 0x4e2b, 0xffff, 0xffff }, /* key 78 - plus sign */
    {	0x4f00, 0x4f31, 0x7500, 0xffff }, /* key 79 - end */
    {	0x5000, 0x5032, 0x9100, 0xa000 }, /* key 80 - cursor down */
    {	0x5100, 0x5133, 0x7600, 0xffff }, /* key 81 - page down */
202
203
    {	CIO_KEY_IC, CIO_KEY_SHIFT_IC, CIO_KEY_CTRL_IC, CIO_KEY_ALT_IC}, /* key 82 - insert */
    {	CIO_KEY_DC, CIO_KEY_SHIFT_DC, CIO_KEY_CTRL_DC, CIO_KEY_ALT_IC}, /* key 83 - delete */
deuce's avatar
deuce committed
204
205
206
207
208
209
210
    {	0xffff, 0xffff, 0xffff, 0xffff }, /* key 84 - sys key */
    {	0xffff, 0xffff, 0xffff, 0xffff }, /* key 85 */
    {	0xffff, 0xffff, 0xffff, 0xffff }, /* key 86 */
    {	0x8500, 0x5787, 0x8900, 0x8b00 }, /* key 87 - F11 */
    {	0x8600, 0x5888, 0x8a00, 0x8c00 }, /* key 88 - F12 */
};

deuce's avatar
deuce committed
211
212
static void resize_xim(void)
{
Deucе's avatar
Deucе committed
213
	int width = bitmap_width * x_cvstat.scaling;
214
	int height = bitmap_height * x_cvstat.scaling;
Deucе's avatar
Deucе committed
215

216
	aspect_correct(&width, &height, x_cvstat.aspect_width, x_cvstat.aspect_height);
deuce's avatar
deuce committed
217
	if (xim) {
Deucе's avatar
Deucе committed
218
219
		if (width == xim->width
		    && height == xim->height) {
220
221
222
223
			if (last) {
				release_buffer(last);
				last = NULL;
			}
224
			x11.XFillRectangle(dpy, win, gc, 0, 0, x11_window_width, x11_window_height);
225
226
			return;
		}
deuce's avatar
deuce committed
227
228
229
230
231
232
#ifdef XDestroyImage
		XDestroyImage(xim);
#else
		x11.XDestroyImage(xim);
#endif
	}
233
	if (last) {
Deucе's avatar
Deucе committed
234
		release_buffer(last);
235
236
		last = NULL;
	}
Deucе's avatar
Deucе committed
237
238
	xim = x11.XCreateImage(dpy, &visual, depth, ZPixmap, 0, NULL, width, height, 32, 0);
	xim->data=(char *)calloc(1, xim->bytes_per_line*xim->height);
239
	x11.XFillRectangle(dpy, win, gc, 0, 0, x11_window_width, x11_window_height);
deuce's avatar
deuce committed
240
241
}

242
243
244
245
246
247
248
249
250
251
252
253
254
/* Swiped from FreeBSD libc */
static int
my_fls(unsigned long mask)
{
        int bit;

        if (mask == 0)
                return (0);
        for (bit = 1; mask != 1; bit++)
                mask = mask >> 1;
        return (bit);
}

deuce's avatar
deuce committed
255
256
257
/* Get a connection to the X server and create the window. */
static int init_window()
{
258
259
	XGCValues gcv;
	int i;
260
	XWMHints *wmhints;
deuce's avatar
deuce committed
261
	XClassHint *classhints;
262
263
264
265
266
267
	int ret;
	int best=-1;
	int best_depth=0;
	int best_cmap=0;
	XVisualInfo template = {0};
	XVisualInfo *vi;
deuce's avatar
deuce committed
268
269

	dpy = x11.XOpenDisplay(NULL);
270
	if (dpy == NULL) {
deuce's avatar
deuce committed
271
272
		return(-1);
	}
273
274
	xfd = ConnectionNumber(dpy);
	x11.utf8 = x11.XInternAtom(dpy, "UTF8_STRING", False);
275
	x11.targets = x11.XInternAtom(dpy, "TARGETS", False);
deuce's avatar
deuce committed
276

277
278
279
280
281
282
283
284
285
286
287
	template.screen = DefaultScreen(dpy);
	template.class = TrueColor;
	vi = x11.XGetVisualInfo(dpy, VisualScreenMask | VisualClassMask, &template, &ret);
	for (i=0; i<ret; i++) {
		if (vi[i].depth >= best_depth && vi[i].colormap_size >= best_cmap) {
			best = i;
			best_depth = vi[i].depth;
		}
	}
	if (best != -1) {
		visual = *vi[best].visual;
288
289
290
291
292
293
294
		/*
		 * TODO: Set VisualIsRGB8 if appropriate...
		 * "appropriate" in this context means it's a sequence of
		 * unpadded uint32_t values in XXRRGGBB format where XX is
		 * ignored, and RR, GG, and BB are Red, Green, Blue values
		 * respectively.
		 */
295
296
297
298
		base_pixel = ULONG_MAX;
		base_pixel &= ~visual.red_mask;
		base_pixel &= ~visual.green_mask;
		base_pixel &= ~visual.blue_mask;
299
300
301
		r_shift = my_fls(visual.red_mask)-16;
		g_shift = my_fls(visual.green_mask)-16;
		b_shift = my_fls(visual.blue_mask)-16;
302
303
304
305
306
307
308
309
	}
	else {
		fprintf(stderr, "Unable to find TrueColor visual\n");
		x11.XFree(vi);
		return -1;
	}
	x11.XFree(vi);

deuce's avatar
deuce committed
310
311
312
313
314
	/* Allocate black and white */
	black=BlackPixel(dpy, DefaultScreen(dpy));
	white=WhitePixel(dpy, DefaultScreen(dpy));

    /* Create window, but defer setting a size and GC. */
315
316
317
318
319
320
	XSetWindowAttributes wa = {0};
	wa.colormap = x11.XCreateColormap(dpy, DefaultRootWindow(dpy), &visual, AllocNone);
	wa.background_pixel = black;
	wa.border_pixel = black;
	depth = best_depth;
    win = x11.XCreateWindow(dpy, DefaultRootWindow(dpy), 0, 0,
321
			      640*x_cvstat.scaling, 400*x_cvstat.scaling, 2, depth, InputOutput, &visual, CWColormap | CWBorderPixel | CWBackPixel, &wa);
deuce's avatar
deuce committed
322

deuce's avatar
deuce committed
323
324
325
	classhints=x11.XAllocClassHint();
	if (classhints)
		classhints->res_name = classhints->res_class = "CIOLIB";
326
327
328
	wmhints=x11.XAllocWMHints();
	if(wmhints) {
		wmhints->initial_state=NormalState;
deuce's avatar
deuce committed
329
		wmhints->flags = (StateHint/* | IconPixmapHint | IconMaskHint*/ | InputHint);
330
		wmhints->input = True;
deuce's avatar
deuce committed
331
332
		x11.XSetWMProperties(dpy, win, NULL, NULL, 0, 0, NULL, wmhints, classhints);
		x11.XFree(wmhints);
333
	}
deuce's avatar
deuce committed
334
335
336
337
338
339
340
	im = x11.XOpenIM(dpy, NULL, "CIOLIB", "CIOLIB");
	if (im != NULL) {
		ic = x11.XCreateIC(im, XNClientWindow, win, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, NULL);
		if (ic)
			x11.XSetICFocus(ic);
	}

deuce's avatar
deuce committed
341
342
	if (classhints)
		x11.XFree(classhints);
343

deuce's avatar
deuce committed
344
345
	WM_DELETE_WINDOW = x11.XInternAtom(dpy, "WM_DELETE_WINDOW", False);

deuce's avatar
deuce committed
346
	gcv.function = GXcopy;
347
348
	gcv.foreground = black | 0xff000000;
	gcv.background = white;
deuce's avatar
deuce committed
349
	gcv.graphics_exposures = False;
350
	gc=x11.XCreateGC(dpy, win, GCFunction | GCForeground | GCBackground | GCGraphicsExposures, &gcv);
deuce's avatar
deuce committed
351

352
	x11.XSelectInput(dpy, win, KeyReleaseMask | KeyPressMask |
deuce's avatar
deuce committed
353
354
355
		     ExposureMask | ButtonPressMask
		     | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask);

356
	x11.XStoreName(dpy, win, "SyncConsole");
deuce's avatar
deuce committed
357
	x11.XSetWMProtocols(dpy, win, &WM_DELETE_WINDOW, 1);
deuce's avatar
deuce committed
358
359
360
361

	return(0);
}

362
363
364
365
/*
 * Actually maps (shows) the window
 */
static void map_window()
deuce's avatar
deuce committed
366
{
367
368
369
	XSizeHints *sh;
	int minwidth = bitmap_width;
	int minheight = bitmap_height;
deuce's avatar
deuce committed
370

371
372
	sh = x11.XAllocSizeHints();
	if (sh == NULL) {
deuce's avatar
deuce committed
373
374
375
376
		fprintf(stderr, "Could not get XSizeHints structure");
		exit(1);
	}

377
378
	sh->base_width = bitmap_width * x_cvstat.scaling;
	sh->base_height = bitmap_height * x_cvstat.scaling;
deuce's avatar
deuce committed
379

380
381
	aspect_correct(&sh->base_width, &sh->base_height, x_cvstat.aspect_width, x_cvstat.aspect_height);
	aspect_correct(&minwidth, &minheight, x_cvstat.aspect_width, x_cvstat.aspect_height);
Deucе's avatar
Deucе committed
382

383
384
	sh->min_width = sh->width_inc = sh->min_aspect.x = sh->max_aspect.x = minwidth;
	sh->min_height = sh->height_inc = sh->min_aspect.y = sh->max_aspect.y = minheight;
deuce's avatar
deuce committed
385

386
	sh->flags = USSize | PMinSize | PSize | PResizeInc | PAspect;
deuce's avatar
deuce committed
387

388
389
	x11.XSetWMNormalHints(dpy, win, sh);
	x11.XMapWindow(dpy, win);
deuce's avatar
deuce committed
390

391
392
393
	x11.XFree(sh);

	return;
deuce's avatar
deuce committed
394
395
}

396
397
398
/* Resize the window. This function is called after a mode change. */
static void resize_window()
{
399
400
401
402
403
	int width = bitmap_width * x_cvstat.scaling;
	int height = bitmap_height * x_cvstat.scaling;

	aspect_correct(&width, &height, x_cvstat.aspect_width, x_cvstat.aspect_height);
	x11.XResizeWindow(dpy, win, width, height);
Deucе's avatar
Deucе committed
404
	resize_xim();
deuce's avatar
deuce committed
405

Deucе's avatar
Deucе committed
406
	return;
407
408
}

409
static void init_mode_internal(int mode)
deuce's avatar
deuce committed
410
{
411
	int oldcols;
deuce's avatar
deuce committed
412

413
	oldcols=x_cvstat.cols;
414

415
	pthread_mutex_lock(&blinker_lock);
416
	pthread_mutex_lock(&vstatlock);
417
	if (last) {
Deucе's avatar
Deucе committed
418
		release_buffer(last);
419
420
		last = NULL;
	}
421
	bitmap_drv_init_mode(mode, &bitmap_width, &bitmap_height);
deuce's avatar
deuce committed
422

deuce's avatar
deuce committed
423
	/* Deal with 40 col doubling */
deuce's avatar
deuce committed
424
	if(oldcols != vstat.cols) {
deuce's avatar
deuce committed
425
		if(oldcols == 40)
deuce's avatar
deuce committed
426
427
428
			vstat.scaling /= 2;
		if(vstat.cols == 40)
			vstat.scaling *= 2;
deuce's avatar
deuce committed
429
	}
deuce's avatar
deuce committed
430
431
	if(vstat.scaling < 1)
		vstat.scaling = 1;
deuce's avatar
deuce committed
432

deuce's avatar
deuce committed
433
434
	x_cvstat = vstat;
	pthread_mutex_unlock(&vstatlock);
deuce's avatar
deuce committed
435
	pthread_mutex_unlock(&blinker_lock);
436
	map_window();
437
438
439
440
441
}

static void check_scaling(void)
{
	if (old_scaling != x_cvstat.scaling) {
442
		resize_window();
443
444
445
446
447
448
449
		old_scaling = x_cvstat.scaling;
	}
}

static int init_mode(int mode)
{
	init_mode_internal(mode);
450
	resize_window();
451
	bitmap_drv_request_pixels();
deuce's avatar
deuce committed
452
453

	sem_post(&mode_set);
454
	return(0);
deuce's avatar
deuce committed
455
456
457
458
459
460
461
}

static int video_init()
{
    /* If we are running under X, get a connection to the X server and create
       an empty window of size (1, 1). It makes a couple of init functions a
       lot easier. */
462
463
	if(x_cvstat.scaling<1)
		x_setscaling(1);
deuce's avatar
deuce committed
464
465
466
    if(init_window())
		return(-1);

467
	bitmap_drv_init(x11_drawrect, x11_flush);
deuce's avatar
deuce committed
468
469

    /* Initialize mode 3 (text, 80x25, 16 colors) */
470
    init_mode_internal(3);
deuce's avatar
deuce committed
471
472
473
474

    return(0);
}

Deucе's avatar
Deucе committed
475
476
static void
local_draw_rect(struct rectlist *rect)
deuce's avatar
deuce committed
477
{
478
	int x, y, xoff = 0, yoff = 0;
479
480
	unsigned int r, g, b;
	unsigned long pixel;
Deucе's avatar
Deucе committed
481
482
483
	int cleft;
	int cright = -100;
	int ctop;
484
	int cbottom = -1;
485
	int idx;
Deucе's avatar
Deucе committed
486
	uint32_t last_pixel = 0x55555555;
Deucе's avatar
Deucе committed
487
	struct graphics_buffer *source;
deuce's avatar
deuce committed
488

Deucе's avatar
Deucе committed
489
490
	if (bitmap_width != rect->rect.width || bitmap_height != rect->rect.height) {
		bitmap_drv_free_rect(rect);
491
		return;
Deucе's avatar
Deucе committed
492
	}
493
494
495
496
497
498
499
500

	xoff = (x11_window_width - xim->width) / 2;
	if (xoff < 0)
		xoff = 0;
	yoff = (x11_window_height - xim->height) / 2;
	if (yoff < 0)
		yoff = 0;

Deucе's avatar
Deucе committed
501
	// Scale...
502
	source = do_scale(rect, x_cvstat.scaling, x_cvstat.scaling, x_cvstat.aspect_width, x_cvstat.aspect_height);
Deucе's avatar
Deucе committed
503
504
	bitmap_drv_free_rect(rect);
	if (source == NULL)
Deucе's avatar
Deucе committed
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
		return;
	cleft = source->w;
	ctop = source->h;

	xoff = (x11_window_width - source->w) / 2;
	if (xoff < 0)
		xoff = 0;
	yoff = (x11_window_height - source->h) / 2;
	if (yoff < 0)
		yoff = 0;

	if (last && (last->w != source->w || last->h != source->h)) {
		release_buffer(last);
		last = NULL;
	}
Deucе's avatar
Deucе committed
520

521
	/* TODO: Translate into local colour depth */
Deucе's avatar
Deucе committed
522
	idx = 0;
Deucе's avatar
Deucе committed
523

Deucе's avatar
Deucе committed
524
525
	for (y = 0; y < source->h; y++) {
		for (x = 0; x < source->w; x++) {
526
			if (last) {
Deucе's avatar
Deucе committed
527
				if (last->data[idx] != source->data[idx]) {
528
529
530
531
					if (x < cleft)
						cleft = x;
					if (x > cright)
						cright = x;
532
533
534
535
					if (y < ctop)
						ctop = y;
					if (y > cbottom)
						cbottom = y;
536
537
				}
				else {
Deucе's avatar
Deucе committed
538
539
					idx++;
					continue;
540
541
				}
			}
542
			if (VisualIsRGB8) {
Deucе's avatar
Deucе committed
543
544
				pixel = source->data[idx];
				((uint32_t*)xim->data)[idx] = pixel;
Deucе's avatar
Deucе committed
545
			}
Deucе's avatar
Deucе committed
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
			else {
				if (last_pixel != source->data[idx]) {
					last_pixel = source->data[idx];
					r = source->data[idx] >> 16 & 0xff;
					g = source->data[idx] >> 8 & 0xff;
					b = source->data[idx] & 0xff;
					r = (r<<8)|r;
					g = (g<<8)|g;
					b = (b<<8)|b;
					pixel = base_pixel;
					if (r_shift >= 0)
						pixel |= (r << r_shift) & visual.red_mask;
					else
						pixel |= (r >> (0-r_shift)) & visual.red_mask;
					if (g_shift >= 0)
						pixel |= (g << g_shift) & visual.green_mask;
					else
						pixel |= (g >> (0-g_shift)) & visual.green_mask;
					if (b_shift >= 0)
						pixel |= (b << b_shift) & visual.blue_mask;
					else
						pixel |= (b >> (0-b_shift)) & visual.blue_mask;
				}
deuce's avatar
deuce committed
569
#ifdef XPutPixel
Deucе's avatar
Deucе committed
570
				XPutPixel(xim, x, y, pixel);
deuce's avatar
deuce committed
571
#else
Deucе's avatar
Deucе committed
572
				x11.XPutPixel(xim, x, y, pixel);
deuce's avatar
deuce committed
573
#endif
Deucе's avatar
Deucе committed
574
			}
575
576
577
			idx++;
		}
		/* This line was changed */
Deucе's avatar
Deucе committed
578
		// TODO: Previously this did one update per display line...
Deucе's avatar
Deucе committed
579
		if (last && cright >= 0 && (cbottom != y || y == source->h - 1)) {
580
581
582
			x11.XPutImage(dpy, win, gc, xim, cleft, ctop
			    , cleft + xoff, ctop + yoff
			    , (cright - cleft + 1), (cbottom - ctop + 1));
Deucе's avatar
Deucе committed
583
			cleft = source->w;
Deucе's avatar
Deucе committed
584
			cright = cbottom = -100;
Deucе's avatar
Deucе committed
585
			ctop = source->h;
deuce's avatar
deuce committed
586
587
588
		}
	}

589
	if (last == NULL)
Deucе's avatar
Deucе committed
590
		x11.XPutImage(dpy, win, gc, xim, 0, 0, xoff, yoff, source->w, source->h);
591
	else
Deucе's avatar
Deucе committed
592
593
		release_buffer(last);
	last = source;
deuce's avatar
deuce committed
594
595
}

596
597
598
599
600
static void handle_resize_event(int width, int height)
{
	int newFSH=1;
	int newFSW=1;

601
602
603
	aspect_fix(&width, &height, x_cvstat.aspect_width, x_cvstat.aspect_height);
	newFSH=width / bitmap_width;
	newFSW=height / bitmap_height;
604
605
606
607
608
	if(newFSW<1)
		newFSW=1;
	if(newFSH<1)
		newFSH=1;
	if(newFSH<newFSW)
609
		x_setscaling(newFSH);
610
	else
611
612
613
614
		x_setscaling(newFSW);
	old_scaling = x_cvstat.scaling;
	if(x_cvstat.scaling > 16)
		x_setscaling(16);
615

616
	/*
617
618
	 * We only need to resize if the width/height are not even multiples,
	 * or if the two axis don't scale the same way.
619
620
	 * Otherwise, we can simply resend everything
	 */
621
622
623
624
	if (newFSH != newFSW)
		resize_window();
	else
		resize_xim();
625
	bitmap_drv_request_pixels();
626
627
}

628
static void expose_rect(int x, int y, int width, int height)
629
630
{
	int sx,sy,ex,ey;
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
	int xoff=0, yoff=0;

	xoff = (x11_window_width - xim->width) / 2;
	if (xoff < 0)
		xoff = 0;
	yoff = (x11_window_height - xim->height) / 2;
	if (yoff < 0)
		yoff = 0;

	if (xoff > 0 || yoff > 0) {
		if (x < xoff || y < yoff || x + width > xoff + xim->width || y + height > yoff + xim->height) {
			x11.XFillRectangle(dpy, win, gc, 0, 0, x11_window_width, yoff);
			x11.XFillRectangle(dpy, win, gc, 0, yoff, xoff, yoff + xim->height);
			x11.XFillRectangle(dpy, win, gc, xoff+xim->width, yoff, x11_window_width, yoff + xim->height);
			x11.XFillRectangle(dpy, win, gc, 0, yoff + xim->height, x11_window_width, x11_window_height);
		}
	}
648

649
	sx=(x-xoff)/x_cvstat.scaling;
650
	sy=(y-yoff)/(x_cvstat.scaling);
651
652
653
654
655
656
657
658
659
660
661
	if (sx < 0)
		sx = 0;
	if (sy < 0)
		sy = 0;

	ex=(x-xoff)+width-1;
	ey=(y-yoff)+height-1;
	if (ex < 0)
		ex = 0;
	if (ey < 0)
		ey = 0;
662
663
	if((ex+1)%x_cvstat.scaling) {
		ex += x_cvstat.scaling-(ex%x_cvstat.scaling);
664
	}
665
666
	if((ey+1)%(x_cvstat.scaling)) {
		ey += x_cvstat.scaling-(ey%(x_cvstat.scaling));
667
	}
668
	ex=ex/x_cvstat.scaling;
669
	ey=ey/(x_cvstat.scaling);
670

671
672
	/* Since we're exposing, we *have* to redraw */
	if (last) {
Deucе's avatar
Deucе committed
673
		release_buffer(last);
674
		last = NULL;
675
		bitmap_drv_request_some_pixels(sx, sy, ex-sx+1, ey-sy+1);
676
	}
677
678
679
	// Do nothing...
	if (sx == ex || sy == ey)
		return;
680
	bitmap_drv_request_some_pixels(sx, sy, ex-sx+1, ey-sy+1);
681
682
}

683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
static bool
xlat_mouse_xy(int *x, int *y)
{
	int xoff, yoff;

	xoff = (x11_window_width - xim->width) / 2;
	if (xoff < 0)
		xoff = 0;
	yoff = (x11_window_height - xim->height) / 2;
	if (yoff < 0)
		yoff = 0;

	if (*x < xoff)
		return false;
	if (*y < yoff)
		return false;
	*x -= xoff;
	*y -= yoff;
	if (*x >= xim->width)
		return false;
	if (*y >= xim->height)
		return false;
	*x *= x_cvstat.scrnwidth;
	*y *= x_cvstat.scrnheight;
	*x /= xim->width;
	*y /= xim->height;
	return true;
}

deuce's avatar
deuce committed
712
713
static int x11_event(XEvent *ev)
{
deuce's avatar
deuce committed
714
715
	if (x11.XFilterEvent(ev, win))
		return 0;
deuce's avatar
deuce committed
716
	switch (ev->type) {
deuce's avatar
deuce committed
717
718
719
720
721
722
		case ClientMessage:
			if (ev->xclient.format == 32 && ev->xclient.data.l[0] == WM_DELETE_WINDOW) {
				uint16_t key=CIO_KEY_QUIT;
				write(key_pipe[1], &key, 2);
			}
			break;
deuce's avatar
deuce committed
723
		/* Graphics related events */
724
725
726
		case ConfigureNotify: {
			int width, height;

727
			if (x11_window_xpos != ev->xconfigure.x || x11_window_ypos != ev->xconfigure.y) {
Deucе's avatar
Deucе committed
728
729
				x11_window_xpos=ev->xconfigure.x;
				x11_window_ypos=ev->xconfigure.y;
730
731
			}
			if (x11_window_width != ev->xconfigure.width || x11_window_height != ev->xconfigure.height) {
Deucе's avatar
Deucе committed
732
733
734
				x11_window_width=ev->xconfigure.width;
				x11_window_height=ev->xconfigure.height;
				handle_resize_event(ev->xconfigure.width, ev->xconfigure.height);
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
				break;
			}
			width = bitmap_width * x_cvstat.scaling;
			height = bitmap_height * x_cvstat.scaling;

			aspect_correct(&width, &height, x_cvstat.aspect_width, x_cvstat.aspect_height);
			if (ev->xconfigure.width != width || ev->xconfigure.height != height) {
				// We can't have the size we requested... accept the size we got.
				int newFSH=1;
				int newFSW=1;

				width = ev->xconfigure.width;
				height = ev->xconfigure.height;
				aspect_fix(&width, &height, x_cvstat.aspect_width, x_cvstat.aspect_height);
				newFSH=width / bitmap_width;
				newFSW=height / bitmap_height;
				if(newFSW<1)
					newFSW=1;
				if(newFSH<1)
					newFSH=1;
				if(newFSH<newFSW)
					x_setscaling(newFSH);
				else
					x_setscaling(newFSW);
				old_scaling = x_cvstat.scaling;
				if(x_cvstat.scaling > 16)
					x_setscaling(16);

				resize_xim();
				bitmap_drv_request_pixels();
Deucе's avatar
Deucе committed
765
			}
deuce's avatar
deuce committed
766
			break;
767
		}
768
769
770
		case NoExpose:
			break;
		case GraphicsExpose:
771
			expose_rect(ev->xgraphicsexpose.x, ev->xgraphicsexpose.y, ev->xgraphicsexpose.width, ev->xgraphicsexpose.height);
deuce's avatar
deuce committed
772
			break;
773
		case Expose:
774
			expose_rect(ev->xexpose.x, ev->xexpose.y, ev->xexpose.width, ev->xexpose.height);
deuce's avatar
deuce committed
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
			break;

		/* Copy/Paste events */
		case SelectionClear:
			{
				XSelectionClearEvent *req;

				req=&(ev->xselectionclear);
				pthread_mutex_lock(&copybuf_mutex);
				if(req->selection==CONSOLE_CLIPBOARD)
					FREE_AND_NULL(copybuf);
				pthread_mutex_unlock(&copybuf_mutex);
			}
			break;
		case SelectionNotify:
			{
				int format;
				unsigned long len, bytes_left, dummy;

				if(ev->xselection.selection != CONSOLE_CLIPBOARD)
					break;
				if(ev->xselection.requestor!=win)
					break;
798
				if(ev->xselection.property) {
deuce's avatar
deuce committed
799
					x11.XGetWindowProperty(dpy, win, ev->xselection.property, 0, 0, True, AnyPropertyType, &pastebuf_format, &format, &len, &bytes_left, (unsigned char **)(&pastebuf));
800
					if(bytes_left > 0 && format==8) {
deuce's avatar
deuce committed
801
						x11.XGetWindowProperty(dpy, win, ev->xselection.property, 0, bytes_left, True, AnyPropertyType, &pastebuf_format, &format, &len, &dummy, (unsigned char **)&pastebuf);
802
803
						if (x11.utf8 && pastebuf_format == x11.utf8) {
							char *opb = pastebuf;
deuce's avatar
deuce committed
804
							pastebuf = (char *)utf8_to_cp(CIOLIB_ISO_8859_1, (uint8_t *)pastebuf, '?', strlen(pastebuf), NULL);
805
806
807
808
809
810
							if (pastebuf == NULL)
								pastebuf = opb;
							else
								x11.XFree(opb);
						}
					}
811
812
813
					else
						pastebuf=NULL;
				}
deuce's avatar
deuce committed
814
815
816
817
818
819
				else
					pastebuf=NULL;

				/* Set paste buffer */
				sem_post(&pastebuf_set);
				sem_wait(&pastebuf_used);
820
821
822
823
				if (x11.utf8 && pastebuf_format == x11.utf8)
					free(pastebuf);
				else
					x11.XFree(pastebuf);
deuce's avatar
deuce committed
824
825
826
827
828
829
830
				pastebuf=NULL;
			}
			break;
		case SelectionRequest:
			{
				XSelectionRequestEvent *req;
				XEvent respond;
deuce's avatar
deuce committed
831
832
				Atom supported[3];
				int count = 0;
deuce's avatar
deuce committed
833
834
835

				req=&(ev->xselectionrequest);
				pthread_mutex_lock(&copybuf_mutex);
deuce's avatar
deuce committed
836
837
838
839
				if (x11.targets == 0)
					x11.targets = x11.XInternAtom(dpy, "TARGETS", False);
				respond.xselection.property=None;
				if(copybuf!=NULL) {
deuce's avatar
deuce committed
840
					if(req->target==XA_STRING) {
deuce's avatar
deuce committed
841
						char *cpstr = utf8_to_cp(CIOLIB_ISO_8859_1, (uint8_t *)copybuf, '?', strlen(copybuf), NULL);
deuce's avatar
deuce committed
842
843
						if (cpstr != NULL) {
							x11.XChangeProperty(dpy, req->requestor, req->property, XA_STRING, 8, PropModeReplace, (uint8_t *)cpstr, strlen((char *)cpstr));
844
							respond.xselection.property=req->property;
deuce's avatar
deuce committed
845
							free(cpstr);
846
847
						}
					}
deuce's avatar
deuce committed
848
849
850
851
					else if(req->target == x11.utf8) {
						x11.XChangeProperty(dpy, req->requestor, req->property, x11.utf8, 8, PropModeReplace, (uint8_t *)copybuf, strlen((char *)copybuf));
						respond.xselection.property=req->property;
					}
852
					else if(req->target == x11.targets) {
deuce's avatar
deuce committed
853
854
855
856
857
858
859
860
861
						if (x11.utf8 == 0)
							x11.utf8 = x11.XInternAtom(dpy, "UTF8_STRING", False);

						supported[count++] = x11.targets;
						supported[count++] = XA_STRING;
						if (x11.utf8)
							supported[count++] = x11.utf8;
						x11.XChangeProperty(dpy, req->requestor, req->property, XA_ATOM, 32, PropModeReplace, (unsigned char *)supported, count);
						respond.xselection.property=req->property;
862
					}
deuce's avatar
deuce committed
863
864
865
866
				}
				respond.xselection.requestor=req->requestor;
				respond.xselection.selection=req->selection;
				respond.xselection.time=req->time;
deuce's avatar
deuce committed
867
868
869
				respond.xselection.target=req->target;
				respond.xselection.type=SelectionNotify;
				respond.xselection.display=req->display;
deuce's avatar
deuce committed
870
				x11.XSendEvent(dpy,req->requestor,0,0,&respond);
deuce's avatar
deuce committed
871
				x11.XFlush(dpy);
deuce's avatar
deuce committed
872
873
874
875
876
877
878
879
				pthread_mutex_unlock(&copybuf_mutex);
			}
			break;

		/* Mouse Events */
		case MotionNotify:
			{
				XMotionEvent *me = (XMotionEvent *)ev;
880
881
				if (!xlat_mouse_xy(&me->x, &me->y))
					break;
882
883
884
885
886
				int x_res = me->x;
				int y_res = me->y;

				me->x /= x_cvstat.charwidth;
				me->y /= x_cvstat.charheight;
deuce's avatar
deuce committed
887
888
889
890
891
892
				me->x++;
				me->y++;
				if(me->x<1)
					me->x=1;
				if(me->y<1)
					me->y=1;
893
894
895
896
				if(me->x>x_cvstat.cols)
					me->x=x_cvstat.cols;
				if(me->y>x_cvstat.rows+1)
					me->y=x_cvstat.rows+1;
897
				ciomouse_gotevent(CIOLIB_MOUSE_MOVE,me->x,me->y, x_res, y_res);
deuce's avatar
deuce committed
898
899
900
901
902
	    	}
			break;
		case ButtonRelease:
			{
				XButtonEvent *be = (XButtonEvent *)ev;
903
904
				if (!xlat_mouse_xy(&be->x, &be->y))
					break;
905
906
				int x_res = be->x;
				int y_res = be->y;
deuce's avatar
deuce committed
907

908
909
				be->x/=x_cvstat.charwidth;
				be->y/=x_cvstat.charheight;
deuce's avatar
deuce committed
910
911
912
913
914
915
				be->x++;
				be->y++;
				if(be->x<1)
					be->x=1;
				if(be->y<1)
					be->y=1;
916
917
918
919
				if(be->x>x_cvstat.cols)
					be->x=x_cvstat.cols;
				if(be->y>x_cvstat.rows+1)
					be->y=x_cvstat.rows+1;
deuce's avatar
deuce committed
920
				if (be->button <= 3) {
921
					ciomouse_gotevent(CIOLIB_BUTTON_RELEASE(be->button),be->x,be->y, x_res, y_res);
deuce's avatar
deuce committed
922
923
924
925
926
927
				}
	    	}
			break;
		case ButtonPress:
			{
				XButtonEvent *be = (XButtonEvent *)ev;
928
929
				if (!xlat_mouse_xy(&be->x, &be->y))
					break;
930
931
				int x_res = be->x;
				int y_res = be->y;
deuce's avatar
deuce committed
932

933
934
				be->x/=x_cvstat.charwidth;
				be->y/=x_cvstat.charheight;
deuce's avatar
deuce committed
935
936
937
938
939
940
				be->x++;
				be->y++;
				if(be->x<1)
					be->x=1;
				if(be->y<1)
					be->y=1;
941
942
943
944
				if(be->x>x_cvstat.cols)
					be->x=x_cvstat.cols;
				if(be->y>x_cvstat.rows+1)
					be->y=x_cvstat.rows+1;
945
				if (be->button <= 5) {
946
					ciomouse_gotevent(CIOLIB_BUTTON_PRESS(be->button),be->x,be->y, x_res, y_res);
deuce's avatar
deuce committed
947
948
949
950
951
952
953
954
				}
	    	}
			break;

		/* Keyboard Events */
		case KeyPress:
			{
				static char buf[128];
deuce's avatar
deuce committed
955
				static wchar_t wbuf[128];
deuce's avatar
deuce committed
956
957
958
				KeySym ks;
				int nlock = 0;
				WORD scan = 0xffff;
deuce's avatar
deuce committed
959
960
961
962
				Status lus = 0;
				int cnt;
				int i;
				uint8_t ch;
deuce's avatar
deuce committed
963

deuce's avatar
deuce committed
964
965
966
967
968
969
				if (ic)
					cnt = x11.XwcLookupString(ic, (XKeyPressedEvent *)ev, wbuf, sizeof(wbuf)/sizeof(wbuf[0]), &ks, &lus);
				else {
					cnt = x11.XLookupString((XKeyEvent *)ev, buf, sizeof(buf), &ks, NULL);
					lus = XLookupKeySym;
				}
deuce's avatar
deuce committed
970

deuce's avatar
deuce committed
971
972
973
974
975
				switch(lus) {
					case XLookupNone:
						ks = 0xffff;
						break;
					case XLookupBoth:
976
977
978
					case XLookupChars:
						if (lus == XLookupChars || ((ev->xkey.state & (Mod1Mask | ControlMask)) == 0)) {
							for (i = 0; i < cnt; i++) {
979
980
981
982
								if (wbuf[i] < 127)
									ch = wbuf[i];
								else
									ch = cpchar_from_unicode_cpoint(getcodepage(), wbuf[i], 0);
983
984
985
								if (ch) {
									write(key_pipe[1], &ch, 1);
								}
deuce's avatar
deuce committed
986
							}
987
							break;
deuce's avatar
deuce committed
988
						}
989
						// Fallthrough
deuce's avatar
deuce committed
990
991
992
993
994
995
996
997
998
999
1000
					case XLookupKeySym:
						switch (ks) {
							case XK_Escape:
								scan = 1;
								goto docode;

							case XK_Tab:
							case XK_ISO_Left_Tab:
								scan = 15;
								goto docode;
					
For faster browsing, not all history is shown. View entire blame