#include #include #include #include #include #include #include "device.h" namespace saw { class xcb_device final : public device { public: ::Display *display; int screen; xcb_connection_t *xcb_connection; xcb_screen_t *xcb_screen; own async_notifier; conveyor_sink async_conveyor; std::map windows; std::vector pending_events; public: xcb_device(::Display *display, int screen, xcb_connection_t *xcb_connection, xcb_screen_t *xcb_screen, own &&an); ~xcb_device(); void window_destroyed(xcb_window_t window_id); void handle_events(); own create_xcb_window(const video_mode &mode, std::string_view title_view, int visual_id); own create_window(const video_mode &video_mode, std::string_view title_view) override; void flush() override; }; own create_xcb_device(io_provider &provider); class xcb_window final : public window { public: xcb_device &device_; xcb_window_t xcb_window_; xcb_colormap_t xcb_colormap_; video_mode video_mode_; std::string window_title_; own> event_feeder = nullptr; public: xcb_window(xcb_device &dev, xcb_window_t xcb_win, xcb_colormap_t xcb_colormap_, const video_mode &video_mode_, std::string_view title_view_); ~xcb_window(); void show() override; void hide() override; const video_mode &get_video_mode() const override; const std::string_view title() const override; void resize(size_t width, size_t height) override; conveyor on_event() override; void resize_event(size_t x, size_t y, size_t width, size_t height); void mouse_event(int16_t x, int16_t y, uint16_t state, bool pressed); void mouse_move_event(int16_t x, int16_t y); void keyboard_event(int16_t x, int16_t y, uint32_t keycode, bool pressed, bool repeat); }; xcb_device::xcb_device(::Display *display, int screen, xcb_connection_t *xcb_connection, xcb_screen_t *xcb_screen, own &&an) : display{display}, screen{screen}, xcb_connection{xcb_connection}, xcb_screen{xcb_screen}, async_notifier{std::move(an)}, async_conveyor{async_notifier->read_ready() .then([this]() { handle_events(); }) .sink()} {} xcb_device::~xcb_device() { if (display) { xcb_flush(xcb_connection); ::XCloseDisplay(display); } } void xcb_device::window_destroyed(xcb_window_t window_id) { windows.erase(window_id); } void xcb_device::handle_events() { while (xcb_generic_event_t *event = xcb_poll_for_event(xcb_connection)) { pending_events.push_back(event); } for (size_t i = 0; i < pending_events.size(); ++i) { xcb_generic_event_t *event = pending_events.at(i); switch (event->response_type & ~0x80) { case XCB_MOTION_NOTIFY: { xcb_motion_notify_event_t *motion = reinterpret_cast(event); auto find = windows.find(motion->event); if (find != windows.end()) { assert(find->second); find->second->mouse_move_event(motion->event_x, motion->event_y); } } break; case XCB_EXPOSE: { xcb_expose_event_t *expose = reinterpret_cast(event); auto find = windows.find(expose->window); if (find != windows.end()) { assert(find->second); find->second->resizeevent(static_cast(expose->x), static_cast(expose->y), static_cast(expose->width), static_cast(expose->height)); } } break; case XCB_BUTTON_RELEASE: { xcb_button_release_event_t *button = reinterpret_cast(event); auto find = windows.find(button->event); if (find != windows.end()) { assert(find->second); find->second->mouseevent(button->event_x, button->event_y, button->detail, false); } } break; case XCB_BUTTON_PRESS: { xcb_button_press_event_t *button = reinterpret_cast(event); auto find = windows.find(button->event); if (find != windows.end()) { assert(find->second); find->second->mouseevent(button->event_x, button->event_y, button->detail, true); } } break; case XCB_KEY_RELEASE: { xcb_key_release_event_t *key = reinterpret_cast(event); bool repeat = false; /* * Peek into future events */ for (size_t j = i + 1; j < pending_events.size(); ++j) { xcb_generic_event_t *f_ev = pending_events.at(j); if ((f_ev->response_type & ~0x80) == XCB_KEY_PRESS) { xcb_key_press_event_t *f_key = reinterpret_cast(f_ev); if (key->detail == f_key->detail && key->event == f_key->event) { auto iterator = pending_events.begin() + j; assert(iterator != pending_events.end()); free(*iterator); pending_events.erase(iterator); repeat = true; break; } } } auto find = windows.find(key->event); if (find != windows.end()) { assert(find->second); find->second->keyboardevent(key->event_x, key->event_y, key->detail, repeat, repeat); } } break; case XCB_KEY_PRESS: { xcb_key_press_event_t *key = reinterpret_cast(event); auto find = windows.find(key->event); if (find != windows.end()) { assert(find->second); find->second->keyboardevent(key->event_x, key->event_y, key->detail, true, false); } } break; default: break; } } for (xcb_generic_event_t *event : pending_events) { free(event); } pending_events.clear(); } own xcb_device::create_xcb_window(const video_mode &video_mode, std::string_view title_view, int visual_id) { assert(xcb_screen); assert(xcb_connection); xcb_colormap_t xcb_colormap = xcb_generate_id(xcb_connection); xcb_window_t xcb_window = xcb_generate_id(xcb_connection); xcb_create_colormap(xcb_connection, XCB_COLORMAP_ALLOC_NONE, xcb_colormap, xcb_screen->root, visual_id); uint32_t eventmask = 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_BUTTON_MOTION; uint32_t valuelist[] = {eventmask, xcb_colormap, 0}; uint32_t valuemask = XCB_CW_EVENT_MASK | XCB_CW_COLORMAP; xcb_create_window(xcb_connection, XCB_COPY_FROM_PARENT, xcb_window, xcb_screen->root, 0, 0, video_mode.width, video_mode.height, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visual_id, valuemask, valuelist); xcb_change_property(xcb_connection, XCB_PROP_MODE_REPLACE, xcb_window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, title_view.size(), title_view.data()); xcb_flush(xcb_connection); auto window = heap(*this, xcb_window, xcb_colormap, video_mode, title_view); windows[xcb_window] = window.get(); return window; } own xcb_device::create_window(const video_mode &video_mode, std::string_view title_view) { assert(xcb_screen); return create_xcb_window(video_mode, title_view, xcb_screen->root_visual); } void xcb_device::flush() { assert(xcb_connection); xcb_flush(xcb_connection); } own create_xcb_device(io_provider &provider) { ::Display *display = ::XOpenDisplay(nullptr); if (!display) { /// @todo log errors return nullptr; } int screen = ::XDefaultScreen(display); xcb_connection_t *xcb_connection = ::XGetXCBConnection(display); if (!xcb_connection) { /// @todo log errors ::XCloseDisplay(display); return nullptr; } int fd = xcb_get_file_descriptor(xcb_connection); own fd_wrapped = provider.wrap_input_fd(fd); if (!fd_wrapped) { ::XCloseDisplay(display); return nullptr; } ::XSetEventQueueOwner(display, XCBOwnsEventQueue); xcb_screen_iterator_t screen_iter = xcb_setup_roots_iterator(xcb_get_setup(xcb_connection)); for (int screen_i = screen; screen_iter.rem && screen_i > 0; --screen_i, xcb_screen_next(&screen_iter)) ; xcb_screen_t *xcb_screen = screen_iter.data; return heap(display, screen, xcb_connection, xcb_screen, std::move(fd_wrapped)); } own createdevice(io_provider &provider) { return create_xcb_device(provider); } xcb_window::xcb_window(xcb_device &dev, xcb_window_t xcb_win, xcb_colormap_t xcb_colmap, const video_mode &vid_mode, std::string_view title_view_) : device_{dev}, xcb_window_{xcb_win}, xcb_colormap_{xcb_colmap}, video_mode_{vid_mode}, window_title_{title_view_} {} xcb_window::~xcb_window() { device_.window_destroyed(xcb_window_); xcb_destroy_window(device_.xcb_connection, xcb_window_); device_.flush(); } void xcb_window::show() { assert(device_.xcb_connection); xcb_map_window(device_.xcb_connection, xcb_window_); } void xcb_window::hide() { assert(device_.xcb_connection); xcb_unmap_window(device_.xcb_connection, xcb_window_); } const video_mode &xcb_window::videoMode() const { return video_mode_; } const std::string_view xcb_window::title() const { return window_title_; } void xcb_window::resize(size_t width, size_t height) { const uint32_t values[2] = {static_cast(width), static_cast(height)}; xcb_configure_window(device_.xcb_connection, xcb_window_, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values); video_mode_.width = width; video_mode_.height = height; } conveyor xcb_window::onevent() { auto caf = new_conveyor_and_feeder(); event_feeder = std::move(caf.feeder); return std::move(caf.conveyor); } void xcb_window::resizeevent(size_t x, size_t y, size_t width, size_t height) { (void)x; (void)y; /// @todo maybe include x and y? video_mode_.width = width; video_mode_.height = height; if (event_feeder) { event_feeder->feed( window::variant_event{window::event::resize{width, height}}); } } void xcb_window::mouseevent(int16_t x, int16_t y, uint16_t state, bool pressed) { if (x < 0 || y < 0) { return; } uint32_t ux = static_cast(x); uint32_t uy = static_cast(y); if (ux >= video_mode_.width || uy >= video_mode_.height) { return; } if (event_feeder) { event_feeder->feed(window::variant_event{ window::event::mouse{state, pressed, ux, uy}}); } } void xcb_window::mouse_move_event(int16_t x, int16_t y) { if (x < 0 || y < 0) { return; } uint32_t ux = static_cast(x); uint32_t uy = static_cast(y); if (ux >= video_mode_.width || uy >= video_mode_.height) { return; } if (event_feeder) { event_feeder->feed( window::variant_event{window::event::mouse_move{ux, uy}}); } } void xcb_window::keyboardevent(int16_t x, int16_t y, uint32_t keycode, bool pressed, bool repeat) { if (x < 0 || y < 0) { return; } uint32_t ux = static_cast(x); uint32_t uy = static_cast(y); if (ux >= video_mode_.width || uy >= video_mode_.height) { return; } if (event_feeder) { event_feeder->feed(window::variant_event{ window::event::keyboard{keycode, keycode, pressed, repeat}}); } } } // namespace saw