diff options
Diffstat (limited to 'src/platform/gui/xcb/Window.cpp')
-rw-r--r-- | src/platform/gui/xcb/Window.cpp | 227 |
1 files changed, 223 insertions, 4 deletions
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 |