#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 #include 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(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(user_data); CompositionText text; if (!(frame->status & 1)) { text.text = String::FromUtf8( reinterpret_cast(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(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(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(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(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(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 *XcbXimInputMethodContext::CompositionStartEvent() { return &composition_start_event_; } IEvent *XcbXimInputMethodContext::CompositionEndEvent() { return &composition_end_event_; } IEvent *XcbXimInputMethodContext::CompositionEvent() { return &composition_event_; } IEvent *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(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