diff options
Diffstat (limited to 'src/platform/gui')
-rw-r--r-- | src/platform/gui/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/platform/gui/Window.cpp | 6 | ||||
-rw-r--r-- | src/platform/gui/xcb/CMakeLists.txt | 8 | ||||
-rw-r--r-- | src/platform/gui/xcb/Cursor.cpp | 54 | ||||
-rw-r--r-- | src/platform/gui/xcb/Keyboard.cpp | 12 | ||||
-rw-r--r-- | src/platform/gui/xcb/UiApplication.cpp | 38 | ||||
-rw-r--r-- | src/platform/gui/xcb/Window.cpp | 227 |
7 files changed, 333 insertions, 13 deletions
diff --git a/src/platform/gui/CMakeLists.txt b/src/platform/gui/CMakeLists.txt index 808caf9c..b541428e 100644 --- a/src/platform/gui/CMakeLists.txt +++ b/src/platform/gui/CMakeLists.txt @@ -2,6 +2,7 @@ add_library(CruPlatformGui Keyboard.cpp Menu.cpp UiApplication.cpp + Window.cpp ) target_link_libraries(CruPlatformGui PUBLIC CruPlatformGraphics) target_compile_definitions(CruPlatformGui PRIVATE CRU_PLATFORM_GUI_EXPORT_API) diff --git a/src/platform/gui/Window.cpp b/src/platform/gui/Window.cpp new file mode 100644 index 00000000..fdcfbae4 --- /dev/null +++ b/src/platform/gui/Window.cpp @@ -0,0 +1,6 @@ +#include "cru/platform/gui/Window.h" +#include "cru/base/Base.h" + +namespace cru::platform::gui { +bool INativeWindow::IsCreated() { NotImplemented(); } +} // namespace cru::platform::gui diff --git a/src/platform/gui/xcb/CMakeLists.txt b/src/platform/gui/xcb/CMakeLists.txt index aa6116e4..fa5cc0bf 100644 --- a/src/platform/gui/xcb/CMakeLists.txt +++ b/src/platform/gui/xcb/CMakeLists.txt @@ -1,11 +1,13 @@ find_library(LIBRARY_CAIRO cairo REQUIRED) find_library(LIBRARY_XCB xcb REQUIRED) -add_library(CruPlatformGuiX11 +find_library(LIBRARY_XCB_CURSOR xcb-cursor REQUIRED) +add_library(CruPlatformGuiXcb + Cursor.cpp Keyboard.cpp UiApplication.cpp Window.cpp ) -target_link_libraries(CruPlatformGuiX11 PUBLIC +target_link_libraries(CruPlatformGuiXcb PUBLIC CruPlatformGui CruPlatformGraphicsCairo - ${LIBRARY_XCB} ${LIBRARY_CAIRO} + ${LIBRARY_XCB} ${LIBRARY_XCB_CURSOR} ${LIBRARY_CAIRO} ) diff --git a/src/platform/gui/xcb/Cursor.cpp b/src/platform/gui/xcb/Cursor.cpp new file mode 100644 index 00000000..5582c6a6 --- /dev/null +++ b/src/platform/gui/xcb/Cursor.cpp @@ -0,0 +1,54 @@ +#include "cru/platform/gui/xcb/Cursor.h" +#include "cru/base/Exception.h" +#include "cru/platform/gui/Cursor.h" +#include "cru/platform/gui/xcb/UiApplication.h" + +#include <xcb/xcb.h> +#include <xcb/xcb_cursor.h> +#include <memory> + +namespace cru::platform::gui::xcb { +XcbCursor::XcbCursor(XcbUiApplication* application, xcb_cursor_t cursor, + bool auto_free) + : application_(application), cursor_(cursor), auto_free_(auto_free) {} + +XcbCursor::~XcbCursor() { + if (auto_free_) { + xcb_free_cursor(application_->GetXcbConnection(), cursor_); + } +} + +xcb_cursor_t XcbCursor::GetXcbCursor() { return cursor_; } + +XcbCursorManager::XcbCursorManager(XcbUiApplication* application) + : application_(application) { + auto code = xcb_cursor_context_new(application->GetXcbConnection(), + application->GetFirstXcbScreen(), + &xcb_cursor_context_); + if (code != 0) { + throw PlatformException("Failed to call xcb_cursor_context_new."); + } + + cursors_[SystemCursorType::Arrow] = + std::make_shared<XcbCursor>(application_, XCB_CURSOR_NONE, false); + cursors_[SystemCursorType::Hand] = LoadXCursor("pointer"); + cursors_[SystemCursorType::IBeam] = LoadXCursor("ibeam"); +} + +XcbCursorManager::~XcbCursorManager() { + xcb_cursor_context_free(xcb_cursor_context_); +} + +std::shared_ptr<ICursor> XcbCursorManager::GetSystemCursor( + SystemCursorType type) { + return cursors_[type]; +} + +std::shared_ptr<XcbCursor> XcbCursorManager::LoadXCursor( + std::string_view name) { + return std::make_shared<XcbCursor>( + application_, xcb_cursor_load_cursor(xcb_cursor_context_, name.data()), + true); +} + +} // namespace cru::platform::gui::xcb diff --git a/src/platform/gui/xcb/Keyboard.cpp b/src/platform/gui/xcb/Keyboard.cpp index d7559062..e5f6da8e 100644 --- a/src/platform/gui/xcb/Keyboard.cpp +++ b/src/platform/gui/xcb/Keyboard.cpp @@ -1,4 +1,5 @@ #include "cru/platform/gui/xcb/Keyboard.h" +#include "cru/base/Guard.h" #include "cru/platform/gui/Keyboard.h" #include "cru/platform/gui/xcb/UiApplication.h" @@ -82,8 +83,8 @@ KeyCode XorgKeycodeToCruKeyCode(XcbUiApplication *application, // Get keyboard mapping auto mapping_cookie = xcb_get_keyboard_mapping(connection, keycode, 1); - auto *mapping_reply = - xcb_get_keyboard_mapping_reply(connection, mapping_cookie, NULL); + auto mapping_reply = FreeLater( + xcb_get_keyboard_mapping_reply(connection, mapping_cookie, NULL)); if (!mapping_reply) { throw XcbException("Cannot get keyboard mapping."); @@ -107,7 +108,8 @@ using KeymapBitset = KeymapBitset GetXorgKeymap(xcb_connection_t *connection) { auto keymap_cookie = xcb_query_keymap(connection); - auto keymap_reply = xcb_query_keymap_reply(connection, keymap_cookie, NULL); + auto keymap_reply = + FreeLater(xcb_query_keymap_reply(connection, keymap_cookie, NULL)); if (!keymap_reply) { throw XcbException("Cannot get keymap."); @@ -136,8 +138,8 @@ std::unordered_map<KeyCode, bool> GetKeyboardState( // Get keyboard mapping auto mapping_cookie = xcb_get_keyboard_mapping(connection, min_keycode, max_keycode - min_keycode + 1); - auto *mapping_reply = - xcb_get_keyboard_mapping_reply(connection, mapping_cookie, NULL); + auto mapping_reply = FreeLater( + xcb_get_keyboard_mapping_reply(connection, mapping_cookie, NULL)); if (!mapping_reply) { throw XcbException("Cannot get keyboard mapping."); diff --git a/src/platform/gui/xcb/UiApplication.cpp b/src/platform/gui/xcb/UiApplication.cpp index 0a6ab07f..1e5613b4 100644 --- a/src/platform/gui/xcb/UiApplication.cpp +++ b/src/platform/gui/xcb/UiApplication.cpp @@ -1,10 +1,15 @@ #include "cru/platform/gui/xcb/UiApplication.h" +#include "cru/base/Base.h" +#include "cru/base/Guard.h" #include "cru/platform/graphics/cairo/CairoGraphicsFactory.h" +#include "cru/platform/gui/Window.h" +#include "cru/platform/gui/xcb/Cursor.h" #include "cru/platform/gui/xcb/Window.h" #include <poll.h> #include <xcb/xcb.h> +#include <algorithm> namespace cru::platform::gui::xcb { XcbUiApplication::XcbUiApplication( @@ -20,6 +25,7 @@ XcbUiApplication::XcbUiApplication( int screen_num; xcb_connection_t *connection = xcb_connect(NULL, &screen_num); + xcb_connection_ = connection; this->CheckXcbConnectionError(); event_loop_.SetPoll(xcb_get_file_descriptor(connection), POLLIN, @@ -28,15 +34,23 @@ XcbUiApplication::XcbUiApplication( const xcb_setup_t *setup = xcb_get_setup(connection); xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup); this->screen_ = iter.data; + + cursor_manager_ = new XcbCursorManager(this); } XcbUiApplication::~XcbUiApplication() { + delete cursor_manager_; + xcb_disconnect(this->xcb_connection_); if (release_cairo_factory_) { delete cairo_factory_; } } +graphics::cairo::CairoGraphicsFactory *XcbUiApplication::GetCairoFactory() { + return cairo_factory_; +} + void XcbUiApplication::CheckXcbConnectionError() { if (xcb_connection_has_error(this->xcb_connection_)) { throw XcbException("xcb_connection_has_error returned non-zero."); @@ -47,6 +61,8 @@ xcb_connection_t *XcbUiApplication::GetXcbConnection() { return xcb_connection_; } +void XcbUiApplication::XcbFlush() { xcb_flush(xcb_connection_); } + xcb_screen_t *XcbUiApplication::GetFirstXcbScreen() { return screen_; } xcb_atom_t XcbUiApplication::GetOrCreateXcbAtom(std::string name) { @@ -57,7 +73,8 @@ xcb_atom_t XcbUiApplication::GetOrCreateXcbAtom(std::string name) { auto cookie = xcb_intern_atom(xcb_connection_, false, name.size(), name.data()); - auto reply = xcb_intern_atom_reply(xcb_connection_, cookie, nullptr); + auto reply = + FreeLater(xcb_intern_atom_reply(xcb_connection_, cookie, nullptr)); auto atom = reply->atom; xcb_atom_.emplace(std::move(name), atom); return atom; @@ -122,6 +139,25 @@ void XcbUiApplication::HandleXEvents() { } } +std::vector<INativeWindow *> XcbUiApplication::GetAllWindow() { + std::vector<INativeWindow *> windows(windows_.size()); + std::ranges::copy(windows_, windows.begin()); + return windows; +} + +INativeWindow *XcbUiApplication::CreateWindow() { return new XcbWindow(this); } + +cru::platform::graphics::IGraphicsFactory * +XcbUiApplication::GetGraphicsFactory() { + return cairo_factory_; +} + +ICursorManager *XcbUiApplication::GetCursorManager() { return cursor_manager_; } + +IClipboard *XcbUiApplication::GetClipboard() { NotImplemented(); } + +IMenu *XcbUiApplication::GetApplicationMenu() { return nullptr; } + void XcbUiApplication::RegisterWindow(XcbWindow *window) { windows_.push_back(window); } diff --git a/src/platform/gui/xcb/Window.cpp b/src/platform/gui/xcb/Window.cpp index 188f62b8..e6e92fbc 100644 --- a/src/platform/gui/xcb/Window.cpp +++ b/src/platform/gui/xcb/Window.cpp @@ -1,17 +1,23 @@ #include "cru/platform/gui/xcb/Window.h" #include "cru/base/Base.h" +#include "cru/base/Guard.h" #include "cru/platform/Check.h" +#include "cru/platform/GraphicsBase.h" +#include "cru/platform/graphics/NullPainter.h" #include "cru/platform/graphics/Painter.h" #include "cru/platform/graphics/cairo/CairoPainter.h" #include "cru/platform/gui/Base.h" #include "cru/platform/gui/Keyboard.h" #include "cru/platform/gui/Window.h" +#include "cru/platform/gui/xcb/Cursor.h" #include "cru/platform/gui/xcb/Keyboard.h" #include "cru/platform/gui/xcb/UiApplication.h" #include <cairo-xcb.h> #include <cairo.h> #include <xcb/xcb.h> +#include <xcb/xproto.h> +#include <algorithm> #include <cassert> #include <cinttypes> #include <cstdint> @@ -66,9 +72,12 @@ XcbWindow::XcbWindow(XcbUiApplication *application) XcbWindow::~XcbWindow() { application_->UnregisterWindow(this); } +bool XcbWindow::IsCreated() { return xcb_window_.has_value(); } + void XcbWindow::Close() { if (xcb_window_) { xcb_destroy_window(application_->GetXcbConnection(), *xcb_window_); + application_->XcbFlush(); } } @@ -79,6 +88,7 @@ void XcbWindow::SetParent(INativeWindow *parent) { if (xcb_window_) { DoSetParent(*xcb_window_); } + application_->XcbFlush(); } WindowStyleFlag XcbWindow::GetStyleFlag() { return style_; } @@ -88,6 +98,7 @@ void XcbWindow::SetStyleFlag(WindowStyleFlag flag) { if (xcb_window_) { DoSetStyleFlags(*xcb_window_); } + application_->XcbFlush(); } String XcbWindow::GetTitle() { return String::FromUtf8(title_); } @@ -97,6 +108,7 @@ void XcbWindow::SetTitle(String title) { if (xcb_window_) { DoSetTitle(*xcb_window_); } + application_->XcbFlush(); } namespace { @@ -140,7 +152,6 @@ void XcbWindow::SetVisibility(WindowVisibilityType visibility) { auto old_value = static_cast<std::uint32_t *>( XcbGetProperty(*xcb_window_, atom, atom, 0, 2)); if (old_value) value[1] = old_value[1]; - UnreachableCode(); xcb_change_property(application_->GetXcbConnection(), XCB_PROP_MODE_REPLACE, window, atom, atom, 32, sizeof(value) / sizeof(*value), @@ -175,10 +186,125 @@ void XcbWindow::SetVisibility(WindowVisibilityType visibility) { default: UnreachableCode(); } + + application_->XcbFlush(); +} + +Size XcbWindow::GetClientSize() { return GetClientRect().GetSize(); } + +void XcbWindow::SetClientSize(const Size &size) { + auto rect = GetClientRect(); + SetClientRect(Rect(rect.GetLeftTop(), size)); +} + +Rect XcbWindow::GetClientRect() { + if (!xcb_window_) { + return Rect{}; + } + + auto window = *xcb_window_; + + auto cookie = xcb_get_geometry(application_->GetXcbConnection(), window); + auto reply = FreeLater(xcb_get_geometry_reply( + application_->GetXcbConnection(), cookie, nullptr)); + auto position = GetXcbWindowPosition(window); + + return Rect(position.x, position.y, reply->width, reply->height); +} + +void XcbWindow::SetClientRect(const Rect &rect) { + if (!xcb_window_) return; + DoSetClientRect(*xcb_window_, rect); + application_->XcbFlush(); +} + +Rect XcbWindow::GetWindowRect() { + if (!xcb_window_) return {}; + + auto client_rect = GetClientRect(); + auto frame_properties = Get_NET_FRAME_EXTENTS(*xcb_window_); + + if (frame_properties.has_value()) { + return client_rect.Expand(*frame_properties); + } + + return client_rect; +} + +void XcbWindow::SetWindowRect(const Rect &rect) { + if (!xcb_window_) return; + + auto real_rect = rect; + auto frame_properties = Get_NET_FRAME_EXTENTS(*xcb_window_); + + if (frame_properties.has_value()) { + real_rect = real_rect.Shrink(*frame_properties, true); + } + + SetClientRect(real_rect); +} + +bool XcbWindow::RequestFocus() { + if (!xcb_window_) return false; + xcb_set_input_focus(application_->GetXcbConnection(), XCB_NONE, *xcb_window_, + XCB_CURRENT_TIME); + application_->XcbFlush(); + return true; +} + +Point XcbWindow::GetMousePosition() { + auto window = xcb_window_.value_or(application_->GetFirstXcbScreen()->root); + auto cookie = xcb_query_pointer(application_->GetXcbConnection(), window); + auto reply = FreeLater(xcb_query_pointer_reply( + application_->GetXcbConnection(), cookie, nullptr)); + return Point(reply->win_x, reply->win_y); +} + +bool XcbWindow::CaptureMouse() { + if (!xcb_window_) return false; + // KDE handles grabbing automatically. + // + // xcb_grab_pointer(application_->GetXcbConnection(), TRUE, *xcb_window_, 0, + // XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, XCB_WINDOW_NONE, + // XCB_CURSOR_NONE, XCB_CURRENT_TIME); + // application_->XcbFlush(); + return true; +} + +bool XcbWindow::ReleaseMouse() { + if (!xcb_window_) return false; + // KDE handles grabbing automatically. + // + // xcb_ungrab_pointer(application_->xcb_connection_, XCB_CURRENT_TIME); + // application_->XcbFlush(); + return true; +} + +void XcbWindow::SetCursor(std::shared_ptr<ICursor> cursor) { + if (!xcb_window_) return; + auto xcb_cursor = CheckPlatform<XcbCursor>(cursor, GetPlatformId()); + cursor_ = xcb_cursor; + DoSetCursor(*xcb_window_, xcb_cursor.get()); +} + +void XcbWindow::SetToForeground() { + SetVisibility(WindowVisibilityType::Show); + assert(xcb_window_.has_value()); + const static uint32_t values[] = {XCB_STACK_MODE_ABOVE}; + xcb_configure_window(application_->GetXcbConnection(), *xcb_window_, + XCB_CONFIG_WINDOW_STACK_MODE, values); + application_->XcbFlush(); +} + +void XcbWindow::RequestRepaint() { + // TODO: true throttle + paint_event_.Raise(nullptr); } std::unique_ptr<graphics::IPainter> XcbWindow::BeginPaint() { - assert(cairo_surface_); + if (!xcb_window_.has_value()) { + return std::make_unique<graphics::NullPainter>(); + } auto factory = application_->GetCairoFactory(); cairo_t *cairo = cairo_create(cairo_surface_); @@ -225,6 +351,8 @@ IEvent<NativeKeyEventArgs> *XcbWindow::KeyDownEvent() { IEvent<NativeKeyEventArgs> *XcbWindow::KeyUpEvent() { return &key_up_event_; } +IInputMethodContext *XcbWindow::GetInputMethodContext() { return nullptr; } + std::optional<xcb_window_t> XcbWindow::GetXcbWindow() { return xcb_window_; } xcb_window_t XcbWindow::DoCreateWindow() { @@ -253,8 +381,16 @@ xcb_window_t XcbWindow::DoCreateWindow() { xcb_window_ = window; + std::vector<xcb_atom_t> wm_protocols{ + application_->GetXcbAtomWM_DELETE_WINDOW()}; + xcb_change_property(application_->GetXcbConnection(), XCB_PROP_MODE_REPLACE, + window, application_->GetXcbAtomWM_PROTOCOLS(), + XCB_ATOM_ATOM, 32, wm_protocols.size(), + wm_protocols.data()); + DoSetStyleFlags(window); DoSetParent(window); + DoSetCursor(window, cursor_.get()); xcb_visualtype_t *visual_type; @@ -291,6 +427,14 @@ void XcbWindow::HandleEvent(xcb_generic_event_t *event) { cairo_surface_destroy(cairo_surface_); cairo_surface_ = nullptr; xcb_window_ = std::nullopt; + + if (application_->IsQuitOnAllWindowClosed() && + std::ranges::none_of( + application_->GetAllWindow(), + [](INativeWindow *window) { return window->IsCreated(); })) { + application_->RequestQuit(0); + } + break; } case XCB_CONFIGURE_NOTIFY: { @@ -388,6 +532,16 @@ void XcbWindow::HandleEvent(xcb_generic_event_t *event) { key_up_event_.Raise(std::move(args)); break; } + case XCB_CLIENT_MESSAGE: { + xcb_client_message_event_t *cm = (xcb_client_message_event_t *)event; + if (cm->data.data32[0] == application_->GetXcbAtomWM_DELETE_WINDOW() && + xcb_window_.has_value()) { + xcb_destroy_window(application_->GetXcbConnection(), + xcb_window_.value()); + xcb_flush(application_->GetXcbConnection()); + } + break; + } default: /* Unknown event type, ignore it */ printf("Unknown event: %" PRIu8 "\n", event->response_type); @@ -455,6 +609,10 @@ std::optional<xcb_window_t> XcbWindow::GetEventWindow( xcb_key_release_event_t *kr = (xcb_key_release_event_t *)event; return kr->event; } + case XCB_CLIENT_MESSAGE: { + xcb_client_message_event_t *cm = (xcb_client_message_event_t *)event; + return cm->window; + } default: return std::nullopt; } @@ -493,14 +651,38 @@ void XcbWindow::DoSetTitle(xcb_window_t window) { } } +void XcbWindow::DoSetClientRect(xcb_window_t window, const Rect &rect) { + auto tree_cookie = xcb_query_tree(application_->GetXcbConnection(), window); + auto tree_reply = FreeLater(xcb_query_tree_reply( + application_->GetXcbConnection(), tree_cookie, nullptr)); + auto parent_position = GetXcbWindowPosition(tree_reply->parent); + + std::uint32_t values[4]{ + static_cast<std::uint32_t>(rect.left - parent_position.x), + static_cast<std::uint32_t>(rect.top - parent_position.y), + static_cast<std::uint32_t>(rect.width), + static_cast<std::uint32_t>(rect.height)}; + xcb_configure_window(application_->GetXcbConnection(), window, + XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | + XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, + values); +} + +void XcbWindow::DoSetCursor(xcb_window_t window, XcbCursor *cursor) { + std::uint32_t values[]{cursor ? cursor->GetXcbCursor() : XCB_CURSOR_NONE}; + xcb_change_window_attributes(application_->GetXcbConnection(), window, + XCB_CW_CURSOR, values); + application_->XcbFlush(); +} + void *XcbWindow::XcbGetProperty(xcb_window_t window, xcb_atom_t property, xcb_atom_t type, std::uint32_t offset, std::uint32_t length, std::uint32_t *out_length) { auto cookie = xcb_get_property(application_->GetXcbConnection(), false, window, property, type, offset, length); - auto reply = - xcb_get_property_reply(application_->GetXcbConnection(), cookie, NULL); + auto reply = FreeLater( + xcb_get_property_reply(application_->GetXcbConnection(), cookie, NULL)); if (reply->type == XCB_ATOM_NONE) { return nullptr; } @@ -509,4 +691,41 @@ void *XcbWindow::XcbGetProperty(xcb_window_t window, xcb_atom_t property, } return xcb_get_property_value(reply); } + +std::optional<Thickness> XcbWindow::Get_NET_FRAME_EXTENTS(xcb_window_t window) { + auto frame_properties = static_cast<std::uint32_t *>( + XcbGetProperty(window, application_->GetXcbAtom_NET_FRAME_EXTENTS(), + XCB_ATOM_CARDINAL, 0, 4)); + + if (frame_properties == nullptr) { + return std::nullopt; + } + + return Thickness(frame_properties[0], frame_properties[2], + frame_properties[1], frame_properties[3]); +} + +Point XcbWindow::GetXcbWindowPosition(xcb_window_t window) { + Point result; + + while (true) { + auto cookie = xcb_get_geometry(application_->GetXcbConnection(), window); + auto reply = FreeLater(xcb_get_geometry_reply( + application_->GetXcbConnection(), cookie, nullptr)); + result.x += reply->x; + result.y += reply->y; + + auto tree_cookie = xcb_query_tree(application_->GetXcbConnection(), window); + auto tree_reply = FreeLater(xcb_query_tree_reply( + application_->GetXcbConnection(), tree_cookie, nullptr)); + window = tree_reply->parent; + // TODO: Multi-screen offset? + if (tree_reply->root == window || window == XCB_WINDOW_NONE) { + break; + } + } + + return result; +} + } // namespace cru::platform::gui::xcb |