linux input handling improvements

master
Alex Baines 2020-02-16 13:58:58 +00:00
parent 73b964c3ea
commit d1e86d78bb
2 changed files with 122 additions and 81 deletions

View File

@ -83,16 +83,17 @@
#define Cursor XCursor
#undef function
#undef internal
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>
#include <X11/Xatom.h>
#include <X11/extensions/Xfixes.h>
#include <X11/XKBlib.h>
#include <X11/keysym.h>
#define function static
#undef Cursor
#undef internal
#include <fontconfig/fontconfig.h>
#define internal static
@ -151,8 +152,11 @@ struct Linux_Vars {
XIM xim;
XIC xic;
FcConfig* fontconfig;
XkbDescPtr xkb;
Linux_Input_Chunk input;
int xkb_event;
int xkb_group; // active keyboard layout (0-3)
int epoll;
int step_timer_fd;
@ -593,7 +597,7 @@ linux_find_font(Face_Description* desc) {
FcChar8 *filename = 0;
FcPatternGetString(font, FC_FILE, 0, &filename);
if(filename) {
printf("FONTCONFIG FILENAME = %s\n", filename);
LINUX_FN_DEBUG("FONTCONFIG FILENAME = %s\n", filename);
result.font.file_name = push_u8_stringf(linuxvars.frame_arena, "%s", filename);
}
@ -608,14 +612,19 @@ linux_find_font(Face_Description* desc) {
internal
font_make_face_sig() {
// FIXME
if(description->font.file_name.str[0] != '/') {
Face_Description desc2 = linux_find_font(description);
description = &desc2;
}
Face* result = ft__font_make_face(arena, description, scale_factor);
// if it failed to load the font directly, try via fontconfig.
if(!result) {
Face_Description desc2 = {};
desc2.parameters = description->parameters;
desc2.font.file_name = string_front_of_path(description->font.file_name);
printf("FONT %.*s\n", string_expand(desc2.font.file_name));
desc2 = linux_find_font(&desc2);
result = ft__font_make_face(arena, &desc2, scale_factor);
}
if(!result) {
// is this fatal? 4ed.cpp:277 (caller) does not check for null.
String_Const_u8 s = description->font.file_name;
@ -882,12 +891,6 @@ linux_x11_init(int argc, char** argv, Plat_Settings* settings) {
XSync(linuxvars.dpy, False);
XWindowAttributes attr;
if (XGetWindowAttributes(linuxvars.dpy, linuxvars.win, &attr)){
//*window_width = WinAttribs.width;
//*window_height = WinAttribs.height;
}
Atom wm_protos[] = {
linuxvars.atom_WM_DELETE_WINDOW,
linuxvars.atom__NET_WM_PING
@ -913,12 +916,8 @@ linux_x11_init(int argc, char** argv, Plat_Settings* settings) {
XSetLocaleModifiers("");
b32 locale_supported = XSupportsLocale();
//LOGF("Supported locale?: %s.\n", locale_supported ? "Yes" : "No");
if (!locale_supported){
//LOG("Reverting to 'C' ... ");
setlocale(LC_ALL, "C");
locale_supported = XSupportsLocale();
//LOGF("C is supported? %s.\n", locale_supported ? "Yes" : "No");
}
linuxvars.xim = XOpenIM(dpy, 0, 0, 0);
@ -975,12 +974,27 @@ linux_x11_init(int argc, char** argv, Plat_Settings* settings) {
| EnterWindowMask | LeaveWindowMask
| PointerMotionMask
| FocusChangeMask
| StructureNotifyMask | MappingNotify
| StructureNotifyMask
| ExposureMask | VisibilityChangeMask
| xim_event_mask;
XSelectInput(linuxvars.dpy, linuxvars.win, event_mask);
// init XKB keyboard extension
if(!XkbQueryExtension(linuxvars.dpy, 0, &linuxvars.xkb_event, 0, 0, 0)) {
system_error_box("XKB Extension not available.");
}
XkbSelectEvents(linuxvars.dpy, XkbUseCoreKbd, XkbAllEventsMask, XkbAllEventsMask);
linuxvars.xkb = XkbGetKeyboard(linuxvars.dpy, XkbAllComponentsMask, XkbUseCoreKbd);
if(!linuxvars.xkb) {
system_error_box("Error getting XKB keyboard details.");
}
// closer to windows behaviour (holding key doesn't generate release events)
XkbSetDetectableAutoRepeat(linuxvars.dpy, True, NULL);
XCursor cursors[APP_MOUSE_CURSOR_COUNT] = {
None,
None,
@ -1007,33 +1021,56 @@ global Key_Code keycode_lookup_table[255];
internal void
linux_keycode_init(Display* dpy){
struct SymCode { KeySym sym; Key_Code code; };
SymCode sym_table[100];
block_zero_array(sym_table);
block_zero_array(keycode_lookup_table);
SymCode* p = sym_table;
for(Key_Code k = KeyCode_A; k <= KeyCode_Z; ++k) {
*p++ = { XK_a + (k - KeyCode_A), k };
// Find these keys by physical position, and map to QWERTY KeyCodes
#define K(k) glue(KeyCode_, k)
static const u8 positional_keys[] = {
K(1), K(2), K(3), K(4), K(5), K(6), K(7), K(8), K(9), K(0), K(Minus), K(Equal),
K(Q), K(W), K(E), K(R), K(T), K(Y), K(U), K(I), K(O), K(P), K(LeftBracket), K(RightBracket),
K(A), K(S), K(D), K(F), K(G), K(H), K(J), K(K), K(L), K(Semicolon), K(Quote), /*uk hash*/0,
K(Z), K(X), K(C), K(V), K(B), K(N), K(M), K(Comma), K(Period), K(ForwardSlash), 0, 0
};
#undef K
// XKB gives the alphanumeric keys names like AE01 -> E is the row (from B-E), 01 is the column (01-12).
// to get key names in .ps file: setxkbmap -print | xkbcomp - - | xkbprint -label name - out.ps
static const int ncols = 12;
static const int nrows = 4;
for(int i = XkbMinLegalKeyCode; i <= XkbMaxLegalKeyCode; ++i) {
const char* name = linuxvars.xkb->names->keys[i].name;
if(name[0] == 'A' && name[1] >= 'B' && name[1] <= 'E') {
int row = (nrows - 1) - (name[1] - 'B');
int col = (name[2] - '0') * 10 + (name[3] - '0') - 1;
if(row >= 0 && row < nrows && col >= 0 && col < ncols) {
keycode_lookup_table[i] = positional_keys[row * ncols + col];
}
}
// a few special cases:
else if(memcmp(name, "TLDE", XkbKeyNameLength) == 0) {
keycode_lookup_table[i] = KeyCode_Tick;
} else if(memcmp(name, "BKSL", XkbKeyNameLength) == 0) {
keycode_lookup_table[i] = KeyCode_BackwardSlash;
} else if(memcmp(name, "LSGT", XkbKeyNameLength) == 0) {
// UK extra key between left shift and Z, not sure what to do with it...
// Setting to F13-F16 seems to break text input.
// it prints \ and | with shift. KeyCode_Backslash will be where UK # is.
// keycode_lookup_table[i] =
}
}
for(Key_Code k = KeyCode_0; k <= KeyCode_9; ++k) {
*p++ = { XK_0 + (k - KeyCode_0), k };
}
// Find the rest by their key label
struct SymCode { KeySym sym; Key_Code code; };
SymCode sym_table[100];
SymCode* p = sym_table;
*p++ = { XK_space, KeyCode_Space };
*p++ = { XK_grave, KeyCode_Tick };
*p++ = { XK_minus, KeyCode_Minus };
*p++ = { XK_equal, KeyCode_Equal };
*p++ = { XK_bracketleft, KeyCode_LeftBracket };
*p++ = { XK_bracketright, KeyCode_RightBracket };
*p++ = { XK_semicolon, KeyCode_Semicolon };
*p++ = { XK_apostrophe, KeyCode_Quote };
*p++ = { XK_comma, KeyCode_Comma };
*p++ = { XK_period, KeyCode_Period };
*p++ = { XK_slash, KeyCode_ForwardSlash };
*p++ = { XK_backslash, KeyCode_BackwardSlash };
*p++ = { XK_Tab, KeyCode_Tab };
*p++ = { XK_Escape, KeyCode_Escape };
*p++ = { XK_Pause, KeyCode_Pause };
@ -1069,27 +1106,21 @@ linux_keycode_init(Display* dpy){
const int table_size = p - sym_table;
Assert(table_size < ArrayCount(sym_table));
int key_min, key_max, syms_per_code;
XDisplayKeycodes(dpy, &key_min, &key_max);
for(int i = XkbMinLegalKeyCode; i <= XkbMaxLegalKeyCode; ++i) {
KeySym sym = NoSymbol;
int key_count = (key_max - key_min) + 1;
KeySym* syms = XGetKeyboardMapping(dpy, key_min, key_count, &syms_per_code);
// lookup key in current layout with no modifiers held (0)
if(!XkbTranslateKeyCode(linuxvars.xkb, i, XkbBuildCoreState(0, linuxvars.xkb_group), NULL, &sym)) {
continue;
}
if (syms == 0){
return;
}
int key = key_min;
for(int i = 0; i < key_count * syms_per_code; ++i){
for(int j = 0; j < table_size; ++j){
if (sym_table[j].sym == syms[i]){
keycode_lookup_table[key + (i/syms_per_code)] = sym_table[j].code;
for(int j = 0; j < table_size; ++j) {
if(sym_table[j].sym == sym) {
keycode_lookup_table[i] = sym_table[j].code;
break;
}
}
}
XFree(syms);
}
internal void
@ -1097,9 +1128,8 @@ linux_epoll_init(void) {
struct epoll_event e = {};
e.events = EPOLLIN | EPOLLET;
//linuxvars.inotify_fd = inotify_init1(IN_NONBLOCK);
linuxvars.step_timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
linuxvars.epoll = epoll_create(16);
linuxvars.epoll = epoll_create(16);
e.data.ptr = &epoll_tag_x11;
epoll_ctl(linuxvars.epoll, EPOLL_CTL_ADD, ConnectionNumber(linuxvars.dpy), &e);
@ -1261,18 +1291,16 @@ linux_handle_x11_events() {
int len = Xutf8LookupString(linuxvars.xic, &event.xkey, (char*)buf, sizeof(buf) - 1, &keysym, &status);
if (status == XBufferOverflow){
//TODO(inso): handle properly
Xutf8ResetIC(linuxvars.xic);
XSetICFocus(linuxvars.xic);
}
if (keysym == XK_ISO_Left_Tab){
printf("left tab? [%.*s]\n", len, buf);
add_modifier(mods, KeyCode_Shift);
}
Key_Code key = keycode_lookup_table[(u8)event.xkey.keycode];
printf("key [%d] = %s\n", event.xkey.keycode, key_code_name[key]);
//printf("key %d = %s\n", event.xkey.keycode, key_code_name[key]);
Input_Event* key_event = NULL;
if(key) {
@ -1323,7 +1351,9 @@ linux_handle_x11_events() {
} break;
case MotionNotify: {
linuxvars.input.pers.mouse = { event.xmotion.x, event.xmotion.y };
int x = clamp(0, event.xmotion.x, render_target.width - 1);
int y = clamp(0, event.xmotion.y, render_target.height - 1);
linuxvars.input.pers.mouse = { x, y };
should_step = true;
} break;
@ -1333,6 +1363,16 @@ linux_handle_x11_events() {
case Button1: {
linuxvars.input.trans.mouse_l_press = true;
linuxvars.input.pers.mouse_l = true;
// NOTE(inso): improves selection dragging (especially in notepad-like mode).
// we will still get mouse events when the pointer leaves the window if it's dragging.
XGrabPointer(
linuxvars.dpy,
linuxvars.win,
True, PointerMotionMask | ButtonPressMask | ButtonReleaseMask,
GrabModeAsync, GrabModeAsync,
None, None, CurrentTime);
} break;
case Button3: {
@ -1356,6 +1396,8 @@ linux_handle_x11_events() {
case Button1: {
linuxvars.input.trans.mouse_l_release = true;
linuxvars.input.pers.mouse_l = false;
XUngrabPointer(linuxvars.dpy, CurrentTime);
} break;
case Button3: {
@ -1365,13 +1407,6 @@ linux_handle_x11_events() {
}
} break;
case MappingNotify: {
if (event.xmapping.request == MappingModifier || event.xmapping.request == MappingKeyboard){
XRefreshKeyboardMapping(&event.xmapping);
linux_keycode_init(linuxvars.dpy);
}
} break;
case FocusIn:
case FocusOut: {
linuxvars.input.pers.mouse_l = false;
@ -1454,6 +1489,16 @@ linux_handle_x11_events() {
}
}
else if(event.type == linuxvars.xkb_event) {
XkbEvent* kb = (XkbEvent*)&event;
// Keyboard layout changed, refresh lookup table.
if(kb->any.xkb_type == XkbStateNotify && kb->state.group != linuxvars.xkb_group) {
linuxvars.xkb_group = kb->state.group;
XkbRefreshKeyboardMapping((XkbMapNotifyEvent*)kb);
linux_keycode_init(linuxvars.dpy);
}
}
} break;
}
}

View File

@ -16,8 +16,6 @@ system_get_path(Arena* arena, System_Path_Code path_code){
case SystemPath_CurrentDirectory: {
// glibc extension: getcwd allocates its own memory if passed NULL
char *working_dir = getcwd(NULL, 0);
LINUX_FN_DEBUG("cwd = [%s]", working_dir);
u64 working_dir_len = cstring_length(working_dir);
u8 *out = push_array(arena, u8, working_dir_len + 1);
block_copy(out, working_dir, working_dir_len);
@ -43,8 +41,6 @@ system_get_path(Arena* arena, System_Path_Code path_code){
}
result = string_remove_last_folder(SCu8(buf, n));
LINUX_FN_DEBUG("bin dir = [%.*s]", (int)result.size, result.str);
} break;
}
@ -104,7 +100,7 @@ system_get_canonical(Arena* arena, String_Const_u8 name){
internal File_List
system_get_file_list(Arena* arena, String_Const_u8 directory){
LINUX_FN_DEBUG("%.*s", (int)directory.size, directory.str);
//LINUX_FN_DEBUG("%.*s", (int)directory.size, directory.str);
File_List result = {};
char* path = strndupa((char*)directory.str, directory.size);
@ -162,7 +158,7 @@ system_get_file_list(Arena* arena, String_Const_u8 directory){
internal File_Attributes
system_quick_file_attributes(Arena* scratch, String_Const_u8 file_name){
LINUX_FN_DEBUG("%.*s", (int)file_name.size, file_name.str);
//LINUX_FN_DEBUG("%.*s", (int)file_name.size, file_name.str);
struct stat file_stat;
stat((const char*)file_name.str, &file_stat);
return linux_file_attributes_from_struct_stat(&file_stat);
@ -306,7 +302,7 @@ system_signal_step(u32 code){
internal void
system_sleep(u64 microseconds){
LINUX_FN_DEBUG("%" PRIu64, microseconds);
//LINUX_FN_DEBUG("%" PRIu64, microseconds);
struct timespec requested;
struct timespec remaining;
u64 seconds = microseconds / Million(1);
@ -317,7 +313,7 @@ system_sleep(u64 microseconds){
internal void
system_post_clipboard(String_Const_u8 str){
LINUX_FN_DEBUG("%.*s", (int)str.size, str.str);
//LINUX_FN_DEBUG("%.*s", (int)str.size, str.str);
linalloc_clear(linuxvars.clipboard_out_arena);
linuxvars.clipboard_out_contents = push_u8_stringf(linuxvars.clipboard_out_arena, "%.*s", str.size, str.str);
XSetSelectionOwner(linuxvars.dpy, linuxvars.atom_CLIPBOARD, linuxvars.win, CurrentTime);
@ -377,7 +373,7 @@ system_cli_call(Arena* scratch, char* path, char* script, CLI_Handles* cli_out){
internal void
system_cli_begin_update(CLI_Handles* cli){
// NOTE(inso): I don't think anything needs to be done here.
LINUX_FN_DEBUG();
//LINUX_FN_DEBUG();
}
internal b32
@ -507,7 +503,7 @@ system_thread_get_id(void){
internal void
system_acquire_global_frame_mutex(Thread_Context* tctx){
LINUX_FN_DEBUG();
//LINUX_FN_DEBUG();
if (tctx->kind == ThreadKind_AsyncTasks){
system_mutex_acquire(linuxvars.global_frame_mutex);
}
@ -515,7 +511,7 @@ system_acquire_global_frame_mutex(Thread_Context* tctx){
internal void
system_release_global_frame_mutex(Thread_Context* tctx){
LINUX_FN_DEBUG();
//LINUX_FN_DEBUG();
if (tctx->kind == ThreadKind_AsyncTasks){
system_mutex_release(linuxvars.global_frame_mutex);
}
@ -599,7 +595,7 @@ system_memory_allocate(u64 size, String_Const_u8 location){
void* result = mmap(
NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
// TODO(andrew): Allocation tracking?
//LINUX_FN_DEBUG("%" PRIu64 ", %.*s %p", size, (int)location.size, location.str, result);
LINUX_FN_DEBUG("%" PRIu64 ", %.*s %p", size, (int)location.size, location.str, result);
return result;
}
@ -672,6 +668,6 @@ system_is_fullscreen(void){
internal Input_Modifier_Set
system_get_keyboard_modifiers(Arena* arena){
LINUX_FN_DEBUG();
//LINUX_FN_DEBUG();
return(copy_modifier_set(arena, &linuxvars.input.pers.modifiers));
}