/* * chr - Andrew Chronister * * 12.19.2019 * * Updated linux layer for 4coder * */ // TOP #define FPS 60 #define frame_useconds (1000000 / FPS) #include "4coder_base_types.h" #include "4coder_version.h" #include "4coder_events.h" #include "4coder_table.h" #include "4coder_types.h" #include "4coder_default_colors.h" #include "4coder_system_types.h" #define STATIC_LINK_API #include "generated/system_api.h" #include "4ed_font_interface.h" #define STATIC_LINK_API #include "generated/graphics_api.h" #define STATIC_LINK_API #include "generated/font_api.h" #include "4ed_font_set.h" #include "4ed_render_target.h" #include "4ed_search_list.h" #include "4ed.h" #include "generated/system_api.cpp" #include "generated/graphics_api.cpp" #include "generated/font_api.cpp" #include "4coder_base_types.cpp" #include "4coder_stringf.cpp" #include "4coder_events.cpp" #include "4coder_hash_functions.cpp" #include "4coder_table.cpp" #include "4coder_log.cpp" #include "4ed_search_list.cpp" #include #include #include #include #include #include #include #include #include #include #include #include #define Cursor XCursor #undef function #include #define function static #undef Cursor //////////////////////////// struct Linux_Vars { Display *XDisplay; Window XWindow; int epoll; b32 is_full_screen; b32 should_be_full_screen; Application_Mouse_Cursor cursor; b32 hide_cursor; XCursor hidden_cursor; Node free_linux_objects; System_Mutex global_frame_mutex; }; enum { LINUX_4ED_EVENT_CLI = (UINT64_C(5) << 32), }; typedef i32 Linux_Object_Kind; enum { LinuxObjectKind_Thread = 1, LinuxObjectKind_Mutex = 2, LinuxObjectKind_ConditionVariable = 3, }; struct Linux_Object { Linux_Object_Kind kind; Node node; union { struct { pthread_t pthread; Thread_Function* proc; void* ptr; } thread; pthread_mutex_t mutex; pthread_cond_t condition_variable; }; }; //////////////////////////// global Linux_Vars linuxvars; //////////////////////////// internal Linux_Object* linux_alloc_object(Linux_Object_Kind kind){ Linux_Object* result = NULL; if (linuxvars.free_linux_objects.next != &linuxvars.free_linux_objects) { result = CastFromMember(Linux_Object, node, linuxvars.free_linux_objects.next); } if (result == NULL) { i32 count = 512; Linux_Object* objects = (Linux_Object*)system_memory_allocate( sizeof(Linux_Object), file_name_line_number_lit_u8); objects[0].node.prev = &linuxvars.free_linux_objects; for (i32 i = 1; i < count; ++i) { objects[i - 1].node.next = &objects[i].node; objects[i].node.prev = &objects[i - 1].node; } objects[count - 1].node.next = &linuxvars.free_linux_objects; linuxvars.free_linux_objects.prev = &objects[count - 1].node; result = CastFromMember(Linux_Object, node, linuxvars.free_linux_objects.next); } Assert(result != 0); dll_remove(&result->node); block_zero_struct(result); result->kind = kind; return result; } internal void linux_free_object(Linux_Object *object){ if (object->node.next != 0){ dll_remove(&object->node); } dll_insert(&linuxvars.free_linux_objects, &object->node); } internal system_get_path_sig(){ // Arena* arena, System_Path_Code path_code String_Const_u8 result = {}; switch (path_code){ case SystemPath_CurrentDirectory: { // glibc extension: getcwd allocates its own memory if passed NULL char *working_dir = getcwd(NULL, 0); u64 working_dir_len = cstring_length(working_dir); u8 *out = push_array(arena, u8, working_dir_len); block_copy(out, working_dir, working_dir_len); free(working_dir); result = SCu8(out, working_dir_len); }break; case SystemPath_Binary: { // linux-specific: binary path symlinked at /proc/self/exe ssize_t binary_path_len = readlink("/proc/self/exe", NULL, 0); u8* out = push_array(arena, u8, binary_path_len); readlink("/proc/self/exe", (char*)out, binary_path_len); String_u8 out_str = Su8(out, binary_path_len); out_str.string = string_remove_last_folder(out_str.string); string_null_terminate(&out_str); result = out_str.string; }break; } return(result); } internal system_get_canonical_sig(){ // TODO(andrew): Resolve symlinks ? // TODO(andrew): Resolve . and .. in paths // TODO(andrew): Use realpath(3) return name; } internal int linux_system_get_file_list_filter(const struct dirent *dirent) { String_Const_u8 file_name = SCu8((u8*)dirent->d_name); if (string_match(file_name, string_u8_litexpr("."))) { return 0; } else if (string_match(file_name, string_u8_litexpr(".."))) { return 0; } return 1; } internal int linux_u64_from_timespec(const struct timespec timespec) { return timespec.tv_nsec + 1000000000 * timespec.tv_sec; } internal File_Attribute_Flag linux_convert_file_attribute_flags(int mode) { File_Attribute_Flag result = {}; MovFlag(mode, S_IFDIR, result, FileAttribute_IsDirectory); return result; } internal File_Attributes linux_file_attributes_from_struct_stat(struct stat file_stat) { File_Attributes result = {}; result.size = file_stat.st_size; result.last_write_time = linux_u64_from_timespec(file_stat.st_mtim); result.flags = linux_convert_file_attribute_flags(file_stat.st_mode); return result; } internal system_get_file_list_sig(){ File_List result = {}; String_Const_u8 search_pattern = {}; if (character_is_slash(string_get_character(directory, directory.size - 1))){ search_pattern = push_u8_stringf(arena, "%.*s*", string_expand(directory)); } else{ search_pattern = push_u8_stringf(arena, "%.*s/*", string_expand(directory)); } struct dirent** dir_ents = NULL; int num_ents = scandir( (const char*)search_pattern.str, &dir_ents, linux_system_get_file_list_filter, alphasort); File_Info *first = 0; File_Info *last = 0; for (int i = 0; i < num_ents; ++i) { struct dirent* dirent = dir_ents[i]; File_Info *info = push_array(arena, File_Info, 1); sll_queue_push(first, last, info); info->file_name = SCu8((u8*)dirent->d_name); struct stat file_stat; stat((const char*)dirent->d_name, &file_stat); info->attributes = linux_file_attributes_from_struct_stat(file_stat); } result.infos = push_array(arena, File_Info*, num_ents); result.count = num_ents; i32 info_index = 0; for (File_Info* node = first; node != NULL; node = node->next) { result.infos[info_index] = node; info_index += 1; } return result; } internal system_quick_file_attributes_sig(){ struct stat file_stat; stat((const char*)file_name.str, &file_stat); return linux_file_attributes_from_struct_stat(file_stat); } internal system_load_handle_sig(){ int fd = open(file_name, O_RDONLY); if (fd != -1) { *(int*)out = fd; return true; } return false; } internal system_load_attributes_sig(){ struct stat file_stat; fstat(*(int*)&handle, &file_stat); return linux_file_attributes_from_struct_stat(file_stat); } internal system_load_file_sig(){ int fd = *(int*)&handle; int bytes_read = read(fd, buffer, size); if (bytes_read == size) { return true; } return false; } internal system_load_close_sig(){ int fd = *(int*)&handle; return close(fd) == 0; } internal system_save_file_sig(){ File_Attributes result = {}; int fd = open(file_name, O_WRONLY, O_CREAT); if (fd != -1) { int bytes_written = write(fd, data.str, data.size); if (bytes_written != -1) { struct stat file_stat; fstat(fd, &file_stat); return linux_file_attributes_from_struct_stat(file_stat); } } return result; } typedef void* shared_object_handle; internal system_load_library_sig(){ shared_object_handle library = dlopen((const char*)file_name.str, RTLD_LAZY); if (library != NULL) { *(shared_object_handle*)out = library; return true; } return false; } internal system_release_library_sig(){ return dlclose(*(shared_object_handle*)&handle) == 0; } internal system_get_proc_sig(){ return (Void_Func*)dlsym(*(shared_object_handle*)&handle, proc_name); } internal system_now_time_sig(){ struct timespec time; clock_gettime(CLOCK_MONOTONIC_RAW, &time); return linux_u64_from_timespec(time); } // system_wake_up_timer_create_sig // system_wake_up_timer_release_sig // system_wake_up_timer_set_sig // system_signal_step_sig internal system_sleep_sig(){ struct timespec requested; struct timespec remaining; u64 seconds = microseconds / Million(1); requested.tv_sec = seconds; requested.tv_nsec = (microseconds - seconds * Million(1)) * Thousand(1); nanosleep(&requested, &remaining); } // system_post_clipboard_sig internal system_cli_call_sig() { 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, 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(true); } internal system_cli_begin_update_sig() { // NOTE(inso): I don't think anything needs to be done here. } internal system_cli_update_step_sig(){ 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 system_cli_end_update_sig(){ pid_t pid = *(pid_t*)&cli->proc; b32 close_me = false; int status; if (pid && waitpid(pid, &status, WNOHANG) > 0){ cli->exit = WEXITSTATUS(status); close_me = true; close(*(int*)&cli->out_read); close(*(int*)&cli->out_write); struct epoll_event e = {}; epoll_ctl(linuxvars.epoll, EPOLL_CTL_DEL, *(int*)&cli->out_read, &e); } return(close_me); } // system_open_color_picker internal int linux_get_xsettings_dpi(Display* dpy, int screen) { struct XSettingHeader { u8 type; u8 pad0; u16 name_len; char name[0]; }; struct XSettings { u8 byte_order; u8 pad[3]; u32 serial; u32 num_settings; }; enum { XSettingsTypeInt, XSettingsTypeString, XSettingsTypeColor }; int dpi = -1; unsigned char* prop = NULL; char sel_buffer[64]; struct XSettings* xs; const char* p; snprintf(sel_buffer, sizeof(sel_buffer), "_XSETTINGS_S%d", screen); Atom XSET_SEL = XInternAtom(dpy, sel_buffer, True); Atom XSET_SET = XInternAtom(dpy, "_XSETTINGS_SETTINGS", True); if (XSET_SEL == None || XSET_SET == None){ //LOG("XSETTINGS unavailable.\n"); return(dpi); } Window xset_win = XGetSelectionOwner(dpy, XSET_SEL); if (xset_win == None){ // TODO(inso): listen for the ClientMessage about it becoming available? // there's not much point atm if DPI scaling is only done at startup goto out; } { Atom type; int fmt; unsigned long pad, num; if (XGetWindowProperty(dpy, xset_win, XSET_SET, 0, 1024, False, XSET_SET, &type, &fmt, &num, &pad, &prop) != Success){ //LOG("XSETTINGS: GetWindowProperty failed.\n"); goto out; } if (fmt != 8){ //LOG("XSETTINGS: Wrong format.\n"); goto out; } } xs = (struct XSettings*)prop; p = (char*)(xs + 1); if (xs->byte_order != 0){ //LOG("FIXME: XSETTINGS not host byte order?\n"); goto out; } for (int i = 0; i < xs->num_settings; ++i){ struct XSettingHeader* h = (struct XSettingHeader*)p; p += sizeof(struct XSettingHeader); p += h->name_len; p += ((4 - (h->name_len & 3)) & 3); p += 4; // serial switch (h->type){ case XSettingsTypeInt: { if (strncmp(h->name, "Xft/DPI", h->name_len) == 0){ dpi = *(i32*)p; if (dpi != -1) dpi /= 1024; } p += 4; } break; case XSettingsTypeString: { u32 len = *(u32*)p; p += 4; p += len; p += ((4 - (len & 3)) & 3); } break; case XSettingsTypeColor: { p += 8; } break; default: { //LOG("XSETTINGS: Got invalid type...\n"); goto out; } break; } } out: if (prop){ XFree(prop); } return dpi; } // internal // system_get_screen_scale_factor_sig(){ // return linux_get_xsettings_dpi() / 96.0f; // } internal void* linux_thread_proc_start(void* arg) { Linux_Object* info = (Linux_Object*)arg; Assert(info->kind == LinuxObjectKind_Thread); info->thread.proc(info->thread.ptr); return NULL; } internal system_thread_launch_sig(){ System_Thread result = {}; Linux_Object* thread_info = linux_alloc_object(LinuxObjectKind_Thread); thread_info->thread.proc = proc; thread_info->thread.ptr = ptr; pthread_attr_t thread_attr; pthread_attr_init(&thread_attr); int create_result = pthread_create( &thread_info->thread.pthread, &thread_attr, linux_thread_proc_start, (void*)thread_info); pthread_attr_destroy(&thread_attr); // TODO(andrew): Need to wait for thread to confirm it launched? if (create_result == 0) { static_assert(sizeof(Linux_Object*) <= sizeof(System_Thread)); *(Linux_Object**)&result = thread_info; return result; } return result; } internal system_thread_join_sig(){ Linux_Object* object = *(Linux_Object**)&thread; void* retval_ignored; int result = pthread_join(object->thread.pthread, &retval_ignored); } internal system_thread_free_sig(){ Linux_Object* object = *(Linux_Object**)&thread; Assert(object->kind == LinuxObjectKind_Thread); linux_free_object(object); } internal system_thread_get_id_sig(){ pthread_t tid = pthread_self(); Assert(tid <= (u64)max_i32); return (i32)tid; } internal system_acquire_global_frame_mutex_sig(){ if (tctx->kind == ThreadKind_AsyncTasks){ system_mutex_acquire(linuxvars.global_frame_mutex); } } internal system_release_global_frame_mutex_sig(){ if (tctx->kind == ThreadKind_AsyncTasks){ system_mutex_release(linuxvars.global_frame_mutex); } } internal system_mutex_make_sig(){ System_Mutex result = {}; Linux_Object* object = linux_alloc_object(LinuxObjectKind_Mutex); pthread_mutex_init(&object->mutex, NULL); *(Linux_Object**)&result = object; return result; } internal system_mutex_acquire_sig(){ Linux_Object* object = *(Linux_Object**)&mutex; Assert(object->kind == LinuxObjectKind_Mutex); pthread_mutex_lock(&object->mutex); } internal system_mutex_release_sig(){ Linux_Object* object = *(Linux_Object**)&mutex; Assert(object->kind == LinuxObjectKind_Mutex); pthread_mutex_unlock(&object->mutex); } internal system_mutex_free_sig(){ Linux_Object* object = *(Linux_Object**)&mutex; Assert(object->kind == LinuxObjectKind_Mutex); pthread_mutex_destroy(&object->mutex); linux_free_object(object); } internal system_condition_variable_make_sig(){ System_Condition_Variable result = {}; Linux_Object* object = linux_alloc_object(LinuxObjectKind_ConditionVariable); pthread_cond_init(&object->condition_variable, NULL); *(Linux_Object**)&result = object; return result; } internal system_condition_variable_wait_sig(){ Linux_Object* cv_object = *(Linux_Object**)&cv; Linux_Object* mutex_object = *(Linux_Object**)&mutex; Assert(cv_object->kind == LinuxObjectKind_ConditionVariable); Assert(mutex_object->kind == LinuxObjectKind_Mutex); pthread_cond_wait(&cv_object->condition_variable, &mutex_object->mutex); } internal system_condition_variable_signal_sig(){ Linux_Object* object = *(Linux_Object**)&cv; Assert(object->kind == LinuxObjectKind_ConditionVariable); pthread_cond_signal(&object->condition_variable); } internal system_condition_variable_free_sig(){ Linux_Object* object = *(Linux_Object**)&cv; Assert(object->kind == LinuxObjectKind_ConditionVariable); pthread_cond_destroy(&object->condition_variable); linux_free_object(object); } internal system_memory_allocate_sig(){ void* result = mmap( NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); // TODO(andrew): Allocation tracking? return result; } internal system_memory_set_protection_sig(){ int protect = 0; MovFlag(flags, MemProtect_Read, protect, PROT_READ); MovFlag(flags, MemProtect_Write, protect, PROT_WRITE); MovFlag(flags, MemProtect_Execute, protect, PROT_EXEC); int result = mprotect(ptr, size, protect); return result == 0; } internal system_memory_free_sig(){ munmap(ptr, size); } // system_memory_annotation_sig internal system_show_mouse_cursor_sig(){ linuxvars.hide_cursor = !show; XDefineCursor( linuxvars.XDisplay, linuxvars.XWindow, show ? None : linuxvars.hidden_cursor); } internal system_set_fullscreen_sig(){ linuxvars.should_be_full_screen = full_screen; return true; } internal system_is_fullscreen_sig(){ return linuxvars.is_full_screen; } // system_get_keyboard_modifiers_sig() internal void linux_handle_x11_events() { static XEvent prev_event = {}; b32 should_step = false; while (XPending(linuxvars.XDisplay)) { XEvent event; XNextEvent(linuxvars.XDisplay, &event); if (XFilterEvent(&event, None) == True){ continue; } switch (event.type) { } } } int main(int argc, char **argv){ }