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