diff --git a/platform.d b/platform.d new file mode 100644 index 0000000..a44c8fa --- /dev/null +++ b/platform.d @@ -0,0 +1,622 @@ +import aliases; +import includes; +import std.stdio; +import core.memory; +import core.thread.osthread; +import core.time; + +@nogc: + +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, +}; + +alias KBI = Input; + +version(linux) +{ +import core.sys.posix.dlfcn; +import core.sys.posix.sys.mman; + +struct InputEvent +{ + Input key; + union + { + struct + { + bool pressed; + }; + struct + { + i32 rel_x; + i32 rel_y; + u16 x; + u16 y; + }; + }; +} + +struct Inputs +{ + InputEvent[10] events; + u32 count; +} + +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; + Inputs inputs; +}; + +struct Library +{ + void* ptr; +}; + +struct Function +{ + void* ptr; +}; + +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, + }; + + 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); + + LockCursor(&window); + UnlockCursor(&window); + + return window; +}; + +void +LockCursor(PlatformWindow* window) +{ + 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) + { + break; + } + + assert(counter < 5, "Unable to grab cursor"); + counter += 1; + Thread.sleep(dur!("msecs")(50)); + } + + HideCursor(window); + window.locked_cursor = true; + } +} + +void +UnlockCursor(PlatformWindow* window) +{ + if (window.locked_cursor) + { + xcb_ungrab_pointer(window.conn, XCB_CURRENT_TIME); + ShowCursor(window); + window.locked_cursor = false; + } +} + +// TODO: improve error handling for show/hide cursor +void +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); + CheckErr(window, &hide_cursor_cookie, error, "xcb_xfixes_hide_cursor_checked failure"); + pureFree(error); +} + +void +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); + CheckErr(window, &show_cursor_cookie, error, "xcb_xfixes_show_cursor_checked failure"); + pureFree(error); +} + +void +FlushEvents(PlatformWindow* window) +{ + xcb_generic_event_t* e; + do + { + e = xcb_poll_for_event(window.conn); + } while (e); +} + +void +HandleEvents(PlatformWindow* window) +{ + window.inputs.count = 0; + + xcb_generic_event_t* e; + + bool ignore_mouse_events = false; + + do + { + e = xcb_poll_for_event(window.conn); + + if (e) + { + 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 != window.window) + { + break; + } + + if (msg.data.data32[0] == window.close_event) + { + window.close = true; + } + } break; + case XCB_KEY_RELEASE: + case XCB_KEY_PRESS: + { + // TODO: definitely definitely need to rework this whole thing + if (window.inputs.count == window.inputs.events.length) + { + continue; + } + + 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(window.display, cast(KeyCode)code, 0, 0); + KBI input = ConvertInput(key_sym); + + if (input != KBI.None) + { + window.inputs.events[window.inputs.count].key = input; + window.inputs.events[window.inputs.count].pressed = pressed; + window.inputs.count += 1; + + if (input == KBI.F2) + { + LockCursor(window); + } + else if (input == KBI.F3) + { + UnlockCursor(window); + } + } + } break; + case XCB_BUTTON_PRESS: + case XCB_BUTTON_RELEASE: + { + if (window.inputs.count == window.inputs.events.length) continue; + + 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) + { + window.inputs.events[window.inputs.count].key = input; + window.inputs.events[window.inputs.count].pressed = pressed; + window.inputs.count += 1; + } + } break; + case XCB_MOTION_NOTIFY: + { + if (ignore_mouse_events) continue; + if (window.inputs.count == window.inputs.events.length) 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) + { + window.mouse_prev_x = x; + window.mouse_prev_y = y; + first = false; + } + + if (x > 0 || y > 0) + { + window.inputs.events[window.inputs.count].key = Input.MouseMotion; + window.inputs.events[window.inputs.count].x = move_event.event_x; + window.inputs.events[window.inputs.count].y = move_event.event_y; + window.inputs.events[window.inputs.count].rel_x = window.mouse_prev_x - x; + window.inputs.events[window.inputs.count].rel_y = window.mouse_prev_y - y; + window.inputs.count += 1; + } + + window.mouse_prev_x = x; + window.mouse_prev_y = y; + + if (window.locked_cursor && + (x < WINDOW_EDGE_BUFFER || y < WINDOW_EDGE_BUFFER || x > window.w - WINDOW_EDGE_BUFFER || y > window.h - WINDOW_EDGE_BUFFER)) + { + i16 new_x = cast(i16)(window.w / 2); + i16 new_y = cast(i16)(window.h / 2); + xcb_warp_pointer(window.conn, window.window, window.window, 0, 0, cast(i16)window.w, cast(i16)window.h, new_x, new_y); + window.mouse_prev_x = new_x; + window.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 (window.w != config_event.width || window.h != config_event.height) + { + window.w = config_event.width; + window.h = config_event.height; + } + } break; + default: + break; + } + } + } + while (e); +} + +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 KBI.Backspace; + case XK_Return: return KBI.Enter; + case XK_Tab: return KBI.Tab; + case XK_Pause: return KBI.Pause; + case XK_Caps_Lock: return KBI.CapsLock; + case XK_Escape: return KBI.Esc; + case XK_space: return KBI.Space; + case XK_Prior: return KBI.PageUp; + case XK_Next: return KBI.PageDown; + case XK_End: return KBI.End; + case XK_Home: return KBI.Home; + case XK_Left: return KBI.Left; + case XK_Up: return KBI.Up; + case XK_Right: return KBI.Right; + case XK_Down: return KBI.Down; + case XK_Print: return KBI.PrintScreen; + case XK_Insert: return KBI.Insert; + case XK_Delete: return KBI.Delete; + case XK_Meta_L: + case XK_Super_L: return KBI.LeftSuper; + case XK_Meta_R: + case XK_Super_R: return KBI.RightSuper; + case XK_KP_0: return KBI.Num0; + case XK_KP_1: return KBI.Num1; + case XK_KP_2: return KBI.Num2; + case XK_KP_3: return KBI.Num3; + case XK_KP_4: return KBI.Num4; + case XK_KP_5: return KBI.Num5; + case XK_KP_6: return KBI.Num6; + case XK_KP_7: return KBI.Num7; + case XK_KP_8: return KBI.Num8; + case XK_KP_9: return KBI.Num9; + case XK_multiply: return KBI.NumStar; + case XK_KP_Subtract: return KBI.NumMinus; + case XK_KP_Decimal: return KBI.NumPeriod; + case XK_KP_Divide: return KBI.NumSlash; + case XK_KP_Add: return KBI.NumPlus; + case XK_F1: return KBI.F1; + case XK_F2: return KBI.F2; + case XK_F3: return KBI.F3; + case XK_F4: return KBI.F4; + case XK_F5: return KBI.F5; + case XK_F6: return KBI.F6; + case XK_F7: return KBI.F7; + case XK_F8: return KBI.F8; + case XK_F9: return KBI.F9; + case XK_F10: return KBI.F10; + case XK_F11: return KBI.F11; + case XK_F12: return KBI.F12; + case XK_Num_Lock: return KBI.NumLock; + case XK_Scroll_Lock: return KBI.ScrollLock; + case XK_Shift_L: return KBI.LeftShift; + case XK_Shift_R: return KBI.RightShift; + case XK_Control_L: return KBI.LeftCtrl; + case XK_Control_R: return KBI.RightCtrl; + case XK_Alt_L: return KBI.LeftAlt; + case XK_Alt_R: return KBI.RightAlt; + case XK_semicolon: return KBI.Semicolon; + case XK_bracketleft: return KBI.LeftBrace; + case XK_bracketright: return KBI.RightBrace; + case XK_plus: return KBI.Plus; + case XK_comma: return KBI.Comma; + case XK_minus: return KBI.Minus; + case XK_backslash: return KBI.BackSlash; + case XK_slash: return KBI.ForwardSlash; + case XK_grave: return KBI.Tilde; + case XK_0: return KBI.Zero; + case XK_1: return KBI.One; + case XK_2: return KBI.Two; + case XK_3: return KBI.Three; + case XK_4: return KBI.Four; + case XK_5: return KBI.Five; + case XK_6: return KBI.Six; + case XK_7: return KBI.Seven; + case XK_8: return KBI.Eight; + case XK_9: return KBI.Nine; + case XK_a: + case XK_A: return KBI.A; + case XK_b: + case XK_B: return KBI.B; + case XK_c: + case XK_C: return KBI.C; + case XK_d: + case XK_D: return KBI.D; + case XK_e: + case XK_E: return KBI.E; + case XK_f: + case XK_F: return KBI.F; +case XK_g: + case XK_G: return KBI.G; + case XK_h: + case XK_H: return KBI.H; + case XK_i: + case XK_I: return KBI.I; + case XK_j: + case XK_J: return KBI.J; + case XK_k: + case XK_K: return KBI.K; + case XK_l: + case XK_L: return KBI.L; + case XK_m: + case XK_M: return KBI.M; + case XK_n: + case XK_N: return KBI.N; + case XK_o: + case XK_O: return KBI.O; + case XK_p: + case XK_P: return KBI.P; + case XK_q: + case XK_Q: return KBI.Q; + case XK_r: + case XK_R: return KBI.R; + case XK_s: + case XK_S: return KBI.S; + case XK_t: + case XK_T: return KBI.T; + case XK_u: + case XK_U: return KBI.U; + case XK_v: + case XK_V: return KBI.V; + case XK_w: + case XK_W: return KBI.W; + case XK_x: + case XK_X: return KBI.X; + case XK_y: + case XK_Y: return KBI.Y; + case XK_z: + case XK_Z: return KBI.Z; + default: return KBI.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"); +} + +} + +version(Windows) +{ + +}