diff --git a/WINDOWS-DEVELOPMENT-QUICKSTART-ko.md b/WINDOWS-DEVELOPMENT-QUICKSTART-ko.md index fc90bb2..5f2c90f 100644 --- a/WINDOWS-DEVELOPMENT-QUICKSTART-ko.md +++ b/WINDOWS-DEVELOPMENT-QUICKSTART-ko.md @@ -39,6 +39,28 @@ GitHub 네트워크 작업은 기존 정책을 공통으로 사용한다. 전송 저장소에 접근할 수 없으면 경고 후 TizenVG 없이 완료한다. 저장소 접근 후 발생한 TizenVG configure, build, install 오류는 숨기지 않는다. +TizenVG에 이어 WebView용 LWE(Starfish) SDK도 자동으로 준비한다 +(`vcpkg-script\setup-starfish.ps1`). 첫 실행은 소스 빌드 때문에 오래 +걸리며(약 30~60분), 실패 시 경고만 남기고 계속한다. 자세한 내용과 재실행 +방법은 아래 "LWE(Starfish) SDK 준비" 절을 참고한다. + +Debug 빌드가 필요하면 `-Configuration Debug`를 지정한다 (기본은 Release). +공개 SDK는 Release 전용이므로 이때 정적 라이브러리 +`dali-windows-dependencies.lib`만 Debug로 자동 재빌드한다. 같은 SDK로 +Release를 다시 쓰려면 `-Configuration Release`로 재실행하면 된다. + +```powershell +.\install.ps1 -Configuration Debug +``` + +Debug 빌드 참고: + +- WebView(LWE)는 현재 Release 전용이다. LWE 공개 API가 std::string을 DLL + 경계로 주고받아 전체 구성 일치가 필수인데 Starfish SDK가 Release로만 + 빌드되기 때문이다. `lwe-web-engine-pluginuild.ps1`은 Debug 요청 시 + 명확한 오류로 중단한다. +- TizenVG는 C API라 구성 혼용이 안전해 Release 그대로 사용한다. + ## 프로젝트별 빌드와 설치 ```powershell @@ -48,6 +70,9 @@ cd \dali-core cd \dali-adaptor .\build\windows\build.ps1 +cd \windows-dependencies\lwe-web-engine-plugin +.\build.ps1 + cd \dali-ui .\build\windows\build.ps1 @@ -55,6 +80,15 @@ cd \dali-ui\samples .\build.ps1 ``` +`lwe-web-engine-plugin`은 WebView용 LWE(Starfish) 웹엔진 플러그인 +(`dali2-web-engine-lwe-plugin.dll`)을 빌드해 `dali-env\bin`에 설치한다. +dali-env에 설치된 dali2-core/dali2-adaptor 패키지를 링크하므로 반드시 +dali-adaptor 다음, dali-ui 이전 순서로 빌드한다. 선행 조건으로 +`WindowsDependenciesSDK`에 LWE SDK(`include\LWEWebView.h`, +`lib\Starfish.lib`, `bin\Starfish.dll`)가 있어야 하며, 준비 방법은 +아래 "LWE(Starfish) SDK 준비" 절을 참고한다. WebView가 필요 없으면 이 +단계는 건너뛴다. + 각 명령은 configure, build, install을 모두 수행한다. 빌드 중간 산출물은 각 저장소의 `_build\windows`에 있고 설치 결과는 `dali-env`에 모인다. @@ -110,3 +144,48 @@ sdk-contents.json 실행의 비교를 위해 입력 manifest만 갱신한다. 정식 버전 정책은 `WINDOWS-SDK-VERSIONING-TODO.md`에 정리되어 있다. + +## LWE(Starfish) SDK 준비 + +`lwe-web-engine-plugin` 빌드의 선행 조건으로, `WindowsDependenciesSDK`에 +LWE 헤더(`LWEWebView.h` 등)와 `lib\Starfish.lib`, `bin\Starfish.dll`이 +있어야 한다. + +**`install.ps1`이 자동으로 처리한다.** 의존성 설치 단계에서 +`vcpkg-script\setup-starfish.ps1`이 함께 실행되어 LWE SDK까지 준비된다. +실패해도 경고만 남기고 SDK 설치는 계속되며(WebView만 비활성), 문제를 +해결한 뒤 스크립트만 다시 실행하면 된다: + +```powershell +cd \windows-dependencies +.\vcpkg-script\setup-starfish.ps1 # 재실행 (이미 완료된 단계는 건너뜀) +.\vcpkg-script\setup-starfish.ps1 -Force # 강제 재빌드 +# install.ps1 -SkipStarfish # LWE를 아예 건너뛰기 +``` + +스크립트가 자동으로 수행하는 일: + +1. **vcpkg 추가 포트**: openssl / libwebsockets / glew 설치 (구버전 포트 + 호환 수정 포함, release 전용). openssl이 10~20분 걸린다. +2. **Starfish 소스**: `github.sec.samsung.net/lws/starfish` 클론 + 서브모듈 + + `lwe-web-engine-plugin\patches\`의 x64 패치 적용 + 바인딩 생성기용 + python3/jinja2/ply 준비. +3. **빌드·설치**: VS2022 x64 Release로 `Starfish.dll` 빌드(첫 빌드 20~40분) + 후 SDK의 `include\`, `lib\`, `bin\`에 복사. + +추가 전제 조건: Python 3 + pip, `github.sec.samsung.net` 접근 권한. + +**지름길**: 이미 빌드된 SDK가 있으면(예: 다른 작업공간) `include\LWEWebView.h`, +`include\LWEWorker.h`, `include\PlatformIntegrationData.h`, `lib\Starfish.lib`, +`bin\Starfish.dll`(+선택 `bin\Starfish.pdb`)만 새 SDK로 복사해도 된다 — +스크립트가 이 파일들을 발견하면 전체를 건너뛴다. + +## WebView 샘플 실행 + +```powershell +cd +. .\dali-env\setenv.ps1 +.\dali-env\bin\web-view-win.example.exe +``` + +키: `ESC` 종료, `B`/`F` 뒤로/앞으로, `R` 새로고침, `H` 인라인 HTML, `J` JS 실행. diff --git a/build_windows_dependencies.ps1 b/build_windows_dependencies.ps1 index 118e96f..0e96866 100644 --- a/build_windows_dependencies.ps1 +++ b/build_windows_dependencies.ps1 @@ -2,6 +2,11 @@ param( [string]$Proxy = "", [string]$VcpkgRoot = "", + [ValidateSet("Debug", "Release")] + [string]$Configuration = "Release", + [string]$StarfishRepository = "", + [string]$StarfishRevision = "", + [switch]$SkipStarfish, [switch]$SkipTizenVg, [switch]$SkipThirdParty, [switch]$Clean, @@ -48,6 +53,7 @@ if(-not $SkipThirdParty -and -not $SkipTizenVg) VcpkgRoot = (Join-Path $WorkspaceContext.SdkRoot "vcpkg") InstallPrefix = $WorkspaceContext.SdkRoot SkipVcpkg = $true + Configuration = $Configuration } if($Proxy) { @@ -61,7 +67,35 @@ $Context = New-DaliBuildContext ` -VcpkgRoot (Join-Path $WorkspaceContext.SdkRoot "vcpkg") ` -InstallPrefix $WorkspaceContext.SdkRoot Initialize-DaliBuildEnvironment -Context $Context -$Common = Get-DaliCommonCMakeArguments -Context $Context + +# LWE (Starfish) web engine SDK. Optional: a failure only warns, WebView is +# simply unavailable and setup-starfish.ps1 can be re-run standalone. +if(-not $SkipStarfish) +{ + $StarfishArguments = @{ + InstallPrefix = $Context.InstallPrefix + Jobs = $Jobs + } + if($StarfishRepository) + { + $StarfishArguments.Repository = $StarfishRepository + } + if($StarfishRevision) + { + $StarfishArguments.Revision = $StarfishRevision + } + try + { + & (Join-Path $ScriptRoot "vcpkg-script/setup-starfish.ps1") @StarfishArguments + } + catch + { + Write-Warning "LWE (Starfish) SDK setup failed: $($_.Exception.Message)" + Write-Warning "WebView will be unavailable; re-run vcpkg-script/setup-starfish.ps1 after fixing." + } +} + +$Common = Get-DaliCommonCMakeArguments -Context $Context -Configuration $Configuration Invoke-DaliCMakeProject ` -Name "windows-dependencies" ` diff --git a/install.ps1 b/install.ps1 index fcee4e5..eb55420 100644 --- a/install.ps1 +++ b/install.ps1 @@ -1,9 +1,16 @@ [CmdletBinding()] param( + # Configuration the DALi projects will be built with. The published SDK is + # Release-only; for Debug the dali-windows-dependencies static library is + # rebuilt in Debug so dali-core links without a CRT mismatch (LNK2038). + [ValidateSet("Debug", "Release")] + [string]$Configuration = "Release", [string]$Proxy = "", [string]$ReleaseRepository = "dalihub/windows-dependencies", [string]$ReleaseTag = "windows-sdk-latest", [switch]$BuildFromSource, + # Skip the LWE (Starfish) web-engine SDK build; WebView will be unavailable. + [switch]$SkipStarfish, [int]$Jobs = 8 ) @@ -27,6 +34,37 @@ if($ReleaseRepository -notmatch '^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$') . (Join-Path $ScriptRoot "vcpkg-script\dali-build-common.ps1") Set-DaliProxyEnvironment -Proxy $Proxy +function Move-DirectoryWithRetry +{ + param( + [Parameter(Mandatory = $true)] + [string]$LiteralPath, + [Parameter(Mandatory = $true)] + [string]$Destination, + [int]$RetryCount = 10, + [int]$RetryDelayMs = 500 + ) + + $Attempt = 0 + while($Attempt -lt $RetryCount) + { + try + { + Move-Item -LiteralPath $LiteralPath -Destination $Destination -Force + return + } + catch + { + $Attempt++ + if($Attempt -ge $RetryCount) + { + throw + } + Start-Sleep -Milliseconds $RetryDelayMs + } + } +} + function Install-DownloadedSdk { $BaseUri = "https://github.com/$ReleaseRepository/releases/download/$ReleaseTag" @@ -64,11 +102,7 @@ function Install-DownloadedSdk New-Item -ItemType Directory -Force -Path $ExtractRoot | Out-Null try { - & tar.exe -xf $ArchivePath -C $ExtractRoot - if($LASTEXITCODE -ne 0) - { - throw "Failed to extract $ArchivePath" - } + Expand-Archive -LiteralPath $ArchivePath -DestinationPath $ExtractRoot -Force $ExpectedToolchain = Join-Path $ExtractRoot "vcpkg\scripts\buildsystems\vcpkg.cmake" if(-not (Test-Path -LiteralPath $ExpectedToolchain)) { @@ -82,11 +116,11 @@ function Install-DownloadedSdk } if(Test-Path -LiteralPath $SdkRoot) { - Move-Item -LiteralPath $SdkRoot -Destination $BackupRoot + Move-DirectoryWithRetry -LiteralPath $SdkRoot -Destination $BackupRoot } try { - Move-Item -LiteralPath $ExtractRoot -Destination $SdkRoot + Move-DirectoryWithRetry -LiteralPath $ExtractRoot -Destination $SdkRoot } catch { @@ -123,6 +157,7 @@ if(-not $InstalledRelease) { Write-Host "No usable published SDK was found. Building the same SDK layout from source." -ForegroundColor Yellow $BuildArguments = @{ + Configuration = $Configuration SkipTizenVg = $true Clean = $true Jobs = $Jobs @@ -131,9 +166,32 @@ if(-not $InstalledRelease) { $BuildArguments.Proxy = $Proxy } + if($SkipStarfish) + { + $BuildArguments.SkipStarfish = $true + } & (Join-Path $ScriptRoot "build_windows_dependencies.ps1") @BuildArguments } +# The published SDK ships a Release-only dali-windows-dependencies.lib. It is +# a static library, so a Debug DALi build cannot link it (LNK2038 CRT +# mismatch); rebuild just that project in Debug into the SDK. +if($InstalledRelease -and $Configuration -eq "Debug") +{ + Write-Host "Rebuilding dali-windows-dependencies.lib as Debug (published SDK is Release-only)." -ForegroundColor Yellow + $DebugContext = New-DaliBuildContext -WindowsDependenciesRoot $ScriptRoot ` + -VcpkgRoot (Join-Path $SdkRoot "vcpkg") -InstallPrefix $SdkRoot + Initialize-DaliBuildEnvironment -Context $DebugContext + $DebugArguments = Get-DaliCommonCMakeArguments -Context $DebugContext -Configuration $Configuration + Invoke-DaliCMakeProject ` + -Name "windows-dependencies ($Configuration)" ` + -SourceDirectory (Join-Path $ScriptRoot "build") ` + -BuildDirectory (Join-Path $ScriptRoot "_build\windows") ` + -ConfigureArguments $DebugArguments ` + -Clean ` + -Jobs $Jobs +} + $TizenVgArguments = @{ DaliRoot = $WorkspaceRoot VcpkgRoot = (Join-Path $SdkRoot "vcpkg") @@ -146,6 +204,29 @@ if($Proxy) } & (Join-Path $ScriptRoot "vcpkg-script\setup-dali-dependencies.ps1") @TizenVgArguments +# LWE (Starfish) web engine SDK for the WebView plugin. A failure here only +# warns: WebView is optional, and setup-starfish.ps1 can be re-run standalone. +# Note: the LWE SDK is currently Release-only; with -Configuration Debug the +# WebView plugin cannot be used (LWE passes std::string across the DLL +# boundary, so all configurations must match). +if(-not $SkipStarfish) +{ + if($Configuration -eq "Debug") + { + Write-Warning "LWE (Starfish) is built Release-only for now; WebView is unavailable in Debug builds." + } + try + { + & (Join-Path $ScriptRoot "vcpkg-script\setup-starfish.ps1") -InstallPrefix $SdkRoot -Jobs $Jobs + } + catch + { + Write-Warning "LWE (Starfish) SDK setup failed: $($_.Exception.Message)" + Write-Warning "WebView will be unavailable. Fix the issue and re-run vcpkg-script\setup-starfish.ps1," + Write-Warning "or skip building lwe-web-engine-plugin." + } +} + $RuntimeContext = New-DaliBuildContext -WindowsDependenciesRoot $ScriptRoot Install-DaliRuntimeScripts -Context $RuntimeContext Write-Host "`nDALi Windows dependencies are ready in $SdkRoot" -ForegroundColor Green diff --git a/lwe-web-engine-plugin/CMakeLists.txt b/lwe-web-engine-plugin/CMakeLists.txt new file mode 100644 index 0000000..b5860cb --- /dev/null +++ b/lwe-web-engine-plugin/CMakeLists.txt @@ -0,0 +1,61 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 3.16) + +# LWE (Lightweight Web Engine / Starfish) WebEnginePlugin for DALi on Windows. +# +# dali-adaptor's web-engine-impl.cpp dlopen's "libdali2-web-engine-lwe-plugin.so" +# at runtime; the Windows dlfcn shim maps that to dali2-web-engine-lwe-plugin.dll +# and loads it from the directory containing dali2-adaptor.dll. This project +# builds that DLL out-of-tree so dali-adaptor itself carries no plugin sources. +# +# Build AFTER dali-core and dali-adaptor are installed into dali-env: the plugin +# links their installed packages (public/devel API only). The LWE SDK +# (LWEWebView.h / Starfish.lib) comes from WindowsDependenciesSDK via +# CMAKE_PREFIX_PATH. + +PROJECT(dali2-web-engine-lwe-plugin-win CXX) + +SET(CMAKE_CXX_STANDARD 17) +SET(CMAKE_CXX_STANDARD_REQUIRED ON) + +FIND_PACKAGE(dali2-core CONFIG REQUIRED) +FIND_PACKAGE(dali2-adaptor CONFIG REQUIRED) + +FIND_PATH(LWE_INCLUDE_DIR NAMES LWEWebView.h REQUIRED) +FIND_LIBRARY(STARFISH_LIBRARY NAMES Starfish REQUIRED) +MESSAGE(STATUS "LWE include: ${LWE_INCLUDE_DIR}") +MESSAGE(STATUS "Starfish : ${STARFISH_LIBRARY}") + +ADD_LIBRARY(${PROJECT_NAME} SHARED + DaliInputEventConverter.cpp + LWEWebEngineBackForwardList.cpp + LWEWebEngineContext.cpp + LWEWebEngineCookieManager.cpp + LWEWebEngineLoadError.cpp + LWEWebEnginePlugin.cpp + LWEWebEngineSettings.cpp + web-engine-lwe-plugin.cpp +) + +# The DLL must carry the exact name the dlfcn shim derives from +# libdali2-web-engine-lwe-plugin.so. +SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES + OUTPUT_NAME "dali2-web-engine-lwe-plugin" + PREFIX "" +) + +TARGET_INCLUDE_DIRECTORIES(${PROJECT_NAME} PRIVATE ${LWE_INCLUDE_DIR}) + +# Match the DALi Windows ABI: /vmg (member-function-pointer representation used +# by DALi callbacks) and the __restrict__ mapping, same as the dali-ui samples. +TARGET_COMPILE_OPTIONS(${PROJECT_NAME} PRIVATE /vmg /utf-8 /wd4251) +TARGET_COMPILE_DEFINITIONS(${PROJECT_NAME} PRIVATE __restrict__=__restrict) + +TARGET_LINK_LIBRARIES(${PROJECT_NAME} PRIVATE + dali2-adaptor::dali2-adaptor + dali2-core::dali2-core + ${STARFISH_LIBRARY} + dbghelp # TEMP DEBUG: crash/abort stack dumps in LWEWebEnginePlugin.cpp +) + +# Install next to dali2-adaptor.dll so the dlfcn shim finds it. +INSTALL(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) diff --git a/lwe-web-engine-plugin/DaliInputEventConverter.cpp b/lwe-web-engine-plugin/DaliInputEventConverter.cpp new file mode 100644 index 0000000..465f33c --- /dev/null +++ b/lwe-web-engine-plugin/DaliInputEventConverter.cpp @@ -0,0 +1,121 @@ +#include "DaliInputEventConverter.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace LWEDaliBridge +{ +namespace +{ +LWE::KeyValue MapNamedKey(const std::string& keyName) +{ + if (keyName == "Left") return LWE::ArrowLeftKey; + if (keyName == "Right") return LWE::ArrowRightKey; + if (keyName == "Up") return LWE::ArrowUpKey; + if (keyName == "Down") return LWE::ArrowDownKey; + if (keyName == "Return" || keyName == "KP_Enter") return LWE::EnterKey; + if (keyName == "BackSpace") return LWE::BackspaceKey; + if (keyName == "Tab") return LWE::TabKey; + if (keyName == "Escape") return LWE::EscapeKey; + if (keyName == "Delete") return LWE::DeleteKey; + if (keyName == "Insert") return LWE::InsertKey; + if (keyName == "Home") return LWE::HomeKey; + if (keyName == "End") return LWE::EndKey; + if (keyName == "Prior") return LWE::PageUpKey; + if (keyName == "Next") return LWE::PageDownKey; + return LWE::UnidentifiedKey; +} +} // namespace + +LWE::KeyValue ToLWEKeyValue(const Dali::KeyEvent& keyEvent) +{ + // Resolve named/special keys (arrows, Return, Backspace, ...) FIRST. DALi reports the arrow + // keys with a one-byte GetKeyString() whose value is the Win32 VK code (Left=0x25='%', + // Up=0x26='&', Down=0x28='('), which falls in the printable ASCII range - so keying off the + // string would mis-map arrows to the characters '%'/'&'/'(' and never reach the arrow + // handling. The key name ("Left"/"Up"/...) is unambiguous, so try it before the string. + LWE::KeyValue named = MapNamedKey(keyEvent.GetKeyName().CStr()); + if (named != LWE::UnidentifiedKey) + { + return named; + } + + const Dali::String& keyString = keyEvent.GetKeyString(); + if (keyString.Size() == 1) + { + unsigned char ch = static_cast(keyString.CStr()[0]); + if (ch >= 32 && ch <= 126) + { + return static_cast(ch); + } + } + return named; +} + +void DispatchTouchEvent(LWE::WebContainer* container, const Dali::TouchEvent& touch) +{ + if (touch.GetPointCount() == 0) + { + return; + } + + // LWE expects coordinates relative to the web content's top-left, i.e. relative to the + // WebView actor that received the touch. GetScreenPosition() is window-absolute and would + // land the click at the wrong place in the page (so DOM inputs never get focus / text). + const Dali::Vector2& position = touch.GetLocalPosition(0); + { FILE* f = fopen("d:\\lwe_webview.log", "a"); if(f){ const Dali::Vector2& sp = touch.GetScreenPosition(0); fprintf(f, "[MOUSE] state=%d local=(%.0f,%.0f) screen=(%.0f,%.0f)\n", (int)touch.GetState(0), position.x, position.y, sp.x, sp.y); fclose(f);} } + switch (touch.GetState(0)) + { + case Dali::PointState::STARTED: + container->DispatchMouseDownEvent(LWE::LeftButton, LWE::LeftButtonDown, position.x, position.y); + break; + case Dali::PointState::MOTION: + container->DispatchMouseMoveEvent(LWE::LeftButton, LWE::LeftButtonDown, position.x, position.y); + break; + case Dali::PointState::FINISHED: + case Dali::PointState::LEAVE: + case Dali::PointState::INTERRUPTED: + default: + container->DispatchMouseUpEvent(LWE::NoButton, LWE::NoButtonDown, position.x, position.y); + break; + } +} + +void DispatchKeyEvent(LWE::WebContainer* container, const Dali::KeyEvent& keyEvent) +{ + LWE::KeyValue value = ToLWEKeyValue(keyEvent); + { FILE* f = fopen("d:\\lwe_webview.log", "a"); if(f){ fprintf(f, "[KEY] DispatchKeyEvent name='%s' str='%s' code=%d state=%d -> lweVal=%d\n", keyEvent.GetKeyName().CStr(), keyEvent.GetKeyString().CStr(), keyEvent.GetKeyCode(), (int)keyEvent.GetState(), (int)value); fclose(f);} } + if (keyEvent.GetState() == Dali::KeyEvent::DOWN) + { + container->DispatchKeyDownEvent(value); + container->DispatchKeyPressEvent(value); + } + else + { + container->DispatchKeyUpEvent(value); + } +} + +void DispatchHoverEvent(LWE::WebContainer* container, const Dali::HoverEvent& hover) +{ + if (hover.GetPointCount() == 0) + { + return; + } + const Dali::Vector2& position = hover.GetScreenPosition(0); + container->DispatchMouseMoveEvent(LWE::NoButton, LWE::NoButtonDown, position.x, position.y); +} + +void DispatchWheelEvent(LWE::WebContainer* container, const Dali::WheelEvent& wheel) +{ + const Dali::Vector2& point = wheel.GetPoint(); + container->DispatchMouseWheelEvent(point.x, point.y, wheel.GetDelta()); +} + +} // namespace LWEDaliBridge diff --git a/lwe-web-engine-plugin/DaliInputEventConverter.h b/lwe-web-engine-plugin/DaliInputEventConverter.h new file mode 100644 index 0000000..29df5bb --- /dev/null +++ b/lwe-web-engine-plugin/DaliInputEventConverter.h @@ -0,0 +1,37 @@ +#ifndef DALI_LWE_INPUT_EVENT_CONVERTER_H +#define DALI_LWE_INPUT_EVENT_CONVERTER_H + +#include + +namespace Dali +{ +class TouchEvent; +class KeyEvent; +class HoverEvent; +class WheelEvent; +} // namespace Dali + +namespace LWEDaliBridge +{ +// Maps a Dali::KeyEvent onto LWE::KeyValue. Dali key names follow the X11/Ecore +// keysym naming convention across all its platform ports, which is what this +// mapping assumes for non-printable keys; printable keys share LWE::KeyValue's +// ASCII-aligned numbering (32-126) with Dali::KeyEvent::GetKeyString(). +LWE::KeyValue ToLWEKeyValue(const Dali::KeyEvent& keyEvent); + +// Dispatches a Dali::TouchEvent onto a WebContainer. Only the primary point +// (index 0) is forwarded, as mouse-style press/move/release - LWE's multi-point +// touch dispatch (DispatchTouchStart/Move/EndEvent) exists but is not wired here; +// see plugins/web-engine-lwe/README.md. +void DispatchTouchEvent(LWE::WebContainer* container, const Dali::TouchEvent& touch); + +void DispatchKeyEvent(LWE::WebContainer* container, const Dali::KeyEvent& keyEvent); + +// Approximated as mouse move, same caveat as DispatchTouchEvent. +void DispatchHoverEvent(LWE::WebContainer* container, const Dali::HoverEvent& hover); + +void DispatchWheelEvent(LWE::WebContainer* container, const Dali::WheelEvent& wheel); + +} // namespace LWEDaliBridge + +#endif // DALI_LWE_INPUT_EVENT_CONVERTER_H diff --git a/lwe-web-engine-plugin/LWEWebEngineBackForwardList.cpp b/lwe-web-engine-plugin/LWEWebEngineBackForwardList.cpp new file mode 100644 index 0000000..eaa54f2 --- /dev/null +++ b/lwe-web-engine-plugin/LWEWebEngineBackForwardList.cpp @@ -0,0 +1,22 @@ +#include "LWEWebEngineBackForwardList.h" + +namespace LWEDaliBridge +{ + +std::unique_ptr LWEWebEngineBackForwardList::GetCurrentItem() const { return nullptr; } +std::unique_ptr LWEWebEngineBackForwardList::GetPreviousItem() const { return nullptr; } +std::unique_ptr LWEWebEngineBackForwardList::GetNextItem() const { return nullptr; } +std::unique_ptr LWEWebEngineBackForwardList::GetItemAtIndex(uint32_t) const { return nullptr; } +uint32_t LWEWebEngineBackForwardList::GetItemCount() const { return 0; } + +std::vector> LWEWebEngineBackForwardList::GetBackwardItems(int) +{ + return {}; +} + +std::vector> LWEWebEngineBackForwardList::GetForwardItems(int) +{ + return {}; +} + +} // namespace LWEDaliBridge diff --git a/lwe-web-engine-plugin/LWEWebEngineBackForwardList.h b/lwe-web-engine-plugin/LWEWebEngineBackForwardList.h new file mode 100644 index 0000000..4d1223b --- /dev/null +++ b/lwe-web-engine-plugin/LWEWebEngineBackForwardList.h @@ -0,0 +1,25 @@ +#ifndef DALI_LWE_WEB_ENGINE_BACK_FORWARD_LIST_H +#define DALI_LWE_WEB_ENGINE_BACK_FORWARD_LIST_H + +#include + +namespace LWEDaliBridge +{ +// LWE exposes only CanGoBack/CanGoForward/GoBack/GoForward on WebContainer (handled +// directly by LWEWebEnginePlugin) with no way to enumerate history items, so this +// is a stub returning empty results throughout; see plugins/web-engine-lwe/README.md. +class LWEWebEngineBackForwardList : public Dali::WebEngineBackForwardList +{ +public: + std::unique_ptr GetCurrentItem() const override; + std::unique_ptr GetPreviousItem() const override; + std::unique_ptr GetNextItem() const override; + std::unique_ptr GetItemAtIndex(uint32_t index) const override; + uint32_t GetItemCount() const override; + std::vector> GetBackwardItems(int limit) override; + std::vector> GetForwardItems(int limit) override; +}; + +} // namespace LWEDaliBridge + +#endif // DALI_LWE_WEB_ENGINE_BACK_FORWARD_LIST_H diff --git a/lwe-web-engine-plugin/LWEWebEngineContext.cpp b/lwe-web-engine-plugin/LWEWebEngineContext.cpp new file mode 100644 index 0000000..53a2bd9 --- /dev/null +++ b/lwe-web-engine-plugin/LWEWebEngineContext.cpp @@ -0,0 +1,54 @@ +#include "LWEWebEngineContext.h" + +namespace LWEDaliBridge +{ + +LWEWebEngineContext::LWEWebEngineContext() +: mCacheModel(CacheModel::DOCUMENT_VIEWER) +, mCacheEnabled(true) +, mDefaultZoomFactor(1.0f) +{ +} + +Dali::WebEngineContext::CacheModel LWEWebEngineContext::GetCacheModel() const { return mCacheModel; } +void LWEWebEngineContext::SetCacheModel(CacheModel cacheModel) { mCacheModel = cacheModel; } +void LWEWebEngineContext::SetProxyUri(const std::string& uri) { mProxyUri = uri; } +std::string LWEWebEngineContext::GetProxyUri() const { return mProxyUri; } +void LWEWebEngineContext::SetDefaultProxyAuth(const std::string&, const std::string&) {} +void LWEWebEngineContext::SetProxyBypassRule(const std::string&, const std::string& bypass) { mProxyBypassRule = bypass; } +std::string LWEWebEngineContext::GetProxyBypassRule() const { return mProxyBypassRule; } +void LWEWebEngineContext::SetCertificateFilePath(const std::string& certificatePath) { mCertificateFilePath = certificatePath; } +std::string LWEWebEngineContext::GetCertificateFilePath() const { return mCertificateFilePath; } +void LWEWebEngineContext::DeleteAllWebDatabase() {} +bool LWEWebEngineContext::GetWebDatabaseOrigins(WebEngineSecurityOriginAcquiredCallback) { return false; } +bool LWEWebEngineContext::DeleteWebDatabase(Dali::WebEngineSecurityOrigin&) { return false; } +bool LWEWebEngineContext::GetWebStorageOrigins(WebEngineSecurityOriginAcquiredCallback) { return false; } +bool LWEWebEngineContext::GetWebStorageUsageForOrigin(Dali::WebEngineSecurityOrigin&, WebEngineStorageUsageAcquiredCallback) { return false; } +void LWEWebEngineContext::DeleteAllWebStorage() {} +bool LWEWebEngineContext::DeleteWebStorage(Dali::WebEngineSecurityOrigin&) { return false; } +void LWEWebEngineContext::DeleteLocalFileSystem() {} +void LWEWebEngineContext::ClearCache() {} +bool LWEWebEngineContext::DeleteApplicationCache(Dali::WebEngineSecurityOrigin&) { return false; } +void LWEWebEngineContext::GetFormPasswordList(WebEngineFormPasswordAcquiredCallback) {} +void LWEWebEngineContext::RegisterDownloadStartedCallback(WebEngineDownloadStartedCallback) {} +void LWEWebEngineContext::RegisterMimeOverriddenCallback(WebEngineMimeOverriddenCallback) {} +void LWEWebEngineContext::RegisterRequestInterceptedCallback(WebEngineRequestInterceptedCallback) {} +void LWEWebEngineContext::EnableCache(bool cacheEnabled) { mCacheEnabled = cacheEnabled; } +bool LWEWebEngineContext::IsCacheEnabled() const { return mCacheEnabled; } +void LWEWebEngineContext::SetAppId(const std::string&) {} +bool LWEWebEngineContext::SetAppVersion(const std::string&) { return true; } +void LWEWebEngineContext::SetApplicationType(const ApplicationType) {} +void LWEWebEngineContext::SetTimeOffset(float) {} +void LWEWebEngineContext::SetTimeZoneOffset(float, float) {} +void LWEWebEngineContext::SetDefaultZoomFactor(float zoomFactor) { mDefaultZoomFactor = zoomFactor; } +float LWEWebEngineContext::GetDefaultZoomFactor() const { return mDefaultZoomFactor; } +void LWEWebEngineContext::RegisterUrlSchemesAsCorsEnabled(const std::vector&) {} +void LWEWebEngineContext::RegisterJsPluginMimeTypes(const std::vector&) {} +bool LWEWebEngineContext::DeleteAllApplicationCache() { return true; } +bool LWEWebEngineContext::DeleteAllWebIndexedDatabase() { return true; } +void LWEWebEngineContext::DeleteFormPasswordDataList(const std::vector&) {} +void LWEWebEngineContext::DeleteAllFormPasswordData() {} +void LWEWebEngineContext::DeleteAllFormCandidateData() {} +bool LWEWebEngineContext::FreeUnusedMemory() { return true; } + +} // namespace LWEDaliBridge diff --git a/lwe-web-engine-plugin/LWEWebEngineContext.h b/lwe-web-engine-plugin/LWEWebEngineContext.h new file mode 100644 index 0000000..67469a1 --- /dev/null +++ b/lwe-web-engine-plugin/LWEWebEngineContext.h @@ -0,0 +1,68 @@ +#ifndef DALI_LWE_WEB_ENGINE_CONTEXT_H +#define DALI_LWE_WEB_ENGINE_CONTEXT_H + +#include + +namespace LWEDaliBridge +{ +// LWE has no context-level API (proxy/cache/storage/etc. are all per-WebContainer, +// see LWEWebEnginePlugin) so this only stores whatever the caller last set, with no +// effect on any actual WebContainer; see plugins/web-engine-lwe/README.md. +class LWEWebEngineContext : public Dali::WebEngineContext +{ +public: + LWEWebEngineContext(); + + CacheModel GetCacheModel() const override; + void SetCacheModel(CacheModel cacheModel) override; + void SetProxyUri(const std::string& uri) override; + std::string GetProxyUri() const override; + void SetDefaultProxyAuth(const std::string& username, const std::string& password) override; + void SetProxyBypassRule(const std::string& proxy, const std::string& bypass) override; + std::string GetProxyBypassRule() const override; + void SetCertificateFilePath(const std::string& certificatePath) override; + std::string GetCertificateFilePath() const override; + void DeleteAllWebDatabase() override; + bool GetWebDatabaseOrigins(WebEngineSecurityOriginAcquiredCallback callback) override; + bool DeleteWebDatabase(Dali::WebEngineSecurityOrigin& origin) override; + bool GetWebStorageOrigins(WebEngineSecurityOriginAcquiredCallback callback) override; + bool GetWebStorageUsageForOrigin(Dali::WebEngineSecurityOrigin& origin, WebEngineStorageUsageAcquiredCallback callback) override; + void DeleteAllWebStorage() override; + bool DeleteWebStorage(Dali::WebEngineSecurityOrigin& origin) override; + void DeleteLocalFileSystem() override; + void ClearCache() override; + bool DeleteApplicationCache(Dali::WebEngineSecurityOrigin& origin) override; + void GetFormPasswordList(WebEngineFormPasswordAcquiredCallback callback) override; + void RegisterDownloadStartedCallback(WebEngineDownloadStartedCallback callback) override; + void RegisterMimeOverriddenCallback(WebEngineMimeOverriddenCallback callback) override; + void RegisterRequestInterceptedCallback(WebEngineRequestInterceptedCallback callback) override; + void EnableCache(bool cacheEnabled) override; + bool IsCacheEnabled() const override; + void SetAppId(const std::string& appId) override; + bool SetAppVersion(const std::string& appVersion) override; + void SetApplicationType(const ApplicationType applicationType) override; + void SetTimeOffset(float timeOffset) override; + void SetTimeZoneOffset(float timeZoneOffset, float daylightSavingTime) override; + void SetDefaultZoomFactor(float zoomFactor) override; + float GetDefaultZoomFactor() const override; + void RegisterUrlSchemesAsCorsEnabled(const std::vector& schemes) override; + void RegisterJsPluginMimeTypes(const std::vector& mimeTypes) override; + bool DeleteAllApplicationCache() override; + bool DeleteAllWebIndexedDatabase() override; + void DeleteFormPasswordDataList(const std::vector& list) override; + void DeleteAllFormPasswordData() override; + void DeleteAllFormCandidateData() override; + bool FreeUnusedMemory() override; + +private: + CacheModel mCacheModel; + std::string mProxyUri; + std::string mProxyBypassRule; + std::string mCertificateFilePath; + bool mCacheEnabled; + float mDefaultZoomFactor; +}; + +} // namespace LWEDaliBridge + +#endif // DALI_LWE_WEB_ENGINE_CONTEXT_H diff --git a/lwe-web-engine-plugin/LWEWebEngineCookieManager.cpp b/lwe-web-engine-plugin/LWEWebEngineCookieManager.cpp new file mode 100644 index 0000000..fc3970b --- /dev/null +++ b/lwe-web-engine-plugin/LWEWebEngineCookieManager.cpp @@ -0,0 +1,27 @@ +#include "LWEWebEngineCookieManager.h" + +#include + +namespace LWEDaliBridge +{ + +void LWEWebEngineCookieManager::SetCookieAcceptPolicy(CookieAcceptPolicy policy) +{ + mAcceptPolicy = policy; +} + +Dali::WebEngineCookieManager::CookieAcceptPolicy LWEWebEngineCookieManager::GetCookieAcceptPolicy() const +{ + return mAcceptPolicy; +} + +void LWEWebEngineCookieManager::ClearCookies() +{ + LWE::CookieManager::GetInstance()->ClearCookies(); +} + +void LWEWebEngineCookieManager::SetPersistentStorage(const std::string&, CookiePersistentStorage) {} + +void LWEWebEngineCookieManager::ChangesWatch(WebEngineCookieManagerChangesWatchCallback) {} + +} // namespace LWEDaliBridge diff --git a/lwe-web-engine-plugin/LWEWebEngineCookieManager.h b/lwe-web-engine-plugin/LWEWebEngineCookieManager.h new file mode 100644 index 0000000..54a0b9e --- /dev/null +++ b/lwe-web-engine-plugin/LWEWebEngineCookieManager.h @@ -0,0 +1,26 @@ +#ifndef DALI_LWE_WEB_ENGINE_COOKIE_MANAGER_H +#define DALI_LWE_WEB_ENGINE_COOKIE_MANAGER_H + +#include + +namespace LWEDaliBridge +{ +// Wraps LWE::CookieManager (a process-wide singleton), which only supports +// GetCookie/HasCookies/ClearCookies - accept policy and persistent storage have +// no LWE equivalent; see plugins/web-engine-lwe/README.md. +class LWEWebEngineCookieManager : public Dali::WebEngineCookieManager +{ +public: + void SetCookieAcceptPolicy(CookieAcceptPolicy policy) override; + CookieAcceptPolicy GetCookieAcceptPolicy() const override; + void ClearCookies() override; + void SetPersistentStorage(const std::string& path, CookiePersistentStorage storage) override; + void ChangesWatch(WebEngineCookieManagerChangesWatchCallback callback) override; + +private: + CookieAcceptPolicy mAcceptPolicy = CookieAcceptPolicy::NO_THIRD_PARTY; +}; + +} // namespace LWEDaliBridge + +#endif // DALI_LWE_WEB_ENGINE_COOKIE_MANAGER_H diff --git a/lwe-web-engine-plugin/LWEWebEngineLoadError.cpp b/lwe-web-engine-plugin/LWEWebEngineLoadError.cpp new file mode 100644 index 0000000..c8616eb --- /dev/null +++ b/lwe-web-engine-plugin/LWEWebEngineLoadError.cpp @@ -0,0 +1,27 @@ +#include "LWEWebEngineLoadError.h" + +namespace LWEDaliBridge +{ + +LWEWebEngineLoadError::LWEWebEngineLoadError(const LWE::ResourceError& error) +: mUrl(error.GetUrl()) +, mDescription(error.GetDescription()) +, mCode(error.GetErrorCode()) +{ +} + +std::string LWEWebEngineLoadError::GetUrl() const { return mUrl; } + +Dali::WebEngineLoadError::ErrorCode LWEWebEngineLoadError::GetCode() const +{ + return static_cast(mCode); +} + +std::string LWEWebEngineLoadError::GetDescription() const { return mDescription; } + +Dali::WebEngineLoadError::ErrorType LWEWebEngineLoadError::GetType() const +{ + return ErrorType::NETWORK; +} + +} // namespace LWEDaliBridge diff --git a/lwe-web-engine-plugin/LWEWebEngineLoadError.h b/lwe-web-engine-plugin/LWEWebEngineLoadError.h new file mode 100644 index 0000000..473a80e --- /dev/null +++ b/lwe-web-engine-plugin/LWEWebEngineLoadError.h @@ -0,0 +1,29 @@ +#ifndef DALI_LWE_WEB_ENGINE_LOAD_ERROR_H +#define DALI_LWE_WEB_ENGINE_LOAD_ERROR_H + +#include +#include + +namespace LWEDaliBridge +{ +// LWE::ResourceError only carries a numeric code, description and URL - it has no +// notion of ErrorType, so GetType() always reports NETWORK; see plugins/web-engine-lwe/README.md. +class LWEWebEngineLoadError : public Dali::WebEngineLoadError +{ +public: + explicit LWEWebEngineLoadError(const LWE::ResourceError& error); + + std::string GetUrl() const override; + ErrorCode GetCode() const override; + std::string GetDescription() const override; + ErrorType GetType() const override; + +private: + std::string mUrl; + std::string mDescription; + int mCode; +}; + +} // namespace LWEDaliBridge + +#endif // DALI_LWE_WEB_ENGINE_LOAD_ERROR_H diff --git a/lwe-web-engine-plugin/LWEWebEnginePlugin.cpp b/lwe-web-engine-plugin/LWEWebEnginePlugin.cpp new file mode 100644 index 0000000..aef8a6d --- /dev/null +++ b/lwe-web-engine-plugin/LWEWebEnginePlugin.cpp @@ -0,0 +1,615 @@ +#include "LWEWebEnginePlugin.h" + +#include "DaliInputEventConverter.h" +#include "LWEWebEngineBackForwardList.h" +#include "LWEWebEngineLoadError.h" +#include "LWEWebEngineSettings.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace LWEDaliBridge +{ +namespace +{ +// TEMP DEBUG +void DbgLog(const char* fmt, ...) { FILE* f = fopen("d:\\lwe_webview.log", "a"); if(!f) return; va_list ap; va_start(ap, fmt); vfprintf(f, fmt, ap); va_end(ap); fputc('\n', f); fclose(f); } + +// TEMP DEBUG: shared stack walker for the crash/abort hooks below. +void DumpStackFromContext(CONTEXT ctx) +{ + HANDLE proc = GetCurrentProcess(); + SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS); + SymInitialize(proc, NULL, TRUE); + + STACKFRAME64 frame = {}; + frame.AddrPC.Offset = ctx.Rip; frame.AddrPC.Mode = AddrModeFlat; + frame.AddrFrame.Offset = ctx.Rbp; frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrStack.Offset = ctx.Rsp; frame.AddrStack.Mode = AddrModeFlat; + + char symBuf[sizeof(SYMBOL_INFO) + 512] = {}; + SYMBOL_INFO* sym = reinterpret_cast(symBuf); + sym->SizeOfStruct = sizeof(SYMBOL_INFO); + sym->MaxNameLen = 500; + + for (int i = 0; i < 40; ++i) + { + if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, proc, GetCurrentThread(), &frame, + &ctx, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) + { + break; + } + DWORD64 addr = frame.AddrPC.Offset; + if (!addr) break; + + char modName[MAX_PATH] = "?"; + HMODULE mod = NULL; + if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + (LPCSTR)addr, &mod) && mod) + { + GetModuleFileNameA(mod, modName, sizeof(modName)); + } + + DWORD64 disp = 0; + if (SymFromAddr(proc, addr, &disp, sym)) + { + DbgLog("[crash] #%d %s!%s+0x%llx", i, modName, sym->Name, (unsigned long long)disp); + } + else + { + DWORD64 base = mod ? (DWORD64)mod : 0; + DbgLog("[crash] #%d %s+0x%llx", i, modName, (unsigned long long)(addr - base)); + } + } +} + +// TEMP DEBUG: abort() (ucrtbase __fastfail) bypasses vectored exception +// handlers; hook SIGABRT to log the aborting thread's stack first. +void AbortSignalHandler(int) +{ + DbgLog("[abort] SIGABRT on tid=%lu", GetCurrentThreadId()); + CONTEXT ctx = {}; + ctx.ContextFlags = CONTEXT_FULL; + RtlCaptureContext(&ctx); + DumpStackFromContext(ctx); +} + +// TEMP DEBUG: capture a symbolized stack on the first access violation, then let +// the process crash as usual. Installed once in Create(). +LONG WINAPI CrashStackFilter(EXCEPTION_POINTERS* info) +{ + const DWORD code = info->ExceptionRecord->ExceptionCode; + if (code != EXCEPTION_ACCESS_VIOLATION && code != EXCEPTION_BREAKPOINT && + code != 0xC0000374u /* STATUS_HEAP_CORRUPTION */ && + code != 0xC0000421u /* STATUS_VERIFIER_STOP (page heap violation) */) + { + return EXCEPTION_CONTINUE_SEARCH; + } + static bool s_logged = false; + if (s_logged) return EXCEPTION_CONTINUE_SEARCH; // first fault only + s_logged = true; + DbgLog("[crash] code=0x%08lx at %p tid=%lu", (unsigned long)code, info->ExceptionRecord->ExceptionAddress, GetCurrentThreadId()); + + HANDLE proc = GetCurrentProcess(); + SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS); + SymInitialize(proc, NULL, TRUE); + + CONTEXT ctx = *info->ContextRecord; + STACKFRAME64 frame = {}; + frame.AddrPC.Offset = ctx.Rip; frame.AddrPC.Mode = AddrModeFlat; + frame.AddrFrame.Offset = ctx.Rbp; frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrStack.Offset = ctx.Rsp; frame.AddrStack.Mode = AddrModeFlat; + + char symBuf[sizeof(SYMBOL_INFO) + 512] = {}; + SYMBOL_INFO* sym = reinterpret_cast(symBuf); + sym->SizeOfStruct = sizeof(SYMBOL_INFO); + sym->MaxNameLen = 500; + + for (int i = 0; i < 40; ++i) + { + if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, proc, GetCurrentThread(), &frame, + &ctx, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) + { + break; + } + DWORD64 addr = frame.AddrPC.Offset; + if (!addr) break; + + char modName[MAX_PATH] = "?"; + HMODULE mod = NULL; + if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + (LPCSTR)addr, &mod) && mod) + { + GetModuleFileNameA(mod, modName, sizeof(modName)); // full path (kernel32, no psapi) + } + + DWORD64 disp = 0; + if (SymFromAddr(proc, addr, &disp, sym)) + { + DbgLog("[crash] #%d %s!%s+0x%llx", i, modName, sym->Name, (unsigned long long)disp); + } + else + { + DWORD64 base = mod ? (DWORD64)mod : 0; + DbgLog("[crash] #%d %s+0x%llx", i, modName, (unsigned long long)(addr - base)); + } + } + return EXCEPTION_CONTINUE_SEARCH; +} + +constexpr char kStorageDirectory[] = "./lwe_dali_plugin_storage"; + +// LWE's Cairo-based Windows canvas backend renders into CAIRO_FORMAT_ARGB32, which is +// laid out as BGRA in memory on little-endian Windows - see README.md for the caveat +// that this hasn't been visually verified. +constexpr Dali::Pixel::Format kRenderBufferPixelFormat = Dali::Pixel::BGRA8888; +constexpr uint32_t kBytesPerPixel = 4; +} // namespace + +LWEWebEnginePlugin::LWEWebEnginePlugin() +: mContainer(nullptr) +, mPageZoomFactor(1.0f) +, mTextZoomFactor(1.0f) +, mScaleFactor(1.0f) +, mIncognito(false) +{ +} + +LWEWebEnginePlugin::~LWEWebEnginePlugin() +{ + Destroy(); +} + +void LWEWebEnginePlugin::EnsureInitialized(const std::string& locale, const std::string& timezoneId) +{ + if (!LWE::LWE::IsInitialized()) + { + // PreferSeparateThread: LWE runs on its own dedicated thread with its own + // Win32 message queue (MessageLoopWindows::run). This keeps LWE's messages off + // the DALi/SDL main-thread queue (which would otherwise consume and drop them), + // and lets rendering advance without the host pumping anything. The only DALi + // work is marshalled back to the main thread via mEventThreadCallback. + LWE::LWE::Initialize(kStorageDirectory, LWE::InitializeOption::PreferSeparateThread); + } +} + +void LWEWebEnginePlugin::Create(uint32_t width, uint32_t height, const std::string& locale, const std::string& timezoneId) +{ + DbgLog("[LWE] Create %ux%u locale=%s tz=%s", width, height, locale.c_str(), timezoneId.c_str()); + static bool s_crashFilterInstalled = false; + if (!s_crashFilterInstalled) + { + s_crashFilterInstalled = true; + AddVectoredExceptionHandler(1 /*first*/, CrashStackFilter); + signal(SIGABRT, AbortSignalHandler); // TEMP DEBUG: see AbortSignalHandler + } + DbgLog("[LWE] Create: before EnsureInitialized"); + EnsureInitialized(locale, timezoneId); + DbgLog("[LWE] Create: after EnsureInitialized, before WebContainer::Create"); + mContainer = LWE::WebContainer::Create(width, height, 1.0f, "sans-serif", locale.c_str(), timezoneId.c_str()); + DbgLog("[LWE] Create: after WebContainer::Create container=%p", (void*)mContainer); + mSettings = std::make_unique(mContainer); + mBackForwardList = std::make_unique(); + + mWidth.store(width); + mHeight.store(height); + + // Constructed on the DALi main thread (required by EventThreadCallback). LWE's + // render thread calls Trigger(); DALi then runs OnFrameReadyMainThread on the main + // thread. + mEventThreadCallback = std::make_unique( + Dali::MakeCallback(this, &LWEWebEnginePlugin::OnFrameReadyMainThread)); + + // Both handlers below run on LWE's render thread. They must not touch any DALi + // object; they only manage plugin-owned CPU buffers and signal the main thread. + mContainer->RegisterPreRenderingHandler([this]() -> LWE::WebContainer::RenderInfo { + const uint32_t w = mWidth.load(); + const uint32_t h = mHeight.load(); + const size_t needed = static_cast(w) * h * kBytesPerPixel; + if (mRenderBuffer.size() != needed) + { + mRenderBuffer.assign(needed, 0); // (re)sized only here, on the LWE thread + } + LWE::WebContainer::RenderInfo info; + info.updatedBufferAddress = mRenderBuffer.data(); + info.bufferStride = static_cast(w) * kBytesPerPixel; + return info; + }); + mContainer->RegisterOnRenderedHandler([this](LWE::WebContainer*, const LWE::WebContainer::RenderResult&) { + DbgLog("[LWE] OnRendered(LWE thread) enter renderBuf=%zu", mRenderBuffer.size()); + { + std::lock_guard lock(mBufferMutex); + mReadyBuffer = mRenderBuffer; + mReadyWidth = mWidth.load(); + mReadyHeight = mHeight.load(); + } + if (mEventThreadCallback) + { + mEventThreadCallback->Trigger(); + } + DbgLog("[LWE] OnRendered(LWE thread) triggered"); + }); +} + +void LWEWebEnginePlugin::Create(uint32_t width, uint32_t height, uint32_t, char**) +{ + Create(width, height, "en-US", "UTC"); +} + +void LWEWebEnginePlugin::OnFrameReadyMainThread() +{ + DbgLog("[LWE] OnFrameReadyMainThread(main) enter"); + uint32_t w = 0, h = 0; + { + std::lock_guard lock(mBufferMutex); + w = mReadyWidth; + h = mReadyHeight; + if (w == 0 || h == 0 || mReadyBuffer.size() != static_cast(w) * h * kBytesPerPixel) + { + return; + } + if (!mNativeImage || mImageWidth != w || mImageHeight != h) + { + mNativeImage = Dali::NativeImage::New(w, h, Dali::NativeImage::COLOR_DEPTH_32); + mImageWidth = w; + mImageHeight = h; + } + { + const size_t cx = (static_cast(h) / 2) * (static_cast(w) * kBytesPerPixel) + (static_cast(w) / 2) * kBytesPerPixel; + const uint8_t* p0 = mReadyBuffer.data(); + const uint8_t* pc = mReadyBuffer.data() + cx; + DbgLog("[LWE] pixels px0=[%02x %02x %02x %02x] center=[%02x %02x %02x %02x]", + p0[0], p0[1], p0[2], p0[3], pc[0], pc[1], pc[2], pc[3]); + } + DbgLog("[LWE] OnFrameReadyMainThread SetPixels %ux%u img=%p", w, h, (void*)mNativeImage.Get()); + Dali::DevelNativeImage::SetPixels(*mNativeImage, mReadyBuffer.data(), kRenderBufferPixelFormat); + } + // WebViewImpl only (re)creates its image visual in response to this callback. + if (mFrameRenderedCallback) + { + mFrameRenderedCallback(); + } + DbgLog("[LWE] OnFrameReadyMainThread done cb=%d", (int)(bool)mFrameRenderedCallback); +} + +void LWEWebEnginePlugin::Destroy() +{ + if (mContainer) + { + mContainer->Destroy(); + mContainer = nullptr; + } +} + +bool LWEWebEnginePlugin::IsIncognito() const { return mIncognito; } + +Dali::WebEngineSettings& LWEWebEnginePlugin::GetSettings() const { return *mSettings; } + +Dali::WebEngineBackForwardList& LWEWebEnginePlugin::GetBackForwardList() const { return *mBackForwardList; } + +void LWEWebEnginePlugin::LoadUrl(const std::string& url) { mContainer->LoadURL(url); } + +std::string LWEWebEnginePlugin::GetTitle() const { return mContainer->GetTitle(); } + +Dali::PixelData LWEWebEnginePlugin::GetFavicon() const +{ + // LWE has no favicon API; requires Dali::PixelData::New(), which is only + // implemented in dali-core.lib (not linked here - see plugins/web-engine-lwe/README.md). + return Dali::PixelData(); +} + +Dali::NativeImagePtr LWEWebEnginePlugin::GetNativeImage() +{ + return mNativeImage; +} + +void LWEWebEnginePlugin::ChangeOrientation(int) {} + +std::string LWEWebEnginePlugin::GetUrl() const { return mContainer->GetURL(); } + +void LWEWebEnginePlugin::LoadHtmlString(const std::string& htmlString) { mContainer->LoadData(htmlString); } + +bool LWEWebEnginePlugin::LoadHtmlStringOverrideCurrentEntry(const std::string& html, const std::string&, const std::string&) +{ + mContainer->LoadData(html); + return true; +} + +bool LWEWebEnginePlugin::LoadContents(const int8_t*, uint32_t, const std::string&, const std::string&, const std::string&) +{ + return false; +} + +void LWEWebEnginePlugin::Reload() { mContainer->Reload(); } + +bool LWEWebEnginePlugin::ReloadWithoutCache() +{ + mContainer->Reload(); + return true; +} + +void LWEWebEnginePlugin::StopLoading() { mContainer->StopLoading(); } +void LWEWebEnginePlugin::Suspend() { mContainer->Pause(); } +void LWEWebEnginePlugin::Resume() { mContainer->Resume(); } +void LWEWebEnginePlugin::SuspendNetworkLoading() {} +void LWEWebEnginePlugin::ResumeNetworkLoading() {} +bool LWEWebEnginePlugin::AddCustomHeader(const std::string&, const std::string&) { return false; } +bool LWEWebEnginePlugin::RemoveCustomHeader(const std::string&) { return false; } +uint32_t LWEWebEnginePlugin::StartInspectorServer(uint32_t) { return 0; } +bool LWEWebEnginePlugin::StopInspectorServer() { return false; } + +void LWEWebEnginePlugin::ScrollBy(int32_t deltaX, int32_t deltaY) { mContainer->ScrollBy(deltaX, deltaY); } +bool LWEWebEnginePlugin::ScrollEdgeBy(int32_t, int32_t) { return false; } +void LWEWebEnginePlugin::SetScrollPosition(int32_t x, int32_t y) { mContainer->ScrollTo(x, y); } + +Dali::Vector2 LWEWebEnginePlugin::GetScrollPosition() const +{ + return Dali::Vector2(static_cast(mContainer->GetScrollX()), static_cast(mContainer->GetScrollY())); +} + +Dali::Vector2 LWEWebEnginePlugin::GetScrollSize() const +{ + return Dali::Vector2(static_cast(mContainer->Width()), static_cast(mContainer->Height())); +} + +Dali::Vector2 LWEWebEnginePlugin::GetContentSize() const +{ + return Dali::Vector2(static_cast(mContainer->Width()), static_cast(mContainer->Height())); +} + +bool LWEWebEnginePlugin::CanGoForward() { return mContainer->CanGoForward(); } +void LWEWebEnginePlugin::GoForward() { mContainer->GoForward(); } +bool LWEWebEnginePlugin::CanGoBack() { return mContainer->CanGoBack(); } +void LWEWebEnginePlugin::GoBack() { mContainer->GoBack(); } + +void LWEWebEnginePlugin::EvaluateJavaScript(const std::string& script, JavaScriptMessageHandlerCallback resultHandler) +{ + mContainer->EvaluateJavaScript(script, [resultHandler](const std::string& result) { + if (resultHandler) + { + resultHandler(result); + } + }); +} + +void LWEWebEnginePlugin::AddJavaScriptMessageHandler(const std::string& exposedObjectName, JavaScriptMessageHandlerCallback handler) +{ + mContainer->AddJavaScriptInterface(exposedObjectName, "postMessage", [handler](const std::string& message) -> std::string { + if (handler) + { + handler(message); + } + return ""; + }); +} + +void LWEWebEnginePlugin::AddJavaScriptEntireMessageHandler(const std::string& exposedObjectName, JavaScriptEntireMessageHandlerCallback handler) +{ + mContainer->AddJavaScriptInterface(exposedObjectName, "postMessage", [handler](const std::string& message) -> std::string { + if (handler) + { + handler("", message); + } + return ""; + }); +} + +void LWEWebEnginePlugin::RegisterJavaScriptAlertCallback(JavaScriptAlertCallback callback) +{ + mContainer->RegisterShowAlertHandler([callback](LWE::WebContainer*, const std::string&, const std::string& message) { + if (callback) + { + callback(message); + } + }); +} + +void LWEWebEnginePlugin::JavaScriptAlertReply() {} + +void LWEWebEnginePlugin::RegisterJavaScriptConfirmCallback(JavaScriptConfirmCallback) {} +void LWEWebEnginePlugin::JavaScriptConfirmReply(bool) {} +void LWEWebEnginePlugin::RegisterJavaScriptPromptCallback(JavaScriptPromptCallback) {} +void LWEWebEnginePlugin::JavaScriptPromptReply(const std::string&) {} + +std::unique_ptr LWEWebEnginePlugin::CreateHitTest(int32_t, int32_t, Dali::WebEngineHitTest::HitTestMode) +{ + return nullptr; +} + +bool LWEWebEnginePlugin::CreateHitTestAsynchronously(int32_t, int32_t, Dali::WebEngineHitTest::HitTestMode, WebEngineHitTestCreatedCallback) +{ + return false; +} + +void LWEWebEnginePlugin::ClearHistory() { mContainer->ClearHistory(); } +void LWEWebEnginePlugin::ClearAllTilesResources() {} + +std::string LWEWebEnginePlugin::GetUserAgent() const { return mUserAgent; } + +void LWEWebEnginePlugin::SetUserAgent(const std::string& userAgent) +{ + mUserAgent = userAgent; + mContainer->SetUserAgentString(userAgent); +} + +void LWEWebEnginePlugin::SetSize(uint32_t width, uint32_t height) +{ + // Only publish the new desired size (atomic). The LWE render thread resizes + // mRenderBuffer in its PreRendering handler, and the main thread recreates + // mNativeImage in OnFrameReadyMainThread when the arriving frame size changes - + // so neither buffer nor image is reallocated from the wrong thread. + mWidth.store(width); + mHeight.store(height); + mContainer->ResizeTo(width, height); +} +void LWEWebEnginePlugin::SetDocumentBackgroundColor(Dali::Vector4) {} +void LWEWebEnginePlugin::ClearTilesWhenHidden(bool) {} +void LWEWebEnginePlugin::SetTileCoverAreaMultiplier(float) {} +void LWEWebEnginePlugin::EnableCursorByClient(bool) {} +std::string LWEWebEnginePlugin::GetSelectedText() const { return ""; } + +bool LWEWebEnginePlugin::SendTouchEvent(const Dali::TouchEvent& touch) +{ + DispatchTouchEvent(mContainer, touch); + return true; +} + +bool LWEWebEnginePlugin::SendKeyEvent(const Dali::KeyEvent& event) +{ + DispatchKeyEvent(mContainer, event); + return true; +} + +void LWEWebEnginePlugin::EnableMouseEvents(bool) {} +void LWEWebEnginePlugin::EnableKeyEvents(bool) {} +void LWEWebEnginePlugin::SetFocus(bool focused) +{ + if (focused) + { + mContainer->Focus(); + } + else + { + mContainer->Blur(); + } +} +bool LWEWebEnginePlugin::SetImePositionAndAlignment(Dali::Vector2, int) { return false; } +void LWEWebEnginePlugin::SetCursorThemeName(const std::string) {} + +void LWEWebEnginePlugin::SetPageZoomFactor(float zoomFactor) { mPageZoomFactor = zoomFactor; } +float LWEWebEnginePlugin::GetPageZoomFactor() const { return mPageZoomFactor; } +void LWEWebEnginePlugin::SetTextZoomFactor(float zoomFactor) { mTextZoomFactor = zoomFactor; } +float LWEWebEnginePlugin::GetTextZoomFactor() const { return mTextZoomFactor; } +float LWEWebEnginePlugin::GetLoadProgressPercentage() const { return 0.0f; } +void LWEWebEnginePlugin::SetScaleFactor(float scaleFactor, Dali::Vector2) { mScaleFactor = scaleFactor; } +float LWEWebEnginePlugin::GetScaleFactor() const { return mScaleFactor; } +void LWEWebEnginePlugin::ActivateAccessibility(bool) {} + +Dali::Devel::Accessibility::Address LWEWebEnginePlugin::GetAccessibilityAddress() +{ + return Dali::Devel::Accessibility::Address(); +} + +bool LWEWebEnginePlugin::SetVisibility(bool) { return true; } +bool LWEWebEnginePlugin::HighlightText(const std::string&, FindOption, uint32_t) { return false; } +void LWEWebEnginePlugin::AddDynamicCertificatePath(const std::string&, const std::string&) {} + +Dali::PixelData LWEWebEnginePlugin::GetScreenshot(Dali::BoundsInteger, float) +{ + // Requires Dali::PixelData::New() from dali-core.lib; see plugins/web-engine-lwe/README.md. + return Dali::PixelData(); +} + +bool LWEWebEnginePlugin::GetScreenshotAsynchronously(Dali::BoundsInteger, float, ScreenshotCapturedCallback) { return false; } +bool LWEWebEnginePlugin::CheckVideoPlayingAsynchronously(VideoPlayingCallback) { return false; } +void LWEWebEnginePlugin::RegisterGeolocationPermissionCallback(GeolocationPermissionCallback) {} +void LWEWebEnginePlugin::UpdateDisplayArea(Dali::BoundsInteger displayArea) { SetSize(displayArea.width, displayArea.height); } +void LWEWebEnginePlugin::EnableVideoHole(bool) {} + +bool LWEWebEnginePlugin::SendHoverEvent(const Dali::HoverEvent& event) +{ + DispatchHoverEvent(mContainer, event); + return true; +} + +bool LWEWebEnginePlugin::SendWheelEvent(const Dali::WheelEvent& event) +{ + DispatchWheelEvent(mContainer, event); + return true; +} + +void LWEWebEnginePlugin::ExitFullscreen() {} +void LWEWebEnginePlugin::RegisterFrameRenderedCallback(WebEngineFrameRenderedCallback callback) +{ + mFrameRenderedCallback = callback; +} + +void LWEWebEnginePlugin::RegisterPageLoadStartedCallback(WebEnginePageLoadCallback callback) +{ + mContainer->RegisterOnPageStartedHandler([callback](LWE::WebContainer*, const std::string& url) { + if (callback) + { + callback(url); + } + }); +} + +void LWEWebEnginePlugin::RegisterPageLoadInProgressCallback(WebEnginePageLoadCallback) {} + +void LWEWebEnginePlugin::RegisterPageLoadFinishedCallback(WebEnginePageLoadCallback callback) +{ + mContainer->RegisterOnPageLoadedHandler([callback](LWE::WebContainer*, const std::string& url) { + if (callback) + { + callback(url); + } + }); +} + +void LWEWebEnginePlugin::RegisterPageLoadErrorCallback(WebEnginePageLoadErrorCallback callback) +{ + mContainer->RegisterOnReceivedErrorHandler([callback](LWE::WebContainer*, LWE::ResourceError error) { + if (callback) + { + callback(std::make_unique(error)); + } + }); +} + +void LWEWebEnginePlugin::RegisterScrollEdgeReachedCallback(WebEngineScrollEdgeReachedCallback) {} +void LWEWebEnginePlugin::RegisterOverScrolledCallback(WebEngineOverScrolledCallback) {} + +void LWEWebEnginePlugin::RegisterUrlChangedCallback(WebEngineUrlChangedCallback callback) +{ + mContainer->RegisterOnPageStartedHandler([callback](LWE::WebContainer*, const std::string& url) { + if (callback) + { + callback(url); + } + }); +} + +void LWEWebEnginePlugin::RegisterFormRepostDecidedCallback(WebEngineFormRepostDecidedCallback) {} +void LWEWebEnginePlugin::RegisterConsoleMessageReceivedCallback(WebEngineConsoleMessageReceivedCallback) {} +void LWEWebEnginePlugin::RegisterResponsePolicyDecidedCallback(WebEngineResponsePolicyDecidedCallback) {} +void LWEWebEnginePlugin::RegisterNavigationPolicyDecidedCallback(WebEngineNavigationPolicyDecidedCallback) {} +void LWEWebEnginePlugin::RegisterNewWindowPolicyDecidedCallback(WebEngineNewWindowPolicyDecidedCallback) {} +void LWEWebEnginePlugin::RegisterNewWindowCreatedCallback(WebEngineNewWindowCreatedCallback) {} +void LWEWebEnginePlugin::RegisterCertificateConfirmedCallback(WebEngineCertificateCallback) {} +void LWEWebEnginePlugin::RegisterSslCertificateChangedCallback(WebEngineCertificateCallback) {} +void LWEWebEnginePlugin::RegisterHttpAuthHandlerCallback(WebEngineHttpAuthHandlerCallback) {} +void LWEWebEnginePlugin::RegisterContextMenuShownCallback(WebEngineContextMenuShownCallback) {} +void LWEWebEnginePlugin::RegisterContextMenuHiddenCallback(WebEngineContextMenuHiddenCallback) {} +void LWEWebEnginePlugin::RegisterFullscreenEnteredCallback(WebEngineFullscreenEnteredCallback) {} +void LWEWebEnginePlugin::RegisterFullscreenExitedCallback(WebEngineFullscreenExitedCallback) {} +void LWEWebEnginePlugin::RegisterTextFoundCallback(WebEngineTextFoundCallback) {} +void LWEWebEnginePlugin::GetPlainTextAsynchronously(PlainTextReceivedCallback) {} +void LWEWebEnginePlugin::WebAuthenticationCancel() {} +void LWEWebEnginePlugin::RegisterWebAuthDisplayQRCallback(WebEngineWebAuthDisplayQRCallback) {} +void LWEWebEnginePlugin::RegisterWebAuthResponseCallback(WebEngineWebAuthResponseCallback) {} +void LWEWebEnginePlugin::RegisterFileChooserRequestedCallback(WebEngineFileChooserRequestedCallback) {} +void LWEWebEnginePlugin::RegisterWebProcessCrashedCallback(WebEngineWebProcessCrashedCallback) {} +void LWEWebEnginePlugin::RegisterUserMediaPermissionRequestCallback(WebEngineUserMediaPermissionRequestCallback) {} +void LWEWebEnginePlugin::RegisterDeviceConnectionChangedCallback(WebEngineDeviceConnectionChangedCallback) {} +void LWEWebEnginePlugin::RegisterDeviceListGetCallback(WebEngineDeviceListGetCallback) {} + +void LWEWebEnginePlugin::FeedMouseWheel(bool, int step, int x, int y) +{ + mContainer->DispatchMouseWheelEvent(x, y, step); +} + +void LWEWebEnginePlugin::SetVideoHole(bool, bool) {} + +} // namespace LWEDaliBridge diff --git a/lwe-web-engine-plugin/LWEWebEnginePlugin.h b/lwe-web-engine-plugin/LWEWebEnginePlugin.h new file mode 100644 index 0000000..f760e94 --- /dev/null +++ b/lwe-web-engine-plugin/LWEWebEnginePlugin.h @@ -0,0 +1,197 @@ +#ifndef DALI_LWE_WEB_ENGINE_PLUGIN_H +#define DALI_LWE_WEB_ENGINE_PLUGIN_H + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace LWEDaliBridge +{ +class LWEWebEngineSettings; +class LWEWebEngineBackForwardList; + +// Implements Dali::WebEnginePlugin on top of LWE::WebContainer. See +// plugins/web-engine-lwe/README.md for the LWE<->WebEnginePlugin mapping and +// plugins/web-engine-lwe/README.md for what has no LWE equivalent and is stubbed. +class LWEWebEnginePlugin : public Dali::WebEnginePlugin +{ +public: + LWEWebEnginePlugin(); + ~LWEWebEnginePlugin() override; + + void Create(uint32_t width, uint32_t height, const std::string& locale, const std::string& timezoneId) override; + void Create(uint32_t width, uint32_t height, uint32_t argc, char** argv) override; + void Destroy() override; + bool IsIncognito() const override; + Dali::WebEngineSettings& GetSettings() const override; + Dali::WebEngineBackForwardList& GetBackForwardList() const override; + void LoadUrl(const std::string& url) override; + std::string GetTitle() const override; + Dali::PixelData GetFavicon() const override; + Dali::NativeImagePtr GetNativeImage() override; + void ChangeOrientation(int orientation) override; + std::string GetUrl() const override; + void LoadHtmlString(const std::string& htmlString) override; + bool LoadHtmlStringOverrideCurrentEntry(const std::string& html, const std::string& basicUri, const std::string& unreachableUrl) override; + bool LoadContents(const int8_t* contents, uint32_t contentSize, const std::string& mimeType, const std::string& encoding, const std::string& baseUri) override; + void Reload() override; + bool ReloadWithoutCache() override; + void StopLoading() override; + void Suspend() override; + void Resume() override; + void SuspendNetworkLoading() override; + void ResumeNetworkLoading() override; + bool AddCustomHeader(const std::string& name, const std::string& value) override; + bool RemoveCustomHeader(const std::string& name) override; + uint32_t StartInspectorServer(uint32_t port) override; + bool StopInspectorServer() override; + void ScrollBy(int32_t deltaX, int32_t deltaY) override; + bool ScrollEdgeBy(int32_t deltaX, int32_t deltaY) override; + void SetScrollPosition(int32_t x, int32_t y) override; + Dali::Vector2 GetScrollPosition() const override; + Dali::Vector2 GetScrollSize() const override; + Dali::Vector2 GetContentSize() const override; + bool CanGoForward() override; + void GoForward() override; + bool CanGoBack() override; + void GoBack() override; + void EvaluateJavaScript(const std::string& script, JavaScriptMessageHandlerCallback resultHandler) override; + void AddJavaScriptMessageHandler(const std::string& exposedObjectName, JavaScriptMessageHandlerCallback handler) override; + void AddJavaScriptEntireMessageHandler(const std::string& exposedObjectName, JavaScriptEntireMessageHandlerCallback handler) override; + void RegisterJavaScriptAlertCallback(JavaScriptAlertCallback callback) override; + void JavaScriptAlertReply() override; + void RegisterJavaScriptConfirmCallback(JavaScriptConfirmCallback callback) override; + void JavaScriptConfirmReply(bool confirmed) override; + void RegisterJavaScriptPromptCallback(JavaScriptPromptCallback callback) override; + void JavaScriptPromptReply(const std::string& result) override; + std::unique_ptr CreateHitTest(int32_t x, int32_t y, Dali::WebEngineHitTest::HitTestMode mode) override; + bool CreateHitTestAsynchronously(int32_t x, int32_t y, Dali::WebEngineHitTest::HitTestMode mode, WebEngineHitTestCreatedCallback callback) override; + void ClearHistory() override; + void ClearAllTilesResources() override; + std::string GetUserAgent() const override; + void SetUserAgent(const std::string& userAgent) override; + void SetSize(uint32_t width, uint32_t height) override; + void SetDocumentBackgroundColor(Dali::Vector4 color) override; + void ClearTilesWhenHidden(bool cleared) override; + void SetTileCoverAreaMultiplier(float multiplier) override; + void EnableCursorByClient(bool enabled) override; + std::string GetSelectedText() const override; + bool SendTouchEvent(const Dali::TouchEvent& touch) override; + bool SendKeyEvent(const Dali::KeyEvent& event) override; + void EnableMouseEvents(bool enabled) override; + void EnableKeyEvents(bool enabled) override; + void SetFocus(bool focused) override; + bool SetImePositionAndAlignment(Dali::Vector2 position, int alignment) override; + void SetCursorThemeName(const std::string themeName) override; + void SetPageZoomFactor(float zoomFactor) override; + float GetPageZoomFactor() const override; + void SetTextZoomFactor(float zoomFactor) override; + float GetTextZoomFactor() const override; + float GetLoadProgressPercentage() const override; + void SetScaleFactor(float scaleFactor, Dali::Vector2 point) override; + float GetScaleFactor() const override; + void ActivateAccessibility(bool activated) override; + Dali::Devel::Accessibility::Address GetAccessibilityAddress() override; + bool SetVisibility(bool visible) override; + bool HighlightText(const std::string& text, FindOption options, uint32_t maxMatchCount) override; + void AddDynamicCertificatePath(const std::string& host, const std::string& certPath) override; + Dali::PixelData GetScreenshot(Dali::BoundsInteger viewArea, float scaleFactor) override; + bool GetScreenshotAsynchronously(Dali::BoundsInteger viewArea, float scaleFactor, ScreenshotCapturedCallback callback) override; + bool CheckVideoPlayingAsynchronously(VideoPlayingCallback callback) override; + void RegisterGeolocationPermissionCallback(GeolocationPermissionCallback callback) override; + void UpdateDisplayArea(Dali::BoundsInteger displayArea) override; + void EnableVideoHole(bool enabled) override; + bool SendHoverEvent(const Dali::HoverEvent& event) override; + bool SendWheelEvent(const Dali::WheelEvent& event) override; + void ExitFullscreen() override; + void RegisterFrameRenderedCallback(WebEngineFrameRenderedCallback callback) override; + void RegisterPageLoadStartedCallback(WebEnginePageLoadCallback callback) override; + void RegisterPageLoadInProgressCallback(WebEnginePageLoadCallback callback) override; + void RegisterPageLoadFinishedCallback(WebEnginePageLoadCallback callback) override; + void RegisterPageLoadErrorCallback(WebEnginePageLoadErrorCallback callback) override; + void RegisterScrollEdgeReachedCallback(WebEngineScrollEdgeReachedCallback callback) override; + void RegisterOverScrolledCallback(WebEngineOverScrolledCallback callback) override; + void RegisterUrlChangedCallback(WebEngineUrlChangedCallback callback) override; + void RegisterFormRepostDecidedCallback(WebEngineFormRepostDecidedCallback callback) override; + void RegisterConsoleMessageReceivedCallback(WebEngineConsoleMessageReceivedCallback callback) override; + void RegisterResponsePolicyDecidedCallback(WebEngineResponsePolicyDecidedCallback callback) override; + void RegisterNavigationPolicyDecidedCallback(WebEngineNavigationPolicyDecidedCallback callback) override; + void RegisterNewWindowPolicyDecidedCallback(WebEngineNewWindowPolicyDecidedCallback callback) override; + void RegisterNewWindowCreatedCallback(WebEngineNewWindowCreatedCallback callback) override; + void RegisterCertificateConfirmedCallback(WebEngineCertificateCallback callback) override; + void RegisterSslCertificateChangedCallback(WebEngineCertificateCallback callback) override; + void RegisterHttpAuthHandlerCallback(WebEngineHttpAuthHandlerCallback callback) override; + void RegisterContextMenuShownCallback(WebEngineContextMenuShownCallback callback) override; + void RegisterContextMenuHiddenCallback(WebEngineContextMenuHiddenCallback callback) override; + void RegisterFullscreenEnteredCallback(WebEngineFullscreenEnteredCallback callback) override; + void RegisterFullscreenExitedCallback(WebEngineFullscreenExitedCallback callback) override; + void RegisterTextFoundCallback(WebEngineTextFoundCallback callback) override; + void GetPlainTextAsynchronously(PlainTextReceivedCallback callback) override; + void WebAuthenticationCancel() override; + void RegisterWebAuthDisplayQRCallback(WebEngineWebAuthDisplayQRCallback callback) override; + void RegisterWebAuthResponseCallback(WebEngineWebAuthResponseCallback callback) override; + void RegisterFileChooserRequestedCallback(WebEngineFileChooserRequestedCallback callback) override; + void RegisterWebProcessCrashedCallback(WebEngineWebProcessCrashedCallback callback) override; + void RegisterUserMediaPermissionRequestCallback(WebEngineUserMediaPermissionRequestCallback callback) override; + void RegisterDeviceConnectionChangedCallback(WebEngineDeviceConnectionChangedCallback callback) override; + void RegisterDeviceListGetCallback(WebEngineDeviceListGetCallback callback) override; + void FeedMouseWheel(bool yDirection, int step, int x, int y) override; + void SetVideoHole(bool enabled, bool isWaylandWindow) override; + +private: + void EnsureInitialized(const std::string& locale, const std::string& timezoneId); + + // Runs on the DALi main/event thread only (via mEventThreadCallback). Uploads + // the latest finished frame into mNativeImage and notifies the frame-rendered + // callback. This is the ONLY place DALi objects are touched. + void OnFrameReadyMainThread(); + + LWE::WebContainer* mContainer; + std::unique_ptr mSettings; + std::unique_ptr mBackForwardList; + std::string mUserAgent; + float mPageZoomFactor; + float mTextZoomFactor; + float mScaleFactor; + bool mIncognito; + + // Render bridge (thread mode - see EnsureInitialized). LWE runs on its own + // dedicated thread; DALi objects may only be touched on the DALi main thread. + // Threading of the members below: + // - mWidth/mHeight: desired size, set on the main thread (SetSize), read on the + // LWE thread; atomic. + // - mRenderBuffer: owned and (re)sized ONLY on the LWE thread, inside the + // RegisterPreRenderingHandler callback, so its storage is never freed under a + // concurrent LWE-thread draw. + // - mReadyBuffer/mReadyWidth/mReadyHeight: the last finished frame, produced on + // the LWE thread (RegisterOnRenderedHandler) and consumed on the main thread + // (OnFrameReadyMainThread); guarded by mBufferMutex. + // - mNativeImage/mImageWidth/mImageHeight: touched ONLY on the main thread. + // - mEventThreadCallback: the LWE thread calls Trigger(); DALi then runs + // OnFrameReadyMainThread on the main thread. Constructed on the main thread. + std::atomic mWidth{0}; + std::atomic mHeight{0}; + std::vector mRenderBuffer; + + std::mutex mBufferMutex; + std::vector mReadyBuffer; + uint32_t mReadyWidth{0}; + uint32_t mReadyHeight{0}; + + Dali::NativeImagePtr mNativeImage; + uint32_t mImageWidth{0}; + uint32_t mImageHeight{0}; + + std::unique_ptr mEventThreadCallback; + WebEngineFrameRenderedCallback mFrameRenderedCallback; +}; + +} // namespace LWEDaliBridge + +#endif // DALI_LWE_WEB_ENGINE_PLUGIN_H diff --git a/lwe-web-engine-plugin/LWEWebEngineSettings.cpp b/lwe-web-engine-plugin/LWEWebEngineSettings.cpp new file mode 100644 index 0000000..b05b1a4 --- /dev/null +++ b/lwe-web-engine-plugin/LWEWebEngineSettings.cpp @@ -0,0 +1,148 @@ +#include "LWEWebEngineSettings.h" + +namespace LWEDaliBridge +{ + +LWEWebEngineSettings::LWEWebEngineSettings(LWE::WebContainer* container) +: mContainer(container) +, mDefaultFontSize(LWE_DEFAULT_FONT_SIZE) +, mJavaScriptEnabled(true) +, mAutoFittingEnabled(true) +, mPluginsEnabled(false) +, mPrivateBrowsingEnabled(false) +, mLinkMagnifierEnabled(false) +, mKeypadWithoutUserActionUsed(true) +, mAutofillPasswordFormEnabled(false) +, mFormCandidateDataEnabled(false) +, mTextSelectionEnabled(true) +, mTextAutosizingEnabled(false) +, mArrowScrollEnabled(true) +, mClipboardEnabled(true) +, mImePanelEnabled(true) +, mImagesLoadedAutomatically(true) +, mZoomForced(false) +, mTextZoomEnabled(true) +, mImeStyle(0) +, mDefaultTextEncodingName("UTF-8") +{ +} + +void LWEWebEngineSettings::AllowMixedContents(bool) {} + +void LWEWebEngineSettings::EnableSpatialNavigation(bool enabled) +{ + LWE::Settings settings = mContainer->GetSettings(); + settings.SetUseSpatialNavigation(enabled); + mContainer->SetSettings(settings); +} + +uint32_t LWEWebEngineSettings::GetDefaultFontSize() const +{ + return mDefaultFontSize; +} + +void LWEWebEngineSettings::SetDefaultFontSize(uint32_t size) +{ + mDefaultFontSize = size; + LWE::Settings settings = mContainer->GetSettings(); + settings.setDefaultFontSize(static_cast(size)); + mContainer->SetSettings(settings); +} + +void LWEWebEngineSettings::EnableWebSecurity(bool enabled) +{ + LWE::Settings settings = mContainer->GetSettings(); + settings.SetWebSecurityMode(enabled ? LWE::WebSecurityMode::Enable : LWE::WebSecurityMode::Disable); + mContainer->SetSettings(settings); +} + +void LWEWebEngineSettings::EnableCacheBuilder(bool) {} +void LWEWebEngineSettings::UseScrollbarThumbFocusNotifications(bool) {} +void LWEWebEngineSettings::EnableDoNotTrack(bool) {} +void LWEWebEngineSettings::AllowFileAccessFromExternalUrl(bool) {} + +bool LWEWebEngineSettings::IsJavaScriptEnabled() const { return mJavaScriptEnabled; } +void LWEWebEngineSettings::EnableJavaScript(bool enabled) { mJavaScriptEnabled = enabled; } + +bool LWEWebEngineSettings::IsAutoFittingEnabled() const { return mAutoFittingEnabled; } +void LWEWebEngineSettings::EnableAutoFitting(bool enabled) { mAutoFittingEnabled = enabled; } + +bool LWEWebEngineSettings::ArePluginsEnabled() const { return mPluginsEnabled; } +void LWEWebEngineSettings::EnablePlugins(bool enabled) { mPluginsEnabled = enabled; } + +bool LWEWebEngineSettings::IsPrivateBrowsingEnabled() const { return mPrivateBrowsingEnabled; } +void LWEWebEngineSettings::EnablePrivateBrowsing(bool enabled) { mPrivateBrowsingEnabled = enabled; } + +bool LWEWebEngineSettings::IsLinkMagnifierEnabled() const { return mLinkMagnifierEnabled; } +void LWEWebEngineSettings::EnableLinkMagnifier(bool enabled) { mLinkMagnifierEnabled = enabled; } + +bool LWEWebEngineSettings::IsKeypadWithoutUserActionUsed() const { return mKeypadWithoutUserActionUsed; } +void LWEWebEngineSettings::UseKeypadWithoutUserAction(bool used) { mKeypadWithoutUserActionUsed = used; } + +bool LWEWebEngineSettings::IsAutofillPasswordFormEnabled() const { return mAutofillPasswordFormEnabled; } +void LWEWebEngineSettings::EnableAutofillPasswordForm(bool enabled) { mAutofillPasswordFormEnabled = enabled; } + +bool LWEWebEngineSettings::IsFormCandidateDataEnabled() const { return mFormCandidateDataEnabled; } +void LWEWebEngineSettings::EnableFormCandidateData(bool enabled) { mFormCandidateDataEnabled = enabled; } + +bool LWEWebEngineSettings::IsTextSelectionEnabled() const { return mTextSelectionEnabled; } +void LWEWebEngineSettings::EnableTextSelection(bool enabled) { mTextSelectionEnabled = enabled; } + +bool LWEWebEngineSettings::IsTextAutosizingEnabled() const { return mTextAutosizingEnabled; } +void LWEWebEngineSettings::EnableTextAutosizing(bool enabled) { mTextAutosizingEnabled = enabled; } + +bool LWEWebEngineSettings::IsArrowScrollEnabled() const { return mArrowScrollEnabled; } +void LWEWebEngineSettings::EnableArrowScroll(bool enable) { mArrowScrollEnabled = enable; } + +bool LWEWebEngineSettings::IsClipboardEnabled() const { return mClipboardEnabled; } +void LWEWebEngineSettings::EnableClipboard(bool enabled) { mClipboardEnabled = enabled; } + +bool LWEWebEngineSettings::IsImePanelEnabled() const { return mImePanelEnabled; } +void LWEWebEngineSettings::EnableImePanel(bool enabled) { mImePanelEnabled = enabled; } + +void LWEWebEngineSettings::AllowScriptsOpenWindows(bool) {} + +bool LWEWebEngineSettings::AreImagesLoadedAutomatically() const { return mImagesLoadedAutomatically; } +void LWEWebEngineSettings::AllowImagesLoadAutomatically(bool automatic) { mImagesLoadedAutomatically = automatic; } + +std::string LWEWebEngineSettings::GetDefaultTextEncodingName() const { return mDefaultTextEncodingName; } +void LWEWebEngineSettings::SetDefaultTextEncodingName(const std::string& defaultTextEncodingName) +{ + mDefaultTextEncodingName = defaultTextEncodingName; +} + +bool LWEWebEngineSettings::SetViewportMetaTag(bool) { return true; } + +bool LWEWebEngineSettings::SetForceZoom(bool enable) +{ + mZoomForced = enable; + return true; +} +bool LWEWebEngineSettings::IsZoomForced() const { return mZoomForced; } + +bool LWEWebEngineSettings::SetTextZoomEnabled(bool enable) +{ + mTextZoomEnabled = enable; + return true; +} +bool LWEWebEngineSettings::IsTextZoomEnabled() const { return mTextZoomEnabled; } + +void LWEWebEngineSettings::SetExtraFeature(const std::string& feature, bool enable) +{ + mExtraFeatures[feature] = enable; +} + +bool LWEWebEngineSettings::IsExtraFeatureEnabled(const std::string& feature) const +{ + auto it = mExtraFeatures.find(feature); + return it != mExtraFeatures.end() && it->second; +} + +void LWEWebEngineSettings::SetImeStyle(int style) { mImeStyle = style; } +int LWEWebEngineSettings::GetImeStyle() const { return mImeStyle; } + +void LWEWebEngineSettings::SetDefaultAudioInputDevice(const std::string&) const {} + +void LWEWebEngineSettings::EnableDragAndDrop(bool) {} + +} // namespace LWEDaliBridge diff --git a/lwe-web-engine-plugin/LWEWebEngineSettings.h b/lwe-web-engine-plugin/LWEWebEngineSettings.h new file mode 100644 index 0000000..359f7e2 --- /dev/null +++ b/lwe-web-engine-plugin/LWEWebEngineSettings.h @@ -0,0 +1,99 @@ +#ifndef DALI_LWE_WEB_ENGINE_SETTINGS_H +#define DALI_LWE_WEB_ENGINE_SETTINGS_H + +#include +#include + +#include +#include + +namespace LWEDaliBridge +{ +// Wraps LWE::Settings for the subset of WebEngineSettings that LWE actually +// supports (spatial navigation, web security mode, default font size). The +// rest have no LWE counterpart and are tracked here only so the getters +// return whatever was last set; see plugins/web-engine-lwe/README.md. +class LWEWebEngineSettings : public Dali::WebEngineSettings +{ +public: + explicit LWEWebEngineSettings(LWE::WebContainer* container); + + void AllowMixedContents(bool allowed) override; + void EnableSpatialNavigation(bool enabled) override; + uint32_t GetDefaultFontSize() const override; + void SetDefaultFontSize(uint32_t size) override; + void EnableWebSecurity(bool enabled) override; + void EnableCacheBuilder(bool enabled) override; + void UseScrollbarThumbFocusNotifications(bool used) override; + void EnableDoNotTrack(bool enabled) override; + void AllowFileAccessFromExternalUrl(bool allowed) override; + bool IsJavaScriptEnabled() const override; + void EnableJavaScript(bool enabled) override; + bool IsAutoFittingEnabled() const override; + void EnableAutoFitting(bool enabled) override; + bool ArePluginsEnabled() const override; + void EnablePlugins(bool enabled) override; + bool IsPrivateBrowsingEnabled() const override; + void EnablePrivateBrowsing(bool enabled) override; + bool IsLinkMagnifierEnabled() const override; + void EnableLinkMagnifier(bool enabled) override; + bool IsKeypadWithoutUserActionUsed() const override; + void UseKeypadWithoutUserAction(bool used) override; + bool IsAutofillPasswordFormEnabled() const override; + void EnableAutofillPasswordForm(bool enabled) override; + bool IsFormCandidateDataEnabled() const override; + void EnableFormCandidateData(bool enabled) override; + bool IsTextSelectionEnabled() const override; + void EnableTextSelection(bool enabled) override; + bool IsTextAutosizingEnabled() const override; + void EnableTextAutosizing(bool enabled) override; + bool IsArrowScrollEnabled() const override; + void EnableArrowScroll(bool enable) override; + bool IsClipboardEnabled() const override; + void EnableClipboard(bool enabled) override; + bool IsImePanelEnabled() const override; + void EnableImePanel(bool enabled) override; + void AllowScriptsOpenWindows(bool allowed) override; + bool AreImagesLoadedAutomatically() const override; + void AllowImagesLoadAutomatically(bool automatic) override; + std::string GetDefaultTextEncodingName() const override; + void SetDefaultTextEncodingName(const std::string& defaultTextEncodingName) override; + bool SetViewportMetaTag(bool enable) override; + bool SetForceZoom(bool enable) override; + bool IsZoomForced() const override; + bool SetTextZoomEnabled(bool enable) override; + bool IsTextZoomEnabled() const override; + void SetExtraFeature(const std::string& feature, bool enable) override; + bool IsExtraFeatureEnabled(const std::string& feature) const override; + void SetImeStyle(int style) override; + int GetImeStyle() const override; + void SetDefaultAudioInputDevice(const std::string& deviceId) const override; + void EnableDragAndDrop(bool enable) override; + +private: + LWE::WebContainer* mContainer; + uint32_t mDefaultFontSize; + bool mJavaScriptEnabled; + bool mAutoFittingEnabled; + bool mPluginsEnabled; + bool mPrivateBrowsingEnabled; + bool mLinkMagnifierEnabled; + bool mKeypadWithoutUserActionUsed; + bool mAutofillPasswordFormEnabled; + bool mFormCandidateDataEnabled; + bool mTextSelectionEnabled; + bool mTextAutosizingEnabled; + bool mArrowScrollEnabled; + bool mClipboardEnabled; + bool mImePanelEnabled; + bool mImagesLoadedAutomatically; + bool mZoomForced; + bool mTextZoomEnabled; + int mImeStyle; + std::string mDefaultTextEncodingName; + std::map mExtraFeatures; +}; + +} // namespace LWEDaliBridge + +#endif // DALI_LWE_WEB_ENGINE_SETTINGS_H diff --git a/lwe-web-engine-plugin/README.md b/lwe-web-engine-plugin/README.md new file mode 100644 index 0000000..7303d98 --- /dev/null +++ b/lwe-web-engine-plugin/README.md @@ -0,0 +1,114 @@ +# LWE(Lightweight Web Engine) 기반 Windows WebEnginePlugin 구현 + +이 디렉터리는 Windows에서 `Dali::WebEnginePlugin`을 LWE(Lightweight Web Engine, +`Starfish.dll`)로 구현한 어댑터입니다. **소스는 dali-adaptor가 아닌 이 저장소 +(windows-dependencies)에 있으며**, 별도 플러그인 DLL +`dali2-web-engine-lwe-plugin.dll` 로 빌드되어 dali-envin(= dali2-adaptor.dll 옆)에 +설치됩니다. dali-adaptor의 `web-engine-impl.cpp`가 런타임에 +`libdali2-web-engine-lwe-plugin.so`를 `dlopen`하면 Windows dlfcn shim이 이를 +`dali2-web-engine-lwe-plugin.dll`로 변환해 로드하므로, dali-adaptor에는 어떤 +빌드타임 의존성도 없습니다. 플러그인 진입점(`CreateWebEnginePlugin` 등 4개)은 +`web-engine-lwe-plugin.cpp`에 있습니다. + +## 빌드 방법 + +```powershell +# 선행: install.ps1(SDK+Starfish) -> dali-core -> dali-adaptor 빌드 후 +.uild.ps1 # Release (기본) +.uild.ps1 -Configuration Debug +``` + +플러그인은 dali-env에 **설치된** dali2-core/dali2-adaptor 패키지(public/devel +API 헤더 + import lib)와 WindowsDependenciesSDK의 LWE SDK만 사용한다. + +## 빌드 의존성 + +- `LWEWebView.h`(LWE 공개 API), `Starfish.lib`, `Starfish.dll` 및 LWE 런타임 DLL들은 + windows-dependencies의 `vcpkg-script/setup-starfish.ps1`이 사내 저장소 + (`github.sec.samsung.net/lws/starfish`)를 클론·빌드해 `WindowsDependenciesSDK`의 + `include/`, `lib/`, `bin/`으로 설치합니다. LWE 소스/바이너리를 저장소에 직접 + 벤더링하는 것은 금지되어 있어, tizenvg와 같은 소스 빌드 방식을 따릅니다. +- `build/tizen/plugins/CMakeLists.txt`가 WINDOWS 프로파일에서 `LWEWebView.h`와 + `Starfish.lib`를 찾으면(`CMAKE_PREFIX_PATH`의 SDK 경로) 이 플러그인을 빌드하고, + 없으면 플러그인 빌드를 건너뜁니다. +- LWE 공개 API가 `std::string` 등 CRT/STL 객체를 DLL 경계로 주고받으므로, + Starfish의 빌드 구성(`setup-starfish.ps1 -Mode`, 기본 debug)과 dali의 빌드 구성 + (`build\windows\build.ps1 -Configuration Debug`)을 반드시 맞춰야 합니다. +- **주의**: starfish master의 `build/windows.cmake`는 ARCH=x86만 허용합니다. dali는 + x64이므로 x64를 지원하는 브랜치/리비전을 `setup-starfish.ps1 -Revision`으로 + 고정해야 합니다 (이전에 쓰던 x64 Debug 드롭이 존재하므로 해당 브랜치를 LWS 팀에 + 확인할 것). + +## 임시 디버그 계측 (TEMP DEBUG) + +`LWEWebEnginePlugin.cpp`에는 화살표 키 입력 이슈 조사용 임시 계측이 남아 있습니다: +`DbgLog()`(고정 경로 `d:\lwe_webview.log`에 파일 로깅)와 `CrashStackFilter`(dbghelp 기반 +크래시 스택 덤프, `AddVectoredExceptionHandler`). 이슈 해결 후 반드시 제거할 것. + +## LWE에 대응 기능이 없어 no-op/기본값으로 남긴 것 + +### WebEnginePlugin (`LWEWebEnginePlugin`) +- `IsIncognito` - 항상 false +- `GetFavicon`, `GetScreenshot`, `GetScreenshotAsynchronously` - 아직 `Dali::PixelData()` 기본값 + (실제 dali-core/dali-adaptor가 정적으로 링크되어 있으므로 `PixelData::New()` 호출은 가능함 - + 아직 구현 안 됨) +- `GetNativeImage` - **구현됨.** `Create()`/`SetSize()`에서 `Dali::NativeImage::New()`로 + 고정 크기 버퍼를 만들고, `WebContainer::RegisterPreRenderingHandler`로 LWE에 렌더 대상 + 버퍼를 알려준 뒤 `RegisterOnRenderedHandler`에서 `Dali::DevelNativeImage::SetPixels()`로 + 매 프레임 복사한다. **주의**: 픽셀 포맷을 `Pixel::BGRA8888`로 가정했는데(LWE의 Cairo + 기반 Windows 캔버스가 `CAIRO_FORMAT_ARGB32`를 쓰고 이는 리틀엔디언에서 메모리상 + BGRA 순서라는 가정), 실제 화면에 그려서 색상이 맞는지 육안 검증은 아직 안 됨 - 만약 + 색이 반전되어 보이면 `LWEWebEnginePlugin.cpp`의 `kRenderBufferPixelFormat`을 + `Pixel::RGBA8888`로 바꿔볼 것. 또한 리사이즈 시마다 `NativeImage`를 새로 만드므로 + (public API에 리사이즈가 없음) DALi 쪽에서 이미지 핸들 교체를 인지하는지 확인 필요. +- `LoadContents`, `ReloadWithoutCache`(단순 Reload로 근사), `AddCustomHeader`/`RemoveCustomHeader`, + `StartInspectorServer`/`StopInspectorServer`, `ScrollEdgeBy`, `ClearAllTilesResources`, + `ChangeOrientation`, `SetDocumentBackgroundColor`, `ClearTilesWhenHidden`, + `SetTileCoverAreaMultiplier`, `EnableCursorByClient`, `GetSelectedText`, + `EnableMouseEvents`/`EnableKeyEvents`, `SetImePositionAndAlignment`, `SetCursorThemeName`, + `GetLoadProgressPercentage`, `ActivateAccessibility`/`GetAccessibilityAddress`, + `SetVisibility`(항상 true), `HighlightText`, `AddDynamicCertificatePath`, + `CheckVideoPlayingAsynchronously`, `EnableVideoHole`/`SetVideoHole`, `ExitFullscreen`, + `CreateHitTest`/`CreateHitTestAsynchronously`(항상 nullptr/false) +- Register 계열 중 실제로 LWE 콜백에 연결한 것: `PageLoadStarted`, `PageLoadFinished`, + `PageLoadError`, `UrlChanged`, `JavaScriptAlert`(Confirm/Prompt는 LWE에 대응 콜백 없어 + 등록만 받고 트리거 안 함), `FrameRendered`(비어 있음). 나머지 Register* 전부 + (ScrollEdgeReached, OverScrolled, FormRepostDecided, ConsoleMessageReceived, + Response/Navigation/NewWindowPolicyDecided, NewWindowCreated, CertificateConfirmed, + SslCertificateChanged, HttpAuthHandler, ContextMenuShown/Hidden, FullscreenEntered/Exited, + TextFound, WebAuthDisplayQR/Response, FileChooserRequested, WebProcessCrashed, + UserMediaPermissionRequest, DeviceConnectionChanged/DeviceListGet, GeolocationPermission)는 + 콜백을 저장만 하고 호출되지 않음(LWE에 대응 이벤트가 없음) +- `SendTouchEvent`/`SendHoverEvent` - 첫 번째 포인트만 마우스 이벤트로 근사 변환 + (LWE의 멀티터치 `DispatchTouchStart/Move/EndEvent` API는 아직 연결 안 함) + +### WebEngineSettings +LWE가 실제로 지원하는 것(`EnableSpatialNavigation`, `SetDefaultFontSize`, +`EnableWebSecurity`)만 `LWE::Settings`에 위임. 나머지 30여 개 설정 항목은 멤버 변수에 +저장만 하고 실제 엔진 동작에 영향 없음. + +### WebEngineBackForwardList +`CanGoBack`/`GoBack` 등은 `LWEWebEnginePlugin`이 `WebContainer`에 직접 위임하지만, +이 클래스(히스토리 아이템 열거) 자체는 전부 빈 값/nullptr - LWE에 히스토리 아이템 +열거 API가 없음. + +### WebEngineContext +LWE는 컨텍스트 단위(프록시/캐시/스토리지) API가 전혀 없고 전부 `WebContainer` 단위라서, +이 클래스는 호출값을 멤버에 저장만 하고 어떤 `WebContainer`에도 영향을 주지 않음. + +### WebEngineCookieManager +`ClearCookies`만 실제로 `LWE::CookieManager`에 위임. accept policy/영구 저장소는 저장만. + +## 다음에 볼 사람을 위한 리스크 요약 +1. **GetNativeImage 렌더 브릿지 구현됨, 육안 검증 필요** - 픽셀 포맷(BGRA vs RGBA) 가정을 + 실제 화면 렌더로 확인할 것 (위 GetNativeImage 항목 참고). +2. **Debug/Release CRT 혼용 주의** - 플러그인 DLL과 Starfish.dll의 빌드 구성이 다르면 + `std::string`을 주고받는 LWE API 경계에서 크래시 위험. 현재 Starfish SDK가 Debug만 + 제공되므로 dali 전체를 Debug로 빌드해서 맞출 것 (위 빌드 의존성 참고). +3. LWE 런타임 DLL 중 `harfbuzz.dll`, `cairo-2.dll`, `fontconfig-1.dll` 등은 vcpkg가 제공하는 + DLL과 이름이 겹치는데, SDK 스테이징에서 **vcpkg 쪽을 우선**하고 겹치는 LWE DLL은 + 복사하지 않는다(windows-dependencies의 `setup-starfish.ps1`). Windows는 프로세스당 + 같은 이름의 DLL을 하나만 로드하므로, LWE(Debug 빌드)가 vcpkg Release DLL과 조합될 때 + 문제가 생기면 이 지점부터 의심할 것. +4. 통합 테스트는 정적 검증(빌드 성공) 수준까지만 했고, 실제 프로세스에서 + `WebEngine::New()` → `LoadUrl` → 렌더까지 실행해서 확인하지는 못했음. diff --git a/lwe-web-engine-plugin/build.ps1 b/lwe-web-engine-plugin/build.ps1 new file mode 100644 index 0000000..b0cdbb4 --- /dev/null +++ b/lwe-web-engine-plugin/build.ps1 @@ -0,0 +1,66 @@ +[CmdletBinding()] +param( + [ValidateSet("Debug", "Release")] + [string]$Configuration = "Release", + [string]$VcpkgRoot = "", + [switch]$Clean, + [int]$Jobs = 8 +) + +# Builds the DALi LWE web-engine plugin (dali2-web-engine-lwe-plugin.dll) and +# installs it into dali-env\bin next to dali2-adaptor.dll. +# +# Prerequisites (in order): +# 1. windows-dependencies\install.ps1 (WindowsDependenciesSDK + Starfish/LWE) +# 2. dali-core\build\windows\build.ps1 +# 3. dali-adaptor\build\windows\build.ps1 +# Then run this script. dali-ui does not need to be built first. + +$ErrorActionPreference = "Stop" + +# LWE's public API passes std::string across the DLL boundary, so the plugin, +# DALi and Starfish must share one configuration. The Starfish SDK is +# currently built Release-only; a Debug plugin would crash at runtime. +if($Configuration -eq "Debug") +{ + throw "The LWE plugin currently supports Release only (the Starfish SDK is a Release build)." +} + +$PluginRoot = $PSScriptRoot +$WindowsDependenciesRoot = Split-Path -Parent $PluginRoot +$CommonScript = Join-Path $WindowsDependenciesRoot "vcpkg-script\dali-build-common.ps1" +if(-not (Test-Path -LiteralPath $CommonScript)) +{ + throw "dali-build-common.ps1 not found: $CommonScript" +} +. $CommonScript + +$Context = New-DaliBuildContext -WindowsDependenciesRoot $WindowsDependenciesRoot -VcpkgRoot $VcpkgRoot +Initialize-DaliBuildEnvironment -Context $Context + +$CorePackage = Join-Path $Context.InstallPrefix "share\dali2-core" +$AdaptorPackage = Join-Path $Context.InstallPrefix "share\dali2-adaptor" +Assert-DaliPaths -Paths @( + (Join-Path $CorePackage "dali2-core-config.cmake"), + (Join-Path $AdaptorPackage "dali2-adaptor-config.cmake") +) -Description "DALi package; build dali-core and dali-adaptor first" + +Assert-DaliPaths -Paths @( + (Join-Path $Context.SdkRoot "include\LWEWebView.h"), + (Join-Path $Context.SdkRoot "lib\Starfish.lib") +) -Description "LWE (Starfish) SDK; install it into WindowsDependenciesSDK first" + +$Arguments = (Get-DaliCommonCMakeArguments -Context $Context -Configuration $Configuration) + @( + "-Ddali2-core_DIR=$CorePackage", + "-Ddali2-adaptor_DIR=$AdaptorPackage" +) + +Invoke-DaliCMakeProject ` + -Name "lwe-web-engine-plugin" ` + -SourceDirectory $PluginRoot ` + -BuildDirectory (Join-Path $PluginRoot "_build\windows") ` + -ConfigureArguments $Arguments ` + -Clean:$Clean ` + -Jobs $Jobs + +Write-Host "`ndali2-web-engine-lwe-plugin.dll installed in $($Context.InstallPrefix)\bin." -ForegroundColor Green diff --git a/lwe-web-engine-plugin/patches/escargot-0001-skip-unumrf-on-windows-system-icu.patch b/lwe-web-engine-plugin/patches/escargot-0001-skip-unumrf-on-windows-system-icu.patch new file mode 100644 index 0000000..bcd5eff --- /dev/null +++ b/lwe-web-engine-plugin/patches/escargot-0001-skip-unumrf-on-windows-system-icu.patch @@ -0,0 +1,32 @@ +diff --git a/src/intl/IntlNumberFormat.cpp b/src/intl/IntlNumberFormat.cpp +index 9ce5b87..c00ccca 100644 +--- a/src/intl/IntlNumberFormat.cpp ++++ b/src/intl/IntlNumberFormat.cpp +@@ -634,6 +634,11 @@ void IntlNumberFormat::initialize(ExecutionState& state, Object* numberFormat, V + if (true + #if defined(ENABLE_RUNTIME_ICU_BINDER) + && versionArray[0] >= 68 ++#endif ++#if defined(ENABLE_RUNTIME_ICU_BINDER) && defined(_WIN32) ++ // Windows' system icu.dll reports ICU >= 68 but does not export the ++ // unumrf_* (NumberRangeFormatter) C API; skip range support there. ++ && false + #endif + ) { + data->numberRangeFormatter = unumrf_openForSkeletonWithCollapseAndIdentityFallback((UChar*)skeleton.data(), skeleton.length(), +diff --git a/src/intl/IntlPluralRules.cpp b/src/intl/IntlPluralRules.cpp +index f7cd732..675609a 100644 +--- a/src/intl/IntlPluralRules.cpp ++++ b/src/intl/IntlPluralRules.cpp +@@ -129,6 +129,11 @@ IntlPluralRulesObject::IntlPluralRulesObject(ExecutionState& state, Object* prot + if (true + #if defined(ENABLE_RUNTIME_ICU_BINDER) + && versionArray[0] >= 68 ++#endif ++#if defined(ENABLE_RUNTIME_ICU_BINDER) && defined(_WIN32) ++ // Windows' system icu.dll reports ICU >= 68 but does not export the ++ // unumrf_* (NumberRangeFormatter) C API; skip range support there. ++ && false + #endif + ) { + m_icuNumberRangeFormat = unumrf_openForSkeletonWithCollapseAndIdentityFallback((UChar*)skeleton.data(), skeleton.length(), diff --git a/lwe-web-engine-plugin/patches/starfish-0001-x64-vcpkg-build-and-windows-thread-mode.patch b/lwe-web-engine-plugin/patches/starfish-0001-x64-vcpkg-build-and-windows-thread-mode.patch new file mode 100644 index 0000000..a19fa67 --- /dev/null +++ b/lwe-web-engine-plugin/patches/starfish-0001-x64-vcpkg-build-and-windows-thread-mode.patch @@ -0,0 +1,164 @@ +diff --git a/build/windows.cmake b/build/windows.cmake +index 470fedeae..8153f206a 100644 +--- a/build/windows.cmake ++++ b/build/windows.cmake +@@ -14,6 +14,14 @@ SET (TOOL_ROOT ${STARFISH_ROOT}/tool) + ####################################################### + IF (${ARCH} STREQUAL "x86") + SET(WINDOWS_ARCH "Win32") ++ELSEIF (${ARCH} STREQUAL "x64") ++ # x64 builds take third-party dependencies from vcpkg instead of the ++ # vendored Win32-only projects/binaries. Pass VCPKG_INSTALLED_DIR ++ # (e.g. .../vcpkg/installed/x64-windows). ++ SET(WINDOWS_ARCH "x64") ++ IF (NOT DEFINED VCPKG_INSTALLED_DIR) ++ MESSAGE(FATAL_ERROR "x64 requires -DVCPKG_INSTALLED_DIR=/installed/x64-windows") ++ ENDIF() + ELSE() + MESSAGE(FATAL_ERROR "Unsupported arch") + ENDIF() +@@ -24,8 +32,11 @@ ELSE() + SET(WINDOWS_MODE "Release") + ENDIF() + ++SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DEBUG") ++ + SET(STARFISH_CXXFLAGS + /std:c++14 ++ /Zi + /Oy- + /fp:strict + /Zc:__cplusplus +@@ -73,7 +84,6 @@ SET(STARFISH_DEFINES + -DSTARFISH_ENABLE_CSS_WEBKIT_TRANSFORM_PREFIX + -DSTARFISH_ENABLE_CSS_WEBKIT_TRANSITION_PREFIX + -DSTARFISH_ENABLE_ANIMATION +- -DSTARFISH_ENABLE_WEBSOCKET + -DSTARFISH_IGNORE_CROSS_ORIGIN + -DSTARFISH_IGNORE_SSL_VERIFYPEER + -DSTARFISH_BACKEND_STR="windows" +@@ -124,6 +134,17 @@ SET (STARFISH_INCLUDE_DIRS + ${THIRD_PARTY_ROOT}/windows/glew/include + ) + ++IF (${ARCH} STREQUAL "x64") ++ # vcpkg headers; the vendored third_party/windows include dirs above are ++ # empty (uninitialised submodules) so they are harmless to keep listed. ++ SET (STARFISH_INCLUDE_DIRS ++ ${STARFISH_INCLUDE_DIRS} ++ ${VCPKG_INSTALLED_DIR}/include ++ ${VCPKG_INSTALLED_DIR}/include/cairo ++ ${VCPKG_INSTALLED_DIR}/include/harfbuzz ++ ) ++ENDIF() ++ + SET (STARFISH_DEPENDENCIES) + SET (STARFISH_THIRD_PARTY_INCLUDE_DIRS) + SET (STARFISH_THIRD_PARTY_LINK_LIBRARIES) +@@ -201,6 +222,7 @@ SET (STARFISH_INCLUDE_DIRS + # THIRD_PARTY (build outside) + ####################################################### + ++IF (${ARCH} STREQUAL "x86") + # libpng + SET (LIBPNG_TARGET ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libpng.lib ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libpng16.dll ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/zlib1.dll) + ADD_CUSTOM_COMMAND (OUTPUT ${LIBPNG_TARGET} +@@ -343,6 +365,28 @@ SET (STARFISH_DEPENDENCIES + windows_pthread + copy_glewdll + ) ++ELSE() # x64: link the vcpkg-built dependencies instead of the vendored ones ++SET (STARFISH_THIRD_PARTY_INCLUDE_DIRS ${VCPKG_INSTALLED_DIR}/include) ++SET (STARFISH_THIRD_PARTY_LINK_LIBRARIES ${STARFISH_THIRD_PARTY_LINK_LIBRARIES} ++ ${VCPKG_INSTALLED_DIR}/lib/libpng16.lib ++ ${VCPKG_INSTALLED_DIR}/lib/zlib.lib ++ ${VCPKG_INSTALLED_DIR}/lib/libcurl.lib ++ ${VCPKG_INSTALLED_DIR}/lib/gif.lib ++ ${VCPKG_INSTALLED_DIR}/lib/harfbuzz.lib ++ ${VCPKG_INSTALLED_DIR}/lib/cairo.lib ++ ${VCPKG_INSTALLED_DIR}/lib/fontconfig.lib ++ ${VCPKG_INSTALLED_DIR}/lib/freetype.lib ++ ${VCPKG_INSTALLED_DIR}/lib/websockets.lib ++ # vcpkg 2019.09's openssl port is 1.0.2 (libeay32/ssleay32); the LWE code ++ # uses the matching 1.0-era API (CRYPTO_set_locking_callback, SHA*_CTX). ++ ${VCPKG_INSTALLED_DIR}/lib/libeay32.lib ++ ${VCPKG_INSTALLED_DIR}/lib/ssleay32.lib ++ ${VCPKG_INSTALLED_DIR}/lib/pthreadVC3.lib ++ ${VCPKG_INSTALLED_DIR}/lib/glew32.lib ++ ws2_32.lib ++ crypt32.lib ++) ++ENDIF() + + ####################################################### + # THIRD_PARTY (build here) +diff --git a/src/platform/message_loop/MessageLoopWindows.cpp b/src/platform/message_loop/MessageLoopWindows.cpp +index 45ae92170..4a8e327a1 100644 +--- a/src/platform/message_loop/MessageLoopWindows.cpp ++++ b/src/platform/message_loop/MessageLoopWindows.cpp +@@ -319,11 +319,32 @@ void MessageLoopWindows::init() + needsInit = false; + pthread_mutex_init(&g_threadSyncFlowControler, NULL); + } ++ // Ensure this thread owns a Win32 message queue before other threads ++ // PostThreadMessage to it (the queue is lazily created on the first ++ // message API call, and posts to a queue-less thread are dropped). ++ MSG message; ++ PeekMessage(&message, NULL, WM_USER, WM_USER, PM_NOREMOVE); + } + + void MessageLoopWindows::run() + { +- STARFISH_UNIMPLEMENTED(); ++ // Message pump for the dedicated LWE thread (thread mode). The winform ++ // shell instead pumps through the exported startMessageLoop(), which ++ // resolves the loop instance from its WebContainer; here each idler ++ // message carries its owning loop in IdlerData::m_ml. ++ MSG message; ++ BOOL ret; ++ while ((ret = GetMessage(&message, NULL, 0, 0)) != 0) { ++ if (ret == -1) { ++ break; ++ } ++ MessageLoopWindows* target = nullptr; ++ if (message.message == IDLE_MESSAGE || ++ message.message == IDLE_MESSAGE_FROM_OTHER_THREAD) { ++ target = ((IdlerData*)message.wParam)->m_ml; ++ } ++ MessageLoopImpl::processMessage(target, message); ++ } + } + + void MessageLoopWindows::stop() +@@ -340,7 +361,10 @@ void MessageLoopWindows::runOnMainThreadSync( + } + + pthread_mutex_lock(&g_threadSyncFlowControler); +- PostMessage(NULL, IDLE_MESSAGE_FROM_OTHER_THREAD_SYNC, (size_t)&functor, 0); ++ // PostMessage(NULL, ...) would post to the *calling* thread's queue; ++ // the functor must run on the LWE main thread, like the async variant. ++ PostThreadMessage(mainThreadID(), IDLE_MESSAGE_FROM_OTHER_THREAD_SYNC, ++ (size_t)&functor, 0); + pthread_mutex_lock(&g_threadSyncFlowControler); + pthread_mutex_unlock(&g_threadSyncFlowControler); + } +diff --git a/src/public/delegate/LWEDelegate.cpp b/src/public/delegate/LWEDelegate.cpp +index b734cb0f0..db90f4adf 100644 +--- a/src/public/delegate/LWEDelegate.cpp ++++ b/src/public/delegate/LWEDelegate.cpp +@@ -76,6 +76,15 @@ void LWE::Initialize(const char* storageDirectoryPath, uint32_t option) + if (backend == "glib_headless") { + rendererType = Starfish::StarfishRendererType::kHeadless; + } ++#if defined(STARFISH_WINDOWS) ++ if (backend == "windows") { ++ // Embedders on this backend use the render-to-buffer container path ++ // (WebContainer::Create) without a RendererGLConfiguration, so GL ++ // canvas surfaces would invoke empty std::function GL callbacks. ++ // Use the software (cairo) canvas surface instead. ++ rendererType = Starfish::StarfishRendererType::kSoftware; ++ } ++#endif + #ifdef STARFISH_ENABLE_TEST + setenv("BACKEND", backend.data(), TRUE); + #endif diff --git a/lwe-web-engine-plugin/web-engine-lwe-plugin.cpp b/lwe-web-engine-plugin/web-engine-lwe-plugin.cpp new file mode 100644 index 0000000..b60962e --- /dev/null +++ b/lwe-web-engine-plugin/web-engine-lwe-plugin.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2026 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "LWEWebEngineContext.h" +#include "LWEWebEngineCookieManager.h" +#include "LWEWebEnginePlugin.h" + +// The factory entry points dali-adaptor's web-engine-impl.cpp resolves with +// dlsym() after dlopen'ing this plugin. On Windows the dlfcn shim +// (third-party/windows-platform/dlfcn.cpp) maps the requested +// "libdali2-web-engine-lwe-plugin.so" to "dali2-web-engine-lwe-plugin-win.dll" +// and loads it from the directory of dali2-adaptor.dll. + +#ifdef _WIN32 +#define LWE_PLUGIN_EXPORT extern "C" __declspec(dllexport) +#else +#define LWE_PLUGIN_EXPORT extern "C" +#endif + +LWE_PLUGIN_EXPORT Dali::WebEnginePlugin* CreateWebEnginePlugin() +{ + return new LWEDaliBridge::LWEWebEnginePlugin(); +} + +LWE_PLUGIN_EXPORT void DestroyWebEnginePlugin(Dali::WebEnginePlugin* plugin) +{ + delete plugin; +} + +LWE_PLUGIN_EXPORT Dali::WebEngineContext* GetWebEngineContext(bool /*isIncognito*/) +{ + // Incognito is ignored: LWE has no context-level API, so the bridge exposes a + // single global context - see README.md (WebEngineContext section). + static LWEDaliBridge::LWEWebEngineContext context; + return &context; +} + +LWE_PLUGIN_EXPORT Dali::WebEngineCookieManager* GetWebEngineCookieManager(bool /*isIncognito*/) +{ + static LWEDaliBridge::LWEWebEngineCookieManager cookieManager; + return &cookieManager; +} diff --git a/vcpkg-script/setup-starfish.ps1 b/vcpkg-script/setup-starfish.ps1 new file mode 100644 index 0000000..4c48d14 --- /dev/null +++ b/vcpkg-script/setup-starfish.ps1 @@ -0,0 +1,317 @@ +[CmdletBinding()] +param( + # WindowsDependenciesSDK root. Defaults to \WindowsDependenciesSDK. + [string]$InstallPrefix = "", + [string]$Repository = "https://github.sec.samsung.net/lws/starfish.git", + [string]$Branch = "master", + # Optional exact revision to pin. Empty = tip of $Branch at clone time. + [string]$Revision = "", + # Rebuild and reinstall even if the SDK already contains LWE. + [switch]$Force, + [int]$Jobs = 8 +) + +# Builds the LWE (Lightweight Web Engine / Starfish) SDK for Windows x64 +# Release and installs it into the WindowsDependenciesSDK: +# include\LWEWebView.h (+LWEWorker.h, PlatformIntegrationData.h) +# lib\Starfish.lib +# bin\Starfish.dll (+ Starfish.pdb) +# +# The official Starfish Windows build is x86-only; this script builds x64 by +# taking third-party dependencies from the SDK's vcpkg instead of the vendored +# Win32 binaries. It automates, in order: +# A) vcpkg ports: openssl / libwebsockets / glew (with the port fixes the +# old vcpkg snapshot needs), installed release-only +# B) sources: clone + submodules + the x64 patches shipped in +# lwe-web-engine-plugin\patches + python3/jinja2/ply for the binding +# generator +# C) CMake (VS 2022, x64, Release) build of starfish.shared_library and +# installation into the SDK +# +# Vendoring the LWE sources or binaries in this repository is not allowed; +# the engine is always cloned and built from $Repository. +# +# Prerequisites: VS2022 (C++ desktop), git, Python 3 + pip, and access to +# github.sec.samsung.net. Safe to re-run: every step is skipped when its +# output already exists. + +$ErrorActionPreference = "Stop" +$ScriptRoot = $PSScriptRoot +$WindowsDependenciesRoot = Split-Path -Parent $ScriptRoot +$WorkspaceRoot = Split-Path -Parent $WindowsDependenciesRoot +if(-not $InstallPrefix) +{ + $InstallPrefix = Join-Path $WorkspaceRoot "WindowsDependenciesSDK" +} +$VcpkgRoot = Join-Path $InstallPrefix "vcpkg" +$VcpkgExe = Join-Path $VcpkgRoot "vcpkg.exe" +$VcpkgInstalled = Join-Path $VcpkgRoot "installed\x64-windows" +$DepsRoot = Join-Path $WindowsDependenciesRoot ".deps" +$SourceRoot = Join-Path $DepsRoot "starfish" +$PatchDir = Join-Path $WindowsDependenciesRoot "lwe-web-engine-plugin\patches" + +function Write-Step([string]$Message) +{ + Write-Host "`n=== LWE: $Message ===" -ForegroundColor Cyan +} + +function Invoke-Git([string[]]$Arguments) +{ + & git @Arguments + if($LASTEXITCODE -ne 0) + { + throw "git $($Arguments -join ' ') failed with exit code $LASTEXITCODE" + } +} + +# Applies a git patch once: skipped when already applied (reverse-check). +function Invoke-GitPatch([string]$RepoDir, [string]$PatchPath) +{ + if(-not (Test-Path -LiteralPath $PatchPath)) + { + throw "Patch not found: $PatchPath" + } + & git -C $RepoDir apply --reverse --check $PatchPath 2>$null + if($LASTEXITCODE -eq 0) + { + Write-Host " already applied: $(Split-Path -Leaf $PatchPath)" + return + } + Invoke-Git @("-C", $RepoDir, "apply", "-3", $PatchPath) + Write-Host " applied: $(Split-Path -Leaf $PatchPath)" +} + +# --------------------------------------------------------------------------- +# 0) Skip everything if the SDK already has LWE. +# --------------------------------------------------------------------------- +$LweHeader = Join-Path $InstallPrefix "include\LWEWebView.h" +$LweLib = Join-Path $InstallPrefix "lib\Starfish.lib" +$LweDll = Join-Path $InstallPrefix "bin\Starfish.dll" +if(-not $Force -and (Test-Path $LweHeader) -and (Test-Path $LweLib) -and (Test-Path $LweDll)) +{ + Write-Host "LWE SDK already present in $InstallPrefix (use -Force to rebuild)." -ForegroundColor Green + exit 0 +} + +if(-not (Test-Path -LiteralPath $VcpkgExe)) +{ + throw "vcpkg not found: $VcpkgExe. Run install.ps1 (SDK setup) first." +} + +# --------------------------------------------------------------------------- +# A) vcpkg ports LWE needs beyond dali's own set. +# --------------------------------------------------------------------------- +Write-Step "vcpkg ports (openssl / libwebsockets / glew)" +$VcpkgLibDir = Join-Path $VcpkgInstalled "lib" +$PortsReady = (Test-Path (Join-Path $VcpkgLibDir "libeay32.lib")) -and + (Test-Path (Join-Path $VcpkgLibDir "websockets.lib")) -and + (Test-Path (Join-Path $VcpkgLibDir "glew32.lib")) +if($PortsReady) +{ + Write-Host " already installed." +} +else +{ + # Fix 1: this vcpkg snapshot's OpenSSL (1.0.2) generates asm that modern + # nasm rejects; build it without asm on x64. + $OpensslPort = Join-Path $VcpkgRoot "ports\openssl-windows\portfile.cmake" + $Content = Get-Content -LiteralPath $OpensslPort -Raw + if($Content -notmatch "no-asm") + { + $Anchor = 'set(OPENSSL_DO "ms\\do_win64a.bat")' + if(-not $Content.Contains($Anchor)) + { + throw "openssl portfile anchor not found; port layout changed?" + } + $Insert = $Anchor + "`r`n" + + " # Modern nasm rejects the asm this old OpenSSL generates; build without asm." + "`r`n" + + " set(CONFIGURE_COMMAND `${CONFIGURE_COMMAND}" + "`r`n" + + " no-asm" + "`r`n" + + " )" + Set-Content -LiteralPath $OpensslPort -Value ($Content.Replace($Anchor, $Insert)) -NoNewline + Write-Host " patched openssl portfile (no-asm)." + } + + # Fix 2: the libwebsockets portfile reads the debug targets file + # unconditionally, which does not exist in a release-only install. + $LwsPort = Join-Path $VcpkgRoot "ports\libwebsockets\portfile.cmake" + $Content = Get-Content -LiteralPath $LwsPort -Raw + if($Content -notmatch [regex]::Escape("if(EXISTS `${CURRENT_PACKAGES_DIR}/share/libwebsockets/LibwebsocketsTargets-debug.cmake)")) + { + $DebugRead = 'file(READ ${CURRENT_PACKAGES_DIR}/share/libwebsockets/LibwebsocketsTargets-debug.cmake LIBWEBSOCKETSTARGETSDEBUG_CMAKE)' + $DebugWrite = 'file(WRITE ${CURRENT_PACKAGES_DIR}/share/libwebsockets/LibwebsocketsTargets-debug.cmake "${LIBWEBSOCKETSTARGETSDEBUG_CMAKE}")' + if($Content.Contains($DebugRead) -and $Content.Contains($DebugWrite)) + { + $Content = $Content.Replace($DebugRead, + 'if(EXISTS ${CURRENT_PACKAGES_DIR}/share/libwebsockets/LibwebsocketsTargets-debug.cmake)' + "`r`n" + $DebugRead) + $Content = $Content.Replace($DebugWrite, $DebugWrite + "`r`n" + 'endif()') + Set-Content -LiteralPath $LwsPort -Value $Content -NoNewline + Write-Host " patched libwebsockets portfile (release-only guard)." + } + } + + # Fix 3: install release-only (the OpenSSL debug build cannot be produced at + # all), by temporarily marking the triplet. Reverted afterwards. + $Triplet = Join-Path $VcpkgRoot "triplets\x64-windows.cmake" + $TripletOriginal = Get-Content -LiteralPath $Triplet -Raw + if($TripletOriginal -notmatch "VCPKG_BUILD_TYPE") + { + Set-Content -LiteralPath $Triplet -Value ($TripletOriginal.TrimEnd() + "`r`n" + "set(VCPKG_BUILD_TYPE release)" + "`r`n") -NoNewline + } + try + { + Write-Host " installing (openssl takes 10-20 minutes)..." + & $VcpkgExe install openssl:x64-windows libwebsockets:x64-windows glew:x64-windows + if($LASTEXITCODE -ne 0) + { + throw "vcpkg install failed with exit code $LASTEXITCODE" + } + } + finally + { + Set-Content -LiteralPath $Triplet -Value $TripletOriginal -NoNewline + } +} + +# --------------------------------------------------------------------------- +# B) Starfish sources: clone + submodules + x64 patches + python tooling. +# --------------------------------------------------------------------------- +Write-Step "Starfish sources" +New-Item -ItemType Directory -Force -Path $DepsRoot | Out-Null +if(-not (Test-Path (Join-Path $SourceRoot ".git"))) +{ + Invoke-Git @("clone", "--branch", $Branch, $Repository, $SourceRoot) +} +if($Revision) +{ + Invoke-Git @("-C", $SourceRoot, "fetch", "origin", $Revision) + Invoke-Git @("-C", $SourceRoot, "checkout", $Revision) +} + +Invoke-Git @("-C", $SourceRoot, "submodule", "update", "--init", "--depth", "1", + "third_party/escargot", "binding_generator", "third_party/clipper", + "third_party/skia_matrix", "third_party/earcut.hpp", "third_party/robin_map", + "third_party/rapidxml", "third_party/MP4Parse", "third_party/webm") +$EscargotRoot = Join-Path $SourceRoot "third_party\escargot" +Invoke-Git @("-C", $EscargotRoot, "submodule", "update", "--init", "--depth", "1", "third_party/GCutil") +$GCutilRoot = Join-Path $EscargotRoot "third_party\GCutil" +if(-not (Test-Path (Join-Path $GCutilRoot "CMakeLists.txt"))) +{ + # Shallow submodule checkouts occasionally leave the tree unpopulated. + Invoke-Git @("-C", $GCutilRoot, "checkout", "-f", "HEAD") +} + +Write-Host "applying x64 patches from lwe-web-engine-plugin\patches" +Invoke-GitPatch $SourceRoot (Join-Path $PatchDir "starfish-0001-x64-vcpkg-build-and-windows-thread-mode.patch") +Invoke-GitPatch $EscargotRoot (Join-Path $PatchDir "escargot-0001-skip-unumrf-on-windows-system-icu.patch") + +# Python for the binding generator: needs `python3` on PATH with jinja2 + ply. +$Python = Get-Command python -ErrorAction SilentlyContinue +if(-not $Python) +{ + throw "python was not found on PATH. Install Python 3 and pip." +} +& python -c "import jinja2, ply" 2>$null +if($LASTEXITCODE -ne 0) +{ + Write-Host " installing python modules jinja2 + ply..." + & python -m pip install jinja2 ply 2>$null + if($LASTEXITCODE -ne 0) + { + # Corporate proxies commonly break pip's TLS verification. + & python -m pip install jinja2 ply --trusted-host=pypi.org --trusted-host=files.pythonhosted.org + if($LASTEXITCODE -ne 0) + { + throw "pip install jinja2 ply failed." + } + } +} +if(-not (Get-Command python3 -ErrorAction SilentlyContinue)) +{ + $ShimDir = Join-Path $DepsRoot "py3shim" + New-Item -ItemType Directory -Force -Path $ShimDir | Out-Null + Copy-Item -LiteralPath $Python.Source -Destination (Join-Path $ShimDir "python3.exe") -Force + $env:PATH = "$ShimDir;$env:PATH" + Write-Host " python3 shim: $ShimDir\python3.exe" +} + +# --------------------------------------------------------------------------- +# C) Build (VS 2022 generator, x64 Release) and install into the SDK. +# --------------------------------------------------------------------------- +Write-Step "build (first build takes 20-40 minutes)" + +# The SDK vcpkg's cmake 3.14 predates the VS2022 generator; require >= 3.21. +$Cmake = $null +$CmakeCmd = Get-Command cmake -ErrorAction SilentlyContinue +if($CmakeCmd) +{ + $VersionText = (& $CmakeCmd.Source --version | Select-Object -First 1) -replace "[^0-9.]", "" + if([Version]$VersionText -ge [Version]"3.21") + { + $Cmake = $CmakeCmd.Source + } +} +if(-not $Cmake) +{ + $VsWhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe" + if(Test-Path -LiteralPath $VsWhere) + { + $VsInstall = & $VsWhere -latest -products * ` + -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 ` + -property installationPath | Select-Object -First 1 + if($VsInstall) + { + $Candidate = Join-Path $VsInstall "Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" + if(Test-Path -LiteralPath $Candidate) + { + $Cmake = $Candidate + } + } + } +} +if(-not $Cmake) +{ + throw "CMake >= 3.21 was not found (PATH or the VS2022-bundled CMake)." +} +Write-Host " cmake: $Cmake" + +$BuildDir = Join-Path $SourceRoot "out_win64" +Push-Location $SourceRoot +try +{ + & $Cmake -G "Visual Studio 17 2022" -A x64 ` + -DHOST=windows -DARCH=x64 -DMODE=release ` + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 ` + -DVCPKG_INSTALLED_DIR="$($VcpkgInstalled -replace '\\', '/')" ` + -B out_win64 + if($LASTEXITCODE -ne 0) { throw "Starfish CMake configure failed." } + + & $Cmake --build out_win64 --config Release --target starfish.shared_library -- /m + if($LASTEXITCODE -ne 0) { throw "Starfish build failed." } +} +finally +{ + Pop-Location +} + +Write-Step "install into $InstallPrefix" +$BuiltDll = Join-Path $BuildDir "Release\Starfish.dll" +$BuiltLib = Join-Path $BuildDir "Release\Release\Starfish.lib" +$BuiltPdb = Join-Path $BuildDir "Release\Release\Starfish.pdb" +foreach($Required in @($BuiltDll, $BuiltLib)) +{ + if(-not (Test-Path -LiteralPath $Required)) + { + throw "Expected build output missing: $Required" + } +} +Copy-Item (Join-Path $SourceRoot "inc\*.h") (Join-Path $InstallPrefix "include\") -Force +Copy-Item $BuiltLib (Join-Path $InstallPrefix "lib\") -Force +Copy-Item $BuiltDll (Join-Path $InstallPrefix "bin\") -Force +if(Test-Path -LiteralPath $BuiltPdb) +{ + Copy-Item $BuiltPdb (Join-Path $InstallPrefix "bin\") -Force +} + +Write-Host "`nLWE (Starfish) SDK installed in $InstallPrefix." -ForegroundColor Green +Write-Host "Known limitation: the JS WebSocket API is disabled (vcpkg libwebsockets 3.2 < required 4.x)."