/* * Mr. 4th Dimention - Allen Webster * * 14.11.2015 * * Linux layer for project codename "4ed" * */ // TOP #include "4ed_config.h" #include "4ed_meta.h" #define FCPP_FORBID_MALLOC #include "4cpp_types.h" #define FCPP_STRING_IMPLEMENTATION #include "4coder_string.h" #include "4ed_mem.cpp" #include "4ed_math.cpp" #include "4coder_custom.h" #include "4ed_system.h" #include "4ed_rendering.h" #include "4ed.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include #include #include #include #include #include "4ed_internal.h" #include "4ed_linux_keyboard.cpp" #include "system_shared.h" #include #include #include #include #include struct Linux_Vars{ Display *XDisplay; Window XWindow; Render_Target target; XIM input_method; XIMStyle input_style; XIC input_context; Key_Codes key_codes; Key_Input_Data key_data; Mouse_State mouse_data; String clipboard_contents; String clipboard_outgoing; Atom atom_CLIPBOARD; Atom atom_UTF8_STRING; void *app_code; void *custom; Plat_Settings settings; System_Functions *system; App_Functions app; Custom_API custom_api; b32 first; b32 redraw; b32 vsync; #if FRED_INTERNAL Sys_Bubble internal_bubble; #endif Font_Load_System fnt; }; #define LINUX_MAX_PASTE_CHARS 0x10000L #define FPS 60 #define frame_useconds (1000000 / FPS) globalvar Linux_Vars linuxvars; globalvar Application_Memory memory_vars; globalvar Exchange exchange_vars; #define LinuxGetMemory(size) LinuxGetMemory_(size, __LINE__, __FILE__) internal void* LinuxGetMemory_(i32 size, i32 line_number, char *file_name){ // TODO(allen): Implement without stdlib.h void *result = 0; Assert(size != 0); #if FRED_INTERNAL Sys_Bubble *bubble; result = malloc(size + sizeof(Sys_Bubble)); bubble = (Sys_Bubble*)result; bubble->flags = MEM_BUBBLE_SYS_DEBUG; bubble->line_number = line_number; bubble->file_name = file_name; bubble->size = size; // TODO(allen): make Sys_Bubble list thread safe insert_bubble(&linuxvars.internal_bubble, bubble); result = bubble + 1; #else result = malloc(size); #endif return(result); } internal void LinuxFreeMemory(void *block){ // TODO(allen): Implement without stdlib.h if (block){ #if FRED_INTERNAL Sys_Bubble *bubble; bubble = (Sys_Bubble*)block; --bubble; Assert((bubble->flags & MEM_BUBBLE_DEBUG_MASK) == MEM_BUBBLE_SYS_DEBUG); // TODO(allen): make Sys_Bubble list thread safe remove_bubble(bubble); free(bubble); #else free(block); #endif } } internal Partition LinuxScratchPartition(i32 size){ Partition part; void *data; data = LinuxGetMemory(size); part = partition_open(data, size); return(part); } internal void LinuxScratchPartitionGrow(Partition *part, i32 new_size){ void *data; if (new_size > part->max){ data = LinuxGetMemory(new_size); memcpy(data, part->base, part->pos); LinuxFreeMemory(part->base); part->base = (u8*)data; } } internal void LinuxScratchPartitionDouble(Partition *part){ LinuxScratchPartitionGrow(part, part->max*2); } internal Sys_Get_Memory_Sig(system_get_memory_){ return(LinuxGetMemory_(size, line_number, file_name)); } internal Sys_Free_Memory_Sig(system_free_memory){ LinuxFreeMemory(block); } internal void LinuxStringDup(String* str, void* data, size_t size){ if(str->memory_size < size){ if(str->str){ LinuxFreeMemory(str->str); } str->memory_size = size; str->str = (char*)LinuxGetMemory(size); //TODO(inso): handle alloc failure case } str->size = size; memcpy(str->str, data, size); } #if (defined(_BSD_SOURCE) || defined(_SVID_SOURCE)) #define TimeBySt #endif #if (_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700) #define TimeBySt #endif #ifdef TimeBySt #define nano_mtime_field st_mtim.tv_nsec #undef TimeBySt #else #define nano_mtime_field st_mtimensec #endif Sys_File_Time_Stamp_Sig(system_file_time_stamp){ struct stat info; i64 nanosecond_timestamp; u64 result; stat(filename, &info); nanosecond_timestamp = info.nano_mtime_field; if (nanosecond_timestamp != 0){ result = (u64)(nanosecond_timestamp / 1000); } else{ result = (u64)(info.st_mtime * 1000000); } return(result); } // TODO(allen): DOES THIS AGREE WITH THE FILESTAMP TIMES? // NOTE(inso): I don't think so, CLOCK_MONOTONIC is an arbitrary number Sys_Time_Sig(system_time){ struct timespec spec; u64 result; clock_gettime(CLOCK_MONOTONIC, &spec); result = (spec.tv_sec * 1000000) + (spec.tv_nsec / 1000); return(result); } Sys_Set_File_List_Sig(system_set_file_list){ DIR *d; struct dirent *entry; char *fname, *cursor, *cursor_start; File_Info *info_ptr; i32 count, file_count, size, required_size; terminate_with_null(&directory); d = opendir(directory.str); if (d){ count = 0; file_count = 0; for (entry = readdir(d); entry != 0; entry = readdir(d)){ fname = entry->d_name; ++file_count; for (size = 0; fname[size]; ++size); count += size + 1; } required_size = count + file_count * sizeof(File_Info); if (file_list->block_size < required_size){ system_free_memory(file_list->block); file_list->block = system_get_memory(required_size); } file_list->infos = (File_Info*)file_list->block; cursor = (char*)(file_list->infos + file_count); rewinddir(d); info_ptr = file_list->infos; for (entry = readdir(d); entry != 0; entry = readdir(d), ++info_ptr){ fname = entry->d_name; cursor_start = cursor; for (; *fname; ) *cursor++ = *fname++; #ifdef _DIRENT_HAVE_D_TYPE if(entry->d_type != DT_UNKNOWN){ info_ptr->folder = entry->d_type == DT_DIR; } else #endif { struct stat st; if(lstat(entry->d_name, &st) != -1){ info_ptr->folder = S_ISDIR(st.st_mode); } else { info_ptr->folder = 0; } } info_ptr->filename.str = cursor_start; info_ptr->filename.size = (i32)(cursor - cursor_start); *cursor++ = 0; info_ptr->filename.memory_size = info_ptr->filename.size + 1; } file_list->count = file_count; closedir(d); } } Sys_Post_Clipboard_Sig(system_post_clipboard){ LinuxStringDup(&linuxvars.clipboard_outgoing, str.str, str.size); XSetSelectionOwner(linuxvars.XDisplay, linuxvars.atom_CLIPBOARD, linuxvars.XWindow, CurrentTime); } Sys_CLI_Call_Sig(system_cli_call){ // TODO(allen): Implement AllowLocal(path); AllowLocal(script_name); AllowLocal(cli_out); } Sys_CLI_Begin_Update_Sig(system_cli_begin_update){ // TODO(allen): Implement AllowLocal(cli); } Sys_CLI_Update_Step_Sig(system_cli_update_step){ // TODO(allen): Implement AllowLocal(cli); AllowLocal(dest); AllowLocal(max); AllowLocal(amount); } Sys_CLI_End_Update_Sig(system_cli_end_update){ // TODO(allen): Implement AllowLocal(cli); } Sys_Post_Job_Sig(system_post_job){ // TODO(allen): Implement AllowLocal(group_id); AllowLocal(job); } Sys_Cancel_Job_Sig(system_cancel_job){ // TODO(allen): Implement AllowLocal(group_id); AllowLocal(job_id); } Sys_Acquire_Lock_Sig(system_acquire_lock){ // TODO(allen): Implement AllowLocal(id); } Sys_Release_Lock_Sig(system_release_lock){ // TODO(allen): Implement AllowLocal(id); } Sys_Grow_Thread_Memory_Sig(system_grow_thread_memory){ void *old_data; i32 old_size, new_size; system_acquire_lock(CANCEL_LOCK0 + memory->id - 1); old_data = memory->data; old_size = memory->size; new_size = LargeRoundUp(memory->size*2, Kbytes(4)); memory->data = system_get_memory(new_size); memory->size = new_size; if (old_data){ memcpy(memory->data, old_data, old_size); system_free_memory(old_data); } system_release_lock(CANCEL_LOCK0 + memory->id - 1); } INTERNAL_Sys_Sentinel_Sig(internal_sentinel){ Bubble *result; #if FRED_INTERNAL result = &linuxvars.internal_bubble; #else result = 0; #endif return(result); } INTERNAL_Sys_Get_Thread_States_Sig(internal_get_thread_states){ // TODO(allen): Implement AllowLocal(id); AllowLocal(running); AllowLocal(pending); } INTERNAL_Sys_Debug_Message_Sig(internal_debug_message){ printf("%s", message); } DIRECTORY_HAS_FILE_SIG(system_directory_has_file){ int result = 0; //TODO(inso): implement char buff[PATH_MAX] = {}; memcpy(buff, dir.str, dir.size); printf("Has file %s\n", buff); return(result); } DIRECTORY_CD_SIG(system_directory_cd){ int result = 0; // TODO(allen): Implement printf("Dir CD: %.*s\n", dir->size, dir->str); AllowLocal(dir); AllowLocal(rel_path); return(result); } internal Sys_File_Can_Be_Made(system_file_can_be_made){ // TODO(allen): Implement printf("File can be made: %s\n", filename); AllowLocal(filename); return(0); } internal Sys_Load_File_Sig(system_load_file){ Data result = {}; struct stat info = {}; int fd; u8 *ptr, *read_ptr; size_t bytes_to_read; ssize_t num; fd = open(filename, O_RDONLY); if(fd < 0){ perror("sys_open_file: open"); goto out; } if(fstat(fd, &info) < 0){ perror("sys_open_file: stat"); goto out; } if(info.st_size <= 0){ printf("st_size < 0: %ld\n", info.st_size); goto out; } ptr = (u8*)LinuxGetMemory(info.st_size); if(!ptr){ puts("null pointer from LGM"); goto out; } read_ptr = ptr; bytes_to_read = info.st_size; do { num = read(fd, read_ptr, bytes_to_read); if(num < 0){ if(errno == EINTR){ continue; } else { //TODO(inso): error handling perror("sys_load_file: read"); LinuxFreeMemory(ptr); goto out; } } else { bytes_to_read -= num; read_ptr += num; } } while(bytes_to_read); result.size = info.st_size; result.data = ptr; out: if(fd >= 0) close(fd); return(result); } internal Sys_Save_File_Sig(system_save_file){ b32 result = 0; // TODO(allen): Implement AllowLocal(filename); AllowLocal(data); AllowLocal(size); return(result); } // TODO(allen): Implement this. Also where is this // macro define? Let's try to organize these functions // a little better now that they're starting to settle // into their places. #include "system_shared.cpp" #include "4ed_rendering.cpp" internal Font_Load_Sig(system_draw_font_load){ Font_Load_Parameters *params; system_acquire_lock(FONT_LOCK); params = linuxvars.fnt.free_param.next; fnt__remove(params); fnt__insert(&linuxvars.fnt.used_param, params); system_release_lock(FONT_LOCK); if (linuxvars.fnt.part.base == 0){ linuxvars.fnt.part = LinuxScratchPartition(Mbytes(8)); } b32 done = 0; while(!(done = draw_font_load( linuxvars.fnt.part.base, linuxvars.fnt.part.max, font_out, filename, pt_size, tab_width ) )){ //FIXME(inso): This is an infinite loop if the fonts aren't found! // Figure out how draw_font_load can fail printf("draw_font_load failed, %d\n", linuxvars.fnt.part.max); LinuxScratchPartitionDouble(&linuxvars.fnt.part); } system_acquire_lock(FONT_LOCK); fnt__remove(params); fnt__insert(&linuxvars.fnt.free_param, params); system_release_lock(FONT_LOCK); return(0); } internal Sys_To_Binary_Path(system_to_binary_path){ b32 translate_success = 0; i32 max = out_filename->memory_size; i32 size = readlink("/proc/self/exe", out_filename->str, max); if (size > 0 && size < max-1){ out_filename->str[size] = '\0'; out_filename->size = size + 1; truncate_to_path_of_directory(out_filename); if (append(out_filename, filename) && terminate_with_null(out_filename)){ translate_success = 1; } } return (translate_success); } internal b32 LinuxLoadAppCode(){ b32 result = 0; App_Get_Functions *get_funcs = 0; linuxvars.app_code = dlopen("./4ed_app.so", RTLD_LAZY); if (linuxvars.app_code){ get_funcs = (App_Get_Functions*) dlsym(linuxvars.app_code, "app_get_functions"); } if (get_funcs){ result = 1; linuxvars.app = get_funcs(); } return(result); } internal void LinuxLoadSystemCode(){ linuxvars.system->file_time_stamp = system_file_time_stamp; linuxvars.system->set_file_list = system_set_file_list; linuxvars.system->directory_has_file = system_directory_has_file; linuxvars.system->directory_cd = system_directory_cd; linuxvars.system->post_clipboard = system_post_clipboard; linuxvars.system->time = system_time; linuxvars.system->cli_call = system_cli_call; linuxvars.system->cli_begin_update = system_cli_begin_update; linuxvars.system->cli_update_step = system_cli_update_step; linuxvars.system->cli_end_update = system_cli_end_update; linuxvars.system->post_job = system_post_job; linuxvars.system->cancel_job = system_cancel_job; linuxvars.system->grow_thread_memory = system_grow_thread_memory; linuxvars.system->acquire_lock = system_acquire_lock; linuxvars.system->release_lock = system_release_lock; linuxvars.system->internal_sentinel = internal_sentinel; linuxvars.system->internal_get_thread_states = internal_get_thread_states; linuxvars.system->internal_debug_message = internal_debug_message; linuxvars.system->slash = '/'; } internal void LinuxLoadRenderCode(){ linuxvars.target.push_clip = draw_push_clip; linuxvars.target.pop_clip = draw_pop_clip; linuxvars.target.push_piece = draw_push_piece; linuxvars.target.font_set.font_info_load = draw_font_info_load; linuxvars.target.font_set.font_load = system_draw_font_load; linuxvars.target.font_set.release_font = draw_release_font; } internal void LinuxRedrawTarget(){ system_acquire_lock(RENDER_LOCK); launch_rendering(&linuxvars.target); system_release_lock(RENDER_LOCK); // glFlush(); glXSwapBuffers(linuxvars.XDisplay, linuxvars.XWindow); } internal void LinuxResizeTarget(i32 width, i32 height){ if (width > 0 && height > 0){ glViewport(0, 0, width, height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, width, height, 0, -1, 1); glScissor(0, 0, width, height); linuxvars.target.width = width; linuxvars.target.height = height; linuxvars.redraw = 1; } } // NOTE(allen): Thanks to Casey for providing the linux OpenGL launcher. static bool ctxErrorOccurred = false; static int XInput2OpCode = 0; internal int ctxErrorHandler( Display *dpy, XErrorEvent *ev ) { ctxErrorOccurred = true; return 0; } #if FRED_INTERNAL static void gl_log( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam ){ printf("GL DEBUG: %s\n", message); } #endif internal GLXContext InitializeOpenGLContext(Display *XDisplay, Window XWindow, GLXFBConfig &bestFbc, b32 &IsLegacy) { IsLegacy = false; typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*); const char *glxExts = glXQueryExtensionsString(XDisplay, DefaultScreen(XDisplay)); glXCreateContextAttribsARBProc glXCreateContextAttribsARB = 0; glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc) glXGetProcAddressARB( (const GLubyte *) "glXCreateContextAttribsARB" ); GLXContext ctx = 0; ctxErrorOccurred = false; int (*oldHandler)(Display*, XErrorEvent*) = XSetErrorHandler(&ctxErrorHandler); if (!glXCreateContextAttribsARB) { printf( "glXCreateContextAttribsARB() not found" " ... using old-style GLX context\n" ); ctx = glXCreateNewContext( XDisplay, bestFbc, GLX_RGBA_TYPE, 0, True ); } else { int context_attribs[] = { GLX_CONTEXT_MAJOR_VERSION_ARB, 4, GLX_CONTEXT_MINOR_VERSION_ARB, 3, GLX_CONTEXT_PROFILE_MASK_ARB , GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, #if FRED_INTERNAL GLX_CONTEXT_FLAGS_ARB , GLX_CONTEXT_DEBUG_BIT_ARB, #endif None }; printf("Attribs: %d %d %d %d %d\n", context_attribs[0], context_attribs[1], context_attribs[2], context_attribs[3], context_attribs[4]); printf( "Creating context\n" ); ctx = glXCreateContextAttribsARB( XDisplay, bestFbc, 0, True, context_attribs ); XSync( XDisplay, False ); if ( !ctxErrorOccurred && ctx ) { printf( "Created GL 4.3 context\n" ); } else { ctxErrorOccurred = false; context_attribs[1] = 4; context_attribs[3] = 0; printf( "Creating context\n" ); ctx = glXCreateContextAttribsARB( XDisplay, bestFbc, 0, True, context_attribs ); XSync( XDisplay, False ); if ( !ctxErrorOccurred && ctx ) { printf( "Created GL 4.0 context\n" ); } else { context_attribs[1] = 1; context_attribs[3] = 0; ctxErrorOccurred = false; printf( "Failed to create GL 4.0 context" " ... using old-style GLX context\n" ); ctx = glXCreateContextAttribsARB( XDisplay, bestFbc, 0, True, context_attribs ); IsLegacy = true; } } } XSync( XDisplay, False ); XSetErrorHandler( oldHandler ); if ( ctxErrorOccurred || !ctx ) { printf( "Failed to create an OpenGL context\n" ); exit(1); } if ( ! glXIsDirect ( XDisplay, ctx ) ) { printf( "Indirect GLX rendering context obtained\n" ); } else { printf( "Direct GLX rendering context obtained\n" ); } printf( "Making context current\n" ); glXMakeCurrent( XDisplay, XWindow, ctx ); GLint n; char *Vendor = (char *)glGetString(GL_VENDOR); char *Renderer = (char *)glGetString(GL_RENDERER); char *Version = (char *)glGetString(GL_VERSION); //TODO(inso): glGetStringi is required if the GL version is >= 3.0 char *Extensions = (char *)glGetString(GL_EXTENSIONS); printf("GL_VENDOR: %s\n", Vendor); printf("GL_RENDERER: %s\n", Renderer); printf("GL_VERSION: %s\n", Version); printf("GL_EXTENSIONS: %s\n", Extensions); //TODO(inso): this should be optional if(strstr(glxExts, "GLX_EXT_swap_control ")){ PFNGLXSWAPINTERVALEXTPROC glx_swap_interval = (PFNGLXSWAPINTERVALEXTPROC) glXGetProcAddressARB((const GLubyte*)"glXSwapIntervalEXT"); if(glx_swap_interval){ glx_swap_interval(XDisplay, XWindow, 1); unsigned int swap_val = 0; glXQueryDrawable(XDisplay, XWindow, GLX_SWAP_INTERVAL_EXT, &swap_val); linuxvars.vsync = swap_val == 1; printf("VSync enabled? %d\n", linuxvars.vsync); } } #if FRED_INTERNAL PFNGLDEBUGMESSAGECALLBACKARBPROC gl_dbg_callback = (PFNGLDEBUGMESSAGECALLBACKARBPROC)glXGetProcAddress((const GLubyte*)"glDebugMessageCallback"); if(gl_dbg_callback){ puts("enabling gl debug"); gl_dbg_callback(&gl_log, 0); glEnable(GL_DEBUG_OUTPUT); } #endif glEnable(GL_TEXTURE_2D); glEnable(GL_SCISSOR_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); return(ctx); } internal b32 GLXSupportsModernContexts(Display *XDisplay) { b32 Result = false; int GLXMajor, GLXMinor; char *XVendor = ServerVendor(XDisplay); printf("XWindows vendor: %s\n", XVendor); if(glXQueryVersion(XDisplay, &GLXMajor, &GLXMinor)) { printf("GLX version %d.%d\n", GLXMajor, GLXMinor); if(((GLXMajor == 1 ) && (GLXMinor >= 3)) || (GLXMajor > 1)) { Result = true; } } return(Result); } typedef struct glx_config_result{ b32 Found; GLXFBConfig BestConfig; XVisualInfo BestInfo; } glx_config_result; internal glx_config_result ChooseGLXConfig(Display *XDisplay, int XScreenIndex) { glx_config_result Result = {0}; int DesiredAttributes[] = { 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, //GLX_SAMPLE_BUFFERS , 1, //GLX_SAMPLES , 4, None }; { int ConfigCount; GLXFBConfig *Configs = glXChooseFBConfig(XDisplay, XScreenIndex, DesiredAttributes, &ConfigCount); #if 0 int DiffValues[GLXValueCount]; #endif {for(int ConfigIndex = 0; ConfigIndex < ConfigCount; ++ConfigIndex) { GLXFBConfig &Config = Configs[ConfigIndex]; XVisualInfo *VisualInfo = glXGetVisualFromFBConfig(XDisplay, Config); #if 0 printf(" Option %d:\n", ConfigIndex); printf(" Depth: %d\n", VisualInfo->depth); printf(" Bits per channel: %d\n", VisualInfo->bits_per_rgb); printf(" Mask: R%06x G%06x B%06x\n", (uint32)VisualInfo->red_mask, (uint32)VisualInfo->green_mask, (uint32)VisualInfo->blue_mask); printf(" Class: %d\n", VisualInfo->c_class); #endif #if 0 {for(int ValueIndex = 0; ValueIndex < GLXValueCount; ++ValueIndex) { glx_value_info &ValueInfo = GLXValues[ValueIndex]; int Value; glXGetFBConfigAttrib(XDisplay, Config, ValueInfo.ID, &Value); if(DiffValues[ValueIndex] != Value) { printf(" %s: %d\n", ValueInfo.Name, Value); DiffValues[ValueIndex] = Value; } }} #endif // TODO(casey): How the hell are you supposed to pick a config here?? if(ConfigIndex == 0) { Result.Found = true; Result.BestConfig = Config; Result.BestInfo = *VisualInfo; } XFree(VisualInfo); }} XFree(Configs); } return(Result); } struct Init_Input_Result{ XIM input_method; XIMStyle best_style; XIC xic; }; internal Init_Input_Result InitializeXInput(Display *dpy, Window XWindow) { #if 0 int event, error; if(XQueryExtension(dpy, "XInputExtension", &XInput2OpCode, &event, &error)) { int major = 2, minor = 0; if(XIQueryVersion(dpy, &major, &minor) != BadRequest) { printf("XInput initialized version %d.%d\n", major, minor); } else { printf("XI2 not available. Server supports %d.%d\n", major, minor); } } else { printf("X Input extension not available.\n"); } /* TODO(casey): So, this is all one big clusterfuck I guess. The problem here is that you want to be able to get input from all possible devices that could be a mouse or keyboard (or gamepad, or whatever). So you'd like to be able to register events for XIAllDevices, so that when a new input device is connected, you will start receiving input from it automatically, without having to periodically poll XIQueryDevice to see if a new device has appeared. UNFORTUNATELY, this is not actually possible in Linux because there was a bug in Xorg (as of early 2013, it is still not fixed in most distributions people are using, AFAICT) which makes the XServer return an error if you actually try to do this :( But EVENTUALLY, if that shit gets fixed, then that is the way this should work. */ #if 0 int DeviceCount; XIDeviceInfo *DeviceInfo = XIQueryDevice(dpy, XIAllDevices, &DeviceCount); {for(int32x DeviceIndex = 0; DeviceIndex < DeviceCount; ++DeviceIndex) { XIDeviceInfo *Device = DeviceInfo + DeviceIndex; printf("Device %d: %s\n", Device->deviceid, Device->name); }} XIFreeDeviceInfo(DeviceInfo); #endif XIEventMask Mask = {0}; Mask.deviceid = XIAllDevices; Mask.mask_len = XIMaskLen(XI_RawMotion); size_t MaskSize = Mask.mask_len * sizeof(char unsigned); Mask.mask = (char unsigned *)alloca(MaskSize); memset(Mask.mask, 0, MaskSize); if(Mask.mask) { XISetMask(Mask.mask, XI_ButtonPress); XISetMask(Mask.mask, XI_ButtonRelease); XISetMask(Mask.mask, XI_KeyPress); XISetMask(Mask.mask, XI_KeyRelease); XISetMask(Mask.mask, XI_Motion); XISetMask(Mask.mask, XI_DeviceChanged); XISetMask(Mask.mask, XI_Enter); XISetMask(Mask.mask, XI_Leave); XISetMask(Mask.mask, XI_FocusIn); XISetMask(Mask.mask, XI_FocusOut); XISetMask(Mask.mask, XI_HierarchyChanged); XISetMask(Mask.mask, XI_PropertyEvent); XISelectEvents(dpy, XWindow, &Mask, 1); XSync(dpy, False); Mask.deviceid = XIAllMasterDevices; memset(Mask.mask, 0, MaskSize); XISetMask(Mask.mask, XI_RawKeyPress); XISetMask(Mask.mask, XI_RawKeyRelease); XISetMask(Mask.mask, XI_RawButtonPress); XISetMask(Mask.mask, XI_RawButtonRelease); XISetMask(Mask.mask, XI_RawMotion); XISelectEvents(dpy, DefaultRootWindow(dpy), &Mask, 1); } #endif // NOTE(allen): Annnndddd... here goes some guess work of my own. Init_Input_Result result = {}; XIMStyle style; XIMStyles *styles = 0; i32 i, count; setlocale(LC_ALL, ""); XSetLocaleModifiers(""); printf("is current locale supported?: %d\n", XSupportsLocale()); // TODO(inso): handle the case where it isn't supported somehow? XSelectInput( linuxvars.XDisplay, linuxvars.XWindow, ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask | FocusChangeMask | StructureNotifyMask | MappingNotify ); result.input_method = XOpenIM(dpy, 0, 0, 0); if (!result.input_method){ // NOTE(inso): Try falling back to the internal XIM implementation that // should in theory always exist. XSetLocaleModifiers("@im=none"); result.input_method = XOpenIM(dpy, 0, 0, 0); } if (result.input_method){ if (!XGetIMValues(result.input_method, XNQueryInputStyle, &styles, NULL) && styles){ result.best_style = 0; count = styles->count_styles; for (i = 0; i < count; ++i){ style = styles->supported_styles[i]; if (style == (XIMPreeditNothing | XIMStatusNothing)){ result.best_style = style; break; } } if (result.best_style){ XFree(styles); result.xic = XCreateIC(result.input_method, XNInputStyle, result.best_style, XNClientWindow, XWindow, XNFocusWindow, XWindow, 0, 0, NULL); } else{ result = {}; puts("Could not get minimum required input style"); } } } else{ result = {}; puts("Could not open X Input Method"); } return(result); } static void push_key(u8 code, u8 chr, u8 chr_nocaps, b8 (*mods)[CONTROL_KEY_COUNT], b32 is_hold){ i32 *count; Key_Event_Data *data; if(is_hold){ count = &linuxvars.key_data.hold_count; data = linuxvars.key_data.hold; } else { count = &linuxvars.key_data.press_count; data = linuxvars.key_data.press; } if(*count < KEY_INPUT_BUFFER_SIZE){ data[*count].keycode = code; data[*count].character = chr; data[*count].character_no_caps_lock = chr_nocaps; memcpy(data[*count].modifiers, *mods, sizeof(*mods)); ++(*count); } } int main(int argc, char **argv) { i32 COUNTER = 0; linuxvars = {}; exchange_vars = {}; #if FRED_INTERNAL linuxvars.internal_bubble.next = &linuxvars.internal_bubble; linuxvars.internal_bubble.prev = &linuxvars.internal_bubble; linuxvars.internal_bubble.flags = MEM_BUBBLE_SYS_DEBUG; #endif if (!LinuxLoadAppCode()){ // TODO(allen): Failed to load app code, serious problem. return 99; } System_Functions system_; System_Functions *system = &system_; linuxvars.system = system; LinuxLoadSystemCode(); memory_vars.vars_memory_size = Mbytes(2); memory_vars.vars_memory = system_get_memory(memory_vars.vars_memory_size); memory_vars.target_memory_size = Mbytes(512); memory_vars.target_memory = system_get_memory(memory_vars.target_memory_size); String current_directory; i32 curdir_req, curdir_size; char *curdir_mem; curdir_req = (1 << 9); curdir_mem = (char*)system_get_memory(curdir_req); for (; getcwd(curdir_mem, curdir_req) == 0 && curdir_req < (1 << 13);){ system_free_memory(curdir_mem); curdir_req *= 4; curdir_mem = (char*)system_get_memory(curdir_req); } if (curdir_req >= (1 << 13)){ // TODO(allen): bullshit string APIs makin' me pissed return 57; } for (curdir_size = 0; curdir_mem[curdir_size]; ++curdir_size); current_directory = make_string(curdir_mem, curdir_size, curdir_req); Command_Line_Parameters clparams; clparams.argv = argv; clparams.argc = argc; char **files; i32 *file_count; i32 output_size; output_size = linuxvars.app.read_command_line(system, &memory_vars, current_directory, &linuxvars.settings, &files, &file_count, clparams); if (output_size > 0){ // TODO(allen): crt free version printf("%.*s", output_size, (char*)memory_vars.target_memory); } if (output_size != 0) return 0; sysshared_filter_real_files(files, file_count); linuxvars.XDisplay = XOpenDisplay(0); keycode_init(linuxvars.XDisplay, &linuxvars.key_codes); #ifdef FRED_SUPER char *custom_file_default = "4coder_custom.so"; char *custom_file; if (linuxvars.settings.custom_dll) custom_file = linuxvars.settings.custom_dll; else custom_file = custom_file_default; linuxvars.custom = dlopen(custom_file, RTLD_LAZY); if (!linuxvars.custom && custom_file != custom_file_default){ if (!linuxvars.settings.custom_dll_is_strict){ linuxvars.custom = dlopen(custom_file_default, RTLD_LAZY); } } if (linuxvars.custom){ linuxvars.custom_api.get_bindings = (Get_Binding_Data_Function*) dlsym(linuxvars.custom, "get_bindings"); } #endif // TODO(allen): Setup background threads and locks LinuxLoadRenderCode(); linuxvars.target.max = Mbytes(1); linuxvars.target.push_buffer = (byte*)system_get_memory(linuxvars.target.max); File_Slot file_slots[32]; sysshared_init_file_exchange(&exchange_vars, file_slots, ArrayCount(file_slots), 0); Font_Load_Parameters params[8]; sysshared_init_font_params(&linuxvars.fnt, params, ArrayCount(params)); // NOTE(allen): Here begins the linux screen setup stuff. // Behold the true nature of this wonderful OS: // (thanks again to Casey for providing this stuff) Colormap cmap; XSetWindowAttributes swa; int WinWidth, WinHeight; b32 window_setup_success = 0; WinWidth = 800; WinHeight = 600; if(linuxvars.XDisplay && GLXSupportsModernContexts(linuxvars.XDisplay)) { int XScreenCount = ScreenCount(linuxvars.XDisplay); for(int XScreenIndex = 0; XScreenIndex < XScreenCount; ++XScreenIndex) { Screen *XScreen = ScreenOfDisplay(linuxvars.XDisplay, XScreenIndex); i32 ScrnWidth, ScrnHeight; ScrnWidth = WidthOfScreen(XScreen); ScrnHeight = HeightOfScreen(XScreen); if (ScrnWidth + 50 < WinWidth) WinWidth = ScrnWidth + 50; if (ScrnHeight + 50 < WinHeight) WinHeight = ScrnHeight + 50; glx_config_result Config = ChooseGLXConfig(linuxvars.XDisplay, XScreenIndex); if(Config.Found) { swa.colormap = cmap = XCreateColormap(linuxvars.XDisplay, RootWindow(linuxvars.XDisplay, Config.BestInfo.screen ), Config.BestInfo.visual, AllocNone); swa.background_pixmap = None; swa.border_pixel = 0; swa.event_mask = StructureNotifyMask; linuxvars.XWindow = XCreateWindow(linuxvars.XDisplay, RootWindow(linuxvars.XDisplay, Config.BestInfo.screen), 0, 0, WinWidth, WinHeight, 0, Config.BestInfo.depth, InputOutput, Config.BestInfo.visual, CWBorderPixel|CWColormap|CWEventMask, &swa ); if(linuxvars.XWindow) { XStoreName(linuxvars.XDisplay, linuxvars.XWindow, "4coder-window"); XMapWindow(linuxvars.XDisplay, linuxvars.XWindow); Init_Input_Result input_result = InitializeXInput(linuxvars.XDisplay, linuxvars.XWindow); linuxvars.input_method = input_result.input_method; linuxvars.input_style = input_result.best_style; linuxvars.input_context = input_result.xic; b32 IsLegacy = false; GLXContext GLContext = InitializeOpenGLContext(linuxvars.XDisplay, linuxvars.XWindow, Config.BestConfig, IsLegacy); XWindowAttributes WinAttribs; if(XGetWindowAttributes(linuxvars.XDisplay, linuxvars.XWindow, &WinAttribs)) { WinWidth = WinAttribs.width; WinHeight = WinAttribs.height; } XRaiseWindow(linuxvars.XDisplay, linuxvars.XWindow); XSync(linuxvars.XDisplay, False); window_setup_success = 1; } } } } if (!window_setup_success){ fprintf(stderr, "Error creating window."); exit(1); } XSetICFocus(linuxvars.input_context); linuxvars.atom_CLIPBOARD = XInternAtom(linuxvars.XDisplay, "CLIPBOARD", False); linuxvars.atom_UTF8_STRING = XInternAtom(linuxvars.XDisplay, "UTF8_STRING", False); Atom WM_DELETE_WINDOW = XInternAtom(linuxvars.XDisplay, "WM_DELETE_WINDOW", False); if(WM_DELETE_WINDOW != None){ XSetWMProtocols(linuxvars.XDisplay, linuxvars.XWindow, &WM_DELETE_WINDOW, 1); } linuxvars.app.init(linuxvars.system, &linuxvars.target, &memory_vars, &exchange_vars, &linuxvars.key_codes, linuxvars.clipboard_contents, current_directory, linuxvars.custom_api); LinuxResizeTarget(WinWidth, WinHeight); b32 keep_running = 1; while(keep_running) { XEvent PrevEvent = {}; while(XPending(linuxvars.XDisplay)) { XEvent Event; XNextEvent(linuxvars.XDisplay, &Event); if (XFilterEvent(&Event, None) == True){ continue; } switch (Event.type){ case KeyPress: { b32 is_hold = PrevEvent.type == KeyRelease && PrevEvent.xkey.time == Event.xkey.time && PrevEvent.xkey.keycode == Event.xkey.keycode; b8 mods[CONTROL_KEY_COUNT] = {}; if(Event.xkey.state & ShiftMask) mods[CONTROL_KEY_SHIFT] = 1; if(Event.xkey.state & ControlMask) mods[CONTROL_KEY_CONTROL] = 1; if(Event.xkey.state & LockMask) mods[CONTROL_KEY_CAPS] = 1; if(Event.xkey.state & Mod1Mask) mods[CONTROL_KEY_ALT] = 1; // NOTE(inso): mod5 == AltGr // if(Event.xkey.state & Mod5Mask) mods[CONTROL_KEY_ALT] = 1; KeySym keysym = NoSymbol; char buff[32], no_caps_buff[32]; // NOTE(inso): Turn ControlMask off like the win32 code does. if(mods[CONTROL_KEY_CONTROL] && !mods[CONTROL_KEY_ALT]){ Event.xkey.state &= ~(ControlMask); } // TODO(inso): Use one of the Xutf8LookupString funcs to allow for non-ascii chars XLookupString(&Event.xkey, buff, sizeof(buff), &keysym, NULL); Event.xkey.state &= ~LockMask; XLookupString(&Event.xkey, no_caps_buff, sizeof(no_caps_buff), NULL, NULL); u8 key = keycode_lookup(Event.xkey.keycode); if(key){ push_key(key, 0, 0, &mods, is_hold); } else { key = buff[0] & 0xFF; if(key < 128){ u8 no_caps_key = no_caps_buff[0] & 0xFF; if(key == '\r') key = '\n'; if(no_caps_key == '\r') no_caps_key = '\n'; push_key(key, key, no_caps_key, &mods, is_hold); } else { push_key(0, 0, 0, &mods, is_hold); } } linuxvars.redraw = 1; }break; case KeyRelease: { linuxvars.redraw = 1; }break; case MotionNotify: { linuxvars.mouse_data.x = Event.xmotion.x; linuxvars.mouse_data.y = Event.xmotion.y; linuxvars.redraw = 1; }break; case ButtonPress: { switch(Event.xbutton.button){ case Button1: { linuxvars.mouse_data.left_button_pressed = 1; linuxvars.mouse_data.left_button = 1; } break; case Button3: { linuxvars.mouse_data.right_button_pressed = 1; linuxvars.mouse_data.right_button = 1; } break; } linuxvars.redraw = 1; }break; case ButtonRelease: { switch(Event.xbutton.button){ case Button1: { linuxvars.mouse_data.left_button_released = 1; linuxvars.mouse_data.left_button = 0; } break; case Button3: { linuxvars.mouse_data.right_button_released = 1; linuxvars.mouse_data.right_button = 0; } break; } linuxvars.redraw = 1; }break; case EnterNotify: { linuxvars.mouse_data.out_of_window = 0; linuxvars.redraw = 1; }break; case LeaveNotify: { linuxvars.mouse_data.out_of_window = 1; linuxvars.redraw = 1; }break; case FocusIn: case FocusOut: { linuxvars.mouse_data.left_button = 0; linuxvars.mouse_data.right_button = 0; linuxvars.redraw = 1; }break; case ConfigureNotify: { i32 w = Event.xconfigure.width, h = Event.xconfigure.height; if(w != linuxvars.target.width || h != linuxvars.target.height){ LinuxResizeTarget(Event.xconfigure.width, Event.xconfigure.height); } }break; case MappingNotify: { if(Event.xmapping.request == MappingModifier || Event.xmapping.request == MappingKeyboard){ XRefreshKeyboardMapping(&Event.xmapping); keycode_init(linuxvars.XDisplay, &linuxvars.key_codes); } }break; case ClientMessage: { if ((Atom)Event.xclient.data.l[0] == WM_DELETE_WINDOW) { keep_running = false; } }break; // NOTE(inso): Someone wants us to give them the clipboard data. case SelectionRequest: { XSelectionRequestEvent request = Event.xselectionrequest; XSelectionEvent response = {}; response.type = SelectionNotify; response.requestor = request.requestor; response.selection = request.selection; response.target = request.target; response.time = request.time; response.property = None; //TODO(inso): handle TARGETS negotiation instead of requiring UTF8_STRING if ( linuxvars.clipboard_outgoing.size && request.target == linuxvars.atom_UTF8_STRING && request.selection == linuxvars.atom_CLIPBOARD && request.property != None && request.display && request.requestor ){ XChangeProperty( request.display, request.requestor, request.property, request.target, 8, PropModeReplace, (unsigned char*)linuxvars.clipboard_outgoing.str, linuxvars.clipboard_outgoing.size ); response.property = request.property; } XSendEvent(request.display, request.requestor, True, 0, (XEvent*)&response); }break; // NOTE(inso): Another program is now the clipboard owner. case SelectionClear: { if(Event.xselectionclear.selection == linuxvars.atom_CLIPBOARD){ linuxvars.clipboard_outgoing.size = 0; } }break; // NOTE(inso): A program is giving us the clipboard data we asked for. case SelectionNotify: { XSelectionEvent* e = (XSelectionEvent*)&Event; if( e->selection == linuxvars.atom_CLIPBOARD && e->target == linuxvars.atom_UTF8_STRING && e->property != None ){ Atom type; int fmt; unsigned long nitems, bytes_left; u8 *data; int result = XGetWindowProperty( linuxvars.XDisplay, linuxvars.XWindow, linuxvars.atom_CLIPBOARD, 0L, LINUX_MAX_PASTE_CHARS/4L, False, linuxvars.atom_UTF8_STRING, &type, &fmt, &nitems, &bytes_left, &data ); if(result == Success && fmt == 8){ LinuxStringDup(&linuxvars.clipboard_contents, data, nitems); XFree(data); } } }break; } PrevEvent = Event; } // FIXME(inso): is getting the clipboard every frame a bad idea? XConvertSelection( linuxvars.XDisplay, linuxvars.atom_CLIPBOARD, linuxvars.atom_UTF8_STRING, linuxvars.atom_CLIPBOARD, linuxvars.XWindow, CurrentTime ); Key_Input_Data input_data; Mouse_State mouse; Application_Step_Result result; input_data = linuxvars.key_data; mouse = linuxvars.mouse_data; result.mouse_cursor_type = APP_MOUSE_CURSOR_DEFAULT; result.redraw = linuxvars.redraw; result.lctrl_lalt_is_altgr = 0; u64 start_time = system_time(); linuxvars.app.step(linuxvars.system, &linuxvars.key_codes, &input_data, &mouse, &linuxvars.target, &memory_vars, &exchange_vars, linuxvars.clipboard_contents, 1, linuxvars.first, linuxvars.redraw, &result); if (result.redraw){ LinuxRedrawTarget(); } u64 time_diff = system_time() - start_time; if(time_diff < frame_useconds){ usleep(frame_useconds - time_diff); } linuxvars.redraw = 0; linuxvars.key_data = {}; linuxvars.mouse_data.left_button_pressed = 0; linuxvars.mouse_data.left_button_released = 0; linuxvars.mouse_data.right_button_pressed = 0; linuxvars.mouse_data.right_button_released = 0; } } // BOTTOM