4coder/platform_linux/linux_4ed.cpp

785 lines
20 KiB
C++

/*
* 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 <dirent.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/epoll.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#define Cursor XCursor
#undef function
#include <X11/Xlib.h>
#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){
}