diff --git a/platform_linux/linux_4ed.cpp b/platform_linux/linux_4ed.cpp index cd3dce61..20001ba4 100644 --- a/platform_linux/linux_4ed.cpp +++ b/platform_linux/linux_4ed.cpp @@ -77,6 +77,7 @@ #include #include #include +#include #include #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 diff --git a/platform_linux/linux_4ed_functions.cpp b/platform_linux/linux_4ed_functions.cpp index e66ee31d..8a511d11 100644 --- a/platform_linux/linux_4ed_functions.cpp +++ b/platform_linux/linux_4ed_functions.cpp @@ -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)); }