4coder/code/platform_linux/linux_4ed.cpp

2042 lines
65 KiB
C++
Raw Normal View History

2016-09-04 20:41:48 +00:00
/*
2020-02-02 15:50:18 +00:00
* chr - Andrew Chronister &
* inso - Alex Baines
2016-09-04 20:41:48 +00:00
*
* 12.19.2019
2016-09-04 20:41:48 +00:00
*
* Updated linux layer for 4coder
2016-09-04 20:41:48 +00:00
*
*/
// TOP
#include <stdio.h>
#define FPS 60
#define frame_useconds (Million(1) / FPS)
#define frame_nseconds (Billion(1) / FPS)
2020-02-02 15:50:18 +00:00
#define SLASH '/'
#define DLL "so"
2019-06-01 23:58:28 +00:00
#include "4coder_base_types.h"
#include "4coder_version.h"
#include "4coder_events.h"
2016-09-04 20:41:48 +00:00
#include "4coder_table.h"
#include "4coder_types.h"
#include "4coder_default_colors.h"
#include "4coder_system_types.h"
2020-02-02 15:50:18 +00:00
#include "4ed_font_interface.h"
#define STATIC_LINK_API
#include "generated/system_api.h"
2016-09-04 20:41:48 +00:00
#define STATIC_LINK_API
#include "generated/graphics_api.h"
2020-02-02 15:50:18 +00:00
#define STATIC_LINK_API
#include "generated/font_api.h"
2016-09-04 20:41:48 +00:00
#include "4ed_font_set.h"
2017-11-10 18:27:39 +00:00
#include "4ed_render_target.h"
#include "4coder_search_list.h"
2016-09-04 20:41:48 +00:00
#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"
2020-02-02 15:50:18 +00:00
#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 "4coder_search_list.cpp"
2020-02-02 15:50:18 +00:00
#include "4ed_font_provider_freetype.h"
#include "4ed_font_provider_freetype.cpp"
2016-09-04 20:41:48 +00:00
2020-01-12 00:19:37 +00:00
#include <dirent.h>
2020-01-12 05:27:40 +00:00
#include <dlfcn.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
2020-02-02 15:50:18 +00:00
#include <locale.h>
#include <errno.h>
2020-01-12 05:27:40 +00:00
#include <pthread.h>
#include <sys/epoll.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
2020-02-02 15:50:18 +00:00
#include <sys/timerfd.h>
#include <sys/eventfd.h>
2020-02-03 21:18:46 +00:00
#include <sys/syscall.h>
2020-01-12 05:27:40 +00:00
#define Cursor XCursor
#undef function
2020-02-16 13:58:58 +00:00
#undef internal
2020-01-12 05:27:40 +00:00
#include <X11/Xlib.h>
2020-02-02 15:50:18 +00:00
#include <X11/Xutil.h>
#include <X11/cursorfont.h>
#include <X11/Xatom.h>
#include <X11/extensions/Xfixes.h>
2020-02-16 13:58:58 +00:00
#include <X11/XKBlib.h>
2020-02-03 21:18:46 +00:00
#include <X11/keysym.h>
2020-01-12 05:27:40 +00:00
#define function static
#undef Cursor
2020-05-16 12:19:25 +00:00
//#include <fontconfig/fontconfig.h>
2020-02-04 21:38:37 +00:00
#define internal static
2020-02-02 15:50:18 +00:00
#include <GL/glx.h>
#include <GL/glext.h>
#ifdef INSO_DEBUG
2020-02-20 05:32:07 +00:00
#define LINUX_FN_DEBUG(fmt, ...) do { \
fprintf(stderr, "%s: " fmt "\n", __func__, ##__VA_ARGS__);\
} while (0)
2020-02-02 20:20:27 +00:00
// I want to see a message
2020-02-20 05:32:07 +00:00
#undef AssertBreak
#define AssertBreak(m) ({\
fprintf(stderr, "\n** ASSERTION FAILURE: %s:%d: %s\n\n", __FILE__, __LINE__, #m);\
2020-02-20 06:03:48 +00:00
*((volatile u64*)0) = 0xba771e70ad5;\
2020-02-20 05:32:07 +00:00
})
2020-02-02 15:50:18 +00:00
#else
2020-02-20 05:32:07 +00:00
#define LINUX_FN_DEBUG(...)
2020-02-02 15:50:18 +00:00
#endif
2021-05-17 06:13:27 +00:00
////////////////////////////////
global b32 log_os_enabled = false;
#define log_os(...) \
Stmnt( if (log_os_enabled){ fprintf(stdout, __VA_ARGS__); fflush(stdout); } )
2020-01-12 05:27:40 +00:00
////////////////////////////
2020-02-03 21:18:46 +00:00
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;
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;
b8 mouse_out_of_window;
2020-02-03 21:18:46 +00:00
};
struct Linux_Input_Chunk {
Linux_Input_Chunk_Transient trans;
Linux_Input_Chunk_Persistent pers;
};
2020-02-16 22:47:42 +00:00
struct Linux_Memory_Tracker_Node {
Linux_Memory_Tracker_Node* prev;
Linux_Memory_Tracker_Node* next;
String_Const_u8 location;
u64 size;
};
2020-01-12 05:27:40 +00:00
struct Linux_Vars {
2020-02-02 20:20:27 +00:00
Thread_Context tctx;
Arena frame_arena;
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
Display* dpy;
Window win;
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
b32 has_xfixes;
int xfixes_selection_event;
XIM xim;
XIC xic;
2020-05-16 12:19:25 +00:00
//FcConfig* fontconfig;
2020-02-16 13:58:58 +00:00
XkbDescPtr xkb;
2020-02-20 05:32:07 +00:00
2020-02-03 21:18:46 +00:00
Linux_Input_Chunk input;
2020-02-16 13:58:58 +00:00
int xkb_event;
int xkb_group; // active keyboard layout (0-3)
KeyCode prev_filtered_key;
2020-02-20 05:32:07 +00:00
Key_Mode key_mode;
2020-02-02 15:50:18 +00:00
int epoll;
int step_timer_fd;
u64 last_step_time;
2020-02-20 05:32:07 +00:00
2020-02-08 17:46:21 +00:00
XCursor xcursors[APP_MOUSE_CURSOR_COUNT];
Application_Mouse_Cursor cursor;
XCursor hidden_cursor;
2020-02-02 15:50:18 +00:00
i32 cursor_show;
i32 prev_cursor_show;
2020-02-20 05:32:07 +00:00
2020-01-12 05:27:40 +00:00
Node free_linux_objects;
2020-02-02 15:50:18 +00:00
Node timer_objects;
2020-02-20 05:32:07 +00:00
2020-01-12 06:07:01 +00:00
System_Mutex global_frame_mutex;
2020-02-16 22:47:42 +00:00
pthread_mutex_t memory_tracker_mutex;
Linux_Memory_Tracker_Node* memory_tracker_head;
Linux_Memory_Tracker_Node* memory_tracker_tail;
int memory_tracker_count;
2020-02-20 05:32:07 +00:00
Arena clipboard_arena;
2020-02-02 15:50:18 +00:00
String_Const_u8 clipboard_contents;
b32 received_new_clipboard;
b32 clipboard_catch_all;
2020-11-24 20:11:47 +00:00
pthread_mutex_t audio_mutex;
pthread_cond_t audio_cond;
void* audio_ctx;
Audio_Mix_Sources_Function* audio_src_func;
Audio_Mix_Destination_Function* audio_dst_func;
System_Thread audio_thread;
2020-02-02 15:50:18 +00:00
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;
2021-05-17 06:13:27 +00:00
Log_Function *log_string;
2020-01-12 05:27:40 +00:00
};
2020-02-02 15:50:18 +00:00
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;
2020-01-12 05:27:40 +00:00
enum {
EPOLL_STEP_TIMER,
2020-02-02 15:50:18 +00:00
EPOLL_X11,
EPOLL_X11_INTERNAL,
EPOLL_CLI_PIPE,
EPOLL_USER_TIMER,
2020-01-12 05:27:40 +00:00
};
2020-02-02 15:50:18 +00:00
// 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_timer = EPOLL_STEP_TIMER;
2020-02-02 15:50:18 +00:00
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;
////////////////////////////
2020-01-12 05:27:40 +00:00
typedef i32 Linux_Object_Kind;
enum {
2020-02-02 15:50:18 +00:00
LinuxObjectKind_ERROR = 0,
LinuxObjectKind_Timer = 1,
LinuxObjectKind_Thread = 2,
LinuxObjectKind_Mutex = 3,
LinuxObjectKind_ConditionVariable = 4,
2020-01-12 05:27:40 +00:00
};
struct Linux_Object {
Linux_Object_Kind kind;
Node node;
union {
2020-02-02 15:50:18 +00:00
struct {
int fd;
Epoll_Kind epoll_tag;
} timer;
2020-01-12 05:27:40 +00:00
struct {
pthread_t pthread;
Thread_Function* proc;
void* ptr;
} thread;
2020-01-12 06:07:01 +00:00
pthread_mutex_t mutex;
pthread_cond_t condition_variable;
2020-01-12 05:27:40 +00:00
};
};
2020-02-02 15:50:18 +00:00
Linux_Object*
handle_to_object(Plat_Handle ph){
return *(Linux_Object**)&ph;
}
2020-01-12 05:27:40 +00:00
2020-11-24 20:11:47 +00:00
Plat_Handle
object_to_handle(Linux_Object* obj) {
return *(Plat_Handle*)&obj;
}
2020-01-12 05:27:40 +00:00
internal Linux_Object*
linux_alloc_object(Linux_Object_Kind kind){
Linux_Object* result = NULL;
2020-02-20 05:32:07 +00:00
2020-01-12 05:27:40 +00:00
if (linuxvars.free_linux_objects.next != &linuxvars.free_linux_objects) {
result = CastFromMember(Linux_Object, node, linuxvars.free_linux_objects.next);
}
2020-02-20 05:32:07 +00:00
2020-01-12 05:27:40 +00:00
if (result == NULL) {
i32 count = 512;
2020-02-20 05:32:07 +00:00
2020-01-12 05:27:40 +00:00
Linux_Object* objects = (Linux_Object*)system_memory_allocate(
2020-02-20 05:32:07 +00:00
sizeof(Linux_Object) * count,
file_name_line_number_lit_u8
);
2020-01-12 05:27:40 +00:00
objects[0].node.prev = &linuxvars.free_linux_objects;
2020-02-02 20:20:27 +00:00
linuxvars.free_linux_objects.next = &objects[0].node;
2020-01-12 05:27:40 +00:00
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;
2020-02-20 05:32:07 +00:00
2020-01-12 05:27:40 +00:00
result = CastFromMember(Linux_Object, node, linuxvars.free_linux_objects.next);
}
2020-02-20 05:32:07 +00:00
2020-01-12 05:27:40 +00:00
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);
}
2016-09-04 20:41:48 +00:00
2020-02-02 15:50:18 +00:00
////////////////////////////
2020-01-12 00:19:37 +00:00
2020-02-04 21:38:37 +00:00
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] == '.';
2020-02-20 05:32:07 +00:00
2020-02-04 21:38:37 +00:00
// hidden files lower in list
if(a_hidden != b_hidden) {
return a_hidden - b_hidden;
}
2020-02-20 05:32:07 +00:00
2020-02-04 21:38:37 +00:00
// push_stringf seems to null terminate
return strcoll((char*)(*a)->file_name.str, (char*)(*b)->file_name.str);
}
2020-01-12 00:19:37 +00:00
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 u64
linux_us_from_timespec(const struct timespec timespec) {
return timespec.tv_nsec/Thousand(1) + Million(1) * timespec.tv_sec;
2020-01-12 00:19:37 +00:00
}
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
2020-02-04 21:38:37 +00:00
linux_file_attributes_from_struct_stat(struct stat* file_stat) {
2020-01-12 00:19:37 +00:00
File_Attributes result = {};
2020-02-04 21:38:37 +00:00
result.size = file_stat->st_size;
result.last_write_time = linux_us_from_timespec(file_stat->st_mtim);
2020-02-04 21:38:37 +00:00
result.flags = linux_convert_file_attribute_flags(file_stat->st_mode);
return(result);
2020-01-12 00:19:37 +00:00
}
2020-02-02 15:50:18 +00:00
internal void
linux_schedule_step(){
u64 now = system_now_time();
u64 diff = (now - linuxvars.last_step_time);
2020-02-20 05:32:07 +00:00
struct itimerspec its = {};
timerfd_gettime(linuxvars.step_timer_fd, &its);
2020-02-20 05:32:07 +00:00
if (diff > frame_useconds) {
its.it_value.tv_nsec = 1;
timerfd_settime(linuxvars.step_timer_fd, 0, &its, NULL);
} else {
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);
}
2020-01-12 05:27:40 +00:00
}
}
enum wm_state_mode {
WM_STATE_DEL = 0,
WM_STATE_ADD = 1,
WM_STATE_TOGGLE = 2,
};
internal void
linux_set_wm_state(Atom one, Atom two, enum wm_state_mode mode){
//NOTE(inso): this will only work after the window has been mapped
2020-02-20 05:32:07 +00:00
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;
2020-02-20 05:32:07 +00:00
XSendEvent(linuxvars.dpy,
RootWindow(linuxvars.dpy, 0),
0, SubstructureNotifyMask | SubstructureRedirectMask, &e);
}
internal void
linux_window_maximize(enum wm_state_mode mode){
linux_set_wm_state(linuxvars.atom__NET_WM_STATE_MAXIMIZED_HORZ, linuxvars.atom__NET_WM_STATE_MAXIMIZED_VERT, mode);
}
internal void
linux_window_fullscreen(enum wm_state_mode mode) {
linux_set_wm_state(linuxvars.atom__NET_WM_STATE_FULLSCREEN, 0, mode);
}
2020-01-12 05:27:40 +00:00
internal int
2020-02-02 15:50:18 +00:00
linux_get_xsettings_dpi(Display* dpy, int screen){
2020-01-12 05:27:40 +00:00
struct XSettingHeader {
u8 type;
u8 pad0;
u16 name_len;
char name[0];
};
2020-02-20 05:32:07 +00:00
2020-01-12 05:27:40 +00:00
struct XSettings {
u8 byte_order;
u8 pad[3];
u32 serial;
u32 num_settings;
};
2020-02-20 05:32:07 +00:00
2020-01-12 05:27:40 +00:00
enum { XSettingsTypeInt, XSettingsTypeString, XSettingsTypeColor };
2020-02-20 05:32:07 +00:00
2020-01-12 05:27:40 +00:00
int dpi = -1;
unsigned char* prop = NULL;
char sel_buffer[64];
struct XSettings* xs;
const char* p;
2020-02-20 05:32:07 +00:00
2020-01-12 05:27:40 +00:00
snprintf(sel_buffer, sizeof(sel_buffer), "_XSETTINGS_S%d", screen);
2020-02-20 05:32:07 +00:00
2020-01-12 05:27:40 +00:00
Atom XSET_SEL = XInternAtom(dpy, sel_buffer, True);
Atom XSET_SET = XInternAtom(dpy, "_XSETTINGS_SETTINGS", True);
2020-02-20 05:32:07 +00:00
2020-01-12 05:27:40 +00:00
if (XSET_SEL == None || XSET_SET == None){
//LOG("XSETTINGS unavailable.\n");
return(dpi);
}
2020-02-20 05:32:07 +00:00
2020-01-12 05:27:40 +00:00
Window xset_win = XGetSelectionOwner(dpy, XSET_SEL);
if (xset_win == None){
// TODO(inso): listen for the ClientMessage about it becoming available?
2020-02-20 05:32:07 +00:00
// there's not much point atm if DPI scaling is only done at startup
2020-01-12 05:27:40 +00:00
goto out;
}
2020-02-20 05:32:07 +00:00
2020-01-12 05:27:40 +00:00
{
Atom type;
int fmt;
unsigned long pad, num;
2020-02-20 05:32:07 +00:00
2020-01-12 05:27:40 +00:00
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;
}
2020-02-20 05:32:07 +00:00
2020-01-12 05:27:40 +00:00
if (fmt != 8){
//LOG("XSETTINGS: Wrong format.\n");
goto out;
}
}
2020-02-20 05:32:07 +00:00
2020-01-12 05:27:40 +00:00
xs = (struct XSettings*)prop;
p = (char*)(xs + 1);
2020-02-20 05:32:07 +00:00
2020-01-12 05:27:40 +00:00
if (xs->byte_order != 0){
//LOG("FIXME: XSETTINGS not host byte order?\n");
goto out;
}
2020-02-20 05:32:07 +00:00
2020-01-12 05:27:40 +00:00
for (int i = 0; i < xs->num_settings; ++i){
struct XSettingHeader* h = (struct XSettingHeader*)p;
2020-02-20 05:32:07 +00:00
2020-01-12 05:27:40 +00:00
p += sizeof(struct XSettingHeader);
p += h->name_len;
p += ((4 - (h->name_len & 3)) & 3);
p += 4; // serial
2020-02-20 05:32:07 +00:00
2020-01-12 05:27:40 +00:00
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;
2020-02-20 05:32:07 +00:00
2020-01-12 05:27:40 +00:00
case XSettingsTypeString: {
u32 len = *(u32*)p;
p += 4;
p += len;
p += ((4 - (len & 3)) & 3);
} break;
2020-02-20 05:32:07 +00:00
2020-01-12 05:27:40 +00:00
case XSettingsTypeColor: {
p += 8;
} break;
2020-02-20 05:32:07 +00:00
2020-01-12 05:27:40 +00:00
default: {
//LOG("XSETTINGS: Got invalid type...\n");
goto out;
} break;
}
}
2020-02-20 05:32:07 +00:00
2020-01-12 05:27:40 +00:00
out:
2020-02-20 05:32:07 +00:00
if (prop){
XFree(prop);
}
2020-01-12 05:27:40 +00:00
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;
}
2020-02-02 15:50:18 +00:00
#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));
2020-01-12 06:07:01 +00:00
}
2020-02-02 15:50:18 +00:00
#include "linux_error_box.cpp"
2020-01-12 05:27:40 +00:00
2020-03-01 21:29:32 +00:00
function void
os_popup_error(char *title, char *message){
system_error_box(message);
exit(1);
}
2020-02-02 15:50:18 +00:00
////////////////////////////
2020-01-12 06:07:01 +00:00
2020-02-02 15:50:18 +00:00
#include "linux_4ed_functions.cpp"
2020-11-24 20:11:47 +00:00
#include "linux_4ed_audio.cpp"
2020-01-12 06:07:01 +00:00
2020-02-02 15:50:18 +00:00
////////////////////////////
2020-01-12 06:07:01 +00:00
2020-02-02 15:50:18 +00:00
#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"
2020-01-12 06:07:01 +00:00
internal
2020-02-02 15:50:18 +00:00
graphics_get_texture_sig(){
return(gl__get_texture(dim, texture_kind));
2020-01-12 06:07:01 +00:00
}
internal
2020-02-02 15:50:18 +00:00
graphics_fill_texture_sig(){
return(gl__fill_texture(texture_kind, texture, p, dim, data));
2020-01-12 06:07:01 +00:00
}
A user reported on the handmade network discord an issue where when you change the font size (e.g. ctrl + scroll up/down) 4coder was crashing. They were using the D3D11 renderer. The crash is caused by dereferencing a invalid pointer to a texture. The D3D11 renderer as a arbitrary limit of 32 textures (there is normally no reason to have more than 2 or 3 textures in 4coder) because it needs to keep track of a few pointer in its state, while OpenGL doesn't (it just uses the id returned by `glGenTexture`). The code that recreate the texture atlas when changing the font size, never frees the previous texture, so both DirectX and OpenGL are leaking texture. So this fix just frees the previous texture after 4coder successfully creates a new one. If for some reason the new texture can't be created, we continue using the last one. This is better than crashing as we can still use 4coder, but since we can't change font size anymore the editor might be stuck with a uncomfortable font size. Unfortunately 4coder doesn't expose a way to delete texture outside the platform layer, and the font size code is not in the platform layer. So I had to expose a `free texture` function, which lead me to understand how 4coder generates API in the `generated` folder. I updated the README to keep that information somewhere since it's not clear from the get go. The basic idea is that there are a few files that you can compile as a small standalone program and run to generate the files. For the graphics api, you need to compile `4ed_graphics_api.cpp`. I took some time to also modify a little the generator so that the generated file contains the name of the file that generated them, and added a bit of indentation to make the file a bit more readable (even if we're not supposed to modify them).
2025-01-06 17:37:44 +00:00
internal
graphics_free_texture_sig(){
gl__free_texture(texid);
}
2020-02-02 15:50:18 +00:00
////////////////////////////
2020-01-12 06:07:01 +00:00
2020-05-16 12:19:25 +00:00
internal Face*
font_make_face(Arena* arena, Face_Description* description, f32 scale_factor) {
2020-05-16 12:19:25 +00:00
Face_Description local_description = *description;
String_Const_u8* name = &local_description.font.file_name;
2020-05-16 12:19:25 +00:00
// if description->font.file_name is a relative path, prepend the font directory.
if(string_get_character(*name, 0) != '/') {
String_Const_u8 binary = system_get_path(arena, SystemPath_Binary);
*name = push_u8_stringf(arena, "%.*sfonts/%.*s", string_expand(binary), string_expand(*name));
2020-02-16 13:58:58 +00:00
}
2020-05-16 12:19:25 +00:00
Face* result = ft__font_make_face(arena, &local_description, scale_factor);
2020-02-02 20:20:27 +00:00
if(!result) {
// is this fatal? 4ed.cpp:277 (caller) does not check for null.
char msg[4096];
2020-05-16 12:19:25 +00:00
snprintf(msg, sizeof(msg), "Unable to load font: %.*s", string_expand(*name));
2020-02-02 20:20:27 +00:00
system_error_box(msg);
}
2020-02-02 15:50:18 +00:00
return(result);
2020-01-12 06:07:01 +00:00
}
2020-02-02 15:50:18 +00:00
////////////////////////////
internal b32
glx_init(void) {
2020-02-02 20:20:27 +00:00
int glx_maj, glx_min;
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
if(!glXQueryVersion(linuxvars.dpy, &glx_maj, &glx_min)) {
return false;
}
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
return glx_maj > 1 || (glx_maj == 1 && glx_min >= 3);
2020-01-12 05:27:40 +00:00
}
2020-02-02 15:50:18 +00:00
internal b32
glx_get_config(GLXFBConfig* fb_config, XVisualInfo* vi) {
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
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
};
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
int conf_count = 0;
GLXFBConfig* conf_list = glXChooseFBConfig(linuxvars.dpy, DefaultScreen(linuxvars.dpy), attrs, &conf_count);
if(!conf_count || conf_count <= 0) {
return false;
}
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
*fb_config = *conf_list;
XFree(conf_list);
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
XVisualInfo* xvi = glXGetVisualFromFBConfig(linuxvars.dpy, *fb_config);
if(!xvi) {
return false;
}
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
*vi = *xvi;
XFree(xvi);
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
return true;
2020-01-12 06:07:01 +00:00
}
2020-02-02 20:20:27 +00:00
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);
2020-02-02 15:50:18 +00:00
internal b32
2020-02-02 20:20:27 +00:00
glx_create_context(GLXFBConfig fb_config){
const char *glx_exts = glXQueryExtensionsString(linuxvars.dpy, DefaultScreen(linuxvars.dpy));
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
glXCreateContextAttribsARB_Function *glXCreateContextAttribsARB = 0;
glXSwapIntervalEXT_Function *glXSwapIntervalEXT = 0;
glXSwapIntervalMESA_Function *glXSwapIntervalMESA = 0;
glXGetSwapIntervalMESA_Function *glXGetSwapIntervalMESA = 0;
glXSwapIntervalSGI_Function *glXSwapIntervalSGI = 0;
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
#define GLXLOAD(f) f = (f##_Function*) glXGetProcAddressARB((const GLubyte*) #f);
GLXLOAD(glXCreateContextAttribsARB);
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
GLXContext ctx = NULL;
int (*old_handler)(Display*, XErrorEvent*) = XSetErrorHandler(&glx_error_handler);
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
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, 3,
GLX_CONTEXT_MINOR_VERSION_ARB, 2,
2020-02-02 20:20:27 +00:00
GLX_CONTEXT_PROFILE_MASK_ARB , GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB,
2020-02-20 06:38:18 +00:00
#if GL_DEBUG_MODE
2020-02-02 20:20:27 +00:00
GLX_CONTEXT_FLAGS_ARB , GLX_CONTEXT_DEBUG_BIT_ARB,
#endif
None
};
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
ctx = glXCreateContextAttribsARB(linuxvars.dpy, fb_config, 0, True, context_attribs);
}
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
XSync(linuxvars.dpy, False);
if(glx_ctx_error || !ctx) {
return false;
}
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
XSync(linuxvars.dpy, False);
XSetErrorHandler(old_handler);
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
//b32 direct = glXIsDirect(linuxvars.dpy, ctx);
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
//LOG("Making context current\n");
glXMakeCurrent(linuxvars.dpy, linuxvars.win, ctx);
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
//glx_enable_vsync();
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
// NOTE(allen): Load gl functions
#define GL_FUNC(f,R,P) GLXLOAD(f)
#include "opengl/4ed_opengl_funcs.h"
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
#undef GLXLOAD
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
return true;
2020-01-12 00:19:37 +00:00
}
2020-02-02 15:50:18 +00:00
////////////////////////////
2020-02-02 15:50:18 +00:00
internal void
linux_x11_init(int argc, char** argv, Plat_Settings* settings) {
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
Display* dpy = XOpenDisplay(0);
if (!dpy){
fprintf(stderr, "FATAL: Cannot open X11 Display!\n");
exit(1);
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
linuxvars.dpy = dpy;
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
#define LOAD_ATOM(x) linuxvars.atom_##x = XInternAtom(linuxvars.dpy, #x, False);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
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);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
#undef LOAD_ATOM
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
if (!glx_init()){
2020-02-02 20:20:27 +00:00
system_error_box("Your XServer's GLX version is too old. GLX 1.3+ is required.");
2020-02-02 15:50:18 +00:00
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
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.");
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
// TODO: window size
#define WINDOW_W_DEFAULT 800
#define WINDOW_H_DEFAULT 600
int w = WINDOW_W_DEFAULT;
int h = WINDOW_H_DEFAULT;
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
// TEMP
render_target.width = w;
render_target.height = h;
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
XSetWindowAttributes swa = {};
swa.backing_store = WhenMapped;
2020-02-02 15:50:18 +00:00
swa.event_mask = StructureNotifyMask;
swa.bit_gravity = NorthWestGravity;
swa.colormap = XCreateColormap(dpy, RootWindow(dpy, vi.screen), vi.visual, AllocNone);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
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);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
if (!linuxvars.win){
system_error_box("XCreateWindow failed. Make sure your display is set up correctly.");
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
//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);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
//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);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
//NOTE(inso): set wm properties
XStoreName(linuxvars.dpy, linuxvars.win, WINDOW_NAME);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
XSizeHints *sz_hints = XAllocSizeHints();
XWMHints *wm_hints = XAllocWMHints();
XClassHint *cl_hints = XAllocClassHint();
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
sz_hints->flags = PMinSize | PMaxSize | PWinGravity;
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
sz_hints->min_width = 50;
sz_hints->min_height = 50;
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
sz_hints->max_width = sz_hints->max_height = (1UL << 16UL);
sz_hints->win_gravity = NorthWestGravity;
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
if (settings->set_window_pos){
sz_hints->flags |= USPosition;
sz_hints->x = settings->window_x;
sz_hints->y = settings->window_y;
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
wm_hints->flags |= InputHint | StateHint;
wm_hints->input = True;
wm_hints->initial_state = NormalState;
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
cl_hints->res_name = "4coder";
cl_hints->res_class = "4coder";
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
char* win_name_list[] = { WINDOW_NAME };
XTextProperty win_name;
XStringListToTextProperty(win_name_list, 1, &win_name);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
XSetWMProperties(linuxvars.dpy, linuxvars.win, &win_name, NULL, argv, argc, sz_hints, wm_hints, cl_hints);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
XFree(win_name.value);
XFree(sz_hints);
XFree(wm_hints);
XFree(cl_hints);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
linux_set_icon(linuxvars.dpy, linuxvars.win);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
// NOTE(inso): make the window visible
XMapWindow(linuxvars.dpy, linuxvars.win);
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
if(!glx_create_context(fb_config)) {
2020-02-02 15:50:18 +00:00
system_error_box("Unable to create GLX context.");
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
XRaiseWindow(linuxvars.dpy, linuxvars.win);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
if (settings->set_window_pos){
XMoveWindow(linuxvars.dpy, linuxvars.win, settings->window_x, settings->window_y);
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
if (settings->maximize_window){
linux_set_wm_state(linuxvars.atom__NET_WM_STATE_MAXIMIZED_HORZ, linuxvars.atom__NET_WM_STATE_MAXIMIZED_VERT, WM_STATE_ADD);
2020-02-02 15:50:18 +00:00
} else if (settings->fullscreen_window){
linux_set_wm_state(linuxvars.atom__NET_WM_STATE_FULLSCREEN, 0, WM_STATE_ADD);
2020-02-02 15:50:18 +00:00
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
XSync(linuxvars.dpy, False);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
Atom wm_protos[] = {
linuxvars.atom_WM_DELETE_WINDOW,
linuxvars.atom__NET_WM_PING
};
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
XSetWMProtocols(linuxvars.dpy, linuxvars.win, wm_protos, 2);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
// 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);
2020-02-20 05:32:07 +00:00
2020-02-08 17:46:21 +00:00
// request notifications for CLIPBOARD updates.
if(has_xfixes) {
XFixesSelectSelectionInput(linuxvars.dpy, linuxvars.win, linuxvars.atom_CLIPBOARD, XFixesSetSelectionOwnerNotifyMask);
}
2020-02-02 15:50:18 +00:00
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
// Input handling init
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
setlocale(LC_ALL, "");
XSetLocaleModifiers("");
b32 locale_supported = XSupportsLocale();
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
if (!locale_supported){
setlocale(LC_ALL, "C");
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
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);
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
// If it still isn't there we're screwed.
if (!linuxvars.xim){
system_error_box("Could not initialize X Input.");
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
XIMStyles *styles = NULL;
2020-02-02 20:20:27 +00:00
const XIMStyle style_want = (XIMPreeditNothing | XIMStatusNothing);
b32 found_style = false;
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
if (!XGetIMValues(linuxvars.xim, XNQueryInputStyle, &styles, NULL) && styles){
for (i32 i = 0; i < styles->count_styles; ++i){
XIMStyle style = styles->supported_styles[i];
2020-02-02 20:20:27 +00:00
if (style == style_want) {
found_style = true;
2020-02-02 15:50:18 +00:00
break;
}
}
}
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
if(!found_style) {
2020-02-02 15:50:18 +00:00
system_error_box("Could not find supported X Input style.");
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
XFree(styles);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
linuxvars.xic = XCreateIC(linuxvars.xim,
2020-02-02 20:20:27 +00:00
XNInputStyle, style_want,
2020-02-02 15:50:18 +00:00
XNClientWindow, linuxvars.win,
XNFocusWindow, linuxvars.win,
NULL);
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
if(!linuxvars.xic) {
system_error_box("Error creating X Input context.");
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
int xim_event_mask;
if (XGetICValues(linuxvars.xic, XNFilterEvents, &xim_event_mask, NULL)){
xim_event_mask = 0;
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
u32 event_mask = ExposureMask
| KeyPressMask | KeyReleaseMask
| ButtonPressMask | ButtonReleaseMask
| EnterWindowMask | LeaveWindowMask
| PointerMotionMask
| FocusChangeMask
2020-02-16 13:58:58 +00:00
| StructureNotifyMask
2020-02-02 15:50:18 +00:00
| ExposureMask | VisibilityChangeMask
| xim_event_mask;
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
XSelectInput(linuxvars.dpy, linuxvars.win, event_mask);
2020-02-20 05:32:07 +00:00
2020-02-16 13:58:58 +00:00
// init XKB keyboard extension
2020-02-20 05:32:07 +00:00
2020-02-16 13:58:58 +00:00
if(!XkbQueryExtension(linuxvars.dpy, 0, &linuxvars.xkb_event, 0, 0, 0)) {
system_error_box("XKB Extension not available.");
}
2020-02-20 05:32:07 +00:00
2020-02-16 13:58:58 +00:00
XkbSelectEvents(linuxvars.dpy, XkbUseCoreKbd, XkbAllEventsMask, XkbAllEventsMask);
linuxvars.xkb = XkbGetMap(linuxvars.dpy, XkbKeyTypesMask | XkbKeySymsMask, XkbUseCoreKbd);
2020-02-16 13:58:58 +00:00
if(!linuxvars.xkb) {
system_error_box("Error getting XKB keyboard map.");
}
if(XkbGetNames(linuxvars.dpy, XkbKeyNamesMask, linuxvars.xkb) != Success) {
system_error_box("Error getting XKB key names.");
2020-02-16 13:58:58 +00:00
}
2020-02-20 05:32:07 +00:00
2020-02-16 13:58:58 +00:00
// closer to windows behaviour (holding key doesn't generate release events)
XkbSetDetectableAutoRepeat(linuxvars.dpy, True, NULL);
2020-02-20 05:32:07 +00:00
2020-02-08 17:46:21 +00:00
XCursor cursors[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)
};
block_copy(linuxvars.xcursors, cursors, sizeof(cursors));
2020-02-20 05:32:07 +00:00
2020-02-08 17:46:21 +00:00
// sneaky invisible cursor
{
char data = 0;
XColor c = {};
Pixmap p = XCreateBitmapFromData(linuxvars.dpy, linuxvars.win, &data, 1, 1);
2020-02-20 05:32:07 +00:00
2020-02-08 17:46:21 +00:00
linuxvars.hidden_cursor = XCreatePixmapCursor(linuxvars.dpy, p, p, &c, &c, 0, 0);
2020-02-20 05:32:07 +00:00
2020-02-08 17:46:21 +00:00
XFreePixmap(linuxvars.dpy, p);
}
2020-02-02 15:50:18 +00:00
}
global Key_Code keycode_lookup_table_physical[255];
global Key_Code keycode_lookup_table_language[255];
struct SymCode {
KeySym sym;
Key_Code code;
};
2020-02-03 21:18:46 +00:00
internal void
linux_keycode_init_common(Display* dpy, Key_Code* keycode_lookup_table, SymCode* sym_table, SymCode* p, size_t sym_table_size){
2020-05-12 12:18:05 +00:00
2020-02-03 21:18:46 +00:00
*p++ = { XK_space, KeyCode_Space };
*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 };
2020-02-20 05:32:07 +00:00
for (Key_Code k = KeyCode_F1; k <= KeyCode_F24; ++k){
2020-02-03 21:18:46 +00:00
*p++ = { XK_F1 + (k - KeyCode_F1), k };
}
2020-02-20 05:32:07 +00:00
2020-03-07 15:09:36 +00:00
for (Key_Code k = KeyCode_NumPad0; k <= KeyCode_NumPad9; ++k){
*p++ = { XK_KP_0 + (k - KeyCode_NumPad0), k };
}
*p++ = { XK_KP_Multiply, KeyCode_NumPadStar };
*p++ = { XK_KP_Add, KeyCode_NumPadPlus };
*p++ = { XK_KP_Subtract, KeyCode_NumPadMinus };
*p++ = { XK_KP_Decimal, KeyCode_NumPadDot };
*p++ = { XK_KP_Delete, KeyCode_NumPadDot }; // seems to take precedence over Decimal...
*p++ = { XK_KP_Divide, KeyCode_NumPadSlash };
*p++ = { XK_KP_Enter, KeyCode_Return }; // NumPadEnter?
2020-02-03 21:18:46 +00:00
const int table_size = p - sym_table;
Assert(table_size < sym_table_size);
2020-03-07 15:09:36 +00:00
Key_Code next_extra = KeyCode_Ex1;
const Key_Code max_extra = KeyCode_Ex29;
2020-02-16 13:58:58 +00:00
for(int i = XkbMinLegalKeyCode; i <= XkbMaxLegalKeyCode; ++i) {
KeySym sym = NoSymbol;
2020-02-20 05:32:07 +00:00
2020-02-16 13:58:58 +00:00
// lookup key in current layout with no modifiers held (0)
if(!XkbTranslateKeyCode(linuxvars.xkb, i, XkbBuildCoreState(0, linuxvars.xkb_group), NULL, &sym)) {
continue;
}
2020-03-07 15:09:36 +00:00
int j;
for(j = 0; j < table_size; ++j) {
2020-02-16 13:58:58 +00:00
if(sym_table[j].sym == sym) {
keycode_lookup_table[i] = sym_table[j].code;
//printf("lookup %s = %d\n", key_code_name[sym_table[j].code], i);
2020-02-03 21:18:46 +00:00
break;
}
}
2020-05-12 12:18:05 +00:00
if(j != table_size){
continue;
}
2020-05-12 12:18:05 +00:00
// nothing found - try with shift held (needed for e.g. belgian numbers to bind).
KeySym shift_sym = NoSymbol;
2020-05-12 12:18:05 +00:00
if(!XkbTranslateKeyCode(linuxvars.xkb, i, XkbBuildCoreState(ShiftMask, linuxvars.xkb_group), NULL, &shift_sym)) {
continue;
}
2020-05-12 12:18:05 +00:00
for(j = 0; j < table_size; ++j) {
if(sym_table[j].sym == shift_sym) {
keycode_lookup_table[i] = sym_table[j].code;
//printf("lookup %s = %d\n", key_code_name[sym_table[j].code], i);
break;
}
}
2020-05-12 12:18:05 +00:00
2020-03-07 15:09:36 +00:00
// something unknown bound, put it in extra
if(j == table_size && sym != NoSymbol && next_extra <= max_extra && keycode_lookup_table[i] == 0) {
keycode_lookup_table[i] = next_extra++;
}
2020-02-03 21:18:46 +00:00
}
2020-05-12 12:18:05 +00:00
}
internal void
linux_keycode_init_language(Display* dpy, Key_Code* keycode_lookup_table){
SymCode sym_table[300];
SymCode* p = sym_table;
2020-05-12 12:18:05 +00:00
for(unsigned int i = 0; i < 26; ++i) {
*p++ = { XK_a + i, KeyCode_A + i};
}
2020-05-12 12:18:05 +00:00
for(unsigned int i = 0; i < 26; ++i) {
*p++ = { XK_A + i, KeyCode_A + i};
}
2020-05-12 12:18:05 +00:00
for(unsigned int i = 0; i <= 9; ++i) {
*p++ = { XK_0 + i, KeyCode_0 + i};
}
2020-05-12 12:18:05 +00:00
*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 };
2020-05-12 12:18:05 +00:00
linux_keycode_init_common(dpy, keycode_lookup_table, sym_table, p, ArrayCount(sym_table));
}
internal void
linux_keycode_init_physical(Display* dpy, Key_Code* keycode_lookup_table){
// Find common keys by their key label
SymCode sym_table[100];
linux_keycode_init_common(dpy, keycode_lookup_table, sym_table, sym_table, ArrayCount(sym_table));
// Find these keys by physical position, and map to QWERTY KeyCodes
#define K(k) glue(KeyCode_, k)
static const u8 positional_keys[] = {
K(1), K(2), K(3), K(4), K(5), K(6), K(7), K(8), K(9), K(0), K(Minus), K(Equal),
K(Q), K(W), K(E), K(R), K(T), K(Y), K(U), K(I), K(O), K(P), K(LeftBracket), K(RightBracket),
K(A), K(S), K(D), K(F), K(G), K(H), K(J), K(K), K(L), K(Semicolon), K(Quote), /*uk hash*/0,
K(Z), K(X), K(C), K(V), K(B), K(N), K(M), K(Comma), K(Period), K(ForwardSlash), 0, 0
};
#undef K
// XKB gives the alphanumeric keys names like AE01 -> E is the row (from B-E), 01 is the column (01-12).
// to get key names in .ps file: setxkbmap -print | xkbcomp - - | xkbprint -label name - out.ps
static const int ncols = 12;
static const int nrows = 4;
for(int i = XkbMinLegalKeyCode; i <= XkbMaxLegalKeyCode; ++i) {
const char* name = linuxvars.xkb->names->keys[i].name;
// alphanumeric keys
if(name[0] == 'A' && name[1] >= 'B' && name[1] <= 'E') {
int row = (nrows - 1) - (name[1] - 'B');
int col = (name[2] - '0') * 10 + (name[3] - '0') - 1;
if(row >= 0 && row < nrows && col >= 0 && col < ncols) {
keycode_lookup_table[i] = positional_keys[row * ncols + col];
}
}
// numpad
else if(name[0] == 'K' && name[1] == 'P' && name[2] >= '0' && name[2] <= '9' && !name[3]) {
// don't overwrite - for e.g. laptops with numpad keys embedded in the normal ones, toggling with numlock
if(keycode_lookup_table[i] == 0) {
keycode_lookup_table[i] = KeyCode_NumPad0 + name[2] - '0';
}
}
// a few special cases:
else if(memcmp(name, "TLDE", XkbKeyNameLength) == 0) {
keycode_lookup_table[i] = KeyCode_Tick;
} else if(memcmp(name, "BKSL", XkbKeyNameLength) == 0) {
keycode_lookup_table[i] = KeyCode_BackwardSlash;
} else if(memcmp(name, "LSGT", XkbKeyNameLength) == 0) {
// UK extra key between left shift and Z
// it prints \ and | with shift. KeyCode_Backslash will be where UK # is.
keycode_lookup_table[i] = KeyCode_Ex0;
}
}
}
internal void
linux_keycode_init(Display* dpy){
block_zero_array(keycode_lookup_table_physical);
block_zero_array(keycode_lookup_table_language);
2020-05-12 12:18:05 +00:00
linux_keycode_init_physical(dpy, keycode_lookup_table_physical);
linux_keycode_init_language(dpy, keycode_lookup_table_language);
2020-02-03 21:18:46 +00:00
}
2020-02-08 17:46:21 +00:00
internal void
linux_epoll_init(void) {
struct epoll_event e = {};
e.events = EPOLLIN | EPOLLET;
2020-02-20 05:32:07 +00:00
2020-02-08 17:46:21 +00:00
linuxvars.step_timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
2020-02-16 13:58:58 +00:00
linuxvars.epoll = epoll_create(16);
2020-02-20 05:32:07 +00:00
2020-02-08 17:46:21 +00:00
e.data.ptr = &epoll_tag_x11;
epoll_ctl(linuxvars.epoll, EPOLL_CTL_ADD, ConnectionNumber(linuxvars.dpy), &e);
2020-02-20 05:32:07 +00:00
2020-02-08 17:46:21 +00:00
e.data.ptr = &epoll_tag_step_timer;
epoll_ctl(linuxvars.epoll, EPOLL_CTL_ADD, linuxvars.step_timer_fd, &e);
}
internal void
linux_clipboard_send(XSelectionRequestEvent* req) {
2020-02-20 05:32:07 +00:00
2020-02-08 17:46:21 +00:00
XSelectionEvent rsp = {};
rsp.type = SelectionNotify;
rsp.requestor = req->requestor;
rsp.selection = req->selection;
rsp.target = req->target;
rsp.time = req->time;
rsp.property = None;
2020-02-20 05:32:07 +00:00
2020-02-08 17:46:21 +00:00
Atom formats[] = {
linuxvars.atom_UTF8_STRING,
XA_STRING,
};
2020-02-20 05:32:07 +00:00
if(linuxvars.clipboard_contents.size == 0) {
2020-02-08 17:46:21 +00:00
goto done;
}
2020-02-20 05:32:07 +00:00
2020-02-08 17:46:21 +00:00
if(req->selection != linuxvars.atom_CLIPBOARD || req->property == None) {
goto done;
}
2020-02-20 05:32:07 +00:00
2020-02-08 17:46:21 +00:00
if (req->target == linuxvars.atom_TARGETS){
2020-02-20 05:32:07 +00:00
2020-02-08 17:46:21 +00:00
XChangeProperty(
2020-02-20 05:32:07 +00:00
req->display,
req->requestor,
req->property,
XA_ATOM,
32,
PropModeReplace,
(u8*)formats,
ArrayCount(formats));
2020-02-08 17:46:21 +00:00
rsp.property = req->property;
2020-02-20 05:32:07 +00:00
2020-02-08 17:46:21 +00:00
} else {
2020-02-20 05:32:07 +00:00
2020-02-08 17:46:21 +00:00
int i;
for(i = 0; i < ArrayCount(formats); ++i){
if (req->target == formats[i]){
break;
}
}
2020-02-20 05:32:07 +00:00
2020-02-08 17:46:21 +00:00
if (i != ArrayCount(formats)){
XChangeProperty(
2020-02-20 05:32:07 +00:00
req->display,
req->requestor,
req->property,
req->target,
8,
PropModeReplace,
linuxvars.clipboard_contents.str,
linuxvars.clipboard_contents.size
);
2020-02-08 17:46:21 +00:00
rsp.property = req->property;
}
}
2020-02-20 05:32:07 +00:00
done:
2020-02-08 17:46:21 +00:00
XSendEvent(req->display, req->requestor, True, 0, (XEvent*)&rsp);
}
internal String_Const_u8
linux_clipboard_recv(Arena *arena){
2020-02-08 17:46:21 +00:00
Atom type;
int fmt;
unsigned long nitems;
unsigned long bytes_left;
u8 *data;
2020-02-20 05:32:07 +00:00
int result = XGetWindowProperty(linuxvars.dpy,
2020-02-20 05:32:07 +00:00
linuxvars.win,
linuxvars.atom_CLIPBOARD,
0L, 0x20000000L, False,
linuxvars.atom_UTF8_STRING,
&type, &fmt, &nitems,
&bytes_left, &data);
String_Const_u8 clip = {};
2020-02-08 17:46:21 +00:00
if(result == Success && fmt == 8){
clip= push_string_copy(arena, SCu8(data, nitems));
2020-02-08 17:46:21 +00:00
XFree(data);
XDeleteProperty(linuxvars.dpy, linuxvars.win, linuxvars.atom_CLIPBOARD);
}
return(clip);
}
internal void
linux_clipboard_recv(XSelectionEvent* ev) {
if(ev->selection != linuxvars.atom_CLIPBOARD ||
ev->target != linuxvars.atom_UTF8_STRING ||
ev->property == None) {
return;
}
Scratch_Block scratch(&linuxvars.tctx);
String_Const_u8 clip = linux_clipboard_recv(scratch);
if (clip.size > 0){
linalloc_clear(&linuxvars.clipboard_arena);
linuxvars.clipboard_contents = push_string_copy(&linuxvars.clipboard_arena, clip);
linuxvars.received_new_clipboard = true;
2020-02-08 17:46:21 +00:00
linux_schedule_step();
}
}
internal
system_get_clipboard_sig(){
// TODO(inso): index?
return(push_string_copy(arena, linuxvars.clipboard_contents));
}
internal void
system_post_clipboard(String_Const_u8 str, i32 index){
// TODO(inso): index?
//LINUX_FN_DEBUG("%.*s", string_expand(str));
linalloc_clear(&linuxvars.clipboard_arena);
linuxvars.clipboard_contents = push_u8_stringf(&linuxvars.clipboard_arena, "%.*s", string_expand(str));
XSetSelectionOwner(linuxvars.dpy, linuxvars.atom_CLIPBOARD, linuxvars.win, CurrentTime);
}
internal void
system_set_clipboard_catch_all(b32 enabled){
LINUX_FN_DEBUG("%d", enabled);
linuxvars.clipboard_catch_all = !!enabled;
}
internal b32
system_get_clipboard_catch_all(void){
return linuxvars.clipboard_catch_all;
}
internal String_Const_u8
linux_filter_text(Arena* arena, u8* buf, int len) {
u8* const result = push_array(arena, u8, len);
u8* outp = result;
2020-02-20 05:32:07 +00:00
for(int i = 0; i < len; ++i) {
u8 c = buf[i];
2020-02-20 05:32:07 +00:00
if(c == '\r') {
*outp++ = '\n';
} else if(c > 127 || (' ' <= c && c <= '~') || c == '\t') {
*outp++ = c;
}
}
2020-02-20 05:32:07 +00:00
return SCu8(result, outp - result);
}
internal KeyCode
linux_numlock_convert(KeyCode in){
static const KeyCode lookup[] = {
KeyCode_Insert,
KeyCode_End,
KeyCode_Down,
KeyCode_PageDown,
KeyCode_Left,
0,
KeyCode_Right,
KeyCode_Home,
KeyCode_Up,
KeyCode_PageUp,
0, 0, 0,
KeyCode_Delete,
};
2020-05-12 12:18:05 +00:00
if(in >= KeyCode_NumPad0 && in <= KeyCode_NumPadDot) {
KeyCode ret = lookup[in - KeyCode_NumPad0];
if(ret != 0) {
return ret;
}
}
2020-05-12 12:18:05 +00:00
return in;
}
2020-02-04 21:38:37 +00:00
internal void
linux_handle_x11_events() {
static XEvent prev_event = {};
b32 should_step = false;
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
while (XPending(linuxvars.dpy)) {
XEvent event;
2020-02-02 15:50:18 +00:00
XNextEvent(linuxvars.dpy, &event);
2020-05-12 12:18:05 +00:00
b32 filtered = false;
if (XFilterEvent(&event, None) == True){
filtered = true;
if(event.type != KeyPress && event.type != KeyRelease) {
continue;
}
}
2020-02-20 05:32:07 +00:00
u64 event_id = (u64)event.xkey.serial << 32 | event.xkey.time;
2020-05-12 12:18:05 +00:00
2020-02-03 21:18:46 +00:00
switch(event.type) {
case KeyPress: {
should_step = true;
2020-02-20 05:32:07 +00:00
2020-02-03 21:18:46 +00:00
Input_Modifier_Set_Fixed* mods = &linuxvars.input.pers.modifiers;
2020-02-20 05:32:07 +00:00
2020-02-03 21:18:46 +00:00
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);
2020-02-20 05:32:07 +00:00
2020-02-03 21:18:46 +00:00
event.xkey.state &= ~(ControlMask);
2020-02-20 05:32:07 +00:00
2020-02-03 21:18:46 +00:00
Status status;
KeySym keysym = NoSymbol;
u8 buf[256] = {};
2020-02-20 05:32:07 +00:00
int len = Xutf8LookupString(linuxvars.xic, &event.xkey, (char*)buf, sizeof(buf) - 1, &keysym, &status);
2020-02-20 05:32:07 +00:00
2020-02-03 21:18:46 +00:00
if (status == XBufferOverflow){
Xutf8ResetIC(linuxvars.xic);
XSetICFocus(linuxvars.xic);
}
2020-02-20 05:32:07 +00:00
2020-02-03 21:18:46 +00:00
if (keysym == XK_ISO_Left_Tab){
add_modifier(mods, KeyCode_Shift);
}
2020-05-12 12:18:05 +00:00
Key_Code key;
if(linuxvars.key_mode == KeyMode_Physical) {
key = keycode_lookup_table_physical[(u8)event.xkey.keycode];
} else {
key = keycode_lookup_table_language[(u8)event.xkey.keycode];
}
2020-05-12 12:18:05 +00:00
if(!(state & Mod2Mask)) {
key = linux_numlock_convert(key);
}
2020-05-12 12:18:05 +00:00
//printf("key %d = %s (f:%d)\n", event.xkey.keycode, key_code_name[key], filtered);
2020-05-12 12:18:05 +00:00
b32 is_dead = false;
if (keysym >= XK_dead_grave && keysym <= XK_dead_greek && len == 0) {
is_dead = true;
}
2020-05-12 12:18:05 +00:00
if(!is_dead && filtered) {
linuxvars.prev_filtered_key = key;
break;
}
2020-05-12 12:18:05 +00:00
// send a keycode for the key after the dead key
if(!key && linuxvars.prev_filtered_key) {
key = linuxvars.prev_filtered_key;
linuxvars.prev_filtered_key = 0;
}
2020-05-12 12:18:05 +00:00
Input_Event* key_event = NULL;
2020-02-04 21:38:37 +00:00
if(key) {
add_modifier(mods, key);
// printf(" push key %d\n", key);
2020-05-12 12:18:05 +00:00
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);
2020-05-12 12:18:05 +00:00
key_event->key.flags = 0;
if (is_dead){
key_event->key.flags |= KeyFlag_IsDeadKey;
}
2020-02-04 21:38:37 +00:00
}
2020-02-20 05:32:07 +00:00
Input_Event* text_event = NULL;
2020-02-04 21:38:37 +00:00
if(status == XLookupChars || status == XLookupBoth) {
String_Const_u8 str = linux_filter_text(&linuxvars.frame_arena, buf, len);
if(str.size) {
// printf(" push txt %d\n", key);
text_event = push_input_event(&linuxvars.frame_arena, &linuxvars.input.trans.event_list);
text_event->kind = InputEventKind_TextInsert;
text_event->text.string = str;
}
}
2020-02-20 05:32:07 +00:00
if(key_event && text_event) {
key_event->key.first_dependent_text = text_event;
}
} break;
2020-02-20 05:32:07 +00:00
case KeyRelease: {
should_step = true;
2020-05-12 12:18:05 +00:00
Input_Modifier_Set_Fixed* mods = &linuxvars.input.pers.modifiers;
2020-02-20 05:32:07 +00:00
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);
2020-02-20 05:32:07 +00:00
Key_Code key;
if(linuxvars.key_mode == KeyMode_Physical) {
key = keycode_lookup_table_physical[(u8)event.xkey.keycode];
} else {
key = keycode_lookup_table_language[(u8)event.xkey.keycode];
}
2020-05-12 12:18:05 +00:00
// num lock off -> convert KP keys to Insert, Home, End etc.
if(!(state & Mod2Mask)) {
key = linux_numlock_convert(key);
}
2020-02-20 05:32:07 +00:00
Input_Event* key_event = NULL;
if(key) {
remove_modifier(mods, key);
key_event = push_input_event(&linuxvars.frame_arena, &linuxvars.input.trans.event_list);
key_event->kind = InputEventKind_KeyRelease;
key_event->key.code = key;
key_event->key.modifiers = copy_modifier_set(&linuxvars.frame_arena, mods);
}
} break;
2020-02-20 05:32:07 +00:00
case MotionNotify: {
2020-02-16 13:58:58 +00:00
int x = clamp(0, event.xmotion.x, render_target.width - 1);
int y = clamp(0, event.xmotion.y, render_target.height - 1);
linuxvars.input.pers.mouse = { x, y };
should_step = true;
} break;
2020-02-20 05:32:07 +00:00
case ButtonPress: {
should_step = true;
switch(event.xbutton.button) {
case Button1: {
linuxvars.input.trans.mouse_l_press = true;
linuxvars.input.pers.mouse_l = true;
2020-02-20 05:32:07 +00:00
2020-02-16 13:58:58 +00:00
// NOTE(inso): improves selection dragging (especially in notepad-like mode).
// we will still get mouse events when the pointer leaves the window if it's dragging.
XGrabPointer(
2020-02-20 05:32:07 +00:00
linuxvars.dpy,
linuxvars.win,
True, PointerMotionMask | ButtonPressMask | ButtonReleaseMask,
GrabModeAsync, GrabModeAsync,
None, None, CurrentTime);
} break;
2020-02-20 05:32:07 +00:00
case Button3: {
linuxvars.input.trans.mouse_r_press = true;
linuxvars.input.pers.mouse_r = true;
} break;
2020-02-20 05:32:07 +00:00
case Button4: {
linuxvars.input.trans.mouse_wheel = -100;
} break;
2020-02-20 05:32:07 +00:00
case Button5: {
linuxvars.input.trans.mouse_wheel = +100;
} break;
2020-02-04 21:38:37 +00:00
}
} break;
2020-02-20 05:32:07 +00:00
case ButtonRelease: {
should_step = true;
switch(event.xbutton.button) {
case Button1: {
linuxvars.input.trans.mouse_l_release = true;
linuxvars.input.pers.mouse_l = false;
2020-02-20 05:32:07 +00:00
2020-02-16 13:58:58 +00:00
XUngrabPointer(linuxvars.dpy, CurrentTime);
} break;
2020-02-20 05:32:07 +00:00
case Button3: {
linuxvars.input.trans.mouse_r_release = true;
linuxvars.input.pers.mouse_r = false;
} break;
}
2020-02-03 21:18:46 +00:00
} break;
2020-02-20 05:32:07 +00:00
case FocusIn:
case FocusOut: {
linuxvars.input.pers.mouse_l = false;
linuxvars.input.pers.mouse_r = false;
block_zero_struct(&linuxvars.input.pers.modifiers);
2020-02-08 17:46:21 +00:00
} break;
2020-02-20 05:32:07 +00:00
case EnterNotify: {
linuxvars.input.pers.mouse_out_of_window = 0;
} break;
2020-02-20 05:32:07 +00:00
case LeaveNotify: {
linuxvars.input.pers.mouse_out_of_window = 1;
2020-02-08 17:46:21 +00:00
} break;
2020-02-20 05:32:07 +00:00
2020-02-04 21:38:37 +00:00
case ConfigureNotify: {
i32 w = event.xconfigure.width;
i32 h = event.xconfigure.height;
2020-02-20 05:32:07 +00:00
2020-02-04 21:38:37 +00:00
if (w != render_target.width || h != render_target.height){
should_step = true;
2020-02-04 21:38:37 +00:00
render_target.width = w;
render_target.height = h;
}
} break;
2020-02-20 05:32:07 +00:00
case ClientMessage: {
Atom atom = event.xclient.data.l[0];
2020-02-20 05:32:07 +00:00
// Window X button clicked
if(atom == linuxvars.atom_WM_DELETE_WINDOW) {
should_step = true;
linuxvars.input.trans.trying_to_kill = true;
}
2020-02-20 05:32:07 +00:00
// 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,
2020-02-20 05:32:07 +00:00
event.xclient.window,
False,
SubstructureRedirectMask | SubstructureNotifyMask,
&event);
}
} break;
2020-02-20 05:32:07 +00:00
2020-02-08 17:46:21 +00:00
case SelectionRequest: {
linux_clipboard_send((XSelectionRequestEvent*)&event);
} break;
2020-02-20 05:32:07 +00:00
2020-02-08 17:46:21 +00:00
case SelectionNotify: {
linux_clipboard_recv((XSelectionEvent*)&event);
} break;
2020-02-20 05:32:07 +00:00
2020-02-08 17:46:21 +00:00
case SelectionClear: {
if(event.xselectionclear.selection == linuxvars.atom_CLIPBOARD) {
linalloc_clear(&linuxvars.clipboard_arena);
block_zero_struct(&linuxvars.clipboard_contents);
2020-02-08 17:46:21 +00:00
}
} break;
2020-02-20 05:32:07 +00:00
2020-02-08 17:46:21 +00:00
case Expose:
case VisibilityNotify: {
should_step = true;
} break;
2020-02-20 05:32:07 +00:00
2020-02-08 17:46:21 +00:00
default: {
// clipboard update notification - ask for the new content
if (event.type == linuxvars.xfixes_selection_event) {
2020-02-08 17:46:21 +00:00
XFixesSelectionNotifyEvent* sne = (XFixesSelectionNotifyEvent*)&event;
if (sne->subtype == XFixesSelectionNotify && sne->owner != linuxvars.win){
XConvertSelection(linuxvars.dpy,
2020-02-20 05:32:07 +00:00
linuxvars.atom_CLIPBOARD,
linuxvars.atom_UTF8_STRING,
linuxvars.atom_CLIPBOARD,
linuxvars.win,
CurrentTime);
2020-02-08 17:46:21 +00:00
}
}
2020-02-20 05:32:07 +00:00
2020-02-16 13:58:58 +00:00
else if(event.type == linuxvars.xkb_event) {
XkbEvent* kb = (XkbEvent*)&event;
2020-02-20 05:32:07 +00:00
2020-02-16 13:58:58 +00:00
// Keyboard layout changed, refresh lookup table.
if(kb->any.xkb_type == XkbStateNotify && kb->state.group != linuxvars.xkb_group) {
linuxvars.xkb_group = kb->state.group;
XkbRefreshKeyboardMapping((XkbMapNotifyEvent*)kb);
linux_keycode_init(linuxvars.dpy);
}
}
2020-02-08 17:46:21 +00:00
} break;
}
}
2020-02-20 05:32:07 +00:00
2020-02-04 21:38:37 +00:00
if(should_step) {
linux_schedule_step();
}
}
2020-02-02 15:50:18 +00:00
internal b32
linux_epoll_process(struct epoll_event* events, int num_events) {
b32 do_step = false;
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
for (int i = 0; i < num_events; ++i){
struct epoll_event* ev = events + i;
Epoll_Kind* tag = (Epoll_Kind*)ev->data.ptr;
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
switch (*tag){
case EPOLL_X11: {
linux_handle_x11_events();
} break;
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
case EPOLL_X11_INTERNAL: {
//XProcessInternalConnection(linuxvars.dpy, fd);
} break;
2020-02-20 05:32:07 +00:00
case EPOLL_STEP_TIMER: {
2020-02-02 15:50:18 +00:00
u64 count;
int ret;
do {
ret = read(linuxvars.step_timer_fd, &count, 8);
} while (ret != -1 || errno != EAGAIN);
do_step = true;
} break;
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
case EPOLL_CLI_PIPE: {
linux_schedule_step();
} break;
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
case EPOLL_USER_TIMER: {
2020-02-04 21:38:37 +00:00
Linux_Object* obj = CastFromMember(Linux_Object, timer.epoll_tag, tag);
2020-02-02 15:50:18 +00:00
close(obj->timer.fd);
obj->timer.fd = -1;
linux_schedule_step();
2020-02-02 15:50:18 +00:00
} break;
}
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
return do_step;
}
int
main(int argc, char **argv){
2021-05-17 06:13:27 +00:00
// NOTE(allen): fucking bullshit. someone get my shit togeth :(er
for (i32 i = 0; i < argc; i += 1){
String_Const_u8 arg = SCu8(argv[i]);
if (string_match(arg, str8_lit("-L"))){
log_os_enabled = true;
}
}
2020-02-20 05:32:07 +00:00
2021-05-17 06:13:27 +00:00
// NOTE(allen): All of This thing
2020-02-16 22:47:42 +00:00
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&linuxvars.memory_tracker_mutex, &attr);
2020-11-24 20:11:47 +00:00
pthread_mutex_init(&linuxvars.audio_mutex, &attr);
pthread_cond_init(&linuxvars.audio_cond, NULL);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
// NOTE(allen): context setup
{
Base_Allocator* alloc = get_base_allocator_system();
thread_ctx_init(&linuxvars.tctx, ThreadKind_Main, alloc, alloc);
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
API_VTable_system system_vtable = {};
system_api_fill_vtable(&system_vtable);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
API_VTable_graphics graphics_vtable = {};
graphics_api_fill_vtable(&graphics_vtable);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
API_VTable_font font_vtable = {};
font_api_fill_vtable(&font_vtable);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
// NOTE(allen): memory
linuxvars.frame_arena = make_arena_system();
linuxvars.clipboard_arena = make_arena_system();
2020-02-02 20:20:27 +00:00
render_target.arena = make_arena_system(KB(256));
2020-02-20 05:32:07 +00:00
2020-05-16 12:19:25 +00:00
//linuxvars.fontconfig = FcInitLoadConfigAndFonts();
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
linuxvars.cursor_show = MouseCursorShow_Always;
linuxvars.prev_cursor_show = MouseCursorShow_Always;
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
dll_init_sentinel(&linuxvars.free_linux_objects);
dll_init_sentinel(&linuxvars.timer_objects);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
//InitializeCriticalSection(&win32vars.thread_launch_mutex);
//InitializeConditionVariable(&win32vars.thread_launch_cv);
2020-02-20 05:32:07 +00:00
linuxvars.clipboard_catch_all = false;
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
// NOTE(allen): load core
System_Library core_library = {};
App_Functions app = {};
{
App_Get_Functions *get_funcs = 0;
Scratch_Block scratch(&linuxvars.tctx);
2020-11-24 20:11:47 +00:00
List_String_Const_u8 search_list = {};
def_search_list_add_system_path(scratch, &search_list, SystemPath_Binary);
2020-02-20 05:32:07 +00:00
String_Const_u8 core_path = def_search_get_full_path(scratch, &search_list, SCu8("4ed_app.so"));
2020-02-02 15:50:18 +00:00
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);
}
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
// NOTE(allen): send system vtable to core
app.load_vtables(&system_vtable, &font_vtable, &graphics_vtable);
2020-02-02 20:20:27 +00:00
// get_logger calls log_init which is needed.
2021-05-17 06:13:27 +00:00
//app.get_logger();
linuxvars.log_string = app.get_logger();
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
// NOTE(allen): init & command line parameters
Plat_Settings plat_settings = {};
void *base_ptr = 0;
{
Scratch_Block scratch(&linuxvars.tctx);
2020-02-02 15:50:18 +00:00
String_Const_u8 curdir = system_get_path(scratch, SystemPath_CurrentDirectory);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
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;
}*/
}
2020-02-20 05:32:07 +00:00
// NOTE(allen): setup user directory override
if (plat_settings.user_directory != 0){
lnx_override_user_directory = plat_settings.user_directory;
}
2020-02-02 15:50:18 +00:00
// 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.";
2020-02-20 06:03:48 +00:00
char custom_fail_load_msg[] = "Failed to load custom code due to missing version information. Try rebuilding with buildsuper.";
char custom_fail_version_msg[] = "Failed to load custom code due to a version mismatch. Try rebuilding with buildsuper.";
2020-02-02 15:50:18 +00:00
char custom_fail_init_apis[] = "Failed to load custom code due to missing 'init_apis' symbol. Try rebuilding with buildsuper";
2020-02-20 05:32:07 +00:00
Scratch_Block scratch(&linuxvars.tctx);
2020-02-02 15:50:18 +00:00
String_Const_u8 default_file_name = string_u8_litexpr("custom_4coder.so");
2020-11-24 20:11:47 +00:00
List_String_Const_u8 search_list = {};
def_search_list_add_system_path(scratch, &search_list, SystemPath_UserDirectory);
2020-11-24 20:11:47 +00:00
def_search_list_add_system_path(scratch, &search_list, SystemPath_Binary);
2020-02-02 15:50:18 +00:00
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 = def_search_get_full_path(scratch, &search_list, custom_file_names[i]);
2020-02-02 15:50:18 +00:00
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;
}
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
if (!has_library){
system_error_box(custom_not_found_msg);
}
custom.get_version = (_Get_Version_Type*)system_get_proc(custom_library, "get_version");
2020-02-20 06:03:48 +00:00
if (custom.get_version == 0){
system_error_box(custom_fail_load_msg);
}
else if (custom.get_version(MAJOR, MINOR, PATCH) == 0){
2020-02-02 15:50:18 +00:00
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);
}
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
linux_x11_init(argc, argv, &plat_settings);
2020-02-03 21:18:46 +00:00
linux_keycode_init(linuxvars.dpy);
2020-02-08 17:46:21 +00:00
linux_epoll_init();
2020-11-24 20:11:47 +00:00
linuxvars.audio_thread = system_thread_launch(&linux_audio_main, NULL);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
// app init
{
Scratch_Block scratch(&linuxvars.tctx);
2020-02-02 15:50:18 +00:00
String_Const_u8 curdir = system_get_path(scratch, SystemPath_CurrentDirectory);
app.init(&linuxvars.tctx, &render_target, base_ptr, curdir, custom);
2020-02-02 15:50:18 +00:00
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
linuxvars.global_frame_mutex = system_mutex_make();
system_mutex_acquire(linuxvars.global_frame_mutex);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
linux_schedule_step();
2020-02-04 21:38:37 +00:00
b32 first_step = true;
u64 timer_start = system_now_time();
2020-02-20 05:32:07 +00:00
for (;;) {
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
if (XEventsQueued(linuxvars.dpy, QueuedAlready)){
linux_handle_x11_events();
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
system_mutex_release(linuxvars.global_frame_mutex);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
struct epoll_event events[16];
int num_events = epoll_wait(linuxvars.epoll, events, ArrayCount(events), -1);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
system_mutex_acquire(linuxvars.global_frame_mutex);
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
if (num_events == -1){
if (errno != EINTR){
perror("epoll_wait");
//LOG("epoll_wait\n");
}
continue;
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
if(!linux_epoll_process(events, num_events)) {
continue;
}
2020-02-20 05:32:07 +00:00
linuxvars.last_step_time = system_now_time();
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
// 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 && linuxvars.clipboard_catch_all)){
2020-02-02 15:50:18 +00:00
XConvertSelection(linuxvars.dpy, linuxvars.atom_CLIPBOARD, linuxvars.atom_UTF8_STRING, linuxvars.atom_CLIPBOARD, linuxvars.win, CurrentTime);
}
2020-02-20 05:32:07 +00:00
2020-02-04 21:38:37 +00:00
Application_Step_Input input = {};
2020-02-20 05:32:07 +00:00
if (linuxvars.received_new_clipboard && linuxvars.clipboard_catch_all){
2020-02-04 21:38:37 +00:00
input.clipboard = linuxvars.clipboard_contents;
2020-02-02 15:50:18 +00:00
}
linuxvars.received_new_clipboard = false;
2020-02-20 05:32:07 +00:00
2020-02-04 21:38:37 +00:00
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;
2020-02-20 05:32:07 +00:00
input.mouse.out_of_window = linuxvars.input.pers.mouse_out_of_window;
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;
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
// NOTE(allen): Application Core Update
Application_Step_Result result = {};
if (app.step != 0){
2020-02-04 21:38:37 +00:00
result = app.step(&linuxvars.tctx, &render_target, base_ptr, &input);
2020-02-02 15:50:18 +00:00
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
// NOTE(allen): Finish the Loop
if (result.perform_kill){
break;
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
// NOTE(NAME): Switch to New Title
if (result.has_new_title){
XStoreName(linuxvars.dpy, linuxvars.win, result.title_string);
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
// NOTE(allen): Switch to New Cursor
2020-02-03 21:18:46 +00:00
if (result.mouse_cursor_type != linuxvars.cursor && !linuxvars.input.pers.mouse_l){
2020-02-08 17:46:21 +00:00
XCursor c = linuxvars.xcursors[result.mouse_cursor_type];
2020-02-02 15:50:18 +00:00
if (linuxvars.cursor_show){
XDefineCursor(linuxvars.dpy, linuxvars.win, c);
}
linuxvars.cursor = result.mouse_cursor_type;
}
2020-02-20 05:32:07 +00:00
2020-02-02 20:20:27 +00:00
gl_render(&render_target);
glXSwapBuffers(linuxvars.dpy, linuxvars.win);
2020-02-20 05:32:07 +00:00
// TODO(allen): don't let the screen size change until HERE after the render
// NOTE(allen): Schedule a step if necessary
if (result.animating){
linux_schedule_step();
}
2020-02-02 15:50:18 +00:00
first_step = false;
2020-02-20 05:32:07 +00:00
linalloc_clear(&linuxvars.frame_arena);
2020-02-04 21:38:37 +00:00
block_zero_struct(&linuxvars.input.trans);
2020-02-02 15:50:18 +00:00
}
2020-02-20 05:32:07 +00:00
2020-02-02 15:50:18 +00:00
return 0;
2016-09-04 20:41:48 +00:00
}
// NOTE(inso): to prevent me continuously messing up indentation
// vim: et:ts=4:sts=4:sw=4