diff --git a/4coder_custom.so b/4coder_custom.so new file mode 100755 index 00000000..8470c958 Binary files /dev/null and b/4coder_custom.so differ diff --git a/4ed_app b/4ed_app new file mode 100755 index 00000000..724bb096 Binary files /dev/null and b/4ed_app differ diff --git a/Makefile b/Makefile index e69de29b..e4a4ea86 100755 --- a/Makefile +++ b/Makefile @@ -0,0 +1,19 @@ +CPP_FILES := $(wildcard *.cpp) $(wildcard **/*.cpp) +C_FILES := $(wildcard *.c) $(wildcard **/*.c) +H_FILES := $(wildcard *.h) $(wildcard **/*.h) + +WARNINGS := -Wno-write-strings +FLAGS := -D_GNU_SOURCE -fPIC + +debug: FLAGS += -DDEV_BUILD +debug: ../build/build + +../build/build: $(CPP_FILES) $(C_FILES) $(H_FILES) + gcc $(WARNINGS) $(FLAGS) build.c -g -o $@ + ../build/build + +clean: + $(RM) ../build/build ../build/fsmgen ../build/metagen ../build/4ed_app.so ../build/4ed + + + diff --git a/build.c b/build.c index 54c6c866..6214fe4f 100644 --- a/build.c +++ b/build.c @@ -4,19 +4,17 @@ // TOP -#include +#include // include system for windows +#include // include system for linux (YAY!) #include #include +#include // // reusable // -#define CL_OPTS \ -"/W4 /wd4310 /wd4100 /wd4201 /wd4505 /wd4996 " \ -"/wd4127 /wd4510 /wd4512 /wd4610 /wd4390 /WX " \ -"/GR- /EHa- /nologo /FC" - +// NOTE(allen): Compiler OS cracking. #if defined(_MSC_VER) #define IS_CL @@ -25,6 +23,18 @@ #if defined(_WIN32) # define IS_WINDOWS # pragma comment(lib, "Kernel32.lib") +#else +# error This compiler/platform combo is not supported yet +#endif + +#elif defined(__GNUC__) || defined(__GNUG__) + +#define IS_GCC + +#if defined(__gnu_linux__) +# define IS_LINUX +#else +# error This compiler/platform combo is not supported yet #endif #else @@ -41,6 +51,11 @@ static int32_t error_state = 0; }while(0) +static void init_time_system(); +static uint64_t get_time(); +static int32_t get_current_directory(char *buffer, int32_t max); +static void execute(char *dir, char *str); + #if defined(IS_WINDOWS) typedef uint32_t DWORD; @@ -101,6 +116,69 @@ execute(char *dir, char *str){ } } +#elif defined(IS_LINUX) + +#include +#include + +typedef struct Temp_Dir{ + char dir[512]; +} Temp_Dir; + +static Temp_Dir +linux_pushd(char *dir){ + Temp_Dir temp; + char *result = getcwd(temp.dir, sizeof(temp.dir)); + int32_t chresult = chdir(dir); + if (result == 0 || chresult != 0){ + printf("trying pushd %s\n", dir); + assert(result != 0); + assert(chresult == 0); + } + return(temp); +} + +static void +linux_popd(Temp_Dir temp){ + chdir(temp.dir); +} + +static void +init_time_system(){ + // NOTE(allen): do nothing +} + +static uint64_t +get_time(){ + struct timespec spec; + uint64_t result; + clock_gettime(CLOCK_MONOTONIC, &spec); + result = (spec.tv_sec * (uint64_t)(1000000)) + (spec.tv_nsec / (uint64_t)(1000)); + return(result); +} + +static int32_t +get_current_directory(char *buffer, int32_t max){ + int32_t result = 0; + char *d = getcwd(buffer, max); + if (d == buffer){ + result = strlen(buffer); + } + return(result); +} + +static void +execute(char *dir, char *str){ + if (dir){ + Temp_Dir temp = linux_pushd(dir); + systemf("%s", str); + linux_popd(temp); + } + else{ + systemf("%s", str); + } +} + #else #error This OS is not supported yet #endif @@ -112,19 +190,11 @@ execute(char *dir, char *str){ // 4coder specific // -#define CL_INCLUDES \ -"/I..\\foreign /I..\\foreign\\freetype2" - -#define CL_LIBS \ -"user32.lib winmm.lib gdi32.lib opengl32.lib ..\\foreign\\freetype.lib" - -#define CL_ICON \ -"..\\foreign\\freetype.lib" static void -swap_ptr(void **A, void **B){ - void *a = *A; - void *b = *B; +swap_ptr(char **A, char **B){ + char *a = *A; + char *b = *B; *A = b; *B = a; } @@ -150,6 +220,62 @@ enum{ }; +#define BUILD_LINE_MAX 4096 +typedef struct Build_Line{ + char build_optionsA[BUILD_LINE_MAX]; + char build_optionsB[BUILD_LINE_MAX]; + char *build_options; + char *build_options_prev; + int32_t build_max; +} Build_Line; + +static void +init_build_line(Build_Line *line){ + line->build_options = line->build_optionsA; + line->build_options_prev = line->build_optionsB; + line->build_optionsA[0] = 0; + line->build_optionsB[0] = 0; + line->build_max = BUILD_LINE_MAX; +} + +#if defined(IS_CL) + +#define build_ap(line, str, ...) do{ \ + snprintf(line.build_options, \ + line.build_max, "%s "str, \ + line.build_options_prev, __VA_ARGS__); \ + swap_ptr(&line.build_options, \ + &line.build_options_prev); \ +}while(0) + +#elif defined(IS_GCC) + +#define build_ap(line, str, ...) do{ \ + snprintf(line.build_options, \ + line.build_max, "%s "str, \ + line.build_options_prev, ##__VA_ARGS__);\ + swap_ptr(&line.build_options, \ + &line.build_options_prev); \ +}while(0) + +#endif + + +#define CL_OPTS \ +"/W4 /wd4310 /wd4100 /wd4201 /wd4505 /wd4996 " \ +"/wd4127 /wd4510 /wd4512 /wd4610 /wd4390 /WX " \ +"/GR- /EHa- /nologo /FC" + +#define CL_INCLUDES \ +"/I..\\foreign /I..\\foreign\\freetype2" + +#define CL_LIBS \ +"user32.lib winmm.lib gdi32.lib opengl32.lib " \ +"..\\foreign\\freetype.lib" + +#define CL_ICON \ +"..\\foreign\\freetype.lib" + static void build_cl(uint32_t flags, char *code_path, char *code_file, @@ -158,6 +284,51 @@ build_cl(uint32_t flags, win32_slash_fix(out_path); win32_slash_fix(code_path); + Build_Line line; + init_build_line(&line); + + if (flags & OPTS){ + build_ap(line, CL_OPTS); + } + + if (flags & INCLUDES){ + build_ap(line, CL_INCLUDES); + } + + if (flags & LIBS){ + build_ap(line, CL_LIBS); + } + + if (flags & ICON){ + build_ap(line, CL_ICON); + } + + if (flags & DEBUG_INFO){ + build_ap(line, "/Zi"); + } + + if (flags & OPTIMIZATION){ + build_ap(line, "/O2"); + } + + if (flags & SHARED_CODE){ + build_ap(line, "/LD"); + } + + if (flags & SUPER){ + build_ap(line, "/DFRED_SUPER"); + } + + if (flags & INTERNAL){ + build_ap(line, "/DFRED_INTERNAL"); + } + + if (flags & KEEP_ASSERT){ + build_ap(line, "/DFRED_KEEP_ASSERT"); + } + + swap_ptr(&line.build_options, &line.build_options_prev); + char link_options[1024]; if (flags & SHARED_CODE){ assert(exports); @@ -167,69 +338,84 @@ build_cl(uint32_t flags, snprintf(link_options, sizeof(link_options), "/NODEFAULTLIB:library"); } - char build_optionsA[4096]; - char build_optionsB[4096]; - char *build_options = build_optionsA; - char *build_options_prev = build_optionsB; - int32_t build_max = sizeof(build_optionsA); - - build_optionsA[0] = 0; - build_optionsB[0] = 0; + systemf("pushd %s & cl %s %s\\%s /Fe%s /link /DEBUG /INCREMENTAL:NO %s", + out_path, line.build_options, code_path, code_file, out_file, link_options); +} + + + +#define GCC_OPTS \ +"-Wno-write-strings -D_GNU_SOURCE -fPIC " \ +"-fno-threadsafe-statics -pthread" + +#define GCC_INCLUDES \ +"-I../foreign" + +#define GCC_LIBS \ +"-L/usr/local/lib -lX11 -lpthread -lm -lrt " \ +"-lGL -ldl -lXfixes -lfreetype -lfontconfig" + +static void +build_gcc(uint32_t flags, + char *code_path, char *code_file, + char *out_path, char *out_file, + char *exports){ + Build_Line line; + init_build_line(&line); if (flags & OPTS){ - snprintf(build_options, build_max, "%s "CL_OPTS, build_options_prev); - swap_ptr(&build_options, &build_options_prev); + build_ap(line, GCC_OPTS); } if (flags & INCLUDES){ - snprintf(build_options, build_max, "%s "CL_INCLUDES, build_options_prev); - swap_ptr(&build_options, &build_options_prev); - } - - if (flags & LIBS){ - snprintf(build_options, build_max, "%s "CL_LIBS, build_options_prev); - swap_ptr(&build_options, &build_options_prev); - } - - if (flags & ICON){ - snprintf(build_options, build_max, "%s "CL_ICON, build_options_prev); - swap_ptr(&build_options, &build_options_prev); + int32_t size = 0; + char freetype_include[512]; + FILE *file = popen("pkg-config --cflags freetype2", "r"); + if (file != 0){ + fgets(freetype_include, sizeof(freetype_include), file); + size = strlen(freetype_include); + freetype_include[size-1] = 0; + pclose(file); + } + + build_ap(line, GCC_INCLUDES" %s", freetype_include); } if (flags & DEBUG_INFO){ - snprintf(build_options, build_max, "%s /Zi", build_options_prev); - swap_ptr(&build_options, &build_options_prev); + build_ap(line, "-g -O0"); } if (flags & OPTIMIZATION){ - snprintf(build_options, build_max, "%s /O2", build_options_prev); - swap_ptr(&build_options, &build_options_prev); + build_ap(line, "-O3"); } if (flags & SHARED_CODE){ - snprintf(build_options, build_max, "%s /LD", build_options_prev); - swap_ptr(&build_options, &build_options_prev); + build_ap(line, "-shared"); } if (flags & SUPER){ - snprintf(build_options, build_max, "%s /DFRED_SUPER", build_options_prev); - swap_ptr(&build_options, &build_options_prev); + build_ap(line, "-DFRED_SUPER"); } if (flags & INTERNAL){ - snprintf(build_options, build_max, "%s /DFRED_INTERNAL", build_options_prev); - swap_ptr(&build_options, &build_options_prev); + build_ap(line, "-DFRED_INTERNAL"); } if (flags & KEEP_ASSERT){ - snprintf(build_options, build_max, "%s /DFRED_KEEP_ASSERT", build_options_prev); - swap_ptr(&build_options, &build_options_prev); + build_ap(line, "-DFRED_KEEP_ASSERT"); } - swap_ptr(&build_options, &build_options_prev); + build_ap(line, "%s/%s", code_path, code_file); - systemf("pushd %s & cl %s %s\\%s /Fe%s /link /DEBUG /INCREMENTAL:NO %s", - out_path, build_options, code_path, code_file, out_file, link_options); + if (flags & LIBS){ + build_ap(line, GCC_LIBS); + } + + swap_ptr(&line.build_options, &line.build_options_prev); + + Temp_Dir temp = linux_pushd(out_path); + systemf("g++ %s -o %s", line.build_options, out_file); + linux_popd(temp); } static void @@ -239,6 +425,8 @@ build(uint32_t flags, char *exports){ #if defined(IS_CL) build_cl(flags, code_path, code_file, out_path, out_file, exports); +#elif defined(IS_GCC) + build_gcc(flags, code_path, code_file, out_path, out_file, exports); #else #error The build rule for this compiler is not ready #endif @@ -254,6 +442,15 @@ buildsuper(char *code_path, char *out_path, char *filename){ systemf("pushd %s & call \"%s\\buildsuper.bat\" %s", out_path, code_path, filename); +#elif defined(IS_GCC) + + Temp_Dir temp = linux_pushd(out_path); + + systemf("\"%s/buildsuper.sh\" %s", + code_path, filename); + + linux_popd(temp); + #else #error The build rule for this compiler is not ready #endif @@ -262,6 +459,14 @@ buildsuper(char *code_path, char *out_path, char *filename){ #define META_DIR "../meta" #define BUILD_DIR "../build" +#if defined(IS_WINDOWS) +#define PLAT_LAYER "win32_4ed.cpp" +#elif defined(IS_LINUX) +#define PLAT_LAYER "linux_4ed.cpp" +#else +#error No platform layer defined for this OS. +#endif + static void standard_build(char *cdir, uint32_t flags){ #if 1 @@ -298,7 +503,11 @@ standard_build(char *cdir, uint32_t flags){ { BEGIN_TIME_SECTION(); //buildsuper(cdir, BUILD_DIR, "../code/4coder_default_bindings.cpp"); +#if IS_WINDOWS buildsuper(cdir, BUILD_DIR, "../code/internal_4coder_tests.cpp"); +#else + buildsuper(cdir, BUILD_DIR, "../code/power/4coder_experiments.cpp"); +#endif //buildsuper(cdir, BUILD_DIR, "../code/power/4coder_casey.cpp"); //buildsuper(cdir, BUILD_DIR, "../4vim/4coder_chronal.cpp"); END_TIME_SECTION("build custom"); @@ -315,16 +524,17 @@ standard_build(char *cdir, uint32_t flags){ { BEGIN_TIME_SECTION(); - build(OPTS | INCLUDES | LIBS | ICON | flags, cdir, "win32_4ed.cpp", + build(OPTS | INCLUDES | LIBS | ICON | flags, cdir, PLAT_LAYER, BUILD_DIR, "4ed", 0); END_TIME_SECTION("build 4ed"); } #endif } -#if defined(DEV_BUILD) +#if defined(DEV_BUILD) + int main(int argc, char **argv){ init_time_system(); diff --git a/fsmgen b/fsmgen new file mode 100755 index 00000000..9804106b Binary files /dev/null and b/fsmgen differ diff --git a/linux_4ed.cpp b/linux_4ed.cpp index 5b0c05e5..6cf30b31 100644 --- a/linux_4ed.cpp +++ b/linux_4ed.cpp @@ -1,3484 +1,3478 @@ -/* - * Mr. 4th Dimention - Allen Webster - * (Mostly by insofaras) - * - * 14.11.2015 - * - * Linux layer for project codename "4ed" - * - */ - -// TOP - -# include "4ed_defines.h" - -#if FRED_SUPER - -# define FSTRING_IMPLEMENTATION -# define FSTRING_C -# include "4coder_string.h" - -#include "4coder_version.h" -# include "4coder_keycodes.h" -# include "4coder_style.h" -# include "4coder_rect.h" - -# include - -# include "4coder_mem.h" - -// TODO(allen): This is duplicated from 4coder_custom.h -// I need to work out a way to avoid this. -#define VIEW_ROUTINE_SIG(name) void name(struct Application_Links *app, int32_t view_id) -#define GET_BINDING_DATA(name) int32_t name(void *data, int32_t size) -#define _GET_VERSION_SIG(n) int32_t n(int32_t maj, int32_t min, int32_t patch) - -typedef VIEW_ROUTINE_SIG(View_Routine_Function); -typedef GET_BINDING_DATA(Get_Binding_Data_Function); -typedef _GET_VERSION_SIG(_Get_Version_Function); - -struct Custom_API{ - View_Routine_Function *view_routine; - Get_Binding_Data_Function *get_bindings; - _Get_Version_Function *get_alpha_4coder_version; -}; - - -typedef void Custom_Command_Function; -#include "4coder_types.h" -struct Application_Links; -# include "4ed_os_custom_api.h" - -//# include "4coder_custom.h" -#else -# include "4coder_default_bindings.cpp" - -# define FSTRING_IMPLEMENTATION -# define FSTRING_C -# include "4coder_string.h" - -#endif - -#include "4ed_math.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 -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - - -// -// Linux macros -// - -#define LINUX_MAX_PASTE_CHARS 0x10000L -#define FPS 60L -#define frame_useconds (1000000UL / FPS) - -#define LinuxGetMemory(size) LinuxGetMemory_(size, __LINE__, __FILE__) - -#if FRED_INTERNAL - #define LINUX_FN_DEBUG(fmt, ...) do { \ - fprintf(stderr, "%s: " fmt "\n", __func__, ##__VA_ARGS__); \ - } while(0) -#else - #define LINUX_FN_DEBUG(fmt, ...) -#endif - -#if (__cplusplus <= 199711L) - #define static_assert(x, ...) -#endif - -#if defined(_BSD_SOURCE) || defined(_SVID_SOURCE) || _POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700 - #define OLD_STAT_NANO_TIME 0 -#else - #define OLD_STAT_NANO_TIME 1 -#endif - -#define SUPPORT_DPI 1 -#define LINUX_FONTS 1 - -#define InterlockedCompareExchange(dest, ex, comp) __sync_val_compare_and_swap((dest), (comp), (ex)) - -#include "filetrack/4tech_file_track_linux.c" -#include "system_shared.h" - -// -// Linux structs / enums -// - -#if FRED_INTERNAL - -struct Sys_Bubble : public Bubble{ - i32 line_number; - char *file_name; -}; - -#endif - -enum { - LINUX_4ED_EVENT_X11 = (UINT64_C(1) << 32), - LINUX_4ED_EVENT_X11_INTERNAL = (UINT64_C(2) << 32), - LINUX_4ED_EVENT_STEP = (UINT64_C(3) << 32), - LINUX_4ED_EVENT_STEP_TIMER = (UINT64_C(4) << 32), - LINUX_4ED_EVENT_CLI = (UINT64_C(5) << 32), -}; - -struct Linux_Coroutine { - Coroutine coroutine; - Linux_Coroutine *next; - ucontext_t ctx, yield_ctx; - stack_t stack; - b32 done; -}; - -struct Thread_Context{ - u32 job_id; - b32 running; - b32 cancel; - - Work_Queue *queue; - u32 id; - u32 group_id; - pthread_t handle; -}; - -struct Thread_Group{ - Thread_Context *threads; - i32 count; - - Unbounded_Work_Queue queue; - - i32 cancel_lock0; - i32 cancel_cv0; -}; - -struct Linux_Vars{ - Display *XDisplay; - Window XWindow; - Render_Target target; - - XIM input_method; - XIMStyle input_style; - XIC input_context; - - Application_Step_Input input; - - String clipboard_contents; - String clipboard_outgoing; - b32 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; - - b32 has_xfixes; - int xfixes_selection_event; - - int epoll; - - int step_timer_fd; - int step_event_fd; - int x11_fd; - int inotify_fd; - - u64 last_step; - - b32 keep_running; - - Application_Mouse_Cursor cursor; - b32 hide_cursor; - Cursor hidden_cursor; - - void *app_code; - void *custom; - - Thread_Memory *thread_memory; - Thread_Group groups[THREAD_GROUP_COUNT]; - Work_Queue queues[THREAD_GROUP_COUNT]; - pthread_mutex_t locks[LOCK_COUNT]; - pthread_cond_t conds[8]; - sem_t thread_semaphore; - - Partition font_part; - -#if SUPPORT_DPI - i32 dpi_x, dpi_y; -#endif - - Plat_Settings settings; - System_Functions system; - App_Functions app; - Custom_API custom_api; - b32 vsync; - -#if FRED_INTERNAL - Sys_Bubble internal_bubble; - pthread_mutex_t DEBUG_sysmem_lock; -#endif - - Linux_Coroutine coroutine_data[18]; - Linux_Coroutine *coroutine_free; -}; - -// -// Linux globals -// - -globalvar Linux_Vars linuxvars; -globalvar Application_Memory memory_vars; - -// -// Linux forward declarations -// - -internal void LinuxScheduleStep(void); - -internal Plat_Handle LinuxSemToHandle(sem_t*); -internal sem_t* LinuxHandleToSem(Plat_Handle); - -internal Plat_Handle LinuxFDToHandle(int); -internal int LinuxHandleToFD(Plat_Handle); - -internal void LinuxStringDup(String*, void*, size_t); -internal void LinuxToggleFullscreen(Display*, Window); - -internal Sys_Acquire_Lock_Sig(system_acquire_lock); -internal Sys_Release_Lock_Sig(system_release_lock); - -internal void system_wait_cv(i32, i32); -internal void system_signal_cv(i32, i32); - -// -// Linux static assertions -// - -static_assert(sizeof(Plat_Handle) >= sizeof(ucontext_t*), "Plat_Handle not big enough"); -static_assert(sizeof(Plat_Handle) >= sizeof(sem_t*), "Plat_Handle not big enough"); -static_assert(sizeof(Plat_Handle) >= sizeof(int), "Plat_Handle not big enough"); - -// -// Shared system functions (system_shared.h) -// - -internal void* -LinuxGetMemory_(i32 size, i32 line_number, char *file_name){ - void *result = 0; - - Assert(size != 0); - -#if FRED_INTERNAL - result = mmap(0, size + sizeof(Sys_Bubble), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - - Sys_Bubble* bubble = (Sys_Bubble*)result; - bubble->flags = 0; - bubble->line_number = line_number; - bubble->file_name = file_name; - bubble->size = size; - - pthread_mutex_lock(&linuxvars.DEBUG_sysmem_lock); - insert_bubble(&linuxvars.internal_bubble, bubble); - pthread_mutex_unlock(&linuxvars.DEBUG_sysmem_lock); - - result = bubble + 1; -#else - size_t real_size = size + sizeof(size_t); - result = mmap(0, real_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if(result == MAP_FAILED){ - perror("mmap"); - result = NULL; - } else { - memcpy(result, &real_size, sizeof(size_t)); - result = (char*)result + sizeof(size_t); - } -#endif - - return(result); -} - -internal void -LinuxFreeMemory(void *block){ - if (block){ -#if FRED_INTERNAL - Sys_Bubble *bubble = ((Sys_Bubble*)block) - 1; - - size_t size = bubble->size + sizeof(Sys_Bubble); - - pthread_mutex_lock(&linuxvars.DEBUG_sysmem_lock); - remove_bubble(bubble); - pthread_mutex_unlock(&linuxvars.DEBUG_sysmem_lock); - - munmap(bubble, size); -#else - block = (char*)block - sizeof(size_t); - size_t size = *(size_t*)block; - munmap(block, size); -#endif - } -} - -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 -Sys_File_Can_Be_Made_Sig(system_file_can_be_made){ - b32 result = access(filename, W_OK) == 0; - LINUX_FN_DEBUG("%s = %d", filename, result); - return(result); -} - -internal -Sys_Get_Binary_Path_Sig(system_get_binary_path){ - ssize_t size = readlink("/proc/self/exe", out->str, out->memory_size - 1); - if(size != -1 && size < out->memory_size - 1){ - out->size = size; - remove_last_folder(out); - terminate_with_null(out); - size = out->size; - } else { - size = 0; - } - - return size; -} - -// -// System Functions (4ed_system.h) -// - -// -// Files -// - -internal -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; - - if(directory.size <= 0){ - if(!directory.str){ - system_free_memory(file_list->block); - file_list->block = 0; - file_list->block_size = 0; - } - file_list->infos = 0; - file_list->count = 0; - return; - } - - char* dir = (char*) alloca(directory.size + 1); - memcpy(dir, directory.str, directory.size); - dir[directory.size] = 0; - - LINUX_FN_DEBUG("%s", dir); - - d = opendir(dir); - if (d){ - count = 0; - file_count = 0; - for (entry = readdir(d); - entry != 0; - entry = readdir(d)){ - fname = entry->d_name; - if(fname[0] == '.' && (fname[1] == 0 || (fname[1] == '.' && fname[2] == 0))){ - continue; - } - ++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)){ - fname = entry->d_name; - if(fname[0] == '.' && (fname[1] == 0 || (fname[1] == '.' && fname[2] == 0))){ - continue; - } - cursor_start = cursor; - for (; *fname; ) *cursor++ = *fname++; - - if(entry->d_type == DT_LNK){ - struct stat st; - if(stat(entry->d_name, &st) != -1){ - info_ptr->folder = S_ISDIR(st.st_mode); - } else { - info_ptr->folder = 0; - } - } else { - info_ptr->folder = entry->d_type == DT_DIR; - } - - info_ptr->filename = cursor_start; - info_ptr->filename_len = (int)(cursor - cursor_start); - *cursor++ = 0; - ++info_ptr; - } - - file_list->count = file_count; - - closedir(d); - } else { - system_free_memory(file_list->block); - file_list->block = 0; - file_list->block_size = 0; - file_list->infos = 0; - file_list->count = 0; - } -} - -internal -Sys_Get_Canonical_Sig(system_get_canonical){ - char* path = (char*) alloca(len + 1); - char* write_p = path; - const char* read_p = filename; - - while(read_p < filename + len){ - if(read_p == filename || read_p[0] == '/'){ - if(read_p[1] == '/'){ - ++read_p; - } else if(read_p[1] == '.'){ - if(read_p[2] == '/' || !read_p[2]){ - read_p += 2; - } else if(read_p[2] == '.' && (read_p[3] == '/' || !read_p[3])){ - while(write_p > path && *--write_p != '/'); - read_p += 3; - } else { - *write_p++ = *read_p++; - } - } else { - *write_p++ = *read_p++; - } - } else { - *write_p++ = *read_p++; - } - } - if(write_p == path) *write_p++ = '/'; - - if(max >= (write_p - path)){ - memcpy(buffer, path, write_p - path); - } else { - write_p = path; - } - -#if FRED_INTERNAL - if(len != (write_p - path) || memcmp(filename, path, len) != 0){ - LINUX_FN_DEBUG("[%.*s] -> [%.*s]", len, filename, (int)(write_p - path), path); - } -#endif - - return write_p - path; -} - -internal -Sys_Load_Handle_Sig(system_load_handle){ - b32 result = 0; - - int fd = open(filename, O_RDONLY); - if(fd == -1){ - perror("open"); - } else { - *(int*)handle_out = fd; - result = 1; - } - - return result; -} - -internal -Sys_Load_Size_Sig(system_load_size){ - u32 result = 0; - - int fd = *(int*)&handle; - struct stat st; - - if(fstat(fd, &st) == -1){ - perror("fstat"); - } else { - result = st.st_size; - } - - return result; -} - -internal -Sys_Load_File_Sig(system_load_file){ - - int fd = *(int*)&handle; - do { - ssize_t n = read(fd, buffer, size); - if(n == -1 && errno != EAGAIN){ - perror("read"); - break; - } else { - size -= n; - buffer += n; - } - } while(size); - - return size == 0; -} - -internal -Sys_Load_Close_Sig(system_load_close){ - b32 result = 1; - - int fd = *(int*)&handle; - if(close(fd) == -1){ - perror("close"); - result = 0; - } - - return result; -} - -internal -Sys_Save_File_Sig(system_save_file){ - b32 result = 0; - int fd = open(filename, O_WRONLY | O_TRUNC | O_CREAT, 00640); - - LINUX_FN_DEBUG("%s %d", filename, size); - - if(fd < 0){ - fprintf(stderr, "system_save_file: open '%s': %s\n", filename, strerror(errno)); - } else { - do { - ssize_t written = write(fd, buffer, size); - if(written == -1){ - if(errno != EINTR){ - perror("system_save_file: write"); - break; - } - } else { - size -= written; - buffer += written; - } - } while(size); - - close(fd); - } - - return (size == 0); -} - -// -// Time -// - -internal -Sys_Now_Time_Sig(system_now_time){ - struct timespec spec; - u64 result; - - clock_gettime(CLOCK_REALTIME, &spec); - result = (spec.tv_sec * UINT64_C(1000000)) + (spec.tv_nsec / UINT64_C(1000)); - - //LINUX_FN_DEBUG("ts: %" PRIu64, result); - - return(result); -} - -// -// 4coder_custom.h -// - -internal -MEMORY_ALLOCATE_SIG(system_memory_allocate){ - // NOTE(allen): This must return the exact base of the vpage. - // We will count on the user to keep track of size themselves. - void *result = mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if(result == MAP_FAILED){ - perror("mmap"); - result = NULL; - } - return(result); -} - -internal -MEMORY_SET_PROTECTION_SIG(system_memory_set_protection){ - // NOTE(allen): - // There is no such thing as "write only" in windows - // so I just made write = write + read in all cases. - bool32 result = 1; - int protect = 0; - - flags = flags & 0x7; - - switch (flags){ - case 0: - protect = PROT_NONE; break; - - case MemProtect_Read: - protect = PROT_READ; break; - - case MemProtect_Write: - case MemProtect_Read|MemProtect_Write: - protect = PROT_READ | PROT_WRITE; break; - - case MemProtect_Execute: - protect = PROT_EXEC; break; - - case MemProtect_Execute|MemProtect_Read: - protect = PROT_READ | PROT_EXEC; break; - - // NOTE(inso): some W^X protection things might be unhappy about this one - case MemProtect_Execute|MemProtect_Write: - case MemProtect_Execute|MemProtect_Write|MemProtect_Read: - protect = PROT_READ | PROT_WRITE | PROT_EXEC; break; - } - - if(mprotect(ptr, size, protect) == -1){ - result = 0; - perror("mprotect"); - } - - return(result); -} - -internal -MEMORY_FREE_SIG(system_memory_free){ - // NOTE(allen): This must take the exact base of the vpage. - munmap(ptr, size); -} - -internal -FILE_EXISTS_SIG(system_file_exists){ - int result = 0; - char buff[PATH_MAX] = {}; - - if(len + 1 > PATH_MAX){ - fputs("system_directory_has_file: path too long\n", stderr); - } else { - memcpy(buff, filename, len); - buff[len] = 0; - struct stat st; - result = stat(buff, &st) == 0 && S_ISREG(st.st_mode); - } - - LINUX_FN_DEBUG("%s: %d", buff, result); - - return(result); -} - -internal -DIRECTORY_CD_SIG(system_directory_cd){ - String directory = make_string_cap(dir, *len, capacity); - b32 result = 0; - i32 old_size; - - if (rel_path[0] != 0){ - if (rel_path[0] == '.' && rel_path[1] == 0){ - result = 1; - } - else if (rel_path[0] == '.' && rel_path[1] == '.' && rel_path[2] == 0){ - result = remove_last_folder(&directory); - terminate_with_null(&directory); - } - else{ - if (directory.size + rel_len + 1 > directory.memory_size){ - old_size = directory.size; - append_partial_sc(&directory, rel_path); - append_s_char(&directory, '/'); - terminate_with_null(&directory); - - struct stat st; - if (stat(directory.str, &st) == 0 && S_ISDIR(st.st_mode)){ - result = 1; - } - else{ - directory.size = old_size; - } - } - } - } - - *len = directory.size; - - LINUX_FN_DEBUG("%.*s: %d", directory.size, directory.str, result); - - return(result); -} - -internal -GET_4ED_PATH_SIG(system_get_4ed_path){ - String str = make_string_cap(out, 0, capacity); - return(system_get_binary_path(&str)); -} - -internal -SHOW_MOUSE_CURSOR_SIG(system_show_mouse_cursor){ - linuxvars.hide_cursor = !show; - XDefineCursor(linuxvars.XDisplay, linuxvars.XWindow, show ? None : linuxvars.hidden_cursor); -} - -internal -TOGGLE_FULLSCREEN_SIG(system_toggle_fullscreen){ - LinuxToggleFullscreen(linuxvars.XDisplay, linuxvars.XWindow); -} - -internal -IS_FULLSCREEN_SIG(system_is_fullscreen){ - b32 result = 0; - - Atom type, *prop; - unsigned long nitems, pad; - int fmt; - - int ret = XGetWindowProperty(linuxvars.XDisplay, - linuxvars.XWindow, - linuxvars.atom__NET_WM_STATE, - 0, 32, False, XA_ATOM, - &type, - &fmt, - &nitems, - &pad, - (unsigned char**)&prop); - - if(ret == Success && prop){ - result = *prop == linuxvars.atom__NET_WM_STATE_FULLSCREEN; - XFree((unsigned char*)prop); - } - - return result; -} - -// -// Clipboard -// - -internal -Sys_Post_Clipboard_Sig(system_post_clipboard){ - LinuxStringDup(&linuxvars.clipboard_outgoing, str.str, str.size); - XSetSelectionOwner(linuxvars.XDisplay, linuxvars.atom_CLIPBOARD, linuxvars.XWindow, CurrentTime); -} - -// -// Coroutine -// - -internal Linux_Coroutine* -LinuxAllocCoroutine(){ - Linux_Coroutine *result = linuxvars.coroutine_free; - Assert(result != 0); - if(getcontext(&result->ctx) == -1){ - perror("getcontext"); - } - result->ctx.uc_stack = result->stack; - linuxvars.coroutine_free = result->next; - return(result); -} - -internal void -LinuxFreeCoroutine(Linux_Coroutine *data){ - data->next = linuxvars.coroutine_free; - linuxvars.coroutine_free = data; -} - -internal void -LinuxCoroutineMain(void *arg_){ - Linux_Coroutine *c = (Linux_Coroutine*)arg_; - c->coroutine.func(&c->coroutine); - c->done = 1; - LinuxFreeCoroutine(c); - setcontext((ucontext_t*)c->coroutine.yield_handle); -} - -internal -Sys_Create_Coroutine_Sig(system_create_coroutine){ - Linux_Coroutine *c = LinuxAllocCoroutine(); - c->done = 0; - - makecontext(&c->ctx, (void (*)())LinuxCoroutineMain, 1, &c->coroutine); - - *(ucontext_t**)&c->coroutine.plat_handle = &c->ctx; - c->coroutine.func = func; - - return(&c->coroutine); -} - -internal -Sys_Launch_Coroutine_Sig(system_launch_coroutine){ - Linux_Coroutine *c = (Linux_Coroutine*)coroutine; - ucontext_t* ctx = *(ucontext**)&coroutine->plat_handle; - - coroutine->yield_handle = &c->yield_ctx; - coroutine->in = in; - coroutine->out = out; - - swapcontext(&c->yield_ctx, ctx); - - if (c->done){ - LinuxFreeCoroutine(c); - coroutine = 0; - } - - return(coroutine); -} - -internal -Sys_Resume_Coroutine_Sig(system_resume_coroutine){ - Linux_Coroutine *c = (Linux_Coroutine*)coroutine; - void *fiber; - - Assert(!c->done); - - coroutine->yield_handle = &c->yield_ctx; - coroutine->in = in; - coroutine->out = out; - - ucontext *ctx = *(ucontext**)&coroutine->plat_handle; - - swapcontext(&c->yield_ctx, ctx); - - if (c->done){ - LinuxFreeCoroutine(c); - coroutine = 0; - } - - return(coroutine); -} - -internal -Sys_Yield_Coroutine_Sig(system_yield_coroutine){ - swapcontext(*(ucontext_t**)&coroutine->plat_handle, (ucontext*)coroutine->yield_handle); -} - -// -// CLI -// - -internal -Sys_CLI_Call_Sig(system_cli_call){ - LINUX_FN_DEBUG("%s %s", path, script_name); - - int pipe_fds[2]; - if(pipe(pipe_fds) == -1){ - perror("system_cli_call: pipe"); - return 0; - } - - pid_t child_pid = fork(); - if(child_pid == -1){ - perror("system_cli_call: fork"); - return 0; - } - - enum { PIPE_FD_READ, PIPE_FD_WRITE }; - - // child - if(child_pid == 0){ - close(pipe_fds[PIPE_FD_READ]); - dup2(pipe_fds[PIPE_FD_WRITE], STDOUT_FILENO); - dup2(pipe_fds[PIPE_FD_WRITE], STDERR_FILENO); - - if(chdir(path) == -1){ - perror("system_cli_call: chdir"); - exit(1); - }; - - char* argv[] = { "sh", "-c", script_name, NULL }; - - if(execv("/bin/sh", argv) == -1){ - perror("system_cli_call: execv"); - } - exit(1); - } else { - close(pipe_fds[PIPE_FD_WRITE]); - - *(pid_t*)&cli_out->proc = child_pid; - *(int*)&cli_out->out_read = pipe_fds[PIPE_FD_READ]; - *(int*)&cli_out->out_write = pipe_fds[PIPE_FD_WRITE]; - - struct epoll_event e = {}; - e.events = EPOLLIN | EPOLLET; - e.data.u64 = LINUX_4ED_EVENT_CLI; - epoll_ctl(linuxvars.epoll, EPOLL_CTL_ADD, pipe_fds[PIPE_FD_READ], &e); - } - - return 1; -} - -internal -Sys_CLI_Begin_Update_Sig(system_cli_begin_update){ - // NOTE(inso): I don't think anything needs to be done here. -} - -internal -Sys_CLI_Update_Step_Sig(system_cli_update_step){ - - int pipe_read_fd = *(int*)&cli->out_read; - - fd_set fds; - FD_ZERO(&fds); - FD_SET(pipe_read_fd, &fds); - - struct timeval tv = {}; - - size_t space_left = max; - char* ptr = dest; - - while(space_left > 0 && select(pipe_read_fd + 1, &fds, NULL, NULL, &tv) == 1){ - ssize_t num = read(pipe_read_fd, ptr, space_left); - if(num == -1){ - perror("system_cli_update_step: read"); - } else if(num == 0){ - // NOTE(inso): EOF - break; - } else { - ptr += num; - space_left -= num; - } - } - - *amount = (ptr - dest); - return (ptr - dest) > 0; -} - -internal -Sys_CLI_End_Update_Sig(system_cli_end_update){ - pid_t pid = *(pid_t*)&cli->proc; - b32 close_me = 0; - - int status; - if(pid && waitpid(pid, &status, WNOHANG) > 0){ - close_me = 1; - - cli->exit = WEXITSTATUS(status); - - struct epoll_event e = {}; - epoll_ctl(linuxvars.epoll, EPOLL_CTL_DEL, *(int*)&cli->out_read, &e); - - close(*(int*)&cli->out_read); - close(*(int*)&cli->out_write); - } - - return close_me; -} - -// -// Threads -// - -internal -Sys_Acquire_Lock_Sig(system_acquire_lock){ - pthread_mutex_lock(linuxvars.locks + id); -} - -internal -Sys_Release_Lock_Sig(system_release_lock){ - pthread_mutex_unlock(linuxvars.locks + id); -} - -internal void -system_wait_cv(i32 lock_id, i32 cv_id){ - pthread_cond_wait(linuxvars.conds + cv_id, linuxvars.locks + lock_id); -} - -internal void -system_signal_cv(i32 lock_id, i32 cv_id){ - pthread_cond_signal(linuxvars.conds + cv_id); -} - -internal void* -JobThreadProc(void* lpParameter){ - Thread_Context *thread = (Thread_Context*)lpParameter; - Work_Queue *queue = linuxvars.queues + thread->group_id; - Thread_Group *group = linuxvars.groups + thread->group_id; - - i32 thread_index = thread->id - 1; - - i32 cancel_lock = group->cancel_lock0 + thread_index; - i32 cancel_cv = group->cancel_cv0 + thread_index; - - Thread_Memory *thread_memory = linuxvars.thread_memory + thread_index; - - if (thread_memory->size == 0){ - i32 new_size = Kbytes(64); - thread_memory->data = LinuxGetMemory(new_size); - thread_memory->size = new_size; - } - - for (;;){ - u32 read_index = queue->read_position; - u32 write_index = queue->write_position; - - if (read_index != write_index){ - // NOTE(allen): Previously I was wrapping by the job wrap then - // wrapping by the queue wrap. That was super stupid what was that? - // Now it just wraps by the queue wrap. - u32 next_read_index = (read_index + 1) % QUEUE_WRAP; - u32 safe_read_index = - InterlockedCompareExchange(&queue->read_position, - next_read_index, read_index); - - if (safe_read_index == read_index){ - Full_Job_Data *full_job = queue->jobs + safe_read_index; - // NOTE(allen): This is interlocked so that it plays nice - // with the cancel job routine, which may try to cancel this job - // at the same time that we try to run it - - i32 safe_running_thread = - InterlockedCompareExchange(&full_job->running_thread, - thread->id, THREAD_NOT_ASSIGNED); - - if (safe_running_thread == THREAD_NOT_ASSIGNED){ - thread->job_id = full_job->id; - thread->running = 1; - - full_job->job.callback(&linuxvars.system, - thread, thread_memory, full_job->job.data); - LinuxScheduleStep(); - //full_job->running_thread = 0; - thread->running = 0; - - system_acquire_lock(cancel_lock); - if (thread->cancel){ - thread->cancel = 0; - system_signal_cv(cancel_lock, cancel_cv); - } - system_release_lock(cancel_lock); - } - } - } - else{ - sem_wait(LinuxHandleToSem(queue->semaphore)); - } - } -} - -internal void -initialize_unbounded_queue(Unbounded_Work_Queue *source_queue){ - i32 max = 512; - source_queue->jobs = (Full_Job_Data*)system_get_memory(max*sizeof(Full_Job_Data)); - source_queue->count = 0; - source_queue->max = max; - source_queue->skip = 0; -} - -inline i32 -get_work_queue_available_space(i32 write, i32 read){ - // NOTE(allen): The only time that queue->write_position == queue->read_position - // is allowed is when the queue is empty. Thus if - // queue->write_position+1 == queue->read_position the available space is zero. - // So these computations both end up leaving one slot unused. The only way I can - // think to easily eliminate this is to have read and write wrap at twice the size - // of the underlying array but modulo their values into the array then if write - // has caught up with read it still will not be equal... but lots of modulos... ehh. - - i32 available_space = 0; - if (write >= read){ - available_space = QUEUE_WRAP - (write - read) - 1; - } - else{ - available_space = (read - write) - 1; - } - - return(available_space); -} - -#define UNBOUNDED_SKIP_MAX 128 - -internal void -flush_to_direct_queue(Unbounded_Work_Queue *source_queue, Work_Queue *queue, i32 thread_count){ - // NOTE(allen): It is understood that read_position may be changed by other - // threads but it will only make more space in the queue if it is changed. - // Meanwhile write_position should not ever be changed by anything but the - // main thread in this system, so it will not be interlocked. - u32 read_position = queue->read_position; - u32 write_position = queue->write_position; - u32 available_space = get_work_queue_available_space(write_position, read_position); - u32 available_jobs = source_queue->count - source_queue->skip; - - u32 writable_count = Min(available_space, available_jobs); - - if (writable_count > 0){ - u32 count1 = writable_count; - - if (count1+write_position > QUEUE_WRAP){ - count1 = QUEUE_WRAP - write_position; - } - - u32 count2 = writable_count - count1; - - Full_Job_Data *job_src1 = source_queue->jobs + source_queue->skip; - Full_Job_Data *job_src2 = job_src1 + count1; - - Full_Job_Data *job_dst1 = queue->jobs + write_position; - Full_Job_Data *job_dst2 = queue->jobs; - - Assert((job_src1->id % QUEUE_WRAP) == write_position); - - memcpy(job_dst1, job_src1, sizeof(Full_Job_Data)*count1); - memcpy(job_dst2, job_src2, sizeof(Full_Job_Data)*count2); - queue->write_position = (write_position + writable_count) % QUEUE_WRAP; - - source_queue->skip += writable_count; - - if (source_queue->skip == source_queue->count){ - source_queue->skip = source_queue->count = 0; - } - else if (source_queue->skip > UNBOUNDED_SKIP_MAX){ - u32 left_over = source_queue->count - source_queue->skip; - memmove(source_queue->jobs, source_queue->jobs + source_queue->skip, - sizeof(Full_Job_Data)*left_over); - source_queue->count = left_over; - source_queue->skip = 0; - } - } - - i32 semaphore_release_count = writable_count; - if (semaphore_release_count > thread_count){ - semaphore_release_count = thread_count; - } - - // NOTE(allen): platform dependent portion... - // TODO(allen): pull out the duplicated part once I see - // that this is pretty much the same on linux. - for (i32 i = 0; i < semaphore_release_count; ++i){ - sem_post(LinuxHandleToSem(queue->semaphore)); - } -} - -internal void -flush_thread_group(i32 group_id){ - Thread_Group *group = linuxvars.groups + group_id; - Work_Queue *queue = linuxvars.queues + group_id; - Unbounded_Work_Queue *source_queue = &group->queue; - flush_to_direct_queue(source_queue, queue, group->count); -} - -// Note(allen): post_job puts the job on the unbounded queue. -// The unbounded queue is entirely managed by the main thread. -// The thread safe queue is bounded in size so the unbounded -// queue is periodically flushed into the direct work queue. -internal -Sys_Post_Job_Sig(system_post_job){ - Thread_Group *group = linuxvars.groups + group_id; - Unbounded_Work_Queue *queue = &group->queue; - - u32 result = queue->next_job_id++; - - while (queue->count >= queue->max){ - i32 new_max = queue->max*2; - Full_Job_Data *new_jobs = (Full_Job_Data*) - system_get_memory(new_max*sizeof(Full_Job_Data)); - - memcpy(new_jobs, queue->jobs, queue->count); - - system_free_memory(queue->jobs); - - queue->jobs = new_jobs; - queue->max = new_max; - } - - Full_Job_Data full_job; - - full_job.job = job; - full_job.running_thread = THREAD_NOT_ASSIGNED; - full_job.id = result; - - queue->jobs[queue->count++] = full_job; - - Work_Queue *direct_queue = linuxvars.queues + group_id; - flush_to_direct_queue(queue, direct_queue, group->count); - - return(result); -} - -internal -Sys_Cancel_Job_Sig(system_cancel_job){ - Thread_Group *group = linuxvars.groups + group_id; - Unbounded_Work_Queue *source_queue = &group->queue; - - b32 handled_in_unbounded = false; - if (source_queue->skip < source_queue->count){ - Full_Job_Data *first_job = source_queue->jobs + source_queue->skip; - if (first_job->id <= job_id){ - u32 index = source_queue->skip + (job_id - first_job->id); - Full_Job_Data *job = source_queue->jobs + index; - job->running_thread = 0; - handled_in_unbounded = true; - } - } - - if (!handled_in_unbounded){ - Work_Queue *queue = linuxvars.queues + group_id; - Full_Job_Data *job = queue->jobs + (job_id % QUEUE_WRAP); - Assert(job->id == job_id); - - u32 thread_id = - InterlockedCompareExchange(&job->running_thread, - 0, THREAD_NOT_ASSIGNED); - - if (thread_id != THREAD_NOT_ASSIGNED && thread_id != 0){ - i32 thread_index = thread_id - 1; - - i32 cancel_lock = group->cancel_lock0 + thread_index; - i32 cancel_cv = group->cancel_cv0 + thread_index; - Thread_Context *thread = group->threads + thread_index; - - - system_acquire_lock(cancel_lock); - - thread->cancel = 1; - - system_release_lock(FRAME_LOCK); - do{ - system_wait_cv(cancel_lock, cancel_cv); - }while (thread->cancel == 1); - system_acquire_lock(FRAME_LOCK); - - system_release_lock(cancel_lock); - } - } -} - -internal -Sys_Check_Cancel_Sig(system_check_cancel){ - b32 result = 0; - - Thread_Group *group = linuxvars.groups + thread->group_id; - i32 thread_index = thread->id - 1; - i32 cancel_lock = group->cancel_lock0 + thread_index; - - system_acquire_lock(cancel_lock); - if (thread->cancel){ - result = 1; - } - system_release_lock(cancel_lock); - - return(result); -} - -internal -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); -} - -// -// Debug -// - -#if FRED_INTERNAL - -internal -INTERNAL_Sys_Sentinel_Sig(internal_sentinel){ - return (&linuxvars.internal_bubble); -} - -#ifdef OLD_JOB_QUEUE -internal -INTERNAL_Sys_Get_Thread_States_Sig(internal_get_thread_states){ - Work_Queue *queue = linuxvars.queues + id; - u32 write = queue->write_position; - u32 read = queue->read_position; - if (write < read) write += QUEUE_WRAP; - *pending = (i32)(write - read); - - Thread_Group *group = linuxvars.groups + id; - for (i32 i = 0; i < group->count; ++i){ - running[i] = (group->threads[i].running != 0); - } -} -#else -internal -INTERNAL_Sys_Get_Thread_States_Sig(internal_get_thread_states){ - Thread_Group *group = linuxvars.groups + id; - Unbounded_Work_Queue *source_queue = &group->queue; - Work_Queue *queue = linuxvars.queues + id; - u32 write = queue->write_position; - u32 read = queue->read_position; - if (write < read) write += QUEUE_WRAP; - *pending = (i32)(write - read) + source_queue->count - source_queue->skip; - - for (i32 i = 0; i < group->count; ++i){ - running[i] = (group->threads[i].running != 0); - } -} -#endif - -internal -INTERNAL_Sys_Debug_Message_Sig(internal_debug_message){ - fprintf(stderr, "%s", message); -} - -#endif - -// -// Linux rendering/font system functions -// - -#include "system_shared.cpp" -#include "linux_font.cpp" - -internal f32 -size_change(i32 dpi_x, i32 dpi_y){ - // TODO(allen): We're just hoping dpi_x == dpi_y for now I guess. - f32 size_x = dpi_x / 96.f; - f32 size_y = dpi_y / 96.f; - f32 size_max = Max(size_x, size_y); - return(size_max); -} - -internal -Font_Load_Sig(system_draw_font_load){ - b32 success = 0; - i32 attempts = 0; - - LINUX_FN_DEBUG("%s, %dpt, tab_width: %d", filename, pt_size, tab_width); - - if (linuxvars.font_part.base == 0){ - linuxvars.font_part = sysshared_scratch_partition(Mbytes(8)); - } - - i32 oversample = 2; - -#if SUPPORT_DPI - pt_size = ROUND32(pt_size * size_change(linuxvars.dpi_x, linuxvars.dpi_y)); -#endif - - for(; attempts < 3; ++attempts){ -#if LINUX_FONTS - success = linux_font_load(&linuxvars.font_part, font_out, filename, pt_size, tab_width, - linuxvars.settings.use_hinting); -#else - success = font_load( - &linuxvars.font_part, - font_out, - filename, - pt_size, - tab_width, - oversample, - store_texture - ); -#endif - - if(success){ - break; - } else { - fprintf(stderr, "draw_font_load failed, %p %d\n", linuxvars.font_part.base, linuxvars.font_part.max); - sysshared_partition_double(&linuxvars.font_part); - } - } - - return success; -} - -// -// End of system funcs -// - -// -// Linux init functions -// - -internal b32 -LinuxLoadAppCode(String* base_dir){ - b32 result = 0; - App_Get_Functions *get_funcs = 0; - - if(!sysshared_to_binary_path(base_dir, "4ed_app.so")){ - return 0; - } - - linuxvars.app_code = dlopen(base_dir->str, RTLD_LAZY); - if (linuxvars.app_code){ - get_funcs = (App_Get_Functions*) - dlsym(linuxvars.app_code, "app_get_functions"); - } else { - fprintf(stderr, "dlopen failed: %s\n", dlerror()); - } - - if (get_funcs){ - result = 1; - linuxvars.app = get_funcs(); - } - - return(result); -} - -internal void -LinuxLoadSystemCode(){ - - // files - linuxvars.system.set_file_list = system_set_file_list; - linuxvars.system.get_canonical = system_get_canonical; - linuxvars.system.add_listener = system_add_listener; - linuxvars.system.remove_listener = system_remove_listener; - linuxvars.system.get_file_change = system_get_file_change; - linuxvars.system.load_handle = system_load_handle; - linuxvars.system.load_size = system_load_size; - linuxvars.system.load_file = system_load_file; - linuxvars.system.load_close = system_load_close; - linuxvars.system.save_file = system_save_file; - - // time - linuxvars.system.now_time = system_now_time; - - // 4coder_custom.h - linuxvars.system.memory_allocate = system_memory_allocate; - linuxvars.system.memory_set_protection = system_memory_set_protection; - linuxvars.system.memory_free = system_memory_free; - linuxvars.system.file_exists = system_file_exists; - linuxvars.system.directory_cd = system_directory_cd; - linuxvars.system.get_4ed_path = system_get_4ed_path; - linuxvars.system.show_mouse_cursor = system_show_mouse_cursor; - linuxvars.system.toggle_fullscreen = system_toggle_fullscreen; - linuxvars.system.is_fullscreen = system_is_fullscreen; - - // clipboard - linuxvars.system.post_clipboard = system_post_clipboard; - - // coroutine - linuxvars.system.create_coroutine = system_create_coroutine; - linuxvars.system.launch_coroutine = system_launch_coroutine; - linuxvars.system.resume_coroutine = system_resume_coroutine; - linuxvars.system.yield_coroutine = system_yield_coroutine; - - // cli - 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; - - // threads - linuxvars.system.post_job = system_post_job; - linuxvars.system.cancel_job = system_cancel_job; - linuxvars.system.check_cancel = system_check_cancel; - linuxvars.system.grow_thread_memory = system_grow_thread_memory; - linuxvars.system.acquire_lock = system_acquire_lock; - linuxvars.system.release_lock = system_release_lock; - - // debug -#if FRED_INTERNAL - linuxvars.system.internal_sentinel = internal_sentinel; - linuxvars.system.internal_get_thread_states = internal_get_thread_states; - linuxvars.system.internal_debug_message = internal_debug_message; -#endif - - // non-function details - 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_load = system_draw_font_load; - linuxvars.target.font_set.release_font = draw_release_font; -} - -// -// Renderer -// - -internal void -LinuxRedrawTarget(){ - launch_rendering(&linuxvars.target); - //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; - } -} - -// -// OpenGL init -// - -// NOTE(allen): Thanks to Casey for providing the linux OpenGL launcher. -static bool ctxErrorOccurred = false; - -internal int -ctxErrorHandler( Display *dpy, XErrorEvent *ev ) -{ - ctxErrorOccurred = true; - return 0; -} - -#if FRED_INTERNAL - -static void LinuxGLDebugCallback( - GLenum source, - GLenum type, - GLuint id, - GLenum severity, - GLsizei length, - const GLchar* message, - const void* userParam -){ - fprintf(stderr, "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*); - - typedef PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXTProc; - typedef PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESAProc; - typedef PFNGLXGETSWAPINTERVALMESAPROC glXGetSwapIntervalMESAProc; - typedef PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGIProc; - - const char *glxExts = glXQueryExtensionsString(XDisplay, DefaultScreen(XDisplay)); - - #define GLXLOAD(x) x ## Proc x = (x ## Proc) glXGetProcAddressARB( (const GLubyte*) #x); - - GLXLOAD(glXCreateContextAttribsARB); - - GLXContext ctx = 0; - ctxErrorOccurred = false; - int (*oldHandler)(Display*, XErrorEvent*) = XSetErrorHandler(&ctxErrorHandler); - - if (!glXCreateContextAttribsARB) - { - fprintf(stderr, "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 - }; - - fprintf(stderr, "Creating GL 4.3 context...\n"); - ctx = glXCreateContextAttribsARB(XDisplay, bestFbc, 0, True, context_attribs); - - XSync( XDisplay, False ); - if (!ctxErrorOccurred && ctx) - { - fprintf(stderr, "Created GL 4.3 context.\n" ); - } - else - { - ctxErrorOccurred = false; - - context_attribs[1] = 3; - context_attribs[3] = 2; - - fprintf(stderr, "GL 4.3 unavailable, creating GL 3.2 context...\n" ); - ctx = glXCreateContextAttribsARB( XDisplay, bestFbc, 0, True, context_attribs ); - - XSync(XDisplay, False); - - if (!ctxErrorOccurred && ctx) - { - fprintf(stderr, "Created GL 3.2 context.\n" ); - } - else - { - context_attribs[1] = 1; - context_attribs[3] = 2; - - ctxErrorOccurred = false; - - fprintf(stderr, "Failed to create GL 3.2 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) - { - fprintf(stderr, "Failed to create an OpenGL context\n"); - exit(1); - } - - b32 Direct; - if (!glXIsDirect(XDisplay, ctx)) - { - fprintf(stderr, "Indirect GLX rendering context obtained\n"); - Direct = 0; - } - else - { - fprintf(stderr, "Direct GLX rendering context obtained\n"); - Direct = 1; - } - - fprintf(stderr, "Making context current\n"); - glXMakeCurrent( XDisplay, XWindow, ctx ); - - char *Vendor = (char *)glGetString(GL_VENDOR); - char *Renderer = (char *)glGetString(GL_RENDERER); - char *Version = (char *)glGetString(GL_VERSION); - - //TODO(inso): glGetStringi is required in core profile if the GL version is >= 3.0 - char *Extensions = (char *)glGetString(GL_EXTENSIONS); - - fprintf(stderr, "GL_VENDOR: %s\n", Vendor); - fprintf(stderr, "GL_RENDERER: %s\n", Renderer); - fprintf(stderr, "GL_VERSION: %s\n", Version); - // fprintf(stderr, "GL_EXTENSIONS: %s\n", Extensions); - - //NOTE(inso): enable vsync if available. this should probably be optional - if(Direct && strstr(glxExts, "GLX_EXT_swap_control ")){ - - GLXLOAD(glXSwapIntervalEXT); - - if(glXSwapIntervalEXT){ - glXSwapIntervalEXT(XDisplay, XWindow, 1); - - unsigned int swap_val = 0; - glXQueryDrawable(XDisplay, XWindow, GLX_SWAP_INTERVAL_EXT, &swap_val); - - linuxvars.vsync = swap_val == 1; - fprintf(stderr, "VSync enabled? %s.\n", linuxvars.vsync ? "Yes" : "No"); - } - - } else if(Direct && strstr(glxExts, "GLX_MESA_swap_control ")){ - - GLXLOAD(glXSwapIntervalMESA); - GLXLOAD(glXGetSwapIntervalMESA); - - if(glXSwapIntervalMESA){ - glXSwapIntervalMESA(1); - - if(glXGetSwapIntervalMESA){ - linuxvars.vsync = glXGetSwapIntervalMESA(); - fprintf(stderr, "VSync enabled? %s (MESA)\n", linuxvars.vsync ? "Yes" : "No"); - } else { - // NOTE(inso): assume it worked? - linuxvars.vsync = 1; - fputs("VSync enabled? possibly (MESA)\n", stderr); - } - } - - } else if(Direct && strstr(glxExts, "GLX_SGI_swap_control ")){ - - GLXLOAD(glXSwapIntervalSGI); - - if(glXSwapIntervalSGI){ - glXSwapIntervalSGI(1); - - //NOTE(inso): The SGI one doesn't seem to have a way to confirm we got it... - linuxvars.vsync = 1; - fputs("VSync enabled? hopefully (SGI)\n", stderr); - } - - } else { - fputs("VSync enabled? nope, no suitable extension\n", stderr); - } - -#if FRED_INTERNAL - typedef PFNGLDEBUGMESSAGECALLBACKARBPROC glDebugMessageCallbackProc; - - GLXLOAD(glDebugMessageCallback); - - if(glDebugMessageCallback){ - fputs("Enabling GL Debug Callback\n", stderr); - glDebugMessageCallback(&LinuxGLDebugCallback, 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); - -#undef GLXLOAD - - return(ctx); -} - -internal b32 -GLXCanUseFBConfig(Display *XDisplay) -{ - b32 Result = false; - - int GLXMajor, GLXMinor; - - char *XVendor = ServerVendor(XDisplay); - fprintf(stderr, "XWindows vendor: %s\n", XVendor); - if(glXQueryVersion(XDisplay, &GLXMajor, &GLXMinor)) - { - fprintf(stderr, "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 = 0; - GLXFBConfig *Configs = glXChooseFBConfig(XDisplay, - XScreenIndex, - DesiredAttributes, - &ConfigCount); - if(Configs && ConfigCount > 0) - { - XVisualInfo* VI = glXGetVisualFromFBConfig(XDisplay, Configs[0]); - if(VI) - { - Result.Found = true; - Result.BestConfig = Configs[0]; - Result.BestInfo = *VI; - - int id = 0; - glXGetFBConfigAttrib(XDisplay, Result.BestConfig, GLX_FBCONFIG_ID, &id); - fprintf(stderr, "Using FBConfig: %d (0x%x)\n", id, id); - - XFree(VI); - } - - XFree(Configs); - } - - return(Result); -} - -// -// X11 input / events init -// - -struct Init_Input_Result{ - XIM input_method; - XIMStyle best_style; - XIC xic; -}; - -inline Init_Input_Result -init_input_result_zero(){ - Init_Input_Result result={0}; - return(result); -} - -internal Init_Input_Result -LinuxInputInit(Display *dpy, Window XWindow) -{ - Init_Input_Result result = {}; - XIMStyles *styles = 0; - XIMStyle style; - unsigned long xim_event_mask = 0; - i32 i; - - setlocale(LC_ALL, ""); - XSetLocaleModifiers(""); - fprintf(stderr, "Supported locale?: %s.\n", XSupportsLocale() ? "Yes" : "No"); - // TODO(inso): handle the case where it isn't supported somehow? - - 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){ - for (i = 0; i < styles->count_styles; ++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, - NULL - ); - - if (XGetICValues(result.xic, XNFilterEvents, &xim_event_mask, NULL)){ - xim_event_mask = 0; - } - } - else{ - result = init_input_result_zero(); - fputs("Could not get minimum required input style.\n", stderr); - } - } - else{ - result = init_input_result_zero(); - fputs("Could not open X Input Method.\n", stderr); - } - - XSelectInput( - linuxvars.XDisplay, - linuxvars.XWindow, - ExposureMask | - KeyPressMask | KeyReleaseMask | - ButtonPressMask | ButtonReleaseMask | - EnterWindowMask | LeaveWindowMask | - PointerMotionMask | - FocusChangeMask | - StructureNotifyMask | - MappingNotify | - ExposureMask | - VisibilityChangeMask | - xim_event_mask - ); - - return(result); -} - -// -// Keyboard handling funcs -// - -globalvar u8 keycode_lookup_table[255]; - -internal void -LinuxKeycodeInit(Display* dpy){ - - // NOTE(inso): This looks a bit dumb, but it's the best way I can think of to do it, since: - // KeySyms are the type representing "virtual" keys, like XK_BackSpace, but they are 32-bit ints. - // KeyCodes are guaranteed to fit in 1 byte (and therefore the keycode_lookup_table) but - // have dynamic numbers assigned by the XServer. - // There is XKeysymToKeycode, but it only returns 1 KeyCode for a KeySym. I have my capslock - // rebound to esc, so there are two KeyCodes for the XK_Escape KeyCode but XKeysymToKeycode only - // gets one of them, hence the need for this crazy lookup which works correctly with rebound keys. - - memset(keycode_lookup_table, 0, sizeof(keycode_lookup_table)); - - struct SymMapping { - KeySym sym; - u8 code; - } sym_table[] = { - { XK_BackSpace, key_back }, - { XK_Delete, key_del }, - { XK_Up, key_up }, - { XK_Down, key_down }, - { XK_Left, key_left }, - { XK_Right, key_right }, - { XK_Insert, key_insert }, - { XK_Home, key_home }, - { XK_End, key_end }, - { XK_Page_Up, key_page_up }, - { XK_Page_Down, key_page_down }, - { XK_Escape, key_esc }, - { XK_F1, key_f1 }, - { XK_F2, key_f2 }, - { XK_F3, key_f3 }, - { XK_F4, key_f4 }, - { XK_F5, key_f5 }, - { XK_F6, key_f6 }, - { XK_F7, key_f7 }, - { XK_F8, key_f8 }, - { XK_F9, key_f9 }, - { XK_F10, key_f10 }, - { XK_F11, key_f11 }, - { XK_F12, key_f12 }, - { XK_F13, key_f13 }, - { XK_F14, key_f14 }, - { XK_F15, key_f15 }, - { XK_F16, key_f16 }, - }; - - const int table_size = sizeof(sym_table) / sizeof(struct SymMapping); - - 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) 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 -LinuxPushKey(u8 code, u8 chr, u8 chr_nocaps, b8 (*mods)[MDFR_INDEX_COUNT], b32 is_hold) -{ - i32 *count; - Key_Event_Data *data; - - if(is_hold){ - count = &linuxvars.input.keys.hold_count; - data = linuxvars.input.keys.hold; - } else { - count = &linuxvars.input.keys.press_count; - data = linuxvars.input.keys.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); - } -} - -// -// Misc utility funcs -// - -internal Plat_Handle -LinuxSemToHandle(sem_t* sem){ - return *(Plat_Handle*)&sem; -} - -internal sem_t* -LinuxHandleToSem(Plat_Handle h){ - return *(sem_t**)&h; -} - -internal Plat_Handle -LinuxFDToHandle(int fd){ - return *(Plat_Handle*)&fd; -} - -internal int -LinuxHandleToFD(Plat_Handle h){ - return *(int*)&h; -} - -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); -} - -internal void -LinuxScheduleStep(void) -{ - u64 now = system_now_time(); - u64 diff = (now - linuxvars.last_step); - - if(diff > (u64)frame_useconds){ - u64 ev = 1; - write(linuxvars.step_event_fd, &ev, sizeof(ev)); - } else { - struct itimerspec its = {}; - timerfd_gettime(linuxvars.step_timer_fd, &its); - - if(its.it_value.tv_sec == 0 && its.it_value.tv_nsec == 0){ - its.it_value.tv_nsec = (frame_useconds - diff) * 1000UL; - timerfd_settime(linuxvars.step_timer_fd, 0, &its, NULL); - } - } -} - -// -// X11 utility funcs -// - -internal void -LinuxSetWMState(Display* d, Window w, Atom one, Atom two, int mode) -{ - //NOTE(inso): this will only work after it is mapped - - enum { STATE_REMOVE, STATE_ADD, STATE_TOGGLE }; - - XEvent e = {}; - - e.xany.type = ClientMessage; - e.xclient.message_type = linuxvars.atom__NET_WM_STATE; - e.xclient.format = 32; - e.xclient.window = w; - 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( - d, - RootWindow(d, 0), - 0, - SubstructureNotifyMask | SubstructureRedirectMask, - &e - ); -} - -internal void -LinuxMaximizeWindow(Display* d, Window w, b32 maximize) -{ - LinuxSetWMState(d, - w, - linuxvars.atom__NET_WM_STATE_MAXIMIZED_HORZ, - linuxvars.atom__NET_WM_STATE_MAXIMIZED_VERT, - maximize != 0); -} - -internal void -LinuxToggleFullscreen(Display* d, Window w) -{ - LinuxSetWMState(d, w, linuxvars.atom__NET_WM_STATE_FULLSCREEN, 0, 2); -} - -#include "linux_icon.h" -internal void -LinuxSetIcon(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) - ); -} - -internal void -LinuxX11ConnectionWatch(Display* dpy, XPointer cdata, int fd, Bool opening, XPointer* wdata) -{ - struct epoll_event e = {}; - e.events = EPOLLIN | EPOLLET; - e.data.u64 = LINUX_4ED_EVENT_X11_INTERNAL | fd; - - int op = opening ? EPOLL_CTL_ADD : EPOLL_CTL_DEL; - epoll_ctl(linuxvars.epoll, op, fd, &e); -} - -// NOTE(inso): this was a quick hack, might need some cleanup. -internal void -LinuxFatalErrorMsg(const char* msg) -{ - fprintf(stderr, "Fatal Error: %s\n", msg); - - Display *dpy = XOpenDisplay(0); - if(!dpy){ - exit(1); - } - - int win_w = 450; - int win_h = 150 + (strlen(msg) / 40) * 24; - - Window w = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 0, 0, win_w, win_h, 0, 0, 0x2EA44F); - XStoreName(dpy, w, "4coder Error"); - - XSizeHints* sh = XAllocSizeHints(); - sh->flags = PMinSize; - sh->min_width = win_w; - sh->min_height = win_h; - XSetWMNormalHints(dpy, w, sh); - - Atom type = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False); - - XChangeProperty(dpy, w, - XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False), - XA_ATOM, - 32, - PropModeReplace, - (unsigned char*) &type, - 1); - - Atom WM_DELETE_WINDOW = XInternAtom(dpy, "WM_DELETE_WINDOW", False); - XSetWMProtocols(dpy, w, &WM_DELETE_WINDOW, 1); - - XMapRaised(dpy, w); - XSync(dpy, False); - - XSelectInput(dpy, w, ExposureMask | StructureNotifyMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask); - - XFontStruct* font = XLoadQueryFont(dpy, "-*-fixed-*-*-*-*-*-140-*-*-*-*-iso8859-1"); - if(!font){ - exit(1); - } - - XGCValues gcv; - gcv.foreground = WhitePixel(dpy, 0); - gcv.background = 0x2EA44F; - gcv.line_width = 2; - gcv.font = font->fid; - - GC gc = XCreateGC(dpy, w, GCForeground | GCBackground | GCFont | GCLineWidth, &gcv); - - gcv.foreground = BlackPixel(dpy, 0); - GC gc2 = XCreateGC(dpy, w, GCForeground | GCBackground | GCFont | GCLineWidth, &gcv); - - int button_trigger = 0; - int button_hi = 0; - - XEvent ev; - while(1){ - XNextEvent(dpy, &ev); - - int redraw = 0; - - if(ev.type == Expose) redraw = 1; - - if(ev.type == ConfigureNotify){ - redraw = 1; - - win_w = ev.xconfigure.width; - win_h = ev.xconfigure.height; - } - - XRectangle button_rect = { win_w/2-40, win_h*0.8f, 80, 20 }; - - if(ev.type == MotionNotify){ - int new_hi = (ev.xmotion.x > button_rect.x && - ev.xmotion.y > button_rect.y && - ev.xmotion.x < button_rect.x + button_rect.width && - ev.xmotion.y < button_rect.y + button_rect.height); - - if(new_hi != button_hi){ - button_hi = new_hi; - redraw = 1; - } - } - - if(ev.type == ButtonPress && ev.xbutton.button == Button1){ - if(button_hi) button_trigger = 1; - redraw = 1; - } - - if(ev.type == ButtonRelease && ev.xbutton.button == Button1){ - if(button_trigger){ - if(button_hi){ - exit(1); - } else { - button_trigger = 0; - } - } - redraw = 1; - } - - if(ev.type == ClientMessage && ev.xclient.window == w && (Atom)ev.xclient.data.l[0] == WM_DELETE_WINDOW){ - exit(1); - } - - if(redraw){ - XClearWindow(dpy, w); - - const char* line_start = msg; - const char* last_space = NULL; - int y = 30; - - { - const char title[] = "4coder - Fatal Error"; - int width = XTextWidth(font, title, sizeof(title)-1); - int x = (win_w/2) - (width/2); - XDrawString(dpy, w, gc2, x+2, y+2, title, sizeof(title)-1); - XDrawString(dpy, w, gc, x, y, title, sizeof(title)-1); - } - - y += 36; - - int width = XTextWidth(font, "x", 1) * 40; - int x = (win_w/2) - (width/2); - for(const char* p = line_start; *p; ++p){ - if(*p == ' ') last_space = p; - if(p - line_start > 40){ - if(!last_space) last_space = p; - - XDrawString(dpy, w, gc2, x+2, y+2, line_start, last_space - line_start); - XDrawString(dpy, w, gc, x, y, line_start, last_space - line_start); - line_start = *last_space == ' ' ? last_space + 1 : p; - last_space = NULL; - y += 18; - } - } - - XDrawString(dpy, w, gc2, x+2, y+2, line_start, strlen(line_start)); - XDrawString(dpy, w, gc, x, y, line_start, strlen(line_start)); - - XDrawRectangles(dpy, w, gc, &button_rect, 1); - if(button_hi || button_trigger){ - XDrawRectangle(dpy, w, gc2, button_rect.x+1, button_rect.y+1, button_rect.width-2, button_rect.height-2); - } - XDrawString(dpy, w, gc2, button_rect.x + 22, button_rect.y + 17, "Drat!", 5); - XDrawString(dpy, w, gc, button_rect.x + 20, button_rect.y + 15, "Drat!", 5); - } - } -} - -internal int -LinuxGetXSettingsDPI(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){ - fputs("XSETTINGS unavailable.\n", stderr); - 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){ - fputs("XSETTINGS: GetWindowProperty failed.\n", stderr); - goto out; - } - - if(fmt != 8){ - fputs("XSETTINGS: Wrong format.\n", stderr); - goto out; - } - } - - xs = (struct XSettings*)prop; - p = (char*)(xs + 1); - - if(xs->byte_order != 0){ - fputs("FIXME: XSETTINGS not host byte order?\n", stderr); - goto out; - } - - for(int i = 0; i < xs->num_settings; ++i){ - struct XSettingHeader* h = (struct XSettingHeader*)p; - - // const char* strs[] = { "Int", "String", "Color" }; - // printf("%s:\t\"%.*s\"\n", strs[h->type], h->name_len, h->name); - - 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: { - fputs("XSETTINGS: Got invalid type...\n", stderr); - goto out; - } break; - } - } - -out: - if(prop){ - XFree(prop); - } - - return dpi; -} - -// -// X11 window init -// - -internal b32 -LinuxX11WindowInit(int argc, char** argv, int* WinWidth, int* WinHeight) -{ - // 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) - -#define BASE_W 800 -#define BASE_H 600 - - if (linuxvars.settings.set_window_size){ - *WinWidth = linuxvars.settings.window_w; - *WinHeight = linuxvars.settings.window_h; - } else { - *WinWidth = BASE_W * size_change(linuxvars.dpi_x, linuxvars.dpi_y); - *WinHeight = BASE_H * size_change(linuxvars.dpi_x, linuxvars.dpi_y); - } - - if (!GLXCanUseFBConfig(linuxvars.XDisplay)){ - LinuxFatalErrorMsg("Your XServer's GLX version is too old. GLX 1.3+ is required."); - return false; - } - - glx_config_result Config = ChooseGLXConfig(linuxvars.XDisplay, DefaultScreen(linuxvars.XDisplay)); - if (!Config.Found){ - LinuxFatalErrorMsg("Could not get a matching GLX FBConfig. Check your OpenGL drivers are installed correctly."); - return false; - } - - XSetWindowAttributes swa = {}; - swa.backing_store = WhenMapped; - swa.event_mask = StructureNotifyMask; - swa.bit_gravity = NorthWestGravity; - swa.colormap = XCreateColormap(linuxvars.XDisplay, - RootWindow(linuxvars.XDisplay, Config.BestInfo.screen), - Config.BestInfo.visual, AllocNone); - - linuxvars.XWindow = - XCreateWindow(linuxvars.XDisplay, - RootWindow(linuxvars.XDisplay, Config.BestInfo.screen), - 0, 0, *WinWidth, *WinHeight, - 0, Config.BestInfo.depth, InputOutput, - Config.BestInfo.visual, - CWBackingStore|CWBitGravity|CWBackPixel|CWBorderPixel|CWColormap|CWEventMask, &swa); - - if (!linuxvars.XWindow){ - LinuxFatalErrorMsg("XCreateWindow failed. Make sure your display is set up correctly."); - return false; - } - - //NOTE(inso): Set the window's type to normal - XChangeProperty( - linuxvars.XDisplay, - linuxvars.XWindow, - 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.XDisplay, - linuxvars.XWindow, - linuxvars.atom__NET_WM_PID, - XA_CARDINAL, - 32, - PropModeReplace, - (unsigned char*)&pid, - 1 - ); - -#define WINDOW_NAME "4coder 4linux: " VERSION - - //NOTE(inso): set wm properties - XStoreName(linuxvars.XDisplay, linuxvars.XWindow, 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); - -/* NOTE(inso): fluxbox thinks this is minimum, so don't set it - sz_hints->base_width = BASE_W; - sz_hints->base_height = BASE_H; -*/ - sz_hints->win_gravity = NorthWestGravity; - - if (linuxvars.settings.set_window_pos){ - sz_hints->flags |= USPosition; - sz_hints->x = linuxvars.settings.window_x; - sz_hints->y = linuxvars.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.XDisplay, - linuxvars.XWindow, - &win_name, NULL, - argv, argc, - sz_hints, wm_hints, cl_hints - ); - - XFree(win_name.value); - - XFree(sz_hints); - XFree(wm_hints); - XFree(cl_hints); - - LinuxSetIcon(linuxvars.XDisplay, linuxvars.XWindow); - - //NOTE(inso): make the window visible - XMapWindow(linuxvars.XDisplay, linuxvars.XWindow); - - b32 IsLegacy = false; - GLXContext GLContext = - InitializeOpenGLContext(linuxvars.XDisplay, linuxvars.XWindow, Config.BestConfig, IsLegacy); - - XRaiseWindow(linuxvars.XDisplay, linuxvars.XWindow); - - if (linuxvars.settings.set_window_pos){ - XMoveWindow( - linuxvars.XDisplay, - linuxvars.XWindow, - linuxvars.settings.window_x, - linuxvars.settings.window_y - ); - } - - if (linuxvars.settings.maximize_window){ - LinuxMaximizeWindow(linuxvars.XDisplay, linuxvars.XWindow, 1); - } - else if(linuxvars.settings.fullscreen_window){ - LinuxToggleFullscreen(linuxvars.XDisplay, linuxvars.XWindow); - } - - XSync(linuxvars.XDisplay, False); - - XWindowAttributes WinAttribs; - if (XGetWindowAttributes(linuxvars.XDisplay, linuxvars.XWindow, &WinAttribs)) - { - *WinWidth = WinAttribs.width; - *WinHeight = WinAttribs.height; - } - - Atom wm_protos[] = { - linuxvars.atom_WM_DELETE_WINDOW, - linuxvars.atom__NET_WM_PING - }; - - XSetWMProtocols(linuxvars.XDisplay, linuxvars.XWindow, wm_protos, 2); -} - -internal void -LinuxHandleX11Events(void) -{ - static XEvent PrevEvent = {}; - b32 should_step = 0; - - while(XPending(linuxvars.XDisplay)) - { - XEvent Event; - XNextEvent(linuxvars.XDisplay, &Event); - - if (XFilterEvent(&Event, None) == True){ - continue; - } - - switch (Event.type){ - case KeyPress: { - should_step = 1; - - b32 is_hold = (PrevEvent.type == KeyRelease && - PrevEvent.xkey.time == Event.xkey.time && - PrevEvent.xkey.keycode == Event.xkey.keycode); - - b8 mods[MDFR_INDEX_COUNT] = {}; - mods[MDFR_HOLD_INDEX] = is_hold; - - if(Event.xkey.state & ShiftMask) mods[MDFR_SHIFT_INDEX] = 1; - if(Event.xkey.state & ControlMask) mods[MDFR_CONTROL_INDEX] = 1; - if(Event.xkey.state & LockMask) mods[MDFR_CAPS_INDEX] = 1; - if(Event.xkey.state & Mod1Mask) mods[MDFR_ALT_INDEX] = 1; - - Event.xkey.state &= ~(ControlMask); - - Status status; - KeySym keysym = NoSymbol; - char buff[32] = {}; - - Xutf8LookupString( - linuxvars.input_context, - &Event.xkey, - buff, - sizeof(buff) - 1, - &keysym, - &status - ); - - if(status == XBufferOverflow){ - //TODO(inso): handle properly - Xutf8ResetIC(linuxvars.input_context); - XSetICFocus(linuxvars.input_context); - fputs("FIXME: XBufferOverflow from LookupString.\n", stderr); - } - - u8 key = *buff, key_no_caps = key; - - if(mods[MDFR_CAPS_INDEX] && status == XLookupBoth && Event.xkey.keycode){ - char buff_no_caps[32] = {}; - Event.xkey.state &= ~(LockMask); - - XLookupString( - &Event.xkey, - buff_no_caps, - sizeof(buff_no_caps) - 1, - NULL, - NULL - ); - - if(*buff_no_caps){ - key_no_caps = *buff_no_caps; - } - } - - if(key == '\r') key = '\n'; - if(key_no_caps == '\r') key_no_caps = '\n'; - - // don't push modifiers - if(keysym >= XK_Shift_L && keysym <= XK_Hyper_R){ - break; - } - - if(keysym == XK_ISO_Left_Tab){ - key = key_no_caps = '\t'; - mods[MDFR_SHIFT_INDEX] = 1; - } - - u8 special_key = keycode_lookup_table[(u8)Event.xkey.keycode]; - - if(special_key){ - LinuxPushKey(special_key, 0, 0, &mods, is_hold); - } else if(key < 128){ - LinuxPushKey(key, key, key_no_caps, &mods, is_hold); - } else { - LinuxPushKey(0, 0, 0, &mods, is_hold); - } - }break; - - case KeyRelease: { - should_step = 1; - }break; - - case MotionNotify: { - should_step = 1; - linuxvars.input.mouse.x = Event.xmotion.x; - linuxvars.input.mouse.y = Event.xmotion.y; - }break; - - case ButtonPress: { - should_step = 1; - switch(Event.xbutton.button){ - case Button1: { - linuxvars.input.mouse.press_l = 1; - linuxvars.input.mouse.l = 1; - } break; - case Button3: { - linuxvars.input.mouse.press_r = 1; - linuxvars.input.mouse.r = 1; - } break; - - //NOTE(inso): scroll up - case Button4: { - linuxvars.input.mouse.wheel = 1; - }break; - - //NOTE(inso): scroll down - case Button5: { - linuxvars.input.mouse.wheel = -1; - }break; - } - }break; - - case ButtonRelease: { - should_step = 1; - switch(Event.xbutton.button){ - case Button1: { - linuxvars.input.mouse.release_l = 1; - linuxvars.input.mouse.l = 0; - } break; - case Button3: { - linuxvars.input.mouse.release_r = 1; - linuxvars.input.mouse.r = 0; - } break; - } - }break; - - case EnterNotify: { - should_step = 1; - linuxvars.input.mouse.out_of_window = 0; - }break; - - case LeaveNotify: { - should_step = 1; - linuxvars.input.mouse.out_of_window = 1; - }break; - - case FocusIn: - case FocusOut: { - should_step = 1; - linuxvars.input.mouse.l = 0; - linuxvars.input.mouse.r = 0; - }break; - - case ConfigureNotify: { - should_step = 1; - 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); - LinuxKeycodeInit(linuxvars.XDisplay); - } - }break; - - case ClientMessage: { - if ((Atom)Event.xclient.data.l[0] == linuxvars.atom_WM_DELETE_WINDOW) { - should_step = 1; - linuxvars.keep_running = 0; - } - else if ((Atom)Event.xclient.data.l[0] == linuxvars.atom__NET_WM_PING) { - Event.xclient.window = DefaultRootWindow(linuxvars.XDisplay); - XSendEvent( - linuxvars.XDisplay, - Event.xclient.window, - False, - SubstructureRedirectMask | SubstructureNotifyMask, - &Event - ); - } - }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; - - if ( - linuxvars.clipboard_outgoing.size && - request.selection == linuxvars.atom_CLIPBOARD && - request.property != None && - request.display && - request.requestor - ){ - Atom atoms[] = { - XA_STRING, - linuxvars.atom_UTF8_STRING - }; - - if(request.target == linuxvars.atom_TARGETS){ - - XChangeProperty( - request.display, - request.requestor, - request.property, - XA_ATOM, - 32, - PropModeReplace, - (u8*)atoms, - ArrayCount(atoms) - ); - - response.property = request.property; - - } else { - b32 found = false; - for(int i = 0; i < ArrayCount(atoms); ++i){ - if(request.target == atoms[i]){ - found = true; - break; - } - } - - if(found){ - XChangeProperty( - request.display, - request.requestor, - request.property, - request.target, - 8, - PropModeReplace, - (u8*)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); - should_step = 1; - linuxvars.new_clipboard = 1; - XFree(data); - XDeleteProperty(linuxvars.XDisplay, linuxvars.XWindow, linuxvars.atom_CLIPBOARD); - } - } - }break; - - case Expose: - case VisibilityNotify: { - should_step = 1; - }break; - - default: { - if(Event.type == linuxvars.xfixes_selection_event){ - XFixesSelectionNotifyEvent* sne = (XFixesSelectionNotifyEvent*)&Event; - if(sne->subtype == XFixesSelectionNotify && sne->owner != linuxvars.XWindow){ - XConvertSelection( - linuxvars.XDisplay, - linuxvars.atom_CLIPBOARD, - linuxvars.atom_UTF8_STRING, - linuxvars.atom_CLIPBOARD, - linuxvars.XWindow, - CurrentTime - ); - } - } - }break; - } - - PrevEvent = Event; - } - - if(should_step){ - LinuxScheduleStep(); - } -} - -// -// Entry point -// - -int -main(int argc, char **argv) -{ - // - // System & Memory init - // - -#if FRED_INTERNAL - linuxvars.internal_bubble.next = &linuxvars.internal_bubble; - linuxvars.internal_bubble.prev = &linuxvars.internal_bubble; - linuxvars.internal_bubble.flags = 0; - - pthread_mutex_init(&linuxvars.DEBUG_sysmem_lock, 0); -#endif - - char base_dir_mem[PATH_MAX]; - String base_dir = make_fixed_width_string(base_dir_mem); - - if (!LinuxLoadAppCode(&base_dir)){ - LinuxFatalErrorMsg("Could not load '4ed_app.so'. This file should be in the same directory as the main '4ed' executable."); - return 99; - } - - LinuxLoadSystemCode(); - LinuxLoadRenderCode(); - - 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); - memory_vars.user_memory_size = Mbytes(2); - memory_vars.user_memory = system_get_memory(memory_vars.user_memory_size); - - linuxvars.target.max = Mbytes(1); - linuxvars.target.push_buffer = (char*)system_get_memory(linuxvars.target.max); - - if(memory_vars.vars_memory == NULL || memory_vars.target_memory == NULL || memory_vars.user_memory == NULL || linuxvars.target.push_buffer == NULL){ - LinuxFatalErrorMsg("Could not allocate sufficient memory. Please make sure you have atleast 512Mb of RAM free. (This requirement will be relaxed in the future)."); - exit(1); - } - - init_shared_vars(); - - // - // Read command line - // - - char* cwd = get_current_dir_name(); - if(!cwd){ - char buf[1024]; - snprintf(buf, sizeof(buf), "Call to get_current_dir_name failed: %s", strerror(errno)); - LinuxFatalErrorMsg(buf); - return 1; - } - - String current_directory = make_string_slowly(cwd); - - 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(&linuxvars.system, - &memory_vars, - current_directory, - &linuxvars.settings, - &files, &file_count, - clparams); - - if (output_size > 0){ - // TODO(allen): crt free version - fprintf(stdout, "%.*s", output_size, (char*)memory_vars.target_memory); - } - if (output_size != 0){ - LinuxFatalErrorMsg("Error reading command-line arguments."); - return 1; - } - - sysshared_filter_real_files(files, file_count); - - // - // Custom layer linkage - // - -#ifdef FRED_SUPER - - char *custom_file_default = "4coder_custom.so"; - sysshared_to_binary_path(&base_dir, custom_file_default); - custom_file_default = base_dir.str; - - 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_alpha_4coder_version = (_Get_Version_Function*) - dlsym(linuxvars.custom, "get_alpha_4coder_version"); - - if (linuxvars.custom_api.get_alpha_4coder_version == 0 || - linuxvars.custom_api.get_alpha_4coder_version(MAJOR, MINOR, PATCH) == 0){ - LinuxFatalErrorMsg("Failed to load '4coder_custom.so': Version mismatch. Try rebuilding it with 'buildsuper.sh'."); - exit(1); - } - else{ - linuxvars.custom_api.get_bindings = (Get_Binding_Data_Function*) - dlsym(linuxvars.custom, "get_bindings"); - linuxvars.custom_api.view_routine = (View_Routine_Function*) - dlsym(linuxvars.custom, "view_routine"); - - if (linuxvars.custom_api.get_bindings == 0){ - LinuxFatalErrorMsg("Failed to load '4coder_custom.so': " - "It does not export the required 'get_bindings' function. " - "Try rebuilding it with 'buildsuper.sh'."); - exit(1); - } - else{ - fprintf(stderr, "Successfully loaded 4coder_custom.so\n"); - } - } - } else { - char buf[4096]; - const char* error = dlerror(); - snprintf(buf, sizeof(buf), "Error loading custom: %s. " - "Make sure this file is in the same directory as the main '4ed' executable.", - error ? error : "'4coder_custom.so' missing"); - LinuxFatalErrorMsg(buf); - exit(1); - } -#else - linuxvars.custom_api.get_bindings = get_bindings; -#endif - - linuxvars.custom_api.view_routine = 0; - -#if 0 - if (linuxvars.custom_api.view_routine == 0){ - linuxvars.custom_api.view_routine = view_routine; - } -#endif - - // - // Coroutine / Thread / Semaphore / Mutex init - // - - linuxvars.coroutine_free = linuxvars.coroutine_data; - for (i32 i = 0; i+1 < ArrayCount(linuxvars.coroutine_data); ++i){ - linuxvars.coroutine_data[i].next = linuxvars.coroutine_data + i + 1; - } - - const size_t stack_size = Mbytes(2); - for (i32 i = 0; i < ArrayCount(linuxvars.coroutine_data); ++i){ - linuxvars.coroutine_data[i].stack.ss_size = stack_size; - linuxvars.coroutine_data[i].stack.ss_sp = system_get_memory(stack_size); - } - - Thread_Context background[4] = {}; - linuxvars.groups[BACKGROUND_THREADS].threads = background; - linuxvars.groups[BACKGROUND_THREADS].count = ArrayCount(background); - linuxvars.groups[BACKGROUND_THREADS].cancel_lock0 = CANCEL_LOCK0; - linuxvars.groups[BACKGROUND_THREADS].cancel_cv0 = 0; - - Thread_Memory thread_memory[ArrayCount(background)]; - linuxvars.thread_memory = thread_memory; - - sem_init(&linuxvars.thread_semaphore, 0, 0); - linuxvars.queues[BACKGROUND_THREADS].semaphore = LinuxSemToHandle(&linuxvars.thread_semaphore); - - for(i32 i = 0; i < linuxvars.groups[BACKGROUND_THREADS].count; ++i){ - Thread_Context *thread = linuxvars.groups[BACKGROUND_THREADS].threads + i; - thread->id = i + 1; - thread->group_id = BACKGROUND_THREADS; - - Thread_Memory *memory = linuxvars.thread_memory + i; - *memory = thread_memory_zero(); - memory->id = thread->id; - - thread->queue = &linuxvars.queues[BACKGROUND_THREADS]; - pthread_create(&thread->handle, NULL, &JobThreadProc, thread); - } - - initialize_unbounded_queue(&linuxvars.groups[BACKGROUND_THREADS].queue); - - for(i32 i = 0; i < LOCK_COUNT; ++i){ - pthread_mutex_init(linuxvars.locks + i, NULL); - } - - for(i32 i = 0; i < ArrayCount(linuxvars.conds); ++i){ - pthread_cond_init(linuxvars.conds + i, NULL); - } - - // - // X11 init - // - - linuxvars.XDisplay = XOpenDisplay(0); - if(!linuxvars.XDisplay){ - // NOTE(inso): probably not worth trying the popup in this case... - fprintf(stderr, "Can't open display!\n"); - return 1; - } - -#define LOAD_ATOM(x) linuxvars.atom_##x = XInternAtom(linuxvars.XDisplay, #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 SUPPORT_DPI - linuxvars.dpi_x = linuxvars.dpi_y = LinuxGetXSettingsDPI(linuxvars.XDisplay, DefaultScreen(linuxvars.XDisplay)); - - // fallback - if(linuxvars.dpi_x == -1){ - int scr = DefaultScreen(linuxvars.XDisplay); - - int dw = DisplayWidth(linuxvars.XDisplay, scr); - int dh = DisplayHeight(linuxvars.XDisplay, scr); - - int dw_mm = DisplayWidthMM(linuxvars.XDisplay, scr); - int dh_mm = DisplayHeightMM(linuxvars.XDisplay, scr); - - linuxvars.dpi_x = dw_mm ? dw / (dw_mm / 25.4) : 96; - linuxvars.dpi_y = dh_mm ? dh / (dh_mm / 25.4) : 96; - - fprintf(stderr, "%dx%d - %dmmx%dmm DPI: %dx%d\n", dw, dh, dw_mm, dh_mm, linuxvars.dpi_x, linuxvars.dpi_y); - } else { - fprintf(stderr, "DPI from XSETTINGS: %d\n", linuxvars.dpi_x); - } -#endif - - int WinWidth, WinHeight; - if(!LinuxX11WindowInit(argc, argv, &WinWidth, &WinHeight)){ - return 1; - } - - int xfixes_version_unused, xfixes_err_unused; - linuxvars.has_xfixes = XQueryExtension( - linuxvars.XDisplay, - "XFIXES", - &xfixes_version_unused, - &linuxvars.xfixes_selection_event, - &xfixes_err_unused - ) == True; - - if(linuxvars.has_xfixes){ - XFixesSelectSelectionInput( - linuxvars.XDisplay, - linuxvars.XWindow, - linuxvars.atom_CLIPBOARD, - XFixesSetSelectionOwnerNotifyMask - ); - } else { - fputs("Your X server doesn't support XFIXES, mention this fact if you report any clipboard-related issues.\n", stderr); - } - - Init_Input_Result input_result = - LinuxInputInit(linuxvars.XDisplay, linuxvars.XWindow); - - linuxvars.input_method = input_result.input_method; - linuxvars.input_style = input_result.best_style; - linuxvars.input_context = input_result.xic; - - LinuxKeycodeInit(linuxvars.XDisplay); - - Cursor xcursors[APP_MOUSE_CURSOR_COUNT] = { - None, - XCreateFontCursor(linuxvars.XDisplay, XC_arrow), - XCreateFontCursor(linuxvars.XDisplay, XC_xterm), - XCreateFontCursor(linuxvars.XDisplay, XC_sb_h_double_arrow), - XCreateFontCursor(linuxvars.XDisplay, XC_sb_v_double_arrow) - }; - - { - char data = 0; - XColor c = {}; - Pixmap p = XCreateBitmapFromData(linuxvars.XDisplay, linuxvars.XWindow, &data, 1, 1); - - linuxvars.hidden_cursor = XCreatePixmapCursor(linuxvars.XDisplay, p, p, &c, &c, 0, 0); - - XFreePixmap(linuxvars.XDisplay, p); - } - - - // - // Epoll init - // - - linuxvars.x11_fd = ConnectionNumber(linuxvars.XDisplay); - linuxvars.inotify_fd = inotify_init1(IN_NONBLOCK); - linuxvars.step_event_fd = eventfd(0, EFD_NONBLOCK); - linuxvars.step_timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); - - linuxvars.epoll = epoll_create(16); - - { - struct epoll_event e = {}; - e.events = EPOLLIN | EPOLLET; - - e.data.u64 = LINUX_4ED_EVENT_X11; - epoll_ctl(linuxvars.epoll, EPOLL_CTL_ADD, linuxvars.x11_fd, &e); - - e.data.u64 = LINUX_4ED_EVENT_STEP; - epoll_ctl(linuxvars.epoll, EPOLL_CTL_ADD, linuxvars.step_event_fd, &e); - - e.data.u64 = LINUX_4ED_EVENT_STEP_TIMER; - epoll_ctl(linuxvars.epoll, EPOLL_CTL_ADD, linuxvars.step_timer_fd, &e); - } - - // - // App init - // - - XAddConnectionWatch(linuxvars.XDisplay, &LinuxX11ConnectionWatch, NULL); - - linuxvars.app.init(&linuxvars.system, - &linuxvars.target, - &memory_vars, - linuxvars.clipboard_contents, - current_directory, - linuxvars.custom_api); - - LinuxResizeTarget(WinWidth, WinHeight); - - // - // Main loop - // - - system_acquire_lock(FRAME_LOCK); - - LinuxScheduleStep(); - - linuxvars.keep_running = 1; - linuxvars.input.first_step = 1; - linuxvars.input.dt = (frame_useconds / 1000000.f); - - while(1){ - - if(XEventsQueued(linuxvars.XDisplay, QueuedAlready)){ - LinuxHandleX11Events(); - } - - system_release_lock(FRAME_LOCK); - - struct epoll_event events[16]; - int num_events = epoll_wait(linuxvars.epoll, events, ArrayCount(events), -1); - - system_acquire_lock(FRAME_LOCK); - - if(num_events == -1){ - if(errno != EINTR){ - perror("epoll_wait"); - } - continue; - } - - b32 do_step = 0; - - for(int i = 0; i < num_events; ++i){ - - int fd = events[i].data.u64 & UINT32_MAX; - u64 type = events[i].data.u64 & ~fd; - - switch(type){ - case LINUX_4ED_EVENT_X11: { - LinuxHandleX11Events(); - } break; - - case LINUX_4ED_EVENT_X11_INTERNAL: { - XProcessInternalConnection(linuxvars.XDisplay, fd); - } break; - - case LINUX_4ED_EVENT_STEP: { - u64 ev; - int ret; - do { - ret = read(linuxvars.step_event_fd, &ev, 8); - } while(ret != -1 || errno != EAGAIN); - do_step = 1; - } break; - - case LINUX_4ED_EVENT_STEP_TIMER: { - u64 count; - int ret; - do { - ret = read(linuxvars.step_timer_fd, &count, 8); - } while(ret != -1 || errno != EAGAIN); - do_step = 1; - } break; - - case LINUX_4ED_EVENT_CLI: { - LinuxScheduleStep(); - } break; - } - } - - if(do_step){ - linuxvars.last_step = system_now_time(); - - if(linuxvars.input.first_step || !linuxvars.has_xfixes){ - XConvertSelection( - linuxvars.XDisplay, - linuxvars.atom_CLIPBOARD, - linuxvars.atom_UTF8_STRING, - linuxvars.atom_CLIPBOARD, - linuxvars.XWindow, - CurrentTime - ); - } - - Application_Step_Result result = {}; - result.mouse_cursor_type = APP_MOUSE_CURSOR_DEFAULT; - result.trying_to_kill = !linuxvars.keep_running; - - if(linuxvars.new_clipboard){ - linuxvars.input.clipboard = linuxvars.clipboard_contents; - linuxvars.new_clipboard = 0; - } else { - linuxvars.input.clipboard = string_zero(); - } - - linuxvars.app.step( - &linuxvars.system, - &linuxvars.target, - &memory_vars, - &linuxvars.input, - &result - ); - - if(result.perform_kill){ - break; - } else { - linuxvars.keep_running = 1; - } - - if(result.animating){ - LinuxScheduleStep(); - } - - LinuxRedrawTarget(); - - if(result.mouse_cursor_type != linuxvars.cursor && !linuxvars.input.mouse.l){ - Cursor c = xcursors[result.mouse_cursor_type]; - if(!linuxvars.hide_cursor){ - XDefineCursor(linuxvars.XDisplay, linuxvars.XWindow, c); - } - linuxvars.cursor = result.mouse_cursor_type; - } - - flush_thread_group(BACKGROUND_THREADS); - - linuxvars.input.first_step = 0; - linuxvars.input.keys = key_input_data_zero(); - linuxvars.input.mouse.press_l = 0; - linuxvars.input.mouse.release_l = 0; - linuxvars.input.mouse.press_r = 0; - linuxvars.input.mouse.release_r = 0; - linuxvars.input.mouse.wheel = 0; - } - } - - return 0; -} - -// BOTTOM -// vim: expandtab:ts=4:sts=4:sw=4 - +/* + * Mr. 4th Dimention - Allen Webster + * (Mostly by insofaras) + * + * 14.11.2015 + * + * Linux layer for project codename "4ed" + * + */ + +// TOP + +# include "4ed_defines.h" + +#if FRED_SUPER + +# define FSTRING_IMPLEMENTATION +# define FSTRING_C +# include "4coder_string.h" + +#include "4coder_version.h" +# include "4coder_keycodes.h" +# include "4coder_style.h" +# include "4coder_rect.h" + +# include + +# include "4coder_mem.h" + +// TODO(allen): This is duplicated from 4coder_custom.h +// I need to work out a way to avoid this. +#define VIEW_ROUTINE_SIG(name) void name(struct Application_Links *app, int32_t view_id) +#define GET_BINDING_DATA(name) int32_t name(void *data, int32_t size) +#define _GET_VERSION_SIG(n) int32_t n(int32_t maj, int32_t min, int32_t patch) + +typedef VIEW_ROUTINE_SIG(View_Routine_Function); +typedef GET_BINDING_DATA(Get_Binding_Data_Function); +typedef _GET_VERSION_SIG(_Get_Version_Function); + +struct Custom_API{ + View_Routine_Function *view_routine; + Get_Binding_Data_Function *get_bindings; + _Get_Version_Function *get_alpha_4coder_version; +}; + + +typedef void Custom_Command_Function; +#include "4coder_types.h" +struct Application_Links; +# include "4ed_os_custom_api.h" + +//# include "4coder_custom.h" +#else +# include "4coder_default_bindings.cpp" + +# define FSTRING_IMPLEMENTATION +# define FSTRING_C +# include "4coder_string.h" + +#endif + +#include "4ed_math.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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + + +// +// Linux macros +// + +#define LINUX_MAX_PASTE_CHARS 0x10000L +#define FPS 60L +#define frame_useconds (1000000UL / FPS) + +#define LinuxGetMemory(size) LinuxGetMemory_(size, __LINE__, __FILE__) + +#if FRED_INTERNAL + #define LINUX_FN_DEBUG(fmt, ...) do { \ + fprintf(stderr, "%s: " fmt "\n", __func__, ##__VA_ARGS__); \ + } while(0) +#else + #define LINUX_FN_DEBUG(fmt, ...) +#endif + +#if (__cplusplus <= 199711L) + #define static_assert(x, ...) +#endif + +#define SUPPORT_DPI 1 +#define LINUX_FONTS 1 + +#define InterlockedCompareExchange(dest, ex, comp) __sync_val_compare_and_swap((dest), (comp), (ex)) + +#include "filetrack/4tech_file_track_linux.c" +#include "system_shared.h" + +// +// Linux structs / enums +// + +#if FRED_INTERNAL + +struct Sys_Bubble : public Bubble{ + i32 line_number; + char *file_name; +}; + +#endif + +enum { + LINUX_4ED_EVENT_X11 = (UINT64_C(1) << 32), + LINUX_4ED_EVENT_X11_INTERNAL = (UINT64_C(2) << 32), + LINUX_4ED_EVENT_STEP = (UINT64_C(3) << 32), + LINUX_4ED_EVENT_STEP_TIMER = (UINT64_C(4) << 32), + LINUX_4ED_EVENT_CLI = (UINT64_C(5) << 32), +}; + +struct Linux_Coroutine { + Coroutine coroutine; + Linux_Coroutine *next; + ucontext_t ctx, yield_ctx; + stack_t stack; + b32 done; +}; + +struct Thread_Context{ + u32 job_id; + b32 running; + b32 cancel; + + Work_Queue *queue; + u32 id; + u32 group_id; + pthread_t handle; +}; + +struct Thread_Group{ + Thread_Context *threads; + i32 count; + + Unbounded_Work_Queue queue; + + i32 cancel_lock0; + i32 cancel_cv0; +}; + +struct Linux_Vars{ + Display *XDisplay; + Window XWindow; + Render_Target target; + + XIM input_method; + XIMStyle input_style; + XIC input_context; + + Application_Step_Input input; + + String clipboard_contents; + String clipboard_outgoing; + b32 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; + + b32 has_xfixes; + int xfixes_selection_event; + + int epoll; + + int step_timer_fd; + int step_event_fd; + int x11_fd; + int inotify_fd; + + u64 last_step; + + b32 keep_running; + + Application_Mouse_Cursor cursor; + b32 hide_cursor; + Cursor hidden_cursor; + + void *app_code; + void *custom; + + Thread_Memory *thread_memory; + Thread_Group groups[THREAD_GROUP_COUNT]; + Work_Queue queues[THREAD_GROUP_COUNT]; + pthread_mutex_t locks[LOCK_COUNT]; + pthread_cond_t conds[8]; + sem_t thread_semaphore; + + Partition font_part; + +#if SUPPORT_DPI + i32 dpi_x, dpi_y; +#endif + + Plat_Settings settings; + System_Functions system; + App_Functions app; + Custom_API custom_api; + b32 vsync; + +#if FRED_INTERNAL + Sys_Bubble internal_bubble; + pthread_mutex_t DEBUG_sysmem_lock; +#endif + + Linux_Coroutine coroutine_data[18]; + Linux_Coroutine *coroutine_free; +}; + +// +// Linux globals +// + +globalvar Linux_Vars linuxvars; +globalvar Application_Memory memory_vars; + +// +// Linux forward declarations +// + +internal void LinuxScheduleStep(void); + +internal Plat_Handle LinuxSemToHandle(sem_t*); +internal sem_t* LinuxHandleToSem(Plat_Handle); + +internal Plat_Handle LinuxFDToHandle(int); +internal int LinuxHandleToFD(Plat_Handle); + +internal void LinuxStringDup(String*, void*, size_t); +internal void LinuxToggleFullscreen(Display*, Window); + +internal Sys_Acquire_Lock_Sig(system_acquire_lock); +internal Sys_Release_Lock_Sig(system_release_lock); + +internal void system_wait_cv(i32, i32); +internal void system_signal_cv(i32, i32); + +// +// Linux static assertions +// + +static_assert(sizeof(Plat_Handle) >= sizeof(ucontext_t*), "Plat_Handle not big enough"); +static_assert(sizeof(Plat_Handle) >= sizeof(sem_t*), "Plat_Handle not big enough"); +static_assert(sizeof(Plat_Handle) >= sizeof(int), "Plat_Handle not big enough"); + +// +// Shared system functions (system_shared.h) +// + +internal void* +LinuxGetMemory_(i32 size, i32 line_number, char *file_name){ + void *result = 0; + + Assert(size != 0); + +#if FRED_INTERNAL + result = mmap(0, size + sizeof(Sys_Bubble), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + Sys_Bubble* bubble = (Sys_Bubble*)result; + bubble->flags = 0; + bubble->line_number = line_number; + bubble->file_name = file_name; + bubble->size = size; + + pthread_mutex_lock(&linuxvars.DEBUG_sysmem_lock); + insert_bubble(&linuxvars.internal_bubble, bubble); + pthread_mutex_unlock(&linuxvars.DEBUG_sysmem_lock); + + result = bubble + 1; +#else + size_t real_size = size + sizeof(size_t); + result = mmap(0, real_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if(result == MAP_FAILED){ + perror("mmap"); + result = NULL; + } else { + memcpy(result, &real_size, sizeof(size_t)); + result = (char*)result + sizeof(size_t); + } +#endif + + return(result); +} + +internal void +LinuxFreeMemory(void *block){ + if (block){ +#if FRED_INTERNAL + Sys_Bubble *bubble = ((Sys_Bubble*)block) - 1; + + size_t size = bubble->size + sizeof(Sys_Bubble); + + pthread_mutex_lock(&linuxvars.DEBUG_sysmem_lock); + remove_bubble(bubble); + pthread_mutex_unlock(&linuxvars.DEBUG_sysmem_lock); + + munmap(bubble, size); +#else + block = (char*)block - sizeof(size_t); + size_t size = *(size_t*)block; + munmap(block, size); +#endif + } +} + +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 +Sys_File_Can_Be_Made_Sig(system_file_can_be_made){ + b32 result = access(filename, W_OK) == 0; + LINUX_FN_DEBUG("%s = %d", filename, result); + return(result); +} + +internal +Sys_Get_Binary_Path_Sig(system_get_binary_path){ + ssize_t size = readlink("/proc/self/exe", out->str, out->memory_size - 1); + if(size != -1 && size < out->memory_size - 1){ + out->size = size; + remove_last_folder(out); + terminate_with_null(out); + size = out->size; + } else { + size = 0; + } + + return size; +} + +// +// System Functions (4ed_system.h) +// + +// +// Files +// + +internal +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; + + if(directory.size <= 0){ + if(!directory.str){ + system_free_memory(file_list->block); + file_list->block = 0; + file_list->block_size = 0; + } + file_list->infos = 0; + file_list->count = 0; + return; + } + + char* dir = (char*) alloca(directory.size + 1); + memcpy(dir, directory.str, directory.size); + dir[directory.size] = 0; + + LINUX_FN_DEBUG("%s", dir); + + d = opendir(dir); + if (d){ + count = 0; + file_count = 0; + for (entry = readdir(d); + entry != 0; + entry = readdir(d)){ + fname = entry->d_name; + if(fname[0] == '.' && (fname[1] == 0 || (fname[1] == '.' && fname[2] == 0))){ + continue; + } + ++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)){ + fname = entry->d_name; + if(fname[0] == '.' && (fname[1] == 0 || (fname[1] == '.' && fname[2] == 0))){ + continue; + } + cursor_start = cursor; + for (; *fname; ) *cursor++ = *fname++; + + if(entry->d_type == DT_LNK){ + struct stat st; + if(stat(entry->d_name, &st) != -1){ + info_ptr->folder = S_ISDIR(st.st_mode); + } else { + info_ptr->folder = 0; + } + } else { + info_ptr->folder = entry->d_type == DT_DIR; + } + + info_ptr->filename = cursor_start; + info_ptr->filename_len = (int)(cursor - cursor_start); + *cursor++ = 0; + ++info_ptr; + } + + file_list->count = file_count; + + closedir(d); + } else { + system_free_memory(file_list->block); + file_list->block = 0; + file_list->block_size = 0; + file_list->infos = 0; + file_list->count = 0; + } +} + +internal +Sys_Get_Canonical_Sig(system_get_canonical){ + char* path = (char*) alloca(len + 1); + char* write_p = path; + const char* read_p = filename; + + while(read_p < filename + len){ + if(read_p == filename || read_p[0] == '/'){ + if(read_p[1] == '/'){ + ++read_p; + } else if(read_p[1] == '.'){ + if(read_p[2] == '/' || !read_p[2]){ + read_p += 2; + } else if(read_p[2] == '.' && (read_p[3] == '/' || !read_p[3])){ + while(write_p > path && *--write_p != '/'); + read_p += 3; + } else { + *write_p++ = *read_p++; + } + } else { + *write_p++ = *read_p++; + } + } else { + *write_p++ = *read_p++; + } + } + if(write_p == path) *write_p++ = '/'; + + if(max >= (write_p - path)){ + memcpy(buffer, path, write_p - path); + } else { + write_p = path; + } + +#if FRED_INTERNAL + if(len != (write_p - path) || memcmp(filename, path, len) != 0){ + LINUX_FN_DEBUG("[%.*s] -> [%.*s]", len, filename, (int)(write_p - path), path); + } +#endif + + return write_p - path; +} + +internal +Sys_Load_Handle_Sig(system_load_handle){ + b32 result = 0; + + int fd = open(filename, O_RDONLY); + if(fd == -1){ + perror("open"); + } else { + *(int*)handle_out = fd; + result = 1; + } + + return result; +} + +internal +Sys_Load_Size_Sig(system_load_size){ + u32 result = 0; + + int fd = *(int*)&handle; + struct stat st; + + if(fstat(fd, &st) == -1){ + perror("fstat"); + } else { + result = st.st_size; + } + + return result; +} + +internal +Sys_Load_File_Sig(system_load_file){ + + int fd = *(int*)&handle; + do { + ssize_t n = read(fd, buffer, size); + if(n == -1 && errno != EAGAIN){ + perror("read"); + break; + } else { + size -= n; + buffer += n; + } + } while(size); + + return size == 0; +} + +internal +Sys_Load_Close_Sig(system_load_close){ + b32 result = 1; + + int fd = *(int*)&handle; + if(close(fd) == -1){ + perror("close"); + result = 0; + } + + return result; +} + +internal +Sys_Save_File_Sig(system_save_file){ + b32 result = 0; + int fd = open(filename, O_WRONLY | O_TRUNC | O_CREAT, 00640); + + LINUX_FN_DEBUG("%s %d", filename, size); + + if(fd < 0){ + fprintf(stderr, "system_save_file: open '%s': %s\n", filename, strerror(errno)); + } else { + do { + ssize_t written = write(fd, buffer, size); + if(written == -1){ + if(errno != EINTR){ + perror("system_save_file: write"); + break; + } + } else { + size -= written; + buffer += written; + } + } while(size); + + close(fd); + } + + return (size == 0); +} + +// +// Time +// + +internal +Sys_Now_Time_Sig(system_now_time){ + struct timespec spec; + u64 result; + + clock_gettime(CLOCK_REALTIME, &spec); + result = (spec.tv_sec * UINT64_C(1000000)) + (spec.tv_nsec / UINT64_C(1000)); + + //LINUX_FN_DEBUG("ts: %" PRIu64, result); + + return(result); +} + +// +// 4coder_custom.h +// + +internal +MEMORY_ALLOCATE_SIG(system_memory_allocate){ + // NOTE(allen): This must return the exact base of the vpage. + // We will count on the user to keep track of size themselves. + void *result = mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if(result == MAP_FAILED){ + perror("mmap"); + result = NULL; + } + return(result); +} + +internal +MEMORY_SET_PROTECTION_SIG(system_memory_set_protection){ + // NOTE(allen): + // There is no such thing as "write only" in windows + // so I just made write = write + read in all cases. + bool32 result = 1; + int protect = 0; + + flags = flags & 0x7; + + switch (flags){ + case 0: + protect = PROT_NONE; break; + + case MemProtect_Read: + protect = PROT_READ; break; + + case MemProtect_Write: + case MemProtect_Read|MemProtect_Write: + protect = PROT_READ | PROT_WRITE; break; + + case MemProtect_Execute: + protect = PROT_EXEC; break; + + case MemProtect_Execute|MemProtect_Read: + protect = PROT_READ | PROT_EXEC; break; + + // NOTE(inso): some W^X protection things might be unhappy about this one + case MemProtect_Execute|MemProtect_Write: + case MemProtect_Execute|MemProtect_Write|MemProtect_Read: + protect = PROT_READ | PROT_WRITE | PROT_EXEC; break; + } + + if(mprotect(ptr, size, protect) == -1){ + result = 0; + perror("mprotect"); + } + + return(result); +} + +internal +MEMORY_FREE_SIG(system_memory_free){ + // NOTE(allen): This must take the exact base of the vpage. + munmap(ptr, size); +} + +internal +FILE_EXISTS_SIG(system_file_exists){ + int result = 0; + char buff[PATH_MAX] = {}; + + if(len + 1 > PATH_MAX){ + fputs("system_directory_has_file: path too long\n", stderr); + } else { + memcpy(buff, filename, len); + buff[len] = 0; + struct stat st; + result = stat(buff, &st) == 0 && S_ISREG(st.st_mode); + } + + LINUX_FN_DEBUG("%s: %d", buff, result); + + return(result); +} + +internal +DIRECTORY_CD_SIG(system_directory_cd){ + String directory = make_string_cap(dir, *len, capacity); + b32 result = 0; + i32 old_size; + + if (rel_path[0] != 0){ + if (rel_path[0] == '.' && rel_path[1] == 0){ + result = 1; + } + else if (rel_path[0] == '.' && rel_path[1] == '.' && rel_path[2] == 0){ + result = remove_last_folder(&directory); + terminate_with_null(&directory); + } + else{ + if (directory.size + rel_len + 1 > directory.memory_size){ + old_size = directory.size; + append_partial_sc(&directory, rel_path); + append_s_char(&directory, '/'); + terminate_with_null(&directory); + + struct stat st; + if (stat(directory.str, &st) == 0 && S_ISDIR(st.st_mode)){ + result = 1; + } + else{ + directory.size = old_size; + } + } + } + } + + *len = directory.size; + + LINUX_FN_DEBUG("%.*s: %d", directory.size, directory.str, result); + + return(result); +} + +internal +GET_4ED_PATH_SIG(system_get_4ed_path){ + String str = make_string_cap(out, 0, capacity); + return(system_get_binary_path(&str)); +} + +internal +SHOW_MOUSE_CURSOR_SIG(system_show_mouse_cursor){ + linuxvars.hide_cursor = !show; + XDefineCursor(linuxvars.XDisplay, linuxvars.XWindow, show ? None : linuxvars.hidden_cursor); +} + +internal +TOGGLE_FULLSCREEN_SIG(system_toggle_fullscreen){ + LinuxToggleFullscreen(linuxvars.XDisplay, linuxvars.XWindow); +} + +internal +IS_FULLSCREEN_SIG(system_is_fullscreen){ + b32 result = 0; + + Atom type, *prop; + unsigned long nitems, pad; + int fmt; + + int ret = XGetWindowProperty(linuxvars.XDisplay, + linuxvars.XWindow, + linuxvars.atom__NET_WM_STATE, + 0, 32, False, XA_ATOM, + &type, + &fmt, + &nitems, + &pad, + (unsigned char**)&prop); + + if(ret == Success && prop){ + result = *prop == linuxvars.atom__NET_WM_STATE_FULLSCREEN; + XFree((unsigned char*)prop); + } + + return result; +} + +// +// Clipboard +// + +internal +Sys_Post_Clipboard_Sig(system_post_clipboard){ + LinuxStringDup(&linuxvars.clipboard_outgoing, str.str, str.size); + XSetSelectionOwner(linuxvars.XDisplay, linuxvars.atom_CLIPBOARD, linuxvars.XWindow, CurrentTime); +} + +// +// Coroutine +// + +internal Linux_Coroutine* +LinuxAllocCoroutine(){ + Linux_Coroutine *result = linuxvars.coroutine_free; + Assert(result != 0); + if(getcontext(&result->ctx) == -1){ + perror("getcontext"); + } + result->ctx.uc_stack = result->stack; + linuxvars.coroutine_free = result->next; + return(result); +} + +internal void +LinuxFreeCoroutine(Linux_Coroutine *data){ + data->next = linuxvars.coroutine_free; + linuxvars.coroutine_free = data; +} + +internal void +LinuxCoroutineMain(void *arg_){ + Linux_Coroutine *c = (Linux_Coroutine*)arg_; + c->coroutine.func(&c->coroutine); + c->done = 1; + LinuxFreeCoroutine(c); + setcontext((ucontext_t*)c->coroutine.yield_handle); +} + +internal +Sys_Create_Coroutine_Sig(system_create_coroutine){ + Linux_Coroutine *c = LinuxAllocCoroutine(); + c->done = 0; + + makecontext(&c->ctx, (void (*)())LinuxCoroutineMain, 1, &c->coroutine); + + *(ucontext_t**)&c->coroutine.plat_handle = &c->ctx; + c->coroutine.func = func; + + return(&c->coroutine); +} + +internal +Sys_Launch_Coroutine_Sig(system_launch_coroutine){ + Linux_Coroutine *c = (Linux_Coroutine*)coroutine; + ucontext_t* ctx = *(ucontext**)&coroutine->plat_handle; + + coroutine->yield_handle = &c->yield_ctx; + coroutine->in = in; + coroutine->out = out; + + swapcontext(&c->yield_ctx, ctx); + + if (c->done){ + LinuxFreeCoroutine(c); + coroutine = 0; + } + + return(coroutine); +} + +internal +Sys_Resume_Coroutine_Sig(system_resume_coroutine){ + Linux_Coroutine *c = (Linux_Coroutine*)coroutine; + void *fiber; + + Assert(!c->done); + + coroutine->yield_handle = &c->yield_ctx; + coroutine->in = in; + coroutine->out = out; + + ucontext *ctx = *(ucontext**)&coroutine->plat_handle; + + swapcontext(&c->yield_ctx, ctx); + + if (c->done){ + LinuxFreeCoroutine(c); + coroutine = 0; + } + + return(coroutine); +} + +internal +Sys_Yield_Coroutine_Sig(system_yield_coroutine){ + swapcontext(*(ucontext_t**)&coroutine->plat_handle, (ucontext*)coroutine->yield_handle); +} + +// +// CLI +// + +internal +Sys_CLI_Call_Sig(system_cli_call){ + LINUX_FN_DEBUG("%s %s", path, script_name); + + int pipe_fds[2]; + if(pipe(pipe_fds) == -1){ + perror("system_cli_call: pipe"); + return 0; + } + + pid_t child_pid = fork(); + if(child_pid == -1){ + perror("system_cli_call: fork"); + return 0; + } + + enum { PIPE_FD_READ, PIPE_FD_WRITE }; + + // child + if(child_pid == 0){ + close(pipe_fds[PIPE_FD_READ]); + dup2(pipe_fds[PIPE_FD_WRITE], STDOUT_FILENO); + dup2(pipe_fds[PIPE_FD_WRITE], STDERR_FILENO); + + if(chdir(path) == -1){ + perror("system_cli_call: chdir"); + exit(1); + }; + + char* argv[] = { "sh", "-c", script_name, NULL }; + + if(execv("/bin/sh", argv) == -1){ + perror("system_cli_call: execv"); + } + exit(1); + } else { + close(pipe_fds[PIPE_FD_WRITE]); + + *(pid_t*)&cli_out->proc = child_pid; + *(int*)&cli_out->out_read = pipe_fds[PIPE_FD_READ]; + *(int*)&cli_out->out_write = pipe_fds[PIPE_FD_WRITE]; + + struct epoll_event e = {}; + e.events = EPOLLIN | EPOLLET; + e.data.u64 = LINUX_4ED_EVENT_CLI; + epoll_ctl(linuxvars.epoll, EPOLL_CTL_ADD, pipe_fds[PIPE_FD_READ], &e); + } + + return 1; +} + +internal +Sys_CLI_Begin_Update_Sig(system_cli_begin_update){ + // NOTE(inso): I don't think anything needs to be done here. +} + +internal +Sys_CLI_Update_Step_Sig(system_cli_update_step){ + + int pipe_read_fd = *(int*)&cli->out_read; + + fd_set fds; + FD_ZERO(&fds); + FD_SET(pipe_read_fd, &fds); + + struct timeval tv = {}; + + size_t space_left = max; + char* ptr = dest; + + while(space_left > 0 && select(pipe_read_fd + 1, &fds, NULL, NULL, &tv) == 1){ + ssize_t num = read(pipe_read_fd, ptr, space_left); + if(num == -1){ + perror("system_cli_update_step: read"); + } else if(num == 0){ + // NOTE(inso): EOF + break; + } else { + ptr += num; + space_left -= num; + } + } + + *amount = (ptr - dest); + return (ptr - dest) > 0; +} + +internal +Sys_CLI_End_Update_Sig(system_cli_end_update){ + pid_t pid = *(pid_t*)&cli->proc; + b32 close_me = 0; + + int status; + if(pid && waitpid(pid, &status, WNOHANG) > 0){ + close_me = 1; + + cli->exit = WEXITSTATUS(status); + + struct epoll_event e = {}; + epoll_ctl(linuxvars.epoll, EPOLL_CTL_DEL, *(int*)&cli->out_read, &e); + + close(*(int*)&cli->out_read); + close(*(int*)&cli->out_write); + } + + return close_me; +} + +// +// Threads +// + +internal +Sys_Acquire_Lock_Sig(system_acquire_lock){ + pthread_mutex_lock(linuxvars.locks + id); +} + +internal +Sys_Release_Lock_Sig(system_release_lock){ + pthread_mutex_unlock(linuxvars.locks + id); +} + +internal void +system_wait_cv(i32 lock_id, i32 cv_id){ + pthread_cond_wait(linuxvars.conds + cv_id, linuxvars.locks + lock_id); +} + +internal void +system_signal_cv(i32 lock_id, i32 cv_id){ + pthread_cond_signal(linuxvars.conds + cv_id); +} + +internal void* +JobThreadProc(void* lpParameter){ + Thread_Context *thread = (Thread_Context*)lpParameter; + Work_Queue *queue = linuxvars.queues + thread->group_id; + Thread_Group *group = linuxvars.groups + thread->group_id; + + i32 thread_index = thread->id - 1; + + i32 cancel_lock = group->cancel_lock0 + thread_index; + i32 cancel_cv = group->cancel_cv0 + thread_index; + + Thread_Memory *thread_memory = linuxvars.thread_memory + thread_index; + + if (thread_memory->size == 0){ + i32 new_size = Kbytes(64); + thread_memory->data = LinuxGetMemory(new_size); + thread_memory->size = new_size; + } + + for (;;){ + u32 read_index = queue->read_position; + u32 write_index = queue->write_position; + + if (read_index != write_index){ + // NOTE(allen): Previously I was wrapping by the job wrap then + // wrapping by the queue wrap. That was super stupid what was that? + // Now it just wraps by the queue wrap. + u32 next_read_index = (read_index + 1) % QUEUE_WRAP; + u32 safe_read_index = + InterlockedCompareExchange(&queue->read_position, + next_read_index, read_index); + + if (safe_read_index == read_index){ + Full_Job_Data *full_job = queue->jobs + safe_read_index; + // NOTE(allen): This is interlocked so that it plays nice + // with the cancel job routine, which may try to cancel this job + // at the same time that we try to run it + + i32 safe_running_thread = + InterlockedCompareExchange(&full_job->running_thread, + thread->id, THREAD_NOT_ASSIGNED); + + if (safe_running_thread == THREAD_NOT_ASSIGNED){ + thread->job_id = full_job->id; + thread->running = 1; + + full_job->job.callback(&linuxvars.system, + thread, thread_memory, full_job->job.data); + LinuxScheduleStep(); + //full_job->running_thread = 0; + thread->running = 0; + + system_acquire_lock(cancel_lock); + if (thread->cancel){ + thread->cancel = 0; + system_signal_cv(cancel_lock, cancel_cv); + } + system_release_lock(cancel_lock); + } + } + } + else{ + sem_wait(LinuxHandleToSem(queue->semaphore)); + } + } +} + +internal void +initialize_unbounded_queue(Unbounded_Work_Queue *source_queue){ + i32 max = 512; + source_queue->jobs = (Full_Job_Data*)system_get_memory(max*sizeof(Full_Job_Data)); + source_queue->count = 0; + source_queue->max = max; + source_queue->skip = 0; +} + +inline i32 +get_work_queue_available_space(i32 write, i32 read){ + // NOTE(allen): The only time that queue->write_position == queue->read_position + // is allowed is when the queue is empty. Thus if + // queue->write_position+1 == queue->read_position the available space is zero. + // So these computations both end up leaving one slot unused. The only way I can + // think to easily eliminate this is to have read and write wrap at twice the size + // of the underlying array but modulo their values into the array then if write + // has caught up with read it still will not be equal... but lots of modulos... ehh. + + i32 available_space = 0; + if (write >= read){ + available_space = QUEUE_WRAP - (write - read) - 1; + } + else{ + available_space = (read - write) - 1; + } + + return(available_space); +} + +#define UNBOUNDED_SKIP_MAX 128 + +internal void +flush_to_direct_queue(Unbounded_Work_Queue *source_queue, Work_Queue *queue, i32 thread_count){ + // NOTE(allen): It is understood that read_position may be changed by other + // threads but it will only make more space in the queue if it is changed. + // Meanwhile write_position should not ever be changed by anything but the + // main thread in this system, so it will not be interlocked. + u32 read_position = queue->read_position; + u32 write_position = queue->write_position; + u32 available_space = get_work_queue_available_space(write_position, read_position); + u32 available_jobs = source_queue->count - source_queue->skip; + + u32 writable_count = Min(available_space, available_jobs); + + if (writable_count > 0){ + u32 count1 = writable_count; + + if (count1+write_position > QUEUE_WRAP){ + count1 = QUEUE_WRAP - write_position; + } + + u32 count2 = writable_count - count1; + + Full_Job_Data *job_src1 = source_queue->jobs + source_queue->skip; + Full_Job_Data *job_src2 = job_src1 + count1; + + Full_Job_Data *job_dst1 = queue->jobs + write_position; + Full_Job_Data *job_dst2 = queue->jobs; + + Assert((job_src1->id % QUEUE_WRAP) == write_position); + + memcpy(job_dst1, job_src1, sizeof(Full_Job_Data)*count1); + memcpy(job_dst2, job_src2, sizeof(Full_Job_Data)*count2); + queue->write_position = (write_position + writable_count) % QUEUE_WRAP; + + source_queue->skip += writable_count; + + if (source_queue->skip == source_queue->count){ + source_queue->skip = source_queue->count = 0; + } + else if (source_queue->skip > UNBOUNDED_SKIP_MAX){ + u32 left_over = source_queue->count - source_queue->skip; + memmove(source_queue->jobs, source_queue->jobs + source_queue->skip, + sizeof(Full_Job_Data)*left_over); + source_queue->count = left_over; + source_queue->skip = 0; + } + } + + i32 semaphore_release_count = writable_count; + if (semaphore_release_count > thread_count){ + semaphore_release_count = thread_count; + } + + // NOTE(allen): platform dependent portion... + // TODO(allen): pull out the duplicated part once I see + // that this is pretty much the same on linux. + for (i32 i = 0; i < semaphore_release_count; ++i){ + sem_post(LinuxHandleToSem(queue->semaphore)); + } +} + +internal void +flush_thread_group(i32 group_id){ + Thread_Group *group = linuxvars.groups + group_id; + Work_Queue *queue = linuxvars.queues + group_id; + Unbounded_Work_Queue *source_queue = &group->queue; + flush_to_direct_queue(source_queue, queue, group->count); +} + +// Note(allen): post_job puts the job on the unbounded queue. +// The unbounded queue is entirely managed by the main thread. +// The thread safe queue is bounded in size so the unbounded +// queue is periodically flushed into the direct work queue. +internal +Sys_Post_Job_Sig(system_post_job){ + Thread_Group *group = linuxvars.groups + group_id; + Unbounded_Work_Queue *queue = &group->queue; + + u32 result = queue->next_job_id++; + + while (queue->count >= queue->max){ + i32 new_max = queue->max*2; + Full_Job_Data *new_jobs = (Full_Job_Data*) + system_get_memory(new_max*sizeof(Full_Job_Data)); + + memcpy(new_jobs, queue->jobs, queue->count); + + system_free_memory(queue->jobs); + + queue->jobs = new_jobs; + queue->max = new_max; + } + + Full_Job_Data full_job; + + full_job.job = job; + full_job.running_thread = THREAD_NOT_ASSIGNED; + full_job.id = result; + + queue->jobs[queue->count++] = full_job; + + Work_Queue *direct_queue = linuxvars.queues + group_id; + flush_to_direct_queue(queue, direct_queue, group->count); + + return(result); +} + +internal +Sys_Cancel_Job_Sig(system_cancel_job){ + Thread_Group *group = linuxvars.groups + group_id; + Unbounded_Work_Queue *source_queue = &group->queue; + + b32 handled_in_unbounded = false; + if (source_queue->skip < source_queue->count){ + Full_Job_Data *first_job = source_queue->jobs + source_queue->skip; + if (first_job->id <= job_id){ + u32 index = source_queue->skip + (job_id - first_job->id); + Full_Job_Data *job = source_queue->jobs + index; + job->running_thread = 0; + handled_in_unbounded = true; + } + } + + if (!handled_in_unbounded){ + Work_Queue *queue = linuxvars.queues + group_id; + Full_Job_Data *job = queue->jobs + (job_id % QUEUE_WRAP); + Assert(job->id == job_id); + + u32 thread_id = + InterlockedCompareExchange(&job->running_thread, + 0, THREAD_NOT_ASSIGNED); + + if (thread_id != THREAD_NOT_ASSIGNED && thread_id != 0){ + i32 thread_index = thread_id - 1; + + i32 cancel_lock = group->cancel_lock0 + thread_index; + i32 cancel_cv = group->cancel_cv0 + thread_index; + Thread_Context *thread = group->threads + thread_index; + + + system_acquire_lock(cancel_lock); + + thread->cancel = 1; + + system_release_lock(FRAME_LOCK); + do{ + system_wait_cv(cancel_lock, cancel_cv); + }while (thread->cancel == 1); + system_acquire_lock(FRAME_LOCK); + + system_release_lock(cancel_lock); + } + } +} + +internal +Sys_Check_Cancel_Sig(system_check_cancel){ + b32 result = 0; + + Thread_Group *group = linuxvars.groups + thread->group_id; + i32 thread_index = thread->id - 1; + i32 cancel_lock = group->cancel_lock0 + thread_index; + + system_acquire_lock(cancel_lock); + if (thread->cancel){ + result = 1; + } + system_release_lock(cancel_lock); + + return(result); +} + +internal +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); +} + +// +// Debug +// + +#if FRED_INTERNAL + +internal +INTERNAL_Sys_Sentinel_Sig(internal_sentinel){ + return (&linuxvars.internal_bubble); +} + +#ifdef OLD_JOB_QUEUE +internal +INTERNAL_Sys_Get_Thread_States_Sig(internal_get_thread_states){ + Work_Queue *queue = linuxvars.queues + id; + u32 write = queue->write_position; + u32 read = queue->read_position; + if (write < read) write += QUEUE_WRAP; + *pending = (i32)(write - read); + + Thread_Group *group = linuxvars.groups + id; + for (i32 i = 0; i < group->count; ++i){ + running[i] = (group->threads[i].running != 0); + } +} +#else +internal +INTERNAL_Sys_Get_Thread_States_Sig(internal_get_thread_states){ + Thread_Group *group = linuxvars.groups + id; + Unbounded_Work_Queue *source_queue = &group->queue; + Work_Queue *queue = linuxvars.queues + id; + u32 write = queue->write_position; + u32 read = queue->read_position; + if (write < read) write += QUEUE_WRAP; + *pending = (i32)(write - read) + source_queue->count - source_queue->skip; + + for (i32 i = 0; i < group->count; ++i){ + running[i] = (group->threads[i].running != 0); + } +} +#endif + +internal +INTERNAL_Sys_Debug_Message_Sig(internal_debug_message){ + fprintf(stderr, "%s", message); +} + +#endif + +// +// Linux rendering/font system functions +// + +#include "system_shared.cpp" +#include "linux_font.cpp" + +internal f32 +size_change(i32 dpi_x, i32 dpi_y){ + // TODO(allen): We're just hoping dpi_x == dpi_y for now I guess. + f32 size_x = dpi_x / 96.f; + f32 size_y = dpi_y / 96.f; + f32 size_max = Max(size_x, size_y); + return(size_max); +} + +internal +Font_Load_Sig(system_draw_font_load){ + b32 success = 0; + i32 attempts = 0; + + LINUX_FN_DEBUG("%s, %dpt, tab_width: %d", filename, pt_size, tab_width); + + if (linuxvars.font_part.base == 0){ + linuxvars.font_part = sysshared_scratch_partition(Mbytes(8)); + } + + i32 oversample = 2; + +#if SUPPORT_DPI + pt_size = ROUND32(pt_size * size_change(linuxvars.dpi_x, linuxvars.dpi_y)); +#endif + + for(; attempts < 3; ++attempts){ +#if LINUX_FONTS + success = linux_font_load(&linuxvars.font_part, font_out, filename, pt_size, tab_width, + linuxvars.settings.use_hinting); +#else + success = font_load( + &linuxvars.font_part, + font_out, + filename, + pt_size, + tab_width, + oversample, + store_texture + ); +#endif + + if(success){ + break; + } else { + fprintf(stderr, "draw_font_load failed, %p %d\n", linuxvars.font_part.base, linuxvars.font_part.max); + sysshared_partition_double(&linuxvars.font_part); + } + } + + return success; +} + +// +// End of system funcs +// + +// +// Linux init functions +// + +internal b32 +LinuxLoadAppCode(String* base_dir){ + b32 result = 0; + App_Get_Functions *get_funcs = 0; + + if(!sysshared_to_binary_path(base_dir, "4ed_app.so")){ + return 0; + } + + linuxvars.app_code = dlopen(base_dir->str, RTLD_LAZY); + if (linuxvars.app_code){ + get_funcs = (App_Get_Functions*) + dlsym(linuxvars.app_code, "app_get_functions"); + } else { + fprintf(stderr, "dlopen failed: %s\n", dlerror()); + } + + if (get_funcs){ + result = 1; + linuxvars.app = get_funcs(); + } + + return(result); +} + +internal void +LinuxLoadSystemCode(){ + + // files + linuxvars.system.set_file_list = system_set_file_list; + linuxvars.system.get_canonical = system_get_canonical; + linuxvars.system.add_listener = system_add_listener; + linuxvars.system.remove_listener = system_remove_listener; + linuxvars.system.get_file_change = system_get_file_change; + linuxvars.system.load_handle = system_load_handle; + linuxvars.system.load_size = system_load_size; + linuxvars.system.load_file = system_load_file; + linuxvars.system.load_close = system_load_close; + linuxvars.system.save_file = system_save_file; + + // time + linuxvars.system.now_time = system_now_time; + + // 4coder_custom.h + linuxvars.system.memory_allocate = system_memory_allocate; + linuxvars.system.memory_set_protection = system_memory_set_protection; + linuxvars.system.memory_free = system_memory_free; + linuxvars.system.file_exists = system_file_exists; + linuxvars.system.directory_cd = system_directory_cd; + linuxvars.system.get_4ed_path = system_get_4ed_path; + linuxvars.system.show_mouse_cursor = system_show_mouse_cursor; + linuxvars.system.toggle_fullscreen = system_toggle_fullscreen; + linuxvars.system.is_fullscreen = system_is_fullscreen; + + // clipboard + linuxvars.system.post_clipboard = system_post_clipboard; + + // coroutine + linuxvars.system.create_coroutine = system_create_coroutine; + linuxvars.system.launch_coroutine = system_launch_coroutine; + linuxvars.system.resume_coroutine = system_resume_coroutine; + linuxvars.system.yield_coroutine = system_yield_coroutine; + + // cli + 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; + + // threads + linuxvars.system.post_job = system_post_job; + linuxvars.system.cancel_job = system_cancel_job; + linuxvars.system.check_cancel = system_check_cancel; + linuxvars.system.grow_thread_memory = system_grow_thread_memory; + linuxvars.system.acquire_lock = system_acquire_lock; + linuxvars.system.release_lock = system_release_lock; + + // debug +#if FRED_INTERNAL + linuxvars.system.internal_sentinel = internal_sentinel; + linuxvars.system.internal_get_thread_states = internal_get_thread_states; + linuxvars.system.internal_debug_message = internal_debug_message; +#endif + + // non-function details + 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_load = system_draw_font_load; + linuxvars.target.font_set.release_font = draw_release_font; +} + +// +// Renderer +// + +internal void +LinuxRedrawTarget(){ + launch_rendering(&linuxvars.target); + //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; + } +} + +// +// OpenGL init +// + +// NOTE(allen): Thanks to Casey for providing the linux OpenGL launcher. +static bool ctxErrorOccurred = false; + +internal int +ctxErrorHandler( Display *dpy, XErrorEvent *ev ) +{ + ctxErrorOccurred = true; + return 0; +} + +#if FRED_INTERNAL + +static void LinuxGLDebugCallback( + GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + const GLchar* message, + const void* userParam +){ + fprintf(stderr, "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*); + + typedef PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXTProc; + typedef PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESAProc; + typedef PFNGLXGETSWAPINTERVALMESAPROC glXGetSwapIntervalMESAProc; + typedef PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGIProc; + + const char *glxExts = glXQueryExtensionsString(XDisplay, DefaultScreen(XDisplay)); + + #define GLXLOAD(x) x ## Proc x = (x ## Proc) glXGetProcAddressARB( (const GLubyte*) #x); + + GLXLOAD(glXCreateContextAttribsARB); + + GLXContext ctx = 0; + ctxErrorOccurred = false; + int (*oldHandler)(Display*, XErrorEvent*) = XSetErrorHandler(&ctxErrorHandler); + + if (!glXCreateContextAttribsARB) + { + fprintf(stderr, "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 + }; + + fprintf(stderr, "Creating GL 4.3 context...\n"); + ctx = glXCreateContextAttribsARB(XDisplay, bestFbc, 0, True, context_attribs); + + XSync( XDisplay, False ); + if (!ctxErrorOccurred && ctx) + { + fprintf(stderr, "Created GL 4.3 context.\n" ); + } + else + { + ctxErrorOccurred = false; + + context_attribs[1] = 3; + context_attribs[3] = 2; + + fprintf(stderr, "GL 4.3 unavailable, creating GL 3.2 context...\n" ); + ctx = glXCreateContextAttribsARB( XDisplay, bestFbc, 0, True, context_attribs ); + + XSync(XDisplay, False); + + if (!ctxErrorOccurred && ctx) + { + fprintf(stderr, "Created GL 3.2 context.\n" ); + } + else + { + context_attribs[1] = 1; + context_attribs[3] = 2; + + ctxErrorOccurred = false; + + fprintf(stderr, "Failed to create GL 3.2 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) + { + fprintf(stderr, "Failed to create an OpenGL context\n"); + exit(1); + } + + b32 Direct; + if (!glXIsDirect(XDisplay, ctx)) + { + fprintf(stderr, "Indirect GLX rendering context obtained\n"); + Direct = 0; + } + else + { + fprintf(stderr, "Direct GLX rendering context obtained\n"); + Direct = 1; + } + + fprintf(stderr, "Making context current\n"); + glXMakeCurrent( XDisplay, XWindow, ctx ); + + char *Vendor = (char *)glGetString(GL_VENDOR); + char *Renderer = (char *)glGetString(GL_RENDERER); + char *Version = (char *)glGetString(GL_VERSION); + + //TODO(inso): glGetStringi is required in core profile if the GL version is >= 3.0 + char *Extensions = (char *)glGetString(GL_EXTENSIONS); + + fprintf(stderr, "GL_VENDOR: %s\n", Vendor); + fprintf(stderr, "GL_RENDERER: %s\n", Renderer); + fprintf(stderr, "GL_VERSION: %s\n", Version); + // fprintf(stderr, "GL_EXTENSIONS: %s\n", Extensions); + + //NOTE(inso): enable vsync if available. this should probably be optional + if(Direct && strstr(glxExts, "GLX_EXT_swap_control ")){ + + GLXLOAD(glXSwapIntervalEXT); + + if(glXSwapIntervalEXT){ + glXSwapIntervalEXT(XDisplay, XWindow, 1); + + unsigned int swap_val = 0; + glXQueryDrawable(XDisplay, XWindow, GLX_SWAP_INTERVAL_EXT, &swap_val); + + linuxvars.vsync = swap_val == 1; + fprintf(stderr, "VSync enabled? %s.\n", linuxvars.vsync ? "Yes" : "No"); + } + + } else if(Direct && strstr(glxExts, "GLX_MESA_swap_control ")){ + + GLXLOAD(glXSwapIntervalMESA); + GLXLOAD(glXGetSwapIntervalMESA); + + if(glXSwapIntervalMESA){ + glXSwapIntervalMESA(1); + + if(glXGetSwapIntervalMESA){ + linuxvars.vsync = glXGetSwapIntervalMESA(); + fprintf(stderr, "VSync enabled? %s (MESA)\n", linuxvars.vsync ? "Yes" : "No"); + } else { + // NOTE(inso): assume it worked? + linuxvars.vsync = 1; + fputs("VSync enabled? possibly (MESA)\n", stderr); + } + } + + } else if(Direct && strstr(glxExts, "GLX_SGI_swap_control ")){ + + GLXLOAD(glXSwapIntervalSGI); + + if(glXSwapIntervalSGI){ + glXSwapIntervalSGI(1); + + //NOTE(inso): The SGI one doesn't seem to have a way to confirm we got it... + linuxvars.vsync = 1; + fputs("VSync enabled? hopefully (SGI)\n", stderr); + } + + } else { + fputs("VSync enabled? nope, no suitable extension\n", stderr); + } + +#if FRED_INTERNAL + typedef PFNGLDEBUGMESSAGECALLBACKARBPROC glDebugMessageCallbackProc; + + GLXLOAD(glDebugMessageCallback); + + if(glDebugMessageCallback){ + fputs("Enabling GL Debug Callback\n", stderr); + glDebugMessageCallback(&LinuxGLDebugCallback, 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); + +#undef GLXLOAD + + return(ctx); +} + +internal b32 +GLXCanUseFBConfig(Display *XDisplay) +{ + b32 Result = false; + + int GLXMajor, GLXMinor; + + char *XVendor = ServerVendor(XDisplay); + fprintf(stderr, "XWindows vendor: %s\n", XVendor); + if(glXQueryVersion(XDisplay, &GLXMajor, &GLXMinor)) + { + fprintf(stderr, "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 = 0; + GLXFBConfig *Configs = glXChooseFBConfig(XDisplay, + XScreenIndex, + DesiredAttributes, + &ConfigCount); + if(Configs && ConfigCount > 0) + { + XVisualInfo* VI = glXGetVisualFromFBConfig(XDisplay, Configs[0]); + if(VI) + { + Result.Found = true; + Result.BestConfig = Configs[0]; + Result.BestInfo = *VI; + + int id = 0; + glXGetFBConfigAttrib(XDisplay, Result.BestConfig, GLX_FBCONFIG_ID, &id); + fprintf(stderr, "Using FBConfig: %d (0x%x)\n", id, id); + + XFree(VI); + } + + XFree(Configs); + } + + return(Result); +} + +// +// X11 input / events init +// + +struct Init_Input_Result{ + XIM input_method; + XIMStyle best_style; + XIC xic; +}; + +inline Init_Input_Result +init_input_result_zero(){ + Init_Input_Result result={0}; + return(result); +} + +internal Init_Input_Result +LinuxInputInit(Display *dpy, Window XWindow) +{ + Init_Input_Result result = {}; + XIMStyles *styles = 0; + XIMStyle style; + unsigned long xim_event_mask = 0; + i32 i; + + setlocale(LC_ALL, ""); + XSetLocaleModifiers(""); + fprintf(stderr, "Supported locale?: %s.\n", XSupportsLocale() ? "Yes" : "No"); + // TODO(inso): handle the case where it isn't supported somehow? + + 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){ + for (i = 0; i < styles->count_styles; ++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, + NULL + ); + + if (XGetICValues(result.xic, XNFilterEvents, &xim_event_mask, NULL)){ + xim_event_mask = 0; + } + } + else{ + result = init_input_result_zero(); + fputs("Could not get minimum required input style.\n", stderr); + } + } + else{ + result = init_input_result_zero(); + fputs("Could not open X Input Method.\n", stderr); + } + + XSelectInput( + linuxvars.XDisplay, + linuxvars.XWindow, + ExposureMask | + KeyPressMask | KeyReleaseMask | + ButtonPressMask | ButtonReleaseMask | + EnterWindowMask | LeaveWindowMask | + PointerMotionMask | + FocusChangeMask | + StructureNotifyMask | + MappingNotify | + ExposureMask | + VisibilityChangeMask | + xim_event_mask + ); + + return(result); +} + +// +// Keyboard handling funcs +// + +globalvar u8 keycode_lookup_table[255]; + +internal void +LinuxKeycodeInit(Display* dpy){ + + // NOTE(inso): This looks a bit dumb, but it's the best way I can think of to do it, since: + // KeySyms are the type representing "virtual" keys, like XK_BackSpace, but they are 32-bit ints. + // KeyCodes are guaranteed to fit in 1 byte (and therefore the keycode_lookup_table) but + // have dynamic numbers assigned by the XServer. + // There is XKeysymToKeycode, but it only returns 1 KeyCode for a KeySym. I have my capslock + // rebound to esc, so there are two KeyCodes for the XK_Escape KeyCode but XKeysymToKeycode only + // gets one of them, hence the need for this crazy lookup which works correctly with rebound keys. + + memset(keycode_lookup_table, 0, sizeof(keycode_lookup_table)); + + struct SymMapping { + KeySym sym; + u8 code; + } sym_table[] = { + { XK_BackSpace, key_back }, + { XK_Delete, key_del }, + { XK_Up, key_up }, + { XK_Down, key_down }, + { XK_Left, key_left }, + { XK_Right, key_right }, + { XK_Insert, key_insert }, + { XK_Home, key_home }, + { XK_End, key_end }, + { XK_Page_Up, key_page_up }, + { XK_Page_Down, key_page_down }, + { XK_Escape, key_esc }, + { XK_F1, key_f1 }, + { XK_F2, key_f2 }, + { XK_F3, key_f3 }, + { XK_F4, key_f4 }, + { XK_F5, key_f5 }, + { XK_F6, key_f6 }, + { XK_F7, key_f7 }, + { XK_F8, key_f8 }, + { XK_F9, key_f9 }, + { XK_F10, key_f10 }, + { XK_F11, key_f11 }, + { XK_F12, key_f12 }, + { XK_F13, key_f13 }, + { XK_F14, key_f14 }, + { XK_F15, key_f15 }, + { XK_F16, key_f16 }, + }; + + const int table_size = sizeof(sym_table) / sizeof(struct SymMapping); + + 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) 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 +LinuxPushKey(u8 code, u8 chr, u8 chr_nocaps, b8 (*mods)[MDFR_INDEX_COUNT], b32 is_hold) +{ + i32 *count; + Key_Event_Data *data; + + if(is_hold){ + count = &linuxvars.input.keys.hold_count; + data = linuxvars.input.keys.hold; + } else { + count = &linuxvars.input.keys.press_count; + data = linuxvars.input.keys.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); + } +} + +// +// Misc utility funcs +// + +internal Plat_Handle +LinuxSemToHandle(sem_t* sem){ + return *(Plat_Handle*)&sem; +} + +internal sem_t* +LinuxHandleToSem(Plat_Handle h){ + return *(sem_t**)&h; +} + +internal Plat_Handle +LinuxFDToHandle(int fd){ + return *(Plat_Handle*)&fd; +} + +internal int +LinuxHandleToFD(Plat_Handle h){ + return *(int*)&h; +} + +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); +} + +internal void +LinuxScheduleStep(void) +{ + u64 now = system_now_time(); + u64 diff = (now - linuxvars.last_step); + + if(diff > (u64)frame_useconds){ + u64 ev = 1; + write(linuxvars.step_event_fd, &ev, sizeof(ev)); + } else { + struct itimerspec its = {}; + timerfd_gettime(linuxvars.step_timer_fd, &its); + + if(its.it_value.tv_sec == 0 && its.it_value.tv_nsec == 0){ + its.it_value.tv_nsec = (frame_useconds - diff) * 1000UL; + timerfd_settime(linuxvars.step_timer_fd, 0, &its, NULL); + } + } +} + +// +// X11 utility funcs +// + +internal void +LinuxSetWMState(Display* d, Window w, Atom one, Atom two, int mode) +{ + //NOTE(inso): this will only work after it is mapped + + enum { STATE_REMOVE, STATE_ADD, STATE_TOGGLE }; + + XEvent e = {}; + + e.xany.type = ClientMessage; + e.xclient.message_type = linuxvars.atom__NET_WM_STATE; + e.xclient.format = 32; + e.xclient.window = w; + 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( + d, + RootWindow(d, 0), + 0, + SubstructureNotifyMask | SubstructureRedirectMask, + &e + ); +} + +internal void +LinuxMaximizeWindow(Display* d, Window w, b32 maximize) +{ + LinuxSetWMState(d, + w, + linuxvars.atom__NET_WM_STATE_MAXIMIZED_HORZ, + linuxvars.atom__NET_WM_STATE_MAXIMIZED_VERT, + maximize != 0); +} + +internal void +LinuxToggleFullscreen(Display* d, Window w) +{ + LinuxSetWMState(d, w, linuxvars.atom__NET_WM_STATE_FULLSCREEN, 0, 2); +} + +#include "linux_icon.h" +internal void +LinuxSetIcon(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) + ); +} + +internal void +LinuxX11ConnectionWatch(Display* dpy, XPointer cdata, int fd, Bool opening, XPointer* wdata) +{ + struct epoll_event e = {}; + e.events = EPOLLIN | EPOLLET; + e.data.u64 = LINUX_4ED_EVENT_X11_INTERNAL | fd; + + int op = opening ? EPOLL_CTL_ADD : EPOLL_CTL_DEL; + epoll_ctl(linuxvars.epoll, op, fd, &e); +} + +// NOTE(inso): this was a quick hack, might need some cleanup. +internal void +LinuxFatalErrorMsg(const char* msg) +{ + fprintf(stderr, "Fatal Error: %s\n", msg); + + Display *dpy = XOpenDisplay(0); + if(!dpy){ + exit(1); + } + + int win_w = 450; + int win_h = 150 + (strlen(msg) / 40) * 24; + + Window w = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 0, 0, win_w, win_h, 0, 0, 0x2EA44F); + XStoreName(dpy, w, "4coder Error"); + + XSizeHints* sh = XAllocSizeHints(); + sh->flags = PMinSize; + sh->min_width = win_w; + sh->min_height = win_h; + XSetWMNormalHints(dpy, w, sh); + + Atom type = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False); + + XChangeProperty(dpy, w, + XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False), + XA_ATOM, + 32, + PropModeReplace, + (unsigned char*) &type, + 1); + + Atom WM_DELETE_WINDOW = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + XSetWMProtocols(dpy, w, &WM_DELETE_WINDOW, 1); + + XMapRaised(dpy, w); + XSync(dpy, False); + + XSelectInput(dpy, w, ExposureMask | StructureNotifyMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask); + + XFontStruct* font = XLoadQueryFont(dpy, "-*-fixed-*-*-*-*-*-140-*-*-*-*-iso8859-1"); + if(!font){ + exit(1); + } + + XGCValues gcv; + gcv.foreground = WhitePixel(dpy, 0); + gcv.background = 0x2EA44F; + gcv.line_width = 2; + gcv.font = font->fid; + + GC gc = XCreateGC(dpy, w, GCForeground | GCBackground | GCFont | GCLineWidth, &gcv); + + gcv.foreground = BlackPixel(dpy, 0); + GC gc2 = XCreateGC(dpy, w, GCForeground | GCBackground | GCFont | GCLineWidth, &gcv); + + int button_trigger = 0; + int button_hi = 0; + + XEvent ev; + while(1){ + XNextEvent(dpy, &ev); + + int redraw = 0; + + if(ev.type == Expose) redraw = 1; + + if(ev.type == ConfigureNotify){ + redraw = 1; + + win_w = ev.xconfigure.width; + win_h = ev.xconfigure.height; + } + + XRectangle button_rect = { win_w/2-40, win_h*0.8f, 80, 20 }; + + if(ev.type == MotionNotify){ + int new_hi = (ev.xmotion.x > button_rect.x && + ev.xmotion.y > button_rect.y && + ev.xmotion.x < button_rect.x + button_rect.width && + ev.xmotion.y < button_rect.y + button_rect.height); + + if(new_hi != button_hi){ + button_hi = new_hi; + redraw = 1; + } + } + + if(ev.type == ButtonPress && ev.xbutton.button == Button1){ + if(button_hi) button_trigger = 1; + redraw = 1; + } + + if(ev.type == ButtonRelease && ev.xbutton.button == Button1){ + if(button_trigger){ + if(button_hi){ + exit(1); + } else { + button_trigger = 0; + } + } + redraw = 1; + } + + if(ev.type == ClientMessage && ev.xclient.window == w && (Atom)ev.xclient.data.l[0] == WM_DELETE_WINDOW){ + exit(1); + } + + if(redraw){ + XClearWindow(dpy, w); + + const char* line_start = msg; + const char* last_space = NULL; + int y = 30; + + { + const char title[] = "4coder - Fatal Error"; + int width = XTextWidth(font, title, sizeof(title)-1); + int x = (win_w/2) - (width/2); + XDrawString(dpy, w, gc2, x+2, y+2, title, sizeof(title)-1); + XDrawString(dpy, w, gc, x, y, title, sizeof(title)-1); + } + + y += 36; + + int width = XTextWidth(font, "x", 1) * 40; + int x = (win_w/2) - (width/2); + for(const char* p = line_start; *p; ++p){ + if(*p == ' ') last_space = p; + if(p - line_start > 40){ + if(!last_space) last_space = p; + + XDrawString(dpy, w, gc2, x+2, y+2, line_start, last_space - line_start); + XDrawString(dpy, w, gc, x, y, line_start, last_space - line_start); + line_start = *last_space == ' ' ? last_space + 1 : p; + last_space = NULL; + y += 18; + } + } + + XDrawString(dpy, w, gc2, x+2, y+2, line_start, strlen(line_start)); + XDrawString(dpy, w, gc, x, y, line_start, strlen(line_start)); + + XDrawRectangles(dpy, w, gc, &button_rect, 1); + if(button_hi || button_trigger){ + XDrawRectangle(dpy, w, gc2, button_rect.x+1, button_rect.y+1, button_rect.width-2, button_rect.height-2); + } + XDrawString(dpy, w, gc2, button_rect.x + 22, button_rect.y + 17, "Drat!", 5); + XDrawString(dpy, w, gc, button_rect.x + 20, button_rect.y + 15, "Drat!", 5); + } + } +} + +internal int +LinuxGetXSettingsDPI(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){ + fputs("XSETTINGS unavailable.\n", stderr); + 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){ + fputs("XSETTINGS: GetWindowProperty failed.\n", stderr); + goto out; + } + + if(fmt != 8){ + fputs("XSETTINGS: Wrong format.\n", stderr); + goto out; + } + } + + xs = (struct XSettings*)prop; + p = (char*)(xs + 1); + + if(xs->byte_order != 0){ + fputs("FIXME: XSETTINGS not host byte order?\n", stderr); + goto out; + } + + for(int i = 0; i < xs->num_settings; ++i){ + struct XSettingHeader* h = (struct XSettingHeader*)p; + + // const char* strs[] = { "Int", "String", "Color" }; + // printf("%s:\t\"%.*s\"\n", strs[h->type], h->name_len, h->name); + + 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: { + fputs("XSETTINGS: Got invalid type...\n", stderr); + goto out; + } break; + } + } + +out: + if(prop){ + XFree(prop); + } + + return dpi; +} + +// +// X11 window init +// + +internal b32 +LinuxX11WindowInit(int argc, char** argv, int* WinWidth, int* WinHeight) +{ + // 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) + +#define BASE_W 800 +#define BASE_H 600 + + if (linuxvars.settings.set_window_size){ + *WinWidth = linuxvars.settings.window_w; + *WinHeight = linuxvars.settings.window_h; + } else { + *WinWidth = BASE_W * size_change(linuxvars.dpi_x, linuxvars.dpi_y); + *WinHeight = BASE_H * size_change(linuxvars.dpi_x, linuxvars.dpi_y); + } + + if (!GLXCanUseFBConfig(linuxvars.XDisplay)){ + LinuxFatalErrorMsg("Your XServer's GLX version is too old. GLX 1.3+ is required."); + return false; + } + + glx_config_result Config = ChooseGLXConfig(linuxvars.XDisplay, DefaultScreen(linuxvars.XDisplay)); + if (!Config.Found){ + LinuxFatalErrorMsg("Could not get a matching GLX FBConfig. Check your OpenGL drivers are installed correctly."); + return false; + } + + XSetWindowAttributes swa = {}; + swa.backing_store = WhenMapped; + swa.event_mask = StructureNotifyMask; + swa.bit_gravity = NorthWestGravity; + swa.colormap = XCreateColormap(linuxvars.XDisplay, + RootWindow(linuxvars.XDisplay, Config.BestInfo.screen), + Config.BestInfo.visual, AllocNone); + + linuxvars.XWindow = + XCreateWindow(linuxvars.XDisplay, + RootWindow(linuxvars.XDisplay, Config.BestInfo.screen), + 0, 0, *WinWidth, *WinHeight, + 0, Config.BestInfo.depth, InputOutput, + Config.BestInfo.visual, + CWBackingStore|CWBitGravity|CWBackPixel|CWBorderPixel|CWColormap|CWEventMask, &swa); + + if (!linuxvars.XWindow){ + LinuxFatalErrorMsg("XCreateWindow failed. Make sure your display is set up correctly."); + return false; + } + + //NOTE(inso): Set the window's type to normal + XChangeProperty( + linuxvars.XDisplay, + linuxvars.XWindow, + 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.XDisplay, + linuxvars.XWindow, + linuxvars.atom__NET_WM_PID, + XA_CARDINAL, + 32, + PropModeReplace, + (unsigned char*)&pid, + 1 + ); + +#define WINDOW_NAME "4coder 4linux: " VERSION + + //NOTE(inso): set wm properties + XStoreName(linuxvars.XDisplay, linuxvars.XWindow, 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); + +/* NOTE(inso): fluxbox thinks this is minimum, so don't set it + sz_hints->base_width = BASE_W; + sz_hints->base_height = BASE_H; +*/ + sz_hints->win_gravity = NorthWestGravity; + + if (linuxvars.settings.set_window_pos){ + sz_hints->flags |= USPosition; + sz_hints->x = linuxvars.settings.window_x; + sz_hints->y = linuxvars.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.XDisplay, + linuxvars.XWindow, + &win_name, NULL, + argv, argc, + sz_hints, wm_hints, cl_hints + ); + + XFree(win_name.value); + + XFree(sz_hints); + XFree(wm_hints); + XFree(cl_hints); + + LinuxSetIcon(linuxvars.XDisplay, linuxvars.XWindow); + + //NOTE(inso): make the window visible + XMapWindow(linuxvars.XDisplay, linuxvars.XWindow); + + b32 IsLegacy = false; + GLXContext GLContext = + InitializeOpenGLContext(linuxvars.XDisplay, linuxvars.XWindow, Config.BestConfig, IsLegacy); + + XRaiseWindow(linuxvars.XDisplay, linuxvars.XWindow); + + if (linuxvars.settings.set_window_pos){ + XMoveWindow( + linuxvars.XDisplay, + linuxvars.XWindow, + linuxvars.settings.window_x, + linuxvars.settings.window_y + ); + } + + if (linuxvars.settings.maximize_window){ + LinuxMaximizeWindow(linuxvars.XDisplay, linuxvars.XWindow, 1); + } + else if(linuxvars.settings.fullscreen_window){ + LinuxToggleFullscreen(linuxvars.XDisplay, linuxvars.XWindow); + } + + XSync(linuxvars.XDisplay, False); + + XWindowAttributes WinAttribs; + if (XGetWindowAttributes(linuxvars.XDisplay, linuxvars.XWindow, &WinAttribs)) + { + *WinWidth = WinAttribs.width; + *WinHeight = WinAttribs.height; + } + + Atom wm_protos[] = { + linuxvars.atom_WM_DELETE_WINDOW, + linuxvars.atom__NET_WM_PING + }; + + XSetWMProtocols(linuxvars.XDisplay, linuxvars.XWindow, wm_protos, 2); +} + +internal void +LinuxHandleX11Events(void) +{ + static XEvent PrevEvent = {}; + b32 should_step = 0; + + while(XPending(linuxvars.XDisplay)) + { + XEvent Event; + XNextEvent(linuxvars.XDisplay, &Event); + + if (XFilterEvent(&Event, None) == True){ + continue; + } + + switch (Event.type){ + case KeyPress: { + should_step = 1; + + b32 is_hold = (PrevEvent.type == KeyRelease && + PrevEvent.xkey.time == Event.xkey.time && + PrevEvent.xkey.keycode == Event.xkey.keycode); + + b8 mods[MDFR_INDEX_COUNT] = {}; + mods[MDFR_HOLD_INDEX] = is_hold; + + if(Event.xkey.state & ShiftMask) mods[MDFR_SHIFT_INDEX] = 1; + if(Event.xkey.state & ControlMask) mods[MDFR_CONTROL_INDEX] = 1; + if(Event.xkey.state & LockMask) mods[MDFR_CAPS_INDEX] = 1; + if(Event.xkey.state & Mod1Mask) mods[MDFR_ALT_INDEX] = 1; + + Event.xkey.state &= ~(ControlMask); + + Status status; + KeySym keysym = NoSymbol; + char buff[32] = {}; + + Xutf8LookupString( + linuxvars.input_context, + &Event.xkey, + buff, + sizeof(buff) - 1, + &keysym, + &status + ); + + if(status == XBufferOverflow){ + //TODO(inso): handle properly + Xutf8ResetIC(linuxvars.input_context); + XSetICFocus(linuxvars.input_context); + fputs("FIXME: XBufferOverflow from LookupString.\n", stderr); + } + + u8 key = *buff, key_no_caps = key; + + if(mods[MDFR_CAPS_INDEX] && status == XLookupBoth && Event.xkey.keycode){ + char buff_no_caps[32] = {}; + Event.xkey.state &= ~(LockMask); + + XLookupString( + &Event.xkey, + buff_no_caps, + sizeof(buff_no_caps) - 1, + NULL, + NULL + ); + + if(*buff_no_caps){ + key_no_caps = *buff_no_caps; + } + } + + if(key == '\r') key = '\n'; + if(key_no_caps == '\r') key_no_caps = '\n'; + + // don't push modifiers + if(keysym >= XK_Shift_L && keysym <= XK_Hyper_R){ + break; + } + + if(keysym == XK_ISO_Left_Tab){ + key = key_no_caps = '\t'; + mods[MDFR_SHIFT_INDEX] = 1; + } + + u8 special_key = keycode_lookup_table[(u8)Event.xkey.keycode]; + + if(special_key){ + LinuxPushKey(special_key, 0, 0, &mods, is_hold); + } else if(key < 128){ + LinuxPushKey(key, key, key_no_caps, &mods, is_hold); + } else { + LinuxPushKey(0, 0, 0, &mods, is_hold); + } + }break; + + case KeyRelease: { + should_step = 1; + }break; + + case MotionNotify: { + should_step = 1; + linuxvars.input.mouse.x = Event.xmotion.x; + linuxvars.input.mouse.y = Event.xmotion.y; + }break; + + case ButtonPress: { + should_step = 1; + switch(Event.xbutton.button){ + case Button1: { + linuxvars.input.mouse.press_l = 1; + linuxvars.input.mouse.l = 1; + } break; + case Button3: { + linuxvars.input.mouse.press_r = 1; + linuxvars.input.mouse.r = 1; + } break; + + //NOTE(inso): scroll up + case Button4: { + linuxvars.input.mouse.wheel = 1; + }break; + + //NOTE(inso): scroll down + case Button5: { + linuxvars.input.mouse.wheel = -1; + }break; + } + }break; + + case ButtonRelease: { + should_step = 1; + switch(Event.xbutton.button){ + case Button1: { + linuxvars.input.mouse.release_l = 1; + linuxvars.input.mouse.l = 0; + } break; + case Button3: { + linuxvars.input.mouse.release_r = 1; + linuxvars.input.mouse.r = 0; + } break; + } + }break; + + case EnterNotify: { + should_step = 1; + linuxvars.input.mouse.out_of_window = 0; + }break; + + case LeaveNotify: { + should_step = 1; + linuxvars.input.mouse.out_of_window = 1; + }break; + + case FocusIn: + case FocusOut: { + should_step = 1; + linuxvars.input.mouse.l = 0; + linuxvars.input.mouse.r = 0; + }break; + + case ConfigureNotify: { + should_step = 1; + 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); + LinuxKeycodeInit(linuxvars.XDisplay); + } + }break; + + case ClientMessage: { + if ((Atom)Event.xclient.data.l[0] == linuxvars.atom_WM_DELETE_WINDOW) { + should_step = 1; + linuxvars.keep_running = 0; + } + else if ((Atom)Event.xclient.data.l[0] == linuxvars.atom__NET_WM_PING) { + Event.xclient.window = DefaultRootWindow(linuxvars.XDisplay); + XSendEvent( + linuxvars.XDisplay, + Event.xclient.window, + False, + SubstructureRedirectMask | SubstructureNotifyMask, + &Event + ); + } + }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; + + if ( + linuxvars.clipboard_outgoing.size && + request.selection == linuxvars.atom_CLIPBOARD && + request.property != None && + request.display && + request.requestor + ){ + Atom atoms[] = { + XA_STRING, + linuxvars.atom_UTF8_STRING + }; + + if(request.target == linuxvars.atom_TARGETS){ + + XChangeProperty( + request.display, + request.requestor, + request.property, + XA_ATOM, + 32, + PropModeReplace, + (u8*)atoms, + ArrayCount(atoms) + ); + + response.property = request.property; + + } else { + b32 found = false; + for(int i = 0; i < ArrayCount(atoms); ++i){ + if(request.target == atoms[i]){ + found = true; + break; + } + } + + if(found){ + XChangeProperty( + request.display, + request.requestor, + request.property, + request.target, + 8, + PropModeReplace, + (u8*)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); + should_step = 1; + linuxvars.new_clipboard = 1; + XFree(data); + XDeleteProperty(linuxvars.XDisplay, linuxvars.XWindow, linuxvars.atom_CLIPBOARD); + } + } + }break; + + case Expose: + case VisibilityNotify: { + should_step = 1; + }break; + + default: { + if(Event.type == linuxvars.xfixes_selection_event){ + XFixesSelectionNotifyEvent* sne = (XFixesSelectionNotifyEvent*)&Event; + if(sne->subtype == XFixesSelectionNotify && sne->owner != linuxvars.XWindow){ + XConvertSelection( + linuxvars.XDisplay, + linuxvars.atom_CLIPBOARD, + linuxvars.atom_UTF8_STRING, + linuxvars.atom_CLIPBOARD, + linuxvars.XWindow, + CurrentTime + ); + } + } + }break; + } + + PrevEvent = Event; + } + + if(should_step){ + LinuxScheduleStep(); + } +} + +// +// Entry point +// + +int +main(int argc, char **argv) +{ + // + // System & Memory init + // + +#if FRED_INTERNAL + linuxvars.internal_bubble.next = &linuxvars.internal_bubble; + linuxvars.internal_bubble.prev = &linuxvars.internal_bubble; + linuxvars.internal_bubble.flags = 0; + + pthread_mutex_init(&linuxvars.DEBUG_sysmem_lock, 0); +#endif + + char base_dir_mem[PATH_MAX]; + String base_dir = make_fixed_width_string(base_dir_mem); + + if (!LinuxLoadAppCode(&base_dir)){ + LinuxFatalErrorMsg("Could not load '4ed_app.so'. This file should be in the same directory as the main '4ed' executable."); + return 99; + } + + LinuxLoadSystemCode(); + LinuxLoadRenderCode(); + + 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); + memory_vars.user_memory_size = Mbytes(2); + memory_vars.user_memory = system_get_memory(memory_vars.user_memory_size); + + linuxvars.target.max = Mbytes(1); + linuxvars.target.push_buffer = (char*)system_get_memory(linuxvars.target.max); + + if(memory_vars.vars_memory == NULL || memory_vars.target_memory == NULL || memory_vars.user_memory == NULL || linuxvars.target.push_buffer == NULL){ + LinuxFatalErrorMsg("Could not allocate sufficient memory. Please make sure you have atleast 512Mb of RAM free. (This requirement will be relaxed in the future)."); + exit(1); + } + + init_shared_vars(); + + // + // Read command line + // + + char* cwd = get_current_dir_name(); + if(!cwd){ + char buf[1024]; + snprintf(buf, sizeof(buf), "Call to get_current_dir_name failed: %s", strerror(errno)); + LinuxFatalErrorMsg(buf); + return 1; + } + + String current_directory = make_string_slowly(cwd); + + 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(&linuxvars.system, + &memory_vars, + current_directory, + &linuxvars.settings, + &files, &file_count, + clparams); + + if (output_size > 0){ + // TODO(allen): crt free version + fprintf(stdout, "%.*s", output_size, (char*)memory_vars.target_memory); + } + if (output_size != 0){ + LinuxFatalErrorMsg("Error reading command-line arguments."); + return 1; + } + + sysshared_filter_real_files(files, file_count); + + // + // Custom layer linkage + // + +#ifdef FRED_SUPER + + char *custom_file_default = "4coder_custom.so"; + sysshared_to_binary_path(&base_dir, custom_file_default); + custom_file_default = base_dir.str; + + 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_alpha_4coder_version = (_Get_Version_Function*) + dlsym(linuxvars.custom, "get_alpha_4coder_version"); + + if (linuxvars.custom_api.get_alpha_4coder_version == 0 || + linuxvars.custom_api.get_alpha_4coder_version(MAJOR, MINOR, PATCH) == 0){ + LinuxFatalErrorMsg("Failed to load '4coder_custom.so': Version mismatch. Try rebuilding it with 'buildsuper.sh'."); + exit(1); + } + else{ + linuxvars.custom_api.get_bindings = (Get_Binding_Data_Function*) + dlsym(linuxvars.custom, "get_bindings"); + linuxvars.custom_api.view_routine = (View_Routine_Function*) + dlsym(linuxvars.custom, "view_routine"); + + if (linuxvars.custom_api.get_bindings == 0){ + LinuxFatalErrorMsg("Failed to load '4coder_custom.so': " + "It does not export the required 'get_bindings' function. " + "Try rebuilding it with 'buildsuper.sh'."); + exit(1); + } + else{ + fprintf(stderr, "Successfully loaded 4coder_custom.so\n"); + } + } + } else { + char buf[4096]; + const char* error = dlerror(); + snprintf(buf, sizeof(buf), "Error loading custom: %s. " + "Make sure this file is in the same directory as the main '4ed' executable.", + error ? error : "'4coder_custom.so' missing"); + LinuxFatalErrorMsg(buf); + exit(1); + } +#else + linuxvars.custom_api.get_bindings = get_bindings; +#endif + + linuxvars.custom_api.view_routine = 0; + +#if 0 + if (linuxvars.custom_api.view_routine == 0){ + linuxvars.custom_api.view_routine = view_routine; + } +#endif + + // + // Coroutine / Thread / Semaphore / Mutex init + // + + linuxvars.coroutine_free = linuxvars.coroutine_data; + for (i32 i = 0; i+1 < ArrayCount(linuxvars.coroutine_data); ++i){ + linuxvars.coroutine_data[i].next = linuxvars.coroutine_data + i + 1; + } + + const size_t stack_size = Mbytes(2); + for (i32 i = 0; i < ArrayCount(linuxvars.coroutine_data); ++i){ + linuxvars.coroutine_data[i].stack.ss_size = stack_size; + linuxvars.coroutine_data[i].stack.ss_sp = system_get_memory(stack_size); + } + + Thread_Context background[4] = {}; + linuxvars.groups[BACKGROUND_THREADS].threads = background; + linuxvars.groups[BACKGROUND_THREADS].count = ArrayCount(background); + linuxvars.groups[BACKGROUND_THREADS].cancel_lock0 = CANCEL_LOCK0; + linuxvars.groups[BACKGROUND_THREADS].cancel_cv0 = 0; + + Thread_Memory thread_memory[ArrayCount(background)]; + linuxvars.thread_memory = thread_memory; + + sem_init(&linuxvars.thread_semaphore, 0, 0); + linuxvars.queues[BACKGROUND_THREADS].semaphore = LinuxSemToHandle(&linuxvars.thread_semaphore); + + for(i32 i = 0; i < linuxvars.groups[BACKGROUND_THREADS].count; ++i){ + Thread_Context *thread = linuxvars.groups[BACKGROUND_THREADS].threads + i; + thread->id = i + 1; + thread->group_id = BACKGROUND_THREADS; + + Thread_Memory *memory = linuxvars.thread_memory + i; + *memory = thread_memory_zero(); + memory->id = thread->id; + + thread->queue = &linuxvars.queues[BACKGROUND_THREADS]; + pthread_create(&thread->handle, NULL, &JobThreadProc, thread); + } + + initialize_unbounded_queue(&linuxvars.groups[BACKGROUND_THREADS].queue); + + for(i32 i = 0; i < LOCK_COUNT; ++i){ + pthread_mutex_init(linuxvars.locks + i, NULL); + } + + for(i32 i = 0; i < ArrayCount(linuxvars.conds); ++i){ + pthread_cond_init(linuxvars.conds + i, NULL); + } + + // + // X11 init + // + + linuxvars.XDisplay = XOpenDisplay(0); + if(!linuxvars.XDisplay){ + // NOTE(inso): probably not worth trying the popup in this case... + fprintf(stderr, "Can't open display!\n"); + return 1; + } + +#define LOAD_ATOM(x) linuxvars.atom_##x = XInternAtom(linuxvars.XDisplay, #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 SUPPORT_DPI + linuxvars.dpi_x = linuxvars.dpi_y = LinuxGetXSettingsDPI(linuxvars.XDisplay, DefaultScreen(linuxvars.XDisplay)); + + // fallback + if(linuxvars.dpi_x == -1){ + int scr = DefaultScreen(linuxvars.XDisplay); + + int dw = DisplayWidth(linuxvars.XDisplay, scr); + int dh = DisplayHeight(linuxvars.XDisplay, scr); + + int dw_mm = DisplayWidthMM(linuxvars.XDisplay, scr); + int dh_mm = DisplayHeightMM(linuxvars.XDisplay, scr); + + linuxvars.dpi_x = dw_mm ? dw / (dw_mm / 25.4) : 96; + linuxvars.dpi_y = dh_mm ? dh / (dh_mm / 25.4) : 96; + + fprintf(stderr, "%dx%d - %dmmx%dmm DPI: %dx%d\n", dw, dh, dw_mm, dh_mm, linuxvars.dpi_x, linuxvars.dpi_y); + } else { + fprintf(stderr, "DPI from XSETTINGS: %d\n", linuxvars.dpi_x); + } +#endif + + int WinWidth, WinHeight; + if(!LinuxX11WindowInit(argc, argv, &WinWidth, &WinHeight)){ + return 1; + } + + int xfixes_version_unused, xfixes_err_unused; + linuxvars.has_xfixes = XQueryExtension( + linuxvars.XDisplay, + "XFIXES", + &xfixes_version_unused, + &linuxvars.xfixes_selection_event, + &xfixes_err_unused + ) == True; + + if(linuxvars.has_xfixes){ + XFixesSelectSelectionInput( + linuxvars.XDisplay, + linuxvars.XWindow, + linuxvars.atom_CLIPBOARD, + XFixesSetSelectionOwnerNotifyMask + ); + } else { + fputs("Your X server doesn't support XFIXES, mention this fact if you report any clipboard-related issues.\n", stderr); + } + + Init_Input_Result input_result = + LinuxInputInit(linuxvars.XDisplay, linuxvars.XWindow); + + linuxvars.input_method = input_result.input_method; + linuxvars.input_style = input_result.best_style; + linuxvars.input_context = input_result.xic; + + LinuxKeycodeInit(linuxvars.XDisplay); + + Cursor xcursors[APP_MOUSE_CURSOR_COUNT] = { + None, + XCreateFontCursor(linuxvars.XDisplay, XC_arrow), + XCreateFontCursor(linuxvars.XDisplay, XC_xterm), + XCreateFontCursor(linuxvars.XDisplay, XC_sb_h_double_arrow), + XCreateFontCursor(linuxvars.XDisplay, XC_sb_v_double_arrow) + }; + + { + char data = 0; + XColor c = {}; + Pixmap p = XCreateBitmapFromData(linuxvars.XDisplay, linuxvars.XWindow, &data, 1, 1); + + linuxvars.hidden_cursor = XCreatePixmapCursor(linuxvars.XDisplay, p, p, &c, &c, 0, 0); + + XFreePixmap(linuxvars.XDisplay, p); + } + + + // + // Epoll init + // + + linuxvars.x11_fd = ConnectionNumber(linuxvars.XDisplay); + linuxvars.inotify_fd = inotify_init1(IN_NONBLOCK); + linuxvars.step_event_fd = eventfd(0, EFD_NONBLOCK); + linuxvars.step_timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); + + linuxvars.epoll = epoll_create(16); + + { + struct epoll_event e = {}; + e.events = EPOLLIN | EPOLLET; + + e.data.u64 = LINUX_4ED_EVENT_X11; + epoll_ctl(linuxvars.epoll, EPOLL_CTL_ADD, linuxvars.x11_fd, &e); + + e.data.u64 = LINUX_4ED_EVENT_STEP; + epoll_ctl(linuxvars.epoll, EPOLL_CTL_ADD, linuxvars.step_event_fd, &e); + + e.data.u64 = LINUX_4ED_EVENT_STEP_TIMER; + epoll_ctl(linuxvars.epoll, EPOLL_CTL_ADD, linuxvars.step_timer_fd, &e); + } + + // + // App init + // + + XAddConnectionWatch(linuxvars.XDisplay, &LinuxX11ConnectionWatch, NULL); + + linuxvars.app.init(&linuxvars.system, + &linuxvars.target, + &memory_vars, + linuxvars.clipboard_contents, + current_directory, + linuxvars.custom_api); + + LinuxResizeTarget(WinWidth, WinHeight); + + // + // Main loop + // + + system_acquire_lock(FRAME_LOCK); + + LinuxScheduleStep(); + + linuxvars.keep_running = 1; + linuxvars.input.first_step = 1; + linuxvars.input.dt = (frame_useconds / 1000000.f); + + while(1){ + + if(XEventsQueued(linuxvars.XDisplay, QueuedAlready)){ + LinuxHandleX11Events(); + } + + system_release_lock(FRAME_LOCK); + + struct epoll_event events[16]; + int num_events = epoll_wait(linuxvars.epoll, events, ArrayCount(events), -1); + + system_acquire_lock(FRAME_LOCK); + + if(num_events == -1){ + if(errno != EINTR){ + perror("epoll_wait"); + } + continue; + } + + b32 do_step = 0; + + for(int i = 0; i < num_events; ++i){ + + int fd = events[i].data.u64 & UINT32_MAX; + u64 type = events[i].data.u64 & ~fd; + + switch(type){ + case LINUX_4ED_EVENT_X11: { + LinuxHandleX11Events(); + } break; + + case LINUX_4ED_EVENT_X11_INTERNAL: { + XProcessInternalConnection(linuxvars.XDisplay, fd); + } break; + + case LINUX_4ED_EVENT_STEP: { + u64 ev; + int ret; + do { + ret = read(linuxvars.step_event_fd, &ev, 8); + } while(ret != -1 || errno != EAGAIN); + do_step = 1; + } break; + + case LINUX_4ED_EVENT_STEP_TIMER: { + u64 count; + int ret; + do { + ret = read(linuxvars.step_timer_fd, &count, 8); + } while(ret != -1 || errno != EAGAIN); + do_step = 1; + } break; + + case LINUX_4ED_EVENT_CLI: { + LinuxScheduleStep(); + } break; + } + } + + if(do_step){ + linuxvars.last_step = system_now_time(); + + if(linuxvars.input.first_step || !linuxvars.has_xfixes){ + XConvertSelection( + linuxvars.XDisplay, + linuxvars.atom_CLIPBOARD, + linuxvars.atom_UTF8_STRING, + linuxvars.atom_CLIPBOARD, + linuxvars.XWindow, + CurrentTime + ); + } + + Application_Step_Result result = {}; + result.mouse_cursor_type = APP_MOUSE_CURSOR_DEFAULT; + result.trying_to_kill = !linuxvars.keep_running; + + if(linuxvars.new_clipboard){ + linuxvars.input.clipboard = linuxvars.clipboard_contents; + linuxvars.new_clipboard = 0; + } else { + linuxvars.input.clipboard = null_string; + } + + linuxvars.app.step( + &linuxvars.system, + &linuxvars.target, + &memory_vars, + &linuxvars.input, + &result + ); + + if(result.perform_kill){ + break; + } else { + linuxvars.keep_running = 1; + } + + if(result.animating){ + LinuxScheduleStep(); + } + + LinuxRedrawTarget(); + + if(result.mouse_cursor_type != linuxvars.cursor && !linuxvars.input.mouse.l){ + Cursor c = xcursors[result.mouse_cursor_type]; + if(!linuxvars.hide_cursor){ + XDefineCursor(linuxvars.XDisplay, linuxvars.XWindow, c); + } + linuxvars.cursor = result.mouse_cursor_type; + } + + flush_thread_group(BACKGROUND_THREADS); + + linuxvars.input.first_step = 0; + linuxvars.input.keys = key_input_data_zero(); + linuxvars.input.mouse.press_l = 0; + linuxvars.input.mouse.release_l = 0; + linuxvars.input.mouse.press_r = 0; + linuxvars.input.mouse.release_r = 0; + linuxvars.input.mouse.wheel = 0; + } + } + + return 0; +} + +// BOTTOM +// vim: expandtab:ts=4:sts=4:sw=4 + diff --git a/metagen b/metagen new file mode 100755 index 00000000..50a89aca Binary files /dev/null and b/metagen differ diff --git a/original.Makefile b/original.Makefile index d1052eda..a98ca7ea 100644 --- a/original.Makefile +++ b/original.Makefile @@ -8,7 +8,7 @@ FLAGS := -D_GNU_SOURCE -fPIC -fno-threadsafe-statics -pthread -I../foreign # main stuff debug: FLAGS += -DFRED_INTERNAL=1 -DFRED_SUPER=1 -g -O0 -debug: ../metagen ../4ed_app.so ../4ed +debug: ../4ed_app.so ../4ed ../metagen: $(CPP_FILES) $(C_FILES) $(H_FILES) g++ $(WARNINGS) $(FLAGS) 4ed_metagen.cpp -iquoteforeign -o $@ @@ -42,8 +42,8 @@ super32: alpha32 PACKAGE_FILES := ../4ed ../4ed_app.so README.txt TODO.txt -../4coder_super.zip: PACKAGE_FILES += 4coder_*.h 4coder_*.cpp buildsuper.sh SUPERREADME.txt -../4coder_super32.zip: PACKAGE_FILES += 4coder_*.h 4coder_*.cpp buildsuper.sh SUPERREADME.txt +../4coder_super.zip: PACKAGE_FILES += 4coder_*.h 4coder_*.cpp buildsuper.sh +../4coder_super32.zip: PACKAGE_FILES += 4coder_*.h 4coder_*.cpp buildsuper.sh ../4coder_%.zip: % zip -j $@ $(PACKAGE_FILES)