diff options
author | Yuqian Yang <crupest@crupest.life> | 2025-09-30 00:16:13 +0800 |
---|---|---|
committer | Yuqian Yang <crupest@crupest.life> | 2025-09-30 00:16:13 +0800 |
commit | 35ba6a2f719327e2ee3f31d5baa04f0ec5a0a09e (patch) | |
tree | 92db6b8b40d409a4faf06c3600111eec200f4775 | |
parent | 5862307366f4b7db492f1630bdf21ce240d8dd6c (diff) | |
download | cru-35ba6a2f719327e2ee3f31d5baa04f0ec5a0a09e.tar.gz cru-35ba6a2f719327e2ee3f31d5baa04f0ec5a0a09e.tar.bz2 cru-35ba6a2f719327e2ee3f31d5baa04f0ec5a0a09e.zip |
Impl input method for xim.
-rw-r--r-- | .github/workflows/ci.yml | 2 | ||||
-rw-r--r-- | demos/CMakeLists.txt | 15 | ||||
-rw-r--r-- | demos/InputMethod/main.cpp | 1 | ||||
-rw-r--r-- | include/cru/base/Bitmask.h | 4 | ||||
-rw-r--r-- | include/cru/base/StringToNumberConverter.h | 10 | ||||
-rw-r--r-- | include/cru/platform/gui/Keyboard.h | 10 | ||||
-rw-r--r-- | include/cru/platform/gui/xcb/InputMethod.h | 77 | ||||
-rw-r--r-- | include/cru/platform/gui/xcb/UiApplication.h | 4 | ||||
-rw-r--r-- | include/cru/platform/gui/xcb/Window.h | 4 | ||||
-rw-r--r-- | src/platform/gui/xcb/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/platform/gui/xcb/InputMethod.cpp | 219 | ||||
-rw-r--r-- | src/platform/gui/xcb/UiApplication.cpp | 19 | ||||
-rw-r--r-- | src/platform/gui/xcb/Window.cpp | 24 |
13 files changed, 360 insertions, 33 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2a624e2..6552f2dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,7 +88,7 @@ jobs: - name: Install Libraries run: | sudo apt update - sudo apt install -y libpng-dev libcairo2-dev libpango1.0-dev libxcb1-dev libxcb-cursor-dev + sudo apt install -y libpng-dev libcairo2-dev libpango1.0-dev libxcb1-dev libxcb-cursor-dev libxcb-imdkit-dev - name: Build run: | diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index 46eda9c4..42335013 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -3,18 +3,11 @@ add_library(CruDemoBase INTERFACE) target_link_libraries(CruDemoBase INTERFACE CruPlatformBootstrap) add_subdirectory(platform) +add_subdirectory(main) +add_subdirectory(ScrollView) +add_subdirectory(InputMethod) -if(WIN32) - add_subdirectory(main) - add_subdirectory(ScrollView) - add_subdirectory(InputMethod) -elseif(APPLE) - add_subdirectory(main) - add_subdirectory(ScrollView) - add_subdirectory(InputMethod) -elseif(UNIX) - add_subdirectory(main) - add_subdirectory(ScrollView) +if(UNIX) add_subdirectory(xcb) endif() diff --git a/demos/InputMethod/main.cpp b/demos/InputMethod/main.cpp index cf42d3c2..430075f4 100644 --- a/demos/InputMethod/main.cpp +++ b/demos/InputMethod/main.cpp @@ -23,6 +23,7 @@ struct InputMethodState { int main() { IUiApplication* application = bootstrap::CreateUiApplication(); + application->SetQuitOnAllWindowClosed(true); auto graphics_factory = application->GetGraphicsFactory(); diff --git a/include/cru/base/Bitmask.h b/include/cru/base/Bitmask.h index 9b6b8957..7606f784 100644 --- a/include/cru/base/Bitmask.h +++ b/include/cru/base/Bitmask.h @@ -10,8 +10,10 @@ struct Bitmask final { constexpr Bitmask() : value(0) {} constexpr explicit Bitmask(TUnderlying value) : value(value) {} + // Start from 1. static constexpr Bitmask FromOffset(int offset) { - return Bitmask(static_cast<TUnderlying>(1u << offset)); + if (offset == 0) return {}; + return Bitmask(static_cast<TUnderlying>(1u << (offset - 1))); } constexpr bool Has(Bitmask rhs) const { return (value & rhs.value) != 0; } diff --git a/include/cru/base/StringToNumberConverter.h b/include/cru/base/StringToNumberConverter.h index 051f44d0..64c29971 100644 --- a/include/cru/base/StringToNumberConverter.h +++ b/include/cru/base/StringToNumberConverter.h @@ -14,15 +14,15 @@ using StringToNumberFlag = Bitmask<details::StringToNumberFlagTag>; struct StringToNumberFlags { constexpr static StringToNumberFlag kAllowLeadingSpaces = - StringToNumberFlag::FromOffset(0); - constexpr static StringToNumberFlag kAllowTrailingSpaces = StringToNumberFlag::FromOffset(1); - constexpr static StringToNumberFlag kAllowTrailingJunk = + constexpr static StringToNumberFlag kAllowTrailingSpaces = StringToNumberFlag::FromOffset(2); - constexpr static StringToNumberFlag kAllowLeadingZeroForInteger = + constexpr static StringToNumberFlag kAllowTrailingJunk = StringToNumberFlag::FromOffset(3); - constexpr static StringToNumberFlag kThrowOnError = + constexpr static StringToNumberFlag kAllowLeadingZeroForInteger = StringToNumberFlag::FromOffset(4); + constexpr static StringToNumberFlag kThrowOnError = + StringToNumberFlag::FromOffset(5); }; template <typename TResult> diff --git a/include/cru/platform/gui/Keyboard.h b/include/cru/platform/gui/Keyboard.h index eb1cc76e..97665e41 100644 --- a/include/cru/platform/gui/Keyboard.h +++ b/include/cru/platform/gui/Keyboard.h @@ -118,16 +118,16 @@ struct TagKeyModifier {}; using KeyModifier = Bitmask<details::TagKeyModifier>; struct KeyModifiers { - static constexpr KeyModifier none{0}; - static constexpr KeyModifier shift{0b1}; - static constexpr KeyModifier ctrl{0b10}; - static constexpr KeyModifier alt{0b100}; - static constexpr KeyModifier command{0b1000}; static constexpr KeyModifier None = KeyModifier::FromOffset(0); static constexpr KeyModifier Shift = KeyModifier::FromOffset(1); static constexpr KeyModifier Ctrl = KeyModifier::FromOffset(2); static constexpr KeyModifier Alt = KeyModifier::FromOffset(3); static constexpr KeyModifier Command = KeyModifier::FromOffset(4); + static constexpr KeyModifier none = None; + static constexpr KeyModifier shift = Shift; + static constexpr KeyModifier ctrl = Ctrl; + static constexpr KeyModifier alt = Alt; + static constexpr KeyModifier command = Command; }; #ifdef CRU_PLATFORM_OSX diff --git a/include/cru/platform/gui/xcb/InputMethod.h b/include/cru/platform/gui/xcb/InputMethod.h new file mode 100644 index 00000000..286f3158 --- /dev/null +++ b/include/cru/platform/gui/xcb/InputMethod.h @@ -0,0 +1,77 @@ +#pragma once + +#include "../InputMethod.h" +#include "Base.h" + +#include <xcb-imdkit/imclient.h> +#include <xcb/xcb.h> +#include <optional> + +namespace cru::platform::gui::xcb { +class XcbUiApplication; +class XcbWindow; + +class XcbXimInputMethodManager : public XcbResource { + friend XcbUiApplication; + + public: + XcbXimInputMethodManager(XcbUiApplication* application); + ~XcbXimInputMethodManager() override; + + xcb_xim_t* GetXcbXim(); + + private: + void DispatchCommit(xcb_xim_t* im, xcb_xic_t ic, std::string text); + void DispatchComposition(xcb_xim_t* im, xcb_xic_t ic, CompositionText text); + + bool HandleXEvent(xcb_generic_event_t* event); + + private: + XcbUiApplication* application_; + xcb_xim_t* im_; +}; + +class XcbXimInputMethodContext : public XcbResource, + public virtual IInputMethodContext { + friend XcbXimInputMethodManager; + + public: + XcbXimInputMethodContext(XcbXimInputMethodManager* manager, + XcbWindow* window); + ~XcbXimInputMethodContext() override; + + bool ShouldManuallyDrawCompositionText() override; + + void EnableIME() override; + void DisableIME() override; + + void CompleteComposition() override; + void CancelComposition() override; + CompositionText GetCompositionText() override; + + // Set the candidate window left-top. Relative to window left-top. Use this + // method to prepare typing. + void SetCandidateWindowPosition(const Point& point) override; + + IEvent<std::nullptr_t>* CompositionStartEvent() override; + IEvent<std::nullptr_t>* CompositionEndEvent() override; + IEvent<std::nullptr_t>* CompositionEvent() override; + IEvent<StringView>* TextEvent() override; + + private: + void CreateIc(xcb_window_t window); + void DestroyIc(); + + private: + XcbXimInputMethodManager* manager_; + XcbWindow* window_; + bool enabled_; + std::optional<xcb_xic_t> ic_; + CompositionText composition_text_; + + Event<std::nullptr_t> composition_start_event_; + Event<std::nullptr_t> composition_end_event_; + Event<std::nullptr_t> composition_event_; + Event<StringView> text_event_; +}; +} // namespace cru::platform::gui::xcb diff --git a/include/cru/platform/gui/xcb/UiApplication.h b/include/cru/platform/gui/xcb/UiApplication.h index b8de86f2..d6971099 100644 --- a/include/cru/platform/gui/xcb/UiApplication.h +++ b/include/cru/platform/gui/xcb/UiApplication.h @@ -13,6 +13,7 @@ namespace cru::platform::gui::xcb { class XcbWindow; class XcbCursorManager; +class XcbXimInputMethodManager; class XcbUiApplication : public XcbResource, public virtual IUiApplication { friend XcbWindow; @@ -49,6 +50,8 @@ class XcbUiApplication : public XcbResource, public virtual IUiApplication { #undef CRU_XCB_UI_APPLICATION_DEFINE_XCB_ATOM + XcbXimInputMethodManager* GetXcbXimInputMethodManager(); + public: int Run() override; @@ -100,5 +103,6 @@ class XcbUiApplication : public XcbResource, public virtual IUiApplication { std::vector<XcbWindow*> windows_; XcbCursorManager* cursor_manager_; + XcbXimInputMethodManager* input_method_manager_; }; } // namespace cru::platform::gui::xcb diff --git a/include/cru/platform/gui/xcb/Window.h b/include/cru/platform/gui/xcb/Window.h index 61e4b616..e330ba7c 100644 --- a/include/cru/platform/gui/xcb/Window.h +++ b/include/cru/platform/gui/xcb/Window.h @@ -12,6 +12,7 @@ namespace cru::platform::gui::xcb { class XcbUiApplication; class XcbCursor; +class XcbXimInputMethodContext; class XcbWindow : public XcbResource, public virtual INativeWindow { friend XcbUiApplication; @@ -79,6 +80,8 @@ class XcbWindow : public XcbResource, public virtual INativeWindow { public: std::optional<xcb_window_t> GetXcbWindow(); + XcbUiApplication* GetXcbUiApplication(); + bool HasFocus(); private: xcb_window_t DoCreateWindow(); @@ -112,6 +115,7 @@ class XcbWindow : public XcbResource, public virtual INativeWindow { std::shared_ptr<XcbCursor> cursor_; XcbWindow* parent_; + XcbXimInputMethodContext* input_method_; Event<std::nullptr_t> create_event_; Event<std::nullptr_t> destroy_event_; diff --git a/src/platform/gui/xcb/CMakeLists.txt b/src/platform/gui/xcb/CMakeLists.txt index fa5cc0bf..35feb8bc 100644 --- a/src/platform/gui/xcb/CMakeLists.txt +++ b/src/platform/gui/xcb/CMakeLists.txt @@ -1,13 +1,15 @@ find_library(LIBRARY_CAIRO cairo REQUIRED) find_library(LIBRARY_XCB xcb REQUIRED) find_library(LIBRARY_XCB_CURSOR xcb-cursor REQUIRED) +find_library(LIBRARY_XCB_IMDKIT xcb-imdkit REQUIRED) add_library(CruPlatformGuiXcb Cursor.cpp + InputMethod.cpp Keyboard.cpp UiApplication.cpp Window.cpp ) target_link_libraries(CruPlatformGuiXcb PUBLIC CruPlatformGui CruPlatformGraphicsCairo - ${LIBRARY_XCB} ${LIBRARY_XCB_CURSOR} ${LIBRARY_CAIRO} + ${LIBRARY_XCB} ${LIBRARY_XCB_CURSOR} ${LIBRARY_XCB_IMDKIT} ${LIBRARY_CAIRO} ) diff --git a/src/platform/gui/xcb/InputMethod.cpp b/src/platform/gui/xcb/InputMethod.cpp new file mode 100644 index 00000000..0cd079b4 --- /dev/null +++ b/src/platform/gui/xcb/InputMethod.cpp @@ -0,0 +1,219 @@ +#include "cru/platform/gui/xcb/InputMethod.h" +#include "cru/platform/Check.h" +#include "cru/platform/gui/InputMethod.h" +#include "cru/platform/gui/xcb/UiApplication.h" +#include "cru/platform/gui/xcb/Window.h" + +#include <xcb-imdkit/encoding.h> +#include <xcb-imdkit/imclient.h> + +namespace cru::platform::gui::xcb { +namespace { +void XimLogger(const char *, ...) {} +} // namespace + +XcbXimInputMethodManager::XcbXimInputMethodManager( + XcbUiApplication *application) + : application_(application) { + auto XimOpenCallback = [](xcb_xim_t *im, void *user_data) {}; + + xcb_xim_im_callback kXimCallbacks = { + .commit_string = + [](xcb_xim_t *im, xcb_xic_t ic, uint32_t flag, char *str, + uint32_t length, uint32_t *keysym, size_t nKeySym, + void *user_data) { + auto manager = static_cast<XcbXimInputMethodManager *>(user_data); + + if (xcb_xim_get_encoding(im) == XCB_XIM_UTF8_STRING) { + manager->DispatchCommit(im, ic, std::string(str, length)); + + } else if (xcb_xim_get_encoding(im) == XCB_XIM_COMPOUND_TEXT) { + size_t newLength = 0; + char *utf8 = xcb_compound_text_to_utf8(str, length, &newLength); + if (utf8) { + int l = newLength; + manager->DispatchCommit(im, ic, std::string(str, length)); + } + } + }, + .preedit_draw = + [](xcb_xim_t *im, xcb_xic_t ic, xcb_im_preedit_draw_fr_t *frame, + void *user_data) { + auto manager = static_cast<XcbXimInputMethodManager *>(user_data); + CompositionText text; + if (!(frame->status & 1)) { + text.text = String::FromUtf8( + reinterpret_cast<const std::byte *>(frame->preedit_string), + frame->length_of_preedit_string); + text.selection = frame->caret; + } + manager->DispatchComposition(im, ic, std::move(text)); + }}; + + xcb_compound_text_init(); + im_ = xcb_xim_create(application->GetXcbConnection(), 0, "@im=fcitx"); + xcb_xim_set_im_callback(im_, &kXimCallbacks, static_cast<void *>(this)); + xcb_xim_set_log_handler(im_, XimLogger); + xcb_xim_set_use_compound_text(im_, true); + xcb_xim_set_use_utf8_string(im_, true); + xcb_xim_open(im_, XimOpenCallback, true, this); +} + +XcbXimInputMethodManager::~XcbXimInputMethodManager() { + xcb_xim_close(im_); + xcb_xim_destroy(im_); +} + +xcb_xim_t *XcbXimInputMethodManager::GetXcbXim() { return im_; } + +void XcbXimInputMethodManager::DispatchCommit(xcb_xim_t *im, xcb_xic_t ic, + std::string text) { + for (auto window : application_->GetAllWindow()) { + auto input_method_context = window->GetInputMethodContext(); + auto context = CheckPlatform<XcbXimInputMethodContext>(input_method_context, + GetPlatformIdUtf8()); + if (context->ic_ == ic) { + context->text_event_.Raise(String::FromUtf8(text)); + } + } +} + +void XcbXimInputMethodManager::DispatchComposition(xcb_xim_t *im, xcb_xic_t ic, + CompositionText text) { + for (auto window : application_->GetAllWindow()) { + auto input_method_context = window->GetInputMethodContext(); + auto context = CheckPlatform<XcbXimInputMethodContext>(input_method_context, + GetPlatformIdUtf8()); + if (context->ic_ == ic) { + context->composition_text_ = std::move(text); + context->composition_event_.Raise(nullptr); + } + } +} + +bool XcbXimInputMethodManager::HandleXEvent(xcb_generic_event_t *event) { + if (xcb_xim_filter_event(im_, event)) return true; + for (auto window : application_->GetAllWindow()) { + auto input_method_context = window->GetInputMethodContext(); + auto context = CheckPlatform<XcbXimInputMethodContext>(input_method_context, + GetPlatformIdUtf8()); + // Forward event to input method if IC is created. + if (context->ic_ && (((event->response_type & ~0x80) == XCB_KEY_PRESS) || + ((event->response_type & ~0x80) == XCB_KEY_RELEASE))) { + xcb_xim_forward_event(im_, *context->ic_, (xcb_key_press_event_t *)event); + return true; + } + } + return false; +} + +XcbXimInputMethodContext::XcbXimInputMethodContext( + XcbXimInputMethodManager *manager, XcbWindow *window) + : manager_(manager), window_(window), enabled_(false) { + window->CreateEvent()->AddSpyOnlyHandler([this, window] { + if (enabled_) { + CreateIc(*window->GetXcbWindow()); + } + }); + + window->DestroyEvent()->AddSpyOnlyHandler([this] { DestroyIc(); }); + + window->FocusEvent()->AddSpyOnlyHandler([this, window] { + auto input_method_context = window->GetInputMethodContext(); + auto context = CheckPlatform<XcbXimInputMethodContext>(input_method_context, + GetPlatformIdUtf8()); + if (context->enabled_ && context->ic_) { + xcb_xim_set_ic_focus(context->manager_->GetXcbXim(), *context->ic_); + } + }); +} + +XcbXimInputMethodContext::~XcbXimInputMethodContext() {} + +bool XcbXimInputMethodContext::ShouldManuallyDrawCompositionText() { + return false; +} + +void XcbXimInputMethodContext::EnableIME() { + if (enabled_) return; + enabled_ = true; + if (!ic_ && window_->GetXcbWindow()) { + CreateIc(*window_->GetXcbWindow()); + } +} + +void XcbXimInputMethodContext::DisableIME() { + if (!enabled_) return; + enabled_ = false; + DestroyIc(); +} + +void XcbXimInputMethodContext::CompleteComposition() { CancelComposition(); } + +void XcbXimInputMethodContext::CancelComposition() { + if (!ic_) return; + auto XimResetIcCallback = [](xcb_xim_t *im, xcb_xic_t ic, + xcb_im_reset_ic_reply_fr_t *reply, + void *user_data) {}; + xcb_xim_reset_ic(manager_->GetXcbXim(), *ic_, XimResetIcCallback, this); +} + +static void EmptyXimDestroyIcCallback(xcb_xim_t *im, xcb_xic_t ic, + void *user_data) {} + +void XcbXimInputMethodContext::SetCandidateWindowPosition(const Point &point) { + if (!ic_) return; + + xcb_point_t spot; + spot.x = point.x; + spot.y = point.y; + xcb_xim_nested_list nested = xcb_xim_create_nested_list( + manager_->GetXcbXim(), XCB_XIM_XNSpotLocation, &spot, NULL); + xcb_xim_set_ic_values(manager_->GetXcbXim(), *ic_, EmptyXimDestroyIcCallback, + this, XCB_XIM_XNPreeditAttributes, &nested, NULL); + ::free(nested.data); +} + +CompositionText XcbXimInputMethodContext::GetCompositionText() { + return composition_text_; +} + +IEvent<std::nullptr_t> *XcbXimInputMethodContext::CompositionStartEvent() { + return &composition_start_event_; +} + +IEvent<std::nullptr_t> *XcbXimInputMethodContext::CompositionEndEvent() { + return &composition_end_event_; +} + +IEvent<std::nullptr_t> *XcbXimInputMethodContext::CompositionEvent() { + return &composition_event_; +} + +IEvent<StringView> *XcbXimInputMethodContext::TextEvent() { + return &text_event_; +} + +void XcbXimInputMethodContext::CreateIc(xcb_window_t window) { + auto XimCreateIcCallback = [](xcb_xim_t *im, xcb_xic_t ic, void *user_data) { + auto context = static_cast<XcbXimInputMethodContext *>(user_data); + context->ic_ = ic; + if (context->window_->HasFocus()) { + xcb_xim_set_ic_focus(context->manager_->GetXcbXim(), ic); + } + }; + + uint32_t input_style = XCB_IM_PreeditPosition | XCB_IM_StatusArea; + xcb_xim_create_ic(manager_->GetXcbXim(), XimCreateIcCallback, this, + XCB_XIM_XNInputStyle, &input_style, XCB_XIM_XNClientWindow, + &window, XCB_XIM_XNFocusWindow, &window, NULL); +} + +void XcbXimInputMethodContext::DestroyIc() { + if (!ic_) return; + xcb_xim_destroy_ic(manager_->GetXcbXim(), *ic_, EmptyXimDestroyIcCallback, + this); + ic_ = std::nullopt; +} + +} // namespace cru::platform::gui::xcb diff --git a/src/platform/gui/xcb/UiApplication.cpp b/src/platform/gui/xcb/UiApplication.cpp index 1e5613b4..03e39985 100644 --- a/src/platform/gui/xcb/UiApplication.cpp +++ b/src/platform/gui/xcb/UiApplication.cpp @@ -5,6 +5,7 @@ #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/InputMethod.h" #include "cru/platform/gui/xcb/Window.h" #include <poll.h> @@ -36,9 +37,11 @@ XcbUiApplication::XcbUiApplication( this->screen_ = iter.data; cursor_manager_ = new XcbCursorManager(this); + input_method_manager_ = new XcbXimInputMethodManager(this); } XcbUiApplication::~XcbUiApplication() { + delete input_method_manager_; delete cursor_manager_; xcb_disconnect(this->xcb_connection_); @@ -80,6 +83,10 @@ xcb_atom_t XcbUiApplication::GetOrCreateXcbAtom(std::string name) { return atom; } +XcbXimInputMethodManager *XcbUiApplication::GetXcbXimInputMethodManager() { + return input_method_manager_; +} + int XcbUiApplication::Run() { auto exit_code = event_loop_.Run(); @@ -128,11 +135,13 @@ void XcbUiApplication::CancelTimer(long long id) { void XcbUiApplication::HandleXEvents() { xcb_generic_event_t *event; while ((event = xcb_poll_for_event(xcb_connection_))) { - auto event_xcb_window = XcbWindow::GetEventWindow(event); - for (auto window : windows_) { - if (window->GetXcbWindow() == event_xcb_window) { - window->HandleEvent(event); - break; + if (!input_method_manager_->HandleXEvent(event)) { + auto event_xcb_window = XcbWindow::GetEventWindow(event); + for (auto window : windows_) { + if (window->GetXcbWindow() == event_xcb_window) { + window->HandleEvent(event); + break; + } } } ::free(event); diff --git a/src/platform/gui/xcb/Window.cpp b/src/platform/gui/xcb/Window.cpp index d45d68b2..2b4d57d9 100644 --- a/src/platform/gui/xcb/Window.cpp +++ b/src/platform/gui/xcb/Window.cpp @@ -10,16 +10,15 @@ #include "cru/platform/gui/Keyboard.h" #include "cru/platform/gui/Window.h" #include "cru/platform/gui/xcb/Cursor.h" +#include "cru/platform/gui/xcb/InputMethod.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> #include <memory> #include <optional> @@ -68,9 +67,14 @@ XcbWindow::XcbWindow(XcbUiApplication *application) cairo_surface_(nullptr), parent_(nullptr) { application->RegisterWindow(this); + input_method_ = new XcbXimInputMethodContext( + application->GetXcbXimInputMethodManager(), this); } -XcbWindow::~XcbWindow() { application_->UnregisterWindow(this); } +XcbWindow::~XcbWindow() { + delete input_method_; + application_->UnregisterWindow(this); +} bool XcbWindow::IsCreated() { return xcb_window_.has_value(); } @@ -351,10 +355,22 @@ IEvent<NativeKeyEventArgs> *XcbWindow::KeyDownEvent() { IEvent<NativeKeyEventArgs> *XcbWindow::KeyUpEvent() { return &key_up_event_; } -IInputMethodContext *XcbWindow::GetInputMethodContext() { return nullptr; } +IInputMethodContext *XcbWindow::GetInputMethodContext() { + return input_method_; +} std::optional<xcb_window_t> XcbWindow::GetXcbWindow() { return xcb_window_; } +XcbUiApplication *XcbWindow::GetXcbUiApplication() { return application_; } + +bool XcbWindow::HasFocus() { + if (!xcb_window_) return false; + auto cookie = xcb_get_input_focus(application_->GetXcbConnection()); + auto focus = xcb_get_input_focus_reply(application_->GetXcbConnection(), + cookie, nullptr); + return focus->focus == *xcb_window_; +} + xcb_window_t XcbWindow::DoCreateWindow() { assert(xcb_window_ == std::nullopt); assert(cairo_surface_ == nullptr); |