diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/ThemeBuilder/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/base/io/Resource.cpp | 22 | ||||
-rw-r--r-- | src/base/platform/unix/EventLoop.cpp | 6 | ||||
-rw-r--r-- | src/platform/bootstrap/Bootstrap.cpp | 6 | ||||
-rw-r--r-- | src/platform/bootstrap/CMakeLists.txt | 10 | ||||
-rw-r--r-- | src/platform/graphics/cairo/CairoPainter.cpp | 2 | ||||
-rw-r--r-- | src/platform/graphics/cairo/PangoFont.cpp | 2 | ||||
-rw-r--r-- | src/platform/graphics/cairo/PangoTextLayout.cpp | 52 | ||||
-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 |
15 files changed, 402 insertions, 46 deletions
diff --git a/src/ThemeBuilder/CMakeLists.txt b/src/ThemeBuilder/CMakeLists.txt index 5e49cf78..93b24e85 100644 --- a/src/ThemeBuilder/CMakeLists.txt +++ b/src/ThemeBuilder/CMakeLists.txt @@ -40,4 +40,6 @@ if(APPLE) ) endif() +target_add_resources(CruThemeBuilder cru/ui) +target_add_resources(CruThemeBuilder cru/theme_builder) target_link_libraries(CruThemeBuilder PRIVATE CruPlatformBootstrap CruUi) diff --git a/src/base/io/Resource.cpp b/src/base/io/Resource.cpp index d369b5f5..e599f8a9 100644 --- a/src/base/io/Resource.cpp +++ b/src/base/io/Resource.cpp @@ -1,19 +1,20 @@ #include "cru/base/io/Resource.h" +#include "cru/base/Base.h" #include "cru/base/Exception.h" -#include "cru/base/log/Logger.h" #if defined(CRU_PLATFORM_OSX) #include <CoreFoundation/CoreFoundation.h> #elif defined(CRU_PLATFORM_WINDOWS) #include <Windows.h> +#elif defined(__linux) +#include <fstream> +#include <sstream> #endif #include <filesystem> namespace cru::io { std::filesystem::path GetResourceDir() { - constexpr auto kLogTag = u"GetResourceDir"; - #if defined(CRU_PLATFORM_OSX) CFBundleRef main_bundle = CFBundleGetMainBundle(); CFURLRef bundle_url = CFBundleCopyBundleURL(main_bundle); @@ -26,10 +27,18 @@ std::filesystem::path GetResourceDir() { CFRelease(cf_string_ref); return bundle_path / "Contents/Resources"; -#elif defined(CRU_PLATFORM_WINDOWS) +#elif defined(_WIN32) || defined(__linux) +#if defined(_WIN32) wchar_t buffer[MAX_PATH]; DWORD size = ::GetModuleFileNameW(nullptr, buffer, MAX_PATH); std::filesystem::path module_path(buffer, buffer + size); +#else // linux + std::ifstream file("/proc/self/cmdline"); + std::stringstream buffer; + buffer << file.rdbuf(); + auto str = buffer.str(); + std::filesystem::path module_path(str.substr(0, str.find('\0'))); +#endif auto p = module_path; while (p.has_parent_path()) { p = p.parent_path(); @@ -41,8 +50,9 @@ std::filesystem::path GetResourceDir() { } throw Exception(u"Failed to find resource directory."); -#else - throw Exception(u"Not implemented."); + #endif + + NotImplemented(); } } // namespace cru::io diff --git a/src/base/platform/unix/EventLoop.cpp b/src/base/platform/unix/EventLoop.cpp index 7c475697..6e8dc16e 100644 --- a/src/base/platform/unix/EventLoop.cpp +++ b/src/base/platform/unix/EventLoop.cpp @@ -34,15 +34,15 @@ int UnixEventLoop::Run() { while (!exit_code_) { int poll_timeout = -1; - while (CheckPoll()) { + if (CheckPoll()) { continue; } - while (CheckTimer()) { + if (CheckTimer()) { continue; } - while (CheckActionPipe()) { + if (CheckActionPipe()) { continue; } diff --git a/src/platform/bootstrap/Bootstrap.cpp b/src/platform/bootstrap/Bootstrap.cpp index e9183550..bcf12613 100644 --- a/src/platform/bootstrap/Bootstrap.cpp +++ b/src/platform/bootstrap/Bootstrap.cpp @@ -1,10 +1,11 @@ #include "cru/platform/bootstrap/Bootstrap.h" -#include "cru/base/Base.h" #if defined(_WIN32) #include "cru/platform/gui/win/UiApplication.h" #elif defined(__APPLE__) #include "cru/platform/gui/osx/UiApplication.h" +#elif defined(__unix) +#include "cru/platform/gui/xcb/UiApplication.h" #else #endif @@ -15,7 +16,8 @@ cru::platform::gui::IUiApplication* CreateUiApplication() { #elif defined(__APPLE__) return new cru::platform::gui::osx::OsxUiApplication(); #else - NotImplemented(); + return new cru::platform::gui::xcb::XcbUiApplication(); #endif + NotImplemented(); } } // namespace cru::platform::bootstrap diff --git a/src/platform/bootstrap/CMakeLists.txt b/src/platform/bootstrap/CMakeLists.txt index 41f1e3d3..24cdff2b 100644 --- a/src/platform/bootstrap/CMakeLists.txt +++ b/src/platform/bootstrap/CMakeLists.txt @@ -17,10 +17,10 @@ if(WIN32) elseif(APPLE) target_link_libraries(CruPlatformGraphicsBootstrap PUBLIC CruPlatformGraphicsQuartz) target_link_libraries(CruPlatformBootstrap PUBLIC CruPlatformGuiOsx) -elseif(EMSCRIPTEN) - target_link_libraries(CruPlatformGraphicsBootstrap PUBLIC CruBase) - target_link_libraries(CruPlatformBootstrap PUBLIC CruBase) # TODO: Remember to change this. -else() +elseif(UNIX) target_link_libraries(CruPlatformGraphicsBootstrap PUBLIC CruPlatformGraphicsCairo) - target_link_libraries(CruPlatformBootstrap PUBLIC CruPlatformGraphicsCairo) + target_link_libraries(CruPlatformBootstrap PUBLIC CruPlatformGuiXcb) +else() + target_link_libraries(CruPlatformGraphicsBootstrap PUBLIC CruBase) + target_link_libraries(CruPlatformBootstrap PUBLIC CruBase) endif() diff --git a/src/platform/graphics/cairo/CairoPainter.cpp b/src/platform/graphics/cairo/CairoPainter.cpp index b9ab50c4..8dd214cc 100644 --- a/src/platform/graphics/cairo/CairoPainter.cpp +++ b/src/platform/graphics/cairo/CairoPainter.cpp @@ -186,6 +186,8 @@ void CairoPainter::DrawText(const Point& offset, ITextLayout* text_layout, cairo_save(cairo_); cairo_set_source(cairo_, cairo_pattern); + cairo_move_to(cairo_, offset.x, offset.y); + pango_cairo_update_layout(cairo_, pango_text_layout->GetPangoLayout()); pango_cairo_show_layout(cairo_, pango_text_layout->GetPangoLayout()); cairo_restore(cairo_); } diff --git a/src/platform/graphics/cairo/PangoFont.cpp b/src/platform/graphics/cairo/PangoFont.cpp index 0de17add..d5c1ad0b 100644 --- a/src/platform/graphics/cairo/PangoFont.cpp +++ b/src/platform/graphics/cairo/PangoFont.cpp @@ -10,7 +10,7 @@ PangoFont::PangoFont(CairoGraphicsFactory* factory, String font_family, auto font_family_str = font_family_.ToUtf8(); pango_font_description_set_family(pango_font_description_, font_family_str.c_str()); - pango_font_description_set_size(pango_font_description_, font_size); + pango_font_description_set_size(pango_font_description_, font_size * PANGO_SCALE); } PangoFont::~PangoFont() { diff --git a/src/platform/graphics/cairo/PangoTextLayout.cpp b/src/platform/graphics/cairo/PangoTextLayout.cpp index 1033ce9e..0ba7c806 100644 --- a/src/platform/graphics/cairo/PangoTextLayout.cpp +++ b/src/platform/graphics/cairo/PangoTextLayout.cpp @@ -6,13 +6,26 @@ #include "cru/platform/graphics/cairo/CairoGraphicsFactory.h" #include "cru/platform/graphics/cairo/PangoFont.h" +#include <pango/pangocairo.h> + namespace cru::platform::graphics::cairo { +namespace { +Rect ConvertFromPango(const Rect& rect) { + auto result = rect; + result.left /= PANGO_SCALE; + result.top /= PANGO_SCALE; + result.width /= PANGO_SCALE; + result.height /= PANGO_SCALE; + return result; +} +} // namespace + PangoTextLayout::PangoTextLayout(CairoGraphicsFactory* factory, std::shared_ptr<IFont> font) : CairoResource(factory) { Expects(font); font_ = CheckPlatform<PangoFont>(font, GetPlatformId()); - pango_layout_ = pango_layout_new(factory->GetDefaultPangoContext()); + pango_layout_ = pango_cairo_create_layout(factory->GetDefaultCairo()); pango_layout_set_font_description(pango_layout_, font_->GetPangoFontDescription()); }; @@ -37,11 +50,11 @@ void PangoTextLayout::SetFont(std::shared_ptr<IFont> font) { } void PangoTextLayout::SetMaxWidth(float max_width) { - return pango_layout_set_width(pango_layout_, max_width); + return pango_layout_set_width(pango_layout_, max_width * PANGO_SCALE); } void PangoTextLayout::SetMaxHeight(float max_height) { - return pango_layout_set_height(pango_layout_, max_height); + return pango_layout_set_height(pango_layout_, max_height * PANGO_SCALE); } bool PangoTextLayout::IsEditMode() { return edit_mode_; } @@ -64,7 +77,7 @@ float PangoTextLayout::GetLineHeight(Index line_index) { auto line = pango_layout_get_line_readonly(pango_layout_, line_index); int height; pango_layout_line_get_height(line, &height); - return height; + return static_cast<float>(height) / PANGO_SCALE; } Index PangoTextLayout::FromUtf8IndexToUtf16Index(Index index) { @@ -90,8 +103,9 @@ Index PangoTextLayout::FromUtf16IndexToUtf8Index(Index index) { Rect PangoTextLayout::GetTextBounds(bool includingTrailingSpace) { PangoRectangle rectangle; - pango_layout_get_extents(pango_layout_, nullptr, &rectangle); - return Rect(rectangle.x, rectangle.y, rectangle.width, rectangle.height); + pango_layout_get_extents(pango_layout_, &rectangle, nullptr); + return ConvertFromPango( + Rect(rectangle.x, rectangle.y, rectangle.width, rectangle.height)); } std::vector<Rect> PangoTextLayout::TextRangeRect(const TextRange& text_range) { @@ -108,32 +122,36 @@ std::vector<Rect> PangoTextLayout::TextRangeRect(const TextRange& text_range) { if (start_line_index == end_line_index) { auto line = pango_layout_get_line(pango_layout_, start_line_index); PangoRectangle rectangle; - pango_layout_line_get_extents(line, nullptr, &rectangle); - return {Rect(rectangle.x + start_x_pos, rectangle.y, - end_x_pos - start_x_pos, rectangle.height)}; + pango_layout_line_get_extents(line, &rectangle, nullptr); + return {ConvertFromPango(Rect(rectangle.x + start_x_pos, rectangle.y, + end_x_pos - start_x_pos, rectangle.height))}; } else { std::vector<Rect> result; PangoRectangle rectangle; auto start_line = pango_layout_get_line(pango_layout_, start_line_index); - pango_layout_line_get_extents(start_line, nullptr, &rectangle); + pango_layout_line_get_extents(start_line, &rectangle, nullptr); result.push_back(Rect(rectangle.x + start_x_pos, rectangle.y, rectangle.width - start_x_pos, rectangle.height)); for (int line_index = start_line_index + 1; line_index < end_line_index; line_index++) { auto line = pango_layout_get_line(pango_layout_, line_index); - pango_layout_line_get_extents(line, nullptr, &rectangle); + pango_layout_line_get_extents(line, &rectangle, nullptr); result.push_back( Rect(rectangle.x, rectangle.y, rectangle.width, rectangle.height)); } auto end_line = pango_layout_get_line(pango_layout_, end_line_index); - pango_layout_line_get_extents(end_line, nullptr, &rectangle); + pango_layout_line_get_extents(end_line, &rectangle, nullptr); result.push_back( Rect(rectangle.x, rectangle.y, end_x_pos, rectangle.height)); + for (auto& r : result) { + r = ConvertFromPango(r); + } + return result; } } @@ -146,15 +164,17 @@ Rect PangoTextLayout::TextSinglePoint(Index position, bool trailing) { auto line = pango_layout_get_line(pango_layout_, line_index); PangoRectangle rectangle; - pango_layout_line_get_extents(line, nullptr, &rectangle); + pango_layout_line_get_extents(line, &rectangle, nullptr); - return Rect(rectangle.x + x_pos, rectangle.y, 0, rectangle.height); + return ConvertFromPango( + Rect(rectangle.x + x_pos, rectangle.y, 0, rectangle.height)); } TextHitTestResult PangoTextLayout::HitTest(const Point& point) { int index, trailing; - auto inside_text = pango_layout_xy_to_index(pango_layout_, point.x, point.y, - &index, &trailing); + auto inside_text = + pango_layout_xy_to_index(pango_layout_, point.x * PANGO_SCALE, + point.y * PANGO_SCALE, &index, &trailing); return TextHitTestResult{FromUtf8IndexToUtf16Index(index), trailing != 0, inside_text != 0}; } 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 |