/* * chr - Andrew Chronister & * inso - Alex Baines * * 12.19.2019 * * Updated linux layer for 4coder * */ // TOP #define FPS 60 #define frame_useconds (Million(1) / FPS) #define frame_nseconds (Billion(1) / 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 #include #define Cursor XCursor #undef function #undef internal #include #include #include #include #include #include #include #define function static #undef Cursor #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; 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; b8 mouse_out_of_window; }; struct Linux_Input_Chunk { Linux_Input_Chunk_Transient trans; Linux_Input_Chunk_Persistent pers; }; struct Linux_Memory_Tracker_Node { Linux_Memory_Tracker_Node* prev; Linux_Memory_Tracker_Node* next; String_Const_u8 location; u64 size; }; 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; XkbDescPtr xkb; Linux_Input_Chunk input; int xkb_event; int xkb_group; // active keyboard layout (0-3) KeyCode prev_filtered_key; Key_Mode key_mode; int epoll; int step_timer_fd; u64 last_step_time; XCursor xcursors[APP_MOUSE_CURSOR_COUNT]; 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; pthread_mutex_t memory_tracker_mutex; Linux_Memory_Tracker_Node* memory_tracker_head; Linux_Memory_Tracker_Node* memory_tracker_tail; int memory_tracker_count; Arena clipboard_arena; String_Const_u8 clipboard_contents; b32 received_new_clipboard; b32 clipboard_catch_all; 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_STEP_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_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; //////////////////////////// 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 u64 linux_us_from_timespec(const struct timespec timespec) { return timespec.tv_nsec/Thousand(1) + Million(1) * 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_us_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 (diff > frame_useconds) { its.it_value.tv_nsec = 1; timerfd_settime(linuxvars.step_timer_fd, 0, &its, NULL); } else { 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); } } } enum wm_state_mode { WM_STATE_DEL = 0, WM_STATE_ADD = 1, WM_STATE_TOGGLE = 2, }; internal void linux_set_wm_state(Atom one, Atom two, enum wm_state_mode mode){ //NOTE(inso): this will only work after the window has been mapped 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 void linux_window_maximize(enum wm_state_mode mode){ linux_set_wm_state(linuxvars.atom__NET_WM_STATE_MAXIMIZED_HORZ, linuxvars.atom__NET_WM_STATE_MAXIMIZED_VERT, mode); } internal void linux_window_fullscreen(enum wm_state_mode mode) { linux_set_wm_state(linuxvars.atom__NET_WM_STATE_FULLSCREEN, 0, mode); } 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; } #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" function void os_popup_error(char *title, char *message){ system_error_box(message); exit(1); } //////////////////////////// #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) { LINUX_FN_DEBUG("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() { 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; 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 GL_DEBUG_MODE 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 = WhenMapped; 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_set_wm_state(linuxvars.atom__NET_WM_STATE_MAXIMIZED_HORZ, linuxvars.atom__NET_WM_STATE_MAXIMIZED_VERT, WM_STATE_ADD); } else if (settings->fullscreen_window){ linux_set_wm_state(linuxvars.atom__NET_WM_STATE_FULLSCREEN, 0, WM_STATE_ADD); } XSync(linuxvars.dpy, False); 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); // request notifications for CLIPBOARD updates. if(has_xfixes) { XFixesSelectSelectionInput(linuxvars.dpy, linuxvars.win, linuxvars.atom_CLIPBOARD, XFixesSetSelectionOwnerNotifyMask); } } // Input handling init setlocale(LC_ALL, ""); XSetLocaleModifiers(""); b32 locale_supported = XSupportsLocale(); if (!locale_supported){ setlocale(LC_ALL, "C"); } 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 | 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 = XkbGetMap(linuxvars.dpy, XkbKeyTypesMask | XkbKeySymsMask, XkbUseCoreKbd); if(!linuxvars.xkb) { system_error_box("Error getting XKB keyboard map."); } if(XkbGetNames(linuxvars.dpy, XkbKeyNamesMask, linuxvars.xkb) != Success) { system_error_box("Error getting XKB key names."); } // closer to windows behaviour (holding key doesn't generate release events) XkbSetDetectableAutoRepeat(linuxvars.dpy, True, NULL); XCursor cursors[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) }; block_copy(linuxvars.xcursors, cursors, sizeof(cursors)); // 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); } } global Key_Code keycode_lookup_table_physical[255]; global Key_Code keycode_lookup_table_language[255]; struct SymCode { KeySym sym; Key_Code code; }; internal void linux_keycode_init_common(Display* dpy, Key_Code* keycode_lookup_table, SymCode* sym_table, SymCode* p, size_t sym_table_size){ *p++ = { XK_space, KeyCode_Space }; *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_F24; ++k){ *p++ = { XK_F1 + (k - KeyCode_F1), k }; } for (Key_Code k = KeyCode_NumPad0; k <= KeyCode_NumPad9; ++k){ *p++ = { XK_KP_0 + (k - KeyCode_NumPad0), k }; } *p++ = { XK_KP_Multiply, KeyCode_NumPadStar }; *p++ = { XK_KP_Add, KeyCode_NumPadPlus }; *p++ = { XK_KP_Subtract, KeyCode_NumPadMinus }; *p++ = { XK_KP_Decimal, KeyCode_NumPadDot }; *p++ = { XK_KP_Delete, KeyCode_NumPadDot }; // seems to take precedence over Decimal... *p++ = { XK_KP_Divide, KeyCode_NumPadSlash }; *p++ = { XK_KP_Enter, KeyCode_Return }; // NumPadEnter? const int table_size = p - sym_table; Assert(table_size < sym_table_size); Key_Code next_extra = KeyCode_Ex1; const Key_Code max_extra = KeyCode_Ex29; for(int i = XkbMinLegalKeyCode; i <= XkbMaxLegalKeyCode; ++i) { KeySym sym = NoSymbol; // lookup key in current layout with no modifiers held (0) if(!XkbTranslateKeyCode(linuxvars.xkb, i, XkbBuildCoreState(0, linuxvars.xkb_group), NULL, &sym)) { continue; } int j; for(j = 0; j < table_size; ++j) { if(sym_table[j].sym == sym) { keycode_lookup_table[i] = sym_table[j].code; //printf("lookup %s = %d\n", key_code_name[sym_table[j].code], i); break; } } if(j != table_size){ continue; } // nothing found - try with shift held (needed for e.g. belgian numbers to bind). KeySym shift_sym = NoSymbol; if(!XkbTranslateKeyCode(linuxvars.xkb, i, XkbBuildCoreState(ShiftMask, linuxvars.xkb_group), NULL, &shift_sym)) { continue; } for(j = 0; j < table_size; ++j) { if(sym_table[j].sym == shift_sym) { keycode_lookup_table[i] = sym_table[j].code; //printf("lookup %s = %d\n", key_code_name[sym_table[j].code], i); break; } } // something unknown bound, put it in extra if(j == table_size && sym != NoSymbol && next_extra <= max_extra && keycode_lookup_table[i] == 0) { keycode_lookup_table[i] = next_extra++; } } } internal void linux_keycode_init_language(Display* dpy, Key_Code* keycode_lookup_table){ SymCode sym_table[300]; SymCode* p = sym_table; for(unsigned int i = 0; i <= 26; ++i) { *p++ = { XK_a + i, KeyCode_A + i}; } for(unsigned int i = 0; i <= 26; ++i) { *p++ = { XK_A + i, KeyCode_A + i}; } for(unsigned int i = 0; i <= 9; ++i) { *p++ = { XK_0 + i, KeyCode_0 + i}; } *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 }; linux_keycode_init_common(dpy, keycode_lookup_table, sym_table, p, ArrayCount(sym_table)); } internal void linux_keycode_init_physical(Display* dpy, Key_Code* keycode_lookup_table){ // Find common keys by their key label SymCode sym_table[100]; linux_keycode_init_common(dpy, keycode_lookup_table, sym_table, sym_table, ArrayCount(sym_table)); // 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; // alphanumeric keys 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]; } } // numpad else if(name[0] == 'K' && name[1] == 'P' && name[2] >= '0' && name[2] <= '9' && !name[3]) { // don't overwrite - for e.g. laptops with numpad keys embedded in the normal ones, toggling with numlock if(keycode_lookup_table[i] == 0) { keycode_lookup_table[i] = KeyCode_NumPad0 + name[2] - '0'; } } // 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 // it prints \ and | with shift. KeyCode_Backslash will be where UK # is. keycode_lookup_table[i] = KeyCode_Ex0; } } } internal void linux_keycode_init(Display* dpy){ block_zero_array(keycode_lookup_table_physical); block_zero_array(keycode_lookup_table_language); linux_keycode_init_physical(dpy, keycode_lookup_table_physical); linux_keycode_init_language(dpy, keycode_lookup_table_language); } internal void linux_epoll_init(void) { struct epoll_event e = {}; e.events = EPOLLIN | EPOLLET; 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_step_timer; epoll_ctl(linuxvars.epoll, EPOLL_CTL_ADD, linuxvars.step_timer_fd, &e); } internal void linux_clipboard_send(XSelectionRequestEvent* req) { XSelectionEvent rsp = {}; rsp.type = SelectionNotify; rsp.requestor = req->requestor; rsp.selection = req->selection; rsp.target = req->target; rsp.time = req->time; rsp.property = None; Atom formats[] = { linuxvars.atom_UTF8_STRING, XA_STRING, }; if(linuxvars.clipboard_contents.size == 0) { goto done; } if(req->selection != linuxvars.atom_CLIPBOARD || req->property == None) { goto done; } if (req->target == linuxvars.atom_TARGETS){ XChangeProperty( req->display, req->requestor, req->property, XA_ATOM, 32, PropModeReplace, (u8*)formats, ArrayCount(formats)); rsp.property = req->property; } else { int i; for(i = 0; i < ArrayCount(formats); ++i){ if (req->target == formats[i]){ break; } } if (i != ArrayCount(formats)){ XChangeProperty( req->display, req->requestor, req->property, req->target, 8, PropModeReplace, linuxvars.clipboard_contents.str, linuxvars.clipboard_contents.size ); rsp.property = req->property; } } done: XSendEvent(req->display, req->requestor, True, 0, (XEvent*)&rsp); } internal String_Const_u8 linux_clipboard_recv(Arena *arena){ Atom type; int fmt; unsigned long nitems; unsigned long bytes_left; u8 *data; int result = XGetWindowProperty(linuxvars.dpy, linuxvars.win, linuxvars.atom_CLIPBOARD, 0L, 0x20000000L, False, linuxvars.atom_UTF8_STRING, &type, &fmt, &nitems, &bytes_left, &data); String_Const_u8 clip = {}; if(result == Success && fmt == 8){ clip= push_string_copy(arena, SCu8(data, nitems)); XFree(data); XDeleteProperty(linuxvars.dpy, linuxvars.win, linuxvars.atom_CLIPBOARD); } return(clip); } internal void linux_clipboard_recv(XSelectionEvent* ev) { if(ev->selection != linuxvars.atom_CLIPBOARD || ev->target != linuxvars.atom_UTF8_STRING || ev->property == None) { return; } Scratch_Block scratch(&linuxvars.tctx); String_Const_u8 clip = linux_clipboard_recv(scratch); if (clip.size > 0){ linalloc_clear(&linuxvars.clipboard_arena); linuxvars.clipboard_contents = push_string_copy(&linuxvars.clipboard_arena, clip); linuxvars.received_new_clipboard = true; linux_schedule_step(); } } internal system_get_clipboard_sig(){ // TODO(inso): index? return(push_string_copy(arena, linuxvars.clipboard_contents)); } internal void system_post_clipboard(String_Const_u8 str, i32 index){ // TODO(inso): index? //LINUX_FN_DEBUG("%.*s", string_expand(str)); linalloc_clear(&linuxvars.clipboard_arena); linuxvars.clipboard_contents = push_u8_stringf(&linuxvars.clipboard_arena, "%.*s", string_expand(str)); XSetSelectionOwner(linuxvars.dpy, linuxvars.atom_CLIPBOARD, linuxvars.win, CurrentTime); } internal void system_set_clipboard_catch_all(b32 enabled){ LINUX_FN_DEBUG("%d", enabled); linuxvars.clipboard_catch_all = !!enabled; } internal b32 system_get_clipboard_catch_all(void){ return linuxvars.clipboard_catch_all; } internal String_Const_u8 linux_filter_text(Arena* arena, u8* buf, int len) { u8* const result = push_array(arena, u8, 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 KeyCode linux_numlock_convert(KeyCode in){ static const KeyCode lookup[] = { KeyCode_Insert, KeyCode_End, KeyCode_Down, KeyCode_PageDown, KeyCode_Left, 0, KeyCode_Right, KeyCode_Home, KeyCode_Up, KeyCode_PageUp, 0, 0, 0, KeyCode_Delete, }; if(in >= KeyCode_NumPad0 && in <= KeyCode_NumPadDot) { KeyCode ret = lookup[in - KeyCode_NumPad0]; if(ret != 0) { return ret; } } return in; } internal void linux_handle_x11_events() { static XEvent prev_event = {}; b32 should_step = false; while (XPending(linuxvars.dpy)) { XEvent event; XNextEvent(linuxvars.dpy, &event); b32 filtered = false; if (XFilterEvent(&event, None) == True){ filtered = true; if(event.type != KeyPress && event.type != KeyRelease) { continue; } } u64 event_id = (u64)event.xkey.serial << 32 | event.xkey.time; switch(event.type) { case KeyPress: { // DEBUG linuxvars.key_mode = KeyMode_Physical; should_step = true; Input_Modifier_Set_Fixed* mods = &linuxvars.input.pers.modifiers; 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 buf[256] = {}; int len = Xutf8LookupString(linuxvars.xic, &event.xkey, (char*)buf, sizeof(buf) - 1, &keysym, &status); if (status == XBufferOverflow){ Xutf8ResetIC(linuxvars.xic); XSetICFocus(linuxvars.xic); } if (keysym == XK_ISO_Left_Tab){ add_modifier(mods, KeyCode_Shift); } Key_Code key; if(linuxvars.key_mode == KeyMode_Physical) { key = keycode_lookup_table_physical[(u8)event.xkey.keycode]; } else { key = keycode_lookup_table_language[(u8)event.xkey.keycode]; } if(!(state & Mod2Mask)) { key = linux_numlock_convert(key); } //printf("key %d = %s (f:%d)\n", event.xkey.keycode, key_code_name[key], filtered); b32 is_dead = false; if (keysym >= XK_dead_grave && keysym <= XK_dead_greek && len == 0) { is_dead = true; printf(" *** Set Dead Key flag here!\n"); } if(!is_dead && filtered) { linuxvars.prev_filtered_key = key; break; } // send a keycode for the key after the dead key if(!key && linuxvars.prev_filtered_key) { key = linuxvars.prev_filtered_key; linuxvars.prev_filtered_key = 0; } Input_Event* key_event = NULL; if(key) { add_modifier(mods, key); // printf(" push key %d\n", key); 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) { String_Const_u8 str = linux_filter_text(&linuxvars.frame_arena, buf, len); if(str.size) { // printf(" push txt %d\n", key); 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: { should_step = true; Input_Modifier_Set_Fixed* mods = &linuxvars.input.pers.modifiers; 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); Key_Code key; if(linuxvars.key_mode == KeyMode_Physical) { key = keycode_lookup_table_physical[(u8)event.xkey.keycode]; } else { key = keycode_lookup_table_language[(u8)event.xkey.keycode]; } // num lock off -> convert KP keys to Insert, Home, End etc. if(!(state & Mod2Mask)) { key = linux_numlock_convert(key); } Input_Event* key_event = NULL; if(key) { remove_modifier(mods, key); key_event = push_input_event(&linuxvars.frame_arena, &linuxvars.input.trans.event_list); key_event->kind = InputEventKind_KeyRelease; key_event->key.code = key; key_event->key.modifiers = copy_modifier_set(&linuxvars.frame_arena, mods); } } break; case MotionNotify: { 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; case ButtonPress: { should_step = true; switch(event.xbutton.button) { 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: { 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; XUngrabPointer(linuxvars.dpy, CurrentTime); } break; case Button3: { linuxvars.input.trans.mouse_r_release = true; linuxvars.input.pers.mouse_r = false; } break; } } break; case FocusIn: case FocusOut: { linuxvars.input.pers.mouse_l = false; linuxvars.input.pers.mouse_r = false; block_zero_struct(&linuxvars.input.pers.modifiers); } break; case EnterNotify: { linuxvars.input.pers.mouse_out_of_window = 0; } break; case LeaveNotify: { linuxvars.input.pers.mouse_out_of_window = 1; } break; case ConfigureNotify: { 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; case SelectionRequest: { linux_clipboard_send((XSelectionRequestEvent*)&event); } break; case SelectionNotify: { linux_clipboard_recv((XSelectionEvent*)&event); } break; case SelectionClear: { if(event.xselectionclear.selection == linuxvars.atom_CLIPBOARD) { linalloc_clear(&linuxvars.clipboard_arena); block_zero_struct(&linuxvars.clipboard_contents); } } break; case Expose: case VisibilityNotify: { should_step = true; } break; default: { // clipboard update notification - ask for the new content if (event.type == linuxvars.xfixes_selection_event) { XFixesSelectionNotifyEvent* sne = (XFixesSelectionNotifyEvent*)&event; if (sne->subtype == XFixesSelectionNotify && sne->owner != linuxvars.win){ XConvertSelection(linuxvars.dpy, linuxvars.atom_CLIPBOARD, linuxvars.atom_UTF8_STRING, linuxvars.atom_CLIPBOARD, linuxvars.win, CurrentTime); } } 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; } } 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_STEP_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; linux_schedule_step(); } break; } } return do_step; } int main(int argc, char **argv){ pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&linuxvars.memory_tracker_mutex, &attr); // 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 = make_arena_system(); linuxvars.clipboard_arena = make_arena_system(); 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); linuxvars.clipboard_catch_all = false; // NOTE(allen): load core System_Library core_library = {}; App_Functions app = {}; { App_Get_Functions *get_funcs = 0; Scratch_Block scratch(&linuxvars.tctx); 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); 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_load_msg[] = "Failed to load custom code due to missing version information. Try rebuilding with buildsuper."; char custom_fail_version_msg[] = "Failed to load custom code due to 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); 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){ system_error_box(custom_fail_load_msg); } else if (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); linux_epoll_init(); // app init { Scratch_Block scratch(&linuxvars.tctx); String_Const_u8 curdir = system_get_path(scratch, SystemPath_CurrentDirectory); app.init(&linuxvars.tctx, &render_target, base_ptr, curdir, custom); } linuxvars.global_frame_mutex = system_mutex_make(); system_mutex_acquire(linuxvars.global_frame_mutex); linux_schedule_step(); b32 first_step = true; u64 timer_start = system_now_time(); for (;;) { 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 && linuxvars.clipboard_catch_all)){ 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 && linuxvars.clipboard_catch_all){ input.clipboard = linuxvars.clipboard_contents; } linuxvars.received_new_clipboard = false; 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 = linuxvars.input.pers.mouse_out_of_window; 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 Application_Step_Result result = {}; if (app.step != 0){ result = app.step(&linuxvars.tctx, &render_target, base_ptr, &input); } // NOTE(allen): Finish the Loop if (result.perform_kill){ break; } // 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 = linuxvars.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); // TODO(allen): don't let the screen size change until HERE after the render // NOTE(allen): Schedule a step if necessary if (result.animating){ linux_schedule_step(); } first_step = false; linalloc_clear(&linuxvars.frame_arena); block_zero_struct(&linuxvars.input.trans); } return 0; } // NOTE(inso): to prevent me continuously messing up indentation // vim: et:ts=4:sts=4:sw=4