From 4951cf8d3e29772ddaf993268cd4197b3257d40b Mon Sep 17 00:00:00 2001 From: Swift Kim Date: Wed, 25 Nov 2020 16:03:46 +0900 Subject: [PATCH] [Tizen] Initial implementation of Tizen platform shell Co-authored-by: Swift Kim Co-authored-by: Xiaowei Guan Co-authored-by: Wanchao Xu Co-authored-by: Boram Bae Co-authored-by: MuHong Byun Co-authored-by: Seungsoo Lee --- shell/platform/tizen/BUILD.gn | 114 ++++ shell/platform/tizen/LICENSE | 26 + .../tizen/channels/key_event_channel.cc | 252 +++++++ .../tizen/channels/key_event_channel.h | 26 + .../tizen/channels/lifecycle_channel.cc | 49 ++ .../tizen/channels/lifecycle_channel.h | 23 + .../tizen/channels/localization_channel.cc | 186 ++++++ .../tizen/channels/localization_channel.h | 24 + .../tizen/channels/navigation_channel.cc | 41 ++ .../tizen/channels/navigation_channel.h | 25 + .../tizen/channels/platform_channel.cc | 57 ++ .../tizen/channels/platform_channel.h | 25 + .../tizen/channels/platform_view_channel.cc | 147 +++++ .../tizen/channels/platform_view_channel.h | 34 + .../tizen/channels/settings_channel.cc | 47 ++ .../tizen/channels/settings_channel.h | 25 + .../tizen/channels/text_input_channel.cc | 617 ++++++++++++++++++ .../tizen/channels/text_input_channel.h | 57 ++ shell/platform/tizen/config.gni | 13 + shell/platform/tizen/external_texture_gl.cc | 141 ++++ shell/platform/tizen/external_texture_gl.h | 55 ++ shell/platform/tizen/flutter_tizen.cc | 272 ++++++++ shell/platform/tizen/key_event_handler.cc | 52 ++ shell/platform/tizen/key_event_handler.h | 26 + shell/platform/tizen/logger.h | 29 + .../tizen/public/flutter_platform_view.h | 67 ++ .../tizen/public/flutter_texture_registrar.h | 43 ++ shell/platform/tizen/public/flutter_tizen.h | 92 +++ shell/platform/tizen/tizen_embedder_engine.cc | 370 +++++++++++ shell/platform/tizen/tizen_embedder_engine.h | 154 +++++ shell/platform/tizen/tizen_event_loop.cc | 92 +++ shell/platform/tizen/tizen_event_loop.h | 69 ++ shell/platform/tizen/tizen_surface.cc | 14 + shell/platform/tizen/tizen_surface.h | 31 + shell/platform/tizen/tizen_surface_gl.cc | 247 +++++++ shell/platform/tizen/tizen_surface_gl.h | 49 ++ shell/platform/tizen/tizen_vsync_waiter.cc | 145 ++++ shell/platform/tizen/tizen_vsync_waiter.h | 45 ++ shell/platform/tizen/touch_event_handler.cc | 87 +++ shell/platform/tizen/touch_event_handler.h | 36 + third_party/txt/src/txt/platform_linux.cc | 2 +- tools/gn | 2 + 42 files changed, 3907 insertions(+), 1 deletion(-) create mode 100644 shell/platform/tizen/BUILD.gn create mode 100644 shell/platform/tizen/LICENSE create mode 100644 shell/platform/tizen/channels/key_event_channel.cc create mode 100644 shell/platform/tizen/channels/key_event_channel.h create mode 100644 shell/platform/tizen/channels/lifecycle_channel.cc create mode 100644 shell/platform/tizen/channels/lifecycle_channel.h create mode 100644 shell/platform/tizen/channels/localization_channel.cc create mode 100644 shell/platform/tizen/channels/localization_channel.h create mode 100644 shell/platform/tizen/channels/navigation_channel.cc create mode 100644 shell/platform/tizen/channels/navigation_channel.h create mode 100644 shell/platform/tizen/channels/platform_channel.cc create mode 100644 shell/platform/tizen/channels/platform_channel.h create mode 100644 shell/platform/tizen/channels/platform_view_channel.cc create mode 100644 shell/platform/tizen/channels/platform_view_channel.h create mode 100644 shell/platform/tizen/channels/settings_channel.cc create mode 100644 shell/platform/tizen/channels/settings_channel.h create mode 100644 shell/platform/tizen/channels/text_input_channel.cc create mode 100644 shell/platform/tizen/channels/text_input_channel.h create mode 100644 shell/platform/tizen/config.gni create mode 100644 shell/platform/tizen/external_texture_gl.cc create mode 100644 shell/platform/tizen/external_texture_gl.h create mode 100644 shell/platform/tizen/flutter_tizen.cc create mode 100644 shell/platform/tizen/key_event_handler.cc create mode 100644 shell/platform/tizen/key_event_handler.h create mode 100644 shell/platform/tizen/logger.h create mode 100644 shell/platform/tizen/public/flutter_platform_view.h create mode 100644 shell/platform/tizen/public/flutter_texture_registrar.h create mode 100644 shell/platform/tizen/public/flutter_tizen.h create mode 100644 shell/platform/tizen/tizen_embedder_engine.cc create mode 100644 shell/platform/tizen/tizen_embedder_engine.h create mode 100644 shell/platform/tizen/tizen_event_loop.cc create mode 100644 shell/platform/tizen/tizen_event_loop.h create mode 100644 shell/platform/tizen/tizen_surface.cc create mode 100644 shell/platform/tizen/tizen_surface.h create mode 100644 shell/platform/tizen/tizen_surface_gl.cc create mode 100644 shell/platform/tizen/tizen_surface_gl.h create mode 100644 shell/platform/tizen/tizen_vsync_waiter.cc create mode 100644 shell/platform/tizen/tizen_vsync_waiter.h create mode 100644 shell/platform/tizen/touch_event_handler.cc create mode 100644 shell/platform/tizen/touch_event_handler.h diff --git a/shell/platform/tizen/BUILD.gn b/shell/platform/tizen/BUILD.gn new file mode 100644 index 0000000000000..b285f11b2260e --- /dev/null +++ b/shell/platform/tizen/BUILD.gn @@ -0,0 +1,114 @@ +# Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved. +# Copyright 2013 The Flutter Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +_public_headers = [ "public/flutter_tizen.h" ] + +# Any files that are built by clients (client_wrapper code, library headers for +# implementations using this shared code, etc.) include the public headers +# assuming they are in the include path. This configuration should be added to +# any such code that is also built by GN to make the includes work. +config("relative_flutter_tizen_headers") { + include_dirs = [ "public" ] +} + +# The headers are a separate source set since the client wrapper is allowed +# to depend on the public headers, but none of the rest of the code. +source_set("flutter_tizen_headers") { + public = _public_headers + + public_deps = + [ "//flutter/shell/platform/common/cpp:common_cpp_library_headers" ] + + configs += + [ "//flutter/shell/platform/common/cpp:desktop_library_implementation" ] + + public_configs = + [ "//flutter/shell/platform/common/cpp:relative_flutter_library_headers" ] +} + +source_set("flutter_tizen") { + sources = [ + "channels/key_event_channel.cc", + "channels/lifecycle_channel.cc", + "channels/localization_channel.cc", + "channels/navigation_channel.cc", + "channels/platform_channel.cc", + "channels/platform_view_channel.cc", + "channels/settings_channel.cc", + "channels/text_input_channel.cc", + "external_texture_gl.cc", + "flutter_tizen.cc", + "key_event_handler.cc", + "tizen_embedder_engine.cc", + "tizen_event_loop.cc", + "tizen_surface.cc", + "tizen_surface_gl.cc", + "tizen_vsync_waiter.cc", + "touch_event_handler.cc", + ] + + configs += + [ "//flutter/shell/platform/common/cpp:desktop_library_implementation" ] + + deps = [ + ":flutter_tizen_headers", + "//flutter/shell/platform/common/cpp:common_cpp", + "//flutter/shell/platform/common/cpp:common_cpp_input", + "//flutter/shell/platform/common/cpp/client_wrapper:client_wrapper", + "//flutter/shell/platform/embedder:embedder_as_internal_library", + "//third_party/rapidjson", + ] + + include_dirs = [ + "//third_party/tizen_tools/sysroot/$target_cpu/usr/include", + "//third_party/tizen_tools/sysroot/$target_cpu/usr/include/base", + "//third_party/tizen_tools/sysroot/$target_cpu/usr/include/dlog", + "//third_party/tizen_tools/sysroot/$target_cpu/usr/include/ecore-1", + "//third_party/tizen_tools/sysroot/$target_cpu/usr/include/ecore-evas-1", + "//third_party/tizen_tools/sysroot/$target_cpu/usr/include/ecore-imf-1", + "//third_party/tizen_tools/sysroot/$target_cpu/usr/include/ecore-imf-evas-1", + "//third_party/tizen_tools/sysroot/$target_cpu/usr/include/ecore-input-1", + "//third_party/tizen_tools/sysroot/$target_cpu/usr/include/ecore-wl2-1", + "//third_party/tizen_tools/sysroot/$target_cpu/usr/include/efl-1", + "//third_party/tizen_tools/sysroot/$target_cpu/usr/include/eina-1", + "//third_party/tizen_tools/sysroot/$target_cpu/usr/include/eina-1/eina", + "//third_party/tizen_tools/sysroot/$target_cpu/usr/include/emile-1", + "//third_party/tizen_tools/sysroot/$target_cpu/usr/include/eo-1", + "//third_party/tizen_tools/sysroot/$target_cpu/usr/include/evas-1", + "//third_party/tizen_tools/sysroot/$target_cpu/usr/include/system", + ] + + lib_dirs = [ "//third_party/tizen_tools/sysroot/$target_cpu/usr/lib" ] + + cflags_cc = [ + "-Wno-newline-eof", + "-Wno-macro-redefined", + ] + + libs = [ + "base-utils-i18n", + "capi-system-info", + "capi-system-system-settings", + "dlog", + "ecore", + "ecore_imf", + "ecore_input", + "ecore_wl2", + "EGL", + "evas", + "GLESv2", + "tbm", + "tdm-client", + "wayland-client", + ] +} + +copy("publish_headers_tizen") { + sources = _public_headers + outputs = [ "$root_out_dir/{{source_file_part}}" ] + + # The Tizen header assumes the presence of the common headers. + deps = [ "//flutter/shell/platform/common/cpp:publish_headers" ] +} diff --git a/shell/platform/tizen/LICENSE b/shell/platform/tizen/LICENSE new file mode 100644 index 0000000000000..5eee0ec439aa1 --- /dev/null +++ b/shell/platform/tizen/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. +Copyright (c) 2013 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the names of the copyright holders nor the names of the + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/shell/platform/tizen/channels/key_event_channel.cc b/shell/platform/tizen/channels/key_event_channel.cc new file mode 100644 index 0000000000000..58536c2af15fd --- /dev/null +++ b/shell/platform/tizen/channels/key_event_channel.cc @@ -0,0 +1,252 @@ +// Copyright 2020 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 "key_event_channel.h" + +#include + +#include "flutter/shell/platform/tizen/logger.h" + +static constexpr char kChannelName[] = "flutter/keyevent"; + +static constexpr char kKeyMapKey[] = "keymap"; +static constexpr char kKeyCodeKey[] = "keyCode"; +static constexpr char kScanCodeKey[] = "scanCode"; +static constexpr char kTypeKey[] = "type"; +static constexpr char kModifiersKey[] = "modifiers"; +static constexpr char kToolkitKey[] = "toolkit"; +static constexpr char kUnicodeScalarValuesKey[] = "unicodeScalarValues"; + +static constexpr char kKeyUp[] = "keyup"; +static constexpr char kKeyDown[] = "keydown"; +static constexpr char kGtkToolkit[] = "gtk"; +static constexpr char kLinuxKeyMap[] = "linux"; + +// Mapping from physical (xkb) to logical (GTK) key codes. +// The values are defined in: +// - flutter/keyboard_maps.dart (kLinuxToPhysicalKey, kGtkToLogicalKey) +static const std::map kKeyCodeMap = { + {0x00000009, 65307}, // LogicalKeyboardKey.escape + {0x0000000a, 49}, // LogicalKeyboardKey.digit1 + {0x0000000b, 50}, // LogicalKeyboardKey.digit2 + {0x0000000c, 51}, // LogicalKeyboardKey.digit3 + {0x0000000d, 52}, // LogicalKeyboardKey.digit4 + {0x0000000e, 53}, // LogicalKeyboardKey.digit5 + {0x0000000f, 54}, // LogicalKeyboardKey.digit6 + {0x00000010, 55}, // LogicalKeyboardKey.digit7 + {0x00000011, 56}, // LogicalKeyboardKey.digit8 + {0x00000012, 57}, // LogicalKeyboardKey.digit9 + {0x00000013, 48}, // LogicalKeyboardKey.digit0 + {0x00000014, 45}, // LogicalKeyboardKey.minus + {0x00000015, 61}, // LogicalKeyboardKey.equal + {0x00000016, 65288}, // LogicalKeyboardKey.backspace + {0x00000017, 65289}, // LogicalKeyboardKey.tab + {0x00000018, 81}, // LogicalKeyboardKey.keyQ + {0x00000019, 87}, // LogicalKeyboardKey.keyW + {0x0000001a, 69}, // LogicalKeyboardKey.keyE + {0x0000001b, 82}, // LogicalKeyboardKey.keyR + {0x0000001c, 84}, // LogicalKeyboardKey.keyT + {0x0000001d, 89}, // LogicalKeyboardKey.keyY + {0x0000001e, 85}, // LogicalKeyboardKey.keyU + {0x0000001f, 73}, // LogicalKeyboardKey.keyI + {0x00000020, 79}, // LogicalKeyboardKey.keyO + {0x00000021, 80}, // LogicalKeyboardKey.keyP + {0x00000022, 91}, // LogicalKeyboardKey.bracketLeft + {0x00000023, 93}, // LogicalKeyboardKey.bracketRight + {0x00000024, 65293}, // LogicalKeyboardKey.enter + {0x00000025, 65507}, // LogicalKeyboardKey.controlLeft + {0x00000026, 65}, // LogicalKeyboardKey.keyA + {0x00000027, 83}, // LogicalKeyboardKey.keyS + {0x00000028, 68}, // LogicalKeyboardKey.keyD + {0x00000029, 70}, // LogicalKeyboardKey.keyF + {0x0000002a, 71}, // LogicalKeyboardKey.keyG + {0x0000002b, 72}, // LogicalKeyboardKey.keyH + {0x0000002c, 74}, // LogicalKeyboardKey.keyJ + {0x0000002d, 75}, // LogicalKeyboardKey.keyK + {0x0000002e, 76}, // LogicalKeyboardKey.keyL + {0x0000002f, 59}, // LogicalKeyboardKey.semicolon + {0x00000030, 39}, // LogicalKeyboardKey.quote + {0x00000031, 96}, // LogicalKeyboardKey.backquote + {0x00000032, 65505}, // LogicalKeyboardKey.shiftLeft + {0x00000033, 92}, // LogicalKeyboardKey.backslash + {0x00000034, 90}, // LogicalKeyboardKey.keyZ + {0x00000035, 88}, // LogicalKeyboardKey.keyX + {0x00000036, 67}, // LogicalKeyboardKey.keyC + {0x00000037, 86}, // LogicalKeyboardKey.keyV + {0x00000038, 66}, // LogicalKeyboardKey.keyB + {0x00000039, 78}, // LogicalKeyboardKey.keyN + {0x0000003a, 77}, // LogicalKeyboardKey.keyM + {0x0000003b, 44}, // LogicalKeyboardKey.comma + {0x0000003c, 46}, // LogicalKeyboardKey.period + {0x0000003d, 47}, // LogicalKeyboardKey.slash + {0x0000003e, 65506}, // LogicalKeyboardKey.shiftRight + {0x0000003f, 65450}, // LogicalKeyboardKey.numpadMultiply + {0x00000040, 65513}, // LogicalKeyboardKey.altLeft + {0x00000041, 32}, // LogicalKeyboardKey.space + {0x00000042, 65509}, // LogicalKeyboardKey.capsLock + {0x00000043, 65470}, // LogicalKeyboardKey.f1 + {0x00000044, 65471}, // LogicalKeyboardKey.f2 + {0x00000045, 65472}, // LogicalKeyboardKey.f3 + {0x00000046, 65473}, // LogicalKeyboardKey.f4 + {0x00000047, 65474}, // LogicalKeyboardKey.f5 + {0x00000048, 65475}, // LogicalKeyboardKey.f6 + {0x00000049, 65476}, // LogicalKeyboardKey.f7 + {0x0000004a, 65477}, // LogicalKeyboardKey.f8 + {0x0000004b, 65478}, // LogicalKeyboardKey.f9 + {0x0000004c, 65479}, // LogicalKeyboardKey.f10 + {0x0000004d, 65407}, // LogicalKeyboardKey.numLock + {0x0000004e, 65300}, // LogicalKeyboardKey.scrollLock + {0x0000004f, 65463}, // LogicalKeyboardKey.numpad7 + {0x00000050, 65464}, // LogicalKeyboardKey.numpad8 + {0x00000051, 65465}, // LogicalKeyboardKey.numpad9 + {0x00000052, 65453}, // LogicalKeyboardKey.numpadSubtract + {0x00000053, 65460}, // LogicalKeyboardKey.numpad4 + {0x00000054, 65461}, // LogicalKeyboardKey.numpad5 + {0x00000055, 65462}, // LogicalKeyboardKey.numpad6 + {0x00000056, 65451}, // LogicalKeyboardKey.numpadAdd + {0x00000057, 65457}, // LogicalKeyboardKey.numpad1 + {0x00000058, 65458}, // LogicalKeyboardKey.numpad2 + {0x00000059, 65459}, // LogicalKeyboardKey.numpad3 + {0x0000005a, 65456}, // LogicalKeyboardKey.numpad0 + {0x0000005b, 65454}, // LogicalKeyboardKey.numpadDecimal + {0x0000005f, 65480}, // LogicalKeyboardKey.f11 + {0x00000060, 65481}, // LogicalKeyboardKey.f12 + {0x00000065, 65406}, // LogicalKeyboardKey.kanaMode + {0x00000068, 65421}, // LogicalKeyboardKey.numpadEnter + {0x00000069, 65508}, // LogicalKeyboardKey.controlRight + {0x0000006a, 65455}, // LogicalKeyboardKey.numpadDivide + {0x0000006b, 64797}, // LogicalKeyboardKey.printScreen + {0x0000006c, 65514}, // LogicalKeyboardKey.altRight + {0x0000006e, 65360}, // LogicalKeyboardKey.home + {0x0000006f, 65362}, // LogicalKeyboardKey.arrowUp + {0x00000070, 65365}, // LogicalKeyboardKey.pageUp + {0x00000071, 65361}, // LogicalKeyboardKey.arrowLeft + {0x00000072, 65363}, // LogicalKeyboardKey.arrowRight + {0x00000073, 65367}, // LogicalKeyboardKey.end + {0x00000074, 65364}, // LogicalKeyboardKey.arrowDown + {0x00000075, 65366}, // LogicalKeyboardKey.pageDown + {0x00000076, 65379}, // LogicalKeyboardKey.insert + {0x00000077, 65535}, // LogicalKeyboardKey.delete + {0x00000079, 269025042}, // LogicalKeyboardKey.audioVolumeMute + {0x0000007a, 269025041}, // LogicalKeyboardKey.audioVolumeDown + {0x0000007b, 269025043}, // LogicalKeyboardKey.audioVolumeUp + {0x0000007c, 269025066}, // LogicalKeyboardKey.power + {0x0000007d, 65469}, // LogicalKeyboardKey.numpadEqual + {0x0000007f, 65299}, // LogicalKeyboardKey.pause + {0x00000084, 165}, // LogicalKeyboardKey.intlYen + {0x00000085, 65511}, // LogicalKeyboardKey.metaLeft + {0x00000086, 65512}, // LogicalKeyboardKey.metaRight + {0x00000087, 65383}, // LogicalKeyboardKey.contextMenu + {0x00000088, 269025064}, // LogicalKeyboardKey.browserStop + {0x0000008b, 65381}, // LogicalKeyboardKey.undo + {0x0000008c, 65376}, // LogicalKeyboardKey.select + {0x0000008d, 269025111}, // LogicalKeyboardKey.copy + {0x0000008e, 269025131}, // LogicalKeyboardKey.open + {0x0000008f, 269025133}, // LogicalKeyboardKey.paste + {0x00000090, 65384}, // LogicalKeyboardKey.find + {0x00000092, 65386}, // LogicalKeyboardKey.help + {0x00000096, 269025071}, // LogicalKeyboardKey.sleep + {0x00000097, 269025067}, // LogicalKeyboardKey.wakeUp + {0x0000009e, 269025070}, // LogicalKeyboardKey.launchInternetBrowser + {0x000000a3, 269025049}, // LogicalKeyboardKey.launchMail + {0x000000a4, 269025072}, // LogicalKeyboardKey.browserFavorites + {0x000000a6, 269025062}, // LogicalKeyboardKey.browserBack + {0x000000a7, 269025063}, // LogicalKeyboardKey.browserForward + {0x000000a9, 269025068}, // LogicalKeyboardKey.eject + {0x000000ab, 269025047}, // LogicalKeyboardKey.mediaTrackNext + {0x000000ad, 269025046}, // LogicalKeyboardKey.mediaTrackPrevious + {0x000000ae, 269025045}, // LogicalKeyboardKey.mediaStop + {0x000000af, 269025052}, // LogicalKeyboardKey.mediaRecord + {0x000000b0, 269025086}, // LogicalKeyboardKey.mediaRewind + {0x000000b1, 269025134}, // LogicalKeyboardKey.launchPhone + {0x000000b4, 269025048}, // LogicalKeyboardKey.browserHome + {0x000000b5, 269025065}, // LogicalKeyboardKey.browserRefresh + {0x000000bd, 269025128}, // LogicalKeyboardKey.newKey + {0x000000be, 65382}, // LogicalKeyboardKey.redo + {0x000000bf, 65482}, // LogicalKeyboardKey.f13 + {0x000000c0, 65483}, // LogicalKeyboardKey.f14 + {0x000000c1, 65484}, // LogicalKeyboardKey.f15 + {0x000000c2, 65485}, // LogicalKeyboardKey.f16 + {0x000000c3, 65486}, // LogicalKeyboardKey.f17 + {0x000000c4, 65487}, // LogicalKeyboardKey.f18 + {0x000000c5, 65488}, // LogicalKeyboardKey.f19 + {0x000000c6, 65489}, // LogicalKeyboardKey.f20 + {0x000000c7, 65490}, // LogicalKeyboardKey.f21 + {0x000000c8, 65491}, // LogicalKeyboardKey.f22 + {0x000000c9, 65492}, // LogicalKeyboardKey.f23 + {0x000000ca, 65493}, // LogicalKeyboardKey.f24 + {0x000000d1, 269025073}, // LogicalKeyboardKey.mediaPause + {0x000000d6, 269025110}, // LogicalKeyboardKey.close + {0x000000d7, 269025044}, // LogicalKeyboardKey.mediaPlay + {0x000000d8, 269025175}, // LogicalKeyboardKey.mediaFastForward + {0x000000da, 65377}, // LogicalKeyboardKey.print + {0x000000e1, 269025051}, // LogicalKeyboardKey.browserSearch + {0x000000e8, 269025027}, // LogicalKeyboardKey.brightnessDown + {0x000000e9, 269025026}, // LogicalKeyboardKey.brightnessUp + {0x000000ed, 269025030}, // LogicalKeyboardKey.kbdIllumDown + {0x000000ee, 269025029}, // LogicalKeyboardKey.kbdIllumUp + {0x000000ef, 269025147}, // LogicalKeyboardKey.mailSend + {0x000000f0, 269025138}, // LogicalKeyboardKey.mailReply + {0x000000f1, 269025168}, // LogicalKeyboardKey.mailForward + {0x000000f2, 269025143}, // LogicalKeyboardKey.save + {0x00000190, 269025170}, // LogicalKeyboardKey.launchAudioBrowser + {0x00000195, 269025056}, // LogicalKeyboardKey.launchCalendar + {0x000001aa, 269025163}, // LogicalKeyboardKey.zoomIn + {0x000001ab, 269025164}, // LogicalKeyboardKey.zoomOut + {0x000001b8, 269025148}, // LogicalKeyboardKey.spellCheck + {0x000001b9, 269025121}, // LogicalKeyboardKey.logOff + {0x0000024d, 269025069}, // LogicalKeyboardKey.launchScreenSaver +}; + +// The values are defined in: +// - efl/Ecore_Input.h +// - flutter/raw_keyboard_linux.dart (GtkKeyHelper) +static const std::map kModifierMap = { + {0x0001, 1 << 0}, // SHIFT (modifierShift) + {0x0002, 1 << 2}, // CTRL (modifierControl) + {0x0004, 1 << 3}, // ALT (modifierMod1) + {0x0008, 1 << 28}, // WIN (modifierMeta) + {0x0010, 0}, // SCROLL (undefined) + {0x0020, 1 << 4}, // NUM (modifierMod2) + {0x0040, 1 << 1}, // CAPS (modifierCapsLock) +}; + +KeyEventChannel::KeyEventChannel(flutter::BinaryMessenger* messenger) + : channel_( + std::make_unique>( + messenger, kChannelName, + &flutter::JsonMessageCodec::GetInstance())) {} + +KeyEventChannel::~KeyEventChannel() {} + +void KeyEventChannel::SendKeyEvent(Ecore_Event_Key* key, bool is_down) { + LoggerD("code: %d, name: %s, mods: %d, type: %s", key->keycode, key->keyname, + key->modifiers, is_down ? kKeyDown : kKeyUp); + + int gtk_keycode = 0; + if (kKeyCodeMap.count(key->keycode) > 0) { + gtk_keycode = kKeyCodeMap.at(key->keycode); + } + int gtk_modifiers = 0; + for (auto element : kModifierMap) { + if (element.first & key->modifiers) { + gtk_modifiers |= element.second; + } + } + + rapidjson::Document event(rapidjson::kObjectType); + auto& allocator = event.GetAllocator(); + event.AddMember(kKeyMapKey, kLinuxKeyMap, allocator); + event.AddMember(kToolkitKey, kGtkToolkit, allocator); + event.AddMember(kUnicodeScalarValuesKey, 0, allocator); + event.AddMember(kKeyCodeKey, gtk_keycode, allocator); + event.AddMember(kScanCodeKey, key->keycode, allocator); + event.AddMember(kModifiersKey, gtk_modifiers, allocator); + if (is_down) { + event.AddMember(kTypeKey, kKeyDown, allocator); + } else { + event.AddMember(kTypeKey, kKeyUp, allocator); + } + channel_->Send(event); +} diff --git a/shell/platform/tizen/channels/key_event_channel.h b/shell/platform/tizen/channels/key_event_channel.h new file mode 100644 index 0000000000000..511df2beba2f9 --- /dev/null +++ b/shell/platform/tizen/channels/key_event_channel.h @@ -0,0 +1,26 @@ +// Copyright 2020 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_KEY_EVENT_CHANNEL_H_ +#define EMBEDDER_KEY_EVENT_CHANNEL_H_ + +#include + +#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/basic_message_channel.h" +#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/binary_messenger.h" +#include "flutter/shell/platform/common/cpp/json_message_codec.h" +#include "rapidjson/document.h" + +class KeyEventChannel { + public: + explicit KeyEventChannel(flutter::BinaryMessenger* messenger); + virtual ~KeyEventChannel(); + + void SendKeyEvent(Ecore_Event_Key* key, bool is_down); + + private: + std::unique_ptr> channel_; +}; + +#endif // EMBEDDER_KEY_EVENT_CHANNEL_H_ diff --git a/shell/platform/tizen/channels/lifecycle_channel.cc b/shell/platform/tizen/channels/lifecycle_channel.cc new file mode 100644 index 0000000000000..920b178ca4658 --- /dev/null +++ b/shell/platform/tizen/channels/lifecycle_channel.cc @@ -0,0 +1,49 @@ +// Copyright 2020 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 "lifecycle_channel.h" + +#include "flutter/shell/platform/tizen/logger.h" + +static constexpr char kChannelName[] = "flutter/lifecycle"; +static constexpr char kInactive[] = "AppLifecycleState.inactive"; +static constexpr char kResumed[] = "AppLifecycleState.resumed"; +static constexpr char kPaused[] = "AppLifecycleState.paused"; +static constexpr char kDetached[] = "AppLifecycleState.detached"; + +LifecycleChannel::LifecycleChannel(FLUTTER_API_SYMBOL(FlutterEngine) + flutter_engine) + : flutter_engine_(flutter_engine) {} + +LifecycleChannel::~LifecycleChannel() {} + +void LifecycleChannel::SendLifecycleMessage(const char message[]) { + FlutterPlatformMessage platformMessage = {}; + platformMessage.struct_size = sizeof(FlutterPlatformMessage); + platformMessage.channel = kChannelName; + platformMessage.message = reinterpret_cast(message); + platformMessage.message_size = strlen(message); + platformMessage.response_handle = nullptr; + FlutterEngineSendPlatformMessage(flutter_engine_, &platformMessage); +} + +void LifecycleChannel::AppIsInactive() { + LoggerD("send app lifecycle state inactive."); + SendLifecycleMessage(kInactive); +} + +void LifecycleChannel::AppIsResumed() { + LoggerD("send app lifecycle state resumed."); + SendLifecycleMessage(kResumed); +} + +void LifecycleChannel::AppIsPaused() { + LoggerD("send app lifecycle state paused."); + SendLifecycleMessage(kPaused); +} + +void LifecycleChannel::AppIsDetached() { + LoggerD("send app lifecycle state detached."); + SendLifecycleMessage(kDetached); +} diff --git a/shell/platform/tizen/channels/lifecycle_channel.h b/shell/platform/tizen/channels/lifecycle_channel.h new file mode 100644 index 0000000000000..5ed67fd09d353 --- /dev/null +++ b/shell/platform/tizen/channels/lifecycle_channel.h @@ -0,0 +1,23 @@ +// Copyright 2020 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_LIFECYCLE_CHANNEL_H_ +#define EMBEDDER_LIFECYCLE_CHANNEL_H_ + +#include "flutter/shell/platform/embedder/embedder.h" + +class LifecycleChannel { + public: + explicit LifecycleChannel(FLUTTER_API_SYMBOL(FlutterEngine) flutter_engine); + virtual ~LifecycleChannel(); + void AppIsInactive(); + void AppIsResumed(); + void AppIsPaused(); + void AppIsDetached(); + void SendLifecycleMessage(const char message[]); + + private: + FLUTTER_API_SYMBOL(FlutterEngine) flutter_engine_; +}; +#endif // EMBEDDER_LIFECYCLE_CHANNEL_H_ diff --git a/shell/platform/tizen/channels/localization_channel.cc b/shell/platform/tizen/channels/localization_channel.cc new file mode 100644 index 0000000000000..55c08cbb49986 --- /dev/null +++ b/shell/platform/tizen/channels/localization_channel.cc @@ -0,0 +1,186 @@ +// Copyright 2020 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 "localization_channel.h" + +#include + +#include + +#include "flutter/shell/platform/tizen/logger.h" +#include "rapidjson/document.h" +#include "rapidjson/writer.h" + +static constexpr char kChannelName[] = "flutter/localization"; + +LocalizationChannel::LocalizationChannel(FLUTTER_API_SYMBOL(FlutterEngine) + flutter_engine) + : flutter_engine_(flutter_engine) {} + +LocalizationChannel::~LocalizationChannel() {} + +void LocalizationChannel::SendLocales() { + int32_t count = i18n_ulocale_count_available(); + if (count > 0) { + FlutterLocale* flutterLocale = nullptr; + std::vector flutterLocales; + for (int i = 0; i < count; i++) { + const char* locale = i18n_ulocale_get_available(i); + flutterLocale = GetFlutterLocale(locale); + if (flutterLocale) { + flutterLocales.push_back(flutterLocale); + } + } + + // send locales to engine + FlutterEngineUpdateLocales( + flutter_engine_, + const_cast(flutterLocales.data()), + flutterLocales.size()); + + for (auto it : flutterLocales) { + DestroyFlutterLocale(it); + } + } + + SendPlatformResolvedLocale(); +} + +void LocalizationChannel::SendPlatformResolvedLocale() { + const char* locale; + int ret = i18n_ulocale_get_default(&locale); + if (ret != I18N_ERROR_NONE) { + LoggerE("i18n_ulocale_get_default() failed."); + return; + } + + FlutterLocale* flutterLocale = GetFlutterLocale(locale); + if (!flutterLocale) { + LoggerE("Language code is required but not present."); + return; + } + + rapidjson::Document document; + auto& allocator = document.GetAllocator(); + + document.SetObject(); + document.AddMember("method", "setPlatformResolvedLocale", allocator); + + rapidjson::Value language_code, country_code, script_code, variant_code; + language_code.SetString(flutterLocale->language_code, allocator); + country_code.SetString( + flutterLocale->country_code ? flutterLocale->country_code : "", + allocator); + script_code.SetString( + flutterLocale->script_code ? flutterLocale->script_code : "", allocator); + variant_code.SetString( + flutterLocale->variant_code ? flutterLocale->variant_code : "", + allocator); + + rapidjson::Value args(rapidjson::kArrayType); + args.Reserve(4, allocator); + args.PushBack(language_code, allocator); + args.PushBack(country_code, allocator); + args.PushBack(script_code, allocator); + args.PushBack(variant_code, allocator); + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + if (!document.Accept(writer)) { + LoggerE("document.Accept failed!"); + return; + } + + FlutterPlatformMessage message = {}; + message.struct_size = sizeof(FlutterPlatformMessage); + message.channel = kChannelName; + message.message = reinterpret_cast(buffer.GetString()); + message.message_size = buffer.GetSize(); + message.response_handle = nullptr; + FlutterEngineSendPlatformMessage(flutter_engine_, &message); + + DestroyFlutterLocale(flutterLocale); +} + +FlutterLocale* LocalizationChannel::GetFlutterLocale(const char* locale) { + int capacity = 128; + char buffer[128] = {0}; + int bufSize; + int error; + char* language = nullptr; + char* country = nullptr; + char* script = nullptr; + char* variant = nullptr; + + // set language code, language code is a required field + error = i18n_ulocale_get_language(locale, buffer, capacity, &bufSize); + if (error == I18N_ERROR_NONE && bufSize > 0) { + language = new char[bufSize + 1]; + memcpy(language, buffer, bufSize); + language[bufSize] = '\0'; + } else { + LoggerE("i18n_ulocale_get_language failed!"); + return nullptr; + } + + // set country code, country code is an optional field + bufSize = i18n_ulocale_get_country(locale, buffer, capacity, &error); + if (error == I18N_ERROR_NONE && bufSize > 0) { + country = new char[bufSize + 1]; + memcpy(country, buffer, bufSize); + country[bufSize] = '\0'; + } + + // set script code, script code is an optional field + bufSize = i18n_ulocale_get_script(locale, buffer, capacity); + if (bufSize > 0) { + script = new char[bufSize + 1]; + memcpy(script, buffer, bufSize); + script[bufSize] = '\0'; + } + + // set variant code, variant code is an optional field + bufSize = i18n_ulocale_get_variant(locale, buffer, capacity); + if (bufSize > 0) { + variant = new char[bufSize + 1]; + memcpy(variant, buffer, bufSize); + variant[bufSize] = '\0'; + } + + FlutterLocale* flutterLocale = new FlutterLocale; + flutterLocale->struct_size = sizeof(FlutterLocale); + flutterLocale->language_code = language; + flutterLocale->country_code = country; + flutterLocale->script_code = script; + flutterLocale->variant_code = variant; + + return flutterLocale; +} + +void LocalizationChannel::DestroyFlutterLocale(FlutterLocale* flutterLocale) { + if (flutterLocale) { + if (flutterLocale->language_code) { + delete[] flutterLocale->language_code; + flutterLocale->language_code = nullptr; + } + + if (flutterLocale->country_code) { + delete[] flutterLocale->country_code; + flutterLocale->country_code = nullptr; + } + + if (flutterLocale->script_code) { + delete[] flutterLocale->script_code; + flutterLocale->script_code = nullptr; + } + + if (flutterLocale->variant_code) { + delete[] flutterLocale->variant_code; + flutterLocale->variant_code = nullptr; + } + + delete flutterLocale; + flutterLocale = nullptr; + } +} diff --git a/shell/platform/tizen/channels/localization_channel.h b/shell/platform/tizen/channels/localization_channel.h new file mode 100644 index 0000000000000..ce65ca670c286 --- /dev/null +++ b/shell/platform/tizen/channels/localization_channel.h @@ -0,0 +1,24 @@ +// Copyright 2020 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_LOCALIZATION_CHANNEL_H_ +#define EMBEDDER_LOCALIZATION_CHANNEL_H_ + +#include "flutter/shell/platform/embedder/embedder.h" + +class LocalizationChannel { + public: + explicit LocalizationChannel(FLUTTER_API_SYMBOL(FlutterEngine) + flutter_engine); + virtual ~LocalizationChannel(); + void SendLocales(); + + private: + void SendPlatformResolvedLocale(); + FlutterLocale* GetFlutterLocale(const char* locale); + void DestroyFlutterLocale(FlutterLocale* flutterLocale); + + FLUTTER_API_SYMBOL(FlutterEngine) flutter_engine_; +}; +#endif // EMBEDDER_LOCALIZATION_CHANNEL_H_ diff --git a/shell/platform/tizen/channels/navigation_channel.cc b/shell/platform/tizen/channels/navigation_channel.cc new file mode 100644 index 0000000000000..0c3f7653afcd0 --- /dev/null +++ b/shell/platform/tizen/channels/navigation_channel.cc @@ -0,0 +1,41 @@ +// Copyright 2020 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 "navigation_channel.h" + +#include "flutter/shell/platform/common/cpp/json_method_codec.h" + +static constexpr char kChannelName[] = "flutter/navigation"; + +static constexpr char kSetInitialRouteMethod[] = "setInitialRoute"; +static constexpr char kPushRouteMethod[] = "pushRoute"; +static constexpr char kPopRouteMethod[] = "popRoute"; + +NavigationChannel::NavigationChannel(flutter::BinaryMessenger* messenger) + : channel_(std::make_unique>( + messenger, kChannelName, &flutter::JsonMethodCodec::GetInstance())) {} + +NavigationChannel::~NavigationChannel() {} + +void NavigationChannel::SetInitialRoute(const std::string& initialRoute) { + auto args = std::make_unique(rapidjson::kObjectType); + args->Parse("\"" + initialRoute + "\""); + + if (!args->HasParseError()) { + channel_->InvokeMethod(kSetInitialRouteMethod, std::move(args)); + } +} + +void NavigationChannel::PushRoute(const std::string& route) { + auto args = std::make_unique(rapidjson::kObjectType); + args->Parse("\"" + route + "\""); + + if (!args->HasParseError()) { + channel_->InvokeMethod(kPushRouteMethod, std::move(args)); + } +} + +void NavigationChannel::PopRoute() { + channel_->InvokeMethod(kPopRouteMethod, nullptr); +} diff --git a/shell/platform/tizen/channels/navigation_channel.h b/shell/platform/tizen/channels/navigation_channel.h new file mode 100644 index 0000000000000..a46e3b4144950 --- /dev/null +++ b/shell/platform/tizen/channels/navigation_channel.h @@ -0,0 +1,25 @@ +// Copyright 2020 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_NAVIGATION_CHANNEL_H_ +#define EMBEDDER_NAVIGATION_CHANNEL_H_ + +#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/binary_messenger.h" +#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/method_channel.h" +#include "rapidjson/document.h" + +class NavigationChannel { + public: + explicit NavigationChannel(flutter::BinaryMessenger* messenger); + virtual ~NavigationChannel(); + + void SetInitialRoute(const std::string& initialRoute); + void PushRoute(const std::string& route); + void PopRoute(); + + private: + std::unique_ptr> channel_; +}; + +#endif // EMBEDDER_NAVIGATION_CHANNEL_H_ diff --git a/shell/platform/tizen/channels/platform_channel.cc b/shell/platform/tizen/channels/platform_channel.cc new file mode 100644 index 0000000000000..65de673403c0e --- /dev/null +++ b/shell/platform/tizen/channels/platform_channel.cc @@ -0,0 +1,57 @@ +// Copyright 2020 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 "platform_channel.h" + +#include "flutter/shell/platform/common/cpp/json_method_codec.h" +#include "flutter/shell/platform/tizen/logger.h" + +static constexpr char kChannelName[] = "flutter/platform"; + +PlatformChannel::PlatformChannel(flutter::BinaryMessenger* messenger) + : channel_(std::make_unique>( + messenger, kChannelName, &flutter::JsonMethodCodec::GetInstance())) { + channel_->SetMethodCallHandler( + [this]( + const flutter::MethodCall& call, + std::unique_ptr> result) { + HandleMethodCall(call, std::move(result)); + }); +} + +PlatformChannel::~PlatformChannel() {} + +void PlatformChannel::HandleMethodCall( + const flutter::MethodCall& call, + std::unique_ptr> result) { + const auto method = call.method_name(); + + if (method == "SystemNavigator.pop") { + exit(EXIT_SUCCESS); + result->Success(); + } else if (method == "SystemSound.play") { + result->NotImplemented(); + } else if (method == "HapticFeedback.vibrate") { + result->NotImplemented(); + } else if (method == "Clipboard.getData") { + result->NotImplemented(); + } else if (method == "Clipboard.setData") { + result->NotImplemented(); + } else if (method == "Clipboard.hasStrings") { + result->NotImplemented(); + } else if (method == "SystemChrome.setPreferredOrientations") { + result->NotImplemented(); + } else if (method == "SystemChrome.setApplicationSwitcherDescription") { + result->NotImplemented(); + } else if (method == "SystemChrome.setEnabledSystemUIOverlays") { + result->NotImplemented(); + } else if (method == "SystemChrome.restoreSystemUIOverlays") { + result->NotImplemented(); + } else if (method == "SystemChrome.setSystemUIOverlayStyle") { + result->NotImplemented(); + } else { + LoggerI("Unimplemented method: %s", method.c_str()); + result->NotImplemented(); + } +} diff --git a/shell/platform/tizen/channels/platform_channel.h b/shell/platform/tizen/channels/platform_channel.h new file mode 100644 index 0000000000000..078c05f5a2b28 --- /dev/null +++ b/shell/platform/tizen/channels/platform_channel.h @@ -0,0 +1,25 @@ +// Copyright 2020 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_PLATFORM_CHANNEL_H_ +#define EMBEDDER_PLATFORM_CHANNEL_H_ + +#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/binary_messenger.h" +#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/method_channel.h" +#include "rapidjson/document.h" + +class PlatformChannel { + public: + explicit PlatformChannel(flutter::BinaryMessenger* messenger); + virtual ~PlatformChannel(); + + private: + std::unique_ptr> channel_; + + void HandleMethodCall( + const flutter::MethodCall& call, + std::unique_ptr> result); +}; + +#endif // EMBEDDER_PLATFORM_CHANNEL_H_ diff --git a/shell/platform/tizen/channels/platform_view_channel.cc b/shell/platform/tizen/channels/platform_view_channel.cc new file mode 100644 index 0000000000000..779b50e4d46b3 --- /dev/null +++ b/shell/platform/tizen/channels/platform_view_channel.cc @@ -0,0 +1,147 @@ +// Copyright 2020 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 "platform_view_channel.h" + +#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/plugin_registrar.h" +#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/standard_method_codec.h" +#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/standard_message_codec.h" +#include "flutter/shell/platform/common/cpp/json_method_codec.h" +#include "flutter/shell/platform/tizen/logger.h" +#include "flutter/shell/platform/tizen/public/flutter_platform_view.h" + +static constexpr char kChannelName[] = "flutter/platform_views"; + +std::string extractStringFromMap(const flutter::EncodableValue& arguments, + const char* key) { + if (std::holds_alternative(arguments)) { + flutter::EncodableMap values = std::get(arguments); + flutter::EncodableValue value = values[flutter::EncodableValue(key)]; + if (std::holds_alternative(value)) + return std::get(value); + } + return std::string(); +} +int extractIntFromMap(const flutter::EncodableValue& arguments, + const char* key) { + if (std::holds_alternative(arguments)) { + flutter::EncodableMap values = std::get(arguments); + flutter::EncodableValue value = values[flutter::EncodableValue(key)]; + if (std::holds_alternative(value)) return std::get(value); + } + return -1; +} +double extractDoubleFromMap(const flutter::EncodableValue& arguments, + const char* key) { + if (std::holds_alternative(arguments)) { + flutter::EncodableMap values = std::get(arguments); + flutter::EncodableValue value = values[flutter::EncodableValue(key)]; + if (std::holds_alternative(value)) return std::get(value); + } + return -1; +} + +flutter::EncodableMap extractMapFromMap( + const flutter::EncodableValue& arguments, const char* key) { + if (std::holds_alternative(arguments)) { + flutter::EncodableMap values = std::get(arguments); + flutter::EncodableValue value = values[flutter::EncodableValue(key)]; + if (std::holds_alternative(value)) + return std::get(value); + } + return flutter::EncodableMap(); +} + +PlatformViewChannel::PlatformViewChannel(flutter::BinaryMessenger* messenger) + : channel_( + std::make_unique>( + messenger, kChannelName, + &flutter::StandardMethodCodec::GetInstance())) { + channel_->SetMethodCallHandler( + [this](const flutter::MethodCall& call, + std::unique_ptr> + result) { HandleMethodCall(call, std::move(result)); }); +} + +PlatformViewChannel::~PlatformViewChannel() { + // Clean-up view_factories_ + for (auto const& [viewType, viewFactory] : view_factories_) { + viewFactory->dispose(); + } + view_factories_.clear(); + + // Clean-up view_instances_ + for (auto const& [viewId, viewInstance] : view_instances_) { + viewInstance->dispose(); + delete viewInstance; + } + view_instances_.clear(); +} + +void PlatformViewChannel::HandleMethodCall( + const flutter::MethodCall& call, + std::unique_ptr> result) { + const auto method = call.method_name(); + const auto& arguments = *call.arguments(); + + if (method == "create") { + std::string viewType = extractStringFromMap(arguments, "viewType"); + int viewId = extractIntFromMap(arguments, "id"); + double width = extractDoubleFromMap(arguments, "width"); + double height = extractDoubleFromMap(arguments, "height"); + + LoggerD( + "PlatformViewChannel create viewType: %s id: %d width: %f height: %f ", + viewType.c_str(), viewId, width, height); + + flutter::EncodableMap values = std::get(arguments); + flutter::EncodableValue value = values[flutter::EncodableValue("params")]; + ByteMessage byteMessage; + if (std::holds_alternative(value)) { + byteMessage = std::get(value); + } + auto it = view_factories_.find(viewType); + if (it != view_factories_.end()) { + auto viewInstance = + it->second->create(viewId, width, height, byteMessage); + view_instances_.insert( + std::pair(viewId, viewInstance)); + result->Success(flutter::EncodableValue(viewInstance->getTextureId())); + } else { + LoggerE("can't find view type = %s", viewType.c_str()); + result->Error("0", "can't find view type"); + } + } else { + int viewId = extractIntFromMap(arguments, "id"); + auto it = view_instances_.find(viewId); + if (viewId >= 0 && it != view_instances_.end()) { + if (method == "dispose") { + LoggerD("PlatformViewChannel dispose"); + it->second->dispose(); + result->Success(); + } else if (method == "resize") { + LoggerD("PlatformViewChannel resize"); + double width = extractDoubleFromMap(arguments, "width"); + double height = extractDoubleFromMap(arguments, "height"); + it->second->resize(width, height); + result->NotImplemented(); + } else if (method == "touch") { + LoggerD("PlatformViewChannel touch"); + result->NotImplemented(); + } else if (method == "setDirection") { + LoggerD("PlatformViewChannel setDirection"); + result->NotImplemented(); + } else if (method == "clearFocus") { + LoggerD("PlatformViewChannel clearFocus"); + it->second->clearFocus(); + result->NotImplemented(); + } else { + LoggerD("Unimplemented method: %s", method.c_str()); + result->NotImplemented(); + } + } else { + LoggerE("can't find view id"); + result->Error("0", "can't find view id"); + } + } +} diff --git a/shell/platform/tizen/channels/platform_view_channel.h b/shell/platform/tizen/channels/platform_view_channel.h new file mode 100644 index 0000000000000..ecd437d7053df --- /dev/null +++ b/shell/platform/tizen/channels/platform_view_channel.h @@ -0,0 +1,34 @@ +// Copyright 2020 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_PLATFORM_VIEW_CHANNEL_H_ +#define EMBEDDER_PLATFORM_VIEW_CHANNEL_H_ + +#include + +#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/binary_messenger.h" +#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/method_channel.h" +#include "rapidjson/document.h" + +class PlatformView; +class PlatformViewFactory; +class PlatformViewChannel { + public: + explicit PlatformViewChannel(flutter::BinaryMessenger* messenger); + virtual ~PlatformViewChannel(); + std::map>& viewFactories() { + return view_factories_; + } + + private: + std::unique_ptr> channel_; + std::map> view_factories_; + std::map view_instances_; + + void HandleMethodCall( + const flutter::MethodCall& call, + std::unique_ptr> result); +}; + +#endif // EMBEDDER_PLATFORM_VIEW_CHANNEL_H_ diff --git a/shell/platform/tizen/channels/settings_channel.cc b/shell/platform/tizen/channels/settings_channel.cc new file mode 100644 index 0000000000000..8c3586beca256 --- /dev/null +++ b/shell/platform/tizen/channels/settings_channel.cc @@ -0,0 +1,47 @@ +// Copyright 2020 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 "settings_channel.h" + +static constexpr char CHANNEL_NAME[] = "flutter/settings"; +static constexpr char TEXT_SCALE_FACTOR[] = "textScaleFactor"; +static constexpr char ALWAYS_USE_24_HOUR_FORMAT[] = "alwaysUse24HourFormat"; +static constexpr char PLATFORM_BRIGHTNESS[] = "platformBrightness"; + +SettingsChannel::SettingsChannel(flutter::BinaryMessenger* messenger) + : channel_( + std::make_unique>( + messenger, + CHANNEL_NAME, + &flutter::JsonMessageCodec::GetInstance())) { + system_settings_set_changed_cb(SYSTEM_SETTINGS_KEY_LOCALE_TIMEFORMAT_24HOUR, + OnSettingsChangedCallback, this); + SendSettingsEvent(); +} + +SettingsChannel::~SettingsChannel() { + system_settings_unset_changed_cb( + SYSTEM_SETTINGS_KEY_LOCALE_TIMEFORMAT_24HOUR); +} + +void SettingsChannel::SendSettingsEvent() { + rapidjson::Document event(rapidjson::kObjectType); + auto& allocator = event.GetAllocator(); + bool value = false; + int ret = system_settings_get_value_bool( + SYSTEM_SETTINGS_KEY_LOCALE_TIMEFORMAT_24HOUR, &value); + if (ret == SYSTEM_SETTINGS_ERROR_NONE) { + event.AddMember(TEXT_SCALE_FACTOR, 1.0, allocator); + event.AddMember(PLATFORM_BRIGHTNESS, "light", allocator); + event.AddMember(ALWAYS_USE_24_HOUR_FORMAT, value, allocator); + channel_->Send(event); + } +} + +void SettingsChannel::OnSettingsChangedCallback(system_settings_key_e key, + void* user_data) { + SettingsChannel* settingsChannel = + reinterpret_cast(user_data); + settingsChannel->SendSettingsEvent(); +} diff --git a/shell/platform/tizen/channels/settings_channel.h b/shell/platform/tizen/channels/settings_channel.h new file mode 100644 index 0000000000000..4c5a5feb4a3fa --- /dev/null +++ b/shell/platform/tizen/channels/settings_channel.h @@ -0,0 +1,25 @@ +// Copyright 2020 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_SETTINGS_CHANNEL_H_ +#define EMBEDDER_SETTINGS_CHANNEL_H_ + +#include +#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/basic_message_channel.h" +#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/binary_messenger.h" +#include "flutter/shell/platform/common/cpp/json_message_codec.h" +#include "rapidjson/document.h" + +class SettingsChannel { + public: + explicit SettingsChannel(flutter::BinaryMessenger* messenger); + virtual ~SettingsChannel(); + + private: + std::unique_ptr> channel_; + static void OnSettingsChangedCallback(system_settings_key_e key, + void* user_data); + void SendSettingsEvent(); +}; +#endif // EMBEDDER_SETTINGS_CHANNEL_H_ diff --git a/shell/platform/tizen/channels/text_input_channel.cc b/shell/platform/tizen/channels/text_input_channel.cc new file mode 100644 index 0000000000000..dd4eec438e35b --- /dev/null +++ b/shell/platform/tizen/channels/text_input_channel.cc @@ -0,0 +1,617 @@ +// Copyright 2020 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 "text_input_channel.h" + +#include + +#include "flutter/shell/platform/tizen/logger.h" +#include "stdlib.h" +#include "string.h" + +static constexpr char kSetEditingStateMethod[] = "TextInput.setEditingState"; +static constexpr char kClearClientMethod[] = "TextInput.clearClient"; +static constexpr char kSetClientMethod[] = "TextInput.setClient"; +static constexpr char kShowMethod[] = "TextInput.show"; +static constexpr char kHideMethod[] = "TextInput.hide"; +static constexpr char kMultilineInputType[] = "TextInputType.multiline"; +static constexpr char kUpdateEditingStateMethod[] = + "TextInputClient.updateEditingState"; +static constexpr char kPerformActionMethod[] = "TextInputClient.performAction"; +static constexpr char kTextInputAction[] = "inputAction"; +static constexpr char kTextInputType[] = "inputType"; +static constexpr char kTextInputTypeName[] = "name"; +static constexpr char kComposingBaseKey[] = "composingBase"; +static constexpr char kComposingExtentKey[] = "composingExtent"; +static constexpr char kSelectionAffinityKey[] = "selectionAffinity"; +static constexpr char kAffinityDownstream[] = "TextAffinity.downstream"; +static constexpr char kSelectionBaseKey[] = "selectionBase"; +static constexpr char kSelectionExtentKey[] = "selectionExtent"; +static constexpr char kSelectionIsDirectionalKey[] = "selectionIsDirectional"; +static constexpr char kTextKey[] = "text"; +static constexpr char kChannelName[] = "flutter/textinput"; +static constexpr char kBadArgumentError[] = "Bad Arguments"; +static constexpr char kInternalConsistencyError[] = + "Internal Consistency Error"; + +static const char* getImfMethod() { + Eina_List* modules; + + modules = ecore_imf_context_available_ids_get(); + if (!modules) return nullptr; + + void* module; + EINA_LIST_FREE(modules, module) { return (const char*)module; } + + return nullptr; +} + +static bool IsASCIIPrintableKey(char c) { + if (c >= 32 && c <= 126) { + return true; + } + return false; +} + +static void CommitCallback(void* data, Ecore_IMF_Context* ctx, + void* event_info) { + TextInputChannel* self = (TextInputChannel*)data; + char* str = (char*)event_info; + self->OnCommit(str); +} + +static void PreeditCallback(void* data, Ecore_IMF_Context* ctx, + void* event_info) { + TextInputChannel* self = (TextInputChannel*)data; + char* preedit_string = nullptr; + int cursor_pos; + ecore_imf_context_preedit_string_get(ctx, &preedit_string, &cursor_pos); + if (preedit_string) { + self->OnPredit(preedit_string, cursor_pos); + free(preedit_string); + } +} + +static void PrivateCommandCallback(void* data, Ecore_IMF_Context* ctx, + void* event_info) { + // TODO + LoggerD("Unimplemented"); +} + +static void DeleteSurroundingCallback(void* data, Ecore_IMF_Context* ctx, + void* event_info) { + // TODO + LoggerD("Unimplemented"); +} + +static void InputPanelStatChangedCallback(void* data, + Ecore_IMF_Context* context, + int value) { + if (!data) { + LoggerD("[No Data]\n"); + return; + } + TextInputChannel* self = (TextInputChannel*)data; + switch (value) { + case ECORE_IMF_INPUT_PANEL_STATE_SHOW: + LoggerD("[PANEL_STATE_SHOW]\n"); + self->ShowSoftwareKeyboard(); + break; + case ECORE_IMF_INPUT_PANEL_STATE_HIDE: + LoggerD("[PANEL_STATE_HIDE]\n"); + self->HideSoftwareKeyboard(); + break; + case ECORE_IMF_INPUT_PANEL_STATE_WILL_SHOW: + LoggerD("[PANEL_STATE_WILL_SHOW]\n"); + break; + default: + LoggerD("[PANEL_STATE_EVENT (default: %d)]\n", value); + break; + } +} + +static Eina_Bool RetrieveSurroundingCallback(void* data, Ecore_IMF_Context* ctx, + char** text, int* cursor_pos) { + // TODO + if (text) { + *text = strdup(""); + } + if (cursor_pos) { + *cursor_pos = 0; + } + return EINA_TRUE; +} + +Ecore_IMF_Keyboard_Modifiers EcoreInputModifierToEcoreIMFModifier( + unsigned int ecoreModifier) { + unsigned int modifier(ECORE_IMF_KEYBOARD_MODIFIER_NONE); + + if (ecoreModifier & ECORE_EVENT_MODIFIER_SHIFT) { + modifier |= ECORE_IMF_KEYBOARD_MODIFIER_SHIFT; + } + + if (ecoreModifier & ECORE_EVENT_MODIFIER_ALT) { + modifier |= ECORE_IMF_KEYBOARD_MODIFIER_ALT; + } + + if (ecoreModifier & ECORE_EVENT_MODIFIER_CTRL) { + modifier |= ECORE_IMF_KEYBOARD_MODIFIER_CTRL; + } + + if (ecoreModifier & ECORE_EVENT_MODIFIER_WIN) { + modifier |= ECORE_IMF_KEYBOARD_MODIFIER_WIN; + } + + if (ecoreModifier & ECORE_EVENT_MODIFIER_ALTGR) { + modifier |= ECORE_IMF_KEYBOARD_MODIFIER_ALTGR; + } + + return static_cast(modifier); +} + +Ecore_IMF_Keyboard_Locks EcoreInputModifierToEcoreIMFLock( + unsigned int modifier) { + // If no other matches, returns NONE. + unsigned int lock(ECORE_IMF_KEYBOARD_LOCK_NONE); + + if (modifier & ECORE_EVENT_LOCK_NUM) { + lock |= ECORE_IMF_KEYBOARD_LOCK_NUM; + } + + if (modifier & ECORE_EVENT_LOCK_CAPS) { + lock |= ECORE_IMF_KEYBOARD_LOCK_CAPS; + } + + if (modifier & ECORE_EVENT_LOCK_SCROLL) { + lock |= ECORE_IMF_KEYBOARD_LOCK_SCROLL; + } + + return static_cast(lock); +} + +Ecore_IMF_Device_Class EoreDeviceClassToEcoreIMFDeviceClass( + Ecore_Device_Class ecoreDeviceClass) { + switch (ecoreDeviceClass) { + case ECORE_DEVICE_CLASS_SEAT: + return ECORE_IMF_DEVICE_CLASS_SEAT; + case ECORE_DEVICE_CLASS_KEYBOARD: + return ECORE_IMF_DEVICE_CLASS_KEYBOARD; + case ECORE_DEVICE_CLASS_MOUSE: + return ECORE_IMF_DEVICE_CLASS_MOUSE; + case ECORE_DEVICE_CLASS_TOUCH: + return ECORE_IMF_DEVICE_CLASS_TOUCH; + case ECORE_DEVICE_CLASS_PEN: + return ECORE_IMF_DEVICE_CLASS_PEN; + case ECORE_DEVICE_CLASS_POINTER: + return ECORE_IMF_DEVICE_CLASS_POINTER; + case ECORE_DEVICE_CLASS_GAMEPAD: + return ECORE_IMF_DEVICE_CLASS_GAMEPAD; + case ECORE_DEVICE_CLASS_NONE: + default: + return ECORE_IMF_DEVICE_CLASS_NONE; + } +} + +Ecore_IMF_Device_Subclass EoreDeviceSubClassToEcoreIMFDeviceSubClass( + Ecore_Device_Subclass ecoreDeviceSubclass) { + switch (ecoreDeviceSubclass) { + case ECORE_DEVICE_SUBCLASS_FINGER: + return ECORE_IMF_DEVICE_SUBCLASS_FINGER; + case ECORE_DEVICE_SUBCLASS_FINGERNAIL: + return ECORE_IMF_DEVICE_SUBCLASS_FINGERNAIL; + case ECORE_DEVICE_SUBCLASS_KNUCKLE: + return ECORE_IMF_DEVICE_SUBCLASS_KNUCKLE; + case ECORE_DEVICE_SUBCLASS_PALM: + return ECORE_IMF_DEVICE_SUBCLASS_PALM; + case ECORE_DEVICE_SUBCLASS_HAND_SIZE: + return ECORE_IMF_DEVICE_SUBCLASS_HAND_SIZE; + case ECORE_DEVICE_SUBCLASS_HAND_FLAT: + return ECORE_IMF_DEVICE_SUBCLASS_HAND_FLAT; + case ECORE_DEVICE_SUBCLASS_PEN_TIP: + return ECORE_IMF_DEVICE_SUBCLASS_PEN_TIP; + case ECORE_DEVICE_SUBCLASS_TRACKPAD: + return ECORE_IMF_DEVICE_SUBCLASS_TRACKPAD; + case ECORE_DEVICE_SUBCLASS_TRACKPOINT: + return ECORE_IMF_DEVICE_SUBCLASS_TRACKPOINT; + case ECORE_DEVICE_SUBCLASS_TRACKBALL: + return ECORE_IMF_DEVICE_SUBCLASS_TRACKBALL; + case ECORE_DEVICE_SUBCLASS_REMOCON: + case ECORE_DEVICE_SUBCLASS_VIRTUAL_KEYBOARD: + // LoggerW("There is no corresponding type"); + case ECORE_DEVICE_SUBCLASS_NONE: + default: + return ECORE_IMF_DEVICE_SUBCLASS_NONE; + } +} + +TextInputChannel::TextInputChannel(flutter::BinaryMessenger* messenger, + Ecore_Wl2_Window* ecoreWindow) + : channel_(std::make_unique>( + messenger, kChannelName, &flutter::JsonMethodCodec::GetInstance())), + active_model_(nullptr), + isSoftwareKeyboardShowing_(false), + lastPreeditStringLength_(0), + imfContext_(nullptr), + isWearable_(false), + inSelectMode_(false) { + const char* elmProfile = getenv("ELM_PROFILE"); + if (!elmProfile || strcmp(elmProfile, "wearable") == 0) { + LoggerD("ELM_PROFILE is wearable"); + isWearable_ = true; + } + + channel_->SetMethodCallHandler( + [this]( + const flutter::MethodCall& call, + std::unique_ptr> result) { + HandleMethodCall(call, std::move(result)); + }); + + ecore_imf_init(); + // Register IMF callbacks + if (ecore_imf_context_default_id_get()) { + imfContext_ = ecore_imf_context_add(ecore_imf_context_default_id_get()); + } else if (getImfMethod() != nullptr) { + imfContext_ = ecore_imf_context_add(getImfMethod()); + } + if (imfContext_) { + ecore_imf_context_client_window_set( + imfContext_, (void*)ecore_wl2_window_id_get(ecoreWindow)); + RegisterIMFCallback(ecoreWindow); + } else { + LoggerE("Failed to create imfContext"); + } +} + +TextInputChannel::~TextInputChannel() { + if (imfContext_) { + UnregisterIMFCallback(); + ecore_imf_context_del(imfContext_); + ecore_imf_shutdown(); + } +} + +void TextInputChannel::OnKeyDown(Ecore_Event_Key* key) { + if (active_model_ && !FilterEvent(key)) { + NonIMFFallback(key); + } +} + +void TextInputChannel::HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result) { + const std::string& method = method_call.method_name(); + + LoggerD("Method : %s", method.data()); + + if (method.compare(kShowMethod) == 0) { + ShowSoftwareKeyboard(); + } else if (method.compare(kHideMethod) == 0) { + HideSoftwareKeyboard(); + } else if (method.compare(kClearClientMethod) == 0) { + active_model_ = nullptr; + } else if (method.compare(kSetClientMethod) == 0) { + if (!method_call.arguments() || method_call.arguments()->IsNull()) { + result->Error(kBadArgumentError, "Method invoked without args"); + return; + } + const rapidjson::Document& args = *method_call.arguments(); + + // TODO(awdavies): There's quite a wealth of arguments supplied with this + // method, and they should be inspected/used. + const rapidjson::Value& client_id_json = args[0]; + const rapidjson::Value& client_config = args[1]; + if (client_id_json.IsNull()) { + result->Error(kBadArgumentError, "Could not set client, ID is null."); + return; + } + if (client_config.IsNull()) { + result->Error(kBadArgumentError, + "Could not set client, missing arguments."); + } + client_id_ = client_id_json.GetInt(); + input_action_ = ""; + auto input_action_json = client_config.FindMember(kTextInputAction); + if (input_action_json != client_config.MemberEnd() && + input_action_json->value.IsString()) { + input_action_ = input_action_json->value.GetString(); + } + input_type_ = ""; + auto input_type_info_json = client_config.FindMember(kTextInputType); + if (input_type_info_json != client_config.MemberEnd() && + input_type_info_json->value.IsObject()) { + auto input_type_json = + input_type_info_json->value.FindMember(kTextInputTypeName); + if (input_type_json != input_type_info_json->value.MemberEnd() && + input_type_json->value.IsString()) { + input_type_ = input_type_json->value.GetString(); + } + } + active_model_ = std::make_unique(); + } else if (method.compare(kSetEditingStateMethod) == 0) { + if (!method_call.arguments() || method_call.arguments()->IsNull()) { + result->Error(kBadArgumentError, "Method invoked without args"); + return; + } + const rapidjson::Document& args = *method_call.arguments(); + + if (active_model_ == nullptr) { + result->Error( + kInternalConsistencyError, + "Set editing state has been invoked, but no client is set."); + return; + } + auto text = args.FindMember(kTextKey); + if (text == args.MemberEnd() || text->value.IsNull()) { + result->Error(kBadArgumentError, + "Set editing state has been invoked, but without text."); + return; + } + auto selection_base = args.FindMember(kSelectionBaseKey); + auto selection_extent = args.FindMember(kSelectionExtentKey); + if (selection_base == args.MemberEnd() || selection_base->value.IsNull() || + selection_extent == args.MemberEnd() || + selection_extent->value.IsNull()) { + result->Error(kInternalConsistencyError, + "Selection base/extent values invalid."); + return; + } + active_model_->SetEditingState(selection_base->value.GetInt(), + selection_extent->value.GetInt(), + text->value.GetString()); + } else { + result->NotImplemented(); + return; + } + // All error conditions return early, so if nothing has gone wrong indicate + // success. + result->Success(); +} + +void TextInputChannel::SendStateUpdate(const flutter::TextInputModel& model) { + auto args = std::make_unique(rapidjson::kArrayType); + auto& allocator = args->GetAllocator(); + args->PushBack(client_id_, allocator); + + rapidjson::Value editing_state(rapidjson::kObjectType); + editing_state.AddMember(kComposingBaseKey, -1, allocator); + editing_state.AddMember(kComposingExtentKey, -1, allocator); + editing_state.AddMember(kSelectionAffinityKey, kAffinityDownstream, + allocator); + editing_state.AddMember(kSelectionBaseKey, model.selection_base(), allocator); + editing_state.AddMember(kSelectionExtentKey, model.selection_extent(), + allocator); + editing_state.AddMember(kSelectionIsDirectionalKey, false, allocator); + editing_state.AddMember( + kTextKey, rapidjson::Value(model.GetText(), allocator).Move(), allocator); + args->PushBack(editing_state, allocator); + + channel_->InvokeMethod(kUpdateEditingStateMethod, std::move(args)); +} + +bool TextInputChannel::FilterEvent(Ecore_Event_Key* keyDownEvent) { + bool handled = false; + + const char* device = ecore_device_name_get(keyDownEvent->dev); + + Ecore_IMF_Event_Key_Down ecoreKeyDownEvent; + ecoreKeyDownEvent.keyname = keyDownEvent->keyname; + ecoreKeyDownEvent.key = keyDownEvent->key; + ecoreKeyDownEvent.string = keyDownEvent->string; + ecoreKeyDownEvent.compose = keyDownEvent->compose; + ecoreKeyDownEvent.timestamp = keyDownEvent->timestamp; + ecoreKeyDownEvent.modifiers = + EcoreInputModifierToEcoreIMFModifier(keyDownEvent->modifiers); + ecoreKeyDownEvent.locks = + EcoreInputModifierToEcoreIMFLock(keyDownEvent->modifiers); + ecoreKeyDownEvent.dev_name = device; + ecoreKeyDownEvent.dev_class = EoreDeviceClassToEcoreIMFDeviceClass( + ecore_device_class_get(keyDownEvent->dev)); + ecoreKeyDownEvent.dev_subclass = EoreDeviceSubClassToEcoreIMFDeviceSubClass( + ecore_device_subclass_get(keyDownEvent->dev)); + ecoreKeyDownEvent.keycode = keyDownEvent->keycode; + + bool isIME = strcmp(device, "ime") == 0; + if (isIME && strcmp(keyDownEvent->key, "Select") == 0) { + if (isWearable_) { + inSelectMode_ = true; + } else { + SelectPressed(active_model_.get()); + return true; + } + } + + if (isIME && strcmp(keyDownEvent->key, "Left") == 0) { + if (active_model_->MoveCursorBack()) { + SendStateUpdate(*active_model_); + return true; + } + } else if (isIME && strcmp(keyDownEvent->key, "Right") == 0) { + if (active_model_->MoveCursorForward()) { + SendStateUpdate(*active_model_); + return true; + } + } else if (isIME && strcmp(keyDownEvent->key, "End") == 0) { + active_model_->MoveCursorToEnd(); + SendStateUpdate(*active_model_); + return true; + } else if (isIME && strcmp(keyDownEvent->key, "Home") == 0) { + active_model_->MoveCursorToBeginning(); + SendStateUpdate(*active_model_); + return true; + } else if (isIME && strcmp(keyDownEvent->key, "BackSpace") == 0) { + if (active_model_->Backspace()) { + SendStateUpdate(*active_model_); + return true; + } + } else if (isIME && strcmp(keyDownEvent->key, "Delete") == 0) { + if (active_model_->Delete()) { + SendStateUpdate(*active_model_); + return true; + } + } else { + handled = ecore_imf_context_filter_event( + imfContext_, ECORE_IMF_EVENT_KEY_DOWN, + reinterpret_cast(&ecoreKeyDownEvent)); + } + + if (!handled && !strcmp(keyDownEvent->key, "Return")) { + if (inSelectMode_) { + inSelectMode_ = false; + handled = true; + } else { + ecore_imf_context_reset(imfContext_); + EnterPressed(active_model_.get()); + } + } + + return handled; +} + +void TextInputChannel::NonIMFFallback(Ecore_Event_Key* keyDownEvent) { + LoggerD("NonIMFFallback key name [%s]", keyDownEvent->keyname); + if (strcmp(keyDownEvent->key, "Left") == 0) { + if (active_model_->MoveCursorBack()) { + SendStateUpdate(*active_model_); + } + } else if (strcmp(keyDownEvent->key, "Right") == 0) { + if (active_model_->MoveCursorForward()) { + SendStateUpdate(*active_model_); + } + } else if (strcmp(keyDownEvent->key, "End") == 0) { + active_model_->MoveCursorToEnd(); + SendStateUpdate(*active_model_); + } else if (strcmp(keyDownEvent->key, "Home") == 0) { + active_model_->MoveCursorToBeginning(); + SendStateUpdate(*active_model_); + } else if (strcmp(keyDownEvent->key, "BackSpace") == 0) { + if (active_model_->Backspace()) { + SendStateUpdate(*active_model_); + } + } else if (strcmp(keyDownEvent->key, "Delete") == 0) { + if (active_model_->Delete()) { + SendStateUpdate(*active_model_); + } + } else if (strcmp(keyDownEvent->key, "Return") == 0) { + EnterPressed(active_model_.get()); + } else if (keyDownEvent->string && strlen(keyDownEvent->string) == 1 && + IsASCIIPrintableKey(keyDownEvent->string[0])) { + active_model_->AddCodePoint(keyDownEvent->string[0]); + SendStateUpdate(*active_model_); + } +} + +void TextInputChannel::EnterPressed(flutter::TextInputModel* model) { + if (input_type_ == kMultilineInputType) { + model->AddCodePoint('\n'); + SendStateUpdate(*model); + } + auto args = std::make_unique(rapidjson::kArrayType); + auto& allocator = args->GetAllocator(); + args->PushBack(client_id_, allocator); + args->PushBack(rapidjson::Value(input_action_, allocator).Move(), allocator); + + channel_->InvokeMethod(kPerformActionMethod, std::move(args)); +} + +void TextInputChannel::SelectPressed(flutter::TextInputModel* model) { + auto args = std::make_unique(rapidjson::kArrayType); + auto& allocator = args->GetAllocator(); + args->PushBack(client_id_, allocator); + args->PushBack(rapidjson::Value(input_action_, allocator).Move(), allocator); + + channel_->InvokeMethod(kPerformActionMethod, std::move(args)); +} + +void TextInputChannel::OnCommit(const char* str) { + for (int i = lastPreeditStringLength_; i > 0; i--) { + active_model_->Backspace(); + } + active_model_->AddText(str); + SendStateUpdate(*active_model_); + lastPreeditStringLength_ = 0; +} + +void TextInputChannel::OnPredit(const char* str, int cursorPos) { + if (strcmp(str, "") == 0) { + lastPreeditStringLength_ = 0; + return; + } + + for (int i = lastPreeditStringLength_; i > 0; i--) { + active_model_->Backspace(); + } + + active_model_->AddText(str); + SendStateUpdate(*active_model_); + lastPreeditStringLength_ = cursorPos; +} + +void TextInputChannel::ShowSoftwareKeyboard() { + LoggerD("ShowPanel() [isSoftwareKeyboardShowing_:%d] \n", + isSoftwareKeyboardShowing_); + if (imfContext_ && !isSoftwareKeyboardShowing_) { + isSoftwareKeyboardShowing_ = true; + ecore_imf_context_input_panel_show(imfContext_); + ecore_imf_context_focus_in(imfContext_); + } +} + +void TextInputChannel::HideSoftwareKeyboard() { + LoggerD("HidePanel() [isSoftwareKeyboardShowing_:%d] \n", + isSoftwareKeyboardShowing_); + if (imfContext_ && isSoftwareKeyboardShowing_) { + isSoftwareKeyboardShowing_ = false; + ecore_imf_context_reset(imfContext_); + ecore_imf_context_focus_out(imfContext_); + ecore_imf_context_input_panel_hide(imfContext_); + } +} + +void TextInputChannel::RegisterIMFCallback(Ecore_Wl2_Window* ecoreWindow) { + // ecore_imf_context_input_panel_enabled_set(imfContext_, false); + ecore_imf_context_event_callback_add(imfContext_, ECORE_IMF_CALLBACK_COMMIT, + CommitCallback, this); + ecore_imf_context_event_callback_add( + imfContext_, ECORE_IMF_CALLBACK_PREEDIT_CHANGED, PreeditCallback, this); + ecore_imf_context_event_callback_add(imfContext_, + ECORE_IMF_CALLBACK_DELETE_SURROUNDING, + DeleteSurroundingCallback, this); + ecore_imf_context_event_callback_add(imfContext_, + ECORE_IMF_CALLBACK_PRIVATE_COMMAND_SEND, + PrivateCommandCallback, this); + ecore_imf_context_input_panel_event_callback_add( + imfContext_, ECORE_IMF_INPUT_PANEL_STATE_EVENT, + InputPanelStatChangedCallback, this); + ecore_imf_context_retrieve_surrounding_callback_set( + imfContext_, RetrieveSurroundingCallback, this); + + // These APIs have to be set when IMF's setting status is changed. + ecore_imf_context_autocapital_type_set(imfContext_, + ECORE_IMF_AUTOCAPITAL_TYPE_NONE); + ecore_imf_context_prediction_allow_set(imfContext_, EINA_FALSE); + ecore_imf_context_input_panel_layout_set(imfContext_, + ECORE_IMF_INPUT_PANEL_LAYOUT_NORMAL); + ecore_imf_context_input_panel_return_key_type_set( + imfContext_, ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_DEFAULT); + ecore_imf_context_input_panel_layout_variation_set(imfContext_, 0); + ecore_imf_context_input_panel_language_set( + imfContext_, ECORE_IMF_INPUT_PANEL_LANG_AUTOMATIC); +} + +void TextInputChannel::UnregisterIMFCallback() { + ecore_imf_context_event_callback_del(imfContext_, ECORE_IMF_CALLBACK_COMMIT, + CommitCallback); + ecore_imf_context_event_callback_del( + imfContext_, ECORE_IMF_CALLBACK_PREEDIT_CHANGED, PreeditCallback); + ecore_imf_context_event_callback_del(imfContext_, + ECORE_IMF_CALLBACK_DELETE_SURROUNDING, + DeleteSurroundingCallback); + ecore_imf_context_event_callback_del(imfContext_, + ECORE_IMF_CALLBACK_PRIVATE_COMMAND_SEND, + PrivateCommandCallback); + ecore_imf_context_input_panel_event_callback_del( + imfContext_, ECORE_IMF_INPUT_PANEL_STATE_EVENT, + InputPanelStatChangedCallback); +} diff --git a/shell/platform/tizen/channels/text_input_channel.h b/shell/platform/tizen/channels/text_input_channel.h new file mode 100644 index 0000000000000..b8d8985bcadf2 --- /dev/null +++ b/shell/platform/tizen/channels/text_input_channel.h @@ -0,0 +1,57 @@ +// Copyright 2020 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_TEXT_INPUT_PLUGIN_H_ +#define EMBEDDER_TEXT_INPUT_PLUGIN_H_ + +#define EFL_BETA_API_SUPPORT +#include +#include +#include + +#include + +#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/binary_messenger.h" +#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/method_channel.h" +#include "flutter/shell/platform/common/cpp/json_method_codec.h" +#include "flutter/shell/platform/common/cpp/text_input_model.h" + +class TextInputChannel { + public: + explicit TextInputChannel(flutter::BinaryMessenger* messenger, + Ecore_Wl2_Window* ecoreWindow); + virtual ~TextInputChannel(); + void OnKeyDown(Ecore_Event_Key* key); + void OnCommit(const char* str); + void OnPredit(const char* str, int cursorPos); + void ShowSoftwareKeyboard(); + void HideSoftwareKeyboard(); + bool isSoftwareKeyboardShowing() { return isSoftwareKeyboardShowing_; } + + private: + void HandleMethodCall( + const flutter::MethodCall& method_call, + std::unique_ptr> result); + void SendStateUpdate(const flutter::TextInputModel& model); + bool FilterEvent(Ecore_Event_Key* keyDownEvent); + void NonIMFFallback(Ecore_Event_Key* keyDownEvent); + void EnterPressed(flutter::TextInputModel* model); + void SelectPressed(flutter::TextInputModel* model); + void RegisterIMFCallback(Ecore_Wl2_Window* ecoreWindow); + void UnregisterIMFCallback(); + + std::unique_ptr> channel_; + std::unique_ptr active_model_; + + private: + int client_id_; + std::string input_type_; + std::string input_action_; + bool isSoftwareKeyboardShowing_; + int lastPreeditStringLength_; + Ecore_IMF_Context* imfContext_; + bool isWearable_; + bool inSelectMode_; +}; +#endif diff --git a/shell/platform/tizen/config.gni b/shell/platform/tizen/config.gni new file mode 100644 index 0000000000000..245032c3255cd --- /dev/null +++ b/shell/platform/tizen/config.gni @@ -0,0 +1,13 @@ +# Copyright 2020 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. + +declare_args() { + # Whether to build the Tizen shell for the host platform, if available. + # + # By default, the Tizen shell is not built if there is a native toolkit shell, + # but it can be enabled for supported platforms (Linux) + # as an extra build artifact with this flag. The native toolkit shell will + # still be built as well. + build_tizen_shell = false +} diff --git a/shell/platform/tizen/external_texture_gl.cc b/shell/platform/tizen/external_texture_gl.cc new file mode 100644 index 0000000000000..3f826d9c74cbb --- /dev/null +++ b/shell/platform/tizen/external_texture_gl.cc @@ -0,0 +1,141 @@ +// Copyright 2020 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 "external_texture_gl.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "flutter/shell/platform/tizen/logger.h" + +struct ExternalTextureGLState { + GLuint gl_texture; +}; + +static std::atomic_long nextTextureId = {1}; + +ExternalTextureGL::ExternalTextureGL() + : state_(std::make_unique()), + texture_tbm_surface_(NULL), + texture_id_(nextTextureId++) {} + +ExternalTextureGL::~ExternalTextureGL() { + mutex_.lock(); + if (state_->gl_texture != 0) { + glDeleteTextures(1, &state_->gl_texture); + } + state_.release(); + if (texture_tbm_surface_) { + tbm_surface_internal_unref(texture_tbm_surface_); + texture_tbm_surface_ = NULL; + } + mutex_.unlock(); +} + +bool ExternalTextureGL::OnFrameAvailable(tbm_surface_h tbm_surface) { + mutex_.lock(); + if (!tbm_surface) { + LoggerE("tbm_surface is null"); + mutex_.unlock(); + return false; + } + if (texture_tbm_surface_) { + LoggerE("texture_tbm_surface_ does not destruction, discard"); + mutex_.unlock(); + return false; + } + if (!tbm_surface_internal_is_valid(tbm_surface)) { + LoggerE("tbm_surface not valid, pass"); + mutex_.unlock(); + return false; + } + texture_tbm_surface_ = tbm_surface; + tbm_surface_internal_ref(texture_tbm_surface_); + mutex_.unlock(); + return true; +} + +bool ExternalTextureGL::PopulateTextureWithIdentifier( + size_t width, size_t height, FlutterOpenGLTexture* opengl_texture) { + mutex_.lock(); + if (!texture_tbm_surface_) { + LoggerE("texture_tbm_surface_ is NULL"); + mutex_.unlock(); + return false; + } + if (!tbm_surface_internal_is_valid(texture_tbm_surface_)) { + LoggerE("tbm_surface not valid"); + mutex_.unlock(); + return false; + } + + PFNEGLCREATEIMAGEKHRPROC n_eglCreateImageKHR = + (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR"); + const EGLint attrs[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE, + EGL_NONE}; + EGLImageKHR eglSrcImage = n_eglCreateImageKHR( + eglGetCurrentDisplay(), EGL_NO_CONTEXT, EGL_NATIVE_SURFACE_TIZEN, + (EGLClientBuffer)texture_tbm_surface_, attrs); + if (!eglSrcImage) { + LoggerE("eglSrcImage create fail!!, errorcode == %d", eglGetError()); + mutex_.unlock(); + return false; + } + if (state_->gl_texture == 0) { + glGenTextures(1, &state_->gl_texture); + glBindTexture(GL_TEXTURE_EXTERNAL_OES, state_->gl_texture); + // set the texture wrapping parameters + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, + GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, + GL_CLAMP_TO_BORDER); + // set texture filtering parameters + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } else { + glBindTexture(GL_TEXTURE_EXTERNAL_OES, state_->gl_texture); + } + PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = + (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress( + "glEGLImageTargetTexture2DOES"); + glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, eglSrcImage); + if (eglSrcImage) { + PFNEGLDESTROYIMAGEKHRPROC n_eglDestoryImageKHR = + (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR"); + n_eglDestoryImageKHR(eglGetCurrentDisplay(), eglSrcImage); + } + opengl_texture->target = GL_TEXTURE_EXTERNAL_OES; + opengl_texture->name = state_->gl_texture; + opengl_texture->format = GL_RGBA8; + opengl_texture->destruction_callback = (VoidCallback)destructionCallback; + opengl_texture->user_data = static_cast(this); + opengl_texture->width = width; + opengl_texture->height = height; + mutex_.unlock(); + return true; +} + +void ExternalTextureGL::DestructionTbmSurface() { + mutex_.lock(); + if (!texture_tbm_surface_) { + LoggerE("tbm_surface_h is NULL"); + mutex_.unlock(); + return; + } + tbm_surface_internal_unref(texture_tbm_surface_); + texture_tbm_surface_ = NULL; + mutex_.unlock(); +} + +void ExternalTextureGL::destructionCallback(void* user_data) { + ExternalTextureGL* externalTextureGL = + reinterpret_cast(user_data); + externalTextureGL->DestructionTbmSurface(); +} diff --git a/shell/platform/tizen/external_texture_gl.h b/shell/platform/tizen/external_texture_gl.h new file mode 100644 index 0000000000000..2defe2800b382 --- /dev/null +++ b/shell/platform/tizen/external_texture_gl.h @@ -0,0 +1,55 @@ +// Copyright 2020 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 FLUTTER_SHELL_PLATFORM_TIZEN_EXTERNAL_TEXTURE_GL_H_ +#define FLUTTER_SHELL_PLATFORM_TIZEN_EXTERNAL_TEXTURE_GL_H_ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "flutter/shell/platform/embedder/embedder.h" + +typedef struct ExternalTextureGLState ExternalTextureGLState; + +// An adaptation class of flutter engine and external texture interface. +class ExternalTextureGL { + public: + ExternalTextureGL(); + + virtual ~ExternalTextureGL(); + + /** + * Returns the unique id for the ExternalTextureGL instance. + */ + int64_t texture_id() { return (int64_t)texture_id_; } + + /** + * Accepts texture buffer copy request from the Flutter engine. + * When the user side marks the texture_id as available, the Flutter engine + * will callback to this method and ask for populate the |opengl_texture| + * object, such as the texture type and the format of the pixel buffer and the + * texture object. + * Returns true on success, false on failure. + */ + bool PopulateTextureWithIdentifier(size_t width, size_t height, + FlutterOpenGLTexture* opengl_texture); + bool OnFrameAvailable(tbm_surface_h tbm_surface); + void DestructionTbmSurface(); + + private: + std::unique_ptr state_; + std::mutex mutex_; + tbm_surface_h texture_tbm_surface_; + static void destructionCallback(void* user_data); + const long texture_id_; +}; + +#endif // FLUTTER_SHELL_PLATFORM_TIZEN_EXTERNAL_TEXTURE_GL_H_ diff --git a/shell/platform/tizen/flutter_tizen.cc b/shell/platform/tizen/flutter_tizen.cc new file mode 100644 index 0000000000000..a945cb3776ef2 --- /dev/null +++ b/shell/platform/tizen/flutter_tizen.cc @@ -0,0 +1,272 @@ +// Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved. +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "public/flutter_tizen.h" + +#include +#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/plugin_registrar.h" +#include "flutter/shell/platform/common/cpp/incoming_message_dispatcher.h" +#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/standard_message_codec.h" +#include "flutter/shell/platform/tizen/logger.h" +#include "flutter/shell/platform/tizen/public/flutter_texture_registrar.h" +#include "flutter/shell/platform/tizen/tizen_embedder_engine.h" +#include "flutter/shell/platform/tizen/public/flutter_platform_view.h" + +// Opaque reference to a Tizen embedder engine. +struct FlutterWindowControllerState { + std::unique_ptr engine; +}; + +// The pipe used for logging to dlog. +static int logging_pipe[2]; +// The thread that constantly writes out stdout to dlog through the pipe. +// Only one logging thread should exist per process. +static pthread_t logging_thread; + +static void* LoggingFunction(void*) { + ssize_t size; + char buffer[1024]; + + while ((size = read(logging_pipe[0], buffer, sizeof(buffer) - 1)) > 0) { + buffer[size] = 0; + __dlog_print(LOG_ID_MAIN, DLOG_INFO, LOG_TAG, "%s", buffer); + } + + close(logging_pipe[0]); + close(logging_pipe[1]); + + return nullptr; +} + +// Redirects the process's standard output/error to dlog for use by the flutter +// tools. +bool InitializeLogging() { + if (logging_thread) { + LoggerD("The logging thread already exists."); + return true; + } + + if (pipe(logging_pipe) < 0) { + LoggerE("Failed to create a pipe."); + return false; + } + + if (dup2(logging_pipe[1], 1) < 0 || dup2(logging_pipe[1], 2) < 0) { + LoggerE("Failed to duplicate file descriptors."); + return false; + } + + if (pthread_create(&logging_thread, 0, LoggingFunction, 0) != 0) { + LoggerE("Failed to create a logging thread."); + logging_thread = 0; + return false; + } + + if (pthread_detach(logging_thread) != 0) { + LoggerE("Failed to detach the logging thread."); + return false; + } + return true; +} + +FlutterWindowControllerRef FlutterCreateWindow( + const FlutterWindowProperties& window_properties, + const FlutterEngineProperties& engine_properties) { + InitializeLogging(); + + auto state = std::make_unique(); + state->engine = std::make_unique(window_properties); + + if (!state->engine->RunEngine(engine_properties)) { + LoggerE("Failed to run the Flutter engine."); + return nullptr; + } + + return state.release(); +} + +void FlutterDestoryWindow(FlutterWindowControllerRef controller) { + if (controller->engine) { + controller->engine->StopEngine(); + } + delete controller; +} + +bool FlutterRunsPrecompiledCode() { + return FlutterEngineRunsAOTCompiledDartCode(); +} + +void FlutterDesktopPluginRegistrarEnableInputBlocking( + FlutterDesktopPluginRegistrarRef registrar, const char* channel) { + registrar->engine->message_dispatcher->EnableInputBlockingForChannel(channel); +} + +FlutterDesktopPluginRegistrarRef FlutterDesktopGetPluginRegistrar( + FlutterWindowControllerRef controller, const char* plugin_name) { + // Currently, one registrar acts as the registrar for all plugins, so the + // name is ignored. It is part of the API to reduce churn in the future when + // aligning more closely with the Flutter registrar system. + return controller->engine->GetPluginRegistrar(); +} + +FlutterDesktopMessengerRef FlutterDesktopPluginRegistrarGetMessenger( + FlutterDesktopPluginRegistrarRef registrar) { + return registrar->engine->messenger.get(); +} + +void FlutterDesktopPluginRegistrarSetDestructionHandler( + FlutterDesktopPluginRegistrarRef registrar, + FlutterDesktopOnPluginRegistrarDestroyed callback) { + registrar->engine->SetPluginRegistrarDestructionCallback(callback); +} + +FlutterTextureRegistrarRef FlutterPluginRegistrarGetTexture( + FlutterDesktopPluginRegistrarRef registrar) { + return registrar->texture_registrar.get(); +} + +bool FlutterDesktopMessengerSend(FlutterDesktopMessengerRef messenger, + const char* channel, const uint8_t* message, + const size_t message_size) { + return FlutterDesktopMessengerSendWithReply(messenger, channel, message, + message_size, nullptr, nullptr); +} + +bool FlutterDesktopMessengerSendWithReply(FlutterDesktopMessengerRef messenger, + const char* channel, + const uint8_t* message, + const size_t message_size, + const FlutterDesktopBinaryReply reply, + void* user_data) { + FlutterPlatformMessageResponseHandle* response_handle = nullptr; + if (reply != nullptr && user_data != nullptr) { + FlutterEngineResult result = FlutterPlatformMessageCreateResponseHandle( + messenger->engine->flutter_engine, reply, user_data, &response_handle); + if (result != kSuccess) { + LoggerE("Failed to create response handle"); + return false; + } + } + FlutterPlatformMessage platform_message = { + sizeof(FlutterPlatformMessage), + channel, + message, + message_size, + response_handle, + }; + FlutterEngineResult message_result = FlutterEngineSendPlatformMessage( + messenger->engine->flutter_engine, &platform_message); + if (response_handle != nullptr) { + FlutterPlatformMessageReleaseResponseHandle( + messenger->engine->flutter_engine, response_handle); + } + return message_result == kSuccess; +} + +void FlutterDesktopMessengerSendResponse( + FlutterDesktopMessengerRef messenger, + const FlutterDesktopMessageResponseHandle* handle, const uint8_t* data, + size_t data_length) { + FlutterEngineSendPlatformMessageResponse(messenger->engine->flutter_engine, + handle, data, data_length); +} + +void FlutterDesktopMessengerSetCallback(FlutterDesktopMessengerRef messenger, + const char* channel, + FlutterDesktopMessageCallback callback, + void* user_data) { + messenger->engine->message_dispatcher->SetMessageCallback(channel, callback, + user_data); +} + +void FlutterNotifyLocaleChange(FlutterWindowControllerRef controller) { + if (controller->engine) { + controller->engine->SendLocales(); + } +} + +void FlutterNotifyAppIsInactive(FlutterWindowControllerRef controller) { + if (controller->engine) { + controller->engine->AppIsInactive(); + } +} + +void FlutterNotifyAppIsResumed(FlutterWindowControllerRef controller) { + if (controller->engine) { + controller->engine->AppIsResumed(); + } +} + +void FlutterNotifyAppIsPaused(FlutterWindowControllerRef controller) { + if (controller->engine) { + controller->engine->AppIsPaused(); + } +} + +void FlutterNotifyAppIsDetached(FlutterWindowControllerRef controller) { + if (controller->engine) { + controller->engine->AppIsDetached(); + } +} + +void FlutterNotifyLowMemoryWarning(FlutterWindowControllerRef controller) { + if (controller->engine->flutter_engine) { + FlutterEngineNotifyLowMemoryWarning(controller->engine->flutter_engine); + } +} + +void FlutterRotateWindow(FlutterWindowControllerRef controller, + int32_t degree) { + if (controller->engine) { + controller->engine->SetWindowOrientation(degree); + } +} + +int64_t FlutterRegisterExternalTexture( + FlutterTextureRegistrarRef texture_registrar) { + LoggerD("FlutterDesktopRegisterExternalTexture"); + auto texture_gl = std::make_unique(); + int64_t texture_id = texture_gl->texture_id(); + texture_registrar->textures[texture_id] = std::move(texture_gl); + if (FlutterEngineRegisterExternalTexture(texture_registrar->flutter_engine, + texture_id) == kSuccess) { + return texture_id; + } + return -1; +} + +bool FlutterUnregisterExternalTexture( + FlutterTextureRegistrarRef texture_registrar, int64_t texture_id) { + auto it = texture_registrar->textures.find(texture_id); + if (it != texture_registrar->textures.end()) + texture_registrar->textures.erase(it); + return (FlutterEngineUnregisterExternalTexture( + texture_registrar->flutter_engine, texture_id) == kSuccess); +} + +bool FlutterMarkExternalTextureFrameAvailable( + FlutterTextureRegistrarRef texture_registrar, int64_t texture_id, + void* tbm_surface) { + auto it = texture_registrar->textures.find(texture_id); + if (it == texture_registrar->textures.end()) { + LoggerE("can't find texture texture_id = %lld", texture_id); + return false; + } + if (!texture_registrar->textures[texture_id]->OnFrameAvailable( + (tbm_surface_h)tbm_surface)) { + LoggerE("OnFrameAvailable fail texture_id = %lld", texture_id); + return false; + } + return (FlutterEngineMarkExternalTextureFrameAvailable( + texture_registrar->flutter_engine, texture_id) == kSuccess); +} + +void FlutterRegisterViewFactory( + FlutterDesktopPluginRegistrarRef registrar, const char* view_type, + std::unique_ptr view_factory) { + registrar->engine->platform_view_channel->viewFactories().insert( + std::pair>( + view_type, std::move(view_factory))); +} diff --git a/shell/platform/tizen/key_event_handler.cc b/shell/platform/tizen/key_event_handler.cc new file mode 100644 index 0000000000000..ced29bc34b345 --- /dev/null +++ b/shell/platform/tizen/key_event_handler.cc @@ -0,0 +1,52 @@ +// Copyright 2020 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 "key_event_handler.h" + +#include "flutter/shell/platform/tizen/logger.h" +#include "flutter/shell/platform/tizen/tizen_embedder_engine.h" + +static constexpr char kPlatformBackButtonName[] = "XF86Back"; + +KeyEventHandler::KeyEventHandler(TizenEmbedderEngine *engine) + : engine_(engine) { + key_event_handlers_.push_back( + ecore_event_handler_add(ECORE_EVENT_KEY_DOWN, OnKey, this)); + key_event_handlers_.push_back( + ecore_event_handler_add(ECORE_EVENT_KEY_UP, OnKey, this)); +} + +KeyEventHandler::~KeyEventHandler() { + for (auto handler : key_event_handlers_) { + ecore_event_handler_del(handler); + } + key_event_handlers_.clear(); +} + +Eina_Bool KeyEventHandler::OnKey(void *data, int type, void *event) { + auto *self = reinterpret_cast(data); + auto *key = reinterpret_cast(event); + auto *engine = self->engine_; + auto is_down = type == ECORE_EVENT_KEY_DOWN; + + if (strcmp(key->keyname, kPlatformBackButtonName) == 0) { + // The device back button was pressed. + if (engine->navigation_channel && !is_down) { + engine->navigation_channel->PopRoute(); + } + } else { + if (engine->text_input_channel) { + if (is_down) { + engine->text_input_channel->OnKeyDown(key); + } + if (engine->text_input_channel->isSoftwareKeyboardShowing()) { + return ECORE_CALLBACK_PASS_ON; + } + } + if (engine->key_event_channel) { + engine->key_event_channel->SendKeyEvent(key, is_down); + } + } + return ECORE_CALLBACK_PASS_ON; +} diff --git a/shell/platform/tizen/key_event_handler.h b/shell/platform/tizen/key_event_handler.h new file mode 100644 index 0000000000000..6253a6ca37056 --- /dev/null +++ b/shell/platform/tizen/key_event_handler.h @@ -0,0 +1,26 @@ +// Copyright 2020 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_KEY_EVENT_HANDLER_H_ +#define EMBEDDER_KEY_EVENT_HANDLER_H_ + +#include + +#include + +class TizenEmbedderEngine; + +class KeyEventHandler { + public: + explicit KeyEventHandler(TizenEmbedderEngine* engine); + virtual ~KeyEventHandler(); + + private: + TizenEmbedderEngine* engine_; + std::vector key_event_handlers_; + + static Eina_Bool OnKey(void* data, int type, void* event); +}; + +#endif // EMBEDDER_KEY_EVENT_HANDLER_H_ diff --git a/shell/platform/tizen/logger.h b/shell/platform/tizen/logger.h new file mode 100644 index 0000000000000..1a1be983d277e --- /dev/null +++ b/shell/platform/tizen/logger.h @@ -0,0 +1,29 @@ +// Copyright 2020 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_LOGGER_H_ +#define EMBEDDER_LOGGER_H_ +#include + +#include + +#ifdef LOG_TAG +#undef LOG_TAG +#endif +#define LOG_TAG "ConsoleMessage" + +#undef LOG_ +#define LOG_(id, prio, tag, fmt, arg...) \ + __dlog_print(id, prio, tag, "%s: %s(%d) > " fmt, __FILE__, __func__, \ + __LINE__, ##arg); + +#define _LOGGER_LOG(prio, fmt, args...) \ + LOG_(LOG_ID_MAIN, prio, LOG_TAG, fmt, ##args) + +#define LoggerD(fmt, args...) _LOGGER_LOG(DLOG_DEBUG, fmt, ##args) +#define LoggerI(fmt, args...) _LOGGER_LOG(DLOG_INFO, fmt, ##args) +#define LoggerW(fmt, args...) _LOGGER_LOG(DLOG_WARN, fmt, ##args) +#define LoggerE(fmt, args...) _LOGGER_LOG(DLOG_ERROR, fmt, ##args) + +#endif // EMBEDDER_LOGGER_H_ diff --git a/shell/platform/tizen/public/flutter_platform_view.h b/shell/platform/tizen/public/flutter_platform_view.h new file mode 100644 index 0000000000000..b86b2f9ccccb9 --- /dev/null +++ b/shell/platform/tizen/public/flutter_platform_view.h @@ -0,0 +1,67 @@ +// Copyright 2020 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 FLUTTER_SHELL_PLATFORM_TIZEN_PUBLIC_FLUTTER_PLATFORM_VIEW_H_ +#define FLUTTER_SHELL_PLATFORM_TIZEN_PUBLIC_FLUTTER_PLATFORM_VIEW_H_ + +#include +#include + +#include "flutter_export.h" + +using ByteMessage = std::vector; + +class PlatformView { + public: + PlatformView(flutter::PluginRegistrar* registrar, int viewId) + : registrar_(registrar), viewId_(viewId), textureId_(0) {} + virtual ~PlatformView() {} + int getViewId() { return viewId_; } + int getTextureId() { return textureId_; } + void setTextureId(int textureId) { textureId_ = textureId; } + flutter::PluginRegistrar* getPluginRegistrar() { return registrar_; } + virtual void dispose() = 0; + virtual void resize(double width, double height) = 0; + virtual void touch() = 0; + virtual void setDirection(int direction) = 0; + virtual void clearFocus() = 0; + + private: + flutter::PluginRegistrar* registrar_; + int viewId_; + int textureId_; +}; + +class PlatformViewFactory { + public: + PlatformViewFactory(flutter::PluginRegistrar* registrar) + : registrar_(registrar), + codec_(flutter::StandardMessageCodec::GetInstance(nullptr)) {} + virtual ~PlatformViewFactory() {} + flutter::PluginRegistrar* getPluginRegistrar() { return registrar_; } + const flutter::MessageCodec& getCodec() { + return codec_; + } + virtual PlatformView* create(int viewId, double width, double height, + const ByteMessage& createParams) = 0; + virtual void dispose() = 0; + + private: + flutter::PluginRegistrar* registrar_; + const flutter::MessageCodec& codec_; +}; + +#if defined(__cplusplus) +extern "C" { +#endif + +FLUTTER_EXPORT void FlutterRegisterViewFactory( + FlutterDesktopPluginRegistrarRef registrar, const char* view_type, + std::unique_ptr view_factory); + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif // FLUTTER_SHELL_PLATFORM_TIZEN_PUBLIC_FLUTTER_PLATFORM_VIEW_H_ \ No newline at end of file diff --git a/shell/platform/tizen/public/flutter_texture_registrar.h b/shell/platform/tizen/public/flutter_texture_registrar.h new file mode 100644 index 0000000000000..d07692c460815 --- /dev/null +++ b/shell/platform/tizen/public/flutter_texture_registrar.h @@ -0,0 +1,43 @@ +// Copyright 2020 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 FLUTTER_SHELL_PLATFORM_TIZEN_PUBLIC_FLUTTER_TEXTURE_REGISTRAR_H_ +#define FLUTTER_SHELL_PLATFORM_TIZEN_PUBLIC_FLUTTER_TEXTURE_REGISTRAR_H_ + +#include +#include + +#include "flutter_export.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +// Opaque reference to a texture registrar. +typedef struct FlutterTextureRegistrar* FlutterTextureRegistrarRef; + +// Returns the texture registrar associated with this registrar. +FLUTTER_EXPORT FlutterTextureRegistrarRef +FlutterPluginRegistrarGetTexture(FlutterDesktopPluginRegistrarRef registrar); + +// Registers a new texture with the Flutter engine and returns the texture ID, +FLUTTER_EXPORT int64_t +FlutterRegisterExternalTexture(FlutterTextureRegistrarRef texture_registrar); + +// Unregisters an existing texture from the Flutter engine for a |texture_id|. +// Returns true on success, false on failure. +FLUTTER_EXPORT bool FlutterUnregisterExternalTexture( + FlutterTextureRegistrarRef texture_registrar, int64_t texture_id); + +// Marks that a new texture frame is available for a given |texture_id|. +// Returns true on success, false on failure. +FLUTTER_EXPORT bool FlutterMarkExternalTextureFrameAvailable( + FlutterTextureRegistrarRef texture_registrar, int64_t texture_id, + void* tbm_surface); + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif // FLUTTER_SHELL_PLATFORM_TIZEN_PUBLIC_FLUTTER_TEXTURE_REGISTRAR_H_ \ No newline at end of file diff --git a/shell/platform/tizen/public/flutter_tizen.h b/shell/platform/tizen/public/flutter_tizen.h new file mode 100644 index 0000000000000..7c09b327a6c0a --- /dev/null +++ b/shell/platform/tizen/public/flutter_tizen.h @@ -0,0 +1,92 @@ +// Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved. +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PUBLIC_FLUTTER_TIZEN_EMBEDDER_H_ +#define PUBLIC_FLUTTER_TIZEN_EMBEDDER_H_ + +#include +#include + +#include "flutter_export.h" +#include "flutter_messenger.h" +#include "flutter_plugin_registrar.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +// Opaque reference to a Flutter window controller. +typedef struct FlutterWindowControllerState* FlutterWindowControllerRef; + +// Properties for configuring the initial settings of a Flutter window. +typedef struct { + // The display title. + const char* title; + int32_t x; + int32_t y; + // Width in screen coordinates. + int32_t width; + // Height in screen coordinates. + int32_t height; +} FlutterWindowProperties; + +// Properties for configuring a Flutter engine instance. +typedef struct { + // The path to the flutter_assets folder for the application to be run. + const char* assets_path; + // The path to the icudtl.dat file for the version of Flutter you are using. + const char* icu_data_path; + // The path to the libapp.so file for the application to be run. + const char* aot_library_path; + // The switches to pass to the Flutter engine. + // + // See: https://github.com/flutter/engine/blob/master/shell/common/switches.h + // for details. Not all arguments will apply. + const char** switches; + // The number of elements in |switches|. + size_t switches_count; +} FlutterEngineProperties; + +FLUTTER_EXPORT FlutterWindowControllerRef +FlutterCreateWindow(const FlutterWindowProperties& window_properties, + const FlutterEngineProperties& engine_properties); + +// Returns the plugin registrar handle for the plugin with the given name. +// +// The name must be unique across the application. +FLUTTER_EXPORT FlutterDesktopPluginRegistrarRef +FlutterDesktopGetPluginRegistrar(FlutterWindowControllerRef controller, + const char* plugin_name); + +FLUTTER_EXPORT void FlutterDestoryWindow(FlutterWindowControllerRef controller); + +FLUTTER_EXPORT bool FlutterRunsPrecompiledCode(); + +FLUTTER_EXPORT void FlutterNotifyLocaleChange( + FlutterWindowControllerRef controller); + +FLUTTER_EXPORT void FlutterNotifyLowMemoryWarning( + FlutterWindowControllerRef controller); + +FLUTTER_EXPORT void FlutterNotifyAppIsInactive( + FlutterWindowControllerRef controller); + +FLUTTER_EXPORT void FlutterNotifyAppIsResumed( + FlutterWindowControllerRef controller); + +FLUTTER_EXPORT void FlutterNotifyAppIsPaused( + FlutterWindowControllerRef controller); + +FLUTTER_EXPORT void FlutterNotifyAppIsDetached( + FlutterWindowControllerRef controller); + +FLUTTER_EXPORT void FlutterRotateWindow(FlutterWindowControllerRef controller, + int32_t degree); + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif // PUBLIC_FLUTTER_TIZEN_EMBEDDER_H_ diff --git a/shell/platform/tizen/tizen_embedder_engine.cc b/shell/platform/tizen/tizen_embedder_engine.cc new file mode 100644 index 0000000000000..cebf3f6ae6b97 --- /dev/null +++ b/shell/platform/tizen/tizen_embedder_engine.cc @@ -0,0 +1,370 @@ +// Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved. +// Copyright 2013 The Flutter Authors. 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_embedder_engine.h" + +#include + +#include +#include +#include + +#include "flutter/shell/platform/tizen/logger.h" + +// Unique number associated with platform tasks. +static constexpr size_t kPlatformTaskRunnerIdentifier = 1; + +TizenEmbedderEngine::TizenEmbedderEngine( + const FlutterWindowProperties& window_properties) { + tizen_surface = std::make_unique( + window_properties.x, window_properties.y, window_properties.width, + window_properties.height); + + // Run flutter task on Tizen main loop. + // Tizen engine has four threads (GPU thread, UI thread, IO thread, platform + // thread). UI threads need to send flutter task to platform thread. + event_loop_ = std::make_unique( + std::this_thread::get_id(), // main thread + [this](const auto* task) { + if (FlutterEngineRunTask(this->flutter_engine, task) != kSuccess) { + LoggerE("Could not post an engine task."); + } + }); + + messenger = std::make_unique(); + messenger->engine = this; + message_dispatcher = + std::make_unique(messenger.get()); + + tizen_vsync_waiter_ = std::make_unique(); +} + +TizenEmbedderEngine::~TizenEmbedderEngine() {} + +// Attempts to load AOT data from the given path, which must be absolute and +// non-empty. Logs and returns nullptr on failure. +UniqueAotDataPtr LoadAotData(std::string aot_data_path) { + if (aot_data_path.empty()) { + LoggerE( + "Attempted to load AOT data, but no aot_library_path was provided."); + return nullptr; + } + if (!std::filesystem::exists(aot_data_path)) { + LoggerE("Can't load AOT data from %s; no such file.", + aot_data_path.c_str()); + return nullptr; + } + FlutterEngineAOTDataSource source = {}; + source.type = kFlutterEngineAOTDataSourceTypeElfPath; + source.elf_path = aot_data_path.c_str(); + FlutterEngineAOTData data = nullptr; + auto result = FlutterEngineCreateAOTData(&source, &data); + if (result != kSuccess) { + LoggerE("Failed to load AOT data from: %s", aot_data_path.c_str()); + return nullptr; + } + return UniqueAotDataPtr(data); +} + +bool TizenEmbedderEngine::RunEngine( + const FlutterEngineProperties& engine_properties) { + if (!tizen_surface->IsValid()) { + LoggerE("The display was not valid."); + return false; + } + + // FlutterProjectArgs is expecting a full argv, so when processing it for + // flags the first item is treated as the executable and ignored. Add a dummy + // value so that all provided arguments are used. + std::vector argv = {"placeholder"}; + if (engine_properties.switches_count > 0) { + argv.insert(argv.end(), &engine_properties.switches[0], + &engine_properties.switches[engine_properties.switches_count]); + } + + // Configure task runner interop. + FlutterTaskRunnerDescription platform_task_runner = {}; + platform_task_runner.struct_size = sizeof(FlutterTaskRunnerDescription); + platform_task_runner.user_data = event_loop_.get(); + platform_task_runner.runs_task_on_current_thread_callback = + [](void* data) -> bool { + return static_cast(data)->RunsTasksOnCurrentThread(); + }; + platform_task_runner.post_task_callback = + [](FlutterTask task, uint64_t target_time_nanos, void* data) -> void { + static_cast(data)->PostTask(task, target_time_nanos); + }; + platform_task_runner.identifier = kPlatformTaskRunnerIdentifier; + + FlutterCustomTaskRunners custom_task_runners = {}; + custom_task_runners.struct_size = sizeof(FlutterCustomTaskRunners); + custom_task_runners.platform_task_runner = &platform_task_runner; + + FlutterRendererConfig config = {}; + config.type = kOpenGL; + config.open_gl.struct_size = sizeof(config.open_gl); + config.open_gl.make_current = MakeContextCurrent; + config.open_gl.make_resource_current = MakeResourceCurrent; + config.open_gl.clear_current = ClearContext; + config.open_gl.present = Present; + config.open_gl.fbo_callback = GetActiveFbo; + config.open_gl.surface_transformation = Transformation; + config.open_gl.gl_proc_resolver = GlProcResolver; + config.open_gl.gl_external_texture_frame_callback = OnAcquireExternalTexture; + + FlutterProjectArgs args = {}; + args.struct_size = sizeof(FlutterProjectArgs); + args.assets_path = engine_properties.assets_path; + args.icu_data_path = engine_properties.icu_data_path; + args.command_line_argc = static_cast(argv.size()); + args.command_line_argv = &argv[0]; + args.platform_message_callback = OnFlutterPlatformMessage; + args.custom_task_runners = &custom_task_runners; + args.vsync_callback = OnVsyncCallback; + + if (FlutterEngineRunsAOTCompiledDartCode()) { + aot_data_ = LoadAotData(engine_properties.aot_library_path); + if (!aot_data_) { + LoggerE("Unable to start engine without AOT data."); + return false; + } + args.aot_data = aot_data_.get(); + } + + auto result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &config, &args, this, + &flutter_engine); + if (result == kSuccess && flutter_engine != nullptr) { + LoggerI("FlutterEngineRun Success!"); + } else { + LoggerE("FlutterEngineRun Failure! result: %d", result); + return false; + } + + tizen_vsync_waiter_->AsyncWaitForRunEngineSuccess(flutter_engine); + + std::unique_ptr textures = + std::make_unique(); + textures->flutter_engine = flutter_engine; + plugin_registrar_ = std::make_unique(); + plugin_registrar_->engine = this; + plugin_registrar_->texture_registrar = std::move(textures); + + internal_plugin_registrar_ = + std::make_unique(plugin_registrar_.get()); + + key_event_channel = std::make_unique( + internal_plugin_registrar_->messenger()); + navigation_channel = std::make_unique( + internal_plugin_registrar_->messenger()); + platform_channel = std::make_unique( + internal_plugin_registrar_->messenger()); + settings_channel = std::make_unique( + internal_plugin_registrar_->messenger()); + text_input_channel = std::make_unique( + internal_plugin_registrar_->messenger(), + ((TizenSurfaceGL*)tizen_surface.get())->wl2_window()); + localization_channel = std::make_unique(flutter_engine); + localization_channel->SendLocales(); + lifecycle_channel = std::make_unique(flutter_engine); + platform_view_channel = std::make_unique( + internal_plugin_registrar_->messenger()); + + key_event_handler_ = std::make_unique(this); + touch_event_handler_ = std::make_unique(this); + + SetWindowOrientation(0); + + return true; +} + +bool TizenEmbedderEngine::StopEngine() { + if (flutter_engine) { + if (plugin_registrar_destruction_callback_) { + plugin_registrar_destruction_callback_(plugin_registrar_.get()); + } + FlutterEngineResult result = FlutterEngineShutdown(flutter_engine); + flutter_engine = nullptr; + return (result == kSuccess); + } + return false; +} + +FlutterDesktopPluginRegistrarRef TizenEmbedderEngine::GetPluginRegistrar() { + return plugin_registrar_.get(); +} + +void TizenEmbedderEngine::SetPluginRegistrarDestructionCallback( + FlutterDesktopOnPluginRegistrarDestroyed callback) { + plugin_registrar_destruction_callback_ = callback; +} + +bool TizenEmbedderEngine::OnAcquireExternalTexture( + void* user_data, int64_t texture_id, size_t width, size_t height, + FlutterOpenGLTexture* texture) { + TizenEmbedderEngine* tizen_embedder_engine = + reinterpret_cast(user_data); + return tizen_embedder_engine->plugin_registrar_->texture_registrar + ->textures[texture_id] + ->PopulateTextureWithIdentifier(width, height, texture); +} + +std::string GetDeviceProfile() { + char* feature_profile; + system_info_get_platform_string("http://tizen.org/feature/profile", + &feature_profile); + std::string profile(feature_profile); + free(feature_profile); + return profile; +} + +double GetDeviceDpi() { + int feature_dpi; + system_info_get_platform_int("http://tizen.org/feature/screen.dpi", + &feature_dpi); + return (double)feature_dpi; +} + +void TizenEmbedderEngine::SendWindowMetrics(int32_t width, int32_t height, + double pixel_ratio) { + FlutterWindowMetricsEvent event; + event.struct_size = sizeof(FlutterWindowMetricsEvent); + event.width = width; + event.height = height; + if (pixel_ratio == 0.0) { + // The scale factor is computed based on the display DPI and the current + // profile. A fixed DPI value (72) is used on TVs. See: + // https://docs.tizen.org/application/native/guides/ui/efl/multiple-screens + std::string profile = GetDeviceProfile(); + double profile_factor = 1.0; + if (profile == "wearable") { + profile_factor = 0.4; + } else if (profile == "mobile") { + profile_factor = 0.7; + } else if (profile == "tv") { + profile_factor = 2.0; + } + double dpi = profile == "tv" ? 72.0 : GetDeviceDpi(); + double scale_factor = dpi / 90.0 * profile_factor; + event.pixel_ratio = std::max(scale_factor, 1.0); + } else { + event.pixel_ratio = pixel_ratio; + } + FlutterEngineSendWindowMetricsEvent(flutter_engine, &event); +} + +// This must be called at least once in order to initialize the value of +// transformation_. +void TizenEmbedderEngine::SetWindowOrientation(int32_t degree) { + if (!tizen_surface) { + return; + } + + // Compute renderer transformation based on the angle of rotation. + double rad = (360 - degree) * M_PI / 180; + double width = tizen_surface->GetWidth(); + double height = tizen_surface->GetHeight(); + double trans_x = 0.0, trans_y = 0.0; + if (degree == 90) { + trans_y = height; + } else if (degree == 180) { + trans_x = width; + trans_y = height; + } else if (degree == 270) { + trans_x = width; + } + transformation_ = { + cos(rad), -sin(rad), trans_x, // x + sin(rad), cos(rad), trans_y, // y + 0.0, 0.0, 1.0 // perspective + }; + touch_event_handler_->rotation = degree; + + if (degree == 90 || degree == 270) { + SendWindowMetrics(height, width, 0.0); + } else { + SendWindowMetrics(width, height, 0.0); + } +} + +void TizenEmbedderEngine::SendLocales() { localization_channel->SendLocales(); } + +void TizenEmbedderEngine::AppIsInactive() { + lifecycle_channel->AppIsInactive(); +} + +void TizenEmbedderEngine::AppIsResumed() { lifecycle_channel->AppIsResumed(); } + +void TizenEmbedderEngine::AppIsPaused() { lifecycle_channel->AppIsPaused(); } + +void TizenEmbedderEngine::AppIsDetached() { + lifecycle_channel->AppIsDetached(); +} + +void TizenEmbedderEngine::OnFlutterPlatformMessage( + const FlutterPlatformMessage* engine_message, void* user_data) { + if (engine_message->struct_size != sizeof(FlutterPlatformMessage)) { + LoggerE("Invalid message size received. Expected: %zu, received %zu", + sizeof(FlutterPlatformMessage), engine_message->struct_size); + return; + } + LoggerD("%s", engine_message->channel); + TizenEmbedderEngine* tizen_embedder_engine = + reinterpret_cast(user_data); + auto message = + tizen_embedder_engine->ConvertToDesktopMessage(*engine_message); + tizen_embedder_engine->message_dispatcher->HandleMessage(message); +} + +void TizenEmbedderEngine::OnVsyncCallback(void* user_data, intptr_t baton) { + TizenEmbedderEngine* tizen_embedder_engine = + reinterpret_cast(user_data); + tizen_embedder_engine->tizen_vsync_waiter_->AsyncWaitForVsync(baton); +} + +// Converts a FlutterPlatformMessage to an equivalent FlutterDesktopMessage. +FlutterDesktopMessage TizenEmbedderEngine::ConvertToDesktopMessage( + const FlutterPlatformMessage& engine_message) { + FlutterDesktopMessage message = {}; + message.struct_size = sizeof(message); + message.channel = engine_message.channel; + message.message = engine_message.message; + message.message_size = engine_message.message_size; + message.response_handle = engine_message.response_handle; + return message; +} + +bool TizenEmbedderEngine::MakeContextCurrent(void* user_data) { + return reinterpret_cast(user_data) + ->tizen_surface->OnMakeCurrent(); +} + +bool TizenEmbedderEngine::ClearContext(void* user_data) { + return reinterpret_cast(user_data) + ->tizen_surface->OnClearCurrent(); +} + +bool TizenEmbedderEngine::Present(void* user_data) { + return reinterpret_cast(user_data) + ->tizen_surface->OnPresent(); +} + +bool TizenEmbedderEngine::MakeResourceCurrent(void* user_data) { + return reinterpret_cast(user_data) + ->tizen_surface->OnMakeResourceCurrent(); +} + +uint32_t TizenEmbedderEngine::GetActiveFbo(void* user_data) { + return reinterpret_cast(user_data) + ->tizen_surface->OnGetFBO(); +} + +FlutterTransformation TizenEmbedderEngine::Transformation(void* user_data) { + return reinterpret_cast(user_data)->transformation_; +} + +void* TizenEmbedderEngine::GlProcResolver(void* user_data, const char* name) { + return reinterpret_cast(user_data) + ->tizen_surface->OnProcResolver(name); +} diff --git a/shell/platform/tizen/tizen_embedder_engine.h b/shell/platform/tizen/tizen_embedder_engine.h new file mode 100644 index 0000000000000..d7156d6074c34 --- /dev/null +++ b/shell/platform/tizen/tizen_embedder_engine.h @@ -0,0 +1,154 @@ +// Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved. +// Copyright 2013 The Flutter Authors. 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_EMBEDDER_ENGINE_H_ +#define EMBEDDER_TIZEN_EMBEDDER_ENGINE_H_ + +#include + +#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/plugin_registrar.h" +#include "flutter/shell/platform/common/cpp/incoming_message_dispatcher.h" +#include "flutter/shell/platform/tizen/channels/key_event_channel.h" +#include "flutter/shell/platform/tizen/channels/lifecycle_channel.h" +#include "flutter/shell/platform/tizen/channels/localization_channel.h" +#include "flutter/shell/platform/tizen/channels/navigation_channel.h" +#include "flutter/shell/platform/tizen/channels/platform_channel.h" +#include "flutter/shell/platform/tizen/channels/platform_view_channel.h" +#include "flutter/shell/platform/tizen/channels/settings_channel.h" +#include "flutter/shell/platform/tizen/channels/text_input_channel.h" +#include "flutter/shell/platform/tizen/external_texture_gl.h" +#include "flutter/shell/platform/tizen/key_event_handler.h" +#include "flutter/shell/platform/tizen/public/flutter_texture_registrar.h" +#include "flutter/shell/platform/tizen/public/flutter_tizen.h" +#include "flutter/shell/platform/tizen/tizen_event_loop.h" +#include "flutter/shell/platform/tizen/tizen_surface.h" +#include "flutter/shell/platform/tizen/tizen_surface_gl.h" +#include "flutter/shell/platform/tizen/tizen_vsync_waiter.h" +#include "flutter/shell/platform/tizen/touch_event_handler.h" + +// State associated with the plugin registrar. +struct FlutterDesktopPluginRegistrar { + // The engine that owns this state object. + TizenEmbedderEngine* engine; + + // The plugin texture registrar handle given to API clients. + std::unique_ptr texture_registrar; +}; + +// State associated with the messenger used to communicate with the engine. +struct FlutterDesktopMessenger { + // The engine that owns this state object. + TizenEmbedderEngine* engine = nullptr; +}; + +// Custom deleter for FlutterEngineAOTData. +struct AOTDataDeleter { + void operator()(FlutterEngineAOTData aot_data) { + FlutterEngineCollectAOTData(aot_data); + } +}; + +// State associated with the texture registrar. +struct FlutterTextureRegistrar { + FLUTTER_API_SYMBOL(FlutterEngine) flutter_engine; + + // The texture registrar managing external texture adapters. + std::map> textures; +}; + +using UniqueAotDataPtr = std::unique_ptr<_FlutterEngineAOTData, AOTDataDeleter>; + +// Manages state associated with the underlying FlutterEngine. +class TizenEmbedderEngine { + public: + explicit TizenEmbedderEngine( + const FlutterWindowProperties& window_properties); + virtual ~TizenEmbedderEngine(); + bool RunEngine(const FlutterEngineProperties& engine_properties); + bool StopEngine(); + + // Returns the currently configured Plugin Registrar. + FlutterDesktopPluginRegistrarRef GetPluginRegistrar(); + + // Sets |callback| to be called when the plugin registrar is destroyed. + void SetPluginRegistrarDestructionCallback( + FlutterDesktopOnPluginRegistrarDestroyed callback); + + void SetWindowOrientation(int32_t degree); + void SendLocales(); + void AppIsInactive(); + void AppIsResumed(); + void AppIsPaused(); + void AppIsDetached(); + + // The Flutter engine instance. + FLUTTER_API_SYMBOL(FlutterEngine) flutter_engine; + + // The plugin messenger handle given to API clients. + std::unique_ptr messenger; + + // Message dispatch manager for messages from the Flutter engine. + std::unique_ptr message_dispatcher; + + // The interface between the Flutter rasterizer and the platform. + std::unique_ptr tizen_surface; + + // The system channels for communicating between Flutter and the platform. + std::unique_ptr key_event_channel; + std::unique_ptr lifecycle_channel; + std::unique_ptr localization_channel; + std::unique_ptr navigation_channel; + std::unique_ptr platform_channel; + std::unique_ptr settings_channel; + std::unique_ptr text_input_channel; + std::unique_ptr platform_view_channel; + + private: + static bool MakeContextCurrent(void* user_data); + static bool ClearContext(void* user_data); + static bool Present(void* user_data); + static bool MakeResourceCurrent(void* user_data); + static uint32_t GetActiveFbo(void* user_data); + static FlutterTransformation Transformation(void* user_data); + static void* GlProcResolver(void* user_data, const char* name); + static void OnFlutterPlatformMessage( + const FlutterPlatformMessage* engine_message, void* user_data); + static void OnVsyncCallback(void* user_data, intptr_t baton); + + void SendWindowMetrics(int32_t width, int32_t height, double pixel_ratio); + FlutterDesktopMessage ConvertToDesktopMessage( + const FlutterPlatformMessage& engine_message); + static bool OnAcquireExternalTexture(void* user_data, int64_t texture_id, + size_t width, size_t height, + FlutterOpenGLTexture* texture); + + // The handlers listening to platform events. + std::unique_ptr key_event_handler_; + std::unique_ptr touch_event_handler_; + + // The plugin registrar handle given to API clients. + std::unique_ptr plugin_registrar_; + + // A callback to be called when the engine (and thus the plugin registrar) + // is being destroyed. + FlutterDesktopOnPluginRegistrarDestroyed + plugin_registrar_destruction_callback_; + + // The plugin registrar managing internal plugins. + std::unique_ptr internal_plugin_registrar_; + + // The event loop for the main thread that allows for delayed task execution. + std::unique_ptr event_loop_; + + // The vsync waiter for the embedder. + std::unique_ptr tizen_vsync_waiter_; + + // AOT data for this engine instance, if applicable. + UniqueAotDataPtr aot_data_; + + // The current renderer transformation. + FlutterTransformation transformation_; +}; +#endif // EMBEDDER_TIZEN_EMBEDDER_ENGINE_H_ diff --git a/shell/platform/tizen/tizen_event_loop.cc b/shell/platform/tizen/tizen_event_loop.cc new file mode 100644 index 0000000000000..876818f05815d --- /dev/null +++ b/shell/platform/tizen/tizen_event_loop.cc @@ -0,0 +1,92 @@ +// Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved. +// Copyright 2013 The Flutter Authors. 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_event_loop.h" + +#include +#include + +TizenEventLoop::TizenEventLoop(std::thread::id main_thread_id, + TaskExpiredCallback on_task_expired) + : main_thread_id_(main_thread_id), + on_task_expired_(std::move(on_task_expired)) { + ecore_pipe_ = ecore_pipe_add(ExcuteTaskEvents, this); +} + +TizenEventLoop::~TizenEventLoop() { + if (ecore_pipe_) { + ecore_pipe_del(ecore_pipe_); + } +} + +bool TizenEventLoop::RunsTasksOnCurrentThread() const { + return std::this_thread::get_id() == main_thread_id_; +} + +void TizenEventLoop::ExcuteTaskEvents(std::chrono::nanoseconds max_wait) { + const auto now = TaskTimePoint::clock::now(); + std::vector expired_tasks; + { + std::lock_guard lock(task_queue_mutex_); + while (!task_queue_.empty()) { + const auto& top = task_queue_.top(); + + if (top.fire_time > now) { + break; + } + expired_tasks.push_back(task_queue_.top().task); + task_queue_.pop(); + } + } + for (const auto& task : expired_tasks) { + on_task_expired_(&task); + } +} + +TizenEventLoop::TaskTimePoint TizenEventLoop::TimePointFromFlutterTime( + uint64_t flutter_target_time_nanos) { + const auto now = TaskTimePoint::clock::now(); + const int64_t flutter_duration = + flutter_target_time_nanos - FlutterEngineGetCurrentTime(); + return now + std::chrono::nanoseconds(flutter_duration); +} + +void TizenEventLoop::PostTask(FlutterTask flutter_task, + uint64_t flutter_target_time_nanos) { + static std::atomic sGlobalTaskOrder(0); + Task task; + task.order = ++sGlobalTaskOrder; + task.fire_time = TimePointFromFlutterTime(flutter_target_time_nanos); + task.task = flutter_task; + if (ecore_pipe_) { + ecore_pipe_write(ecore_pipe_, &task, sizeof(task)); + } +} + +Eina_Bool TizenEventLoop::TaskTimerCallback(void* data) { + TizenEventLoop* tizenEventLoop = reinterpret_cast(data); + tizenEventLoop->ExcuteTaskEvents(); + return EINA_FALSE; +} + +void TizenEventLoop::ExcuteTaskEvents(void* data, void* buffer, + unsigned int nbyte) { + TizenEventLoop* tizenEventLoop = reinterpret_cast(data); + Task* p_task = reinterpret_cast(buffer); + + const double flutter_duration = + ((double)(p_task->fire_time.time_since_epoch().count()) - + FlutterEngineGetCurrentTime()) / + 1000000000.0; + if (flutter_duration > 0) { + { + std::lock_guard lock(tizenEventLoop->task_queue_mutex_); + tizenEventLoop->task_queue_.push(*p_task); + } + ecore_timer_add(flutter_duration, TaskTimerCallback, tizenEventLoop); + } else { + tizenEventLoop->on_task_expired_(&(p_task->task)); + } +} diff --git a/shell/platform/tizen/tizen_event_loop.h b/shell/platform/tizen/tizen_event_loop.h new file mode 100644 index 0000000000000..56b41fae27e34 --- /dev/null +++ b/shell/platform/tizen/tizen_event_loop.h @@ -0,0 +1,69 @@ +// Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved. +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef TIZEN_EVENT_LOOP_H_ +#define TIZEN_EVENT_LOOP_H_ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "flutter/shell/platform/embedder/embedder.h" + +class TizenEventLoop { + public: + using TaskExpiredCallback = std::function; + TizenEventLoop(std::thread::id main_thread_id, + TaskExpiredCallback on_task_expired); + ~TizenEventLoop(); + bool RunsTasksOnCurrentThread() const; + + void ExcuteTaskEvents( + std::chrono::nanoseconds max_wait = std::chrono::nanoseconds::max()); + + // Post a Flutter engine tasks to the event loop for delayed execution. + void PostTask(FlutterTask flutter_task, uint64_t flutter_target_time_nanos); + + private: + using TaskTimePoint = std::chrono::steady_clock::time_point; + struct Task { + 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; + } + }; + }; + std::thread::id main_thread_id_; + TaskExpiredCallback on_task_expired_; + std::mutex task_queue_mutex_; + std::priority_queue, Task::Comparer> task_queue_; + std::condition_variable task_queue_cv_; + Ecore_Pipe* ecore_pipe_; + + TizenEventLoop(const TizenEventLoop&) = delete; + + TizenEventLoop& operator=(const TizenEventLoop&) = delete; + + static void ExcuteTaskEvents(void* data, void* buffer, unsigned int nbyte); + static Eina_Bool TaskTimerCallback(void* data); + + static TaskTimePoint TimePointFromFlutterTime( + uint64_t flutter_target_time_nanos); +}; + +#endif // TIZEN_EVENT_LOOP_H_ diff --git a/shell/platform/tizen/tizen_surface.cc b/shell/platform/tizen/tizen_surface.cc new file mode 100644 index 0000000000000..d841054efdb07 --- /dev/null +++ b/shell/platform/tizen/tizen_surface.cc @@ -0,0 +1,14 @@ +// Copyright 2020 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_surface.h" + +TizenSurface::TizenSurface(int32_t x, int32_t y, int32_t width, int32_t height) + : window_width_(width), window_height_(height), x_(x), y_(y) {} + +TizenSurface::~TizenSurface() {} + +uint32_t TizenSurface::GetWidth() { return window_width_; } + +uint32_t TizenSurface::GetHeight() { return window_height_; } diff --git a/shell/platform/tizen/tizen_surface.h b/shell/platform/tizen/tizen_surface.h new file mode 100644 index 0000000000000..736eb5b593de4 --- /dev/null +++ b/shell/platform/tizen/tizen_surface.h @@ -0,0 +1,31 @@ +// Copyright 2020 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_SURFACE_H_ +#define EMBEDDER_TIZEN_SURFACE_H_ + +#include + +class TizenSurface { + public: + TizenSurface(int32_t x, int32_t y, int32_t width, int32_t height); + virtual ~TizenSurface(); + virtual bool OnMakeCurrent() = 0; + virtual bool OnClearCurrent() = 0; + virtual bool OnMakeResourceCurrent() = 0; + virtual bool OnPresent() = 0; + virtual uint32_t OnGetFBO() = 0; + virtual void* OnProcResolver(const char* name) = 0; + virtual bool IsValid() = 0; + uint32_t GetWidth(); + uint32_t GetHeight(); + + protected: + const int32_t window_width_; + const int32_t window_height_; + int32_t x_; + int32_t y_; +}; + +#endif // EMBEDDER_TIZEN_SURFACE_H_ diff --git a/shell/platform/tizen/tizen_surface_gl.cc b/shell/platform/tizen/tizen_surface_gl.cc new file mode 100644 index 0000000000000..40cc9852f80a1 --- /dev/null +++ b/shell/platform/tizen/tizen_surface_gl.cc @@ -0,0 +1,247 @@ +// Copyright 2020 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_surface_gl.h" + +#include "flutter/shell/platform/tizen/logger.h" + +TizenSurfaceGL::TizenSurfaceGL(int32_t x, int32_t y, int32_t width, + int32_t height) + : TizenSurface(x, y, width, height) { + LoggerD("x =[%d] y = [%d], width = [%d], height = [%d]", x, y, width, height); + InitalizeDisplay(); +} + +bool TizenSurfaceGL::IsValid() { return display_valid_; } + +bool TizenSurfaceGL::OnMakeCurrent() { + if (!display_valid_) { + LoggerE("Invalid Display"); + return false; + } + if (eglMakeCurrent(egl_display_, egl_surface_, egl_surface_, egl_context_) != + EGL_TRUE) { + LoggerE("Could not make the onscreen context current"); + return false; + } + return true; +} + +bool TizenSurfaceGL::OnMakeResourceCurrent() { + if (!display_valid_) { + LoggerE("Invalid Display"); + return false; + } + if (eglMakeCurrent(egl_display_, EGL_NO_SURFACE, EGL_NO_SURFACE, + egl_resource_context_) != EGL_TRUE) { + LoggerE("Could not make the onscreen context current"); + return false; + } + return true; +} + +bool TizenSurfaceGL::OnClearCurrent() { + if (!display_valid_) { + LoggerE("Invalid display"); + return false; + } + + if (eglMakeCurrent(egl_display_, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT) != EGL_TRUE) { + LoggerE("Could not clear context"); + return false; + } + return true; +} + +bool TizenSurfaceGL::OnPresent() { + if (!display_valid_) { + LoggerE("Invalid display"); + return false; + } + + if (eglSwapBuffers(egl_display_, egl_surface_) != EGL_TRUE) { + LoggerE("Could not swap EGl buffer"); + return false; + } + return true; +} + +uint32_t TizenSurfaceGL::OnGetFBO() { + if (!display_valid_) { + LoggerE("Invalid display"); + return 999; + } + LoggerD("OnApplicationGetOnscreenFBO"); + return 0; // FBO0 +} + +void* TizenSurfaceGL::OnProcResolver(const char* name) { + auto address = eglGetProcAddress(name); + if (address != nullptr) { + return reinterpret_cast(address); + } + LoggerW("Could not resolve: %s", name); + return nullptr; +} + +TizenSurfaceGL::~TizenSurfaceGL() { + if (IsValid()) { + display_valid_ = false; + Destroy(); + } +} + +bool TizenSurfaceGL::InitalizeDisplay() { + LoggerD("x =[%d] y = [%d], width = [%d], height = [%d]", x_, y_, + window_width_, window_height_); + if (window_width_ == 0 || window_height_ == 0) { + return false; + } + + // ecore_wl2 INIT + if (!ecore_wl2_init()) { + LoggerE("Could not initialize ecore_wl2"); + return false; + } + + // ecore_wl2 DISPLAY + wl2_display_ = ecore_wl2_display_connect(nullptr); + if (nullptr == wl2_display_) { + LoggerE("Display not found"); + return false; + } + LoggerD("wl2_display_: %p", wl2_display_); + ecore_wl2_sync(); + + // ecore_wl2 WINDOW + wl2_window_ = ecore_wl2_window_new(wl2_display_, nullptr, x_, y_, + window_width_, window_height_); + LoggerD("wl2_window_: %p", wl2_window_); + + ecore_wl2_window_type_set(wl2_window_, ECORE_WL2_WINDOW_TYPE_TOPLEVEL); + ecore_wl2_window_alpha_set(wl2_window_, EINA_FALSE); + // ecore_wl2_sync(); + + // ecore_wl2 SHOW + ecore_wl2_window_show(wl2_window_); + + ecore_wl2_window_position_set(wl2_window_, x_, y_); + ecore_wl2_window_geometry_set(wl2_window_, x_, y_, window_width_, + window_height_); + + // egl WINDOW + egl_window_ = + ecore_wl2_egl_window_create(wl2_window_, window_width_, window_height_); + LoggerD("egl_window_: %p", egl_window_); + + if (!egl_window_) { + LoggerE("Could not create EGL window"); + return false; + } + + // egl DISPLAY + egl_display_ = + eglGetDisplay((EGLNativeDisplayType)ecore_wl2_display_get(wl2_display_)); + if (egl_display_ == EGL_NO_DISPLAY) { + LoggerE("Could not access EGL display"); + return false; + } + LoggerD("egl_display_: %p", egl_display_); + + // egl INTIALIZE + EGLint majorVersion; + EGLint minorVersion; + if (eglInitialize(egl_display_, &majorVersion, &minorVersion) != EGL_TRUE) { + LoggerE("Could not initialize EGL display"); + return false; + } + LoggerD("eglInitialized: %d.%d", majorVersion, minorVersion); + + // egl BINDAPI + if (eglBindAPI(EGL_OPENGL_ES_API) != EGL_TRUE) { + LoggerE("Could not bind API"); + return false; + } + + // egl CONFIG + EGLint config_count; + eglGetConfigs(egl_display_, NULL, 0, &config_count); + LoggerD("config_count: %d", config_count); + + EGLConfig egl_config = nullptr; + + { + EGLint attribs[] = { + // clang-format off + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 0, + EGL_STENCIL_SIZE, 0, + EGL_NONE, // termination sentinel + // clang-format on + }; + + if (eglChooseConfig(egl_display_, attribs, &egl_config, 1, &config_count) != + EGL_TRUE) { + LoggerE("Error when attempting to choose an EGL surface config"); + return false; + } + + if (config_count == 0 || egl_config == nullptr) { + LoggerE("No matching config"); + return false; + } + } + + // egl CONTEXT + const EGLint attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; + + egl_context_ = eglCreateContext(egl_display_, egl_config, nullptr, attribs); + if (egl_context_ == EGL_NO_CONTEXT) { + LoggerE("Could not create an onscreen context"); + return false; + } + + egl_resource_context_ = + eglCreateContext(egl_display_, egl_config, egl_context_, attribs); + if (egl_resource_context_ == EGL_NO_CONTEXT) { + LoggerE("Could not create an resource context"); + return false; + } + + // egl SURFACE + { + egl_surface_ = eglCreateWindowSurface( + egl_display_, egl_config, ecore_wl2_egl_window_native_get(egl_window_), + nullptr); + + if (egl_surface_ == EGL_NO_SURFACE) { + return false; + } + } + LoggerD("egl_surface_: %p", egl_surface_); + display_valid_ = true; + return true; +} + +void TizenSurfaceGL::Destroy() { + if (egl_window_) { + ecore_wl2_egl_window_destroy(egl_window_); + egl_window_ = nullptr; + } + if (wl2_window_) { + ecore_wl2_window_free(wl2_window_); + wl2_window_ = nullptr; + } + if (wl2_display_) { + ecore_wl2_display_destroy(wl2_display_); + wl2_display_ = nullptr; + } + ecore_wl2_shutdown(); +} diff --git a/shell/platform/tizen/tizen_surface_gl.h b/shell/platform/tizen/tizen_surface_gl.h new file mode 100644 index 0000000000000..9fb35d308212f --- /dev/null +++ b/shell/platform/tizen/tizen_surface_gl.h @@ -0,0 +1,49 @@ +// Copyright 2020 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_SURFACE_GL_H_ +#define EMBEDDER_TIZEN_SURFACE_GL_H_ + +#include +#include +#include +#include + +#include +#include +#include +#define EFL_BETA_API_SUPPORT +#include +#include + +#include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/tizen/tizen_surface.h" + +class TizenSurfaceGL : public TizenSurface { + public: + TizenSurfaceGL(int32_t x, int32_t y, int32_t width, int32_t height); + ~TizenSurfaceGL(); + bool OnMakeCurrent(); + bool OnClearCurrent(); + bool OnMakeResourceCurrent(); + bool OnPresent(); + uint32_t OnGetFBO(); + void* OnProcResolver(const char* name); + bool IsValid(); + bool InitalizeDisplay(); + void Destroy(); + Ecore_Wl2_Window* wl2_window() { return wl2_window_; } + + private: + bool display_valid_ = false; + EGLDisplay egl_display_ = EGL_NO_DISPLAY; + EGLSurface egl_surface_ = nullptr; + EGLContext egl_context_ = EGL_NO_CONTEXT; + EGLContext egl_resource_context_ = EGL_NO_CONTEXT; + Ecore_Wl2_Egl_Window* egl_window_ = nullptr; + Ecore_Wl2_Display* wl2_display_ = nullptr; + Ecore_Wl2_Window* wl2_window_ = nullptr; +}; + +#endif // EMBEDDER_TIZEN_SURFACE_GL_H_ diff --git a/shell/platform/tizen/tizen_vsync_waiter.cc b/shell/platform/tizen/tizen_vsync_waiter.cc new file mode 100644 index 0000000000000..01bbda4c341e6 --- /dev/null +++ b/shell/platform/tizen/tizen_vsync_waiter.cc @@ -0,0 +1,145 @@ +// Copyright 2020 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_vsync_waiter.h" + +#include "flutter/shell/platform/tizen/logger.h" + +TizenVsyncWaiter::TizenVsyncWaiter() + : client_(NULL), + output_(NULL), + vblank_(NULL), + flutter_engine_(nullptr), + baton_(0), + vblank_ecore_pipe_(NULL) { + if (CreateTDMVblank()) { + std::thread t(CreateVblankEventLoop, this); + t.join(); + } else { + LoggerE("CreateVsyncVaiter fail"); + } +} + +void TizenVsyncWaiter::CreateVblankEventLoop(void* data) { + TizenVsyncWaiter* tizen_vsync_waiter = + reinterpret_cast(data); + if (!ecore_init()) { + LoggerE("ERROR: Cannot init Ecore!"); + return; + } + tizen_vsync_waiter->vblank_ecore_pipe_ = + ecore_pipe_add(VblankEventLoopCallback, tizen_vsync_waiter); + LoggerD("ecore_init successful"); + ecore_main_loop_begin(); + ecore_shutdown(); +} + +void TizenVsyncWaiter::VblankEventLoopCallback(void* data, void* buffer, + unsigned int nbyte) { + TizenVsyncWaiter* tizen_vsync_waiter = + reinterpret_cast(data); + int* event_type = reinterpret_cast(buffer); + if ((*event_type) == VBLANK_LOOP_REQUEST) { + tizen_vsync_waiter->AsyncWaitForVsyncCallback(); + } else if ((*event_type) == VBLANK_LOOP_DEL_PIPE) { + tizen_vsync_waiter->DeleteVblankEventPipe(); + } +} + +void TizenVsyncWaiter::AsyncWaitForVsyncCallback() { + tdm_error ret; + ret = tdm_client_vblank_wait(vblank_, 1, TdmClientVblankCallback, this); + if (ret != TDM_ERROR_NONE) { + LoggerE("ERROR, ret = %d", ret); + return; + } + tdm_client_handle_events(client_); +} + +void TizenVsyncWaiter::DeleteVblankEventPipe() { + if (vblank_ecore_pipe_) { + ecore_pipe_del(vblank_ecore_pipe_); + vblank_ecore_pipe_ = NULL; + } + ecore_main_loop_quit(); +} + +bool TizenVsyncWaiter::CreateTDMVblank() { + tdm_error ret; + client_ = tdm_client_create(&ret); + if (ret != TDM_ERROR_NONE && client_ != NULL) { + LoggerE("create client fail"); + return false; + } + + output_ = tdm_client_get_output(client_, const_cast("default"), &ret); + if (ret != TDM_ERROR_NONE && output_ != NULL) { + LoggerE("get output fail"); + return false; + } + + vblank_ = tdm_client_output_create_vblank(output_, &ret); + if (ret != TDM_ERROR_NONE && vblank_ != NULL) { + LoggerE("create vblank fail"); + return false; + } + + return true; +} + +void TizenVsyncWaiter::TdmClientVblankCallback( + tdm_client_vblank* vblank, tdm_error error, unsigned int sequence, + unsigned int tv_sec, unsigned int tv_usec, void* user_data) { + TizenVsyncWaiter* tizen_vsync_waiter = + reinterpret_cast(user_data); + if (tizen_vsync_waiter == nullptr) { + LoggerE("tizen_vsync_waiter is null"); + return; + } + if (tizen_vsync_waiter->flutter_engine_ == nullptr) { + LoggerI("flutter engine creation is not completed"); + return; + } + uint64_t frame_start_time_nanos = tv_sec * 1e9 + tv_usec * 1e3; + uint64_t frame_target_time_nanos = 16.6 * 1e6 + frame_start_time_nanos; + FlutterEngineOnVsync(tizen_vsync_waiter->flutter_engine_, + tizen_vsync_waiter->baton_, frame_start_time_nanos, + frame_target_time_nanos); +} + +bool TizenVsyncWaiter::AsyncWaitForVsync() { + if (vblank_ecore_pipe_) { + int event_type = VBLANK_LOOP_REQUEST; + ecore_pipe_write(vblank_ecore_pipe_, &event_type, sizeof(event_type)); + } + return true; +} + +TizenVsyncWaiter::~TizenVsyncWaiter() { + if (vblank_ecore_pipe_) { + int event_type = VBLANK_LOOP_DEL_PIPE; + ecore_pipe_write(vblank_ecore_pipe_, &event_type, sizeof(event_type)); + } + if (vblank_) { + tdm_client_vblank_destroy(vblank_); + } + if (client_) { + tdm_client_destroy(client_); + } +} + +void TizenVsyncWaiter::AsyncWaitForVsync(intptr_t baton) { + baton_ = baton; + AsyncWaitForVsync(); +} + +void TizenVsyncWaiter::AsyncWaitForRunEngineSuccess( + FLUTTER_API_SYMBOL(FlutterEngine) flutter_engine) { + flutter_engine_ = flutter_engine; + if (baton_ == 0) { + LoggerD("baton_ == 0"); + return; + } + AsyncWaitForVsync(); +} diff --git a/shell/platform/tizen/tizen_vsync_waiter.h b/shell/platform/tizen/tizen_vsync_waiter.h new file mode 100644 index 0000000000000..b7bb977fdd477 --- /dev/null +++ b/shell/platform/tizen/tizen_vsync_waiter.h @@ -0,0 +1,45 @@ +// Copyright 2020 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_VSYNC_WAITER_H_ +#define EMBEDDER_TIZEN_VSYNC_WAITER_H_ + +#include +#include + +#include + +#include "flutter/shell/platform/embedder/embedder.h" + +class TizenVsyncWaiter { + public: + TizenVsyncWaiter(); + virtual ~TizenVsyncWaiter(); + bool CreateTDMVblank(); + bool AsyncWaitForVsync(); + void AsyncWaitForVsync(intptr_t baton); + void AsyncWaitForRunEngineSuccess(FLUTTER_API_SYMBOL(FlutterEngine) + flutter_engine); + + private: + static const int VBLANK_LOOP_REQUEST = 1; + static const int VBLANK_LOOP_DEL_PIPE = 2; + void AsyncWaitForVsyncCallback(); + void DeleteVblankEventPipe(); + static void CreateVblankEventLoop(void* data); + static void TdmClientVblankCallback(tdm_client_vblank* vblank, + tdm_error error, unsigned int sequence, + unsigned int tv_sec, unsigned int tv_usec, + void* user_data); + static void VblankEventLoopCallback(void* data, void* buffer, + unsigned int nbyte); + tdm_client* client_; + tdm_client_output* output_; + tdm_client_vblank* vblank_; + FLUTTER_API_SYMBOL(FlutterEngine) flutter_engine_; + intptr_t baton_; + Ecore_Pipe* vblank_ecore_pipe_; +}; + +#endif // EMBEDDER_TIZEN_VSYNC_WAITER_H_ diff --git a/shell/platform/tizen/touch_event_handler.cc b/shell/platform/tizen/touch_event_handler.cc new file mode 100644 index 0000000000000..44bf9311aac24 --- /dev/null +++ b/shell/platform/tizen/touch_event_handler.cc @@ -0,0 +1,87 @@ +// Copyright 2020 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 "touch_event_handler.h" + +#include "flutter/shell/platform/tizen/logger.h" +#include "flutter/shell/platform/tizen/tizen_embedder_engine.h" + +TouchEventHandler::TouchEventHandler(TizenEmbedderEngine *engine) + : engine_(engine) { + touch_event_handlers_.push_back( + ecore_event_handler_add(ECORE_EVENT_MOUSE_BUTTON_DOWN, OnTouch, this)); + touch_event_handlers_.push_back( + ecore_event_handler_add(ECORE_EVENT_MOUSE_BUTTON_UP, OnTouch, this)); + touch_event_handlers_.push_back( + ecore_event_handler_add(ECORE_EVENT_MOUSE_MOVE, OnTouch, this)); + touch_event_handlers_.push_back(ecore_event_handler_add( + ECORE_WL2_EVENT_WINDOW_VISIBILITY_CHANGE, OnTouch, this)); +} + +TouchEventHandler::~TouchEventHandler() { + for (auto handler : touch_event_handlers_) { + ecore_event_handler_del(handler); + } + touch_event_handlers_.clear(); +} + +void TouchEventHandler::SendFlutterPointerEvent(FlutterPointerPhase phase, + double x, double y, + size_t timestamp) { + if (!engine_->flutter_engine) { + return; + } + + // Correct errors caused by window rotation. + double width = engine_->tizen_surface->GetWidth(); + double height = engine_->tizen_surface->GetHeight(); + double new_x = x, new_y = y; + if (rotation == 90) { + new_x = height - y; + new_y = x; + } else if (rotation == 180) { + new_x = width - x; + new_y = height - y; + } else if (rotation == 270) { + new_x = y; + new_y = width - x; + } + + FlutterPointerEvent event = {}; + event.struct_size = sizeof(event); + event.phase = phase; + event.x = new_x; + event.y = new_y; + event.timestamp = timestamp; + FlutterEngineSendPointerEvent(engine_->flutter_engine, &event, 1); +} + +Eina_Bool TouchEventHandler::OnTouch(void *data, int type, void *event) { + auto *self = reinterpret_cast(data); + + if (type == ECORE_EVENT_MOUSE_BUTTON_DOWN) { + self->pointer_state_ = true; + auto *button_event = reinterpret_cast(event); + self->SendFlutterPointerEvent(kDown, button_event->x, button_event->y, + button_event->timestamp); + } else if (type == ECORE_EVENT_MOUSE_BUTTON_UP) { + self->pointer_state_ = false; + auto *button_event = reinterpret_cast(event); + self->SendFlutterPointerEvent(kUp, button_event->x, button_event->y, + button_event->timestamp); + } else if (type == ECORE_EVENT_MOUSE_MOVE) { + if (self->pointer_state_) { + auto *move_event = reinterpret_cast(event); + self->SendFlutterPointerEvent(kMove, move_event->x, move_event->y, + move_event->timestamp); + } + } else if (type == ECORE_WL2_EVENT_WINDOW_VISIBILITY_CHANGE) { + auto *focus_event = + reinterpret_cast(event); + LoggerD("Visibility changed: %u, %d", focus_event->win, + focus_event->fully_obscured); + } + + return ECORE_CALLBACK_PASS_ON; +} diff --git a/shell/platform/tizen/touch_event_handler.h b/shell/platform/tizen/touch_event_handler.h new file mode 100644 index 0000000000000..0b0b678953dbe --- /dev/null +++ b/shell/platform/tizen/touch_event_handler.h @@ -0,0 +1,36 @@ +// Copyright 2020 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_TOUCH_EVENT_HANDLER_H_ +#define EMBEDDER_TOUCH_EVENT_HANDLER_H_ + +#define EFL_BETA_API_SUPPORT +#include +#include + +#include + +#include "flutter/shell/platform/embedder/embedder.h" + +class TizenEmbedderEngine; + +class TouchEventHandler { + public: + explicit TouchEventHandler(TizenEmbedderEngine* engine); + virtual ~TouchEventHandler(); + + int32_t rotation = 0; + + private: + TizenEmbedderEngine* engine_; + std::vector touch_event_handlers_; + bool pointer_state_ = false; + + void SendFlutterPointerEvent(FlutterPointerPhase phase, double x, double y, + size_t timestamp); + + static Eina_Bool OnTouch(void* data, int type, void* event); +}; + +#endif // EMBEDDER_TOUCH_EVENT_HANDLER_H_ diff --git a/third_party/txt/src/txt/platform_linux.cc b/third_party/txt/src/txt/platform_linux.cc index 9e13898d49e7a..b2c77568d2868 100644 --- a/third_party/txt/src/txt/platform_linux.cc +++ b/third_party/txt/src/txt/platform_linux.cc @@ -13,7 +13,7 @@ namespace txt { std::vector GetDefaultFontFamilies() { - return {"Ubuntu", "Cantarell", "DejaVu Sans", "Liberation Sans", "Arial"}; + return {"SamsungOneUI", "BreezeSans", "Ubuntu", "Cantarell", "DejaVu Sans", "Liberation Sans", "Arial"}; } sk_sp GetDefaultFontManager() { diff --git a/tools/gn b/tools/gn index 214cc8d02bb6d..5c410216231cb 100755 --- a/tools/gn +++ b/tools/gn @@ -165,6 +165,7 @@ def to_gn_args(args): gn_args['use_clang_static_analyzer'] = args.clang_static_analyzer gn_args['embedder_for_target'] = args.embedder_for_target + gn_args['build_tizen_shell'] = args.build_tizen_shell gn_args['enable_coverage'] = args.coverage @@ -451,6 +452,7 @@ def parse_args(args): parser.add_argument('--always-use-skshaper', action='store_true', default=False) parser.add_argument('--embedder-for-target', dest='embedder_for_target', action='store_true', default=False) + parser.add_argument('--build-tizen-shell', dest='build_tizen_shell', action='store_true', default=False) parser.add_argument('--coverage', default=False, action='store_true')