diff options
Diffstat (limited to 'src')
-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 |
4 files changed, 256 insertions, 10 deletions
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); |