re-slice linux/win32 backends, top-level folder is backend (demon/win32 -> win32/demon, etc.); os_gfx -> window_manager; fix linux / opengl builds for new apis/types

This commit is contained in:
Ryan Fleury
2026-05-08 15:21:22 -07:00
parent aea4a4ff9d
commit 9a7782de47
50 changed files with 12981 additions and 19517 deletions
File diff suppressed because it is too large Load Diff
+150
View File
@@ -4,4 +4,154 @@
#ifndef LINUX_BASE_H
#define LINUX_BASE_H
////////////////////////////////
//~ rjf: Includes
#include <dirent.h>
#include <dlfcn.h>
#include <dlfcn.h>
#include <errno.h>
#include <execinfo.h>
#include <fcntl.h>
#include <features.h>
#include <linux/limits.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <spawn.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/random.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/sysinfo.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
pid_t gettid(void);
int pthread_setname_np(pthread_t thread, const char *name);
int pthread_getname_np(pthread_t thread, char *name, size_t size);
typedef struct tm tm;
typedef struct timespec timespec;
////////////////////////////////
//~ rjf: Linux Call Interruption Retry Helper
#define OS_LNX_RETRY_ON_EINTR(expr) \
(__extension__({ \
__typeof__(expr) __ret; \
do { \
__ret = (expr); \
} while ((__ret == -1) && errno == EINTR); \
__ret; \
}))
////////////////////////////////
//~ rjf: File Iterator
typedef struct OS_LNX_FileIter OS_LNX_FileIter;
struct OS_LNX_FileIter
{
DIR *dir;
struct dirent *dp;
String8 path;
};
StaticAssert(sizeof(Member(FileIter, memory)) >= sizeof(OS_LNX_FileIter), os_lnx_file_iter_size_check);
////////////////////////////////
//~ rjf: Safe Call Handler Chain
typedef struct OS_LNX_SafeCallChain OS_LNX_SafeCallChain;
struct OS_LNX_SafeCallChain
{
OS_LNX_SafeCallChain *next;
ThreadEntryPointFunctionType *fail_handler;
void *ptr;
};
////////////////////////////////
//~ rjf: Entities
typedef enum OS_LNX_EntityKind
{
OS_LNX_EntityKind_Thread,
OS_LNX_EntityKind_Mutex,
OS_LNX_EntityKind_RWMutex,
OS_LNX_EntityKind_ConditionVariable,
OS_LNX_EntityKind_Barrier,
}
OS_LNX_EntityKind;
typedef struct OS_LNX_Entity OS_LNX_Entity;
struct OS_LNX_Entity
{
OS_LNX_Entity *next;
OS_LNX_EntityKind kind;
union
{
struct
{
pthread_t handle;
ThreadEntryPointFunctionType *func;
void *ptr;
} thread;
pthread_mutex_t mutex_handle;
pthread_rwlock_t rwmutex_handle;
struct
{
pthread_cond_t cond_handle;
pthread_mutex_t rwlock_mutex_handle;
} cv;
pthread_barrier_t barrier;
};
};
////////////////////////////////
//~ rjf: State
typedef struct OS_LNX_State OS_LNX_State;
struct OS_LNX_State
{
Arena *arena;
SystemInfo system_info;
ProcessInfo process_info;
pthread_mutex_t entity_mutex;
Arena *entity_arena;
OS_LNX_Entity *entity_free;
U64 default_env_count;
char **default_env;
};
////////////////////////////////
//~ rjf: Globals
global OS_LNX_State os_lnx_state = {0};
thread_static OS_LNX_SafeCallChain *os_lnx_safe_call_chain = 0;
////////////////////////////////
//~ rjf: Helpers
internal DateTime os_lnx_date_time_from_tm(tm in, U32 msec);
internal tm os_lnx_tm_from_date_time(DateTime dt);
internal timespec os_lnx_timespec_from_date_time(DateTime dt);
internal DenseTime os_lnx_dense_time_from_timespec(timespec in);
internal FileProperties os_lnx_file_properties_from_stat(struct stat *s);
internal void os_lnx_safe_call_sig_handler(int x);
////////////////////////////////
//~ rjf: Entities
internal OS_LNX_Entity *os_lnx_entity_alloc(OS_LNX_EntityKind kind);
internal void os_lnx_entity_release(OS_LNX_Entity *entity);
////////////////////////////////
//~ rjf: Thread Entry Point
internal void *os_lnx_thread_entry_point(void *ptr);
#endif // LINUX_BASE_H
File diff suppressed because it is too large Load Diff
+502
View File
@@ -0,0 +1,502 @@
// Copyright (c) Epic Games Tools
// Licensed under the MIT license (https://opensource.org/license/mit/)
#ifndef DEMON_CORE_LINUX_H
#define DEMON_CORE_LINUX_H
////////////////////////////////
//~ rjf: Register Layouts
//
// These are defined in <sys/user.h>, but only for one architecture at a time
typedef struct OS_LNX_GprsX64 OS_LNX_GprsX64;
struct OS_LNX_GprsX64
{
U64 r15;
U64 r14;
U64 r13;
U64 r12;
U64 rbp;
U64 rbx;
U64 r11;
U64 r10;
U64 r9;
U64 r8;
U64 rax;
U64 rcx;
U64 rdx;
U64 rsi;
U64 rdi;
U64 orig_rax;
U64 rip;
U64 cs;
U64 rflags;
U64 rsp;
U64 ss;
U64 fsbase;
U64 gsbase;
U64 ds;
U64 es;
U64 fs;
U64 gs;
};
typedef struct OS_LNX_UserX64 OS_LNX_UserX64;
struct OS_LNX_UserX64
{
OS_LNX_GprsX64 regs;
S32 u_fpvalid;
U32 _pad0;
X64_FXSave i387;
U64 u_tsize;
U64 u_dsize;
U64 u_ssize;
U64 start_code;
U64 start_stack;
U64 signal;
U32 reserved;
U32 _pad1;
U64 u_ar0;
U64 u_fpstate;
U64 magic;
U8 u_comm[32];
U64 u_debugreg[8];
};
StaticAssert(sizeof(OS_LNX_UserX64) == 912, g_os_lnx_user_x64_size_check);
////////////////////////////////
//~ TLS
typedef struct DMN_LNX_DbDesc
{
U32 bit_size;
U32 count;
U32 offset;
} DMN_LNX_DbDesc;
////////////////////////////////
//~ SDT Probes
typedef struct DMN_LNX_Probe
{
String8 provider;
String8 name;
String8 args_string;
STAP_ArgArray args;
U64 pc;
U64 semaphore;
} DMN_LNX_Probe;
typedef struct DMN_LNX_ProbeNode
{
DMN_LNX_Probe v;
struct DMN_LNX_ProbeNode *next;
} DMN_LNX_ProbeNode;
typedef struct DMN_LNX_ProbeList
{
U64 count;
DMN_LNX_ProbeNode *first;
DMN_LNX_ProbeNode *last;
} DMN_LNX_ProbeList;
#define DMN_LNX_Probe_XList \
X(InitStart, 2, "init_start") \
X(InitComplete, 2, "init_complete") \
X(RelocStart, 2, "reloc_start") \
X(RelocComplete, 3, "reloc_complete") \
X(MapStart, 2, "map_start") \
X(MapComplete, 3, "map_complete") \
X(UnmapStart, 2, "unmap_start") \
X(UnmapComplete, 2, "unmap_complete") \
X(LongJmp, 3, "longjmp") \
X(LongJmpTarget, 3, "longjmp_target") \
X(SetJmp, 3, "setjmp")
typedef enum
{
DMN_LNX_ProbeType_Null,
#define X(_N,...) DMN_LNX_ProbeType_##_N,
DMN_LNX_Probe_XList
#undef X
DMN_LNX_ProbeType_Count,
} DMN_LNX_ProbeType;
////////////////////////////////
//~ Process Info
typedef struct DMN_LNX_Auxv
{
U64 base;
U64 phnum;
U64 phent;
U64 phdr;
U64 execfn;
U64 pagesz;
} DMN_LNX_Auxv;
typedef struct DMN_LNX_DynamicInfo
{
U64 hash_vaddr;
U64 gnu_hash_vaddr;
U64 strtab_vaddr;
U64 strtab_size;
U64 symtab_vaddr;
U64 symtab_entry_size;
} DMN_LNX_DynamicInfo;
////////////////////////////////
//~ Entities
typedef enum DMN_LNX_EntityKind
{
DMN_LNX_EntityKind_Null,
DMN_LNX_EntityKind_Process,
DMN_LNX_EntityKind_ProcessCtx,
DMN_LNX_EntityKind_Thread,
DMN_LNX_EntityKind_Module,
} DMN_LNX_EntityKind;
typedef enum DMN_LNX_ThreadState
{
DMN_LNX_ThreadState_Null,
DMN_LNX_ThreadState_Running,
DMN_LNX_ThreadState_Stopped,
DMN_LNX_ThreadState_Exited,
DMN_LNX_ThreadState_PendingCreation,
} DMN_LNX_ThreadState;
typedef struct DMN_LNX_Thread
{
pid_t tid;
DMN_LNX_ThreadState state;
struct DMN_LNX_Process *process;
void *reg_block;
B32 is_reg_block_dirty;
B32 pass_through_signal;
U64 pass_through_signo;
U64 orig_rax;
struct DMN_LNX_Thread *next;
struct DMN_LNX_Thread *prev;
} DMN_LNX_Thread;
typedef struct DMN_LNX_ThreadPtrNode
{
DMN_LNX_Thread *v;
struct DMN_LNX_ThreadPtrNode *next;
struct DMN_LNX_ThreadPtrNode *prev;
} DMN_LNX_ThreadPtrNode;
typedef struct DMN_LNX_ThreadPtrList
{
U64 count;
DMN_LNX_ThreadPtrNode *first;
DMN_LNX_ThreadPtrNode *last;
} DMN_LNX_ThreadPtrList;
typedef struct DMN_LNX_Module
{
U64 name_vaddr;
U64 base_vaddr;
U64 name_space_id;
U64 size;
U64 phvaddr;
U64 phentsize;
U64 phcount;
U64 tls_index;
U64 tls_offset;
B8 is_live;
B8 is_main;
struct DMN_LNX_Module *next;
struct DMN_LNX_Module *prev;
} DMN_LNX_Module;
typedef struct DMN_LNX_ModulePtrNode
{
DMN_LNX_Module *v;
struct DMN_LNX_ModulePtrNode *next;
} DMN_LNX_ModulePtrNode;
typedef struct DMN_LNX_ModulePtrList
{
U64 count;
DMN_LNX_ModulePtrNode *first;
DMN_LNX_ModulePtrNode *last;
} DMN_LNX_ModulePtrList;
typedef enum
{
DMN_LNX_ProcessState_Null,
DMN_LNX_ProcessState_Launch,
DMN_LNX_ProcessState_Attach,
DMN_LNX_ProcessState_WaitForExec,
DMN_LNX_ProcessState_ExecFailedDoExit,
DMN_LNX_ProcessState_Normal,
} DMN_LNX_ProcessState;
typedef enum
{
DMN_LNX_CreateProcessFlag_DebugSubprocesses = (1 << 0),
DMN_LNX_CreateProcessFlag_Rebased = (1 << 1),
DMN_LNX_CreateProcessFlag_Cow = (1 << 2),
DMN_LNX_CreateProcessFlag_ClonedMemory = (1 << 3),
} DMN_LNX_CreateProcessFlags;
typedef struct DMN_LNX_Process
{
pid_t pid;
int fd;
DMN_LNX_ProcessState state;
B32 debug_subprocesses;
B32 is_cow;
B32 vfork_with_spoof;
U64 thread_count;
DMN_LNX_Thread *first_thread;
DMN_LNX_Thread *last_thread;
U64 main_thread_exit_code;
struct DMN_LNX_Process *parent_process;
struct DMN_LNX_ProcessCtx *ctx;
struct DMN_LNX_Process *next;
struct DMN_LNX_Process *prev;
} DMN_LNX_Process;
typedef struct DMN_LNX_ProcessPtrNode
{
DMN_LNX_Process *v;
struct DMN_LNX_ProcessPtrNode *next;
} DMN_LNX_ProcessPtrNode;
typedef struct DMN_LNX_ProcessPtrList
{
U64 count;
DMN_LNX_ProcessPtrNode *first;
DMN_LNX_ProcessPtrNode *last;
} DMN_LNX_ProcessPtrList;
typedef struct DMN_LNX_ProcessCtx
{
Arena *arena;
Arch arch;
U64 rdebug_vaddr;
ELF_Class dl_class;
HashTable *loaded_modules_ht;
DMN_LNX_Probe **probes;
DMN_ActiveTrap *first_probe_trap;
DMN_ActiveTrap *last_probe_trap;
DMN_LNX_Module *first_module;
DMN_LNX_Module *last_module;
U64 module_count;
U64 ref_count;
String8List free_reg_blocks;
String8List free_reg_block_nodes;
// x64
U64 xcr0;
U64 xsave_size;
X64_XSaveLayout xsave_layout;
} DMN_LNX_ProcessCtx;
typedef struct DMN_LNX_Entity
{
union
{
DMN_LNX_Process process;
DMN_LNX_ProcessCtx process_ctx;
DMN_LNX_Thread thread;
DMN_LNX_Module module;
struct
{
struct DMN_LNX_Entity *next;
};
};
U32 gen;
DMN_LNX_EntityKind kind;
} DMN_LNX_Entity;
typedef struct DMN_LNX_EntityNode
{
DMN_LNX_Entity *v;
struct DMN_LNX_EntityNode *next;
} DMN_LNX_EntityNode;
typedef struct DMN_LNX_EntityList
{
U64 count;
DMN_LNX_EntityNode *first;
DMN_LNX_EntityNode *last;
} DMN_LNX_EntityList;
////////////////////////////////
//~ Global State
typedef struct DMN_LNX_State
{
Arena *arena;
// rjf: access locking mechanism
Mutex access_mutex;
B32 access_run_state;
// rjf: entity storage
Arena *entities_arena;
DMN_LNX_Entity *entities_base;
DMN_LNX_Entity *free_entity;
U64 entities_count;
HashTable *tid_ht; // thread id -> thread entity
HashTable *pid_ht; // process id -> process entity
// process tracking
U64 process_count;
DMN_LNX_Process *first_process;
DMN_LNX_Process *last_process;
// process/thread creation tracking
U64 process_pending_creation;
U64 threads_pending_creation;
// halter
Mutex halter_mutex;
pid_t halter_tid;
U64 halt_code;
U64 halt_user_data;
B32 is_halting;
// TLS
B32 is_tls_detected;
DMN_LNX_DbDesc tls_modid_desc;
DMN_LNX_DbDesc tls_offset_desc;
} DMN_LNX_State;
////////////////////////////////
//~ Globals
global B32 dmn_lnx_ctrl_thread;
global DMN_LNX_State *dmn_lnx_state;
////////////////////////////////
//~ Memory R/W
internal U64 dmn_lnx_read(int memory_fd, Rng1U64 range, void *dst);
internal B32 dmn_lnx_write(int memory_fd, Rng1U64 range, void *src);
internal String8 dmn_lnx_read_string_capped(Arena *arena, int memory_fd, U64 base_vaddr, U64 cap_size);
internal String8 dmn_lnx_read_string(Arena *arena, int memory_fd, U64 base_vaddr);
#define dmn_lnx_read_struct(fd, vaddr, ptr) dmn_lnx_read((fd), r1u64((vaddr), (vaddr)+sizeof(*(ptr))), (ptr))
#define dmn_lnx_write_struct(fd, vaddr, ptr) dmn_lnx_write((fd), r1u64((vaddr), (vaddr)+sizeof(*(ptr))), (ptr))
////////////////////////////////
//~ ELF/GNU info
internal Rng1U64 dmn_lnx_compute_image_vrange(int memory_fd, ELF_Class elf_class, U64 rebase, U64 e_phaddr, U64 e_phentsize, U64 e_phnum);
internal DMN_LNX_DynamicInfo dmn_lnx_dynamic_info_from_memory(int memory_fd, ELF_Class elf_Class, U64 rebase, U64 dynamic_vaddr);
internal U64 dmn_lnx_rdebug_vaddr_from_memory(int memory_fd, U64 loader_vaddr, B32 is_rebased);
////////////////////////////////
//~ Process Info
internal String8 dmn_lnx_exe_path_from_pid(Arena *arena, pid_t pid);
internal String8 dmn_lnx_dl_path_from_pid(Arena *arena, pid_t pid, U64 auxv_base);
internal ELF_Hdr64 dmn_lnx_ehdr_from_pid(pid_t pid);
internal DMN_LNX_Auxv dmn_lnx_auxv_from_pid(pid_t pid, ELF_Class elf_class);
internal DMN_LNX_Thread * dmn_lnx_thread_from_pid(pid_t pid);
internal DMN_LNX_Process * dmn_lnx_process_from_pid(pid_t pid);
////////////////////////////////
//~ Entity
// alloc
internal DMN_LNX_Entity * dmn_lnx_entity_alloc(DMN_LNX_EntityKind kind);
internal DMN_LNX_Process * dmn_lnx_process_alloc(pid_t pid, DMN_LNX_ProcessState state, DMN_LNX_Process *parent_process, B32 debug_subprocess, B32 is_cow);
internal DMN_LNX_ProcessCtx * dmn_lnx_process_ctx_alloc(DMN_LNX_Process *process, B32 is_rebased);
internal DMN_LNX_Thread * dmn_lnx_thread_alloc(DMN_LNX_Process *process, DMN_LNX_ThreadState thread_state, pid_t tid);
internal DMN_LNX_Module * dmn_lnx_module_alloc(DMN_LNX_ProcessCtx *ctx, int memory_fd, U64 base_vaddr, U64 name_vaddr, U64 name_space_id, B32 is_main);
// release
internal void dmn_lnx_entity_release(DMN_LNX_Entity *entity);
internal void dmn_lnx_process_release(DMN_LNX_Process *process);
internal void dmn_lnx_process_ctx_release(DMN_LNX_ProcessCtx *process_ctx);
internal void dmn_lnx_thread_release(DMN_LNX_Thread *thread);
internal void dmn_lnx_module_release(DMN_LNX_ProcessCtx *ctx, DMN_LNX_Module *module);
// clone
internal DMN_LNX_ProcessCtx * dmn_lnx_process_ctx_clone(DMN_LNX_Process *process, DMN_LNX_ProcessCtx *ctx);
internal DMN_LNX_Module * dmn_lnx_module_clone(DMN_LNX_ProcessCtx *process_ctx, DMN_LNX_Module *module);
// entity -> handle
internal DMN_Handle dmn_lnx_handle_from_entity(DMN_LNX_Entity *entity);
internal DMN_Handle dmn_lnx_handle_from_process(DMN_LNX_Process *process);
internal DMN_Handle dmn_lnx_handle_from_process_ctx(DMN_LNX_ProcessCtx *process_ctx);
internal DMN_Handle dmn_lnx_handle_from_thread(DMN_LNX_Thread *thread);
internal DMN_Handle dmn_lnx_handle_from_module(DMN_LNX_Module *module);
// handle -> entity
internal DMN_LNX_Entity * dmn_lnx_entity_from_handle(DMN_Handle handle, DMN_LNX_EntityKind expected_kind);
internal DMN_LNX_Process * dmn_lnx_process_from_handle(DMN_Handle process_handle);
internal DMN_LNX_ProcessCtx * dmn_lnx_process_ctx_from_handle(DMN_Handle process_ctx_handle);
internal DMN_LNX_Thread * dmn_lnx_thread_from_handle(DMN_Handle thread_handle);
internal DMN_LNX_Module * dmn_lnx_module_from_handle(DMN_Handle module_handle);
////////////////////////////////
//~ Process Helpers
internal void dmn_lnx_process_trap_probes(DMN_LNX_Process *process);
internal void dmn_lnx_process_untrap_probes(DMN_LNX_Process *process);
////////////////////////////////
//~ Thread Helpers
internal U64 dmn_lnx_thread_read_ip(DMN_LNX_Thread *thread);
internal U64 dmn_lnx_thread_read_sp(DMN_LNX_Thread *thread);
internal void dmn_lnx_thread_write_ip(DMN_LNX_Thread *thread, U64 ip);
internal void dmn_lnx_thread_write_sp(DMN_LNX_Thread *thread, U64 sp);
internal B32 dmn_lnx_thread_read_reg_block(DMN_LNX_Thread *thread);
internal B32 dmn_lnx_thread_write_reg_block(DMN_LNX_Thread *thread);
internal B32 dmn_lnx_set_single_step_flag(DMN_LNX_Thread *thread, B32 is_on);
////////////////////////////////
//~ List Helpers
internal void dmn_lnx_thread_ptr_list_push_node(DMN_LNX_ThreadPtrList *list, DMN_LNX_ThreadPtrNode *n);
internal DMN_LNX_ThreadPtrNode * dmn_lnx_thread_ptr_list_push(Arena *arena, DMN_LNX_ThreadPtrList *list, DMN_LNX_Thread *v);
internal DMN_LNX_ModulePtrNode * dmn_lnx_module_ptr_list_push(Arena *arena, DMN_LNX_ModulePtrList *list, DMN_LNX_Module *v);
internal DMN_LNX_ProcessPtrNode * dmn_lnx_process_ptr_list_push(Arena *arena, DMN_LNX_ProcessPtrList *list, DMN_LNX_Process *v);
////////////////////////////////
//~ Debug Event Pushers
internal void dmn_lnx_push_event_create_process(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process);
internal void dmn_lnx_push_event_exit_process(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process);
internal void dmn_lnx_push_event_create_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread);
internal void dmn_lnx_push_event_exit_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 exit_code);
internal void dmn_lnx_push_event_load_module(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, DMN_LNX_Module *module);
internal void dmn_lnx_push_event_unload_module(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process, DMN_LNX_Module *module);
internal void dmn_lnx_push_event_handshake_complete(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process);
internal void dmn_lnx_push_event_breakpoint(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 address);
internal void dmn_lnx_push_event_single_step(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread);
internal void dmn_lnx_push_event_exception(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 signo);
internal void dmn_lnx_push_event_halt(Arena *arena, DMN_EventList *events);
internal void dmn_lnx_push_event_not_attached(Arena *arena, DMN_EventList *events);
////////////////////////////////
//~ Debug Event
internal DMN_LNX_Thread * dmn_lnx_event_create_thread(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process, pid_t tid);
internal void dmn_lnx_event_exit_thread(Arena *arena, DMN_EventList *events, pid_t tid, U64 exit_code);
internal DMN_LNX_Process * dmn_lnx_event_create_process(Arena *arena, DMN_EventList *events, pid_t pid, DMN_LNX_Process *parent_process, DMN_LNX_CreateProcessFlags flags);
internal void dmn_lnx_event_exit_process(Arena *arena, DMN_EventList *events, pid_t pid);
internal void dmn_lnx_event_load_module(Arena *arena, DMN_EventList *events, DMN_LNX_Thread *thread, U64 name_space_id, U64 new_link_map_vaddr);
internal void dmn_lnx_event_unload_module(Arena *arena, DMN_EventList *events, DMN_LNX_Process *process, U64 rdebug_vaddr);
internal void dmn_lnx_event_breakpoint(Arena *arena, DMN_EventList *events, DMN_ActiveTrap *user_traps, pid_t tid);
internal void dmn_lnx_event_data_breakpoint(Arena *arena, DMN_EventList *events, pid_t tid);
internal void dmn_lnx_event_halt(Arena *arena, DMN_EventList *events);
internal void dmn_lnx_event_single_step(Arena *arena, DMN_EventList *events, pid_t tid);
internal void dmn_lnx_event_exception(Arena *arena, DMN_EventList *events, pid_t tid, U64 signo);
internal DMN_LNX_Process * dmn_lnx_event_attach(Arena *arena, DMN_EventList *events, pid_t pid);
#endif // DEMON_CORE_LINUX_H
@@ -0,0 +1,719 @@
// Copyright (c) Epic Games Tools
// Licensed under the MIT license (https://opensource.org/license/mit/)
////////////////////////////////
//~ rjf: Helpers
internal OS_LNX_Window *
os_lnx_window_from_x11window(Window window)
{
OS_LNX_Window *result = 0;
for(OS_LNX_Window *w = os_lnx_gfx_state->first_window; w != 0; w = w->next)
{
if(w->window == window)
{
result = w;
break;
}
}
return result;
}
////////////////////////////////
//~ rjf: @os_hooks Main Initialization API (Implemented Per-OS)
internal void
os_gfx_init(void)
{
//- rjf: initialize basics
Arena *arena = arena_alloc();
os_lnx_gfx_state = push_array(arena, OS_LNX_GfxState, 1);
os_lnx_gfx_state->arena = arena;
os_lnx_gfx_state->display = XOpenDisplay(0);
//- rjf: calculate atoms
os_lnx_gfx_state->wm_delete_window_atom = XInternAtom(os_lnx_gfx_state->display, "WM_DELETE_WINDOW", 0);
os_lnx_gfx_state->wm_sync_request_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_SYNC_REQUEST", 0);
os_lnx_gfx_state->wm_sync_request_counter_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_SYNC_REQUEST_COUNTER", 0);
//- rjf: open im
os_lnx_gfx_state->xim = XOpenIM(os_lnx_gfx_state->display, 0, 0, 0);
//- rjf: fill out gfx info
os_lnx_gfx_state->gfx_info.double_click_time = 0.5f;
os_lnx_gfx_state->gfx_info.caret_blink_time = 0.5f;
os_lnx_gfx_state->gfx_info.default_refresh_rate = 60.f;
//- rjf: fill out cursors
{
struct
{
OS_Cursor cursor;
unsigned int id;
}
map[] =
{
{OS_Cursor_Pointer, XC_left_ptr},
{OS_Cursor_IBar, XC_xterm},
{OS_Cursor_LeftRight, XC_sb_h_double_arrow},
{OS_Cursor_UpDown, XC_sb_v_double_arrow},
{OS_Cursor_DownRight, XC_bottom_right_corner},
{OS_Cursor_UpRight, XC_top_right_corner},
{OS_Cursor_UpDownLeftRight, XC_fleur},
{OS_Cursor_HandPoint, XC_hand1},
{OS_Cursor_Disabled, XC_X_cursor},
};
for EachElement(idx, map)
{
os_lnx_gfx_state->cursors[map[idx].cursor] = XCreateFontCursor(os_lnx_gfx_state->display, map[idx].id);
}
}
}
////////////////////////////////
//~ rjf: @os_hooks Graphics System Info (Implemented Per-OS)
internal OS_GfxInfo *
os_get_gfx_info(void)
{
return &os_lnx_gfx_state->gfx_info;
}
////////////////////////////////
//~ rjf: @os_hooks Clipboards (Implemented Per-OS)
internal void
os_set_clipboard_text(String8 string)
{
}
internal String8
os_get_clipboard_text(Arena *arena)
{
String8 result = {0};
return result;
}
////////////////////////////////
//~ rjf: @os_hooks Windows (Implemented Per-OS)
internal OS_Window
os_window_open(Rng2F32 rect, OS_WindowFlags flags, String8 title)
{
Vec2F32 resolution = dim_2f32(rect);
//- rjf: allocate window
OS_LNX_Window *w = os_lnx_gfx_state->free_window;
if(w)
{
SLLStackPop(os_lnx_gfx_state->free_window);
}
else
{
w = push_array_no_zero(os_lnx_gfx_state->arena, OS_LNX_Window, 1);
}
MemoryZeroStruct(w);
DLLPushBack(os_lnx_gfx_state->first_window, os_lnx_gfx_state->last_window, w);
//- rjf: create window & equip with x11 info
w->window = XCreateWindow(os_lnx_gfx_state->display,
XDefaultRootWindow(os_lnx_gfx_state->display),
0, 0, resolution.x, resolution.y,
0,
CopyFromParent,
InputOutput,
CopyFromParent,
0,
0);
XSelectInput(os_lnx_gfx_state->display, w->window,
ExposureMask|
PointerMotionMask|
ButtonPressMask|
ButtonReleaseMask|
KeyPressMask|
KeyReleaseMask|
FocusChangeMask);
Atom protocols[] =
{
os_lnx_gfx_state->wm_delete_window_atom,
os_lnx_gfx_state->wm_sync_request_atom,
};
XSetWMProtocols(os_lnx_gfx_state->display, w->window, protocols, ArrayCount(protocols));
{
XSyncValue initial_value;
XSyncIntToValue(&initial_value, 0);
w->counter_xid = XSyncCreateCounter(os_lnx_gfx_state->display, initial_value);
}
XChangeProperty(os_lnx_gfx_state->display, w->window, os_lnx_gfx_state->wm_sync_request_counter_atom, XA_CARDINAL, 32, PropModeReplace, (U8 *)&w->counter_xid, 1);
//- rjf: create xic
w->xic = XCreateIC(os_lnx_gfx_state->xim,
XNInputStyle, XIMPreeditNothing|XIMStatusNothing,
XNClientWindow, w->window,
XNFocusWindow, w->window,
NULL);
//- rjf: attach name
Temp scratch = scratch_begin(0, 0);
String8 title_copy = push_str8_copy(scratch.arena, title);
XStoreName(os_lnx_gfx_state->display, w->window, (char *)title_copy.str);
scratch_end(scratch);
//- rjf: convert to handle & return
OS_Window handle = {(U64)w};
return handle;
}
internal void
os_window_close(OS_Window handle)
{
if(os_window_match(handle, os_window_zero())) {return;}
OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0];
XDestroyWindow(os_lnx_gfx_state->display, w->window);
}
internal void
os_window_set_title(OS_Window handle, String8 title)
{
if(os_window_match(handle, os_window_zero())) {return;}
Temp scratch = scratch_begin(0, 0);
OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0];
String8 title_copy = push_str8_copy(scratch.arena, title);
XStoreName(os_lnx_gfx_state->display, w->window, (char *)title_copy.str);
scratch_end(scratch);
}
internal void
os_window_first_paint(OS_Window handle)
{
if(os_window_match(handle, os_window_zero())) {return;}
OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0];
XMapWindow(os_lnx_gfx_state->display, w->window);
}
internal void
os_window_focus(OS_Window handle)
{
if(os_window_match(handle, os_window_zero())) {return;}
OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0];
XSetInputFocus(os_lnx_gfx_state->display, w->window, RevertToNone, CurrentTime);
}
internal B32
os_window_is_focused(OS_Window handle)
{
if(os_window_match(handle, os_window_zero())) {return 0;}
OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0];
Window focused_window = 0;
int revert_to = 0;
XGetInputFocus(os_lnx_gfx_state->display, &focused_window, &revert_to);
B32 result = (w->window == focused_window);
return result;
}
internal B32
os_window_is_fullscreen(OS_Window handle)
{
if(os_window_match(handle, os_window_zero())) {return 0;}
// TODO(rjf)
return 0;
}
internal void
os_window_set_fullscreen(OS_Window handle, B32 fullscreen)
{
if(os_window_match(handle, os_window_zero())) {return;}
// TODO(rjf)
}
internal B32
os_window_is_maximized(OS_Window handle)
{
if(os_window_match(handle, os_window_zero())) {return 0;}
// TODO(rjf)
return 0;
}
internal void
os_window_set_maximized(OS_Window handle, B32 maximized)
{
if(os_window_match(handle, os_window_zero())) {return;}
// TODO(rjf)
}
internal B32
os_window_is_minimized(OS_Window handle)
{
if(os_window_match(handle, os_window_zero())) {return 0;}
// TODO(rjf)
return 0;
}
internal void
os_window_set_minimized(OS_Window handle, B32 minimized)
{
if(os_window_match(handle, os_window_zero())) {return;}
// TODO(rjf)
}
internal void
os_window_bring_to_front(OS_Window handle)
{
if(os_window_match(handle, os_window_zero())) {return;}
// TODO(rjf)
}
internal void
os_window_set_monitor(OS_Window handle, OS_Monitor monitor)
{
if(os_window_match(handle, os_window_zero())) {return;}
// TODO(rjf)
}
internal void
os_window_clear_custom_border_data(OS_Window handle)
{
if(os_window_match(handle, os_window_zero())) {return;}
// TODO(rjf)
}
internal void
os_window_push_custom_title_bar(OS_Window handle, F32 thickness)
{
if(os_window_match(handle, os_window_zero())) {return;}
// TODO(rjf)
}
internal void
os_window_push_custom_edges(OS_Window handle, F32 thickness)
{
if(os_window_match(handle, os_window_zero())) {return;}
// TODO(rjf)
}
internal void
os_window_push_custom_title_bar_client_area(OS_Window handle, Rng2F32 rect)
{
if(os_window_match(handle, os_window_zero())) {return;}
// TODO(rjf)
}
internal Rng2F32
os_rect_from_window(OS_Window handle)
{
if(os_window_match(handle, os_window_zero())) {return r2f32p(0, 0, 0, 0);}
OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0];
XWindowAttributes atts = {0};
Status s = XGetWindowAttributes(os_lnx_gfx_state->display, w->window, &atts);
Rng2F32 result = r2f32p((F32)atts.x, (F32)atts.y, (F32)atts.x + (F32)atts.width, (F32)atts.y + (F32)atts.height);
return result;
}
internal Rng2F32
os_client_rect_from_window(OS_Window handle)
{
OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0];
XWindowAttributes atts = {0};
Status s = XGetWindowAttributes(os_lnx_gfx_state->display, w->window, &atts);
Rng2F32 result = r2f32p(0, 0, (F32)atts.width, (F32)atts.height);
return result;
}
internal F32
os_dpi_from_window(OS_Window handle)
{
// TODO(rjf)
return 96.f;
}
////////////////////////////////
//~ rjf: @os_hooks External Windows (Implemented Per-OS)
internal OS_ExternalWindow
os_focused_external_window(void)
{
OS_ExternalWindow result = {0};
// TODO(rjf)
return result;
}
internal void
os_focus_external_window(OS_ExternalWindow handle)
{
// TODO(rjf)
}
////////////////////////////////
//~ rjf: @os_hooks Monitors (Implemented Per-OS)
internal OS_MonitorArray
os_push_monitors_array(Arena *arena)
{
OS_MonitorArray result = {0};
// TODO(rjf)
return result;
}
internal OS_Monitor
os_primary_monitor(void)
{
OS_Monitor result = {0};
// TODO(rjf)
return result;
}
internal OS_Monitor
os_monitor_from_window(OS_Window window)
{
OS_Monitor result = {0};
// TODO(rjf)
return result;
}
internal String8
os_name_from_monitor(Arena *arena, OS_Monitor monitor)
{
// TODO(rjf)
return str8_zero();
}
internal Vec2F32
os_dim_from_monitor(OS_Monitor monitor)
{
// TODO(rjf)
return v2f32(0, 0);
}
internal F32
os_dpi_from_monitor(OS_Monitor monitor)
{
// TODO(rjf)
return 96.f;
}
////////////////////////////////
//~ rjf: @os_hooks Events (Implemented Per-OS)
internal void
os_send_wakeup_event(void)
{
// TODO(rjf)
}
internal OS_EventList
os_get_events(Arena *arena, B32 wait)
{
OS_EventList evts = {0};
for(;XPending(os_lnx_gfx_state->display) > 0 || (wait && evts.count == 0);)
{
XEvent evt = {0};
XNextEvent(os_lnx_gfx_state->display, &evt);
B32 set_mouse_cursor = 0;
switch(evt.type)
{
default:{}break;
//- rjf: key presses/releases
case KeyPress:
case KeyRelease:
{
// rjf: determine flags
OS_Modifiers modifiers = 0;
if(evt.xkey.state & ShiftMask) { modifiers |= OS_Modifier_Shift; }
if(evt.xkey.state & ControlMask) { modifiers |= OS_Modifier_Ctrl; }
if(evt.xkey.state & Mod1Mask) { modifiers |= OS_Modifier_Alt; }
// rjf: map keycode -> keysym & codepoint
OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xkey.window);
KeySym keysym = 0;
U8 text[256] = {0};
U64 text_size = Xutf8LookupString(window->xic, &evt.xkey, (char *)text, sizeof(text), &keysym, 0);
// rjf: map keysym -> OS_Key
B32 is_right_sided = 0;
OS_Key key = OS_Key_Null;
switch(keysym)
{
default:
{
if(0){}
else if(XK_F1 <= keysym && keysym <= XK_F24) { key = (OS_Key)(OS_Key_F1 + (keysym - XK_F1)); }
else if('0' <= keysym && keysym <= '9') { key = OS_Key_0 + (keysym-'0'); }
}break;
case XK_Escape:{key = OS_Key_Esc;};break;
case XK_BackSpace:{key = OS_Key_Backspace;}break;
case XK_Delete:{key = OS_Key_Delete;}break;
case XK_Return:{key = OS_Key_Return;}break;
case XK_Pause:{key = OS_Key_Pause;}break;
case XK_Tab:{key = OS_Key_Tab;}break;
case XK_Left:{key = OS_Key_Left;}break;
case XK_Right:{key = OS_Key_Right;}break;
case XK_Up:{key = OS_Key_Up;}break;
case XK_Down:{key = OS_Key_Down;}break;
case XK_Home:{key = OS_Key_Home;}break;
case XK_End:{key = OS_Key_End;}break;
case XK_Page_Up:{key = OS_Key_PageUp;}break;
case XK_Page_Down:{key = OS_Key_PageDown;}break;
case XK_Alt_L:{ key = OS_Key_Alt; }break;
case XK_Alt_R:{ key = OS_Key_Alt; is_right_sided = 1;}break;
case XK_Shift_L:{ key = OS_Key_Shift; }break;
case XK_Shift_R:{ key = OS_Key_Shift; is_right_sided = 1;}break;
case XK_Control_L:{ key = OS_Key_Ctrl; }break;
case XK_Control_R:{ key = OS_Key_Ctrl; is_right_sided = 1;}break;
case '-':{key = OS_Key_Minus;}break;
case '=':{key = OS_Key_Equal;}break;
case '[':{key = OS_Key_LeftBracket;}break;
case ']':{key = OS_Key_RightBracket;}break;
case ';':{key = OS_Key_Semicolon;}break;
case '\'':{key = OS_Key_Quote;}break;
case '.':{key = OS_Key_Period;}break;
case ',':{key = OS_Key_Comma;}break;
case '/':{key = OS_Key_Slash;}break;
case '\\':{key = OS_Key_BackSlash;}break;
case '\t':{key = OS_Key_Tab;}break;
case 'a':case 'A':{key = OS_Key_A;}break;
case 'b':case 'B':{key = OS_Key_B;}break;
case 'c':case 'C':{key = OS_Key_C;}break;
case 'd':case 'D':{key = OS_Key_D;}break;
case 'e':case 'E':{key = OS_Key_E;}break;
case 'f':case 'F':{key = OS_Key_F;}break;
case 'g':case 'G':{key = OS_Key_G;}break;
case 'h':case 'H':{key = OS_Key_H;}break;
case 'i':case 'I':{key = OS_Key_I;}break;
case 'j':case 'J':{key = OS_Key_J;}break;
case 'k':case 'K':{key = OS_Key_K;}break;
case 'l':case 'L':{key = OS_Key_L;}break;
case 'm':case 'M':{key = OS_Key_M;}break;
case 'n':case 'N':{key = OS_Key_N;}break;
case 'o':case 'O':{key = OS_Key_O;}break;
case 'p':case 'P':{key = OS_Key_P;}break;
case 'q':case 'Q':{key = OS_Key_Q;}break;
case 'r':case 'R':{key = OS_Key_R;}break;
case 's':case 'S':{key = OS_Key_S;}break;
case 't':case 'T':{key = OS_Key_T;}break;
case 'u':case 'U':{key = OS_Key_U;}break;
case 'v':case 'V':{key = OS_Key_V;}break;
case 'w':case 'W':{key = OS_Key_W;}break;
case 'x':case 'X':{key = OS_Key_X;}break;
case 'y':case 'Y':{key = OS_Key_Y;}break;
case 'z':case 'Z':{key = OS_Key_Z;}break;
case ' ':{key = OS_Key_Space;}break;
}
// rjf: push text event
if(evt.type == KeyPress && text_size != 0)
{
for(U64 off = 0; off < text_size;)
{
UnicodeDecode decode = utf8_decode(text+off, text_size-off);
if(decode.codepoint != 0 && (decode.codepoint >= 32 || decode.codepoint == '\t'))
{
OS_Event *e = os_event_list_push_new(arena, &evts, OS_EventKind_Text);
e->window.u64[0] = (U64)window;
e->character = decode.codepoint;
}
if(decode.inc == 0)
{
break;
}
off += decode.inc;
}
}
// rjf: push key event
{
OS_Event *e = os_event_list_push_new(arena, &evts, evt.type == KeyPress ? OS_EventKind_Press : OS_EventKind_Release);
e->window.u64[0] = (U64)window;
e->modifiers = modifiers;
e->key = key;
e->right_sided = is_right_sided;
}
}break;
//- rjf: mouse button presses/releases
case ButtonPress:
case ButtonRelease:
{
// rjf: determine flags
OS_Modifiers modifiers = 0;
if(evt.xbutton.state & ShiftMask) { modifiers |= OS_Modifier_Shift; }
if(evt.xbutton.state & ControlMask) { modifiers |= OS_Modifier_Ctrl; }
if(evt.xbutton.state & Mod1Mask) { modifiers |= OS_Modifier_Alt; }
// rjf: map button -> OS_Key
OS_Key key = OS_Key_Null;
switch(evt.xbutton.button)
{
default:{}break;
case Button1:{key = OS_Key_LeftMouseButton;}break;
case Button2:{key = OS_Key_MiddleMouseButton;}break;
case Button3:{key = OS_Key_RightMouseButton;}break;
}
// rjf: push event
OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xbutton.window);
if(key != OS_Key_Null)
{
OS_Event *e = os_event_list_push_new(arena, &evts, evt.type == ButtonPress ? OS_EventKind_Press : OS_EventKind_Release);
e->window.u64[0] = (U64)window;
e->modifiers = modifiers;
e->key = key;
e->pos = v2f32((F32)evt.xbutton.x, (F32)evt.xbutton.y);
}
else if(evt.xbutton.button == Button4 ||
evt.xbutton.button == Button5)
{
OS_Event *e = os_event_list_push_new(arena, &evts, OS_EventKind_Scroll);
e->window.u64[0] = (U64)window;
e->modifiers = modifiers;
e->delta = v2f32(0, evt.xbutton.button == Button4 ? -1.f : +1.f);
e->pos = v2f32((F32)evt.xbutton.x, (F32)evt.xbutton.y);
}
}break;
//- rjf: mouse motion
case MotionNotify:
{
OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xclient.window);
OS_Event *e = os_event_list_push_new(arena, &evts, OS_EventKind_MouseMove);
e->window.u64[0] = (U64)window;
e->pos.x = (F32)evt.xmotion.x;
e->pos.y = (F32)evt.xmotion.y;
set_mouse_cursor = 1;
}break;
//- rjf: window focus/unfocus
case FocusIn:
{
}break;
case FocusOut:
{
OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xfocus.window);
OS_Event *e = os_event_list_push_new(arena, &evts, OS_EventKind_WindowLoseFocus);
e->window.u64[0] = (U64)window;
}break;
//- rjf: client messages
case ClientMessage:
{
if((Atom)evt.xclient.data.l[0] == os_lnx_gfx_state->wm_delete_window_atom)
{
OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xclient.window);
OS_Event *e = os_event_list_push_new(arena, &evts, OS_EventKind_WindowClose);
e->window.u64[0] = (U64)window;
}
else if((Atom)evt.xclient.data.l[0] == os_lnx_gfx_state->wm_sync_request_atom)
{
OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xclient.window);
if(window != 0)
{
window->counter_value = 0;
window->counter_value |= evt.xclient.data.l[2];
window->counter_value |= (evt.xclient.data.l[3] << 32);
XSyncValue value;
XSyncIntToValue(&value, window->counter_value);
XSyncSetCounter(os_lnx_gfx_state->display, window->counter_xid, value);
}
}
}break;
}
if(set_mouse_cursor)
{
Window root_window = 0;
Window child_window = 0;
int root_rel_x = 0;
int root_rel_y = 0;
int child_rel_x = 0;
int child_rel_y = 0;
unsigned int mask = 0;
if(XQueryPointer(os_lnx_gfx_state->display, XDefaultRootWindow(os_lnx_gfx_state->display), &root_window, &child_window, &root_rel_x, &root_rel_y, &child_rel_x, &child_rel_y, &mask))
{
XDefineCursor(os_lnx_gfx_state->display, root_window, os_lnx_gfx_state->cursors[os_lnx_gfx_state->last_set_cursor]);
XFlush(os_lnx_gfx_state->display);
}
}
}
return evts;
}
internal OS_Modifiers
os_get_modifiers(void)
{
// TODO(rjf)
return 0;
}
internal B32
os_key_is_down(OS_Key key)
{
// TODO(rjf)
return 0;
}
internal Vec2F32
os_mouse_from_window(OS_Window handle)
{
if(os_window_match(handle, os_window_zero())) {return v2f32(0, 0);}
OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0];
Vec2F32 result = {0};
{
Window root_window = 0;
Window child_window = 0;
int root_rel_x = 0;
int root_rel_y = 0;
int child_rel_x = 0;
int child_rel_y = 0;
unsigned int mask = 0;
if(XQueryPointer(os_lnx_gfx_state->display, w->window, &root_window, &child_window, &root_rel_x, &root_rel_y, &child_rel_x, &child_rel_y, &mask))
{
result.x = child_rel_x;
result.y = child_rel_y;
}
}
return result;
}
////////////////////////////////
//~ rjf: @os_hooks Cursors (Implemented Per-OS)
internal void
os_set_cursor(OS_Cursor cursor)
{
os_lnx_gfx_state->last_set_cursor = cursor;
}
////////////////////////////////
//~ rjf: @os_hooks Native User-Facing Graphical Messages (Implemented Per-OS)
internal void
os_graphical_message(B32 error, String8 title, String8 message)
{
if(error)
{
fprintf(stderr, "[X] ");
}
fprintf(stderr, "%.*s\n", str8_varg(title));
fprintf(stderr, "%.*s\n\n", str8_varg(message));
}
internal String8
os_graphical_pick_file(Arena *arena, String8 initial_path)
{
return str8_zero();
}
////////////////////////////////
//~ rjf: @os_hooks Shell Operations
internal void
os_show_in_filesystem_ui(String8 path)
{
// TODO(rjf)
}
internal void
os_open_in_browser(String8 url)
{
// TODO(rjf)
}
@@ -0,0 +1,61 @@
// Copyright (c) Epic Games Tools
// Licensed under the MIT license (https://opensource.org/license/mit/)
#ifndef OS_GFX_LINUX_H
#define OS_GFX_LINUX_H
////////////////////////////////
//~ rjf: Includes
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/cursorfont.h>
#include <X11/extensions/sync.h>
#include <X11/keysym.h>
#include <X11/keysymdef.h>
////////////////////////////////
//~ rjf: Window State
typedef struct OS_LNX_Window OS_LNX_Window;
struct OS_LNX_Window
{
OS_LNX_Window *next;
OS_LNX_Window *prev;
Window window;
XIC xic;
XID counter_xid;
U64 counter_value;
};
////////////////////////////////
//~ rjf: State Bundle
typedef struct OS_LNX_GfxState OS_LNX_GfxState;
struct OS_LNX_GfxState
{
Arena *arena;
Display *display;
XIM xim;
OS_LNX_Window *first_window;
OS_LNX_Window *last_window;
OS_LNX_Window *free_window;
Atom wm_delete_window_atom;
Atom wm_sync_request_atom;
Atom wm_sync_request_counter_atom;
Cursor cursors[OS_Cursor_COUNT];
OS_Cursor last_set_cursor;
OS_GfxInfo gfx_info;
};
////////////////////////////////
//~ rjf: Globals
global OS_LNX_GfxState *os_lnx_gfx_state = 0;
////////////////////////////////
//~ rjf: Helpers
internal OS_LNX_Window *os_lnx_window_from_x11window(Window window);
#endif // OS_GFX_LINUX_H