#include "ehs/io/Window_XCB.h" #include "ehs/UTF.h" #include "ehs/Vec2.h" #include "ehs/io/hid/Keyboard.h" #include "ehs/io/hid/Mouse.h" #include "ehs/io/Console.h" #include #include #include #include namespace ehs { Window::~Window() { if (hdl) xcb_destroy_window(server, hdl); if (server) xcb_disconnect(server); } Window::Window() : server(nullptr), screen(nullptr), hdl(0), masks{}, extOpCode(0), events{XCB_ATOM_NONE, XCB_ATOM_NONE} { } Window::Window(Window&& win) noexcept : BaseWindow((BaseWindow&&)win), server(win.server), screen(win.screen), hdl(win.hdl), masks{win.masks[0], win.masks[1]}, events((Vector)win.events), extOpCode(win.extOpCode), clipboard((Serializer&&)win.clipboard) { win.server = nullptr; win.screen = nullptr; win.hdl = 0; win.masks[0] = 0; win.masks[1] = 0; win.extOpCode = 0; } Window::Window(const Window& win) : BaseWindow(win), server(nullptr), screen(nullptr), hdl(0), masks{}, extOpCode(0), events{XCB_ATOM_NONE, XCB_ATOM_NONE} { } Window& Window::operator=(Window&& win) noexcept { if (this == &win) return *this; Close(); BaseWindow::operator=((BaseWindow&&)win); server = win.server; screen = win.screen; hdl = win.hdl; masks[0] = win.masks[0]; masks[1] = win.masks[1]; extOpCode = win.extOpCode; events = (Vector&&)win.events; clipboard = (Serializer&&)win.clipboard; win.server = nullptr; win.screen = nullptr; win.hdl = 0; win.masks[0] = 0; win.masks[1] = 0; win.extOpCode = 0; return *this; } Window& Window::operator=(const Window& win) { if (this == &win) return *this; Close(); BaseWindow::operator=(win); server = nullptr; screen = nullptr; hdl = 0; masks[0] = 0; masks[1] = 0; extOpCode = 0; events = {}; clipboard = {}; return *this; } void Window::Create_32(const Str_32 &title, const Vec2_s32 &pos, const Vec2_u32 scale) { Create_8(UTF::To_8(title), pos, scale); } void Window::Create_16(const Str_16 &title, const Vec2_s32 &pos, const Vec2_u32 scale) { Create_8(UTF::To_8(title), pos, scale); } void Window::Create_8(const Str_8 &title, const Vec2_s32 &pos, const Vec2_u32 scale) { if (created) return; server = xcb_connect(nullptr, nullptr); if (xcb_connection_has_error(server)) { EHS_LOG_INT(LogType::ERR, 0, "Failed to connect to display server."); return; } screen = xcb_setup_roots_iterator(xcb_get_setup(server)).data; hdl = xcb_generate_id(server); UInt_32 values[2] = { screen->white_pixel, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_FOCUS_CHANGE }; xcb_create_window( server, screen->root_depth, hdl, screen->root, (SInt_16)pos.x, (SInt_16)pos.y, scale.x, scale.y, 1, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, values ); SetTitle_8(title); xcb_atom_t proto = RetrieveAtom(false, "WM_PROTOCOLS"); masks[0] = RetrieveAtom(true, "WM_DELETE_WINDOW"); masks[1] = RetrieveAtom(true, "_NET_WM_PING"); xcb_change_property(server, XCB_PROP_MODE_REPLACE, hdl, proto, XCB_ATOM_ATOM, 32, 2, masks); const xcb_query_extension_reply_t *extension = xcb_get_extension_data(server, &xcb_input_id); if (!extension) { xcb_disconnect(server); EHS_LOG_INT(LogType::WARN, 1, "Failed to query for XCB XInput extension."); return; } if (!extension->present) { xcb_disconnect(server); EHS_LOG_INT(LogType::WARN, 2, "XCB XInput extension is not available."); return; } struct XInputMask { xcb_input_event_mask_t iem; UInt_32 flags; }; XInputMask rootMask = {}; rootMask.iem.deviceid = XCB_INPUT_DEVICE_ALL; rootMask.iem.mask_len = 1; rootMask.flags = XCB_INPUT_XI_EVENT_MASK_RAW_MOTION | XCB_INPUT_XI_EVENT_MASK_RAW_KEY_PRESS | XCB_INPUT_XI_EVENT_MASK_RAW_KEY_RELEASE | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_PRESS | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_RELEASE; xcb_input_xi_select_events(server, screen->root, 1, &rootMask.iem); XInputMask winMask = {}; winMask.iem.deviceid = XCB_INPUT_DEVICE_ALL; winMask.iem.mask_len = 1; winMask.flags = XCB_INPUT_XI_EVENT_MASK_MOTION; xcb_input_xi_select_events(server, hdl, 1, &winMask.iem); QueryPrimaryDevices(); xcb_flush(server); created = true; OnCreated(); } void Window::Close() { if (hdl) { xcb_destroy_window(server, hdl); hdl = 0; } if (server) { xcb_disconnect(server); server = nullptr; } created = false; } void Window::Show() { if (!IsCreated()) return; xcb_map_window(server, hdl); xcb_flush(server); } void Window::Hide() { if (!IsCreated()) return; xcb_unmap_window(server, hdl); xcb_flush(server); } bool Window::Poll() { ih.Poll(); xcb_generic_event_t* event = nullptr; while ((event = RetrieveEvent())) { switch (event->response_type & ~0x80) { case XCB_CLIENT_MESSAGE: { xcb_client_message_event_t* cEvent = (xcb_client_message_event_t*)event; if (cEvent->data.data32[0] == masks[0]) // WM_DELETE_WINDOW { xcb_disconnect(server); server = nullptr; screen = nullptr; hdl = 0; free(event); return false; } else if (cEvent->data.data32[0] == masks[1]) // _NET_WM_PING { cEvent->window = screen->root; xcb_send_event(server, true, hdl, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char*) &cEvent); } break; } case XCB_FOCUS_IN: { focused = true; break; } case XCB_FOCUS_OUT: { ih.ResetAllStates(); focused = false; break; } case XCB_SELECTION_REQUEST: { const xcb_selection_request_event_t* req_event = (xcb_selection_request_event_t*)event; // Change the property of the requestor window with the data xcb_change_property(server, XCB_PROP_MODE_REPLACE, req_event->requestor, req_event->property, XCB_ATOM_STRING, 8, clipboard.Size(), clipboard); // Send a SelectionNotify event to the requestor xcb_selection_notify_event_t notify_event = {}; notify_event.response_type = XCB_SELECTION_NOTIFY; notify_event.requestor = req_event->requestor; notify_event.selection = req_event->selection; notify_event.target = req_event->target; notify_event.property = req_event->property; notify_event.time = req_event->time; xcb_send_event(server, false, req_event->requestor, XCB_EVENT_MASK_NO_EVENT, (char*)¬ify_event); xcb_flush(server); break; } case XCB_GE_GENERIC: { xcb_ge_event_t* ge = (xcb_ge_event_t*)event; switch (ge->event_type) { case XCB_INPUT_MOTION: { xcb_input_motion_event_t *motion_event = (xcb_input_motion_event_t *)ge; cursorPos = {motion_event->event_x >> 16, motion_event->event_y >> 16}; break; } case XCB_INPUT_RAW_MOTION: { const xcb_input_raw_motion_event_t* rm = (xcb_input_raw_motion_event_t*)ge; int axis_len = xcb_input_raw_button_press_axisvalues_length(rm); if (axis_len != 2) break; xcb_input_fp3232_t *pos = xcb_input_raw_button_press_axisvalues_raw(rm); Mouse* device = (Mouse*)ih.GetDevice(rm->deviceid); if (!device) { device = new Mouse(QueryDeviceName(rm->deviceid), rm->deviceid); ih.AddDevice(device); } device->SetDelta({pos[0].integral, pos[1].integral}); break; } case XCB_INPUT_RAW_BUTTON_PRESS: { const xcb_input_raw_button_press_event_t* bpEvent = (xcb_input_raw_button_press_event_t*)event; UInt_32 code = bpEvent->detail; Button button = Mouse::TranslateXCB(code); Mouse* device = (Mouse*)ih.GetDevice(bpEvent->deviceid); if (!device) { device = new Mouse(QueryDeviceName(bpEvent->deviceid), bpEvent->deviceid); ih.AddDevice(device); } device->ButtonDown(button); break; } case XCB_INPUT_RAW_BUTTON_RELEASE: { const xcb_input_raw_button_release_event_t* bpEvent = (xcb_input_raw_button_release_event_t*) event; UInt_32 code = bpEvent->detail; Button button = Mouse::TranslateXCB(code); Mouse* device = (Mouse*)ih.GetDevice(bpEvent->deviceid); if (!device) { device = new Mouse(QueryDeviceName(bpEvent->deviceid), bpEvent->deviceid); ih.AddDevice(device); } device->ButtonUp(button); break; } case XCB_INPUT_RAW_KEY_PRESS: { const xcb_input_raw_key_press_event_t* kpEvent = (xcb_input_raw_key_press_event_t*)event; UInt_32 code = kpEvent->detail - 8; Button button = Keyboard::TranslateScanCode(code); Keyboard* device = (Keyboard*)ih.GetDevice(kpEvent->deviceid); if (!device) { device = new Keyboard(QueryDeviceName(kpEvent->deviceid), kpEvent->deviceid); ih.AddDevice(device); } device->ButtonDown(button); break; } case XCB_INPUT_RAW_KEY_RELEASE: { const xcb_input_raw_key_release_event_t* kpEvent = (xcb_input_raw_key_release_event_t*)event; UInt_32 code = kpEvent->detail - 8; Button button = Keyboard::TranslateScanCode(code); Keyboard* device = (Keyboard*)ih.GetDevice(kpEvent->deviceid); if (!device) { device = new Keyboard(QueryDeviceName(kpEvent->deviceid), kpEvent->deviceid); ih.AddDevice(device); } device->ButtonUp(button); break; } default: { break; } } } default: { break; } } } free(event); BaseWindow::Poll(); return true; } void Window::ShowCursor(bool toggle) { xcb_xfixes_query_version(server, 4, 0); if (toggle) xcb_xfixes_show_cursor(server, hdl); else xcb_xfixes_hide_cursor(server, hdl); xcb_flush(server); cursorVisible = toggle; } void Window::ConstrainCursor(const bool constrain) { if (constrain) { xcb_grab_pointer_cookie_t cookie = xcb_grab_pointer( server, true, hdl, XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, hdl, XCB_NONE, XCB_CURRENT_TIME ); xcb_grab_pointer_reply_t* reply = xcb_grab_pointer_reply(server, cookie, nullptr); if (!reply || reply->status != XCB_GRAB_STATUS_SUCCESS) { free(reply); EHS_LOG_INT(LogType::ERR, 0, "Failed to constrain cursor."); return; } free(reply); } else { xcb_ungrab_pointer(server, XCB_CURRENT_TIME); xcb_flush(server); } cursorConstrained = constrain; } void Window::SetTitle_32(const Str_32& newTitle) { SetTitle_8(UTF::To_8(newTitle)); } Str_32 Window::GetTitle_32() const { return UTF::To_32(GetTitle_8()); } void Window::SetTitle_16(const Str_16& newTitle) { SetTitle_8(UTF::To_8(newTitle)); } Str_16 Window::GetTitle_16() const { return UTF::To_16(GetTitle_8()); } void Window::SetTitle_8(const Str_8 &newTitle) { xcb_change_property(server, XCB_PROP_MODE_REPLACE, hdl, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, newTitle.Size(), &newTitle[0]); } Str_8 Window::GetTitle_8() const { xcb_get_property_reply_t* reply = RetrieveProp(XCB_ATOM_WM_NAME, XCB_ATOM_STRING); Str_8 result((char*)xcb_get_property_value(reply), xcb_get_property_value_length(reply)); free(reply); return result; } void Window::SetPos(const Vec2_s32& newPos) { xcb_configure_window(server, hdl, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &((Byte*)&newPos)[offsetof(Vec2_s32, x)]); xcb_flush(server); } Vec2_s32 Window::GetPos() const { Vec2_s32 result; const xcb_get_geometry_cookie_t geom_cookie = xcb_get_geometry(server, hdl); xcb_get_geometry_reply_t *geom = xcb_get_geometry_reply(server, geom_cookie, nullptr); if (!geom) { EHS_LOG_INT(LogType::ERR, 0, "Failed to retrieve window position."); return result; } result = {geom->x, geom->y}; free(geom); return {}; } void Window::SetScale(const Vec2_u32& newScale) { xcb_configure_window(server, hdl, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, &((Byte*)&newScale)[offsetof(Vec2_u32, x)]); xcb_flush(server); } Vec2_u32 Window::GetScale() const { Vec2_s32 result; const xcb_get_geometry_cookie_t geom_cookie = xcb_get_geometry(server, hdl); xcb_get_geometry_reply_t *geom = xcb_get_geometry_reply(server, geom_cookie, nullptr); if (!geom) { EHS_LOG_INT(LogType::ERR, 0, "Failed to retrieve window scale."); return result; } result = {geom->width, geom->height}; free(geom); return result; } Serializer Window::GetClipboard() { Serializer result; const xcb_atom_t clipboard_atom = RetrieveAtom(false, "CLIPBOARD"); const xcb_atom_t utf8_string_atom = RetrieveAtom(false, "STRING"); const xcb_atom_t property_atom = RetrieveAtom(true, "MATHISART"); if (clipboard_atom == XCB_ATOM_NONE || utf8_string_atom == XCB_ATOM_NONE || property_atom == XCB_ATOM_NONE) { EHS_LOG_INT(LogType::ERR, 1, "Failed to retrieve atoms."); return result; } xcb_convert_selection(server, hdl, clipboard_atom, utf8_string_atom, property_atom, XCB_CURRENT_TIME); xcb_flush(server); //events.Insert(0, xcb_wait_for_event(server)); const xcb_get_property_cookie_t prop_cookie = xcb_get_property(server, 0, hdl, property_atom, utf8_string_atom, 0, UINT32_MAX / 4); if (xcb_get_property_reply_t* prop_reply = xcb_get_property_reply(server, prop_cookie, nullptr); prop_reply) { result = Serializer(Endianness::LE, (Byte*)xcb_get_property_value(prop_reply), xcb_get_property_value_length(prop_reply), 0); free(prop_reply); } return result; } void Window::SetClipboard(Serializer data) { if (clipboard == data) return; clipboard = (Serializer&&)data; const xcb_atom_t clipboard_atom = RetrieveAtom(false, "CLIPBOARD"); if (clipboard_atom == XCB_ATOM_NONE) { EHS_LOG_INT(LogType::ERR, 0, "Failed to retrieve atom."); return; } xcb_set_selection_owner(server, hdl, clipboard_atom, XCB_CURRENT_TIME); xcb_flush(server); } void Window::SetCursorImg(const CursorImg img) { xcb_cursor_t text_cursor = XCB_NONE; if (img == CursorImg::DEFAULT) { xcb_change_window_attributes(server, hdl, XCB_CW_CURSOR, &text_cursor); xcb_flush(server); } if (img == CursorImg::I_BEAM) { xcb_cursor_context_t *cursor_context; xcb_cursor_context_new(server, xcb_setup_roots_iterator(xcb_get_setup(server)).data, &cursor_context); text_cursor = xcb_cursor_load_cursor(cursor_context, "xterm"); xcb_change_window_attributes(server, hdl, XCB_CW_CURSOR, &text_cursor); xcb_cursor_context_free(cursor_context); xcb_flush(server); } } xcb_connection_t *Window::GetServer() { return server; } xcb_generic_event_t* Window::RetrieveEvent() { if (events.Size()) { xcb_generic_event_t* event = events[0]; events.Remove(0); return event; } else return xcb_poll_for_event(server); } xcb_atom_t Window::RetrieveAtom(const bool create, const Str_8& name) const { xcb_intern_atom_reply_t* reply = xcb_intern_atom_reply(server, xcb_intern_atom(server, !create, name.Size(), name), nullptr); if (!reply) return XCB_ATOM_NONE; const xcb_atom_t atom = reply->atom; free(reply); return atom; } xcb_get_property_reply_t *Window::RetrieveProp(const xcb_atom_t prop, const xcb_atom_t type) const { return xcb_get_property_reply(server, xcb_get_property(server, false, hdl, prop, type, 0, 0), nullptr); } void Window::QueryPrimaryDevices() { xcb_input_xi_query_device_cookie_t device_cookie = xcb_input_xi_query_device(server, XCB_INPUT_DEVICE_ALL); xcb_input_xi_query_device_reply_t *device_reply = xcb_input_xi_query_device_reply(server, device_cookie, nullptr); if (!device_reply) { EHS_LOG_INT(LogType::ERR, 0, "Failed to query primary devices."); return; } xcb_input_xi_device_info_iterator_t device_iter = xcb_input_xi_query_device_infos_iterator(device_reply); for (; device_iter.rem; xcb_input_xi_device_info_next(&device_iter)) { xcb_input_xi_device_info_t *device_info = device_iter.data; Str_8 name(xcb_input_xi_device_info_name(device_info), xcb_input_xi_device_info_name_length(device_info)); if (device_info->type == XCB_INPUT_DEVICE_TYPE_MASTER_POINTER) ih.AddDevice(new Mouse(name, device_info->deviceid)); if (device_info->type == XCB_INPUT_DEVICE_TYPE_MASTER_KEYBOARD) ih.AddDevice(new Keyboard(name, device_info->deviceid)); } free(device_reply); } Str_8 Window::QueryDeviceName(const UInt_16 id) { Str_8 result; xcb_input_xi_query_device_cookie_t device_cookie = xcb_input_xi_query_device(server, id); xcb_input_xi_query_device_reply_t *device_reply = xcb_input_xi_query_device_reply(server, device_cookie, nullptr); if (!device_reply) { EHS_LOG_INT(LogType::ERR, 0, "Failed to query device name from the id, \"" + Str_8::FromNum(id) + "\"."); return result; } xcb_input_xi_device_info_iterator_t device_iter = xcb_input_xi_query_device_infos_iterator(device_reply); for (; device_iter.rem; xcb_input_xi_device_info_next(&device_iter)) { xcb_input_xi_device_info_t *device_info = device_iter.data; result = Str_8(xcb_input_xi_device_info_name(device_info), xcb_input_xi_device_info_name_length(device_info)); } free(device_reply); return result; } }