ltk

Socket-based GUI for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

event_xlib.c (13941B)


      1 #include <stdio.h>
      2 
      3 #include <X11/XKBlib.h>
      4 #include <X11/extensions/XKBrules.h>
      5 
      6 #include "util.h"
      7 #include "memory.h"
      8 #include "graphics.h"
      9 #include "xlib_shared.h"
     10 #include "config.h"
     11 #include "clipboard_xlib.h"
     12 
     13 #define TEXT_INITIAL_SIZE 128
     14 
     15 static char *text = NULL;
     16 static size_t text_alloc = 0;
     17 static char *cur_kbd = NULL;
     18 /* FIXME: support more buttons?
     19    -> What even makes sense here? Mice can support a bunch
     20    of buttons, but what is sensible here? Just adding
     21    support for higher button numbers would cause problems
     22    when adding other backends (e.g. SDL) that might do
     23    things completely differently. */
     24 /* FIXME: support touch events? */
     25 /* times of last button press/release,
     26    used to implement double/triple-click */
     27 static Time last_button_press[] = {0, 0, 0};
     28 static Time last_button_release[] = {0, 0, 0};
     29 /* positions of last press/release so double/triple-click is
     30    only generated when the position is near enough */
     31 static struct point {
     32 	int x;
     33 	int y;
     34 } press_pos[] = {{0, 0}, {0, 0}, {0, 0}};
     35 static struct point release_pos[] = {{0, 0}, {0, 0}, {0, 0}};
     36 /* stores whether the last button press already was
     37    a double-click to decide if a triple-click should
     38    be generated (same for release) */
     39 static int was_2press[] = {0, 0, 0};
     40 static int was_2release[] = {0, 0, 0};
     41 /* Used to store special next event - currently just
     42    used to implement double/triple-click because the
     43    actual double/triple-click/release event is
     44    generated in addition to the regular press/release */
     45 static int next_event_valid = 0;
     46 static ltk_button_event next_event;
     47 
     48 static ltk_keysym get_keysym(KeySym sym);
     49 
     50 void
     51 ltk_events_cleanup(void) {
     52 	ltk_free(text);
     53 	ltk_free(cur_kbd);
     54 	cur_kbd = NULL;
     55 	text = NULL;
     56 	text_alloc = 0;
     57 }
     58 
     59 static ltk_button_type
     60 get_button(unsigned int button) {
     61 	switch (button) {
     62 	case Button1: return LTK_BUTTONL;
     63 	case Button2: return LTK_BUTTONM;
     64 	case Button3: return LTK_BUTTONR;
     65 	default: return LTK_BUTTONL; /* FIXME: what to do here? */
     66 	}
     67 }
     68 
     69 /* FIXME: properly implement modifiers - see SDL and GTK */
     70 static ltk_mod_type
     71 get_modmask(unsigned int state) {
     72 	ltk_mod_type t = 0;
     73 	if (state & ControlMask)
     74 		t |= LTK_MOD_CTRL;
     75 	if (state & ShiftMask)
     76 		t |= LTK_MOD_SHIFT;
     77 	if (state & Mod1Mask)
     78 		t |= LTK_MOD_ALT;
     79 	if (state & Mod4Mask)
     80 		t |= LTK_MOD_SUPER;
     81 	return t;
     82 }
     83 #ifdef X_HAVE_UTF8_STRING
     84 #define LOOKUP_STRING_FUNC Xutf8LookupString
     85 #else
     86 #define LOOKUP_STRING_FUNC XmbLookupString
     87 #endif
     88 
     89 static ltk_event
     90 process_key(ltk_renderdata *renderdata, size_t lang_index, XEvent *event, ltk_event_type type) {
     91 	ltk_language_mapping *map = ltk_config_get_language_mapping(lang_index);
     92 	/* FIXME: see comment in keys.c in ledit repository */
     93 	if (!text) {
     94 		text = ltk_malloc(TEXT_INITIAL_SIZE);
     95 		text_alloc = TEXT_INITIAL_SIZE;
     96 	}
     97 	unsigned int state = event->xkey.state;
     98 	event->xkey.state &= ~ControlMask;
     99 	KeySym sym;
    100 	int len = 0;
    101 	Status status;
    102 	if (renderdata->xic && type == LTK_KEYPRESS_EVENT) {
    103 		len = LOOKUP_STRING_FUNC(renderdata->xic, &event->xkey, text, text_alloc - 1, &sym, &status);
    104 		if (status == XBufferOverflow) {
    105 			text_alloc = ideal_array_size(text_alloc, len + 1);
    106 			text = ltk_realloc(text, text_alloc);
    107 			len = LOOKUP_STRING_FUNC(renderdata->xic, &event->xkey, text, text_alloc - 1, &sym, &status);
    108 		}
    109 	} else {
    110 		/* FIXME: anything equivalent to XBufferOverflow here? */
    111 		len = XLookupString(&event->xkey, text, text_alloc - 1, &sym, NULL);
    112 		status = XLookupBoth;
    113 	}
    114 	text[len >= (int)text_alloc ? (int)text_alloc - 1 : len] = '\0';
    115 	char *key_text = (status == XLookupChars || status == XLookupBoth) ? text : NULL;
    116 	char *mapped = key_text;
    117 	/* FIXME: BINARY SEARCH! */
    118 	if (key_text && map) {
    119 		for (size_t i = 0; i < map->mappings_len; i++) {
    120 			if (!strcmp(key_text, map->mappings[i].from)) {
    121 				mapped = map->mappings[i].to;
    122 				break;
    123 			}
    124 		}
    125 	}
    126 	return (ltk_event){.key = {
    127 		.type = type,
    128 		.modmask = get_modmask(state),
    129 		.sym = (status == XLookupKeySym || status == XLookupBoth) ? get_keysym(sym) : LTK_KEY_NONE,
    130 		.text = key_text,
    131 		.mapped = mapped
    132 	}};
    133 }
    134 
    135 void
    136 ltk_generate_keyboard_event(ltk_renderdata *renderdata, ltk_event *event) {
    137 	XkbStateRec s;
    138 	XkbGetState(renderdata->dpy, XkbUseCoreKbd, &s);
    139 	XkbDescPtr desc = XkbGetKeyboard(
    140 	    renderdata->dpy, XkbAllComponentsMask, XkbUseCoreKbd
    141 	);
    142 	char *group = XGetAtomName(
    143 	    renderdata->dpy, desc->names->groups[s.group]
    144 	);
    145 	ltk_free(cur_kbd);
    146 	/* just so the interface is the same for all events and the
    147 	   caller doesn't have to free the contained string(s) */
    148 	cur_kbd = ltk_strdup(group);
    149 	*event = (ltk_event){.keyboard = {
    150 		.type = LTK_KEYBOARDCHANGE_EVENT,
    151 		.new_kbd = cur_kbd
    152 	}};
    153 	XFree(group);
    154 	XkbFreeKeyboard(desc, XkbAllComponentsMask, True);
    155 }
    156 
    157 #define DISTSQ(x0, y0, x1, y1) (((x1) - (x0)) * ((x1) - (x0)) + ((y1) - (y0)) * ((y1) - (y0)))
    158 /* return value 0 means valid event returned,
    159    1 means no events pending,
    160    2 means event discarded (need to call again) */
    161 static int
    162 next_event_base(ltk_renderdata *renderdata, ltk_clipboard *clip, size_t lang_index, ltk_event *event) {
    163 	if (next_event_valid) {
    164 		next_event_valid = 0;
    165 		*event = (ltk_event){.button = next_event};
    166 		return 0;
    167 	}
    168 	XEvent xevent;
    169 	if (!XPending(renderdata->dpy))
    170 		return 1;
    171 	XNextEvent(renderdata->dpy, &xevent);
    172 	if (renderdata->xkb_supported && xevent.type == renderdata->xkb_event_type) {
    173 		ltk_generate_keyboard_event(renderdata, event);
    174 		return 0;
    175 	}
    176 	*event = (ltk_event){.type = LTK_UNKNOWN_EVENT};
    177 	if (XFilterEvent(&xevent, None))
    178 		return 2;
    179 	if (clip && ltk_clipboard_filter_event(clip, &xevent))
    180 		return 2;
    181 	int button = 0;
    182 	switch (xevent.type) {
    183 	case ButtonPress:
    184 		button = xevent.xbutton.button;
    185 		/* FIXME: are the buttons really always defined as exactly these values? */
    186 		if (button >= 1 && button <= 3) {
    187 			if (xevent.xbutton.time - last_button_press[button] <= DOUBLECLICK_TIME &&
    188 			    DISTSQ(press_pos[button].x, press_pos[button].y, xevent.xbutton.x, xevent.xbutton.y) <= DOUBLECLICK_DISTSQ) {
    189 				if (was_2press[button]) {
    190 					/* reset so normal press is sent again next time */
    191 					was_2press[button] = 0;
    192 					last_button_press[button] = 0;
    193 					next_event = (ltk_button_event){
    194 						.type = LTK_3BUTTONPRESS_EVENT,
    195 						.button = get_button(button),
    196 						.x = xevent.xbutton.x,
    197 						.y = xevent.xbutton.y
    198 					};
    199 				} else {
    200 					was_2press[button] = 1;
    201 					last_button_press[button] = xevent.xbutton.time;
    202 					next_event = (ltk_button_event){
    203 						.type = LTK_2BUTTONPRESS_EVENT,
    204 						.button = get_button(button),
    205 						.x = xevent.xbutton.x,
    206 						.y = xevent.xbutton.y
    207 					};
    208 				}
    209 				next_event_valid = 1;
    210 			} else {
    211 				last_button_press[button] = xevent.xbutton.time;
    212 				was_2press[button] = 0;
    213 			}
    214 			*event = (ltk_event){.button = {
    215 				.type = LTK_BUTTONPRESS_EVENT,
    216 				.button = get_button(button),
    217 				.x = xevent.xbutton.x,
    218 				.y = xevent.xbutton.y
    219 			}};
    220 			press_pos[button].x = xevent.xbutton.x;
    221 			press_pos[button].y = xevent.xbutton.y;
    222 		} else if (button >= 4 && button <= 7) {
    223 			/* FIXME: compress multiple scroll events into one */
    224 			*event = (ltk_event){.scroll = {
    225 				.type = LTK_SCROLL_EVENT,
    226 				.x = xevent.xbutton.x,
    227 				.y = xevent.xbutton.y,
    228 				.dx = 0,
    229 				.dy = 0
    230 			}};
    231 			switch (button) {
    232 			case 4:
    233 				event->scroll.dy = 1;
    234 				break;
    235 			case 5:
    236 				event->scroll.dy = -1;
    237 				break;
    238 			case 6:
    239 				event->scroll.dx = -1;
    240 				break;
    241 			case 7:
    242 				event->scroll.dx = 1;
    243 				break;
    244 			}
    245 		} else {
    246 			return 2;
    247 		}
    248 		break;
    249 	case ButtonRelease:
    250 		button = xevent.xbutton.button;
    251 		if (button >= 1 && button <= 3) {
    252 			if (xevent.xbutton.time - last_button_release[button] <= DOUBLECLICK_TIME &&
    253 			    DISTSQ(release_pos[button].x, release_pos[button].y, xevent.xbutton.x, xevent.xbutton.y) <= DOUBLECLICK_DISTSQ) {
    254 				if (was_2release[button]) {
    255 					/* reset so normal release is sent again next time */
    256 					was_2release[button] = 0;
    257 					last_button_release[button] = 0;
    258 					next_event = (ltk_button_event){
    259 						.type = LTK_3BUTTONRELEASE_EVENT,
    260 						.button = get_button(button),
    261 						.x = xevent.xbutton.x,
    262 						.y = xevent.xbutton.y
    263 					};
    264 				} else {
    265 					was_2release[button] = 1;
    266 					last_button_release[button] = xevent.xbutton.time;
    267 					next_event = (ltk_button_event){
    268 						.type = LTK_2BUTTONRELEASE_EVENT,
    269 						.button = get_button(button),
    270 						.x = xevent.xbutton.x,
    271 						.y = xevent.xbutton.y
    272 					};
    273 				}
    274 				next_event_valid = 1;
    275 			} else {
    276 				last_button_release[button] = xevent.xbutton.time;
    277 				was_2release[button] = 0;
    278 			}
    279 			*event = (ltk_event){.button = {
    280 				.type = LTK_BUTTONRELEASE_EVENT,
    281 				.button = get_button(button),
    282 				.x = xevent.xbutton.x,
    283 				.y = xevent.xbutton.y
    284 			}};
    285 			release_pos[button].x = xevent.xbutton.x;
    286 			release_pos[button].y = xevent.xbutton.y;
    287 		} else {
    288 			return 2;
    289 		}
    290 		break;
    291 	case MotionNotify:
    292 		/* FIXME: compress motion events */
    293 		*event = (ltk_event){.motion = {
    294 			.type = LTK_MOTION_EVENT,
    295 			.x = xevent.xmotion.x,
    296 			.y = xevent.xmotion.y
    297 		}};
    298 		break;
    299 	case KeyPress:
    300 		*event = process_key(renderdata, lang_index, &xevent, LTK_KEYPRESS_EVENT);
    301 		break;
    302 	case KeyRelease:
    303 		*event = process_key(renderdata, lang_index, &xevent, LTK_KEYRELEASE_EVENT);
    304 		break;
    305 	case ConfigureNotify:
    306 		*event = (ltk_event){.configure = {
    307 			.type = LTK_CONFIGURE_EVENT,
    308 			.x = xevent.xconfigure.x,
    309 			.y = xevent.xconfigure.y,
    310 			.w = xevent.xconfigure.width,
    311 			.h = xevent.xconfigure.height
    312 		}};
    313 		break;
    314 	case Expose:
    315 		/* FIXME: ignoring all of these events would make it not
    316 		   work anymore if the actual rectangle wasn't ignored
    317 		   later anyways */
    318 		if (xevent.xexpose.count == 0) {
    319 			*event = (ltk_event){.expose = {
    320 				.type = LTK_EXPOSE_EVENT,
    321 				.x = xevent.xexpose.x,
    322 				.y = xevent.xexpose.y,
    323 				.w = xevent.xexpose.width,
    324 				.h = xevent.xexpose.height
    325 			}};
    326 		} else {
    327 			return 2;
    328 		}
    329 		break;
    330 	case ClientMessage:
    331 		if ((Atom)xevent.xclient.data.l[0] == renderdata->wm_delete_msg)
    332 			*event = (ltk_event){.type = LTK_WINDOWCLOSE_EVENT};
    333 		else
    334 			return 2;
    335 		break;
    336 	default:
    337 		break;
    338 	}
    339 	return 0;
    340 }
    341 
    342 int
    343 ltk_next_event(ltk_renderdata *renderdata, ltk_clipboard *clip, size_t lang_index, ltk_event *event) {
    344 	int ret = 0;
    345 	while ((ret = next_event_base(renderdata, clip, lang_index, event)) == 2) {
    346 		/* NOP */
    347 	}
    348 	return ret;
    349 }
    350 
    351 /* FIXME: pre-sort array (current sorting is taken from config.c but doesn't make sense here) */
    352 /* FIXME: can some structure of the X keysyms be abused to make this more efficient */
    353 static struct keysym_mapping {
    354 	KeySym xkeysym;
    355 	ltk_keysym keysym;
    356 } keysym_map[] = {
    357 	{XK_BackSpace, LTK_KEY_BACKSPACE},
    358 	{XK_Begin, LTK_KEY_BEGIN},
    359 	{XK_Break, LTK_KEY_BREAK},
    360 	{XK_Cancel, LTK_KEY_CANCEL},
    361 	{XK_Clear, LTK_KEY_CLEAR},
    362 	{XK_Delete, LTK_KEY_DELETE},
    363 	{XK_Down, LTK_KEY_DOWN},
    364 	{XK_End, LTK_KEY_END},
    365 	{XK_Escape, LTK_KEY_ESCAPE},
    366 	{XK_Execute, LTK_KEY_EXECUTE},
    367 
    368 	{XK_F1, LTK_KEY_F1},
    369 	{XK_F10, LTK_KEY_F10},
    370 	{XK_F11, LTK_KEY_F11},
    371 	{XK_F12, LTK_KEY_F12},
    372 	{XK_F2, LTK_KEY_F2},
    373 	{XK_F3, LTK_KEY_F3},
    374 	{XK_F4, LTK_KEY_F4},
    375 	{XK_F5, LTK_KEY_F5},
    376 	{XK_F6, LTK_KEY_F6},
    377 	{XK_F7, LTK_KEY_F7},
    378 	{XK_F8, LTK_KEY_F8},
    379 	{XK_F9, LTK_KEY_F9},
    380 
    381 	{XK_Find, LTK_KEY_FIND},
    382 	{XK_Help, LTK_KEY_HELP},
    383 	{XK_Home, LTK_KEY_HOME},
    384 	{XK_Insert, LTK_KEY_INSERT},
    385 
    386 	{XK_KP_0, LTK_KEY_KP_0},
    387 	{XK_KP_1, LTK_KEY_KP_1},
    388 	{XK_KP_2, LTK_KEY_KP_2},
    389 	{XK_KP_3, LTK_KEY_KP_3},
    390 	{XK_KP_4, LTK_KEY_KP_4},
    391 	{XK_KP_5, LTK_KEY_KP_5},
    392 	{XK_KP_6, LTK_KEY_KP_6},
    393 	{XK_KP_7, LTK_KEY_KP_7},
    394 	{XK_KP_8, LTK_KEY_KP_8},
    395 	{XK_KP_9, LTK_KEY_KP_9},
    396 	{XK_KP_Add, LTK_KEY_KP_ADD},
    397 	{XK_KP_Begin, LTK_KEY_KP_BEGIN},
    398 	{XK_KP_Decimal, LTK_KEY_KP_DECIMAL},
    399 	{XK_KP_Delete, LTK_KEY_KP_DELETE},
    400 	{XK_KP_Divide, LTK_KEY_KP_DIVIDE},
    401 	{XK_KP_Down, LTK_KEY_KP_DOWN},
    402 	{XK_KP_End, LTK_KEY_KP_END},
    403 	{XK_KP_Enter, LTK_KEY_KP_ENTER},
    404 	{XK_KP_Equal, LTK_KEY_KP_EQUAL},
    405 	{XK_KP_Home, LTK_KEY_KP_HOME},
    406 	{XK_KP_Insert, LTK_KEY_KP_INSERT},
    407 	{XK_KP_Left, LTK_KEY_KP_LEFT},
    408 	{XK_KP_Multiply, LTK_KEY_KP_MULTIPLY},
    409 	{XK_KP_Next, LTK_KEY_KP_NEXT},
    410 	{XK_KP_Page_Down, LTK_KEY_KP_PAGE_DOWN},
    411 	{XK_KP_Page_Up, LTK_KEY_KP_PAGE_UP},
    412 	{XK_KP_Prior, LTK_KEY_KP_PRIOR},
    413 	{XK_KP_Right, LTK_KEY_KP_RIGHT},
    414 	{XK_KP_Separator, LTK_KEY_KP_SEPARATOR},
    415 	{XK_KP_Space, LTK_KEY_KP_SPACE},
    416 	{XK_KP_Subtract, LTK_KEY_KP_SUBTRACT},
    417 	{XK_KP_Tab, LTK_KEY_KP_TAB},
    418 	{XK_KP_Up, LTK_KEY_KP_UP},
    419 
    420 	{XK_Left, LTK_KEY_LEFT},
    421 	{XK_Linefeed, LTK_KEY_LINEFEED},
    422 	{XK_Menu, LTK_KEY_MENU},
    423 	{XK_Mode_switch, LTK_KEY_MODE_SWITCH},
    424 	{XK_Next, LTK_KEY_NEXT},
    425 	{XK_Num_Lock, LTK_KEY_NUM_LOCK},
    426 	{XK_Page_Down, LTK_KEY_PAGE_DOWN},
    427 	{XK_Page_Up, LTK_KEY_PAGE_UP},
    428 	{XK_Pause, LTK_KEY_PAUSE},
    429 	{XK_Print, LTK_KEY_PRINT},
    430 	{XK_Prior, LTK_KEY_PRIOR},
    431 
    432 	{XK_Redo, LTK_KEY_REDO},
    433 	{XK_Return, LTK_KEY_RETURN},
    434 	{XK_Right, LTK_KEY_RIGHT},
    435 	{XK_script_switch, LTK_KEY_SCRIPT_SWITCH},
    436 	{XK_Scroll_Lock, LTK_KEY_SCROLL_LOCK},
    437 	{XK_Select, LTK_KEY_SELECT},
    438 	{XK_space, LTK_KEY_SPACE},
    439 	{XK_Sys_Req, LTK_KEY_SYS_REQ},
    440 	{XK_Tab, LTK_KEY_TAB},
    441 	{XK_Up, LTK_KEY_UP},
    442 	{XK_Undo, LTK_KEY_UNDO},
    443 };
    444 
    445 static int keysym_map_sorted = 0;
    446 static int
    447 keysym_map_search_helper(const void *keyv, const void *entryv) {
    448 	KeySym sym = *((KeySym*)keyv);
    449 	struct keysym_mapping *m = (struct keysym_mapping *)entryv;
    450 	/* FIXME: branchless compare version? */
    451 	return (sym < m->xkeysym) ? -1 : (sym > m->xkeysym);
    452 }
    453 static int
    454 keysym_map_sort_helper(const void *entry1v, const void *entry2v) {
    455 	struct keysym_mapping *m1 = (struct keysym_mapping *)entry1v;
    456 	struct keysym_mapping *m2 = (struct keysym_mapping *)entry2v;
    457 	return (m1->xkeysym < m2->xkeysym) ? -1 : (m1->xkeysym > m2->xkeysym);
    458 }
    459 
    460 static ltk_keysym
    461 get_keysym(KeySym sym) {
    462 	if (!keysym_map_sorted) {
    463 		qsort(
    464 		    keysym_map, LENGTH(keysym_map),
    465 		    sizeof(keysym_map[0]), &keysym_map_sort_helper
    466 		);
    467 		keysym_map_sorted = 1;
    468 	}
    469 	struct keysym_mapping *m = bsearch(
    470 	    &sym, keysym_map, LENGTH(keysym_map),
    471 	    sizeof(keysym_map[0]), &keysym_map_search_helper
    472 	);
    473 	return m ? m->keysym : LTK_KEY_NONE;
    474 }