diff --git a/CMakeLists.txt b/CMakeLists.txt index f94259222..37d5a3a64 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,9 @@ find_package(IsoCodes) find_package(LibXml2) find_package(XKeyboardConfig) find_package(Cairo COMPONENTS Cairo XCB EGL) +find_package(Wayland COMPONENTS Client) +find_package(WaylandScanner) +find_package(WaylandProtocols) set(DEFAULT_XKB_RULES_FILES "${XKEYBOARDCONFIG_XKBBASE}/rules/${DEFAULT_XKB_RULES}.xml") if (NOT EXISTS "${DEFAULT_XKB_RULES_FILES}") diff --git a/cmake/FindWaylandProtocols.cmake b/cmake/FindWaylandProtocols.cmake new file mode 100644 index 000000000..7859af740 --- /dev/null +++ b/cmake/FindWaylandProtocols.cmake @@ -0,0 +1,24 @@ +find_package(PkgConfig) + +pkg_check_modules(WaylandProtocols QUIET "wayland-protocols>=${WaylandProtocols_FIND_VERSION}") + +pkg_get_variable(WaylandProtocols_PKGDATADIR wayland-protocols pkgdatadir) + +mark_as_advanced(WaylandProtocols_PKGDATADIR) + +string(REGEX REPLACE "[\r\n]" "" WaylandProtocols_PKGDATADIR "${WaylandProtocols_PKGDATADIR}") + +find_package_handle_standard_args(WaylandProtocols + FOUND_VAR + WaylandProtocols_FOUND + REQUIRED_VARS + WaylandProtocols_PKGDATADIR + VERSION_VAR + WaylandProtocols_VERSION + HANDLE_COMPONENTS +) + +set(WAYLAND_PROTOCOLS_FOUND ${WaylandProtocols_FOUND}) +set(WAYLAND_PROTOCOLS_PKGDATADIR ${WaylandProtocols_PKGDATADIR}) +set(WAYLAND_PROTOCOLS_VERSION ${WaylandProtocols_VERSION}) + diff --git a/src/frontend/CMakeLists.txt b/src/frontend/CMakeLists.txt index 25c29f0d3..2ce01caf1 100644 --- a/src/frontend/CMakeLists.txt +++ b/src/frontend/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(xim) add_subdirectory(dbusfrontend) +add_subdirectory(waylandim) diff --git a/src/frontend/waylandim/CMakeLists.txt b/src/frontend/waylandim/CMakeLists.txt new file mode 100644 index 000000000..57c7a91de --- /dev/null +++ b/src/frontend/waylandim/CMakeLists.txt @@ -0,0 +1,17 @@ +set(WAYLAND_IM_PROTOCOL_SRCS) + +ecm_add_wayland_client_protocol(WAYLAND_IM_PROTOCOL_SRCS + PROTOCOL ${WAYLAND_PROTOCOLS_PKGDATADIR}/unstable/input-method/input-method-unstable-v1.xml + BASENAME input-method-unstable-v1) + +ecm_add_wayland_client_protocol(WAYLAND_IM_PROTOCOL_SRCS + PROTOCOL ${WAYLAND_PROTOCOLS_PKGDATADIR}/unstable/text-input/text-input-unstable-v1.xml + BASENAME text-input-unstable-v1) + +add_library(waylandim MODULE waylandim.cpp ${WAYLAND_IM_PROTOCOL_SRCS}) +target_include_directories(waylandim PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +target_link_libraries(waylandim Fcitx5::Core Wayland::Client XKBCommon::XKBCommon) +set_target_properties(waylandim PROPERTIES PREFIX "") +install(TARGETS waylandim DESTINATION "${FCITX_INSTALL_ADDONDIR}") +install(FILES waylandim.conf DESTINATION "${FCITX_INSTALL_PKGDATADIR}/addon") + diff --git a/src/frontend/waylandim/waylandim.conf b/src/frontend/waylandim/waylandim.conf new file mode 100644 index 000000000..0873a74e4 --- /dev/null +++ b/src/frontend/waylandim/waylandim.conf @@ -0,0 +1,8 @@ +[Addon] +Name=waylandim +Type=SharedLibrary +Library=waylandim +Category=Frontend +Dependencies/0=wayland +Dependencies/Length=1 + diff --git a/src/frontend/waylandim/waylandim.cpp b/src/frontend/waylandim/waylandim.cpp new file mode 100644 index 000000000..cfd174f08 --- /dev/null +++ b/src/frontend/waylandim/waylandim.cpp @@ -0,0 +1,488 @@ +/* + * Copyright (C) 2016~2016 by CSSlayer + * wengxt@gmail.com + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, + * see . + */ +#include "waylandim.h" +#include "fcitx-utils/utf8.h" +#include "fcitx/inputcontext.h" +#include "wayland-text-input-unstable-v1-client-protocol.h" +#include +#include +#include +#include + +namespace fcitx { + +class WaylandIMServer { + friend class WaylandIMInputContextV1; +public: + WaylandIMServer(wl_display *display, FocusGroup *group, const std::string &name, WaylandIMModule *waylandim); + + InputContextManager &inputContextManager() { return parent_->instance()->inputContextManager(); } + + void registryHandlerGlobal(struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version); + void registryHandlerGlobalRemove(struct wl_registry *registry, uint32_t name); + + void activate(struct zwp_input_method_v1 *inputMethod, struct zwp_input_method_context_v1 *id); + + void deactivate(struct zwp_input_method_v1 *inputMethod, struct zwp_input_method_context_v1 *id); + + ~WaylandIMServer() {} + +private: + static const struct wl_registry_listener registryListener; + static const struct zwp_input_method_v1_listener inputMethodListener; + FocusGroup *group_; + std::string name_; + WaylandIMModule *parent_; + zwp_input_method_v1 *inputMethodV1_; + + std::unique_ptr context_; + std::unique_ptr keymap_; + std::unique_ptr state_; + + struct StateMask { + uint32_t shift_mask; + uint32_t lock_mask; + uint32_t control_mask; + uint32_t mod1_mask; + uint32_t mod2_mask; + uint32_t mod3_mask; + uint32_t mod4_mask; + uint32_t mod5_mask; + uint32_t super_mask; + uint32_t hyper_mask; + uint32_t meta_mask; + } stateMask_; + + KeyStates modifiers_; +}; + +const struct wl_registry_listener WaylandIMServer::registryListener = { + [](void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { + static_cast(data)->registryHandlerGlobal(registry, name, interface, version); + }, + [](void *data, struct wl_registry *registry, uint32_t name) { + static_cast(data)->registryHandlerGlobalRemove(registry, name); + }}; +const struct zwp_input_method_v1_listener WaylandIMServer::inputMethodListener = { + [](void *data, struct zwp_input_method_v1 *inputMethod, struct zwp_input_method_context_v1 *id) { + static_cast(data)->activate(inputMethod, id); + }, + [](void *data, struct zwp_input_method_v1 *inputMethod, struct zwp_input_method_context_v1 *id) { + static_cast(data)->deactivate(inputMethod, id); + }}; + +class WaylandIMInputContextV1 : public InputContext { +public: + WaylandIMInputContextV1(InputContextManager &inputContextManager, WaylandIMServer *server, + zwp_input_method_context_v1 *ic) + : InputContext(inputContextManager), server_(server), ic_(ic) { + zwp_input_method_context_v1_set_user_data(ic_, this); + zwp_input_method_context_v1_add_listener(ic_, &inputMethodContextListener, this); + + auto keyboard = zwp_input_method_context_v1_grab_keyboard(ic_); + wl_keyboard_add_listener(keyboard, &keyboardListener, ic); + } + ~WaylandIMInputContextV1() { zwp_input_method_context_v1_set_user_data(ic_, nullptr); } + +protected: + virtual void commitStringImpl(const std::string &text) override { + zwp_input_method_context_v1_commit_string(ic_, serial_, text.c_str()); + } + virtual void deleteSurroundingTextImpl(int offset, unsigned int size) override { + zwp_input_method_context_v1_delete_surrounding_text(ic_, offset, size); + } + virtual void forwardKeyImpl(const ForwardKeyEvent &key) override { + zwp_input_method_context_v1_keysym( + ic_, serial_, 0, key.rawKey().sym(), + key.isRelease() ? WL_KEYBOARD_KEY_STATE_RELEASED : WL_KEYBOARD_KEY_STATE_PRESSED, key.rawKey().states()); + } + + static inline unsigned int waylandFormat(TextFormatFlags flags) { + unsigned int result = 0; + if (flags & TextFormatFlag::UnderLine) { + result |= ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_UNDERLINE; + } + if (flags & TextFormatFlag::HighLight) { + result |= ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_SELECTION; + } + if (flags & TextFormatFlag::Bold) { + result |= ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_ACTIVE; + } + if (flags & TextFormatFlag::Strike) { + result |= ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_INCORRECT; + } + return result; + } + + virtual void updatePreeditImpl() override { + auto &preedit = this->preedit(); + + for (int i = 0, e = preedit.size(); i < e; i++) { + if (!utf8::validate(preedit.stringAt(i))) { + return; + } + } + + zwp_input_method_context_v1_preedit_cursor(ic_, preedit.cursor()); + // FIXME second string should be for commit + zwp_input_method_context_v1_preedit_string(ic_, serial_, preedit.toString().c_str(), + preedit.toString().c_str()); + unsigned int index; + for (int i = 0, e = preedit.size(); i < e; i++) { + zwp_input_method_context_v1_preedit_styling(ic_, index, preedit.stringAt(i).size(), + waylandFormat(preedit.formatAt(i))); + index += preedit.stringAt(i).size(); + } + } + +private: + void surroundingTextCallback(struct zwp_input_method_context_v1 *inputContext, const char *text, uint32_t cursor, + uint32_t anchor); + void resetCallback(struct zwp_input_method_context_v1 *inputContext); + void contentTypeCallback(struct zwp_input_method_context_v1 *inputContext, uint32_t hint, uint32_t purpose); + void invokeActionCallback(struct zwp_input_method_context_v1 *inputContext, uint32_t button, uint32_t index); + void commitStateCallback(struct zwp_input_method_context_v1 *inputContext, uint32_t serial); + void preferredLanguageCallback(struct zwp_input_method_context_v1 *inputContext, const char *language); + + void keymapCallback(struct wl_keyboard *keyboard, uint32_t format, int32_t fd, uint32_t size); + void keyCallback(struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state); + void modifiersCallback(struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, uint32_t group); + + static const struct zwp_input_method_context_v1_listener inputMethodContextListener; + static const struct wl_keyboard_listener keyboardListener; + WaylandIMServer *server_; + zwp_input_method_context_v1 *ic_; + uint32_t serial_ = 0; +}; + +const struct zwp_input_method_context_v1_listener WaylandIMInputContextV1::inputMethodContextListener = { + [](void *data, struct zwp_input_method_context_v1 *inputContext, const char *text, uint32_t cursor, + uint32_t anchor) { + static_cast(data)->surroundingTextCallback(inputContext, text, cursor, anchor); + }, + [](void *data, struct zwp_input_method_context_v1 *inputContext) { + static_cast(data)->resetCallback(inputContext); + }, + [](void *data, struct zwp_input_method_context_v1 *inputContext, uint32_t hint, uint32_t purpose) { + static_cast(data)->contentTypeCallback(inputContext, hint, purpose); + }, + [](void *data, struct zwp_input_method_context_v1 *inputContext, uint32_t button, uint32_t index) { + static_cast(data)->invokeActionCallback(inputContext, button, index); + }, + [](void *data, struct zwp_input_method_context_v1 *inputContext, uint32_t serial) { + static_cast(data)->commitStateCallback(inputContext, serial); + }, + [](void *data, struct zwp_input_method_context_v1 *inputContext, const char *language) { + static_cast(data)->preferredLanguageCallback(inputContext, language); + }}; + +const struct wl_keyboard_listener WaylandIMInputContextV1::keyboardListener = { + + [](void *data, struct wl_keyboard *keyboard, uint32_t format, int32_t fd, uint32_t size) { + static_cast(data)->keymapCallback(keyboard, format, fd, size); + }, + nullptr, nullptr, + [](void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { + static_cast(data)->keyCallback(keyboard, serial, time, key, state); + }, + [](void *data, struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, + uint32_t mods_locked, uint32_t group) { + static_cast(data)->modifiersCallback(keyboard, serial, mods_depressed, mods_latched, + mods_locked, group); + }, + nullptr, +}; + +WaylandIMServer::WaylandIMServer(wl_display *display, FocusGroup *group, const std::string &name, + WaylandIMModule *waylandim) + : group_(group), name_(name), parent_(waylandim), context_(nullptr, &xkb_context_unref), keymap_(nullptr, xkb_keymap_unref), state_(nullptr, xkb_state_unref) { + struct wl_registry *registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, &WaylandIMServer::registryListener, waylandim); +} + +void WaylandIMServer::registryHandlerGlobal(struct wl_registry *registry, uint32_t name, const char *interface, + uint32_t version) { + FCITX_UNUSED(version); + if (0 == strcmp(interface, "zwp_input_method_v1")) { + inputMethodV1_ = + static_cast(wl_registry_bind(registry, name, &zwp_input_method_v1_interface, 1)); + zwp_input_method_v1_add_listener(inputMethodV1_, &WaylandIMServer::inputMethodListener, this); + } +} + +void WaylandIMServer::registryHandlerGlobalRemove(struct wl_registry *registry, uint32_t name) { + // FIXME do we need anything here? + FCITX_UNUSED(registry); + FCITX_UNUSED(name); +} + +void WaylandIMServer::activate(struct zwp_input_method_v1 *inputMethod, struct zwp_input_method_context_v1 *id) { + assert(inputMethod == inputMethodV1_); + auto ic = new WaylandIMInputContextV1(parent_->instance()->inputContextManager(), this, id); + ic->setDisplayServer("wayland:" + name_); + ic->setFocusGroup(group_); +} + +void WaylandIMServer::deactivate(struct zwp_input_method_v1 *inputMethod, struct zwp_input_method_context_v1 *id) { + assert(inputMethod == inputMethodV1_); + + auto ic = static_cast(zwp_input_method_context_v1_get_user_data(id)); + delete ic; +} + +void WaylandIMInputContextV1::surroundingTextCallback(struct zwp_input_method_context_v1 *inputContext, + const char *text, uint32_t cursor, uint32_t anchor) { + assert(ic_ == inputContext); + surroundingText().setText(text, cursor, anchor); + updateSurroundingText(); +} +void WaylandIMInputContextV1::resetCallback(struct zwp_input_method_context_v1 *inputContext) { + assert(ic_ == inputContext); + reset(); +} +void WaylandIMInputContextV1::contentTypeCallback(struct zwp_input_method_context_v1 *inputContext, uint32_t hint, + uint32_t purpose) { + assert(ic_ == inputContext); + CapabilityFlags flags; + if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_PASSWORD) { + flags |= CapabilityFlag::Password; + } + if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_AUTO_COMPLETION) { + flags |= CapabilityFlag::WordCompletion; + } + if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_AUTO_CORRECTION) { + flags |= CapabilityFlag::SpellCheck; + } + if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_AUTO_CAPITALIZATION) { + flags |= CapabilityFlag::UppercaseWords; + } + if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_LOWERCASE) { + flags |= CapabilityFlag::Lowercase; + } + if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_UPPERCASE) { + flags |= CapabilityFlag::Uppercase; + } + if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_TITLECASE) { + // ?? + } + if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_HIDDEN_TEXT) { + flags |= CapabilityFlag::HiddenText; + } + if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_SENSITIVE_DATA) { + flags |= CapabilityFlag::Sensitive; + } + if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_LATIN) { + flags |= CapabilityFlag::Alpha; + } + if (hint & ZWP_TEXT_INPUT_V1_CONTENT_HINT_MULTILINE) { + flags |= CapabilityFlag::Multiline; + } + if (purpose == ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_ALPHA) { + flags |= CapabilityFlag::Alpha; + } + if (purpose == ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_DIGITS) { + flags |= CapabilityFlag::Digit; + } + if (purpose == ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_NUMBER) { + flags |= CapabilityFlag::Number; + } + if (purpose == ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_PASSWORD) { + flags |= CapabilityFlag::Password; + } + if (purpose == ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_PHONE) { + flags |= CapabilityFlag::Dialable; + } + if (purpose == ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_URL) { + flags |= CapabilityFlag::Url; + } + if (purpose == ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_EMAIL) { + flags |= CapabilityFlag::Email; + } + if (purpose == ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_NAME) { + flags |= CapabilityFlag::Name; + } + if (purpose == ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_DATE) { + flags |= CapabilityFlag::Date; + } + if (purpose == ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_TIME) { + flags |= CapabilityFlag::Time; + } + if (purpose == ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_DATETIME) { + flags |= CapabilityFlag::Date; + flags |= CapabilityFlag::Time; + } + if (purpose == ZWP_TEXT_INPUT_V1_CONTENT_PURPOSE_TERMINAL) { + flags |= CapabilityFlag::Terminal; + } + setCapabilityFlags(flags); +} +void WaylandIMInputContextV1::invokeActionCallback(struct zwp_input_method_context_v1 *inputContext, uint32_t button, + uint32_t index) { + assert(ic_ == inputContext); + FCITX_UNUSED(button); + FCITX_UNUSED(index); +} +void WaylandIMInputContextV1::commitStateCallback(struct zwp_input_method_context_v1 *inputContext, uint32_t serial) { + assert(ic_ == inputContext); + serial_ = serial; +} +void WaylandIMInputContextV1::preferredLanguageCallback(struct zwp_input_method_context_v1 *inputContext, + const char *language) { + assert(ic_ == inputContext); + FCITX_UNUSED(language); +} + +void WaylandIMInputContextV1::keymapCallback(struct wl_keyboard *keyboard, uint32_t format, int32_t fd, uint32_t size) { + if (!server_->context_) { + server_->context_.reset(xkb_context_new(XKB_CONTEXT_NO_FLAGS)); + xkb_context_set_log_level(server_->context_.get(), XKB_LOG_LEVEL_CRITICAL); + } + + if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { + close(fd); + return; + } + + if (server_->keymap_) { + server_->keymap_.reset(); + } + + std::vector buffer; + buffer.resize(1024); + int bufferPtr = 0; + int readSize; + while ((readSize = read(fd, &buffer.data()[bufferPtr], buffer.size() - bufferPtr)) > 0) { + bufferPtr += readSize; + if (bufferPtr == buffer.size()) { + buffer.resize(buffer.size() * 2); + } + } + buffer[bufferPtr] = 0; + + server_->keymap_.reset(xkb_keymap_new_from_string (server_->context_.get(), + buffer.data(), + XKB_KEYMAP_FORMAT_TEXT_V1, + XKB_KEYMAP_COMPILE_NO_FLAGS)); + + close(fd); + + if (!server_->keymap_) { + return; + } + + server_->state_.reset(xkb_state_new (server_->keymap_.get())); + if (!server_->state_) { + server_->keymap_.reset(); + return; + } + + server_->stateMask_.shift_mask = + 1 << xkb_keymap_mod_get_index(server_->keymap_.get(), "Shift"); + server_->stateMask_.lock_mask = + 1 << xkb_keymap_mod_get_index(server_->keymap_.get(), "Lock"); + server_->stateMask_.control_mask = + 1 << xkb_keymap_mod_get_index(server_->keymap_.get(), "Control"); + server_->stateMask_.mod1_mask = + 1 << xkb_keymap_mod_get_index(server_->keymap_.get(), "Mod1"); + server_->stateMask_.mod2_mask = + 1 << xkb_keymap_mod_get_index(server_->keymap_.get(), "Mod2"); + server_->stateMask_.mod3_mask = + 1 << xkb_keymap_mod_get_index(server_->keymap_.get(), "Mod3"); + server_->stateMask_.mod4_mask = + 1 << xkb_keymap_mod_get_index(server_->keymap_.get(), "Mod4"); + server_->stateMask_.mod5_mask = + 1 << xkb_keymap_mod_get_index(server_->keymap_.get(), "Mod5"); + server_->stateMask_.super_mask = + 1 << xkb_keymap_mod_get_index(server_->keymap_.get(), "Super"); + server_->stateMask_.hyper_mask = + 1 << xkb_keymap_mod_get_index(server_->keymap_.get(), "Hyper"); + server_->stateMask_.meta_mask = + 1 << xkb_keymap_mod_get_index(server_->keymap_.get(), "Meta"); +} + +void WaylandIMInputContextV1::keyCallback(struct wl_keyboard *keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { + if (!server_->state_) { + return; + } + + // EVDEV OFFSET + uint32_t code = key + 8; + const xkb_keysym_t *syms; + KeyEvent event( + this, + Key(static_cast(xkb_state_key_get_one_sym(server_->state_.get(), code)), server_->modifiers_), + state == WL_KEYBOARD_KEY_STATE_RELEASED, code, time); + + if (!keyEvent(event)) { + zwp_input_method_context_v1_key(ic_, + serial, + time, + key, + state); + } +} +void WaylandIMInputContextV1::modifiersCallback(struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { + + xkb_mod_mask_t mask; + + xkb_state_update_mask (server_->state_.get(), mods_depressed, + mods_latched, mods_locked, 0, 0, group); + mask = xkb_state_serialize_mods (server_->state_.get(), + static_cast(XKB_STATE_DEPRESSED | + XKB_STATE_LATCHED)); + + server_->modifiers_ = 0; + if (mask & server_->stateMask_.shift_mask) + server_->modifiers_ |= KeyState::Shift; + if (mask & server_->stateMask_.lock_mask) + server_->modifiers_ |= KeyState::CapsLock; + if (mask & server_->stateMask_.control_mask) + server_->modifiers_ |= KeyState::Ctrl; + if (mask & server_->stateMask_.mod1_mask) + server_->modifiers_ |= KeyState::Alt; + if (mask & server_->stateMask_.super_mask) + server_->modifiers_ |= KeyState::Super; + if (mask & server_->stateMask_.hyper_mask) + server_->modifiers_ |= KeyState::Hyper; + if (mask & server_->stateMask_.meta_mask) + server_->modifiers_ |= KeyState::Meta; + + zwp_input_method_context_v1_modifiers(ic_, serial, + mods_depressed, mods_depressed, + mods_latched, group); +} + +WaylandIMModule::WaylandIMModule(Instance *instance) + : instance_(instance), createdCallback_(wayland()->call( + [this](const std::string &name, wl_display *display, FocusGroup *group) { + WaylandIMServer *server = new WaylandIMServer(display, group, name, this); + servers_[name].reset(server); + })), + closedCallback_(wayland()->call( + [this](const std::string &name, wl_display *) { servers_.erase(name); })) {} + +AddonInstance *WaylandIMModule::wayland() { + auto &addonManager = instance_->addonManager(); + return addonManager.addon("wayland"); +} + +WaylandIMModule::~WaylandIMModule() {} +} diff --git a/src/frontend/waylandim/waylandim.h b/src/frontend/waylandim/waylandim.h new file mode 100644 index 000000000..8abe18f52 --- /dev/null +++ b/src/frontend/waylandim/waylandim.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2016~2016 by CSSlayer + * wengxt@gmail.com + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, + * see . + */ +#ifndef _FCITX_FRONTEND_WAYLANDIM_WAYLANDIM_H_ +#define _FCITX_FRONTEND_WAYLANDIM_WAYLANDIM_H_ + +#include +#include +#include +#include +#include "modules/wayland/wayland_public.h" +#include "wayland-input-method-unstable-v1-client-protocol.h" + +namespace fcitx { + +class WaylandIMModule; +class WaylandIMServer; + +class WaylandIMModule : public AddonInstance { +public: + WaylandIMModule(Instance *instance); + ~WaylandIMModule(); + + AddonInstance *wayland(); + Instance *instance() { return instance_; } + +private: + Instance *instance_; + std::unordered_map> servers_; + std::unique_ptr> createdCallback_; + std::unique_ptr> closedCallback_; +}; + +class WaylandIMModuleFactory : public AddonFactory { +public: + AddonInstance *create(AddonManager *manager) override { return new WaylandIMModule(manager->instance()); } +}; +} + +FCITX_ADDON_FACTORY(fcitx::WaylandIMModuleFactory); + +#endif // _FCITX_FRONTEND_WAYLANDIM_WAYLANDIM_H_ diff --git a/src/frontend/xim/xim.cpp b/src/frontend/xim/xim.cpp index e63e92619..26a040a49 100644 --- a/src/frontend/xim/xim.cpp +++ b/src/frontend/xim/xim.cpp @@ -260,7 +260,7 @@ void XIMServer::callback(xcb_im_client_t *client, xcb_im_input_context_t *xic, c KeyEvent event( ic, Key(static_cast(xkb_state_key_get_one_sym(xkbState, xevent->detail)), KeyStates(xevent->state)), (xevent->response_type & ~0x80) == XCB_KEY_RELEASE, xevent->detail, xevent->time); - std::cout << event.key().toString() << std::endl; + if (!ic->keyEvent(event)) { xcb_im_forward_event(im(), xic, xevent); } diff --git a/src/lib/fcitx-utils/utf8.h b/src/lib/fcitx-utils/utf8.h index 7c797daa8..6d5fb2f0d 100644 --- a/src/lib/fcitx-utils/utf8.h +++ b/src/lib/fcitx-utils/utf8.h @@ -30,6 +30,10 @@ FCITXUTILS_EXPORT inline std::string::size_type lengthN(const std::string &s, si return fcitx_utf8_strnlen(s.c_str(), n); } +FCITXUTILS_EXPORT inline bool validate(const std::string &s) { + return fcitx_utf8_check_string(s.c_str()); +} + FCITXUTILS_EXPORT std::string UCS4ToUTF8(uint32_t code); FCITXUTILS_EXPORT inline uint32_t getCharValidated(const std::string &s, std::string::size_type off = 0, diff --git a/src/lib/fcitx/candidatelist.h b/src/lib/fcitx/candidatelist.h new file mode 100644 index 000000000..0220f4548 --- /dev/null +++ b/src/lib/fcitx/candidatelist.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016~2016 by CSSlayer + * wengxt@gmail.com + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, + * see . + */ +#ifndef _FCITX_CANDIDATELIST_H_ +#define _FCITX_CANDIDATELIST_H_ + +#include +#include + +namespace fcitx +{ + +class CandidateWord : public DynamicTrackableObject { +public: + CandidateWord(); + + void remove(); + + bool isPlaceHolder() const; + void setPlaceHolder(bool isPlaceHolder); + + Text &text(); + + FCITX_DECLARE_SIGNAL(CandidateWord, Selected, void()); + +private: + +}; + +class CandidateListPrivate; + +class CandidateList { +public: + CandidateList(); + ~CandidateList(); + + CandidateWord &append(); + CandidateWord &insert(int idx); + void clear(); + +private: + std::unique_ptr d_ptr; + FCITX_DECLARE_PRIVATE(CandidateList); +}; + +} + +#endif // _FCITX_CANDIDATELIST_H_ diff --git a/src/lib/fcitx/inputcontext.h b/src/lib/fcitx/inputcontext.h index 1307c073c..79c098865 100644 --- a/src/lib/fcitx/inputcontext.h +++ b/src/lib/fcitx/inputcontext.h @@ -63,6 +63,12 @@ enum class CapabilityFlag : uint64_t { Alpha = (1 << 21), Name = (1 << 22), RelativeRect = (1 << 23), + Terminal = (1 << 24), + Date = (1 << 25), + Time = (1 << 26), + Multiline = (1 << 27), + Sensitive = (1 << 28), + HiddenText = (1 << 29), }; enum class FocusGroupType { Global, Local, Independent }; diff --git a/src/lib/fcitx/userinterfacemanager.h b/src/lib/fcitx/userinterfacemanager.h index ce26ee2e5..9a4a21afa 100644 --- a/src/lib/fcitx/userinterfacemanager.h +++ b/src/lib/fcitx/userinterfacemanager.h @@ -22,6 +22,7 @@ #include "fcitxcore_export.h" #include #include +#include #include namespace fcitx { @@ -34,6 +35,9 @@ class FCITXCORE_EXPORT UserInterfaceManager { virtual ~UserInterfaceManager(); void init(); + + Menu *mainPanel(); + private: std::unique_ptr d_ptr; diff --git a/src/modules/CMakeLists.txt b/src/modules/CMakeLists.txt index d14aa4ca1..e337c57ec 100644 --- a/src/modules/CMakeLists.txt +++ b/src/modules/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(xcb) add_subdirectory(dbus) +add_subdirectory(wayland) diff --git a/src/modules/wayland/CMakeLists.txt b/src/modules/wayland/CMakeLists.txt new file mode 100644 index 000000000..110935d4d --- /dev/null +++ b/src/modules/wayland/CMakeLists.txt @@ -0,0 +1,5 @@ +add_library(wayland MODULE waylandmodule.cpp) +target_link_libraries(wayland Fcitx5::Core Wayland::Client) +set_target_properties(wayland PROPERTIES PREFIX "") +install(TARGETS wayland DESTINATION "${FCITX_INSTALL_ADDONDIR}") +install(FILES wayland.conf DESTINATION "${FCITX_INSTALL_PKGDATADIR}/addon") diff --git a/src/modules/wayland/wayland.conf b/src/modules/wayland/wayland.conf new file mode 100644 index 000000000..b7791a969 --- /dev/null +++ b/src/modules/wayland/wayland.conf @@ -0,0 +1,6 @@ +[Addon] +Name=wayland +Type=SharedLibrary +Library=wayland +Category=Module + diff --git a/src/modules/wayland/wayland_public.h b/src/modules/wayland/wayland_public.h new file mode 100644 index 000000000..c5df93763 --- /dev/null +++ b/src/modules/wayland/wayland_public.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2016~2016 by CSSlayer + * wengxt@gmail.com + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, + * see . + */ +#ifndef _FCITX_MODULES_WAYLAND_WAYLAND_PUBLIC_H_ +#define _FCITX_MODULES_WAYLAND_WAYLAND_PUBLIC_H_ + +#include +#include +#include +#include +#include + +namespace fcitx { + +typedef std::function WaylandConnectionCreated; +typedef std::function WaylandConnectionClosed; + +} + +FCITX_ADDON_DECLARE_FUNCTION(WaylandModule, addConnectionCreatedCallback, + HandlerTableEntry *(WaylandConnectionCreated)); +FCITX_ADDON_DECLARE_FUNCTION(WaylandModule, addConnectionClosedCallback, + HandlerTableEntry *(WaylandConnectionClosed)); + +#endif // _FCITX_MODULES_WAYLAND_WAYLAND_PUBLIC_H_ diff --git a/src/modules/wayland/waylandmodule.cpp b/src/modules/wayland/waylandmodule.cpp new file mode 100644 index 000000000..f510758d1 --- /dev/null +++ b/src/modules/wayland/waylandmodule.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2016~2016 by CSSlayer + * wengxt@gmail.com + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, + * see . + */ + +#include "waylandmodule.h" +#include +#include + +namespace fcitx { + +WaylandConnection::WaylandConnection(WaylandModule *wayland, const char *name) : + parent_(wayland), name_(name ? name : ""), display_(nullptr, wl_display_disconnect) +{ + display_.reset(wl_display_connect(name)); + if (!display_) { + throw std::runtime_error("Failed to open wayland connection"); + } + + auto &eventLoop = parent_->instance()->eventLoop(); + ioEvent_.reset(eventLoop.addIOEvent(wl_display_get_fd(display_.get()), IOEventFlag::In, [this](EventSource *, int, IOEventFlags) { + onIOEvent(); + return true; + })); + + group_ = new FocusGroup(wayland->instance()->inputContextManager()); + +} + +WaylandConnection::~WaylandConnection() { +} + +void WaylandConnection::onIOEvent() { + if (wl_display_dispatch(display_.get()) == -1) { + error_ = wl_display_get_error(display_.get()); + if (error_ != 0) { + display_.reset(); + parent_->removeDisplay(name_); + } + } +} + +WaylandModule::WaylandModule(fcitx::Instance* instance): instance_(instance) { + openDisplay(""); +} + +void WaylandModule::openDisplay(const std::string &name) { + const char *displayString = nullptr; + if (!name.empty()) { + displayString = name.c_str(); + } + + try { + auto iter = + conns_.emplace(std::piecewise_construct, std::forward_as_tuple(name), std::forward_as_tuple(this, displayString)); + onConnectionCreated(iter.first->second); + } catch (const std::exception &e) { + } +} + +void WaylandModule::removeDisplay(const std::string &name) { + auto iter = conns_.find(name); + if (iter != conns_.end()) { + conns_.erase(iter); + } +} + +HandlerTableEntry *WaylandModule::addConnectionCreatedCallback(WaylandConnectionCreated callback) { + auto result = createdCallbacks_.add(callback); + + for (auto &p : conns_) { + auto &conn = p.second; + callback(conn.name(), conn.display(), conn.focusGroup()); + } + return result; +} + +HandlerTableEntry *WaylandModule::addConnectionClosedCallback(WaylandConnectionClosed callback) { + return closedCallbacks_.add(callback); +} + +void WaylandModule::onConnectionCreated(WaylandConnection &conn) { + for (auto &callback : createdCallbacks_.view()) { + callback(conn.name(), conn.display(), conn.focusGroup()); + } +} + +void WaylandModule::onConnectionClosed(WaylandConnection &conn) { + for (auto &callback : closedCallbacks_.view()) { + callback(conn.name(), conn.display()); + } +} + + + +} diff --git a/src/modules/wayland/waylandmodule.h b/src/modules/wayland/waylandmodule.h new file mode 100644 index 000000000..e819e2835 --- /dev/null +++ b/src/modules/wayland/waylandmodule.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2016~2016 by CSSlayer + * wengxt@gmail.com + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; see the file COPYING. If not, + * see . + */ +#ifndef _FCITX_MODULES_WAYLAND_WAYLANDMODULE_H_ +#define _FCITX_MODULES_WAYLAND_WAYLANDMODULE_H_ + +#include +#include +#include +#include +#include +#include +#include "wayland_public.h" +#include + +namespace fcitx { + +class WaylandModule; + +class WaylandConnection { +public: + WaylandConnection(WaylandModule *wayland, const char *name); + ~WaylandConnection(); + + const std::string &name() const { return name_; } + wl_display *display() const { return display_.get(); } + FocusGroup *focusGroup() const { return group_; } + +private: + void onIOEvent(); + + WaylandModule *parent_; + std::string name_; + std::unique_ptr display_; + std::unique_ptr ioEvent_; + FocusGroup *group_ = nullptr; + int error_ = 0; +}; + +class WaylandModule : public AddonInstance { +public: + WaylandModule(Instance *instance); + Instance *instance() { return instance_; } + + void openDisplay(const std::string &display); + void removeDisplay(const std::string &name); + + HandlerTableEntry *addConnectionCreatedCallback(WaylandConnectionCreated callback); + HandlerTableEntry *addConnectionClosedCallback(WaylandConnectionClosed callback); +private: + void onConnectionCreated(WaylandConnection &conn); + void onConnectionClosed(WaylandConnection &conn); + + + Instance *instance_; + std::unordered_map conns_; + HandlerTable createdCallbacks_; + HandlerTable closedCallbacks_; + FCITX_ADDON_EXPORT_FUNCTION(WaylandModule, addConnectionCreatedCallback); + FCITX_ADDON_EXPORT_FUNCTION(WaylandModule, addConnectionClosedCallback); +}; + +class WaylandModuleFactory : public AddonFactory { +public: + AddonInstance *create(AddonManager *manager) override { return new WaylandModule(manager->instance()); } +}; +} + +FCITX_ADDON_FACTORY(fcitx::WaylandModuleFactory); + +#endif // _FCITX_MODULES_WAYLAND_WAYLANDMODULE_H_