diff --git a/.gitattributes b/.gitattributes
index 10cf9919c..b7f18b462 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,4 +1,4 @@
-src/InfiniFrame.Native/Dependencies/* linguist-vendored
-src/InfiniFrame.Native/Dependencies/**/* linguist-vendored
+src/InfiniFrame.NativeBridge/Native/Dependencies/* linguist-vendored
+src/InfiniFrame.NativeBridge/Native/Dependencies/**/* linguist-vendored
*.sh text eol=lf
\ No newline at end of file
diff --git a/.github/codeql-config.yml b/.github/codeql-config.yml
index 45ee89219..3bb3c721b 100644
--- a/.github/codeql-config.yml
+++ b/.github/codeql-config.yml
@@ -10,16 +10,16 @@ paths:
# Exclude third-party and generated content from CodeQL alerting.
paths-ignore:
- - "src/InfiniFrame.Native/artifacts/**"
- - "src/InfiniFrame.Native/build/**"
- - "src/InfiniFrame.Native/packages/**"
- - "src/InfiniFrame.Native/cmake-build-debug/**"
- - "src/InfiniFrame.Native/cmake-build-debug-linux/**"
- - "src/InfiniFrame.Native/cmake-build-debug-windows/**"
- - "src/InfiniFrame.Native/cmake-build-release/**"
- - "src/InfiniFrame.Native/cmake-build-release-linux/**"
- - "src/InfiniFrame.Native/cmake-build-release-windows/**"
- - "src/InfiniFrame.Native/Dependencies/**"
+ - "src/InfiniFrame.NativeBridge/artifacts/**"
+ - "src/InfiniFrame.NativeBridge/build/**"
+ - "src/InfiniFrame.NativeBridge/Native/cmake-build-debug/**"
+ - "src/InfiniFrame.NativeBridge/Native/cmake-build-debug-linux/**"
+ - "src/InfiniFrame.NativeBridge/Native/cmake-build-debug-windows/**"
+ - "src/InfiniFrame.NativeBridge/Native/cmake-build-release/**"
+ - "src/InfiniFrame.NativeBridge/Native/cmake-build-release-linux/**"
+ - "src/InfiniFrame.NativeBridge/Native/cmake-build-release-windows/**"
+ - "src/InfiniFrame.NativeBridge/Native/Dependencies/**"
+ - "src/InfiniFrame.NativeBridge/Native/packages/**"
- "tests/**"
- "**/node_modules/**"
- "**/dist/**"
diff --git a/.github/scripts/bump_version.py b/.github/scripts/bump_version.py
index 526ae5b27..cf4e49f8b 100644
--- a/.github/scripts/bump_version.py
+++ b/.github/scripts/bump_version.py
@@ -11,7 +11,7 @@
# Resolve paths from the repository root: .github/scripts -> repo root is three levels up.
REPO_ROOT: Final[Path] = Path(__file__).parent.parent.parent
FILE: Final[Path] = REPO_ROOT / "src" / "Directory.Build.props"
-CMAKE_FILE: Final[Path] = REPO_ROOT / "src" / "InfiniFrame.Native" / "CMakeLists.txt"
+CMAKE_FILE: Final[Path] = REPO_ROOT / "src" / "InfiniFrame.NativeBridge" / "Native" / "CMakeLists.txt"
VERSION_PATTERN: Final[re.Pattern[str]] = re.compile(r"^\d+\.\d+\.\d+(-preview\.\d+)?$")
BumpPart = Literal["major", "minor", "patch", "preview"]
diff --git a/.github/workflows/ci-codeql.yml b/.github/workflows/ci-codeql.yml
index ff402c488..2464640f8 100644
--- a/.github/workflows/ci-codeql.yml
+++ b/.github/workflows/ci-codeql.yml
@@ -1,3 +1,4 @@
+#file: noinspection UndefinedAction,UndefinedParamsPresent
name: "CodeQL Advanced"
concurrency:
@@ -73,7 +74,7 @@ jobs:
- '.github/codeql-config.yml'
cpp:
- - 'src/InfiniFrame.Native/**'
+ - 'src/InfiniFrame.NativeBridge/Native/**'
- 'native-vendor-deps.json'
- 'global.json'
- '.github/actions/setup-dependencies-native/**'
@@ -220,11 +221,11 @@ jobs:
category: "/language:csharp"
analyze-cpp:
- name: Analyze C/C++ (${{ matrix.os }})
+ name: Analyze C/C++ (${{ matrix.os }} / ${{ matrix.arch }})
needs: changes
runs-on: ${{ matrix.os }}
timeout-minutes: 90
-
+
if: >
(
github.event_name == 'workflow_dispatch' &&
@@ -232,15 +233,20 @@ jobs:
) || (
needs.changes.outputs.cpp == 'true'
)
-
+
strategy:
fail-fast: false
matrix:
- os:
- - ubuntu-latest
- - windows-latest
- # - macos-latest # support is currently experimental and requires additional setup
-
+ include:
+ - os: ubuntu-latest
+ arch: x64
+
+ - os: windows-latest
+ arch: x64
+
+# - os: macos-latest
+# arch: arm64
+
permissions:
contents: read
security-events: write
@@ -248,7 +254,7 @@ jobs:
packages: read
pull-requests: write
checks: write
-
+
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -261,8 +267,8 @@ jobs:
- name: Setup Native Dependencies
uses: ./.github/actions/setup-dependencies-native
with:
- brew-cache-key: ${{ runner.os }}-brew-codeql-${{ hashFiles('.github/actions/setup-dependencies-native/action.yml', '.github/workflows/shared-testing-macos.yml') }}
- brew-restore-key: ${{ runner.os }}-brew-codeql-
+ brew-cache-key: ${{ matrix.os }}-${{ matrix.arch }}-brew-codeql-${{ hashFiles('.github/actions/setup-dependencies-native/action.yml', '.github/workflows/ci-codeql.yml') }}
+ brew-restore-key: ${{ matrix.os }}-${{ matrix.arch }}-brew-codeql-
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
@@ -277,18 +283,14 @@ jobs:
dotnet restore src/InfiniFrame.NativeBridge/InfiniFrame.NativeBridge.csproj /p:NoWarn=NU1503
- name: Build NativeBridge
- shell: pwsh
+ timeout-minutes: 45
run: |
- dotnet build src/InfiniFrame.NativeBridge/InfiniFrame.NativeBridge.csproj `
- --configuration Release `
- --no-restore `
- -p:SolutionDir="${{ github.workspace }}/" `
- -p:Platform=x64
+ dotnet build src/InfiniFrame.NativeBridge/InfiniFrame.NativeBridge.csproj --configuration Release --no-restore /p:SolutionDir=${{ github.workspace }}/ /p:NativeArch=${{ matrix.arch }}
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
with:
- category: "/language:c-cpp"
+ category: "/language:c-cpp/${{ matrix.os }}-${{ matrix.arch }}"
analyze-javascript-typescript:
name: Analyze JavaScript / TypeScript
diff --git a/.github/workflows/ci-release-docs.yml b/.github/workflows/ci-release-docs.yml
index d2af5a28f..4f90bfedf 100644
--- a/.github/workflows/ci-release-docs.yml
+++ b/.github/workflows/ci-release-docs.yml
@@ -1,3 +1,4 @@
+#file: noinspection UndefinedAction,UndefinedParamsPresent
name: "CI: Release Docs"
on:
workflow_dispatch:
@@ -8,7 +9,6 @@ permissions:
id-token: write
jobs:
- # noinspection UndefinedAction, UndefinedParamsPresent
deploy-docs:
name: Publish Docs
uses: ./.github/workflows/shared-release-publish-docs.yml
diff --git a/.github/workflows/ci-release.yml b/.github/workflows/ci-release.yml
index abd640296..1dbc122a3 100644
--- a/.github/workflows/ci-release.yml
+++ b/.github/workflows/ci-release.yml
@@ -1,3 +1,4 @@
+#file: noinspection UndefinedAction,UndefinedParamsPresent
name: "CI: Release"
on:
workflow_dispatch:
@@ -35,12 +36,10 @@ permissions:
id-token: write
jobs:
- # noinspection UndefinedAction
tests-python:
name: Python Tests
uses: ./.github/workflows/shared-testing-python.yml
- # noinspection UndefinedAction, UndefinedParamsPresent
tests-platform:
name: Platform Tests
needs: tests-python
@@ -54,12 +53,12 @@ jobs:
run_macos: true
run_docs: true
run_trim_aot: true
+ enable_test_exports: false
secrets: inherit
# ------------------------------------------------------------------------------------------------------------------
# Bump version, commit, and tag
# ------------------------------------------------------------------------------------------------------------------
- # noinspection UndefinedAction, UndefinedParamsPresent
bump-version:
name: Bump Version
needs: tests-platform
@@ -70,7 +69,6 @@ jobs:
custom_version: ${{ inputs.custom_version }}
secrets: inherit
- # noinspection UndefinedAction, UndefinedParamsPresent
build:
name: Build Native
needs: bump-version
@@ -79,7 +77,6 @@ jobs:
tag_name: ${{ needs.bump-version.outputs.tag_name }}
secrets: inherit
- # noinspection UndefinedAction, UndefinedParamsPresent
publish:
name: Publish Release
needs: [ build, bump-version ]
@@ -90,7 +87,6 @@ jobs:
publish_to_nuget: ${{ inputs.publish_to_nuget }}
secrets: inherit
- # noinspection UndefinedAction, UndefinedParamsPresent
publish-docs:
name: Publish Docs
needs: [ publish, bump-version ]
diff --git a/.github/workflows/ci-testing-python.yml b/.github/workflows/ci-testing-python.yml
index 6fd79653c..49bf36193 100644
--- a/.github/workflows/ci-testing-python.yml
+++ b/.github/workflows/ci-testing-python.yml
@@ -1,3 +1,4 @@
+#file: noinspection UndefinedAction,UndefinedParamsPresent
name: "CI: Python Tests"
on:
pull_request:
@@ -20,7 +21,6 @@ permissions:
contents: read
jobs:
- # noinspection UndefinedAction
run:
name: Python Tests
uses: ./.github/workflows/shared-testing-python.yml
diff --git a/.github/workflows/ci-testing.yml b/.github/workflows/ci-testing.yml
index 909605230..1ea2c7d9f 100644
--- a/.github/workflows/ci-testing.yml
+++ b/.github/workflows/ci-testing.yml
@@ -1,3 +1,4 @@
+#file: noinspection UndefinedAction,UndefinedParamsPresent
name: "CI: Full Test Suite"
permissions:
@@ -38,12 +39,10 @@ on:
default: true
jobs:
- # noinspection UndefinedAction
python-tests:
name: Python Tests
uses: ./.github/workflows/shared-testing-python.yml
- # noinspection UndefinedAction, UndefinedParamsPresent
run:
name: Full Test Suite
needs: python-tests
@@ -56,4 +55,5 @@ jobs:
run_macos: ${{ inputs.run_macos }}
run_docs: ${{ inputs.run_docs }}
run_trim_aot: ${{ inputs.run_trim_aot }}
+ enable_test_exports: true
secrets: inherit
diff --git a/.github/workflows/ci-todo.yml b/.github/workflows/ci-todo.yml
index f63416fa1..b35132747 100644
--- a/.github/workflows/ci-todo.yml
+++ b/.github/workflows/ci-todo.yml
@@ -1,3 +1,4 @@
+#file: noinspection UndefinedAction,UndefinedParamsPresent
name: "CI: Todo Sync"
on:
push:
diff --git a/.github/workflows/ci-update-native-vendor-deps.yml b/.github/workflows/ci-update-native-vendor-deps.yml
index 446755bc0..f0eb91c4f 100644
--- a/.github/workflows/ci-update-native-vendor-deps.yml
+++ b/.github/workflows/ci-update-native-vendor-deps.yml
@@ -1,3 +1,4 @@
+#file: noinspection UndefinedAction,UndefinedParamsPresent
name: "CI: Update Native Vendor Deps"
on:
diff --git a/.github/workflows/shared-release-build.yml b/.github/workflows/shared-release-build.yml
index 3a8dbe3e1..0f2069225 100644
--- a/.github/workflows/shared-release-build.yml
+++ b/.github/workflows/shared-release-build.yml
@@ -1,3 +1,4 @@
+#file: noinspection UndefinedAction,UndefinedParamsPresent
name: "Shared: Release Build"
on:
workflow_call:
@@ -57,9 +58,7 @@ jobs:
10.x
- name: Setup Native dependencies
- # noinspection UndefinedAction
uses: ./.github/actions/setup-dependencies-native
- # noinspection UndefinedParamsPresent
with:
brew-cache-key: ${{ matrix.os }}-${{ matrix.arch }}-brew-native-${{ hashFiles('.github/actions/setup-dependencies-native/action.yml', '.github/workflows/shared-release-build.yml') }}
brew-restore-key: ${{ matrix.os }}-${{ matrix.arch }}-brew-native-
diff --git a/.github/workflows/shared-release-bump-version.yml b/.github/workflows/shared-release-bump-version.yml
index 9c322cd7b..52c0b5d51 100644
--- a/.github/workflows/shared-release-bump-version.yml
+++ b/.github/workflows/shared-release-bump-version.yml
@@ -1,3 +1,4 @@
+#file: noinspection UndefinedAction,UndefinedParamsPresent
name: "Shared: Release Bump Version"
on:
workflow_call:
diff --git a/.github/workflows/shared-release-publish-docs.yml b/.github/workflows/shared-release-publish-docs.yml
index 494bd81d0..904385cb1 100644
--- a/.github/workflows/shared-release-publish-docs.yml
+++ b/.github/workflows/shared-release-publish-docs.yml
@@ -1,3 +1,4 @@
+#file: noinspection UndefinedAction,UndefinedParamsPresent
name: "Shared: Release Docs"
on:
workflow_call:
diff --git a/.github/workflows/shared-release-publish.yml b/.github/workflows/shared-release-publish.yml
index 061dc29fe..98869e080 100644
--- a/.github/workflows/shared-release-publish.yml
+++ b/.github/workflows/shared-release-publish.yml
@@ -1,3 +1,4 @@
+#file: noinspection UndefinedAction,UndefinedParamsPresent
name: "Shared: Release Publish"
on:
workflow_call:
@@ -50,9 +51,7 @@ jobs:
--prefix InfiniLore
- name: Setup Native dependencies
- # noinspection UndefinedAction
uses: ./.github/actions/setup-dependencies-native
- # noinspection UndefinedParamsPresent
with:
brew-cache-key: ${{ matrix.os }}-${{ matrix.arch }}-brew-native-${{ hashFiles('.github/actions/setup-dependencies-native/action.yml', '.github/workflows/shared-release-publish.yml') }}
brew-restore-key: ${{ matrix.os }}-${{ matrix.arch }}-brew-native-
diff --git a/.github/workflows/shared-testing-build.yml b/.github/workflows/shared-testing-build.yml
index a5735591c..10bcc7472 100644
--- a/.github/workflows/shared-testing-build.yml
+++ b/.github/workflows/shared-testing-build.yml
@@ -1,3 +1,4 @@
+#file: noinspection UndefinedAction,UndefinedParamsPresent
name: "Shared: Testing Build"
on:
workflow_call:
@@ -15,6 +16,11 @@ on:
description: 'Resolved commit SHA used by testing workflows.'
type: string
required: true
+ enable_test_exports:
+ description: 'Enable native test exports in testing native artifacts.'
+ type: boolean
+ required: false
+ default: false
permissions:
contents: read
@@ -68,7 +74,8 @@ jobs:
run: |
./src/InfiniFrame.NativeBridge/native-build.ps1 `
"Release" `
- "${{ matrix.arch }}"
+ "${{ matrix.arch }}" `
+ "${{ inputs.enable_test_exports }}"
- name: Upload Build Artifact
uses: actions/upload-artifact@v7
diff --git a/.github/workflows/shared-testing-docs.yml b/.github/workflows/shared-testing-docs.yml
index 8585f0d97..b1c783ead 100644
--- a/.github/workflows/shared-testing-docs.yml
+++ b/.github/workflows/shared-testing-docs.yml
@@ -1,3 +1,4 @@
+#file: noinspection UndefinedAction,UndefinedParamsPresent
name: "Shared: Docs Tests"
on:
@@ -46,9 +47,7 @@ jobs:
- name: Set Docs Pending
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- # noinspection UndefinedAction, UndefinedParamsPresent
uses: ./.github/actions/sync-check
- # noinspection UndefinedAction, UndefinedParamsPresent
with:
repo: ${{ github.repository }}
sha: ${{ env.TARGET_SHA }}
@@ -66,9 +65,7 @@ jobs:
if: always()
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- # noinspection UndefinedAction, UndefinedParamsPresent
uses: ./.github/actions/sync-check-finalize
- # noinspection UndefinedParamsPresent
with:
repo: ${{ github.repository }}
sha: ${{ env.TARGET_SHA }}
diff --git a/.github/workflows/shared-testing-dotnetpack.yml b/.github/workflows/shared-testing-dotnetpack.yml
index feaa5112c..7a11048c2 100644
--- a/.github/workflows/shared-testing-dotnetpack.yml
+++ b/.github/workflows/shared-testing-dotnetpack.yml
@@ -1,3 +1,4 @@
+#file: noinspection UndefinedAction,UndefinedParamsPresent
name: "Shared: Dotnet Pack Tests"
on:
@@ -37,12 +38,13 @@ jobs:
with:
cache-dotnet: false
+ - name: Setup dependencies
+ uses: ./.github/actions/setup-dependencies-native
+
- name: Set Dotnet Pack Check Pending
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- # noinspection UndefinedAction
uses: ./.github/actions/sync-check
- # noinspection UndefinedParamsPresent
with:
repo: ${{ github.repository }}
sha: ${{ inputs.target_sha }}
@@ -66,14 +68,16 @@ jobs:
--configuration Release \
--no-restore \
/p:SolutionDir=${{ github.workspace }}/ \
- /p:InfiniFrameSkipNativeBuild=true
+ /p:InfiniFrameSkipNativeBuild=false \
+ /p:InfiniFrameEnableTestExports=false
- name: Pack
run: |
dotnet pack InfiniFrame.GitHubActions.Release.slnf \
--configuration Release \
--no-build \
- /p:InfiniFrameSkipNativeBuild=true \
+ /p:InfiniFrameSkipNativeBuild=false \
+ /p:InfiniFrameEnableTestExports=false \
-o ./release
- name: Verify NativeBridge Package Natives
@@ -112,6 +116,59 @@ jobs:
echo "❌ $MISSING native file(s) missing in NativeBridge package"
exit 1
fi
+
+ - name: Verify Lack of NativeBridge Test Exports
+ shell: bash
+ run: |
+ set -euo pipefail
+
+ PACKAGE=$(ls ./release/InfiniLore.InfiniFrame.NativeBridge.*.nupkg 2>/dev/null | grep -v '\.symbols\.nupkg$' | head -n 1 || true)
+
+ if [ -z "$PACKAGE" ]; then
+ echo "❌ No InfiniFrame.NativeBridge package found in ./release"
+ exit 1
+ fi
+
+ echo "Inspecting package: $PACKAGE"
+
+ DLL_PATH="runtimes/win-x64/native/InfiniFrame.Native.dll"
+
+ if ! unzip -l "$PACKAGE" | grep -q "$DLL_PATH"; then
+ echo "❌ Missing $DLL_PATH in package"
+ exit 1
+ fi
+
+ mkdir -p ./tmp/native-check
+
+ unzip -p "$PACKAGE" "$DLL_PATH" > ./tmp/native-check/InfiniFrame.Native.dll
+
+ sudo apt-get update
+ sudo apt-get install -y binutils-mingw-w64-x86-64
+
+ DLL=./tmp/native-check/InfiniFrame.Native.dll
+
+ FORBIDDEN_EXPORTS=(
+ "InfiniFrameNativeTests_NativeParametersReturnAsIs"
+ "InfiniFrameNativeTests_FreeInitParams"
+ )
+
+ FOUND=0
+
+ for export_name in "${FORBIDDEN_EXPORTS[@]}"; do
+ if x86_64-w64-mingw32-nm -g --defined-only "$DLL" | awk '{print $3}' | grep -Fxq "$export_name"; then
+ echo "❌ Forbidden test export found: $export_name"
+ FOUND=$((FOUND+1))
+ else
+ echo "✅ Export not present: $export_name"
+ fi
+ done
+
+ if [ "$FOUND" -gt 0 ]; then
+ echo "❌ $FOUND forbidden test export(s) found in InfiniFrame.Native.dll"
+ exit 1
+ fi
+
+ echo "✅ No forbidden test exports found"
- name: Complete Dotnet Pack Check
if: always()
diff --git a/.github/workflows/shared-testing-js.yml b/.github/workflows/shared-testing-js.yml
index 22134c3aa..e0c051bc3 100644
--- a/.github/workflows/shared-testing-js.yml
+++ b/.github/workflows/shared-testing-js.yml
@@ -1,3 +1,4 @@
+#file: noinspection UndefinedAction,UndefinedParamsPresent
name: "Shared: Js Tests"
on:
@@ -55,9 +56,7 @@ jobs:
if: always()
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- # noinspection UndefinedAction, UndefinedParamsPresent
uses: ./.github/actions/sync-check-finalize
- # noinspection UndefinedParamsPresent
with:
repo: ${{ github.repository }}
sha: ${{ env.TARGET_SHA }}
diff --git a/.github/workflows/shared-testing-linux.yml b/.github/workflows/shared-testing-linux.yml
index 4fab3a858..49d9eeab5 100644
--- a/.github/workflows/shared-testing-linux.yml
+++ b/.github/workflows/shared-testing-linux.yml
@@ -1,3 +1,4 @@
+#file: noinspection UndefinedAction,UndefinedParamsPresent
name: "Shared: Linux Tests"
on:
workflow_call:
@@ -15,6 +16,10 @@ on:
workflow_url:
type: string
required: true
+ enable_test_exports:
+ type: boolean
+ required: false
+ default: false
jobs:
linux:
@@ -56,9 +61,7 @@ jobs:
- name: Set Linux Check Pending
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- # noinspection UndefinedAction
uses: ./.github/actions/sync-check
- # noinspection UndefinedParamsPresent
with:
repo: ${{ github.repository }}
sha: ${{ inputs.target_sha }}
@@ -77,7 +80,7 @@ jobs:
run: sudo glib-compile-schemas /usr/share/glib-2.0/schemas/
- name: Restore
- run: dotnet restore InfiniFrame.GitHubActions.Testing.slnf /p:NoWarn=NU1503
+ run: dotnet restore InfiniFrame.GitHubActions.Testing.slnf /p:NoWarn=NU1503 /p:NativeArch=${{ matrix.arch }}
- name: Build Release
run: |
@@ -85,8 +88,9 @@ jobs:
--configuration Release \
--no-restore \
-p:SolutionDir=${{ github.workspace }}/ \
- -p:CMakePlatform=${{ matrix.arch }} \
- -p:InfiniFrameSkipNativeBuild=true
+ -p:NativeArch=${{ matrix.arch }} \
+ -p:InfiniFrameSkipNativeBuild=true \
+ -p:InfiniFrameEnableTestExports=${{ inputs.enable_test_exports }}
- name: Export Actions Runtime
uses: actions/github-script@v9
@@ -168,13 +172,12 @@ jobs:
--configuration Release \
--no-build \
--no-restore \
- -p:CMakePlatform=${{ matrix.arch }} \
- -p:InfiniFrameSkipNativeBuild=true
+ -p:NativeArch=${{ matrix.arch }} \
+ -p:InfiniFrameSkipNativeBuild=true \
+ -p:InfiniFrameEnableTestExports=${{ inputs.enable_test_exports }}
- name: Pack Tool E2E
- # noinspection UndefinedAction
uses: ./.github/actions/packtool-e2e
- # noinspection UndefinedParamsPresent
with:
project: examples/InfiniFrameExample.SingleFileExe/InfiniFrameExample.SingleFileExe.csproj
rid: ${{ matrix.rid }}
@@ -191,9 +194,7 @@ jobs:
if: always()
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- # noinspection UndefinedAction, UndefinedParamsPresent
uses: ./.github/actions/sync-check-finalize
- # noinspection UndefinedParamsPresent
with:
repo: ${{ github.repository }}
sha: ${{ inputs.target_sha }}
diff --git a/.github/workflows/shared-testing-macos.yml b/.github/workflows/shared-testing-macos.yml
index 8c2db0dd1..cfd954481 100644
--- a/.github/workflows/shared-testing-macos.yml
+++ b/.github/workflows/shared-testing-macos.yml
@@ -1,3 +1,4 @@
+#file: noinspection UndefinedAction,UndefinedParamsPresent
name: "Shared: macOS Tests"
on:
workflow_call:
@@ -15,6 +16,10 @@ on:
workflow_url:
type: string
required: true
+ enable_test_exports:
+ type: boolean
+ required: false
+ default: false
jobs:
macos:
@@ -61,9 +66,7 @@ jobs:
- name: Set macOS Check Pending
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- # noinspection UndefinedAction
uses: ./.github/actions/sync-check
- # noinspection UndefinedParamsPresent
with:
repo: ${{ github.repository }}
sha: ${{ inputs.target_sha }}
@@ -79,7 +82,7 @@ jobs:
artifact-type: testing
- name: Restore
- run: dotnet restore InfiniFrame.GitHubActions.Testing.slnf /p:NoWarn=NU1503
+ run: dotnet restore InfiniFrame.GitHubActions.Testing.slnf /p:NoWarn=NU1503 /p:NativeArch=${{ matrix.arch }}
- name: Build Release
run: |
@@ -87,8 +90,9 @@ jobs:
--configuration Release \
--no-restore \
-p:SolutionDir=${{ github.workspace }}/ \
- -p:CMakePlatform=${{ matrix.arch }} \
- -p:InfiniFrameSkipNativeBuild=true
+ -p:NativeArch=${{ matrix.arch }} \
+ -p:InfiniFrameSkipNativeBuild=true \
+ -p:InfiniFrameEnableTestExports=${{ inputs.enable_test_exports }}
- name: Export Actions Runtime
uses: actions/github-script@v9
@@ -115,13 +119,12 @@ jobs:
--configuration Release \
--no-build \
--no-restore \
- -p:CMakePlatform=${{ matrix.arch }} \
- -p:InfiniFrameSkipNativeBuild=true
+ -p:NativeArch=${{ matrix.arch }} \
+ -p:InfiniFrameSkipNativeBuild=true \
+ -p:InfiniFrameEnableTestExports=${{ inputs.enable_test_exports }}
- name: Pack Tool E2E
- # noinspection UndefinedAction
uses: ./.github/actions/packtool-e2e
- # noinspection UndefinedParamsPresent
with:
project: examples/InfiniFrameExample.SingleFileExe/InfiniFrameExample.SingleFileExe.csproj
rid: ${{ matrix.rid }}
@@ -138,9 +141,7 @@ jobs:
if: always()
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- # noinspection UndefinedAction
uses: ./.github/actions/sync-check-finalize
- # noinspection UndefinedParamsPresent
with:
repo: ${{ github.repository }}
sha: ${{ inputs.target_sha }}
diff --git a/.github/workflows/shared-testing-python.yml b/.github/workflows/shared-testing-python.yml
index 8dcc64b4b..5af6b9803 100644
--- a/.github/workflows/shared-testing-python.yml
+++ b/.github/workflows/shared-testing-python.yml
@@ -1,3 +1,4 @@
+#file: noinspection UndefinedAction,UndefinedParamsPresent
name: "Shared: Python Tests"
permissions:
contents: read
diff --git a/.github/workflows/shared-testing-windows-playwright.yml b/.github/workflows/shared-testing-windows-playwright.yml
index 97b457aa0..8f205f680 100644
--- a/.github/workflows/shared-testing-windows-playwright.yml
+++ b/.github/workflows/shared-testing-windows-playwright.yml
@@ -1,3 +1,4 @@
+#file: noinspection UndefinedAction,UndefinedParamsPresent
name: "Shared: Windows Playwright Tests"
on:
@@ -16,6 +17,10 @@ on:
workflow_url:
type: string
required: true
+ enable_test_exports:
+ type: boolean
+ required: false
+ default: false
permissions:
contents: read
@@ -38,9 +43,7 @@ jobs:
- name: Set Playwright Check Pending
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- # noinspection UndefinedAction
uses: ./.github/actions/sync-check
- # noinspection UndefinedParamsPresent
with:
repo: ${{ github.repository }}
sha: ${{ inputs.target_sha }}
@@ -98,9 +101,7 @@ jobs:
- name: Set Playwright Check Pending
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- # noinspection UndefinedAction
uses: ./.github/actions/sync-check
- # noinspection UndefinedParamsPresent
with:
repo: ${{ github.repository }}
sha: ${{ inputs.target_sha }}
@@ -146,7 +147,8 @@ jobs:
--configuration Release `
--no-restore `
/p:SolutionDir=$env:GITHUB_WORKSPACE/ `
- /p:InfiniFrameSkipNativeBuild=true
+ /p:InfiniFrameSkipNativeBuild=true `
+ /p:InfiniFrameEnableTestExports=${{ inputs.enable_test_exports }}
- name: Install Playwright Browsers
shell: pwsh
@@ -187,7 +189,8 @@ jobs:
--no-build `
--no-restore `
--framework net8.0 `
- /p:InfiniFrameSkipNativeBuild=true
+ /p:InfiniFrameSkipNativeBuild=true `
+ /p:InfiniFrameEnableTestExports=${{ inputs.enable_test_exports }}
- name: Run Playwright tests NET 9.0
shell: pwsh
@@ -198,7 +201,8 @@ jobs:
--no-build `
--no-restore `
--framework net9.0 `
- /p:InfiniFrameSkipNativeBuild=true
+ /p:InfiniFrameSkipNativeBuild=true `
+ /p:InfiniFrameEnableTestExports=${{ inputs.enable_test_exports }}
- name: Run Playwright tests NET 10.0
shell: pwsh
@@ -209,15 +213,14 @@ jobs:
--no-build `
--no-restore `
--framework net10.0 `
- /p:InfiniFrameSkipNativeBuild=true
+ /p:InfiniFrameSkipNativeBuild=true `
+ /p:InfiniFrameEnableTestExports=${{ inputs.enable_test_exports }}
- name: Update Playwright Check
if: always()
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- # noinspection UndefinedAction
uses: ./.github/actions/sync-check-finalize
- # noinspection UndefinedParamsPresent
with:
repo: ${{ github.repository }}
sha: ${{ inputs.target_sha }}
@@ -245,9 +248,7 @@ jobs:
- name: Set Playwright Check Final Status
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- # noinspection UndefinedAction
uses: ./.github/actions/sync-check-finalize
- # noinspection UndefinedParamsPresent
with:
repo: ${{ github.repository }}
sha: ${{ inputs.target_sha }}
diff --git a/.github/workflows/shared-testing-windows-trim-aot.yml b/.github/workflows/shared-testing-windows-trim-aot.yml
index d102b3ff4..5f6823dfd 100644
--- a/.github/workflows/shared-testing-windows-trim-aot.yml
+++ b/.github/workflows/shared-testing-windows-trim-aot.yml
@@ -1,3 +1,4 @@
+#file: noinspection UndefinedAction,UndefinedParamsPresent
name: "Shared: Windows Trim/AOT Validation"
on:
@@ -12,6 +13,11 @@ on:
description: 'Commit SHA used for status/check updates'
type: string
required: true
+ enable_test_exports:
+ description: 'Enable native/managed test exports in trim/AOT validation builds.'
+ type: boolean
+ required: false
+ default: false
permissions:
contents: read
@@ -42,9 +48,7 @@ jobs:
- name: Set Trim/AOT Check Pending
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- # noinspection UndefinedAction, UndefinedParamsPresent
uses: ./.github/actions/sync-check
- # noinspection UndefinedAction, UndefinedParamsPresent
with:
repo: ${{ github.repository }}
sha: ${{ env.TARGET_SHA }}
@@ -55,9 +59,7 @@ jobs:
allow-status-422: 'true'
- name: Setup Native dependencies
- # noinspection UndefinedAction
uses: ./.github/actions/setup-dependencies-native
- # noinspection UndefinedParamsPresent
with:
brew-cache-key: ${{ runner.os }}-x64-brew-native-${{ hashFiles('.github/actions/setup-dependencies-native/action.yml', '.github/workflows/shared-testing-windows-trim-aot.yml') }}
brew-restore-key: ${{ runner.os }}-x64-brew-native-
@@ -75,6 +77,7 @@ jobs:
-p:SolutionDir=$env:GITHUB_WORKSPACE/ `
-p:Platform=x64 `
-p:InfiniFrameSkipNativeBuild=true `
+ -p:InfiniFrameEnableTestExports=${{ inputs.enable_test_exports }} `
-p:EnableTrimAnalyzer=true `
-p:EnableAotAnalyzer=true `
-p:GeneratePackageOnBuild=false
@@ -85,6 +88,7 @@ jobs:
-p:SolutionDir=$env:GITHUB_WORKSPACE/ `
-p:Platform=x64 `
-p:InfiniFrameSkipNativeBuild=true `
+ -p:InfiniFrameEnableTestExports=${{ inputs.enable_test_exports }} `
-p:EnableTrimAnalyzer=true `
-p:EnableAotAnalyzer=true `
-p:GeneratePackageOnBuild=false `
@@ -96,6 +100,7 @@ jobs:
-p:SolutionDir=$env:GITHUB_WORKSPACE/ `
-p:Platform=x64 `
-p:InfiniFrameSkipNativeBuild=true `
+ -p:InfiniFrameEnableTestExports=${{ inputs.enable_test_exports }} `
-p:EnableTrimAnalyzer=true `
-p:EnableAotAnalyzer=true `
-p:GeneratePackageOnBuild=false `
@@ -110,6 +115,7 @@ jobs:
-p:SolutionDir=$env:GITHUB_WORKSPACE/ `
-p:Platform=x64 `
-p:InfiniFrameSkipNativeBuild=true `
+ -p:InfiniFrameEnableTestExports=${{ inputs.enable_test_exports }} `
-p:GeneratePackageOnBuild=false `
-p:SkipTypeScriptBuild=true
@@ -145,9 +151,7 @@ jobs:
if: always()
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- # noinspection UndefinedAction, UndefinedParamsPresent
uses: ./.github/actions/sync-check-finalize
- # noinspection UndefinedParamsPresent
with:
repo: ${{ github.repository }}
sha: ${{ env.TARGET_SHA }}
diff --git a/.github/workflows/shared-testing-windows.yml b/.github/workflows/shared-testing-windows.yml
index 36dca79f8..17da2199a 100644
--- a/.github/workflows/shared-testing-windows.yml
+++ b/.github/workflows/shared-testing-windows.yml
@@ -1,3 +1,4 @@
+#file: noinspection UndefinedAction,UndefinedParamsPresent
name: "Shared: Windows Tests"
on:
workflow_call:
@@ -15,6 +16,10 @@ on:
workflow_url:
type: string
required: true
+ enable_test_exports:
+ type: boolean
+ required: false
+ default: false
jobs:
windows:
@@ -58,9 +63,7 @@ jobs:
- name: Set Windows Check Pending
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- # noinspection UndefinedAction
uses: ./.github/actions/sync-check
- # noinspection UndefinedParamsPresent
with:
repo: ${{ github.repository }}
sha: ${{ inputs.target_sha }}
@@ -76,7 +79,7 @@ jobs:
artifact-type: testing
- name: Restore
- run: dotnet restore InfiniFrame.GitHubActions.Testing.slnf /p:NoWarn=NU1503
+ run: dotnet restore InfiniFrame.GitHubActions.Testing.slnf /p:NoWarn=NU1503 /p:NativeArch=${{ matrix.arch }}
- name: Build Release
shell: pwsh
@@ -85,8 +88,9 @@ jobs:
--configuration Release `
--no-restore `
/p:SolutionDir=$env:GITHUB_WORKSPACE/ `
- /p:CMakePlatform=${{ matrix.arch }} `
- /p:InfiniFrameSkipNativeBuild=true
+ /p:NativeArch=${{ matrix.arch }} `
+ /p:InfiniFrameSkipNativeBuild=true `
+ /p:InfiniFrameEnableTestExports=${{ inputs.enable_test_exports }}
- name: Prepare ARM64 Crash Diagnostics
if: matrix.arch == 'arm64'
@@ -199,8 +203,9 @@ jobs:
--configuration Release `
--no-build `
--no-restore `
- /p:CMakePlatform=${{ matrix.arch }} `
- /p:InfiniFrameSkipNativeBuild=true
+ /p:NativeArch=${{ matrix.arch }} `
+ /p:InfiniFrameSkipNativeBuild=true `
+ /p:InfiniFrameEnableTestExports=${{ inputs.enable_test_exports }}
# - name: Upload ARM64 Crash Diagnostics
# if: always() && matrix.arch == 'arm64'
@@ -215,9 +220,7 @@ jobs:
# artifacts/testresults/**
- name: Pack Tool E2E
- # noinspection UndefinedAction
uses: ./.github/actions/packtool-e2e
- # noinspection UndefinedParamsPresent
with:
project: examples/InfiniFrameExample.SingleFileExe/InfiniFrameExample.SingleFileExe.csproj
rid: ${{ matrix.rid }}
@@ -234,9 +237,7 @@ jobs:
if: always()
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- # noinspection UndefinedAction, UndefinedParamsPresent
uses: ./.github/actions/sync-check-finalize
- # noinspection UndefinedParamsPresent
with:
repo: ${{ github.repository }}
sha: ${{ inputs.target_sha }}
diff --git a/.github/workflows/shared-testing.yml b/.github/workflows/shared-testing.yml
index dd0627efa..3cc819fdd 100644
--- a/.github/workflows/shared-testing.yml
+++ b/.github/workflows/shared-testing.yml
@@ -1,3 +1,4 @@
+#file: noinspection UndefinedAction,UndefinedParamsPresent
name: "Shared: Platform Tests"
permissions:
contents: read
@@ -39,6 +40,11 @@ on:
description: 'Run trim and NativeAOT compatibility validation'
type: boolean
required: true
+ enable_test_exports:
+ description: 'Enable native/managed test exports for test workflows'
+ type: boolean
+ required: false
+ default: false
jobs:
@@ -71,7 +77,6 @@ jobs:
echo "sha=$SHA" >> $GITHUB_OUTPUT
- # noinspection UndefinedAction, UndefinedParamsPresent
docs-validation:
name: Validate Docs
if: ${{ inputs.run_docs == true }}
@@ -82,7 +87,6 @@ jobs:
target_sha: ${{ needs.prepare.outputs.sha }}
secrets: inherit
- # noinspection UndefinedAction, UndefinedParamsPresent
js-validation:
name: Validate JS
needs: [ prepare ]
@@ -91,7 +95,6 @@ jobs:
checkout_ref: ${{ inputs.checkout_ref }}
target_sha: ${{ needs.prepare.outputs.sha }}
- # noinspection UndefinedAction, UndefinedParamsPresent
native-build:
name: Build Native Artifacts
needs: [ prepare ]
@@ -100,8 +103,8 @@ jobs:
pr_number: ${{ inputs.pr_number }}
checkout_ref: ${{ inputs.checkout_ref }}
target_sha: ${{ needs.prepare.outputs.sha }}
+ enable_test_exports: ${{ inputs.enable_test_exports }}
- # noinspection UndefinedAction, UndefinedParamsPresent
dotnetpack-validation:
name: Validate Dotnet Pack
needs: [ prepare, native-build ]
@@ -110,7 +113,6 @@ jobs:
checkout_ref: ${{ inputs.checkout_ref }}
target_sha: ${{ needs.prepare.outputs.sha }}
- # noinspection UndefinedAction, UndefinedParamsPresent
linux:
name: Linux Tests
if: ${{ inputs.run_linux == true }}
@@ -121,9 +123,9 @@ jobs:
checkout_ref: ${{ inputs.checkout_ref }}
target_sha: ${{ needs.prepare.outputs.sha }}
workflow_url: ${{ format('https://github.com/{0}/actions/runs/{1}', github.repository, github.run_id) }}
+ enable_test_exports: ${{ inputs.enable_test_exports }}
secrets: inherit
- # noinspection UndefinedAction, UndefinedParamsPresent
macos:
name: macOS Tests
if: ${{ inputs.run_macos == true }}
@@ -134,9 +136,9 @@ jobs:
checkout_ref: ${{ inputs.checkout_ref }}
target_sha: ${{ needs.prepare.outputs.sha }}
workflow_url: ${{ format('https://github.com/{0}/actions/runs/{1}', github.repository, github.run_id) }}
+ enable_test_exports: ${{ inputs.enable_test_exports }}
secrets: inherit
- # noinspection UndefinedAction, UndefinedParamsPresent
windows:
name: Windows Tests
if: ${{ inputs.run_windows == true }}
@@ -147,9 +149,9 @@ jobs:
checkout_ref: ${{ inputs.checkout_ref }}
target_sha: ${{ needs.prepare.outputs.sha }}
workflow_url: ${{ format('https://github.com/{0}/actions/runs/{1}', github.repository, github.run_id) }}
+ enable_test_exports: ${{ inputs.enable_test_exports }}
secrets: inherit
- # noinspection UndefinedAction, UndefinedParamsPresent
windows-playwright:
name: Windows Playwright Tests
if: ${{ inputs.run_windows_playwright == true }}
@@ -160,9 +162,9 @@ jobs:
checkout_ref: ${{ inputs.checkout_ref }}
target_sha: ${{ needs.prepare.outputs.sha }}
workflow_url: ${{ format('https://github.com/{0}/actions/runs/{1}', github.repository, github.run_id) }}
+ enable_test_exports: ${{ inputs.enable_test_exports }}
secrets: inherit
- # noinspection UndefinedAction, UndefinedParamsPresent
windows-trim-aot:
name: Validate Trim/AOT
if: ${{ inputs.run_trim_aot == true }}
@@ -171,3 +173,4 @@ jobs:
with:
checkout_ref: ${{ inputs.checkout_ref }}
target_sha: ${{ needs.prepare.outputs.sha }}
+ enable_test_exports: ${{ inputs.enable_test_exports }}
diff --git a/.gitignore b/.gitignore
index 6845edc86..8a1c6c100 100644
--- a/.gitignore
+++ b/.gitignore
@@ -346,11 +346,16 @@ healthchecksdb
/src/InfiniFrame.NativeBridge/artifacts/
/src/InfiniFrame.NativeBridge/build/
/src/InfiniFrame.NativeBridge/Native/build/
+/src/InfiniFrame.NativeBridge/Native/build-clang-tidy/
/src/InfiniFrame.NativeBridge/Native/packages/
/src/InfiniFrame.NativeBridge/Native/cmake-build-debug/
/src/InfiniFrame.NativeBridge/Native/cmake-build-debug-linux/
/src/InfiniFrame.NativeBridge/Native/cmake-build-debug-windows/
+/src/InfiniFrame.NativeBridge/Native/cmake-build-release/
+/src/InfiniFrame.NativeBridge/Native/cmake-build-release-linux/
+/src/InfiniFrame.NativeBridge/Native/cmake-build-release-windows/
/src/InfiniFrame.NativeBridge/Native/Embedded/InfiniFrameJs/InfiniFrameJs.cpp
+/src/InfiniFrame.NativeBridge/Native/Embedded/InfiniFrameJs/InfiniFrameJs.h
# wwwroot folders from js web based projects
/examples/InfiniFrameExample.WebApp.React/wwwroot/
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 08a46edde..2307816fb 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -33,9 +33,9 @@
-
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/InfiniFrame.slnx b/InfiniFrame.slnx
index 85f479354..1d28fd2f5 100644
--- a/InfiniFrame.slnx
+++ b/InfiniFrame.slnx
@@ -177,7 +177,6 @@
-
diff --git a/assets/icon.png b/assets/icon.png
index 0d00ee25f..ad89505d6 100644
Binary files a/assets/icon.png and b/assets/icon.png differ
diff --git a/examples/Directory.Build.props b/examples/Directory.Build.props
index 0e955e684..18a8d8e74 100644
--- a/examples/Directory.Build.props
+++ b/examples/Directory.Build.props
@@ -2,10 +2,10 @@
Exe
-
+
net10.0
14.0
-
+
enable
enable
false
diff --git a/examples/InfiniFrameExample.BlazorWebView/Components/App.razor b/examples/InfiniFrameExample.BlazorWebView/Components/App.razor
index 1ad1dd6ec..7978ff4ce 100644
--- a/examples/InfiniFrameExample.BlazorWebView/Components/App.razor
+++ b/examples/InfiniFrameExample.BlazorWebView/Components/App.razor
@@ -1,6 +1,5 @@
@using InfiniFrameExample.BlazorWebView.Components.Layouts
@using InfiniFrameExample.BlazorWebView.Components.Pages
-
diff --git a/examples/InfiniFrameExample.BlazorWebView/Components/Pages/PageNotFound.razor b/examples/InfiniFrameExample.BlazorWebView/Components/Pages/PageNotFound.razor
index 2d1913c9b..20b1013c5 100644
--- a/examples/InfiniFrameExample.BlazorWebView/Components/Pages/PageNotFound.razor
+++ b/examples/InfiniFrameExample.BlazorWebView/Components/Pages/PageNotFound.razor
@@ -1,12 +1,12 @@
@* ------------------------------------------------------------------------------------------------------------------ *@
@* Imports
@* ------------------------------------------------------------------------------------------------------------------ *@
-@using InfiniFrameExample.BlazorWebView.Components.Layouts
@* ------------------------------------------------------------------------------------------------------------------ *@
@* Descriptors
@* ------------------------------------------------------------------------------------------------------------------ *@
@page "/PageNotFound"
+@using InfiniFrameExample.BlazorWebView.Components.Layouts
@layout MainLayout
@* ------------------------------------------------------------------------------------------------------------------ *@
@@ -19,6 +19,6 @@
@* ------------------------------------------------------------------------------------------------------------------ *@
@code {
-
+
}
diff --git a/examples/InfiniFrameExample.BlazorWebView/InfiniFrameExample.BlazorWebView.csproj b/examples/InfiniFrameExample.BlazorWebView/InfiniFrameExample.BlazorWebView.csproj
index 96eda0933..32916d77a 100644
--- a/examples/InfiniFrameExample.BlazorWebView/InfiniFrameExample.BlazorWebView.csproj
+++ b/examples/InfiniFrameExample.BlazorWebView/InfiniFrameExample.BlazorWebView.csproj
@@ -5,9 +5,9 @@
-
-
-
+
+
+
diff --git a/examples/InfiniFrameExample.BlazorWebView/README.md b/examples/InfiniFrameExample.BlazorWebView/README.md
index 92baad31b..e7b4277e6 100644
--- a/examples/InfiniFrameExample.BlazorWebView/README.md
+++ b/examples/InfiniFrameExample.BlazorWebView/README.md
@@ -1,6 +1,7 @@
# Example: BlazorWebView
-Demonstrates the minimal setup for hosting a Blazor application inside a native InfiniFrame window using `InfiniLore.InfiniFrame.BlazorWebView`
+Demonstrates the minimal setup for hosting a Blazor application inside a native InfiniFrame window using
+`InfiniLore.InfiniFrame.BlazorWebView`
## What it shows
diff --git a/examples/InfiniFrameExample.BlazorWebView/wwwroot/sample-data/weather.json b/examples/InfiniFrameExample.BlazorWebView/wwwroot/sample-data/weather.json
index 06463c02f..bed52c923 100644
--- a/examples/InfiniFrameExample.BlazorWebView/wwwroot/sample-data/weather.json
+++ b/examples/InfiniFrameExample.BlazorWebView/wwwroot/sample-data/weather.json
@@ -1,27 +1,27 @@
[
- {
- "date": "2018-05-06",
- "temperatureC": 1,
- "summary": "Freezing"
- },
- {
- "date": "2018-05-07",
- "temperatureC": 14,
- "summary": "Bracing"
- },
- {
- "date": "2018-05-08",
- "temperatureC": -13,
- "summary": "Freezing"
- },
- {
- "date": "2018-05-09",
- "temperatureC": -16,
- "summary": "Balmy"
- },
- {
- "date": "2018-05-10",
- "temperatureC": -2,
- "summary": "Chilly"
- }
+ {
+ "date": "2018-05-06",
+ "temperatureC": 1,
+ "summary": "Freezing"
+ },
+ {
+ "date": "2018-05-07",
+ "temperatureC": 14,
+ "summary": "Bracing"
+ },
+ {
+ "date": "2018-05-08",
+ "temperatureC": -13,
+ "summary": "Freezing"
+ },
+ {
+ "date": "2018-05-09",
+ "temperatureC": -16,
+ "summary": "Balmy"
+ },
+ {
+ "date": "2018-05-10",
+ "temperatureC": -2,
+ "summary": "Chilly"
+ }
]
diff --git a/scripts/BuildFrontend.mjs b/scripts/BuildFrontend.mjs
index 9e7207a31..d30625e6c 100644
--- a/scripts/BuildFrontend.mjs
+++ b/scripts/BuildFrontend.mjs
@@ -45,6 +45,22 @@ function isProcessRunning(processId) {
function shouldRemoveExistingLock() {
const ownerFile = path.join(lockDirectory, 'owner.txt');
const staleLockThresholdMilliseconds = 10 * 60 * 1000;
+ const lockAgeMilliseconds = (() => {
+ try {
+ return Date.now() - statSync(lockDirectory).mtimeMs;
+ } catch (error) {
+ if (error?.code === 'ENOENT') {
+ return null;
+ }
+
+ throw error;
+ }
+ })();
+
+ // PIDs can be recycled by the OS. If the lock is old, remove it regardless of PID state.
+ if (lockAgeMilliseconds !== null && lockAgeMilliseconds > staleLockThresholdMilliseconds) {
+ return true;
+ }
if (existsSync(ownerFile)) {
const ownerProcessId = Number.parseInt(readFileSync(ownerFile, 'utf8'), 10);
@@ -56,17 +72,7 @@ function shouldRemoveExistingLock() {
return false;
}
}
-
- try {
- const lockAgeMilliseconds = Date.now() - statSync(lockDirectory).mtimeMs;
- return lockAgeMilliseconds > staleLockThresholdMilliseconds;
- } catch (error) {
- if (error?.code === 'ENOENT') {
- return false;
- }
-
- throw error;
- }
+ return false;
}
function acquireLock() {
diff --git a/scripts/clean.ps1 b/scripts/clean.ps1
index b15dede17..292fdef01 100644
--- a/scripts/clean.ps1
+++ b/scripts/clean.ps1
@@ -10,4 +10,25 @@ Get-ChildItem -Path $Root -Directory -Recurse |
Remove-Item $_.FullName -Recurse -Force -ErrorAction SilentlyContinue
}
-Write-Host "Done cleaning bin/obj folders."
\ No newline at end of file
+# Additional cleanup paths
+$ExtraPaths = @(
+ "../src/InfiniFrame.NativeBridge/build",
+ "../src/InfiniFrame.NativeBridge/artifacts",
+ "../src/InfiniFrame.Js/node_modules",
+ "../src/InfiniFrame.NativeBridge/Native/cmake-build-debug-linux",
+ "../src/InfiniFrame.NativeBridge/Native/cmake-build-debug-windows",
+ "../src/InfiniFrame.NativeBridge/Native/cmake-build-release-linux",
+ "../src/InfiniFrame.NativeBridge/Native/cmake-build-release-windows",
+ "../src/InfiniFrame.NativeBridge/Native/packages"
+)
+
+foreach ($RelativePath in $ExtraPaths) {
+ $FullPath = Join-Path $Root $RelativePath
+
+ if (Test-Path $FullPath) {
+ Write-Host "Deleting $FullPath"
+ Remove-Item $FullPath -Recurse -Force -ErrorAction SilentlyContinue
+ }
+}
+
+Write-Host "Done cleaning bin/obj folders and extra build artifacts."
\ No newline at end of file
diff --git a/scripts/clion-linux-environment.sh b/scripts/clion-linux-environment.sh
index 20f3bba93..d4584728e 100644
--- a/scripts/clion-linux-environment.sh
+++ b/scripts/clion-linux-environment.sh
@@ -11,10 +11,24 @@ sudo apt install -y \
gnupg \
software-properties-common \
wget \
+ curl \
build-essential \
pkg-config \
lsb-release
+# ----------------------------------------------------------------------------------------------------------------------
+# Node.js 24
+# ----------------------------------------------------------------------------------------------------------------------
+echo "Installing Node.js 24..."
+
+curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
+
+sudo apt install -y nodejs
+
+echo "Node version:"
+node --version
+npm --version
+
# ----------------------------------------------------------------------------------------------------------------------
# CMake (latest via Kitware)
# ----------------------------------------------------------------------------------------------------------------------
@@ -77,6 +91,21 @@ echo "Installing Clang toolchain (optional but recommended)..."
sudo apt install -y clang libc++-dev libc++abi-dev || true
+# ----------------------------------------------------------------------------------------------------------------------
+# GDB / WSL Debugging Support
+# ----------------------------------------------------------------------------------------------------------------------
+echo "Installing GDB debugger support..."
+
+sudo apt install -y \
+ gdb \
+ gdbserver
+
+echo "GDB version:"
+gdb --version || true
+
+echo "GDB path:"
+which gdb || true
+
# ----------------------------------------------------------------------------------------------------------------------
# GTK / WebKit / Native deps
# ----------------------------------------------------------------------------------------------------------------------
@@ -106,6 +135,12 @@ echo "Verifying toolchain..."
echo "CMake version:"
cmake --version
+echo "Node version:"
+node --version
+
+echo "NPM version:"
+npm --version
+
echo "GCC version:"
gcc --version || true
@@ -115,5 +150,12 @@ g++ --version || true
echo "Clang version:"
clang++ --version || true
+echo "GDB version:"
+gdb --version || true
+
+echo ""
+echo "Setup complete!"
+
echo ""
-echo "Setup complete!"
\ No newline at end of file
+echo "Recommended CLion debugger path:"
+echo "/usr/bin/gdb"
\ No newline at end of file
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 61a0873c2..e8c3d3eaf 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -32,8 +32,7 @@
true
true
- false
-
+ true
0.13.1
InfiniFrame, TryPhotino
diff --git a/src/InfiniFrame.NativeBridge/InfiniFrame.NativeBridge.csproj b/src/InfiniFrame.NativeBridge/InfiniFrame.NativeBridge.csproj
index 0423a2968..17a71ac77 100644
--- a/src/InfiniFrame.NativeBridge/InfiniFrame.NativeBridge.csproj
+++ b/src/InfiniFrame.NativeBridge/InfiniFrame.NativeBridge.csproj
@@ -3,11 +3,15 @@
InfiniLore.InfiniFrame.NativeBridge
Library
+ true
true
- x64
- $(Platform)
+ x64
+ x64
+ x64
+ arm64
+ arm64
windows
linux
@@ -18,6 +22,8 @@
$(MSBuildThisFileDirectory)artifacts\native
false
+ true
+ false
$(NativeOutputRoot)\windows\x64\$(Configuration)
$(NativeOutputRoot)\windows\arm64\$(Configuration)
@@ -27,6 +33,10 @@
$(NativeOutputRoot)\osx\arm64\$(Configuration)
+
+ $(DefineConstants);InfiniFrameNativeTestExports
+
+
@@ -48,77 +58,101 @@
-
-
+
+ false
+ InfiniFrame.Native.dll
+ runtimes/win-x64/native/
+ PreserveNewest
+ PreserveNewest
+ true
+
-
+
+ false
+ WebView2Loader.dll
+ runtimes/win-x64/native/
+ PreserveNewest
+ PreserveNewest
+ true
+
-
+
+ false
+ InfiniFrame.Native.dll
+ runtimes/win-arm64/native/
+ PreserveNewest
+ PreserveNewest
+ true
+
-
+
+ false
+ WebView2Loader.dll
+ runtimes/win-arm64/native/
+ PreserveNewest
+ PreserveNewest
+ true
+
-
+
+ false
+ InfiniFrame.Native.so
+ runtimes/linux-x64/native/
+ PreserveNewest
+ PreserveNewest
+ true
+
-
+
+ false
+ InfiniFrame.Native.so
+ runtimes/linux-arm64/native/
+ PreserveNewest
+ PreserveNewest
+ true
+
-
+
+ false
+ InfiniFrame.Native.dylib
+ runtimes/osx-x64/native/
+ PreserveNewest
+ PreserveNewest
+ true
+
-
+
+ false
+ InfiniFrame.Native.dylib
+ runtimes/osx-arm64/native/
+ PreserveNewest
+ PreserveNewest
+ true
+
@@ -127,16 +161,16 @@
-
+
-
+
@@ -147,28 +181,28 @@
Text="✅ Found InfiniFrame.Native.dll in $(WindowsX64)"
Importance="High"/>
@@ -177,28 +211,28 @@
Text="✅ Found InfiniFrame.Native.dll in $(WindowsArm64)"
Importance="High"/>
diff --git a/src/InfiniFrame.NativeBridge/Managed/LibraryImports/InfiniFrameNative.cs b/src/InfiniFrame.NativeBridge/Managed/LibraryImports/InfiniFrameNative.cs
index e6e3732c5..f3fae3bab 100644
--- a/src/InfiniFrame.NativeBridge/Managed/LibraryImports/InfiniFrameNative.cs
+++ b/src/InfiniFrame.NativeBridge/Managed/LibraryImports/InfiniFrameNative.cs
@@ -18,226 +18,229 @@ public static partial class InfiniFrameNative {
#region MARSHAL CALLS FROM Non-UI Thread to UI Thread
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_Invoke", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void Invoke(IntPtr instance, Action callback);
+ internal static partial InfiniFrameNativeInteropStatus Invoke(IntPtr instance, Action callback);
#endregion
#region Register
// ReSharper disable once UnusedMethodReturnValue.Local
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_register_win32", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void RegisterWin32(IntPtr hInstance);
+ internal static partial InfiniFrameNativeInteropStatus RegisterWin32(IntPtr hInstance);
// ReSharper disable once UnusedMethodReturnValue.Local
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_register_mac", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void RegisterMac();
+ internal static partial InfiniFrameNativeInteropStatus RegisterMac();
#endregion
#region CTOR-DTOR
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_ctor", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial IntPtr Constructor([MarshalUsing(typeof(InfiniFrameNativeParametersMarshaller))] in InfiniFrameNativeParameters parameters);
+ internal static partial InfiniFrameNativeInteropStatus Constructor([MarshalUsing(typeof(InfiniFrameNativeParametersMarshaller))] in InfiniFrameNativeParameters parameters, out IntPtr value);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_dtor"), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void Destructor(IntPtr instance);
+ internal static partial InfiniFrameNativeInteropStatus Destructor(IntPtr instance);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_AddCustomSchemeName", SetLastError = true, StringMarshalling = StringMarshalling.Utf8), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void AddCustomSchemeName(IntPtr instance, string scheme);
+ internal static partial InfiniFrameNativeInteropStatus AddCustomSchemeName(IntPtr instance, string scheme);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_Close", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void Close(IntPtr instance);
+ internal static partial InfiniFrameNativeInteropStatus Close(IntPtr instance);
#endregion
#region Get
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_getHwnd_win32", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial IntPtr GetWindowHandlerWin32(IntPtr instance);
+ internal static partial InfiniFrameNativeInteropStatus GetWindowHandlerWin32(IntPtr instance, out IntPtr value);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetAllMonitors", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void GetAllMonitors(IntPtr instance, CppGetAllMonitorsDelegate callback);
+ internal static partial InfiniFrameNativeInteropStatus GetAllMonitors(IntPtr instance, CppGetAllMonitorsDelegate callback);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetTransparentEnabled", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void GetTransparentEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool enabled);
+ internal static partial InfiniFrameNativeInteropStatus GetTransparentEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool enabled);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetContextMenuEnabled", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void GetContextMenuEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool enabled);
+ internal static partial InfiniFrameNativeInteropStatus GetContextMenuEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool enabled);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetDevToolsEnabled", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void GetDevToolsEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool enabled);
+ internal static partial InfiniFrameNativeInteropStatus GetDevToolsEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool enabled);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetFullScreen", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void GetFullScreen(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool fullScreen);
+ internal static partial InfiniFrameNativeInteropStatus GetFullScreen(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool fullScreen);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetGrantBrowserPermissions", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void GetGrantBrowserPermissions(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool grant);
+ internal static partial InfiniFrameNativeInteropStatus GetGrantBrowserPermissions(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool grant);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetUserAgent", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial IntPtr GetUserAgent(IntPtr instance);
+ internal static partial InfiniFrameNativeInteropStatus GetUserAgent(IntPtr instance, out IntPtr value);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetMediaAutoplayEnabled", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void GetMediaAutoplayEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool enabled);
+ internal static partial InfiniFrameNativeInteropStatus GetMediaAutoplayEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool enabled);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetFileSystemAccessEnabled", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void GetFileSystemAccessEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool enabled);
+ internal static partial InfiniFrameNativeInteropStatus GetFileSystemAccessEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool enabled);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetWebSecurityEnabled", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void GetWebSecurityEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool enabled);
+ internal static partial InfiniFrameNativeInteropStatus GetWebSecurityEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool enabled);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetJavascriptClipboardAccessEnabled", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void GetJavascriptClipboardAccessEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool enabled);
+ internal static partial InfiniFrameNativeInteropStatus GetJavascriptClipboardAccessEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool enabled);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetMediaStreamEnabled", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void GetMediaStreamEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool enabled);
+ internal static partial InfiniFrameNativeInteropStatus GetMediaStreamEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool enabled);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetSmoothScrollingEnabled", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void GetSmoothScrollingEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool enabled);
+ internal static partial InfiniFrameNativeInteropStatus GetSmoothScrollingEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool enabled);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetIgnoreCertificateErrorsEnabled", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void GetIgnoreCertificateErrorsEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool enabled);
+ internal static partial InfiniFrameNativeInteropStatus GetIgnoreCertificateErrorsEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool enabled);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetNotificationsEnabled", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void GetNotificationsEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool enabled);
+ internal static partial InfiniFrameNativeInteropStatus GetNotificationsEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool enabled);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetPosition", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void GetPosition(IntPtr instance, out int x, out int y);
+ internal static partial InfiniFrameNativeInteropStatus GetPosition(IntPtr instance, out int x, out int y);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetResizable", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void GetResizable(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool resizable);
+ internal static partial InfiniFrameNativeInteropStatus GetResizable(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool resizable);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetScreenDpi", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial uint GetScreenDpi(IntPtr instance);
+ internal static partial InfiniFrameNativeInteropStatus GetScreenDpi(IntPtr instance, out uint value);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetSize", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void GetSize(IntPtr instance, out int width, out int height);
+ internal static partial InfiniFrameNativeInteropStatus GetSize(IntPtr instance, out int width, out int height);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetMaxSize", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void GetMaxSize(IntPtr instance, out int maxWidth, out int maxHeight);
+ internal static partial InfiniFrameNativeInteropStatus GetMaxSize(IntPtr instance, out int maxWidth, out int maxHeight);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetMinSize", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void GetMinSize(IntPtr instance, out int minWidth, out int minHeight);
+ internal static partial InfiniFrameNativeInteropStatus GetMinSize(IntPtr instance, out int minWidth, out int minHeight);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetTitle", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial IntPtr GetTitle(IntPtr instance);
+ internal static partial InfiniFrameNativeInteropStatus GetTitle(IntPtr instance, out IntPtr value);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetTopmost", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void GetTopmost(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool topmost);
+ internal static partial InfiniFrameNativeInteropStatus GetTopmost(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool topmost);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetZoom", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void GetZoom(IntPtr instance, out int zoom);
+ internal static partial InfiniFrameNativeInteropStatus GetZoom(IntPtr instance, out int zoom);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetMaximized", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void GetMaximized(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool maximized);
+ internal static partial InfiniFrameNativeInteropStatus GetMaximized(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool maximized);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetMinimized", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void GetMinimized(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool minimized);
+ internal static partial InfiniFrameNativeInteropStatus GetMinimized(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool minimized);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetZoomEnabled", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void GetZoomEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool zoomEnabled);
+ internal static partial InfiniFrameNativeInteropStatus GetZoomEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool zoomEnabled);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetIconFileName", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial IntPtr GetIconFileName(IntPtr instance);
+ internal static partial InfiniFrameNativeInteropStatus GetIconFileName(IntPtr instance, out IntPtr value);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetFocused", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void GetFocused(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool isFocused);
+ internal static partial InfiniFrameNativeInteropStatus GetFocused(IntPtr instance, [MarshalAs(UnmanagedType.I1)] out bool isFocused);
#endregion
#region Navigate
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_NavigateToString", SetLastError = true, StringMarshalling = StringMarshalling.Utf8), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void NavigateToString(IntPtr instance, string content);
+ internal static partial InfiniFrameNativeInteropStatus NavigateToString(IntPtr instance, string content);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_NavigateToUrl", SetLastError = true, StringMarshalling = StringMarshalling.Utf8), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void NavigateToUrl(IntPtr instance, string url);
+ internal static partial InfiniFrameNativeInteropStatus NavigateToUrl(IntPtr instance, string url);
#endregion
#region Set
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_setWebView2RuntimePath_win32", SetLastError = true, StringMarshalling = StringMarshalling.Utf8), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void SetWebView2RuntimePath_win32(IntPtr instance, string webView2RuntimePath);
+ internal static partial InfiniFrameNativeInteropStatus SetWebView2RuntimePath_win32(IntPtr instance, string webView2RuntimePath);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_SetTransparentEnabled", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void SetTransparentEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] bool enabled);
+ internal static partial InfiniFrameNativeInteropStatus SetTransparentEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] bool enabled);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_SetContextMenuEnabled", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void SetContextMenuEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] bool enabled);
+ internal static partial InfiniFrameNativeInteropStatus SetContextMenuEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] bool enabled);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_SetDevToolsEnabled", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void SetDevToolsEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] bool enabled);
+ internal static partial InfiniFrameNativeInteropStatus SetDevToolsEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] bool enabled);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_SetFullScreen", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void SetFullScreen(IntPtr instance, [MarshalAs(UnmanagedType.I1)] bool fullScreen);
+ internal static partial InfiniFrameNativeInteropStatus SetFullScreen(IntPtr instance, [MarshalAs(UnmanagedType.I1)] bool fullScreen);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_SetMaximized", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void SetMaximized(IntPtr instance, [MarshalAs(UnmanagedType.I1)] bool maximized);
+ internal static partial InfiniFrameNativeInteropStatus SetMaximized(IntPtr instance, [MarshalAs(UnmanagedType.I1)] bool maximized);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_SetMaxSize", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void SetMaxSize(IntPtr instance, int maxWidth, int maxHeight);
+ internal static partial InfiniFrameNativeInteropStatus SetMaxSize(IntPtr instance, int maxWidth, int maxHeight);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_SetMinimized", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void SetMinimized(IntPtr instance, [MarshalAs(UnmanagedType.I1)] bool minimized);
+ internal static partial InfiniFrameNativeInteropStatus SetMinimized(IntPtr instance, [MarshalAs(UnmanagedType.I1)] bool minimized);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_SetMinSize", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void SetMinSize(IntPtr instance, int minWidth, int minHeight);
+ internal static partial InfiniFrameNativeInteropStatus SetMinSize(IntPtr instance, int minWidth, int minHeight);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_SetResizable", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void SetResizable(IntPtr instance, [MarshalAs(UnmanagedType.I1)] bool resizable);
+ internal static partial InfiniFrameNativeInteropStatus SetResizable(IntPtr instance, [MarshalAs(UnmanagedType.I1)] bool resizable);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_SetPosition", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void SetPosition(IntPtr instance, int x, int y);
+ internal static partial InfiniFrameNativeInteropStatus SetPosition(IntPtr instance, int x, int y);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_SetSize", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void SetSize(IntPtr instance, int width, int height);
+ internal static partial InfiniFrameNativeInteropStatus SetSize(IntPtr instance, int width, int height);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_SetTitle", SetLastError = true, StringMarshalling = StringMarshalling.Utf8), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void SetTitle(IntPtr instance, string? title);
+ internal static partial InfiniFrameNativeInteropStatus SetTitle(IntPtr instance, string? title);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_SetTopmost", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void SetTopmost(IntPtr instance, [MarshalAs(UnmanagedType.I1)] bool topmost);
+ internal static partial InfiniFrameNativeInteropStatus SetTopmost(IntPtr instance, [MarshalAs(UnmanagedType.I1)] bool topmost);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_SetIconFile", SetLastError = true, StringMarshalling = StringMarshalling.Utf8), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void SetIconFile(IntPtr instance, string filename);
+ internal static partial InfiniFrameNativeInteropStatus SetIconFile(IntPtr instance, string filename);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_SetZoom", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void SetZoom(IntPtr instance, int zoom);
+ internal static partial InfiniFrameNativeInteropStatus SetZoom(IntPtr instance, int zoom);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_SetZoomEnabled", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void SetZoomEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] bool zoomEnabled);
+ internal static partial InfiniFrameNativeInteropStatus SetZoomEnabled(IntPtr instance, [MarshalAs(UnmanagedType.I1)] bool zoomEnabled);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_SetFocused", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void SetFocused(IntPtr instance);
+ internal static partial InfiniFrameNativeInteropStatus SetFocused(IntPtr instance);
#endregion
#region Misc
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_Center", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void Center(IntPtr instance);
+ internal static partial InfiniFrameNativeInteropStatus Center(IntPtr instance);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_Restore", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void Restore(IntPtr instance);
+ internal static partial InfiniFrameNativeInteropStatus Restore(IntPtr instance);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_ClearBrowserAutoFill", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void ClearBrowserAutoFill(IntPtr instance);
+ internal static partial InfiniFrameNativeInteropStatus ClearBrowserAutoFill(IntPtr instance);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_SendWebMessage", SetLastError = true, StringMarshalling = StringMarshalling.Utf8), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void SendWebMessage(IntPtr instance, string message);
+ internal static partial InfiniFrameNativeInteropStatus SendWebMessage(IntPtr instance, string message);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_ShowNotification", SetLastError = true, StringMarshalling = StringMarshalling.Utf8), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void ShowNotification(IntPtr instance, string title, string body);
+ internal static partial InfiniFrameNativeInteropStatus ShowNotification(IntPtr instance, string title, string body);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_WaitForExit", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void WaitForExit(IntPtr instance);
+ internal static partial InfiniFrameNativeInteropStatus WaitForExit(IntPtr instance);
#endregion
#region Dialog
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_ShowOpenFile", SetLastError = true, StringMarshalling = StringMarshalling.Utf8), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial IntPtr ShowOpenFile(IntPtr inst, string title, string defaultPath, [MarshalAs(UnmanagedType.I1)] bool multiSelect, string[] filters, int filtersCount, out int resultCount);
+ internal static partial InfiniFrameNativeInteropStatus ShowOpenFile(IntPtr inst, string title, string defaultPath, [MarshalAs(UnmanagedType.I1)] bool multiSelect, string[] filters, int filtersCount, out int resultCount, out IntPtr values);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_ShowOpenFolder", SetLastError = true, StringMarshalling = StringMarshalling.Utf8), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial IntPtr ShowOpenFolder(IntPtr inst, string title, string defaultPath, [MarshalAs(UnmanagedType.I1)] bool multiSelect, out int resultCount);
+ internal static partial InfiniFrameNativeInteropStatus ShowOpenFolder(IntPtr inst, string title, string defaultPath, [MarshalAs(UnmanagedType.I1)] bool multiSelect, out int resultCount, out IntPtr values);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_ShowSaveFile", SetLastError = true, StringMarshalling = StringMarshalling.Utf8), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial IntPtr ShowSaveFile(IntPtr inst, string title, string defaultPath, string[] filters, int filtersCount, string? defaultFileName);
+ internal static partial InfiniFrameNativeInteropStatus ShowSaveFile(IntPtr inst, string title, string defaultPath, string[] filters, int filtersCount, string? defaultFileName, out IntPtr value);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_ShowMessage", SetLastError = true, StringMarshalling = StringMarshalling.Utf8), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial InfiniFrameDialogResult ShowMessage(IntPtr inst, string title, string text, InfiniFrameDialogButtons buttons, InfiniFrameDialogIcon icon);
+ internal static partial InfiniFrameNativeInteropStatus ShowMessage(IntPtr inst, string title, string text, InfiniFrameDialogButtons buttons, InfiniFrameDialogIcon icon, out InfiniFrameDialogResult value);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_FreeString", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void FreeString(IntPtr value);
+ internal static partial InfiniFrameNativeInteropStatus FreeString(IntPtr value);
[LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_FreeStringArray", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- internal static partial void FreeStringArray(IntPtr values, int count);
+ internal static partial InfiniFrameNativeInteropStatus FreeStringArray(IntPtr values, int count);
+
+ [LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrame_GetLastErrorMessage", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ private static partial InfiniFrameNativeInteropStatus GetLastErrorMessagePtr(out IntPtr value);
#endregion
#region Overloads
@@ -249,59 +252,147 @@ public static partial class InfiniFrameNative {
: Marshal.PtrToStringUTF8(ptr);
}
- internal static void GetHeight(IntPtr instance, out int height) => GetSize(instance, out _, out height);
- internal static void GetWidth(IntPtr instance, out int width) => GetSize(instance, out width, out _);
- internal static void GetMaxHeight(IntPtr instance, out int maxHeight) => GetMaxSize(instance, out _, out maxHeight);
- internal static void GetMaxWidth(IntPtr instance, out int maxWidth) => GetMaxSize(instance, out maxWidth, out _);
- internal static void GetMinHeight(IntPtr instance, out int minHeight) => GetMinSize(instance, out _, out minHeight);
- internal static void GetMinWidth(IntPtr instance, out int minWidth) => GetMinSize(instance, out minWidth, out _);
+ internal static InfiniFrameNativeInteropStatus GetHeight(IntPtr instance, out int height)
+ => GetSize(instance, out _, out height);
+
+ internal static InfiniFrameNativeInteropStatus GetWidth(IntPtr instance, out int width)
+ => GetSize(instance, out width, out _);
+
+ internal static InfiniFrameNativeInteropStatus GetMaxHeight(IntPtr instance, out int maxHeight)
+ => GetMaxSize(instance, out _, out maxHeight);
+
+ internal static InfiniFrameNativeInteropStatus GetMaxWidth(IntPtr instance, out int maxWidth)
+ => GetMaxSize(instance, out maxWidth, out _);
+
+ internal static InfiniFrameNativeInteropStatus GetMinHeight(IntPtr instance, out int minHeight)
+ => GetMinSize(instance, out _, out minHeight);
+
+ internal static InfiniFrameNativeInteropStatus GetMinWidth(IntPtr instance, out int minWidth)
+ => GetMinSize(instance, out minWidth, out _);
+
+ internal static InfiniFrameNativeInteropStatus GetLeft(IntPtr instance, out int left)
+ => GetPosition(instance, out left, out _);
- internal static void GetLeft(IntPtr instance, out int left) => GetPosition(instance, out left, out _);
- internal static void GetTop(IntPtr instance, out int top) => GetPosition(instance, out _, out top);
+ internal static InfiniFrameNativeInteropStatus GetTop(IntPtr instance, out int top)
+ => GetPosition(instance, out _, out top);
- internal static void GetSize(IntPtr instance, out Size size) {
- GetSize(instance, out int width, out int height);
+ internal static InfiniFrameNativeInteropStatus GetSize(IntPtr instance, out Size size) {
+ InfiniFrameNativeInteropStatus status = GetSize(instance, out int width, out int height);
size = new Size(width, height);
+ return status;
}
- internal static void GetMaxSize(IntPtr instance, out Size size) {
- GetMaxSize(instance, out int width, out int height);
+ internal static InfiniFrameNativeInteropStatus GetMaxSize(IntPtr instance, out Size size) {
+ InfiniFrameNativeInteropStatus status = GetMaxSize(instance, out int width, out int height);
size = new Size(width, height);
+ return status;
}
- internal static void GetMinSize(IntPtr instance, out Size size) {
- GetMinSize(instance, out int width, out int height);
+ internal static InfiniFrameNativeInteropStatus GetMinSize(IntPtr instance, out Size size) {
+ InfiniFrameNativeInteropStatus status = GetMinSize(instance, out int width, out int height);
size = new Size(width, height);
+ return status;
}
- internal static void GetPosition(IntPtr instance, out Point position) {
- GetPosition(instance, out int left, out int top);
+ internal static InfiniFrameNativeInteropStatus GetPosition(IntPtr instance, out Point position) {
+ InfiniFrameNativeInteropStatus status = GetPosition(instance, out int left, out int top);
position = new Point(left, top);
+ return status;
}
- internal static void GetWindowRectangle(IntPtr instance, out int x, out int y, out int width, out int height) {
- GetSize(instance, out width, out height);
- GetPosition(instance, out x, out y);
+ internal static InfiniFrameNativeInteropStatus GetWindowRectangle(IntPtr instance, out int x, out int y, out int width, out int height) {
+ InfiniFrameNativeInteropStatus sizeStatus = GetSize(instance, out width, out height);
+ if (sizeStatus != InfiniFrameNativeInteropStatus.Success) {
+ x = 0;
+ y = 0;
+ return sizeStatus;
+ }
+
+ return GetPosition(instance, out x, out y);
}
- internal static void GetWindowRectangle(IntPtr instance, out Rectangle rectangle) {
- GetWindowRectangle(instance, out int x, out int y, out int width, out int height);
+ internal static InfiniFrameNativeInteropStatus GetWindowRectangle(IntPtr instance, out Rectangle rectangle) {
+ InfiniFrameNativeInteropStatus status = GetWindowRectangle(instance, out int x, out int y, out int width, out int height);
rectangle = new Rectangle(x, y, width, height);
+ return status;
+ }
+
+ internal static InfiniFrameNativeInteropStatus GetUserAgent(IntPtr instance, out string? userAgent) {
+ InfiniFrameNativeInteropStatus status = GetUserAgent(instance, out IntPtr ptr);
+ try {
+ userAgent = PtrToNativeString(ptr);
+ }
+ finally {
+ if (ptr != IntPtr.Zero) {
+ FreeString(ptr);
+ }
+ }
+
+ return status;
}
- internal static void GetUserAgent(IntPtr instance, out string? userAgent) {
- IntPtr ptr = GetUserAgent(instance);
- userAgent = PtrToNativeString(ptr);
+ internal static InfiniFrameNativeInteropStatus GetTitle(IntPtr instance, out string? title) {
+ InfiniFrameNativeInteropStatus status = GetTitle(instance, out IntPtr ptr);
+ try {
+ title = PtrToNativeString(ptr);
+ }
+ finally {
+ if (ptr != IntPtr.Zero) {
+ FreeString(ptr);
+ }
+ }
+
+ return status;
}
- internal static void GetTitle(IntPtr instance, out string? title) {
- IntPtr ptr = GetTitle(instance);
- title = PtrToNativeString(ptr);
+ internal static InfiniFrameNativeInteropStatus GetIconFileName(IntPtr instance, out string iconFileName) {
+ InfiniFrameNativeInteropStatus status = GetIconFileName(instance, out IntPtr ptr);
+ try {
+ iconFileName = PtrToNativeString(ptr) ?? string.Empty;
+ }
+ finally {
+ if (ptr != IntPtr.Zero) {
+ FreeString(ptr);
+ }
+ }
+
+ return status;
+ }
+
+ internal static string? GetLastErrorMessage() {
+ InfiniFrameNativeInteropStatus status = GetLastErrorMessagePtr(out IntPtr ptr);
+ if (status != InfiniFrameNativeInteropStatus.Success || ptr == IntPtr.Zero) return null;
+
+ try {
+ return PtrToNativeString(ptr);
+ }
+ finally {
+ FreeString(ptr);
+ }
}
- internal static void GetIconFileName(IntPtr instance, out string iconFileName) {
- IntPtr ptr = GetIconFileName(instance);
- iconFileName = PtrToNativeString(ptr) ?? string.Empty;
+ internal static InfiniFrameNativeInteropStatus EnsureSucceeded(InfiniFrameNativeInteropStatus status, string operationName) {
+
+ int fallbackLastError = Marshal.GetLastPInvokeError();
+
+ if (status is InfiniFrameNativeInteropStatus.Success && fallbackLastError is 0) return status;
+
+ InfiniFrameNativeInteropStatus fallbackStatus = GetLastErrorMessagePtr(out IntPtr ptr);
+
+ string? fallbackMessage;
+ if (fallbackStatus != InfiniFrameNativeInteropStatus.Success || ptr == IntPtr.Zero) {
+ fallbackMessage = "No native error message provided.";
+ }
+ else {
+ try {
+ fallbackMessage = PtrToNativeString(ptr);
+ }
+ finally {
+ FreeString(ptr);
+ }
+ }
+
+ throw new ApplicationException($"Native interop call '{operationName}' failed with unknown status state. Fallback last error {fallbackLastError}. {fallbackMessage} {fallbackStatus}");
}
#endregion
}
diff --git a/src/InfiniFrame.NativeBridge/Managed/LibraryImports/InfiniFrameNativeInteropStatus.cs b/src/InfiniFrame.NativeBridge/Managed/LibraryImports/InfiniFrameNativeInteropStatus.cs
new file mode 100644
index 000000000..d86e6c874
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Managed/LibraryImports/InfiniFrameNativeInteropStatus.cs
@@ -0,0 +1,13 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+namespace InfiniFrame.NativeBridge;
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+internal enum InfiniFrameNativeInteropStatus {
+ Success = 0,
+ InvalidArgument = 22,
+ OutParameterSetToInvalidNull = 2001,
+ OperationFailed = 14
+}
diff --git a/src/InfiniFrame.NativeBridge/Managed/LibraryImports/InfiniFrameNativeTesting.cs b/src/InfiniFrame.NativeBridge/Managed/LibraryImports/InfiniFrameNativeTesting.cs
index 1eb408204..cb545f853 100644
--- a/src/InfiniFrame.NativeBridge/Managed/LibraryImports/InfiniFrameNativeTesting.cs
+++ b/src/InfiniFrame.NativeBridge/Managed/LibraryImports/InfiniFrameNativeTesting.cs
@@ -12,22 +12,25 @@ namespace InfiniFrame.NativeBridge;
// Code
// ---------------------------------------------------------------------------------------------------------------------
public static partial class InfiniFrameNativeTesting {
- [LibraryImport(NativeLibraryName, EntryPoint = "InfiniWindowTests_NativeParametersReturnAsIs", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- private static partial void NativeParametersReturnAsIsNative(
+// #if InfiniFrameNativeTestExports
+ [LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrameNativeTests_NativeParametersReturnAsIs", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ private static partial InfiniFrameNativeInteropStatus NativeParametersReturnAsIsNative(
[MarshalUsing(typeof(InfiniFrameNativeParametersMarshaller))]
in InfiniFrameNativeParameters parameters,
out IntPtr newParameters
);
- [LibraryImport(NativeLibraryName, EntryPoint = "InfiniWindowTests_FreeInitParams", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
- private static partial void FreeInitParamsNative(IntPtr parameters);
+ [LibraryImport(NativeLibraryName, EntryPoint = "InfiniFrameNativeTests_FreeInitParams", SetLastError = true), UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
+ private static partial InfiniFrameNativeInteropStatus FreeInitParamsNative(IntPtr parameters);
///
/// Returns a native pointer to a newly allocated InfiniFrameInitParams clone.
/// Ownership is transferred to managed caller, which must call exactly once.
///
internal static IntPtr NativeParametersReturnAsIsPtr(ref InfiniFrameNativeParameters parameters) {
- NativeParametersReturnAsIsNative(in parameters, out IntPtr newParametersPtr);
+ InfiniFrameNative.EnsureSucceeded(
+ NativeParametersReturnAsIsNative(in parameters, out IntPtr newParametersPtr),
+ nameof(NativeParametersReturnAsIsNative));
// ReSharper disable once ConvertIfStatementToReturnStatement
if (newParametersPtr == IntPtr.Zero) throw new InvalidOperationException("Native function returned null pointer");
@@ -38,6 +41,19 @@ internal static IntPtr NativeParametersReturnAsIsPtr(ref InfiniFrameNativeParame
internal static void FreeInitParams(IntPtr newParametersPtr) {
if (newParametersPtr == IntPtr.Zero) return;
- FreeInitParamsNative(newParametersPtr);
+ InfiniFrameNative.EnsureSucceeded(
+ FreeInitParamsNative(newParametersPtr),
+ nameof(FreeInitParamsNative));
}
+// #else
+// internal static IntPtr NativeParametersReturnAsIsPtr(ref InfiniFrameNativeParameters parameters) {
+// throw new PlatformNotSupportedException("InfiniFrame native test exports are not enabled for this build.");
+// }
+//
+// internal static void FreeInitParams(IntPtr newParametersPtr) {
+// if (newParametersPtr != IntPtr.Zero) {
+// throw new PlatformNotSupportedException("InfiniFrame native test exports are not enabled for this build.");
+// }
+// }
+// #endif
}
diff --git a/src/InfiniFrame.NativeBridge/Native/.clang-format b/src/InfiniFrame.NativeBridge/Native/.clang-format
new file mode 100644
index 000000000..da19dd245
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/.clang-format
@@ -0,0 +1,119 @@
+BasedOnStyle: Microsoft
+Language: Cpp
+
+# ----------------------------------------------------------------------------------------------------------------------
+# General Style
+# ----------------------------------------------------------------------------------------------------------------------
+
+IndentWidth: 4
+TabWidth: 4
+UseTab: Never
+
+ColumnLimit: 120
+MaxEmptyLinesToKeep: 1
+
+DerivePointerAlignment: false
+PointerAlignment: Left
+ReferenceAlignment: Left
+
+NamespaceIndentation: All
+
+SortIncludes: false
+SortUsingDeclarations: false
+
+# ----------------------------------------------------------------------------------------------------------------------
+# Braces / K&R Style
+# ----------------------------------------------------------------------------------------------------------------------
+
+BreakBeforeBraces: Attach
+
+BraceWrapping:
+ AfterClass: false
+ AfterControlStatement: false
+ AfterEnum: false
+ AfterFunction: false
+ AfterNamespace: false
+ AfterStruct: false
+ AfterUnion: false
+ BeforeCatch: true
+ BeforeElse: true
+ IndentBraces: false
+ SplitEmptyFunction: false
+ SplitEmptyRecord: false
+ SplitEmptyNamespace: false
+
+# ----------------------------------------------------------------------------------------------------------------------
+# Short Statements
+# ----------------------------------------------------------------------------------------------------------------------
+
+AllowShortBlocksOnASingleLine: Empty
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: Empty
+AllowShortIfStatementsOnASingleLine: Never
+AllowShortLoopsOnASingleLine: false
+AllowShortEnumsOnASingleLine: false
+
+# ----------------------------------------------------------------------------------------------------------------------
+# Templates
+# ----------------------------------------------------------------------------------------------------------------------
+
+AlwaysBreakTemplateDeclarations: MultiLine
+
+# ----------------------------------------------------------------------------------------------------------------------
+# Spacing
+# ----------------------------------------------------------------------------------------------------------------------
+
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesInParentheses: false
+SpacesInAngles: Never
+SpacesInContainerLiterals: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCpp11BracedList: false
+
+# ----------------------------------------------------------------------------------------------------------------------
+# Alignment
+# ----------------------------------------------------------------------------------------------------------------------
+
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignOperands: DontAlign
+AlignTrailingComments: false
+
+# ----------------------------------------------------------------------------------------------------------------------
+# Access Modifiers
+# ----------------------------------------------------------------------------------------------------------------------
+
+IndentAccessModifiers: false
+AccessModifierOffset: 0
+
+# ----------------------------------------------------------------------------------------------------------------------
+# Includes
+# ----------------------------------------------------------------------------------------------------------------------
+
+IncludeBlocks: Preserve
+
+# ----------------------------------------------------------------------------------------------------------------------
+# Line Breaking
+# ----------------------------------------------------------------------------------------------------------------------
+
+AlignAfterOpenBracket: BlockIndent
+BinPackParameters: false
+BreakConstructorInitializers: BeforeComma
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+
+# ----------------------------------------------------------------------------------------------------------------------
+# C++11/20 Friendly
+# ----------------------------------------------------------------------------------------------------------------------
+
+Standard: Latest
+Cpp11BracedListStyle: true
+
+# ----------------------------------------------------------------------------------------------------------------------
+# Misc
+# ----------------------------------------------------------------------------------------------------------------------
+
+IndentCaseLabels: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+ReflowComments: false
diff --git a/src/InfiniFrame.NativeBridge/Native/.clang-tidy b/src/InfiniFrame.NativeBridge/Native/.clang-tidy
index e7949f724..351dac4af 100644
--- a/src/InfiniFrame.NativeBridge/Native/.clang-tidy
+++ b/src/InfiniFrame.NativeBridge/Native/.clang-tidy
@@ -1,146 +1,100 @@
-# Generated from CLion Inspection settings
----
-Checks: '-*,
-bugprone-argument-comment,
-bugprone-assert-side-effect,
-bugprone-bad-signal-to-kill-thread,
-bugprone-branch-clone,
-bugprone-copy-constructor-init,
-bugprone-dangling-handle,
-bugprone-dynamic-static-initializers,
-bugprone-fold-init-type,
-bugprone-forward-declaration-namespace,
-bugprone-forwarding-reference-overload,
-bugprone-inaccurate-erase,
-bugprone-incorrect-roundings,
-bugprone-integer-division,
-bugprone-lambda-function-name,
-bugprone-macro-parentheses,
-bugprone-macro-repeated-side-effects,
-bugprone-misplaced-operator-in-strlen-in-alloc,
-bugprone-misplaced-pointer-arithmetic-in-alloc,
-bugprone-misplaced-widening-cast,
-bugprone-move-forwarding-reference,
-bugprone-multiple-statement-macro,
-bugprone-no-escape,
-bugprone-parent-virtual-call,
-bugprone-posix-return,
-bugprone-reserved-identifier,
-bugprone-sizeof-container,
-bugprone-sizeof-expression,
-bugprone-spuriously-wake-up-functions,
-bugprone-string-constructor,
-bugprone-string-integer-assignment,
-bugprone-string-literal-with-embedded-nul,
-bugprone-suspicious-enum-usage,
-bugprone-suspicious-include,
-bugprone-suspicious-memset-usage,
-bugprone-suspicious-missing-comma,
-bugprone-suspicious-semicolon,
-bugprone-suspicious-string-compare,
-bugprone-suspicious-memory-comparison,
-bugprone-suspicious-realloc-usage,
-bugprone-swapped-arguments,
-bugprone-terminating-continue,
-bugprone-throw-keyword-missing,
-bugprone-too-small-loop-variable,
-bugprone-undefined-memory-manipulation,
-bugprone-undelegated-constructor,
-bugprone-unhandled-self-assignment,
-bugprone-unused-raii,
-bugprone-unused-return-value,
-bugprone-use-after-move,
-bugprone-virtual-near-miss,
-cert-dcl21-cpp,
-cert-dcl58-cpp,
-cert-err34-c,
-cert-err52-cpp,
-cert-err60-cpp,
-cert-flp30-c,
-cert-msc50-cpp,
-cert-msc51-cpp,
-cert-str34-c,
-cppcoreguidelines-interfaces-global-init,
-cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-pro-type-member-init,
-cppcoreguidelines-pro-type-static-cast-downcast,
-cppcoreguidelines-slicing,
-google-default-arguments,
-google-explicit-constructor,
-google-runtime-operator,
-hicpp-exception-baseclass,
-hicpp-multiway-paths-covered,
-misc-misplaced-const,
-misc-new-delete-overloads,
-misc-non-copyable-objects,
-misc-throw-by-value-catch-by-reference,
-misc-unconventional-assign-operator,
-misc-uniqueptr-reset-release,
-modernize-avoid-bind,
-modernize-concat-nested-namespaces,
-modernize-deprecated-headers,
-modernize-deprecated-ios-base-aliases,
-modernize-loop-convert,
-modernize-make-shared,
-modernize-make-unique,
-modernize-pass-by-value,
-modernize-raw-string-literal,
-modernize-redundant-void-arg,
-modernize-replace-auto-ptr,
-modernize-replace-disallow-copy-and-assign-macro,
-modernize-replace-random-shuffle,
-modernize-return-braced-init-list,
-modernize-shrink-to-fit,
-modernize-unary-static-assert,
-modernize-use-auto,
-modernize-use-bool-literals,
-modernize-use-emplace,
-modernize-use-equals-default,
-modernize-use-equals-delete,
-modernize-use-nodiscard,
-modernize-use-noexcept,
-modernize-use-nullptr,
-modernize-use-override,
-modernize-use-transparent-functors,
-modernize-use-uncaught-exceptions,
-mpi-buffer-deref,
-mpi-type-mismatch,
-openmp-use-default-none,
-performance-faster-string-find,
-performance-for-range-copy,
-performance-implicit-conversion-in-loop,
-performance-inefficient-algorithm,
-performance-inefficient-string-concatenation,
-performance-inefficient-vector-operation,
-performance-move-const-arg,
-performance-move-constructor-init,
-performance-no-automatic-move,
-performance-noexcept-move-constructor,
-performance-trivially-destructible,
-performance-type-promotion-in-math-fn,
-performance-unnecessary-copy-initialization,
-performance-unnecessary-value-param,
-portability-simd-intrinsics,
-readability-avoid-const-params-in-decls,
-readability-const-return-type,
-readability-container-size-empty,
-readability-convert-member-functions-to-static,
-readability-delete-null-pointer,
-readability-deleted-default,
-readability-inconsistent-declaration-parameter-name,
-readability-make-member-function-const,
-readability-misleading-indentation,
-readability-misplaced-array-index,
-readability-non-const-parameter,
-readability-redundant-control-flow,
-readability-redundant-declaration,
-readability-redundant-function-ptr-dereference,
-readability-redundant-smartptr-get,
-readability-redundant-string-cstr,
-readability-redundant-string-init,
-readability-simplify-subscript-expr,
-readability-static-accessed-through-instance,
-readability-static-definition-in-anonymous-namespace,
-readability-string-compare,
-readability-uniqueptr-delete-release,
-readability-use-anyofallof'
\ No newline at end of file
+Checks: >
+ -*,
+ bugprone-*,
+ cppcoreguidelines-*,
+ modernize-*,
+ performance-*,
+ readability-*,
+ -modernize-use-trailing-return-type,
+ -cppcoreguidelines-pro-bounds-pointer-arithmetic,
+ -cppcoreguidelines-pro-type-reinterpret-cast,
+ -cppcoreguidelines-pro-type-union-access,
+ -cppcoreguidelines-owning-memory,
+ -hicpp-*,
+
+WarningsAsErrors: ''
+
+HeaderFilterRegex: '.*'
+
+FormatStyle: file
+
+CheckOptions:
+
+ # --------------------------------------------------------------------------------------------------------------------
+ # Naming Rules (C#-Style)
+ # --------------------------------------------------------------------------------------------------------------------
+
+ - key: readability-identifier-naming.ClassCase
+ value: CamelCase
+
+ - key: readability-identifier-naming.StructCase
+ value: CamelCase
+
+ - key: readability-identifier-naming.EnumCase
+ value: CamelCase
+
+ - key: readability-identifier-naming.EnumConstantCase
+ value: CamelCase
+
+ - key: readability-identifier-naming.FunctionCase
+ value: CamelCase
+
+ - key: readability-identifier-naming.MethodCase
+ value: CamelCase
+
+ - key: readability-identifier-naming.NamespaceCase
+ value: CamelCase
+
+ - key: readability-identifier-naming.VariableCase
+ value: camelBack
+
+ - key: readability-identifier-naming.ParameterCase
+ value: camelBack
+
+ - key: readability-identifier-naming.MemberCase
+ value: CamelCase
+
+ - key: readability-identifier-naming.PrivateMemberPrefix
+ value: _
+
+ - key: readability-identifier-naming.PrivateMemberCase
+ value: camelBack
+
+ - key: readability-identifier-naming.ProtectedMemberPrefix
+ value: _
+
+ - key: readability-identifier-naming.ProtectedMemberCase
+ value: camelBack
+
+ - key: readability-identifier-naming.ConstantCase
+ value: CamelCase
+
+ - key: readability-identifier-naming.StaticConstantCase
+ value: CamelCase
+
+ # --------------------------------------------------------------------------------------------------------------------
+ # Modernization
+ # --------------------------------------------------------------------------------------------------------------------
+
+ - key: modernize-use-nullptr.NullMacros
+ value: 'NULL'
+
+ - key: modernize-loop-convert.MinConfidence
+ value: reasonable
+
+ # --------------------------------------------------------------------------------------------------------------------
+ # Readability
+ # --------------------------------------------------------------------------------------------------------------------
+
+ - key: readability-function-cognitive-complexity.Threshold
+ value: '25'
+
+ - key: readability-function-size.LineThreshold
+ value: '300'
+
+ # --------------------------------------------------------------------------------------------------------------------
+ # Performance
+ # --------------------------------------------------------------------------------------------------------------------
+
+ - key: performance-move-const-arg.CheckTriviallyCopyableMove
+ value: 'false'
diff --git a/src/InfiniFrame.NativeBridge/Native/.cmake/Embed.InfiniFrameJs.Impl.cmake b/src/InfiniFrame.NativeBridge/Native/.cmake/Embed.InfiniFrameJs.Impl.cmake
index 0ea2fbf01..c8dbf964f 100644
--- a/src/InfiniFrame.NativeBridge/Native/.cmake/Embed.InfiniFrameJs.Impl.cmake
+++ b/src/InfiniFrame.NativeBridge/Native/.cmake/Embed.InfiniFrameJs.Impl.cmake
@@ -19,23 +19,28 @@ endforeach()
string(TIMESTAMP GENERATED_AT "%Y-%m-%d %H:%M:%S UTC" UTC)
# Header file
-file(WRITE "${OUTPUT_HEADER}" "#pragma once
+file(WRITE "${OUTPUT_HEADER}" "// -----------------------------------------------------------------------------
+// Auto-generated file. Do not edit manually.
+// Generated at: ${GENERATED_AT}
+// -----------------------------------------------------------------------------
+#pragma once
+
// ReSharper disable once CppUnusedIncludeDirective
#include
-extern const unsigned char g_infiniframe_js_data[];
-extern const size_t g_infiniframe_js_size;
+extern const unsigned char GInfiniframeJsData[]; // NOLINT(*-avoid-c-arrays)
+extern const size_t GInfiniframeJsSize;
")
# Source file
-file(WRITE "${OUTPUT_SOURCE}" "#include \"InfiniFrameJs.h\"
-
-// -----------------------------------------------------------------------------
+file(WRITE "${OUTPUT_SOURCE}" "// -----------------------------------------------------------------------------
// Auto-generated file. Do not edit manually.
// Generated at: ${GENERATED_AT}
// -----------------------------------------------------------------------------
-alignas(16) const unsigned char g_infiniframe_js_data[] = {${BYTES}};
+#include \"Embedded/InfiniFrameJs/InfiniFrameJs.h\"
+
+alignas(16) const unsigned char GInfiniframeJsData[] = {${BYTES}};
-const size_t g_infiniframe_js_size = sizeof(g_infiniframe_js_data);
+const size_t GInfiniframeJsSize = sizeof(GInfiniframeJsData);
")
\ No newline at end of file
diff --git a/src/InfiniFrame.NativeBridge/Native/.cmake/Platform.MacOS.cmake b/src/InfiniFrame.NativeBridge/Native/.cmake/Platform.MacOS.cmake
index 8be141301..90f94f39d 100644
--- a/src/InfiniFrame.NativeBridge/Native/.cmake/Platform.MacOS.cmake
+++ b/src/InfiniFrame.NativeBridge/Native/.cmake/Platform.MacOS.cmake
@@ -1,26 +1,29 @@
# Configure the macOS native target.
# Params:
# - target_name: final CMake target name (usually `${PROJECT_NAME}`)
+# - common_sources: list of cross-platform source files
+# - test_sources: list of test/helper source files compiled into the native target
# - mac_sources: list of macOS-only source files
# - header_files: list of header files for IDE organization
-function(infiniframe_configure_macos_target target_name mac_sources header_files)
- configure_file(Exports.cpp ${CMAKE_CURRENT_BINARY_DIR}/Exports.mm COPYONLY)
- configure_file(Exports.Tests.cpp ${CMAKE_CURRENT_BINARY_DIR}/Exports.Tests.mm COPYONLY)
-
+function(infiniframe_configure_macos_target target_name common_sources test_sources mac_sources header_files)
add_library(${target_name} SHARED
- ${CMAKE_CURRENT_BINARY_DIR}/Exports.mm
- ${CMAKE_CURRENT_BINARY_DIR}/Exports.Tests.mm
+ ${common_sources}
+ ${test_sources}
${mac_sources}
${header_files}
)
+ # Export units include platform headers that require Objective-C++ on macOS.
+ set_source_files_properties(${common_sources} ${test_sources} PROPERTIES
+ LANGUAGE OBJCXX
+ )
+
target_include_directories(${target_name} PRIVATE "${CMAKE_SOURCE_DIR}")
set_target_properties(${target_name} PROPERTIES
PREFIX ""
OUTPUT_NAME "InfiniFrame.Native"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
- OSX_ARCHITECTURES "x86_64;arm64"
)
target_link_libraries(${target_name} PRIVATE
@@ -34,7 +37,10 @@ function(infiniframe_configure_macos_target target_name mac_sources header_files
target_compile_options(${target_name} PRIVATE
-Wall -Wextra
- -O2
+ $<$:-O0 -g>
+ $<$:-O2>
+ $<$:-O2 -g>
+ $<$:-Os>
-fPIC
)
endfunction()
diff --git a/src/InfiniFrame.NativeBridge/Native/.idea/cmake.xml b/src/InfiniFrame.NativeBridge/Native/.idea/cmake.xml
new file mode 100644
index 000000000..6b28f1cd6
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/.idea/cmake.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/InfiniFrame.NativeBridge/Native/CMakeLists.txt b/src/InfiniFrame.NativeBridge/Native/CMakeLists.txt
index bf0790ce3..b3ffb5c9e 100644
--- a/src/InfiniFrame.NativeBridge/Native/CMakeLists.txt
+++ b/src/InfiniFrame.NativeBridge/Native/CMakeLists.txt
@@ -1,20 +1,23 @@
-cmake_minimum_required(VERSION 4.0)
+cmake_minimum_required(VERSION 4.0)
project(InfiniFrame.Native
VERSION 1.1.1
LANGUAGES CXX
DESCRIPTION "InfiniFrame.Native webview-wrapper library"
)
+if (APPLE)
+ enable_language(OBJCXX)
+endif ()
+
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
+set(CMAKE_OBJCXX_STANDARD 23)
+set(CMAKE_OBJCXX_STANDARD_REQUIRED ON)
+set(CMAKE_OBJCXX_EXTENSIONS OFF)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>")
-if (APPLE)
- set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64" CACHE STRING "" FORCE)
-endif ()
-
# ----------------------------------------------------------------------------------------------------------------------
# Dependencies
# ----------------------------------------------------------------------------------------------------------------------
@@ -31,21 +34,50 @@ infiniframe_setup_dependencies()
# Source Files
# ----------------------------------------------------------------------------------------------------------------------
set(COMMON_SOURCES
- Exports.cpp
+ Public/Exports/Exports.Platform.cpp
+ Public/Exports/Exports.Lifecycle.cpp
+ Public/Exports/Exports.WindowState.cpp
+ Public/Exports/Exports.WindowCommands.cpp
+ Public/Exports/Exports.Dialog.cpp
+ Public/Exports/Exports.Events.cpp
+ Public/Exports/Exports.Memory.cpp
)
set(TEST_SOURCES
- Exports.Tests.cpp
+ Public/Exports/Exports.Tests.cpp
)
set(WINDOWS_SOURCES
- Platform/Windows/Window.cpp
+ Platform/Windows/Core/WindowCore.Win32.cpp
+ Platform/Windows/Core/WindowTracing.Win32.cpp
+ Platform/Windows/Core/WindowEncoding.Win32.cpp
+ Platform/Windows/Core/WindowStorage.Win32.cpp
+ Platform/Windows/Core/WindowOwnership.Win32.cpp
+ Platform/Windows/Core/WindowLifecycle.Win32.cpp
+ Platform/Windows/Core/WindowProc.Win32.cpp
+ Platform/Windows/Core/WindowState.Win32.cpp
+ Platform/Windows/Core/WindowEvents.Win32.cpp
+ Platform/Windows/WebView/WebView2Host.Win32.cpp
+ Platform/Windows/WebView/WebView2Runtime.Win32.cpp
+ Platform/Windows/WebView/WebView2Controller.Win32.cpp
+ Platform/Windows/WebView/WebView2Attach.Win32.cpp
+ Platform/Windows/Core/UiDispatcher.Win32.cpp
Platform/Windows/DarkMode.cpp
Platform/Windows/Dialog.cpp
)
set(LINUX_SOURCES
- Platform/Linux/Window.cpp
+ Platform/Linux/Core/WindowCore.Gtk.cpp
+ Platform/Linux/Core/WindowInitialization.Gtk.cpp
+ Platform/Linux/Core/WindowLifecycle.Gtk.cpp
+ Platform/Linux/Core/WindowState.Gtk.cpp
+ Platform/Linux/Core/WindowEvents.Gtk.cpp
+ Platform/Linux/Core/UiDispatcher.Gtk.cpp
+ Platform/Linux/WebKit/WebKitHost.Gtk.cpp
+ Platform/Linux/WebKit/WebKitSettings.Gtk.cpp
+ Platform/Linux/WebKit/WebKitMessaging.Gtk.cpp
+ Platform/Linux/WebKit/WebKitCustomSchemes.Gtk.cpp
+ Platform/Linux/Core/WindowSignals.Gtk.cpp
Platform/Linux/Dialog.cpp
)
@@ -57,22 +89,42 @@ set(MAC_SOURCES
Platform/Mac/UrlSchemeHandler.mm
Platform/Mac/NSWindowBorderless.mm
Platform/Mac/Dialog.mm
- Platform/Mac/Window.mm
+ Platform/Mac/Core/WindowCore.Cocoa.mm
+ Platform/Mac/Core/WindowLifecycle.Cocoa.mm
+ Platform/Mac/Core/WindowState.Cocoa.mm
+ Platform/Mac/Core/WindowEvents.Cocoa.mm
+ Platform/Mac/Core/UiDispatcher.Cocoa.mm
+ Platform/Mac/WebKit/WebKitBridge.Cocoa.mm
+ Platform/Mac/WebKit/WebKitCustomSchemes.Cocoa.mm
+ Platform/Mac/WebKit/WebKitHost.Cocoa.mm
)
set(HEADER_FILES
- Core/InfiniFrame.h
- Core/InfiniFrameWindow.h
- Core/InfiniFrameDialog.h
+ Public/InfiniFrame.h
+ Public/InfiniFrameWindow.h
+ Public/InfiniFrameDialog.h
Embedded/Embedded.h
Embedded/InfiniFrameJs/InfiniFrameJs.h
Types/Basic.h
Types/Dialog.h
- Core/InfiniFrameInitParams.h
+ Types/DialogResult.h
+ Types/DialogButtons.h
+ Types/DialogIcon.h
+ Types/Monitor.h
+ Public/InfiniFrameInitParams.h
Types/Callbacks.h
Utils/Common.h
+ Utils/Dimensions.h
+ Utils/ErrorCode.h
+ Utils/Result.h
+ Utils/StringCopy.h
+ Utils/WindowsHandles.h
Utils/Event.h
+ Public/Exports/Exports.h
+ Utils/ExportGuards.h
Platform/Windows/ToastHandler.h
+ Platform/Windows/Window.Win32.Internal.h
+ Platform/Windows/Window.Win32.Context.h
Platform/Windows/DarkMode.h
Platform/Mac/AppDelegate.h
Platform/Mac/NavigationDelegate.h
@@ -80,6 +132,9 @@ set(HEADER_FILES
Platform/Mac/UiDelegate.h
Platform/Mac/WindowDelegate.h
Platform/Mac/UrlSchemeHandler.h
+ Platform/Linux/Window.Gtk.Internal.h
+ Platform/Linux/WebKit/WebKit.Gtk.Internal.h
+ Platform/Mac/Window.Cocoa.Internal.h
)
if (WIN32)
@@ -96,6 +151,8 @@ if (WIN32)
elseif (APPLE)
infiniframe_configure_macos_target(
${PROJECT_NAME}
+ "${COMMON_SOURCES}"
+ "${TEST_SOURCES}"
"${MAC_SOURCES}"
"${HEADER_FILES}"
)
@@ -113,21 +170,29 @@ elseif (UNIX)
infiniframe_setup_embed_js(${PROJECT_NAME})
endif ()
+if (DEFINED INFINIFRAME_BUILD_TEST_EXPORTS)
+ target_compile_definitions(${PROJECT_NAME} PRIVATE
+ $<$:INFINIFRAME_BUILD_TEST_EXPORTS=1>
+ )
+else ()
+ target_compile_definitions(${PROJECT_NAME} PRIVATE
+ $<$:INFINIFRAME_BUILD_TEST_EXPORTS=1>
+ )
+endif ()
+
# ----------------------------------------------------------------------------------------------------------------------
# Sanitizers (Debug only)
# ----------------------------------------------------------------------------------------------------------------------
-if (CMAKE_BUILD_TYPE STREQUAL "Debug")
- if (CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
- message(STATUS "Enabling sanitizers for Debug build")
- target_compile_options(${PROJECT_NAME} PRIVATE
- -fsanitize=address,undefined,leak
- -fno-omit-frame-pointer
- -fno-optimize-sibling-calls
- )
- target_link_options(${PROJECT_NAME} PRIVATE
- -fsanitize=address,undefined,leak
- )
- endif ()
+if (CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
+ message(STATUS "Enabling sanitizers for Debug configuration")
+ target_compile_options(${PROJECT_NAME} PRIVATE
+ $<$:-fsanitize=address,undefined,leak>
+ $<$:-fno-omit-frame-pointer>
+ $<$:-fno-optimize-sibling-calls>
+ )
+ target_link_options(${PROJECT_NAME} PRIVATE
+ $<$:-fsanitize=address,undefined,leak>
+ )
endif ()
# ----------------------------------------------------------------------------------------------------------------------
diff --git a/src/InfiniFrame.NativeBridge/Native/Core/InfiniFrame.h b/src/InfiniFrame.NativeBridge/Native/Core/InfiniFrame.h
deleted file mode 100644
index cb38411cc..000000000
--- a/src/InfiniFrame.NativeBridge/Native/Core/InfiniFrame.h
+++ /dev/null
@@ -1,36 +0,0 @@
-#pragma once
-/**
- * @file InfiniFrame.h
- * @brief Main header file for InfiniFrame native interop
- *
- * This file provides unified access to all InfiniFrame types and classes.
- * It is the primary include file for C API consumers.
- */
-
-#ifndef INFINIFRAME_H
-#define INFINIFRAME_H
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Core Types
-// ---------------------------------------------------------------------------------------------------------------------
-
-#include "../Types/Basic.h"
-#include "../Types/Dialog.h"
-#include "../Types/Callbacks.h"
-#include "InfiniFrameInitParams.h"
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Core Classes
-// ---------------------------------------------------------------------------------------------------------------------
-
-#include "InfiniFrameWindow.h"
-#include "InfiniFrameDialog.h"
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Utilities
-// ---------------------------------------------------------------------------------------------------------------------
-
-#include "../Utils/Common.h"
-#include "../Utils/Event.h"
-
-#endif // INFINIFRAME_H
diff --git a/src/InfiniFrame.NativeBridge/Native/Dependencies/simdutf/simdutf.h b/src/InfiniFrame.NativeBridge/Native/Dependencies/simdutf/simdutf.h
index b1af31254..65241451c 100644
--- a/src/InfiniFrame.NativeBridge/Native/Dependencies/simdutf/simdutf.h
+++ b/src/InfiniFrame.NativeBridge/Native/Dependencies/simdutf/simdutf.h
@@ -14157,4 +14157,4 @@ template consteval auto operator""_base64() {
SIMDUTF_POP_DISABLE_WARNINGS
#endif // SIMDUTF_H
-/* end file include/simdutf.h */
+/* end file include/simdutf.h */
\ No newline at end of file
diff --git a/src/InfiniFrame.NativeBridge/Native/Embedded/Embedded.h b/src/InfiniFrame.NativeBridge/Native/Embedded/Embedded.h
index 6cfe33018..5c3d510eb 100644
--- a/src/InfiniFrame.NativeBridge/Native/Embedded/Embedded.h
+++ b/src/InfiniFrame.NativeBridge/Native/Embedded/Embedded.h
@@ -1,35 +1,36 @@
#pragma once
-
-#include "InfiniFrameJs.h"
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include "Embedded/InfiniFrameJs/InfiniFrameJs.h"
#include
-
+#include
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
namespace Embedded {
inline const std::wstring& InfiniFrameJsUtf16() {
- static const std::wstring cached = [] {
- const auto* src = reinterpret_cast(g_infiniframe_js_data);
+ static const std::wstring Cached = [] {
+ const auto* src = reinterpret_cast(GInfiniframeJsData);
std::u16string temp;
- temp.resize(simdutf::utf16_length_from_utf8(src, g_infiniframe_js_size));
+ temp.resize(simdutf::utf16_length_from_utf8(src, GInfiniframeJsSize));
- const size_t written = simdutf::convert_utf8_to_utf16(
- src,
- g_infiniframe_js_size,
- temp.data()
- );
+ const size_t written = simdutf::convert_utf8_to_utf16(src, GInfiniframeJsSize, temp.data());
temp.resize(written);
return std::wstring(temp.begin(), temp.end());
}();
- return cached;
+ return Cached;
}
-
+
inline const std::string& InfiniFrameJsUtf8() {
- static const std::string cached = [] {
- const auto* src = reinterpret_cast(g_infiniframe_js_data);
- return std::string(src, g_infiniframe_js_size);
+ static const std::string Cached = [] {
+ const auto* src = reinterpret_cast(GInfiniframeJsData);
+ return std::string(src, GInfiniframeJsSize);
}();
- return cached;
+ return Cached;
}
}
diff --git a/src/InfiniFrame.NativeBridge/Native/Embedded/InfiniFrameJs/InfiniFrameJs.h b/src/InfiniFrame.NativeBridge/Native/Embedded/InfiniFrameJs/InfiniFrameJs.h
deleted file mode 100644
index f5a4595d5..000000000
--- a/src/InfiniFrame.NativeBridge/Native/Embedded/InfiniFrameJs/InfiniFrameJs.h
+++ /dev/null
@@ -1,6 +0,0 @@
-#pragma once
-// ReSharper disable once CppUnusedIncludeDirective
-#include
-
-extern const unsigned char g_infiniframe_js_data[];
-extern const size_t g_infiniframe_js_size;
diff --git a/src/InfiniFrame.NativeBridge/Native/Exports.cpp b/src/InfiniFrame.NativeBridge/Native/Exports.cpp
deleted file mode 100644
index a463e6ccc..000000000
--- a/src/InfiniFrame.NativeBridge/Native/Exports.cpp
+++ /dev/null
@@ -1,774 +0,0 @@
-#include "Core/InfiniFrame.h"
-#ifdef __linux__
-#include
-#endif
-
-#ifdef _WIN32
-#define EXPORTED __declspec(dllexport)
-#else
-#define EXPORTED
-#endif
-
-/**
- * @file Exports.cpp
- * @brief C API for InfiniFrame native interop
- *
- * Memory management:
- * - InfiniFrame_ctor returns ownership to caller (.NET side)
- * - InfiniFrame_dtor transfers ownership back and destroys instance
- * - All string returns (AutoString) must be freed with InfiniFrame_FreeString
- *
- * Thread safety:
- * - All methods except Invoke must be called from UI thread
- * - Invoke marshals calls to UI thread safely
- */
-
-extern "C" {
-#ifdef _WIN32
- /**
- * @brief Register InfiniFrame window class (Windows)
- * @param hInstance Application instance handle
- */
- EXPORTED void InfiniFrame_register_win32(const HINSTANCE hInstance) {
- InfiniFrameWindow::Register(hInstance);
- }
-
- /**
- * @brief Get native window handle (Windows)
- * @param instance InfiniFrame instance
- * @return HWND window handle
- */
- EXPORTED HWND InfiniFrame_getHwnd_win32(InfiniFrameWindow* instance) {
- return instance->getHwnd();
- }
-
- /**
- * @brief Set WebView2 runtime path (Windows)
- * @param instance InfiniFrame instance
- * @param webView2RuntimePath Path to WebView2 runtime
- */
- EXPORTED void InfiniFrame_setWebView2RuntimePath_win32(InfiniFrameWindow*, const AutoString webView2RuntimePath) {
- InfiniFrameWindow::SetWebView2RuntimePath(webView2RuntimePath);
- }
-
- /**
- * @brief Get notifications enabled status (Windows)
- * @param instance InfiniFrame instance
- * @param disabled Output: notifications disabled status
- */
- EXPORTED void InfiniFrame_GetNotificationsEnabled(InfiniFrameWindow* instance, bool* disabled) {
- instance->GetNotificationsEnabled(disabled);
- }
-#elif __APPLE__
- /**
- * @brief Register InfiniFrame application (macOS)
- */
- EXPORTED void InfiniFrame_register_mac() {
- InfiniFrameWindow::Register();
- }
-#endif
-
- /**
- * @brief Create new InfiniFrame window instance
- * @param initParams Initialization parameters
- * @return Raw pointer - ownership transferred to caller (.NET)
- */
- EXPORTED InfiniFrameWindow* InfiniFrame_ctor(InfiniFrameInitParams* initParams) {
- auto instance = std::make_unique(initParams);
- return instance.release();
- }
-
- /**
- * @brief Destroy InfiniFrame window instance
- * @param instance Raw pointer from InfiniFrame_ctor
- */
- EXPORTED void InfiniFrame_dtor(InfiniFrameWindow* instance) {
- if (instance != nullptr) {
- std::unique_ptr guard{instance};
- }
- }
-
- /**
- * @brief Center window on screen
- * @param instance InfiniFrame instance
- */
- EXPORTED void InfiniFrame_Center(InfiniFrameWindow* instance) {
- instance->Center();
- }
-
- /**
- * @brief Clear browser auto-fill data
- * @param instance InfiniFrame instance
- */
- EXPORTED void InfiniFrame_ClearBrowserAutoFill(InfiniFrameWindow* instance) {
- instance->ClearBrowserAutoFill();
- }
-
- /**
- * @brief Close window
- * @param instance InfiniFrame instance
- */
- EXPORTED void InfiniFrame_Close(InfiniFrameWindow* instance) {
- instance->Close();
- }
-
- /**
- * @brief Get transparent enabled status
- * @param instance InfiniFrame instance
- * @param enabled Output: transparent enabled status
- */
- EXPORTED void InfiniFrame_GetTransparentEnabled(InfiniFrameWindow* instance, bool* enabled) {
- instance->GetTransparentEnabled(enabled);
- }
-
- /**
- * @brief Get context menu enabled status
- * @param instance InfiniFrame instance
- * @param enabled Output: context menu enabled status
- */
- EXPORTED void InfiniFrame_GetContextMenuEnabled(InfiniFrameWindow* instance, bool* enabled) {
- instance->GetContextMenuEnabled(enabled);
- }
-
- /**
- * @brief Get zoom enabled status
- * @param instance InfiniFrame instance
- * @param enabled Output: zoom enabled status
- */
- EXPORTED void InfiniFrame_GetZoomEnabled(InfiniFrameWindow* instance, bool* enabled) {
- instance->GetZoomEnabled(enabled);
- }
-
- /**
- * @brief Get dev tools enabled status
- * @param instance InfiniFrame instance
- * @param enabled Output: dev tools enabled status
- */
- EXPORTED void InfiniFrame_GetDevToolsEnabled(InfiniFrameWindow* instance, bool* enabled) {
- instance->GetDevToolsEnabled(enabled);
- }
-
- /**
- * @brief Get full screen status
- * @param instance InfiniFrame instance
- * @param fullScreen Output: full screen status
- */
- EXPORTED void InfiniFrame_GetFullScreen(InfiniFrameWindow* instance, bool* fullScreen) {
- instance->GetFullScreen(fullScreen);
- }
-
- /**
- * @brief Get grant browser permissions status
- * @param instance InfiniFrame instance
- * @param grant Output: grant browser permissions status
- */
- EXPORTED void InfiniFrame_GetGrantBrowserPermissions(InfiniFrameWindow* instance, bool* grant) {
- instance->GetGrantBrowserPermissions(grant);
- }
-
- /**
- * @brief Get user agent string
- * @param instance InfiniFrame instance
- * @return User agent string
- */
- EXPORTED AutoString InfiniFrame_GetUserAgent(InfiniFrameWindow* instance) {
- return instance->GetUserAgent();
- }
-
- /**
- * @brief Get media autoplay enabled status
- * @param instance InfiniFrame instance
- * @param enabled Output: media autoplay enabled status
- */
- EXPORTED void InfiniFrame_GetMediaAutoplayEnabled(InfiniFrameWindow* instance, bool* enabled) {
- instance->GetMediaAutoplayEnabled(enabled);
- }
-
- /**
- * @brief Get file system access enabled status
- * @param instance InfiniFrame instance
- * @param enabled Output: file system access enabled status
- */
- EXPORTED void InfiniFrame_GetFileSystemAccessEnabled(InfiniFrameWindow* instance, bool* enabled) {
- instance->GetFileSystemAccessEnabled(enabled);
- }
-
- /**
- * @brief Get web security enabled status
- * @param instance InfiniFrame instance
- * @param enabled Output: web security enabled status
- */
- EXPORTED void InfiniFrame_GetWebSecurityEnabled(InfiniFrameWindow* instance, bool* enabled) {
- instance->GetWebSecurityEnabled(enabled);
- }
-
- /**
- * @brief Get JavaScript clipboard access enabled status
- * @param instance InfiniFrame instance
- * @param enabled Output: JavaScript clipboard access enabled status
- */
- EXPORTED void InfiniFrame_GetJavascriptClipboardAccessEnabled(InfiniFrameWindow* instance, bool* enabled) {
- instance->GetJavascriptClipboardAccessEnabled(enabled);
- }
-
- /**
- * @brief Get media stream enabled status
- * @param instance InfiniFrame instance
- * @param enabled Output: media stream enabled status
- */
- EXPORTED void InfiniFrame_GetMediaStreamEnabled(InfiniFrameWindow* instance, bool* enabled) {
- instance->GetMediaStreamEnabled(enabled);
- }
-
- /**
- * @brief Get smooth scrolling enabled status
- * @param instance InfiniFrame instance
- * @param enabled Output: smooth scrolling enabled status
- */
- EXPORTED void InfiniFrame_GetSmoothScrollingEnabled(InfiniFrameWindow* instance, bool* enabled) {
- instance->GetSmoothScrollingEnabled(enabled);
- }
-
- /**
- * @brief Get maximized status
- * @param instance InfiniFrame instance
- * @param isMaximized Output: maximized status
- */
- EXPORTED void InfiniFrame_GetMaximized(InfiniFrameWindow* instance, bool* isMaximized) {
- instance->GetMaximized(isMaximized);
- }
-
- /**
- * @brief Get minimized status
- * @param instance InfiniFrame instance
- * @param isMinimized Output: minimized status
- */
- EXPORTED void InfiniFrame_GetMinimized(InfiniFrameWindow* instance, bool* isMinimized) {
- instance->GetMinimized(isMinimized);
- }
-
- /**
- * @brief Get ignore certificate errors enabled status
- * @param instance InfiniFrame instance
- * @param disabled Output: ignore certificate errors enabled status
- */
- EXPORTED void InfiniFrame_GetIgnoreCertificateErrorsEnabled(InfiniFrameWindow* instance, bool* disabled) {
- instance->GetIgnoreCertificateErrorsEnabled(disabled);
- }
-
- /**
- * @brief Get window position
- * @param instance InfiniFrame instance
- * @param x Output: X coordinate
- * @param y Output: Y coordinate
- */
- EXPORTED void InfiniFrame_GetPosition(InfiniFrameWindow* instance, int* x, int* y) {
- instance->GetPosition(x, y);
- }
-
- /**
- * @brief Get resizable status
- * @param instance InfiniFrame instance
- * @param resizable Output: resizable status
- */
- EXPORTED void InfiniFrame_GetResizable(InfiniFrameWindow* instance, bool* resizable) {
- instance->GetResizable(resizable);
- }
-
- /**
- * @brief Get screen DPI
- * @param instance InfiniFrame instance
- * @return Screen DPI value
- */
- EXPORTED unsigned int InfiniFrame_GetScreenDpi(InfiniFrameWindow* instance) {
- return instance->GetScreenDpi();
- }
-
- /**
- * @brief Get window size
- * @param instance InfiniFrame instance
- * @param width Output: window width
- * @param height Output: window height
- */
- EXPORTED void InfiniFrame_GetSize(InfiniFrameWindow* instance, int* width, int* height) {
- instance->GetSize(width, height);
- }
-
- /**
- * @brief Get the window maximum size constraints
- * @param instance InfiniFrame instance
- * @param width Output: maximum window width
- * @param height Output: maximum window height
- */
- EXPORTED void InfiniFrame_GetMaxSize(InfiniFrameWindow* instance, int* width, int* height) {
- instance->GetMaxSize(width, height);
- }
-
- /**
- * @brief Get the window minimum size constraints
- * @param instance InfiniFrame instance
- * @param width Output: minimum window width
- * @param height Output: minimum window height
- */
- EXPORTED void InfiniFrame_GetMinSize(InfiniFrameWindow* instance, int* width, int* height) {
- instance->GetMinSize(width, height);
- }
-
- /**
- * @brief Get window title
- * @param instance InfiniFrame instance
- * @return Window title string
- */
- EXPORTED AutoString InfiniFrame_GetTitle(InfiniFrameWindow* instance) {
- return instance->GetTitle();
- }
-
- /**
- * @brief Get topmost status
- * @param instance InfiniFrame instance
- * @param topmost Output: topmost status
- */
- EXPORTED void InfiniFrame_GetTopmost(InfiniFrameWindow* instance, bool* topmost) {
- instance->GetTopmost(topmost);
- }
-
- /**
- * @brief Get zoom level
- * @param instance InfiniFrame instance
- * @param zoom Output: zoom level percentage
- */
- EXPORTED void InfiniFrame_GetZoom(InfiniFrameWindow* instance, int* zoom) {
- instance->GetZoom(zoom);
- }
-
- /**
- * @brief Get focused status
- * @param instance InfiniFrame instance
- * @param isFocused Output: focused status
- */
- EXPORTED void InfiniFrame_GetFocused(InfiniFrameWindow* instance, bool* isFocused) {
- instance->GetFocused(isFocused);
- }
-
- /**
- * @brief Get icon file name
- * @param instance InfiniFrame instance
- * @return Icon file name string
- */
- EXPORTED AutoString InfiniFrame_GetIconFileName(InfiniFrameWindow* instance) {
- return instance->GetIconFileName();
- }
-
- /**
- * @brief Navigate to HTML string
- * @param instance InfiniFrame instance
- * @param content HTML content string
- */
- EXPORTED void InfiniFrame_NavigateToString(InfiniFrameWindow* instance, const AutoString content) {
- instance->NavigateToString(content);
- }
-
- /**
- * @brief Navigate to URL
- * @param instance InfiniFrame instance
- * @param url URL to navigate to
- */
- EXPORTED void InfiniFrame_NavigateToUrl(InfiniFrameWindow* instance, const AutoString url) {
- instance->NavigateToUrl(url);
- }
-
- /**
- * @brief Restore window from minimized/maximized state
- * @param instance InfiniFrame instance
- */
- EXPORTED void InfiniFrame_Restore(InfiniFrameWindow* instance) {
- instance->Restore();
- }
-
- /**
- * @brief Send message to WebView JavaScript
- * @param instance InfiniFrame instance
- * @param message Message string to send
- */
- EXPORTED void InfiniFrame_SendWebMessage(InfiniFrameWindow* instance, const AutoString message) {
- instance->SendWebMessage(message);
- }
-
- /**
- * @brief Set transparent enabled status
- * @param instance InfiniFrame instance
- * @param enabled Transparent enabled status
- */
- EXPORTED void InfiniFrame_SetTransparentEnabled(InfiniFrameWindow* instance, const bool enabled) {
- instance->SetTransparentEnabled(enabled);
- }
-
- /**
- * @brief Set context menu enabled status
- * @param instance InfiniFrame instance
- * @param enabled Context menu enabled status
- */
- EXPORTED void InfiniFrame_SetContextMenuEnabled(InfiniFrameWindow* instance, const bool enabled) {
- instance->SetContextMenuEnabled(enabled);
- }
-
- /**
- * @brief Set zoom enabled status
- * @param instance InfiniFrame instance
- * @param enabled Zoom enabled status
- */
- EXPORTED void InfiniFrame_SetZoomEnabled(InfiniFrameWindow* instance, const bool enabled) {
- instance->SetZoomEnabled(enabled);
- }
-
- /**
- * @brief Set dev tools enabled status
- * @param instance InfiniFrame instance
- * @param enabled Dev tools enabled status
- */
- EXPORTED void InfiniFrame_SetDevToolsEnabled(InfiniFrameWindow* instance, const bool enabled) {
- instance->SetDevToolsEnabled(enabled);
- }
-
- /**
- * @brief Set full screen status
- * @param instance InfiniFrame instance
- * @param fullScreen Full screen status
- */
- EXPORTED void InfiniFrame_SetFullScreen(InfiniFrameWindow* instance, const bool fullScreen) {
- instance->SetFullScreen(fullScreen);
- }
-
- /**
- * @brief Set window icon from file
- * @param instance InfiniFrame instance
- * @param filename Icon file path
- */
- EXPORTED void InfiniFrame_SetIconFile(InfiniFrameWindow* instance, const AutoString filename) {
- instance->SetIconFile(filename);
- }
-
- /**
- * @brief Set maximized status
- * @param instance InfiniFrame instance
- * @param maximized Maximized status
- */
- EXPORTED void InfiniFrame_SetMaximized(InfiniFrameWindow* instance, const bool maximized) {
- instance->SetMaximized(maximized);
- }
-
- /**
- * @brief Set maximum window size
- * @param instance InfiniFrame instance
- * @param width Maximum width
- * @param height Maximum height
- */
- EXPORTED void InfiniFrame_SetMaxSize(InfiniFrameWindow* instance, const int width, const int height) {
- instance->SetMaxSize(width, height);
- }
-
- /**
- * @brief Set minimized status
- * @param instance InfiniFrame instance
- * @param minimized Minimized status
- */
- EXPORTED void InfiniFrame_SetMinimized(InfiniFrameWindow* instance, const bool minimized) {
- instance->SetMinimized(minimized);
- }
-
- /**
- * @brief Set minimum window size
- * @param instance InfiniFrame instance
- * @param width Minimum width
- * @param height Minimum height
- */
- EXPORTED void InfiniFrame_SetMinSize(InfiniFrameWindow* instance, const int width, const int height) {
- instance->SetMinSize(width, height);
- }
-
- /**
- * @brief Set window position
- * @param instance InfiniFrame instance
- * @param x X coordinate
- * @param y Y coordinate
- */
- EXPORTED void InfiniFrame_SetPosition(InfiniFrameWindow* instance, const int x, const int y) {
- instance->SetPosition(x, y);
- }
-
- /**
- * @brief Set resizable status
- * @param instance InfiniFrame instance
- * @param resizable Resizable status
- */
- EXPORTED void InfiniFrame_SetResizable(InfiniFrameWindow* instance, const bool resizable) {
- instance->SetResizable(resizable);
- }
-
- /**
- * @brief Set window size
- * @param instance InfiniFrame instance
- * @param width Window width
- * @param height Window height
- */
- EXPORTED void InfiniFrame_SetSize(InfiniFrameWindow* instance, const int width, const int height) {
- instance->SetSize(width, height);
- }
-
- /**
- * @brief Set window title
- * @param instance InfiniFrame instance
- * @param title Window title string
- */
- EXPORTED void InfiniFrame_SetTitle(InfiniFrameWindow* instance, const AutoString title) {
- instance->SetTitle(title);
- }
-
- /**
- * @brief Set topmost status
- * @param instance InfiniFrame instance
- * @param topmost Topmost status
- */
- EXPORTED void InfiniFrame_SetTopmost(InfiniFrameWindow* instance, const bool topmost) {
- instance->SetTopmost(topmost);
- }
-
- /**
- * @brief Set zoom level
- * @param instance InfiniFrame instance
- * @param zoom Zoom level percentage
- */
- EXPORTED void InfiniFrame_SetZoom(InfiniFrameWindow* instance, const int zoom) {
- instance->SetZoom(zoom);
- }
-
- /**
- * @brief Show notification
- * @param instance InfiniFrame instance
- * @param title Notification title
- * @param body Notification body
- */
- EXPORTED void InfiniFrame_ShowNotification(
- InfiniFrameWindow* instance,
- const AutoString title,
- const AutoString body
- ) {
- instance->ShowNotification(title, body);
- }
-
- /**
- * @brief Wait for window exit
- * @param instance InfiniFrame instance
- */
- EXPORTED void InfiniFrame_WaitForExit(InfiniFrameWindow* instance) {
- instance->WaitForExit();
- }
-
- /**
- * @brief Free string allocated by native code
- * @param value String to free
- */
- EXPORTED void InfiniFrame_FreeString(AutoString value) {
- if (value == nullptr)
- return;
-#ifdef _WIN32
- delete[] value;
-#elif __linux__
- g_free(value);
-#elif __APPLE__
- free(value);
-#else
- free(value);
-#endif
- }
-
- /**
- * @brief Free string array allocated by native code
- * @param values String array to free
- * @param count Number of strings in array
- */
- EXPORTED void InfiniFrame_FreeStringArray(AutoString* values, const int count) {
- if (values == nullptr)
- return;
-
- for (int i = 0; i < count; ++i) {
- InfiniFrame_FreeString(values[i]);
- }
-
-#ifdef _WIN32
- delete[] values;
-#elif __linux__
- delete[] values;
-#elif __APPLE__
- free(values);
-#else
- free(values);
-#endif
- }
-
- /**
- * @brief Show open file dialog
- * @param inst InfiniFrame instance
- * @param title Dialog title
- * @param defaultPath Default path
- * @param multiSelect Allow multiple selection
- * @param filters File filters
- * @param filterCount Number of filters
- * @param resultCount Output: number of selected files
- * @return Array of selected file paths
- */
- EXPORTED AutoString* InfiniFrame_ShowOpenFile(
- InfiniFrameWindow* inst,
- const AutoString title,
- const AutoString defaultPath,
- const bool multiSelect,
- AutoString* filters,
- const int filterCount,
- int* resultCount
- ) {
- return inst->GetDialog()->ShowOpenFile(title, defaultPath, multiSelect, filters, filterCount, resultCount);
- }
-
- /**
- * @brief Show open folder dialog
- * @param inst InfiniFrame instance
- * @param title Dialog title
- * @param defaultPath Default path
- * @param multiSelect Allow multiple selection
- * @param resultCount Output: number of selected folders
- * @return Array of selected folder paths
- */
- EXPORTED AutoString* InfiniFrame_ShowOpenFolder(
- InfiniFrameWindow* inst,
- const AutoString title,
- const AutoString defaultPath,
- const bool multiSelect,
- int* resultCount
- ) {
- return inst->GetDialog()->ShowOpenFolder(title, defaultPath, multiSelect, resultCount);
- }
-
- /**
- * @brief Show save file dialog
- * @param inst InfiniFrame instance
- * @param title Dialog title
- * @param defaultPath Default path
- * @param filters File filters
- * @param filterCount Number of filters
- * @param defaultFileName Default file name
- * @return Selected file path
- */
- EXPORTED AutoString InfiniFrame_ShowSaveFile(
- InfiniFrameWindow* inst,
- const AutoString title,
- const AutoString defaultPath,
- AutoString* filters,
- const int filterCount,
- const AutoString defaultFileName
- ) {
- return inst->GetDialog()->ShowSaveFile(title, defaultPath, filters, filterCount, defaultFileName);
- }
-
- /**
- * @brief Show message dialog
- * @param inst InfiniFrame instance
- * @param title Dialog title
- * @param text Message text
- * @param buttons Button configuration
- * @param icon Icon type
- * @return User response
- */
- EXPORTED DialogResult InfiniFrame_ShowMessage(
- InfiniFrameWindow* inst,
- const AutoString title,
- const AutoString text,
- const DialogButtons buttons,
- const DialogIcon icon
- ) {
- return inst->GetDialog()->ShowMessage(title, text, buttons, icon);
- }
-
- /**
- * @brief Add custom scheme name
- * @param instance InfiniFrame instance
- * @param scheme Scheme name to add
- */
- EXPORTED void InfiniFrame_AddCustomSchemeName(InfiniFrameWindow* instance, const AutoString scheme) {
- instance->AddCustomSchemeName(scheme);
- }
-
- /**
- * @brief Get all monitors
- * @param instance InfiniFrame instance
- * @param callback Callback function to receive monitor info
- */
- EXPORTED void InfiniFrame_GetAllMonitors(InfiniFrameWindow* instance, const GetAllMonitorsCallback callback) {
- instance->GetAllMonitors(callback);
- }
-
- /**
- * @brief Set closing callback
- * @param instance InfiniFrame instance
- * @param callback Closing callback
- */
- EXPORTED void InfiniFrame_SetClosingCallback(InfiniFrameWindow* instance, const ClosingCallback callback) {
- instance->SetClosingCallback(callback);
- }
-
- EXPORTED void InfiniFrame_setClosedClosedCallback(InfiniFrameWindow* instance, const ClosedCallback callback)
- { instance->SetClosedCallback(callback);
- }
-
- /**
- * @brief Set focus-in callback
- * @param instance InfiniFrame instance
- * @param callback Focus-in callback
- */
- EXPORTED void InfiniFrame_SetFocusInCallback(InfiniFrameWindow* instance, const FocusInCallback callback) {
- instance->SetFocusInCallback(callback);
- }
-
- /**
- * @brief Set focus-out callback
- * @param instance InfiniFrame instance
- * @param callback Focus-out callback
- */
- EXPORTED void InfiniFrame_SetFocusOutCallback(InfiniFrameWindow* instance, const FocusOutCallback callback) {
- instance->SetFocusOutCallback(callback);
- }
-
- /**
- * @brief Set moved callback
- * @param instance InfiniFrame instance
- * @param callback Moved callback
- */
- EXPORTED void InfiniFrame_SetMovedCallback(InfiniFrameWindow* instance, const MovedCallback callback) {
- instance->SetMovedCallback(callback);
- }
-
- /**
- * @brief Set resized callback
- * @param instance InfiniFrame instance
- * @param callback Resized callback
- */
- EXPORTED void InfiniFrame_SetResizedCallback(InfiniFrameWindow* instance, const ResizedCallback callback) {
- instance->SetResizedCallback(callback);
- }
-
- /**
- * @brief Invoke callback on UI thread
- * @param instance InfiniFrame instance
- * @param callback Callback to invoke
- */
- EXPORTED void InfiniFrame_Invoke(InfiniFrameWindow* instance, const ACTION callback) {
- instance->Invoke(callback);
- }
-
- /**
- * @brief Set window focused
- * @param instance InfiniFrame instance
- */
- EXPORTED void InfiniFrame_SetFocused(InfiniFrameWindow* instance) {
- instance->SetFocused();
- }
-}
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Core/UiDispatcher.Gtk.cpp b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Core/UiDispatcher.Gtk.cpp
new file mode 100644
index 000000000..03680e670
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Core/UiDispatcher.Gtk.cpp
@@ -0,0 +1,39 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include
+#include
+
+#include "Platform/Linux/Window.Gtk.Internal.h"
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+namespace {
+ std::mutex invokeLockMutex;
+
+ struct InvokeWaitInfo {
+ ACTION callback;
+ std::condition_variable completionNotifier;
+ bool isCompleted;
+ };
+
+ gboolean invokeCallback(const gpointer data) {
+ auto* waitInfo = reinterpret_cast(data);
+ waitInfo->callback();
+ {
+ std::lock_guard guard(invokeLockMutex);
+ waitInfo->isCompleted = true;
+ }
+ waitInfo->completionNotifier.notify_one();
+ return false;
+ }
+} // namespace
+
+void InfiniFrameWindow::Invoke(const ACTION callback) {
+ InvokeWaitInfo waitInfo = {};
+ waitInfo.callback = callback;
+ gdk_threads_add_idle(invokeCallback, &waitInfo);
+
+ std::unique_lock uLock(invokeLockMutex);
+ waitInfo.completionNotifier.wait(uLock, [&] { return waitInfo.isCompleted; });
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Core/WindowCore.Gtk.cpp b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Core/WindowCore.Gtk.cpp
new file mode 100644
index 000000000..fd9fb7817
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Core/WindowCore.Gtk.cpp
@@ -0,0 +1,50 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include
+#include
+
+#include "Platform/Linux/Window.Gtk.Internal.h"
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+InfiniFrameWindow::InfiniFrameWindow(InfiniFrameInitParams* initParams)
+ : m_impl(std::make_unique()) {
+ XInitThreads();
+ gtk_init(nullptr, nullptr);
+ notify_init(initParams->Title);
+
+ if (initParams->StructSize != sizeof(InfiniFrameInitParams)) {
+ GtkWidget* dialog = gtk_message_dialog_new(
+ nullptr, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
+ "Initial parameters passed are %i bytes, but expected %lu bytes.", initParams->StructSize,
+ sizeof(InfiniFrameInitParams)
+ );
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ exit(0);
+ }
+
+ m_impl->InitializeFromParams(initParams);
+ m_impl->ConfigureInitialWindow(this, initParams);
+ m_impl->ApplyInitialWindowState(this, initParams);
+ m_impl->ConnectWindowSignals(this);
+
+ // Register custom schemes before first navigation to avoid first-load races.
+ m_impl->AddCustomSchemeHandlers();
+
+ Show(false);
+
+ m_impl->ConnectWebViewSignals(this);
+
+ if (initParams->Transparent)
+ SetTransparentEnabled(true);
+
+ if (m_impl->_zoom != 100.0)
+ SetZoom(m_impl->_zoom);
+}
+
+InfiniFrameWindow::~InfiniFrameWindow() {
+ notify_uninit();
+ gtk_widget_destroy(m_impl->_window);
+}
\ No newline at end of file
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Core/WindowEvents.Gtk.cpp b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Core/WindowEvents.Gtk.cpp
new file mode 100644
index 000000000..dfcb4b017
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Core/WindowEvents.Gtk.cpp
@@ -0,0 +1,150 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include "Platform/Linux/Window.Gtk.Internal.h"
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+InfiniFrameDialog* InfiniFrameWindow::GetDialog() const {
+ return m_impl->_dialog.get();
+}
+
+void InfiniFrameWindow::AddCustomSchemeName(const AutoStringConst scheme) {
+ if (scheme == nullptr) {
+ return;
+ }
+
+ m_impl->_customSchemeNames.emplace_back(scheme);
+}
+
+void InfiniFrameWindow::GetAllMonitors(const GetAllMonitorsCallback callback) const {
+ if (callback == nullptr) {
+ return;
+ }
+
+ GdkScreen* screen = gtk_window_get_screen(GTK_WINDOW(m_impl->_window));
+ GdkDisplay* display = gdk_screen_get_display(screen);
+
+ const int MonitorCount = gdk_display_get_n_monitors(display);
+
+ for (int i = 0; i < MonitorCount; i++) {
+ GdkMonitor* monitor = gdk_display_get_monitor(display, i);
+
+ Monitor props = {};
+ gdk_monitor_get_geometry(monitor, reinterpret_cast(&props.monitor));
+ gdk_monitor_get_workarea(monitor, reinterpret_cast(&props.work));
+ props.scale = gdk_monitor_get_scale_factor(monitor);
+
+ if (callback(&props) == 0) {
+ break;
+ }
+ }
+}
+
+void InfiniFrameWindow::SetClosingCallback(const ClosingCallback callback) {
+ m_impl->_closingCallback = callback;
+}
+
+void InfiniFrameWindow::SetClosedCallback(const ClosedCallback callback) {
+ m_impl->_closedCallback = callback;
+}
+
+void InfiniFrameWindow::SetFocusInCallback(const FocusInCallback callback) {
+ m_impl->_focusInCallback = callback;
+}
+
+void InfiniFrameWindow::SetFocusOutCallback(const FocusOutCallback callback) {
+ m_impl->_focusOutCallback = callback;
+}
+
+void InfiniFrameWindow::SetMovedCallback(const MovedCallback callback) {
+ m_impl->_movedCallback = callback;
+}
+
+void InfiniFrameWindow::SetResizedCallback(const ResizedCallback callback) {
+ m_impl->_resizedCallback = callback;
+}
+
+void InfiniFrameWindow::SetMaximizedCallback(const MaximizedCallback callback) {
+ m_impl->_maximizedCallback = callback;
+}
+
+void InfiniFrameWindow::SetRestoredCallback(const RestoredCallback callback) {
+ m_impl->_restoredCallback = callback;
+}
+
+void InfiniFrameWindow::SetMinimizedCallback(const MinimizedCallback callback) {
+ m_impl->_minimizedCallback = callback;
+}
+
+[[nodiscard]] bool InfiniFrameWindow::InvokeClose() const noexcept {
+ if (m_impl->_closingCallback == nullptr) {
+ return false;
+ }
+
+ return m_impl->_closingCallback();
+}
+
+void InfiniFrameWindow::InvokeClosed() const noexcept {
+ if (m_impl->_closedCallback == nullptr) {
+ return;
+ }
+
+ m_impl->_closedCallback();
+}
+
+void InfiniFrameWindow::InvokeFocusIn() const noexcept {
+ if (m_impl->_focusInCallback == nullptr) {
+ return;
+ }
+
+ m_impl->_focusInCallback();
+}
+
+void InfiniFrameWindow::InvokeFocusOut() const noexcept {
+ if (m_impl->_focusOutCallback == nullptr) {
+ return;
+ }
+
+ m_impl->_focusOutCallback();
+}
+
+void InfiniFrameWindow::InvokeMove(int x, int y) const noexcept {
+ if (m_impl->_movedCallback == nullptr) {
+ return;
+ }
+
+ m_impl->_movedCallback(x, y);
+}
+
+void InfiniFrameWindow::InvokeResize(int width, int height) const noexcept {
+ if (m_impl->_resizedCallback == nullptr) {
+ return;
+ }
+
+ m_impl->_resizedCallback(width, height);
+}
+
+void InfiniFrameWindow::InvokeMaximized() const noexcept {
+ if (m_impl->_maximizedCallback == nullptr) {
+ return;
+ }
+
+ m_impl->_maximizedCallback();
+}
+
+void InfiniFrameWindow::InvokeRestored() const noexcept {
+ if (m_impl->_restoredCallback == nullptr) {
+ return;
+ }
+
+ m_impl->_restoredCallback();
+}
+
+void InfiniFrameWindow::InvokeMinimized() const noexcept {
+ if (m_impl->_minimizedCallback == nullptr) {
+ return;
+ }
+
+ m_impl->_minimizedCallback();
+}
\ No newline at end of file
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Core/WindowInitialization.Gtk.cpp b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Core/WindowInitialization.Gtk.cpp
new file mode 100644
index 000000000..246a1018b
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Core/WindowInitialization.Gtk.cpp
@@ -0,0 +1,173 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include
+#include
+
+#include "Public/InfiniFrameDialog.h"
+#include "Platform/Linux/Window.Gtk.Internal.h"
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+gboolean on_configure_event(GtkWidget* widget, GdkEvent* event, gpointer self);
+gboolean on_window_state_event(GtkWidget* widget, GdkEventWindowState* event, gpointer self);
+gboolean on_widget_deleted(GtkWidget* widget, GdkEvent* event, gpointer self);
+void on_widget_destroyed(GtkWidget* widget, gpointer self);
+gboolean on_focus_in_event(GtkWidget* widget, GdkEvent* event, gpointer self);
+gboolean on_focus_out_event(GtkWidget* widget, GdkEvent* event, gpointer self);
+gboolean on_webview_context_menu(
+ WebKitWebView* web_view,
+ GtkWidget* default_menu,
+ WebKitHitTestResult* hit_test_result,
+ gboolean triggered_with_keyboard,
+ gpointer user_data
+);
+gboolean on_permission_request(WebKitWebView* web_view, WebKitPermissionRequest* request, gpointer user_data);
+
+void InfiniFrameWindow::Impl::InitializeFromParams(const InfiniFrameInitParams* initParams) {
+ if (initParams->Title != nullptr) {
+ _windowTitle = initParams->Title;
+ } else {
+ _windowTitle = "";
+ }
+
+ if (initParams->StartUrl != nullptr) {
+ _startUrl = initParams->StartUrl;
+ }
+ if (initParams->StartString != nullptr) {
+ _startString = initParams->StartString;
+ }
+ if (initParams->TemporaryFilesPath != nullptr) {
+ _temporaryFilesPath = initParams->TemporaryFilesPath;
+ }
+ if (initParams->UserAgent != nullptr) {
+ _userAgent = initParams->UserAgent;
+ }
+ if (initParams->BrowserControlInitParameters != nullptr) {
+ _browserControlInitParameters = initParams->BrowserControlInitParameters;
+ }
+
+ _transparentEnabled = initParams->Transparent;
+ _contextMenuEnabled = initParams->ContextMenuEnabled;
+ _zoomEnabled = initParams->ZoomEnabled;
+ _devToolsEnabled = initParams->DevToolsEnabled;
+ _grantBrowserPermissions = initParams->GrantBrowserPermissions;
+ _mediaAutoplayEnabled = initParams->MediaAutoplayEnabled;
+ _fileSystemAccessEnabled = initParams->FileSystemAccessEnabled;
+ _webSecurityEnabled = initParams->WebSecurityEnabled;
+ _javascriptClipboardAccessEnabled = initParams->JavascriptClipboardAccessEnabled;
+ _mediaStreamEnabled = initParams->MediaStreamEnabled;
+ _smoothScrollingEnabled = initParams->SmoothScrollingEnabled;
+ _ignoreCertificateErrorsEnabled = initParams->IgnoreCertificateErrorsEnabled;
+ _isFullScreen = initParams->FullScreen;
+
+ _zoom = initParams->Zoom;
+ _minWidth = initParams->MinWidth;
+ _minHeight = initParams->MinHeight;
+ _maxWidth = initParams->MaxWidth;
+ _maxHeight = initParams->MaxHeight;
+
+ _webMessageReceivedCallback = initParams->WebMessageReceivedHandler;
+ _resizedCallback = initParams->ResizedHandler;
+ _movedCallback = initParams->MovedHandler;
+ _closingCallback = initParams->ClosingHandler;
+ _closedCallback = initParams->ClosedHandler;
+ _focusInCallback = initParams->FocusInHandler;
+ _focusOutCallback = initParams->FocusOutHandler;
+ _maximizedCallback = initParams->MaximizedHandler;
+ _minimizedCallback = initParams->MinimizedHandler;
+ _restoredCallback = initParams->RestoredHandler;
+ _customSchemeCallback = initParams->CustomSchemeHandler;
+
+ _customSchemeNames.clear();
+ for (auto* customSchemeName : initParams->CustomSchemeNames) {
+ if (customSchemeName == nullptr) {
+ continue;
+ }
+ _customSchemeNames.emplace_back(customSchemeName);
+ }
+
+ _parent = initParams->ParentInstance;
+}
+
+void InfiniFrameWindow::Impl::ConfigureInitialWindow(InfiniFrameWindow* window, InfiniFrameInitParams* initParams) {
+ _window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ _dialog = std::make_unique();
+
+ if (initParams->FullScreen) {
+ window->SetFullScreen(true);
+ return;
+ }
+
+ initParams->Width = std::min(initParams->Width, initParams->MaxWidth);
+ initParams->Height = std::min(initParams->Height, initParams->MaxHeight);
+ initParams->Width = std::max(initParams->Width, initParams->MinWidth);
+ initParams->Height = std::max(initParams->Height, initParams->MinHeight);
+
+ if (initParams->UseOsDefaultSize) {
+ gtk_window_set_default_size(GTK_WINDOW(_window), -1, -1);
+ } else {
+ gtk_window_set_default_size(GTK_WINDOW(_window), initParams->Width, initParams->Height);
+ }
+
+ window->SetMinSize(initParams->MinWidth, initParams->MinHeight);
+ window->SetMaxSize(initParams->MaxWidth, initParams->MaxHeight);
+
+ if (initParams->UseOsDefaultLocation) {
+ gtk_window_set_position(GTK_WINDOW(_window), GTK_WIN_POS_NONE);
+ } else if (initParams->CenterOnInitialize) {
+ gtk_window_set_position(GTK_WINDOW(_window), GTK_WIN_POS_CENTER);
+ } else {
+ gtk_window_move(GTK_WINDOW(_window), initParams->Left, initParams->Top);
+ }
+}
+
+void InfiniFrameWindow::Impl::ApplyInitialWindowState(
+ InfiniFrameWindow* window, const InfiniFrameInitParams* initParams
+) {
+ window->SetTitle(const_cast(_windowTitle.c_str()));
+
+ if (initParams->Chromeless) {
+ gtk_window_set_decorated(GTK_WINDOW(_window), false);
+ }
+
+ if (initParams->WindowIconFile != nullptr && std::strlen(initParams->WindowIconFile) > 0) {
+ window->SetIconFile(initParams->WindowIconFile);
+ }
+
+ if (initParams->CenterOnInitialize) {
+ window->Center();
+ }
+ if (initParams->Minimized) {
+ window->SetMinimized(true);
+ }
+ if (initParams->Maximized) {
+ window->SetMaximized(true);
+ }
+ if (!initParams->Resizable) {
+ window->SetResizable(false);
+ }
+ if (initParams->Topmost) {
+ window->SetTopmost(true);
+ }
+}
+
+void InfiniFrameWindow::Impl::ConnectWindowSignals(InfiniFrameWindow* window) {
+ g_signal_connect(G_OBJECT(_window), "configure-event", G_CALLBACK(on_configure_event), window);
+
+ g_signal_connect(G_OBJECT(_window), "window-state-event", G_CALLBACK(on_window_state_event), window);
+
+ g_signal_connect(G_OBJECT(_window), "delete-event", G_CALLBACK(on_widget_deleted), window);
+
+ g_signal_connect(G_OBJECT(_window), "destroy", G_CALLBACK(on_widget_destroyed), window);
+
+ g_signal_connect(G_OBJECT(_window), "focus-in-event", G_CALLBACK(on_focus_in_event), window);
+
+ g_signal_connect(G_OBJECT(_window), "focus-out-event", G_CALLBACK(on_focus_out_event), window);
+}
+
+void InfiniFrameWindow::Impl::ConnectWebViewSignals(InfiniFrameWindow* window) {
+ g_signal_connect(G_OBJECT(_webview), "context-menu", G_CALLBACK(on_webview_context_menu), window);
+
+ g_signal_connect(G_OBJECT(_webview), "permission-request", G_CALLBACK(on_permission_request), window);
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Core/WindowLifecycle.Gtk.cpp b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Core/WindowLifecycle.Gtk.cpp
new file mode 100644
index 000000000..bb7c479b0
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Core/WindowLifecycle.Gtk.cpp
@@ -0,0 +1,70 @@
+ // ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include
+
+#include "Platform/Linux/Window.Gtk.Internal.h"
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+void InfiniFrameWindow::Center() {
+ gint windowWidth, windowHeight;
+ gtk_window_get_size(GTK_WINDOW(m_impl->_window), &windowWidth, &windowHeight);
+
+ GdkRectangle screen = {};
+
+ GdkDisplay* display = gdk_display_get_default();
+ if (display == nullptr) {
+ GtkWidget* dialog = gtk_message_dialog_new(
+ nullptr, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
+ "gdk_display_get_default() returned NULL"
+ );
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ return;
+ }
+
+ GdkMonitor* monitor = gdk_display_get_primary_monitor(display);
+ if (monitor == nullptr) {
+ monitor = gdk_display_get_monitor(display, 0);
+ if (monitor == nullptr) {
+ GtkWidget* dialog = gtk_message_dialog_new(
+ nullptr, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
+ "gdk_display_get_primary_monitor() returned NULL"
+ );
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ return;
+ }
+ }
+
+ gdk_monitor_get_geometry(monitor, &screen);
+
+ gtk_window_move(GTK_WINDOW(m_impl->_window), (screen.width - windowWidth) / 2, (screen.height - windowHeight) / 2);
+}
+
+void InfiniFrameWindow::ClearBrowserAutoFill() {
+ // TODO
+}
+
+void InfiniFrameWindow::Close() {
+ gtk_window_close(GTK_WINDOW(m_impl->_window));
+}
+
+void InfiniFrameWindow::ShowNotification(const AutoString title, const AutoString message) {
+ NotifyNotification* notification = notify_notification_new(title, message, nullptr);
+ notify_notification_set_icon_from_pixbuf(notification, gtk_window_get_icon(GTK_WINDOW(m_impl->_window)));
+ notify_notification_show(notification, nullptr);
+ g_object_unref(G_OBJECT(notification));
+}
+
+void InfiniFrameWindow::WaitForExit() {
+ g_signal_connect(
+ G_OBJECT(m_impl->_window), "destroy", G_CALLBACK(+[](GtkWidget*, gpointer) { gtk_main_quit(); }), nullptr
+ );
+ gtk_main();
+}
+
+void InfiniFrameWindow::CloseWebView() {
+ // Not implemented on Linux
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Core/WindowSignals.Gtk.cpp b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Core/WindowSignals.Gtk.cpp
new file mode 100644
index 000000000..9e83ed3e0
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Core/WindowSignals.Gtk.cpp
@@ -0,0 +1,171 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include
+
+#include "Platform/Linux/Window.Gtk.Internal.h"
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+namespace {
+ bool linux_webview_diagnostics_enabled() {
+ const char* value = g_getenv("INFINIFRAME_LINUX_WEBVIEW_DIAGNOSTICS");
+ return value != nullptr && value[0] != '\0' && g_strcmp0(value, "0") != 0;
+ }
+
+ const char* webkit_load_event_to_string(WebKitLoadEvent event) {
+ switch (event) {
+ case WEBKIT_LOAD_STARTED:
+ return "started";
+ case WEBKIT_LOAD_REDIRECTED:
+ return "redirected";
+ case WEBKIT_LOAD_COMMITTED:
+ return "committed";
+ case WEBKIT_LOAD_FINISHED:
+ return "finished";
+ default:
+ return "unknown";
+ }
+ }
+
+ const char* webkit_termination_reason_to_string(WebKitWebProcessTerminationReason reason) {
+ switch (reason) {
+ case WEBKIT_WEB_PROCESS_CRASHED:
+ return "crashed";
+ case WEBKIT_WEB_PROCESS_EXCEEDED_MEMORY_LIMIT:
+ return "exceeded-memory-limit";
+ case WEBKIT_WEB_PROCESS_TERMINATED_BY_API:
+ return "terminated-by-api";
+ default:
+ return "unknown";
+ }
+ }
+} // namespace
+
+void InfiniFrameWindow::OnConfigureEvent(int x, int y, int width, int height) {
+ if (m_impl->_lastLeft != x || m_impl->_lastTop != y) {
+ InvokeMove(x, y);
+ m_impl->_lastLeft = x;
+ m_impl->_lastTop = y;
+ }
+
+ if (m_impl->_lastHeight != height || m_impl->_lastWidth != width) {
+ InvokeResize(width, height);
+ m_impl->_lastWidth = width;
+ m_impl->_lastHeight = height;
+ }
+}
+
+void InfiniFrameWindow::OnWindowStateEvent(GdkWindowState newState) {
+ if (newState & GDK_WINDOW_STATE_MAXIMIZED) {
+ InvokeMaximized();
+ } else if ((newState & GDK_WINDOW_STATE_ICONIFIED) || !gtk_widget_get_mapped(m_impl->_window)) {
+ InvokeMinimized();
+ } else if (!(newState & GDK_WINDOW_STATE_MAXIMIZED) && !(newState & GDK_WINDOW_STATE_ICONIFIED)) {
+ InvokeRestored();
+ }
+}
+
+gboolean on_configure_event(GtkWidget* widget, GdkEvent* event, const gpointer self) {
+ if (event->type == GDK_CONFIGURE) {
+ auto* instance = reinterpret_cast(self);
+ instance->OnConfigureEvent(
+ event->configure.x, event->configure.y, event->configure.width, event->configure.height
+ );
+ }
+ return FALSE;
+}
+
+gboolean on_window_state_event(GtkWidget* widget, GdkEventWindowState* event, const gpointer self) {
+ auto* instance = reinterpret_cast(self);
+ instance->OnWindowStateEvent(event->new_window_state);
+ return TRUE;
+}
+
+gboolean on_widget_deleted(GtkWidget* widget, GdkEvent* event, const gpointer self) {
+ auto* instance = reinterpret_cast(self);
+ return instance->InvokeClose();
+}
+
+void on_widget_destroyed(GtkWidget* widget, const gpointer self) {
+ auto* instance = reinterpret_cast(self);
+ instance->InvokeClosed();
+}
+
+gboolean on_focus_in_event(GtkWidget* widget, GdkEvent* event, const gpointer self) {
+ auto* instance = reinterpret_cast(self);
+ instance->InvokeFocusIn();
+ return FALSE;
+}
+
+gboolean on_focus_out_event(GtkWidget* widget, GdkEvent* event, const gpointer self) {
+ auto* instance = reinterpret_cast(self);
+ instance->InvokeFocusOut();
+ return FALSE;
+}
+
+gboolean on_webview_context_menu(
+ WebKitWebView* web_view,
+ GtkWidget* default_menu,
+ WebKitHitTestResult* hit_test_result,
+ gboolean triggered_with_keyboard,
+ const gpointer self
+) {
+ auto* instance = reinterpret_cast(self);
+ bool contextMenuEnabled = false;
+ instance->GetContextMenuEnabled(&contextMenuEnabled);
+ return !contextMenuEnabled;
+}
+
+gboolean on_permission_request(WebKitWebView* web_view, WebKitPermissionRequest* request, gpointer user_data) {
+ auto* instance = reinterpret_cast(user_data);
+ bool grant = false;
+ instance->GetGrantBrowserPermissions(&grant);
+ if (grant)
+ webkit_permission_request_allow(request);
+ else
+ webkit_permission_request_deny(request);
+ return TRUE;
+}
+
+void on_webview_load_changed(WebKitWebView* web_view, WebKitLoadEvent load_event, gpointer user_data) {
+ if (!linux_webview_diagnostics_enabled())
+ return;
+
+ const char* uri = webkit_web_view_get_uri(web_view);
+ g_message(
+ "[InfiniFrame/Linux] WebKit load-changed: event=%s uri=%s", webkit_load_event_to_string(load_event),
+ uri ? uri : ""
+ );
+}
+
+gboolean on_webview_load_failed(
+ WebKitWebView* web_view, WebKitLoadEvent load_event, gchar* failing_uri, GError* error, gpointer user_data
+) {
+ if (!linux_webview_diagnostics_enabled())
+ return FALSE;
+
+ g_warning(
+ "[InfiniFrame/Linux] WebKit load-failed: event=%s uri=%s error=%s", webkit_load_event_to_string(load_event),
+ failing_uri ? failing_uri : "", error ? error->message : ""
+ );
+ return FALSE;
+}
+
+void on_webview_process_terminated(
+ WebKitWebView* web_view, WebKitWebProcessTerminationReason reason, gpointer user_data
+) {
+ g_warning(
+ "[InfiniFrame/Linux] WebKit web process terminated: reason=%s", webkit_termination_reason_to_string(reason)
+ );
+}
+
+void on_webview_size_allocate(GtkWidget* widget, GtkAllocation* allocation, gpointer user_data) {
+ if (!linux_webview_diagnostics_enabled())
+ return;
+
+ g_message(
+ "[InfiniFrame/Linux] WebView size-allocate: %dx%d", allocation ? allocation->width : -1,
+ allocation ? allocation->height : -1
+ );
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Core/WindowState.Gtk.cpp b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Core/WindowState.Gtk.cpp
new file mode 100644
index 000000000..e0337e3d7
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Core/WindowState.Gtk.cpp
@@ -0,0 +1,325 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include
+#include
+
+#include "Utils/Common.h"
+#include "Platform/Linux/Window.Gtk.Internal.h"
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+void InfiniFrameWindow::GetTransparentEnabled(bool* enabled) const {
+ *enabled = m_impl->_transparentEnabled;
+}
+
+void InfiniFrameWindow::GetContextMenuEnabled(bool* enabled) const {
+ *enabled = m_impl->_contextMenuEnabled;
+}
+
+void InfiniFrameWindow::GetZoomEnabled(bool* enabled) const {
+ *enabled = m_impl->_zoomEnabled;
+}
+
+void InfiniFrameWindow::GetDevToolsEnabled(bool* enabled) const {
+ WebKitSettings* settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(m_impl->_webview));
+ *enabled = webkit_settings_get_enable_developer_extras(settings);
+}
+
+void InfiniFrameWindow::GetFullScreen(bool* fullScreen) const {
+ *fullScreen = m_impl->_isFullScreen;
+}
+
+void InfiniFrameWindow::GetGrantBrowserPermissions(bool* grant) const {
+ *grant = m_impl->_grantBrowserPermissions;
+}
+
+AutoString InfiniFrameWindow::GetUserAgent() const {
+ return AllocateStringCopy(m_impl->_userAgent);
+}
+
+void InfiniFrameWindow::GetMediaAutoplayEnabled(bool* enabled) const {
+ *enabled = m_impl->_mediaAutoplayEnabled;
+}
+
+void InfiniFrameWindow::GetFileSystemAccessEnabled(bool* enabled) const {
+ *enabled = m_impl->_fileSystemAccessEnabled;
+}
+
+void InfiniFrameWindow::GetWebSecurityEnabled(bool* enabled) const {
+ *enabled = m_impl->_webSecurityEnabled;
+}
+
+void InfiniFrameWindow::GetJavascriptClipboardAccessEnabled(bool* enabled) const {
+ *enabled = m_impl->_javascriptClipboardAccessEnabled;
+}
+
+void InfiniFrameWindow::GetMediaStreamEnabled(bool* enabled) const {
+ *enabled = m_impl->_mediaStreamEnabled;
+}
+
+void InfiniFrameWindow::GetSmoothScrollingEnabled(bool* enabled) const {
+ *enabled = m_impl->_smoothScrollingEnabled;
+}
+
+void InfiniFrameWindow::GetIgnoreCertificateErrorsEnabled(bool* enabled) const {
+ *enabled = m_impl->_ignoreCertificateErrorsEnabled;
+}
+
+void InfiniFrameWindow::GetMaximized(bool* isMaximized) const {
+ GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(m_impl->_window));
+ GdkWindowState flags = gdk_window_get_state(gdk_window);
+ *isMaximized = flags & GDK_WINDOW_STATE_MAXIMIZED;
+}
+
+void InfiniFrameWindow::GetMinimized(bool* isMinimized) const {
+ GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(m_impl->_window));
+ GdkWindowState flags = gdk_window_get_state(gdk_window);
+ *isMinimized = flags & GDK_WINDOW_STATE_ICONIFIED;
+}
+
+void InfiniFrameWindow::GetPosition(int* x, int* y) const {
+ gtk_window_get_position(GTK_WINDOW(m_impl->_window), x, y);
+}
+
+void InfiniFrameWindow::GetResizable(bool* resizable) const {
+ *resizable = gtk_window_get_resizable(GTK_WINDOW(m_impl->_window));
+}
+
+unsigned int InfiniFrameWindow::GetScreenDpi() const {
+ GdkScreen* screen = gtk_window_get_screen(GTK_WINDOW(m_impl->_window));
+ gdouble dpi = gdk_screen_get_resolution(screen);
+ if (dpi < 0)
+ return 96;
+ else
+ return static_cast(dpi);
+}
+
+void InfiniFrameWindow::GetSize(int* width, int* height) const {
+ gtk_window_get_size(GTK_WINDOW(m_impl->_window), width, height);
+}
+
+void InfiniFrameWindow::GetMaxSize(int* width, int* height) const {
+ if (width)
+ *width = m_impl->_maxWidth;
+ if (height)
+ *height = m_impl->_maxHeight;
+}
+
+void InfiniFrameWindow::GetMinSize(int* width, int* height) const {
+ if (width)
+ *width = m_impl->_minWidth;
+ if (height)
+ *height = m_impl->_minHeight;
+}
+
+AutoString InfiniFrameWindow::GetTitle() const {
+ const char* title = gtk_window_get_title(GTK_WINDOW(m_impl->_window));
+ return g_strdup(title ? title : "");
+}
+
+void InfiniFrameWindow::GetTopmost(bool* topmost) const {
+ GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(m_impl->_window));
+ GdkWindowState flags = gdk_window_get_state(gdk_window);
+ *topmost = flags & GDK_WINDOW_STATE_ABOVE;
+}
+
+void InfiniFrameWindow::GetZoom(int* zoom) const {
+ double rawValue = webkit_web_view_get_zoom_level(WEBKIT_WEB_VIEW(m_impl->_webview));
+ rawValue = (rawValue * 100.0) + 0.5;
+ *zoom = static_cast(rawValue);
+}
+
+void InfiniFrameWindow::GetFocused(bool* isFocused) const {
+ *isFocused = gtk_window_is_active(GTK_WINDOW(m_impl->_window));
+}
+
+AutoString InfiniFrameWindow::GetIconFileName() const {
+ return AllocateStringCopy(m_impl->_iconFileName);
+}
+
+void InfiniFrameWindow::NavigateToString(const AutoString content) {
+ webkit_web_view_load_html(WEBKIT_WEB_VIEW(m_impl->_webview), content, nullptr);
+}
+
+void InfiniFrameWindow::NavigateToUrl(const AutoString url) {
+ webkit_web_view_load_uri(WEBKIT_WEB_VIEW(m_impl->_webview), url);
+}
+
+void InfiniFrameWindow::Restore() {
+ gtk_window_present(GTK_WINDOW(m_impl->_window));
+}
+
+static std::string escapeJsonString(std::string_view input) {
+ std::string result;
+ result.reserve(input.size() + 2);
+
+ for (char c : input) {
+ switch (c) {
+ case '"':
+ result += "\\\"";
+ break;
+ case '\\':
+ result += "\\\\";
+ break;
+ case '\b':
+ result += "\\b";
+ break;
+ case '\f':
+ result += "\\f";
+ break;
+ case '\n':
+ result += "\\n";
+ break;
+ case '\r':
+ result += "\\r";
+ break;
+ case '\t':
+ result += "\\t";
+ break;
+ default:
+ if (static_cast(c) < 0x20) {
+ std::format_to(std::back_inserter(result), "\\u{:04x}", static_cast(c));
+ } else {
+ result += c;
+ }
+ }
+ }
+
+ return result;
+}
+
+static void webview_eval_finished(GObject* object, GAsyncResult* result, gpointer) {
+ GError* error = nullptr;
+ webkit_web_view_evaluate_javascript_finish(WEBKIT_WEB_VIEW(object), result, &error);
+ if (error) {
+ g_warning("JavaScript evaluation failed: %s", error->message);
+ g_error_free(error);
+ }
+}
+
+void InfiniFrameWindow::SendWebMessage(const AutoString message) {
+ std::string escaped = escapeJsonString(message ? message : "");
+
+ std::string js;
+ js.append("__dispatchMessageCallback(\"");
+ js.append(escaped);
+ js.append("\")");
+
+ webkit_web_view_evaluate_javascript(
+ WEBKIT_WEB_VIEW(m_impl->_webview), js.c_str(), -1, nullptr, nullptr, nullptr, webview_eval_finished, nullptr
+ );
+}
+
+void InfiniFrameWindow::SetContextMenuEnabled(const bool enabled) {
+ m_impl->_contextMenuEnabled = enabled;
+}
+
+void InfiniFrameWindow::SetZoomEnabled(bool enabled) {
+ (void)enabled;
+}
+
+void InfiniFrameWindow::SetDevToolsEnabled(const bool enabled) {
+ m_impl->_devToolsEnabled = enabled;
+ WebKitSettings* settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(m_impl->_webview));
+ webkit_settings_set_enable_developer_extras(settings, m_impl->_devToolsEnabled);
+}
+
+void InfiniFrameWindow::SetFullScreen(const bool fullScreen) {
+ if (fullScreen)
+ gtk_window_fullscreen(GTK_WINDOW(m_impl->_window));
+ else
+ gtk_window_unfullscreen(GTK_WINDOW(m_impl->_window));
+
+ m_impl->_isFullScreen = fullScreen;
+}
+
+void InfiniFrameWindow::SetIconFile(const AutoString filename) {
+ gtk_window_set_icon_from_file(GTK_WINDOW(m_impl->_window), filename, nullptr);
+ m_impl->_iconFileName = filename ? filename : "";
+}
+
+void InfiniFrameWindow::SetMinimized(const bool minimized) {
+ if (minimized)
+ gtk_window_iconify(GTK_WINDOW(m_impl->_window));
+ else
+ gtk_window_deiconify(GTK_WINDOW(m_impl->_window));
+}
+
+void InfiniFrameWindow::SetMaximized(const bool maximized) {
+ if (maximized)
+ gtk_window_maximize(GTK_WINDOW(m_impl->_window));
+ else
+ gtk_window_unmaximize(GTK_WINDOW(m_impl->_window));
+}
+
+void InfiniFrameWindow::SetPosition(const int x, const int y) {
+ gtk_window_move(GTK_WINDOW(m_impl->_window), x, y);
+}
+
+void InfiniFrameWindow::SetResizable(const bool resizable) {
+ gtk_window_set_resizable(GTK_WINDOW(m_impl->_window), resizable);
+}
+
+void InfiniFrameWindow::SetMinSize(const int width, const int height) {
+ m_impl->_minWidth = width;
+ m_impl->_minHeight = height;
+ m_impl->_hints.min_width = width;
+ m_impl->_hints.min_height = height;
+
+ gtk_window_set_geometry_hints(
+ GTK_WINDOW(m_impl->_window), nullptr, &m_impl->_hints,
+ static_cast(GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE)
+ );
+}
+
+void InfiniFrameWindow::SetMaxSize(const int width, const int height) {
+ m_impl->_maxWidth = width;
+ m_impl->_maxHeight = height;
+ m_impl->_hints.max_width = width;
+ m_impl->_hints.max_height = height;
+
+ gtk_window_set_geometry_hints(
+ GTK_WINDOW(m_impl->_window), nullptr, &m_impl->_hints,
+ static_cast(GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE)
+ );
+}
+
+void InfiniFrameWindow::SetSize(const int width, const int height) {
+ gtk_window_resize(GTK_WINDOW(m_impl->_window), width, height);
+}
+
+void InfiniFrameWindow::SetTitle(const AutoString title) {
+ gtk_window_set_title(GTK_WINDOW(m_impl->_window), title);
+}
+
+void InfiniFrameWindow::SetTopmost(const bool topmost) {
+ gtk_window_set_keep_above(GTK_WINDOW(m_impl->_window), topmost);
+}
+
+void InfiniFrameWindow::SetZoom(const int zoom) {
+ double newZoom = zoom / 100.0;
+ webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(m_impl->_webview), newZoom);
+}
+
+void InfiniFrameWindow::SetFocused() {
+ gtk_window_present(GTK_WINDOW(m_impl->_window));
+}
+
+void InfiniFrameWindow::SetTransparentEnabled(const bool enabled) {
+ m_impl->_transparentEnabled = enabled;
+
+ gtk_window_set_decorated(GTK_WINDOW(m_impl->_window), !enabled);
+
+ GdkScreen* screen = gtk_window_get_screen(GTK_WINDOW(m_impl->_window));
+ GdkVisual* rgba_visual = gdk_screen_get_rgba_visual(screen);
+ if (rgba_visual) {
+ gtk_widget_set_visual(GTK_WIDGET(m_impl->_window), rgba_visual);
+ gtk_widget_set_app_paintable(GTK_WIDGET(m_impl->_window), true);
+
+ GdkRGBA color;
+ webkit_web_view_get_background_color(WEBKIT_WEB_VIEW(m_impl->_webview), &color);
+ color.alpha = enabled ? 0 : 1;
+ webkit_web_view_set_background_color(WEBKIT_WEB_VIEW(m_impl->_webview), &color);
+ }
+}
\ No newline at end of file
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Dialog.cpp b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Dialog.cpp
index 43b1c0d9c..1f7ccc5cd 100644
--- a/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Dialog.cpp
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Dialog.cpp
@@ -1,12 +1,12 @@
-#ifdef __linux__
-/**
- * @file Dialog.cpp (Linux)
- * @brief Linux implementation of InfiniFrameDialog using GTK3 file-chooser and message dialogs
- */
-
-#include "Core/InfiniFrameDialog.h"
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
#include
+#include "Public/InfiniFrameDialog.h"
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
/** @brief Distinguishes which GtkFileChooserAction to configure in ShowDialog */
enum DialogType {
OpenFile, /// GTK_FILE_CHOOSER_ACTION_OPEN — select one or more files
@@ -69,7 +69,7 @@ AutoString* ShowDialog(
const int filterCount,
int* resultCount,
const AutoString defaultFileName = nullptr
- ) {
+) {
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
const char* buttonText = "_Open";
switch (type) {
@@ -88,11 +88,8 @@ AutoString* ShowDialog(
}
GtkWidget* dialog = gtk_file_chooser_dialog_new(
- title, nullptr, action,
- "_Cancel", GTK_RESPONSE_CANCEL,
- buttonText, GTK_RESPONSE_ACCEPT,
- nullptr
- );
+ title, nullptr, action, "_Cancel", GTK_RESPONSE_CANCEL, buttonText, GTK_RESPONSE_ACCEPT, nullptr
+ );
if (defaultPath != nullptr) {
gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), defaultPath);
@@ -130,19 +127,16 @@ AutoString* ShowDialog(
*resultCount = count;
gtk_widget_destroy(dialog);
return results;
- }
- else {
+ } else {
char* result = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
gtk_widget_destroy(dialog);
return new char*[1]{result};
}
}
-InfiniFrameDialog::InfiniFrameDialog() {
-}
+InfiniFrameDialog::InfiniFrameDialog() {}
-InfiniFrameDialog::~InfiniFrameDialog() {
-}
+InfiniFrameDialog::~InfiniFrameDialog() {}
AutoString* InfiniFrameDialog::ShowOpenFile(
const AutoString title,
@@ -151,16 +145,13 @@ AutoString* InfiniFrameDialog::ShowOpenFile(
AutoString* filters,
const int filterCount,
int* resultCount
- ) {
+) {
return ShowDialog(OpenFile, title, defaultPath, multiSelect, filters, filterCount, resultCount);
}
AutoString* InfiniFrameDialog::ShowOpenFolder(
- const AutoString title,
- const AutoString defaultPath,
- const bool multiSelect,
- int* resultCount
- ) {
+ const AutoString title, const AutoString defaultPath, const bool multiSelect, int* resultCount
+) {
return ShowDialog(OpenFolder, title, defaultPath, multiSelect, nullptr, 0, resultCount);
}
@@ -170,7 +161,7 @@ AutoString InfiniFrameDialog::ShowSaveFile(
AutoString* filters,
const int filterCount,
const AutoString defaultFileName
- ) {
+) {
char** result = ShowDialog(SaveFile, title, defaultPath, false, filters, filterCount, nullptr, defaultFileName);
if (result != nullptr) {
char* value = result[0];
@@ -181,11 +172,8 @@ AutoString InfiniFrameDialog::ShowSaveFile(
}
DialogResult InfiniFrameDialog::ShowMessage(
- const AutoString title,
- const AutoString text,
- const DialogButtons buttons,
- const DialogIcon icon
- ) {
+ const AutoString title, const AutoString text, const DialogButtons buttons, const DialogIcon icon
+) {
GtkWidget* dialog;
GtkMessageType type;
@@ -207,14 +195,7 @@ DialogResult InfiniFrameDialog::ShowMessage(
break;
}
- dialog = gtk_message_dialog_new(
- nullptr,
- GTK_DIALOG_MODAL,
- type,
- GTK_BUTTONS_NONE,
- "%s",
- title
- );
+ dialog = gtk_message_dialog_new(nullptr, GTK_DIALOG_MODAL, type, GTK_BUTTONS_NONE, "%s", title);
gtk_message_dialog_set_markup(GTK_MESSAGE_DIALOG(dialog), text);
switch (buttons) {
@@ -272,4 +253,3 @@ DialogResult InfiniFrameDialog::ShowMessage(
return DialogResult::Cancel;
}
}
-#endif
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Linux/WebKit/WebKit.Gtk.Internal.h b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/WebKit/WebKit.Gtk.Internal.h
new file mode 100644
index 000000000..1791398ba
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/WebKit/WebKit.Gtk.Internal.h
@@ -0,0 +1,15 @@
+#pragma once
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+namespace gtk_webkit {
+ void HandleWebMessage(
+ WebKitUserContentManager* contentManager, WebKitJavascriptResult* jsResult, gpointer userData
+ );
+
+ void HandleCustomSchemeRequest(WebKitURISchemeRequest* request, gpointer userData);
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Linux/WebKit/WebKitCustomSchemes.Gtk.cpp b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/WebKit/WebKitCustomSchemes.Gtk.cpp
new file mode 100644
index 000000000..08fb18952
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/WebKit/WebKitCustomSchemes.Gtk.cpp
@@ -0,0 +1,52 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include
+#include
+
+#include "Platform/Linux/Window.Gtk.Internal.h"
+#include "Platform/Linux/WebKit/WebKit.Gtk.Internal.h"
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+namespace gtk_webkit {
+ void HandleCustomSchemeRequest(WebKitURISchemeRequest* request, const gpointer user_data) {
+ WebResourceRequestedCallback webResourceRequestedCallback =
+ reinterpret_cast(user_data);
+ if (webResourceRequestedCallback == nullptr) {
+ GError* error =
+ g_error_new_literal(G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "No custom scheme handler is registered.");
+ webkit_uri_scheme_request_finish_error(request, error);
+ g_error_free(error);
+ return;
+ }
+
+ const gchar* uri = webkit_uri_scheme_request_get_uri(request);
+ int numBytes = 0;
+ AutoString contentType = nullptr;
+ void* dotNetResponse = webResourceRequestedCallback(const_cast(uri), &numBytes, &contentType);
+ GInputStream* stream = g_memory_input_stream_new_from_data(dotNetResponse, numBytes, nullptr);
+ webkit_uri_scheme_request_finish(request, reinterpret_cast(stream), -1, contentType);
+ g_object_unref(stream);
+ free(contentType);
+ }
+} // namespace gtk_webkit
+
+void InfiniFrameWindow::Impl::AddCustomSchemeHandlers() {
+ if (_customSchemeCallback == nullptr)
+ return;
+
+ WebKitWebContext* context = webkit_web_context_get_default();
+ WebKitSecurityManager* securityManager = webkit_web_context_get_security_manager(context);
+ for (const auto& value : _customSchemeNames) {
+ if (securityManager != nullptr && g_ascii_strcasecmp(value.c_str(), "app") == 0) {
+ webkit_security_manager_register_uri_scheme_as_secure(securityManager, value.c_str());
+ }
+
+ webkit_web_context_register_uri_scheme(
+ context, value.c_str(),
+ reinterpret_cast(gtk_webkit::HandleCustomSchemeRequest),
+ reinterpret_cast(_customSchemeCallback), nullptr
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Linux/WebKit/WebKitHost.Gtk.cpp b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/WebKit/WebKitHost.Gtk.cpp
new file mode 100644
index 000000000..a4829d224
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/WebKit/WebKitHost.Gtk.cpp
@@ -0,0 +1,82 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include
+#include
+
+#include "Embedded/Embedded.h"
+#include "Platform/Linux/WebKit/WebKit.Gtk.Internal.h"
+#include "Platform/Linux/Window.Gtk.Internal.h"
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+extern void on_webview_load_changed(WebKitWebView* web_view, WebKitLoadEvent load_event, gpointer user_data);
+extern gboolean on_webview_load_failed(
+ WebKitWebView* web_view, WebKitLoadEvent load_event, gchar* failing_uri, GError* error, gpointer user_data
+);
+extern void on_webview_process_terminated(
+ WebKitWebView* web_view, WebKitWebProcessTerminationReason reason, gpointer user_data
+);
+extern void on_webview_size_allocate(GtkWidget* widget, GtkAllocation* allocation, gpointer user_data);
+
+void InfiniFrameWindow::Show(bool isAlreadyShown) {
+ if (m_impl->_webview) {
+ return;
+ }
+
+ struct sigaction oldAction{};
+ sigaction(SIGCHLD, nullptr, &oldAction);
+ WebKitUserContentManager* contentManager = webkit_user_content_manager_new();
+ m_impl->_webview = webkit_web_view_new_with_user_content_manager(contentManager);
+
+ m_impl->set_webkit_settings();
+
+ gtk_container_add(GTK_CONTAINER(m_impl->_window), m_impl->_webview);
+ gtk_widget_set_hexpand(m_impl->_webview, TRUE);
+ gtk_widget_set_vexpand(m_impl->_webview, TRUE);
+
+ const auto& jsCode = Embedded::InfiniFrameJsUtf8();
+
+ WebKitUserScript* script = webkit_user_script_new(
+ jsCode.c_str(), WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, nullptr,
+ nullptr
+ );
+
+ webkit_user_content_manager_add_script(contentManager, script);
+ webkit_user_script_unref(script);
+
+ g_signal_connect(
+ contentManager, "script-message-received::infiniFrameInterop", G_CALLBACK(gtk_webkit::HandleWebMessage),
+ reinterpret_cast(m_impl->_webMessageReceivedCallback)
+ );
+ webkit_user_content_manager_register_script_message_handler(contentManager, "infiniFrameInterop");
+
+ g_signal_connect(G_OBJECT(m_impl->_webview), "load-changed", G_CALLBACK(on_webview_load_changed), this);
+ g_signal_connect(G_OBJECT(m_impl->_webview), "load-failed", G_CALLBACK(on_webview_load_failed), this);
+ g_signal_connect(
+ G_OBJECT(m_impl->_webview), "web-process-terminated", G_CALLBACK(on_webview_process_terminated), this
+ );
+ g_signal_connect(G_OBJECT(m_impl->_webview), "size-allocate", G_CALLBACK(on_webview_size_allocate), this);
+
+ if (!m_impl->_startUrl.empty()) {
+ NavigateToUrl(const_cast(m_impl->_startUrl.c_str()));
+ } else if (!m_impl->_startString.empty()) {
+ NavigateToString(const_cast(m_impl->_startString.c_str()));
+ } else {
+ GtkWidget* dialog = gtk_message_dialog_new(
+ nullptr, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
+ "Neither StartUrl nor StartString was specified"
+ );
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ sigaction(SIGCHLD, &oldAction, nullptr);
+ return;
+ }
+ sigaction(SIGCHLD, &oldAction, nullptr);
+
+ gtk_widget_show_all(m_impl->_window);
+}
+
+void InfiniFrameWindow::AttachWebView() {
+ // On Linux, WebView is attached in Show()
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Linux/WebKit/WebKitMessaging.Gtk.cpp b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/WebKit/WebKitMessaging.Gtk.cpp
new file mode 100644
index 000000000..981322b9a
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/WebKit/WebKitMessaging.Gtk.cpp
@@ -0,0 +1,49 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include
+#include
+
+#include "Types/Basic.h"
+#include "Types/Callbacks.h"
+#include "Platform/Linux/WebKit/WebKit.Gtk.Internal.h"
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+namespace gtk_webkit {
+ void HandleWebMessage(
+ WebKitUserContentManager* contentManager, WebKitJavascriptResult* jsResult, const gpointer userData
+ ) {
+ JSCValue* jsValue = webkit_javascript_result_get_js_value(jsResult);
+ if (jsc_value_is_string(jsValue)) {
+ AutoString str_value = jsc_value_to_string(jsValue);
+ auto callback = reinterpret_cast(userData);
+ AutoString originValue = nullptr;
+
+ JSGlobalContextRef context = webkit_javascript_result_get_global_context(jsResult);
+ JSStringRef script = JSStringCreateWithUTF8CString("window.location.href");
+ JSValueRef locationValue = JSEvaluateScript(context, script, nullptr, nullptr, 0, nullptr);
+ JSStringRelease(script);
+
+ if (locationValue != nullptr) {
+ JSStringRef locationString = JSValueToStringCopy(context, locationValue, nullptr);
+ if (locationString != nullptr) {
+ size_t maxBytes = JSStringGetMaximumUTF8CStringSize(locationString);
+ originValue = static_cast(g_malloc(maxBytes));
+ JSStringGetUTF8CString(locationString, originValue, maxBytes);
+ JSStringRelease(locationString);
+ }
+ }
+
+ if (callback != nullptr) {
+ callback(str_value, originValue);
+ }
+
+ if (originValue != nullptr)
+ g_free(originValue);
+
+ g_free(str_value);
+ }
+ webkit_javascript_result_unref(jsResult);
+ }
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Linux/WebKit/WebKitSettings.Gtk.cpp b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/WebKit/WebKitSettings.Gtk.cpp
new file mode 100644
index 000000000..40c0da1e8
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/WebKit/WebKitSettings.Gtk.cpp
@@ -0,0 +1,99 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include
+
+#include "Platform/Linux/Window.Gtk.Internal.h"
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+void InfiniFrameWindow::Impl::set_webkit_settings() {
+ WebKitSettings* settings = webkit_settings_new_with_settings(
+ "allow_modal_dialogs", TRUE, "allow_top_navigation_to_data_urls", TRUE, "allow_universal_access_from_file_urls",
+ TRUE, "enable_back_forward_navigation_gestures", TRUE, "enable_media_capabilities", TRUE,
+ "enable_mock_capture_devices", TRUE, "enable_page_cache", TRUE, "enable_webrtc", TRUE,
+ "javascript_can_open_windows_automatically", TRUE,
+
+ "allow_file_access_from_file_urls", _fileSystemAccessEnabled, "disable_web_security", !_webSecurityEnabled,
+ "enable_developer_extras", _devToolsEnabled, "enable_media_stream", _mediaStreamEnabled,
+ "enable_smooth_scrolling", _smoothScrollingEnabled, "javascript_can_access_clipboard",
+ _javascriptClipboardAccessEnabled, "media_playback_requires_user_gesture", !_mediaAutoplayEnabled, "user_agent",
+ _userAgent.c_str(),
+
+ NULL
+ );
+
+ if (!_browserControlInitParameters.empty())
+ set_webkit_customsettings(settings);
+
+ WebKitWebsiteDataManager* manager = webkit_web_view_get_website_data_manager(WEBKIT_WEB_VIEW(_webview));
+ if (_ignoreCertificateErrorsEnabled)
+ webkit_website_data_manager_set_tls_errors_policy(manager, WEBKIT_TLS_ERRORS_POLICY_IGNORE);
+ else
+ webkit_website_data_manager_set_tls_errors_policy(manager, WEBKIT_TLS_ERRORS_POLICY_FAIL);
+
+ webkit_web_view_set_settings(WEBKIT_WEB_VIEW(_webview), settings);
+}
+
+void InfiniFrameWindow::Impl::set_webkit_customsettings(WebKitSettings* settings) {
+ try {
+ simdjson::ondemand::parser parser;
+ auto padded = simdjson::padded_string(_browserControlInitParameters);
+ auto doc = parser.iterate(padded);
+
+ for (auto field : doc.get_object()) {
+ std::string_view keyView = field.unescaped_key();
+ auto value = field.value();
+
+ gchar* propertyName = g_strdup(std::string(keyView).c_str());
+ GValue propertyValue = G_VALUE_INIT;
+ bool hasValidValue = false;
+
+ switch (value.type()) {
+ case simdjson::ondemand::json_type::string: {
+ std::string_view strVal;
+ if (value.get(strVal) == simdjson::SUCCESS) {
+ g_value_init(&propertyValue, G_TYPE_STRING);
+ g_value_set_string(&propertyValue, std::string(strVal).c_str());
+ hasValidValue = true;
+ }
+ break;
+ }
+ case simdjson::ondemand::json_type::boolean: {
+ bool boolVal;
+ if (value.get(boolVal) == simdjson::SUCCESS) {
+ g_value_init(&propertyValue, G_TYPE_BOOLEAN);
+ g_value_set_boolean(&propertyValue, boolVal);
+ hasValidValue = true;
+ }
+ break;
+ }
+ case simdjson::ondemand::json_type::number: {
+ int64_t intVal;
+ if (value.get(intVal) == simdjson::SUCCESS) {
+ g_value_init(&propertyValue, G_TYPE_INT);
+ g_value_set_int(&propertyValue, static_cast(intVal));
+ hasValidValue = true;
+ } else {
+ double doubleVal;
+ if (value.get(doubleVal) == simdjson::SUCCESS) {
+ g_value_init(&propertyValue, G_TYPE_DOUBLE);
+ g_value_set_double(&propertyValue, doubleVal);
+ hasValidValue = true;
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (hasValidValue) {
+ g_object_set_property(G_OBJECT(settings), propertyName, &propertyValue);
+ g_value_unset(&propertyValue);
+ }
+
+ g_free(propertyName);
+ }
+ } catch (const simdjson::simdjson_error&) {}
+}
\ No newline at end of file
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Window.Gtk.Internal.h b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Window.Gtk.Internal.h
new file mode 100644
index 000000000..0be61c9d7
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Window.Gtk.Internal.h
@@ -0,0 +1,43 @@
+#pragma once
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include
+#include
+#include
+#include
+
+#include "Public/InfiniFrameWindow.h"
+#include "Public/InfiniFrameWindowImpl.h"
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+struct InfiniFrameWindow::Impl : InfiniFrameWindowImpl {
+ GtkWidget* _window = nullptr;
+ GtkWidget* _webview = nullptr;
+
+ std::string _temporaryFilesPath;
+
+ bool _isFullScreen = false;
+ double _zoom = 100.0;
+ int _minWidth = 0;
+ int _minHeight = 0;
+ int _maxWidth = INT_MAX;
+ int _maxHeight = INT_MAX;
+
+ GdkGeometry _hints = {};
+
+ int _lastLeft = 0;
+ int _lastTop = 0;
+ int _lastWidth = 0;
+ int _lastHeight = 0;
+
+ void set_webkit_settings();
+ void set_webkit_customsettings(WebKitSettings* settings);
+ void AddCustomSchemeHandlers();
+ void InitializeFromParams(const InfiniFrameInitParams* initParams);
+ void ConfigureInitialWindow(InfiniFrameWindow* window, InfiniFrameInitParams* initParams);
+ void ApplyInitialWindowState(InfiniFrameWindow* window, const InfiniFrameInitParams* initParams);
+ void ConnectWindowSignals(InfiniFrameWindow* window);
+ void ConnectWebViewSignals(InfiniFrameWindow* window);
+};
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Window.cpp b/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Window.cpp
deleted file mode 100644
index 0dc0a7de8..000000000
--- a/src/InfiniFrame.NativeBridge/Native/Platform/Linux/Window.cpp
+++ /dev/null
@@ -1,1284 +0,0 @@
-#ifdef __linux__
-#include "Core/InfiniFrameWindow.h"
-#include "Core/InfiniFrameDialog.h"
-#include "Core/InfiniFrameWindowImpl.h"
-#include "Utils/Common.h"
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include "Embedded/Embedded.h"
-
-std::mutex invokeLockMutex;
-
-struct InvokeWaitInfo {
- ACTION callback;
- std::condition_variable completionNotifier;
- bool isCompleted;
-};
-
-// Forward declarations for GTK signal handlers
-gboolean on_configure_event(GtkWidget* widget, GdkEvent* event, gpointer self);
-gboolean on_window_state_event(GtkWidget* widget, GdkEventWindowState* event, gpointer self);
-gboolean on_widget_deleted(GtkWidget* widget, GdkEvent* event, gpointer self);
-void on_widget_destroyed(GtkWidget* widget, gpointer self);
-gboolean on_focus_in_event(GtkWidget* widget, GdkEvent* event, gpointer self);
-gboolean on_focus_out_event(GtkWidget* widget, GdkEvent* event, gpointer self);
-gboolean on_webview_context_menu(
- WebKitWebView* web_view,
- GtkWidget* default_menu,
- WebKitHitTestResult* hit_test_result,
- gboolean triggered_with_keyboard,
- gpointer user_data
- );
-gboolean on_permission_request(WebKitWebView* web_view, WebKitPermissionRequest* request, gpointer user_data);
-void on_webview_load_changed(WebKitWebView* web_view, WebKitLoadEvent load_event, gpointer user_data);
-gboolean on_webview_load_failed(
- WebKitWebView* web_view,
- WebKitLoadEvent load_event,
- gchar* failing_uri,
- GError* error,
- gpointer user_data
- );
-void on_webview_process_terminated(
- WebKitWebView* web_view,
- WebKitWebProcessTerminationReason reason,
- gpointer user_data
- );
-void on_webview_size_allocate(GtkWidget* widget, GtkAllocation* allocation, gpointer user_data);
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Platform Impl
-// ---------------------------------------------------------------------------------------------------------------------
-
-struct InfiniFrameWindow::Impl : InfiniFrameWindowImpl {
- GtkWidget* _window = nullptr;
- GtkWidget* _webview = nullptr;
-
- std::string _temporaryFilesPath;
-
- bool _isFullScreen = false;
- double _zoom = 100.0;
- int _minWidth = 0;
- int _minHeight = 0;
- int _maxWidth = INT_MAX;
- int _maxHeight = INT_MAX;
-
- GdkGeometry _hints = {};
-
- int _lastLeft = 0;
- int _lastTop = 0;
- int _lastWidth = 0;
- int _lastHeight = 0;
-
- void set_webkit_settings();
- void set_webkit_customsettings(WebKitSettings* settings);
- void AddCustomSchemeHandlers();
-};
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Static signal handlers and helpers
-// ---------------------------------------------------------------------------------------------------------------------
-
-static gboolean invokeCallback(const gpointer data) {
- auto* waitInfo = reinterpret_cast(data);
- waitInfo->callback();
- {
- std::lock_guard guard(invokeLockMutex);
- waitInfo->isCompleted = true;
- }
- waitInfo->completionNotifier.notify_one();
- return false;
-}
-
-static void HandleWebMessage(
- WebKitUserContentManager* contentManager,
- WebKitJavascriptResult* jsResult,
- const gpointer userData
- ) {
- JSCValue* jsValue = webkit_javascript_result_get_js_value(jsResult);
- if (jsc_value_is_string(jsValue)) {
- AutoString str_value = jsc_value_to_string(jsValue);
- WebMessageReceivedCallback callback = reinterpret_cast(userData);
- AutoString originValue = nullptr;
-
- JSGlobalContextRef context = webkit_javascript_result_get_global_context(jsResult);
- JSStringRef script = JSStringCreateWithUTF8CString("window.location.href");
- JSValueRef locationValue = JSEvaluateScript(context, script, nullptr, nullptr, 0, nullptr);
- JSStringRelease(script);
-
- if (locationValue != nullptr) {
- JSStringRef locationString = JSValueToStringCopy(context, locationValue, nullptr);
- if (locationString != nullptr) {
- size_t maxBytes = JSStringGetMaximumUTF8CStringSize(locationString);
- originValue = static_cast(g_malloc(maxBytes));
- JSStringGetUTF8CString(locationString, originValue, maxBytes);
- JSStringRelease(locationString);
- }
- }
-
- if (callback != nullptr) {
- callback(str_value, originValue);
- }
-
- if (originValue != nullptr)
- g_free(originValue);
-
- g_free(str_value);
- }
- webkit_javascript_result_unref(jsResult);
-}
-
-static void HandleCustomSchemeRequest(WebKitURISchemeRequest* request, const gpointer user_data) {
- WebResourceRequestedCallback webResourceRequestedCallback = reinterpret_cast(
- user_data);
- if (webResourceRequestedCallback == nullptr) {
- GError* error = g_error_new_literal(
- G_IO_ERROR,
- G_IO_ERROR_NOT_SUPPORTED,
- "No custom scheme handler is registered.");
- webkit_uri_scheme_request_finish_error(request, error);
- g_error_free(error);
- return;
- }
-
- const gchar* uri = webkit_uri_scheme_request_get_uri(request);
- int numBytes = 0;
- AutoString contentType = nullptr;
- void* dotNetResponse = webResourceRequestedCallback(const_cast(uri), &numBytes, &contentType);
- GInputStream* stream = g_memory_input_stream_new_from_data(dotNetResponse, numBytes, nullptr);
- webkit_uri_scheme_request_finish(request, reinterpret_cast(stream), -1, contentType);
- g_object_unref(stream);
- free(contentType);
-}
-
-static std::string escapeJsonString(std::string_view input) {
- std::string result;
- result.reserve(input.size() + 2);
-
- for (char c : input) {
- switch (c) {
- case '"':
- result += "\\\"";
- break;
- case '\\':
- result += "\\\\";
- break;
- case '\b':
- result += "\\b";
- break;
- case '\f':
- result += "\\f";
- break;
- case '\n':
- result += "\\n";
- break;
- case '\r':
- result += "\\r";
- break;
- case '\t':
- result += "\\t";
- break;
- default:
- if (static_cast(c) < 0x20) {
- std::format_to(std::back_inserter(result), "\\u{:04x}", static_cast(c));
- }
- else {
- result += c;
- }
- }
- }
-
- return result;
-}
-
-static bool linux_webview_diagnostics_enabled() {
- const char* value = g_getenv("INFINIFRAME_LINUX_WEBVIEW_DIAGNOSTICS");
- return value != nullptr && value[0] != '\0' && g_strcmp0(value, "0") != 0;
-}
-
-static const char* webkit_load_event_to_string(WebKitLoadEvent event) {
- switch (event) {
- case WEBKIT_LOAD_STARTED:
- return "started";
- case WEBKIT_LOAD_REDIRECTED:
- return "redirected";
- case WEBKIT_LOAD_COMMITTED:
- return "committed";
- case WEBKIT_LOAD_FINISHED:
- return "finished";
- default:
- return "unknown";
- }
-}
-
-static const char* webkit_termination_reason_to_string(WebKitWebProcessTerminationReason reason) {
- switch (reason) {
- case WEBKIT_WEB_PROCESS_CRASHED:
- return "crashed";
- case WEBKIT_WEB_PROCESS_EXCEEDED_MEMORY_LIMIT:
- return "exceeded-memory-limit";
- case WEBKIT_WEB_PROCESS_TERMINATED_BY_API:
- return "terminated-by-api";
- default:
- return "unknown";
- }
-}
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Impl method definitions
-// ---------------------------------------------------------------------------------------------------------------------
-
-void InfiniFrameWindow::Impl::set_webkit_settings() {
- WebKitSettings* settings = webkit_settings_new_with_settings(
- "allow_modal_dialogs", TRUE,
- "allow_top_navigation_to_data_urls", TRUE,
- "allow_universal_access_from_file_urls", TRUE,
- "enable_back_forward_navigation_gestures", TRUE,
- "enable_media_capabilities", TRUE,
- "enable_mock_capture_devices", TRUE,
- "enable_page_cache", TRUE,
- "enable_webrtc", TRUE,
- "javascript_can_open_windows_automatically", TRUE,
-
- "allow_file_access_from_file_urls", _fileSystemAccessEnabled,
- "disable_web_security", !_webSecurityEnabled,
- "enable_developer_extras", _devToolsEnabled,
- "enable_media_stream", _mediaStreamEnabled,
- "enable_smooth_scrolling", _smoothScrollingEnabled,
- "javascript_can_access_clipboard", _javascriptClipboardAccessEnabled,
- "media_playback_requires_user_gesture", !_mediaAutoplayEnabled,
- "user_agent", _userAgent.c_str(),
-
- NULL
- );
-
- if (!_browserControlInitParameters.empty())
- set_webkit_customsettings(settings);
-
- WebKitWebsiteDataManager* manager = webkit_web_view_get_website_data_manager(WEBKIT_WEB_VIEW(_webview));
- if (_ignoreCertificateErrorsEnabled)
- webkit_website_data_manager_set_tls_errors_policy(manager, WEBKIT_TLS_ERRORS_POLICY_IGNORE);
- else
- webkit_website_data_manager_set_tls_errors_policy(manager, WEBKIT_TLS_ERRORS_POLICY_FAIL);
-
- webkit_web_view_set_settings(WEBKIT_WEB_VIEW(_webview), settings);
-}
-
-void InfiniFrameWindow::Impl::set_webkit_customsettings(WebKitSettings* settings) {
- try {
- simdjson::ondemand::parser parser;
- auto padded = simdjson::padded_string(_browserControlInitParameters);
- auto doc = parser.iterate(padded);
-
- for (auto field : doc.get_object()) {
- std::string_view keyView = field.unescaped_key();
- auto value = field.value();
-
- gchar* propertyName = g_strdup(std::string(keyView).c_str());
- GValue propertyValue = G_VALUE_INIT;
- bool hasValidValue = false;
-
- switch (value.type()) {
- case simdjson::ondemand::json_type::string: {
- std::string_view strVal;
- if (value.get(strVal) == simdjson::SUCCESS) {
- g_value_init(&propertyValue, G_TYPE_STRING);
- g_value_set_string(&propertyValue, std::string(strVal).c_str());
- hasValidValue = true;
- }
- break;
- }
- case simdjson::ondemand::json_type::boolean: {
- bool boolVal;
- if (value.get(boolVal) == simdjson::SUCCESS) {
- g_value_init(&propertyValue, G_TYPE_BOOLEAN);
- g_value_set_boolean(&propertyValue, boolVal);
- hasValidValue = true;
- }
- break;
- }
- case simdjson::ondemand::json_type::number: {
- int64_t intVal;
- if (value.get(intVal) == simdjson::SUCCESS) {
- g_value_init(&propertyValue, G_TYPE_INT);
- g_value_set_int(&propertyValue, static_cast(intVal));
- hasValidValue = true;
- }
- else {
- double doubleVal;
- if (value.get(doubleVal) == simdjson::SUCCESS) {
- g_value_init(&propertyValue, G_TYPE_DOUBLE);
- g_value_set_double(&propertyValue, doubleVal);
- hasValidValue = true;
- }
- }
- break;
- }
- default:
- // Ignore unsupported JSON value types instead of crashing.
- break;
- }
-
- if (hasValidValue) {
- g_object_set_property(G_OBJECT(settings), propertyName, &propertyValue);
- g_value_unset(&propertyValue);
- }
-
- g_free(propertyName);
- }
- }
- catch (const simdjson::simdjson_error&) {
- // Some callers pass CLI-like strings (e.g. --remote-debugging-port=9222).
- // Ignore non-JSON payloads instead of aborting the process.
- }
-}
-
-void InfiniFrameWindow::Impl::AddCustomSchemeHandlers() {
- if (_customSchemeCallback == nullptr)
- return;
-
- WebKitWebContext* context = webkit_web_context_get_default();
- WebKitSecurityManager* securityManager = webkit_web_context_get_security_manager(context);
- for (const auto& value : _customSchemeNames) {
- if (securityManager != nullptr && g_ascii_strcasecmp(value.c_str(), "app") == 0) {
- // Mirror Windows behavior for embedded static assets:
- // only app:// is explicitly treated as a secure custom scheme.
- webkit_security_manager_register_uri_scheme_as_secure(securityManager, value.c_str());
- }
-
- webkit_web_context_register_uri_scheme(
- context, value.c_str(),
- reinterpret_cast(HandleCustomSchemeRequest),
- reinterpret_cast(_customSchemeCallback),
- nullptr
- );
- }
-}
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Constructor / Destructor
-// ---------------------------------------------------------------------------------------------------------------------
-
-InfiniFrameWindow::InfiniFrameWindow(InfiniFrameInitParams* initParams) :
- m_impl(std::make_unique()) {
- XInitThreads();
- gtk_init(nullptr, nullptr);
- notify_init(initParams->Title);
-
- if (initParams->Size != sizeof(InfiniFrameInitParams)) {
- GtkWidget* dialog = gtk_message_dialog_new(
- nullptr, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
- "Initial parameters passed are %i bytes, but expected %lu bytes.",
- initParams->Size, sizeof(InfiniFrameInitParams)
- );
- gtk_dialog_run(GTK_DIALOG(dialog));
- gtk_widget_destroy(dialog);
- exit(0);
- }
-
- m_impl->_windowTitle = initParams->Title ? initParams->Title : "";
-
- if (initParams->StartUrl != nullptr)
- m_impl->_startUrl = initParams->StartUrl;
-
- if (initParams->StartString != nullptr)
- m_impl->_startString = initParams->StartString;
-
- if (initParams->TemporaryFilesPath != nullptr)
- m_impl->_temporaryFilesPath = initParams->TemporaryFilesPath;
-
- if (initParams->UserAgent != nullptr)
- m_impl->_userAgent = initParams->UserAgent;
-
- if (initParams->BrowserControlInitParameters != nullptr)
- m_impl->_browserControlInitParameters = initParams->BrowserControlInitParameters;
-
- m_impl->_transparentEnabled = initParams->Transparent;
- m_impl->_contextMenuEnabled = initParams->ContextMenuEnabled;
- m_impl->_zoomEnabled = initParams->ZoomEnabled;
- m_impl->_devToolsEnabled = initParams->DevToolsEnabled;
- m_impl->_grantBrowserPermissions = initParams->GrantBrowserPermissions;
- m_impl->_mediaAutoplayEnabled = initParams->MediaAutoplayEnabled;
- m_impl->_fileSystemAccessEnabled = initParams->FileSystemAccessEnabled;
- m_impl->_webSecurityEnabled = initParams->WebSecurityEnabled;
- m_impl->_javascriptClipboardAccessEnabled = initParams->JavascriptClipboardAccessEnabled;
- m_impl->_mediaStreamEnabled = initParams->MediaStreamEnabled;
- m_impl->_smoothScrollingEnabled = initParams->SmoothScrollingEnabled;
- m_impl->_ignoreCertificateErrorsEnabled = initParams->IgnoreCertificateErrorsEnabled;
- m_impl->_isFullScreen = initParams->FullScreen;
-
- m_impl->_zoom = initParams->Zoom;
- m_impl->_minWidth = initParams->MinWidth;
- m_impl->_minHeight = initParams->MinHeight;
- m_impl->_maxWidth = initParams->MaxWidth;
- m_impl->_maxHeight = initParams->MaxHeight;
-
- m_impl->_webMessageReceivedCallback = initParams->WebMessageReceivedHandler;
- m_impl->_resizedCallback = initParams->ResizedHandler;
- m_impl->_movedCallback = initParams->MovedHandler;
- m_impl->_closingCallback = initParams->ClosingHandler;
- m_impl->_closedCallback = initParams->ClosedHandler;
- m_impl->_focusInCallback = initParams->FocusInHandler;
- m_impl->_focusOutCallback = initParams->FocusOutHandler;
- m_impl->_maximizedCallback = initParams->MaximizedHandler;
- m_impl->_minimizedCallback = initParams->MinimizedHandler;
- m_impl->_restoredCallback = initParams->RestoredHandler;
- m_impl->_customSchemeCallback = initParams->CustomSchemeHandler;
-
- for (int i = 0; i < 16; ++i) {
- if (initParams->CustomSchemeNames[i] != nullptr)
- m_impl->_customSchemeNames.emplace_back(initParams->CustomSchemeNames[i]);
- }
-
- m_impl->_parent = initParams->ParentInstance;
-
- m_impl->_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
- m_impl->_dialog = std::make_unique();
-
- if (initParams->FullScreen)
- SetFullScreen(true);
- else {
- if (initParams->Width > initParams->MaxWidth)
- initParams->Width = initParams->MaxWidth;
- if (initParams->Height > initParams->MaxHeight)
- initParams->Height = initParams->MaxHeight;
- if (initParams->Width < initParams->MinWidth)
- initParams->Width = initParams->MinWidth;
- if (initParams->Height < initParams->MinHeight)
- initParams->Height = initParams->MinHeight;
-
- if (initParams->UseOsDefaultSize)
- gtk_window_set_default_size(GTK_WINDOW(m_impl->_window), -1, -1);
- else
- gtk_window_set_default_size(GTK_WINDOW(m_impl->_window), initParams->Width, initParams->Height);
-
- SetMinSize(initParams->MinWidth, initParams->MinHeight);
- SetMaxSize(initParams->MaxWidth, initParams->MaxHeight);
-
- if (initParams->UseOsDefaultLocation)
- gtk_window_set_position(GTK_WINDOW(m_impl->_window), GTK_WIN_POS_NONE);
- else if (initParams->CenterOnInitialize && !initParams->FullScreen)
- gtk_window_set_position(GTK_WINDOW(m_impl->_window), GTK_WIN_POS_CENTER);
- else
- gtk_window_move(GTK_WINDOW(m_impl->_window), initParams->Left, initParams->Top);
- }
-
- SetTitle(const_cast(m_impl->_windowTitle.c_str()));
-
- if (initParams->Chromeless)
- gtk_window_set_decorated(GTK_WINDOW(m_impl->_window), false);
-
- if (initParams->WindowIconFile != nullptr && strlen(initParams->WindowIconFile) > 0)
- SetIconFile(initParams->WindowIconFile);
-
- if (initParams->CenterOnInitialize)
- Center();
-
- if (initParams->Minimized)
- SetMinimized(true);
-
- if (initParams->Maximized)
- SetMaximized(true);
-
- if (!initParams->Resizable)
- SetResizable(false);
-
- if (initParams->Topmost)
- SetTopmost(true);
-
- g_signal_connect(
- G_OBJECT(m_impl->_window), "configure-event",
- G_CALLBACK(on_configure_event), this
- );
-
- g_signal_connect(
- G_OBJECT(m_impl->_window), "window-state-event",
- G_CALLBACK(on_window_state_event), this
- );
-
- g_signal_connect(
- G_OBJECT(m_impl->_window), "delete-event",
- G_CALLBACK(on_widget_deleted), this
- );
-
- g_signal_connect(
- G_OBJECT(m_impl->_window), "destroy",
- G_CALLBACK(on_widget_destroyed), this
- );
-
- // Register custom schemes before first navigation to avoid first-load races.
- m_impl->AddCustomSchemeHandlers();
-
- Show(false);
-
- g_signal_connect(
- G_OBJECT(m_impl->_window), "focus-in-event",
- G_CALLBACK(on_focus_in_event), this
- );
-
- g_signal_connect(
- G_OBJECT(m_impl->_window), "focus-out-event",
- G_CALLBACK(on_focus_out_event), this
- );
-
- g_signal_connect(
- G_OBJECT(m_impl->_webview), "context-menu",
- G_CALLBACK(on_webview_context_menu), this
- );
-
- g_signal_connect(
- G_OBJECT(m_impl->_webview), "permission-request",
- G_CALLBACK(on_permission_request), this
- );
-
- if (initParams->Transparent)
- SetTransparentEnabled(true);
-
- if (m_impl->_zoom != 100.0)
- SetZoom(m_impl->_zoom);
-}
-
-InfiniFrameWindow::~InfiniFrameWindow() {
- notify_uninit();
- gtk_widget_destroy(m_impl->_window);
-}
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Window Operations
-// ---------------------------------------------------------------------------------------------------------------------
-
-void InfiniFrameWindow::Center() {
- gint windowWidth, windowHeight;
- gtk_window_get_size(GTK_WINDOW(m_impl->_window), &windowWidth, &windowHeight);
-
- GdkRectangle screen = {0};
-
- GdkDisplay* d = gdk_display_get_default();
- if (d == nullptr) {
- GtkWidget* dialog = gtk_message_dialog_new(
- nullptr, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
- "gdk_display_get_default() returned NULL"
- );
- gtk_dialog_run(GTK_DIALOG(dialog));
- gtk_widget_destroy(dialog);
- return;
- }
-
- GdkMonitor* m = gdk_display_get_primary_monitor(d);
- if (m == nullptr) {
- m = gdk_display_get_monitor(d, 0);
- if (m == nullptr) {
- GtkWidget* dialog = gtk_message_dialog_new(
- nullptr, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
- "gdk_display_get_primary_monitor() returned NULL"
- );
- gtk_dialog_run(GTK_DIALOG(dialog));
- gtk_widget_destroy(dialog);
- return;
- }
- }
-
- gdk_monitor_get_geometry(m, &screen);
-
- gtk_window_move(
- GTK_WINDOW(m_impl->_window),
- (screen.width - windowWidth) / 2,
- (screen.height - windowHeight) / 2
- );
-}
-
-void InfiniFrameWindow::ClearBrowserAutoFill() {
- // TODO
-}
-
-void InfiniFrameWindow::Close() {
- gtk_window_close(GTK_WINDOW(m_impl->_window));
-}
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Get Properties
-// ---------------------------------------------------------------------------------------------------------------------
-
-void InfiniFrameWindow::GetTransparentEnabled(bool* enabled) const {
- *enabled = m_impl->_transparentEnabled;
-}
-
-void InfiniFrameWindow::GetContextMenuEnabled(bool* enabled) const {
- *enabled = m_impl->_contextMenuEnabled;
-}
-
-void InfiniFrameWindow::GetZoomEnabled(bool* enabled) const {
- *enabled = m_impl->_zoomEnabled;
-}
-
-void InfiniFrameWindow::GetDevToolsEnabled(bool* enabled) const {
- WebKitSettings* settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(m_impl->_webview));
- *enabled = webkit_settings_get_enable_developer_extras(settings);
-}
-
-void InfiniFrameWindow::GetFullScreen(bool* fullScreen) const {
- *fullScreen = m_impl->_isFullScreen;
-}
-
-void InfiniFrameWindow::GetGrantBrowserPermissions(bool* grant) const {
- *grant = m_impl->_grantBrowserPermissions;
-}
-
-AutoString InfiniFrameWindow::GetUserAgent() const {
- return AllocateStringCopy(m_impl->_userAgent);
-}
-
-void InfiniFrameWindow::GetMediaAutoplayEnabled(bool* enabled) const {
- *enabled = m_impl->_mediaAutoplayEnabled;
-}
-
-void InfiniFrameWindow::GetFileSystemAccessEnabled(bool* enabled) const {
- *enabled = m_impl->_fileSystemAccessEnabled;
-}
-
-void InfiniFrameWindow::GetWebSecurityEnabled(bool* enabled) const {
- *enabled = m_impl->_webSecurityEnabled;
-}
-
-void InfiniFrameWindow::GetJavascriptClipboardAccessEnabled(bool* enabled) const {
- *enabled = m_impl->_javascriptClipboardAccessEnabled;
-}
-
-void InfiniFrameWindow::GetMediaStreamEnabled(bool* enabled) const {
- *enabled = m_impl->_mediaStreamEnabled;
-}
-
-void InfiniFrameWindow::GetSmoothScrollingEnabled(bool* enabled) const {
- *enabled = m_impl->_smoothScrollingEnabled;
-}
-
-void InfiniFrameWindow::GetIgnoreCertificateErrorsEnabled(bool* enabled) const {
- *enabled = m_impl->_ignoreCertificateErrorsEnabled;
-}
-
-void InfiniFrameWindow::GetMaximized(bool* isMaximized) const {
- GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(m_impl->_window));
- GdkWindowState flags = gdk_window_get_state(gdk_window);
- *isMaximized = flags & GDK_WINDOW_STATE_MAXIMIZED;
-}
-
-void InfiniFrameWindow::GetMinimized(bool* isMinimized) const {
- GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(m_impl->_window));
- GdkWindowState flags = gdk_window_get_state(gdk_window);
- *isMinimized = flags & GDK_WINDOW_STATE_ICONIFIED;
-}
-
-void InfiniFrameWindow::GetPosition(int* x, int* y) const {
- gtk_window_get_position(GTK_WINDOW(m_impl->_window), x, y);
-}
-
-void InfiniFrameWindow::GetResizable(bool* resizable) const {
- *resizable = gtk_window_get_resizable(GTK_WINDOW(m_impl->_window));
-}
-
-unsigned int InfiniFrameWindow::GetScreenDpi() const {
- GdkScreen* screen = gtk_window_get_screen(GTK_WINDOW(m_impl->_window));
- gdouble dpi = gdk_screen_get_resolution(screen);
- if (dpi < 0)
- return 96;
- else
- return static_cast(dpi);
-}
-
-void InfiniFrameWindow::GetSize(int* width, int* height) const {
- gtk_window_get_size(GTK_WINDOW(m_impl->_window), width, height);
-}
-
-void InfiniFrameWindow::GetMaxSize(int* width, int* height) const {
- if (width)
- *width = m_impl->_maxWidth;
- if (height)
- *height = m_impl->_maxHeight;
-}
-
-void InfiniFrameWindow::GetMinSize(int* width, int* height) const {
- if (width)
- *width = m_impl->_minWidth;
- if (height)
- *height = m_impl->_minHeight;
-}
-
-AutoString InfiniFrameWindow::GetTitle() const {
- const char* title = gtk_window_get_title(GTK_WINDOW(m_impl->_window));
- return g_strdup(title ? title : "");
-}
-
-void InfiniFrameWindow::GetTopmost(bool* topmost) const {
- GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(m_impl->_window));
- GdkWindowState flags = gdk_window_get_state(gdk_window);
- *topmost = flags & GDK_WINDOW_STATE_ABOVE;
-}
-
-void InfiniFrameWindow::GetZoom(int* zoom) const {
- double rawValue = webkit_web_view_get_zoom_level(WEBKIT_WEB_VIEW(m_impl->_webview));
- rawValue = (rawValue * 100.0) + 0.5;
- *zoom = static_cast(rawValue);
-}
-
-void InfiniFrameWindow::GetFocused(bool* isFocused) const {
- *isFocused = gtk_window_is_active(GTK_WINDOW(m_impl->_window));
-}
-
-AutoString InfiniFrameWindow::GetIconFileName() const {
- return AllocateStringCopy(m_impl->_iconFileName);
-}
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Navigation
-// ---------------------------------------------------------------------------------------------------------------------
-
-void InfiniFrameWindow::NavigateToString(const AutoString content) {
- webkit_web_view_load_html(WEBKIT_WEB_VIEW(m_impl->_webview), content, nullptr);
-}
-
-void InfiniFrameWindow::NavigateToUrl(const AutoString url) {
- webkit_web_view_load_uri(WEBKIT_WEB_VIEW(m_impl->_webview), url);
-}
-
-void InfiniFrameWindow::Restore() {
- gtk_window_present(GTK_WINDOW(m_impl->_window));
-}
-
-static void webview_eval_finished(GObject* object, GAsyncResult* result, gpointer) {
- GError* error = nullptr;
- webkit_web_view_evaluate_javascript_finish(WEBKIT_WEB_VIEW(object), result, &error);
- if (error) {
- g_warning("JavaScript evaluation failed: %s", error->message);
- g_error_free(error);
- }
-}
-
-void InfiniFrameWindow::SendWebMessage(const AutoString message) {
- std::string escaped = escapeJsonString(message ? message : "");
-
- std::string js;
- js.append("__dispatchMessageCallback(\"");
- js.append(escaped);
- js.append("\")");
-
- webkit_web_view_evaluate_javascript(
- WEBKIT_WEB_VIEW(m_impl->_webview),
- js.c_str(),
- -1,
- nullptr,
- nullptr,
- nullptr,
- webview_eval_finished,
- nullptr
- );
-}
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Set Properties
-// ---------------------------------------------------------------------------------------------------------------------
-
-void InfiniFrameWindow::SetContextMenuEnabled(const bool enabled) {
- m_impl->_contextMenuEnabled = enabled;
-}
-
-void InfiniFrameWindow::SetZoomEnabled(bool enabled) {
- // Not implemented on Linux
-}
-
-void InfiniFrameWindow::SetDevToolsEnabled(const bool enabled) {
- m_impl->_devToolsEnabled = enabled;
- WebKitSettings* settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(m_impl->_webview));
- webkit_settings_set_enable_developer_extras(settings, m_impl->_devToolsEnabled);
-}
-
-void InfiniFrameWindow::SetFullScreen(const bool fullScreen) {
- if (fullScreen)
- gtk_window_fullscreen(GTK_WINDOW(m_impl->_window));
- else
- gtk_window_unfullscreen(GTK_WINDOW(m_impl->_window));
-
- m_impl->_isFullScreen = fullScreen;
-}
-
-void InfiniFrameWindow::SetIconFile(const AutoString filename) {
- gtk_window_set_icon_from_file(GTK_WINDOW(m_impl->_window), filename, nullptr);
- m_impl->_iconFileName = filename ? filename : "";
-}
-
-void InfiniFrameWindow::SetMinimized(const bool minimized) {
- if (minimized)
- gtk_window_iconify(GTK_WINDOW(m_impl->_window));
- else
- gtk_window_deiconify(GTK_WINDOW(m_impl->_window));
-}
-
-void InfiniFrameWindow::SetMaximized(const bool maximized) {
- if (maximized)
- gtk_window_maximize(GTK_WINDOW(m_impl->_window));
- else
- gtk_window_unmaximize(GTK_WINDOW(m_impl->_window));
-}
-
-void InfiniFrameWindow::SetPosition(const int x, const int y) {
- gtk_window_move(GTK_WINDOW(m_impl->_window), x, y);
-}
-
-void InfiniFrameWindow::SetResizable(const bool resizable) {
- gtk_window_set_resizable(GTK_WINDOW(m_impl->_window), resizable);
-}
-
-void InfiniFrameWindow::SetMinSize(const int width, const int height) {
- m_impl->_minWidth = width;
- m_impl->_minHeight = height;
- m_impl->_hints.min_width = width;
- m_impl->_hints.min_height = height;
-
- gtk_window_set_geometry_hints(
- GTK_WINDOW(m_impl->_window),
- nullptr,
- &m_impl->_hints,
- (GdkWindowHints)(GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE)
- );
-}
-
-void InfiniFrameWindow::SetMaxSize(const int width, const int height) {
- m_impl->_maxWidth = width;
- m_impl->_maxHeight = height;
- m_impl->_hints.max_width = width;
- m_impl->_hints.max_height = height;
-
- gtk_window_set_geometry_hints(
- GTK_WINDOW(m_impl->_window),
- nullptr,
- &m_impl->_hints,
- (GdkWindowHints)(GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE)
- );
-}
-
-void InfiniFrameWindow::SetSize(const int width, const int height) {
- gtk_window_resize(GTK_WINDOW(m_impl->_window), width, height);
-}
-
-void InfiniFrameWindow::SetTitle(const AutoString title) {
- gtk_window_set_title(GTK_WINDOW(m_impl->_window), title);
-}
-
-void InfiniFrameWindow::SetTopmost(const bool topmost) {
- gtk_window_set_keep_above(GTK_WINDOW(m_impl->_window), topmost);
-}
-
-void InfiniFrameWindow::SetZoom(const int zoom) {
- double newZoom = zoom / 100.0;
- webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(m_impl->_webview), newZoom);
-}
-
-void InfiniFrameWindow::SetFocused() {
- gtk_window_present(GTK_WINDOW(m_impl->_window));
-}
-
-void InfiniFrameWindow::SetTransparentEnabled(const bool enabled) {
- m_impl->_transparentEnabled = enabled;
-
- gtk_window_set_decorated(GTK_WINDOW(m_impl->_window), !enabled);
-
- GdkScreen* screen = gtk_window_get_screen(GTK_WINDOW(m_impl->_window));
- GdkVisual* rgba_visual = gdk_screen_get_rgba_visual(screen);
- if (rgba_visual) {
- gtk_widget_set_visual(GTK_WIDGET(m_impl->_window), rgba_visual);
- gtk_widget_set_app_paintable(GTK_WIDGET(m_impl->_window), true);
-
- GdkRGBA color;
- webkit_web_view_get_background_color(WEBKIT_WEB_VIEW(m_impl->_webview), &color);
- color.alpha = enabled ? 0 : 1;
- webkit_web_view_set_background_color(WEBKIT_WEB_VIEW(m_impl->_webview), &color);
- }
-}
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Notifications / Event loop
-// ---------------------------------------------------------------------------------------------------------------------
-
-void InfiniFrameWindow::ShowNotification(const AutoString title, const AutoString message) {
- NotifyNotification* notification = notify_notification_new(title, message, nullptr);
- notify_notification_set_icon_from_pixbuf(notification, gtk_window_get_icon(GTK_WINDOW(m_impl->_window)));
- notify_notification_show(notification, nullptr);
- g_object_unref(G_OBJECT(notification));
-}
-
-void InfiniFrameWindow::WaitForExit() {
- g_signal_connect(
- G_OBJECT(m_impl->_window), "destroy",
- G_CALLBACK(
- +[](GtkWidget*, gpointer) {
- gtk_main_quit();
- }
- ),
- nullptr
- );
- gtk_main();
-}
-
-void InfiniFrameWindow::CloseWebView() {
- // Not implemented on Linux
-}
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Callbacks
-// ---------------------------------------------------------------------------------------------------------------------
-
-InfiniFrameDialog* InfiniFrameWindow::GetDialog() const {
- return m_impl->_dialog.get();
-}
-
-void InfiniFrameWindow::AddCustomSchemeName(const AutoStringConst scheme) {
- if (scheme)
- m_impl->_customSchemeNames.emplace_back(scheme);
-}
-
-void InfiniFrameWindow::GetAllMonitors(const GetAllMonitorsCallback callback) const {
- if (callback) {
- GdkScreen* screen = gtk_window_get_screen(GTK_WINDOW(m_impl->_window));
- GdkDisplay* display = gdk_screen_get_display(screen);
- int n = gdk_display_get_n_monitors(display);
- for (int i = 0; i < n; i++) {
- GdkMonitor* monitor = gdk_display_get_monitor(display, i);
- Monitor props = {};
- gdk_monitor_get_geometry(monitor, (GdkRectangle*)&props.monitor);
- gdk_monitor_get_workarea(monitor, (GdkRectangle*)&props.work);
- props.scale = gdk_monitor_get_scale_factor(monitor);
- if (!callback(&props))
- break;
- }
- }
-}
-
-void InfiniFrameWindow::SetClosingCallback(const ClosingCallback callback) {
- m_impl->_closingCallback = callback;
-}
-
-void InfiniFrameWindow::SetClosedCallback(const ClosedCallback callback) {
- m_impl->_closedCallback = callback;
-}
-
-void InfiniFrameWindow::SetFocusInCallback(const FocusInCallback callback) {
- m_impl->_focusInCallback = callback;
-}
-
-void InfiniFrameWindow::SetFocusOutCallback(const FocusOutCallback callback) {
- m_impl->_focusOutCallback = callback;
-}
-
-void InfiniFrameWindow::SetMovedCallback(const MovedCallback callback) {
- m_impl->_movedCallback = callback;
-}
-
-void InfiniFrameWindow::SetResizedCallback(const ResizedCallback callback) {
- m_impl->_resizedCallback = callback;
-}
-
-void InfiniFrameWindow::SetMaximizedCallback(const MaximizedCallback callback) {
- m_impl->_maximizedCallback = callback;
-}
-
-void InfiniFrameWindow::SetRestoredCallback(const RestoredCallback callback) {
- m_impl->_restoredCallback = callback;
-}
-
-void InfiniFrameWindow::SetMinimizedCallback(const MinimizedCallback callback) {
- m_impl->_minimizedCallback = callback;
-}
-
-void InfiniFrameWindow::Invoke(const ACTION callback) {
- InvokeWaitInfo waitInfo = {};
- waitInfo.callback = callback;
- gdk_threads_add_idle(invokeCallback, &waitInfo);
-
- std::unique_lock uLock(invokeLockMutex);
- waitInfo.completionNotifier.wait(
- uLock, [&] {
- return waitInfo.isCompleted;
- }
- );
-}
-
-[[nodiscard]] bool InfiniFrameWindow::InvokeClose() const noexcept {
- if (m_impl->_closingCallback)
- return m_impl->_closingCallback();
- return false;
-}
-
-void InfiniFrameWindow::InvokeClosed() const noexcept {
- if (m_impl->_closedCallback)
- m_impl->_closedCallback();
-}
-
-void InfiniFrameWindow::InvokeFocusIn() const noexcept {
- if (m_impl->_focusInCallback)
- m_impl->_focusInCallback();
-}
-
-void InfiniFrameWindow::InvokeFocusOut() const noexcept {
- if (m_impl->_focusOutCallback)
- m_impl->_focusOutCallback();
-}
-
-void InfiniFrameWindow::InvokeMove(int x, int y) const noexcept {
- if (m_impl->_movedCallback)
- m_impl->_movedCallback(x, y);
-}
-
-void InfiniFrameWindow::InvokeResize(int width, int height) const noexcept {
- if (m_impl->_resizedCallback)
- m_impl->_resizedCallback(width, height);
-}
-
-void InfiniFrameWindow::InvokeMaximized() const noexcept {
- if (m_impl->_maximizedCallback)
- m_impl->_maximizedCallback();
-}
-
-void InfiniFrameWindow::InvokeRestored() const noexcept {
- if (m_impl->_restoredCallback)
- m_impl->_restoredCallback();
-}
-
-void InfiniFrameWindow::InvokeMinimized() const noexcept {
- if (m_impl->_minimizedCallback)
- m_impl->_minimizedCallback();
-}
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Private methods
-// ---------------------------------------------------------------------------------------------------------------------
-
-void InfiniFrameWindow::Show(bool isAlreadyShown) {
- if (!m_impl->_webview) {
- struct sigaction old_action;
- sigaction(SIGCHLD, nullptr, &old_action);
- WebKitUserContentManager* contentManager = webkit_user_content_manager_new();
- m_impl->_webview = webkit_web_view_new_with_user_content_manager(contentManager);
-
- m_impl->set_webkit_settings();
-
- gtk_container_add(GTK_CONTAINER(m_impl->_window), m_impl->_webview);
- gtk_widget_set_hexpand(m_impl->_webview, TRUE);
- gtk_widget_set_vexpand(m_impl->_webview, TRUE);
-
- auto js = Embedded::InfiniFrameJsUtf8();
-
- WebKitUserScript* script = webkit_user_script_new(
- js.c_str(),
- WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
- WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START,
- nullptr,
- nullptr
- );
-
- webkit_user_content_manager_add_script(contentManager, script);
- webkit_user_script_unref(script);
-
- g_signal_connect(
- contentManager, "script-message-received::infiniFrameInterop",
- G_CALLBACK(HandleWebMessage),
- reinterpret_cast(m_impl->_webMessageReceivedCallback)
- );
- webkit_user_content_manager_register_script_message_handler(contentManager, "infiniFrameInterop");
-
- g_signal_connect(
- G_OBJECT(m_impl->_webview), "load-changed",
- G_CALLBACK(on_webview_load_changed), this
- );
- g_signal_connect(
- G_OBJECT(m_impl->_webview), "load-failed",
- G_CALLBACK(on_webview_load_failed), this
- );
- g_signal_connect(
- G_OBJECT(m_impl->_webview), "web-process-terminated",
- G_CALLBACK(on_webview_process_terminated), this
- );
- g_signal_connect(
- G_OBJECT(m_impl->_webview), "size-allocate",
- G_CALLBACK(on_webview_size_allocate), this
- );
-
- if (!m_impl->_startUrl.empty())
- NavigateToUrl(const_cast(m_impl->_startUrl.c_str()));
- else if (!m_impl->_startString.empty())
- NavigateToString(const_cast(m_impl->_startString.c_str()));
- else {
- GtkWidget* dialog = gtk_message_dialog_new(
- nullptr, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
- "Neither StartUrl nor StartString was specified"
- );
- gtk_dialog_run(GTK_DIALOG(dialog));
- gtk_widget_destroy(dialog);
- sigaction(SIGCHLD, &old_action, nullptr);
- return;
- }
- sigaction(SIGCHLD, &old_action, nullptr);
- }
-
- gtk_widget_show_all(m_impl->_window);
-}
-
-void InfiniFrameWindow::AttachWebView() {
- // On Linux, WebView is attached in Show()
-}
-
-void InfiniFrameWindow::OnConfigureEvent(int x, int y, int width, int height) {
- if (m_impl->_lastLeft != x || m_impl->_lastTop != y) {
- InvokeMove(x, y);
- m_impl->_lastLeft = x;
- m_impl->_lastTop = y;
- }
-
- if (m_impl->_lastHeight != height || m_impl->_lastWidth != width) {
- InvokeResize(width, height);
- m_impl->_lastWidth = width;
- m_impl->_lastHeight = height;
- }
-}
-
-void InfiniFrameWindow::OnWindowStateEvent(GdkWindowState newState) {
- if (newState & GDK_WINDOW_STATE_MAXIMIZED) {
- InvokeMaximized();
- }
- else if ((newState & GDK_WINDOW_STATE_ICONIFIED) || !gtk_widget_get_mapped(m_impl->_window)) {
- InvokeMinimized();
- }
- else if (!(newState & GDK_WINDOW_STATE_MAXIMIZED) && !(newState & GDK_WINDOW_STATE_ICONIFIED)) {
- InvokeRestored();
- }
-}
-
-// ---------------------------------------------------------------------------------------------------------------------
-// GTK Signal Handlers
-// ---------------------------------------------------------------------------------------------------------------------
-
-gboolean on_configure_event(GtkWidget* widget, GdkEvent* event, const gpointer self) {
- if (event->type == GDK_CONFIGURE) {
- auto* instance = reinterpret_cast(self);
- instance->OnConfigureEvent(
- event->configure.x, event->configure.y,
- event->configure.width, event->configure.height
- );
- }
- return FALSE;
-}
-
-gboolean on_window_state_event(GtkWidget* widget, GdkEventWindowState* event, const gpointer self) {
- auto* instance = reinterpret_cast(self);
- instance->OnWindowStateEvent(event->new_window_state);
- return TRUE;
-}
-
-gboolean on_widget_deleted(GtkWidget* widget, GdkEvent* event, const gpointer self) {
- auto* instance = reinterpret_cast(self);
- return instance->InvokeClose();
-}
-
-void on_widget_destroyed(GtkWidget* widget, const gpointer self) {
- auto* instance = reinterpret_cast(self);
- instance->InvokeClosed();
-}
-
-gboolean on_focus_in_event(GtkWidget* widget, GdkEvent* event, const gpointer self) {
- auto* instance = reinterpret_cast(self);
- instance->InvokeFocusIn();
- return FALSE;
-}
-
-gboolean on_focus_out_event(GtkWidget* widget, GdkEvent* event, const gpointer self) {
- auto* instance = reinterpret_cast(self);
- instance->InvokeFocusOut();
- return FALSE;
-}
-
-gboolean on_webview_context_menu(
- WebKitWebView* web_view,
- GtkWidget* default_menu,
- WebKitHitTestResult* hit_test_result,
- gboolean triggered_with_keyboard,
- const gpointer self
- ) {
- auto* instance = reinterpret_cast(self);
- bool contextMenuEnabled = false;
- instance->GetContextMenuEnabled(&contextMenuEnabled);
- return !contextMenuEnabled;
-}
-
-gboolean on_permission_request(WebKitWebView* web_view, WebKitPermissionRequest* request, gpointer user_data) {
- auto* instance = reinterpret_cast(user_data);
- bool grant = false;
- instance->GetGrantBrowserPermissions(&grant);
- if (grant)
- webkit_permission_request_allow(request);
- else
- webkit_permission_request_deny(request);
- return TRUE;
-}
-
-void on_webview_load_changed(WebKitWebView* web_view, WebKitLoadEvent load_event, gpointer user_data) {
- if (!linux_webview_diagnostics_enabled())
- return;
-
- const char* uri = webkit_web_view_get_uri(web_view);
- g_message(
- "[InfiniFrame/Linux] WebKit load-changed: event=%s uri=%s",
- webkit_load_event_to_string(load_event),
- uri ? uri : ""
- );
-}
-
-gboolean on_webview_load_failed(
- WebKitWebView* web_view,
- WebKitLoadEvent load_event,
- gchar* failing_uri,
- GError* error,
- gpointer user_data
- ) {
- if (!linux_webview_diagnostics_enabled())
- return FALSE;
-
- g_warning(
- "[InfiniFrame/Linux] WebKit load-failed: event=%s uri=%s error=%s",
- webkit_load_event_to_string(load_event),
- failing_uri ? failing_uri : "",
- error ? error->message : ""
- );
- return FALSE;
-}
-
-void on_webview_process_terminated(
- WebKitWebView* web_view,
- WebKitWebProcessTerminationReason reason,
- gpointer user_data
- ) {
- g_warning(
- "[InfiniFrame/Linux] WebKit web process terminated: reason=%s",
- webkit_termination_reason_to_string(reason)
- );
-}
-
-void on_webview_size_allocate(GtkWidget* widget, GtkAllocation* allocation, gpointer user_data) {
- if (!linux_webview_diagnostics_enabled())
- return;
-
- g_message(
- "[InfiniFrame/Linux] WebView size-allocate: %dx%d",
- allocation ? allocation->width : -1,
- allocation ? allocation->height : -1
- );
-}
-
-#endif
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Mac/Core/UiDispatcher.Cocoa.mm b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/Core/UiDispatcher.Cocoa.mm
new file mode 100644
index 000000000..6ef3bea97
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/Core/UiDispatcher.Cocoa.mm
@@ -0,0 +1,13 @@
+#ifdef __APPLE__
+
+#include "../Window.Cocoa.Internal.h"
+
+void InfiniFrameWindow::Invoke(ACTION callback)
+{
+ if ([NSThread isMainThread])
+ callback();
+ else
+ dispatch_sync(dispatch_get_main_queue(), ^(void){ callback(); });
+}
+
+#endif
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Mac/Core/WindowCore.Cocoa.mm b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/Core/WindowCore.Cocoa.mm
new file mode 100644
index 000000000..b5a5e569f
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/Core/WindowCore.Cocoa.mm
@@ -0,0 +1,300 @@
+#ifdef __APPLE__
+
+#include
+
+#include "../AppDelegate.h"
+#include "../../../Public/InfiniFrameDialog.h"
+#include "../../../Public/InfiniFrameWindow.h"
+#include "../NSWindowBorderless.h"
+#include "../Window.Cocoa.Internal.h"
+#include "../WindowDelegate.h"
+
+void InfiniFrameWindow::Register()
+{
+ [NSAutoreleasePool new];
+
+ AppDelegate *appDelegate = [[[AppDelegate alloc] init] autorelease];
+
+ NSApplication *application = [NSApplication sharedApplication];
+ [application setDelegate: appDelegate];
+ [application setActivationPolicy: NSApplicationActivationPolicyRegular];
+
+ NSString *appName = [[NSProcessInfo processInfo] processName];
+
+ NSMenu *mainMenu = [[NSMenu new] autorelease];
+ NSMenuItem *mainMenuItem = [[NSMenuItem new] autorelease];
+ [mainMenu addItem: mainMenuItem];
+
+ NSMenu *mainSubMenu = [[NSMenu new] autorelease];
+ [mainMenuItem setSubmenu: mainSubMenu];
+
+ NSMenuItem *selectMenuItem = [[
+ [NSMenuItem alloc]
+ initWithTitle: @"Select All"
+ action: @selector(selectAll:)
+ keyEquivalent: @"a"
+ ] autorelease];
+ [mainSubMenu addItem: selectMenuItem];
+
+ NSMenuItem *cutMenuItem = [[
+ [NSMenuItem alloc]
+ initWithTitle: @"Cut"
+ action: @selector(cut:)
+ keyEquivalent: @"x"
+ ] autorelease];
+ [mainSubMenu addItem: cutMenuItem];
+
+ NSMenuItem *copyMenuItem = [[
+ [NSMenuItem alloc]
+ initWithTitle: @"Copy"
+ action: @selector(copy:)
+ keyEquivalent: @"c"
+ ] autorelease];
+ [mainSubMenu addItem: copyMenuItem];
+
+ NSMenuItem *pasteMenuItem = [[
+ [NSMenuItem alloc]
+ initWithTitle: @"Paste"
+ action: @selector(paste:)
+ keyEquivalent: @"v"
+ ] autorelease];
+ [mainSubMenu addItem: pasteMenuItem];
+
+ NSMenuItem *quitMenuItem = [[
+ [NSMenuItem alloc]
+ initWithTitle: [@"Quit " stringByAppendingString: appName]
+ action: @selector(terminate:)
+ keyEquivalent: @"q"
+ ] autorelease];
+ [mainSubMenu addItem: quitMenuItem];
+
+ [NSApp setMainMenu: mainMenu];
+}
+
+InfiniFrameWindow::InfiniFrameWindow(InfiniFrameInitParams* initParams) : m_impl(std::make_unique())
+{
+ m_impl->_windowTitle = initParams->Title ? initParams->Title : "";
+
+ if (initParams->StartUrl != nullptr)
+ m_impl->_startUrl = initParams->StartUrl;
+
+ if (initParams->StartString != nullptr)
+ m_impl->_startString = initParams->StartString;
+
+ if (initParams->TemporaryFilesPath != nullptr)
+ m_impl->_temporaryFilesPath = initParams->TemporaryFilesPath;
+
+ m_impl->_ignoreCertificateErrorsEnabled = initParams->IgnoreCertificateErrorsEnabled;
+ m_impl->_contextMenuEnabled = initParams->ContextMenuEnabled;
+ m_impl->_zoomEnabled = initParams->ZoomEnabled;
+ m_impl->_grantBrowserPermissions = initParams->GrantBrowserPermissions;
+
+ m_impl->_webMessageReceivedCallback = initParams->WebMessageReceivedHandler;
+ m_impl->_resizedCallback = initParams->ResizedHandler;
+ m_impl->_movedCallback = initParams->MovedHandler;
+ m_impl->_closingCallback = initParams->ClosingHandler;
+ m_impl->_closedCallback = initParams->ClosedHandler;
+ m_impl->_focusInCallback = initParams->FocusInHandler;
+ m_impl->_focusOutCallback = initParams->FocusOutHandler;
+ m_impl->_maximizedCallback = initParams->MaximizedHandler;
+ m_impl->_minimizedCallback = initParams->MinimizedHandler;
+ m_impl->_restoredCallback = initParams->RestoredHandler;
+ m_impl->_customSchemeCallback = initParams->CustomSchemeHandler;
+
+ for (int i = 0; i < 16; ++i)
+ {
+ if (initParams->CustomSchemeNames[i] != nullptr)
+ m_impl->_customSchemeNames.emplace_back(initParams->CustomSchemeNames[i]);
+ }
+
+ m_impl->_parent = initParams->ParentInstance;
+
+ if (initParams->UseOsDefaultSize)
+ {
+ initParams->Width = 800;
+ initParams->Height = 600;
+ }
+ else
+ {
+ if (initParams->Width < 0) initParams->Width = 800;
+ if (initParams->Height < 0) initParams->Height = 600;
+ }
+
+ if (initParams->UseOsDefaultLocation)
+ {
+ initParams->Left = 0;
+ initParams->Top = 0;
+ }
+
+ NSRect frame = NSMakeRect(0, 0, 0, 0);
+
+ m_impl->_chromeless = initParams->Chromeless;
+ if (initParams->Chromeless)
+ {
+ m_impl->_window = [[NSWindowBorderless alloc]
+ initWithContentRect: frame
+ styleMask: NSWindowStyleMaskBorderless
+ | NSWindowStyleMaskClosable
+ | NSWindowStyleMaskResizable
+ | NSWindowStyleMaskMiniaturizable
+ backing: NSBackingStoreBuffered
+ defer: true];
+ }
+ else
+ {
+ m_impl->_window = [[NSWindow alloc]
+ initWithContentRect: frame
+ styleMask: NSWindowStyleMaskTitled
+ | NSWindowStyleMaskClosable
+ | NSWindowStyleMaskResizable
+ | NSWindowStyleMaskMiniaturizable
+ backing: NSBackingStoreBuffered
+ defer: true];
+ }
+
+ m_impl->_transparentEnabled = initParams->Transparent;
+
+ if (m_impl->_parent != nullptr && m_impl->_parent->m_impl != nullptr)
+ {
+ auto* parentImpl = static_cast(m_impl->_parent->m_impl.get());
+ m_impl->_nativeParentWindow = parentImpl->_window;
+ if (m_impl->_nativeParentWindow != nil && m_impl->_nativeParentWindow != m_impl->_window)
+ {
+ [m_impl->_nativeParentWindow addChildWindow:m_impl->_window ordered:NSWindowAbove];
+
+ NSWindow* childWindow = m_impl->_window;
+ m_impl->_parentWillCloseObserver = [[NSNotificationCenter defaultCenter]
+ addObserverForName:NSWindowWillCloseNotification
+ object:m_impl->_nativeParentWindow
+ queue:nil
+ usingBlock:^(NSNotification*) {
+ [childWindow close];
+ }];
+ }
+ }
+
+ [m_impl->_window setCollectionBehavior:
+ [m_impl->_window collectionBehavior] | NSWindowCollectionBehaviorFullScreenPrimary];
+
+ WindowDelegate *windowDelegate = [WindowDelegate new];
+ windowDelegate->infiniFrame = this;
+ m_impl->_window.delegate = windowDelegate;
+
+ SetTitle(const_cast(m_impl->_windowTitle.c_str()));
+
+ if (initParams->WindowIconFile != nullptr && initParams->WindowIconFile[0] != '\0')
+ SetIconFile(initParams->WindowIconFile);
+
+ SetTopmost(initParams->Topmost);
+ SetPosition(initParams->Left, initParams->Top);
+
+ SetMinSize(initParams->MinWidth, initParams->MinHeight);
+ SetMaxSize(initParams->MaxWidth, initParams->MaxHeight);
+ SetSize(initParams->Width, initParams->Height);
+
+ SetMinimized(initParams->Minimized);
+ SetMaximized(initParams->Maximized);
+ SetResizable(initParams->Resizable);
+
+ if (initParams->CenterOnInitialize)
+ Center();
+
+ m_impl->_webviewConfiguration = [[WKWebViewConfiguration alloc] init];
+
+ for (const auto & scheme : m_impl->_customSchemeNames)
+ {
+ m_impl->AddCustomScheme(scheme.c_str(), m_impl->_customSchemeCallback);
+ }
+
+ AttachWebView();
+
+ m_impl->SetUserAgent(initParams->UserAgent);
+
+ m_impl->SetPreference(@"developerExtrasEnabled", initParams->DevToolsEnabled ? @YES : @NO);
+ m_impl->SetPreference(@"allowFileAccessFromFileURLs", initParams->FileSystemAccessEnabled ? @YES : @NO);
+ m_impl->SetPreference(@"webSecurityEnabled", initParams->WebSecurityEnabled ? @YES : @NO);
+ m_impl->SetPreference(@"javaScriptCanAccessClipboard", initParams->JavascriptClipboardAccessEnabled ? @YES : @NO);
+ m_impl->SetPreference(@"mediaStreamEnabled", initParams->MediaStreamEnabled ? @YES : @NO);
+
+ m_impl->SetPreference(@"mediaDevicesEnabled", @YES);
+ m_impl->SetPreference(@"mediaCaptureRequiresSecureConnection", @NO);
+
+ if ([NSProcessInfo.processInfo isOperatingSystemAtLeastVersion: NSOperatingSystemVersion({13, 3, 0})])
+ {
+ m_impl->SetPreference(@"notificationEventEnabled", @YES);
+ }
+
+ m_impl->SetPreference(@"notificationsEnabled", @YES);
+ m_impl->SetPreference(@"screenCaptureEnabled", @YES);
+
+ if (initParams->BrowserControlInitParameters != nullptr)
+ {
+ simdjson::ondemand::parser parser;
+ auto doc = parser.iterate(initParams->BrowserControlInitParameters);
+
+ for (auto field : doc.get_object()) {
+ std::string_view key = field.unescaped_key().value();
+ auto value = field.value();
+
+ NSString *preferenceKey = [[NSString alloc] initWithBytes:key.data() length:key.length() encoding:NSUTF8StringEncoding];
+
+ switch (value.type()) {
+ case simdjson::ondemand::json_type::number: {
+ int64_t intVal;
+ if (value.get(intVal) == simdjson::SUCCESS) {
+ m_impl->SetPreference(preferenceKey, [NSNumber numberWithInt: (int)intVal]);
+ } else {
+ double doubleVal;
+ if (value.get(doubleVal) == simdjson::SUCCESS) {
+ m_impl->SetPreference(preferenceKey, [NSNumber numberWithDouble: doubleVal]);
+ }
+ }
+ break;
+ }
+ case simdjson::ondemand::json_type::boolean: {
+ bool boolVal;
+ if (value.get(boolVal) == simdjson::SUCCESS) {
+ m_impl->SetPreference(preferenceKey, [NSNumber numberWithBool: boolVal]);
+ }
+ break;
+ }
+ case simdjson::ondemand::json_type::string: {
+ std::string_view strVal;
+ if (value.get(strVal) == simdjson::SUCCESS) {
+ NSString *preferenceValue = [[NSString alloc] initWithBytes:strVal.data()
+ length:strVal.length()
+ encoding:NSUTF8StringEncoding];
+ m_impl->SetPreference(preferenceKey, preferenceValue);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+
+ m_impl->_dialog = std::make_unique();
+
+ Show(false);
+ SetFullScreen(initParams->FullScreen);
+}
+
+InfiniFrameWindow::~InfiniFrameWindow()
+{
+ if (m_impl->_parentWillCloseObserver != nil) {
+ [[NSNotificationCenter defaultCenter] removeObserver:m_impl->_parentWillCloseObserver];
+ m_impl->_parentWillCloseObserver = nil;
+ }
+
+ if (m_impl->_nativeParentWindow != nil && m_impl->_window != nil) {
+ [m_impl->_nativeParentWindow removeChildWindow:m_impl->_window];
+ m_impl->_nativeParentWindow = nil;
+ }
+
+ [m_impl->_webviewConfiguration release];
+ [m_impl->_webview release];
+ [m_impl->_window performClose: m_impl->_window];
+}
+
+#endif
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Mac/Core/WindowEvents.Cocoa.mm b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/Core/WindowEvents.Cocoa.mm
new file mode 100644
index 000000000..ee92e136b
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/Core/WindowEvents.Cocoa.mm
@@ -0,0 +1,143 @@
+#ifdef __APPLE__
+
+#include "../Window.Cocoa.Internal.h"
+
+InfiniFrameDialog* InfiniFrameWindow::GetDialog() const
+{
+ return m_impl->_dialog.get();
+}
+
+void InfiniFrameWindow::AddCustomSchemeName(const AutoStringConst scheme)
+{
+ if (scheme)
+ m_impl->_customSchemeNames.emplace_back(scheme);
+}
+
+void InfiniFrameWindow::GetAllMonitors(GetAllMonitorsCallback callback) const
+{
+ if (callback)
+ {
+ for (NSScreen* screen in [NSScreen screens])
+ {
+ Monitor props = {};
+
+ NSRect frame = [screen frame];
+ props.monitor.x = static_cast(roundf(frame.origin.x));
+ props.monitor.y = static_cast(roundf(frame.origin.y));
+ props.monitor.width = static_cast(roundf(frame.size.width));
+ props.monitor.height = static_cast(roundf(frame.size.height));
+
+ NSRect vframe = [screen visibleFrame];
+ props.work.x = static_cast(roundf(vframe.origin.x));
+ props.work.y = static_cast(roundf(vframe.origin.y));
+ props.work.width = static_cast(roundf(vframe.size.width));
+ props.work.height = static_cast(roundf(vframe.size.height));
+
+ props.scale = [screen backingScaleFactor];
+
+ callback(&props);
+ }
+ }
+}
+
+void InfiniFrameWindow::SetClosingCallback(const ClosingCallback callback)
+{
+ m_impl->_closingCallback = callback;
+}
+
+void InfiniFrameWindow::SetClosedCallback(const ClosedCallback callback)
+{
+ m_impl->_closedCallback = callback;
+}
+
+void InfiniFrameWindow::SetFocusInCallback(const FocusInCallback callback)
+{
+ m_impl->_focusInCallback = callback;
+}
+
+void InfiniFrameWindow::SetFocusOutCallback(const FocusOutCallback callback)
+{
+ m_impl->_focusOutCallback = callback;
+}
+
+void InfiniFrameWindow::SetMovedCallback(const MovedCallback callback)
+{
+ m_impl->_movedCallback = callback;
+}
+
+void InfiniFrameWindow::SetResizedCallback(const ResizedCallback callback)
+{
+ m_impl->_resizedCallback = callback;
+}
+
+void InfiniFrameWindow::SetMaximizedCallback(const MaximizedCallback callback)
+{
+ m_impl->_maximizedCallback = callback;
+}
+
+void InfiniFrameWindow::SetRestoredCallback(const RestoredCallback callback)
+{
+ m_impl->_restoredCallback = callback;
+}
+
+void InfiniFrameWindow::SetMinimizedCallback(const MinimizedCallback callback)
+{
+ m_impl->_minimizedCallback = callback;
+}
+
+[[nodiscard]] bool InfiniFrameWindow::InvokeClose() const noexcept
+{
+ if (m_impl->_closingCallback)
+ return m_impl->_closingCallback();
+ return false;
+}
+
+void InfiniFrameWindow::InvokeClosed() const noexcept
+{
+ if (m_impl->_closedCallback)
+ m_impl->_closedCallback();
+}
+
+void InfiniFrameWindow::InvokeFocusIn() const noexcept
+{
+ if (m_impl->_focusInCallback)
+ m_impl->_focusInCallback();
+}
+
+void InfiniFrameWindow::InvokeFocusOut() const noexcept
+{
+ if (m_impl->_focusOutCallback)
+ m_impl->_focusOutCallback();
+}
+
+void InfiniFrameWindow::InvokeMove(int x, int y) const noexcept
+{
+ if (m_impl->_movedCallback)
+ m_impl->_movedCallback(x, y);
+}
+
+void InfiniFrameWindow::InvokeResize(int width, int height) const noexcept
+{
+ if (m_impl->_resizedCallback)
+ m_impl->_resizedCallback(width, height);
+}
+
+void InfiniFrameWindow::InvokeMaximized() const noexcept
+{
+ if (m_impl->_maximizedCallback)
+ m_impl->_maximizedCallback();
+}
+
+void InfiniFrameWindow::InvokeRestored() const noexcept
+{
+ if (m_impl->_restoredCallback)
+ m_impl->_restoredCallback();
+}
+
+void InfiniFrameWindow::InvokeMinimized() const noexcept
+{
+ if (m_impl->_minimizedCallback)
+ m_impl->_minimizedCallback();
+}
+
+#endif
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Mac/Core/WindowLifecycle.Cocoa.mm b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/Core/WindowLifecycle.Cocoa.mm
new file mode 100644
index 000000000..8bb671e17
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/Core/WindowLifecycle.Cocoa.mm
@@ -0,0 +1,79 @@
+#ifdef __APPLE__
+
+#include "../Window.Cocoa.Internal.h"
+
+void InfiniFrameWindow::Center()
+{
+ [m_impl->_window center];
+ [m_impl->_window makeKeyAndOrderFront: m_impl->_window];
+}
+
+void InfiniFrameWindow::ClearBrowserAutoFill()
+{
+ // TODO
+}
+
+void InfiniFrameWindow::Close()
+{
+ if (m_impl->_parentWillCloseObserver != nil) {
+ [[NSNotificationCenter defaultCenter] removeObserver:m_impl->_parentWillCloseObserver];
+ m_impl->_parentWillCloseObserver = nil;
+ }
+
+ if (m_impl->_nativeParentWindow != nil && m_impl->_window != nil) {
+ [m_impl->_nativeParentWindow removeChildWindow:m_impl->_window];
+ m_impl->_nativeParentWindow = nil;
+ }
+
+ if (m_impl->_chromeless)
+ [m_impl->_window close];
+ else
+ [m_impl->_window performClose: m_impl->_window];
+}
+
+void InfiniFrameWindow::ShowNotification(AutoString title, AutoString body)
+{
+ UNMutableNotificationContent *objNotificationContent = [[UNMutableNotificationContent alloc] init];
+ objNotificationContent.title = [NSString stringWithUTF8String: title];
+ objNotificationContent.body = [NSString stringWithUTF8String: body];
+ objNotificationContent.sound = [UNNotificationSound defaultSound];
+ UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval: 0.3 repeats: NO];
+ UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier: @"three"
+ content: objNotificationContent
+ trigger: trigger];
+ UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
+ [center addNotificationRequest: request withCompletionHandler: ^(NSError * _Nullable error) {
+ (void)error;
+ }];
+}
+
+void InfiniFrameWindow::WaitForExit()
+{
+ if (![NSApp isRunning]) {
+ [NSApp run];
+ return;
+ }
+
+ __block bool windowClosed = false;
+ id observer = [[NSNotificationCenter defaultCenter]
+ addObserverForName: NSWindowWillCloseNotification
+ object: m_impl->_window
+ queue: nil
+ usingBlock: ^(NSNotification*) {
+ windowClosed = true;
+ }];
+
+ while (!windowClosed) {
+ [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
+ beforeDate: [NSDate dateWithTimeIntervalSinceNow: 0.05]];
+ }
+
+ [[NSNotificationCenter defaultCenter] removeObserver: observer];
+}
+
+void InfiniFrameWindow::CloseWebView()
+{
+ // Not implemented on macOS
+}
+
+#endif
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Mac/Core/WindowState.Cocoa.mm b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/Core/WindowState.Cocoa.mm
new file mode 100644
index 000000000..a72a33cef
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/Core/WindowState.Cocoa.mm
@@ -0,0 +1,383 @@
+#ifdef __APPLE__
+
+#include "../Window.Cocoa.Internal.h"
+
+#include "Utils/Common.h"
+
+static const int MAX_WINDOW_DIMENSION = 10000;
+
+void InfiniFrameWindow::GetTransparentEnabled(bool* enabled) const
+{
+ *enabled = false;
+}
+
+void InfiniFrameWindow::GetContextMenuEnabled(bool* enabled) const
+{
+ *enabled = m_impl->_contextMenuEnabled;
+}
+
+void InfiniFrameWindow::GetZoomEnabled(bool* enabled) const
+{
+ *enabled = m_impl->_zoomEnabled;
+}
+
+void InfiniFrameWindow::GetDevToolsEnabled(bool* enabled) const
+{
+ *enabled = m_impl->_devToolsEnabled;
+}
+
+void InfiniFrameWindow::GetGrantBrowserPermissions(bool* enabled) const
+{
+ *enabled = m_impl->_grantBrowserPermissions;
+}
+
+AutoString InfiniFrameWindow::GetUserAgent() const
+{
+ return AllocateStringCopy(m_impl->_userAgent);
+}
+
+void InfiniFrameWindow::GetMediaAutoplayEnabled(bool* enabled) const
+{
+ *enabled = true;
+}
+
+void InfiniFrameWindow::GetFileSystemAccessEnabled(bool* enabled) const
+{
+ *enabled = m_impl->_fileSystemAccessEnabled;
+}
+
+void InfiniFrameWindow::GetSmoothScrollingEnabled(bool* enabled) const
+{
+ *enabled = false;
+}
+
+void InfiniFrameWindow::GetWebSecurityEnabled(bool* enabled) const
+{
+ *enabled = m_impl->_webSecurityEnabled;
+}
+
+void InfiniFrameWindow::GetJavascriptClipboardAccessEnabled(bool* enabled) const
+{
+ *enabled = m_impl->_javascriptClipboardAccessEnabled;
+}
+
+void InfiniFrameWindow::GetMediaStreamEnabled(bool* enabled) const
+{
+ *enabled = m_impl->_mediaStreamEnabled;
+}
+
+void InfiniFrameWindow::GetFullScreen(bool* fullScreen) const
+{
+ *fullScreen = ([m_impl->_window styleMask] & NSWindowStyleMaskFullScreen) != 0;
+}
+
+void InfiniFrameWindow::GetMaximized(bool* isMaximized) const
+{
+ bool isFullScreen = false;
+ GetFullScreen(&isFullScreen);
+ if (isFullScreen)
+ {
+ *isMaximized = false;
+ return;
+ }
+ *isMaximized = [m_impl->_window isZoomed];
+}
+
+void InfiniFrameWindow::GetMinimized(bool* isMinimized) const
+{
+ *isMinimized = [m_impl->_window isMiniaturized];
+}
+
+void InfiniFrameWindow::GetPosition(int* x, int* y) const
+{
+ NSRect frame = [m_impl->_window frame];
+ NSScreen* screen = [m_impl->_window screen];
+ if (!screen) screen = [NSScreen mainScreen];
+ NSRect screenFrame = [screen frame];
+ int height = static_cast(roundf(frame.size.height));
+ *x = static_cast(roundf(frame.origin.x));
+ *y = static_cast(roundf(screenFrame.origin.y + screenFrame.size.height - (frame.origin.y + height)));
+}
+
+void InfiniFrameWindow::GetResizable(bool* resizable) const
+{
+ *resizable = (([m_impl->_window styleMask] & NSWindowStyleMaskResizable) == NSWindowStyleMaskResizable);
+}
+
+void InfiniFrameWindow::GetIgnoreCertificateErrorsEnabled(bool* enabled) const
+{
+ *enabled = m_impl->_ignoreCertificateErrorsEnabled;
+}
+
+void InfiniFrameWindow::GetFocused(bool* isFocused) const
+{
+ if (!isFocused)
+ return;
+
+ if (!m_impl->_window)
+ {
+ *isFocused = false;
+ return;
+ }
+
+ *isFocused = [NSApp isActive] && [m_impl->_window isKeyWindow];
+}
+
+unsigned int InfiniFrameWindow::GetScreenDpi() const
+{
+ return 72;
+}
+
+void InfiniFrameWindow::GetSize(int* width, int* height) const
+{
+ NSSize size = [m_impl->_window frame].size;
+ if (width) *width = static_cast(roundf(size.width));
+ if (height) *height = static_cast(roundf(size.height));
+}
+
+void InfiniFrameWindow::GetMaxSize(int* width, int* height) const
+{
+ NSSize maxSize = [m_impl->_window maxSize];
+ if (width) *width = static_cast(roundf(maxSize.width));
+ if (height) *height = static_cast(roundf(maxSize.height));
+}
+
+void InfiniFrameWindow::GetMinSize(int* width, int* height) const
+{
+ NSSize minSize = [m_impl->_window minSize];
+ if (width) *width = static_cast(roundf(minSize.width));
+ if (height) *height = static_cast(roundf(minSize.height));
+}
+
+AutoString InfiniFrameWindow::GetTitle() const
+{
+ return AllocateStringCopy(m_impl->_windowTitle);
+}
+
+void InfiniFrameWindow::GetTopmost(bool* topmost) const
+{
+ *topmost = ([m_impl->_window level] & NSFloatingWindowLevel) == NSFloatingWindowLevel;
+}
+
+void InfiniFrameWindow::GetZoom(int* zoom) const
+{
+ CGFloat rawValue = [m_impl->_webview magnification];
+ rawValue = (rawValue * 100.0) + 0.5;
+ *zoom = static_cast(rawValue);
+}
+
+AutoString InfiniFrameWindow::GetIconFileName() const
+{
+ return AllocateStringCopy(m_impl->_iconFileName);
+}
+
+void InfiniFrameWindow::NavigateToString(AutoString content)
+{
+ [m_impl->_webview loadHTMLString: [NSString stringWithUTF8String: content] baseURL: nil];
+}
+
+void InfiniFrameWindow::NavigateToUrl(AutoString url)
+{
+ NSString* nsurlstring = [NSString stringWithUTF8String: url];
+ NSURL *nsurl = [NSURL URLWithString: nsurlstring];
+ NSURLRequest *nsrequest = [NSURLRequest requestWithURL: nsurl];
+ [m_impl->_webview loadRequest: nsrequest];
+}
+
+void InfiniFrameWindow::Restore()
+{
+ bool minimized;
+ bool maximized;
+ GetMinimized(&minimized);
+ GetMaximized(&maximized);
+ if (minimized) SetMinimized(false);
+ if (maximized) SetMaximized(false);
+}
+
+void InfiniFrameWindow::SendWebMessage(AutoString message)
+{
+ NSString* nsmessage = [NSString stringWithUTF8String: message];
+
+ NSData* data = [
+ NSJSONSerialization
+ dataWithJSONObject: @[nsmessage]
+ options: 0
+ error: nil];
+
+ NSString *nsmessageJson = [[
+ [NSString alloc]
+ initWithData: data
+ encoding: NSUTF8StringEncoding] autorelease];
+
+ nsmessageJson = [
+ [nsmessageJson substringToIndex: ([nsmessageJson length] - 1)]
+ substringFromIndex: 1
+ ];
+
+ NSString *javaScriptToEval = [NSString stringWithFormat: @"__dispatchMessageCallback(%@)", nsmessageJson];
+ [m_impl->_webview evaluateJavaScript: javaScriptToEval completionHandler: nil];
+}
+
+void InfiniFrameWindow::SetDevToolsEnabled(bool enabled)
+{
+ m_impl->_devToolsEnabled = enabled;
+ m_impl->SetPreference(@"developerExtrasEnabled", enabled ? @YES : @NO);
+}
+
+void InfiniFrameWindow::SetTransparentEnabled(bool enabled)
+{
+ (void)enabled;
+}
+
+void InfiniFrameWindow::SetContextMenuEnabled(bool enabled)
+{
+ (void)enabled;
+}
+
+void InfiniFrameWindow::SetZoomEnabled(bool enabled)
+{
+ (void)enabled;
+}
+
+void InfiniFrameWindow::SetIconFile(AutoString filename)
+{
+ NSString* path = [NSString stringWithUTF8String: filename];
+ NSImage* icon = [[NSImage alloc] initWithContentsOfFile: path];
+ if (icon != nil)
+ [[m_impl->_window standardWindowButton: NSWindowDocumentIconButton] setImage: icon];
+
+ m_impl->_iconFileName = filename ? filename : "";
+}
+
+void InfiniFrameWindow::SetFullScreen(bool fullScreen)
+{
+ bool isFullScreen = ([m_impl->_window styleMask] & NSWindowStyleMaskFullScreen) != 0;
+ if (fullScreen != isFullScreen)
+ [m_impl->_window toggleFullScreen: nil];
+}
+
+void InfiniFrameWindow::SetMinimized(bool minimized)
+{
+ if (m_impl->_window.isMiniaturized == minimized) return;
+
+ if (minimized)
+ [m_impl->_window miniaturize: nullptr];
+ else
+ [m_impl->_window deminiaturize: nullptr];
+}
+
+void InfiniFrameWindow::SetMaximized(bool maximized)
+{
+ if (maximized)
+ {
+ NSRect window = [m_impl->_window frame];
+ m_impl->_preMaximizedWidth = window.size.width;
+ m_impl->_preMaximizedHeight = window.size.height;
+ m_impl->_preMaximizedXPosition = window.origin.x;
+ m_impl->_preMaximizedYPosition = window.origin.y;
+
+ NSRect screen = [[m_impl->_window screen] visibleFrame];
+ [m_impl->_window setFrame: NSMakeRect(screen.origin.x, screen.origin.y,
+ screen.size.width, screen.size.height)
+ display: YES];
+ }
+ else if (!maximized && m_impl->_preMaximizedWidth > 0 && m_impl->_preMaximizedHeight > 0)
+ {
+ [m_impl->_window setFrame: NSMakeRect(m_impl->_preMaximizedXPosition,
+ m_impl->_preMaximizedYPosition,
+ m_impl->_preMaximizedWidth,
+ m_impl->_preMaximizedHeight)
+ display: YES];
+ }
+}
+
+void InfiniFrameWindow::SetPosition(int x, int y)
+{
+ NSScreen* screen = [m_impl->_window screen];
+ if (!screen) screen = [NSScreen mainScreen];
+ NSRect screenFrame = [screen frame];
+
+ NSRect frame = [m_impl->_window frame];
+ int height = static_cast(roundf(frame.size.height));
+
+ auto left = static_cast(x);
+ auto top = static_cast(screenFrame.origin.y + screenFrame.size.height - (y + height));
+
+ [m_impl->_window setFrameOrigin: CGPointMake(left, top)];
+}
+
+void InfiniFrameWindow::SetResizable(bool resizable)
+{
+ if (resizable)
+ m_impl->_window.styleMask |= NSWindowStyleMaskResizable;
+ else
+ m_impl->_window.styleMask &= ~NSWindowStyleMaskResizable;
+}
+
+void InfiniFrameWindow::SetSize(int width, int height)
+{
+ width = width > MAX_WINDOW_DIMENSION ? MAX_WINDOW_DIMENSION : width;
+ height = height > MAX_WINDOW_DIMENSION ? MAX_WINDOW_DIMENSION : height;
+
+ if (width > m_impl->_window.maxSize.width) width = m_impl->_window.maxSize.width;
+ if (height > m_impl->_window.maxSize.height) height = m_impl->_window.maxSize.height;
+ if (width < m_impl->_window.minSize.width) width = m_impl->_window.minSize.width;
+ if (height < m_impl->_window.minSize.height) height = m_impl->_window.minSize.height;
+
+ NSRect frame = [m_impl->_window frame];
+ CGFloat oldHeight = frame.size.height;
+ frame.size = CGSizeMake(static_cast(width), static_cast(height));
+ frame.origin.y -= static_cast(height) - oldHeight;
+
+ [m_impl->_window setFrame: frame display: true];
+}
+
+void InfiniFrameWindow::SetMinSize(int width, int height)
+{
+ width = width > MAX_WINDOW_DIMENSION ? MAX_WINDOW_DIMENSION : width;
+ height = height > MAX_WINDOW_DIMENSION ? MAX_WINDOW_DIMENSION : height;
+
+ [m_impl->_window setMinSize: NSMakeSize(width, height)];
+}
+
+void InfiniFrameWindow::SetMaxSize(int width, int height)
+{
+ width = width > MAX_WINDOW_DIMENSION ? MAX_WINDOW_DIMENSION : width;
+ height = height > MAX_WINDOW_DIMENSION ? MAX_WINDOW_DIMENSION : height;
+
+ [m_impl->_window setMaxSize: NSMakeSize(width, height)];
+}
+
+void InfiniFrameWindow::SetTitle(AutoString title)
+{
+ m_impl->_windowTitle = title ? title : "";
+ [m_impl->_window setTitle: [NSString stringWithUTF8String: title]];
+}
+
+void InfiniFrameWindow::SetTopmost(bool topmost)
+{
+ if (topmost) [m_impl->_window setLevel: NSFloatingWindowLevel];
+ else [m_impl->_window setLevel: NSNormalWindowLevel];
+}
+
+void InfiniFrameWindow::SetZoom(int zoom)
+{
+ CGFloat newZoom = zoom / 100.0;
+ [m_impl->_webview setMagnification: newZoom];
+}
+
+void InfiniFrameWindow::SetFocused()
+{
+ if (!m_impl->_window) return;
+
+ [NSApp activateIgnoringOtherApps: YES];
+ [m_impl->_window makeKeyAndOrderFront: m_impl->_window];
+
+ if (![m_impl->_window isKeyWindow])
+ {
+ [m_impl->_window orderFrontRegardless];
+ [m_impl->_window makeKeyWindow];
+ }
+}
+
+#endif
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Mac/Dialog.mm b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/Dialog.mm
index 80144d07b..09ac5845b 100644
--- a/src/InfiniFrame.NativeBridge/Native/Platform/Mac/Dialog.mm
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/Dialog.mm
@@ -4,7 +4,7 @@
* @brief macOS implementation of InfiniFrameDialog using NSOpenPanel, NSSavePanel, and NSAlert
*/
-#import "Core/InfiniFrameDialog.h"
+#import "Public/InfiniFrameDialog.h"
#if defined(VSTGUI_USE_OBJC_UTTYPE)
#import
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Mac/NSWindowBorderless.h b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/NSWindowBorderless.h
index fae41e0bb..4aebdb279 100644
--- a/src/InfiniFrame.NativeBridge/Native/Platform/Mac/NSWindowBorderless.h
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/NSWindowBorderless.h
@@ -7,7 +7,7 @@
* Used when InfiniFrameInitParams::Transparent is set, allowing the WebView to render
* over a fully transparent window background without the standard title bar and borders
*/
-#include "Core/InfiniFrame.h"
+#include "Public/InfiniFrame.h"
/**
* @brief Borderless, transparent NSWindow subclass.
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Mac/NavigationDelegate.h b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/NavigationDelegate.h
index 2d638074e..d8ee80f75 100644
--- a/src/InfiniFrame.NativeBridge/Native/Platform/Mac/NavigationDelegate.h
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/NavigationDelegate.h
@@ -4,7 +4,7 @@
* @file NavigationDelegate.h
* @brief WKNavigationDelegate that handles TLS certificate validation for the embedded WebView
*/
-#include "Core/InfiniFrame.h"
+#include "Public/InfiniFrame.h"
/**
* @brief Navigation delegate conforming to WKNavigationDelegate.
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Mac/UiDelegate.h b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/UiDelegate.h
index 3941068c1..1c5768891 100644
--- a/src/InfiniFrame.NativeBridge/Native/Platform/Mac/UiDelegate.h
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/UiDelegate.h
@@ -4,7 +4,7 @@
* @file UiDelegate.h
* @brief WKUIDelegate and WKScriptMessageHandler that routes JavaScript messages to the .NET layer
*/
-#include "Core/InfiniFrame.h"
+#include "Public/InfiniFrame.h"
/**
* @brief UI delegate conforming to WKUIDelegate and WKScriptMessageHandler.
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Mac/UiDelegate.mm b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/UiDelegate.mm
index 525cd055d..0e0ab8023 100644
--- a/src/InfiniFrame.NativeBridge/Native/Platform/Mac/UiDelegate.mm
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/UiDelegate.mm
@@ -35,6 +35,7 @@ - (void)webView:(WKWebView *)webView
[alert addButtonWithTitle:@"OK"];
[alert beginSheetModalForWindow:window completionHandler:^void (NSModalResponse response) {
+ (void)response;
completionHandler();
[alert release];
}];
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Mac/UrlSchemeHandler.h b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/UrlSchemeHandler.h
index 9d5f201f0..0639b7600 100644
--- a/src/InfiniFrame.NativeBridge/Native/Platform/Mac/UrlSchemeHandler.h
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/UrlSchemeHandler.h
@@ -4,7 +4,7 @@
* @file UrlSchemeHandler.h
* @brief WKURLSchemeHandler that intercepts custom-scheme requests and serves responses from the .NET layer
*/
-#include "Core/InfiniFrame.h"
+#include "Public/InfiniFrame.h"
/**
* @brief URL scheme handler conforming to WKURLSchemeHandler.
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Mac/WebKit/WebKitBridge.Cocoa.mm b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/WebKit/WebKitBridge.Cocoa.mm
new file mode 100644
index 000000000..1a2b8f684
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/WebKit/WebKitBridge.Cocoa.mm
@@ -0,0 +1,57 @@
+#ifdef __APPLE__
+
+#include
+
+#include "../Window.Cocoa.Internal.h"
+
+std::vector InfiniFrameWindow::Impl::GetMonitors() const
+{
+ std::vector monitors;
+
+ for (NSScreen *screen : [NSScreen screens])
+ {
+ NSRect monitorFrame = [screen frame];
+ Monitor::MonitorRect monitorArea;
+ monitorArea.x = static_cast(roundf(monitorFrame.origin.x));
+ monitorArea.y = static_cast(roundf(monitorFrame.origin.y));
+ monitorArea.width = static_cast(roundf(monitorFrame.size.width));
+ monitorArea.height = static_cast(roundf(monitorFrame.size.height));
+
+ NSRect workFrame = [screen visibleFrame];
+ Monitor::MonitorRect workArea;
+ workArea.x = static_cast(roundf(workFrame.origin.x));
+ workArea.y = static_cast(roundf(workFrame.origin.y));
+ workArea.width = static_cast(roundf(workFrame.size.width));
+ workArea.height = static_cast(roundf(workFrame.size.height));
+
+ CGFloat scaleFactor = [screen backingScaleFactor];
+ monitors.push_back({monitorArea, workArea, static_cast(scaleFactor)});
+ }
+
+ return monitors;
+}
+
+void InfiniFrameWindow::Impl::SetUserAgent(AutoString userAgent)
+{
+ if (userAgent != nullptr)
+ {
+ _userAgent = userAgent;
+ [_webview setCustomUserAgent: [NSString stringWithUTF8String: userAgent]];
+ }
+ else
+ {
+ _userAgent.clear();
+ }
+}
+
+void InfiniFrameWindow::Impl::SetPreference(NSString *key, NSNumber *value)
+{
+ [_webviewConfiguration.preferences setValue: value forKey: key];
+}
+
+void InfiniFrameWindow::Impl::SetPreference(NSString *key, NSString *value)
+{
+ [_webviewConfiguration.preferences setValue: value forKey: key];
+}
+
+#endif
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Mac/WebKit/WebKitCustomSchemes.Cocoa.mm b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/WebKit/WebKitCustomSchemes.Cocoa.mm
new file mode 100644
index 000000000..e7401b90c
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/WebKit/WebKitCustomSchemes.Cocoa.mm
@@ -0,0 +1,22 @@
+#ifdef __APPLE__
+
+#include "../UrlSchemeHandler.h"
+#include "../Window.Cocoa.Internal.h"
+
+void InfiniFrameWindow::Impl::AddCustomScheme(
+ const AutoStringConst scheme,
+ WebResourceRequestedCallback requestHandler
+ )
+{
+ if (requestHandler == nullptr)
+ return;
+
+ UrlSchemeHandler* schemeHandler = [[[UrlSchemeHandler alloc] init] autorelease];
+ schemeHandler->requestHandler = requestHandler;
+
+ [_webviewConfiguration
+ setURLSchemeHandler: schemeHandler
+ forURLScheme: [NSString stringWithUTF8String: scheme]];
+}
+
+#endif
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Mac/WebKit/WebKitHost.Cocoa.mm b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/WebKit/WebKitHost.Cocoa.mm
new file mode 100644
index 000000000..e06e6f23a
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/WebKit/WebKitHost.Cocoa.mm
@@ -0,0 +1,71 @@
+#ifdef __APPLE__
+
+#include "../../../Embedded/Embedded.h"
+#include "../NavigationDelegate.h"
+#include "../UiDelegate.h"
+#include "../Window.Cocoa.Internal.h"
+
+void InfiniFrameWindow::AttachWebView()
+{
+ auto js = Embedded::InfiniFrameJsUtf8();
+
+ WKUserScript *script =
+ [[WKUserScript alloc]
+ initWithSource:[NSString stringWithUTF8String:js.c_str()]
+ injectionTime:WKUserScriptInjectionTimeAtDocumentStart
+ forMainFrameOnly:NO];
+
+ WKUserContentController *userContentController =
+ [[WKUserContentController alloc] init];
+
+ [userContentController addUserScript:script];
+
+ m_impl->_webviewConfiguration.userContentController = userContentController;
+
+ m_impl->_webview = [
+ [WKWebView alloc]
+ initWithFrame: m_impl->_window.contentView.frame
+ configuration: m_impl->_webviewConfiguration];
+
+ [m_impl->_webview setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable];
+ [m_impl->_window.contentView addSubview: m_impl->_webview];
+ [m_impl->_window.contentView setAutoresizesSubviews: true];
+
+ UiDelegate *uiDelegate = [[[UiDelegate alloc] init] autorelease];
+ uiDelegate->infiniFrame = this;
+ uiDelegate->window = m_impl->_window;
+ uiDelegate->webMessageReceivedCallback = m_impl->_webMessageReceivedCallback;
+
+ NavigationDelegate *navDelegate = [[[NavigationDelegate alloc] init] autorelease];
+ navDelegate->infiniFrame = this;
+ navDelegate->window = m_impl->_window;
+
+ [userContentController addScriptMessageHandler: uiDelegate name: @"infiniFrameInterop"];
+
+ m_impl->_webview.UIDelegate = uiDelegate;
+ m_impl->_webview.navigationDelegate = navDelegate;
+
+ if (!m_impl->_startUrl.empty())
+ NavigateToUrl(const_cast(m_impl->_startUrl.c_str()));
+ else if (!m_impl->_startString.empty())
+ NavigateToString(const_cast(m_impl->_startString.c_str()));
+ else
+ {
+ NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+ [alert setMessageText: @"Neither StartUrl nor StartString was specified"];
+ [alert runModal];
+ }
+}
+
+void InfiniFrameWindow::Show(bool isAlreadyShown)
+{
+ (void)isAlreadyShown;
+
+ if (m_impl->_webview == nil)
+ AttachWebView();
+
+ [m_impl->_window makeKeyAndOrderFront: m_impl->_window];
+ [m_impl->_window orderFrontRegardless];
+}
+
+#endif
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Mac/Window.Cocoa.Internal.h b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/Window.Cocoa.Internal.h
new file mode 100644
index 000000000..0bfc61030
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/Window.Cocoa.Internal.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include
+
+#include
+#include
+#include
+
+#include "Public/InfiniFrameWindow.h"
+#include "Public/InfiniFrameWindowImpl.h"
+
+struct InfiniFrameWindow::Impl : InfiniFrameWindowImpl {
+ NSWindow* _window = nil;
+ WKWebView* _webview = nil;
+ WKWebViewConfiguration* _webviewConfiguration = nil;
+ NSWindow* _nativeParentWindow = nil;
+ id _parentWillCloseObserver = nil;
+
+ std::string _temporaryFilesPath;
+
+ bool _chromeless = false;
+
+ CGFloat _preMaximizedWidth = 0;
+ CGFloat _preMaximizedHeight = 0;
+ CGFloat _preMaximizedXPosition = 0;
+ CGFloat _preMaximizedYPosition = 0;
+
+ std::vector GetMonitors() const;
+ void SetUserAgent(AutoString userAgent);
+ void SetPreference(NSString* key, NSNumber* value);
+ void SetPreference(NSString* key, NSString* value);
+ void AddCustomScheme(const AutoStringConst scheme, WebResourceRequestedCallback requestHandler);
+};
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Mac/Window.mm b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/Window.mm
deleted file mode 100644
index d55bb721d..000000000
--- a/src/InfiniFrame.NativeBridge/Native/Platform/Mac/Window.mm
+++ /dev/null
@@ -1,1097 +0,0 @@
-#ifdef __APPLE__
-#include "Core/InfiniFrameWindow.h"
-#include "Core/InfiniFrameDialog.h"
-#include "Core/InfiniFrameWindowImpl.h"
-#include "Embedded/Embedded.h"
-#include "Utils/Common.h"
-#include "AppDelegate.h"
-#include "UiDelegate.h"
-#include "WindowDelegate.h"
-#include "UrlSchemeHandler.h"
-#include "NSWindowBorderless.h"
-#include "NavigationDelegate.h"
-#include
-#include
-
-using namespace std;
-
-static const int MAX_WINDOW_DIMENSION = 10000;
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Platform Impl
-// ---------------------------------------------------------------------------------------------------------------------
-
-struct InfiniFrameWindow::Impl : InfiniFrameWindowImpl
-{
- NSWindow* _window = nil;
- WKWebView* _webview = nil;
- WKWebViewConfiguration* _webviewConfiguration = nil;
- NSWindow* _nativeParentWindow = nil;
- id _parentWillCloseObserver = nil;
-
- std::string _temporaryFilesPath;
-
- bool _chromeless = false;
-
- CGFloat _preMaximizedWidth = 0;
- CGFloat _preMaximizedHeight = 0;
- CGFloat _preMaximizedXPosition = 0;
- CGFloat _preMaximizedYPosition = 0;
-
- std::vector GetMonitors() const;
- void SetUserAgent(AutoString userAgent);
- void SetPreference(NSString* key, NSNumber* value);
- void SetPreference(NSString* key, NSString* value);
- void AddCustomScheme(const AutoStringConst scheme, WebResourceRequestedCallback requestHandler);
-};
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Impl method definitions
-// ---------------------------------------------------------------------------------------------------------------------
-
-std::vector InfiniFrameWindow::Impl::GetMonitors() const
-{
- std::vector monitors;
-
- for (NSScreen *screen : [NSScreen screens])
- {
- NSRect monitorFrame = [screen frame];
- Monitor::MonitorRect monitorArea;
- monitorArea.x = static_cast(roundf(monitorFrame.origin.x));
- monitorArea.y = static_cast(roundf(monitorFrame.origin.y));
- monitorArea.width = static_cast(roundf(monitorFrame.size.width));
- monitorArea.height = static_cast(roundf(monitorFrame.size.height));
-
- NSRect workFrame = [screen visibleFrame];
- Monitor::MonitorRect workArea;
- workArea.x = static_cast(roundf(workFrame.origin.x));
- workArea.y = static_cast(roundf(workFrame.origin.y));
- workArea.width = static_cast(roundf(workFrame.size.width));
- workArea.height = static_cast(roundf(workFrame.size.height));
-
- CGFloat scaleFactor = [screen backingScaleFactor];
- monitors.push_back({monitorArea, workArea, static_cast(scaleFactor)});
- }
-
- return monitors;
-}
-
-void InfiniFrameWindow::Impl::SetUserAgent(AutoString userAgent)
-{
- if (userAgent != nullptr)
- {
- _userAgent = userAgent;
- [_webview setCustomUserAgent: [NSString stringWithUTF8String: userAgent]];
- }
- else
- {
- _userAgent.clear();
- }
-}
-
-void InfiniFrameWindow::Impl::SetPreference(NSString *key, NSNumber *value)
-{
- [_webviewConfiguration.preferences setValue: value forKey: key];
-}
-
-void InfiniFrameWindow::Impl::SetPreference(NSString *key, NSString *value)
-{
- [_webviewConfiguration.preferences setValue: value forKey: key];
-}
-
-void InfiniFrameWindow::Impl::AddCustomScheme(const AutoStringConst scheme, WebResourceRequestedCallback requestHandler)
-{
- if (requestHandler == nullptr)
- return;
-
- UrlSchemeHandler* schemeHandler = [[[UrlSchemeHandler alloc] init] autorelease];
- schemeHandler->requestHandler = requestHandler;
-
- [_webviewConfiguration
- setURLSchemeHandler: schemeHandler
- forURLScheme: [NSString stringWithUTF8String: scheme]];
-}
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Register (static — called once)
-// ---------------------------------------------------------------------------------------------------------------------
-
-void InfiniFrameWindow::Register()
-{
- [NSAutoreleasePool new];
-
- AppDelegate *appDelegate = [[[AppDelegate alloc] init] autorelease];
-
- NSApplication *application = [NSApplication sharedApplication];
- [application setDelegate: appDelegate];
- [application setActivationPolicy: NSApplicationActivationPolicyRegular];
-
- NSString *appName = [[NSProcessInfo processInfo] processName];
-
- NSMenu *mainMenu = [[NSMenu new] autorelease];
- NSMenuItem *mainMenuItem = [[NSMenuItem new] autorelease];
- [mainMenu addItem: mainMenuItem];
-
- NSMenu *mainSubMenu = [[NSMenu new] autorelease];
- [mainMenuItem setSubmenu: mainSubMenu];
-
- NSMenuItem *selectMenuItem = [[
- [NSMenuItem alloc]
- initWithTitle: @"Select All"
- action: @selector(selectAll:)
- keyEquivalent: @"a"
- ] autorelease];
- [mainSubMenu addItem: selectMenuItem];
-
- NSMenuItem *cutMenuItem = [[
- [NSMenuItem alloc]
- initWithTitle: @"Cut"
- action: @selector(cut:)
- keyEquivalent: @"x"
- ] autorelease];
- [mainSubMenu addItem: cutMenuItem];
-
- NSMenuItem *copyMenuItem = [[
- [NSMenuItem alloc]
- initWithTitle: @"Copy"
- action: @selector(copy:)
- keyEquivalent: @"c"
- ] autorelease];
- [mainSubMenu addItem: copyMenuItem];
-
- NSMenuItem *pasteMenuItem = [[
- [NSMenuItem alloc]
- initWithTitle: @"Paste"
- action: @selector(paste:)
- keyEquivalent: @"v"
- ] autorelease];
- [mainSubMenu addItem: pasteMenuItem];
-
- NSMenuItem *quitMenuItem = [[
- [NSMenuItem alloc]
- initWithTitle: [@"Quit " stringByAppendingString: appName]
- action: @selector(terminate:)
- keyEquivalent: @"q"
- ] autorelease];
- [mainSubMenu addItem: quitMenuItem];
-
- [NSApp setMainMenu: mainMenu];
-}
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Constructor / Destructor
-// ---------------------------------------------------------------------------------------------------------------------
-
-InfiniFrameWindow::InfiniFrameWindow(InfiniFrameInitParams* initParams) : m_impl(std::make_unique())
-{
- m_impl->_windowTitle = initParams->Title ? initParams->Title : "";
-
- if (initParams->StartUrl != nullptr)
- m_impl->_startUrl = initParams->StartUrl;
-
- if (initParams->StartString != nullptr)
- m_impl->_startString = initParams->StartString;
-
- if (initParams->TemporaryFilesPath != nullptr)
- m_impl->_temporaryFilesPath = initParams->TemporaryFilesPath;
-
- m_impl->_ignoreCertificateErrorsEnabled = initParams->IgnoreCertificateErrorsEnabled;
- m_impl->_contextMenuEnabled = initParams->ContextMenuEnabled;
- m_impl->_zoomEnabled = initParams->ZoomEnabled;
- m_impl->_grantBrowserPermissions = initParams->GrantBrowserPermissions;
-
- m_impl->_webMessageReceivedCallback = initParams->WebMessageReceivedHandler;
- m_impl->_resizedCallback = initParams->ResizedHandler;
- m_impl->_movedCallback = initParams->MovedHandler;
- m_impl->_closingCallback = initParams->ClosingHandler;
- m_impl->_closedCallback = initParams->ClosedHandler;
- m_impl->_focusInCallback = initParams->FocusInHandler;
- m_impl->_focusOutCallback = initParams->FocusOutHandler;
- m_impl->_maximizedCallback = initParams->MaximizedHandler;
- m_impl->_minimizedCallback = initParams->MinimizedHandler;
- m_impl->_restoredCallback = initParams->RestoredHandler;
- m_impl->_customSchemeCallback = initParams->CustomSchemeHandler;
-
- for (int i = 0; i < 16; ++i)
- {
- if (initParams->CustomSchemeNames[i] != nullptr)
- m_impl->_customSchemeNames.emplace_back(initParams->CustomSchemeNames[i]);
- }
-
- m_impl->_parent = initParams->ParentInstance;
-
- if (initParams->UseOsDefaultSize)
- {
- initParams->Width = 800;
- initParams->Height = 600;
- }
- else
- {
- if (initParams->Width < 0) initParams->Width = 800;
- if (initParams->Height < 0) initParams->Height = 600;
- }
-
- if (initParams->UseOsDefaultLocation)
- {
- initParams->Left = 0;
- initParams->Top = 0;
- }
-
- NSRect frame = NSMakeRect(0, 0, 0, 0);
-
- m_impl->_chromeless = initParams->Chromeless;
- if (initParams->Chromeless)
- {
- m_impl->_window = [[NSWindowBorderless alloc]
- initWithContentRect: frame
- styleMask: NSWindowStyleMaskBorderless
- | NSWindowStyleMaskClosable
- | NSWindowStyleMaskResizable
- | NSWindowStyleMaskMiniaturizable
- backing: NSBackingStoreBuffered
- defer: true];
- }
- else
- {
- m_impl->_window = [[NSWindow alloc]
- initWithContentRect: frame
- styleMask: NSWindowStyleMaskTitled
- | NSWindowStyleMaskClosable
- | NSWindowStyleMaskResizable
- | NSWindowStyleMaskMiniaturizable
- backing: NSBackingStoreBuffered
- defer: true];
- }
-
- m_impl->_transparentEnabled = initParams->Transparent;
-
- if (m_impl->_parent != nullptr && m_impl->_parent->m_impl != nullptr)
- {
- auto* parentImpl = static_cast(m_impl->_parent->m_impl.get());
- m_impl->_nativeParentWindow = parentImpl->_window;
- if (m_impl->_nativeParentWindow != nil && m_impl->_nativeParentWindow != m_impl->_window)
- {
- [m_impl->_nativeParentWindow addChildWindow:m_impl->_window ordered:NSWindowAbove];
-
- NSWindow* childWindow = m_impl->_window;
- m_impl->_parentWillCloseObserver = [[NSNotificationCenter defaultCenter]
- addObserverForName:NSWindowWillCloseNotification
- object:m_impl->_nativeParentWindow
- queue:nil
- usingBlock:^(NSNotification*) {
- [childWindow close];
- }];
- }
- }
-
- [m_impl->_window setCollectionBehavior:
- [m_impl->_window collectionBehavior] | NSWindowCollectionBehaviorFullScreenPrimary];
-
- WindowDelegate *windowDelegate = [WindowDelegate new];
- windowDelegate->infiniFrame = this;
- m_impl->_window.delegate = windowDelegate;
-
- SetTitle(const_cast(m_impl->_windowTitle.c_str()));
-
- if (initParams->WindowIconFile != nullptr && initParams->WindowIconFile[0] != '\0')
- SetIconFile(initParams->WindowIconFile);
-
- SetTopmost(initParams->Topmost);
- SetPosition(initParams->Left, initParams->Top);
-
- SetMinSize(initParams->MinWidth, initParams->MinHeight);
- SetMaxSize(initParams->MaxWidth, initParams->MaxHeight);
- SetSize(initParams->Width, initParams->Height);
-
- SetMinimized(initParams->Minimized);
- SetMaximized(initParams->Maximized);
- SetResizable(initParams->Resizable);
-
- if (initParams->CenterOnInitialize)
- Center();
-
- m_impl->_webviewConfiguration = [[WKWebViewConfiguration alloc] init];
-
- for (const auto & scheme : m_impl->_customSchemeNames)
- {
- // Note:
- // Unlike WebView2 (Windows) and WebKitGTK (Linux security manager),
- // WKURLSchemeHandler does not expose per-scheme "secure"/authority flags.
- // We still register all custom schemes here for routing, but "app" trust
- // semantics cannot be configured at the same granularity on macOS.
- m_impl->AddCustomScheme(scheme.c_str(), m_impl->_customSchemeCallback);
- }
-
- AttachWebView();
-
- m_impl->SetUserAgent(initParams->UserAgent);
-
- m_impl->SetPreference(@"developerExtrasEnabled", initParams->DevToolsEnabled ? @YES : @NO);
- m_impl->SetPreference(@"allowFileAccessFromFileURLs", initParams->FileSystemAccessEnabled ? @YES : @NO);
- m_impl->SetPreference(@"webSecurityEnabled", initParams->WebSecurityEnabled ? @YES : @NO);
- m_impl->SetPreference(@"javaScriptCanAccessClipboard", initParams->JavascriptClipboardAccessEnabled ? @YES : @NO);
- m_impl->SetPreference(@"mediaStreamEnabled", initParams->MediaStreamEnabled ? @YES : @NO);
-
- m_impl->SetPreference(@"mediaDevicesEnabled", @YES);
- m_impl->SetPreference(@"mediaCaptureRequiresSecureConnection", @NO);
-
- if ([NSProcessInfo.processInfo isOperatingSystemAtLeastVersion: NSOperatingSystemVersion({13, 3, 0})])
- {
- m_impl->SetPreference(@"notificationEventEnabled", @YES);
- }
-
- m_impl->SetPreference(@"notificationsEnabled", @YES);
- m_impl->SetPreference(@"screenCaptureEnabled", @YES);
-
- if (initParams->BrowserControlInitParameters != nullptr)
- {
- simdjson::ondemand::parser parser;
- auto doc = parser.iterate(initParams->BrowserControlInitParameters);
-
- for (auto field : doc.get_object()) {
- std::string_view key = field.unescaped_key().value();
- auto value = field.value();
-
- NSString *preferenceKey = [[NSString alloc] initWithBytes:key.data() length:key.length() encoding:NSUTF8StringEncoding];
-
- switch (value.type()) {
- case simdjson::ondemand::json_type::number: {
- int64_t intVal;
- if (value.get(intVal) == simdjson::SUCCESS) {
- m_impl->SetPreference(preferenceKey, [NSNumber numberWithInt: (int)intVal]);
- } else {
- double doubleVal;
- if (value.get(doubleVal) == simdjson::SUCCESS) {
- m_impl->SetPreference(preferenceKey, [NSNumber numberWithDouble: doubleVal]);
- }
- }
- break;
- }
- case simdjson::ondemand::json_type::boolean: {
- bool boolVal;
- if (value.get(boolVal) == simdjson::SUCCESS) {
- m_impl->SetPreference(preferenceKey, [NSNumber numberWithBool: boolVal]);
- }
- break;
- }
- case simdjson::ondemand::json_type::string: {
- std::string_view strVal;
- if (value.get(strVal) == simdjson::SUCCESS) {
- NSString *preferenceValue = [[NSString alloc] initWithBytes:strVal.data()
- length:strVal.length()
- encoding:NSUTF8StringEncoding];
- m_impl->SetPreference(preferenceKey, preferenceValue);
- }
- break;
- }
- default:
- break;
- }
- }
- }
-
- m_impl->_dialog = std::make_unique();
-
- Show(false);
- SetFullScreen(initParams->FullScreen);
-}
-
-InfiniFrameWindow::~InfiniFrameWindow()
-{
- if (m_impl->_parentWillCloseObserver != nil) {
- [[NSNotificationCenter defaultCenter] removeObserver:m_impl->_parentWillCloseObserver];
- m_impl->_parentWillCloseObserver = nil;
- }
-
- if (m_impl->_nativeParentWindow != nil && m_impl->_window != nil) {
- [m_impl->_nativeParentWindow removeChildWindow:m_impl->_window];
- m_impl->_nativeParentWindow = nil;
- }
-
- [m_impl->_webviewConfiguration release];
- [m_impl->_webview release];
- [m_impl->_window performClose: m_impl->_window];
-}
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Window Operations
-// ---------------------------------------------------------------------------------------------------------------------
-
-void InfiniFrameWindow::Center()
-{
- [m_impl->_window center];
- [m_impl->_window makeKeyAndOrderFront: m_impl->_window];
-}
-
-void InfiniFrameWindow::ClearBrowserAutoFill()
-{
- // TODO
-}
-
-void InfiniFrameWindow::Close()
-{
- if (m_impl->_parentWillCloseObserver != nil) {
- [[NSNotificationCenter defaultCenter] removeObserver:m_impl->_parentWillCloseObserver];
- m_impl->_parentWillCloseObserver = nil;
- }
-
- if (m_impl->_nativeParentWindow != nil && m_impl->_window != nil) {
- [m_impl->_nativeParentWindow removeChildWindow:m_impl->_window];
- m_impl->_nativeParentWindow = nil;
- }
-
- if (m_impl->_chromeless)
- [m_impl->_window close];
- else
- [m_impl->_window performClose: m_impl->_window];
-}
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Get Properties
-// ---------------------------------------------------------------------------------------------------------------------
-
-void InfiniFrameWindow::GetTransparentEnabled(bool* enabled) const
-{
- *enabled = false;
-}
-
-void InfiniFrameWindow::GetContextMenuEnabled(bool* enabled) const
-{
- *enabled = m_impl->_contextMenuEnabled;
-}
-
-void InfiniFrameWindow::GetZoomEnabled(bool* enabled) const
-{
- *enabled = m_impl->_zoomEnabled;
-}
-
-void InfiniFrameWindow::GetDevToolsEnabled(bool* enabled) const
-{
- *enabled = m_impl->_devToolsEnabled;
-}
-
-void InfiniFrameWindow::GetGrantBrowserPermissions(bool* enabled) const
-{
- *enabled = m_impl->_grantBrowserPermissions;
-}
-
-AutoString InfiniFrameWindow::GetUserAgent() const
-{
- return AllocateStringCopy(m_impl->_userAgent);
-}
-
-void InfiniFrameWindow::GetMediaAutoplayEnabled(bool* enabled) const
-{
- *enabled = true;
-}
-
-void InfiniFrameWindow::GetFileSystemAccessEnabled(bool* enabled) const
-{
- *enabled = m_impl->_fileSystemAccessEnabled;
-}
-
-void InfiniFrameWindow::GetSmoothScrollingEnabled(bool* enabled) const
-{
- *enabled = false;
-}
-
-void InfiniFrameWindow::GetWebSecurityEnabled(bool* enabled) const
-{
- *enabled = m_impl->_webSecurityEnabled;
-}
-
-void InfiniFrameWindow::GetJavascriptClipboardAccessEnabled(bool* enabled) const
-{
- *enabled = m_impl->_javascriptClipboardAccessEnabled;
-}
-
-void InfiniFrameWindow::GetMediaStreamEnabled(bool* enabled) const
-{
- *enabled = m_impl->_mediaStreamEnabled;
-}
-
-void InfiniFrameWindow::GetFullScreen(bool* fullScreen) const
-{
- *fullScreen = ([m_impl->_window styleMask] & NSWindowStyleMaskFullScreen) != 0;
-}
-
-void InfiniFrameWindow::GetMaximized(bool* isMaximized) const
-{
- bool isFullScreen = false;
- GetFullScreen(&isFullScreen);
- if (isFullScreen)
- {
- *isMaximized = false;
- return;
- }
- *isMaximized = [m_impl->_window isZoomed];
-}
-
-void InfiniFrameWindow::GetMinimized(bool* isMinimized) const
-{
- *isMinimized = [m_impl->_window isMiniaturized];
-}
-
-void InfiniFrameWindow::GetPosition(int* x, int* y) const
-{
- NSRect frame = [m_impl->_window frame];
- NSScreen* screen = [m_impl->_window screen];
- if (!screen) screen = [NSScreen mainScreen];
- NSRect screenFrame = [screen frame];
- int height = static_cast(roundf(frame.size.height));
- *x = static_cast(roundf(frame.origin.x));
- *y = static_cast(roundf(screenFrame.origin.y + screenFrame.size.height - (frame.origin.y + height)));
-}
-
-void InfiniFrameWindow::GetResizable(bool* resizable) const
-{
- *resizable = (([m_impl->_window styleMask] & NSWindowStyleMaskResizable) == NSWindowStyleMaskResizable);
-}
-
-void InfiniFrameWindow::GetIgnoreCertificateErrorsEnabled(bool* enabled) const
-{
- *enabled = m_impl->_ignoreCertificateErrorsEnabled;
-}
-
-void InfiniFrameWindow::GetFocused(bool* isFocused) const
-{
- if (!isFocused)
- return;
-
- if (!m_impl->_window)
- {
- *isFocused = false;
- return;
- }
-
- *isFocused = [NSApp isActive] && [m_impl->_window isKeyWindow];
-}
-
-unsigned int InfiniFrameWindow::GetScreenDpi() const
-{
- return 72;
-}
-
-void InfiniFrameWindow::GetSize(int* width, int* height) const
-{
- NSSize size = [m_impl->_window frame].size;
- if (width) *width = static_cast(roundf(size.width));
- if (height) *height = static_cast(roundf(size.height));
-}
-
-void InfiniFrameWindow::GetMaxSize(int* width, int* height) const
-{
- NSSize maxSize = [m_impl->_window maxSize];
- if (width) *width = static_cast(roundf(maxSize.width));
- if (height) *height = static_cast(roundf(maxSize.height));
-}
-
-void InfiniFrameWindow::GetMinSize(int* width, int* height) const
-{
- NSSize minSize = [m_impl->_window minSize];
- if (width) *width = static_cast(roundf(minSize.width));
- if (height) *height = static_cast(roundf(minSize.height));
-}
-
-AutoString InfiniFrameWindow::GetTitle() const
-{
- return AllocateStringCopy(m_impl->_windowTitle);
-}
-
-void InfiniFrameWindow::GetTopmost(bool* topmost) const
-{
- *topmost = ([m_impl->_window level] & NSFloatingWindowLevel) == NSFloatingWindowLevel;
-}
-
-void InfiniFrameWindow::GetZoom(int* zoom) const
-{
- CGFloat rawValue = [m_impl->_webview magnification];
- rawValue = (rawValue * 100.0) + 0.5;
- *zoom = static_cast(rawValue);
-}
-
-AutoString InfiniFrameWindow::GetIconFileName() const
-{
- return AllocateStringCopy(m_impl->_iconFileName);
-}
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Navigation
-// ---------------------------------------------------------------------------------------------------------------------
-
-void InfiniFrameWindow::NavigateToString(AutoString content)
-{
- [m_impl->_webview loadHTMLString: [NSString stringWithUTF8String: content] baseURL: nil];
-}
-
-void InfiniFrameWindow::NavigateToUrl(AutoString url)
-{
- NSString* nsurlstring = [NSString stringWithUTF8String: url];
- NSURL *nsurl = [NSURL URLWithString: nsurlstring];
- NSURLRequest *nsrequest = [NSURLRequest requestWithURL: nsurl];
- [m_impl->_webview loadRequest: nsrequest];
-}
-
-void InfiniFrameWindow::Restore()
-{
- bool minimized;
- bool maximized;
- GetMinimized(&minimized);
- GetMaximized(&maximized);
- if (minimized) SetMinimized(false);
- if (maximized) SetMaximized(false);
-}
-
-void InfiniFrameWindow::SendWebMessage(AutoString message)
-{
- NSString* nsmessage = [NSString stringWithUTF8String: message];
-
- NSData* data = [
- NSJSONSerialization
- dataWithJSONObject: @[nsmessage]
- options: 0
- error: nil];
-
- NSString *nsmessageJson = [[
- [NSString alloc]
- initWithData: data
- encoding: NSUTF8StringEncoding] autorelease];
-
- nsmessageJson = [
- [nsmessageJson substringToIndex: ([nsmessageJson length] - 1)]
- substringFromIndex: 1
- ];
-
- NSString *javaScriptToEval = [NSString stringWithFormat: @"__dispatchMessageCallback(%@)", nsmessageJson];
- [m_impl->_webview evaluateJavaScript: javaScriptToEval completionHandler: nil];
-}
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Set Properties
-// ---------------------------------------------------------------------------------------------------------------------
-
-void InfiniFrameWindow::SetDevToolsEnabled(bool enabled)
-{
- m_impl->_devToolsEnabled = enabled;
- m_impl->SetPreference(@"developerExtrasEnabled", enabled ? @YES : @NO);
-}
-
-void InfiniFrameWindow::SetTransparentEnabled(bool enabled)
-{
- // Not implemented on macOS
-}
-
-void InfiniFrameWindow::SetContextMenuEnabled(bool enabled)
-{
- // Not supported on macOS
-}
-
-void InfiniFrameWindow::SetZoomEnabled(bool enabled)
-{
- // Not implemented on macOS
-}
-
-void InfiniFrameWindow::SetIconFile(AutoString filename)
-{
- NSString* path = [NSString stringWithUTF8String: filename];
- NSImage* icon = [[NSImage alloc] initWithContentsOfFile: path];
- if (icon != nil)
- [[m_impl->_window standardWindowButton: NSWindowDocumentIconButton] setImage: icon];
-
- m_impl->_iconFileName = filename ? filename : "";
-}
-
-void InfiniFrameWindow::SetFullScreen(bool fullScreen)
-{
- bool isFullScreen = ([m_impl->_window styleMask] & NSWindowStyleMaskFullScreen) != 0;
- if (fullScreen != isFullScreen)
- [m_impl->_window toggleFullScreen: nil];
-}
-
-void InfiniFrameWindow::SetMinimized(bool minimized)
-{
- if (m_impl->_window.isMiniaturized == minimized) return;
-
- if (minimized)
- [m_impl->_window miniaturize: nullptr];
- else
- [m_impl->_window deminiaturize: nullptr];
-}
-
-void InfiniFrameWindow::SetMaximized(bool maximized)
-{
- if (maximized)
- {
- NSRect window = [m_impl->_window frame];
- m_impl->_preMaximizedWidth = window.size.width;
- m_impl->_preMaximizedHeight = window.size.height;
- m_impl->_preMaximizedXPosition = window.origin.x;
- m_impl->_preMaximizedYPosition = window.origin.y;
-
- NSRect screen = [[m_impl->_window screen] visibleFrame];
- [m_impl->_window setFrame: NSMakeRect(screen.origin.x, screen.origin.y,
- screen.size.width, screen.size.height)
- display: YES];
- }
- else if (!maximized && m_impl->_preMaximizedWidth > 0 && m_impl->_preMaximizedHeight > 0)
- {
- [m_impl->_window setFrame: NSMakeRect(m_impl->_preMaximizedXPosition,
- m_impl->_preMaximizedYPosition,
- m_impl->_preMaximizedWidth,
- m_impl->_preMaximizedHeight)
- display: YES];
- }
-}
-
-void InfiniFrameWindow::SetPosition(int x, int y)
-{
- NSScreen* screen = [m_impl->_window screen];
- if (!screen) screen = [NSScreen mainScreen];
- NSRect screenFrame = [screen frame];
-
- NSRect frame = [m_impl->_window frame];
- int height = static_cast(roundf(frame.size.height));
-
- auto left = static_cast(x);
- auto top = static_cast(screenFrame.origin.y + screenFrame.size.height - (y + height));
-
- [m_impl->_window setFrameOrigin: CGPointMake(left, top)];
-}
-
-void InfiniFrameWindow::SetResizable(bool resizable)
-{
- if (resizable)
- m_impl->_window.styleMask |= NSWindowStyleMaskResizable;
- else
- m_impl->_window.styleMask &= ~NSWindowStyleMaskResizable;
-}
-
-void InfiniFrameWindow::SetSize(int width, int height)
-{
- width = width > MAX_WINDOW_DIMENSION ? MAX_WINDOW_DIMENSION : width;
- height = height > MAX_WINDOW_DIMENSION ? MAX_WINDOW_DIMENSION : height;
-
- if (width > m_impl->_window.maxSize.width) width = m_impl->_window.maxSize.width;
- if (height > m_impl->_window.maxSize.height) height = m_impl->_window.maxSize.height;
- if (width < m_impl->_window.minSize.width) width = m_impl->_window.minSize.width;
- if (height < m_impl->_window.minSize.height) height = m_impl->_window.minSize.height;
-
- NSRect frame = [m_impl->_window frame];
- CGFloat oldHeight = frame.size.height;
- frame.size = CGSizeMake(static_cast(width), static_cast(height));
- frame.origin.y -= static_cast(height) - oldHeight;
-
- [m_impl->_window setFrame: frame display: true];
-}
-
-void InfiniFrameWindow::SetMinSize(int width, int height)
-{
- width = width > MAX_WINDOW_DIMENSION ? MAX_WINDOW_DIMENSION : width;
- height = height > MAX_WINDOW_DIMENSION ? MAX_WINDOW_DIMENSION : height;
-
- [m_impl->_window setMinSize: NSMakeSize(width, height)];
-}
-
-void InfiniFrameWindow::SetMaxSize(int width, int height)
-{
- width = width > MAX_WINDOW_DIMENSION ? MAX_WINDOW_DIMENSION : width;
- height = height > MAX_WINDOW_DIMENSION ? MAX_WINDOW_DIMENSION : height;
-
- [m_impl->_window setMaxSize: NSMakeSize(width, height)];
-}
-
-void InfiniFrameWindow::SetTitle(AutoString title)
-{
- m_impl->_windowTitle = title ? title : "";
- [m_impl->_window setTitle: [NSString stringWithUTF8String: title]];
-}
-
-void InfiniFrameWindow::SetTopmost(bool topmost)
-{
- if (topmost) [m_impl->_window setLevel: NSFloatingWindowLevel];
- else [m_impl->_window setLevel: NSNormalWindowLevel];
-}
-
-void InfiniFrameWindow::SetZoom(int zoom)
-{
- CGFloat newZoom = zoom / 100.0;
- [m_impl->_webview setMagnification: newZoom];
-}
-
-void InfiniFrameWindow::SetFocused()
-{
- if (!m_impl->_window) return;
-
- [NSApp activateIgnoringOtherApps: YES];
- [m_impl->_window makeKeyAndOrderFront: m_impl->_window];
-
- if (![m_impl->_window isKeyWindow])
- {
- [m_impl->_window orderFrontRegardless];
- [m_impl->_window makeKeyWindow];
- }
-}
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Notifications / Event loop
-// ---------------------------------------------------------------------------------------------------------------------
-
-void InfiniFrameWindow::ShowNotification(AutoString title, AutoString body)
-{
- UNMutableNotificationContent *objNotificationContent = [[UNMutableNotificationContent alloc] init];
- objNotificationContent.title = [NSString stringWithUTF8String: title];
- objNotificationContent.body = [NSString stringWithUTF8String: body];
- objNotificationContent.sound = [UNNotificationSound defaultSound];
- UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval: 0.3 repeats: NO];
- UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier: @"three"
- content: objNotificationContent
- trigger: trigger];
- UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
- [center addNotificationRequest: request withCompletionHandler: ^(NSError * _Nullable error) {}];
-}
-
-void InfiniFrameWindow::WaitForExit()
-{
- if (![NSApp isRunning]) {
- [NSApp run];
- return;
- }
-
- __block bool windowClosed = false;
- id observer = [[NSNotificationCenter defaultCenter]
- addObserverForName: NSWindowWillCloseNotification
- object: m_impl->_window
- queue: nil
- usingBlock: ^(NSNotification*) {
- windowClosed = true;
- }];
-
- while (!windowClosed) {
- [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
- beforeDate: [NSDate dateWithTimeIntervalSinceNow: 0.05]];
- }
-
- [[NSNotificationCenter defaultCenter] removeObserver: observer];
-}
-
-void InfiniFrameWindow::CloseWebView()
-{
- // Not implemented on macOS
-}
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Callbacks
-// ---------------------------------------------------------------------------------------------------------------------
-
-InfiniFrameDialog* InfiniFrameWindow::GetDialog() const
-{
- return m_impl->_dialog.get();
-}
-
-void InfiniFrameWindow::AddCustomSchemeName(const AutoStringConst scheme)
-{
- if (scheme)
- m_impl->_customSchemeNames.emplace_back(scheme);
-}
-
-void InfiniFrameWindow::GetAllMonitors(GetAllMonitorsCallback callback) const
-{
- if (callback)
- {
- for (NSScreen* screen in [NSScreen screens])
- {
- Monitor props = {};
-
- NSRect frame = [screen frame];
- props.monitor.x = static_cast(roundf(frame.origin.x));
- props.monitor.y = static_cast(roundf(frame.origin.y));
- props.monitor.width = static_cast(roundf(frame.size.width));
- props.monitor.height = static_cast(roundf(frame.size.height));
-
- NSRect vframe = [screen visibleFrame];
- props.work.x = static_cast(roundf(vframe.origin.x));
- props.work.y = static_cast(roundf(vframe.origin.y));
- props.work.width = static_cast(roundf(vframe.size.width));
- props.work.height = static_cast(roundf(vframe.size.height));
-
- props.scale = [screen backingScaleFactor];
-
- callback(&props);
- }
- }
-}
-
-void InfiniFrameWindow::SetClosingCallback(const ClosingCallback callback)
-{
- m_impl->_closingCallback = callback;
-}
-
-void InfiniFrameWindow::SetClosedCallback(const ClosedCallback callback)
-{
- m_impl->_closedCallback = callback;
-}
-
-void InfiniFrameWindow::SetFocusInCallback(const FocusInCallback callback)
-{
- m_impl->_focusInCallback = callback;
-}
-
-void InfiniFrameWindow::SetFocusOutCallback(const FocusOutCallback callback)
-{
- m_impl->_focusOutCallback = callback;
-}
-
-void InfiniFrameWindow::SetMovedCallback(const MovedCallback callback)
-{
- m_impl->_movedCallback = callback;
-}
-
-void InfiniFrameWindow::SetResizedCallback(const ResizedCallback callback)
-{
- m_impl->_resizedCallback = callback;
-}
-
-void InfiniFrameWindow::SetMaximizedCallback(const MaximizedCallback callback)
-{
- m_impl->_maximizedCallback = callback;
-}
-
-void InfiniFrameWindow::SetRestoredCallback(const RestoredCallback callback)
-{
- m_impl->_restoredCallback = callback;
-}
-
-void InfiniFrameWindow::SetMinimizedCallback(const MinimizedCallback callback)
-{
- m_impl->_minimizedCallback = callback;
-}
-
-void InfiniFrameWindow::Invoke(ACTION callback)
-{
- if ([NSThread isMainThread])
- callback();
- else
- dispatch_sync(dispatch_get_main_queue(), ^(void){ callback(); });
-}
-
-[[nodiscard]] bool InfiniFrameWindow::InvokeClose() const noexcept
-{
- if (m_impl->_closingCallback)
- return m_impl->_closingCallback();
- return false;
-}
-
-void InfiniFrameWindow::InvokeClosed() const noexcept
-{
- if (m_impl->_closedCallback)
- m_impl->_closedCallback();
-}
-
-void InfiniFrameWindow::InvokeFocusIn() const noexcept
-{
- if (m_impl->_focusInCallback)
- m_impl->_focusInCallback();
-}
-
-void InfiniFrameWindow::InvokeFocusOut() const noexcept
-{
- if (m_impl->_focusOutCallback)
- m_impl->_focusOutCallback();
-}
-
-void InfiniFrameWindow::InvokeMove(int x, int y) const noexcept
-{
- if (m_impl->_movedCallback)
- m_impl->_movedCallback(x, y);
-}
-
-void InfiniFrameWindow::InvokeResize(int width, int height) const noexcept
-{
- if (m_impl->_resizedCallback)
- m_impl->_resizedCallback(width, height);
-}
-
-void InfiniFrameWindow::InvokeMaximized() const noexcept
-{
- if (m_impl->_maximizedCallback)
- m_impl->_maximizedCallback();
-}
-
-void InfiniFrameWindow::InvokeRestored() const noexcept
-{
- if (m_impl->_restoredCallback)
- m_impl->_restoredCallback();
-}
-
-void InfiniFrameWindow::InvokeMinimized() const noexcept
-{
- if (m_impl->_minimizedCallback)
- m_impl->_minimizedCallback();
-}
-
-// ---------------------------------------------------------------------------------------------------------------------
-// Private methods
-// ---------------------------------------------------------------------------------------------------------------------
-
-void InfiniFrameWindow::AttachWebView()
-{
- auto js = Embedded::InfiniFrameJsUtf8();
-
- WKUserScript *script =
- [[WKUserScript alloc]
- initWithSource:[NSString stringWithUTF8String:js.c_str()]
- injectionTime:WKUserScriptInjectionTimeAtDocumentStart
- forMainFrameOnly:NO];
-
- WKUserContentController *userContentController =
- [[WKUserContentController alloc] init];
-
- [userContentController addUserScript:script];
-
- m_impl->_webviewConfiguration.userContentController = userContentController;
-
- m_impl->_webview = [
- [WKWebView alloc]
- initWithFrame: m_impl->_window.contentView.frame
- configuration: m_impl->_webviewConfiguration];
-
- [m_impl->_webview setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable];
- [m_impl->_window.contentView addSubview: m_impl->_webview];
- [m_impl->_window.contentView setAutoresizesSubviews: true];
-
- UiDelegate *uiDelegate = [[[UiDelegate alloc] init] autorelease];
- uiDelegate->infiniFrame = this;
- uiDelegate->window = m_impl->_window;
- uiDelegate->webMessageReceivedCallback = m_impl->_webMessageReceivedCallback;
-
- NavigationDelegate *navDelegate = [[[NavigationDelegate alloc] init] autorelease];
- navDelegate->infiniFrame = this;
- navDelegate->window = m_impl->_window;
-
- [userContentController addScriptMessageHandler: uiDelegate name: @"infiniFrameInterop"];
-
- m_impl->_webview.UIDelegate = uiDelegate;
- m_impl->_webview.navigationDelegate = navDelegate;
-
- if (!m_impl->_startUrl.empty())
- NavigateToUrl(const_cast(m_impl->_startUrl.c_str()));
- else if (!m_impl->_startString.empty())
- NavigateToString(const_cast(m_impl->_startString.c_str()));
- else
- {
- NSAlert *alert = [[[NSAlert alloc] init] autorelease];
- [alert setMessageText: @"Neither StartUrl nor StartString was specified"];
- [alert runModal];
- }
-}
-
-void InfiniFrameWindow::Show(bool isAlreadyShown)
-{
- if (m_impl->_webview == nil)
- AttachWebView();
-
- [m_impl->_window makeKeyAndOrderFront: m_impl->_window];
- [m_impl->_window orderFrontRegardless];
-}
-
-#endif
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Mac/WindowDelegate.h b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/WindowDelegate.h
index d9c42685b..93f3c0729 100644
--- a/src/InfiniFrame.NativeBridge/Native/Platform/Mac/WindowDelegate.h
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Mac/WindowDelegate.h
@@ -4,7 +4,7 @@
* @file WindowDelegate.h
* @brief NSWindow delegate that forwards window lifecycle events to InfiniFrameWindow callbacks
*/
-#include "Core/InfiniFrame.h"
+#include "Public/InfiniFrame.h"
/**
* @brief Per-window delegate conforming to NSWindowDelegate.
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Windows/Core/UiDispatcher.Win32.cpp b/src/InfiniFrame.NativeBridge/Native/Platform/Windows/Core/UiDispatcher.Win32.cpp
new file mode 100644
index 000000000..81ccfa54c
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Windows/Core/UiDispatcher.Win32.cpp
@@ -0,0 +1,75 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include "chrono"
+
+#include "Platform/Windows/Window.Win32.Context.h"
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+void InfiniFrameWindow::WaitForExit() {
+ auto* impl = m_impl.get();
+ ApplyPendingOwnerWindow(impl, L"wait_for_exit");
+
+ messageLoopRootWindowHandle = impl->_hWnd;
+ TraceTeardown(L"WaitForExit start instance=%p hwnd=%p", this, impl->_hWnd);
+
+ MSG msg = {};
+ while (true) {
+ const int getMessageResult = GetMessage(&msg, nullptr, 0, 0);
+ if (getMessageResult == -1) {
+ TraceTeardown(L"WaitForExit GetMessage failed err=%lu", GetLastError());
+ break;
+ }
+ if (getMessageResult == 0)
+ break;
+
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+
+ messageLoopRootWindowHandle = nullptr;
+ TraceTeardown(L"WaitForExit end instance=%p hwnd=%p", this, impl->_hWnd);
+}
+
+void InfiniFrameWindow::Invoke(ACTION callback) {
+ if (!callback) {
+ return;
+ }
+
+ auto* impl = m_impl.get();
+ if (impl->_hWnd == nullptr || IsWindow(impl->_hWnd) == 0) {
+ return;
+ }
+
+ auto* waitInfo = new InvokeWaitInfo();
+ if (!PostMessage(
+ impl->_hWnd, WM_USER_INVOKE, reinterpret_cast(callback), reinterpret_cast(waitInfo)
+ )) {
+ delete waitInfo;
+ return;
+ }
+
+ std::unique_lock uLock(waitInfo->mutex);
+ const bool completed =
+ waitInfo->completionNotifier.wait_for(uLock, std::chrono::seconds(15), [&] { return waitInfo->isCompleted; });
+
+ if (!completed) {
+ bool deleteWaitInfo = false;
+ if (waitInfo->isCompleted)
+ deleteWaitInfo = true;
+ else
+ waitInfo->isAbandoned = true;
+
+ uLock.unlock();
+
+ if (deleteWaitInfo)
+ delete waitInfo;
+
+ OutputDebugStringW(L"InfiniFrameWindow::Invoke timed out waiting for UI thread callback.\n");
+ return;
+ }
+
+ uLock.unlock();
+ delete waitInfo;
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Windows/Core/WindowCore.Win32.cpp b/src/InfiniFrame.NativeBridge/Native/Platform/Windows/Core/WindowCore.Win32.cpp
new file mode 100644
index 000000000..dc13e6730
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Windows/Core/WindowCore.Win32.cpp
@@ -0,0 +1,15 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include "Utils/Common.h"
+#include "Platform/Windows/Window.Win32.Context.h"
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+static_assert(sizeof(wchar_t) == sizeof(char16_t));
+
+const wchar_t* CLASS_NAME = L"InfiniFrame";
+std::atomic _hInstance{nullptr};
+thread_local HWND messageLoopRootWindowHandle = nullptr;
+wchar_t _webview2RuntimePath[MAX_PATH];
+std::mutex webview2RuntimePathMutex;
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Windows/Core/WindowEncoding.Win32.cpp b/src/InfiniFrame.NativeBridge/Native/Platform/Windows/Core/WindowEncoding.Win32.cpp
new file mode 100644
index 000000000..fa35407f7
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Windows/Core/WindowEncoding.Win32.cpp
@@ -0,0 +1,48 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include
+#include
+
+#include "Platform/Windows/Window.Win32.Context.h"
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+std::wstring Utf8ToWide(const AutoString source) {
+ if (source == nullptr)
+ return {};
+
+ const auto* utf8 = reinterpret_cast(source);
+ const size_t utf8Length = strlen(utf8);
+ if (utf8Length == 0)
+ return {};
+
+ if (const auto validation = simdutf::validate_utf8_with_errors(utf8, utf8Length); validation.is_err())
+ return {};
+
+ std::u16string utf16(simdutf::utf16_length_from_utf8(utf8, utf8Length), u'\0');
+ const size_t written =
+ simdutf::convert_valid_utf8_to_utf16(utf8, utf8Length, reinterpret_cast(utf16.data()));
+ utf16.resize(written);
+
+ return {reinterpret_cast(utf16.data()), utf16.size()};
+}
+
+std::string WideToUtf8(const AutoString source) {
+ if (source == nullptr)
+ return {};
+
+ const size_t utf16Length = wcslen(source);
+ if (utf16Length == 0)
+ return {};
+
+ const auto* utf16 = reinterpret_cast(source);
+ if (const auto validation = simdutf::validate_utf16_with_errors(utf16, utf16Length); validation.is_err())
+ return {};
+
+ std::string utf8(simdutf::utf8_length_from_utf16(utf16, utf16Length), '\0');
+ const size_t written = simdutf::convert_valid_utf16_to_utf8(utf16, utf16Length, utf8.data());
+ utf8.resize(written);
+
+ return utf8;
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Windows/Core/WindowEvents.Win32.cpp b/src/InfiniFrame.NativeBridge/Native/Platform/Windows/Core/WindowEvents.Win32.cpp
new file mode 100644
index 000000000..679ded74d
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Windows/Core/WindowEvents.Win32.cpp
@@ -0,0 +1,142 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include
+
+#include "Platform/Windows/Window.Win32.Internal.h"
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+BOOL MonitorEnum(const HMONITOR monitor, HDC, LPRECT, const LPARAM arg) {
+ auto callback = reinterpret_cast(arg);
+ UINT dpiX, dpiY;
+ MONITORINFO info = {};
+ info.cbSize = sizeof(MONITORINFO);
+ GetMonitorInfo(monitor, &info);
+ GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY);
+ Monitor props = {};
+ props.monitor.x = info.rcMonitor.left;
+ props.monitor.y = info.rcMonitor.top;
+ props.monitor.width = info.rcMonitor.right - info.rcMonitor.left;
+ props.monitor.height = info.rcMonitor.bottom - info.rcMonitor.top;
+ props.work.x = info.rcWork.left;
+ props.work.y = info.rcWork.top;
+ props.work.width = info.rcWork.right - info.rcWork.left;
+ props.work.height = info.rcWork.bottom - info.rcWork.top;
+ props.scale = dpiY / 96.0;
+ return callback(&props) ? TRUE : FALSE;
+}
+
+void InfiniFrameWindow::ShowNotification(AutoString title, AutoString body) {
+ std::wstring wideTitle = ToUTF16String(title);
+ std::wstring wideBody = ToUTF16String(body);
+ if (m_impl->_notificationsEnabled && WinToast::isCompatible()) {
+ WinToastTemplate toast =
+ WinToastTemplate(WinToastTemplate::ImageAndText02);
+ toast.setTextField(wideTitle.c_str(), WinToastTemplate::FirstLine);
+ toast.setTextField(wideBody.c_str(), WinToastTemplate::SecondLine);
+ if (!m_impl->_iconFileName.empty())
+ toast.setImagePath(m_impl->_iconFileName);
+ WinToast::instance()->showToast(toast, m_impl->_toastHandler.get());
+ }
+}
+
+void InfiniFrameWindow::GetAllMonitors(GetAllMonitorsCallback callback) const {
+ if (callback) {
+ EnumDisplayMonitors(
+ nullptr, nullptr, reinterpret_cast(MonitorEnum), reinterpret_cast(callback)
+ );
+ }
+}
+
+InfiniFrameDialog* InfiniFrameWindow::GetDialog() const {
+ return m_impl->_dialog.get();
+}
+
+void InfiniFrameWindow::AddCustomSchemeName(const AutoStringConst scheme) {
+ if (scheme)
+ m_impl->_customSchemeNames.emplace_back(ToUTF16String(const_cast(scheme)));
+}
+
+void InfiniFrameWindow::SetClosingCallback(const ClosingCallback callback) {
+ m_impl->_closingCallback = callback;
+}
+
+void InfiniFrameWindow::SetClosedCallback(const ClosedCallback callback) {
+ m_impl->_closedCallback = callback;
+}
+
+void InfiniFrameWindow::SetFocusInCallback(const FocusInCallback callback) {
+ m_impl->_focusInCallback = callback;
+}
+
+void InfiniFrameWindow::SetFocusOutCallback(const FocusOutCallback callback) {
+ m_impl->_focusOutCallback = callback;
+}
+
+void InfiniFrameWindow::SetMovedCallback(const MovedCallback callback) {
+ m_impl->_movedCallback = callback;
+}
+
+void InfiniFrameWindow::SetResizedCallback(const ResizedCallback callback) {
+ m_impl->_resizedCallback = callback;
+}
+
+void InfiniFrameWindow::SetMaximizedCallback(const MaximizedCallback callback) {
+ m_impl->_maximizedCallback = callback;
+}
+
+void InfiniFrameWindow::SetRestoredCallback(const RestoredCallback callback) {
+ m_impl->_restoredCallback = callback;
+}
+
+void InfiniFrameWindow::SetMinimizedCallback(const MinimizedCallback callback) {
+ m_impl->_minimizedCallback = callback;
+}
+
+bool InfiniFrameWindow::InvokeClose() const noexcept {
+ if (m_impl->_closingCallback)
+ return m_impl->_closingCallback();
+ return false;
+}
+
+void InfiniFrameWindow::InvokeClosed() const noexcept {
+ if (!m_impl->_closedCallback)
+ return;
+ m_impl->_closedCallback();
+}
+
+void InfiniFrameWindow::InvokeFocusIn() const noexcept {
+ if (m_impl->_focusInCallback)
+ m_impl->_focusInCallback();
+}
+
+void InfiniFrameWindow::InvokeFocusOut() const noexcept {
+ if (m_impl->_focusOutCallback)
+ m_impl->_focusOutCallback();
+}
+
+void InfiniFrameWindow::InvokeMove(int x, int y) const noexcept {
+ if (m_impl->_movedCallback)
+ m_impl->_movedCallback(x, y);
+}
+
+void InfiniFrameWindow::InvokeResize(int width, int height) const noexcept {
+ if (m_impl->_resizedCallback)
+ m_impl->_resizedCallback(width, height);
+}
+
+void InfiniFrameWindow::InvokeMaximized() const noexcept {
+ if (m_impl->_maximizedCallback)
+ m_impl->_maximizedCallback();
+}
+
+void InfiniFrameWindow::InvokeRestored() const noexcept {
+ if (m_impl->_restoredCallback)
+ m_impl->_restoredCallback();
+}
+
+void InfiniFrameWindow::InvokeMinimized() const noexcept {
+ if (m_impl->_minimizedCallback)
+ m_impl->_minimizedCallback();
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Windows/Core/WindowLifecycle.Win32.cpp b/src/InfiniFrame.NativeBridge/Native/Platform/Windows/Core/WindowLifecycle.Win32.cpp
new file mode 100644
index 000000000..327e0efdc
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Windows/Core/WindowLifecycle.Win32.cpp
@@ -0,0 +1,270 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include
+
+#include "Platform/Windows/DarkMode.h"
+#include "Platform/Windows/Window.Win32.Context.h"
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+using namespace WinToastLib;
+
+LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+
+namespace {
+ class BrushManager {
+ public:
+ static BrushManager& instance() noexcept {
+ static BrushManager inst;
+ return inst;
+ }
+
+ HBRUSH dark() const noexcept {
+ return static_cast(m_darkBrush.get());
+ }
+
+ HBRUSH light() const noexcept {
+ return static_cast(m_lightBrush.get());
+ }
+
+ private:
+ BrushManager() noexcept {
+ m_darkBrush.reset(CreateSolidBrush(RGB(0, 0, 0)));
+ m_lightBrush.reset(CreateSolidBrush(RGB(255, 255, 255)));
+ }
+
+ ~BrushManager() noexcept = default;
+
+ struct HBRUSHDeleter {
+ void operator()(void* h) const noexcept {
+ if (h)
+ DeleteObject(static_cast(h));
+ }
+ };
+
+ std::unique_ptr m_darkBrush;
+ std::unique_ptr m_lightBrush;
+ };
+} // namespace
+
+HBRUSH GetDarkBrush() {
+ return BrushManager::instance().dark();
+}
+
+HBRUSH GetLightBrush() {
+ return BrushManager::instance().light();
+}
+
+void InfiniFrameWindow::Register(const HINSTANCE hInstance) {
+ InitDarkModeSupport();
+
+ _hInstance.store(hInstance, std::memory_order_release);
+
+ WNDCLASSEX wcx;
+ wcx.cbSize = sizeof(WNDCLASSEX);
+ wcx.style = CS_HREDRAW | CS_VREDRAW;
+ wcx.lpfnWndProc = WindowProc;
+ wcx.cbClsExtra = 0;
+ wcx.cbWndExtra = 0;
+ wcx.hInstance = hInstance;
+ wcx.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
+ wcx.hCursor = LoadCursor(nullptr, IDC_ARROW);
+ wcx.hbrBackground = IsDarkModeEnabled() ? GetDarkBrush() : GetLightBrush();
+ wcx.lpszMenuName = nullptr;
+ wcx.lpszClassName = CLASS_NAME;
+ wcx.hIconSm = LoadIcon(hInstance, IDI_APPLICATION);
+
+ RegisterClassEx(&wcx);
+
+ SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
+}
+
+// Initializes native window lifecycle state from host-provided startup parameters.
+// Flow:
+// 1) Allocate implementation storage.
+// 2) Validate ABI compatibility of InfiniFrameInitParams via StructSize.
+// 3) Configure window identity/notifications and startup payload values.
+// 4) Continue with remaining platform/window initialization in this constructor.
+InfiniFrameWindow::InfiniFrameWindow(InfiniFrameInitParams* initParams) {
+ // Backing implementation object must exist before any field assignment.
+ m_impl = std::make_unique();
+
+ // Fail fast if caller and native side disagree on struct layout/version.
+ if (initParams->StructSize != sizeof(InfiniFrameInitParams)) {
+ auto msg = std::format(
+ L"Initial parameters passed are {} bytes, but expected {} bytes.", initParams->StructSize,
+ sizeof(InfiniFrameInitParams)
+ );
+ MessageBox(nullptr, msg.c_str(), L"Native Initialization Failed", MB_OK);
+ exit(0);
+ }
+
+ // Initialize window title and optional toast notification identity.
+ if (initParams->Title != nullptr) {
+ m_impl->_windowTitle = ToUTF16String(initParams->Title);
+ if (initParams->NotificationsEnabled) {
+ WinToast::instance()->setAppName(m_impl->_windowTitle.c_str());
+ if (m_impl->_notificationRegistrationId.empty())
+ WinToast::instance()->setAppUserModelId(m_impl->_windowTitle.c_str());
+ }
+ }
+
+ // Capture startup URL (if provided) for initial navigation/bootstrap.
+ if (initParams->StartUrl != nullptr)
+ m_impl->_startUrl = ToUTF16String(initParams->StartUrl);
+
+ // Capture startup string payload (if provided) for host-defined boot data.
+ if (initParams->StartString != nullptr)
+ m_impl->_startString = ToUTF16String(initParams->StartString);
+
+ if (initParams->TemporaryFilesPath != nullptr)
+ m_impl->_temporaryFilesPath = ToUTF16String(initParams->TemporaryFilesPath);
+
+ if (initParams->UserAgent != nullptr)
+ m_impl->_userAgent = ToUTF16String(initParams->UserAgent);
+
+ if (initParams->BrowserControlInitParameters != nullptr)
+ m_impl->_browserControlInitParameters = ToUTF16String(initParams->BrowserControlInitParameters);
+
+ if (initParams->NotificationRegistrationId != nullptr)
+ m_impl->_notificationRegistrationId = ToUTF16String(initParams->NotificationRegistrationId);
+
+ m_impl->_transparentEnabled = initParams->Transparent;
+ m_impl->_contextMenuEnabled = initParams->ContextMenuEnabled;
+ m_impl->_zoomEnabled = initParams->ZoomEnabled;
+ m_impl->_devToolsEnabled = initParams->DevToolsEnabled;
+ m_impl->_grantBrowserPermissions = initParams->GrantBrowserPermissions;
+ m_impl->_mediaAutoplayEnabled = initParams->MediaAutoplayEnabled;
+ m_impl->_fileSystemAccessEnabled = initParams->FileSystemAccessEnabled;
+ m_impl->_webSecurityEnabled = initParams->WebSecurityEnabled;
+ m_impl->_javascriptClipboardAccessEnabled = initParams->JavascriptClipboardAccessEnabled;
+ m_impl->_mediaStreamEnabled = initParams->MediaStreamEnabled;
+ m_impl->_smoothScrollingEnabled = initParams->SmoothScrollingEnabled;
+ m_impl->_ignoreCertificateErrorsEnabled = initParams->IgnoreCertificateErrorsEnabled;
+ m_impl->_notificationsEnabled = initParams->NotificationsEnabled;
+
+ m_impl->_zoom = initParams->Zoom;
+ m_impl->_minWidth = initParams->MinWidth;
+ m_impl->_minHeight = initParams->MinHeight;
+ m_impl->_maxWidth = initParams->MaxWidth;
+ m_impl->_maxHeight = initParams->MaxHeight;
+
+ m_impl->_webMessageReceivedCallback = initParams->WebMessageReceivedHandler;
+ m_impl->_resizedCallback = initParams->ResizedHandler;
+ m_impl->_maximizedCallback = initParams->MaximizedHandler;
+ m_impl->_restoredCallback = initParams->RestoredHandler;
+ m_impl->_minimizedCallback = initParams->MinimizedHandler;
+ m_impl->_movedCallback = initParams->MovedHandler;
+ m_impl->_closingCallback = initParams->ClosingHandler;
+ m_impl->_closedCallback = initParams->ClosedHandler;
+ m_impl->_focusInCallback = initParams->FocusInHandler;
+ m_impl->_focusOutCallback = initParams->FocusOutHandler;
+ m_impl->_customSchemeCallback = initParams->CustomSchemeHandler;
+
+ for (int i = 0; i < 16; ++i) {
+ if (initParams->CustomSchemeNames[i] != nullptr)
+ m_impl->_customSchemeNames.emplace_back(ToUTF16String(initParams->CustomSchemeNames[i]));
+ }
+
+ m_impl->_parent = initParams->ParentInstance;
+
+ int normalizedWidth = initParams->Width;
+ int normalizedHeight = initParams->Height;
+ int normalizedLeft = initParams->Left;
+ int normalizedTop = initParams->Top;
+ bool centerOnInitialize = initParams->CenterOnInitialize;
+
+ if (initParams->UseOsDefaultSize) {
+ normalizedWidth = CW_USEDEFAULT;
+ normalizedHeight = CW_USEDEFAULT;
+ } else {
+ if (normalizedWidth < 0)
+ normalizedWidth = CW_USEDEFAULT;
+ if (normalizedHeight < 0)
+ normalizedHeight = CW_USEDEFAULT;
+ }
+
+ if (initParams->UseOsDefaultLocation) {
+ normalizedLeft = CW_USEDEFAULT;
+ normalizedTop = CW_USEDEFAULT;
+ }
+
+ if (initParams->FullScreen) {
+ normalizedLeft = 0;
+ normalizedTop = 0;
+ normalizedWidth = GetSystemMetrics(SM_CXSCREEN);
+ normalizedHeight = GetSystemMetrics(SM_CYSCREEN);
+ }
+
+ if (initParams->Chromeless) {
+ if (normalizedLeft == CW_USEDEFAULT && normalizedTop == CW_USEDEFAULT)
+ centerOnInitialize = true;
+ if (normalizedLeft == CW_USEDEFAULT)
+ normalizedLeft = 0;
+ if (normalizedTop == CW_USEDEFAULT)
+ normalizedTop = 0;
+ if (normalizedHeight == CW_USEDEFAULT)
+ normalizedHeight = 600;
+ if (normalizedWidth == CW_USEDEFAULT)
+ normalizedWidth = 800;
+ }
+
+ if (normalizedHeight > initParams->MaxHeight)
+ normalizedHeight = initParams->MaxHeight;
+ if (normalizedHeight < initParams->MinHeight && initParams->MinHeight > 0)
+ normalizedHeight = initParams->MinHeight;
+ if (normalizedWidth > initParams->MaxWidth)
+ normalizedWidth = initParams->MaxWidth;
+ if (normalizedWidth < initParams->MinWidth && initParams->MinWidth > 0)
+ normalizedWidth = initParams->MinWidth;
+
+ const HWND parentWindowHandle = ResolveParentWindowHandle(m_impl->_parent);
+ m_impl->_pendingOwnerHwnd = parentWindowHandle;
+
+ const HINSTANCE windowInstance = _hInstance.load(std::memory_order_acquire);
+ m_impl->_hWnd = CreateWindowEx(
+ initParams->Transparent ? WS_EX_LAYERED : 0, CLASS_NAME, m_impl->_windowTitle.c_str(),
+ initParams->Chromeless || initParams->FullScreen ? WS_POPUP : WS_OVERLAPPEDWINDOW, normalizedLeft,
+ normalizedTop, normalizedWidth, normalizedHeight, nullptr, nullptr, windowInstance, this
+ );
+
+ ApplyPendingOwnerWindow(m_impl.get(), L"ctor");
+
+ if (initParams->WindowIconFile != nullptr) {
+ SetIconFile(initParams->WindowIconFile);
+ }
+
+ if (centerOnInitialize)
+ Center();
+
+ if (initParams->Minimized)
+ SetMinimized(true);
+
+ if (initParams->Maximized)
+ SetMaximized(true);
+
+ SetResizable(initParams->Resizable);
+
+ if (initParams->Topmost)
+ SetTopmost(true);
+
+ if (initParams->NotificationsEnabled) {
+ if (!m_impl->_notificationRegistrationId.empty())
+ WinToast::instance()->setAppUserModelId(m_impl->_notificationRegistrationId.c_str());
+
+ m_impl->_toastHandler = std::make_unique(this);
+ WinToast::instance()->initialize();
+ }
+
+ m_impl->_dialog = std::make_unique(this);
+
+ bool isAlreadyShown = initParams->Minimized || initParams->Maximized;
+ Show(isAlreadyShown);
+}
+
+InfiniFrameWindow::~InfiniFrameWindow() {}
+
+HWND InfiniFrameWindow::getHwnd() {
+ return m_impl->_hWnd;
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Windows/Core/WindowOwnership.Win32.cpp b/src/InfiniFrame.NativeBridge/Native/Platform/Windows/Core/WindowOwnership.Win32.cpp
new file mode 100644
index 000000000..39f9941e1
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Windows/Core/WindowOwnership.Win32.cpp
@@ -0,0 +1,21 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include "Platform/Windows/Window.Win32.Context.h"
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+InfiniFrameWindow* LookupWindowInstance(const HWND hwnd) {
+ return reinterpret_cast(GetWindowLongPtr(hwnd, GWLP_USERDATA));
+}
+
+HWND ResolveParentWindowHandle(InfiniFrameWindow* parent) {
+ if (parent == nullptr)
+ return nullptr;
+
+ HWND parentHwnd = parent->getHwnd();
+ if (parentHwnd == nullptr || !IsWindow(parentHwnd))
+ return nullptr;
+
+ return parentHwnd;
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Windows/Core/WindowProc.Win32.cpp b/src/InfiniFrame.NativeBridge/Native/Platform/Windows/Core/WindowProc.Win32.cpp
new file mode 100644
index 000000000..c360fdc53
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Windows/Core/WindowProc.Win32.cpp
@@ -0,0 +1,165 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include "Platform/Windows/DarkMode.h"
+#include "Platform/Windows/Window.Win32.Context.h"
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+// Central Win32 message dispatcher for an InfiniFrame top-level window.
+// This procedure coordinates native lifecycle events with managed/window context state:
+// - stores and retrieves the InfiniFrameWindow instance
+// - reacts to theme and color-scheme changes (dark/light mode)
+// - applies per-monitor DPI resize recommendations
+// - forwards focus and close events to the owning instance
+// - paints the window background according to current theme
+LRESULT CALLBACK WindowProc(const HWND hwnd, const UINT uMsg, const WPARAM wParam, const LPARAM lParam) {
+ switch (uMsg) {
+ case WM_NCCREATE: {
+ // Capture the instance pointer at non-client creation so later messages can
+ // resolve window state via GWLP_USERDATA.
+ const auto* createParams = reinterpret_cast(lParam);
+ auto* instance = reinterpret_cast(createParams->lpCreateParams);
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast(instance));
+ return TRUE;
+ }
+ case WM_CREATE: {
+ // Initialize dark mode support once the window is created.
+ EnableDarkMode(hwnd, true);
+ if (IsDarkModeEnabled())
+ RefreshNonClientArea(hwnd);
+ break;
+ }
+ case WM_DPICHANGED: {
+ // Use the system-provided suggested rectangle to keep the window properly sized
+ // and positioned when moving between monitors with different DPI.
+ RECT* newWindowRect = reinterpret_cast(lParam);
+
+ SetWindowPos(
+ hwnd, nullptr, newWindowRect->left, newWindowRect->top, newWindowRect->right - newWindowRect->left,
+ newWindowRect->bottom - newWindowRect->top, SWP_NOZORDER | SWP_NOACTIVATE
+ );
+
+ return 0;
+ }
+ case WM_SETTINGCHANGE: {
+ // Forward color-scheme changes to the same theme-refresh path used by WM_THEMECHANGED.
+ if (IsColorSchemeChange(lParam))
+ SendMessageW(hwnd, WM_THEMECHANGED, 0, 0);
+
+ break;
+ }
+ case WM_THEMECHANGED: {
+ // Reapply dark mode and redraw client/non-client regions after a theme transition.
+ EnableDarkMode(hwnd, IsDarkModeEnabled());
+ RefreshNonClientArea(hwnd);
+ InvalidateRect(hwnd, nullptr, TRUE);
+ break;
+ }
+ case WM_PAINT: {
+ // Paint only the invalidated region with the active theme background brush.
+ PAINTSTRUCT ps;
+ HDC hdc = BeginPaint(hwnd, &ps);
+
+ if (IsDarkModeEnabled()) {
+ FillRect(hdc, &ps.rcPaint, GetDarkBrush());
+ } else {
+ FillRect(hdc, &ps.rcPaint, GetLightBrush());
+ }
+
+ EndPaint(hwnd, &ps);
+ break;
+ }
+ case WM_ACTIVATE: {
+ // Keep WebView/focus state synchronized with native activation transitions.
+ InfiniFrameWindow* instance = LookupWindowInstance(hwnd);
+ if (instance) {
+ if (LOWORD(wParam) == WA_INACTIVE) {
+ instance->InvokeFocusOut();
+ } else {
+ instance->FocusWebView2();
+ instance->InvokeFocusIn();
+
+ return 0;
+ }
+ }
+ break;
+ }
+ case WM_CLOSE: {
+ // Give the instance a chance to cancel close. If close proceeds, clear owner
+ // relationship before destruction to avoid shutdown-order and ownership edge cases.
+ InfiniFrameWindow* instance = LookupWindowInstance(hwnd);
+ if (instance) {
+ TraceTeardown(L"WM_CLOSE hwnd=%p instance=%p", hwnd, instance);
+ bool doNotClose = instance->InvokeClose();
+
+ if (!doNotClose) {
+ SetLastError(0);
+ const LONG_PTR previousOwner = SetWindowLongPtr(hwnd, GWLP_HWNDPARENT, 0);
+ const DWORD ownerDetachError = GetLastError();
+ if (previousOwner != 0 || ownerDetachError == 0) {
+ TraceTeardown(
+ L"WM_CLOSE detached owner hwnd=%p prevOwner=%p err=%lu", hwnd,
+ reinterpret_cast(previousOwner), ownerDetachError
+ );
+ }
+
+ DestroyWindow(hwnd);
+ }
+ }
+
+ return 0;
+ }
+ case WM_DESTROY: {
+ InfiniFrameWindow* instance = LookupWindowInstance(hwnd);
+ if (instance) {
+ instance->m_impl->_isClosingOrClosed.store(true, std::memory_order_release);
+ TraceTeardown(L"WM_DESTROY begin hwnd=%p instance=%p", hwnd, instance);
+ instance->CloseWebView();
+ instance->InvokeClosed();
+ TraceTeardown(L"WM_DESTROY end hwnd=%p instance=%p", hwnd, instance);
+ }
+ if (hwnd == messageLoopRootWindowHandle)
+ PostQuitMessage(0);
+
+ return 0;
+ }
+ case WM_NCDESTROY: {
+ InfiniFrameWindow* instance = LookupWindowInstance(hwnd);
+ if (instance) {
+ instance->m_impl->_isClosingOrClosed.store(true, std::memory_order_release);
+ instance->m_impl->_hWnd = nullptr;
+ }
+ TraceTeardown(L"WM_NCDESTROY hwnd=%p instance=%p", hwnd, instance);
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, 0);
+ break;
+ }
+ case WM_USER_INVOKE: {
+ auto callback = reinterpret_cast(wParam);
+ auto* waitInfo = reinterpret_cast(lParam);
+
+ if (waitInfo == nullptr) {
+ if (callback)
+ callback();
+ return 0;
+ }
+
+ bool deleteWaitInfo = false;
+ {
+ std::lock_guard guard(waitInfo->mutex);
+ if (!waitInfo->isAbandoned && callback)
+ callback();
+ waitInfo->isCompleted = true;
+ deleteWaitInfo = waitInfo->isAbandoned;
+ }
+
+ waitInfo->completionNotifier.notify_one();
+
+ if (deleteWaitInfo)
+ delete waitInfo;
+ return 0;
+ }
+ }
+
+ return DefWindowProc(hwnd, uMsg, wParam, lParam);
+}
diff --git a/src/InfiniFrame.NativeBridge/Native/Platform/Windows/Core/WindowState.Win32.cpp b/src/InfiniFrame.NativeBridge/Native/Platform/Windows/Core/WindowState.Win32.cpp
new file mode 100644
index 000000000..43c58dc0d
--- /dev/null
+++ b/src/InfiniFrame.NativeBridge/Native/Platform/Windows/Core/WindowState.Win32.cpp
@@ -0,0 +1,459 @@
+// ---------------------------------------------------------------------------------------------------------------------
+// Imports
+// ---------------------------------------------------------------------------------------------------------------------
+#include
+
+#include "Utils/Common.h"
+#include "Platform/Windows/Window.Win32.Internal.h"
+// ---------------------------------------------------------------------------------------------------------------------
+// Code
+// ---------------------------------------------------------------------------------------------------------------------
+void InfiniFrameWindow::Center() {
+ int screenDpi = GetDpiForWindow(m_impl->_hWnd);
+ int screenHeight = GetSystemMetricsForDpi(SM_CYSCREEN, screenDpi);
+ int screenWidth = GetSystemMetricsForDpi(SM_CXSCREEN, screenDpi);
+
+ RECT windowRect = {};
+ GetWindowRect(m_impl->_hWnd, &windowRect);
+ int windowHeight = windowRect.bottom - windowRect.top;
+ int windowWidth = windowRect.right - windowRect.left;
+
+ int left = (screenWidth / 2) - (windowWidth / 2);
+ int top = (screenHeight / 2) - (windowHeight / 2);
+
+ SetPosition(left, top);
+}
+
+void InfiniFrameWindow::Close() {
+ PostMessage(m_impl->_hWnd, WM_CLOSE, 0, 0);
+}
+
+void InfiniFrameWindow::GetTransparentEnabled(bool* enabled) const {
+ if (!m_impl->_webviewController) {
+ *enabled = m_impl->_transparentEnabled;
+ return;
+ }
+ wil::com_ptr