diff --git a/flutter/shell/platform/tizen/BUILD.gn b/flutter/shell/platform/tizen/BUILD.gn index 89c2d30..53d08a5 100644 --- a/flutter/shell/platform/tizen/BUILD.gn +++ b/flutter/shell/platform/tizen/BUILD.gn @@ -198,6 +198,10 @@ template("embedder") { defines += [ "CLIPBOARD_SUPPORT" ] } + if (api_version != "6.0" && api_version != "6.5" && api_version != "7.0") { + defines += [ "DIRECTIONAL_KEY_REPEAT_SUPPORT" ] + } + configs += [ ":flutter_tizen_config", "//flutter/shell/platform/common:desktop_library_implementation", diff --git a/flutter/shell/platform/tizen/tizen_window_ecore_wl2.cc b/flutter/shell/platform/tizen/tizen_window_ecore_wl2.cc index 3d22b61..0fb2e7b 100644 --- a/flutter/shell/platform/tizen/tizen_window_ecore_wl2.cc +++ b/flutter/shell/platform/tizen/tizen_window_ecore_wl2.cc @@ -4,6 +4,9 @@ #include "tizen_window_ecore_wl2.h" +#include +#include + #ifdef TV_PROFILE #include #include @@ -325,6 +328,28 @@ void TizenWindowEcoreWl2::ShowUnsupportedToast() { #endif void TizenWindowEcoreWl2::RegisterEventHandlers() { + for (int event_type : + {ECORE_EVENT_DEVICE_DEL, ECORE_WL2_EVENT_SEAT_KEYMAP_CHANGED, + ECORE_WL2_EVENT_SEAT_KEYBOARD_REPEAT_CHANGED}) { + ecore_event_handlers_.push_back(ecore_event_handler_add( + event_type, + [](void* data, int type, void* event) -> Eina_Bool { + static_cast(data)->ResetKeyRepeat(); + return ECORE_CALLBACK_PASS_ON; + }, + this)); + } + ecore_event_handlers_.push_back(ecore_event_handler_add( + ECORE_WL2_EVENT_FOCUS_OUT, + [](void* data, int type, void* event) -> Eina_Bool { + auto* self = static_cast(data); + auto* focus_event = static_cast(event); + if (focus_event->window == self->GetWindowId()) { + self->ResetKeyRepeat(); + } + return ECORE_CALLBACK_PASS_ON; + }, + this)); ecore_event_handlers_.push_back(ecore_event_handler_add( ECORE_WL2_EVENT_WINDOW_ROTATE, [](void* data, int type, void* event) -> Eina_Bool { @@ -482,11 +507,15 @@ void TizenWindowEcoreWl2::RegisterEventHandlers() { if (self->view_delegate_) { auto* key_event = reinterpret_cast(event); if (key_event->window == self->GetWindowId()) { + const bool is_modifier = self->UpdateKeyModifiers(*key_event, true); bool handled = false; if (self->input_method_context_->ShouldFilterKey(key_event->key)) { handled = self->input_method_context_->HandleEcoreEventKey( key_event, true); } + if (!is_modifier) { + self->StartKeyRepeat(*key_event, handled); + } if (!handled) { self->view_delegate_->OnKey( key_event->key, key_event->string, key_event->compose, @@ -507,6 +536,10 @@ void TizenWindowEcoreWl2::RegisterEventHandlers() { if (self->view_delegate_) { auto* key_event = reinterpret_cast(event); if (key_event->window == self->GetWindowId()) { + self->UpdateKeyModifiers(*key_event, false); + if (self->repeat_scan_code_ == key_event->keycode) { + self->StopKeyRepeat(); + } bool handled = false; if (self->input_method_context_->ShouldFilterKey(key_event->key)) { handled = self->input_method_context_->HandleEcoreEventKey( @@ -526,12 +559,121 @@ void TizenWindowEcoreWl2::RegisterEventHandlers() { } void TizenWindowEcoreWl2::UnregisterEventHandlers() { + ResetKeyRepeat(); for (Ecore_Event_Handler* handler : ecore_event_handlers_) { ecore_event_handler_del(handler); } ecore_event_handlers_.clear(); } +bool TizenWindowEcoreWl2::UpdateKeyModifiers(const Ecore_Event_Key& event, + bool is_down) { + key_modifiers_ = event.modifiers; + const auto modifier = event.key + ? ecore_event_update_modifier(event.key, nullptr, 0) + : ECORE_NONE; + const uint32_t bit = ecore_event_modifier_mask(modifier); + constexpr uint32_t kHeldModifiers = + ECORE_EVENT_MODIFIER_SHIFT | ECORE_EVENT_MODIFIER_CTRL | + ECORE_EVENT_MODIFIER_ALT | ECORE_EVENT_MODIFIER_WIN | + ECORE_EVENT_MODIFIER_ALTGR; + if (bit & kHeldModifiers) { + if (is_down) { + pressed_modifiers_[event.keycode] = bit; + } else { + pressed_modifiers_.erase(event.keycode); + } + key_modifiers_ &= ~bit; + for (const auto& entry : pressed_modifiers_) { + key_modifiers_ |= entry.second; + } + } + return modifier != ECORE_NONE; +} + +void TizenWindowEcoreWl2::StartKeyRepeat(const Ecore_Event_Key& event, + bool imf_handled) { + bool native_repeat = + repeat_scan_code_ != 0 && repeat_scan_code_ == event.keycode; +#ifdef DIRECTIONAL_KEY_REPEAT_SUPPORT + native_repeat |= (event.event_flags & ECORE_EVENT_FLAG_REPEAT) != 0; +#endif + StopKeyRepeat(); + const std::string key = event.key ? event.key : ""; + const bool is_arrow = + key == "Up" || key == "Down" || key == "Left" || key == "Right"; + if (imf_handled || !is_arrow || !event.dev || event.keycode == 0 || + ecore_device_class_get(event.dev) != ECORE_DEVICE_CLASS_KEYBOARD || + ecore_device_subclass_get(event.dev) == ECORE_DEVICE_SUBCLASS_REMOCON) { + return; + } + repeat_scan_code_ = event.keycode; + if (native_repeat) { + return; + } + repeat_key_ = key; + const char* name = ecore_device_name_get(event.dev); + repeat_device_name_ = name ? name : ""; + ScheduleKeyRepeat(true); +} + +void TizenWindowEcoreWl2::StopKeyRepeat() { + if (key_repeat_timer_id_) { + g_source_remove(key_repeat_timer_id_); + key_repeat_timer_id_ = 0; + } + repeat_scan_code_ = 0; +} + +void TizenWindowEcoreWl2::ResetKeyRepeat() { + StopKeyRepeat(); + key_modifiers_ = 0; + pressed_modifiers_.clear(); +} + +void TizenWindowEcoreWl2::ScheduleKeyRepeat(bool initial) { + auto* input = ecore_wl2_window_input_get(ecore_wl2_window_); + if (!input) { + return; + } + double rate = 0; + double delay = 0; + bool have_repeat = false; +#ifdef DIRECTIONAL_KEY_REPEAT_SUPPORT + have_repeat = (repeat_key_ == "Left" || repeat_key_ == "Right") + ? ecore_wl2_input_keyboard_horizontal_way_repeat_get( + input, &rate, &delay) + : ecore_wl2_input_keyboard_vertical_way_repeat_get( + input, &rate, &delay); +#endif + if (!have_repeat) { + have_repeat = ecore_wl2_input_keyboard_repeat_get(input, &rate, &delay); + } + const double interval_ms = + std::ceil(rate * 1000 + (initial ? delay * 1000 : 0)); + if (!have_repeat || !std::isfinite(rate) || !std::isfinite(delay) || + rate <= 0 || delay < 0 || !std::isfinite(interval_ms) || + interval_ms > G_MAXUINT) { + return; + } + key_repeat_timer_id_ = g_timeout_add( + static_cast(std::max(interval_ms, 1.0)), + [](gpointer data) -> gboolean { + auto* self = static_cast(data); + self->key_repeat_timer_id_ = 0; + self->ScheduleKeyRepeat(false); + if (self->key_repeat_timer_id_ && self->view_delegate_) { + const auto key = self->repeat_key_; + const auto device_name = self->repeat_device_name_; + self->view_delegate_->OnKey( + key.c_str(), nullptr, nullptr, self->key_modifiers_, + self->repeat_scan_code_, device_name.c_str(), true); + } + return G_SOURCE_REMOVE; + }, + this); +} + void TizenWindowEcoreWl2::DestroyWindow() { if (ecore_wl2_egl_window_) { ecore_wl2_egl_window_destroy(ecore_wl2_egl_window_); diff --git a/flutter/shell/platform/tizen/tizen_window_ecore_wl2.h b/flutter/shell/platform/tizen/tizen_window_ecore_wl2.h index 3cc1a9d..656b79b 100644 --- a/flutter/shell/platform/tizen/tizen_window_ecore_wl2.h +++ b/flutter/shell/platform/tizen/tizen_window_ecore_wl2.h @@ -7,9 +7,11 @@ #define EFL_BETA_API_SUPPORT #include +#include #include #include +#include #include #include @@ -89,6 +91,16 @@ class TizenWindowEcoreWl2 : public TizenWindow { void PrepareInputMethod(); + bool UpdateKeyModifiers(const Ecore_Event_Key& event, bool is_down); + + void StartKeyRepeat(const Ecore_Event_Key& event, bool imf_handled); + + void StopKeyRepeat(); + + void ResetKeyRepeat(); + + void ScheduleKeyRepeat(bool initial); + Ecore_Wl2_Display* ecore_wl2_display_ = nullptr; Ecore_Wl2_Window* ecore_wl2_window_ = nullptr; Ecore_Wl2_Egl_Window* ecore_wl2_egl_window_ = nullptr; @@ -97,6 +109,12 @@ class TizenWindowEcoreWl2 : public TizenWindow { std::vector ecore_event_handlers_; tizen_policy* tizen_policy_ = nullptr; uint32_t resource_id_ = 0; + guint key_repeat_timer_id_ = 0; + uint32_t repeat_scan_code_ = 0; + std::string repeat_key_; + std::string repeat_device_name_; + uint32_t key_modifiers_ = 0; + std::map pressed_modifiers_; #ifdef TV_PROFILE bool pointing_device_support_ = true; diff --git a/flutter/shell/platform/tizen/tizen_window_tcore_wl.cc b/flutter/shell/platform/tizen/tizen_window_tcore_wl.cc index 29651fd..67057e8 100644 --- a/flutter/shell/platform/tizen/tizen_window_tcore_wl.cc +++ b/flutter/shell/platform/tizen/tizen_window_tcore_wl.cc @@ -4,6 +4,10 @@ #include "tizen_window_tcore_wl.h" +#include +#include +#include + #ifdef TV_PROFILE #include #include @@ -366,6 +370,25 @@ void TizenWindowTcoreWl::AddEventListener(tizen_core_wl_event_type_e type, } void TizenWindowTcoreWl::RegisterEventHandlers() { + for (auto type : + {TIZEN_CORE_WL_EVENT_DEVICE_DEL, TIZEN_CORE_WL_EVENT_SEAT_KEYMAP_CHANGED, + TIZEN_CORE_WL_EVENT_SEAT_KEYREPEAT_CHANGED}) { + AddEventListener( + type, [](void* event, tizen_core_wl_event_type_e type, void* data) { + static_cast(data)->ResetKeyRepeat(); + }); + } + AddEventListener( + TIZEN_CORE_WL_EVENT_FOCUS_OUT, + [](void* event, tizen_core_wl_event_type_e type, void* data) { + auto* self = static_cast(data); + auto* ev = static_cast(event); + tizen_core_wl_window_h window = nullptr; + tizen_core_wl_event_input_base_get_window(ev, &window); + if (window == self->tcore_wl_window_) { + self->ResetKeyRepeat(); + } + }); AddEventListener( TIZEN_CORE_WL_EVENT_WINDOW_ROTATION, [](void* event, tizen_core_wl_event_type_e type, void* data) { @@ -572,12 +595,17 @@ void TizenWindowTcoreWl::RegisterEventHandlers() { tizen_core_wl_event_input_base_get_device_identifier(ev, &dev_identifier); + const bool is_modifier = + self->UpdateKeyModifiers(keysymbol, keycode, modifiers, true); bool handled = false; if (self->input_method_context_ && self->input_method_context_->ShouldFilterKey(keysymbol)) { handled = self->input_method_context_->HandleTcoreWlEventKey(event, true); } + if (!is_modifier) { + self->StartKeyRepeat(ev, keysymbol, keycode, dev_identifier, handled); + } if (!handled) { // Match TizenWindowEcoreWl2 OnKey arg semantics: // param 1 (key) = XKB key symbol name (e.g. "period") @@ -621,6 +649,10 @@ void TizenWindowTcoreWl::RegisterEventHandlers() { unsigned int keycode = 0; tizen_core_wl_event_key_get_keycode(ev, &keycode); + self->UpdateKeyModifiers(keysymbol, keycode, modifiers, false); + if (self->repeat_scan_code_ == keycode) { + self->StopKeyRepeat(); + } bool handled = false; if (self->input_method_context_ && self->input_method_context_->ShouldFilterKey(keysymbol)) { @@ -639,12 +671,166 @@ void TizenWindowTcoreWl::RegisterEventHandlers() { } void TizenWindowTcoreWl::UnregisterEventHandlers() { + ResetKeyRepeat(); for (tizen_core_wl_event_listener_h listener : tcore_event_listeners_) { tizen_core_wl_event_remove_listener(tcore_wl_event_, listener); } tcore_event_listeners_.clear(); } +bool TizenWindowTcoreWl::UpdateKeyModifiers(const char* key, + uint32_t keycode, + uint32_t modifiers, + bool is_down) { + key_modifiers_ = modifiers; + const std::string symbol = key ? key : ""; + uint32_t bit = 0; + if (symbol == "Shift_L" || symbol == "Shift_R") { + bit = TIZEN_CORE_WL_MODIFIER_SHIFT; + } else if (symbol == "Control_L" || symbol == "Control_R") { + bit = TIZEN_CORE_WL_MODIFIER_CTRL; + } else if (symbol == "Alt_L" || symbol == "Alt_R") { + bit = TIZEN_CORE_WL_MODIFIER_ALT; + } else if (symbol == "Super_L" || symbol == "Super_R") { + bit = TIZEN_CORE_WL_MODIFIER_WIN; + } else if (symbol == "ISO_Level3_Shift") { + bit = TIZEN_CORE_WL_MODIFIER_ALTGR; + } + if (bit) { + if (is_down) { + pressed_modifiers_[keycode] = bit; + } else { + pressed_modifiers_.erase(keycode); + } + key_modifiers_ &= ~bit; + for (const auto& entry : pressed_modifiers_) { + key_modifiers_ |= entry.second; + } + } + return bit || symbol == "Caps_Lock" || symbol == "Scroll_Lock"; +} + +void TizenWindowTcoreWl::StartKeyRepeat(tizen_core_wl_event_input_base_h event, + const char* key, + uint32_t keycode, + const char* device_identifier, + bool imf_handled) { + unsigned int flags = 0; + tizen_core_wl_event_key_get_event_flags(event, &flags); + const bool native_repeat = + (repeat_scan_code_ != 0 && repeat_scan_code_ == keycode) || + (flags & TIZEN_CORE_WL_EVENT_FLAG_REPEAT); + StopKeyRepeat(); + const std::string symbol = key ? key : ""; + const bool is_arrow = symbol == "Up" || symbol == "Down" || + symbol == "Left" || symbol == "Right"; + if (imf_handled || !is_arrow || !keycode || !device_identifier) { + return; + } + + tizen_core_wl_seat_h seat = nullptr; + tizen_core_wl_display_get_default_seat(tcore_wl_display_, &seat); + GList* devices = nullptr; + if (tizen_core_wl_seat_get_input_device_list(seat, &devices) != + TIZEN_CORE_WL_ERROR_NONE) { + return; + } + bool is_keyboard = false; + for (GList* node = devices; node; node = node->next) { + auto device = static_cast(node->data); + const char* identifier = nullptr; + tizen_core_wl_input_device_get_identifier(device, &identifier); + if (identifier && std::strcmp(identifier, device_identifier) == 0) { + tizen_core_wl_device_class_e device_class = + TIZEN_CORE_WL_DEVICE_CLASS_NONE; + tizen_core_wl_device_subclass_e subclass = + TIZEN_CORE_WL_DEVICE_SUBCLASS_NONE; + is_keyboard = tizen_core_wl_input_device_get_class( + device, &device_class) == TIZEN_CORE_WL_ERROR_NONE && + tizen_core_wl_input_device_get_subclass( + device, &subclass) == TIZEN_CORE_WL_ERROR_NONE && + device_class == TIZEN_CORE_WL_DEVICE_CLASS_KEYBOARD && + subclass != TIZEN_CORE_WL_DEVICE_SUBCLASS_REMOCON; + break; + } + } + g_list_free(devices); + if (!is_keyboard) { + return; + } + repeat_scan_code_ = keycode; + if (native_repeat) { + return; + } + repeat_key_ = symbol; + repeat_device_identifier_ = device_identifier; + ScheduleKeyRepeat(true); +} + +void TizenWindowTcoreWl::StopKeyRepeat() { + if (key_repeat_timer_) { + tizen_core_remove_source(key_repeat_core_, key_repeat_timer_); + key_repeat_timer_ = nullptr; + } + repeat_scan_code_ = 0; +} + +void TizenWindowTcoreWl::ResetKeyRepeat() { + StopKeyRepeat(); + key_modifiers_ = 0; + pressed_modifiers_.clear(); +} + +void TizenWindowTcoreWl::ScheduleKeyRepeat(bool initial) { + tizen_core_wl_seat_h seat = nullptr; + if (tizen_core_wl_display_get_default_seat(tcore_wl_display_, &seat) != + TIZEN_CORE_WL_ERROR_NONE || + !seat || + tizen_core_find_from_this_thread(&key_repeat_core_) != + TIZEN_CORE_ERROR_NONE) { + return; + } + const auto type = (repeat_key_ == "Left" || repeat_key_ == "Right") + ? TIZEN_CORE_WL_KEYBOARD_REPEAT_HORIZONTAL + : TIZEN_CORE_WL_KEYBOARD_REPEAT_VERTICAL; + double rate = 0; + double delay = 0; + auto result = + tizen_core_wl_seat_get_keyboard_repeat(seat, type, &rate, &delay); + if (result != TIZEN_CORE_WL_ERROR_NONE) { + result = tizen_core_wl_seat_get_keyboard_repeat( + seat, TIZEN_CORE_WL_KEYBOARD_REPEAT_DEFAULT, &rate, &delay); + } + // Tizen stores both the repeat interval (rate) and delay in seconds. + const double interval_ms = + std::ceil(rate * 1000 + (initial ? delay * 1000 : 0)); + if (result != TIZEN_CORE_WL_ERROR_NONE || !std::isfinite(rate) || + !std::isfinite(delay) || rate <= 0 || delay < 0 || + !std::isfinite(interval_ms) || interval_ms > G_MAXUINT) { + return; + } + if (tizen_core_add_timer( + key_repeat_core_, + static_cast(std::max(interval_ms, 1.0)), + [](void* data) -> bool { + auto* self = static_cast(data); + self->key_repeat_timer_ = nullptr; + self->ScheduleKeyRepeat(false); + if (self->key_repeat_timer_ && self->view_delegate_) { + const auto key = self->repeat_key_; + const auto device = self->repeat_device_identifier_; + self->view_delegate_->OnKey( + key.c_str(), nullptr, nullptr, self->key_modifiers_, + self->repeat_scan_code_, device.c_str(), true); + } + return false; + }, + this, &key_repeat_timer_) != TIZEN_CORE_ERROR_NONE) { + key_repeat_timer_ = nullptr; + FT_LOG(Error) << "Failed to add a key repeat timer."; + } +} + void TizenWindowTcoreWl::DestroyWindow() { if (tcore_wl_egl_window_) { tizen_core_wl_egl_window_destroy(tcore_wl_egl_window_); diff --git a/flutter/shell/platform/tizen/tizen_window_tcore_wl.h b/flutter/shell/platform/tizen/tizen_window_tcore_wl.h index 96058c6..2b67439 100644 --- a/flutter/shell/platform/tizen/tizen_window_tcore_wl.h +++ b/flutter/shell/platform/tizen/tizen_window_tcore_wl.h @@ -6,10 +6,12 @@ #define EMBEDDER_TIZEN_WINDOW_TCORE_WL_H_ #include +#include #include #include #include +#include #include #include @@ -92,6 +94,23 @@ class TizenWindowTcoreWl : public TizenWindow { void PrepareInputMethod(); + bool UpdateKeyModifiers(const char* key, + uint32_t keycode, + uint32_t modifiers, + bool is_down); + + void StartKeyRepeat(tizen_core_wl_event_input_base_h event, + const char* key, + uint32_t keycode, + const char* device_identifier, + bool imf_handled); + + void StopKeyRepeat(); + + void ResetKeyRepeat(); + + void ScheduleKeyRepeat(bool initial); + tizen_core_wl_display_h tcore_wl_display_ = nullptr; bool owns_display_ = false; tizen_core_wl_window_h tcore_wl_window_ = nullptr; @@ -101,6 +120,13 @@ class TizenWindowTcoreWl : public TizenWindow { wl_surface* wl2_surface_ = nullptr; std::vector tcore_event_listeners_; uint32_t resource_id_ = 0; + tizen_core_h key_repeat_core_ = nullptr; + tizen_core_source_h key_repeat_timer_ = nullptr; + uint32_t repeat_scan_code_ = 0; + std::string repeat_key_; + std::string repeat_device_identifier_; + uint32_t key_modifiers_ = 0; + std::map pressed_modifiers_; #ifdef TV_PROFILE bool pointing_device_support_ = true;