dlib/platform.d
2025-09-15 04:41:23 +10:00

1294 lines
27 KiB
D

module dlib.platform;
import dlib.aliases;
import dlib.alloc : Reset;
import dlib.alloc;
import dlib.util;
import includes;
import std.typecons;
import std.stdio;
import core.memory;
import core.thread.osthread;
import core.time;
import core.volatile;
import core.atomic;
const WINDOW_EDGE_BUFFER = 50;
enum Input
{
None,
// Keyboard
A, B, C, D, E, F, G, H, I, J, K, L, M,
N, O, P, Q, R, S, T, U, V, W, X, Y, Z,
Zero, One, Two, Three, Four, Five, Six, Seven, Eight, Nine,
Num0, Num1, Num2, Num3, Num4, Num5, Num6, Num7, Num8, Num9,
NumLock, NumSlash, NumStar, NumMinus, NumPlus, NumEnter, NumPeriod,
Insert, Delete, Home, End, PageUp, PageDown,
PrintScreen, ScrollLock, Pause,
Comma, Period, BackSlash, Backspace, ForwardSlash, Minus, Plus,
F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12,
Up, Down, Left, Right,
LeftCtrl, LeftAlt, LeftShift, LeftSuper,
Tab, CapsLock,
RightCtrl, RightAlt, RightSuper, RightShift,
Enter, Space,
Tilde, Esc,
Semicolon, Quote, LeftBrace, RightBrace,
// Mouse
MouseMotion,
LeftClick, MiddleClick, RightClick,
};
enum Modifier : u32
{
None = 0x00,
LeftShift = 0x01,
RightShift = 0x02,
LeftCtrl = 0x04,
RightCtrl = 0x08,
LeftAlt = 0x10,
RightAlt = 0x20,
}
alias MD = Modifier;
struct InputEvent
{
Input key;
Modifier md;
bool pressed;
i32 x;
i32 y;
i32 rel_x;
i32 rel_y;
}
struct Inputs
{
DLList!(InputEvent) list;
Arena arena;
}
Inputs*
GetEvents(PlatformWindow* window)
{
Lock(&window.input_mutex);
Inputs* inputs = &window.inputs[window.input_idx];
window.input_idx = (window.input_idx + 1) % 2;
ResetInputs(&window.inputs[window.input_idx]);
Unlock(&window.input_mutex);
return inputs;
}
Inputs*
GetInputs(PlatformWindow* window)
{
Lock(&window.input_mutex);
Inputs* inputs = window.inputs.ptr + window.input_idx;
return inputs;
}
void
ReturnInputs(PlatformWindow* window)
{
Unlock(&window.input_mutex);
}
enum SysMessageType
{
None,
Quit,
LockCursor,
UnlockCursor,
HideCursor,
ShowCursor,
}
alias SMT = SysMessageType;
struct Mut
{
bool locked;
}
struct TicketMut
{
u64 ticket;
u64 next_ticket;
}
Mut
CreateMut()
{
return Mut(locked: false);
}
bool
TryLock(Mut* mut)
{
return cas!(MemoryOrder.acq, MemoryOrder.raw)(&mut.locked, false, true);
}
void
Unlock(Mut* mut)
{
atomicStore!(MemoryOrder.rel, bool)(mut.locked, false);
}
TicketMut
CreateTicketMut()
{
return TicketMut(ticket: 0, next_ticket: 0);
}
void
Lock(TicketMut* mut)
{
u64 ticket = atomicFetchAdd!(MemoryOrder.acq, u64)(mut.ticket, 1);
while (ticket != volatileLoad(&mut.next_ticket)) {}
}
void
Unlock(TicketMut* mut)
{
atomicFetchAdd!(MemoryOrder.rel, u64)(mut.next_ticket, 1);
}
const DNode!(SysMessage) g_sys_message;
DNode!(SysMessage)* g_NIL_MSG;
struct SysMessage
{
SysMessageType type;
}
struct MessageQueue
{
DLList!(SysMessage) list;
DLList!(SysMessage) free_list;
TicketMut mut;
Arena arena;
}
MessageQueue
CreateMessageQueue()
{
if (g_NIL_MSG == null)
{
g_NIL_MSG = cast(DNode!(SysMessage)*)&g_sys_message;
}
MessageQueue queue = {
list: {
first: g_NIL_MSG,
last: g_NIL_MSG,
},
free_list: {
first: g_NIL_MSG,
last: g_NIL_MSG,
},
mut: CreateTicketMut(),
arena: CreateArena(MB(1)),
};
return queue;
}
pragma(inline) bool
Nil(DNode!(SysMessage)* msg)
{
return msg == null || msg == g_NIL_MSG;
}
DNode!(SysMessage)*
Pop(MessageQueue* queue)
{
Lock(&queue.mut);
DNode!(SysMessage)* node = DLLPop(&queue.list, g_NIL_MSG);
Unlock(&queue.mut);
return node;
}
void
Push(PlatformWindow* w, SysMessageType msg)
{
Lock(&w.msg_queue.mut);
DNode!(SysMessage)* node = g_NIL_MSG;
if (Nil(w.msg_queue.free_list.first))
{
node = Alloc!(DNode!(SysMessage))(&w.msg_queue.arena);
}
else
{
node = DLLPop(&w.msg_queue.free_list, g_NIL_MSG);
}
node.value.type = msg;
DLLPush(&w.msg_queue.list, node, g_NIL_MSG);
Unlock(&w.msg_queue.mut);
}
void
PushFree(PlatformWindow* w, DNode!(SysMessage)* node)
{
DLLPush(&w.msg_queue.list, node, g_NIL_MSG);
}
version(linux)
{
import core.sys.posix.dlfcn;
import core.sys.posix.sys.mman;
import core.sys.linux.sys.inotify;
import core.sys.linux.fcntl;
import core.sys.posix.unistd;
import core.sys.posix.pthread : PThread = pthread_t,
PThreadCond = pthread_cond_t,
PThreadMutex = pthread_mutex_t,
PThreadMutexInit = pthread_mutex_init,
PThreadCondInit = pthread_cond_init,
PThreadCreate = pthread_create,
PThreadMutexLock = pthread_mutex_lock,
PThreadCondWait = pthread_cond_wait,
PThreadMutexUnlock = pthread_mutex_unlock,
PThreadCondSignal = pthread_cond_signal,
PThreadExit = pthread_exit;
import core.stdc.string : strlen;
struct SysThread
{
PThread handle;
PThreadCond cond;
PThreadMutex mut;
}
alias PThreadProc = extern (C) void* function(void*);
SysThread
CreateThread(void* proc, void* param)
{
SysThread thread;
assert(!PThreadMutexInit(&thread.mut, null));
assert(!PThreadCondInit(&thread.cond, null));
assert(!PThreadCreate(&thread.handle, null, cast(PThreadProc)proc, param));
return thread;
}
void
Suspend(SysThread* thread)
{
PThreadMutexLock(&thread.mut);
PThreadCondWait(&thread.cond, &thread.mut);
PThreadMutexUnlock(&thread.mut);
}
void
Wake(SysThread* thread)
{
PThreadCondSignal(&thread.cond);
}
void
Kill()
{
PThreadExit(null);
}
void
ResetInputs(Inputs* inputs)
{
inputs.list.first = inputs.list.last = null;
Reset(&inputs.arena);
}
void
Push(Inputs* inputs, Input input, i32 x, i32 y, bool pressed, Modifier md)
{
DNode!(InputEvent)* node = Alloc!(DNode!(InputEvent))(&inputs.arena);
node.value.key = input;
node.value.pressed = pressed;
node.value.x = x;
node.value.y = y;
node.value.md = md;
DLLPushFront(&inputs.list, node, null);
}
void
PushMotion(Inputs* inputs, i32 rel_x, i32 rel_y, i32 x, i32 y)
{
DNode!(InputEvent)* node = Alloc!(DNode!(InputEvent))(&inputs.arena);
node.value.key = Input.MouseMotion;
node.value.rel_x = rel_x;
node.value.rel_y = rel_y;
node.value.x = x;
node.value.y = y;
node.value.pressed = false;
DLLPushFront(&inputs.list, node, null);
}
struct PlatformWindow
{
Display *display;
xcb_connection_t *conn;
xcb_screen_t *screen;
xcb_window_t window;
xcb_atom_t close_event;
xcb_atom_t minimize_event;
u16 w;
u16 h;
i32 mouse_prev_x;
i32 mouse_prev_y;
bool locked_cursor;
bool close;
Modifier modifier;
SysThread thread;
MessageQueue msg_queue;
u32 input_idx;
Inputs[2] inputs;
TicketMut input_mutex;
};
struct Library
{
void* ptr;
};
struct Function
{
void* ptr;
};
pragma(inline) bool
NilErr(PlatformWindow* window, xcb_void_cookie_t* cookie, xcb_generic_error_t* err)
{
bool result = err == null;
pureFree(err);
return result;
}
pragma(inline) void
CheckErr(PlatformWindow *window, xcb_void_cookie_t *cookie, xcb_generic_error_t *err, string msg)
{
assert(err == null, msg);
pureFree(err);
};
PlatformWindow
CreateWindow(string name, u16 width, u16 height)
{
PlatformWindow window = {
w: width,
h: height,
input_mutex: CreateTicketMut(),
msg_queue: CreateMessageQueue(),
inputs: [
{ arena: CreateArena(MB(1)) },
{ arena: CreateArena(MB(1)) },
],
};
assert(width > 0 && height > 0, "CreateWindow error: width and height must be above 0");
window.display = XOpenDisplay(null);
assert(window.display != null, "XOpenDisplay failure");
window.conn = XGetXCBConnection(window.display);
assert(window.conn != null, "XGetXCBConnection failure");
xcb_void_cookie_t cookie;
xcb_generic_error_t *error;
xcb_setup_t *setup = xcb_get_setup(window.conn);
xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);
window.screen = iter.data;
i32 event_mask = XCB_EVENT_MASK_EXPOSURE |
XCB_EVENT_MASK_KEY_PRESS |
XCB_EVENT_MASK_KEY_RELEASE |
XCB_EVENT_MASK_BUTTON_PRESS |
XCB_EVENT_MASK_BUTTON_RELEASE |
XCB_EVENT_MASK_POINTER_MOTION |
XCB_EVENT_MASK_STRUCTURE_NOTIFY;
i32[2] val_win = [window.screen.black_pixel, event_mask];
i32 val_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
window.window = xcb_generate_id(window.conn);
cookie = xcb_create_window_checked(
window.conn,
XCB_COPY_FROM_PARENT,
window.window,
window.screen.root,
0, // x pos
0, // y pos
window.w, // width
window.h, // height
0,
XCB_WINDOW_CLASS_INPUT_OUTPUT,
window.screen.root_visual,
val_mask,
val_win.ptr
);
error = xcb_request_check(window.conn, cookie);
CheckErr(&window, &cookie, error, "xcb_create_window failure");
pureFree(error);
cookie = xcb_map_window_checked(window.conn, window.window);
error = xcb_request_check(window.conn, cookie);
CheckErr(&window, &cookie, error, "xcb_map_window_checked failure");
pureFree(error);
cookie = xcb_change_property_checked(
window.conn,
XCB_PROP_MODE_REPLACE,
window.window,
XCB_ATOM_WM_NAME,
XCB_ATOM_STRING,
8,
cast(u32)name.length,
name.ptr
);
error = xcb_request_check(window.conn, cookie);
CheckErr(&window, &cookie, error, "xcb_change_property_checked failure");
pureFree(error);
xcb_intern_atom_cookie_t c_proto = xcb_intern_atom(window.conn, 1, 12, "WM_PROTOCOLS");
xcb_intern_atom_reply_t *r_proto = xcb_intern_atom_reply(window.conn, c_proto, &error);
CheckErr(&window, &cookie, error, "xcb_intern_atom WM_PROTOCOLS failure");
pureFree(error);
xcb_intern_atom_cookie_t c_close = xcb_intern_atom(window.conn, 0, 16, "WM_DELETE_WINDOW");
xcb_intern_atom_reply_t *r_close = xcb_intern_atom_reply(window.conn, c_close, &error);
CheckErr(&window, &cookie, error, "xcb_intern_atom WM_DELETE_WINDOW failure");
pureFree(error);
xcb_intern_atom_cookie_t c_minimize = xcb_intern_atom(window.conn, 0, 20, "_NET_WM_STATE_HIDDEN");
xcb_intern_atom_reply_t *r_minimize = xcb_intern_atom_reply(window.conn, c_minimize, &error);
CheckErr(&window, &cookie, error, "xcb_intern_atom _NET_WM_STATE_HIDDEN failure");
pureFree(error);
cookie = xcb_change_property_checked(
window.conn,
XCB_PROP_MODE_REPLACE,
window.window,
r_proto.atom,
XCB_ATOM_ATOM,
32,
1,
&r_close.atom
);
error = xcb_request_check(window.conn, cookie);
CheckErr(&window, &cookie, error, "xcb_change_property_checked failure");
pureFree(error);
window.close_event = r_close.atom;
window.minimize_event = r_minimize.atom;
pureFree(r_proto);
pureFree(r_close);
pureFree(r_minimize);
xcb_map_window(window.conn, window.window);
i32 stream_result = xcb_flush(window.conn);
assert(stream_result > 0, "xcb_flush failure");
xcb_xfixes_query_version(window.conn, 4, 0);
return window;
};
void
StartPlatformThread(PlatformWindow* window)
{
window.thread = CreateThread(&HandleEvents, window);
}
bool
LockCursor(PlatformWindow* window)
{
bool result = window.locked_cursor;
if (!window.locked_cursor)
{
u32 counter = 0;
for(;;)
{
xcb_generic_error_t *error;
xcb_grab_pointer_cookie_t grab_cookie = xcb_grab_pointer(window.conn, true, window.window, 0, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, window.window, XCB_NONE, XCB_CURRENT_TIME);
xcb_grab_pointer_reply_t* grab_reply = xcb_grab_pointer_reply(window.conn, grab_cookie, &error);
scope(exit)
{
pureFree(error);
pureFree(grab_reply);
}
if (grab_reply.status == XCB_GRAB_STATUS_SUCCESS)
{
result = true;
break;
}
if (counter > 5)
{
break;
}
counter += 1;
Thread.sleep(dur!("msecs")(50));
}
HideCursor(window);
window.locked_cursor = result;
}
return result;
}
void
UnlockCursor(PlatformWindow* window)
{
if (window.locked_cursor)
{
xcb_ungrab_pointer(window.conn, XCB_CURRENT_TIME);
ShowCursor(window);
window.locked_cursor = false;
}
}
bool
HideCursor(PlatformWindow* window)
{
xcb_void_cookie_t hide_cursor_cookie = xcb_xfixes_hide_cursor_checked(window.conn, window.window);
xcb_generic_error_t *error = xcb_request_check(window.conn, hide_cursor_cookie);
return NilErr(window, &hide_cursor_cookie, error);
}
bool
ShowCursor(PlatformWindow* window)
{
xcb_void_cookie_t show_cursor_cookie = xcb_xfixes_show_cursor_checked(window.conn, window.window);
xcb_generic_error_t *error = xcb_request_check(window.conn, show_cursor_cookie);
return NilErr(window, &show_cursor_cookie, error);
}
void
FlushEvents(PlatformWindow* window)
{
xcb_generic_event_t* e;
do
{
e = xcb_poll_for_event(window.conn);
} while (e);
}
void
HandleEvents(void* window_ptr)
{
PlatformWindow* w = cast(PlatformWindow*)window_ptr;
DNode!(SysMessage)* sys_msg = g_NIL_MSG;
xcb_generic_event_t* e;
bool ignore_mouse_events = false;
for(;;)
{
if (w.close)
{
Kill();
}
sys_msg = Pop(&w.msg_queue);
if (!Nil(sys_msg))
{
SysMessage msg = sys_msg.value;
switch (msg.type)
{
case SMT.Quit:
{
w.close = true;
} break;
case SMT.LockCursor:
{
for(u64 i = 0; i < 5; i += 1)
{
if (LockCursor(w))
{
break;
}
}
} break;
case SMT.UnlockCursor:
{
UnlockCursor(w);
} break;
case SMT.HideCursor:
{
for (u64 i = 0; i < 5; i += 1)
{
if (HideCursor(w))
{
break;
}
}
} break;
case SMT.ShowCursor:
{
for(u64 i = 0; i < 5; i += 1)
{
if (ShowCursor(w))
{
break;
}
}
} break;
default: break;
}
}
e = xcb_poll_for_event(w.conn);
if (e)
{
Inputs* inputs = GetInputs(w);
switch (e.response_type & ~0x80)
{
case XCB_CLIENT_MESSAGE:
{
xcb_client_message_event_t* msg = cast(xcb_client_message_event_t*)e;
if (msg.window != w.window)
{
break;
}
if (msg.data.data32[0] == w.close_event)
{
w.close = true;
}
} break;
case XCB_KEY_RELEASE:
case XCB_KEY_PRESS:
{
xcb_key_press_event_t* keyboard_event = cast(xcb_key_press_event_t*)e;
bool pressed = e.response_type == XCB_KEY_PRESS;
xcb_keycode_t code = keyboard_event.detail;
KeySym key_sym = XkbKeycodeToKeysym(w.display, cast(KeyCode)code, 0, 0);
Input input = ConvertInput(key_sym);
enum modifiers = [Input.LeftShift, Input.RightShift, Input.LeftCtrl, Input.RightCtrl, Input.LeftAlt, Input.RightAlt];
static foreach(md; modifiers)
{
if (input == md)
{
w.modifier = cast(Modifier)(pressed ? (w.modifier | md) : (w.modifier & ~md));
}
}
if (input != Input.None)
{
Push(inputs, input, keyboard_event.event_x, keyboard_event.event_y, pressed, w.modifier);
}
} break;
case XCB_BUTTON_PRESS:
case XCB_BUTTON_RELEASE:
{
xcb_button_press_event_t* mouse_event = cast(xcb_button_press_event_t*)e;
bool pressed = e.response_type == XCB_BUTTON_PRESS;
Input input = Input.None;
switch (mouse_event.detail)
{
case XCB_BUTTON_INDEX_1: input = Input.LeftClick; break;
case XCB_BUTTON_INDEX_2: input = Input.MiddleClick; break;
case XCB_BUTTON_INDEX_3: input = Input.RightClick; break;
default: break;
}
if (input != Input.None)
{
Push(inputs, input, mouse_event.event_x, mouse_event.event_y, pressed, w.modifier);
}
} break;
case XCB_MOTION_NOTIFY:
{
if (ignore_mouse_events) continue;
xcb_motion_notify_event_t* move_event = cast(xcb_motion_notify_event_t*)e;
i16 x = move_event.event_x;
i16 y = move_event.event_y;
static bool first = true;
if (first)
{
w.mouse_prev_x = x;
w.mouse_prev_y = y;
first = false;
}
if (x > 0 || y > 0)
{
PushMotion(inputs, w.mouse_prev_x-x, w.mouse_prev_y-y, x, y);
}
w.mouse_prev_x = x;
w.mouse_prev_y = y;
if (w.locked_cursor &&
(x < WINDOW_EDGE_BUFFER || y < WINDOW_EDGE_BUFFER || x > w.w - WINDOW_EDGE_BUFFER || y > w.h - WINDOW_EDGE_BUFFER))
{
i16 new_x = cast(i16)(w.w / 2);
i16 new_y = cast(i16)(w.h / 2);
xcb_warp_pointer(w.conn, w.window, w.window, 0, 0, cast(i16)w.w, cast(i16)w.h, new_x, new_y);
w.mouse_prev_x = new_x;
w.mouse_prev_y = new_y;
ignore_mouse_events = true;
}
} break;
case XCB_CONFIGURE_NOTIFY:
{
xcb_configure_notify_event_t* config_event = cast(xcb_configure_notify_event_t*)e;
if (w.w != config_event.width || w.h != config_event.height)
{
w.w = config_event.width;
w.h = config_event.height;
}
} break;
default:
break;
}
ReturnInputs(w);
}
}
}
Library
LoadLibrary(string name)
{
Library lib = {
ptr: null,
};
lib.ptr = dlopen(name.ptr, RTLD_NOW);
return lib;
};
Function
LoadFunction(Library lib, string name)
{
Function fn = {
ptr: null,
};
fn.ptr = dlsym(lib.ptr, name.ptr);
return fn;
};
Input
ConvertInput(u64 x_key)
{
switch (x_key)
{
case XK_BackSpace: return Input.Backspace;
case XK_Return: return Input.Enter;
case XK_Tab: return Input.Tab;
case XK_Pause: return Input.Pause;
case XK_Caps_Lock: return Input.CapsLock;
case XK_Escape: return Input.Esc;
case XK_space: return Input.Space;
case XK_Prior: return Input.PageUp;
case XK_Next: return Input.PageDown;
case XK_End: return Input.End;
case XK_Home: return Input.Home;
case XK_Left: return Input.Left;
case XK_Up: return Input.Up;
case XK_Right: return Input.Right;
case XK_Down: return Input.Down;
case XK_Print: return Input.PrintScreen;
case XK_Insert: return Input.Insert;
case XK_Delete: return Input.Delete;
case XK_Meta_L:
case XK_Super_L: return Input.LeftSuper;
case XK_Meta_R:
case XK_Super_R: return Input.RightSuper;
case XK_KP_0: return Input.Num0;
case XK_KP_1: return Input.Num1;
case XK_KP_2: return Input.Num2;
case XK_KP_3: return Input.Num3;
case XK_KP_4: return Input.Num4;
case XK_KP_5: return Input.Num5;
case XK_KP_6: return Input.Num6;
case XK_KP_7: return Input.Num7;
case XK_KP_8: return Input.Num8;
case XK_KP_9: return Input.Num9;
case XK_multiply: return Input.NumStar;
case XK_KP_Subtract: return Input.NumMinus;
case XK_KP_Decimal: return Input.NumPeriod;
case XK_KP_Divide: return Input.NumSlash;
case XK_KP_Add: return Input.NumPlus;
case XK_F1: return Input.F1;
case XK_F2: return Input.F2;
case XK_F3: return Input.F3;
case XK_F4: return Input.F4;
case XK_F5: return Input.F5;
case XK_F6: return Input.F6;
case XK_F7: return Input.F7;
case XK_F8: return Input.F8;
case XK_F9: return Input.F9;
case XK_F10: return Input.F10;
case XK_F11: return Input.F11;
case XK_F12: return Input.F12;
case XK_Num_Lock: return Input.NumLock;
case XK_Scroll_Lock: return Input.ScrollLock;
case XK_Shift_L: return Input.LeftShift;
case XK_Shift_R: return Input.RightShift;
case XK_Control_L: return Input.LeftCtrl;
case XK_Control_R: return Input.RightCtrl;
case XK_Alt_L: return Input.LeftAlt;
case XK_Alt_R: return Input.RightAlt;
case XK_semicolon: return Input.Semicolon;
case XK_bracketleft: return Input.LeftBrace;
case XK_bracketright: return Input.RightBrace;
case XK_plus: return Input.Plus;
case XK_comma: return Input.Comma;
case XK_minus: return Input.Minus;
case XK_backslash: return Input.BackSlash;
case XK_slash: return Input.ForwardSlash;
case XK_grave: return Input.Tilde;
case XK_0: return Input.Zero;
case XK_1: return Input.One;
case XK_2: return Input.Two;
case XK_3: return Input.Three;
case XK_4: return Input.Four;
case XK_5: return Input.Five;
case XK_6: return Input.Six;
case XK_7: return Input.Seven;
case XK_8: return Input.Eight;
case XK_9: return Input.Nine;
case XK_a:
case XK_A: return Input.A;
case XK_b:
case XK_B: return Input.B;
case XK_c:
case XK_C: return Input.C;
case XK_d:
case XK_D: return Input.D;
case XK_e:
case XK_E: return Input.E;
case XK_f:
case XK_F: return Input.F;
case XK_g:
case XK_G: return Input.G;
case XK_h:
case XK_H: return Input.H;
case XK_i:
case XK_I: return Input.I;
case XK_j:
case XK_J: return Input.J;
case XK_k:
case XK_K: return Input.K;
case XK_l:
case XK_L: return Input.L;
case XK_m:
case XK_M: return Input.M;
case XK_n:
case XK_N: return Input.N;
case XK_o:
case XK_O: return Input.O;
case XK_p:
case XK_P: return Input.P;
case XK_q:
case XK_Q: return Input.Q;
case XK_r:
case XK_R: return Input.R;
case XK_s:
case XK_S: return Input.S;
case XK_t:
case XK_T: return Input.T;
case XK_u:
case XK_U: return Input.U;
case XK_v:
case XK_V: return Input.V;
case XK_w:
case XK_W: return Input.W;
case XK_x:
case XK_X: return Input.X;
case XK_y:
case XK_Y: return Input.Y;
case XK_z:
case XK_Z: return Input.Z;
default: return Input.None;
}
}
void*
MemAlloc(u64 size)
{
return mmap(null, size, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, -1, 0);
}
void
MemFree(void* ptr, u64 size)
{
assert(munmap(ptr, size) == 0, "MemFree failure");
}
struct Watcher
{
Arena arena;
u8[] buffer;
WatcherH handle;
WatchH dir_handle;
u8[] watched_dir;
bool blocking;
}
alias WatcherH = int;
alias WatchH = int;
enum WatchType
{
None,
Access = IN_ACCESS,
Metadata = IN_ATTRIB,
Create = IN_CREATE,
Delete = IN_DELETE,
Modify = IN_MODIFY,
Moved = IN_MOVED_FROM | IN_MOVED_TO,
}
alias WT = WatchType;
Watcher
WatchDirectory(string dir, WatchType type, bool blocking = false)
{
assert(dir.length > 0);
Watcher watcher = {
arena: CreateArena(MB(4)),
buffer: AllocArray!(u8)(MB(1)),
blocking: blocking,
watched_dir: (cast(u8*)dir.ptr)[0 .. dir.length],
};
watcher.handle = inotify_init();
assert(watcher.dir_handle >= 0, "WatchDirectory failure: unable to initialize");
if (!blocking)
{
fcntl(watcher.handle, F_SETFL, fcntl(watcher.handle, F_GETFL) | O_NONBLOCK);
}
watcher.dir_handle = inotify_add_watch(watcher.handle, dir.ptr, type);
return watcher;
}
WatchEvent[]
ViewChanges(Watcher* watcher)
{
assert(watcher.handle >= 0 && watcher.dir_handle >= 0, "ViewChanges failure: handles are not valid");
Reset(&watcher.arena);
WatchEvent[] events;
i64 length = read(watcher.handle, watcher.buffer.ptr, watcher.buffer.length);
if (length > 0)
{
i64 count = 0;
i64 i = 0;
while(i < length)
{
inotify_event* event = (cast(inotify_event*)(watcher.buffer.ptr + i));
count += 1;
assert(event.wd == watcher.dir_handle);
i += inotify_event.sizeof + event.len;
}
if (count > 0)
{
struct Moved
{
u32 cookie;
i64 to;
i64 from;
}
Moved[] moved = AllocArray!(Moved)(&watcher.arena, (count/2)+1);
i64 m_count = 0;
events = AllocArray!(WatchEvent)(&watcher.arena, count);
count = 0;
i = 0;
while (i < length)
{
inotify_event* event = (cast(inotify_event*)(watcher.buffer.ptr + i));
if (event.len > 0)
{
u8[] file_name = (cast(u8*)event.name)[0 .. strlen(event.name.ptr)];
if (event.mask & IN_MOVED_FROM || event.mask & IN_MOVED_TO)
{
bool from = (event.mask & IN_MOVED_FROM) > 0;
Moved* m;
foreach(j; 0 .. m_count)
{
if (moved[j].cookie == event.cookie)
{
m = moved.ptr + j;
}
}
if (m != null)
{
if (from && m.to >= 0)
{
events[m.to].names[0] = file_name;
if (watcher.watched_dir == file_name)
{
events[m.to].type &= ~(WET.File | WET.Dir);
events[m.to].type |= WET.Self;
}
}
else if (!from && m.from >= 0)
{
events[m.from].names[1] = file_name;
}
}
else
{
WatchEvent* ev = events.ptr + count;
ev.type = (event.mask & IN_ISDIR) ? WET.Dir : WET.File;
ev.type |= WET.Moved;
moved[m_count].cookie = event.cookie;
if (from)
{
ev.names[0] = file_name;
moved[m_count].from = count;
moved[m_count].to = -1;
if (watcher.watched_dir == file_name)
{
ev.type &= ~(WET.File | WET.Dir);
ev.type |= WET.Self;
}
}
else
{
ev.names[1] = file_name;
moved[m_count].to = count;
moved[m_count].from = -1;
}
count += 1;
m_count += 1;
}
}
else
{
WatchEvent* ev = events.ptr + count;
ev.type = (event.mask & IN_ISDIR) ? WET.Dir : WET.File;
ev.names[0] = file_name;
if (ev.names[0] == watcher.watched_dir)
{
ev.type = WET.Self;
}
SetEventType(ev, event.mask);
count += 1;
}
}
else
{
WatchEvent* ev = events.ptr + count;
ev.type = (event.mask & IN_ISDIR) ? WET.Dir : WET.File;
SetEventType(ev, event.mask);
count += 1;
}
i += inotify_event.sizeof + event.len;
}
}
}
return events;
}
void
SetEventType(WatchEvent* ev, u32 mask)
{
if (mask & IN_ACCESS)
{
ev.type |= WET.Accessed;
}
else if (mask & IN_ATTRIB)
{
ev.type |= WET.Metadata;
}
else if (mask & IN_CREATE)
{
ev.type |= WET.Created;
}
else if (mask & IN_MODIFY)
{
ev.type |= WET.Modified;
}
else if (mask & IN_DELETE)
{
ev.type |= WET.Deleted;
}
}
unittest
{
import std.stdio;
import std.file : Remove = remove;
Watcher fw = WatchDirectory("./", WT.Create | WT.Delete | WT.Moved | WT.Modify | WT.Access | WT.Metadata | WT.Access | WT.Metadata);
auto f = File("test_file.txt", "wb");
f.write("test");
f.sync();
f.close();
Remove("./test_file.txt");
WatchEvent[] events = ViewChanges(&fw);
assert(events.length == 3);
assert(events[0].type == WET.FileCreated);
assert(events[0].names[0] == r"test_file.txt");
assert(events[1].type == WET.FileModified);
assert(events[1].names[0] == r"test_file.txt");
assert(events[2].type == WET.FileDeleted);
assert(events[2].names[0] == r"test_file.txt");
}
}
enum WatchEventType
{
None = 0x0000,
File = 0x0001,
Dir = 0x0002,
Self = 0x0004,
Created = 0x0008,
Modified = 0x0010,
Deleted = 0x0020,
Moved = 0x0040,
Metadata = 0x0080,
Accessed = 0x0100,
FileCreated = WET.File | WET.Created,
FileModified = WET.File | WET.Modified,
FileDeleted = WET.File | WET.Deleted,
FileMoved = WET.File | WET.Moved,
FileMetadata = WET.File | WET.Metadata,
FileAccessed = WET.File | WET.Accessed,
DirCreated = WET.Dir | WET.Created,
DirModified = WET.Dir | WET.Modified,
DirDeleted = WET.Dir | WET.Deleted,
DirMoved = WET.Dir | WET.Moved,
DirMetadata = WET.Dir | WET.Metadata,
DirAccessed = WET.Dir | WET.Accessed,
SelfCreated = WET.Self | WET.Created,
SelfModified = WET.Self | WET.Modified,
SelfDeleted = WET.Self | WET.Deleted,
SelfMoved = WET.Self | WET.Moved,
SelfMetadata = WET.Self | WET.Metadata,
SelfAccessed = WET.Self | WET.Accessed,
}
alias WET = WatchEventType;
struct WatchEvent
{
WatchEventType type;
u8[][2] names;
}
version(Windows)
{
import core.sys.windows.windows;
struct SysThread
{
HANDLE handle;
}
alias Win32ThreadProc = extern (C) u32 function(void*);
SysThread
CreateThread(void* proc, void* param)
{
SysThread thread;
CreateThread(null, 0, cast(Win32ThreadProc)proc, param, 0, null);
return thread;
}
void
Suspend(SysThread* thread)
{
SuspendThread(thread.handle);
}
void
Wake(SysThread* thread)
{
ResumeThread(thread.handle);
}
void
Kill()
{
ExitThread(0);
}
}