1832 lines
37 KiB
D
1832 lines
37 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 : u32
|
|
{
|
|
None,
|
|
Backspace = 0x08,
|
|
Tab = 0x09,
|
|
Enter = 0x0A,
|
|
Escape = 0x1B,
|
|
Space = 0x20,
|
|
Exclamation = 0x21,
|
|
DoubleQuote = 0x22,
|
|
Hash = 0x23,
|
|
Dollar = 0x24,
|
|
Percent = 0x25,
|
|
Ampersand = 0x26,
|
|
SingleQuote = 0x27,
|
|
LeftParen = 0x28,
|
|
RightParent = 0x29,
|
|
Asterisk = 0x2A,
|
|
Plus = 0x2B,
|
|
Comma = 0x2C,
|
|
Minus = 0x2D,
|
|
Period = 0x2E,
|
|
Slash = 0x2F,
|
|
Zero = 0x30,
|
|
One = 0x31,
|
|
Two = 0x32,
|
|
Three = 0x33,
|
|
Four = 0x34,
|
|
Five = 0x35,
|
|
Six = 0x36,
|
|
Seven = 0x37,
|
|
Eight = 0x38,
|
|
Nine = 0x39,
|
|
Colon = 0x3A,
|
|
Semicolon = 0x3B,
|
|
LessThan = 0x3C,
|
|
Equals = 0x3D,
|
|
GreaterThan = 0x3E,
|
|
Question = 0x3F,
|
|
At = 0x40,
|
|
A = 0x41, B = 0x42, C = 0x43, D = 0x44, E = 0x45, F = 0x46, G = 0x47, H = 0x48, I = 0x49, J = 0x4A, K = 0x4B, L = 0x4C, M = 0x4D,
|
|
N = 0x4E, O = 0x4F, P = 0x50, Q = 0x51, R = 0x52, S = 0x53, T = 0x54, U = 0x55, V = 0x56, W = 0x57, X = 0x58, Y = 0x59, Z = 0x5A,
|
|
LeftBracket = 0x5B,
|
|
BackSlash = 0x5C,
|
|
RightBracket = 0x5D,
|
|
Caret = 0x5E,
|
|
Underscore = 0x5F,
|
|
Grave = 0x60,
|
|
a = I.A+32, b = I.B+32, c = I.C+32, d = I.D+32, e = I.E+32, f = I.F+32, g = I.G+32, h = I.H+32, i = I.I+32, j = I.J+32, k = I.K+32, l = I.L+32, m = I.M+32,
|
|
n = I.N+32, o = I.O+32, p = I.P+32, q = I.Q+32, r = I.R+32, s = I.S+32, t = I.T+32, u = I.U+32, v = I.V+32, w = I.W+32, x = I.X+32, y = I.Y+32, z = I.Z+32,
|
|
LeftBrace = 0x7B,
|
|
VerticalBar = 0x7C,
|
|
RightBrace = 0x7D,
|
|
Tilde = 0x7E,
|
|
Delete = 0x7F,
|
|
|
|
F1 = 0x150,
|
|
F2 = 0x151,
|
|
F3 = 0x152,
|
|
F4 = 0x153,
|
|
F5 = 0x154,
|
|
F6 = 0x155,
|
|
F7 = 0x156,
|
|
F8 = 0x157,
|
|
F9 = 0x158,
|
|
F10 = 0x159,
|
|
F11 = 0x15A,
|
|
F12 = 0x15B,
|
|
PrintScreen = 0x10C,
|
|
ScrollLock = 0x10D,
|
|
Pause = 0x10E,
|
|
Insert = 0x10F,
|
|
Home = 0x110,
|
|
End = 0x111,
|
|
PageUp = 0x112,
|
|
PageDown = 0x113,
|
|
NumLock = 0x114,
|
|
|
|
NumEnter = 0x10A,
|
|
NumAsterisk = 0x12A,
|
|
NumPlus = 0x12B,
|
|
NumMinus = 0x12D,
|
|
NumPeriod = 0x12E,
|
|
NumSlash = 0x12F,
|
|
NumZero = 0x130,
|
|
NumOne = 0x131,
|
|
NumTwo = 0x132,
|
|
NumThree = 0x133,
|
|
NumFour = 0x134,
|
|
NumFive = 0x135,
|
|
NumSix = 0x136,
|
|
NumSeven = 0x137,
|
|
NumEight = 0x138,
|
|
NumNine = 0x139,
|
|
|
|
LeftCtrl = 0x13A,
|
|
RightCtrl = 0x13B,
|
|
LeftShift = 0x13C,
|
|
RightShift = 0x13D,
|
|
LeftSuper = 0x13E,
|
|
RightSuper = 0x13F,
|
|
LeftAlt = 0x140,
|
|
RightAlt = 0x141,
|
|
CapsLock = 0x142,
|
|
|
|
Left = 0x143,
|
|
Right = 0x144,
|
|
Up = 0x145,
|
|
Down = 0x146,
|
|
|
|
// Mouse
|
|
MouseMotion = 0x147,
|
|
|
|
LeftClick = 0x148, MiddleClick = 0x149, RightClick = 0x14A,
|
|
};
|
|
|
|
alias I = Input;
|
|
|
|
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;
|
|
}
|
|
|
|
enum ClipboardMode
|
|
{
|
|
Clipboard,
|
|
Primary,
|
|
Selection = Primary,
|
|
Secondary,
|
|
Max
|
|
}
|
|
|
|
alias CBM = ClipboardMode;
|
|
|
|
struct Inputs
|
|
{
|
|
DLList!(InputEvent) list;
|
|
Arena arena;
|
|
}
|
|
|
|
pragma(inline) bool
|
|
Shift(Modifier md)
|
|
{
|
|
return cast(bool)(md & (MD.LeftShift | MD.RightShift));
|
|
}
|
|
|
|
pragma(inline) bool
|
|
Ctrl(Modifier md)
|
|
{
|
|
return cast(bool)(md & (MD.LeftCtrl | MD.RightCtrl));
|
|
}
|
|
|
|
pragma(inline) bool
|
|
Alt(Modifier md)
|
|
{
|
|
return cast(bool)(md & (MD.LeftAlt | MD.RightAlt));
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
__gshared const DNode!(SysMessage) g_sys_message;
|
|
__gshared 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.sys.time : timespec, timeval, gettimeofday;
|
|
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,
|
|
PThreadCondTimedWait = pthread_cond_timedwait;
|
|
|
|
import core.stdc.string : strlen;
|
|
|
|
const u32 X11_CB_TRANSFER_SIZE_DEFAULT = 1048576;
|
|
const u32 X11_TIMEOUT_DEFAULT = 1500;
|
|
|
|
struct SysThread
|
|
{
|
|
PThread handle;
|
|
PThreadCond cond;
|
|
PThreadMutex mut;
|
|
}
|
|
|
|
struct Selection
|
|
{
|
|
bool owned;
|
|
u8[] data;
|
|
xcb_atom_t target;
|
|
xcb_atom_t xmode;
|
|
}
|
|
|
|
enum Atoms
|
|
{
|
|
Targets,
|
|
Multiple,
|
|
Timestamp,
|
|
Incr,
|
|
Clipboard,
|
|
Utf8String,
|
|
WMProtocols,
|
|
DeleteWindow,
|
|
StateHidden,
|
|
Max,
|
|
}
|
|
|
|
const char[][] ATOM_STRS = [
|
|
CastStr!(char)("TARGETS"),
|
|
CastStr!(char)("MULTIPLE"),
|
|
CastStr!(char)("TIMESTAMP"),
|
|
CastStr!(char)("INCR"),
|
|
CastStr!(char)("CLIPBOARD"),
|
|
CastStr!(char)("UTF8_STRING"),
|
|
CastStr!(char)("WM_PROTOCOLS"),
|
|
CastStr!(char)("WM_DELETE_WINDOW"),
|
|
CastStr!(char)("_NET_WM_STATE_HIDDEN"),
|
|
];
|
|
|
|
alias PThreadProc = extern (C) void* function(void*);
|
|
|
|
SysThread
|
|
CreateThread(void* proc, void* param)
|
|
{
|
|
SysThread thread;
|
|
bool result;
|
|
|
|
result = cast(bool)(!PThreadMutexInit(&thread.mut, null));
|
|
result &= cast(bool)(!PThreadCondInit(&thread.cond, null));
|
|
result &= cast(bool)(!PThreadCreate(&thread.handle, null, cast(PThreadProc)proc, param));
|
|
assert(result);
|
|
|
|
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
|
|
{
|
|
xcb_atom_t[Atoms.max] atoms;
|
|
Display* display;
|
|
xcb_connection_t* conn;
|
|
xcb_screen_t* screen;
|
|
xcb_window_t window;
|
|
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;
|
|
|
|
Mut cb_msg_mut;
|
|
TicketMut cb_mut;
|
|
Selection[CBM.max] selections;
|
|
version(linux) u32 cb_transfer_size;
|
|
};
|
|
|
|
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(),
|
|
cb_mut: CreateTicketMut(),
|
|
cb_msg_mut: CreateMut(),
|
|
cb_timer: CreateTimer(X11_TIMEOUT_DEFAULT),
|
|
inputs: [
|
|
{ arena: CreateArena(MB(1)) },
|
|
{ arena: CreateArena(MB(1)) },
|
|
],
|
|
};
|
|
|
|
version(linux)
|
|
{
|
|
window.cb_transfer_size = X11_CB_TRANSFER_SIZE_DEFAULT;
|
|
}
|
|
|
|
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|
|
|
XCB_EVENT_MASK_PROPERTY_CHANGE;
|
|
|
|
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);
|
|
|
|
for(u64 i = 0; i < Atoms.max; i += 1)
|
|
{
|
|
xcb_intern_atom_cookie_t intern = xcb_intern_atom(window.conn, 1, cast(u16)ATOM_STRS[i].length, ATOM_STRS[i].ptr);
|
|
xcb_intern_atom_reply_t* reply = xcb_intern_atom_reply(window.conn, intern, &error);
|
|
CheckErr(&window, &cookie, error, "xcb_intern_atom failure");
|
|
|
|
window.atoms[i] = reply.atom;
|
|
|
|
pureFree(error);
|
|
pureFree(reply);
|
|
}
|
|
|
|
window.selections[CBM.Clipboard].xmode = window.atoms[Atoms.Clipboard];
|
|
window.selections[CBM.Primary].xmode = XCB_ATOM_PRIMARY;
|
|
window.selections[CBM.Secondary].xmode = XCB_ATOM_SECONDARY;
|
|
|
|
cookie = xcb_change_property_checked(
|
|
window.conn,
|
|
XCB_PROP_MODE_REPLACE,
|
|
window.window,
|
|
window.atoms[Atoms.WMProtocols],
|
|
XCB_ATOM_ATOM,
|
|
32,
|
|
1,
|
|
&window.atoms[Atoms.DeleteWindow]
|
|
);
|
|
error = xcb_request_check(window.conn, cookie);
|
|
CheckErr(&window, &cookie, error, "xcb_change_property_checked failure");
|
|
pureFree(error);
|
|
|
|
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);
|
|
}
|
|
|
|
bool
|
|
TransmitSelection(PlatformWindow* w, xcb_selection_request_event_t* ev)
|
|
{
|
|
bool result;
|
|
if(ev.property == XCB_NONE)
|
|
{
|
|
ev.property = ev.target;
|
|
}
|
|
|
|
if(ev.target == w.atoms[Atoms.Targets])
|
|
{
|
|
xcb_atom_t[3] targets = [
|
|
w.atoms[Atoms.Timestamp],
|
|
w.atoms[Atoms.Targets],
|
|
w.atoms[Atoms.Utf8String]
|
|
];
|
|
|
|
xcb_change_property(
|
|
w.conn,
|
|
XCB_PROP_MODE_REPLACE,
|
|
ev.requestor,
|
|
ev.property,
|
|
XCB_ATOM_ATOM,
|
|
xcb_atom_t.sizeof * 8,
|
|
targets.length,
|
|
targets.ptr
|
|
);
|
|
|
|
result = true;
|
|
}
|
|
else if(ev.target == w.atoms[Atoms.Timestamp])
|
|
{
|
|
xcb_timestamp_t cur = XCB_CURRENT_TIME;
|
|
xcb_change_property(
|
|
w.conn,
|
|
XCB_PROP_MODE_REPLACE,
|
|
ev.requestor,
|
|
ev.property,
|
|
XCB_ATOM_INTEGER,
|
|
cur.sizeof * 8,
|
|
1,
|
|
&cur
|
|
);
|
|
|
|
result = true;
|
|
}
|
|
else if(ev.target == w.atoms[Atoms.Utf8String])
|
|
{
|
|
Selection* sel = null;
|
|
Lock(&w.cb_mut);
|
|
|
|
foreach(i; CBM.min .. CBM.max)
|
|
{
|
|
if(w.selections[i].xmode == ev.selection)
|
|
{
|
|
sel = &w.selections[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(sel != null && sel.owned && sel.data.length > 0 && sel.target == ev.target)
|
|
{
|
|
xcb_change_property(
|
|
w.conn,
|
|
XCB_PROP_MODE_REPLACE,
|
|
ev.requestor,
|
|
ev.property,
|
|
ev.target,
|
|
8,
|
|
cast(u32)sel.data.length,
|
|
sel.data.ptr
|
|
);
|
|
|
|
result = true;
|
|
}
|
|
|
|
Unlock(&w.cb_mut);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool
|
|
ClipboardOwns(PlatformWindow* w, ClipboardMode mode)
|
|
{
|
|
bool result;
|
|
|
|
Lock(&w.cb_mut);
|
|
|
|
result = w.selections[mode].owned;
|
|
Unlock(&w.cb_mut);
|
|
|
|
return result;
|
|
}
|
|
|
|
u8[]
|
|
ClipboardText(PlatformWindow* w, ClipboardMode mode)
|
|
{
|
|
u8[] buf;
|
|
|
|
Lock(&w.cb_mut);
|
|
|
|
scope(exit) Unlock(&w.cb_mut);
|
|
|
|
Selection* sel = &w.selections[mode];
|
|
|
|
if(sel.owned)
|
|
{
|
|
buf = GetClipboardSelection(w, sel);
|
|
}
|
|
else
|
|
{
|
|
auto owner = xcb_get_selection_owner_reply(w.conn, xcb_get_selection_owner(w.conn, sel.xmode), null);
|
|
scope(exit) pureFree(owner);
|
|
|
|
if(owner != null && owner.owner != 0)
|
|
{
|
|
FreeArray(sel.data);
|
|
sel.data = [];
|
|
|
|
sel.target = w.atoms[Atoms.Utf8String];
|
|
xcb_convert_selection(w.conn, w.window, sel.xmode, sel.target, sel.xmode, XCB_CURRENT_TIME);
|
|
xcb_flush(w.conn);
|
|
|
|
while(!w.cb_msg_mut.locked) {}
|
|
while(w.cb_msg_mut.locked) {}
|
|
|
|
GetClipboardSelection(w, sel);
|
|
}
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
u8[]
|
|
ClipboardText(PlatformWindow* w)
|
|
{
|
|
return ClipboardText(w, CBM.Clipboard);
|
|
}
|
|
|
|
bool
|
|
SetClipboard(PlatformWindow* w, u8[] data, ClipboardMode mode)
|
|
{
|
|
bool result = true;
|
|
|
|
if(data.length > 0)
|
|
{
|
|
Lock(&w.cb_mut);
|
|
scope(exit) Unlock(&w.cb_mut);
|
|
|
|
Selection* sel = &w.selections[mode];
|
|
if(sel.data.length > 0)
|
|
{
|
|
FreeArray(sel.data);
|
|
sel.data = [];
|
|
}
|
|
|
|
sel.data = Alloc!(u8)(data.length+1);
|
|
if(sel.data.length > 0)
|
|
{
|
|
MemCpy(sel.data.ptr, data.ptr, data.length);
|
|
|
|
sel.data[sel.data.length-1] = '\0';
|
|
sel.owned = true;
|
|
sel.target = w.atoms[Atoms.Utf8String];
|
|
|
|
xcb_set_selection_owner(w.conn, w.window, sel.xmode, XCB_CURRENT_TIME);
|
|
xcb_flush(w.conn);
|
|
}
|
|
else
|
|
{
|
|
result = false;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool
|
|
SetClipboard(PlatformWindow* w, u8[] data)
|
|
{
|
|
return SetClipboard(w, data, CBM.Clipboard);
|
|
}
|
|
|
|
u8[]
|
|
GetClipboardSelection(PlatformWindow* w, Selection* sel)
|
|
{
|
|
u8[] buf;
|
|
|
|
if(sel.data.length > 0 && sel.target == w.atoms[Atoms.Utf8String])
|
|
{
|
|
buf = ScratchAllocCopy(sel.data);
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
void
|
|
RetrieveSelection(PlatformWindow* w, xcb_selection_notify_event_t* ev)
|
|
{
|
|
u8[] buf;
|
|
u64 buf_size;
|
|
u64 bytes_after = 1;
|
|
xcb_get_property_reply_t* reply;
|
|
xcb_atom_t actual_type;
|
|
u8 actual_format;
|
|
|
|
if(ev.property == XCB_ATOM_PRIMARY || ev.property == XCB_ATOM_SECONDARY || ev.property == w.atoms[Atoms.Clipboard])
|
|
{
|
|
while(bytes_after > 0)
|
|
{
|
|
scope(exit) pureFree(reply);
|
|
|
|
xcb_get_property_cookie_t cookie = xcb_get_property(
|
|
w.conn,
|
|
true,
|
|
w.window,
|
|
ev.property,
|
|
XCB_ATOM_ANY,
|
|
cast(u32)(buf_size/4),
|
|
cast(u32)(w.cb_transfer_size/4)
|
|
);
|
|
|
|
reply = xcb_get_property_reply(w.conn, cookie, null);
|
|
|
|
if(reply == null || (buf_size > 0 && (reply.format != actual_format || reply.type != actual_type)) || reply.format%8 != 0)
|
|
{
|
|
Errf("RetrieveSelection failure: Invalid return value from xcb_get_property_reply");
|
|
break;
|
|
}
|
|
|
|
if(buf_size == 0)
|
|
{
|
|
actual_type = reply.type;
|
|
actual_format = reply.format;
|
|
}
|
|
|
|
int nitems = xcb_get_property_value_length(reply);
|
|
if(nitems > 0)
|
|
{
|
|
if(buf_size%4 != 0)
|
|
{
|
|
Errf("RetrieveSelection failure: Data size is not a multiple of 4");
|
|
break;
|
|
}
|
|
|
|
u64 unit_size = reply.format/8;
|
|
buf = Alloc!(u8)(unit_size * (buf_size + nitems));
|
|
|
|
MemCpy(buf.ptr + buf_size, xcb_get_property_value(reply), nitems * unit_size);
|
|
buf_size += nitems * unit_size;
|
|
}
|
|
|
|
bytes_after = reply.bytes_after;
|
|
}
|
|
}
|
|
|
|
if(buf != null)
|
|
{
|
|
Lock(&w.cb_mut);
|
|
|
|
Selection* sel;
|
|
foreach(i; CBM.min .. CBM.max)
|
|
{
|
|
if(w.selections[i].xmode == ev.property)
|
|
{
|
|
sel = &w.selections[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(sel != null && sel.target == actual_type)
|
|
{
|
|
FreeArray(sel.data);
|
|
sel.data = buf;
|
|
buf = [];
|
|
}
|
|
else
|
|
{
|
|
Errf("RetrieveSelection failure: mismatched selection actual_type: %s", actual_type);
|
|
}
|
|
|
|
Unlock(&w.cb_mut);
|
|
}
|
|
else
|
|
{
|
|
FreeArray(buf);
|
|
}
|
|
}
|
|
|
|
void
|
|
ClearSelection(PlatformWindow* w, xcb_selection_clear_event_t* ev)
|
|
{
|
|
if(ev.owner == w.window)
|
|
{
|
|
foreach(i; CBM.min .. CBM.max)
|
|
{
|
|
Selection* sel = &w.selections[i];
|
|
if(sel.xmode == ev.selection)
|
|
{
|
|
Lock(&w.cb_mut);
|
|
|
|
FreeArray(sel.data);
|
|
sel.data = [];
|
|
sel.owned = false;
|
|
sel.target = XCB_NONE;
|
|
|
|
Unlock(&w.cb_mut);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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.atoms[Atoms.DeleteWindow])
|
|
{
|
|
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 modifier_inputs = [Input.LeftShift, Input.RightShift, Input.LeftCtrl, Input.RightCtrl, Input.LeftAlt, Input.RightAlt];
|
|
enum modifiers = [MD.LeftShift, MD.RightShift, MD.LeftCtrl, MD.RightCtrl, MD.LeftAlt, MD.RightAlt];
|
|
|
|
static foreach(i, md; modifier_inputs)
|
|
{
|
|
if(input == md)
|
|
{
|
|
w.modifier = cast(Modifier)(pressed ? (w.modifier | modifiers[i]) : (w.modifier & ~modifiers[i]));
|
|
}
|
|
}
|
|
|
|
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;
|
|
case XCB_SELECTION_CLEAR:
|
|
{
|
|
ClearSelection(w, cast(xcb_selection_clear_event_t*)e);
|
|
} break;
|
|
case XCB_SELECTION_NOTIFY:
|
|
{
|
|
TryLock(&w.cb_msg_mut);
|
|
RetrieveSelection(w, cast(xcb_selection_notify_event_t*)e);
|
|
Unlock(&w.cb_msg_mut);
|
|
} break;
|
|
case XCB_SELECTION_REQUEST:
|
|
{
|
|
auto req = cast(xcb_selection_request_event_t*)e;
|
|
xcb_selection_notify_event_t notify = {
|
|
response_type: XCB_SELECTION_NOTIFY,
|
|
time: XCB_CURRENT_TIME,
|
|
requestor: req.requestor,
|
|
selection: req.selection,
|
|
target: req.target,
|
|
property: TransmitSelection(w, req) ? req.property : XCB_NONE,
|
|
};
|
|
|
|
xcb_send_event(w.conn, false, req.requestor, XCB_EVENT_MASK_PROPERTY_CHANGE, cast(char*)¬ify);
|
|
xcb_flush(w.conn);
|
|
} 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;
|
|
}
|
|
|
|
u8
|
|
InputToChar(Input input, bool modified = false)
|
|
{
|
|
switch(input)
|
|
{
|
|
case Input.Tab:
|
|
case Input.Enter:
|
|
case Input.Space:
|
|
{
|
|
return cast(u8)(input);
|
|
}
|
|
case Input.LeftBracket:
|
|
case Input.RightBracket:
|
|
case Input.BackSlash:
|
|
case Input.Minus:
|
|
case Input.Equals:
|
|
case Input.Grave:
|
|
case Input.Semicolon:
|
|
case Input.SingleQuote:
|
|
case Input.Comma:
|
|
case Input.Period:
|
|
case Input.Slash:
|
|
case Input.Zero: .. case Input.Nine:
|
|
{
|
|
u32 code = cast(u32)(0xFF & input);
|
|
if(!modified)
|
|
{
|
|
return cast(u8)code;
|
|
}
|
|
|
|
switch(cast(Input)code)
|
|
{
|
|
case Input.Zero: return ')';
|
|
case Input.One: return '!';
|
|
case Input.Two: return '@';
|
|
case Input.Three: return '#';
|
|
case Input.Four: return '$';
|
|
case Input.Five: return '%';
|
|
case Input.Six: return '^';
|
|
case Input.Seven: return '&';
|
|
case Input.Eight: return '*';
|
|
case Input.Nine: return '(';
|
|
case Input.LeftBracket: return '{';
|
|
case Input.RightBracket: return '}';
|
|
case Input.BackSlash: return '|';
|
|
case Input.Minus: return '_';
|
|
case Input.Equals: return '+';
|
|
case Input.Grave: return '~';
|
|
case Input.Semicolon: return ':';
|
|
case Input.SingleQuote: return '"';
|
|
case Input.Comma: return '<';
|
|
case Input.Period: return '>';
|
|
case Input.Slash: return '?';
|
|
default: return 0;
|
|
}
|
|
}
|
|
case Input.a: .. case Input.z:
|
|
{
|
|
return !modified ? cast(u8)input : cast(u8)(input-32);
|
|
}
|
|
case Input.NumEnter:
|
|
case Input.NumAsterisk:
|
|
case Input.NumPlus:
|
|
case Input.NumMinus:
|
|
case Input.NumPeriod:
|
|
case Input.NumSlash:
|
|
case Input.NumZero: .. case Input.NumNine:
|
|
{
|
|
u32 code = cast(u32)(input & 0xFF);
|
|
return cast(u8)(code);
|
|
}
|
|
default:
|
|
{
|
|
u32 code = cast(u32)input;
|
|
return code >= 0x20 && code <= 0x7E ? cast(u8)code : 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
Input
|
|
ConvertInput(u64 x_key)
|
|
{
|
|
switch (x_key)
|
|
{
|
|
case XK_space: .. case XK_asciitilde:
|
|
{
|
|
return cast(Input)(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.Escape;
|
|
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.NumZero;
|
|
case XK_KP_1: return Input.NumOne;
|
|
case XK_KP_2: return Input.NumTwo;
|
|
case XK_KP_3: return Input.NumThree;
|
|
case XK_KP_4: return Input.NumFour;
|
|
case XK_KP_5: return Input.NumFive;
|
|
case XK_KP_6: return Input.NumSix;
|
|
case XK_KP_7: return Input.NumSeven;
|
|
case XK_KP_8: return Input.NumEight;
|
|
case XK_KP_9: return Input.NumNine;
|
|
case XK_KP_Multiply: return Input.NumAsterisk;
|
|
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;
|
|
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)
|
|
{
|
|
auto result = munmap(ptr, size);
|
|
assert(result == 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);
|
|
}
|
|
|
|
}
|
|
|
|
unittest
|
|
{
|
|
{ // Keys
|
|
u8 ch = InputToChar(Input.NumEight);
|
|
assert(ch == '8');
|
|
ch = InputToChar(Input.b, true);
|
|
assert(ch == 'B');
|
|
|
|
version(linux)
|
|
{
|
|
ch = InputToChar(ConvertInput(XK_asciitilde));
|
|
assert(ch == '~');
|
|
ch = InputToChar(ConvertInput(XK_grave));
|
|
assert(ch == '`');
|
|
}
|
|
}
|
|
}
|