4coder/platform_linux/linux_4ed.cpp

1593 lines
47 KiB
C++

/*
* chr - Andrew Chronister &
* inso - Alex Baines
*
* 12.19.2019
*
* Updated linux layer for 4coder
*
*/
// TOP
#define FPS 60
#define frame_useconds (1000000 / FPS)
#define SLASH '/'
#define DLL "so"
#include "4coder_base_types.h"
#include "4coder_version.h"
#include "4coder_events.h"
#include "4coder_table.h"
#include "4coder_types.h"
#include "4coder_default_colors.h"
#include "4coder_system_types.h"
#include "4ed_font_interface.h"
#define STATIC_LINK_API
#include "generated/system_api.h"
#define STATIC_LINK_API
#include "generated/graphics_api.h"
#define STATIC_LINK_API
#include "generated/font_api.h"
#include "4ed_font_set.h"
#include "4ed_render_target.h"
#include "4ed_search_list.h"
#include "4ed.h"
#include "generated/system_api.cpp"
#include "generated/graphics_api.cpp"
#include "generated/font_api.cpp"
#include "4coder_base_types.cpp"
#include "4coder_stringf.cpp"
#include "4coder_events.cpp"
#include "4coder_hash_functions.cpp"
#include "4coder_table.cpp"
#include "4coder_log.cpp"
#include "4coder_hash_functions.cpp"
#include "4coder_system_allocator.cpp"
#include "4coder_malloc_allocator.cpp"
#include "4coder_codepoint_map.cpp"
#include "4ed_mem.cpp"
#include "4ed_font_set.cpp"
#include "4ed_search_list.cpp"
#include "4ed_font_provider_freetype.h"
#include "4ed_font_provider_freetype.cpp"
#include <dirent.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <locale.h>
#include <errno.h>
#include <pthread.h>
#include <sys/epoll.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/timerfd.h>
#include <sys/eventfd.h>
#include <sys/syscall.h>
#define Cursor XCursor
#undef function
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>
#include <X11/Xatom.h>
#include <X11/extensions/Xfixes.h>
#include <X11/keysym.h>
#define function static
#undef Cursor
#undef internal
#include <fontconfig/fontconfig.h>
#define internal static
#include <GL/glx.h>
#include <GL/glext.h>
#ifdef INSO_DEBUG
#define LINUX_FN_DEBUG(fmt, ...) do { \
fprintf(stderr, "%s: " fmt "\n", __func__, ##__VA_ARGS__);\
} while (0)
// I want to see a message
#undef AssertBreak
#define AssertBreak(m) ({\
fprintf(stderr, "\n** ASSERTION FAILURE: %s:%d: %s\n\n", __FILE__, __LINE__, #m);\
*((volatile u64*)0) = 0xba771e70ad5;\
})
#else
#define LINUX_FN_DEBUG(...)
#endif
////////////////////////////
struct Linux_Input_Chunk_Transient {
Input_List event_list;
b8 mouse_l_press;
b8 mouse_l_release;
b8 mouse_r_press;
b8 mouse_r_release;
b8 out_of_window;
i8 mouse_wheel;
b8 trying_to_kill;
};
struct Linux_Input_Chunk_Persistent {
Vec2_i32 mouse;
Input_Modifier_Set_Fixed modifiers;
b8 mouse_l;
b8 mouse_r;
};
struct Linux_Input_Chunk {
Linux_Input_Chunk_Transient trans;
Linux_Input_Chunk_Persistent pers;
};
struct Linux_Vars {
Thread_Context tctx;
Arena *frame_arena;
Display* dpy;
Window win;
b32 has_xfixes;
int xfixes_selection_event;
XIM xim;
XIC xic;
FcConfig* fontconfig;
Linux_Input_Chunk input;
int epoll;
int step_event_fd;
int step_timer_fd;
u64 last_step_time;
Application_Mouse_Cursor cursor;
XCursor hidden_cursor;
i32 cursor_show;
i32 prev_cursor_show;
Node free_linux_objects;
Node timer_objects;
System_Mutex global_frame_mutex;
Arena clipboard_out_arena;
String_Const_u8 clipboard_contents;
b32 received_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;
};
global Linux_Vars linuxvars;
global Render_Target render_target;
////////////////////////////
// Defererencing an epoll_event's .data.ptr will always give one of these event types.
typedef i32 Epoll_Kind;
enum {
EPOLL_STEP_EVENT,
EPOLL_STEP_TIMER,
EPOLL_X11,
EPOLL_X11_INTERNAL,
EPOLL_CLI_PIPE,
EPOLL_USER_TIMER,
};
// Where per-event epoll data is not needed, .data.ptr will point to one of
// these static vars below.
// If per-event data is needed, container_of can be used on data.ptr
// to access the containing struct and all its other members.
internal Epoll_Kind epoll_tag_step_event = EPOLL_STEP_EVENT;
internal Epoll_Kind epoll_tag_step_timer = EPOLL_STEP_TIMER;
internal Epoll_Kind epoll_tag_x11 = EPOLL_X11;
internal Epoll_Kind epoll_tag_x11_internal = EPOLL_X11_INTERNAL;
internal Epoll_Kind epoll_tag_cli_pipe = EPOLL_CLI_PIPE;
////////////////////////////
typedef i32 Linux_Object_Kind;
enum {
LinuxObjectKind_ERROR = 0,
LinuxObjectKind_Timer = 1,
LinuxObjectKind_Thread = 2,
LinuxObjectKind_Mutex = 3,
LinuxObjectKind_ConditionVariable = 4,
};
struct Linux_Object {
Linux_Object_Kind kind;
Node node;
union {
struct {
int fd;
Epoll_Kind epoll_tag;
} timer;
struct {
pthread_t pthread;
Thread_Function* proc;
void* ptr;
} thread;
pthread_mutex_t mutex;
pthread_cond_t condition_variable;
};
};
Linux_Object*
handle_to_object(Plat_Handle ph){
return *(Linux_Object**)&ph;
}
internal Linux_Object*
linux_alloc_object(Linux_Object_Kind kind){
Linux_Object* result = NULL;
if (linuxvars.free_linux_objects.next != &linuxvars.free_linux_objects) {
result = CastFromMember(Linux_Object, node, linuxvars.free_linux_objects.next);
}
if (result == NULL) {
i32 count = 512;
Linux_Object* objects = (Linux_Object*)system_memory_allocate(
sizeof(Linux_Object) * count,
file_name_line_number_lit_u8
);
objects[0].node.prev = &linuxvars.free_linux_objects;
linuxvars.free_linux_objects.next = &objects[0].node;
for (i32 i = 1; i < count; ++i) {
objects[i - 1].node.next = &objects[i].node;
objects[i].node.prev = &objects[i - 1].node;
}
objects[count - 1].node.next = &linuxvars.free_linux_objects;
linuxvars.free_linux_objects.prev = &objects[count - 1].node;
result = CastFromMember(Linux_Object, node, linuxvars.free_linux_objects.next);
}
Assert(result != 0);
dll_remove(&result->node);
block_zero_struct(result);
result->kind = kind;
return result;
}
internal void
linux_free_object(Linux_Object *object){
if (object->node.next != 0){
dll_remove(&object->node);
}
dll_insert(&linuxvars.free_linux_objects, &object->node);
}
////////////////////////////
internal int
linux_compare_file_infos(File_Info** a, File_Info** b) {
b32 a_hidden = (*a)->file_name.str[0] == '.';
b32 b_hidden = (*b)->file_name.str[0] == '.';
// hidden files lower in list
if(a_hidden != b_hidden) {
return a_hidden - b_hidden;
}
// push_stringf seems to null terminate
return strcoll((char*)(*a)->file_name.str, (char*)(*b)->file_name.str);
}
internal int
linux_system_get_file_list_filter(const struct dirent *dirent) {
String_Const_u8 file_name = SCu8((u8*)dirent->d_name);
if (string_match(file_name, string_u8_litexpr("."))) {
return 0;
}
else if (string_match(file_name, string_u8_litexpr(".."))) {
return 0;
}
return 1;
}
internal int
linux_u64_from_timespec(const struct timespec timespec) {
return timespec.tv_nsec + UINT64_C(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 void
linux_schedule_step(){
u64 now = system_now_time();
u64 diff = (now - linuxvars.last_step_time);
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);
}
}
}
internal void
linux_set_wm_state(Atom one, Atom two, int mode){
//NOTE(inso): this will only work after the window has been 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 = linuxvars.win;
e.xclient.data.l[0] = mode;
e.xclient.data.l[1] = one;
e.xclient.data.l[2] = two;
e.xclient.data.l[3] = 1L;
XSendEvent(linuxvars.dpy,
RootWindow(linuxvars.dpy, 0),
0, SubstructureNotifyMask | SubstructureRedirectMask, &e);
}
internal int
linux_get_xsettings_dpi(Display* dpy, int screen){
struct XSettingHeader {
u8 type;
u8 pad0;
u16 name_len;
char name[0];
};
struct XSettings {
u8 byte_order;
u8 pad[3];
u32 serial;
u32 num_settings;
};
enum { XSettingsTypeInt, XSettingsTypeString, XSettingsTypeColor };
int dpi = -1;
unsigned char* prop = NULL;
char sel_buffer[64];
struct XSettings* xs;
const char* p;
snprintf(sel_buffer, sizeof(sel_buffer), "_XSETTINGS_S%d", screen);
Atom XSET_SEL = XInternAtom(dpy, sel_buffer, True);
Atom XSET_SET = XInternAtom(dpy, "_XSETTINGS_SETTINGS", True);
if (XSET_SEL == None || XSET_SET == None){
//LOG("XSETTINGS unavailable.\n");
return(dpi);
}
Window xset_win = XGetSelectionOwner(dpy, XSET_SEL);
if (xset_win == None){
// TODO(inso): listen for the ClientMessage about it becoming available?
// there's not much point atm if DPI scaling is only done at startup
goto out;
}
{
Atom type;
int fmt;
unsigned long pad, num;
if (XGetWindowProperty(dpy, xset_win, XSET_SET, 0, 1024, False, XSET_SET, &type, &fmt, &num, &pad, &prop) != Success){
//LOG("XSETTINGS: GetWindowProperty failed.\n");
goto out;
}
if (fmt != 8){
//LOG("XSETTINGS: Wrong format.\n");
goto out;
}
}
xs = (struct XSettings*)prop;
p = (char*)(xs + 1);
if (xs->byte_order != 0){
//LOG("FIXME: XSETTINGS not host byte order?\n");
goto out;
}
for (int i = 0; i < xs->num_settings; ++i){
struct XSettingHeader* h = (struct XSettingHeader*)p;
p += sizeof(struct XSettingHeader);
p += h->name_len;
p += ((4 - (h->name_len & 3)) & 3);
p += 4; // serial
switch (h->type){
case XSettingsTypeInt: {
if (strncmp(h->name, "Xft/DPI", h->name_len) == 0){
dpi = *(i32*)p;
if (dpi != -1) dpi /= 1024;
}
p += 4;
} break;
case XSettingsTypeString: {
u32 len = *(u32*)p;
p += 4;
p += len;
p += ((4 - (len & 3)) & 3);
} break;
case XSettingsTypeColor: {
p += 8;
} break;
default: {
//LOG("XSETTINGS: Got invalid type...\n");
goto out;
} break;
}
}
out:
if (prop){
XFree(prop);
}
return dpi;
}
internal void*
linux_thread_proc_start(void* arg) {
Linux_Object* info = (Linux_Object*)arg;
Assert(info->kind == LinuxObjectKind_Thread);
info->thread.proc(info->thread.ptr);
return NULL;
}
internal void
linux_window_maximize(b32 enable){
}
internal void
linux_window_fullscreen_toggle(){
}
#include "linux_icon.h"
internal void
linux_set_icon(Display* d, Window w){
Atom WM_ICON = XInternAtom(d, "_NET_WM_ICON", False);
XChangeProperty(d, w, WM_ICON, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)linux_icon, sizeof(linux_icon) / sizeof(long));
}
#include "linux_error_box.cpp"
////////////////////////////
#include "linux_4ed_functions.cpp"
////////////////////////////
#include <GL/gl.h>
#include "opengl/4ed_opengl_defines.h"
#define GL_FUNC(N,R,P) typedef R (CALL_CONVENTION N##_Function)P; N##_Function *N = 0;
#include "opengl/4ed_opengl_funcs.h"
#include "opengl/4ed_opengl_render.cpp"
internal
graphics_get_texture_sig(){
return(gl__get_texture(dim, texture_kind));
}
internal
graphics_fill_texture_sig(){
return(gl__fill_texture(texture_kind, texture, p, dim, data));
}
////////////////////////////
internal Face_Description
linux_find_font(Face_Description* desc) {
Face_Description result = *desc;
char* name = strndupa((char*)desc->font.file_name.str, desc->font.file_name.size);
double size;
const char* style;
{
Face_Load_Parameters* p = &desc->parameters;
size = p->pt_size;
if(p->bold && p->italic) {
style = "Bold Italic";
} else if(p->bold) {
style = "Bold";
} else if(p->italic) {
style = "Italic";
} else {
style = "Regular";
}
}
FcPattern *pattern = FcPatternBuild(
0,
FC_POSTSCRIPT_NAME, FcTypeString, name,
FC_SIZE, FcTypeDouble, size,
FC_FONTFORMAT, FcTypeString, "TrueType",
FC_STYLE, FcTypeString, (FcChar8*)style,
NULL);
if(!pattern) {
return result;
}
if (FcConfigSubstitute(linuxvars.fontconfig, pattern, FcMatchPattern)){
FcDefaultSubstitute(pattern);
FcResult res;
FcPattern *font = FcFontMatch(linuxvars.fontconfig, pattern, &res);
if (!font){
return result;
}
FcChar8 *filename = 0;
FcPatternGetString(font, FC_FILE, 0, &filename);
if(filename) {
printf("FONTCONFIG FILENAME = %s\n", filename);
result.font.file_name = push_u8_stringf(linuxvars.frame_arena, "%s", filename);
}
FcPatternDestroy(font);
}
FcPatternDestroy(pattern);
return result;
}
internal
font_make_face_sig() {
// FIXME
if(description->font.file_name.str[0] != '/') {
Face_Description desc2 = linux_find_font(description);
description = &desc2;
}
Face* result = ft__font_make_face(arena, description, scale_factor);
if(!result) {
// is this fatal? 4ed.cpp:277 (caller) does not check for null.
String_Const_u8 s = description->font.file_name;
char msg[4096];
snprintf(msg, sizeof(msg), "Unable to load font: %.*s", (int)s.size, s.str);
system_error_box(msg);
}
return(result);
}
////////////////////////////
internal b32
glx_init(void) {
int glx_maj, glx_min;
if(!glXQueryVersion(linuxvars.dpy, &glx_maj, &glx_min)) {
return false;
}
return glx_maj > 1 || (glx_maj == 1 && glx_min >= 3);
}
internal b32
glx_get_config(GLXFBConfig* fb_config, XVisualInfo* vi) {
static const int attrs[] = {
GLX_X_RENDERABLE , True,
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
GLX_RENDER_TYPE , GLX_RGBA_BIT,
GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
GLX_RED_SIZE , 8,
GLX_GREEN_SIZE , 8,
GLX_BLUE_SIZE , 8,
GLX_ALPHA_SIZE , 8,
GLX_DEPTH_SIZE , 24,
GLX_STENCIL_SIZE , 8,
GLX_DOUBLEBUFFER , True,
None
};
int conf_count = 0;
GLXFBConfig* conf_list = glXChooseFBConfig(linuxvars.dpy, DefaultScreen(linuxvars.dpy), attrs, &conf_count);
if(!conf_count || conf_count <= 0) {
return false;
}
*fb_config = *conf_list;
XFree(conf_list);
XVisualInfo* xvi = glXGetVisualFromFBConfig(linuxvars.dpy, *fb_config);
if(!xvi) {
return false;
}
*vi = *xvi;
XFree(xvi);
return true;
}
internal b32 glx_ctx_error;
internal int
glx_error_handler(Display* dpy, XErrorEvent* ev){
glx_ctx_error = true;
return 0;
}
typedef GLXContext (glXCreateContextAttribsARB_Function)(Display*, GLXFBConfig, GLXContext, Bool, const int*);
typedef void (glXSwapIntervalEXT_Function) (Display *dpy, GLXDrawable drawable, int interval);
typedef int (glXSwapIntervalMESA_Function) (unsigned int interval);
typedef int (glXGetSwapIntervalMESA_Function) (void);
typedef int (glXSwapIntervalSGI_Function) (int interval);
internal b32
glx_create_context(GLXFBConfig fb_config){
const char *glx_exts = glXQueryExtensionsString(linuxvars.dpy, DefaultScreen(linuxvars.dpy));
glXCreateContextAttribsARB_Function *glXCreateContextAttribsARB = 0;
glXSwapIntervalEXT_Function *glXSwapIntervalEXT = 0;
glXSwapIntervalMESA_Function *glXSwapIntervalMESA = 0;
glXGetSwapIntervalMESA_Function *glXGetSwapIntervalMESA = 0;
glXSwapIntervalSGI_Function *glXSwapIntervalSGI = 0;
#define GLXLOAD(f) f = (f##_Function*) glXGetProcAddressARB((const GLubyte*) #f);
GLXLOAD(glXCreateContextAttribsARB);
GLXContext ctx = NULL;
int (*old_handler)(Display*, XErrorEvent*) = XSetErrorHandler(&glx_error_handler);
if (glXCreateContextAttribsARB == NULL){
//LOG("glXCreateContextAttribsARB() not found, using old-style GLX context\n" );
ctx = glXCreateNewContext(linuxvars.dpy, fb_config, GLX_RGBA_TYPE, 0, True);
} else {
static const int context_attribs[] = {
GLX_CONTEXT_MAJOR_VERSION_ARB, 2,
GLX_CONTEXT_MINOR_VERSION_ARB, 1,
GLX_CONTEXT_PROFILE_MASK_ARB , GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB,
#if defined(FRED_INTERNAL)
GLX_CONTEXT_FLAGS_ARB , GLX_CONTEXT_DEBUG_BIT_ARB,
#endif
None
};
//LOG("Creating GL 2.1 context... ");
ctx = glXCreateContextAttribsARB(linuxvars.dpy, fb_config, 0, True, context_attribs);
}
XSync(linuxvars.dpy, False);
if(glx_ctx_error || !ctx) {
return false;
}
XSync(linuxvars.dpy, False);
XSetErrorHandler(old_handler);
//b32 direct = glXIsDirect(linuxvars.dpy, ctx);
//LOG("Making context current\n");
glXMakeCurrent(linuxvars.dpy, linuxvars.win, ctx);
//glx_enable_vsync();
// NOTE(allen): Load gl functions
#define GL_FUNC(f,R,P) GLXLOAD(f)
#include "opengl/4ed_opengl_funcs.h"
#undef GLXLOAD
return true;
}
////////////////////////////
internal void
linux_x11_init(int argc, char** argv, Plat_Settings* settings) {
Display* dpy = XOpenDisplay(0);
if (!dpy){
fprintf(stderr, "FATAL: Cannot open X11 Display!\n");
exit(1);
}
linuxvars.dpy = dpy;
#define LOAD_ATOM(x) linuxvars.atom_##x = XInternAtom(linuxvars.dpy, #x, False);
LOAD_ATOM(TARGETS);
LOAD_ATOM(CLIPBOARD);
LOAD_ATOM(UTF8_STRING);
LOAD_ATOM(_NET_WM_STATE);
LOAD_ATOM(_NET_WM_STATE_MAXIMIZED_HORZ);
LOAD_ATOM(_NET_WM_STATE_MAXIMIZED_VERT);
LOAD_ATOM(_NET_WM_STATE_FULLSCREEN);
LOAD_ATOM(_NET_WM_PING);
LOAD_ATOM(_NET_WM_WINDOW_TYPE);
LOAD_ATOM(_NET_WM_WINDOW_TYPE_NORMAL);
LOAD_ATOM(_NET_WM_PID);
LOAD_ATOM(WM_DELETE_WINDOW);
#undef LOAD_ATOM
if (!glx_init()){
system_error_box("Your XServer's GLX version is too old. GLX 1.3+ is required.");
}
GLXFBConfig fb_config;
XVisualInfo vi;
if (!glx_get_config(&fb_config, &vi)){
system_error_box("Could not get a matching GLX FBConfig. Check your OpenGL drivers are installed correctly.");
}
// TODO: window size
#define WINDOW_W_DEFAULT 800
#define WINDOW_H_DEFAULT 600
int w = WINDOW_W_DEFAULT;
int h = WINDOW_H_DEFAULT;
// TEMP
render_target.width = w;
render_target.height = h;
XSetWindowAttributes swa = {};
swa.backing_store = WhenMapped;
swa.event_mask = StructureNotifyMask;
swa.bit_gravity = NorthWestGravity;
swa.colormap = XCreateColormap(dpy, RootWindow(dpy, vi.screen), vi.visual, AllocNone);
u32 CWflags = CWBackingStore|CWBitGravity|CWBackPixel|CWBorderPixel|CWColormap|CWEventMask;
linuxvars.win = XCreateWindow(dpy, RootWindow(dpy, vi.screen), 0, 0, w, h, 0, vi.depth, InputOutput, vi.visual, CWflags, &swa);
if (!linuxvars.win){
system_error_box("XCreateWindow failed. Make sure your display is set up correctly.");
}
//NOTE(inso): Set the window's type to normal
XChangeProperty(linuxvars.dpy, linuxvars.win, linuxvars.atom__NET_WM_WINDOW_TYPE, XA_ATOM, 32, PropModeReplace, (unsigned char*)&linuxvars.atom__NET_WM_WINDOW_TYPE_NORMAL, 1);
//NOTE(inso): window managers want the PID as a window property for some reason.
pid_t pid = getpid();
XChangeProperty(linuxvars.dpy, linuxvars.win, linuxvars.atom__NET_WM_PID, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)&pid, 1);
//NOTE(inso): set wm properties
XStoreName(linuxvars.dpy, linuxvars.win, WINDOW_NAME);
XSizeHints *sz_hints = XAllocSizeHints();
XWMHints *wm_hints = XAllocWMHints();
XClassHint *cl_hints = XAllocClassHint();
sz_hints->flags = PMinSize | PMaxSize | PWinGravity;
sz_hints->min_width = 50;
sz_hints->min_height = 50;
sz_hints->max_width = sz_hints->max_height = (1UL << 16UL);
sz_hints->win_gravity = NorthWestGravity;
if (settings->set_window_pos){
sz_hints->flags |= USPosition;
sz_hints->x = settings->window_x;
sz_hints->y = settings->window_y;
}
wm_hints->flags |= InputHint | StateHint;
wm_hints->input = True;
wm_hints->initial_state = NormalState;
cl_hints->res_name = "4coder";
cl_hints->res_class = "4coder";
char* win_name_list[] = { WINDOW_NAME };
XTextProperty win_name;
XStringListToTextProperty(win_name_list, 1, &win_name);
XSetWMProperties(linuxvars.dpy, linuxvars.win, &win_name, NULL, argv, argc, sz_hints, wm_hints, cl_hints);
XFree(win_name.value);
XFree(sz_hints);
XFree(wm_hints);
XFree(cl_hints);
linux_set_icon(linuxvars.dpy, linuxvars.win);
// NOTE(inso): make the window visible
XMapWindow(linuxvars.dpy, linuxvars.win);
if(!glx_create_context(fb_config)) {
system_error_box("Unable to create GLX context.");
}
XRaiseWindow(linuxvars.dpy, linuxvars.win);
if (settings->set_window_pos){
XMoveWindow(linuxvars.dpy, linuxvars.win, settings->window_x, settings->window_y);
}
if (settings->maximize_window){
linux_window_maximize(true);
} else if (settings->fullscreen_window){
linux_window_fullscreen_toggle();
}
XSync(linuxvars.dpy, False);
XWindowAttributes attr;
if (XGetWindowAttributes(linuxvars.dpy, linuxvars.win, &attr)){
//*window_width = WinAttribs.width;
//*window_height = WinAttribs.height;
}
Atom wm_protos[] = {
linuxvars.atom_WM_DELETE_WINDOW,
linuxvars.atom__NET_WM_PING
};
XSetWMProtocols(linuxvars.dpy, linuxvars.win, wm_protos, 2);
// XFixes extension for clipboard notification.
{
int xfixes_version_unused, xfixes_err_unused;
Bool has_xfixes = XQueryExtension(linuxvars.dpy, "XFIXES", &xfixes_version_unused, &linuxvars.xfixes_selection_event, &xfixes_err_unused);
linuxvars.has_xfixes = (has_xfixes == True);
}
// Input handling init
setlocale(LC_ALL, "");
XSetLocaleModifiers("");
b32 locale_supported = XSupportsLocale();
//LOGF("Supported locale?: %s.\n", locale_supported ? "Yes" : "No");
if (!locale_supported){
//LOG("Reverting to 'C' ... ");
setlocale(LC_ALL, "C");
locale_supported = XSupportsLocale();
//LOGF("C is supported? %s.\n", locale_supported ? "Yes" : "No");
}
linuxvars.xim = XOpenIM(dpy, 0, 0, 0);
if (!linuxvars.xim){
// NOTE(inso): Try falling back to the internal XIM implementation that
// should in theory always exist.
XSetLocaleModifiers("@im=none");
linuxvars.xim = XOpenIM(dpy, 0, 0, 0);
}
// If it still isn't there we're screwed.
if (!linuxvars.xim){
system_error_box("Could not initialize X Input.");
}
XIMStyles *styles = NULL;
const XIMStyle style_want = (XIMPreeditNothing | XIMStatusNothing);
b32 found_style = false;
if (!XGetIMValues(linuxvars.xim, XNQueryInputStyle, &styles, NULL) && styles){
for (i32 i = 0; i < styles->count_styles; ++i){
XIMStyle style = styles->supported_styles[i];
if (style == style_want) {
found_style = true;
break;
}
}
}
if(!found_style) {
system_error_box("Could not find supported X Input style.");
}
XFree(styles);
linuxvars.xic = XCreateIC(linuxvars.xim,
XNInputStyle, style_want,
XNClientWindow, linuxvars.win,
XNFocusWindow, linuxvars.win,
NULL);
if(!linuxvars.xic) {
system_error_box("Error creating X Input context.");
}
int xim_event_mask;
if (XGetICValues(linuxvars.xic, XNFilterEvents, &xim_event_mask, NULL)){
xim_event_mask = 0;
}
u32 event_mask = ExposureMask
| KeyPressMask | KeyReleaseMask
| ButtonPressMask | ButtonReleaseMask
| EnterWindowMask | LeaveWindowMask
| PointerMotionMask
| FocusChangeMask
| StructureNotifyMask | MappingNotify
| ExposureMask | VisibilityChangeMask
| xim_event_mask;
XSelectInput(linuxvars.dpy, linuxvars.win, event_mask);
}
global Key_Code keycode_lookup_table[255];
internal void
linux_keycode_init(Display* dpy){
struct SymCode { KeySym sym; Key_Code code; };
SymCode sym_table[100];
block_zero_array(sym_table);
block_zero_array(keycode_lookup_table);
SymCode* p = sym_table;
for(Key_Code k = KeyCode_A; k <= KeyCode_Z; ++k) {
*p++ = { XK_a + (k - KeyCode_A), k };
}
for(Key_Code k = KeyCode_0; k <= KeyCode_9; ++k) {
*p++ = { XK_0 + (k - KeyCode_0), k };
}
*p++ = { XK_space, KeyCode_Space };
*p++ = { XK_grave, KeyCode_Tick };
*p++ = { XK_minus, KeyCode_Minus };
*p++ = { XK_equal, KeyCode_Equal };
*p++ = { XK_bracketleft, KeyCode_LeftBracket };
*p++ = { XK_bracketright, KeyCode_RightBracket };
*p++ = { XK_semicolon, KeyCode_Semicolon };
*p++ = { XK_apostrophe, KeyCode_Quote };
*p++ = { XK_comma, KeyCode_Comma };
*p++ = { XK_period, KeyCode_Period };
*p++ = { XK_slash, KeyCode_ForwardSlash };
*p++ = { XK_backslash, KeyCode_BackwardSlash };
*p++ = { XK_Tab, KeyCode_Tab };
*p++ = { XK_Escape, KeyCode_Escape };
*p++ = { XK_Pause, KeyCode_Pause };
*p++ = { XK_Up, KeyCode_Up };
*p++ = { XK_Down, KeyCode_Down };
*p++ = { XK_Left, KeyCode_Left };
*p++ = { XK_Right, KeyCode_Right };
*p++ = { XK_BackSpace, KeyCode_Backspace };
*p++ = { XK_Return, KeyCode_Return };
*p++ = { XK_Delete, KeyCode_Delete };
*p++ = { XK_Insert, KeyCode_Insert };
*p++ = { XK_Home, KeyCode_Home };
*p++ = { XK_End, KeyCode_End };
*p++ = { XK_Page_Up, KeyCode_PageUp };
*p++ = { XK_Page_Down, KeyCode_PageDown };
*p++ = { XK_Caps_Lock, KeyCode_CapsLock };
*p++ = { XK_Num_Lock, KeyCode_NumLock };
*p++ = { XK_Scroll_Lock, KeyCode_ScrollLock };
*p++ = { XK_Menu, KeyCode_Menu };
*p++ = { XK_Shift_L, KeyCode_Shift };
*p++ = { XK_Shift_R, KeyCode_Shift };
*p++ = { XK_Control_L, KeyCode_Control };
*p++ = { XK_Control_R, KeyCode_Control };
*p++ = { XK_Alt_L, KeyCode_Alt };
*p++ = { XK_Alt_R, KeyCode_Alt };
*p++ = { XK_Super_L, KeyCode_Command };
*p++ = { XK_Super_R, KeyCode_Command };
for(Key_Code k = KeyCode_F1; k <= KeyCode_F16; ++k) {
*p++ = { XK_F1 + (k - KeyCode_F1), k };
}
const int table_size = p - sym_table;
Assert(table_size < ArrayCount(sym_table));
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 == 0){
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 String_Const_u8
linux_filter_text(Arena* arena, u8* buf, int len) {
u8* const result = push_array(arena, u8, len);
u8* const endp = buf + len;
u8* outp = result;
for(int i = 0; i < len; ++i) {
u8 c = buf[i];
if(c == '\r') {
*outp++ = '\n';
} else if(c > 127 || (' ' <= c && c <= '~') || c == '\t') {
*outp++ = c;
}
}
return SCu8(result, outp - result);
}
internal void
linux_handle_x11_events() {
static XEvent prev_event = {};
b32 should_step = false;
while (XPending(linuxvars.dpy)) {
XEvent event;
XNextEvent(linuxvars.dpy, &event);
if (XFilterEvent(&event, None) == True){
continue;
}
switch(event.type) {
case KeyPress: {
should_step = true;
Input_Modifier_Set_Fixed* mods = &linuxvars.input.pers.modifiers;
block_zero_struct(mods);
int state = event.xkey.state;
set_modifier(mods, KeyCode_Shift, state & ShiftMask);
set_modifier(mods, KeyCode_Control, state & ControlMask);
set_modifier(mods, KeyCode_CapsLock, state & LockMask);
set_modifier(mods, KeyCode_Alt, state & Mod1Mask);
event.xkey.state &= ~(ControlMask);
Status status;
KeySym keysym = NoSymbol;
u8 buf[256] = {};
int len = Xutf8LookupString(linuxvars.xic, &event.xkey, (char*)buf, sizeof(buf) - 1, &keysym, &status);
if (status == XBufferOverflow){
//TODO(inso): handle properly
Xutf8ResetIC(linuxvars.xic);
XSetICFocus(linuxvars.xic);
}
if (keysym == XK_ISO_Left_Tab){
printf("left tab? [%.*s]\n", len, buf);
add_modifier(mods, KeyCode_Shift);
}
Key_Code key = keycode_lookup_table[(u8)event.xkey.keycode];
printf("key [%d] = %s\n", event.xkey.keycode, key_code_name[key]);
Input_Event* key_event = NULL;
if(key) {
key_event = push_input_event(linuxvars.frame_arena, &linuxvars.input.trans.event_list);
key_event->kind = InputEventKind_KeyStroke;
key_event->key.code = key;
key_event->key.modifiers = copy_modifier_set(linuxvars.frame_arena, mods);
}
Input_Event* text_event = NULL;
if(status == XLookupChars || status == XLookupBoth) {
String_Const_u8 str = linux_filter_text(linuxvars.frame_arena, buf, len);
if(str.size) {
text_event = push_input_event(linuxvars.frame_arena, &linuxvars.input.trans.event_list);
text_event->kind = InputEventKind_TextInsert;
text_event->text.string = str;
}
}
if(key_event && text_event) {
key_event->key.first_dependent_text = text_event;
}
} break;
case KeyRelease: {
} break;
case MotionNotify: {
linuxvars.input.pers.mouse = { event.xmotion.x, event.xmotion.y };
should_step = true;
} break;
case ButtonPress: {
should_step = true;
switch(event.xbutton.button) {
case Button1: {
linuxvars.input.trans.mouse_l_press = true;
linuxvars.input.pers.mouse_l = true;
} break;
case Button3: {
linuxvars.input.trans.mouse_r_press = true;
linuxvars.input.pers.mouse_r = true;
} break;
case Button4: {
linuxvars.input.trans.mouse_wheel = -100;
} break;
case Button5: {
linuxvars.input.trans.mouse_wheel = +100;
} break;
}
} break;
case ButtonRelease: {
should_step = true;
switch(event.xbutton.button) {
case Button1: {
linuxvars.input.trans.mouse_l_release = true;
linuxvars.input.pers.mouse_l = false;
} break;
case Button3: {
linuxvars.input.trans.mouse_r_release = true;
linuxvars.input.pers.mouse_r = false;
} break;
}
} break;
case MappingNotify: {
if (event.xmapping.request == MappingModifier || event.xmapping.request == MappingKeyboard){
XRefreshKeyboardMapping(&event.xmapping);
linux_keycode_init(linuxvars.dpy);
}
} break;
case ConfigureNotify: {
i32 w = event.xconfigure.width;
i32 h = event.xconfigure.height;
if (w != render_target.width || h != render_target.height){
should_step = true;
render_target.width = w;
render_target.height = h;
}
} break;
case ClientMessage: {
Atom atom = event.xclient.data.l[0];
// Window X button clicked
if(atom == linuxvars.atom_WM_DELETE_WINDOW) {
should_step = true;
linuxvars.input.trans.trying_to_kill = true;
}
// Notify WM that we're still responding (don't grey our window out).
else if(atom == linuxvars.atom__NET_WM_PING) {
event.xclient.window = DefaultRootWindow(linuxvars.dpy);
XSendEvent(
linuxvars.dpy,
event.xclient.window,
False,
SubstructureRedirectMask | SubstructureNotifyMask,
&event);
}
} break;
}
}
if(should_step) {
linux_schedule_step();
}
}
internal b32
linux_epoll_process(struct epoll_event* events, int num_events) {
b32 do_step = false;
for (int i = 0; i < num_events; ++i){
struct epoll_event* ev = events + i;
Epoll_Kind* tag = (Epoll_Kind*)ev->data.ptr;
switch (*tag){
case EPOLL_X11: {
linux_handle_x11_events();
} break;
case EPOLL_X11_INTERNAL: {
//XProcessInternalConnection(linuxvars.dpy, fd);
} break;
case EPOLL_STEP_EVENT: {
u64 ev;
int ret;
do {
ret = read(linuxvars.step_event_fd, &ev, 8);
} while (ret != -1 || errno != EAGAIN);
do_step = true;
} break;
case EPOLL_STEP_TIMER: {
u64 count;
int ret;
do {
ret = read(linuxvars.step_timer_fd, &count, 8);
} while (ret != -1 || errno != EAGAIN);
do_step = true;
} break;
case EPOLL_CLI_PIPE: {
linux_schedule_step();
} break;
case EPOLL_USER_TIMER: {
Linux_Object* obj = CastFromMember(Linux_Object, timer.epoll_tag, tag);
close(obj->timer.fd);
obj->timer.fd = -1;
do_step = true;
} break;
}
}
return do_step;
}
int
main(int argc, char **argv){
// NOTE(allen): context setup
{
Base_Allocator* alloc = get_base_allocator_system();
thread_ctx_init(&linuxvars.tctx, ThreadKind_Main, alloc, alloc);
}
API_VTable_system system_vtable = {};
system_api_fill_vtable(&system_vtable);
API_VTable_graphics graphics_vtable = {};
graphics_api_fill_vtable(&graphics_vtable);
API_VTable_font font_vtable = {};
font_api_fill_vtable(&font_vtable);
// NOTE(allen): memory
linuxvars.frame_arena = reserve_arena(&linuxvars.tctx);
// TODO(allen): *arena;
render_target.arena = make_arena_system(KB(256));
linuxvars.fontconfig = FcInitLoadConfigAndFonts();
linuxvars.cursor_show = MouseCursorShow_Always;
linuxvars.prev_cursor_show = MouseCursorShow_Always;
dll_init_sentinel(&linuxvars.free_linux_objects);
dll_init_sentinel(&linuxvars.timer_objects);
//InitializeCriticalSection(&win32vars.thread_launch_mutex);
//InitializeConditionVariable(&win32vars.thread_launch_cv);
// dpi ?
// NOTE(allen): load core
System_Library core_library = {};
App_Functions app = {};
{
App_Get_Functions *get_funcs = 0;
Scratch_Block scratch(&linuxvars.tctx, Scratch_Share);
Path_Search_List search_list = {};
search_list_add_system_path(scratch, &search_list, SystemPath_Binary);
String_Const_u8 core_path = get_full_path(scratch, &search_list, SCu8("4ed_app.so"));
if (system_load_library(scratch, core_path, &core_library)){
get_funcs = (App_Get_Functions*)system_get_proc(core_library, "app_get_functions");
if (get_funcs != 0){
app = get_funcs();
}
else{
char msg[] = "Failed to get application code from '4ed_app.so'.";
system_error_box(msg);
}
}
else{
char msg[] = "Could not load '4ed_app.so'. This file should be in the same directory as the main '4ed' executable.";
system_error_box(msg);
}
}
// NOTE(allen): send system vtable to core
app.load_vtables(&system_vtable, &font_vtable, &graphics_vtable);
// get_logger calls log_init which is needed.
app.get_logger();
//linuxvars.log_string = app.get_logger();
// NOTE(allen): init & command line parameters
Plat_Settings plat_settings = {};
void *base_ptr = 0;
{
Scratch_Block scratch(&linuxvars.tctx, Scratch_Share);
String_Const_u8 curdir = system_get_path(scratch, SystemPath_CurrentDirectory);
char **files = 0;
i32 *file_count = 0;
base_ptr = app.read_command_line(&linuxvars.tctx, curdir, &plat_settings, &files, &file_count, argc, argv);
/* TODO(inso): what is this doing?
{
i32 end = *file_count;
i32 i = 0, j = 0;
for (; i < end; ++i){
if (system_file_can_be_made(scratch, (u8*)files[i])){
files[j] = files[i];
++j;
}
}
*file_count = j;
}*/
}
// NOTE(allen): load custom layer
System_Library custom_library = {};
Custom_API custom = {};
{
char custom_not_found_msg[] = "Did not find a library for the custom layer.";
char custom_fail_version_msg[] = "Failed to load custom code due to missing version information or a version mismatch. Try rebuilding with buildsuper.";
char custom_fail_init_apis[] = "Failed to load custom code due to missing 'init_apis' symbol. Try rebuilding with buildsuper";
Scratch_Block scratch(&linuxvars.tctx, Scratch_Share);
String_Const_u8 default_file_name = string_u8_litexpr("custom_4coder.so");
Path_Search_List search_list = {};
search_list_add_system_path(scratch, &search_list, SystemPath_CurrentDirectory);
search_list_add_system_path(scratch, &search_list, SystemPath_Binary);
String_Const_u8 custom_file_names[2] = {};
i32 custom_file_count = 1;
if (plat_settings.custom_dll != 0){
custom_file_names[0] = SCu8(plat_settings.custom_dll);
if (!plat_settings.custom_dll_is_strict){
custom_file_names[1] = default_file_name;
custom_file_count += 1;
}
}
else{
custom_file_names[0] = default_file_name;
}
String_Const_u8 custom_file_name = {};
for (i32 i = 0; i < custom_file_count; i += 1){
custom_file_name = get_full_path(scratch, &search_list, custom_file_names[i]);
if (custom_file_name.size > 0){
break;
}
}
b32 has_library = false;
if (custom_file_name.size > 0){
if (system_load_library(scratch, custom_file_name, &custom_library)){
has_library = true;
}
}
if (!has_library){
system_error_box(custom_not_found_msg);
}
custom.get_version = (_Get_Version_Type*)system_get_proc(custom_library, "get_version");
if (custom.get_version == 0 || custom.get_version(MAJOR, MINOR, PATCH) == 0){
system_error_box(custom_fail_version_msg);
}
custom.init_apis = (_Init_APIs_Type*)system_get_proc(custom_library, "init_apis");
if (custom.init_apis == 0){
system_error_box(custom_fail_init_apis);
}
}
linux_x11_init(argc, argv, &plat_settings);
linux_keycode_init(linuxvars.dpy);
// TODO(inso): move to x11 init?
XCursor xcursors[APP_MOUSE_CURSOR_COUNT] = {
None,
None,
XCreateFontCursor(linuxvars.dpy, XC_xterm),
XCreateFontCursor(linuxvars.dpy, XC_sb_h_double_arrow),
XCreateFontCursor(linuxvars.dpy, XC_sb_v_double_arrow)
};
// sneaky invisible cursor
{
char data = 0;
XColor c = {};
Pixmap p = XCreateBitmapFromData(linuxvars.dpy, linuxvars.win, &data, 1, 1);
linuxvars.hidden_cursor = XCreatePixmapCursor(linuxvars.dpy, p, p, &c, &c, 0, 0);
XFreePixmap(linuxvars.dpy, p);
}
// epoll init
{
struct epoll_event e = {};
e.events = EPOLLIN | EPOLLET;
//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);
e.data.ptr = &epoll_tag_x11;
epoll_ctl(linuxvars.epoll, EPOLL_CTL_ADD, ConnectionNumber(linuxvars.dpy), &e);
e.data.ptr = &epoll_tag_step_event;
epoll_ctl(linuxvars.epoll, EPOLL_CTL_ADD, linuxvars.step_event_fd, &e);
e.data.ptr = &epoll_tag_step_timer;
epoll_ctl(linuxvars.epoll, EPOLL_CTL_ADD, linuxvars.step_timer_fd, &e);
}
// app init
{
Scratch_Block scratch(&linuxvars.tctx, Scratch_Share);
String_Const_u8 curdir = system_get_path(scratch, SystemPath_CurrentDirectory);
app.init(&linuxvars.tctx, &render_target, base_ptr, linuxvars.clipboard_contents, curdir, custom);
}
linuxvars.global_frame_mutex = system_mutex_make();
system_mutex_acquire(linuxvars.global_frame_mutex);
linux_schedule_step();
b32 first_step = true;
for (;;) {
if (XEventsQueued(linuxvars.dpy, QueuedAlready)){
linux_handle_x11_events();
}
system_mutex_release(linuxvars.global_frame_mutex);
struct epoll_event events[16];
int num_events = epoll_wait(linuxvars.epoll, events, ArrayCount(events), -1);
system_mutex_acquire(linuxvars.global_frame_mutex);
if (num_events == -1){
if (errno != EINTR){
perror("epoll_wait");
//LOG("epoll_wait\n");
}
continue;
}
if(!linux_epoll_process(events, num_events)) {
continue;
}
u64 now = system_now_time();
printf(" ***** STEP DIFF = %.2f\n", (now - linuxvars.last_step_time) / 1000000.0);
linuxvars.last_step_time = now;
// NOTE(allen): Frame Clipboard Input
// Request clipboard contents from X11 on first step, or every step if they don't have XFixes notification ability.
if (first_step || !linuxvars.has_xfixes){
XConvertSelection(linuxvars.dpy, linuxvars.atom_CLIPBOARD, linuxvars.atom_UTF8_STRING, linuxvars.atom_CLIPBOARD, linuxvars.win, CurrentTime);
}
Application_Step_Input input = {};
if (linuxvars.received_new_clipboard){
input.clipboard = linuxvars.clipboard_contents;
linuxvars.received_new_clipboard = false;
}
input.first_step = first_step;
input.dt = frame_useconds/1000000.f; // variable?
input.events = linuxvars.input.trans.event_list;
input.trying_to_kill = linuxvars.input.trans.trying_to_kill;
input.mouse.out_of_window = false;
input.mouse.p = linuxvars.input.pers.mouse;
input.mouse.l = linuxvars.input.pers.mouse_l;
input.mouse.r = linuxvars.input.pers.mouse_r;
input.mouse.press_l = linuxvars.input.trans.mouse_l_press;
input.mouse.release_l = linuxvars.input.trans.mouse_l_release;
input.mouse.press_r = linuxvars.input.trans.mouse_r_press;
input.mouse.release_r = linuxvars.input.trans.mouse_r_release;
input.mouse.wheel = linuxvars.input.trans.mouse_wheel;
// NOTE(allen): Application Core Update
Application_Step_Result result = {};
if (app.step != 0){
result = app.step(&linuxvars.tctx, &render_target, base_ptr, &input);
}
// NOTE(allen): Finish the Loop
if (result.perform_kill){
break;
}
// NOTE(NAME): Switch to New Title
if (result.has_new_title){
XStoreName(linuxvars.dpy, linuxvars.win, result.title_string);
}
// NOTE(allen): Switch to New Cursor
if (result.mouse_cursor_type != linuxvars.cursor && !linuxvars.input.pers.mouse_l){
XCursor c = xcursors[result.mouse_cursor_type];
if (linuxvars.cursor_show){
XDefineCursor(linuxvars.dpy, linuxvars.win, c);
}
linuxvars.cursor = result.mouse_cursor_type;
}
gl_render(&render_target);
glXSwapBuffers(linuxvars.dpy, linuxvars.win);
first_step = false;
linalloc_clear(linuxvars.frame_arena);
block_zero_struct(&linuxvars.input.trans);
}
return 0;
}
// NOTE(inso): to prevent me continuously messing up indentation
// vim: et:ts=4:sts=4:sw=4