#include "ime.hpp" #include "wlserver.hpp" #include "log.hpp" #include #include #include extern "C" { #define delete delete_ #include #include #include #include #undef delete } /* The C/C++ standard library doesn't expose a reliable way to decode UTF-8, * so we need to ship our own implementation. Yay for locales. */ static const uint32_t UTF8_INVALID = 0xFFFD; static size_t utf8_size(const char *str) { uint8_t u8 = (uint8_t)str[0]; if ((u8 & 0x80) == 0) { return 1; } else if ((u8 & 0xE0) == 0xC0) { return 2; } else if ((u8 & 0xF0) == 0xE0) { return 3; } else if ((u8 & 0xF8) == 0xF0) { return 4; } else { return 0; } } static uint32_t utf8_decode(const char **str_ptr) { const char *str = *str_ptr; size_t size = utf8_size(str); if (size == 0) { *str_ptr = &str[1]; return UTF8_INVALID; } *str_ptr = &str[size]; const uint32_t masks[] = { 0x7F, 0x1F, 0x0F, 0x07 }; uint32_t ret = (uint32_t)str[0] & masks[size - 1]; for (size_t i = 1; i < size; i++) { ret <<= 6; ret |= str[i] & 0x3F; } return ret; } struct wlserver_input_method_key { xkb_keycode_t keycode; xkb_keysym_t keysym; }; struct wlserver_input_method { struct wlr_input_method_v2 *input_method; struct wlserver_t *server; // Used to send emulated input events struct wlr_keyboard keyboard; struct wlr_input_device keyboard_device; std::unordered_map keys; struct wl_listener commit; struct wl_listener destroy; }; static LogScope ime_log("ime"); static struct wlserver_input_method *active_input_method = nullptr; static xkb_keycode_t keycode_from_ch(struct wlserver_input_method *ime, uint32_t ch) { if (ime->keys.count(ch) > 0) { return ime->keys[ch].keycode; } xkb_keysym_t keysym = xkb_utf32_to_keysym(ch); if (keysym == XKB_KEY_NoSymbol) { return XKB_KEYCODE_INVALID; } // Add 1 because some clients don't handle correctly zero keycodes xkb_keycode_t keycode = ime->keys.size() + 1; ime->keys[ch] = (struct wlserver_input_method_key){ keycode, keysym }; return keycode; } static struct xkb_keymap *generate_keymap(struct wlserver_input_method *ime) { uint32_t keycode_offset = 8; char *str = NULL; size_t str_size = 0; FILE *f = open_memstream(&str, &str_size); fprintf(f, "xkb_keymap {\n" "\n" "xkb_keycodes \"(unnamed)\" {\n" " minimum = %u;\n" " maximum = %lu;\n", keycode_offset + 1, ime->keys.size() + keycode_offset + 1 ); for (const auto kv : ime->keys) { xkb_keycode_t keycode = kv.second.keycode; fprintf(f, " = %u;\n", keycode, keycode + keycode_offset); } // TODO: should we really be including "complete" here? fprintf(f, "};\n" "\n" "xkb_types \"(unnamed)\" { include \"complete\" };\n" "\n" "xkb_compatibility \"(unnamed)\" { include \"complete\" };\n" "\n" "xkb_symbols \"(unnamed)\" {\n" ); for (const auto kv : ime->keys) { xkb_keycode_t keycode = kv.second.keycode; xkb_keysym_t keysym = kv.second.keysym; char keysym_name[256]; int ret = xkb_keysym_get_name(keysym, keysym_name, sizeof(keysym_name)); if (ret <= 0) { ime_log.errorf("xkb_keysym_get_name failed for keysym %u", keysym); return nullptr; } fprintf(f, " key {[ %s ]};\n", keycode, keysym_name); } fprintf(f, "};\n" "\n" "};\n" ); fclose(f); struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); struct xkb_keymap *keymap = xkb_keymap_new_from_buffer(context, str, str_size, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); xkb_context_unref(context); free(str); return keymap; } static void type_text(struct wlserver_input_method *ime, const char *text) { ime->keys.clear(); xkb_keycode_t void_keycode = 1; ime->keys[0] = (struct wlserver_input_method_key){ void_keycode, XKB_KEY_VoidSymbol }; std::vector keycodes; while (text[0] != '\0') { uint32_t ch = utf8_decode(&text); xkb_keycode_t keycode = keycode_from_ch(ime, ch); if (keycode == XKB_KEYCODE_INVALID) { ime_log.errorf("warning: cannot type character U+%X\n", ch); continue; } keycodes.push_back(keycode); } struct xkb_keymap *keymap = generate_keymap(ime); if (keymap == nullptr) { ime_log.errorf("failed to generate keymap\n"); return; } wlr_keyboard_set_keymap(&ime->keyboard, keymap); xkb_keymap_unref(keymap); struct wlr_seat *seat = ime->server->wlr.seat; wlr_seat_set_keyboard(seat, &ime->keyboard_device); // Note: Xwayland doesn't care about the time field of the events // Hack: Chromium doesn't accept some characters like "#" or "é" when // they're the first one of the string. Send a void keysym to workaround // this. wlr_seat_keyboard_notify_key(seat, 0, void_keycode, WL_KEYBOARD_KEY_STATE_PRESSED); wlr_seat_keyboard_notify_key(seat, 0, void_keycode, WL_KEYBOARD_KEY_STATE_RELEASED); for (size_t i = 0; i < keycodes.size(); i++) { wlr_seat_keyboard_notify_key(seat, 0, keycodes[i], WL_KEYBOARD_KEY_STATE_PRESSED); wlr_seat_keyboard_notify_key(seat, 0, keycodes[i], WL_KEYBOARD_KEY_STATE_RELEASED); } } static void ime_handle_commit(struct wl_listener *l, void *data) { struct wlserver_input_method *ime = wl_container_of(l, ime, commit); const char *text = ime->input_method->current.commit_text; if (text != nullptr) { type_text(ime, text); } } static void ime_handle_destroy(struct wl_listener *l, void *data) { struct wlserver_input_method *ime = wl_container_of(l, ime, destroy); active_input_method = nullptr; wlr_input_device_destroy(&ime->keyboard_device); wl_list_remove(&ime->commit.link); wl_list_remove(&ime->destroy.link); delete ime; } static void keyboard_destroy(struct wlr_keyboard *kyeboard) {} static const struct wlr_keyboard_impl keyboard_impl = { .destroy = keyboard_destroy, }; static void keyboard_device_destroy(struct wlr_input_device *dev) {} static const struct wlr_input_device_impl keyboard_device_impl = { .destroy = keyboard_device_destroy, }; static void wlserver_new_input_method(struct wl_listener *l, void *data) { struct wlserver_t *wlserver = wl_container_of(l, wlserver, new_input_method); struct wlr_input_method_v2 *wlr_ime = (struct wlr_input_method_v2 *)data; if (active_input_method != nullptr) { wlr_input_method_v2_send_unavailable(wlr_ime); return; } struct wlserver_input_method *ime = new wlserver_input_method(); ime->input_method = wlr_ime; ime->server = wlserver; ime->commit.notify = ime_handle_commit; wl_signal_add(&wlr_ime->events.commit, &ime->commit); ime->destroy.notify = ime_handle_destroy; wl_signal_add(&wlr_ime->events.destroy, &ime->destroy); wlr_keyboard_init(&ime->keyboard, &keyboard_impl); wlr_input_device_init(&ime->keyboard_device, WLR_INPUT_DEVICE_KEYBOARD, &keyboard_device_impl, "ime", 0, 0); ime->keyboard_device.keyboard = &ime->keyboard; wlr_keyboard_set_repeat_info(&ime->keyboard, 0, 0); wlr_input_method_v2_send_activate(wlr_ime); wlr_input_method_v2_send_done(wlr_ime); active_input_method = ime; } void create_ime_manager(struct wlserver_t *wlserver) { struct wlr_input_method_manager_v2 *ime_manager = wlr_input_method_manager_v2_create(wlserver->display); wlserver->new_input_method.notify = wlserver_new_input_method; wl_signal_add(&ime_manager->events.input_method, &wlserver->new_input_method); }