fix linux input handling, implement more stuff

it seems to occasionally crash and might be leaking memory currently...
master
Alex Baines 2020-02-08 15:25:15 +00:00
parent 56f5d78fa2
commit 61ddb8a9d1
2 changed files with 291 additions and 93 deletions

View File

@ -77,6 +77,7 @@
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/timerfd.h>
#include <sys/eventfd.h>
#include <sys/syscall.h>
#define Cursor XCursor
@ -153,10 +154,9 @@ struct Linux_Vars {
Linux_Input_Chunk input;
int epoll;
int step_event_fd;
int step_timer_fd;
u64 last_step_time;
b32 is_full_screen;
b32 should_be_full_screen;
Application_Mouse_Cursor cursor;
XCursor hidden_cursor;
@ -195,7 +195,8 @@ global Render_Target render_target;
typedef i32 Epoll_Kind;
enum {
EPOLL_FRAME_TIMER,
EPOLL_STEP_EVENT,
EPOLL_STEP_TIMER,
EPOLL_X11,
EPOLL_X11_INTERNAL,
EPOLL_CLI_PIPE,
@ -207,7 +208,8 @@ enum {
// If per-event data is needed, container_of can be used on data.ptr
// to access the containing struct and all its other members.
internal Epoll_Kind epoll_tag_frame_timer = EPOLL_FRAME_TIMER;
internal Epoll_Kind epoll_tag_step_event = EPOLL_STEP_EVENT;
internal Epoll_Kind epoll_tag_step_timer = EPOLL_STEP_TIMER;
internal Epoll_Kind epoll_tag_x11 = EPOLL_X11;
internal Epoll_Kind epoll_tag_x11_internal = EPOLL_X11_INTERNAL;
internal Epoll_Kind epoll_tag_cli_pipe = EPOLL_CLI_PIPE;
@ -342,18 +344,39 @@ internal void
linux_schedule_step(){
u64 now = system_now_time();
u64 diff = (now - linuxvars.last_step_time);
struct itimerspec its = {};
timerfd_gettime(linuxvars.step_timer_fd, &its);
if (its.it_value.tv_sec == 0 && its.it_value.tv_nsec == 0){
if(diff > (u64)frame_useconds) {
its.it_value.tv_nsec = 1;
} else {
its.it_value.tv_nsec = (frame_useconds - diff) * 1000UL;
}
timerfd_settime(linuxvars.step_timer_fd, 0, &its, NULL);
if (diff > (u64)frame_useconds){
u64 ev = 1;
write(linuxvars.step_event_fd, &ev, sizeof(ev));
}
else{
struct itimerspec its = {};
timerfd_gettime(linuxvars.step_timer_fd, &its);
if (its.it_value.tv_sec == 0 && its.it_value.tv_nsec == 0){
its.it_value.tv_nsec = (frame_useconds - diff) * 1000UL;
timerfd_settime(linuxvars.step_timer_fd, 0, &its, NULL);
}
}
}
internal void
linux_set_wm_state(Atom one, Atom two, int mode){
//NOTE(inso): this will only work after the window has been mapped
enum { STATE_REMOVE, STATE_ADD, STATE_TOGGLE };
XEvent e = {};
e.xany.type = ClientMessage;
e.xclient.message_type = linuxvars.atom__NET_WM_STATE;
e.xclient.format = 32;
e.xclient.window = linuxvars.win;
e.xclient.data.l[0] = mode;
e.xclient.data.l[1] = one;
e.xclient.data.l[2] = two;
e.xclient.data.l[3] = 1L;
XSendEvent(linuxvars.dpy,
RootWindow(linuxvars.dpy, 0),
0, SubstructureNotifyMask | SubstructureRedirectMask, &e);
}
internal int
@ -770,7 +793,7 @@ linux_x11_init(int argc, char** argv, Plat_Settings* settings) {
render_target.height = h;
XSetWindowAttributes swa = {};
swa.backing_store = NotUseful;
swa.backing_store = WhenMapped;
swa.event_mask = StructureNotifyMask;
swa.bit_gravity = NorthWestGravity;
swa.colormap = XCreateColormap(dpy, RootWindow(dpy, vi.screen), vi.visual, AllocNone);
@ -1036,6 +1059,25 @@ linux_keycode_init(Display* dpy){
XFree(syms);
}
internal String_Const_u8
linux_filter_text(Arena* arena, u8* buf, int len) {
u8* const result = push_array(arena, u8, len);
u8* const endp = buf + len;
u8* outp = result;
for(int i = 0; i < len; ++i) {
u8 c = buf[i];
if(c == '\r') {
*outp++ = '\n';
} else if(c > 127 || (' ' <= c && c <= '~') || c == '\t') {
*outp++ = c;
}
}
return SCu8(result, outp - result);
}
internal void
linux_handle_x11_events() {
static XEvent prev_event = {};
@ -1053,12 +1095,6 @@ linux_handle_x11_events() {
case KeyPress: {
should_step = true;
/*
b32 is_hold = (prev_event.type == KeyRelease &&
prev_event.xkey.time == event.xkey.time &&
prev_event.xkey.keycode == event.xkey.keycode);
*/
Input_Modifier_Set_Fixed* mods = &linuxvars.input.pers.modifiers;
block_zero_struct(mods);
@ -1072,9 +1108,9 @@ linux_handle_x11_events() {
Status status;
KeySym keysym = NoSymbol;
u8 buff[256] = {};
u8 buf[256] = {};
int len = Xutf8LookupString(linuxvars.xic, &event.xkey, (char*)buff, sizeof(buff) - 1, &keysym, &status);
int len = Xutf8LookupString(linuxvars.xic, &event.xkey, (char*)buf, sizeof(buf) - 1, &keysym, &status);
if (status == XBufferOverflow){
//TODO(inso): handle properly
@ -1083,27 +1119,82 @@ linux_handle_x11_events() {
}
if (keysym == XK_ISO_Left_Tab){
//text = '\t';
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]);
Input_Event* key_event = NULL;
if(key) {
Input_Event *ev = push_input_event(linuxvars.frame_arena, &linuxvars.input.trans.event_list);
ev->kind = InputEventKind_KeyStroke;
ev->key.code = key;
ev->key.modifiers = copy_modifier_set(linuxvars.frame_arena, mods);
key_event = push_input_event(linuxvars.frame_arena, &linuxvars.input.trans.event_list);
key_event->kind = InputEventKind_KeyStroke;
key_event->key.code = key;
key_event->key.modifiers = copy_modifier_set(linuxvars.frame_arena, mods);
}
Input_Event* text_event = NULL;
if(status == XLookupChars || status == XLookupBoth) {
u8* ptr = push_array_write(linuxvars.frame_arena, u8, len, buff);
Input_Event* ev = push_input_event(linuxvars.frame_arena, &linuxvars.input.trans.event_list);
ev->kind = InputEventKind_TextInsert;
ev->text.string = SCu8(ptr, len);
String_Const_u8 str = linux_filter_text(linuxvars.frame_arena, buf, len);
if(str.size) {
text_event = push_input_event(linuxvars.frame_arena, &linuxvars.input.trans.event_list);
text_event->kind = InputEventKind_TextInsert;
text_event->text.string = str;
}
}
if(key_event && text_event) {
key_event->key.first_dependent_text = text_event;
}
} break;
case KeyRelease: {
} break;
case MotionNotify: {
linuxvars.input.pers.mouse = { event.xmotion.x, event.xmotion.y };
should_step = true;
} break;
case ButtonPress: {
should_step = true;
switch(event.xbutton.button) {
case Button1: {
linuxvars.input.trans.mouse_l_press = true;
linuxvars.input.pers.mouse_l = true;
} break;
case Button3: {
linuxvars.input.trans.mouse_r_press = true;
linuxvars.input.pers.mouse_r = true;
} break;
case Button4: {
linuxvars.input.trans.mouse_wheel = -100;
} break;
case Button5: {
linuxvars.input.trans.mouse_wheel = +100;
} break;
}
} break;
case ButtonRelease: {
should_step = true;
switch(event.xbutton.button) {
case Button1: {
linuxvars.input.trans.mouse_l_release = true;
linuxvars.input.pers.mouse_l = false;
} break;
case Button3: {
linuxvars.input.trans.mouse_r_release = true;
linuxvars.input.pers.mouse_r = false;
} break;
}
} break;
case MappingNotify: {
@ -1114,15 +1205,36 @@ linux_handle_x11_events() {
} break;
case ConfigureNotify: {
should_step = true;
i32 w = event.xconfigure.width;
i32 h = event.xconfigure.height;
if (w != render_target.width || h != render_target.height){
should_step = true;
render_target.width = w;
render_target.height = h;
}
} break;
case ClientMessage: {
Atom atom = event.xclient.data.l[0];
// Window X button clicked
if(atom == linuxvars.atom_WM_DELETE_WINDOW) {
should_step = true;
linuxvars.input.trans.trying_to_kill = true;
}
// Notify WM that we're still responding (don't grey our window out).
else if(atom == linuxvars.atom__NET_WM_PING) {
event.xclient.window = DefaultRootWindow(linuxvars.dpy);
XSendEvent(
linuxvars.dpy,
event.xclient.window,
False,
SubstructureRedirectMask | SubstructureNotifyMask,
&event);
}
} break;
}
}
@ -1148,7 +1260,16 @@ linux_epoll_process(struct epoll_event* events, int num_events) {
//XProcessInternalConnection(linuxvars.dpy, fd);
} break;
case EPOLL_FRAME_TIMER: {
case EPOLL_STEP_EVENT: {
u64 ev;
int ret;
do {
ret = read(linuxvars.step_event_fd, &ev, 8);
} while (ret != -1 || errno != EAGAIN);
do_step = true;
} break;
case EPOLL_STEP_TIMER: {
u64 count;
int ret;
do {
@ -1346,13 +1467,17 @@ main(int argc, char **argv){
e.events = EPOLLIN | EPOLLET;
//linuxvars.inotify_fd = inotify_init1(IN_NONBLOCK);
linuxvars.step_event_fd = eventfd(0, EFD_NONBLOCK);
linuxvars.step_timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
linuxvars.epoll = epoll_create(16);
e.data.ptr = &epoll_tag_x11;
epoll_ctl(linuxvars.epoll, EPOLL_CTL_ADD, ConnectionNumber(linuxvars.dpy), &e);
e.data.ptr = &epoll_tag_frame_timer;
e.data.ptr = &epoll_tag_step_event;
epoll_ctl(linuxvars.epoll, EPOLL_CTL_ADD, linuxvars.step_event_fd, &e);
e.data.ptr = &epoll_tag_step_timer;
epoll_ctl(linuxvars.epoll, EPOLL_CTL_ADD, linuxvars.step_timer_fd, &e);
}
@ -1367,10 +1492,9 @@ main(int argc, char **argv){
system_mutex_acquire(linuxvars.global_frame_mutex);
linux_schedule_step();
b32 first_step = true;
b32 keep_running = true;
for (;keep_running;){
for (;;) {
if (XEventsQueued(linuxvars.dpy, QueuedAlready)){
linux_handle_x11_events();
@ -1395,7 +1519,9 @@ main(int argc, char **argv){
continue;
}
linuxvars.last_step_time = system_now_time();
u64 now = system_now_time();
printf(" ***** STEP DIFF = %.2f\n", (now - linuxvars.last_step_time) / 1000000.0);
linuxvars.last_step_time = now;
// NOTE(allen): Frame Clipboard Input
// Request clipboard contents from X11 on first step, or every step if they don't have XFixes notification ability.
@ -1410,32 +1536,31 @@ main(int argc, char **argv){
linuxvars.received_new_clipboard = false;
}
// TODO: fill the rest of it
input.trying_to_kill = !keep_running;
input.dt = frame_useconds/1000000.f;
input.events = linuxvars.input.trans.event_list;
input.first_step = first_step;
input.dt = frame_useconds/1000000.f; // variable?
input.events = linuxvars.input.trans.event_list;
input.trying_to_kill = linuxvars.input.trans.trying_to_kill;
input.mouse.out_of_window = false;
input.mouse.p = linuxvars.input.pers.mouse;
input.mouse.l = linuxvars.input.pers.mouse_l;
input.mouse.r = linuxvars.input.pers.mouse_r;
input.mouse.press_l = linuxvars.input.trans.mouse_l_press;
input.mouse.release_l = linuxvars.input.trans.mouse_l_release;
input.mouse.press_r = linuxvars.input.trans.mouse_r_press;
input.mouse.release_r = linuxvars.input.trans.mouse_r_release;
input.mouse.wheel = linuxvars.input.trans.mouse_wheel;
// NOTE(allen): Application Core Update
// render_target.buffer.pos = 0;
Application_Step_Result result = {};
if (app.step != 0){
result = app.step(&linuxvars.tctx, &render_target, base_ptr, &input);
}
else{
//LOG("app.step == 0 -- skipping\n");
}
// NOTE(allen): Finish the Loop
if (result.perform_kill){
break;
}
// TODO
#if 0
else if (!keep_running && !linuxvars.keep_running){
linuxvars.keep_running = true;
}
#endif
// NOTE(NAME): Switch to New Title
if (result.has_new_title){
@ -1462,3 +1587,6 @@ main(int argc, char **argv){
return 0;
}
// NOTE(inso): to prevent me continuously messing up indentation
// vim: et:ts=4:sts=4:sw=4

View File

@ -53,11 +53,53 @@ system_get_path(Arena* arena, System_Path_Code path_code){
internal String_Const_u8
system_get_canonical(Arena* arena, String_Const_u8 name){
LINUX_FN_DEBUG("%.*s", (int)name.size, name.str);
// TODO(andrew): Resolve symlinks ?
// TODO(andrew): Resolve . and .. in paths
// TODO(andrew): Use realpath(3)
return name;
// first remove redundant ../, //, ./ parts
const u8* input = (u8*) strndupa((char*)name.str, name.size);
u8* output = push_array(arena, u8, name.size + 1);
const u8* p = input;
u8* q = output;
while(*p) {
// not a slash - copy char
if(p[0] != '/') {
*q++ = *p++;
continue;
}
// two slashes in a row, skip one.
if(p[1] == '/') {
++p;
}
else if(p[1] == '.') {
// skip "/./" or trailing "/."
if(p[2] == '/' || p[2] == '\0') {
p += 2;
}
// if we encounter "/../" or trailing "/..", remove last directory instead
else if(p[2] == '.' && (p[3] == '/' || p[3] == '\0')) {
while(q > output && *--q != '/'){};
p += 3;
}
else {
*q++ = *p++;
}
}
else {
*q++ = *p++;
}
}
LINUX_FN_DEBUG("[%.*s] -> [%.*s]", (int)name.size, name.str, (int)(q - output), output);
// TODO: use realpath at this point to resolve symlinks?
return SCu8(output, q - output);
}
internal File_List
@ -100,17 +142,28 @@ system_get_file_list(Arena* arena, String_Const_u8 directory){
}
closedir(dir);
result.infos = fip = push_array(arena, File_Info*, result.count);
if(result.count > 0) {
result.infos = fip = push_array(arena, File_Info*, result.count);
for(File_Info* f = head; f; f = f->next) {
*fip++ = f;
for(File_Info* f = head; f; f = f->next) {
*fip++ = f;
}
// NOTE(inso): I want to sort them like this (. files lower), but it looks like
// the sorting is done on the custom-layer side (lister), so this is pointless.
// TODO(inso): add linux-specific custom layer code?
/*
qsort(result.infos, result.count, sizeof(File_Info*), (__compar_fn_t)&linux_compare_file_infos);
for(u32 i = 0; i < result.count - 1; ++i) {
result.infos[i]->next = result.infos[i+1];
}
result.infos[result.count-1]->next = NULL;
*/
}
qsort(result.infos, result.count, sizeof(File_Info*), (__compar_fn_t)&linux_compare_file_infos);
// TODO: relink in new order?
return result;
}
@ -200,7 +253,7 @@ system_get_proc(System_Library handle, char* proc_name){
internal u64
system_now_time(void){
LINUX_FN_DEBUG();
//LINUX_FN_DEBUG();
struct timespec time;
clock_gettime(CLOCK_MONOTONIC_RAW, &time);
return linux_u64_from_timespec(time);
@ -232,23 +285,23 @@ system_wake_up_timer_release(Plat_Handle handle){
internal void
system_wake_up_timer_set(Plat_Handle handle, u32 time_milliseconds){
LINUX_FN_DEBUG("%d", time_milliseconds);
LINUX_FN_DEBUG("%u", time_milliseconds);
Linux_Object* object = handle_to_object(handle);
if (object->kind == LinuxObjectKind_Timer){
if(object->timer.fd == -1) {
object->timer.fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
ev.data.ptr = &object->timer.epoll_tag;
epoll_ctl(linuxvars.epoll, EPOLL_CTL_ADD, object->timer.fd, &ev);
}
struct itimerspec it = {};
it.it_value.tv_sec = time_milliseconds / 1000;
it.it_value.tv_nsec = (time_milliseconds % 1000) * UINT64_C(1000000);
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
ev.data.ptr = &object->timer.epoll_tag;
epoll_ctl(linuxvars.epoll, EPOLL_CTL_ADD, object->timer.fd, &ev);
timerfd_settime(object->timer.fd, 0, &it, NULL);
}
}
@ -461,7 +514,7 @@ system_thread_free(System_Thread thread){
internal i32
system_thread_get_id(void){
pid_t id = syscall(__NR_gettid);
LINUX_FN_DEBUG("%d", id);
//LINUX_FN_DEBUG("%d", id);
return id;
}
@ -487,14 +540,14 @@ system_mutex_make(void){
Linux_Object* object = linux_alloc_object(LinuxObjectKind_Mutex);
pthread_mutex_init(&object->mutex, NULL);
*(Linux_Object**)&result = object;
LINUX_FN_DEBUG("%p", object);
//LINUX_FN_DEBUG("%p", object);
return result;
}
internal void
system_mutex_acquire(System_Mutex mutex){
Linux_Object* object = *(Linux_Object**)&mutex;
LINUX_FN_DEBUG("%p", object);
//LINUX_FN_DEBUG("%p", object);
Assert(object->kind == LinuxObjectKind_Mutex);
pthread_mutex_lock(&object->mutex);
}
@ -502,7 +555,7 @@ system_mutex_acquire(System_Mutex mutex){
internal void
system_mutex_release(System_Mutex mutex){
Linux_Object* object = *(Linux_Object**)&mutex;
LINUX_FN_DEBUG("%p", object);
//LINUX_FN_DEBUG("%p", object);
Assert(object->kind == LinuxObjectKind_Mutex);
pthread_mutex_unlock(&object->mutex);
}
@ -510,7 +563,7 @@ system_mutex_release(System_Mutex mutex){
internal void
system_mutex_free(System_Mutex mutex){
Linux_Object* object = *(Linux_Object**)&mutex;
LINUX_FN_DEBUG("%p", object);
//LINUX_FN_DEBUG("%p", object);
Assert(object->kind == LinuxObjectKind_Mutex);
pthread_mutex_destroy(&object->mutex);
linux_free_object(object);
@ -520,7 +573,7 @@ internal System_Condition_Variable
system_condition_variable_make(void){
System_Condition_Variable result = {};
Linux_Object* object = linux_alloc_object(LinuxObjectKind_ConditionVariable);
LINUX_FN_DEBUG("%p", object);
//LINUX_FN_DEBUG("%p", object);
pthread_cond_init(&object->condition_variable, NULL);
*(Linux_Object**)&result = object;
return result;
@ -530,7 +583,7 @@ internal void
system_condition_variable_wait(System_Condition_Variable cv, System_Mutex mutex){
Linux_Object* cv_object = *(Linux_Object**)&cv;
Linux_Object* mutex_object = *(Linux_Object**)&mutex;
LINUX_FN_DEBUG("%p / %p", cv_object, mutex_object);
//LINUX_FN_DEBUG("%p / %p", cv_object, mutex_object);
Assert(cv_object->kind == LinuxObjectKind_ConditionVariable);
Assert(mutex_object->kind == LinuxObjectKind_Mutex);
pthread_cond_wait(&cv_object->condition_variable, &mutex_object->mutex);
@ -539,7 +592,7 @@ system_condition_variable_wait(System_Condition_Variable cv, System_Mutex mutex)
internal void
system_condition_variable_signal(System_Condition_Variable cv){
Linux_Object* object = *(Linux_Object**)&cv;
LINUX_FN_DEBUG("%p", object);
//LINUX_FN_DEBUG("%p", object);
Assert(object->kind == LinuxObjectKind_ConditionVariable);
pthread_cond_signal(&object->condition_variable);
}
@ -547,7 +600,7 @@ system_condition_variable_signal(System_Condition_Variable cv){
internal void
system_condition_variable_free(System_Condition_Variable cv){
Linux_Object* object = *(Linux_Object**)&cv;
LINUX_FN_DEBUG("%p", object);
//LINUX_FN_DEBUG("%p", object);
Assert(object->kind == LinuxObjectKind_ConditionVariable);
pthread_cond_destroy(&object->condition_variable);
linux_free_object(object);
@ -559,7 +612,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;
}
@ -576,7 +629,7 @@ system_memory_set_protection(void* ptr, u64 size, u32 flags){
internal void
system_memory_free(void* ptr, u64 size){
LINUX_FN_DEBUG("%p / %ld", ptr, size);
//LINUX_FN_DEBUG("%p / %ld", ptr, size);
munmap(ptr, size);
}
@ -600,21 +653,38 @@ system_show_mouse_cursor(i32 show){
internal b32
system_set_fullscreen(b32 full_screen){
LINUX_FN_DEBUG("%d", full_screen);
linuxvars.should_be_full_screen = full_screen;
linux_set_wm_state(linuxvars.atom__NET_WM_STATE_FULLSCREEN, 0, full_screen);
return true;
}
internal b32
system_is_fullscreen(void){
LINUX_FN_DEBUG();
return linuxvars.is_full_screen;
b32 result = 0;
// NOTE(inso): This will get the "true" state of fullscreen,
// even if it was toggled outside of 4coder.
// (e.g. super-F11 on some WMs sets fullscreen for any window/program)
Atom type, *prop;
unsigned long nitems, pad;
int fmt;
int ret = XGetWindowProperty(linuxvars.dpy,
linuxvars.win,
linuxvars.atom__NET_WM_STATE,
0, 32, False, XA_ATOM,
&type, &fmt, &nitems, &pad,
(unsigned char**)&prop);
if(ret == Success && prop){
result = *prop == linuxvars.atom__NET_WM_STATE_FULLSCREEN;
XFree((unsigned char*)prop);
}
return result;
}
internal Input_Modifier_Set
system_get_keyboard_modifiers(Arena* arena){
LINUX_FN_DEBUG();
// TODO:
//return(copy_modifier_set(arena, &linuxvars.input_chunk.pers.modifiers));
return(copy_modifier_set(arena, &linuxvars.input.pers.modifiers));
}