diff --git a/build/config/BUILDCONFIG.gn b/build/config/BUILDCONFIG.gn index 65876b36..baea25a7 100644 --- a/build/config/BUILDCONFIG.gn +++ b/build/config/BUILDCONFIG.gn @@ -31,6 +31,9 @@ declare_args() { # Use the system libstdc++ without building third_party/libcxx from source. use_system_cxx = false + + # Use tizen-core-wl API instead of ecore. + use_tcore = false } _default_configs = [ diff --git a/flutter/shell/platform/tizen/BUILD.gn b/flutter/shell/platform/tizen/BUILD.gn index 3c21911a..6578a72c 100644 --- a/flutter/shell/platform/tizen/BUILD.gn +++ b/flutter/shell/platform/tizen/BUILD.gn @@ -14,15 +14,6 @@ config("flutter_tizen_config") { "${sysroot_path}/usr/include/appfw", "${sysroot_path}/usr/include/base", "${sysroot_path}/usr/include/dlog", - "${sysroot_path}/usr/include/ecore-1", - "${sysroot_path}/usr/include/ecore-imf-1", - "${sysroot_path}/usr/include/ecore-input-1", - "${sysroot_path}/usr/include/ecore-wayland-1", - "${sysroot_path}/usr/include/ecore-wl2-1", - "${sysroot_path}/usr/include/efl-1", - "${sysroot_path}/usr/include/eina-1", - "${sysroot_path}/usr/include/eina-1/eina", - "${sysroot_path}/usr/include/eo-1", "${sysroot_path}/usr/include/feedback", "${sysroot_path}/usr/include/system", "${sysroot_path}/usr/include/tzsh", @@ -32,6 +23,28 @@ config("flutter_tizen_config") { "${sysroot_path}/usr/lib/glib-2.0/include", ] + # tcore-specific includes + if (use_tcore) { + include_dirs += [ + "${sysroot_path}/usr/include/tizen-core", + "${sysroot_path}/usr/include/tizen-core-imf", + "${sysroot_path}/usr/include/tizen-core-wl", + ] + } else { + # ecore-specific includes + include_dirs += [ + "${sysroot_path}/usr/include/ecore-1", + "${sysroot_path}/usr/include/ecore-imf-1", + "${sysroot_path}/usr/include/ecore-input-1", + "${sysroot_path}/usr/include/ecore-wayland-1", + "${sysroot_path}/usr/include/ecore-wl2-1", + "${sysroot_path}/usr/include/efl-1", + "${sysroot_path}/usr/include/eina-1", + "${sysroot_path}/usr/include/eina-1/eina", + "${sysroot_path}/usr/include/eo-1", + ] + } + cflags_cc = [ "-Wno-newline-eof", "-Wno-macro-redefined", @@ -90,14 +103,26 @@ template("embedder") { "logger.cc", "system_utils.cc", "tizen_event_loop.cc", - "tizen_input_method_context.cc", "tizen_renderer.cc", "tizen_renderer_egl.cc", "tizen_renderer_gl.cc", "tizen_vsync_waiter.cc", - "tizen_window_ecore_wl2.cc", + "tizen_window_util.cc", ] + # Select window implementation based on use_tcore + if (use_tcore) { + sources += [ + "tizen_input_method_context_tcore.cc", + "tizen_window_tcore_wl.cc", + ] + } else { + sources += [ + "tizen_input_method_context.cc", + "tizen_window_ecore_wl2.cc", + ] + } + lib_dirs = [ "//engine/${target_cpu}" ] libs = [ @@ -110,11 +135,7 @@ template("embedder") { "capi-system-info", "capi-system-system-settings", "dlog", - "ecore", - "ecore_imf", - "ecore_input", - "ecore_wl2", - "eina", + "glib-2.0", "gio-2.0", "feedback", "flutter_engine", @@ -127,6 +148,23 @@ template("embedder") { "GLESv2", ] + # tcore-specific or ecore-specific libraries + if (use_tcore) { + libs += [ + "tizen-core", + "tizen-core-imf", + "tizen-core-wl", + ] + } else { + libs += [ + "ecore", + "ecore_imf", + "ecore_input", + "ecore_wl2", + "eina", + ] + } + if (target_name == "flutter_tizen_common" || target_name == "flutter_tizen_common_experimental") { sources += [ "channels/tizen_shell.cc" ] @@ -140,6 +178,11 @@ template("embedder") { defines += invoker.defines defines += [ "FLUTTER_ENGINE_NO_PROTOTYPES" ] + # Add USE_TCORE_WL define when using tcore + if (use_tcore) { + defines += [ "USE_TCORE_WL" ] + } + if (target_name == "flutter_tizen_mobile_experimental" || target_name == "flutter_tizen_tv_experimental" || target_name == "flutter_tizen_common_experimental") { @@ -236,10 +279,15 @@ executable("flutter_tizen_unittests") { "flutter_tizen_engine_unittest.cc", "flutter_tizen_texture_registrar_unittests.cc", "flutter_tizen_view_unittests.cc", + "tizen_event_loop_unittests.cc", ] ldflags = [ "-Wl,--unresolved-symbols=ignore-in-shared-libs" ] + if (use_tcore) { + defines = [ "USE_TCORE_WL" ] + } + configs += [ ":flutter_tizen_config" ] deps += [ diff --git a/flutter/shell/platform/tizen/channels/input_panel_channel.cc b/flutter/shell/platform/tizen/channels/input_panel_channel.cc index d3e48236..e1467a7d 100644 --- a/flutter/shell/platform/tizen/channels/input_panel_channel.cc +++ b/flutter/shell/platform/tizen/channels/input_panel_channel.cc @@ -7,7 +7,11 @@ #include "flutter/shell/platform/common/client_wrapper/include/flutter/event_stream_handler_functions.h" #include "flutter/shell/platform/common/client_wrapper/include/flutter/standard_method_codec.h" #include "flutter/shell/platform/tizen/logger.h" +#ifdef USE_TCORE_WL +#include "flutter/shell/platform/tizen/tizen_input_method_context_tcore.h" +#else #include "flutter/shell/platform/tizen/tizen_input_method_context.h" +#endif namespace { diff --git a/flutter/shell/platform/tizen/channels/key_mapping.cc b/flutter/shell/platform/tizen/channels/key_mapping.cc index 35c0f368..54ea04de 100644 --- a/flutter/shell/platform/tizen/channels/key_mapping.cc +++ b/flutter/shell/platform/tizen/channels/key_mapping.cc @@ -177,7 +177,7 @@ std::map kScanCodeToGtkKeyCode = { {0x0000024d, 269025069}, // launchScreenSaver }; -std::map kEcoreModifierToGtkModifier = { +std::map kModifierToGtkModifier = { {0x0001, 1 << 0}, // SHIFT -> modifierShift {0x0002, 1 << 2}, // CTRL -> modifierControl {0x0004, 1 << 3}, // ALT -> modifierMod1 diff --git a/flutter/shell/platform/tizen/channels/key_mapping.h b/flutter/shell/platform/tizen/channels/key_mapping.h index d2ec300b..e07e1fb6 100644 --- a/flutter/shell/platform/tizen/channels/key_mapping.h +++ b/flutter/shell/platform/tizen/channels/key_mapping.h @@ -19,15 +19,15 @@ // legacy RawKeyboard API is removed from the framework in the future. extern std::map kScanCodeToGtkKeyCode; -// Maps Ecore modifiers to GTK modifiers. +// Maps Tizen modifier bitmask values to GTK modifiers. // -// The values are originally defined in: -// - efl/Ecore_Input.h -// - flutter/raw_keyboard_linux.dart (GtkKeyHelper) +// The modifier bitmask values match tizen_core_wl_modifier_e. +// The GTK modifier values are from flutter/raw_keyboard_linux.dart +// (GtkKeyHelper). // // Provided only for backward compatibility. This will be removed after the // legacy RawKeyboard API is removed from the framework in the future. -extern std::map kEcoreModifierToGtkModifier; +extern std::map kModifierToGtkModifier; // Maps XKB scan codes to Flutter's physical key codes. // diff --git a/flutter/shell/platform/tizen/channels/keyboard_channel.cc b/flutter/shell/platform/tizen/channels/keyboard_channel.cc index d48caecb..8b6e32cb 100644 --- a/flutter/shell/platform/tizen/channels/keyboard_channel.cc +++ b/flutter/shell/platform/tizen/channels/keyboard_channel.cc @@ -164,7 +164,7 @@ void KeyboardChannel::SendChannelEvent(const char* key, } int gtk_modifiers = 0; - for (auto element : kEcoreModifierToGtkModifier) { + for (auto element : kModifierToGtkModifier) { if (element.first & modifiers) { gtk_modifiers |= element.second; } diff --git a/flutter/shell/platform/tizen/channels/text_input_channel.h b/flutter/shell/platform/tizen/channels/text_input_channel.h index 11d3aa35..91c2a68a 100644 --- a/flutter/shell/platform/tizen/channels/text_input_channel.h +++ b/flutter/shell/platform/tizen/channels/text_input_channel.h @@ -11,7 +11,11 @@ #include "flutter/shell/platform/common/client_wrapper/include/flutter/binary_messenger.h" #include "flutter/shell/platform/common/client_wrapper/include/flutter/method_channel.h" #include "flutter/shell/platform/common/text_input_model.h" +#ifdef USE_TCORE_WL +#include "flutter/shell/platform/tizen/tizen_input_method_context_tcore.h" +#else #include "flutter/shell/platform/tizen/tizen_input_method_context.h" +#endif #include "rapidjson/document.h" namespace flutter { diff --git a/flutter/shell/platform/tizen/channels/window_channel.cc b/flutter/shell/platform/tizen/channels/window_channel.cc index 5e618e55..242f4bfe 100644 --- a/flutter/shell/platform/tizen/channels/window_channel.cc +++ b/flutter/shell/platform/tizen/channels/window_channel.cc @@ -8,7 +8,6 @@ #include "flutter/shell/platform/tizen/channels/encodable_value_holder.h" #include "flutter/shell/platform/tizen/logger.h" #include "flutter/shell/platform/tizen/tizen_window.h" -#include "flutter/shell/platform/tizen/tizen_window_ecore_wl2.h" namespace flutter { diff --git a/flutter/shell/platform/tizen/flutter_tizen.cc b/flutter/shell/platform/tizen/flutter_tizen.cc index 5105c0e1..1fde694e 100644 --- a/flutter/shell/platform/tizen/flutter_tizen.cc +++ b/flutter/shell/platform/tizen/flutter_tizen.cc @@ -15,7 +15,11 @@ #include "flutter/shell/platform/tizen/logger.h" #include "flutter/shell/platform/tizen/public/flutter_platform_view.h" #include "flutter/shell/platform/tizen/tizen_window.h" +#ifdef USE_TCORE_WL +#include "flutter/shell/platform/tizen/tizen_window_tcore_wl.h" +#else #include "flutter/shell/platform/tizen/tizen_window_ecore_wl2.h" +#endif namespace { @@ -208,12 +212,21 @@ FlutterDesktopViewRef FlutterDesktopViewCreateFromNewWindow( std::unique_ptr window; +#ifdef USE_TCORE_WL + window = std::make_unique( + window_geometry, window_properties.transparent, + window_properties.focusable, window_properties.top_level, + window_properties.pointing_device_support, + window_properties.floating_menu_support, window_properties.window_handle, + window_properties.renderer_type == kEVulkan); +#else window = std::make_unique( window_geometry, window_properties.transparent, window_properties.focusable, window_properties.top_level, window_properties.pointing_device_support, window_properties.floating_menu_support, window_properties.window_handle, window_properties.renderer_type == kEVulkan); +#endif auto view = std::make_unique( flutter::kImplicitViewId, std::move(window), diff --git a/flutter/shell/platform/tizen/flutter_tizen_display_monitor.cc b/flutter/shell/platform/tizen/flutter_tizen_display_monitor.cc index 0a5bdf61..740260a1 100644 --- a/flutter/shell/platform/tizen/flutter_tizen_display_monitor.cc +++ b/flutter/shell/platform/tizen/flutter_tizen_display_monitor.cc @@ -3,7 +3,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include #include #include "flutter/shell/platform/tizen/flutter_tizen_display_monitor.h" @@ -29,12 +28,7 @@ void FlutterTizenDisplayMonitor::UpdateDisplays() { display.display_id = 0; display.single_display = true; - double fps = ecore_animator_frametime_get(); - if (fps <= 0.0) { - display.refresh_rate = 0.0; - } else { - display.refresh_rate = 1 / fps; - } + display.refresh_rate = 60.0; int32_t width = 0, height = 0, dpi = 0; FlutterTizenView* view = engine_->view(); diff --git a/flutter/shell/platform/tizen/flutter_tizen_engine.cc b/flutter/shell/platform/tizen/flutter_tizen_engine.cc index 2de1a11a..c88d9d59 100644 --- a/flutter/shell/platform/tizen/flutter_tizen_engine.cc +++ b/flutter/shell/platform/tizen/flutter_tizen_engine.cc @@ -15,7 +15,11 @@ #include "flutter/shell/platform/tizen/flutter_tizen_view.h" #include "flutter/shell/platform/tizen/logger.h" #include "flutter/shell/platform/tizen/system_utils.h" +#ifdef USE_TCORE_WL +#include "flutter/shell/platform/tizen/tizen_input_method_context_tcore.h" +#else #include "flutter/shell/platform/tizen/tizen_input_method_context.h" +#endif #include "flutter/shell/platform/tizen/tizen_renderer_egl.h" #ifdef FLUTTER_TIZEN_EXPERIMENTAL diff --git a/flutter/shell/platform/tizen/flutter_tizen_engine_unittest.cc b/flutter/shell/platform/tizen/flutter_tizen_engine_unittest.cc index 15665584..a5665550 100644 --- a/flutter/shell/platform/tizen/flutter_tizen_engine_unittest.cc +++ b/flutter/shell/platform/tizen/flutter_tizen_engine_unittest.cc @@ -4,8 +4,6 @@ #include "flutter/shell/platform/tizen/flutter_tizen_engine.h" -#include - #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" #include "flutter/shell/platform/tizen/testing/engine_modifier.h" #include "gtest/gtest.h" @@ -14,9 +12,6 @@ namespace flutter { namespace testing { class FlutterTizenEngineTest : public ::testing::Test { - public: - FlutterTizenEngineTest() { ecore_init(); } - protected: void SetUp() { FlutterDesktopEngineProperties engine_prop = {}; diff --git a/flutter/shell/platform/tizen/flutter_tizen_texture_registrar_unittests.cc b/flutter/shell/platform/tizen/flutter_tizen_texture_registrar_unittests.cc index b76074e0..3d7e7e5d 100644 --- a/flutter/shell/platform/tizen/flutter_tizen_texture_registrar_unittests.cc +++ b/flutter/shell/platform/tizen/flutter_tizen_texture_registrar_unittests.cc @@ -4,8 +4,6 @@ #include "flutter/shell/platform/tizen/flutter_tizen_texture_registrar.h" -#include - #include #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" @@ -17,9 +15,6 @@ namespace flutter { namespace testing { class FlutterTizenTextureRegistrarTest : public ::testing::Test { - public: - FlutterTizenTextureRegistrarTest() { ecore_init(); } - protected: void SetUp() { FlutterDesktopEngineProperties engine_prop = {}; diff --git a/flutter/shell/platform/tizen/flutter_tizen_view_unittests.cc b/flutter/shell/platform/tizen/flutter_tizen_view_unittests.cc index 0ac71df2..35fc9b7b 100644 --- a/flutter/shell/platform/tizen/flutter_tizen_view_unittests.cc +++ b/flutter/shell/platform/tizen/flutter_tizen_view_unittests.cc @@ -4,8 +4,6 @@ #include "flutter/shell/platform/tizen/flutter_tizen_view.h" -#include - #include #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" @@ -47,7 +45,6 @@ class TestTizenView : public TizenWindow { }; TEST(FlutterTizenViewTest, SendsAndRequestsViewFocus) { - ecore_init(); { FlutterDesktopEngineProperties properties = {}; properties.assets_path = "/foo/flutter_assets"; @@ -110,7 +107,6 @@ TEST(FlutterTizenViewTest, SendsAndRequestsViewFocus) { view.OnFocusChangeRequest(request); EXPECT_FALSE(tizen_view_ptr->activated); } - ecore_shutdown(); } } // namespace diff --git a/flutter/shell/platform/tizen/public/flutter_tizen.h b/flutter/shell/platform/tizen/public/flutter_tizen.h index e272a7ef..4a7b0875 100644 --- a/flutter/shell/platform/tizen/public/flutter_tizen.h +++ b/flutter/shell/platform/tizen/public/flutter_tizen.h @@ -179,7 +179,9 @@ FLUTTER_EXPORT void FlutterDesktopViewDestroy(FlutterDesktopViewRef view); // Returns a native UI toolkit handle for manipulation in host application. // -// Cast the returned void* to Ecore_Wl2_Window*. +// Cast the returned void*: +// - default (ecore_wl2) : to Ecore_Wl2_Window* +// - --use-tcore build : to tizen_core_wl_window_h // @warning This API is a work-in-progress and may change. FLUTTER_EXPORT void* FlutterDesktopViewGetNativeHandle( FlutterDesktopViewRef view); diff --git a/flutter/shell/platform/tizen/tizen_clipboard.cc b/flutter/shell/platform/tizen/tizen_clipboard.cc index c8393aae..8b85c067 100644 --- a/flutter/shell/platform/tizen/tizen_clipboard.cc +++ b/flutter/shell/platform/tizen/tizen_clipboard.cc @@ -4,9 +4,19 @@ #include "tizen_clipboard.h" +#include + +#include +#include + #include "flutter/shell/platform/tizen/logger.h" #include "flutter/shell/platform/tizen/tizen_window.h" + +#ifdef USE_TCORE_WL +#include "flutter/shell/platform/tizen/tizen_window_tcore_wl.h" +#else #include "flutter/shell/platform/tizen/tizen_window_ecore_wl2.h" +#endif namespace flutter { @@ -16,6 +26,272 @@ constexpr char kMimeTypeTextPlain[] = "text/plain;charset=utf-8"; } // namespace +#ifdef USE_TCORE_WL + +TizenClipboard::TizenClipboard(TizenViewBase* view) { + if (auto* window = dynamic_cast(view)) { + tizen_core_wl_window_h tcore_window = + static_cast(window->GetNativeHandle()); + tizen_core_wl_window_get_display(tcore_window, &display_); + } else { + if (tizen_core_wl_init() != TIZEN_CORE_WL_ERROR_NONE || + tizen_core_wl_display_create(&display_) != TIZEN_CORE_WL_ERROR_NONE || + tizen_core_wl_display_connect(display_, nullptr) != + TIZEN_CORE_WL_ERROR_NONE) { + FT_LOG(Error) << "Failed to connect display for clipboard."; + display_ = nullptr; + return; + } + owns_display_ = true; + } + + if (tizen_core_wl_display_get_event(display_, &tcore_wl_event_) != + TIZEN_CORE_WL_ERROR_NONE) { + FT_LOG(Error) << "Failed to get event handle for clipboard."; + return; + } + + tizen_core_wl_event_add_listener( + tcore_wl_event_, TIZEN_CORE_WL_EVENT_DATA_SOURCE_SEND, + [](void* event, tizen_core_wl_event_type_e type, void* data) { + auto* self = static_cast(data); + self->SendData(event); + }, + this, &send_listener_); + + tizen_core_wl_event_add_listener( + tcore_wl_event_, TIZEN_CORE_WL_EVENT_DATA_READY, + [](void* event, tizen_core_wl_event_type_e type, void* data) { + auto* self = static_cast(data); + self->ReceiveData(event); + }, + this, &receive_listener_); +} + +TizenClipboard::~TizenClipboard() { + on_data_callback_ = nullptr; + + if (send_listener_) { + tizen_core_wl_event_remove_listener(tcore_wl_event_, send_listener_); + } + if (receive_listener_) { + tizen_core_wl_event_remove_listener(tcore_wl_event_, receive_listener_); + } + + if (owns_display_ && display_) { + tizen_core_wl_display_disconnect(display_); + tizen_core_wl_display_destroy(display_); + tizen_core_wl_shutdown(); + } +} + +void TizenClipboard::SendData(void* event) { + if (!event) { + return; + } + tizen_core_wl_event_data_source_send_h send_event = + static_cast(event); + + char* mime_type = nullptr; + tizen_core_wl_event_data_source_send_get_type(send_event, &mime_type); + int fd = -1; + tizen_core_wl_event_data_source_send_get_fd(send_event, &fd); + + // Get serial from base event. + tizen_core_wl_event_data_source_base_h base_event = + static_cast(event); + unsigned int serial = 0; + tizen_core_wl_event_data_source_base_get_serial(base_event, &serial); + + if (!mime_type || + (strlen(mime_type) != 0 && strcmp(mime_type, kMimeTypeTextPlain))) { + FT_LOG(Error) << "Invaild mime type(" << (mime_type ? mime_type : "null") + << ")."; + if (fd >= 0) { + close(fd); + } + return; + } + + if (serial != selection_serial_) { + FT_LOG(Error) << "The serial doesn't match."; + if (fd >= 0) { + close(fd); + } + return; + } + + const char* buffer = data_.c_str(); + size_t remaining = data_.length(); + while (remaining > 0) { + ssize_t written = write(fd, buffer, remaining); + if (written < 0) { + if (errno == EINTR) { + continue; + } + FT_LOG(Error) << "Failed to write clipboard data: " << strerror(errno); + break; + } + buffer += written; + remaining -= written; + } + close(fd); +} + +void TizenClipboard::ReceiveData(void* event) { + if (!event) { + return; + } + tizen_core_wl_event_data_ready_h ready_event = + static_cast(event); + + void* raw_data = nullptr; + tizen_core_wl_event_data_ready_get_data(ready_event, &raw_data); + const char* data = static_cast(raw_data); + int len = 0; + tizen_core_wl_event_data_ready_get_len(ready_event, &len); + + if (data == nullptr || len < 1) { + FT_LOG(Info) << "No data available."; + if (on_data_callback_) { + on_data_callback_(""); + on_data_callback_ = nullptr; + } + return; + } + + tizen_core_wl_data_offer_h offer = nullptr; + tizen_core_wl_event_data_ready_get_offer(ready_event, &offer); + + if (offer != selection_offer_) { + FT_LOG(Error) << "The offer doesn't match."; + if (on_data_callback_) { + on_data_callback_(std::nullopt); + on_data_callback_ = nullptr; + } + return; + } + + size_t data_length = strlen(data); + size_t buffer_size = len; + std::string content; + + if (data_length < buffer_size) { + content.append(data, data_length); + } else { + content.append(data, buffer_size); + } + + if (on_data_callback_) { + on_data_callback_(content); + on_data_callback_ = nullptr; + } +} + +void TizenClipboard::SetData(const std::string& data) { + data_ = data; + + const char* mime_types[3]; + mime_types[0] = kMimeTypeTextPlain; + // TODO(jsuya): There is an issue where ECORE_WL2_EVENT_DATA_SOURCE_SEND event + // does not work properly even if ecore_wl2_dnd_selection_set() is called in + // Tizen 6.5 or lower. Therefore, add empty mimetype for event call from the + // cbhm module. Since it works normally from Tizen 8.0, this part may be + // modified in the future. + mime_types[1] = ""; + mime_types[2] = nullptr; + + tizen_core_wl_seat_h default_seat = nullptr; + tizen_core_wl_display_get_default_seat(display_, &default_seat); + if (default_seat) { + tizen_core_wl_data_h data_handle = nullptr; + tizen_core_wl_seat_get_data(default_seat, &data_handle); + if (data_handle) { + tizen_core_wl_data_set_selection(data_handle, mime_types, + &selection_serial_); + } + } + tizen_core_wl_display_flush(display_); +} + +bool TizenClipboard::GetData(ClipboardCallback on_data_callback) { + on_data_callback_ = std::move(on_data_callback); + + tizen_core_wl_seat_h default_seat = nullptr; + tizen_core_wl_display_get_default_seat(display_, &default_seat); + if (!default_seat) { + if (on_data_callback_) { + on_data_callback_ = nullptr; + } + return false; + } + + tizen_core_wl_data_h data_handle = nullptr; + tizen_core_wl_seat_get_data(default_seat, &data_handle); + if (!data_handle) { + if (on_data_callback_) { + on_data_callback_ = nullptr; + } + return false; + } + + tizen_core_wl_data_offer_h offer = nullptr; + tizen_core_wl_data_get_selection(data_handle, &offer); + selection_offer_ = offer; + if (!selection_offer_) { + FT_LOG(Error) << "tizen_core_wl_data_get_selection() failed."; + if (on_data_callback_) { + on_data_callback_ = nullptr; + } + return false; + } + + tizen_core_wl_data_receive(selection_offer_, + const_cast(kMimeTypeTextPlain)); + return true; +} + +bool TizenClipboard::HasStrings() { + tizen_core_wl_seat_h default_seat = nullptr; + tizen_core_wl_display_get_default_seat(display_, &default_seat); + if (!default_seat) { + return false; + } + + tizen_core_wl_data_h data_handle = nullptr; + tizen_core_wl_seat_get_data(default_seat, &data_handle); + if (!data_handle) { + return false; + } + + tizen_core_wl_data_offer_h offer = nullptr; + tizen_core_wl_data_get_selection(data_handle, &offer); + selection_offer_ = offer; + if (!selection_offer_) { + return false; + } + + char** mimes = nullptr; + int mime_count = 0; + tizen_core_wl_data_get_mimes(selection_offer_, &mimes, &mime_count); + + if (!mimes) { + return false; + } + + bool found = false; + for (int i = 0; i < mime_count; ++i) { + if (mimes[i] && !strcmp(kMimeTypeTextPlain, mimes[i])) { + found = true; + break; + } + } + free(mimes); + return found; +} + +#else // USE_TCORE_WL + TizenClipboard::TizenClipboard(TizenViewBase* view) { if (auto* window = dynamic_cast(view)) { auto* ecore_wl2_window = @@ -179,4 +455,6 @@ bool TizenClipboard::HasStrings() { return false; } +#endif // USE_TCORE_WL + } // namespace flutter diff --git a/flutter/shell/platform/tizen/tizen_clipboard.h b/flutter/shell/platform/tizen/tizen_clipboard.h index 244eafe8..df8d70c9 100644 --- a/flutter/shell/platform/tizen/tizen_clipboard.h +++ b/flutter/shell/platform/tizen/tizen_clipboard.h @@ -5,8 +5,12 @@ #ifndef EMBEDDER_TIZEN_CLIPBOARD_H_ #define EMBEDDER_TIZEN_CLIPBOARD_H_ +#ifdef USE_TCORE_WL +#include +#else #define EFL_BETA_API_SUPPORT #include +#endif #include #include @@ -35,10 +39,19 @@ class TizenClipboard { std::string data_; ClipboardCallback on_data_callback_; uint32_t selection_serial_ = 0; +#ifdef USE_TCORE_WL + tizen_core_wl_data_offer_h selection_offer_ = nullptr; + tizen_core_wl_display_h display_ = nullptr; + bool owns_display_ = false; + tizen_core_event_h tcore_wl_event_ = nullptr; + tizen_core_wl_event_listener_h send_listener_ = nullptr; + tizen_core_wl_event_listener_h receive_listener_ = nullptr; +#else Ecore_Wl2_Offer* selection_offer_ = nullptr; Ecore_Wl2_Display* display_ = nullptr; Ecore_Event_Handler* send_handler = nullptr; Ecore_Event_Handler* receive_handler = nullptr; +#endif }; } // namespace flutter diff --git a/flutter/shell/platform/tizen/tizen_event_loop.cc b/flutter/shell/platform/tizen/tizen_event_loop.cc index 0724b864..c5b3ac8b 100644 --- a/flutter/shell/platform/tizen/tizen_event_loop.cc +++ b/flutter/shell/platform/tizen/tizen_event_loop.cc @@ -6,109 +6,111 @@ #include "tizen_event_loop.h" #include +#include namespace flutter { +namespace { + +GSourceFuncs kTaskSourceFuncs = { + nullptr, + nullptr, + [](GSource*, GSourceFunc callback, gpointer user_data) -> gboolean { + return callback(user_data); + }, + nullptr, + nullptr, + nullptr, +}; + +} // namespace + TizenEventLoop::TizenEventLoop(std::thread::id main_thread_id, CurrentTimeProc get_current_time, TaskExpiredCallback on_task_expired) : main_thread_id_(main_thread_id), get_current_time_(get_current_time), - on_task_expired_(std::move(on_task_expired)) { - ecore_pipe_ = ecore_pipe_add( - [](void* data, void* buffer, unsigned int nbyte) -> void { - auto* self = static_cast(data); - self->ExecuteTaskEvents(); + on_task_expired_(std::move(on_task_expired)), + task_source_(g_source_new(&kTaskSourceFuncs, sizeof(GSource))) { + g_source_set_callback( + task_source_, + [](gpointer data) -> gboolean { + static_cast(data)->ExecuteTaskEvents(); + return G_SOURCE_CONTINUE; }, - this); + this, nullptr); + + GMainContext* main_context = g_main_context_ref_thread_default(); + g_source_attach(task_source_, main_context); + g_main_context_unref(main_context); } TizenEventLoop::~TizenEventLoop() { - if (ecore_pipe_) { - ecore_pipe_del(ecore_pipe_); - } + g_source_destroy(task_source_); + g_source_unref(task_source_); } bool TizenEventLoop::RunsTasksOnCurrentThread() const { return std::this_thread::get_id() == main_thread_id_; } +void TizenEventLoop::PostTask(FlutterTask flutter_task, + uint64_t flutter_target_time_nanos) { + std::lock_guard lock(task_queue_mutex_); + const bool should_reschedule = + task_queue_.empty() || + flutter_target_time_nanos < task_queue_.top().target_time_nanos; + task_queue_.push({flutter_target_time_nanos, task_order_++, flutter_task}); + if (should_reschedule) { + UpdateSourceReadyTimeLocked(get_current_time_()); + } +} + void TizenEventLoop::ExecuteTaskEvents() { - const TaskTimePoint now = TaskTimePoint::clock::now(); + std::vector expired_tasks; { - std::lock_guard lock1(task_queue_mutex_); - std::lock_guard lock2(expired_tasks_mutex_); - while (!task_queue_.empty()) { - const Task& top = task_queue_.top(); - - if (top.fire_time > now) { - break; - } - - expired_tasks_.push_back(task_queue_.top()); + std::lock_guard lock(task_queue_mutex_); + const uint64_t now = get_current_time_(); + while (!task_queue_.empty() && task_queue_.top().target_time_nanos <= now) { + expired_tasks.push_back(task_queue_.top().task); task_queue_.pop(); } + UpdateSourceReadyTimeLocked(now); } - OnTaskExpired(); -} -TizenEventLoop::TaskTimePoint TizenEventLoop::TimePointFromFlutterTime( - uint64_t flutter_target_time_nanos) { - const TaskTimePoint now = TaskTimePoint::clock::now(); - const uint64_t flutter_duration = - flutter_target_time_nanos - get_current_time_(); - return now + std::chrono::nanoseconds(flutter_duration); + for (const FlutterTask& task : expired_tasks) { + OnTaskExpired(&task); + } } -void TizenEventLoop::PostTask(FlutterTask flutter_task, - uint64_t flutter_target_time_nanos) { - Task task; - task.order = ++task_order_; - task.fire_time = TimePointFromFlutterTime(flutter_target_time_nanos); - task.task = flutter_task; - { - std::lock_guard lock(task_queue_mutex_); - task_queue_.push(task); +void TizenEventLoop::UpdateSourceReadyTimeLocked(uint64_t now) { + if (task_queue_.empty()) { + g_source_set_ready_time(task_source_, -1); + return; } - const double flutter_duration = - static_cast(flutter_target_time_nanos) - get_current_time_(); - if (flutter_duration > 0) { - ecore_timer_add( - flutter_duration / 1000000000.0, - [](void* data) -> Eina_Bool { - auto* self = static_cast(data); - if (self->ecore_pipe_) { - ecore_pipe_write(self->ecore_pipe_, nullptr, 0); - } - return ECORE_CALLBACK_CANCEL; - }, - this); - } else { - if (ecore_pipe_) { - ecore_pipe_write(ecore_pipe_, nullptr, 0); - } + const uint64_t target_time_nanos = task_queue_.top().target_time_nanos; + if (target_time_nanos <= now) { + g_source_set_ready_time(task_source_, 0); + return; } + + const uint64_t delay_nanos = target_time_nanos - now; + const uint64_t delay_micros = delay_nanos / 1000 + (delay_nanos % 1000 != 0); + g_source_set_ready_time( + task_source_, g_get_monotonic_time() + static_cast(delay_micros)); } TizenPlatformEventLoop::TizenPlatformEventLoop( std::thread::id main_thread_id, CurrentTimeProc get_current_time, TaskExpiredCallback on_task_expired) - : TizenEventLoop(main_thread_id, get_current_time, on_task_expired) {} - -TizenPlatformEventLoop::~TizenPlatformEventLoop() {} - -void TizenPlatformEventLoop::OnTaskExpired() { - std::vector local_expired_tasks; - { - std::lock_guard lock(expired_tasks_mutex_); - local_expired_tasks = std::move(expired_tasks_); - } + : TizenEventLoop(main_thread_id, + get_current_time, + std::move(on_task_expired)) {} - for (const Task& task : local_expired_tasks) { - on_task_expired_(&task.task); - } +void TizenPlatformEventLoop::OnTaskExpired(const FlutterTask* task) { + on_task_expired_(task); } } // namespace flutter diff --git a/flutter/shell/platform/tizen/tizen_event_loop.h b/flutter/shell/platform/tizen/tizen_event_loop.h index bf7817e6..e428d171 100644 --- a/flutter/shell/platform/tizen/tizen_event_loop.h +++ b/flutter/shell/platform/tizen/tizen_event_loop.h @@ -6,22 +6,18 @@ #ifndef EMBEDDER_TIZEN_EVENT_LOOP_H_ #define EMBEDDER_TIZEN_EVENT_LOOP_H_ -#include +#include -#include -#include -#include #include #include #include #include #include "flutter/shell/platform/embedder/embedder.h" -#include "flutter/shell/platform/tizen/tizen_renderer.h" namespace flutter { -typedef uint64_t (*CurrentTimeProc)(); +using CurrentTimeProc = uint64_t (*)(); class TizenEventLoop { public: @@ -32,61 +28,50 @@ class TizenEventLoop { TaskExpiredCallback on_task_expired); virtual ~TizenEventLoop(); - // Prevent copying. TizenEventLoop(const TizenEventLoop&) = delete; TizenEventLoop& operator=(const TizenEventLoop&) = delete; bool RunsTasksOnCurrentThread() const; - - void ExecuteTaskEvents(); - - // Post a Flutter engine tasks to the event loop for delayed execution. void PostTask(FlutterTask flutter_task, uint64_t flutter_target_time_nanos); - virtual void OnTaskExpired() = 0; + virtual void OnTaskExpired(const FlutterTask* task) = 0; protected: - using TaskTimePoint = std::chrono::steady_clock::time_point; + std::thread::id main_thread_id_; + CurrentTimeProc get_current_time_; + TaskExpiredCallback on_task_expired_; + private: struct Task { + uint64_t target_time_nanos; uint64_t order; - TaskTimePoint fire_time; FlutterTask task; - struct Comparer { - bool operator()(const Task& a, const Task& b) { - if (a.fire_time == b.fire_time) { - return a.order > b.order; - } - return a.fire_time > b.fire_time; + bool operator<(const Task& other) const { + if (target_time_nanos == other.target_time_nanos) { + return order > other.order; } - }; + return target_time_nanos > other.target_time_nanos; + } }; - std::thread::id main_thread_id_; - CurrentTimeProc get_current_time_; - TaskExpiredCallback on_task_expired_; - std::mutex task_queue_mutex_; - std::priority_queue, Task::Comparer> task_queue_; - std::vector expired_tasks_; - std::mutex expired_tasks_mutex_; - std::atomic task_order_ = 0; - - private: - Ecore_Pipe* ecore_pipe_ = nullptr; + void ExecuteTaskEvents(); + void UpdateSourceReadyTimeLocked(uint64_t now); - // Returns a TaskTimePoint computed from the given target time from Flutter. - TaskTimePoint TimePointFromFlutterTime(uint64_t flutter_target_time_nanos); + std::mutex task_queue_mutex_; + std::priority_queue task_queue_; + uint64_t task_order_ = 0; + GSource* task_source_ = nullptr; }; -class TizenPlatformEventLoop : public TizenEventLoop { +class TizenPlatformEventLoop final : public TizenEventLoop { public: TizenPlatformEventLoop(std::thread::id main_thread_id, CurrentTimeProc get_current_time, TaskExpiredCallback on_task_expired); - virtual ~TizenPlatformEventLoop(); + ~TizenPlatformEventLoop() override = default; - virtual void OnTaskExpired() override; + void OnTaskExpired(const FlutterTask* task) override; }; } // namespace flutter diff --git a/flutter/shell/platform/tizen/tizen_event_loop_unittests.cc b/flutter/shell/platform/tizen/tizen_event_loop_unittests.cc new file mode 100644 index 00000000..6f2d748d --- /dev/null +++ b/flutter/shell/platform/tizen/tizen_event_loop_unittests.cc @@ -0,0 +1,126 @@ +// Copyright 2026 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/tizen/tizen_event_loop.h" + +#include +#include +#include +#include +#include + +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +namespace { + +using namespace std::chrono_literals; + +uint64_t GetCurrentTimeNanos() { + return static_cast(g_get_monotonic_time()) * 1000; +} + +FlutterTask MakeTask(uint64_t id) { + FlutterTask task = {}; + task.task = id; + return task; +} + +} // namespace + +class TizenEventLoopTest : public ::testing::Test { + protected: + void SetUp() override { + main_context_ = g_main_context_new(); + g_main_context_push_thread_default(main_context_); + event_loop_ = std::make_unique( + std::this_thread::get_id(), GetCurrentTimeNanos, + [this](const FlutterTask* task) { + executed_tasks_.push_back(task->task); + execution_time_ = GetCurrentTimeNanos(); + execution_thread_ = std::this_thread::get_id(); + if (on_task_) { + on_task_(*task); + } + }); + } + + void TearDown() override { + event_loop_.reset(); + g_main_context_pop_thread_default(main_context_); + g_main_context_unref(main_context_); + } + + bool RunUntil(const std::function& condition, + std::chrono::milliseconds timeout = 1s) { + const auto deadline = std::chrono::steady_clock::now() + timeout; + while (!condition() && std::chrono::steady_clock::now() < deadline) { + g_main_context_iteration(main_context_, FALSE); + std::this_thread::sleep_for(1ms); + } + return condition(); + } + + GMainContext* main_context_ = nullptr; + std::unique_ptr event_loop_; + std::vector executed_tasks_; + uint64_t execution_time_ = 0; + std::thread::id execution_thread_; + std::function on_task_; +}; + +TEST_F(TizenEventLoopTest, ExecutesPastAndImmediateTasksInPostingOrder) { + const uint64_t now = GetCurrentTimeNanos(); + event_loop_->PostTask(MakeTask(1), now - 1); + event_loop_->PostTask(MakeTask(2), now); + event_loop_->PostTask(MakeTask(3), now); + + ASSERT_TRUE(RunUntil([this] { return executed_tasks_.size() == 3; })); + EXPECT_EQ(executed_tasks_, (std::vector{1, 2, 3})); +} + +TEST_F(TizenEventLoopTest, ReschedulesEarlierTaskWithoutRunningItEarly) { + const uint64_t now = GetCurrentTimeNanos(); + const uint64_t earlier_target = now + 20'000'000; + event_loop_->PostTask(MakeTask(1), now + 500'000'000); + event_loop_->PostTask(MakeTask(2), earlier_target); + + ASSERT_TRUE(RunUntil([this] { return !executed_tasks_.empty(); }, 250ms)); + EXPECT_EQ(executed_tasks_, (std::vector{2})); + EXPECT_GE(execution_time_, earlier_target); +} + +TEST_F(TizenEventLoopTest, RunsWorkerThreadTaskOnMainThread) { + std::thread worker( + [this] { event_loop_->PostTask(MakeTask(1), GetCurrentTimeNanos()); }); + worker.join(); + + ASSERT_TRUE(RunUntil([this] { return !executed_tasks_.empty(); })); + EXPECT_EQ(execution_thread_, std::this_thread::get_id()); +} + +TEST_F(TizenEventLoopTest, SupportsReentrantPostTask) { + on_task_ = [this](const FlutterTask& task) { + if (task.task == 1) { + event_loop_->PostTask(MakeTask(2), GetCurrentTimeNanos()); + } + }; + event_loop_->PostTask(MakeTask(1), GetCurrentTimeNanos()); + + ASSERT_TRUE(RunUntil([this] { return executed_tasks_.size() == 2; })); + EXPECT_EQ(executed_tasks_, (std::vector{1, 2})); +} + +TEST_F(TizenEventLoopTest, RemovesPendingSourceOnDestruction) { + event_loop_->PostTask(MakeTask(1), GetCurrentTimeNanos()); + event_loop_.reset(); + + g_main_context_iteration(main_context_, FALSE); + EXPECT_TRUE(executed_tasks_.empty()); +} + +} // namespace testing +} // namespace flutter diff --git a/flutter/shell/platform/tizen/tizen_input_method_context_tcore.cc b/flutter/shell/platform/tizen/tizen_input_method_context_tcore.cc new file mode 100644 index 00000000..5f07bfe4 --- /dev/null +++ b/flutter/shell/platform/tizen/tizen_input_method_context_tcore.cc @@ -0,0 +1,453 @@ +// Copyright 2026 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "tizen_input_method_context_tcore.h" + +#include + +#include "flutter/shell/platform/tizen/logger.h" + +namespace { + +tizen_core_imf_input_panel_layout_e TextInputTypeToImfInputPanelLayout( + const std::string& text_input_type) { + if (text_input_type == "TextInputType.text" || + text_input_type == "TextInputType.multiline") { + return TIZEN_CORE_IMF_INPUT_PANEL_LAYOUT_NORMAL; + } else if (text_input_type == "TextInputType.number") { + return TIZEN_CORE_IMF_INPUT_PANEL_LAYOUT_NUMBERONLY; + } else if (text_input_type == "TextInputType.phone") { + return TIZEN_CORE_IMF_INPUT_PANEL_LAYOUT_PHONENUMBER; + } else if (text_input_type == "TextInputType.datetime") { + return TIZEN_CORE_IMF_INPUT_PANEL_LAYOUT_DATETIME; + } else if (text_input_type == "TextInputType.emailAddress" || + text_input_type == "TextInputType.twitter") { + return TIZEN_CORE_IMF_INPUT_PANEL_LAYOUT_EMAIL; + } else if (text_input_type == "TextInputType.url" || + text_input_type == "TextInputType.webSearch") { + return TIZEN_CORE_IMF_INPUT_PANEL_LAYOUT_URL; + } else if (text_input_type == "TextInputType.visiblePassword") { + return TIZEN_CORE_IMF_INPUT_PANEL_LAYOUT_PASSWORD; + } else { + FT_LOG(Warn) << "The requested input type " << text_input_type + << " is not supported."; + return TIZEN_CORE_IMF_INPUT_PANEL_LAYOUT_NORMAL; + } +} + +tizen_core_imf_keyboard_modifiers_e ModifiersToImfModifiers( + unsigned int modifiers) { + unsigned int imf_modifiers = TIZEN_CORE_IMF_KEYBOARD_MODIFIERS_NONE; + if (modifiers & TIZEN_CORE_WL_MODIFIER_SHIFT) { + imf_modifiers |= TIZEN_CORE_IMF_KEYBOARD_MODIFIERS_SHIFT; + } + if (modifiers & TIZEN_CORE_WL_MODIFIER_ALT) { + imf_modifiers |= TIZEN_CORE_IMF_KEYBOARD_MODIFIERS_ALT; + } + if (modifiers & TIZEN_CORE_WL_MODIFIER_CTRL) { + imf_modifiers |= TIZEN_CORE_IMF_KEYBOARD_MODIFIERS_CTRL; + } + if (modifiers & TIZEN_CORE_WL_MODIFIER_WIN) { + imf_modifiers |= TIZEN_CORE_IMF_KEYBOARD_MODIFIERS_WIN; + } + if (modifiers & TIZEN_CORE_WL_MODIFIER_ALTGR) { + imf_modifiers |= TIZEN_CORE_IMF_KEYBOARD_MODIFIERS_ALTGR; + } + return static_cast(imf_modifiers); +} + +tizen_core_imf_keyboard_locks_e ModifiersToImfLocks(unsigned int modifiers) { + unsigned int locks = TIZEN_CORE_IMF_KEYBOARD_LOCKS_NONE; + if (modifiers & TIZEN_CORE_WL_MODIFIER_NUM) { + locks |= TIZEN_CORE_IMF_KEYBOARD_LOCKS_NUM; + } + if (modifiers & TIZEN_CORE_WL_MODIFIER_CAPS) { + locks |= TIZEN_CORE_IMF_KEYBOARD_LOCKS_CAPS; + } + if (modifiers & TIZEN_CORE_WL_MODIFIER_SCROLL) { + locks |= TIZEN_CORE_IMF_KEYBOARD_LOCKS_SCROLL; + } + return static_cast(locks); +} + +tizen_core_imf_event_key_h CreateImfKeyEventFromTcoreWlEvent(void* event) { + auto* ev = static_cast(event); + + tizen_core_imf_event_key_h imf_key = nullptr; + tizen_core_imf_event_key_create(&imf_key); + if (!imf_key) { + return nullptr; + } + + char* keyname = nullptr; + tizen_core_wl_event_key_get_keyname(ev, &keyname); + if (keyname) { + tizen_core_imf_event_key_set_keyname(imf_key, keyname); + tizen_core_imf_event_key_set_key(imf_key, keyname); + free(keyname); + } + + char* keysymbol = nullptr; + tizen_core_wl_event_key_get_keysymbol(ev, &keysymbol); + if (keysymbol) { + tizen_core_imf_event_key_set_string(imf_key, keysymbol); + free(keysymbol); + } + + char* compose = nullptr; + tizen_core_wl_event_key_get_compose(ev, &compose); + if (compose) { + tizen_core_imf_event_key_set_compose(imf_key, compose); + free(compose); + } + + unsigned int keycode = 0; + tizen_core_wl_event_key_get_keycode(ev, &keycode); + tizen_core_imf_event_key_set_keycode(imf_key, keycode); + + unsigned int modifiers = 0; + tizen_core_wl_event_key_get_modifiers(ev, &modifiers); + tizen_core_imf_event_key_set_modifiers(imf_key, + ModifiersToImfModifiers(modifiers)); + tizen_core_imf_event_key_set_locks(imf_key, ModifiersToImfLocks(modifiers)); + + uint32_t timestamp = 0; + tizen_core_wl_event_input_base_get_timestamp(ev, ×tamp); + tizen_core_imf_event_key_set_timestamp(imf_key, timestamp); + + char* dev_identifier = nullptr; + tizen_core_wl_event_input_base_get_device_identifier(ev, &dev_identifier); + if (dev_identifier) { + tizen_core_imf_event_key_set_device_name(imf_key, dev_identifier); + free(dev_identifier); + } + + return imf_key; +} + +} // namespace + +namespace flutter { + +TizenInputMethodContext::TizenInputMethodContext(uintptr_t window_id) { + tizen_core_imf_init(); + + if (tizen_core_imf_context_create(&imf_context_) != + TIZEN_CORE_IMF_ERROR_NONE) { + FT_LOG(Error) << "Failed to create tizen_core_imf_context."; + return; + } + + tizen_core_imf_context_set_client_window(imf_context_, + reinterpret_cast(window_id)); + SetContextOptions(); + SetInputPanelOptions(); + RegisterEventCallbacks(); + RegisterInputPanelEventCallback(); +} + +TizenInputMethodContext::~TizenInputMethodContext() { + UnregisterInputPanelEventCallback(); + UnregisterEventCallbacks(); + + if (imf_context_) { + tizen_core_imf_context_destroy(imf_context_); + } + + tizen_core_imf_shutdown(); +} + +bool TizenInputMethodContext::HandleTcoreWlEventKey(void* event, bool is_down) { + FT_ASSERT(imf_context_); + FT_ASSERT(event); + + tizen_core_imf_event_key_h imf_key = CreateImfKeyEventFromTcoreWlEvent(event); + if (!imf_key) { + return false; + } + + bool filter_result = false; + tizen_core_imf_context_filter_event(imf_context_, + is_down + ? TIZEN_CORE_IMF_EVENT_TYPE_KEY_DOWN + : TIZEN_CORE_IMF_EVENT_TYPE_KEY_UP, + imf_key, &filter_result); + tizen_core_imf_event_key_destroy(imf_key); + return filter_result; +} + +#ifdef NUI_SUPPORT +bool TizenInputMethodContext::HandleNuiKeyEvent(const char* device_name, + uint32_t device_class, + uint32_t device_subclass, + const char* key, + const char* string, + uint32_t modifiers, + uint32_t scan_code, + size_t timestamp, + bool is_down) { + tizen_core_imf_event_key_h imf_key = nullptr; + tizen_core_imf_event_key_create(&imf_key); + if (!imf_key) { + return false; + } + + if (key) { + tizen_core_imf_event_key_set_keyname(imf_key, key); + tizen_core_imf_event_key_set_key(imf_key, key); + } + if (string) { + tizen_core_imf_event_key_set_string(imf_key, string); + } + + tizen_core_imf_event_key_set_modifiers(imf_key, + ModifiersToImfModifiers(modifiers)); + tizen_core_imf_event_key_set_locks(imf_key, ModifiersToImfLocks(modifiers)); + tizen_core_imf_event_key_set_keycode(imf_key, scan_code); + tizen_core_imf_event_key_set_timestamp(imf_key, timestamp); + + if (device_name) { + tizen_core_imf_event_key_set_device_name(imf_key, device_name); + } + tizen_core_imf_event_key_set_device_class( + imf_key, static_cast(device_class)); + tizen_core_imf_event_key_set_device_subclass( + imf_key, static_cast(device_subclass)); + + bool filter_result = false; + tizen_core_imf_context_filter_event(imf_context_, + is_down + ? TIZEN_CORE_IMF_EVENT_TYPE_KEY_DOWN + : TIZEN_CORE_IMF_EVENT_TYPE_KEY_UP, + imf_key, &filter_result); + tizen_core_imf_event_key_destroy(imf_key); + return filter_result; +} +#endif + +InputPanelGeometry TizenInputMethodContext::GetInputPanelGeometry() { + FT_ASSERT(imf_context_); + InputPanelGeometry geometry; + tizen_core_imf_context_get_input_panel_geometry( + imf_context_, &geometry.x, &geometry.y, &geometry.w, &geometry.h); + return geometry; +} + +void TizenInputMethodContext::ResetInputMethodContext() { + FT_ASSERT(imf_context_); + tizen_core_imf_context_reset(imf_context_); +} + +void TizenInputMethodContext::ShowInputPanel() { + FT_ASSERT(imf_context_); + tizen_core_imf_context_input_panel_show(imf_context_); + tizen_core_imf_context_focus_in(imf_context_); +} + +void TizenInputMethodContext::HideInputPanel() { + FT_ASSERT(imf_context_); + tizen_core_imf_context_focus_out(imf_context_); + tizen_core_imf_context_input_panel_hide(imf_context_); +} + +bool TizenInputMethodContext::IsInputPanelShown() { + tizen_core_imf_input_panel_state_e state; + tizen_core_imf_context_get_input_panel_state(imf_context_, &state); + return state == TIZEN_CORE_IMF_INPUT_PANEL_STATE_SHOW; +} + +void TizenInputMethodContext::SetInputPanelLayout( + const std::string& input_type) { + FT_ASSERT(imf_context_); + tizen_core_imf_input_panel_layout_e panel_layout = + TextInputTypeToImfInputPanelLayout(input_type); + tizen_core_imf_context_set_input_panel_layout(imf_context_, panel_layout); +} + +void TizenInputMethodContext::SetInputPanelLayoutVariation(bool is_signed, + bool is_decimal) { + tizen_core_imf_layout_numberonly_variation_e variation; + if (is_signed && is_decimal) { + variation = TIZEN_CORE_IMF_LAYOUT_NUMBERONLY_VARIATION_SIGNED_AND_DECIMAL; + } else if (is_signed) { + variation = TIZEN_CORE_IMF_LAYOUT_NUMBERONLY_VARIATION_SIGNED; + } else if (is_decimal) { + variation = TIZEN_CORE_IMF_LAYOUT_NUMBERONLY_VARIATION_DECIMAL; + } else { + variation = TIZEN_CORE_IMF_LAYOUT_NUMBERONLY_VARIATION_NORMAL; + } + tizen_core_imf_context_set_input_panel_layout_variation(imf_context_, + variation); +} + +void TizenInputMethodContext::SetAutocapitalType(const std::string& type) { + tizen_core_imf_autocapital_type_e autocapital_type = + TIZEN_CORE_IMF_AUTOCAPITAL_TYPE_NONE; + + if (type == "TextCapitalization.characters") { + autocapital_type = TIZEN_CORE_IMF_AUTOCAPITAL_TYPE_ALLCHARACTER; + } else if (type == "TextCapitalization.words") { + autocapital_type = TIZEN_CORE_IMF_AUTOCAPITAL_TYPE_WORD; + } else if (type == "TextCapitalization.sentences") { + autocapital_type = TIZEN_CORE_IMF_AUTOCAPITAL_TYPE_SENTENCE; + } else if (type == "TextCapitalization.none") { + autocapital_type = TIZEN_CORE_IMF_AUTOCAPITAL_TYPE_NONE; + } + tizen_core_imf_context_set_autocapital_type(imf_context_, autocapital_type); +} + +void TizenInputMethodContext::SetEditingActive(bool active) { + if (!imf_context_) { + return; + } + if (active) { + tizen_core_imf_context_focus_in(imf_context_); + } else { + tizen_core_imf_context_focus_out(imf_context_); + } +} + +void TizenInputMethodContext::SetInputPanelEnabled(bool enabled) { + if (!imf_context_) { + return; + } + tizen_core_imf_context_set_input_panel_enabled(imf_context_, enabled); +} + +void TizenInputMethodContext::RegisterEventCallbacks() { + FT_ASSERT(imf_context_); + + // commit callback + event_callbacks_[TIZEN_CORE_IMF_CALLBACK_COMMIT] = + [](tizen_core_imf_context_h ctx, void* event_info, void* data) { + auto* self = static_cast(data); + char* str = static_cast(event_info); + if (self->on_commit_) { + self->on_commit_(str); + } + }; + tizen_core_imf_context_add_event_callback( + imf_context_, TIZEN_CORE_IMF_CALLBACK_COMMIT, + event_callbacks_[TIZEN_CORE_IMF_CALLBACK_COMMIT], this); + + // pre-edit start callback + event_callbacks_[TIZEN_CORE_IMF_CALLBACK_PREEDIT_START] = + [](tizen_core_imf_context_h ctx, void* event_info, void* data) { + auto* self = static_cast(data); + if (self->on_preedit_start_) { + self->on_preedit_start_(); + } + }; + tizen_core_imf_context_add_event_callback( + imf_context_, TIZEN_CORE_IMF_CALLBACK_PREEDIT_START, + event_callbacks_[TIZEN_CORE_IMF_CALLBACK_PREEDIT_START], this); + + // pre-edit end callback + event_callbacks_[TIZEN_CORE_IMF_CALLBACK_PREEDIT_END] = + [](tizen_core_imf_context_h ctx, void* event_info, void* data) { + auto* self = static_cast(data); + if (self->on_preedit_end_) { + self->on_preedit_end_(); + } + }; + tizen_core_imf_context_add_event_callback( + imf_context_, TIZEN_CORE_IMF_CALLBACK_PREEDIT_END, + event_callbacks_[TIZEN_CORE_IMF_CALLBACK_PREEDIT_END], this); + + // pre-edit changed callback + event_callbacks_[TIZEN_CORE_IMF_CALLBACK_PREEDIT_CHANGED] = + [](tizen_core_imf_context_h ctx, void* event_info, void* data) { + auto* self = static_cast(data); + if (self->on_preedit_changed_) { + char* str = nullptr; + int cursor_pos = 0; + tizen_core_imf_context_get_preedit_string(ctx, &str, nullptr, nullptr, + &cursor_pos); + if (str) { + self->on_preedit_changed_(str, cursor_pos); + free(str); + } + } + }; + tizen_core_imf_context_add_event_callback( + imf_context_, TIZEN_CORE_IMF_CALLBACK_PREEDIT_CHANGED, + event_callbacks_[TIZEN_CORE_IMF_CALLBACK_PREEDIT_CHANGED], this); +} + +void TizenInputMethodContext::UnregisterEventCallbacks() { + FT_ASSERT(imf_context_); + tizen_core_imf_context_del_event_callback( + imf_context_, TIZEN_CORE_IMF_CALLBACK_COMMIT, + event_callbacks_[TIZEN_CORE_IMF_CALLBACK_COMMIT]); + tizen_core_imf_context_del_event_callback( + imf_context_, TIZEN_CORE_IMF_CALLBACK_PREEDIT_CHANGED, + event_callbacks_[TIZEN_CORE_IMF_CALLBACK_PREEDIT_CHANGED]); + tizen_core_imf_context_del_event_callback( + imf_context_, TIZEN_CORE_IMF_CALLBACK_PREEDIT_START, + event_callbacks_[TIZEN_CORE_IMF_CALLBACK_PREEDIT_START]); + tizen_core_imf_context_del_event_callback( + imf_context_, TIZEN_CORE_IMF_CALLBACK_PREEDIT_END, + event_callbacks_[TIZEN_CORE_IMF_CALLBACK_PREEDIT_END]); +} + +void TizenInputMethodContext::SetContextOptions() { + FT_ASSERT(imf_context_); + tizen_core_imf_context_set_autocapital_type( + imf_context_, TIZEN_CORE_IMF_AUTOCAPITAL_TYPE_NONE); +} + +void TizenInputMethodContext::SetInputPanelOptions() { + FT_ASSERT(imf_context_); + tizen_core_imf_context_set_input_panel_layout( + imf_context_, TIZEN_CORE_IMF_INPUT_PANEL_LAYOUT_NORMAL); + tizen_core_imf_context_set_input_panel_return_key_type( + imf_context_, TIZEN_CORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_DEFAULT); +} + +void TizenInputMethodContext::InputPanelStateChangedCallback( + tizen_core_imf_context_h ctx, + int value, + void* data) { + auto* self = static_cast(data); + tizen_core_imf_input_panel_state_e state = + static_cast(value); + + std::string state_str; + switch (state) { + case TIZEN_CORE_IMF_INPUT_PANEL_STATE_SHOW: + state_str = "show"; + break; + case TIZEN_CORE_IMF_INPUT_PANEL_STATE_HIDE: + state_str = "hide"; + break; + case TIZEN_CORE_IMF_INPUT_PANEL_STATE_WILL_SHOW: + state_str = "will_show"; + break; + default: + state_str = "unknown"; + break; + } + + if (self->on_input_panel_state_changed_) { + self->on_input_panel_state_changed_(state_str); + } +} + +void TizenInputMethodContext::RegisterInputPanelEventCallback() { + FT_ASSERT(imf_context_); + + tizen_core_imf_context_add_input_panel_event_callback( + imf_context_, TIZEN_CORE_IMF_INPUT_PANEL_EVENT_STATE, + InputPanelStateChangedCallback, this); +} + +void TizenInputMethodContext::UnregisterInputPanelEventCallback() { + FT_ASSERT(imf_context_); + + tizen_core_imf_context_del_input_panel_event_callback( + imf_context_, TIZEN_CORE_IMF_INPUT_PANEL_EVENT_STATE, + InputPanelStateChangedCallback); +} + +} // namespace flutter diff --git a/flutter/shell/platform/tizen/tizen_input_method_context_tcore.h b/flutter/shell/platform/tizen/tizen_input_method_context_tcore.h new file mode 100644 index 00000000..9c4d79d5 --- /dev/null +++ b/flutter/shell/platform/tizen/tizen_input_method_context_tcore.h @@ -0,0 +1,107 @@ +// Copyright 2026 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EMBEDDER_TIZEN_INPUT_METHOD_CONTEXT_H_ +#define EMBEDDER_TIZEN_INPUT_METHOD_CONTEXT_H_ + +#include + +#include +#include +#include + +namespace flutter { + +using OnCommit = std::function; +using OnPreeditChanged = std::function; +using OnPreeditStart = std::function; +using OnPreeditEnd = std::function; +using OnInputPanelStateChanged = std::function; + +struct InputPanelGeometry { + int32_t x = 0, y = 0, w = 0, h = 0; +}; + +class TizenInputMethodContext { + public: + TizenInputMethodContext(uintptr_t window_id); + ~TizenInputMethodContext(); + + bool HandleTcoreWlEventKey(void* event, bool is_down); + +#ifdef NUI_SUPPORT + bool HandleNuiKeyEvent(const char* device_name, + uint32_t device_class, + uint32_t device_subclass, + const char* key, + const char* string, + uint32_t modifiers, + uint32_t scan_code, + size_t timestamp, + bool is_down); +#endif + + InputPanelGeometry GetInputPanelGeometry(); + + void ResetInputMethodContext(); + + void ShowInputPanel(); + + void HideInputPanel(); + + bool IsInputPanelShown(); + + void SetInputPanelLayout(const std::string& layout); + + void SetInputPanelLayoutVariation(bool is_signed, bool is_decimal); + + void SetAutocapitalType(const std::string& type); + + void SetEditingActive(bool active); + + void SetInputPanelEnabled(bool enabled); + + void SetOnCommit(OnCommit callback) { on_commit_ = callback; } + + void SetOnPreeditChanged(OnPreeditChanged callback) { + on_preedit_changed_ = callback; + } + + void SetOnPreeditStart(OnPreeditStart callback) { + on_preedit_start_ = callback; + } + + void SetOnPreeditEnd(OnPreeditEnd callback) { on_preedit_end_ = callback; } + + void SetOnInputPanelStateChanged(OnInputPanelStateChanged callback) { + on_input_panel_state_changed_ = callback; + } + + void RegisterInputPanelEventCallback(); + void UnregisterInputPanelEventCallback(); + + private: + static void InputPanelStateChangedCallback(tizen_core_imf_context_h ctx, + int value, + void* data); + + void RegisterEventCallbacks(); + void UnregisterEventCallbacks(); + + void SetContextOptions(); + void SetInputPanelOptions(); + + tizen_core_imf_context_h imf_context_ = nullptr; + OnCommit on_commit_; + OnPreeditChanged on_preedit_changed_; + OnPreeditStart on_preedit_start_; + OnPreeditEnd on_preedit_end_; + OnInputPanelStateChanged on_input_panel_state_changed_; + std::unordered_map + event_callbacks_; +}; + +} // namespace flutter + +#endif // EMBEDDER_TIZEN_INPUT_METHOD_CONTEXT_H_ diff --git a/flutter/shell/platform/tizen/tizen_renderer_egl.cc b/flutter/shell/platform/tizen/tizen_renderer_egl.cc index 5da84ad7..f2847091 100644 --- a/flutter/shell/platform/tizen/tizen_renderer_egl.cc +++ b/flutter/shell/platform/tizen/tizen_renderer_egl.cc @@ -4,8 +4,12 @@ #include "flutter/shell/platform/tizen/tizen_renderer_egl.h" +#ifdef USE_TCORE_WL +#include +#else #define EFL_BETA_API_SUPPORT #include +#endif #include #include #include @@ -89,8 +93,13 @@ bool TizenRendererEgl::CreateSurface(void* render_target, const EGLint attribs[] = {EGL_NONE}; if (render_target_display) { +#ifdef USE_TCORE_WL + void* egl_window = tizen_core_wl_egl_window_native_get( + static_cast(render_target)); +#else const auto egl_window = ecore_wl2_egl_window_native_get( static_cast(render_target)); +#endif egl_surface_ = eglCreateWindowSurface( egl_display_, egl_config_, reinterpret_cast(egl_window), attribs); diff --git a/flutter/shell/platform/tizen/tizen_view_base.h b/flutter/shell/platform/tizen/tizen_view_base.h index 40a1b8f9..9354dd4a 100644 --- a/flutter/shell/platform/tizen/tizen_view_base.h +++ b/flutter/shell/platform/tizen/tizen_view_base.h @@ -10,7 +10,11 @@ #include #include +#ifdef USE_TCORE_WL +#include "flutter/shell/platform/tizen/tizen_input_method_context_tcore.h" +#else #include "flutter/shell/platform/tizen/tizen_input_method_context.h" +#endif #include "flutter/shell/platform/tizen/tizen_view_event_handler_delegate.h" namespace flutter { diff --git a/flutter/shell/platform/tizen/tizen_window_ecore_wl2.cc b/flutter/shell/platform/tizen/tizen_window_ecore_wl2.cc index 98bfb7cd..c8e5946e 100644 --- a/flutter/shell/platform/tizen/tizen_window_ecore_wl2.cc +++ b/flutter/shell/platform/tizen/tizen_window_ecore_wl2.cc @@ -7,10 +7,11 @@ #ifdef TV_PROFILE #include #include -#include #include #include #include + +#include "flutter/shell/platform/tizen/tizen_window_util.h" #endif #include "flutter/shell/platform/embedder/embedder.h" @@ -268,29 +269,8 @@ void TizenWindowEcoreWl2::SetWindowOptions() { void TizenWindowEcoreWl2::EnableCursor() { #ifdef TV_PROFILE - // dlopen is used here because the TV-specific library libvd-win-util.so - // and the relevant headers are not present in the rootstrap. - void* handle = dlopen("libvd-win-util.so", RTLD_LAZY); - if (!handle) { - FT_LOG(Error) << "Could not open a shared library libvd-win-util.so."; - return; - } - - // These functions are defined in vd-win-util's cursor_module.h. - int (*CursorModule_Initialize)(wl_display* display, wl_registry* registry, - wl_seat* seat, unsigned int id); - int (*Cursor_Set_Config)(wl_surface* surface, uint32_t config_type, - void* data); - void (*CursorModule_Finalize)(void); - *(void**)(&CursorModule_Initialize) = - dlsym(handle, "CursorModule_Initialize"); - *(void**)(&Cursor_Set_Config) = dlsym(handle, "Cursor_Set_Config"); - *(void**)(&CursorModule_Finalize) = dlsym(handle, "CursorModule_Finalize"); - - if (!CursorModule_Initialize || !Cursor_Set_Config || - !CursorModule_Finalize) { - FT_LOG(Error) << "Could not load symbols from the library."; - dlclose(handle); + TizenWindowUtil window_util; + if (!window_util.IsValid()) { return; } @@ -300,7 +280,6 @@ void TizenWindowEcoreWl2::EnableCursor() { if (!registry || !seat) { FT_LOG(Error) << "Could not retreive wl_registry or wl_seat from the display."; - dlclose(handle); return; } @@ -309,9 +288,7 @@ void TizenWindowEcoreWl2::EnableCursor() { EINA_ITERATOR_FOREACH(iter, global) { if (strcmp(global->interface, "tizen_cursor") == 0) { - if (!CursorModule_Initialize(wl2_display_, registry, seat, global->id)) { - FT_LOG(Error) << "Failed to initialize the cursor module."; - } + window_util.InitializeCursorModule(wl2_display_, registry, seat, global->id); } } eina_iterator_free(iter); @@ -319,92 +296,28 @@ void TizenWindowEcoreWl2::EnableCursor() { ecore_wl2_sync(); wl_surface* surface = ecore_wl2_window_surface_get(ecore_wl2_window_); - // The config_type 1 refers to TIZEN_CURSOR_CONFIG_CURSOR_AVAILABLE - // defined in the TV extension protocol tizen-extension-tv.xml. - if (!Cursor_Set_Config(surface, 1, nullptr)) { - FT_LOG(Error) << "Failed to set a cursor config value."; - } + window_util.SetCursorConfig( + surface, TizenWindowUtil::kCursorConfigCursorAvailable, nullptr); - CursorModule_Finalize(); - dlclose(handle); + window_util.FinalizeCursorModule(); #endif } #ifdef TV_PROFILE -typedef enum _MouseSupport { DISABLE = 0, ENABLE } MouseSupport; -typedef enum _Device_Type { MOUSE_DEVICE = 3, TOUCH_DEVICE } Device_Type; - void TizenWindowEcoreWl2::SetPointingDeviceSupport() { - // dlopen is used here because the TV-specific library libvd-win-util.so - // and the relevant headers are not present in the rootstrap. - void* handle = dlopen("libvd-win-util.so", RTLD_LAZY); - if (!handle) { - FT_LOG(Error) << "Could not open a shared library libvd-win-util.so."; - return; - } - - // These functions are defined in vd-win-util's cursor_module.h. - int (*Mouse_Pointer_Support)(MouseSupport type, void* ecore_wl2_win); - *(void**)(&Mouse_Pointer_Support) = dlsym(handle, "Mouse_Pointer_Support"); - - if (!Mouse_Pointer_Support) { - FT_LOG(Error) << "Could not load symbols from the library."; - dlclose(handle); - return; - } - - Mouse_Pointer_Support(pointing_device_support_ ? ENABLE : DISABLE, - ecore_wl2_window_); - dlclose(handle); + TizenWindowUtil window_util; + window_util.SetMousePointerSupport(pointing_device_support_, ecore_wl2_window_); } void TizenWindowEcoreWl2::SetFloatingMenuSupport() { - // dlopen is used here because the TV-specific library libvd-win-util.so - // and the relevant headers are not present in the rootstrap. - void* handle = dlopen("libvd-win-util.so", RTLD_LAZY); - if (!handle) { - FT_LOG(Error) << "Could not open a shared library libvd-win-util.so."; - return; - } - - // These functions are defined in vd-win-util's cursor_module.h. - int (*Mouse_Pointer_Not_Allow)(int enable, void* ecore_wl2_win); - *(void**)(&Mouse_Pointer_Not_Allow) = - dlsym(handle, "Mouse_Pointer_Not_Allow"); - - if (!Mouse_Pointer_Not_Allow) { - FT_LOG(Error) << "Could not load symbols from the library."; - dlclose(handle); - return; - } - - Mouse_Pointer_Not_Allow(!floating_menu_support_, ecore_wl2_window_); - dlclose(handle); + TizenWindowUtil window_util; + window_util.SetMousePointerNotAllow(!floating_menu_support_, ecore_wl2_window_); } void TizenWindowEcoreWl2::ShowUnsupportedToast() { - // dlopen is used here because the TV-specific library libvd-win-util.so - // and the relevant headers are not present in the rootstrap. - void* handle = dlopen("libvd-win-util.so", RTLD_LAZY); - if (!handle) { - FT_LOG(Error) << "Could not open a shared library libvd-win-util.so."; - return; - } - - // These functions are defined in vd-win-util's cursor_module.h. - void (*Unsupported_Toast_Launch)(Device_Type type, int show, int enable, - void* ecore_wl2_win); - *(void**)(&Unsupported_Toast_Launch) = - dlsym(handle, "Unsupported_Toast_Launch"); - - if (!Unsupported_Toast_Launch) { - FT_LOG(Error) << "Could not load symbols from the library."; - dlclose(handle); - return; - } - - Unsupported_Toast_Launch(MOUSE_DEVICE, 1, 1, ecore_wl2_window_); - dlclose(handle); + TizenWindowUtil window_util; + window_util.LaunchUnsupportedToast(TizenWindowUtil::kDeviceTypeMouse, true, + true, ecore_wl2_window_); } #endif diff --git a/flutter/shell/platform/tizen/tizen_window_tcore_wl.cc b/flutter/shell/platform/tizen/tizen_window_tcore_wl.cc new file mode 100644 index 00000000..89da9fd0 --- /dev/null +++ b/flutter/shell/platform/tizen/tizen_window_tcore_wl.cc @@ -0,0 +1,874 @@ +// Copyright 2026 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "tizen_window_tcore_wl.h" + +#ifdef TV_PROFILE +#include +#include +#include +#include +#include + +#include "flutter/shell/platform/tizen/tizen_window_util.h" +#endif + +#include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/tizen/logger.h" +#include "flutter/shell/platform/tizen/tizen_view_event_handler_delegate.h" + +namespace flutter { + +namespace { + +constexpr int kScrollDirectionVertical = 0; +constexpr int kScrollDirectionHorizontal = 1; + +#ifdef TV_PROFILE +constexpr char kSysMouseCursorPointerSizeVConfKey[] = + "db/menu/system/mouse-pointer-size"; +constexpr char kSysPointingDeviceSupportToastSharedPreferenceKey[] = + "flutter-tizen/preference/pointing-device-support-toast"; +constexpr char kTcoreWlInputCursorThemeName[] = "vd-cursors"; +#endif + +FlutterPointerMouseButtons ToFlutterPointerButton(int32_t button) { + if (button == 2) { + return kFlutterPointerButtonMouseMiddle; + } else if (button == 3) { + return kFlutterPointerButtonMouseSecondary; + } else { + return kFlutterPointerButtonMousePrimary; + } +} + +// FlutterPointerDeviceKind GetDeviceKindFromEventType(int event_type) { +// if (event_type == TIZEN_CORE_WL_EVENT_MOUSE_BUTTON_DOWN || +// event_type == TIZEN_CORE_WL_EVENT_MOUSE_BUTTON_UP || +// event_type == TIZEN_CORE_WL_EVENT_MOUSE_MOVE || +// event_type == TIZEN_CORE_WL_EVENT_MOUSE_WHEEL) { +// // The event system doesn't distinguish mouse vs touch at event level. +// // Default to touch; mouse events can be differentiated by touch_id == 0. +// return kFlutterPointerDeviceKindTouch; +// } +// return kFlutterPointerDeviceKindTouch; +// } + +#ifdef TV_PROFILE +time_t GetBootTimeEpoch() { + struct timespec now, boot_time; + if (clock_gettime(CLOCK_REALTIME, &now) != 0) { + FT_LOG(Error) << "Fail to get clock_gettime(CLOCK_REALTIME)."; + return -1; + } + if (clock_gettime(CLOCK_BOOTTIME, &boot_time) != 0) { + FT_LOG(Error) << "Fail to get clock_gettime(CLOCK_BOOTTIME)."; + return -1; + } + time_t boot_time_epoch = now.tv_sec - boot_time.tv_sec; + return (boot_time_epoch / 10) * 10; +} + +bool PreferenceItemCallback(const char* key, void* user_data) { + char* app_id = (char*)user_data; + if (!app_id || !key) { + return true; + } + + std::string preference_key = + std::string(kSysPointingDeviceSupportToastSharedPreferenceKey) + "/" + + app_id; + if (!strncmp(key, preference_key.c_str(), preference_key.length())) { + preference_remove(key); + } + return true; +} + +std::string GetPreferenceKey(bool clear_exist_key) { + time_t boot_time = GetBootTimeEpoch(); + if (boot_time == -1) { + return ""; + } + + char* id = nullptr; + int ret = app_get_id(&id); + if (ret != APP_CONTROL_ERROR_NONE || !id) { + FT_LOG(Error) << "Fail to get app id."; + return std::string(); + } + + std::string app_id = id; + free(id); + + std::ostringstream boot_time_buffer; + boot_time_buffer << boot_time; + + std::string preference_key = + std::string(kSysPointingDeviceSupportToastSharedPreferenceKey) + "/" + + app_id + "/" + boot_time_buffer.str(); + + if (clear_exist_key) { + preference_foreach_item(PreferenceItemCallback, (void*)app_id.c_str()); + } + return preference_key; +} + +bool GetPointingDeviceToastPreference() { + bool show_unsupported_toast = false; + std::string preference_key = GetPreferenceKey(false); + if (preference_key.empty()) { + return false; + } + + int ret = + preference_get_boolean(preference_key.c_str(), &show_unsupported_toast); + if (ret != PREFERENCE_ERROR_NONE) { + return false; + } + return show_unsupported_toast; +} + +void SetPointingDevicePreference() { + std::string preference_key = GetPreferenceKey(true); + if (preference_key.empty()) { + return; + } + + int ret = preference_set_boolean(preference_key.c_str(), true); + if (ret != PREFERENCE_ERROR_NONE) { + FT_LOG(Error) << "Fail to set toasted preference."; + } +} +#endif + +} // namespace + +TizenWindowTcoreWl::TizenWindowTcoreWl(TizenGeometry geometry, + bool transparent, + bool focusable, + bool top_level, + bool pointing_device_support, + bool floating_menu_support, + void* window_handle = nullptr, + bool is_vulkan = false) + : TizenWindow(geometry, transparent, focusable, top_level) +#ifdef TV_PROFILE + , + pointing_device_support_(pointing_device_support), + floating_menu_support_(floating_menu_support) +#endif + , + is_vulkan_(is_vulkan) { + if (!CreateWindow(window_handle)) { + FT_LOG(Error) << "Failed to create a platform window."; + return; + } + + SetWindowOptions(); + RegisterEventHandlers(); + PrepareInputMethod(); + Show(); +} + +TizenWindowTcoreWl::~TizenWindowTcoreWl() { + UnregisterEventHandlers(); + DestroyWindow(); +} + +bool TizenWindowTcoreWl::CreateWindow(void* window_handle) { + if (tizen_core_wl_init() != TIZEN_CORE_WL_ERROR_NONE) { + FT_LOG(Error) << "Could not initialize tizen core wl."; + return false; + } + + if (tizen_core_wl_display_create(&tcore_wl_display_) != + TIZEN_CORE_WL_ERROR_NONE) { + FT_LOG(Error) << "Could not create tizen core wl display."; + return false; + } + + if (tizen_core_wl_display_connect(tcore_wl_display_, nullptr) != + TIZEN_CORE_WL_ERROR_NONE) { + FT_LOG(Error) << "Tizen core wl display not found."; + return false; + } + + tizen_core_wl_display_private_get_wl_display(tcore_wl_display_, + &wl2_display_); + + if (tizen_core_wl_display_sync(tcore_wl_display_) != + TIZEN_CORE_WL_ERROR_NONE) { + FT_LOG(Error) << "Failed to sync tizen core wl display."; + return false; + } + + if (tizen_core_wl_display_get_event(tcore_wl_display_, &tcore_wl_event_) != + TIZEN_CORE_WL_ERROR_NONE) { + FT_LOG(Error) << "Could not get tizen core wl event handle."; + return false; + } + + int32_t width = 0, height = 0; + GList* output_list = nullptr; + tizen_core_wl_display_get_output_device_list(tcore_wl_display_, &output_list); + if (output_list) { + tizen_core_wl_output_h output = + static_cast(output_list->data); + tizen_core_wl_output_device_get_geometry(output, &width, &height); + g_list_free(output_list); + } + if (width == 0 || height == 0) { + FT_LOG(Error) << "Invalid screen size: " << width << " x " << height; + return false; + } + + if (initial_geometry_.width == 0) { + initial_geometry_.width = width; + } + if (initial_geometry_.height == 0) { + initial_geometry_.height = height; + } + + if (window_handle == nullptr) { + if (tizen_core_wl_create_window( + tcore_wl_display_, nullptr, initial_geometry_.left, + initial_geometry_.top, initial_geometry_.width, + initial_geometry_.height, + &tcore_wl_window_) != TIZEN_CORE_WL_ERROR_NONE) { + FT_LOG(Error) << "Could not create tizen core wl window."; + return false; + } + } else { + tcore_wl_window_ = static_cast(window_handle); + } + + if (is_vulkan_) { + tizen_core_wl_window_private_get_wl_surface(tcore_wl_window_, + &wl2_surface_); + return wl2_surface_ && wl2_display_; + } else { + if (tizen_core_wl_create_egl_window( + tcore_wl_window_, initial_geometry_.width, initial_geometry_.height, + &tcore_wl_egl_window_) != TIZEN_CORE_WL_ERROR_NONE) { + FT_LOG(Error) << "Could not create tizen core wl egl window."; + return false; + } + return tcore_wl_egl_window_ && wl2_display_; + } +} + +void TizenWindowTcoreWl::SetWindowOptions() { + tizen_core_wl_window_set_type( + tcore_wl_window_, top_level_ ? TIZEN_CORE_WL_WINDOW_TYPE_NOTIFICATION + : TIZEN_CORE_WL_WINDOW_TYPE_TOPLEVEL); + if (top_level_) { + SetNotificationLevel(TIZEN_CORE_WL_NOTIFICATION_LEVEL_TOP); + } + + tizen_core_wl_window_set_position(tcore_wl_window_, initial_geometry_.left, + initial_geometry_.top); + tizen_core_wl_window_set_aux_hint(tcore_wl_window_, + "wm.policy.win.user.geometry", "1"); + + tizen_core_wl_window_set_alpha(tcore_wl_window_, transparent_); + + if (!focusable_) { + tizen_core_wl_window_set_focus_skip(tcore_wl_window_, true); + } + +#ifdef TV_PROFILE + tizen_core_wl_window_angle_e rotations[1] = {TIZEN_CORE_WL_WINDOW_ANGLE_0}; +#else + tizen_core_wl_window_angle_e rotations[4] = { + TIZEN_CORE_WL_WINDOW_ANGLE_0, TIZEN_CORE_WL_WINDOW_ANGLE_90, + TIZEN_CORE_WL_WINDOW_ANGLE_180, TIZEN_CORE_WL_WINDOW_ANGLE_270}; +#endif + tizen_core_wl_window_set_available_rotation_angle_list( + tcore_wl_window_, rotations, sizeof(rotations) / sizeof(rotations[0])); + EnableCursor(); +} + +void TizenWindowTcoreWl::EnableCursor() { +#ifdef TV_PROFILE + TizenWindowUtil window_util; + if (!window_util.IsValid()) { + return; + } + + tizen_core_wl_seat_h default_seat = nullptr; + tizen_core_wl_display_get_default_seat(tcore_wl_display_, &default_seat); + if (!default_seat) { + FT_LOG(Error) << "Could not get default seat."; + return; + } + struct wl_seat* seat = nullptr; + tizen_core_wl_seat_private_get_wl_seat(default_seat, &seat); + if (!seat) { + FT_LOG(Error) << "Could not get wl_seat from the default seat."; + return; + } + + int cursor_global_id = 0; + if (tizen_core_wl_display_private_get_global_id( + tcore_wl_display_, "tizen_cursor", &cursor_global_id) == + TIZEN_CORE_WL_ERROR_NONE && + cursor_global_id > 0) { + struct wl_registry* registry = wl_display_get_registry(wl2_display_); + if (registry) { + window_util.InitializeCursorModule(wl2_display_, registry, seat, + cursor_global_id); + wl_registry_destroy(registry); + } + } + + tizen_core_wl_display_sync(tcore_wl_display_); + + struct wl_surface* surface = nullptr; + tizen_core_wl_window_private_get_wl_surface(tcore_wl_window_, &surface); + if (surface) { + window_util.SetCursorConfig( + surface, TizenWindowUtil::kCursorConfigCursorAvailable, nullptr); + } + + window_util.FinalizeCursorModule(); +#endif +} + +#ifdef TV_PROFILE +void TizenWindowTcoreWl::SetPointingDeviceSupport() { + TizenWindowUtil window_util; + window_util.SetMousePointerSupport(pointing_device_support_, tcore_wl_window_); +} + +void TizenWindowTcoreWl::SetFloatingMenuSupport() { + TizenWindowUtil window_util; + window_util.SetMousePointerNotAllow(!floating_menu_support_, tcore_wl_window_); +} + +void TizenWindowTcoreWl::ShowUnsupportedToast() { + TizenWindowUtil window_util; + window_util.LaunchUnsupportedToast(TizenWindowUtil::kDeviceTypeMouse, true, + true, tcore_wl_window_); +} +#endif + +void TizenWindowTcoreWl::AddEventListener(tizen_core_wl_event_type_e type, + tizen_core_wl_event_cb callback) { + tizen_core_wl_event_listener_h listener = nullptr; + if (tizen_core_wl_event_add_listener(tcore_wl_event_, type, callback, this, + &listener) != TIZEN_CORE_WL_ERROR_NONE) { + FT_LOG(Error) << "Failed to add tizen core wl event listener."; + return; + } + tcore_event_listeners_.push_back(listener); +} + +void TizenWindowTcoreWl::RegisterEventHandlers() { + // Window rotation event. + AddEventListener( + TIZEN_CORE_WL_EVENT_WINDOW_ROTATION, + [](void* event, tizen_core_wl_event_type_e type, void* data) { + auto* self = static_cast(data); + if (self->view_delegate_) { + auto* base_event = + static_cast(event); + tizen_core_wl_window_h event_window = nullptr; + tizen_core_wl_event_window_base_get_window(base_event, &event_window); + if (event_window == self->tcore_wl_window_) { + tizen_core_wl_event_window_rotation_h rot_event = nullptr; + tizen_core_wl_event_window_base_to_window_rotation(base_event, + &rot_event); + tizen_core_wl_window_angle_e angle = TIZEN_CORE_WL_WINDOW_ANGLE_0; + tizen_core_wl_event_window_rotation_get_angle(rot_event, &angle); + int degree = static_cast(angle); + self->view_delegate_->OnRotate(degree); + tizen_core_wl_window_set_rotation_angle( + self->tcore_wl_window_, + static_cast(degree)); + tizen_core_wl_window_send_rotation_change_done( + self->tcore_wl_window_, degree); + } + } + }); + + // Window configure event. + if (!is_vulkan_) { + AddEventListener( + TIZEN_CORE_WL_EVENT_WINDOW_CONFIGURE_COMPLETE, + [](void* event, tizen_core_wl_event_type_e type, void* data) { + auto* self = static_cast(data); + if (self->view_delegate_) { + auto* base_event = + static_cast(event); + tizen_core_wl_window_h event_window = nullptr; + tizen_core_wl_event_window_base_get_window(base_event, + &event_window); + if (event_window == self->tcore_wl_window_) { + int x = 0, y = 0, w = 0, h = 0; + tizen_core_wl_window_get_geometry(self->tcore_wl_window_, &x, &y, + &w, &h); + int32_t rotation = self->GetRotation(); + tizen_core_wl_egl_window_resize(self->tcore_wl_egl_window_, w, h); + tizen_core_wl_egl_window_set_window_transform( + self->tcore_wl_egl_window_, rotation / 90); + self->view_delegate_->OnResize(x, y, w, h); + } + } + }); + } + + // Mouse button down. + AddEventListener( + TIZEN_CORE_WL_EVENT_MOUSE_BUTTON_DOWN, + [](void* event, tizen_core_wl_event_type_e type, void* data) { + auto* self = static_cast(data); +#ifdef TV_PROFILE + if ((!self->pointing_device_support_ || + !self->floating_menu_support_) && + !self->show_unsupported_toast_) { + bool shown = GetPointingDeviceToastPreference(); + if (!self->floating_menu_support_) { + self->SetFloatingMenuSupport(); + } + if (!shown) { + self->ShowUnsupportedToast(); + SetPointingDevicePreference(); + } + self->show_unsupported_toast_ = true; + if (self->floating_menu_support_ && !self->pointing_device_support_) { + self->SetPointingDeviceSupport(); + } + return; + } +#endif + if (self->view_delegate_) { + auto* ev = static_cast(event); + tizen_core_wl_window_h event_window = nullptr; + tizen_core_wl_event_input_base_get_window(ev, &event_window); + if (event_window == self->tcore_wl_window_) { + int x = 0, y = 0; + tizen_core_wl_event_mouse_button_get_position(ev, &x, &y); + unsigned int buttons = 0; + tizen_core_wl_event_mouse_button_get_buttons(ev, &buttons); + uint32_t timestamp = 0; + tizen_core_wl_event_input_base_get_timestamp(ev, ×tamp); + unsigned int touch_id = 0; + tizen_core_wl_event_mouse_button_get_touch_id(ev, &touch_id); + self->view_delegate_->OnPointerDown( + x, y, ToFlutterPointerButton(buttons), timestamp, + touch_id == 0 ? kFlutterPointerDeviceKindMouse + : kFlutterPointerDeviceKindTouch, + touch_id); + } + } + }); + + // Mouse button up. + AddEventListener( + TIZEN_CORE_WL_EVENT_MOUSE_BUTTON_UP, + [](void* event, tizen_core_wl_event_type_e type, void* data) { + auto* self = static_cast(data); + if (self->view_delegate_) { + auto* ev = static_cast(event); + tizen_core_wl_window_h event_window = nullptr; + tizen_core_wl_event_input_base_get_window(ev, &event_window); + if (event_window == self->tcore_wl_window_) { + int x = 0, y = 0; + tizen_core_wl_event_mouse_button_get_position(ev, &x, &y); + unsigned int buttons = 0; + tizen_core_wl_event_mouse_button_get_buttons(ev, &buttons); + uint32_t timestamp = 0; + tizen_core_wl_event_input_base_get_timestamp(ev, ×tamp); + unsigned int touch_id = 0; + tizen_core_wl_event_mouse_button_get_touch_id(ev, &touch_id); + self->view_delegate_->OnPointerUp( + x, y, ToFlutterPointerButton(buttons), timestamp, + touch_id == 0 ? kFlutterPointerDeviceKindMouse + : kFlutterPointerDeviceKindTouch, + touch_id); + } + } + }); + + // Mouse move. + AddEventListener( + TIZEN_CORE_WL_EVENT_MOUSE_MOVE, + [](void* event, tizen_core_wl_event_type_e type, void* data) { + auto* self = static_cast(data); + if (self->view_delegate_) { + auto* ev = static_cast(event); + tizen_core_wl_window_h event_window = nullptr; + tizen_core_wl_event_input_base_get_window(ev, &event_window); + if (event_window == self->tcore_wl_window_) { + int x = 0, y = 0; + tizen_core_wl_event_mouse_move_get_position(ev, &x, &y); + uint32_t timestamp = 0; + tizen_core_wl_event_input_base_get_timestamp(ev, ×tamp); + unsigned int touch_id = 0; + tizen_core_wl_event_mouse_move_get_touch_id(ev, &touch_id); + self->view_delegate_->OnPointerMove( + x, y, timestamp, + touch_id == 0 ? kFlutterPointerDeviceKindMouse + : kFlutterPointerDeviceKindTouch, + touch_id); + } + } + }); + + // Mouse wheel. + AddEventListener( + TIZEN_CORE_WL_EVENT_MOUSE_WHEEL, + [](void* event, tizen_core_wl_event_type_e type, void* data) { + auto* self = static_cast(data); + if (self->view_delegate_) { + auto* ev = static_cast(event); + tizen_core_wl_window_h event_window = nullptr; + tizen_core_wl_event_input_base_get_window(ev, &event_window); + if (event_window == self->tcore_wl_window_) { + int x = 0, y = 0; + tizen_core_wl_event_mouse_wheel_get_position(ev, &x, &y); + int direction = 0; + tizen_core_wl_event_mouse_wheel_get_direction(ev, &direction); + int z = 0; + tizen_core_wl_event_mouse_wheel_get_z(ev, &z); + uint32_t timestamp = 0; + tizen_core_wl_event_input_base_get_timestamp(ev, ×tamp); + + double delta_x = 0.0; + double delta_y = 0.0; + if (direction == kScrollDirectionVertical) { + delta_y += z; + } else if (direction == kScrollDirectionHorizontal) { + delta_x += z; + } + + self->view_delegate_->OnScroll(x, y, delta_x, delta_y, timestamp, + kFlutterPointerDeviceKindMouse, 0); + } + } + }); + + // Key down. + AddEventListener( + TIZEN_CORE_WL_EVENT_KEY_DOWN, + [](void* event, tizen_core_wl_event_type_e type, void* data) { + auto* self = static_cast(data); + if (!self->view_delegate_) { + return; + } + auto* ev = static_cast(event); + tizen_core_wl_window_h event_window = nullptr; + tizen_core_wl_event_input_base_get_window(ev, &event_window); + if (event_window != self->tcore_wl_window_) { + return; + } + + char* keyname = nullptr; + tizen_core_wl_event_key_get_keyname(ev, &keyname); + char* keysymbol = nullptr; + tizen_core_wl_event_key_get_keysymbol(ev, &keysymbol); + char* compose = nullptr; + tizen_core_wl_event_key_get_compose(ev, &compose); + unsigned int modifiers = 0; + tizen_core_wl_event_key_get_modifiers(ev, &modifiers); + unsigned int keycode = 0; + tizen_core_wl_event_key_get_keycode(ev, &keycode); + char* dev_identifier = nullptr; + tizen_core_wl_event_input_base_get_device_identifier(ev, + &dev_identifier); + + bool handled = false; + if (self->input_method_context_ && + self->input_method_context_->IsInputPanelShown()) { + handled = + self->input_method_context_->HandleTcoreWlEventKey(event, true); + } + if (!handled) { + // Match TizenWindowEcoreWl2 OnKey arg semantics: + // param 1 (key) = XKB key symbol name (e.g. "period") + // param 2 (string) = composed printable string (e.g. ".") + // tcore's keysymbol corresponds to ecore's key_event->key, and + // tcore's compose corresponds to ecore's key_event->string. + // Passing keysymbol as `string` breaks the printable-key fallback + // in TextInputChannel::HandleKey() for keys whose symbol name is + // multi-char ("period", "minus", "equal", ...). + self->view_delegate_->OnKey(keysymbol, compose, compose, modifiers, + keycode, dev_identifier, true); + } + free(keyname); + free(keysymbol); + free(compose); + free(dev_identifier); + }); + + // Key up. + AddEventListener( + TIZEN_CORE_WL_EVENT_KEY_UP, + [](void* event, tizen_core_wl_event_type_e type, void* data) { + auto* self = static_cast(data); + if (!self->view_delegate_) { + return; + } + auto* ev = static_cast(event); + tizen_core_wl_window_h event_window = nullptr; + tizen_core_wl_event_input_base_get_window(ev, &event_window); + if (event_window != self->tcore_wl_window_) { + return; + } + + char* keyname = nullptr; + tizen_core_wl_event_key_get_keyname(ev, &keyname); + char* keysymbol = nullptr; + tizen_core_wl_event_key_get_keysymbol(ev, &keysymbol); + char* compose = nullptr; + tizen_core_wl_event_key_get_compose(ev, &compose); + unsigned int modifiers = 0; + tizen_core_wl_event_key_get_modifiers(ev, &modifiers); + unsigned int keycode = 0; + tizen_core_wl_event_key_get_keycode(ev, &keycode); + + bool handled = false; + if (self->input_method_context_ && + self->input_method_context_->IsInputPanelShown()) { + handled = + self->input_method_context_->HandleTcoreWlEventKey(event, false); + } + if (!handled) { + // See key-down handler for the keysymbol vs compose rationale. + self->view_delegate_->OnKey(keysymbol, compose, compose, modifiers, + keycode, nullptr, false); + } + free(keyname); + free(keysymbol); + free(compose); + }); +} + +void TizenWindowTcoreWl::UnregisterEventHandlers() { + 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(); +} + +void TizenWindowTcoreWl::DestroyWindow() { + if (tcore_wl_egl_window_) { + tizen_core_wl_egl_window_destroy(tcore_wl_egl_window_); + tcore_wl_egl_window_ = nullptr; + } + + if (tcore_wl_window_) { + tizen_core_wl_window_destroy(tcore_wl_window_); + tcore_wl_window_ = nullptr; + } + + if (tcore_wl_display_) { + tizen_core_wl_display_disconnect(tcore_wl_display_); + tizen_core_wl_display_destroy(tcore_wl_display_); + tcore_wl_display_ = nullptr; + } + tizen_core_wl_shutdown(); +} + +TizenGeometry TizenWindowTcoreWl::GetGeometry() { + TizenGeometry result; + tizen_core_wl_window_get_geometry(tcore_wl_window_, &result.left, &result.top, + &result.width, &result.height); + return result; +} + +bool TizenWindowTcoreWl::SetGeometry(TizenGeometry geometry) { + tizen_core_wl_window_set_geometry(tcore_wl_window_, geometry.left, + geometry.top, geometry.width, + geometry.height); + tizen_core_wl_window_set_position(tcore_wl_window_, geometry.left, + geometry.top); + return true; +} + +TizenGeometry TizenWindowTcoreWl::GetScreenGeometry() { + TizenGeometry result = {}; + GList* output_list = nullptr; + tizen_core_wl_display_get_output_device_list(tcore_wl_display_, &output_list); + if (output_list) { + tizen_core_wl_output_h output = + static_cast(output_list->data); + tizen_core_wl_output_device_get_geometry(output, &result.width, + &result.height); + g_list_free(output_list); + } + return result; +} + +int32_t TizenWindowTcoreWl::GetRotation() { + tizen_core_wl_window_angle_e angle = TIZEN_CORE_WL_WINDOW_ANGLE_0; + tizen_core_wl_window_get_rotation_angle(tcore_wl_window_, &angle); + return static_cast(angle); +} + +int32_t TizenWindowTcoreWl::GetDpi() { + GList* output_list = nullptr; + tizen_core_wl_display_get_output_device_list(tcore_wl_display_, &output_list); + if (!output_list) { + FT_LOG(Error) << "Could not find an output device."; + return 0; + } + tizen_core_wl_output_h output = + static_cast(output_list->data); + g_list_free(output_list); + int dpi = 0; + tizen_core_wl_output_device_get_dpi(output, &dpi); + return dpi; +} + +uintptr_t TizenWindowTcoreWl::GetWindowId() { + return reinterpret_cast(tcore_wl_window_); +} + +uint32_t TizenWindowTcoreWl::GetResourceId() { + if (resource_id_ > 0) { + return resource_id_; + } + + unsigned int res_id = 0; + if (tizen_core_wl_window_private_get_resource_id(tcore_wl_window_, &res_id) == + TIZEN_CORE_WL_ERROR_NONE) { + resource_id_ = res_id; + } + return resource_id_; +} + +void TizenWindowTcoreWl::SetPreferredOrientations( + const std::vector& rotations) { + std::vector angles; + for (int rot : rotations) { + angles.push_back(static_cast(rot)); + } + tizen_core_wl_window_set_available_rotation_angle_list( + tcore_wl_window_, angles.data(), angles.size()); +} + +void TizenWindowTcoreWl::BindKeys(const std::vector& keys) { + for (const std::string& key : keys) { + tizen_core_wl_keygrab_info_h info = nullptr; + tizen_core_wl_keygrab_info_create(key.c_str(), + TIZEN_CORE_WL_KEYGRAB_TOPMOST, &info); + if (info) { + GList* list = g_list_append(nullptr, info); + tizen_core_wl_window_set_keygrab_list(tcore_wl_window_, list); + g_list_free(list); + tizen_core_wl_keygrab_info_destroy(info); + } + } +} + +void TizenWindowTcoreWl::Show() { + tizen_core_wl_window_show(tcore_wl_window_); +} + +void TizenWindowTcoreWl::UpdateFlutterCursor(const std::string& kind) { +#ifdef TV_PROFILE + int pointer_size = -1; + if (vconf_get_int(kSysMouseCursorPointerSizeVConfKey, &pointer_size) < 0) { + FT_LOG(Info) << "Failed to load cursor size."; + } + + std::string cursor_name = "normal_default"; + if (kind == "basic") { + if (pointer_size == 0) { + cursor_name = "large_normal"; + } else if (pointer_size == 1) { + cursor_name = "medium_normal"; + } else if (pointer_size == 2) { + cursor_name = "small_normal"; + } else { + cursor_name = "normal_default"; + } + } else if (kind == "click") { + if (pointer_size == 0) { + cursor_name = "large_normal_pnh"; + } else if (pointer_size == 1) { + cursor_name = "medium_normal_pnh"; + } else if (pointer_size == 2) { + cursor_name = "small_normal_pnh"; + } else { + cursor_name = "normal_pnh"; + } + } else if (kind == "text") { + if (pointer_size == 0) { + cursor_name = "large_normal_input_field"; + } else if (pointer_size == 1) { + cursor_name = "medium_normal_input_field"; + } else if (pointer_size == 2) { + cursor_name = "small_normal_input_field"; + } else { + cursor_name = "normal_input_field"; + } + } else if (kind == "none") { + cursor_name = "normal_transparent"; + } else { + FT_LOG(Info) << kind << " cursor is not supported."; + } + tizen_core_wl_seat_h default_seat = nullptr; + tizen_core_wl_display_get_default_seat(tcore_wl_display_, &default_seat); + if (default_seat) { + tizen_core_wl_seat_set_cursor_theme(default_seat, + kTcoreWlInputCursorThemeName); + tizen_core_wl_seat_set_cursor_name(default_seat, cursor_name.c_str()); + } else { + FT_LOG(Error) << "Failed to get default seat; cannot update cursor."; + } +#else + tizen_core_wl_seat_h default_seat = nullptr; + tizen_core_wl_display_get_default_seat(tcore_wl_display_, &default_seat); + if (default_seat) { + tizen_core_wl_seat_set_cursor_theme(default_seat, "default"); + tizen_core_wl_seat_set_cursor_name(default_seat, "left_ptr"); + } + FT_LOG(Info) << "UpdateFlutterCursor is not supported."; +#endif +} + +void TizenWindowTcoreWl::SetNotificationLevel(int level) { + tizen_core_wl_notification_set_level( + tcore_wl_window_, static_cast(level)); +} + +void TizenWindowTcoreWl::PrepareInputMethod() { + input_method_context_ = + std::make_unique(GetWindowId()); + + input_method_context_->SetOnPreeditStart( + [this]() { view_delegate_->OnComposeBegin(); }); + input_method_context_->SetOnPreeditChanged( + [this](std::string str, int cursor_pos) { + view_delegate_->OnComposeChange(str, cursor_pos); + }); + input_method_context_->SetOnPreeditEnd( + [this]() { view_delegate_->OnComposeEnd(); }); + input_method_context_->SetOnCommit( + [this](std::string str) { view_delegate_->OnCommit(str); }); +} + +void* TizenWindowTcoreWl::GetRenderTarget() { + if (is_vulkan_) { + return wl2_surface_; + } else { + return tcore_wl_egl_window_; + } +} + +void TizenWindowTcoreWl::ActivateWindow() { + tizen_core_wl_window_activate(tcore_wl_window_); +} + +void TizenWindowTcoreWl::RaiseWindow() { + tizen_core_wl_window_raise(tcore_wl_window_); +} + +void TizenWindowTcoreWl::LowerWindow() { + tizen_core_wl_window_lower(tcore_wl_window_); +} + +} // namespace flutter diff --git a/flutter/shell/platform/tizen/tizen_window_tcore_wl.h b/flutter/shell/platform/tizen/tizen_window_tcore_wl.h new file mode 100644 index 00000000..1f124b0f --- /dev/null +++ b/flutter/shell/platform/tizen/tizen_window_tcore_wl.h @@ -0,0 +1,114 @@ +// Copyright 2026 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EMBEDDER_TIZEN_WINDOW_TCORE_WL_H_ +#define EMBEDDER_TIZEN_WINDOW_TCORE_WL_H_ + +#include +#include +#include + +#include +#include +#include + +#include "flutter/shell/platform/tizen/tizen_window.h" + +namespace flutter { + +class TizenWindowTcoreWl : public TizenWindow { + public: + TizenWindowTcoreWl(TizenGeometry geometry, + bool transparent, + bool focusable, + bool top_level, + bool pointing_device_support, + bool floating_menu_support, + void* window_handle, + bool is_vulkan); + + ~TizenWindowTcoreWl(); + + TizenGeometry GetGeometry() override; + + bool SetGeometry(TizenGeometry geometry) override; + + TizenGeometry GetScreenGeometry() override; + + void* GetRenderTarget() override; + + void* GetRenderTargetDisplay() override { return wl2_display_; } + + void* GetNativeHandle() override { return tcore_wl_window_; } + + int32_t GetRotation() override; + + int32_t GetDpi() override; + + uintptr_t GetWindowId() override; + + uint32_t GetResourceId() override; + + void SetPreferredOrientations(const std::vector& rotations) override; + + void BindKeys(const std::vector& keys) override; + + void Show() override; + + void UpdateFlutterCursor(const std::string& kind) override; + + void ActivateWindow() override; + + void RaiseWindow() override; + + void LowerWindow() override; + + private: + bool CreateWindow(void* window_handle); + + void DestroyWindow(); + + void SetWindowOptions(); + + void EnableCursor(); + +#ifdef TV_PROFILE + void SetPointingDeviceSupport(); + + void SetFloatingMenuSupport(); + + void ShowUnsupportedToast(); +#endif + + void RegisterEventHandlers(); + + void AddEventListener(tizen_core_wl_event_type_e type, + tizen_core_wl_event_cb callback); + + void UnregisterEventHandlers(); + + void SetNotificationLevel(int level); + + void PrepareInputMethod(); + + tizen_core_wl_display_h tcore_wl_display_ = nullptr; + tizen_core_wl_window_h tcore_wl_window_ = nullptr; + tizen_core_wl_egl_window_h tcore_wl_egl_window_ = nullptr; + tizen_core_event_h tcore_wl_event_ = nullptr; + wl_display* wl2_display_ = nullptr; + wl_surface* wl2_surface_ = nullptr; + std::vector tcore_event_listeners_; + uint32_t resource_id_ = 0; + +#ifdef TV_PROFILE + bool pointing_device_support_ = true; + bool floating_menu_support_ = true; + bool show_unsupported_toast_ = false; +#endif + bool is_vulkan_ = false; +}; + +} // namespace flutter + +#endif // EMBEDDER_TIZEN_WINDOW_TCORE_WL_H_ diff --git a/flutter/shell/platform/tizen/tizen_window_util.cc b/flutter/shell/platform/tizen/tizen_window_util.cc new file mode 100644 index 00000000..ed103d4d --- /dev/null +++ b/flutter/shell/platform/tizen/tizen_window_util.cc @@ -0,0 +1,117 @@ +// Copyright 2026 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/tizen/tizen_window_util.h" + +#ifdef TV_PROFILE + +#include + +#include "flutter/shell/platform/tizen/logger.h" + +namespace flutter { + +namespace { + +constexpr char kLibraryName[] = "libvd-win-util.so"; + +} // namespace + +TizenWindowUtil::TizenWindowUtil() { + handle_ = dlopen(kLibraryName, RTLD_LAZY); + if (!handle_) { + FT_LOG(Error) << "Could not open a shared library " << kLibraryName << "."; + return; + } + + *(void**)(&cursor_module_initialize_) = Resolve("CursorModule_Initialize"); + *(void**)(&cursor_set_config_) = Resolve("Cursor_Set_Config"); + *(void**)(&cursor_module_finalize_) = Resolve("CursorModule_Finalize"); + *(void**)(&mouse_pointer_support_) = Resolve("Mouse_Pointer_Support"); + *(void**)(&mouse_pointer_not_allow_) = Resolve("Mouse_Pointer_Not_Allow"); + *(void**)(&unsupported_toast_launch_) = Resolve("Unsupported_Toast_Launch"); +} + +TizenWindowUtil::~TizenWindowUtil() { + if (handle_) { + dlclose(handle_); + handle_ = nullptr; + } +} + +void* TizenWindowUtil::Resolve(const char* name) { + void* symbol = dlsym(handle_, name); + if (!symbol) { + FT_LOG(Error) << "Could not load the symbol " << name << " from " + << kLibraryName << "."; + } + return symbol; +} + +bool TizenWindowUtil::InitializeCursorModule(wl_display* display, + wl_registry* registry, + wl_seat* seat, + unsigned int cursor_global_id) { + if (!cursor_module_initialize_) { + return false; + } + if (!cursor_module_initialize_(display, registry, seat, cursor_global_id)) { + FT_LOG(Error) << "Failed to initialize the cursor module."; + return false; + } + return true; +} + +bool TizenWindowUtil::SetCursorConfig(wl_surface* surface, + uint32_t config_type, + void* data) { + if (!cursor_set_config_) { + return false; + } + if (!cursor_set_config_(surface, config_type, data)) { + FT_LOG(Error) << "Failed to set a cursor config value."; + return false; + } + return true; +} + +void TizenWindowUtil::FinalizeCursorModule() { + if (cursor_module_finalize_) { + cursor_module_finalize_(); + } +} + +bool TizenWindowUtil::SetMousePointerSupport(bool enable, + void* native_window) { + if (!mouse_pointer_support_) { + return false; + } + mouse_pointer_support_(enable ? kMouseSupportEnable : kMouseSupportDisable, + native_window); + return true; +} + +bool TizenWindowUtil::SetMousePointerNotAllow(bool not_allow, + void* native_window) { + if (!mouse_pointer_not_allow_) { + return false; + } + mouse_pointer_not_allow_(not_allow, native_window); + return true; +} + +bool TizenWindowUtil::LaunchUnsupportedToast(DeviceType type, + bool show, + bool enable, + void* native_window) { + if (!unsupported_toast_launch_) { + return false; + } + unsupported_toast_launch_(type, show, enable, native_window); + return true; +} + +} // namespace flutter + +#endif // TV_PROFILE diff --git a/flutter/shell/platform/tizen/tizen_window_util.h b/flutter/shell/platform/tizen/tizen_window_util.h new file mode 100644 index 00000000..5216e485 --- /dev/null +++ b/flutter/shell/platform/tizen/tizen_window_util.h @@ -0,0 +1,96 @@ +// Copyright 2026 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EMBEDDER_TIZEN_WINDOW_UTIL_H_ +#define EMBEDDER_TIZEN_WINDOW_UTIL_H_ + +#ifdef TV_PROFILE + +#include + +struct wl_display; +struct wl_registry; +struct wl_seat; +struct wl_surface; + +namespace flutter { + +// Loads the TV-specific library libvd-win-util.so and exposes the subset of +// its cursor / mouse-pointer / toast entry points that the window backends +// need. +// +// dlopen is used because neither the library nor its headers +// (vd-win-util's cursor_module.h) are present in the rootstrap. The symbols +// are internal to the Samsung TV platform and may be renamed or removed in +// future releases, so every accessor tolerates a missing library or symbol by +// logging and returning false. Callers must keep working with the +// corresponding feature disabled. +// +// The library is opened for the lifetime of the instance. Create one where the +// original per-call dlopen used to be; a single instance must stay alive +// across an InitializeCursorModule / SetCursorConfig / FinalizeCursorModule +// sequence. +class TizenWindowUtil { + public: + // Values are defined by libvd-win-util and must not be renumbered. + enum MouseSupport { kMouseSupportDisable = 0, kMouseSupportEnable = 1 }; + enum DeviceType { kDeviceTypeMouse = 3, kDeviceTypeTouch = 4 }; + + // The config_type 1 refers to TIZEN_CURSOR_CONFIG_CURSOR_AVAILABLE defined + // in the TV extension protocol tizen-extension-tv.xml. + static constexpr uint32_t kCursorConfigCursorAvailable = 1; + + TizenWindowUtil(); + ~TizenWindowUtil(); + + // Prevent copying. + TizenWindowUtil(const TizenWindowUtil&) = delete; + TizenWindowUtil& operator=(const TizenWindowUtil&) = delete; + + // Returns true if the library and all required symbols were resolved. + bool IsValid() const { return handle_ != nullptr; } + + bool InitializeCursorModule(wl_display* display, + wl_registry* registry, + wl_seat* seat, + unsigned int cursor_global_id); + bool SetCursorConfig(wl_surface* surface, uint32_t config_type, void* data); + void FinalizeCursorModule(); + + // |native_window| is the backend-specific window handle + // (Ecore_Wl2_Window* or tizen_core_wl_window_h). + bool SetMousePointerSupport(bool enable, void* native_window); + bool SetMousePointerNotAllow(bool not_allow, void* native_window); + bool LaunchUnsupportedToast(DeviceType type, + bool show, + bool enable, + void* native_window); + + private: + // Resolves |name| from the library, logging on failure. + void* Resolve(const char* name); + + void* handle_ = nullptr; + + int (*cursor_module_initialize_)(wl_display* display, + wl_registry* registry, + wl_seat* seat, + unsigned int id) = nullptr; + int (*cursor_set_config_)(wl_surface* surface, + uint32_t config_type, + void* data) = nullptr; + void (*cursor_module_finalize_)(void) = nullptr; + int (*mouse_pointer_support_)(int type, void* window) = nullptr; + int (*mouse_pointer_not_allow_)(int enable, void* window) = nullptr; + void (*unsupported_toast_launch_)(int type, + int show, + int enable, + void* window) = nullptr; +}; + +} // namespace flutter + +#endif // TV_PROFILE + +#endif // EMBEDDER_TIZEN_WINDOW_UTIL_H_ diff --git a/tools/generate_sysroot.py b/tools/generate_sysroot.py index 21cab37f..4afe988c 100755 --- a/tools/generate_sysroot.py +++ b/tools/generate_sysroot.py @@ -95,6 +95,18 @@ 'wayland-devel', ] +# Required for --use-tcore builds. Only available for Tizen 10.0 and above. +tcore_packages = [ + 'tizen-core', + 'tizen-core-devel', + 'tizen-core-imf', + 'tizen-core-imf-devel', + 'tizen-core-wl', + 'tizen-core-wl-devel', + 'libwayland-egl-tizen', + 'libwayland-egl-tizen-devel', +] + def generate_sysroot(sysroot: Path, api_version: float, arch: str, quiet=False): target = 'standard' @@ -137,6 +149,8 @@ def generate_sysroot(sysroot: Path, api_version: float, arch: str, quiet=False): existing_rpms = [f for f in download_path.iterdir() if f.suffix == '.rpm'] packages = base_packages + unified_packages + if api_version > 10.0: + packages += tcore_packages for package in packages: quoted = urllib.parse.quote(package) diff --git a/tools/gn b/tools/gn index 7038677f..aba985c7 100755 --- a/tools/gn +++ b/tools/gn @@ -14,6 +14,9 @@ from pathlib import Path def get_out_dir(args): target_dir = ['tizen', args.target_cpu] + if args.use_tcore: + target_dir.append('tcore') + if args.unoptimized: target_dir.append('unopt') @@ -78,6 +81,7 @@ def to_gn_args(args): gn_args['clang_version'] = get_clang_version(args.target_toolchain) gn_args['api_version'] = args.api_version gn_args['use_system_cxx'] = not args.no_system_cxx + gn_args['use_tcore'] = args.use_tcore for key, val in gn_args.items(): if isinstance(val, bool): @@ -104,6 +108,8 @@ def parse_args(args): parser.add_argument('--api-version', default='6.0', type=str) parser.add_argument('--no-system-cxx', default=False, action='store_true') + parser.add_argument('--use-tcore', default=False, action='store_true', + help='Use tizen-core-wl API instead of ecore') parser.add_argument('--target-dir', type=str) parser.add_argument('--verbose', default=False, action='store_true') @@ -114,6 +120,16 @@ def parse_args(args): def main(): args = parse_args(sys.argv) + if args.use_tcore: + try: + api_value = float(args.api_version) + except ValueError: + api_value = 0.0 + if api_value < 10.0: + sys.exit('--use-tcore requires --api-version 10.0 or higher ' + '(tizen-core packages are only published for Tizen 11+). ' + 'Got --api-version {}.'.format(args.api_version)) + root_dir = Path(__file__).parent.parent.resolve() gn = root_dir / 'third_party' / 'gn' / 'gn' command = [gn, 'gen', '--check', '--export-compile-commands']