/* * chr - Andrew Chronister & * inso - Alex Baines * * 12.19.2019 * * Updated linux layer for 4coder * */ // TOP #define FPS 60 #define frame_useconds (1000000 / FPS) #define SLASH '/' #define DLL "so" #include "4coder_base_types.h" #include "4coder_version.h" #include "4coder_events.h" #include "4coder_table.h" #include "4coder_types.h" #include "4coder_default_colors.h" #include "4coder_system_types.h" #include "4ed_font_interface.h" #define STATIC_LINK_API #include "generated/system_api.h" #define STATIC_LINK_API #include "generated/graphics_api.h" #define STATIC_LINK_API #include "generated/font_api.h" #include "4ed_font_set.h" #include "4ed_render_target.h" #include "4ed_search_list.h" #include "4ed.h" #include "generated/system_api.cpp" #include "generated/graphics_api.cpp" #include "generated/font_api.cpp" #include "4coder_base_types.cpp" #include "4coder_stringf.cpp" #include "4coder_events.cpp" #include "4coder_hash_functions.cpp" #include "4coder_table.cpp" #include "4coder_log.cpp" #include "4coder_hash_functions.cpp" #include "4coder_system_allocator.cpp" #include "4coder_malloc_allocator.cpp" #include "4coder_codepoint_map.cpp" #include "4ed_mem.cpp" #include "4ed_font_set.cpp" #include "4ed_search_list.cpp" #include "4ed_font_provider_freetype.h" #include "4ed_font_provider_freetype.cpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define Cursor XCursor #undef function #include #include #include #include #include #include #define function static #undef Cursor #undef internal #include #define internal static #include #include #ifdef INSO_DEBUG #define LINUX_FN_DEBUG(fmt, ...) do { \ fprintf(stderr, "%s: " fmt "\n", __func__, ##__VA_ARGS__);\ } while (0) // I want to see a message #undef AssertBreak #define AssertBreak(m) ({\ fprintf(stderr, "\n** ASSERTION FAILURE: %s:%d: %s\n\n", __FILE__, __LINE__, #m);\ *((volatile u64*)0) = 0xba771e70ad5;\ }) #else #define LINUX_FN_DEBUG(...) #endif //////////////////////////// struct Linux_Input_Chunk_Transient { Input_List event_list; b8 mouse_l_press; b8 mouse_l_release; b8 mouse_r_press; b8 mouse_r_release; b8 out_of_window; i8 mouse_wheel; b8 trying_to_kill; }; struct Linux_Input_Chunk_Persistent { Vec2_i32 mouse; Input_Modifier_Set_Fixed modifiers; b8 mouse_l; b8 mouse_r; }; struct Linux_Input_Chunk { Linux_Input_Chunk_Transient trans; Linux_Input_Chunk_Persistent pers; }; struct Linux_Vars { Thread_Context tctx; Arena *frame_arena; Display* dpy; Window win; b32 has_xfixes; int xfixes_selection_event; XIM xim; XIC xic; FcConfig* fontconfig; Linux_Input_Chunk input; int epoll; int step_timer_fd; u64 last_step_time; b32 is_full_screen; b32 should_be_full_screen; Application_Mouse_Cursor cursor; XCursor hidden_cursor; i32 cursor_show; i32 prev_cursor_show; Node free_linux_objects; Node timer_objects; System_Mutex global_frame_mutex; Arena clipboard_out_arena; String_Const_u8 clipboard_contents; b32 received_new_clipboard; Atom atom_TARGETS; Atom atom_CLIPBOARD; Atom atom_UTF8_STRING; Atom atom__NET_WM_STATE; Atom atom__NET_WM_STATE_MAXIMIZED_HORZ; Atom atom__NET_WM_STATE_MAXIMIZED_VERT; Atom atom__NET_WM_STATE_FULLSCREEN; Atom atom__NET_WM_PING; Atom atom__NET_WM_WINDOW_TYPE; Atom atom__NET_WM_WINDOW_TYPE_NORMAL; Atom atom__NET_WM_PID; Atom atom_WM_DELETE_WINDOW; }; global Linux_Vars linuxvars; global Render_Target render_target; //////////////////////////// // Defererencing an epoll_event's .data.ptr will always give one of these event types. typedef i32 Epoll_Kind; enum { EPOLL_FRAME_TIMER, EPOLL_X11, EPOLL_X11_INTERNAL, EPOLL_CLI_PIPE, EPOLL_USER_TIMER, }; // Where per-event epoll data is not needed, .data.ptr will point to one of // these static vars below. // 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_x11 = EPOLL_X11; internal Epoll_Kind epoll_tag_x11_internal = EPOLL_X11_INTERNAL; internal Epoll_Kind epoll_tag_cli_pipe = EPOLL_CLI_PIPE; //////////////////////////// typedef i32 Linux_Object_Kind; enum { LinuxObjectKind_ERROR = 0, LinuxObjectKind_Timer = 1, LinuxObjectKind_Thread = 2, LinuxObjectKind_Mutex = 3, LinuxObjectKind_ConditionVariable = 4, }; struct Linux_Object { Linux_Object_Kind kind; Node node; union { struct { int fd; Epoll_Kind epoll_tag; } timer; struct { pthread_t pthread; Thread_Function* proc; void* ptr; } thread; pthread_mutex_t mutex; pthread_cond_t condition_variable; }; }; Linux_Object* handle_to_object(Plat_Handle ph){ return *(Linux_Object**)&ph; } internal Linux_Object* linux_alloc_object(Linux_Object_Kind kind){ Linux_Object* result = NULL; if (linuxvars.free_linux_objects.next != &linuxvars.free_linux_objects) { result = CastFromMember(Linux_Object, node, linuxvars.free_linux_objects.next); } if (result == NULL) { i32 count = 512; Linux_Object* objects = (Linux_Object*)system_memory_allocate( sizeof(Linux_Object) * count, file_name_line_number_lit_u8 ); objects[0].node.prev = &linuxvars.free_linux_objects; linuxvars.free_linux_objects.next = &objects[0].node; for (i32 i = 1; i < count; ++i) { objects[i - 1].node.next = &objects[i].node; objects[i].node.prev = &objects[i - 1].node; } objects[count - 1].node.next = &linuxvars.free_linux_objects; linuxvars.free_linux_objects.prev = &objects[count - 1].node; result = CastFromMember(Linux_Object, node, linuxvars.free_linux_objects.next); } Assert(result != 0); dll_remove(&result->node); block_zero_struct(result); result->kind = kind; return result; } internal void linux_free_object(Linux_Object *object){ if (object->node.next != 0){ dll_remove(&object->node); } dll_insert(&linuxvars.free_linux_objects, &object->node); } //////////////////////////// internal int linux_compare_file_infos(File_Info** a, File_Info** b) { b32 a_hidden = (*a)->file_name.str[0] == '.'; b32 b_hidden = (*b)->file_name.str[0] == '.'; // hidden files lower in list if(a_hidden != b_hidden) { return a_hidden - b_hidden; } // push_stringf seems to null terminate return strcoll((char*)(*a)->file_name.str, (char*)(*b)->file_name.str); } internal int linux_system_get_file_list_filter(const struct dirent *dirent) { String_Const_u8 file_name = SCu8((u8*)dirent->d_name); if (string_match(file_name, string_u8_litexpr("."))) { return 0; } else if (string_match(file_name, string_u8_litexpr(".."))) { return 0; } return 1; } internal int linux_u64_from_timespec(const struct timespec timespec) { return timespec.tv_nsec + UINT64_C(1000000000) * timespec.tv_sec; } internal File_Attribute_Flag linux_convert_file_attribute_flags(int mode) { File_Attribute_Flag result = {}; MovFlag(mode, S_IFDIR, result, FileAttribute_IsDirectory); return result; } internal File_Attributes linux_file_attributes_from_struct_stat(struct stat* file_stat) { File_Attributes result = {}; result.size = file_stat->st_size; result.last_write_time = linux_u64_from_timespec(file_stat->st_mtim); result.flags = linux_convert_file_attribute_flags(file_stat->st_mode); return result; } 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); } } internal int linux_get_xsettings_dpi(Display* dpy, int screen){ struct XSettingHeader { u8 type; u8 pad0; u16 name_len; char name[0]; }; struct XSettings { u8 byte_order; u8 pad[3]; u32 serial; u32 num_settings; }; enum { XSettingsTypeInt, XSettingsTypeString, XSettingsTypeColor }; int dpi = -1; unsigned char* prop = NULL; char sel_buffer[64]; struct XSettings* xs; const char* p; snprintf(sel_buffer, sizeof(sel_buffer), "_XSETTINGS_S%d", screen); Atom XSET_SEL = XInternAtom(dpy, sel_buffer, True); Atom XSET_SET = XInternAtom(dpy, "_XSETTINGS_SETTINGS", True); if (XSET_SEL == None || XSET_SET == None){ //LOG("XSETTINGS unavailable.\n"); return(dpi); } Window xset_win = XGetSelectionOwner(dpy, XSET_SEL); if (xset_win == None){ // TODO(inso): listen for the ClientMessage about it becoming available? // there's not much point atm if DPI scaling is only done at startup goto out; } { Atom type; int fmt; unsigned long pad, num; if (XGetWindowProperty(dpy, xset_win, XSET_SET, 0, 1024, False, XSET_SET, &type, &fmt, &num, &pad, &prop) != Success){ //LOG("XSETTINGS: GetWindowProperty failed.\n"); goto out; } if (fmt != 8){ //LOG("XSETTINGS: Wrong format.\n"); goto out; } } xs = (struct XSettings*)prop; p = (char*)(xs + 1); if (xs->byte_order != 0){ //LOG("FIXME: XSETTINGS not host byte order?\n"); goto out; } for (int i = 0; i < xs->num_settings; ++i){ struct XSettingHeader* h = (struct XSettingHeader*)p; p += sizeof(struct XSettingHeader); p += h->name_len; p += ((4 - (h->name_len & 3)) & 3); p += 4; // serial switch (h->type){ case XSettingsTypeInt: { if (strncmp(h->name, "Xft/DPI", h->name_len) == 0){ dpi = *(i32*)p; if (dpi != -1) dpi /= 1024; } p += 4; } break; case XSettingsTypeString: { u32 len = *(u32*)p; p += 4; p += len; p += ((4 - (len & 3)) & 3); } break; case XSettingsTypeColor: { p += 8; } break; default: { //LOG("XSETTINGS: Got invalid type...\n"); goto out; } break; } } out: if (prop){ XFree(prop); } return dpi; } internal void* linux_thread_proc_start(void* arg) { Linux_Object* info = (Linux_Object*)arg; Assert(info->kind == LinuxObjectKind_Thread); info->thread.proc(info->thread.ptr); return NULL; } internal void linux_window_maximize(b32 enable){ } internal void linux_window_fullscreen_toggle(){ } #include "linux_icon.h" internal void linux_set_icon(Display* d, Window w){ Atom WM_ICON = XInternAtom(d, "_NET_WM_ICON", False); XChangeProperty(d, w, WM_ICON, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)linux_icon, sizeof(linux_icon) / sizeof(long)); } #include "linux_error_box.cpp" //////////////////////////// #include "linux_4ed_functions.cpp" //////////////////////////// #include #include "opengl/4ed_opengl_defines.h" #define GL_FUNC(N,R,P) typedef R (CALL_CONVENTION N##_Function)P; N##_Function *N = 0; #include "opengl/4ed_opengl_funcs.h" #include "opengl/4ed_opengl_render.cpp" internal graphics_get_texture_sig(){ return(gl__get_texture(dim, texture_kind)); } internal graphics_fill_texture_sig(){ return(gl__fill_texture(texture_kind, texture, p, dim, data)); } //////////////////////////// internal Face_Description linux_find_font(Face_Description* desc) { Face_Description result = *desc; char* name = strndupa((char*)desc->font.file_name.str, desc->font.file_name.size); double size; const char* style; { Face_Load_Parameters* p = &desc->parameters; size = p->pt_size; if(p->bold && p->italic) { style = "Bold Italic"; } else if(p->bold) { style = "Bold"; } else if(p->italic) { style = "Italic"; } else { style = "Regular"; } } FcPattern *pattern = FcPatternBuild( 0, FC_POSTSCRIPT_NAME, FcTypeString, name, FC_SIZE, FcTypeDouble, size, FC_FONTFORMAT, FcTypeString, "TrueType", FC_STYLE, FcTypeString, (FcChar8*)style, NULL); if(!pattern) { return result; } if (FcConfigSubstitute(linuxvars.fontconfig, pattern, FcMatchPattern)){ FcDefaultSubstitute(pattern); FcResult res; FcPattern *font = FcFontMatch(linuxvars.fontconfig, pattern, &res); if (!font){ return result; } FcChar8 *filename = 0; FcPatternGetString(font, FC_FILE, 0, &filename); if(filename) { printf("FONTCONFIG FILENAME = %s\n", filename); result.font.file_name = push_u8_stringf(linuxvars.frame_arena, "%s", filename); } FcPatternDestroy(font); } FcPatternDestroy(pattern); return result; } 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(!result) { // is this fatal? 4ed.cpp:277 (caller) does not check for null. String_Const_u8 s = description->font.file_name; char msg[4096]; snprintf(msg, sizeof(msg), "Unable to load font: %.*s", (int)s.size, s.str); system_error_box(msg); } return(result); } //////////////////////////// internal b32 glx_init(void) { int glx_maj, glx_min; if(!glXQueryVersion(linuxvars.dpy, &glx_maj, &glx_min)) { return false; } return glx_maj > 1 || (glx_maj == 1 && glx_min >= 3); } internal b32 glx_get_config(GLXFBConfig* fb_config, XVisualInfo* vi) { static const int attrs[] = { GLX_X_RENDERABLE , True, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, GLX_RENDER_TYPE , GLX_RGBA_BIT, GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, GLX_RED_SIZE , 8, GLX_GREEN_SIZE , 8, GLX_BLUE_SIZE , 8, GLX_ALPHA_SIZE , 8, GLX_DEPTH_SIZE , 24, GLX_STENCIL_SIZE , 8, GLX_DOUBLEBUFFER , True, None }; int conf_count = 0; GLXFBConfig* conf_list = glXChooseFBConfig(linuxvars.dpy, DefaultScreen(linuxvars.dpy), attrs, &conf_count); if(!conf_count || conf_count <= 0) { return false; } *fb_config = *conf_list; XFree(conf_list); XVisualInfo* xvi = glXGetVisualFromFBConfig(linuxvars.dpy, *fb_config); if(!xvi) { return false; } *vi = *xvi; XFree(xvi); return true; } internal b32 glx_ctx_error; internal int glx_error_handler(Display* dpy, XErrorEvent* ev){ glx_ctx_error = true; return 0; } typedef GLXContext (glXCreateContextAttribsARB_Function)(Display*, GLXFBConfig, GLXContext, Bool, const int*); typedef void (glXSwapIntervalEXT_Function) (Display *dpy, GLXDrawable drawable, int interval); typedef int (glXSwapIntervalMESA_Function) (unsigned int interval); typedef int (glXGetSwapIntervalMESA_Function) (void); typedef int (glXSwapIntervalSGI_Function) (int interval); internal b32 glx_create_context(GLXFBConfig fb_config){ const char *glx_exts = glXQueryExtensionsString(linuxvars.dpy, DefaultScreen(linuxvars.dpy)); glXCreateContextAttribsARB_Function *glXCreateContextAttribsARB = 0; glXSwapIntervalEXT_Function *glXSwapIntervalEXT = 0; glXSwapIntervalMESA_Function *glXSwapIntervalMESA = 0; glXGetSwapIntervalMESA_Function *glXGetSwapIntervalMESA = 0; glXSwapIntervalSGI_Function *glXSwapIntervalSGI = 0; #define GLXLOAD(f) f = (f##_Function*) glXGetProcAddressARB((const GLubyte*) #f); GLXLOAD(glXCreateContextAttribsARB); GLXContext ctx = NULL; int (*old_handler)(Display*, XErrorEvent*) = XSetErrorHandler(&glx_error_handler); if (glXCreateContextAttribsARB == NULL){ //LOG("glXCreateContextAttribsARB() not found, using old-style GLX context\n" ); ctx = glXCreateNewContext(linuxvars.dpy, fb_config, GLX_RGBA_TYPE, 0, True); } else { static const int context_attribs[] = { GLX_CONTEXT_MAJOR_VERSION_ARB, 2, GLX_CONTEXT_MINOR_VERSION_ARB, 1, GLX_CONTEXT_PROFILE_MASK_ARB , GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, #if defined(FRED_INTERNAL) GLX_CONTEXT_FLAGS_ARB , GLX_CONTEXT_DEBUG_BIT_ARB, #endif None }; //LOG("Creating GL 2.1 context... "); ctx = glXCreateContextAttribsARB(linuxvars.dpy, fb_config, 0, True, context_attribs); } XSync(linuxvars.dpy, False); if(glx_ctx_error || !ctx) { return false; } XSync(linuxvars.dpy, False); XSetErrorHandler(old_handler); //b32 direct = glXIsDirect(linuxvars.dpy, ctx); //LOG("Making context current\n"); glXMakeCurrent(linuxvars.dpy, linuxvars.win, ctx); //glx_enable_vsync(); // NOTE(allen): Load gl functions #define GL_FUNC(f,R,P) GLXLOAD(f) #include "opengl/4ed_opengl_funcs.h" #undef GLXLOAD return true; } //////////////////////////// internal void linux_x11_init(int argc, char** argv, Plat_Settings* settings) { Display* dpy = XOpenDisplay(0); if (!dpy){ fprintf(stderr, "FATAL: Cannot open X11 Display!\n"); exit(1); } linuxvars.dpy = dpy; #define LOAD_ATOM(x) linuxvars.atom_##x = XInternAtom(linuxvars.dpy, #x, False); LOAD_ATOM(TARGETS); LOAD_ATOM(CLIPBOARD); LOAD_ATOM(UTF8_STRING); LOAD_ATOM(_NET_WM_STATE); LOAD_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ); LOAD_ATOM(_NET_WM_STATE_MAXIMIZED_VERT); LOAD_ATOM(_NET_WM_STATE_FULLSCREEN); LOAD_ATOM(_NET_WM_PING); LOAD_ATOM(_NET_WM_WINDOW_TYPE); LOAD_ATOM(_NET_WM_WINDOW_TYPE_NORMAL); LOAD_ATOM(_NET_WM_PID); LOAD_ATOM(WM_DELETE_WINDOW); #undef LOAD_ATOM if (!glx_init()){ system_error_box("Your XServer's GLX version is too old. GLX 1.3+ is required."); } GLXFBConfig fb_config; XVisualInfo vi; if (!glx_get_config(&fb_config, &vi)){ system_error_box("Could not get a matching GLX FBConfig. Check your OpenGL drivers are installed correctly."); } // TODO: window size #define WINDOW_W_DEFAULT 800 #define WINDOW_H_DEFAULT 600 int w = WINDOW_W_DEFAULT; int h = WINDOW_H_DEFAULT; // TEMP render_target.width = w; render_target.height = h; XSetWindowAttributes swa = {}; swa.backing_store = NotUseful; swa.event_mask = StructureNotifyMask; swa.bit_gravity = NorthWestGravity; swa.colormap = XCreateColormap(dpy, RootWindow(dpy, vi.screen), vi.visual, AllocNone); u32 CWflags = CWBackingStore|CWBitGravity|CWBackPixel|CWBorderPixel|CWColormap|CWEventMask; linuxvars.win = XCreateWindow(dpy, RootWindow(dpy, vi.screen), 0, 0, w, h, 0, vi.depth, InputOutput, vi.visual, CWflags, &swa); if (!linuxvars.win){ system_error_box("XCreateWindow failed. Make sure your display is set up correctly."); } //NOTE(inso): Set the window's type to normal XChangeProperty(linuxvars.dpy, linuxvars.win, linuxvars.atom__NET_WM_WINDOW_TYPE, XA_ATOM, 32, PropModeReplace, (unsigned char*)&linuxvars.atom__NET_WM_WINDOW_TYPE_NORMAL, 1); //NOTE(inso): window managers want the PID as a window property for some reason. pid_t pid = getpid(); XChangeProperty(linuxvars.dpy, linuxvars.win, linuxvars.atom__NET_WM_PID, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)&pid, 1); //NOTE(inso): set wm properties XStoreName(linuxvars.dpy, linuxvars.win, WINDOW_NAME); XSizeHints *sz_hints = XAllocSizeHints(); XWMHints *wm_hints = XAllocWMHints(); XClassHint *cl_hints = XAllocClassHint(); sz_hints->flags = PMinSize | PMaxSize | PWinGravity; sz_hints->min_width = 50; sz_hints->min_height = 50; sz_hints->max_width = sz_hints->max_height = (1UL << 16UL); sz_hints->win_gravity = NorthWestGravity; if (settings->set_window_pos){ sz_hints->flags |= USPosition; sz_hints->x = settings->window_x; sz_hints->y = settings->window_y; } wm_hints->flags |= InputHint | StateHint; wm_hints->input = True; wm_hints->initial_state = NormalState; cl_hints->res_name = "4coder"; cl_hints->res_class = "4coder"; char* win_name_list[] = { WINDOW_NAME }; XTextProperty win_name; XStringListToTextProperty(win_name_list, 1, &win_name); XSetWMProperties(linuxvars.dpy, linuxvars.win, &win_name, NULL, argv, argc, sz_hints, wm_hints, cl_hints); XFree(win_name.value); XFree(sz_hints); XFree(wm_hints); XFree(cl_hints); linux_set_icon(linuxvars.dpy, linuxvars.win); // NOTE(inso): make the window visible XMapWindow(linuxvars.dpy, linuxvars.win); if(!glx_create_context(fb_config)) { system_error_box("Unable to create GLX context."); } XRaiseWindow(linuxvars.dpy, linuxvars.win); if (settings->set_window_pos){ XMoveWindow(linuxvars.dpy, linuxvars.win, settings->window_x, settings->window_y); } if (settings->maximize_window){ linux_window_maximize(true); } else if (settings->fullscreen_window){ linux_window_fullscreen_toggle(); } 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 }; XSetWMProtocols(linuxvars.dpy, linuxvars.win, wm_protos, 2); // XFixes extension for clipboard notification. { int xfixes_version_unused, xfixes_err_unused; Bool has_xfixes = XQueryExtension(linuxvars.dpy, "XFIXES", &xfixes_version_unused, &linuxvars.xfixes_selection_event, &xfixes_err_unused); linuxvars.has_xfixes = (has_xfixes == True); } // Input handling init setlocale(LC_ALL, ""); 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); if (!linuxvars.xim){ // NOTE(inso): Try falling back to the internal XIM implementation that // should in theory always exist. XSetLocaleModifiers("@im=none"); linuxvars.xim = XOpenIM(dpy, 0, 0, 0); } // If it still isn't there we're screwed. if (!linuxvars.xim){ system_error_box("Could not initialize X Input."); } XIMStyles *styles = NULL; const XIMStyle style_want = (XIMPreeditNothing | XIMStatusNothing); b32 found_style = false; if (!XGetIMValues(linuxvars.xim, XNQueryInputStyle, &styles, NULL) && styles){ for (i32 i = 0; i < styles->count_styles; ++i){ XIMStyle style = styles->supported_styles[i]; if (style == style_want) { found_style = true; break; } } } if(!found_style) { system_error_box("Could not find supported X Input style."); } XFree(styles); linuxvars.xic = XCreateIC(linuxvars.xim, XNInputStyle, style_want, XNClientWindow, linuxvars.win, XNFocusWindow, linuxvars.win, NULL); if(!linuxvars.xic) { system_error_box("Error creating X Input context."); } int xim_event_mask; if (XGetICValues(linuxvars.xic, XNFilterEvents, &xim_event_mask, NULL)){ xim_event_mask = 0; } u32 event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask | FocusChangeMask | StructureNotifyMask | MappingNotify | ExposureMask | VisibilityChangeMask | xim_event_mask; XSelectInput(linuxvars.dpy, linuxvars.win, event_mask); } 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 }; } for(Key_Code k = KeyCode_0; k <= KeyCode_9; ++k) { *p++ = { XK_0 + (k - KeyCode_0), k }; } *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 }; *p++ = { XK_Up, KeyCode_Up }; *p++ = { XK_Down, KeyCode_Down }; *p++ = { XK_Left, KeyCode_Left }; *p++ = { XK_Right, KeyCode_Right }; *p++ = { XK_BackSpace, KeyCode_Backspace }; *p++ = { XK_Return, KeyCode_Return }; *p++ = { XK_Delete, KeyCode_Delete }; *p++ = { XK_Insert, KeyCode_Insert }; *p++ = { XK_Home, KeyCode_Home }; *p++ = { XK_End, KeyCode_End }; *p++ = { XK_Page_Up, KeyCode_PageUp }; *p++ = { XK_Page_Down, KeyCode_PageDown }; *p++ = { XK_Caps_Lock, KeyCode_CapsLock }; *p++ = { XK_Num_Lock, KeyCode_NumLock }; *p++ = { XK_Scroll_Lock, KeyCode_ScrollLock }; *p++ = { XK_Menu, KeyCode_Menu }; *p++ = { XK_Shift_L, KeyCode_Shift }; *p++ = { XK_Shift_R, KeyCode_Shift }; *p++ = { XK_Control_L, KeyCode_Control }; *p++ = { XK_Control_R, KeyCode_Control }; *p++ = { XK_Alt_L, KeyCode_Alt }; *p++ = { XK_Alt_R, KeyCode_Alt }; *p++ = { XK_Super_L, KeyCode_Command }; *p++ = { XK_Super_R, KeyCode_Command }; for(Key_Code k = KeyCode_F1; k <= KeyCode_F16; ++k) { *p++ = { XK_F1 + (k - KeyCode_F1), k }; } 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); int key_count = (key_max - key_min) + 1; KeySym* syms = XGetKeyboardMapping(dpy, key_min, key_count, &syms_per_code); 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; break; } } } XFree(syms); } internal void linux_handle_x11_events() { static XEvent prev_event = {}; b32 should_step = false; while (XPending(linuxvars.dpy)) { XEvent event; XNextEvent(linuxvars.dpy, &event); if (XFilterEvent(&event, None) == True){ continue; } switch(event.type) { 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); int state = event.xkey.state; set_modifier(mods, KeyCode_Shift, state & ShiftMask); set_modifier(mods, KeyCode_Control, state & ControlMask); set_modifier(mods, KeyCode_CapsLock, state & LockMask); set_modifier(mods, KeyCode_Alt, state & Mod1Mask); event.xkey.state &= ~(ControlMask); Status status; KeySym keysym = NoSymbol; u8 buff[256] = {}; int len = Xutf8LookupString(linuxvars.xic, &event.xkey, (char*)buff, sizeof(buff) - 1, &keysym, &status); if (status == XBufferOverflow){ //TODO(inso): handle properly Xutf8ResetIC(linuxvars.xic); XSetICFocus(linuxvars.xic); } if (keysym == XK_ISO_Left_Tab){ //text = '\t'; 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]); 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); } 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); } } break; case MappingNotify: { if (event.xmapping.request == MappingModifier || event.xmapping.request == MappingKeyboard){ XRefreshKeyboardMapping(&event.xmapping); linux_keycode_init(linuxvars.dpy); } } 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){ render_target.width = w; render_target.height = h; } } break; } } if(should_step) { linux_schedule_step(); } } internal b32 linux_epoll_process(struct epoll_event* events, int num_events) { b32 do_step = false; for (int i = 0; i < num_events; ++i){ struct epoll_event* ev = events + i; Epoll_Kind* tag = (Epoll_Kind*)ev->data.ptr; switch (*tag){ case EPOLL_X11: { linux_handle_x11_events(); } break; case EPOLL_X11_INTERNAL: { //XProcessInternalConnection(linuxvars.dpy, fd); } break; case EPOLL_FRAME_TIMER: { u64 count; int ret; do { ret = read(linuxvars.step_timer_fd, &count, 8); } while (ret != -1 || errno != EAGAIN); do_step = true; } break; case EPOLL_CLI_PIPE: { linux_schedule_step(); } break; case EPOLL_USER_TIMER: { Linux_Object* obj = CastFromMember(Linux_Object, timer.epoll_tag, tag); close(obj->timer.fd); obj->timer.fd = -1; do_step = true; } break; } } return do_step; } int main(int argc, char **argv){ // NOTE(allen): context setup { Base_Allocator* alloc = get_base_allocator_system(); thread_ctx_init(&linuxvars.tctx, ThreadKind_Main, alloc, alloc); } API_VTable_system system_vtable = {}; system_api_fill_vtable(&system_vtable); API_VTable_graphics graphics_vtable = {}; graphics_api_fill_vtable(&graphics_vtable); API_VTable_font font_vtable = {}; font_api_fill_vtable(&font_vtable); // NOTE(allen): memory linuxvars.frame_arena = reserve_arena(&linuxvars.tctx); // TODO(allen): *arena; render_target.arena = make_arena_system(KB(256)); linuxvars.fontconfig = FcInitLoadConfigAndFonts(); linuxvars.cursor_show = MouseCursorShow_Always; linuxvars.prev_cursor_show = MouseCursorShow_Always; dll_init_sentinel(&linuxvars.free_linux_objects); dll_init_sentinel(&linuxvars.timer_objects); //InitializeCriticalSection(&win32vars.thread_launch_mutex); //InitializeConditionVariable(&win32vars.thread_launch_cv); // dpi ? // NOTE(allen): load core System_Library core_library = {}; App_Functions app = {}; { App_Get_Functions *get_funcs = 0; Scratch_Block scratch(&linuxvars.tctx, Scratch_Share); Path_Search_List search_list = {}; search_list_add_system_path(scratch, &search_list, SystemPath_Binary); String_Const_u8 core_path = get_full_path(scratch, &search_list, SCu8("4ed_app.so")); if (system_load_library(scratch, core_path, &core_library)){ get_funcs = (App_Get_Functions*)system_get_proc(core_library, "app_get_functions"); if (get_funcs != 0){ app = get_funcs(); } else{ char msg[] = "Failed to get application code from '4ed_app.so'."; system_error_box(msg); } } else{ char msg[] = "Could not load '4ed_app.so'. This file should be in the same directory as the main '4ed' executable."; system_error_box(msg); } } // NOTE(allen): send system vtable to core app.load_vtables(&system_vtable, &font_vtable, &graphics_vtable); // get_logger calls log_init which is needed. app.get_logger(); //linuxvars.log_string = app.get_logger(); // NOTE(allen): init & command line parameters Plat_Settings plat_settings = {}; void *base_ptr = 0; { Scratch_Block scratch(&linuxvars.tctx, Scratch_Share); String_Const_u8 curdir = system_get_path(scratch, SystemPath_CurrentDirectory); char **files = 0; i32 *file_count = 0; base_ptr = app.read_command_line(&linuxvars.tctx, curdir, &plat_settings, &files, &file_count, argc, argv); /* TODO(inso): what is this doing? { i32 end = *file_count; i32 i = 0, j = 0; for (; i < end; ++i){ if (system_file_can_be_made(scratch, (u8*)files[i])){ files[j] = files[i]; ++j; } } *file_count = j; }*/ } // NOTE(allen): load custom layer System_Library custom_library = {}; Custom_API custom = {}; { char custom_not_found_msg[] = "Did not find a library for the custom layer."; char custom_fail_version_msg[] = "Failed to load custom code due to missing version information or a version mismatch. Try rebuilding with buildsuper."; char custom_fail_init_apis[] = "Failed to load custom code due to missing 'init_apis' symbol. Try rebuilding with buildsuper"; Scratch_Block scratch(&linuxvars.tctx, Scratch_Share); String_Const_u8 default_file_name = string_u8_litexpr("custom_4coder.so"); Path_Search_List search_list = {}; search_list_add_system_path(scratch, &search_list, SystemPath_CurrentDirectory); search_list_add_system_path(scratch, &search_list, SystemPath_Binary); String_Const_u8 custom_file_names[2] = {}; i32 custom_file_count = 1; if (plat_settings.custom_dll != 0){ custom_file_names[0] = SCu8(plat_settings.custom_dll); if (!plat_settings.custom_dll_is_strict){ custom_file_names[1] = default_file_name; custom_file_count += 1; } } else{ custom_file_names[0] = default_file_name; } String_Const_u8 custom_file_name = {}; for (i32 i = 0; i < custom_file_count; i += 1){ custom_file_name = get_full_path(scratch, &search_list, custom_file_names[i]); if (custom_file_name.size > 0){ break; } } b32 has_library = false; if (custom_file_name.size > 0){ if (system_load_library(scratch, custom_file_name, &custom_library)){ has_library = true; } } if (!has_library){ system_error_box(custom_not_found_msg); } custom.get_version = (_Get_Version_Type*)system_get_proc(custom_library, "get_version"); if (custom.get_version == 0 || custom.get_version(MAJOR, MINOR, PATCH) == 0){ system_error_box(custom_fail_version_msg); } custom.init_apis = (_Init_APIs_Type*)system_get_proc(custom_library, "init_apis"); if (custom.init_apis == 0){ system_error_box(custom_fail_init_apis); } } linux_x11_init(argc, argv, &plat_settings); linux_keycode_init(linuxvars.dpy); // TODO(inso): move to x11 init? XCursor xcursors[APP_MOUSE_CURSOR_COUNT] = { None, None, XCreateFontCursor(linuxvars.dpy, XC_xterm), XCreateFontCursor(linuxvars.dpy, XC_sb_h_double_arrow), XCreateFontCursor(linuxvars.dpy, XC_sb_v_double_arrow) }; // sneaky invisible cursor { char data = 0; XColor c = {}; Pixmap p = XCreateBitmapFromData(linuxvars.dpy, linuxvars.win, &data, 1, 1); linuxvars.hidden_cursor = XCreatePixmapCursor(linuxvars.dpy, p, p, &c, &c, 0, 0); XFreePixmap(linuxvars.dpy, p); } // epoll init { 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); e.data.ptr = &epoll_tag_x11; epoll_ctl(linuxvars.epoll, EPOLL_CTL_ADD, ConnectionNumber(linuxvars.dpy), &e); e.data.ptr = &epoll_tag_frame_timer; epoll_ctl(linuxvars.epoll, EPOLL_CTL_ADD, linuxvars.step_timer_fd, &e); } // app init { Scratch_Block scratch(&linuxvars.tctx, Scratch_Share); String_Const_u8 curdir = system_get_path(scratch, SystemPath_CurrentDirectory); app.init(&linuxvars.tctx, &render_target, base_ptr, linuxvars.clipboard_contents, curdir, custom); } linuxvars.global_frame_mutex = system_mutex_make(); system_mutex_acquire(linuxvars.global_frame_mutex); linux_schedule_step(); b32 first_step = true; b32 keep_running = true; for (;keep_running;){ if (XEventsQueued(linuxvars.dpy, QueuedAlready)){ linux_handle_x11_events(); } system_mutex_release(linuxvars.global_frame_mutex); struct epoll_event events[16]; int num_events = epoll_wait(linuxvars.epoll, events, ArrayCount(events), -1); system_mutex_acquire(linuxvars.global_frame_mutex); if (num_events == -1){ if (errno != EINTR){ perror("epoll_wait"); //LOG("epoll_wait\n"); } continue; } if(!linux_epoll_process(events, num_events)) { continue; } linuxvars.last_step_time = system_now_time(); // NOTE(allen): Frame Clipboard Input // Request clipboard contents from X11 on first step, or every step if they don't have XFixes notification ability. if (first_step || !linuxvars.has_xfixes){ XConvertSelection(linuxvars.dpy, linuxvars.atom_CLIPBOARD, linuxvars.atom_UTF8_STRING, linuxvars.atom_CLIPBOARD, linuxvars.win, CurrentTime); } Application_Step_Input input = {}; if (linuxvars.received_new_clipboard){ input.clipboard = linuxvars.clipboard_contents; 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; // 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){ XStoreName(linuxvars.dpy, linuxvars.win, result.title_string); } // NOTE(allen): Switch to New Cursor if (result.mouse_cursor_type != linuxvars.cursor && !linuxvars.input.pers.mouse_l){ XCursor c = xcursors[result.mouse_cursor_type]; if (linuxvars.cursor_show){ XDefineCursor(linuxvars.dpy, linuxvars.win, c); } linuxvars.cursor = result.mouse_cursor_type; } gl_render(&render_target); glXSwapBuffers(linuxvars.dpy, linuxvars.win); first_step = false; linalloc_clear(linuxvars.frame_arena); block_zero_struct(&linuxvars.input.trans); } return 0; }